diff --git a/.gitignore b/.gitignore index 46fd768..0839667 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,8 @@ build/** !Images/* !.github/** !HarmonyLinkLib/** -!HarmonyLinkTest/** \ No newline at end of file +!HarmonyLinkTest/** + +# Blacklist specific build directories +linuxbuild/ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bf0723..02296e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,16 @@ +# Copyright (c) 2024 Jordon Brooks +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + cmake_minimum_required(VERSION 3.10) project(HarmonyLinkProject) @@ -14,6 +27,8 @@ else() message(STATUS "Building with unspecified build type") endif() +add_compile_definitions($<$:DEBUG_MODE>) + #set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Platform-specific definitions @@ -55,7 +70,7 @@ add_subdirectory(HarmonyLinkTest) #add_executable(Testing ${TEST_SOURCES}) # Set HarmonyLinkTest as the default startup project -set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT HarmonyLinkTest) +set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT HarmonyLinkTestShared) # Link Google Test and HarmonyLink library to the test executable #target_link_libraries(Testing gtest gtest_main gmock HarmonyLinkLib) diff --git a/HarmonyLinkLib/CMakeLists.txt b/HarmonyLinkLib/CMakeLists.txt index 8412f45..6727506 100644 --- a/HarmonyLinkLib/CMakeLists.txt +++ b/HarmonyLinkLib/CMakeLists.txt @@ -1,5 +1,36 @@ +# Copyright (c) 2024 Jordon Brooks +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + cmake_minimum_required(VERSION 3.10) -project(HarmonyLinkLib VERSION 2.0.0) +project(HarmonyLinkLib VERSION 2.1.0) + +include(FetchContent) + +FetchContent_Declare( + fmt + GIT_REPOSITORY https://github.com/fmtlib/fmt.git + GIT_TAG 10.2.1 # Specify the desired version of {fmt} +) + +FetchContent_MakeAvailable(fmt) +if(NOT fmt_POPULATED) + FetchContent_Populate(fmt) + # Add fmt but exclude it from the ALL target, reducing IDE clutter + add_subdirectory(${fmt_SOURCE_DIR} ${fmt_BINARY_DIR} EXCLUDE_FROM_ALL) +endif() + +set_target_properties(fmt PROPERTIES FOLDER External) +set_target_properties(fmt PROPERTIES POSITION_INDEPENDENT_CODE TRUE) # Find the current Git branch and the last commit timestamp find_package(Git QUIET) @@ -27,6 +58,16 @@ configure_file(include/Version.h.in Version.generated.h) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) +# Define metadata variables +set(FILE_DESCRIPTION "Enhances handheld gaming with intelligent hardware recognition, dynamic adaptability, and robust API access for Windows and Linux, including Steam Deck and Wine support.") +set(INTERNAL_NAME "HarmonyLinkLib") +set(ORIGINAL_FILENAME "HarmonyLinkLib.dll") +set(PRODUCT_NAME "HarmonyLinkLib") +set(COMMENTS "") + +# Configure version.rc file for shared library +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Resources/Version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc @ONLY) + # Explicitly list source files set(COMMON_SOURCES "src/Platform/IPlatformUtilities.cpp" @@ -34,6 +75,7 @@ set(COMMON_SOURCES "src/Version.cpp" "src/dllmain.cpp" "src/Platform/WineUtilities.cpp" + "src/Utilities.cpp" ) # Explicitly list include files @@ -43,16 +85,15 @@ set(COMMON_INCLUDES "include/Structs/FOSVerInfo.h" "include/Structs/FDevice.h" "include/Structs/FCPUInfo.h" - "include/Enums/EDevice.h" "include/Enums/EPlatform.h" - + "include/Enums/ESteamDeck.h" "include/FString.h" "include/HarmonyLinkLib.h" "include/Version.h" - "src/Platform/IPlatformUtilities.h" "src/Platform/WineUtilities.h" + "src/Utilities.h" ) set(WINDOWS_SOURCES @@ -88,42 +129,55 @@ if(WIN32) message(STATUS "Compiling for Windows...") list(APPEND LIB_SOURCES ${COMMON_SOURCES} ${WINDOWS_SOURCES}) list(APPEND LIB_INCLUDES ${COMMON_INCLUDES} ${WINDOWS_INCLUDES}) + list(APPEND SHARED_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/version.rc) elseif(UNIX) - message(STATUS "Compiling for Unix-based systems...") - if(APPLE) - message(STATUS "Compiling for Mac...") - list(APPEND LIB_SOURCES ${COMMON_SOURCES} ${MAC_SOURCES}) - list(APPEND LIB_INCLUDES ${COMMON_INCLUDES} ${MAC_INCLUDES}) - else() - message(STATUS "Compiling for Linux...") - list(APPEND LIB_SOURCES ${COMMON_SOURCES} ${LINUX_SOURCES}) - list(APPEND LIB_INCLUDES ${COMMON_INCLUDES} ${LINUX_INCLUDES}) - endif() + message(STATUS "Compiling for Unix-based systems...") + if(APPLE) + message(STATUS "Compiling for Mac...") + list(APPEND LIB_SOURCES ${COMMON_SOURCES} ${MAC_SOURCES}) + list(APPEND LIB_INCLUDES ${COMMON_INCLUDES} ${MAC_INCLUDES}) + else() + message(STATUS "Compiling for Linux...") + list(APPEND LIB_SOURCES ${COMMON_SOURCES} ${LINUX_SOURCES}) + list(APPEND LIB_INCLUDES ${COMMON_INCLUDES} ${LINUX_INCLUDES}) + endif() endif() -# Add library -add_library(HarmonyLinkLib SHARED ${LIB_SOURCES} ${LIB_INCLUDES}) - -target_include_directories(HarmonyLinkLib +# Create the shared library +add_library(HarmonyLinkLibShared SHARED ${LIB_SOURCES} ${SHARED_SOURCES}) +target_include_directories(HarmonyLinkLibShared PRIVATE "${PROJECT_SOURCE_DIR}/src" PUBLIC "${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/include" ) +target_compile_definitions(HarmonyLinkLibShared PRIVATE HARMONYLINKLIB_SHARED) -target_compile_definitions(HarmonyLinkLib PRIVATE HARMONYLINKLIB_EXPORTS) - -if(CMAKE_BUILD_TYPE STREQUAL "Debug") - target_compile_definitions(HarmonyLinkLib PRIVATE "DEBUG_MODE") -endif() +# Create the static library +add_library(HarmonyLinkLibStatic STATIC ${LIB_SOURCES}) +target_include_directories(HarmonyLinkLibStatic + PRIVATE + "${PROJECT_SOURCE_DIR}/src" + PUBLIC + "${PROJECT_BINARY_DIR}" + "${PROJECT_SOURCE_DIR}/include" +) +target_compile_definitions(HarmonyLinkLibStatic PRIVATE HARMONYLINKLIB_STATIC) # Set output directories for all build types foreach(TYPE IN ITEMS DEBUG RELEASE) string(TOUPPER ${TYPE} TYPE_UPPER) - set_target_properties(${PROJECT_NAME} PROPERTIES - RUNTIME_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/bin/${TYPE}/${PROJECT_NAME}" - LIBRARY_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/lib/${TYPE}/${PROJECT_NAME}" - ARCHIVE_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/archive/${TYPE}/${PROJECT_NAME}" + set_target_properties(HarmonyLinkLibShared PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/bin/${TYPE}/HarmonyLinkLib" + LIBRARY_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/lib/${TYPE}/HarmonyLinkLib" + ARCHIVE_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/archive/${TYPE}/HarmonyLinkLib" + ) + set_target_properties(HarmonyLinkLibStatic PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/archive/${TYPE}/HarmonyLinkLibStatic" ) endforeach() + +# Link fmt library to both shared and static libraries +target_link_libraries(HarmonyLinkLibShared PRIVATE fmt::fmt) +target_link_libraries(HarmonyLinkLibStatic PRIVATE fmt::fmt) diff --git a/HarmonyLinkLib/Resources/Version.rc.in b/HarmonyLinkLib/Resources/Version.rc.in new file mode 100644 index 0000000..1d443f8 --- /dev/null +++ b/HarmonyLinkLib/Resources/Version.rc.in @@ -0,0 +1,49 @@ +// Copyright (c) 2024 Jordon Brooks +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +VS_VERSION_INFO VERSIONINFO + FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 + PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 + FILEFLAGSMASK 0x3fL + FILEFLAGS 0x0L + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +{ + BLOCK "StringFileInfo" + { + BLOCK "040904b0" + { + VALUE "CompanyName", "N/A" + VALUE "FileDescription", "@FILE_DESCRIPTION@" + VALUE "FileVersion", "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.0" + VALUE "InternalName", "@INTERNAL_NAME@" + VALUE "OriginalFilename", "@ORIGINAL_FILENAME@" + VALUE "ProductName", "@PRODUCT_NAME@" + VALUE "ProductVersion", "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.0" + VALUE "Comments", "@COMMENTS@" + VALUE "LegalCopyright", "N/A" + VALUE "LegalTrademarks", "N/A" + VALUE "PrivateBuild", "N/A" + VALUE "SpecialBuild", "N/A" + } + } + BLOCK "VarFileInfo" + { + VALUE "Translation", 0x409, 1200 + } +} diff --git a/HarmonyLinkLib/include/Core.h b/HarmonyLinkLib/include/Core.h index ec6ef9d..94a32d2 100644 --- a/HarmonyLinkLib/include/Core.h +++ b/HarmonyLinkLib/include/Core.h @@ -16,10 +16,14 @@ // Use a preprocessor definition to switch between export and import declarations #ifdef _WIN32 - #ifdef HARMONYLINKLIB_EXPORTS - #define HARMONYLINKLIB_API __declspec(dllexport) + #ifdef HARMONYLINKLIB_STATIC + #define HARMONYLINKLIB_API #else - #define HARMONYLINKLIB_API __declspec(dllimport) + #ifdef HARMONYLINKLIB_SHARED + #define HARMONYLINKLIB_API __declspec(dllexport) + #else + #define HARMONYLINKLIB_API __declspec(dllimport) + #endif #endif #else #define HARMONYLINKLIB_API diff --git a/HarmonyLinkLib/include/Enums/EDevice.h b/HarmonyLinkLib/include/Enums/EDevice.h index 31f9bfe..f49c7ec 100644 --- a/HarmonyLinkLib/include/Enums/EDevice.h +++ b/HarmonyLinkLib/include/Enums/EDevice.h @@ -21,6 +21,7 @@ namespace HarmonyLinkLib { enum class EDevice : uint8_t { + UNKNOWN, DESKTOP, LAPTOP, HANDHELD, diff --git a/HarmonyLinkLib/include/Enums/EPlatform.h b/HarmonyLinkLib/include/Enums/EPlatform.h index 66697ee..ffa2be7 100644 --- a/HarmonyLinkLib/include/Enums/EPlatform.h +++ b/HarmonyLinkLib/include/Enums/EPlatform.h @@ -21,6 +21,7 @@ namespace HarmonyLinkLib { enum class EPlatform : uint8_t { + UNKNOWN, WINDOWS, LINUX, MAC, diff --git a/HarmonyLinkLib/include/Enums/ESteamDeck.h b/HarmonyLinkLib/include/Enums/ESteamDeck.h new file mode 100644 index 0000000..9fbad64 --- /dev/null +++ b/HarmonyLinkLib/include/Enums/ESteamDeck.h @@ -0,0 +1,29 @@ +// Copyright (c) 2024 Jordon Brooks +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +// Enum class for representing different types of devices +namespace HarmonyLinkLib +{ + enum class ESteamDeck : uint8_t + { + NONE, // Device is not a steam deck + UNKNOWN, // Device is a steam deck but model cannot be determined + LCD, + OLED, + }; +} diff --git a/HarmonyLinkLib/include/HarmonyLinkLib.h b/HarmonyLinkLib/include/HarmonyLinkLib.h index aaa1887..d6575a5 100644 --- a/HarmonyLinkLib/include/HarmonyLinkLib.h +++ b/HarmonyLinkLib/include/HarmonyLinkLib.h @@ -46,6 +46,10 @@ namespace HarmonyLinkLib { extern "C" HARMONYLINKLIB_API bool get_is_wine(); + 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/include/Structs/FDevice.h b/HarmonyLinkLib/include/Structs/FDevice.h index 50a1a5c..b574d6a 100644 --- a/HarmonyLinkLib/include/Structs/FDevice.h +++ b/HarmonyLinkLib/include/Structs/FDevice.h @@ -18,13 +18,15 @@ #include "Enums/EDevice.h" #include "Enums/EPlatform.h" +#include "Enums/ESteamDeck.h" namespace HarmonyLinkLib { // Struct to represent a specific device with both platform and device type struct FDevice : HarmonyLinkStruct { - EPlatform platform; - EDevice device; + EPlatform platform = EPlatform::UNKNOWN; + EDevice device = EDevice::UNKNOWN; + ESteamDeck steam_deck_model = ESteamDeck::NONE; }; } diff --git a/HarmonyLinkLib/include/Version.h.in b/HarmonyLinkLib/include/Version.h.in index 08881c5..0802c01 100644 --- a/HarmonyLinkLib/include/Version.h.in +++ b/HarmonyLinkLib/include/Version.h.in @@ -23,3 +23,7 @@ #define GIT_BRANCH_NAME "@GIT_BRANCH_NAME@" #define GIT_COMMIT_TIMESTAMP "@GIT_COMMIT_TIMESTAMP@" + + +#include +#include diff --git a/HarmonyLinkLib/src/HarmonyLinkLib.cpp b/HarmonyLinkLib/src/HarmonyLinkLib.cpp index fecfc97..91ccc2c 100644 --- a/HarmonyLinkLib/src/HarmonyLinkLib.cpp +++ b/HarmonyLinkLib/src/HarmonyLinkLib.cpp @@ -32,6 +32,28 @@ namespace HarmonyLinkLib return PlatformUtilities->is_running_under_wine(); } + bool get_is_linux() + { + if (!PlatformUtilities) + { + std::wcout << "Failed to get platform utilities!\n"; + return false; + } + + 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..735eeab 100644 --- a/HarmonyLinkLib/src/Platform/IPlatformUtilities.cpp +++ b/HarmonyLinkLib/src/Platform/IPlatformUtilities.cpp @@ -14,7 +14,11 @@ #include "IPlatformUtilities.h" +#include #include +#include + +#include "Utilities.h" #include "WineUtilities.h" #if BUILD_WINDOWS @@ -31,106 +35,211 @@ 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 - } - - 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()) - { - 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(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) { - return true; - } - - // 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; + 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 } - } else { - wprintf(L"OS version information not available.\n"); + + return INSTANCE; } - // Set of known Steam Deck CPU model names - const std::set steam_deck_models = {"amd custom apu 0405" /*, other models... */}; + bool IPlatformUtilities::is_running_under_wine() + { + return WineUtilities::is_wine_present(); + } - // 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; + bool IPlatformUtilities::is_linux() + { + #ifdef BUILD_LINUX + return true; + #else + return is_running_under_wine(); + #endif + } + + 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 = 4; + static constexpr uint8_t STEAM_DECK_RESOLUTION_SCORE = 3; + static constexpr uint8_t KEYBOARD_DETECTION_SCORE = 1; + static constexpr uint8_t MOUSE_DETECTION_SCORE = 2; + static constexpr uint8_t CONTROLLER_DETECTION_SCORE = 3; + static constexpr uint8_t FINAL_TARGET_DETECTION_SCORE = 9; + + + 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; + + Utilities::DebugPrint("Detected: ", false); + + if (is_charging()) + { + Utilities::DebugPrint("Charging, ", false); + score += CHARGING_SCORE; + } + + if (get_is_external_monitor_connected()) + { + Utilities::DebugPrint("External monitor, ", false); + score += EXTERNAL_MONITOR_SCORE; + } + + if (get_is_steam_deck_native_resolution()) + { + Utilities::DebugPrint("Non-native resolution, ", false); + score += STEAM_DECK_RESOLUTION_SCORE; + } + + if (get_keyboard_detected()) + { + Utilities::DebugPrint("keyboard ", false); + score += KEYBOARD_DETECTION_SCORE; + } + + if (get_mouse_detected()) + { + Utilities::DebugPrint("mouse, ", false); + score += MOUSE_DETECTION_SCORE; + } + + if (get_external_controller_detected()) + { + Utilities::DebugPrint("external controller, ", false); + score += CONTROLLER_DETECTION_SCORE; + } + + Utilities::DebugPrint(fmt::format("Score: {}/{}", score, FINAL_TARGET_DETECTION_SCORE).c_str()); + + 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; + } + + const ESteamDeck steam_deck_model = detect_steam_deck(new_device); + + if (steam_deck_model != ESteamDeck::NONE) { + new_device.device = EDevice::STEAM_DECK; + new_device.steam_deck_model = steam_deck_model; + } + return std::make_shared(new_device); + } + + // Helper function to check if the device is a Steam Deck + ESteamDeck IPlatformUtilities::detect_steam_deck(const FDevice& device) { + // Check if the device is already identified as a Steam Deck + if (device.device == EDevice::STEAM_DECK && device.steam_deck_model != ESteamDeck::NONE) { + return device.steam_deck_model; + } + + ESteamDeck steam_deck_model = ESteamDeck::NONE; + + // Retrieve and process CPU information + const std::shared_ptr cpu_info = get_cpu_info(); + if (!cpu_info) { + wprintf(L"CPU information not available.\n"); + } else { + // Convert the CPU model name to lower case once + FString cpu_model_lower = FString::to_lower(cpu_info->Model_Name); + + // Map of CPU models to their corresponding Steam Deck models + static const std::unordered_map model_map = { + {FString::to_lower("amd custom apu 0405"), ESteamDeck::LCD}, + {FString::to_lower("amd custom apu 0932"), ESteamDeck::OLED} + }; + + auto iterator = model_map.find(cpu_model_lower); + if (iterator != model_map.end()) { + steam_deck_model = iterator->second; + wprintf(L"Steam Deck detected by CPU model name: %hs.\n", cpu_model_lower.c_str()); } } - } else { - wprintf(L"CPU information not available.\n"); + + // Check for Steam Deck by OS version only if no model has been detected yet + if (steam_deck_model == ESteamDeck::NONE) + { + if (const std::shared_ptr version = get_os_version()) + { + if (version->variant_id == "steamdeck" && version->name == "SteamOS") + { + // Use UNKNOWN if OS matches but CPU model doesn't fit known profiles + steam_deck_model = ESteamDeck::UNKNOWN; + wprintf(L"Steam Deck OS detected but model is unknown.\n"); + } + } + else + { + wprintf(L"OS version information not available.\n"); + } + } + + return steam_deck_model; } - 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..e6f034f 100644 --- a/HarmonyLinkLib/src/Platform/IPlatformUtilities.h +++ b/HarmonyLinkLib/src/Platform/IPlatformUtilities.h @@ -14,6 +14,7 @@ #pragma once +#include "Enums/ESteamDeck.h" #include "Structs/FBattery.h" #include "Structs/FCPUInfo.h" #include "Structs/FDevice.h" @@ -35,13 +36,24 @@ 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_keyboard_detected() = 0; + virtual bool get_mouse_detected() = 0; + virtual bool get_external_controller_detected() = 0; + virtual bool get_is_steam_deck_native_resolution() = 0; + //virtual bool get_is_ethernet_connected() = 0; + //virtual bool get_is_external_input_detected() = 0; - bool is_steam_deck(const FDevice& device); + ESteamDeck detect_steam_deck(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..0d3b777 100644 --- a/HarmonyLinkLib/src/Platform/Unix/UnixUtilities.cpp +++ b/HarmonyLinkLib/src/Platform/Unix/UnixUtilities.cpp @@ -18,21 +18,55 @@ 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_keyboard_detected() + { + std::wcout << "This feature is not supported on unix-based systems yet.\n"; + return false; + } + + bool UnixUtilities::get_mouse_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; + } + + bool UnixUtilities::get_is_steam_deck_native_resolution() + { + 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..8a06546 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,16 @@ 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_keyboard_detected() override; + + bool get_mouse_detected() override; + + bool get_external_controller_detected() override; + + bool get_is_steam_deck_native_resolution() 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..dfd7aa8 100644 --- a/HarmonyLinkLib/src/Platform/Windows/WindowsUtilities.cpp +++ b/HarmonyLinkLib/src/Platform/Windows/WindowsUtilities.cpp @@ -17,79 +17,161 @@ #include #include +#include +#pragma comment(lib, "XInput.lib") + #include "Platform/WineUtilities.h" +#include 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_keyboard_detected() + { + UINT n_devices; + std::vector devices; + + GetRawInputDeviceList(devices.data(), &n_devices, sizeof(RAWINPUTDEVICELIST)); + + if (n_devices == 0) + { + return false; + } + + return std::any_of(devices.begin(), devices.end(), [](const RAWINPUTDEVICELIST& device) + { + return device.dwType == RIM_TYPEKEYBOARD; + }); + } + + bool WindowsUtilities::get_mouse_detected() + { + UINT n_devices; + std::vector devices; + + GetRawInputDeviceList(devices.data(), &n_devices, sizeof(RAWINPUTDEVICELIST)); + + if (n_devices == 0) + { + return false; + } + + return std::any_of(devices.begin(), devices.end(), [](const RAWINPUTDEVICELIST& device) + { + return device.dwType == RIM_TYPEMOUSE; + }); + } + + 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; + } + + bool WindowsUtilities::get_is_steam_deck_native_resolution() + { + DEVMODE devMode; + devMode.dmSize = sizeof(DEVMODE); + + // Get the current display settings for the primary monitor + if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devMode)) { + // Check if the resolution is higher than 800p (1280x800) + if (devMode.dmPelsWidth > 1280 || devMode.dmPelsHeight > 800) { + return true; + } + } + return false; + } } diff --git a/HarmonyLinkLib/src/Platform/Windows/WindowsUtilities.h b/HarmonyLinkLib/src/Platform/Windows/WindowsUtilities.h index 583ca45..809b204 100644 --- a/HarmonyLinkLib/src/Platform/Windows/WindowsUtilities.h +++ b/HarmonyLinkLib/src/Platform/Windows/WindowsUtilities.h @@ -26,5 +26,15 @@ 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_keyboard_detected() override; + + bool get_mouse_detected() override; + + bool get_external_controller_detected() override; + + bool get_is_steam_deck_native_resolution() override; }; } diff --git a/HarmonyLinkLib/src/Utilities.cpp b/HarmonyLinkLib/src/Utilities.cpp new file mode 100644 index 0000000..608437c --- /dev/null +++ b/HarmonyLinkLib/src/Utilities.cpp @@ -0,0 +1,53 @@ +// Copyright (c) 2024 Jordon Brooks +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Utilities.h" + +#include + +#include "FString.h" + +void HarmonyLinkLib::Utilities::DebugPrint(const FString& String, bool AddNewline) +{ +#ifdef DEBUG_MODE + std::wcout << String.c_str(); + + if (AddNewline) + { + std::wcout << L"\n"; + } +#endif +} + +void HarmonyLinkLib::Utilities::DebugPrint(const char* String, bool AddNewline) +{ +#ifdef DEBUG_MODE + std::wcout << std::wstring(String, String + std::strlen(String)); + + if (AddNewline) { + std::wcout << L"\n"; + } +#endif +} + +void HarmonyLinkLib::Utilities::DebugPrint(const wchar_t* String, bool AddNewline) +{ +#ifdef DEBUG_MODE + std::wcout << String; + + if (AddNewline) { + std::wcout << L"\n"; + } +#endif +} diff --git a/HarmonyLinkLib/src/Utilities.h b/HarmonyLinkLib/src/Utilities.h new file mode 100644 index 0000000..d6114c8 --- /dev/null +++ b/HarmonyLinkLib/src/Utilities.h @@ -0,0 +1,30 @@ +// Copyright (c) 2024 Jordon Brooks +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "HarmonyLinkLib.h" + +namespace HarmonyLinkLib +{ + class FString; + + class Utilities + { + public: + static void DebugPrint(const FString& String, bool AddNewline = true); + static void DebugPrint(const char* String, bool AddNewline = true); + static void DebugPrint(const wchar_t* String, bool AddNewline = true); + }; +} diff --git a/HarmonyLinkTest/CMakeLists.txt b/HarmonyLinkTest/CMakeLists.txt index 5616a24..949ea4f 100644 --- a/HarmonyLinkTest/CMakeLists.txt +++ b/HarmonyLinkTest/CMakeLists.txt @@ -1,3 +1,16 @@ +# Copyright (c) 2024 Jordon Brooks +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + cmake_minimum_required(VERSION 3.10) project(HarmonyLinkTest) @@ -9,23 +22,37 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) file(GLOB_RECURSE TEST_SOURCES "src/*.cpp") file(GLOB_RECURSE TEST_HEADERS "src/*.h" "src/*.hpp") -# Add executable -add_executable(HarmonyLinkTest ${TEST_SOURCES} ${TEST_HEADERS}) +# Add executable for static library +add_executable(HarmonyLinkTestStatic ${TEST_SOURCES} ${TEST_HEADERS}) +target_link_libraries(HarmonyLinkTestStatic PRIVATE HarmonyLinkLibStatic) +target_compile_definitions(HarmonyLinkTestStatic PRIVATE HARMONYLINKLIB_STATIC) -# Link the HarmonyLinkLib with HarmonyLinkTest -target_link_libraries(HarmonyLinkTest PRIVATE HarmonyLinkLib) +# Add executable for shared library +add_executable(HarmonyLinkTestShared ${TEST_SOURCES} ${TEST_HEADERS}) +target_link_libraries(HarmonyLinkTestShared PRIVATE HarmonyLinkLibShared) +target_compile_definitions(HarmonyLinkTestShared PRIVATE HARMONYLINKLIB_SHARED) # Set output directories for all build types foreach(TYPE IN ITEMS DEBUG RELEASE) string(TOUPPER ${TYPE} TYPE_UPPER) - set_target_properties(${PROJECT_NAME} PROPERTIES - RUNTIME_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/bin/${TYPE}/${PROJECT_NAME}" - LIBRARY_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/bin/${TYPE}/${PROJECT_NAME}" - ARCHIVE_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/bin/${TYPE}/${PROJECT_NAME}" + + # Static test executable properties + set_target_properties(HarmonyLinkTestStatic PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/bin/${TYPE}/HarmonyLinkTestStatic" + LIBRARY_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/lib/${TYPE}/HarmonyLinkTestStatic" + ARCHIVE_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/archive/${TYPE}/HarmonyLinkTestStatic" + ) + + # Shared test executable properties + set_target_properties(HarmonyLinkTestShared PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/bin/${TYPE}/HarmonyLinkTestShared" + LIBRARY_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/lib/${TYPE}/HarmonyLinkTestShared" + ARCHIVE_OUTPUT_DIRECTORY_${TYPE_UPPER} "${CMAKE_BINARY_DIR}/archive/${TYPE}/HarmonyLinkTestShared" ) endforeach() -add_custom_command(TARGET HarmonyLinkTest POST_BUILD +# Copy the DLL to the executable directory after building the shared test executable +add_custom_command(TARGET HarmonyLinkTestShared POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different - "$" - "$") + "$" + "$") diff --git a/HarmonyLinkTest/src/main.cpp b/HarmonyLinkTest/src/main.cpp index 69c0957..e9eaf87 100644 --- a/HarmonyLinkTest/src/main.cpp +++ b/HarmonyLinkTest/src/main.cpp @@ -128,6 +128,12 @@ int main() battery->free(); } + const bool is_docked = HarmonyLinkLib::get_is_docked(); + + const char* dock_check_string = is_docked ? "is" : "isn't"; + + wprintf(L"Device %hs docked\n", dock_check_string); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); }