/*---------------------------------------------------------*\ | ResourceManager.cpp | | | | OpenRGB Resource Manager controls access to application | | components including RGBControllers, I2C interfaces, | | and network SDK components | | | | Adam Honse (CalcProgrammer1) 27 Sep 2020 | | | | This file is part of the OpenRGB project | | SPDX-License-Identifier: GPL-2.0-or-later | \*---------------------------------------------------------*/ #ifdef _WIN32 #include #include #endif #include #include #include "cli.h" #include "DetectionManager.h" #include "ResourceManager.h" #include "ProfileManager.h" #include "LogManager.h" #include "SettingsManager.h" #include "NetworkClient.h" #include "NetworkServer.h" #include "filesystem.h" using namespace std::chrono_literals; /*---------------------------------------------------------*\ | ResourceManager Callback Functions | \*---------------------------------------------------------*/ static void ResourceManagerDetectionCallback(void * this_ptr, unsigned int update_reason) { ResourceManager* this_obj = (ResourceManager *)this_ptr; switch(update_reason) { case DETECTIONMANAGER_UPDATE_REASON_I2C_BUS_REGISTERED: this_obj->SignalResourceManagerUpdate(RESOURCEMANAGER_UPDATE_REASON_I2C_BUS_LIST_UPDATED); break; case DETECTIONMANAGER_UPDATE_REASON_DETECTION_STARTED: this_obj->SignalResourceManagerUpdate(RESOURCEMANAGER_UPDATE_REASON_DETECTION_STARTED); this_obj->ClearLocalDevices(); break; case DETECTIONMANAGER_UPDATE_REASON_RGBCONTROLLER_REGISTERED: case DETECTIONMANAGER_UPDATE_REASON_RGBCONTROLLER_UNREGISTERED: this_obj->UpdateDeviceList(); break; case DETECTIONMANAGER_UPDATE_REASON_DETECTION_PROGRESS_CHANGED: this_obj->SignalResourceManagerUpdate(RESOURCEMANAGER_UPDATE_REASON_DETECTION_PROGRESS_CHANGED); break; case DETECTIONMANAGER_UPDATE_REASON_DETECTION_COMPLETE: this_obj->SignalResourceManagerUpdate(RESOURCEMANAGER_UPDATE_REASON_DETECTION_COMPLETE); this_obj->UpdateDeviceList(); break; } } static void ResourceManagerNetworkClientInfoChangeCallback(void* this_ptr) { ResourceManager* this_obj = (ResourceManager*)this_ptr; this_obj->SignalResourceManagerUpdate(RESOURCEMANAGER_UPDATE_REASON_CLIENT_INFO_UPDATED); this_obj->UpdateDeviceList(); } /*---------------------------------------------------------*\ | ResourceManager name for log entries | \*---------------------------------------------------------*/ const char* RESOURCEMANAGER = "ResourceManager"; /*---------------------------------------------------------*\ | ResourceManager Global Instance Pointer | \*---------------------------------------------------------*/ ResourceManager* ResourceManager::instance; ResourceManager::ResourceManager() { /*-----------------------------------------------------*\ | Initialize global instance pointer the when created | | There should only ever be one instance of | | ResourceManager | \*-----------------------------------------------------*/ if(!instance) { instance = this; } /*-----------------------------------------------------*\ | If, for whatever reason, ResourceManager already | | exists, delete this instance as only one should exist | \*-----------------------------------------------------*/ else { delete this; return; } /*-----------------------------------------------------*\ | Initialize Detection Variables | \*-----------------------------------------------------*/ auto_connection_client = NULL; auto_connection_active = false; detection_enabled = true; init_finished = false; plugin_manager = NULL; SetupConfigurationDirectory(); /*-----------------------------------------------------*\ | Load settings from file | \*-----------------------------------------------------*/ settings_manager = new SettingsManager(); settings_manager->LoadSettings(GetConfigurationDirectory() / "OpenRGB.json"); /*-----------------------------------------------------*\ | Configure the log manager | \*-----------------------------------------------------*/ LogManager::get()->configure(settings_manager->GetSettings("LogManager"), GetConfigurationDirectory()); /*-----------------------------------------------------*\ | Initialize Server Instance | | If configured, pass through full controller list | | including clients. Otherwise, pass only local | | hardware controllers | \*-----------------------------------------------------*/ json server_settings = settings_manager->GetSettings("Server"); bool all_controllers = false; bool legacy_workaround = false; if(server_settings.contains("all_controllers")) { all_controllers = server_settings["all_controllers"]; } if(all_controllers) { server = new NetworkServer(rgb_controllers); } else { server = new NetworkServer(DetectionManager::get()->GetRGBControllers()); } /*-----------------------------------------------------*\ | Set server name | \*-----------------------------------------------------*/ std::string titleString = "OpenRGB "; titleString.append(VERSION_STRING); server->SetName(titleString); server->SetSettingsManager(settings_manager); /*-----------------------------------------------------*\ | Enable legacy SDK workaround in server if configured | \*-----------------------------------------------------*/ if(server_settings.contains("legacy_workaround")) { legacy_workaround = server_settings["legacy_workaround"]; } if(legacy_workaround) { server->SetLegacyWorkaroundEnable(true); } /*-----------------------------------------------------*\ | Load sizes list from file | \*-----------------------------------------------------*/ profile_manager = new ProfileManager(GetConfigurationDirectory()); server->SetProfileManager(profile_manager); } ResourceManager::~ResourceManager() { } /*---------------------------------------------------------*\ | ResourceManager Global Instance Accessor | \*---------------------------------------------------------*/ ResourceManager* ResourceManager::get() { /*-----------------------------------------------------*\ | If ResourceManager does not exist yet, create it | \*-----------------------------------------------------*/ if(!instance) { instance = new ResourceManager(); } return instance; } /*---------------------------------------------------------*\ | Resource Accessors | \*---------------------------------------------------------*/ std::vector& ResourceManager::GetClients() { return(clients); } filesystem::path ResourceManager::GetConfigurationDirectory() { return(config_dir); } std::vector & ResourceManager::GetI2CBusses() { return DetectionManager::get()->GetI2CBuses(); } PluginManagerInterface* ResourceManager::GetPluginManager() { return(plugin_manager); } ProfileManager* ResourceManager::GetProfileManager() { return(profile_manager); } std::vector & ResourceManager::GetRGBControllers() { return rgb_controllers; } NetworkServer* ResourceManager::GetServer() { return(server); } SettingsManager* ResourceManager::GetSettingsManager() { return(settings_manager); } void ResourceManager::SetConfigurationDirectory(const filesystem::path &directory) { config_dir = directory; settings_manager->LoadSettings(directory / "OpenRGB.json"); profile_manager->SetConfigurationDirectory(directory); } void ResourceManager::SetPluginManager(PluginManagerInterface* plugin_manager_ptr) { plugin_manager = plugin_manager_ptr; server->SetPluginManager(plugin_manager); } /*---------------------------------------------------------*\ | Network Client Registration | \*---------------------------------------------------------*/ void ResourceManager::RegisterNetworkClient(NetworkClient* new_client) { new_client->RegisterClientInfoChangeCallback(ResourceManagerNetworkClientInfoChangeCallback, this); clients.push_back(new_client); } void ResourceManager::UnregisterNetworkClient(NetworkClient* network_client) { /*-----------------------------------------------------*\ | Stop the disconnecting client | \*-----------------------------------------------------*/ network_client->StopClient(); /*-----------------------------------------------------*\ | Clear callbacks from the client before removal | \*-----------------------------------------------------*/ network_client->ClearCallbacks(); /*-----------------------------------------------------*\ | Find the client to remove and remove it from the | | clients list | \*-----------------------------------------------------*/ std::vector::iterator client_it = std::find(clients.begin(), clients.end(), network_client); if(client_it != clients.end()) { clients.erase(client_it); } /*-----------------------------------------------------*\ | Delete the client | \*-----------------------------------------------------*/ delete network_client; UpdateDeviceList(); } /*---------------------------------------------------------*\ | Local Client Accessors | \*---------------------------------------------------------*/ NetworkClient* ResourceManager::GetLocalClient() { return(auto_connection_client); } unsigned int ResourceManager::GetLocalClientProtocolVersion() { return(auto_connection_client->GetProtocolVersion()); } bool ResourceManager::IsLocalClient() { return(auto_connection_active); } /*---------------------------------------------------------*\ | Callback Registration Functions | \*---------------------------------------------------------*/ void ResourceManager::RegisterResourceManagerCallback(ResourceManagerCallback new_callback, void * new_callback_arg) { ResourceManagerCallbackMutex.lock(); for(size_t idx = 0; idx < ResourceManagerCallbacks.size(); idx++) { if(ResourceManagerCallbacks[idx] == new_callback && ResourceManagerCallbackArgs[idx] == new_callback_arg) { ResourceManagerCallbackMutex.unlock(); LOG_TRACE("[%s] Tried to register an already registered ResourceManager callback, skipping. Total callbacks registered: %d", RESOURCEMANAGER, ResourceManagerCallbacks.size()); return; } } ResourceManagerCallbacks.push_back(new_callback); ResourceManagerCallbackArgs.push_back(new_callback_arg); ResourceManagerCallbackMutex.unlock(); LOG_TRACE("[%s] Registered ResourceManager callback. Total callbacks registered: %d", RESOURCEMANAGER, ResourceManagerCallbacks.size()); } void ResourceManager::UnregisterResourceManagerCallback(ResourceManagerCallback callback, void * callback_arg) { ResourceManagerCallbackMutex.lock(); for(size_t idx = 0; idx < ResourceManagerCallbacks.size(); idx++) { if(ResourceManagerCallbacks[idx] == callback && ResourceManagerCallbackArgs[idx] == callback_arg) { ResourceManagerCallbacks.erase(ResourceManagerCallbacks.begin() + idx); ResourceManagerCallbackArgs.erase(ResourceManagerCallbackArgs.begin() + idx); } } ResourceManagerCallbackMutex.unlock(); LOG_TRACE("[%s] Unregistered ResourceManager callback. Total callbacks registered: %d", RESOURCEMANAGER, ResourceManagerCallbackArgs.size()); } /*---------------------------------------------------------*\ | Functions to manage detection | \*---------------------------------------------------------*/ bool ResourceManager::GetDetectionEnabled() { return(detection_enabled); } unsigned int ResourceManager::GetDetectionPercent() { //TODO: get from DetectionManager or client return DetectionManager::get()->GetDetectionPercent(); } std::string ResourceManager::GetDetectionString() { //TODO: get from DetectionManager or client return DetectionManager::get()->GetDetectionString(); } void ResourceManager::RescanDevices() { /*-----------------------------------------------------*\ | If automatic local connection is active, the primary | | instance is the local server, so send rescan requests | | to the automatic local connection client | \*-----------------------------------------------------*/ if(auto_connection_active && auto_connection_client != NULL) { auto_connection_client->SendRequest_RescanDevices(); } /*-----------------------------------------------------*\ | If detection is disabled and there is exactly one | | client, the primary instance is the connected server, | | so send rescan requests to the first (and only) | | client | \*-----------------------------------------------------*/ else if(!detection_enabled && clients.size() == 1) { clients[0]->SendRequest_RescanDevices(); } /*-----------------------------------------------------*\ | If detection is enabled, start detection | \*-----------------------------------------------------*/ if(detection_enabled) { DetectionManager::get()->BeginDetection(); } } void ResourceManager::StopDeviceDetection() { //TODO: Call DetectionManager::AbortDetection() or send SDK command if local client } void ResourceManager::ClearLocalDevices() { DeviceListChangeMutex.lock(); /*-----------------------------------------------------*\ | Remove hardware controllers from controller list | \*-----------------------------------------------------*/ std::vector& rgb_controllers_hw = DetectionManager::get()->GetRGBControllers(); for(std::size_t hw_controller_idx = 0; hw_controller_idx < rgb_controllers_hw.size(); hw_controller_idx++) { for(std::size_t controller_idx = 0; controller_idx < rgb_controllers.size(); controller_idx++) { if(rgb_controllers[controller_idx] == rgb_controllers_hw[hw_controller_idx]) { rgb_controllers.erase(rgb_controllers.begin() + controller_idx); break; } } } DeviceListChangeMutex.unlock(); /*-----------------------------------------------------*\ | Signal device list update | \*-----------------------------------------------------*/ SignalResourceManagerUpdate(RESOURCEMANAGER_UPDATE_REASON_DEVICE_LIST_UPDATED); /*-----------------------------------------------------*\ | Device list has changed, inform all clients connected | | to this server | \*-----------------------------------------------------*/ server->DeviceListChanged(); } void ResourceManager::UpdateDeviceList() { DeviceListChangeMutex.lock(); /*-----------------------------------------------------*\ | Insert hardware controllers into controller list | \*-----------------------------------------------------*/ std::vector& rgb_controllers_hw = DetectionManager::get()->GetRGBControllers(); for(std::size_t hw_controller_idx = 0; hw_controller_idx < rgb_controllers_hw.size(); hw_controller_idx++) { /*-------------------------------------------------*\ | Check if the controller is already in the list | | at the correct index | \*-------------------------------------------------*/ if(hw_controller_idx < rgb_controllers.size()) { if(rgb_controllers[hw_controller_idx] == rgb_controllers_hw[hw_controller_idx]) { continue; } } /*-------------------------------------------------*\ | If not, check if the controller is already in the | | list at a different index | \*-------------------------------------------------*/ for(std::size_t controller_idx = 0; controller_idx < rgb_controllers.size(); controller_idx++) { if(rgb_controllers[controller_idx] == rgb_controllers_hw[hw_controller_idx]) { rgb_controllers.erase(rgb_controllers.begin() + controller_idx); rgb_controllers.insert(rgb_controllers.begin() + hw_controller_idx, rgb_controllers_hw[hw_controller_idx]); break; } } /*-------------------------------------------------*\ | If it still hasn't been found, add it to the list | \*-------------------------------------------------*/ rgb_controllers.insert(rgb_controllers.begin() + hw_controller_idx, rgb_controllers_hw[hw_controller_idx]); } DeviceListChangeMutex.unlock(); /*-----------------------------------------------------*\ | Signal device list update | \*-----------------------------------------------------*/ SignalResourceManagerUpdate(RESOURCEMANAGER_UPDATE_REASON_DEVICE_LIST_UPDATED); /*-----------------------------------------------------*\ | Device list has changed, inform all clients connected | | to this server | \*-----------------------------------------------------*/ server->DeviceListChanged(); } void ResourceManager::WaitForDetection() { DetectionManager::get()->WaitForDetection(); } /*---------------------------------------------------------*\ | Function to signal update callbacks | \*---------------------------------------------------------*/ void ResourceManager::SignalResourceManagerUpdate(unsigned int update_reason) { ResourceManagerCallbackMutex.lock(); for(std::size_t callback_idx = 0; callback_idx < ResourceManagerCallbacks.size(); callback_idx++) { ResourceManagerCallbacks[callback_idx](ResourceManagerCallbackArgs[callback_idx], update_reason); } ResourceManagerCallbackMutex.unlock(); LOG_TRACE("[%s] ResourceManager update signalled: %d", RESOURCEMANAGER, update_reason); } void ResourceManager::SetupConfigurationDirectory() { config_dir.clear(); #ifdef _WIN32 const wchar_t* appdata = _wgetenv(L"APPDATA"); if(appdata != NULL) { config_dir = appdata; } #else const char* xdg_config_home = getenv("XDG_CONFIG_HOME"); const char* home = getenv("HOME"); /*-----------------------------------------------------*\ | Check both XDG_CONFIG_HOME and APPDATA environment | | variables. If neither exist, use current directory | \*-----------------------------------------------------*/ if(xdg_config_home != NULL) { config_dir = xdg_config_home; } else if(home != NULL) { config_dir = home; config_dir /= ".config"; } #endif /*-----------------------------------------------------*\ | If a configuration directory was found, append OpenRGB| \*-----------------------------------------------------*/ if(config_dir != "") { config_dir.append("OpenRGB"); /*-------------------------------------------------*\ | Create OpenRGB configuration directory if it | | doesn't exist | \*-------------------------------------------------*/ filesystem::create_directories(config_dir); } else { config_dir = "./"; } } bool ResourceManager::AttemptLocalConnection() { LOG_DEBUG("[%s] Attempting local server connection...", RESOURCEMANAGER); bool success = false; auto_connection_client = new NetworkClient(ResourceManager::get()->GetRGBControllers()); std::string titleString = "OpenRGB "; titleString.append(VERSION_STRING); auto_connection_client->SetName(titleString.c_str()); auto_connection_client->StartClient(); for(int timeout = 0; timeout < 10; timeout++) { if(auto_connection_client->GetConnected()) { break; } std::this_thread::sleep_for(5ms); } if(!auto_connection_client->GetConnected()) { LOG_TRACE("[%s] Client failed to connect", RESOURCEMANAGER); auto_connection_client->StopClient(); LOG_TRACE("[%s] Client stopped", RESOURCEMANAGER); delete auto_connection_client; auto_connection_client = NULL; } else { ResourceManager::get()->RegisterNetworkClient(auto_connection_client); LOG_TRACE("[%s] Registered network client", RESOURCEMANAGER); success = true; /*-------------------------------------------------*\ | Wait up to 5 seconds for the client connection to | | retrieve all controllers | \*-------------------------------------------------*/ for(int timeout = 0; timeout < 1000; timeout++) { if(auto_connection_client->GetOnline()) { break; } std::this_thread::sleep_for(5ms); } } return success; } void ResourceManager::Initialize(bool tryConnect, bool detectDevices, bool startServer, bool applyPostOptions) { /*-----------------------------------------------------*\ | Cache the parameters | | TODO: Possibly cache them in the CLI file somewhere | \*-----------------------------------------------------*/ tryAutoConnect = tryConnect; detection_enabled = detectDevices; start_server = startServer; apply_post_options = applyPostOptions; /*-----------------------------------------------------*\ | If enabled, try connecting to local server instead of | | detecting devices from this instance of OpenRGB | \*-----------------------------------------------------*/ if(tryAutoConnect) { /*-------------------------------------------------*\ | Attempt connection to local server | \*-------------------------------------------------*/ if(AttemptLocalConnection()) { LOG_DEBUG("[%s] Local OpenRGB server connected, running in client mode", RESOURCEMANAGER); /*---------------------------------------------*\ | Set auto connection active flag and disable | | detection if the local server was connected | \*---------------------------------------------*/ auto_connection_active = true; detection_enabled = false; profile_manager->UpdateProfileList(); } tryAutoConnect = false; } /*-----------------------------------------------------*\ | Initialize Saved Client Connections | \*-----------------------------------------------------*/ json client_settings = settings_manager->GetSettings("Client"); if(client_settings.contains("clients")) { for(unsigned int client_idx = 0; client_idx < client_settings["clients"].size(); client_idx++) { NetworkClient * client = new NetworkClient(rgb_controllers); std::string titleString = "OpenRGB "; titleString.append(VERSION_STRING); std::string client_ip = client_settings["clients"][client_idx]["ip"]; unsigned short client_port = client_settings["clients"][client_idx]["port"]; client->SetIP(client_ip.c_str()); client->SetName(titleString.c_str()); client->SetPort(client_port); client->StartClient(); for(int timeout = 0; timeout < 100; timeout++) { if(client->GetConnected()) { break; } std::this_thread::sleep_for(10ms); } RegisterNetworkClient(client); } } /*-----------------------------------------------------*\ | Start server if requested | \*-----------------------------------------------------*/ if(start_server) { GetServer()->StartServer(); if(!GetServer()->GetOnline()) { LOG_DEBUG("[%s] Server failed to start", RESOURCEMANAGER); } } /*-----------------------------------------------------*\ | Perform actual detection if enabled | \*-----------------------------------------------------*/ if(detection_enabled) { LOG_DEBUG("[%s] Local OpenRGB server not found, running in standalone mode", RESOURCEMANAGER); DetectionManager::get()->RegisterDetectionCallback(ResourceManagerDetectionCallback, this); DetectionManager::get()->BeginDetection(); } /*-----------------------------------------------------*\ | Process command line arguments after detection only | | if the pre-detection parsing indicated it should be | | run | \*-----------------------------------------------------*/ if(apply_post_options) { cli_post_detection(); } init_finished = true; } void ResourceManager::WaitForInitialization() { /*-----------------------------------------------------*\ | A reliable sychronization of this kind is impossible | | without the use of a `barrier` implementation, which | | is only introduced in C++20 | \*-----------------------------------------------------*/ while(!init_finished) { std::this_thread::sleep_for(1ms); }; }