From cf910d26168ef910cb289fb6eae1d12c88cb610d Mon Sep 17 00:00:00 2001 From: Adam Honse Date: Mon, 17 Aug 2026 19:36:05 -0500 Subject: [PATCH] Implement profile loading on service shutdown --- ProfileManager.cpp | 20 +++++++++++++- ProfileManager.h | 1 + ResourceManager.cpp | 41 ++++++++++++++++++++++++++++ ResourceManager.h | 5 ++++ startup/main_FreeBSD_Linux_MacOS.cpp | 40 ++++++++++++++++++++++++++- startup/main_Windows.cpp | 16 ++++++++++- 6 files changed, 120 insertions(+), 3 deletions(-) diff --git a/ProfileManager.cpp b/ProfileManager.cpp index ac2d4d639..a7b3ff780 100644 --- a/ProfileManager.cpp +++ b/ProfileManager.cpp @@ -60,9 +60,13 @@ ProfileManager::ProfileManager(const filesystem::path& config_dir) profilemanager_settings_schema["resume_profile"]["type"] = "profile"; profilemanager_settings_schema["resume_profile"]["description"] = QT_TRANSLATE_NOOP("Settings", "Profile to load after system resumes from sleep"); + profilemanager_settings_schema["service_shutdown_profile"]["title"] = QT_TRANSLATE_NOOP("Settings", "Load Profile on Service Shutdown"); + profilemanager_settings_schema["service_shutdown_profile"]["type"] = "profile"; + profilemanager_settings_schema["service_shutdown_profile"]["description"] = QT_TRANSLATE_NOOP("Settings", "Profile to load when the OpenRGB background service shuts down"); + profilemanager_settings_schema["service_startup_profile"]["title"] = QT_TRANSLATE_NOOP("Settings", "Load Profile on Service Startup"); profilemanager_settings_schema["service_startup_profile"]["type"] = "profile"; - profilemanager_settings_schema["service_startup_profile"]["description"] = QT_TRANSLATE_NOOP("Settings", "Profile to load when the OpenRGB background service starts"); + profilemanager_settings_schema["service_startup_profile"]["description"] = QT_TRANSLATE_NOOP("Settings", "Profile to load when the OpenRGB background service starts up"); profilemanager_settings_schema["suspend_profile"]["title"] = QT_TRANSLATE_NOOP("Settings", "Load Profile on Suspend"); profilemanager_settings_schema["suspend_profile"]["type"] = "profile"; @@ -104,6 +108,15 @@ ProfileManager::ProfileManager(const filesystem::path& config_dir) new_settings_keys = true; } + if(!profilemanager_settings.contains("service_shutdown_profile")) + { + json profile; + profile["enabled"] = false; + profile["name"] = ""; + profilemanager_settings["service_shutdown_profile"] = profile; + new_settings_keys = true; + } + if(!profilemanager_settings.contains("service_startup_profile")) { json profile; @@ -400,6 +413,11 @@ bool ProfileManager::LoadAutoProfileResume() return(LoadAutoProfile("resume_profile")); } +bool ProfileManager::LoadAutoProfileServiceShutdown() +{ + return(LoadAutoProfile("service_shutdown_profile")); +} + bool ProfileManager::LoadAutoProfileServiceStartup() { return(LoadAutoProfile("service_startup_profile")); diff --git a/ProfileManager.h b/ProfileManager.h index f78eaab97..7a0df627b 100644 --- a/ProfileManager.h +++ b/ProfileManager.h @@ -93,6 +93,7 @@ public: bool LoadAutoProfileExit(); bool LoadAutoProfileOpen(); bool LoadAutoProfileResume(); + bool LoadAutoProfileServiceShutdown(); bool LoadAutoProfileServiceStartup(); bool LoadAutoProfileSuspend(); diff --git a/ResourceManager.cpp b/ResourceManager.cpp index 0de66ef3c..46bbeba0a 100644 --- a/ResourceManager.cpp +++ b/ResourceManager.cpp @@ -838,6 +838,47 @@ void ResourceManager::HandleDetectionComplete() } } +void ResourceManager::ServiceShutdown() +{ + /*-----------------------------------------------------*\ + | Only run service shutdown operations when running as | + | a background service (headless server mode). In GUI | + | mode, the GUI close event handles profile loading. | + \*-----------------------------------------------------*/ + if(!start_server || start_gui) + { + return; + } + + LOG_INFO("[%s] Service shutting down", RESOURCEMANAGER); + + /*-----------------------------------------------------*\ + | Wait for any in-progress detection to finish before | + | loading a profile so that the full device list is | + | available. | + \*-----------------------------------------------------*/ + WaitForDetection(); + + /*-----------------------------------------------------*\ + | Load the exit profile if one is configured. This | + | runs the same profile mechanism used when the GUI | + | closes, allowing a profile to be applied to | + | controllers before they are closed and deleted during | + | Cleanup(). | + \*-----------------------------------------------------*/ + if(profile_manager) + { + if(profile_manager->LoadAutoProfileServiceShutdown()) + { + /*---------------------------------------------*\ + | Pause briefly to ensure that all profiles | + | are loaded. | + \*---------------------------------------------*/ + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + } + } +} + void ResourceManager::RescanDevices() { /*-----------------------------------------------------*\ diff --git a/ResourceManager.h b/ResourceManager.h index bb45dd6ee..601f758f9 100644 --- a/ResourceManager.h +++ b/ResourceManager.h @@ -141,6 +141,11 @@ public: void WaitForInitialization(); + /*-----------------------------------------------------*\ + | Service Shutdown | + \*-----------------------------------------------------*/ + void ServiceShutdown(); + private: bool AttemptLocalConnection(); void ClientTeardownThreadFunction(); diff --git a/startup/main_FreeBSD_Linux_MacOS.cpp b/startup/main_FreeBSD_Linux_MacOS.cpp index 307b24e9b..65ce0fff4 100644 --- a/startup/main_FreeBSD_Linux_MacOS.cpp +++ b/startup/main_FreeBSD_Linux_MacOS.cpp @@ -23,8 +23,23 @@ io_connect_t macUSPCIO_driver_connection; #include "macutils.h" #endif +#include +#include + using namespace std::chrono_literals; +#include + +static volatile bool service_stop_requested = false; +static std::mutex service_stop_mutex; +static std::condition_variable service_stop_cv; + +static void sigHandler(int s) +{ + service_stop_requested = true; + service_stop_cv.notify_one(); +} + /*---------------------------------------------------------*\ | WaitWhileServerOnline | | | @@ -33,9 +48,15 @@ using namespace std::chrono_literals; \*---------------------------------------------------------*/ void WaitWhileServerOnline(NetworkServer* srv) { + std::unique_lock lock(service_stop_mutex); while(srv->GetOnline()) { - std::this_thread::sleep_for(1s); + if(service_stop_requested) + { + srv->StopServer(); + break; + } + service_stop_cv.wait_for(lock, 1s); }; } @@ -69,6 +90,16 @@ int main(int argc, char* argv[]) ret_flags & RET_FLAG_CLI_POST_DETECTION, ret_flags & RET_FLAG_START_GUI); + /*-----------------------------------------------------*\ + | If running as a headless server, register signal | + | handler for stopping server | + \*-----------------------------------------------------*/ + if((ret_flags & RET_FLAG_START_SERVER) && !(ret_flags & RET_FLAG_START_GUI)) + { + std::signal(SIGINT, sigHandler); + std::signal(SIGTERM, sigHandler); + } + /*-----------------------------------------------------*\ | Perform application startup and run the application. | | This call returns only when the GUI application is | @@ -89,6 +120,13 @@ int main(int argc, char* argv[]) } } + /*-----------------------------------------------------*\ + | Call ServiceShutdown to allow operations before | + | controllers are closed and deleted. Only runs when | + | running as a background service (headless server). | + \*-----------------------------------------------------*/ + ResourceManager::get()->ServiceShutdown(); + /*-----------------------------------------------------*\ | Clean up detected devices so destructors can run. | \*-----------------------------------------------------*/ diff --git a/startup/main_Windows.cpp b/startup/main_Windows.cpp index cf8263da5..67f03ea84 100644 --- a/startup/main_Windows.cpp +++ b/startup/main_Windows.cpp @@ -10,6 +10,8 @@ \*---------------------------------------------------------*/ #include +#include +#include #include #include #include @@ -40,6 +42,8 @@ static SERVICE_STATUS service_status; static bool started_as_service; static volatile bool service_stop_requested; +static std::mutex service_stop_mutex; +static std::condition_variable service_stop_cv; static bool have_console; static std::mutex service_status_mutex; @@ -224,6 +228,7 @@ static DWORD WINAPI ServiceControlHandler(DWORD dwControl, DWORD dwEventType, LP case SERVICE_CONTROL_PRESHUTDOWN: ReportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 10000); service_stop_requested = true; + service_stop_cv.notify_one(); break; default: @@ -518,13 +523,15 @@ void InitializeTimerResolutionThreadFunction() \*---------------------------------------------------------*/ static void WaitWhileServerOnline(NetworkServer* srv) { + std::unique_lock lock(service_stop_mutex); while(srv->GetOnline()) { - std::this_thread::sleep_for(1s); if(service_stop_requested) { srv->StopServer(); + break; } + service_stop_cv.wait_for(lock, 1s); }; } @@ -633,6 +640,13 @@ static int common_main(int argc, char* argv[]) } } + /*-----------------------------------------------------*\ + | Call ServiceShutdown to allow operations before | + | controllers are closed and deleted. Only runs when | + | running as a background service (headless server). | + \*-----------------------------------------------------*/ + ResourceManager::get()->ServiceShutdown(); + /*-----------------------------------------------------*\ | Clean up detected devices so destructors can run. | \*-----------------------------------------------------*/