diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index d62db50..6aa87e3 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -21,12 +21,18 @@ limitations under the License. #include #include #include +#include +#include + +#pragma comment(lib, "Shell32.lib") #endif #include "Settings.h" #include -AppLauncher::AppLauncher(std::function shutdown) : shutdown_(std::move(shutdown)) +AppLauncher::AppLauncher( + std::vector& process_hwnds, + std::function shutdown) : process_hwnds_(process_hwnds), shutdown_(std::move(shutdown)) { #ifdef _WIN32 UnPatchValveHooks(); @@ -38,8 +44,10 @@ void AppLauncher::launchApp(const std::wstring& path, const std::wstring& args) #ifdef _WIN32 if (Settings::launch.isUWP) { spdlog::info("LaunchApp is UWP, launching..."); + launched_uwp_path_ = path; launchUWPApp(path.data(), args); - } else { + } + else { spdlog::info("LaunchApp is Win32, launching..."); launchWin32App(path, args); } @@ -75,6 +83,7 @@ void AppLauncher::update() shutdown_(); } } + getProcessHwnds(); #endif process_check_clock_.restart(); } @@ -115,6 +124,37 @@ void AppLauncher::getChildPids(DWORD parent_pid) } CloseHandle(hp); } + +void AppLauncher::getProcessHwnds() +{ + process_hwnds_.clear(); + HWND curr_wnd = nullptr; + do { + curr_wnd = FindWindowEx(nullptr, curr_wnd, nullptr, nullptr); + DWORD check_pid = 0; + GetWindowThreadProcessId(curr_wnd, &check_pid); + if (check_pid == launched_pid_ || (std::ranges::find_if(child_pids_, [check_pid](auto pid) { + return pid == check_pid; + }) != child_pids_.end())) { + process_hwnds_.push_back(curr_wnd); + } + } while (curr_wnd != nullptr); + if (!launched_uwp_path_.empty()) { + // UWP and ApplicationFrameHost Bullshit. + // iterate all "ApplicationFrameWindow"; check the AppUserModelId (used for launching) and add on match. + do { + curr_wnd = FindWindowEx(nullptr, curr_wnd, L"ApplicationFrameWindow", nullptr); + IPropertyStore* propStore; + SHGetPropertyStoreForWindow(curr_wnd, IID_IPropertyStore, reinterpret_cast(&propStore)); + PROPVARIANT prop; + propStore->GetValue(PKEY_AppUserModel_ID, &prop); + if (prop.bstrVal != nullptr && std::wstring(prop.bstrVal) == launched_uwp_path_) { + process_hwnds_.push_back(curr_wnd); + } + } while (curr_wnd != nullptr); + } +} + #endif #ifdef _WIN32 @@ -203,10 +243,12 @@ void AppLauncher::launchUWPApp(const LPCWSTR package_full_name, const std::wstri result = sp_app_activation_manager->ActivateApplication(package_full_name, args.empty() ? nullptr : args.data(), AO_NONE, &launched_pid_); if (!SUCCEEDED(result)) { spdlog::error("ActivateApplication failed: Code {}", result); - } else { + } + else { spdlog::info(L"Launched UWP Package \"{}\"", package_full_name); } - } else { + } + else { spdlog::error("CoCreateInstance failed: Code {}", result); } CoUninitialize(); diff --git a/GlosSITarget/AppLauncher.h b/GlosSITarget/AppLauncher.h index 8e1298a..dca8df5 100644 --- a/GlosSITarget/AppLauncher.h +++ b/GlosSITarget/AppLauncher.h @@ -26,20 +26,25 @@ limitations under the License. class AppLauncher { public: - explicit AppLauncher(std::function shutdown = [](){}); + explicit AppLauncher( + std::vector& process_hwnds, + std::function shutdown = []() {}); void launchApp(const std::wstring& path, const std::wstring& args = L""); void update(); void close(); -private: - + private: std::function shutdown_; sf::Clock process_check_clock_; #ifdef _WIN32 static bool IsProcessRunning(DWORD pid); void getChildPids(DWORD parent_pid); + void getProcessHwnds(); + std::vector& process_hwnds_; + + std::wstring launched_uwp_path_; // Valve also hooks "CreateProcess" // Unpatch that so that launched programs don't also get hooked... diff --git a/GlosSITarget/Settings.h b/GlosSITarget/Settings.h index 6998540..7f34899 100644 --- a/GlosSITarget/Settings.h +++ b/GlosSITarget/Settings.h @@ -46,6 +46,7 @@ inline struct Window { inline struct Controller { int maxControllers = 4; + bool allowDesktopConfig = true; } controller; inline bool checkIsUwp(const std::wstring& launch_path) @@ -132,6 +133,7 @@ inline void Parse(std::string arg1) if (auto controllerConf = json["controller"]; controllerConf.is_object()) { safeParseValue(controllerConf, "maxControllers", controller.maxControllers); + safeParseValue(controllerConf, "allowDesktopConfig", controller.allowDesktopConfig); } json_file.close(); diff --git a/GlosSITarget/SteamTarget.cpp b/GlosSITarget/SteamTarget.cpp index b6ecf72..4ab0014 100644 --- a/GlosSITarget/SteamTarget.cpp +++ b/GlosSITarget/SteamTarget.cpp @@ -41,7 +41,7 @@ SteamTarget::SteamTarget(int argc, char* argv[]) }), overlay_(window_.getOverlay()), detector_([this](bool overlay_open) { onOverlayChanged(overlay_open); }), - launcher_([this] { + launcher_(force_config_hwnds_, [this] { delayed_shutdown_ = true; delay_shutdown_clock_.restart(); }) @@ -363,6 +363,13 @@ bool SteamTarget::getXBCRebindingEnabled() return xbsup == "1"; } +/* + * The "magic" that keeps a controller-config forced (without hooking into Steam) + * + * Hook into own process and detour "GetForegroundWindow" + * Deatour function always returns HWND of own application window + * Steam now doesn't detect application changes and keeps the game-specific input config without reverting to desktop-conf + */ void SteamTarget::keepControllerConfig(bool keep) { #ifdef _WIN32 @@ -386,7 +393,28 @@ void SteamTarget::keepControllerConfig(bool keep) #ifdef _WIN32 HWND SteamTarget::keepFgWindowHookFn() { - return target_window_handle_; + if (!Settings::controller.allowDesktopConfig) { + return target_window_handle_; + } + subhook::ScopedHookRemove remove(&getFgWinHook); + HWND real_fg_win = GetForegroundWindow(); + if (real_fg_win == nullptr) { + return target_window_handle_; + } + if (std::ranges::find_if(force_config_hwnds_, [real_fg_win](auto hwnd) { + return hwnd == real_fg_win; + }) != force_config_hwnds_.end()) { + if (last_real_hwnd_ != real_fg_win) { + last_real_hwnd_ = real_fg_win; + spdlog::debug("Active window (\"{:#x}\") in launched process window list, forcing specific config", reinterpret_cast(real_fg_win)); + } + return target_window_handle_; + } + if (last_real_hwnd_ != real_fg_win) { + last_real_hwnd_ = real_fg_win; + spdlog::debug("Active window (\"{:#x}\") not in launched process window list, allowing desktop-config", reinterpret_cast(real_fg_win)); + } + return real_fg_win; } #endif diff --git a/GlosSITarget/SteamTarget.h b/GlosSITarget/SteamTarget.h index 13a1c62..65e47db 100644 --- a/GlosSITarget/SteamTarget.h +++ b/GlosSITarget/SteamTarget.h @@ -58,6 +58,8 @@ class SteamTarget { #ifdef _WIN32 static HWND keepFgWindowHookFn(); static inline subhook::Hook getFgWinHook; + static inline std::vector force_config_hwnds_ = {}; + static inline HWND last_real_hwnd_ = nullptr; #endif /* @@ -89,7 +91,6 @@ class SteamTarget { bool delayed_shutdown_ = false; sf::Clock delay_shutdown_clock_; - static constexpr std::wstring_view user_data_path_ = L"/userdata/"; static constexpr std::wstring_view config_file_name_ = L"/config/localconfig.vdf"; static constexpr std::string_view overlay_hotkey_name_ = "InGameOverlayShortcutKey ";