/** * @file src/main.cpp * @brief Definitions for the main entry point for Sunshine. */ // standard includes #include #include #include #include // local includes #include "confighttp.h" #include "display_device.h" #include "entry_handler.h" #include "globals.h" #include "httpcommon.h" #include "logging.h" #include "main.h" #include "nvhttp.h" #include "process.h" #include "system_tray.h" #include "upnp.h" #include "version.h" #include "video.h" extern "C" { #include "rswrapper.h" } using namespace std::literals; std::map> signal_handlers; void on_signal_forwarder(int sig) { signal_handlers.at(sig)(); } template void on_signal(int sig, FN &&fn) { signal_handlers.emplace(sig, std::forward(fn)); std::signal(sig, on_signal_forwarder); } std::map> cmd_to_func { { "creds"sv, [](const char *name, int argc, char **argv) { return args::creds(name, argc, argv); } }, { "help"sv, [](const char *name, int argc, char **argv) { return args::help(name); } }, { "version"sv, [](const char *name, int argc, char **argv) { return args::version(); } }, #ifdef _WIN32 { "restore-nvprefs-undo"sv, [](const char *name, int argc, char **argv) { return args::restore_nvprefs_undo(); } }, #endif }; #ifdef _WIN32 LRESULT CALLBACK SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CLOSE: DestroyWindow(hwnd); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; case WM_ENDSESSION: { // Terminate ourselves with a blocking exit call std::cout << "Received WM_ENDSESSION"sv << std::endl; lifetime::exit_sunshine(0, false); return 0; } default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } } WINAPI BOOL ConsoleCtrlHandler(DWORD type) { if (type == CTRL_CLOSE_EVENT) { BOOST_LOG(info) << "Console closed handler called"; lifetime::exit_sunshine(0, false); } return FALSE; } #endif int main(int argc, char *argv[]) { lifetime::argv = argv; task_pool_util::TaskPool::task_id_t force_shutdown = nullptr; #ifdef _WIN32 setlocale(LC_ALL, "C"); #endif #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" // Use UTF-8 conversion for the default C++ locale (used by boost::log) std::locale::global(std::locale(std::locale(), new std::codecvt_utf8)); #pragma GCC diagnostic pop mail::man = std::make_shared(); if (config::parse(argc, argv)) { return 0; } auto log_deinit_guard = logging::init(config::sunshine.min_log_level, config::sunshine.log_file); if (!log_deinit_guard) { BOOST_LOG(error) << "Logging failed to initialize"sv; } // logging can begin at this point // if anything is logged prior to this point, it will appear in stdout, but not in the log viewer in the UI // the version should be printed to the log before anything else BOOST_LOG(info) << PROJECT_NAME << " version: " << PROJECT_VER; // Log publisher metadata log_publisher_data(); if (!config::sunshine.cmd.name.empty()) { auto fn = cmd_to_func.find(config::sunshine.cmd.name); if (fn == std::end(cmd_to_func)) { BOOST_LOG(fatal) << "Unknown command: "sv << config::sunshine.cmd.name; BOOST_LOG(info) << "Possible commands:"sv; for (auto &[key, _] : cmd_to_func) { BOOST_LOG(info) << '\t' << key; } return 7; } return fn->second(argv[0], config::sunshine.cmd.argc, config::sunshine.cmd.argv); } // Adding guard here first as it also performs recovery after crash, // otherwise people could theoretically end up without display output. // It also should be destroyed before forced shutdown to expedite the cleanup. auto display_device_deinit_guard = display_device::init(); if (!display_device_deinit_guard) { BOOST_LOG(error) << "Display device session failed to initialize"sv; } #ifdef WIN32 // Modify relevant NVIDIA control panel settings if the system has corresponding gpu if (nvprefs_instance.load()) { // Restore global settings to the undo file left by improper termination of sunshine.exe nvprefs_instance.restore_from_and_delete_undo_file_if_exists(); // Modify application settings for sunshine.exe nvprefs_instance.modify_application_profile(); // Modify global settings, undo file is produced in the process to restore after improper termination nvprefs_instance.modify_global_profile(); // Unload dynamic library to survive driver re-installation nvprefs_instance.unload(); } // Wait as long as possible to terminate Sunshine.exe during logoff/shutdown SetProcessShutdownParameters(0x100, SHUTDOWN_NORETRY); // We must create a hidden window to receive shutdown notifications since we load gdi32.dll std::promise session_monitor_hwnd_promise; auto session_monitor_hwnd_future = session_monitor_hwnd_promise.get_future(); std::promise session_monitor_join_thread_promise; auto session_monitor_join_thread_future = session_monitor_join_thread_promise.get_future(); std::thread session_monitor_thread([&]() { session_monitor_join_thread_promise.set_value_at_thread_exit(); WNDCLASSA wnd_class {}; wnd_class.lpszClassName = "SunshineSessionMonitorClass"; wnd_class.lpfnWndProc = SessionMonitorWindowProc; if (!RegisterClassA(&wnd_class)) { session_monitor_hwnd_promise.set_value(NULL); BOOST_LOG(error) << "Failed to register session monitor window class"sv << std::endl; return; } auto wnd = CreateWindowExA( 0, wnd_class.lpszClassName, "Sunshine Session Monitor Window", 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, nullptr, nullptr); session_monitor_hwnd_promise.set_value(wnd); if (!wnd) { BOOST_LOG(error) << "Failed to create session monitor window"sv << std::endl; return; } ShowWindow(wnd, SW_HIDE); // Run the message loop for our window MSG msg {}; while (GetMessage(&msg, nullptr, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } }); auto session_monitor_join_thread_guard = util::fail_guard([&]() { if (session_monitor_hwnd_future.wait_for(1s) == std::future_status::ready) { if (HWND session_monitor_hwnd = session_monitor_hwnd_future.get()) { PostMessage(session_monitor_hwnd, WM_CLOSE, 0, 0); } if (session_monitor_join_thread_future.wait_for(1s) == std::future_status::ready) { session_monitor_thread.join(); return; } else { BOOST_LOG(warning) << "session_monitor_join_thread_future reached timeout"; } } else { BOOST_LOG(warning) << "session_monitor_hwnd_future reached timeout"; } session_monitor_thread.detach(); }); #endif task_pool.start(1); #if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 // create tray thread and detach it system_tray::run_tray(); #endif // Create signal handler after logging has been initialized auto shutdown_event = mail::man->event(mail::shutdown); on_signal(SIGINT, [&force_shutdown, &display_device_deinit_guard, shutdown_event]() { BOOST_LOG(info) << "Interrupt handler called"sv; auto task = []() { BOOST_LOG(fatal) << "10 seconds passed, yet Sunshine's still running: Forcing shutdown"sv; logging::log_flush(); lifetime::debug_trap(); }; force_shutdown = task_pool.pushDelayed(task, 10s).task_id; shutdown_event->raise(true); display_device_deinit_guard = nullptr; }); on_signal(SIGTERM, [&force_shutdown, &display_device_deinit_guard, shutdown_event]() { BOOST_LOG(info) << "Terminate handler called"sv; auto task = []() { BOOST_LOG(fatal) << "10 seconds passed, yet Sunshine's still running: Forcing shutdown"sv; logging::log_flush(); lifetime::debug_trap(); }; force_shutdown = task_pool.pushDelayed(task, 10s).task_id; shutdown_event->raise(true); display_device_deinit_guard = nullptr; }); #ifdef _WIN32 // Terminate gracefully on Windows when console window is closed SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); #endif proc::refresh(config::stream.file_apps); // If any of the following fail, we log an error and continue event though sunshine will not function correctly. // This allows access to the UI to fix configuration problems or view the logs. auto platf_deinit_guard = platf::init(); if (!platf_deinit_guard) { BOOST_LOG(error) << "Platform failed to initialize"sv; } auto proc_deinit_guard = proc::init(); if (!proc_deinit_guard) { BOOST_LOG(error) << "Proc failed to initialize"sv; } reed_solomon_init(); auto input_deinit_guard = input::init(); if (input::probe_gamepads()) { BOOST_LOG(warning) << "No gamepad input is available"sv; } if (video::probe_encoders()) { BOOST_LOG(error) << "Video failed to find working encoder"sv; } if (http::init()) { 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; std::this_thread::sleep_for(10s); #endif return -1; } std::unique_ptr mDNS; auto sync_mDNS = std::async(std::launch::async, [&mDNS]() { mDNS = platf::publish::start(); }); std::unique_ptr upnp_unmap; auto sync_upnp = std::async(std::launch::async, [&upnp_unmap]() { upnp_unmap = upnp::start(); }); // FIXME: Temporary workaround: Simple-Web_server needs to be updated or replaced if (shutdown_event->peek()) { return lifetime::desired_exit_code; } std::thread httpThread { nvhttp::start }; std::thread configThread { confighttp::start }; #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; } #endif rtsp_stream::rtpThread(); httpThread.join(); configThread.join(); task_pool.stop(); task_pool.join(); // stop system tray #if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 system_tray::end_tray(); #endif #ifdef WIN32 // Restore global NVIDIA control panel settings if (nvprefs_instance.owning_undo_file() && nvprefs_instance.load()) { nvprefs_instance.restore_global_profile(); nvprefs_instance.unload(); } #endif return lifetime::desired_exit_code; }