From 545815fd6dc9d9d36bedd7add168e65baa315a30 Mon Sep 17 00:00:00 2001 From: Jordon Brooks Date: Tue, 16 Jan 2024 00:09:12 +0000 Subject: [PATCH] Add initial score-based dock detection This is early development & untested code specifically targeting the steam deck for now until further finalised --- HarmonyLinkLib/include/HarmonyLinkLib.h | 2 + HarmonyLinkLib/src/HarmonyLinkLib.cpp | 11 + .../src/Platform/IPlatformUtilities.cpp | 237 +++++++++++------- .../src/Platform/IPlatformUtilities.h | 11 +- .../src/Platform/Unix/UnixUtilities.cpp | 24 +- .../src/Platform/Unix/UnixUtilities.h | 8 + .../src/Platform/Windows/WindowsUtilities.cpp | 187 +++++++++----- .../src/Platform/Windows/WindowsUtilities.h | 6 + 8 files changed, 336 insertions(+), 150 deletions(-) diff --git a/HarmonyLinkLib/include/HarmonyLinkLib.h b/HarmonyLinkLib/include/HarmonyLinkLib.h index dd6bebd..d6575a5 100644 --- a/HarmonyLinkLib/include/HarmonyLinkLib.h +++ b/HarmonyLinkLib/include/HarmonyLinkLib.h @@ -48,6 +48,8 @@ namespace HarmonyLinkLib extern "C" HARMONYLINKLIB_API bool get_is_linux(); + extern "C" HARMONYLINKLIB_API bool get_is_docked(); + extern "C" HARMONYLINKLIB_API FCPUInfo* get_cpu_info(); extern "C" HARMONYLINKLIB_API FDevice* get_device_info(); diff --git a/HarmonyLinkLib/src/HarmonyLinkLib.cpp b/HarmonyLinkLib/src/HarmonyLinkLib.cpp index ff05bae..91ccc2c 100644 --- a/HarmonyLinkLib/src/HarmonyLinkLib.cpp +++ b/HarmonyLinkLib/src/HarmonyLinkLib.cpp @@ -43,6 +43,17 @@ namespace HarmonyLinkLib return PlatformUtilities->is_linux(); } + bool get_is_docked() + { + if (!PlatformUtilities) + { + std::wcout << "Failed to get platform utilities!\n"; + return false; + } + + return PlatformUtilities->is_docked(); + } + FCPUInfo* get_cpu_info() { if (!PlatformUtilities) diff --git a/HarmonyLinkLib/src/Platform/IPlatformUtilities.cpp b/HarmonyLinkLib/src/Platform/IPlatformUtilities.cpp index 4475379..8c53695 100644 --- a/HarmonyLinkLib/src/Platform/IPlatformUtilities.cpp +++ b/HarmonyLinkLib/src/Platform/IPlatformUtilities.cpp @@ -31,106 +31,173 @@ namespace HarmonyLinkLib { static std::shared_ptr INSTANCE = nullptr; -std::shared_ptr& IPlatformUtilities::GetInstance() -{ - if (!INSTANCE) + std::shared_ptr& IPlatformUtilities::GetInstance() { -#if BUILD_WINDOWS - INSTANCE = std::make_shared(); -#elif BUILD_LINUX - INSTANCE = std::make_shared(); -#elif BUILD_MAC - INSTANCE = std::make_shared(); -#elif BUILD_UNIX - INSTANCE = std::make_shared(); -// ... other platform checks -#else - std::wcout << "Platform is not supported.\n" -#endif + if (!INSTANCE) + { + #if BUILD_WINDOWS + INSTANCE = std::make_shared(); + #elif BUILD_LINUX + INSTANCE = std::make_shared(); + #elif BUILD_MAC + INSTANCE = std::make_shared(); + #elif BUILD_UNIX + INSTANCE = std::make_shared(); + // ... other platform checks + #else + std::wcout << "Platform is not supported.\n" + #endif + } + + return INSTANCE; } - - return INSTANCE; -} -bool IPlatformUtilities::is_running_under_wine() -{ - return WineUtilities::is_wine_present(); -} - -bool IPlatformUtilities::is_linux() -{ -#ifdef BUILD_LINUX - return true; -#else - return is_running_under_wine(); -#endif -} - -std::shared_ptr IPlatformUtilities::get_device() -{ - FDevice new_device; - - if (is_linux()) + bool IPlatformUtilities::is_running_under_wine() { - new_device.platform = EPlatform::LINUX; + return WineUtilities::is_wine_present(); } - else + + bool IPlatformUtilities::is_linux() { - new_device.platform = EPlatform::WINDOWS; - } - - const std::shared_ptr battery_status = get_battery_status(); - - if (battery_status && !battery_status->has_battery) - { - new_device.device = EDevice::DESKTOP; - } - else - { - new_device.device = EDevice::LAPTOP; - } - - if (is_steam_deck(new_device)) { - new_device.device = EDevice::STEAM_DECK; - } - return std::make_shared(new_device); -} - -// Helper function to check if the device is a Steam Deck -bool IPlatformUtilities::is_steam_deck(const FDevice& device) { - - // Check if the device is already identified as a Steam Deck - if (device.device == EDevice::STEAM_DECK) { + #ifdef BUILD_LINUX return true; + #else + return is_running_under_wine(); + #endif } - // Check for Steam Deck by OS version - if (const std::shared_ptr version = get_os_version()) { - if (version->variant_id == "steamdeck" && version->name == "SteamOS") { + bool IPlatformUtilities::is_steam_deck() + { + const std::shared_ptr device = get_device(); + + return device && device->device == EDevice::STEAM_DECK; + } + + bool IPlatformUtilities::is_docked() + { + static constexpr uint8_t CHARGING_SCORE = 3; + static constexpr uint8_t EXTERNAL_MONITOR_SCORE = 3; + static constexpr uint8_t KEYBOARD_DETECTION_SCORE = 2; + static constexpr uint8_t CONTROLLER_DETECTION_SCORE = 2; + static constexpr uint8_t FINAL_TARGET_DETECTION_SCORE = 6; + + + const std::shared_ptr device = get_device(); + + if (!device) + { + std::wcout << "Error: failed to get device.\n"; + return false; + } + + if (device->device != EDevice::STEAM_DECK) + { + std::wcout << "Error: Dock detection is currently only supported on Steam Decks.\n"; + return false; + } + + uint8_t score = 0; + + if (is_charging()) + { + score += CHARGING_SCORE; + } + + if (get_is_external_monitor_connected()) + { + score += EXTERNAL_MONITOR_SCORE; + } + + if (get_mouse_keyboard_detected()) + { + score += KEYBOARD_DETECTION_SCORE; + } + + if (get_external_controller_detected()) + { + score += CONTROLLER_DETECTION_SCORE; + } + + return score >= FINAL_TARGET_DETECTION_SCORE; + } + + std::shared_ptr IPlatformUtilities::get_device() + { + FDevice new_device; + + if (is_linux()) + { + new_device.platform = EPlatform::LINUX; + } + else + { + new_device.platform = EPlatform::WINDOWS; + } + + const std::shared_ptr battery_status = get_battery_status(); + + if (battery_status && !battery_status->has_battery) + { + new_device.device = EDevice::DESKTOP; + } + else + { + new_device.device = EDevice::LAPTOP; + } + + if (is_steam_deck_detected(new_device)) { + new_device.device = EDevice::STEAM_DECK; + } + return std::make_shared(new_device); + } + + // Helper function to check if the device is a Steam Deck + bool IPlatformUtilities::is_steam_deck_detected(const FDevice& device) { + + // Check if the device is already identified as a Steam Deck + if (device.device == EDevice::STEAM_DECK) { return true; } - } else { - wprintf(L"OS version information not available.\n"); - } - // Set of known Steam Deck CPU model names - const std::set steam_deck_models = {"amd custom apu 0405" /*, other models... */}; - - // Check for Steam Deck by CPU model name - if (const std::shared_ptr cpu_info = get_cpu_info()) { - const FString cpu_model_lower = FString::to_lower(cpu_info->Model_Name); - for (const auto& model : steam_deck_models) { - if (cpu_model_lower == model) { - wprintf(L"Steam Deck detected by CPU model name.\n"); + // Check for Steam Deck by OS version + if (const std::shared_ptr version = get_os_version()) { + if (version->variant_id == "steamdeck" && version->name == "SteamOS") { return true; } + } else { + wprintf(L"OS version information not available.\n"); } - } else { - wprintf(L"CPU information not available.\n"); + + // Set of known Steam Deck CPU model names + const std::set steam_deck_models = {"amd custom apu 0405" /*, other models... */}; + + // Check for Steam Deck by CPU model name + if (const std::shared_ptr cpu_info = get_cpu_info()) { + const FString cpu_model_lower = FString::to_lower(cpu_info->Model_Name); + for (const auto& model : steam_deck_models) { + if (cpu_model_lower == model) { + wprintf(L"Steam Deck detected by CPU model name.\n"); + return true; + } + } + } else { + wprintf(L"CPU information not available.\n"); + } + + wprintf(L"Device is not a Steam Deck.\n"); + + return false; } - wprintf(L"Device is not a Steam Deck.\n"); - - return false; -} + bool IPlatformUtilities::is_connected_to_ac() + { + const std::shared_ptr battery = get_battery_status(); + return battery && battery->is_connected_to_ac; + } + + bool IPlatformUtilities::is_charging() + { + const std::shared_ptr battery = get_battery_status(); + return battery && battery->has_battery && battery->is_connected_to_ac; + } } diff --git a/HarmonyLinkLib/src/Platform/IPlatformUtilities.h b/HarmonyLinkLib/src/Platform/IPlatformUtilities.h index 933b5db..856e1a4 100644 --- a/HarmonyLinkLib/src/Platform/IPlatformUtilities.h +++ b/HarmonyLinkLib/src/Platform/IPlatformUtilities.h @@ -35,13 +35,22 @@ namespace HarmonyLinkLib // General OS-level functions virtual bool is_running_under_wine(); virtual bool is_linux(); + virtual bool is_steam_deck(); + virtual bool is_docked(); virtual std::shared_ptr get_device(); virtual std::shared_ptr get_cpu_info() = 0; virtual std::shared_ptr get_battery_status() = 0; virtual std::shared_ptr get_os_version() = 0; + virtual bool get_is_external_monitor_connected() = 0; + virtual bool get_mouse_keyboard_detected() = 0; + virtual bool get_external_controller_detected() = 0; + //virtual bool get_is_ethernet_connected() = 0; + //virtual bool get_is_external_input_detected() = 0; - bool is_steam_deck(const FDevice& device); + bool is_steam_deck_detected(const FDevice& device); + bool is_connected_to_ac(); + bool is_charging(); // Add more virtual functions for other OS interactions here }; diff --git a/HarmonyLinkLib/src/Platform/Unix/UnixUtilities.cpp b/HarmonyLinkLib/src/Platform/Unix/UnixUtilities.cpp index 98864a1..63fef64 100644 --- a/HarmonyLinkLib/src/Platform/Unix/UnixUtilities.cpp +++ b/HarmonyLinkLib/src/Platform/Unix/UnixUtilities.cpp @@ -18,21 +18,43 @@ namespace HarmonyLinkLib { bool UnixUtilities::is_running_under_wine() { - return false; + std::wcout << "This feature is not supported on unix systems yet.\n"; + return false; } std::shared_ptr UnixUtilities::get_cpu_info() { + std::wcout << "This feature is not supported on unix systems yet.\n"; return nullptr; } std::shared_ptr UnixUtilities::get_battery_status() { + std::wcout << "This feature is not supported on unix systems yet.\n"; return nullptr; } std::shared_ptr UnixUtilities::get_os_version() { + std::wcout << "This feature is not supported on unix systems yet.\n"; return nullptr; } + + bool UnixUtilities::get_is_external_monitor_connected() + { + std::wcout << "This feature is not supported on unix-based systems yet.\n"; + return false; + } + + bool UnixUtilities::get_mouse_keyboard_detected() + { + std::wcout << "This feature is not supported on unix-based systems yet.\n"; + return false; + } + + bool UnixUtilities::get_external_controller_detected() + { + std::wcout << "This feature is not supported on unix-based systems yet.\n"; + return false; + } } \ No newline at end of file diff --git a/HarmonyLinkLib/src/Platform/Unix/UnixUtilities.h b/HarmonyLinkLib/src/Platform/Unix/UnixUtilities.h index c627c0a..7e0fd5a 100644 --- a/HarmonyLinkLib/src/Platform/Unix/UnixUtilities.h +++ b/HarmonyLinkLib/src/Platform/Unix/UnixUtilities.h @@ -17,6 +17,8 @@ #include "Platform/IPlatformUtilities.h" namespace HarmonyLinkLib { + // This is more of a "catch all" for all unix-based systems + // that don't have their own implementations. class UnixUtilities : public IPlatformUtilities { public: bool is_running_under_wine() override; @@ -24,6 +26,12 @@ namespace HarmonyLinkLib std::shared_ptr get_cpu_info() override; std::shared_ptr get_battery_status() override; std::shared_ptr get_os_version() override; + + bool get_is_external_monitor_connected() override; + + bool get_mouse_keyboard_detected() override; + + bool get_external_controller_detected() override; // Implementation for other Unix/Linux-specific functions }; diff --git a/HarmonyLinkLib/src/Platform/Windows/WindowsUtilities.cpp b/HarmonyLinkLib/src/Platform/Windows/WindowsUtilities.cpp index 6185502..f363bd8 100644 --- a/HarmonyLinkLib/src/Platform/Windows/WindowsUtilities.cpp +++ b/HarmonyLinkLib/src/Platform/Windows/WindowsUtilities.cpp @@ -17,79 +17,140 @@ #include #include +#include +#pragma comment(lib, "XInput.lib") + #include "Platform/WineUtilities.h" namespace HarmonyLinkLib { std::shared_ptr WindowsUtilities::get_battery_status() -{ - if (is_linux()) { - return WineUtilities::get_battery_status(); - } - FBattery result; - - SYSTEM_POWER_STATUS status; - if (GetSystemPowerStatus(&status)) { - result.has_battery = status.BatteryFlag != 128; // 128 indicates no battery - result.is_connected_to_ac = status.ACLineStatus == 1; - result.battery_percent = result.has_battery ? status.BatteryLifePercent : 0; - } else { - std::wcout << "Failed to get power statistics.\n"; - } - - return std::make_shared(result); -} - -std::shared_ptr WindowsUtilities::get_cpu_info() -{ - if (is_linux()) - { - return WineUtilities::get_cpu_info(); - } - - return {}; -} - -std::shared_ptr WindowsUtilities::get_os_version() -{ - if (is_linux()) - { - return WineUtilities::get_linux_info(); - } - - OSVERSIONINFOEX os_version_info; - ZeroMemory(&os_version_info, sizeof(OSVERSIONINFOEX)); - os_version_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - - if (!GetVersionEx(reinterpret_cast(&os_version_info))) { - // Handle error if needed - return nullptr; - } - - FOSVerInfo os_version; - - HKEY h_key; - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &h_key) == ERROR_SUCCESS) - { - DWORD dw_size; - DWORD dw_type; - char sz_product_name[256]; - dw_size = sizeof(sz_product_name); - if (RegQueryValueEx(h_key, TEXT("ProductName"), nullptr, &dw_type, reinterpret_cast(sz_product_name), &dw_size) == ERROR_SUCCESS) + if (is_linux()) { - os_version.pretty_name = sz_product_name; + return WineUtilities::get_battery_status(); } - RegCloseKey(h_key); + FBattery result; + + SYSTEM_POWER_STATUS status; + if (GetSystemPowerStatus(&status)) { + result.has_battery = status.BatteryFlag != 128; // 128 indicates no battery + result.is_connected_to_ac = status.ACLineStatus == 1; + result.battery_percent = result.has_battery ? status.BatteryLifePercent : 0; + } else { + std::wcout << "Failed to get power statistics.\n"; + } + + return std::make_shared(result); } - std::stringstream version; - version << os_version_info.dwMajorVersion << "." << os_version_info.dwMinorVersion; - os_version.version_id = version.str(); + std::shared_ptr WindowsUtilities::get_cpu_info() + { + if (is_linux()) + { + return WineUtilities::get_cpu_info(); + } + + return {}; + } - os_version.name = "Windows"; - os_version.version = os_version_info.dwBuildNumber; // Build number as the version + std::shared_ptr WindowsUtilities::get_os_version() + { + if (is_linux()) + { + return WineUtilities::get_linux_info(); + } + + OSVERSIONINFOEX os_version_info; + ZeroMemory(&os_version_info, sizeof(OSVERSIONINFOEX)); + os_version_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - return std::make_shared(os_version); -} + if (!GetVersionEx(reinterpret_cast(&os_version_info))) { + // Handle error if needed + return nullptr; + } + + FOSVerInfo os_version; + + HKEY h_key; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &h_key) == ERROR_SUCCESS) + { + DWORD dw_size; + DWORD dw_type; + char sz_product_name[256]; + dw_size = sizeof(sz_product_name); + if (RegQueryValueEx(h_key, TEXT("ProductName"), nullptr, &dw_type, reinterpret_cast(sz_product_name), &dw_size) == ERROR_SUCCESS) + { + os_version.pretty_name = sz_product_name; + } + RegCloseKey(h_key); + } + + std::stringstream version; + version << os_version_info.dwMajorVersion << "." << os_version_info.dwMinorVersion; + os_version.version_id = version.str(); + + os_version.name = "Windows"; + os_version.version = os_version_info.dwBuildNumber; // Build number as the version + + return std::make_shared(os_version); + } + + bool WindowsUtilities::get_is_external_monitor_connected() + { + // SM_CMONITORS returns the count of all display monitors. + const int monitorCount = GetSystemMetrics(SM_CMONITORS); + + // More than one monitor implies an external monitor is connected. + return monitorCount > 1; + } + + bool WindowsUtilities::get_mouse_keyboard_detected() + { + UINT n_devices; + GetRawInputDeviceList(nullptr, &n_devices, sizeof(RAWINPUTDEVICELIST)); + + if (n_devices > 0) { + bool mouse_detected = false; + bool keyboard_detected = false; + + std::vector devices(n_devices); + GetRawInputDeviceList(devices.data(), &n_devices, sizeof(RAWINPUTDEVICELIST)); + + for (const auto& device : devices) { + switch (device.dwType) + { + case RIM_TYPEMOUSE: + mouse_detected = true; + break; + case RIM_TYPEKEYBOARD: + keyboard_detected = true; + break; + + default: + break; + } + } + return mouse_detected && keyboard_detected; + } + return false; + } + + bool WindowsUtilities::get_external_controller_detected() + { + static_assert(XUSER_MAX_COUNT <= UINT8_MAX, "XUSER_MAX_COUNT exceeds uint8_t size"); + + uint8_t connectedGamepads = 0; + + for (DWORD i = 0; i < XUSER_MAX_COUNT; ++i) { + XINPUT_STATE state; + ZeroMemory(&state, sizeof(XINPUT_STATE)); + + if (XInputGetState(i, &state) == ERROR_SUCCESS) { + connectedGamepads++; + } + } + + return connectedGamepads > 1; + } } diff --git a/HarmonyLinkLib/src/Platform/Windows/WindowsUtilities.h b/HarmonyLinkLib/src/Platform/Windows/WindowsUtilities.h index 583ca45..c51eacb 100644 --- a/HarmonyLinkLib/src/Platform/Windows/WindowsUtilities.h +++ b/HarmonyLinkLib/src/Platform/Windows/WindowsUtilities.h @@ -26,5 +26,11 @@ namespace HarmonyLinkLib std::shared_ptr get_cpu_info() override; std::shared_ptr get_os_version() override; + + bool get_is_external_monitor_connected() override; + + bool get_mouse_keyboard_detected() override; + + bool get_external_controller_detected() override; }; }