Switch monitors based on keyboard shortcuts

This commit is contained in:
loki 2021-07-29 16:48:03 +02:00
parent c243e82047
commit 2af179630a
6 changed files with 136 additions and 21 deletions

View File

@ -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

View File

@ -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<int>(mail::switch_display)->raise(keyCode - VK_F1);
return 1;
}
switch(keyCode) {
case 0x4E /* VKEY_N */:
display_cursor = !display_cursor;

View File

@ -44,6 +44,8 @@ MAIL(broadcast_shutdown);
MAIL(video_packets);
MAIL(audio_packets);
MAIL(switch_display);
// Local mail
MAIL(touch_port);
MAIL(idr);

View File

@ -271,7 +271,18 @@ std::string from_sockaddr(const sockaddr *const);
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const);
std::unique_ptr<audio_control_t> audio_control();
std::shared_ptr<display_t> 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_t> display(mem_type_e hwdevice_type, const std::string &display_name, int framerate);
std::vector<std::string> display_names();
input_t input();
void move_mouse(input_t &input, int deltaX, int deltaY);

View File

@ -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_t> display(platf::mem_type_e hwdevice_type, int framerate) {
std::shared_ptr<display_t> 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_t> 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<shm_attr_t>(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_t> display(platf::mem_type_e hwdevice_type, int framerat
// Fallback
auto x11_disp = std::make_shared<x11_attr_t>(hwdevice_type);
if(x11_disp->init(framerate)) {
if(x11_disp->init(framerate, output_name)) {
return nullptr;
}
return x11_disp;
}
std::vector<std::string> 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<std::string> 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);
}

View File

@ -551,11 +551,11 @@ static std::vector<encoder_t> encoders {
software
};
void reset_display(std::shared_ptr<platf::display_t> &disp, AVHWDeviceType type, int framerate) {
void reset_display(std::shared_ptr<platf::display_t> &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<int>(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<platf::img_t> &img) -> std::shared_ptr<platf::img_t> {
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<platf::img_t> &img) -> std::shared_ptr<platf::img_t> {
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<sync_session_t> make_synced_session(platf::display_t *disp, const
return std::move(encode_session);
}
encode_e encode_run_sync(std::vector<std::unique_ptr<sync_session_ctx_t>> &synced_session_ctxs, encode_session_ctx_queue_t &encode_session_ctx_queue) {
encode_e encode_run_sync(
std::vector<std::unique_ptr<sync_session_ctx_t>> &synced_session_ctxs,
encode_session_ctx_queue_t &encode_session_ctx_queue,
int &display_p, const std::vector<std::string> &display_names) {
const auto &encoder = encoders.front();
std::shared_ptr<platf::display_t> disp;
auto switch_display_event = mail::man->event<int>(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<std::unique_ptr<sync_session_ctx_t>> &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<std::unique_ptr<sync_session_ctx_t>> &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<platf::display_t> &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;
}