diff --git a/src/config.cpp b/src/config.cpp index ac3955b8..699d0c43 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -439,6 +439,7 @@ namespace config { }; sunshine_t sunshine { + false, // hide_tray_controls "en", // locale 2, // min_log_level 0, // flags @@ -1102,6 +1103,7 @@ namespace config { bool_f(vars, "high_resolution_scrolling", input.high_resolution_scrolling); bool_f(vars, "native_pen_touch", input.native_pen_touch); + bool_f(vars, "hide_tray_controls", sunshine.hide_tray_controls); bool_f(vars, "notify_pre_releases", sunshine.notify_pre_releases); int port = sunshine.port; diff --git a/src/config.h b/src/config.h index 8adcd898..94bfc69c 100644 --- a/src/config.h +++ b/src/config.h @@ -163,6 +163,7 @@ namespace config { bool elevated; }; struct sunshine_t { + bool hide_tray_controls; std::string locale; int min_log_level; std::bitset flags; diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 7e38cbd7..8b9dbb86 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -621,6 +621,32 @@ namespace confighttp { platf::restart(); } + void + quit(resp_https_t response, req_https_t request) { + if (!authenticate(response, request)) return; + + print_req(request); + + // We do want to return here + std::thread quit_thread([]{ + sleep(1000); + #ifdef _WIN32 + // If we're running in a service, return a special status to + // tell it to terminate too, otherwise it will just respawn us. + if (GetConsoleWindow() == NULL) { + lifetime::exit_sunshine(ERROR_SHUTDOWN_IN_PROGRESS, true); + return; + } + #endif + + lifetime::exit_sunshine(0, true); + }); + + quit_thread.detach(); + + response->write(); + } + void savePassword(resp_https_t response, req_https_t request) { if (!config::sunshine.username.empty() && !authenticate(response, request)) return; @@ -825,6 +851,7 @@ namespace confighttp { server.resource["^/api/config$"]["POST"] = saveConfig; server.resource["^/api/configLocale$"]["GET"] = getLocale; server.resource["^/api/restart$"]["POST"] = restart; + server.resource["^/api/quit$"]["POST"] = quit; server.resource["^/api/password$"]["POST"] = savePassword; server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; server.resource["^/api/clients/unpair-all$"]["POST"] = unpairAll; diff --git a/src/main.cpp b/src/main.cpp index 6bfe17f3..44c3791e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -289,7 +289,7 @@ main(int argc, char *argv[]) { BOOST_LOG(fatal) << "HTTP interface failed to initialize"sv; #ifdef _WIN32 - BOOST_LOG(fatal) << "To relaunch Sunshine successfully, use the shortcut in the Start Menu. Do not run Sunshine.exe manually."sv; + BOOST_LOG(fatal) << "To relaunch Apollo successfully, use the shortcut in the Start Menu. Do not run Sunshine.exe manually."sv; std::this_thread::sleep_for(10s); #endif @@ -317,8 +317,8 @@ main(int argc, char *argv[]) { #ifdef _WIN32 // If we're using the default port and GameStream is enabled, warn the user if (config::sunshine.port == 47989 && is_gamestream_enabled()) { - BOOST_LOG(fatal) << "GameStream is still enabled in GeForce Experience! This *will* cause streaming problems with Sunshine!"sv; - BOOST_LOG(fatal) << "Disable GameStream on the SHIELD tab in GeForce Experience or change the Port setting on the Advanced tab in the Sunshine Web UI."sv; + BOOST_LOG(fatal) << "GameStream is still enabled in GeForce Experience! This *will* cause streaming problems with Apollo!"sv; + BOOST_LOG(fatal) << "Disable GameStream on the SHIELD tab in GeForce Experience or change the Port setting on the Advanced tab in the Apollo Web UI."sv; } #endif diff --git a/src/system_tray.cpp b/src/system_tray.cpp index a1f00c06..9e58a821 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -97,6 +97,7 @@ namespace system_tray { (struct tray_menu[]) { // todo - use boost/locale to translate menu strings { .text = "Open Apollo", .cb = tray_open_ui_cb }, + { .text = "-" }, // { .text = "-" }, // { .text = "Donate", // .submenu = @@ -231,6 +232,10 @@ namespace system_tray { #endif tray.menu[0].text = title_str.c_str(); + if (config::sunshine.hide_tray_controls) { + tray.menu[1].text = nullptr; + } + std::thread tray_thread(system_tray); tray_thread.detach(); #endif @@ -269,7 +274,7 @@ namespace system_tray { tray.notification_text = msg; tray.tooltip = msg; tray.notification_icon = TRAY_ICON_PLAYING; - tray.menu[1].text = force_close_msg; + tray.menu[2].text = force_close_msg; tray_update(&tray); } @@ -320,7 +325,7 @@ namespace system_tray { tray.notification_title = "Application Stopped"; tray.notification_text = msg; tray.tooltip = PROJECT_NAME; - tray.menu[1].text = TRAY_MSG_NO_APP_RUNNING; + tray.menu[2].text = TRAY_MSG_NO_APP_RUNNING; tray_update(&tray); } diff --git a/src_assets/common/assets/web/configs/tabs/General.vue b/src_assets/common/assets/web/configs/tabs/General.vue index acc5ab8f..42feebe5 100644 --- a/src_assets/common/assets/web/configs/tabs/General.vue +++ b/src_assets/common/assets/web/configs/tabs/General.vue @@ -129,6 +129,13 @@ function removeCmd(index) { + +
+ + +
{{ $t('config.hide_tray_controls_desc') }}
+
+
diff --git a/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue b/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue index 0b97287e..bc7b1195 100644 --- a/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue +++ b/src_assets/common/assets/web/configs/tabs/audiovideo/DisplayModesSettings.vue @@ -36,8 +36,8 @@ const fpsIn = ref("")
-
- +
+
{{ $t('config.headless_mode_desc') }}
diff --git a/src_assets/common/assets/web/public/assets/locale/en.json b/src_assets/common/assets/web/public/assets/locale/en.json index 48edd31c..1350f266 100644 --- a/src_assets/common/assets/web/public/assets/locale/en.json +++ b/src_assets/common/assets/web/public/assets/locale/en.json @@ -191,6 +191,8 @@ "hevc_mode_2": "Apollo will advertise support for HEVC Main profile", "hevc_mode_3": "Apollo will advertise support for HEVC Main and Main10 (HDR) profiles", "hevc_mode_desc": "Allows the client to request HEVC Main or HEVC Main10 video streams. HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.", + "hide_tray_controls": "Hide tray control options", + "hide_tray_controls_desc": "Do not show \"Force Stop\", \"Restart\" and \"Quit\" in tray menu.", "high_resolution_scrolling": "High Resolution Scrolling Support", "high_resolution_scrolling_desc": "When enabled, Apollo will pass through high resolution scroll events from Moonlight clients. This can be useful to disable for older applications that scroll too fast with high resolution scroll events.", "install_steam_audio_drivers": "Install Steam Audio Drivers", @@ -394,6 +396,10 @@ "restart_apollo": "Restart Apollo", "restart_apollo_desc": "If Apollo isn't working properly, you can try restarting it. This will terminate any running sessions.", "restart_apollo_success": "Apollo is restarting", + "quit_apollo": "Quit Apollo", + "quit_apollo_desc": "Exit Apollo. This will terminate any running sessions.", + "quit_apollo_success": "Apollo has exited.", + "quit_apollo_success_ongoing": "Apollo is quitting...", "troubleshooting": "Troubleshooting", "unpair_all": "Unpair All", "unpair_all_error": "Error while unpairing", diff --git a/src_assets/common/assets/web/public/assets/locale/zh.json b/src_assets/common/assets/web/public/assets/locale/zh.json index 03e137c2..4b84a1e1 100644 --- a/src_assets/common/assets/web/public/assets/locale/zh.json +++ b/src_assets/common/assets/web/public/assets/locale/zh.json @@ -190,6 +190,8 @@ "hevc_mode_2": "Apollo 将通告 HEVC Main 配置支持", "hevc_mode_3": "Apollo 将通告 HEVC Main 和 Main10 (HDR) 配置支持", "hevc_mode_desc": "允许客户端请求HEVC 主流或 HEVC Main10 视频流。 HEVC更需要编码,因此在使用软件编码时可能降低性能。", + "hide_tray_controls": "隐藏托盘图标控制选项", + "hide_tray_controls_desc": "不在托盘图标菜单内显示 \"Force Stop\", \"Restart\" and \"Quit\"。", "high_resolution_scrolling": "高分辨率鼠标滚动支持", "high_resolution_scrolling_desc": "启用后,Apollo 将透传来自 Moonlight 客户端的高分辨率滚动事件。对于那些使用高分辨率滚动事件时滚动速度过快的旧版应用程序来说,禁用此功能非常有用。", "install_steam_audio_drivers": "安装 Steam 音频驱动程序", @@ -392,9 +394,13 @@ "logs": "日志", "logs_desc": "查看 Apollo 上传的日志", "logs_find": "查找...", - "restart_apollo": "重启 Sunhine", - "restart_apollo_desc": "如果 Apollo 无法正常工作,可以尝试重新启动。这将终止任何正在运行的会话。", - "restart_apollo_success": "Sunhine 正在重启", + "restart_apollo": "重启 Apollo", + "restart_apollo_desc": "如果 Apollo 无法正常工作,可以尝试重新启动。这将终止任何正在进行的会话。", + "restart_apollo_success": "Apollo 正在重启", + "quit_apollo": "退出 Apollo", + "quit_apollo_desc": "停止运行 Apollo。这将会终止任何正在进行的会话。", + "quit_apollo_success": "Apollo has exited.", + "quit_apollo_success_ongoing": "Apollo is quitting...", "troubleshooting": "故障排除", "unpair_all": "全部取消配对", "unpair_all_error": "取消配对时出错", diff --git a/src_assets/common/assets/web/troubleshooting.html b/src_assets/common/assets/web/troubleshooting.html index f4801c17..acc17d0c 100644 --- a/src_assets/common/assets/web/troubleshooting.html +++ b/src_assets/common/assets/web/troubleshooting.html @@ -65,16 +65,35 @@

{{ $t('troubleshooting.restart_apollo') }}


{{ $t('troubleshooting.restart_apollo_desc') }}

-
+
{{ $t('troubleshooting.restart_apollo_success') }}
-
+ +
+
+

{{ $t('troubleshooting.quit_apollo') }}

+
+

{{ $t('troubleshooting.quit_apollo_desc') }}

+
+ {{ $t('troubleshooting.quit_apollo_success') }} +
+
+ {{ $t('troubleshooting.quit_apollo_success_ongoing') }} +
+
+ +
+
+
@@ -107,7 +126,7 @@
    {{ $t('troubleshooting.unpair_single_no_devices') }}
- +
@@ -144,7 +163,9 @@ logs: 'Loading...', logFilter: null, logInterval: null, - restartPressed: false, + serterRestarting: false, + serverQuitting: false, + serverQuit: false, showApplyMessage: false, unpairAllPressed: false, unpairAllStatus: null, @@ -228,14 +249,48 @@ navigator.clipboard.writeText(this.actualLogs); }, restart() { - this.restartPressed = true; + this.serverRestarting = true; setTimeout(() => { - this.restartPressed = false; + this.serverRestarting = false; }, 5000); fetch("/api/restart", { method: "POST", }); }, + quit() { + if (window.confirm("Do you really want to quit Apollo? You'll not be able to start Apollo again if you have no other methods to operate your computer.")) { + this.serverQuitting = true; + const timeoutID = setTimeout(() => { + this.serverQuitting = false; + }, 5000); + fetch("/api/quit", { + method: "POST", + }) + .then(() => { + clearTimeout(timeoutID); + return new Promise((resolve, reject) => { + setTimeout(() => { + fetch("/", { + signal: AbortSignal.timeout(1000) + }).then(() => { + reject(); + }).catch(() => { + resolve(); + }) + }, 1000); + }); + }) + .then(() => { + this.serverQuitting = false; + this.serverQuit = true; + }) + .catch(() => { + this.serverQuitting = false; + this.serverQuit = false; + alert("Exit failed!"); + }); + } + } }, });