diff --git a/app/include/ingame_overlay_view.hpp b/app/include/ingame_overlay_view.hpp index 005c3a61..44837659 100644 --- a/app/include/ingame_overlay_view.hpp +++ b/app/include/ingame_overlay_view.hpp @@ -56,6 +56,7 @@ class OptionsTab : public brls::Box { BRLS_BIND(brls::SelectorCell, keyboardType, "keyboard_type"); BRLS_BIND(brls::SelectorCell, keyboardFingers, "keyboard_fingers"); BRLS_BIND(brls::BooleanCell, touchscreenMouseMode, "touchscreen_mouse_mode"); + BRLS_BIND(brls::SelectorCell, switchDisplay, "switch_display"); BRLS_BIND(brls::DetailCell, guideKeyButtons, "guide_key_buttons"); BRLS_BIND(brls::SelectorCell, guideBySystemButton, "guide_by_system_button"); BRLS_BIND(brls::Header, volumeHeader, "volume_header"); diff --git a/app/include/streaming_view.hpp b/app/include/streaming_view.hpp index 2557fa44..e138f2fe 100644 --- a/app/include/streaming_view.hpp +++ b/app/include/streaming_view.hpp @@ -30,6 +30,10 @@ class StreamingView : public brls::Box { void terminate(bool terminateApp); + // Whether the connected host is Sunshine, which supports switching the + // streamed display via the Ctrl+Alt+Shift+F key combo. + bool isSunshineHost() const { return sunshineHost; } + bool draw_stats = false; Host getHost() { return host; } @@ -44,6 +48,7 @@ class StreamingView : public brls::Box { Box* keyboardHolder = nullptr; KeyboardView* keyboard = nullptr; bool blocked = false; + bool sunshineHost = false; bool terminated = false; bool tempInputLock = false; brls::Event::Subscription keysSubscription; diff --git a/app/src/ingame_overlay_view.cpp b/app/src/ingame_overlay_view.cpp index 4476f6ba..d9858169 100644 --- a/app/src/ingame_overlay_view.cpp +++ b/app/src/ingame_overlay_view.cpp @@ -13,6 +13,7 @@ #include "ingame_overlay_view.hpp" #include "streaming_input_overlay.hpp" #include "button_selecting_dialog.hpp" +#include "InputManager.hpp" #include "UpscalingSupport.hpp" #include @@ -199,6 +200,24 @@ OptionsTab::OptionsTab(StreamingView* streamView) : streamView(streamView) { return true; }); + // Display switching via Ctrl+Alt+Shift+F is only supported by Sunshine + // hosts, so only show the option when connected to such a host. + if (streamView->isSunshineHost()) { + std::vector switchDisplayOptions; + for (int i = 1; i <= 12; i++) { + switchDisplayOptions.push_back("streaming/display"_i18n + " " + + std::to_string(i)); + } + switchDisplay->setText("streaming/switch_display"_i18n); + switchDisplay->setData(switchDisplayOptions); + switchDisplay->setSelection(0, true); + switchDisplay->getEvent()->subscribe([](int selected) { + MoonlightInputManager::switchDisplay(selected + 1); + }); + } else { + switchDisplay->removeFromSuperView(); + } + std::vector keyboardTypes = { "settings/keyboard_compact"_i18n, "settings/keyboard_fullsized"_i18n}; keyboardType->setText("settings/keyboard_type"_i18n); diff --git a/app/src/streaming/InputManager.cpp b/app/src/streaming/InputManager.cpp index 11fddf2d..806671fc 100644 --- a/app/src/streaming/InputManager.cpp +++ b/app/src/streaming/InputManager.cpp @@ -563,6 +563,38 @@ void MoonlightInputManager::rightMouseClick() { LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_MOUSE_RIGHT); } +void MoonlightInputManager::switchDisplay(int displayNumber) { + if (displayNumber < 1 || displayNumber > 12) { + brls::Logger::warning("Ignoring out-of-range display switch request {}", displayNumber); + return; + } + + // Virtual key codes + const short VK_LSHIFT = 0xA0; + const short VK_LCONTROL = 0xA2; + const short VK_LMENU = 0xA4; // Left Alt + const short vkFKey = 0x70 + (displayNumber - 1); // VK_F1 .. VK_F12 + + const char modifiers = MODIFIER_CTRL | MODIFIER_ALT | MODIFIER_SHIFT; + + brls::Logger::info("Switching streamed display to {} (Ctrl+Alt+Shift+F{})", + displayNumber, displayNumber); + + // Press modifiers in order, accumulating the modifier flags. + LiSendKeyboardEvent(VK_LCONTROL, KEY_ACTION_DOWN, MODIFIER_CTRL); + LiSendKeyboardEvent(VK_LMENU, KEY_ACTION_DOWN, MODIFIER_CTRL | MODIFIER_ALT); + LiSendKeyboardEvent(VK_LSHIFT, KEY_ACTION_DOWN, modifiers); + + // Tap the function key with all modifiers held. + LiSendKeyboardEvent(vkFKey, KEY_ACTION_DOWN, modifiers); + LiSendKeyboardEvent(vkFKey, KEY_ACTION_UP, modifiers); + + // Release modifiers in reverse order. + LiSendKeyboardEvent(VK_LSHIFT, KEY_ACTION_UP, MODIFIER_CTRL | MODIFIER_ALT); + LiSendKeyboardEvent(VK_LMENU, KEY_ACTION_UP, MODIFIER_CTRL); + LiSendKeyboardEvent(VK_LCONTROL, KEY_ACTION_UP, 0); +} + short MoonlightInputManager::glfwKeyToVKKey(BrlsKeyboardScancode key) { if (BRLS_KBD_KEY_F1 <= key && key <= BRLS_KBD_KEY_F12) return key - BRLS_KBD_KEY_F1 + 0x70; diff --git a/app/src/streaming/InputManager.hpp b/app/src/streaming/InputManager.hpp index 0020b835..c7c0913f 100644 --- a/app/src/streaming/InputManager.hpp +++ b/app/src/streaming/InputManager.hpp @@ -59,6 +59,10 @@ class MoonlightInputManager : public Singleton { void setInputEnabled(bool enabled) { inputEnabled = enabled; } static void leftMouseClick(); static void rightMouseClick(); + // Sends the Ctrl+Alt+Shift+F combo to the host, which + // tells Sunshine/GeForce Experience to switch the streamed display. + // displayNumber is 1-based (1 maps to F1, 12 maps to F12). + static void switchDisplay(int displayNumber); private: RumbleValues rumbleCache[GAMEPADS_MAX]; diff --git a/app/src/streaming_view.cpp b/app/src/streaming_view.cpp index fba3d6f9..a5aba963 100644 --- a/app/src/streaming_view.cpp +++ b/app/src/streaming_view.cpp @@ -96,6 +96,8 @@ StreamingView::StreamingView(const Host& host, const AppInfo& app) : host(host), session->set_address( GameStreamClient::instance().active_address(this->host)); + this->sunshineHost = result.value().isSunshine(); + ASYNC_RETAIN session->start([ASYNC_TOKEN](GSResult result) { ASYNC_RELEASE diff --git a/resources/i18n/de/main.json b/resources/i18n/de/main.json index c6ed7202..b46e4fff 100644 --- a/resources/i18n/de/main.json +++ b/resources/i18n/de/main.json @@ -177,6 +177,7 @@ "connection": "Verbindung", "debug_info": "Debug Informationen", "disconnect": "Verbindung trennen", + "display": "Bildschirm", "esc": "ESC Taste", "input": "Eingabe", "keys": "Tasten", @@ -185,6 +186,7 @@ "mouse_speed": "Mausbeschleunigung", "options": "Einstellungen", "show_logs": "Protokolle anzeigen", + "switch_display": "Bildschirm wechseln", "terminate": "Programm beenden", "volume": "Lautstärke" }, diff --git a/resources/i18n/en-US/main.json b/resources/i18n/en-US/main.json index 51aa9257..cdebb670 100644 --- a/resources/i18n/en-US/main.json +++ b/resources/i18n/en-US/main.json @@ -177,6 +177,7 @@ "connection": "Connection", "debug_info": "Debug info", "disconnect": "Disconnect", + "display": "Display", "esc": "ESC button", "input": "Input", "keys": "Keys", @@ -185,6 +186,7 @@ "mouse_speed": "Mouse acceleration", "options": "Options", "show_logs": "Show logs", + "switch_display": "Switch display", "terminate": "Terminate app", "volume": "Volume" }, diff --git a/resources/i18n/es/main.json b/resources/i18n/es/main.json index c801718e..ee1edabd 100644 --- a/resources/i18n/es/main.json +++ b/resources/i18n/es/main.json @@ -177,6 +177,7 @@ "connection": "Conexión", "debug_info": "Información de debug", "disconnect": "Desconectar", + "display": "Pantalla", "esc": "Botón de Escape", "input": "Entrada", "keys": "Botones", @@ -185,6 +186,7 @@ "mouse_speed": "Aceleración del ratón", "options": "Ajustes", "show_logs": "Mostrar logs", + "switch_display": "Cambiar de pantalla", "terminate": "Cerrar aplicación", "volume": "Volumen" }, diff --git a/resources/i18n/fr/main.json b/resources/i18n/fr/main.json index 759aaed2..a8d2b124 100644 --- a/resources/i18n/fr/main.json +++ b/resources/i18n/fr/main.json @@ -177,6 +177,7 @@ "connection": "Connexion", "debug_info": "Infos de Debug", "disconnect": "Déconnecter", + "display": "Écran", "esc": "Esc", "input": "Modes de saisie", "keys": "Touches", @@ -185,6 +186,7 @@ "mouse_speed": "Vitesse de la souris", "options": "Options", "show_logs": "Voir les logs", + "switch_display": "Changer d'écran", "terminate": "Fermer l'application", "volume": "Volume" }, diff --git a/resources/i18n/it/main.json b/resources/i18n/it/main.json index e855984b..244181c2 100644 --- a/resources/i18n/it/main.json +++ b/resources/i18n/it/main.json @@ -177,6 +177,7 @@ "connection": "Connessione", "debug_info": "Info di Debug", "disconnect": "Disconnetti", + "display": "Schermo", "esc": "Pulsante ESC", "input": "Input", "keys": "Tasti", @@ -185,6 +186,7 @@ "mouse_speed": "Accelerazione Mouse", "options": "Opzioni", "show_logs": "Mostra log", + "switch_display": "Cambia schermo", "terminate": "Termina app", "volume": "Volume" }, diff --git a/resources/i18n/ja/main.json b/resources/i18n/ja/main.json index 64e8cd27..e5ee2b31 100644 --- a/resources/i18n/ja/main.json +++ b/resources/i18n/ja/main.json @@ -177,6 +177,7 @@ "connection": "接続", "debug_info": "デバッグ情報", "disconnect": "切断する", + "display": "ディスプレイ", "esc": "エスケープボタン", "input": "入力", "keys": "キー", @@ -185,6 +186,7 @@ "mouse_speed": "マウスの加速", "options": "設定", "show_logs": "ログを表示する", + "switch_display": "ディスプレイを切り替える", "terminate": "アプリを終了します", "volume": "音量" }, diff --git a/resources/i18n/ko/main.json b/resources/i18n/ko/main.json index e8792503..226c0ef8 100644 --- a/resources/i18n/ko/main.json +++ b/resources/i18n/ko/main.json @@ -177,6 +177,7 @@ "connection": "연결", "debug_info": "디버그 정보", "disconnect": "연결 해제", + "display": "디스플레이", "esc": "ESC 버튼", "input": "입력", "keys": "키", @@ -185,6 +186,7 @@ "mouse_speed": "마우스 가속", "options": "옵션", "show_logs": "정보 표시", + "switch_display": "디스플레이 전환", "terminate": "앱 종료", "volume": "볼륨" }, diff --git a/resources/i18n/pt-BR/main.json b/resources/i18n/pt-BR/main.json index be2637e6..0a89564f 100644 --- a/resources/i18n/pt-BR/main.json +++ b/resources/i18n/pt-BR/main.json @@ -177,6 +177,7 @@ "connection": "Conexão", "debug_info": "Informações de debug", "disconnect": "Desconectar", + "display": "Tela", "esc": "Botão ESC", "input": "Entrada", "keys": "Teclas", @@ -185,6 +186,7 @@ "mouse_speed": "Aceleração do mouse", "options": "Opções", "show_logs": "Mostrar logs", + "switch_display": "Trocar de tela", "terminate": "Encerrar aplicativo", "volume": "Volume" }, diff --git a/resources/i18n/ru/main.json b/resources/i18n/ru/main.json index 1821a09d..eb35cce8 100644 --- a/resources/i18n/ru/main.json +++ b/resources/i18n/ru/main.json @@ -177,6 +177,7 @@ "connection": "Соединение", "debug_info": "Отладочная информация", "disconnect": "Отключиться", + "display": "Дисплей", "esc": "Кнопка ESC", "input": "Ввод", "keys": "Кнопки", @@ -185,6 +186,7 @@ "mouse_speed": "Скорость мыши", "options": "Настройки", "show_logs": "Показать логи", + "switch_display": "Сменить дисплей", "terminate": "Завершить приложение", "volume": "Громкость звука" }, diff --git a/resources/i18n/zh-Hans/main.json b/resources/i18n/zh-Hans/main.json index a5595c02..767f8172 100644 --- a/resources/i18n/zh-Hans/main.json +++ b/resources/i18n/zh-Hans/main.json @@ -177,6 +177,7 @@ "connection": "连接选项", "debug_info": "调试信息", "disconnect": "断开连接", + "display": "显示器", "esc": "ESC按键", "input": "输入", "keys": "按键", @@ -185,6 +186,7 @@ "mouse_speed": "鼠标速度", "options": "选项", "show_logs": "显示日志", + "switch_display": "切换显示器", "terminate": "终止应用程序", "volume": "音量" }, diff --git a/resources/i18n/zh-Hant/main.json b/resources/i18n/zh-Hant/main.json index 23dd792a..9442565e 100644 --- a/resources/i18n/zh-Hant/main.json +++ b/resources/i18n/zh-Hant/main.json @@ -177,6 +177,7 @@ "connection": "連接選項", "debug_info": "除錯訊息", "disconnect": "斷開連接", + "display": "顯示器", "esc": "ESC按鍵", "input": "輸入", "keys": "按鍵", @@ -185,6 +186,7 @@ "mouse_speed": "滑鼠速度", "options": "選項", "show_logs": "顯示日誌", + "switch_display": "切換顯示器", "terminate": "終止應用程式", "volume": "音量" }, diff --git a/resources/xml/views/ingame_overlay/options_tab.xml b/resources/xml/views/ingame_overlay/options_tab.xml index f817d72c..c8c63462 100644 --- a/resources/xml/views/ingame_overlay/options_tab.xml +++ b/resources/xml/views/ingame_overlay/options_tab.xml @@ -34,6 +34,9 @@ + +