From 2af179630abfe23501f973e212bfed4d4943d732 Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 29 Jul 2021 16:48:03 +0200 Subject: [PATCH] Switch monitors based on keyboard shortcuts --- README.md | 1 + sunshine/input.cpp | 9 +++ sunshine/main.h | 2 + sunshine/platform/common.h | 13 ++++- sunshine/platform/linux/display.cpp | 47 +++++++++++++--- sunshine/video.cpp | 85 +++++++++++++++++++++++++---- 6 files changed, 136 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index ec35b044..0a5a7f30 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ sunshine needs access to uinput to create mouse and gamepad events: All shortcuts start with CTRL + ALT + SHIFT, just like Moonlight - CTRL + ALT + SHIFT + N --> Hide/Unhide the cursor (This may be usefull for Remote Desktop Mode for Moonlight) +- CTRL + ALT + SHIFT + F1/F13 --> Switch to different monitor for Streaming ## Note: - The Windows key is not passed through by Moonlight, therefore Sunshine maps Right-Alt key to the Windows key diff --git a/sunshine/input.cpp b/sunshine/input.cpp index 7dce90cc..fa3241f0 100644 --- a/sunshine/input.cpp +++ b/sunshine/input.cpp @@ -136,7 +136,16 @@ struct input_t { * return 0 */ inline int apply_shortcut(short keyCode) { + constexpr auto VK_F1 = 0x70; + constexpr auto VK_F13 = 0x7C; + BOOST_LOG(debug) << "Apply Shortcut: 0x"sv << util::hex((std::uint8_t)keyCode).to_string_view(); + + if(keyCode >= VK_F1 && keyCode <= VK_F13) { + mail::man->event(mail::switch_display)->raise(keyCode - VK_F1); + return 1; + } + switch(keyCode) { case 0x4E /* VKEY_N */: display_cursor = !display_cursor; diff --git a/sunshine/main.h b/sunshine/main.h index 986a4be8..aa9558b3 100644 --- a/sunshine/main.h +++ b/sunshine/main.h @@ -44,6 +44,8 @@ MAIL(broadcast_shutdown); MAIL(video_packets); MAIL(audio_packets); +MAIL(switch_display); + // Local mail MAIL(touch_port); MAIL(idr); diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 22005e5b..f342751d 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -271,7 +271,18 @@ std::string from_sockaddr(const sockaddr *const); std::pair from_sockaddr_ex(const sockaddr *const); std::unique_ptr audio_control(); -std::shared_ptr display(mem_type_e hwdevice_type, int framerate); + +/** + * display_name --> The name of the monitor that SHOULD be displayed + * If display_name is empty --> Use the first monitor that's compatible you can find + * If you require to use this parameter in a seperate thread --> make a copy of it. + * + * framerate --> The peak number of images per second + * + * Returns display_t based on hwdevice_type + */ +std::shared_ptr display(mem_type_e hwdevice_type, const std::string &display_name, int framerate); +std::vector display_names(); input_t input(); void move_mouse(input_t &input, int deltaX, int deltaY); diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/display.cpp index 7a59c9ab..f6a89511 100644 --- a/sunshine/platform/linux/display.cpp +++ b/sunshine/platform/linux/display.cpp @@ -155,7 +155,7 @@ struct x11_attr_t : public display_t { XInitThreads(); } - int init(int framerate) { + int init(int framerate, const std::string &output_name) { if(!xdisplay) { BOOST_LOG(error) << "Could not open X11 display"sv; return -1; @@ -168,8 +168,8 @@ struct x11_attr_t : public display_t { refresh(); int streamedMonitor = -1; - if(!config::video.output_name.empty()) { - streamedMonitor = (int)util::from_view(config::video.output_name); + if(!output_name.empty()) { + streamedMonitor = (int)util::from_view(output_name); } if(streamedMonitor != -1) { @@ -399,8 +399,8 @@ struct shm_attr_t : public x11_attr_t { return 0; } - int init(int framerate) { - if(x11_attr_t::init(framerate)) { + int init(int framerate, const std::string &output_name) { + if(x11_attr_t::init(framerate, output_name)) { return 1; } @@ -443,7 +443,7 @@ struct shm_attr_t : public x11_attr_t { } }; -std::shared_ptr display(platf::mem_type_e hwdevice_type, int framerate) { +std::shared_ptr display(platf::mem_type_e hwdevice_type, const std::string &output_name, int framerate) { if(hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) { BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv; return nullptr; @@ -452,7 +452,7 @@ std::shared_ptr display(platf::mem_type_e hwdevice_type, int framerat // Attempt to use shared memory X11 to avoid copying the frame auto shm_disp = std::make_shared(hwdevice_type); - auto status = shm_disp->init(framerate); + auto status = shm_disp->init(framerate, output_name); if(status > 0) { // x11_attr_t::init() failed, don't bother trying again. return nullptr; @@ -464,13 +464,44 @@ std::shared_ptr display(platf::mem_type_e hwdevice_type, int framerat // Fallback auto x11_disp = std::make_shared(hwdevice_type); - if(x11_disp->init(framerate)) { + if(x11_disp->init(framerate, output_name)) { return nullptr; } return x11_disp; } +std::vector display_names() { + BOOST_LOG(info) << "Detecting connected monitors"sv; + + xdisplay_t xdisplay { XOpenDisplay(nullptr) }; + if(!xdisplay) { + return {}; + } + + auto xwindow = DefaultRootWindow(xdisplay.get()); + screen_res_t screenr { XRRGetScreenResources(xdisplay.get(), xwindow) }; + int output = screenr->noutput; + + int monitor = 0; + for(int x = 0; x < output; ++x) { + output_info_t out_info { XRRGetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) }; + if(out_info && out_info->connection == RR_Connected) { + ++monitor; + } + } + + std::vector names; + names.reserve(monitor); + + for(auto x = 0; x < monitor; ++x) { + BOOST_LOG(fatal) << x; + names.emplace_back(std::to_string(x)); + } + + return names; +} + void freeImage(XImage *p) { XDestroyImage(p); } diff --git a/sunshine/video.cpp b/sunshine/video.cpp index af0d3b36..5ba90323 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -551,11 +551,11 @@ static std::vector encoders { software }; -void reset_display(std::shared_ptr &disp, AVHWDeviceType type, int framerate) { +void reset_display(std::shared_ptr &disp, AVHWDeviceType type, const std::string &display_name, int framerate) { // We try this twice, in case we still get an error on reinitialization for(int x = 0; x < 2; ++x) { disp.reset(); - disp = platf::display(map_dev_type(type), framerate); + disp = platf::display(map_dev_type(type), display_name, framerate); if(disp) { break; } @@ -583,11 +583,30 @@ void captureThread( } }); + auto switch_display_event = mail::man->event(mail::switch_display); + + // Get all the monitor names now, rather than at boot, to + // get the most up-to-date list available monitors + auto display_names = platf::display_names(); + int display_p = 0; + + if(display_names.empty()) { + display_names.emplace_back(config::video.output_name); + } + + for(int x = 0; x < display_names.size(); ++x) { + if(display_names[x] == config::video.output_name) { + display_p = x; + + break; + } + } + if(auto capture_ctx = capture_ctx_queue->pop()) { capture_ctxs.emplace_back(std::move(*capture_ctx)); } - auto disp = platf::display(map_dev_type(encoder.dev_type), capture_ctxs.front().framerate); + auto disp = platf::display(map_dev_type(encoder.dev_type), display_names[display_p], capture_ctxs.front().framerate); if(!disp) { return; } @@ -605,11 +624,9 @@ void captureThread( } while(capture_ctx_queue->running()) { - auto status = disp->capture([&](std::shared_ptr &img) -> std::shared_ptr { - while(capture_ctx_queue->peek()) { - capture_ctxs.emplace_back(std::move(*capture_ctx_queue->pop())); - } + bool artificial_reinit = false; + auto status = disp->capture([&](std::shared_ptr &img) -> std::shared_ptr { KITTY_WHILE_LOOP(auto capture_ctx = std::begin(capture_ctxs), capture_ctx != std::end(capture_ctxs), { if(!capture_ctx->images->running()) { capture_ctx = capture_ctxs.erase(capture_ctx); @@ -624,6 +641,16 @@ void captureThread( if(!capture_ctx_queue->running()) { return nullptr; } + while(capture_ctx_queue->peek()) { + capture_ctxs.emplace_back(std::move(*capture_ctx_queue->pop())); + } + + if(switch_display_event->peek()) { + artificial_reinit = true; + + display_p = std::clamp(*switch_display_event->pop(), 0, (int)display_names.size() - 1); + return nullptr; + } auto &next_img = *round_robin++; while(next_img.use_count() > 1) {} @@ -633,6 +660,12 @@ void captureThread( *round_robin++, &display_cursor); + if(artificial_reinit && status != platf::capture_e::error) { + status = platf::capture_e::reinit; + + artificial_reinit = false; + } + switch(status) { case platf::capture_e::reinit: { reinit_event.raise(true); @@ -651,7 +684,7 @@ void captureThread( } while(capture_ctx_queue->running()) { - reset_display(disp, encoder.dev_type, capture_ctxs.front().framerate); + reset_display(disp, encoder.dev_type, display_names[display_p], capture_ctxs.front().framerate); if(disp) { break; @@ -1080,11 +1113,17 @@ std::optional make_synced_session(platf::display_t *disp, const return std::move(encode_session); } -encode_e encode_run_sync(std::vector> &synced_session_ctxs, encode_session_ctx_queue_t &encode_session_ctx_queue) { +encode_e encode_run_sync( + std::vector> &synced_session_ctxs, + encode_session_ctx_queue_t &encode_session_ctx_queue, + int &display_p, const std::vector &display_names) { + const auto &encoder = encoders.front(); std::shared_ptr disp; + auto switch_display_event = mail::man->event(mail::switch_display); + if(synced_session_ctxs.empty()) { auto ctx = encode_session_ctx_queue.pop(); if(!ctx) { @@ -1097,7 +1136,7 @@ encode_e encode_run_sync(std::vector> &synce int framerate = synced_session_ctxs.front()->config.framerate; while(encode_session_ctx_queue.running()) { - reset_display(disp, encoder.dev_type, framerate); + reset_display(disp, encoder.dev_type, display_names[display_p], framerate); if(disp) { break; } @@ -1190,6 +1229,13 @@ encode_e encode_run_sync(std::vector> &synce ++pos; }) + if(switch_display_event->peek()) { + ec = platf::capture_e::reinit; + + display_p = std::clamp(*switch_display_event->pop(), 0, (int)display_names.size() - 1); + return nullptr; + } + return img; }; @@ -1226,7 +1272,22 @@ void captureThreadSync() { } }); - while(encode_run_sync(synced_session_ctxs, ctx) == encode_e::reinit) {} + auto display_names = platf::display_names(); + int display_p = 0; + + if(display_names.empty()) { + display_names.emplace_back(config::video.output_name); + } + + for(int x = 0; x < display_names.size(); ++x) { + if(display_names[x] == config::video.output_name) { + display_p = x; + + break; + } + } + + while(encode_run_sync(synced_session_ctxs, ctx, display_p, display_names) == encode_e::reinit) {} } void capture_async( @@ -1338,7 +1399,7 @@ enum validate_flag_e { }; int validate_config(std::shared_ptr &disp, const encoder_t &encoder, const config_t &config) { - reset_display(disp, encoder.dev_type, config.framerate); + reset_display(disp, encoder.dev_type, config::video.output_name, config.framerate); if(!disp) { return -1; }