diff --git a/.gitmodules b/.gitmodules index dc00523b..bbb705ee 100644 --- a/.gitmodules +++ b/.gitmodules @@ -48,7 +48,7 @@ branch = master [submodule "third-party/tray"] path = third-party/tray - url = https://github.com/dmikushin/tray + url = https://github.com/LizardByte/tray branch = master [submodule "third-party/nvapi-open-source-sdk"] path = third-party/nvapi-open-source-sdk diff --git a/cmake/compile_definitions/linux.cmake b/cmake/compile_definitions/linux.cmake index 4663e1d0..c88b62b2 100644 --- a/cmake/compile_definitions/linux.cmake +++ b/cmake/compile_definitions/linux.cmake @@ -157,14 +157,18 @@ endif() if(${SUNSHINE_ENABLE_TRAY}) pkg_check_modules(APPINDICATOR appindicator3-0.1) if(NOT APPINDICATOR_FOUND) + pkg_check_modules(APPINDICATOR ayatana-appindicator3-0.1) + endif() + pkg_check_modules(LIBNOTIFY libnotify) + if(NOT APPINDICATOR_FOUND OR NOT LIBNOTIFY_FOUND) message(WARNING "Missing appindicator, disabling tray icon") set(SUNSHINE_TRAY 0) else() - include_directories(SYSTEM ${APPINDICATOR_INCLUDE_DIRS}) - link_directories(${APPINDICATOR_LIBRARY_DIRS}) + include_directories(SYSTEM ${APPINDICATOR_INCLUDE_DIRS} ${LIBNOTIFY_INCLUDE_DIRS}) + link_directories(${APPINDICATOR_LIBRARY_DIRS} ${LIBNOTIFY_LIBRARY_DIRS}) list(APPEND PLATFORM_TARGET_FILES third-party/tray/tray_linux.c) - list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${APPINDICATOR_LIBRARIES}) + list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${APPINDICATOR_LIBRARIES} ${LIBNOTIFY_LIBRARIES}) endif() else() set(SUNSHINE_TRAY 0) diff --git a/cmake/packaging/linux.cmake b/cmake/packaging/linux.cmake index 7935f9a9..99a9c9ef 100644 --- a/cmake/packaging/linux.cmake +++ b/cmake/packaging/linux.cmake @@ -69,7 +69,14 @@ set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # tray icon if(${SUNSHINE_TRAY} STREQUAL 1) - install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons") + install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg" + DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons") + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/images/sunshine-playing.svg" + DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons") + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/images/sunshine-pausing.svg" + DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons") + install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/images/sunshine-locked.svg" + DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons") set(CPACK_DEBIAN_PACKAGE_DEPENDS "\ ${CPACK_DEBIAN_PACKAGE_DEPENDS}, \ diff --git a/src/main.cpp b/src/main.cpp index e5352d1b..c04e5dcd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -390,6 +390,20 @@ launch_ui() { platf::open_url(url); } +/** + * @brief Launch the Web UI at a specific endpoint. + * + * EXAMPLES: + * ```cpp + * launch_ui_with_path("/pin"); + * ``` + */ +void +launch_ui_with_path(std::string path) { + std::string url = "https://localhost:" + std::to_string(map_port(confighttp::PORT_HTTPS)) + path; + platf::open_url(url); +} + /** * @brief Flush the log. * diff --git a/src/main.h b/src/main.h index a4797662..8eb3e7ee 100644 --- a/src/main.h +++ b/src/main.h @@ -48,6 +48,8 @@ std::uint16_t map_port(int port); void launch_ui(); +void +launch_ui_with_path(std::string path); // namespaces namespace mail { diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 962310f3..50819e8e 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -29,6 +29,7 @@ #include "platform/common.h" #include "process.h" #include "rtsp.h" +#include "system_tray.h" #include "utility.h" #include "uuid.h" #include "video.h" @@ -507,7 +508,6 @@ namespace nvhttp { auto ptr = map_id_sess.emplace(sess.client.uniqueID, std::move(sess)).first; ptr->second.async_insert_pin.salt = std::move(get_arg(args, "salt")); - if (config::sunshine.flags[config::flag::PIN_STDIN]) { std::string pin; @@ -517,6 +517,9 @@ namespace nvhttp { getservercert(ptr->second, tree, pin); } else { +#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 + system_tray::update_tray_require_pin(); +#endif ptr->second.async_insert_pin.response = std::move(response); fg.disable(); diff --git a/src/process.cpp b/src/process.cpp index 83ada346..d018f475 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -24,6 +24,7 @@ #include "crypto.h" #include "main.h" #include "platform/common.h" +#include "system_tray.h" #include "utility.h" #ifdef _WIN32 @@ -116,7 +117,6 @@ namespace proc { _app_id = app_id; _app = *iter; - _app_prep_begin = std::begin(_app.prep_cmds); _app_prep_it = _app_prep_begin; @@ -244,9 +244,8 @@ namespace proc { void proc_t::terminate() { + bool has_run = _app_id > 0; std::error_code ec; - - // Ensure child process is terminated placebo = false; process_end(_process, _process_handle); _process = bp::child(); @@ -279,6 +278,13 @@ namespace proc { } _pipe.reset(); +#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 + // Only show the Stopped notification if we actually have an app to stop + // Since terminate() is always run when a new app has started + if (proc::proc.get_last_run_app_name().length() > 0 && has_run) { + system_tray::update_tray_stopped(proc::proc.get_last_run_app_name()); + } +#endif } const std::vector & @@ -304,6 +310,11 @@ namespace proc { return validate_app_image_path(app_image_path); } + std::string + proc_t::get_last_run_app_name() { + return _app.name; + } + proc_t::~proc_t() { // It's not safe to call terminate() here because our proc_t is a static variable // that may be destroyed after the Boost loggers have been destroyed. Instead, diff --git a/src/process.h b/src/process.h index 25aa1714..5e658146 100644 --- a/src/process.h +++ b/src/process.h @@ -82,7 +82,8 @@ namespace proc { get_apps(); std::string get_app_image(int app_id); - + std::string + get_last_run_app_name(); void terminate(); diff --git a/src/stream.cpp b/src/stream.cpp index 8b8497e2..c212b255 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -25,6 +25,7 @@ extern "C" { #include "stat_trackers.h" #include "stream.h" #include "sync.h" +#include "system_tray.h" #include "thread_safe.h" #include "utility.h" @@ -1732,6 +1733,11 @@ namespace stream { // If this is the last session, invoke the platform callbacks if (--running_sessions == 0) { +#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 + if (proc::proc.running()) { + system_tray::update_tray_pausing(proc::proc.get_last_run_app_name()); + } +#endif platf::streaming_will_stop(); } @@ -1776,6 +1782,9 @@ namespace stream { // If this is the first session, invoke the platform callbacks if (++running_sessions == 1) { platf::streaming_will_start(); +#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 + system_tray::update_tray_playing(proc::proc.get_last_run_app_name()); +#endif } return 0; diff --git a/src/system_tray.cpp b/src/system_tray.cpp index bc082f15..99a30222 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -10,10 +10,19 @@ #include #include #define TRAY_ICON WEB_DIR "images/favicon.ico" + #define TRAY_ICON_PLAYING WEB_DIR "images/sunshine-playing.ico" + #define TRAY_ICON_PAUSING WEB_DIR "images/sunshine-pausing.ico" + #define TRAY_ICON_LOCKED WEB_DIR "images/sunshine-locked.ico" #elif defined(__linux__) || defined(linux) || defined(__linux) #define TRAY_ICON "sunshine" + #define TRAY_ICON_PLAYING "sunshine-playing" + #define TRAY_ICON_PAUSING "sunshine-pausing" + #define TRAY_ICON_LOCKED "sunshine-locked" #elif defined(__APPLE__) || defined(__MACH__) #define TRAY_ICON WEB_DIR "images/logo-sunshine-16.png" + #define TRAY_ICON_PLAYING WEB_DIR "images/sunshine-playing-16.png" + #define TRAY_ICON_PAUSING WEB_DIR "images/sunshine-pausing-16.png" + #define TRAY_ICON_LOCKED WEB_DIR "images/sunshine-locked-16.png" #include #endif @@ -268,5 +277,93 @@ namespace system_tray { return 0; } + /** + * @brief Sets the tray icon in playing mode and spawns the appropriate notification + * @param app_name The started application name + */ + void + update_tray_playing(std::string app_name) { + tray.notification_title = NULL; + tray.notification_text = NULL; + tray.notification_cb = NULL; + tray.notification_icon = NULL; + tray.icon = TRAY_ICON_PLAYING; + tray_update(&tray); + tray.icon = TRAY_ICON_PLAYING; + tray.notification_title = "Stream Started"; + char msg[256]; + sprintf(msg, "Streaming started for %s", app_name.c_str()); + tray.notification_text = msg; + tray.tooltip = msg; + tray.notification_icon = TRAY_ICON_PLAYING; + tray_update(&tray); + } + + /** + * @brief Sets the tray icon in pausing mode (stream stopped but app running) and spawns the appropriate notification + * @param app_name The paused application name + */ + void + update_tray_pausing(std::string app_name) { + tray.notification_title = NULL; + tray.notification_text = NULL; + tray.notification_cb = NULL; + tray.notification_icon = NULL; + tray.icon = TRAY_ICON_PAUSING; + tray_update(&tray); + char msg[256]; + sprintf(msg, "Streaming paused for %s", app_name.c_str()); + tray.icon = TRAY_ICON_PAUSING; + tray.notification_title = "Stream Paused"; + tray.notification_text = msg; + tray.tooltip = msg; + tray.notification_icon = TRAY_ICON_PAUSING; + tray_update(&tray); + } + + /** + * @brief Sets the tray icon in stopped mode (app and stream stopped) and spawns the appropriate notification + * @param app_name The started application name + */ + void + update_tray_stopped(std::string app_name) { + tray.notification_title = NULL; + tray.notification_text = NULL; + tray.notification_cb = NULL; + tray.notification_icon = NULL; + tray.icon = TRAY_ICON; + tray_update(&tray); + char msg[256]; + sprintf(msg, "Application %s successfully stopped", app_name.c_str()); + tray.icon = TRAY_ICON; + tray.notification_icon = TRAY_ICON; + tray.notification_title = "Application Stopped"; + tray.notification_text = msg; + tray.tooltip = "Sunshine"; + tray_update(&tray); + } + + /** + * @brief Spawns a notification for PIN Pairing. Clicking it opens the PIN Web UI Page + */ + void + update_tray_require_pin() { + tray.notification_title = NULL; + tray.notification_text = NULL; + tray.notification_cb = NULL; + tray.notification_icon = NULL; + tray.icon = TRAY_ICON; + tray_update(&tray); + tray.icon = TRAY_ICON; + tray.notification_title = "Incoming Pairing Request"; + tray.notification_text = "Click here to complete the pairing process"; + tray.notification_icon = TRAY_ICON_LOCKED; + tray.tooltip = "Sunshine"; + tray.notification_cb = []() { + launch_ui_with_path("/pin"); + }; + tray_update(&tray); + } + } // namespace system_tray #endif diff --git a/src/system_tray.h b/src/system_tray.h index 18e3445f..f824c8a1 100644 --- a/src/system_tray.h +++ b/src/system_tray.h @@ -27,5 +27,13 @@ namespace system_tray { run_tray(); int end_tray(); + void + update_tray_playing(std::string app_name); + void + update_tray_pausing(std::string app_name); + void + update_tray_stopped(std::string app_name); + void + update_tray_require_pin(); } // namespace system_tray diff --git a/src_assets/common/assets/web/images/sunshine-locked-16.png b/src_assets/common/assets/web/images/sunshine-locked-16.png new file mode 100644 index 00000000..a7a3e88d Binary files /dev/null and b/src_assets/common/assets/web/images/sunshine-locked-16.png differ diff --git a/src_assets/common/assets/web/images/sunshine-locked-45.png b/src_assets/common/assets/web/images/sunshine-locked-45.png new file mode 100644 index 00000000..5cf346d5 Binary files /dev/null and b/src_assets/common/assets/web/images/sunshine-locked-45.png differ diff --git a/src_assets/common/assets/web/images/sunshine-locked.ico b/src_assets/common/assets/web/images/sunshine-locked.ico new file mode 100644 index 00000000..44f9f94f Binary files /dev/null and b/src_assets/common/assets/web/images/sunshine-locked.ico differ diff --git a/src_assets/common/assets/web/images/sunshine-locked.png b/src_assets/common/assets/web/images/sunshine-locked.png new file mode 100644 index 00000000..e18daef0 Binary files /dev/null and b/src_assets/common/assets/web/images/sunshine-locked.png differ diff --git a/src_assets/common/assets/web/images/sunshine-locked.svg b/src_assets/common/assets/web/images/sunshine-locked.svg new file mode 100644 index 00000000..6a507865 --- /dev/null +++ b/src_assets/common/assets/web/images/sunshine-locked.svg @@ -0,0 +1,75 @@ + + + + diff --git a/src_assets/common/assets/web/images/sunshine-pausing-16.png b/src_assets/common/assets/web/images/sunshine-pausing-16.png new file mode 100644 index 00000000..af4e4551 Binary files /dev/null and b/src_assets/common/assets/web/images/sunshine-pausing-16.png differ diff --git a/src_assets/common/assets/web/images/sunshine-pausing-45.png b/src_assets/common/assets/web/images/sunshine-pausing-45.png new file mode 100644 index 00000000..8b115267 Binary files /dev/null and b/src_assets/common/assets/web/images/sunshine-pausing-45.png differ diff --git a/src_assets/common/assets/web/images/sunshine-pausing.ico b/src_assets/common/assets/web/images/sunshine-pausing.ico new file mode 100644 index 00000000..6d459a74 Binary files /dev/null and b/src_assets/common/assets/web/images/sunshine-pausing.ico differ diff --git a/src_assets/common/assets/web/images/sunshine-pausing.png b/src_assets/common/assets/web/images/sunshine-pausing.png new file mode 100644 index 00000000..4e158195 Binary files /dev/null and b/src_assets/common/assets/web/images/sunshine-pausing.png differ diff --git a/src_assets/common/assets/web/images/sunshine-pausing.svg b/src_assets/common/assets/web/images/sunshine-pausing.svg new file mode 100644 index 00000000..0826be1d --- /dev/null +++ b/src_assets/common/assets/web/images/sunshine-pausing.svg @@ -0,0 +1,84 @@ + + + + diff --git a/src_assets/common/assets/web/images/sunshine-playing-16.png b/src_assets/common/assets/web/images/sunshine-playing-16.png new file mode 100644 index 00000000..8c59b5cb Binary files /dev/null and b/src_assets/common/assets/web/images/sunshine-playing-16.png differ diff --git a/src_assets/common/assets/web/images/sunshine-playing-45.png b/src_assets/common/assets/web/images/sunshine-playing-45.png new file mode 100644 index 00000000..40698dda Binary files /dev/null and b/src_assets/common/assets/web/images/sunshine-playing-45.png differ diff --git a/src_assets/common/assets/web/images/sunshine-playing.ico b/src_assets/common/assets/web/images/sunshine-playing.ico new file mode 100644 index 00000000..9ee52472 Binary files /dev/null and b/src_assets/common/assets/web/images/sunshine-playing.ico differ diff --git a/src_assets/common/assets/web/images/sunshine-playing.png b/src_assets/common/assets/web/images/sunshine-playing.png new file mode 100644 index 00000000..824909c7 Binary files /dev/null and b/src_assets/common/assets/web/images/sunshine-playing.png differ diff --git a/src_assets/common/assets/web/images/sunshine-playing.svg b/src_assets/common/assets/web/images/sunshine-playing.svg new file mode 100644 index 00000000..518af24f --- /dev/null +++ b/src_assets/common/assets/web/images/sunshine-playing.svg @@ -0,0 +1,89 @@ + + + + diff --git a/third-party/tray b/third-party/tray index 75106962..2921d962 160000 --- a/third-party/tray +++ b/third-party/tray @@ -1 +1 @@ -Subproject commit 75106962a8ec5abb86157908d12cb799147babb4 +Subproject commit 2921d9628e150c29f6e9f2ad86fd48b842aad5ca