From 9e08309a1a10945329e5eafa5dd231a8e6eafc45 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Fri, 7 Oct 2022 12:37:04 +0200 Subject: [PATCH 01/44] SteamTarget: always enable UWP overlay --- GlosSITarget/SteamTarget.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/GlosSITarget/SteamTarget.cpp b/GlosSITarget/SteamTarget.cpp index 332ffc3..6fff749 100644 --- a/GlosSITarget/SteamTarget.cpp +++ b/GlosSITarget/SteamTarget.cpp @@ -48,12 +48,14 @@ SteamTarget::SteamTarget() { target_window_handle_ = window_.getSystemHandle(); #ifdef _WIN32 - if (Settings::launch.isUWP) { - UWPOverlayEnabler::EnableUwpOverlay(); - } - else { - UWPOverlayEnabler::AddUwpOverlayOvWidget(); - } + //if (Settings::launch.isUWP) { + // UWPOverlayEnabler::EnableUwpOverlay(); + //} + //else { + // UWPOverlayEnabler::AddUwpOverlayOvWidget(); + //} + UWPOverlayEnabler::EnableUwpOverlay(); + #endif } From f9c993dc500c69fbaa376c2bcf51db54442ee50f Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Fri, 7 Oct 2022 13:08:55 +0200 Subject: [PATCH 02/44] GlosSITarget: Add cli-switches: -disableuwpoverlay -disablewatchdog --- GlosSITarget/Settings.h | 38 +++++++++++++++++++++++++++--------- GlosSITarget/SteamTarget.cpp | 25 ++++++++++++------------ GlosSITarget/main.cpp | 5 +++-- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/GlosSITarget/Settings.h b/GlosSITarget/Settings.h index eaef719..374b93a 100644 --- a/GlosSITarget/Settings.h +++ b/GlosSITarget/Settings.h @@ -59,6 +59,11 @@ inline struct Controller { bool emulateDS4 = false; } controller; +inline struct Cli { + bool no_uwp_overlay = false; + bool disable_watchdog = false; +} cli; + inline bool extendedLogging = false; inline std::filesystem::path settings_path_ = ""; @@ -114,14 +119,29 @@ inline void checkWinVer() } #endif -inline void Parse(std::wstring arg1) +inline void Parse(const std::vector& args) { - const auto config_specified = !std::views::filter(arg1, [](const auto& ch) { - return ch != ' '; - }).empty(); - if (config_specified) { - if (!arg1.ends_with(L".json")) { - arg1 += L".json"; + std::wstring configName; + for (const auto& arg : args) { + if (arg.empty()) { + continue; + } + if (arg[0] != L'-') { + configName = std::wstring(arg.begin(), arg.end()); + } + if (arg[0] == L'-') { + if (arg == L"-disableuwpoverlay") { + cli.no_uwp_overlay = true; + } + if (arg == L"-disablewatchdog") { + cli.disable_watchdog = true; + } + } + } + + if (!configName.empty()) { + if (!configName.ends_with(L".json")) { + configName += L".json"; } } wchar_t* localAppDataFolder; @@ -135,9 +155,9 @@ inline void Parse(std::wstring arg1) path /= "Roaming"; path /= "GlosSI"; - if (config_specified) { + if (!configName.empty()) { path /= "Targets"; - path /= arg1; + path /= configName; } else { spdlog::info("No config file specified, using default"); diff --git a/GlosSITarget/SteamTarget.cpp b/GlosSITarget/SteamTarget.cpp index 6fff749..0192256 100644 --- a/GlosSITarget/SteamTarget.cpp +++ b/GlosSITarget/SteamTarget.cpp @@ -48,14 +48,11 @@ SteamTarget::SteamTarget() { target_window_handle_ = window_.getSystemHandle(); #ifdef _WIN32 - //if (Settings::launch.isUWP) { - // UWPOverlayEnabler::EnableUwpOverlay(); - //} - //else { - // UWPOverlayEnabler::AddUwpOverlayOvWidget(); - //} - UWPOverlayEnabler::EnableUwpOverlay(); - + if (Settings::cli.no_uwp_overlay) { + UWPOverlayEnabler::AddUwpOverlayOvWidget(); + } else { + UWPOverlayEnabler::EnableUwpOverlay(); + } #endif } @@ -78,12 +75,14 @@ Application will not function!"); steam_overlay_present_ = true; #ifdef WIN32 - wchar_t buff[MAX_PATH]; - GetModuleFileName(GetModuleHandle(NULL), buff, MAX_PATH); - std::wstring watchDogPath(buff); - watchDogPath = watchDogPath.substr(0, 1 + watchDogPath.find_last_of(L'\\')) + L"GlosSIWatchdog.dll"; + if (!Settings::cli.disable_watchdog) { + wchar_t buff[MAX_PATH]; + GetModuleFileName(GetModuleHandle(NULL), buff, MAX_PATH); + std::wstring watchDogPath(buff); + watchDogPath = watchDogPath.substr(0, 1 + watchDogPath.find_last_of(L'\\')) + L"GlosSIWatchdog.dll"; - DllInjector::injectDllInto(watchDogPath, L"explorer.exe"); + DllInjector::injectDllInto(watchDogPath, L"explorer.exe"); + } #endif } getXBCRebindingEnabled(); diff --git a/GlosSITarget/main.cpp b/GlosSITarget/main.cpp index 6ce5a1e..620eb4f 100644 --- a/GlosSITarget/main.cpp +++ b/GlosSITarget/main.cpp @@ -164,10 +164,11 @@ int main(int argc, char* argv[]) #ifdef _WIN32 int numArgs; LPWSTR* args = CommandLineToArgvW(GetCommandLine(), &numArgs); - std::wstring argsv = L""; + std::vector argsv; + argsv.reserve(numArgs); if (numArgs > 1) { for (int i = 1; i < numArgs; i++) - argsv += i == 1 ? args[i] : std::wstring(L" ") + args[i]; + argsv.emplace_back(args[i]); } Settings::Parse(argsv); Settings::checkWinVer(); From ab575c1e290174c518a35dce1519a81d38ba80ce Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Sat, 8 Oct 2022 20:31:42 +0200 Subject: [PATCH 03/44] Only allow single instance of GlosSITarget --- GlosSITarget/main.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/GlosSITarget/main.cpp b/GlosSITarget/main.cpp index 620eb4f..c2ae528 100644 --- a/GlosSITarget/main.cpp +++ b/GlosSITarget/main.cpp @@ -162,6 +162,13 @@ int main(int argc, char* argv[]) auto exit = 1; try { #ifdef _WIN32 + + auto existingwindow = FindWindowA(nullptr, "GlosSITarget"); + if (existingwindow) { + spdlog::error("GlosSITarget is already running!"); + return 1; + } + int numArgs; LPWSTR* args = CommandLineToArgvW(GetCommandLine(), &numArgs); std::vector argsv; From c9c6a324327bad1993c98c146359b45e18e34d3b Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 00:28:28 +0200 Subject: [PATCH 04/44] GlosSITarget: Add HttpServer: Get launched pids --- .gitmodules | 3 + GlosSITarget/AppLauncher.cpp | 21 ++++++- GlosSITarget/AppLauncher.h | 5 ++ GlosSITarget/GlosSITarget.vcxproj | 6 +- GlosSITarget/GlosSITarget.vcxproj.filters | 6 ++ GlosSITarget/HidHide.cpp | 8 ++- GlosSITarget/HidHide.h | 4 ++ GlosSITarget/HttpServer.cpp | 47 ++++++++++++++++ GlosSITarget/HttpServer.h | 37 ++++++++++++ GlosSITarget/InputRedirector.h | 1 + GlosSITarget/Resource.rc | 68 +++++++++++++++++++++-- GlosSITarget/SteamTarget.cpp | 12 +++- GlosSITarget/SteamTarget.h | 4 ++ GlosSITarget/main.cpp | 3 + deps/cpp-httplib | 1 + 15 files changed, 214 insertions(+), 12 deletions(-) create mode 100644 GlosSITarget/HttpServer.cpp create mode 100644 GlosSITarget/HttpServer.h create mode 160000 deps/cpp-httplib diff --git a/.gitmodules b/.gitmodules index e54a7a8..d488b86 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,3 +34,6 @@ [submodule "deps/Shortcuts_VDF"] path = deps/Shortcuts_VDF url = git@github.com:Alia5/Shortcuts_VDF.git +[submodule "deps/cpp-httplib"] + path = deps/cpp-httplib + url = git@github.com:yhirose/cpp-httplib.git diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index 92ebe32..37ffdc1 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -23,6 +23,8 @@ limitations under the License. #include #include #include +#include + #pragma comment(lib, "Shell32.lib") #endif @@ -67,6 +69,7 @@ void AppLauncher::launchApp(const std::wstring& path, const std::wstring& args) void AppLauncher::update() { if (process_check_clock_.getElapsedTime().asMilliseconds() > 250) { + pid_mutex_.lock(); #ifdef _WIN32 if (!pids_.empty() && pids_[0] > 0) { if (Settings::launch.waitForChildProcs) { @@ -98,6 +101,7 @@ void AppLauncher::update() } getProcessHwnds(); #endif + pid_mutex_.unlock(); process_check_clock_.restart(); } } @@ -112,6 +116,16 @@ void AppLauncher::close() #endif } +std::vector AppLauncher::launchedPids() +{ + pid_mutex_.lock(); + std::vector res = pids_; + std::ranges::copy(pids_.begin(), pids_.end(), + std::back_inserter(res)); + pid_mutex_.unlock(); + return res; +} + #ifdef _WIN32 bool AppLauncher::IsProcessRunning(DWORD pid) { @@ -213,7 +227,9 @@ void AppLauncher::launchWin32App(const std::wstring& path, const std::wstring& a // spdlog::info(L"Started Program: \"{}\" in directory: \"{}\"", native_seps_path, launch_dir); spdlog::info(L"Started Program: \"{}\"; PID: {}", native_seps_path, process_info.dwProcessId); if (!watchdog) { + pid_mutex_.lock(); pids_.push_back(process_info.dwProcessId); + pid_mutex_.unlock(); } } else { @@ -243,7 +259,7 @@ void AppLauncher::launchUWPApp(const LPCWSTR package_full_name, const std::wstri if (!SUCCEEDED(result)) { spdlog::warn("CoAllowSetForegroundWindow failed. Code: {}", result); } - + pid_mutex_.lock(); pids_.push_back(0); // Launch the app result = sp_app_activation_manager->ActivateApplication( @@ -258,6 +274,7 @@ void AppLauncher::launchUWPApp(const LPCWSTR package_full_name, const std::wstri else { spdlog::info(L"Launched UWP Package \"{}\"; PID: {}", package_full_name, pids_[0]); } + pid_mutex_.unlock(); } else { spdlog::error("CoCreateInstance failed: Code {}", result); @@ -298,8 +315,10 @@ void AppLauncher::launchURL(const std::wstring& url, const std::wstring& args, c if (execute_info.hProcess != nullptr) { if (const auto pid = GetProcessId(execute_info.hProcess); pid > 0) { + pid_mutex_.lock(); pids_.push_back(pid); spdlog::trace("Launched URL; PID: {}", pid); + pid_mutex_.unlock(); return; } } diff --git a/GlosSITarget/AppLauncher.h b/GlosSITarget/AppLauncher.h index 231a0bf..7203028 100644 --- a/GlosSITarget/AppLauncher.h +++ b/GlosSITarget/AppLauncher.h @@ -17,9 +17,11 @@ limitations under the License. #ifdef _WIN32 #define NOMINMAX +#define WIN32_LEAN_AND_MEAN #include #endif #include +#include #include #include #include @@ -33,10 +35,13 @@ class AppLauncher { void launchApp(const std::wstring& path, const std::wstring& args = L""); void update(); void close(); + + std::vector launchedPids(); private: std::function shutdown_; sf::Clock process_check_clock_; + std::mutex pid_mutex_; #ifdef _WIN32 static bool IsProcessRunning(DWORD pid); diff --git a/GlosSITarget/GlosSITarget.vcxproj b/GlosSITarget/GlosSITarget.vcxproj index 1dd6096..4970649 100644 --- a/GlosSITarget/GlosSITarget.vcxproj +++ b/GlosSITarget/GlosSITarget.vcxproj @@ -80,7 +80,7 @@ true - ..\deps\SFML\include;..\deps\WinReg;..\deps\spdlog\include;..\deps\ValveFileVDF;..\deps\subhook;..\deps\ViGEmClient\include;..\deps\imgui;..\deps\imgui-sfml;..\deps\json\include;..\deps\traypp\tray\include;$(ExternalIncludePath) + ..\deps\SFML\include;..\deps\WinReg;..\deps\spdlog\include;..\deps\ValveFileVDF;..\deps\subhook;..\deps\ViGEmClient\include;..\deps\imgui;..\deps\imgui-sfml;..\deps\json\include;..\deps\traypp\tray\include;..\deps\cpp-httplib;$(ExternalIncludePath) ..\deps\SFML\out\Debug\lib\Debug;..\deps\ViGEmClient\lib\debug\x64;$(LibraryPath) false true @@ -88,7 +88,7 @@ false - ..\deps\SFML\include;..\deps\WinReg;..\deps\spdlog\include;..\deps\ValveFileVDF;..\deps\subhook;..\deps\ViGEmClient\include;..\deps\imgui;..\deps\imgui-sfml;..\deps\json\include;..\deps\traypp\tray\include;$(ExternalIncludePath) + ..\deps\SFML\include;..\deps\WinReg;..\deps\spdlog\include;..\deps\ValveFileVDF;..\deps\subhook;..\deps\ViGEmClient\include;..\deps\imgui;..\deps\imgui-sfml;..\deps\json\include;..\deps\traypp\tray\include;..\deps\cpp-httplib;$(ExternalIncludePath) ..\deps\SFML\out\Release\lib\RelWithDebInfo;..\deps\ViGEmClient\lib\release\x64;$(LibraryPath) ResourceCompile true @@ -188,6 +188,7 @@ + @@ -204,6 +205,7 @@ + diff --git a/GlosSITarget/GlosSITarget.vcxproj.filters b/GlosSITarget/GlosSITarget.vcxproj.filters index df4cc29..b2bcd12 100644 --- a/GlosSITarget/GlosSITarget.vcxproj.filters +++ b/GlosSITarget/GlosSITarget.vcxproj.filters @@ -117,6 +117,9 @@ Source Files + + Source Files + @@ -185,6 +188,9 @@ Header Files + + Header Files + diff --git a/GlosSITarget/HidHide.cpp b/GlosSITarget/HidHide.cpp index 82798d4..6667828 100644 --- a/GlosSITarget/HidHide.cpp +++ b/GlosSITarget/HidHide.cpp @@ -24,11 +24,10 @@ limitations under the License. #include #include +#include // Device configuration related #include -#include -// #ifndef WATCHDOG #include "Overlay.h" #endif @@ -38,9 +37,14 @@ limitations under the License. #include #include #include +#include +#include + #include "UnhookUtil.h" +#pragma comment(lib, "Setupapi.lib") + // {D61CA365-5AF4-4486-998B-9DB4734C6CA3}add the XUSB class GUID as it is missing in the public interfaces DEFINE_GUID(GUID_DEVCLASS_XUSBCLASS, 0xD61CA365, 0x5AF4, 0x4486, 0x99, 0x8B, 0x9D, 0xB4, 0x73, 0x4C, 0x6C, 0xA3); // {EC87F1E3-C13B-4100-B5F7-8B84D54260CB} add the XUSB interface class GUID as it is missing in the public interfaces diff --git a/GlosSITarget/HidHide.h b/GlosSITarget/HidHide.h index 72095b0..4fa1b27 100644 --- a/GlosSITarget/HidHide.h +++ b/GlosSITarget/HidHide.h @@ -15,7 +15,10 @@ limitations under the License. */ #pragma once #define NOMINMAX +#define WIN32_LEAN_AND_MEAN #include +#include +#include #include @@ -25,6 +28,7 @@ limitations under the License. #include #include #include + #ifndef WATCHDOG #include #endif diff --git a/GlosSITarget/HttpServer.cpp b/GlosSITarget/HttpServer.cpp new file mode 100644 index 0000000..3d52343 --- /dev/null +++ b/GlosSITarget/HttpServer.cpp @@ -0,0 +1,47 @@ +/* +Copyright 2021-2022 Peter Repukat - FlatspotSoftware + +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 "HttpServer.h" + +#include +#include + +#include "AppLauncher.h" + +HttpServer::HttpServer(AppLauncher& app_launcher) : app_launcher_(app_launcher) +{} + +void HttpServer::run() +{ + server_.Get("/launched-pids", [this](const httplib::Request& req, httplib::Response& res) { + const nlohmann::json j = app_launcher_.launchedPids(); + res.set_content(j.dump(), "text/json"); + }); + + server_thread_ = std::thread([this]() { + if (!server_.listen("0.0.0.0", port_)) { + spdlog::error("Couldn't start http-server"); + return; + } + spdlog::debug("Started http-server on port"); + }); +} + +void HttpServer::stop() +{ + server_.stop(); + if (server_thread_.joinable()) + server_thread_.join(); +} diff --git a/GlosSITarget/HttpServer.h b/GlosSITarget/HttpServer.h new file mode 100644 index 0000000..b23bff5 --- /dev/null +++ b/GlosSITarget/HttpServer.h @@ -0,0 +1,37 @@ +/* +Copyright 2021-2022 Peter Repukat - FlatspotSoftware + +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 + +#include + +class AppLauncher; + +class HttpServer { + + public: + explicit HttpServer(AppLauncher& app_launcher); + + void run(); + void stop(); + + private: + httplib::Server server_; + std::thread server_thread_; + uint16_t port_ = 8756; + + AppLauncher& app_launcher_; +}; \ No newline at end of file diff --git a/GlosSITarget/InputRedirector.h b/GlosSITarget/InputRedirector.h index 875b155..0e74653 100644 --- a/GlosSITarget/InputRedirector.h +++ b/GlosSITarget/InputRedirector.h @@ -17,6 +17,7 @@ limitations under the License. #include #ifdef _WIN32 #define NOMINMAX +#define WIN32_LEAN_AND_MEAN #include #include #include diff --git a/GlosSITarget/Resource.rc b/GlosSITarget/Resource.rc index 3b1b5a1..ccb5d81 100644 --- a/GlosSITarget/Resource.rc +++ b/GlosSITarget/Resource.rc @@ -51,8 +51,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,0,9,1040000008386 - PRODUCTVERSION 0,0,9,1040000008386 + FILEVERSION 0,1,0,203000008020 + PRODUCTVERSION 0,1,0,203000008020 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -69,12 +69,12 @@ BEGIN BEGIN VALUE "CompanyName", "Peter Repukat - FlatspotSoftware" VALUE "FileDescription", "GlosSI - SteamTarget" - VALUE "FileVersion", "0.0.9.1-40-gdae8386" + VALUE "FileVersion", "0.1.0.2-3-gcea802b" VALUE "InternalName", "GlosSITarget" VALUE "LegalCopyright", "Copyright (C) 2021-2022 Peter Repukat - FlatspotSoftware" VALUE "OriginalFilename", "GlosSITarget.exe" VALUE "ProductName", "GlosSI" - VALUE "ProductVersion", "0.0.9.1-40-gdae8386" + VALUE "ProductVersion", "0.1.0.2-3-gcea802b" END END BLOCK "VarFileInfo" @@ -160,6 +160,66 @@ IDI_ICON1 ICON "..\\GlosSI_Icon.ico" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GlosSITarget/SteamTarget.cpp b/GlosSITarget/SteamTarget.cpp index 0192256..4c11e07 100644 --- a/GlosSITarget/SteamTarget.cpp +++ b/GlosSITarget/SteamTarget.cpp @@ -30,6 +30,7 @@ limitations under the License. #include #endif + SteamTarget::SteamTarget() : window_( [this] { run_ = false; }, @@ -44,13 +45,15 @@ SteamTarget::SteamTarget() launcher_(force_config_hwnds_, [this] { delayed_shutdown_ = true; delay_shutdown_clock_.restart(); - }) + }), + server_(launcher_) { target_window_handle_ = window_.getSystemHandle(); #ifdef _WIN32 if (Settings::cli.no_uwp_overlay) { UWPOverlayEnabler::AddUwpOverlayOvWidget(); - } else { + } + else { UWPOverlayEnabler::EnableUwpOverlay(); } #endif @@ -81,7 +84,7 @@ Application will not function!"); std::wstring watchDogPath(buff); watchDogPath = watchDogPath.substr(0, 1 + watchDogPath.find_last_of(L'\\')) + L"GlosSIWatchdog.dll"; - DllInjector::injectDllInto(watchDogPath, L"explorer.exe"); + DllInjector::injectDllInto(watchDogPath, L"explorer.exe"); } #endif } @@ -124,6 +127,8 @@ Application will not function!"); run_ = false; }}); + server_.run(); + while (run_) { detector_.update(); overlayHotkeyWorkaround(); @@ -140,6 +145,7 @@ Application will not function!"); } tray.exit(); + server_.stop(); #ifdef _WIN32 input_redirector_.stop(); hidhide_.disableHidHide(); diff --git a/GlosSITarget/SteamTarget.h b/GlosSITarget/SteamTarget.h index ccff866..ff39ff1 100644 --- a/GlosSITarget/SteamTarget.h +++ b/GlosSITarget/SteamTarget.h @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ #pragma once +#define WIN32_LEAN_AND_MEAN #include "SteamOverlayDetector.h" @@ -27,6 +28,8 @@ limitations under the License. #include "AppLauncher.h" #include "Overlay.h" +#include "HttpServer.h" + #include @@ -81,6 +84,7 @@ class SteamTarget { std::weak_ptr overlay_; SteamOverlayDetector detector_; AppLauncher launcher_; + HttpServer server_; WindowHandle last_foreground_window_ = nullptr; static inline WindowHandle target_window_handle_ = nullptr; diff --git a/GlosSITarget/main.cpp b/GlosSITarget/main.cpp index c2ae528..65bbc06 100644 --- a/GlosSITarget/main.cpp +++ b/GlosSITarget/main.cpp @@ -14,11 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ #ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #include #include +#include #endif +#include #include #include diff --git a/deps/cpp-httplib b/deps/cpp-httplib new file mode 160000 index 0000000..cae5a8b --- /dev/null +++ b/deps/cpp-httplib @@ -0,0 +1 @@ +Subproject commit cae5a8be1c9ef8399f45b89f6fc55afb122808cb From e879d0ba5c1573f79b0ca8d5d0a4cf4bb9e5ec13 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 00:34:07 +0200 Subject: [PATCH 05/44] GlosSITarget: Fix logging port --- GlosSITarget/HttpServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GlosSITarget/HttpServer.cpp b/GlosSITarget/HttpServer.cpp index 3d52343..f9c3424 100644 --- a/GlosSITarget/HttpServer.cpp +++ b/GlosSITarget/HttpServer.cpp @@ -35,7 +35,7 @@ void HttpServer::run() spdlog::error("Couldn't start http-server"); return; } - spdlog::debug("Started http-server on port"); + spdlog::debug("Started http-server on port {}", static_cast(port_)); }); } From febebafc250aff8e967d5ca689f813d1676be6bc Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 00:34:34 +0200 Subject: [PATCH 06/44] GlosSITarget: Fix httpserver pids entries doubled --- GlosSITarget/AppLauncher.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index 37ffdc1..a5eacf2 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -119,7 +119,8 @@ void AppLauncher::close() std::vector AppLauncher::launchedPids() { pid_mutex_.lock(); - std::vector res = pids_; + std::vector res; + res.reserve(pids_.size()); std::ranges::copy(pids_.begin(), pids_.end(), std::back_inserter(res)); pid_mutex_.unlock(); From 3903f38c0ec5e611d7c13c5c958b71a5dc0d37fe Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 00:41:29 +0200 Subject: [PATCH 07/44] GlosSITarget: fix reading config-files with spaces --- GlosSITarget/Settings.h | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/GlosSITarget/Settings.h b/GlosSITarget/Settings.h index 374b93a..a446f8a 100644 --- a/GlosSITarget/Settings.h +++ b/GlosSITarget/Settings.h @@ -126,20 +126,19 @@ inline void Parse(const std::vector& args) if (arg.empty()) { continue; } - if (arg[0] != L'-') { - configName = std::wstring(arg.begin(), arg.end()); - } - if (arg[0] == L'-') { - if (arg == L"-disableuwpoverlay") { - cli.no_uwp_overlay = true; - } - if (arg == L"-disablewatchdog") { - cli.disable_watchdog = true; - } + if (arg == L"-disableuwpoverlay") { + cli.no_uwp_overlay = true; + } else if (arg == L"-disablewatchdog") { + cli.disable_watchdog = true; + } else { + configName += L" " + std::wstring(arg.begin(), arg.end()); } + } - if (!configName.empty()) { + if (configName[0] == L' ') { + configName.erase(configName.begin()); + } if (!configName.ends_with(L".json")) { configName += L".json"; } From 655d3b0853cba4525c601f802fbcddab310143d5 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 19:58:36 +0200 Subject: [PATCH 08/44] Watchdog: Fix unloading if no GlosSIWindow was found --- GlosSIWatchdog/dllmain.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/GlosSIWatchdog/dllmain.cpp b/GlosSIWatchdog/dllmain.cpp index 088a6b0..18f23dc 100644 --- a/GlosSIWatchdog/dllmain.cpp +++ b/GlosSIWatchdog/dllmain.cpp @@ -60,6 +60,7 @@ DWORD WINAPI watchdog(HMODULE hModule) if (!glossi_hwnd) { spdlog::error("Couldn't find GlosSITarget window. Exiting..."); + FreeLibraryAndExitThread(hModule, 1); return 1; } spdlog::debug("Found GlosSITarget window; Starting watch loop"); From 73bbffd6cfcf77e327bc56c84bb4b643f0b5c28f Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 20:27:59 +0200 Subject: [PATCH 09/44] Watchdog: Kill launched processes after GlosSITarget closed --- GlosSIWatchdog/GlosSIWatchdog.vcxproj | 2 +- GlosSIWatchdog/dllmain.cpp | 55 ++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/GlosSIWatchdog/GlosSIWatchdog.vcxproj b/GlosSIWatchdog/GlosSIWatchdog.vcxproj index 5884944..17b5cb9 100644 --- a/GlosSIWatchdog/GlosSIWatchdog.vcxproj +++ b/GlosSIWatchdog/GlosSIWatchdog.vcxproj @@ -71,7 +71,7 @@ - ..\deps\spdlog\include;..\deps\json\include;$(IncludePath) + ..\deps\spdlog\include;..\deps\json\include;..\deps\cpp-httplib;$(IncludePath) ..\deps\spdlog\include;..\deps\json\include;$(IncludePath) diff --git a/GlosSIWatchdog/dllmain.cpp b/GlosSIWatchdog/dllmain.cpp index 18f23dc..f145e3c 100644 --- a/GlosSIWatchdog/dllmain.cpp +++ b/GlosSIWatchdog/dllmain.cpp @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +#include +#define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #include @@ -24,9 +26,20 @@ limitations under the License. #include #include +#include + #include "../version.hpp" #include "../GlosSITarget/HidHide.h" +bool IsProcessRunning(DWORD pid) +{ + const HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, pid); + if (process == nullptr) + return false; + const DWORD ret = WaitForSingleObject(process, 0); + CloseHandle(process); + return ret == WAIT_TIMEOUT; +} DWORD WINAPI watchdog(HMODULE hModule) { @@ -65,14 +78,52 @@ DWORD WINAPI watchdog(HMODULE hModule) } spdlog::debug("Found GlosSITarget window; Starting watch loop"); + httplib::Client http_client("http://localhost:8756"); + http_client.set_connection_timeout(1); + auto http_res = http_client.Get("/launched-pids"); + std::vector pids = http_res.error() == httplib::Error::Success && http_res->status == 200 ? nlohmann::json::parse(http_res->body).get>() : std::vector(); while (glossi_hwnd) { + http_client.set_connection_timeout(120); + http_res = http_client.Get("/launched-pids"); + if (http_res.error() == httplib::Error::Success && http_res->status == 200) + { + pids = nlohmann::json::parse(http_res->body).get>(); + } else { + spdlog::error("Couldn't fetch launched PIDs: {}", (int)http_res.error()); + } + glossi_hwnd = FindWindowA(nullptr, "GlosSITarget"); - Sleep(1337); + + Sleep(333); } - spdlog::info("GlosSITarget was closed. Cleaning up..."); + spdlog::info("GlosSITarget was closed. Resetting HidHide state..."); HidHide hidhide; hidhide.disableHidHide(); + // TODO: read settings; check if watchdog should close launched procs + + spdlog::info("Closing launched processes"); + + + for (const auto pid : pids) + { + if (IsProcessRunning(pid)) + { + if (const auto proc = OpenProcess(PROCESS_TERMINATE, FALSE, pid)) + { + spdlog::debug("Terminating process: {}", pid); + const auto terminate_res = TerminateProcess(proc, 0); + if (!terminate_res) + { + spdlog::error("Failed to terminate process: {}", pid); + } + CloseHandle(proc); + } + } + } + + // \ + spdlog::info("Unloading Watchdog..."); FreeLibraryAndExitThread(hModule, 0); } From a1c1a7d1416b398c9f8e196a654069f3c70e6f74 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 20:37:58 +0200 Subject: [PATCH 10/44] GlosSITarget: Refactor Settings.h - Overload parse function to also accept nlohmann::json object - Function to convert settings to nlohmann::json --- GlosSITarget/Settings.h | 129 ++++++++++++++++++++++------------------ 1 file changed, 71 insertions(+), 58 deletions(-) diff --git a/GlosSITarget/Settings.h b/GlosSITarget/Settings.h index a446f8a..56bc0d6 100644 --- a/GlosSITarget/Settings.h +++ b/GlosSITarget/Settings.h @@ -119,59 +119,9 @@ inline void checkWinVer() } #endif -inline void Parse(const std::vector& args) -{ - std::wstring configName; - for (const auto& arg : args) { - if (arg.empty()) { - continue; - } - if (arg == L"-disableuwpoverlay") { - cli.no_uwp_overlay = true; - } else if (arg == L"-disablewatchdog") { - cli.disable_watchdog = true; - } else { - configName += L" " + std::wstring(arg.begin(), arg.end()); - } - - } - if (!configName.empty()) { - if (configName[0] == L' ') { - configName.erase(configName.begin()); - } - if (!configName.ends_with(L".json")) { - configName += L".json"; - } - } - wchar_t* localAppDataFolder; - std::filesystem::path path; - if (SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, NULL, &localAppDataFolder) != S_OK) { - path = std::filesystem::temp_directory_path().parent_path().parent_path().parent_path(); - } - else { - path = std::filesystem::path(localAppDataFolder).parent_path(); - } - - path /= "Roaming"; - path /= "GlosSI"; - if (!configName.empty()) { - path /= "Targets"; - path /= configName; - } - else { - spdlog::info("No config file specified, using default"); - path /= "default.json"; - } - - std::ifstream json_file; - json_file.open(path); - if (!json_file.is_open()) { - spdlog::error(L"Couldn't open settings file {}", path.wstring()); - spdlog::debug(L"Using sane defaults..."); - return; - } - settings_path_ = path; +inline void Parse(const nlohmann::basic_json<>& json) +{ auto safeParseValue = [](const auto& object, const auto& key, auto& value) { try { if (object.is_null() || object.empty() || object.at(key).empty() || object.at(key).is_null()) { @@ -198,7 +148,6 @@ inline void Parse(const std::vector& args) } }; - const auto json = nlohmann::json::parse(json_file); int version; safeParseValue(json, "version", version); if (version != 1) { // TODO: versioning stuff @@ -241,17 +190,75 @@ inline void Parse(const std::vector& args) } safeParseValue(json, "extendedLogging", extendedLogging); - - json_file.close(); - - spdlog::debug("Read config file \"{}\"; config: {}", path.string(), json.dump()); + if (launch.launch) { launch.isUWP = checkIsUwp(launch.launchPath); } } -inline void StoreSettings() +inline void Parse(const std::vector& args) +{ + std::wstring configName; + for (const auto& arg : args) { + if (arg.empty()) { + continue; + } + if (arg == L"-disableuwpoverlay") { + cli.no_uwp_overlay = true; + } + else if (arg == L"-disablewatchdog") { + cli.disable_watchdog = true; + } + else { + configName += L" " + std::wstring(arg.begin(), arg.end()); + } + } + if (!configName.empty()) { + if (configName[0] == L' ') { + configName.erase(configName.begin()); + } + if (!configName.ends_with(L".json")) { + configName += L".json"; + } + } + wchar_t* localAppDataFolder; + std::filesystem::path path; + if (SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, NULL, &localAppDataFolder) != S_OK) { + path = std::filesystem::temp_directory_path().parent_path().parent_path().parent_path(); + } + else { + path = std::filesystem::path(localAppDataFolder).parent_path(); + } + + path /= "Roaming"; + path /= "GlosSI"; + if (!configName.empty()) { + path /= "Targets"; + path /= configName; + } + else { + spdlog::info("No config file specified, using default"); + path /= "default.json"; + } + + std::ifstream json_file; + json_file.open(path); + if (!json_file.is_open()) { + spdlog::error(L"Couldn't open settings file {}", path.wstring()); + spdlog::debug(L"Using sane defaults..."); + return; + } + settings_path_ = path; + const auto& json = nlohmann::json::parse(json_file); + Parse(json); + + spdlog::debug("Read config file \"{}\"; config: {}", path.string(), json.dump()); + json_file.close(); +} + + +inline nlohmann::json toJson() { nlohmann::json json; json["version"] = 1; @@ -271,6 +278,12 @@ inline void StoreSettings() json["controller"]["emulateDS4"] = controller.emulateDS4; json["extendedLogging"] = extendedLogging; + return json; +} + +inline void StoreSettings() +{ + const auto& json = toJson(); std::ofstream json_file; json_file.open(settings_path_); From 31ce36095f5aaf569fa1a28070c613bf18293972 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 20:44:24 +0200 Subject: [PATCH 11/44] HttpServer: endpoint to query shortcut settings --- GlosSITarget/HttpServer.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/GlosSITarget/HttpServer.cpp b/GlosSITarget/HttpServer.cpp index f9c3424..85466e1 100644 --- a/GlosSITarget/HttpServer.cpp +++ b/GlosSITarget/HttpServer.cpp @@ -19,9 +19,11 @@ limitations under the License. #include #include "AppLauncher.h" +#include "Settings.h" HttpServer::HttpServer(AppLauncher& app_launcher) : app_launcher_(app_launcher) -{} +{ +} void HttpServer::run() { @@ -30,6 +32,10 @@ void HttpServer::run() res.set_content(j.dump(), "text/json"); }); + server_.Get("/settings", [this](const httplib::Request& req, httplib::Response& res) { + res.set_content(Settings::toJson().dump(), "text/json"); + }); + server_thread_ = std::thread([this]() { if (!server_.listen("0.0.0.0", port_)) { spdlog::error("Couldn't start http-server"); From 1b2b8b759a466a47ffe3110761f9f1aefba0cef1 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 20:51:59 +0200 Subject: [PATCH 12/44] GlosSITarget: Settings: move extendedLogging and cli-opts to common-struct --- GlosSITarget/AppLauncher.cpp | 2 +- GlosSITarget/HidHide.cpp | 14 +++++++------- GlosSITarget/Overlay.cpp | 2 +- GlosSITarget/Settings.h | 6 +++--- GlosSITarget/SteamOverlayDetector.cpp | 2 +- GlosSITarget/SteamTarget.cpp | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index a5eacf2..b2350c7 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -147,7 +147,7 @@ void AppLauncher::getChildPids(DWORD parent_pid) do { if (pe.th32ParentProcessID == parent_pid) { if (std::ranges::find(pids_, pe.th32ProcessID) == pids_.end()) { - if (Settings::extendedLogging) { + if (Settings::common.extendedLogging) { spdlog::info("Found new child process with PID \"{}\"", pe.th32ProcessID); } pids_.push_back(pe.th32ProcessID); diff --git a/GlosSITarget/HidHide.cpp b/GlosSITarget/HidHide.cpp index 6667828..086383d 100644 --- a/GlosSITarget/HidHide.cpp +++ b/GlosSITarget/HidHide.cpp @@ -122,7 +122,7 @@ void HidHide::hideDevices(const std::filesystem::path& steam_path) whitelist.push_back(path); } } - if (Settings::extendedLogging) { + if (Settings::common.extendedLogging) { std::ranges::for_each(whitelist, [](const auto& exe) { spdlog::trace(L"Whitelisted executable: {}", exe); }); @@ -130,7 +130,7 @@ void HidHide::hideDevices(const std::filesystem::path& steam_path) setAppWhiteList(whitelist); avail_devices_ = GetHidDeviceList(); - if (Settings::extendedLogging) { + if (Settings::common.extendedLogging) { std::ranges::for_each(avail_devices_, [](const auto& dev) { spdlog::trace(L"AvailDevice device: {}", dev.name); }); @@ -157,7 +157,7 @@ void HidHide::hideDevices(const std::filesystem::path& steam_path) setBlacklistDevices(blacklisted_devices_); setActive(true); spdlog::info("Hid Gaming Devices; Enabling Overlay element..."); - if (Settings::extendedLogging) { + if (Settings::common.extendedLogging) { std::ranges::for_each(blacklisted_devices_, [](const auto& dev) { spdlog::trace(L"Blacklisted device: {}", dev); }); @@ -205,14 +205,14 @@ void HidHide::enableOverlayElement() // UnPatchValveHooks(); openCtrlDevice(); bool hidehide_state_store = hidhide_active_; - if (Settings::extendedLogging) { + if (Settings::common.extendedLogging) { spdlog::debug("Refreshing HID devices"); } if (hidhide_active_) { setActive(false); } avail_devices_ = GetHidDeviceList(); - if (Settings::extendedLogging) { + if (Settings::common.extendedLogging) { std::ranges::for_each(avail_devices_, [](const auto& dev) { spdlog::trace(L"AvailDevice device: {}", dev.name); }); @@ -248,7 +248,7 @@ void HidHide::enableOverlayElement() blacklisted_devices_.end()); } setBlacklistDevices(blacklisted_devices_); - if (Settings::extendedLogging) { + if (Settings::common.extendedLogging) { std::ranges::for_each(blacklisted_devices_, [](const auto& dev) { spdlog::trace(L"Blacklisted device: {}", dev); }); @@ -355,7 +355,7 @@ void HidHide::setActive(bool active) return; } hidhide_active_ = active; - if (Settings::extendedLogging) { + if (Settings::common.extendedLogging) { spdlog::debug("HidHide State set to {}", active); } } diff --git a/GlosSITarget/Overlay.cpp b/GlosSITarget/Overlay.cpp index 39b3d31..c5427d5 100644 --- a/GlosSITarget/Overlay.cpp +++ b/GlosSITarget/Overlay.cpp @@ -199,7 +199,7 @@ void Overlay::update() Settings::StoreSettings(); } } - ImGui::Checkbox("Extended logging", &Settings::extendedLogging); + ImGui::Checkbox("Extended logging", &Settings::common.extendedLogging); ImGuiID dockspace_id = ImGui::GetID("GlosSI-DockSpace"); ImGui::DockSpace(dockspace_id); diff --git a/GlosSITarget/Settings.h b/GlosSITarget/Settings.h index 56bc0d6..0625073 100644 --- a/GlosSITarget/Settings.h +++ b/GlosSITarget/Settings.h @@ -59,12 +59,12 @@ inline struct Controller { bool emulateDS4 = false; } controller; -inline struct Cli { +inline struct Common { bool no_uwp_overlay = false; bool disable_watchdog = false; -} cli; -inline bool extendedLogging = false; + bool extendedLogging = false; +} common; inline std::filesystem::path settings_path_ = ""; diff --git a/GlosSITarget/SteamOverlayDetector.cpp b/GlosSITarget/SteamOverlayDetector.cpp index 12fdf86..76d2b9a 100644 --- a/GlosSITarget/SteamOverlayDetector.cpp +++ b/GlosSITarget/SteamOverlayDetector.cpp @@ -47,7 +47,7 @@ void SteamOverlayDetector::update() if (PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE)) { // filter out some messages as not all get altered by steam... - if (Settings::extendedLogging && msg.message != 512 && msg.message != 5374) { + if (Settings::common.extendedLogging && msg.message != 512 && msg.message != 5374) { spdlog::trace("PeekMessage: Window msg: {}", msg.message); } diff --git a/GlosSITarget/SteamTarget.cpp b/GlosSITarget/SteamTarget.cpp index 4c11e07..fc3c357 100644 --- a/GlosSITarget/SteamTarget.cpp +++ b/GlosSITarget/SteamTarget.cpp @@ -50,7 +50,7 @@ SteamTarget::SteamTarget() { target_window_handle_ = window_.getSystemHandle(); #ifdef _WIN32 - if (Settings::cli.no_uwp_overlay) { + if (Settings::common.no_uwp_overlay) { UWPOverlayEnabler::AddUwpOverlayOvWidget(); } else { @@ -78,7 +78,7 @@ Application will not function!"); steam_overlay_present_ = true; #ifdef WIN32 - if (!Settings::cli.disable_watchdog) { + if (!Settings::common.disable_watchdog) { wchar_t buff[MAX_PATH]; GetModuleFileName(GetModuleHandle(NULL), buff, MAX_PATH); std::wstring watchDogPath(buff); From 14d3e453790bfdd742d740e8557b58dc369b1ae5 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 20:52:36 +0200 Subject: [PATCH 13/44] GlosSSITarget: Settings: parse missing but unused common values (icon/name/version) --- GlosSITarget/Settings.h | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/GlosSITarget/Settings.h b/GlosSITarget/Settings.h index 0625073..dfe0eb6 100644 --- a/GlosSITarget/Settings.h +++ b/GlosSITarget/Settings.h @@ -62,8 +62,10 @@ inline struct Controller { inline struct Common { bool no_uwp_overlay = false; bool disable_watchdog = false; - bool extendedLogging = false; + std::wstring name; + std::wstring icon; + int version; } common; inline std::filesystem::path settings_path_ = ""; @@ -119,7 +121,6 @@ inline void checkWinVer() } #endif - inline void Parse(const nlohmann::basic_json<>& json) { auto safeParseValue = [](const auto& object, const auto& key, auto& value) { @@ -189,8 +190,10 @@ inline void Parse(const nlohmann::basic_json<>& json) spdlog::warn("Err parsing config: {}", e.what()); } - safeParseValue(json, "extendedLogging", extendedLogging); - + safeParseValue(json, "extendedLogging", common.extendedLogging); + safeWStringParse(json, "name", common.name); + safeWStringParse(json, "icon", common.icon); + safeParseValue(json, "version", common.version); if (launch.launch) { launch.isUWP = checkIsUwp(launch.launchPath); @@ -205,10 +208,10 @@ inline void Parse(const std::vector& args) continue; } if (arg == L"-disableuwpoverlay") { - cli.no_uwp_overlay = true; + common.no_uwp_overlay = true; } else if (arg == L"-disablewatchdog") { - cli.disable_watchdog = true; + common.disable_watchdog = true; } else { configName += L" " + std::wstring(arg.begin(), arg.end()); @@ -252,12 +255,11 @@ inline void Parse(const std::vector& args) settings_path_ = path; const auto& json = nlohmann::json::parse(json_file); Parse(json); - + spdlog::debug("Read config file \"{}\"; config: {}", path.string(), json.dump()); json_file.close(); } - inline nlohmann::json toJson() { nlohmann::json json; @@ -277,7 +279,10 @@ inline nlohmann::json toJson() json["controller"]["allowDesktopConfig"] = controller.allowDesktopConfig; json["controller"]["emulateDS4"] = controller.emulateDS4; - json["extendedLogging"] = extendedLogging; + json["extendedLogging"] = common.extendedLogging; + json["name"] = std::wstring_convert>().to_bytes(common.name); + json["icon"] = std::wstring_convert>().to_bytes(common.icon); + json["version"] = common.version; return json; } From 496b8fc1469aff7298e34b3652d0ce7db2c98053 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 21:08:22 +0200 Subject: [PATCH 14/44] Watchdog: Include and retrieve settings from Target; only kill launched processes if "closeOnExit" --- GlosSIWatchdog/dllmain.cpp | 56 ++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/GlosSIWatchdog/dllmain.cpp b/GlosSIWatchdog/dllmain.cpp index f145e3c..cc762a0 100644 --- a/GlosSIWatchdog/dllmain.cpp +++ b/GlosSIWatchdog/dllmain.cpp @@ -29,6 +29,7 @@ limitations under the License. #include #include "../version.hpp" +#include "../GlosSITarget/Settings.h" #include "../GlosSITarget/HidHide.h" bool IsProcessRunning(DWORD pid) @@ -80,15 +81,27 @@ DWORD WINAPI watchdog(HMODULE hModule) httplib::Client http_client("http://localhost:8756"); http_client.set_connection_timeout(1); - auto http_res = http_client.Get("/launched-pids"); - std::vector pids = http_res.error() == httplib::Error::Success && http_res->status == 200 ? nlohmann::json::parse(http_res->body).get>() : std::vector(); + + auto http_res = http_client.Get("/settings"); + if (http_res.error() == httplib::Error::Success && http_res->status == 200) + { + Settings::Parse(nlohmann::json::parse(http_res->body)); + } + + + std::vector pids; while (glossi_hwnd) { http_client.set_connection_timeout(120); http_res = http_client.Get("/launched-pids"); if (http_res.error() == httplib::Error::Success && http_res->status == 200) { - pids = nlohmann::json::parse(http_res->body).get>(); + const auto json = nlohmann::json::parse(http_res->body); + if (Settings::common.extendedLogging) + { + spdlog::trace("Received pids: {}", json.dump()); + } + pids = json.get>(); } else { spdlog::error("Couldn't fetch launched PIDs: {}", (int)http_res.error()); } @@ -100,30 +113,39 @@ DWORD WINAPI watchdog(HMODULE hModule) spdlog::info("GlosSITarget was closed. Resetting HidHide state..."); HidHide hidhide; hidhide.disableHidHide(); - // TODO: read settings; check if watchdog should close launched procs - - spdlog::info("Closing launched processes"); - - for (const auto pid : pids) + if (Settings::launch.closeOnExit) { - if (IsProcessRunning(pid)) + spdlog::info("Closing launched processes"); + + for (const auto pid : pids) { - if (const auto proc = OpenProcess(PROCESS_TERMINATE, FALSE, pid)) + if (Settings::common.extendedLogging) + { + spdlog::debug("Checking if process {} is running", pid); + } + if (IsProcessRunning(pid)) + { + if (const auto proc = OpenProcess(PROCESS_TERMINATE, FALSE, pid)) + { + spdlog::debug("Terminating process: {}", pid); + const auto terminate_res = TerminateProcess(proc, 0); + if (!terminate_res) + { + spdlog::error("Failed to terminate process: {}", pid); + } + CloseHandle(proc); + } + } else { - spdlog::debug("Terminating process: {}", pid); - const auto terminate_res = TerminateProcess(proc, 0); - if (!terminate_res) + if (Settings::common.extendedLogging) { - spdlog::error("Failed to terminate process: {}", pid); + spdlog::debug("Process {} is not running", pid); } - CloseHandle(proc); } } } - // \ - spdlog::info("Unloading Watchdog..."); FreeLibraryAndExitThread(hModule, 0); } From 7b8eb7391bcb1da545001c134a38173ebd78d042 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 21:12:00 +0200 Subject: [PATCH 15/44] Watchdog: Log settings retrieval --- GlosSIWatchdog/dllmain.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/GlosSIWatchdog/dllmain.cpp b/GlosSIWatchdog/dllmain.cpp index cc762a0..8cbb224 100644 --- a/GlosSIWatchdog/dllmain.cpp +++ b/GlosSIWatchdog/dllmain.cpp @@ -85,7 +85,12 @@ DWORD WINAPI watchdog(HMODULE hModule) auto http_res = http_client.Get("/settings"); if (http_res.error() == httplib::Error::Success && http_res->status == 200) { - Settings::Parse(nlohmann::json::parse(http_res->body)); + const auto json = nlohmann::json::parse(http_res->body); + spdlog::debug("Received settings from GlosSITarget: {}", json.dump()); + Settings::Parse(json); + } else + { + spdlog::error("Couldn't get settings from GlosSITarget."); } From d9c45a4a7ba0d8f5e04afbcf42d2c5d65a1bc7a1 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 21:21:48 +0200 Subject: [PATCH 16/44] Watchdog: retry fetching settings a few times --- GlosSIWatchdog/dllmain.cpp | 44 ++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/GlosSIWatchdog/dllmain.cpp b/GlosSIWatchdog/dllmain.cpp index 8cbb224..957d9df 100644 --- a/GlosSIWatchdog/dllmain.cpp +++ b/GlosSIWatchdog/dllmain.cpp @@ -42,6 +42,27 @@ bool IsProcessRunning(DWORD pid) return ret == WAIT_TIMEOUT; } +void fetchSettings(httplib::Client& http_client, int retried_count = 0) { + http_client.set_connection_timeout(1 + (retried_count > 0 ? 2 : 0)); + + auto http_res = http_client.Get("/settings"); + if (http_res.error() == httplib::Error::Success && http_res->status == 200) + { + const auto json = nlohmann::json::parse(http_res->body); + spdlog::debug("Received settings from GlosSITarget: {}", json.dump()); + Settings::Parse(json); + } + else + { + spdlog::error("Couldn't get settings from GlosSITarget. Error: {}", (int)http_res.error()); + if (retried_count < 2) + { + spdlog::info("Retrying... {}", retried_count); + fetchSettings(http_client, retried_count + 1); + } + } +} + DWORD WINAPI watchdog(HMODULE hModule) { wchar_t* localAppDataFolder; @@ -80,25 +101,14 @@ DWORD WINAPI watchdog(HMODULE hModule) spdlog::debug("Found GlosSITarget window; Starting watch loop"); httplib::Client http_client("http://localhost:8756"); - http_client.set_connection_timeout(1); - - auto http_res = http_client.Get("/settings"); - if (http_res.error() == httplib::Error::Success && http_res->status == 200) - { - const auto json = nlohmann::json::parse(http_res->body); - spdlog::debug("Received settings from GlosSITarget: {}", json.dump()); - Settings::Parse(json); - } else - { - spdlog::error("Couldn't get settings from GlosSITarget."); - } + fetchSettings(http_client); std::vector pids; while (glossi_hwnd) { http_client.set_connection_timeout(120); - http_res = http_client.Get("/launched-pids"); + const auto http_res = http_client.Get("/launched-pids"); if (http_res.error() == httplib::Error::Success && http_res->status == 200) { const auto json = nlohmann::json::parse(http_res->body); @@ -107,7 +117,8 @@ DWORD WINAPI watchdog(HMODULE hModule) spdlog::trace("Received pids: {}", json.dump()); } pids = json.get>(); - } else { + } + else { spdlog::error("Couldn't fetch launched PIDs: {}", (int)http_res.error()); } @@ -141,7 +152,8 @@ DWORD WINAPI watchdog(HMODULE hModule) } CloseHandle(proc); } - } else + } + else { if (Settings::common.extendedLogging) { @@ -168,5 +180,5 @@ BOOL APIENTRY DllMain(HMODULE hModule, } return TRUE; - return 0; + return 0; } From ed0b43a651df3440795b7e4a972928f888451b58 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 21:25:35 +0200 Subject: [PATCH 17/44] GlosSIConfig: Change/Update wording on "closeOnExit"/"waitForChildren" Options now works different as watchdog also kills launched processes --- GlosSIConfig/qml/AdvancedTargetSettings.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GlosSIConfig/qml/AdvancedTargetSettings.qml b/GlosSIConfig/qml/AdvancedTargetSettings.qml index 8798ee0..05c80ca 100644 --- a/GlosSIConfig/qml/AdvancedTargetSettings.qml +++ b/GlosSIConfig/qml/AdvancedTargetSettings.qml @@ -62,7 +62,7 @@ CollapsiblePane { spacing: 2 CheckBox { id: closeOnExit - text: qsTr("Close when launched app quits") + text: qsTr("Close GlosSI when launched app quits and vice versa") checked: shortcutInfo.launch.closeOnExit onCheckedChanged: function() { shortcutInfo.launch.closeOnExit = checked @@ -82,7 +82,7 @@ CollapsiblePane { } CheckBox { id: waitForChildren - text: qsTr("Wait for child processes") + text: qsTr("Include child processes") checked: shortcutInfo.launch.waitForChildProcs onCheckedChanged: function(){ shortcutInfo.launch.waitForChildProcs = checked From e3a55d9b1c5f1b1a9786f4efe87c5ffecf2a563a Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Mon, 10 Oct 2022 21:58:50 +0200 Subject: [PATCH 18/44] Watchdog: Fix release mode include paths --- GlosSIWatchdog/GlosSIWatchdog.vcxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GlosSIWatchdog/GlosSIWatchdog.vcxproj b/GlosSIWatchdog/GlosSIWatchdog.vcxproj index 17b5cb9..01a1dbb 100644 --- a/GlosSIWatchdog/GlosSIWatchdog.vcxproj +++ b/GlosSIWatchdog/GlosSIWatchdog.vcxproj @@ -74,7 +74,7 @@ ..\deps\spdlog\include;..\deps\json\include;..\deps\cpp-httplib;$(IncludePath) - ..\deps\spdlog\include;..\deps\json\include;$(IncludePath) + ..\deps\spdlog\include;..\deps\json\include;..\deps\cpp-httplib;$(IncludePath) From bf89cb8ddcdd4c618efdbf45ed2728d73abd2334 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Tue, 11 Oct 2022 19:54:57 +0200 Subject: [PATCH 19/44] GlosSITarget: Cleanup: util --- GlosSITarget/util.h | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/GlosSITarget/util.h b/GlosSITarget/util.h index d4d106e..8bc318d 100644 --- a/GlosSITarget/util.h +++ b/GlosSITarget/util.h @@ -1,6 +1,19 @@ -#pragma once +/* +Copyright 2021-2022 Peter Repukat - FlatspotSoftware + +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 -#include "DllInjector.h" +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 namespace glossi_util { From d22f44d37db347a13163b7b9692017627801c395 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Tue, 11 Oct 2022 19:55:45 +0200 Subject: [PATCH 20/44] =?UTF-8?q?GlosSITarget:=20Add=20somewhat=20of=20epi?= =?UTF-8?q?c=20games=20launcher=20process=20detection=20workaround=20?= =?UTF-8?q?=F0=9F=A4=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GlosSITarget/AppLauncher.cpp | 27 +++++++++++++++++++++++++++ GlosSITarget/AppLauncher.h | 5 +++++ 2 files changed, 32 insertions(+) diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index b2350c7..0b174a8 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -33,6 +33,7 @@ limitations under the License. #include #include "UnhookUtil.h" +#include "util.h" AppLauncher::AppLauncher( std::vector& process_hwnds, @@ -71,6 +72,9 @@ void AppLauncher::update() if (process_check_clock_.getElapsedTime().asMilliseconds() > 250) { pid_mutex_.lock(); #ifdef _WIN32 + if (was_egs_launch_ && pids_.empty()) { + findEgsPid(); + } if (!pids_.empty() && pids_[0] > 0) { if (Settings::launch.waitForChildProcs) { getChildPids(pids_[0]); @@ -191,6 +195,16 @@ void AppLauncher::getProcessHwnds() #endif #ifdef _WIN32 +bool AppLauncher::findEgsPid() +{ + if (const auto pid = glossi_util::PidByName(L"EpicGamesLauncher.exe")) { + spdlog::debug("Found EGS-Launcher running"); + pids_.push_back(pid); + return true; + } + return false; +} + void AppLauncher::UnPatchValveHooks() { // need to load addresses that way.. Otherwise we may land before some jumps... @@ -323,6 +337,19 @@ void AppLauncher::launchURL(const std::wstring& url, const std::wstring& args, c return; } } + + if (url.find(L"epicgames.launcher") != std::wstring::npos) { + was_egs_launch_ = true; + spdlog::debug("Epic Games launch; Couldn't find egs launcher PID"); + pid_mutex_.lock(); + + const auto pid = glossi_util::PidByName(L"EpicGamesLauncher.exe"); + if (!findEgsPid()) { + spdlog::debug("Did not find EGS-Launcher not running, retrying later..."); + } + pid_mutex_.unlock(); + return; + } spdlog::warn("Couldn't get PID of launched URL process"); } #endif diff --git a/GlosSITarget/AppLauncher.h b/GlosSITarget/AppLauncher.h index 7203028..4b1472f 100644 --- a/GlosSITarget/AppLauncher.h +++ b/GlosSITarget/AppLauncher.h @@ -49,6 +49,11 @@ class AppLauncher { void getProcessHwnds(); std::vector& process_hwnds_; + + bool was_egs_launch_ = false; + bool findEgsPid(); + + std::wstring launched_uwp_path_; static void UnPatchValveHooks(); From 51c2268dc0954eae42a18e101d7d3b3628022ee1 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Tue, 11 Oct 2022 20:16:14 +0200 Subject: [PATCH 21/44] GlosSIUtil: Add KIllProcess --- GlosSITarget/util.h | 39 ++++++++++++++++++++++++++++++++++++++ GlosSIWatchdog/dllmain.cpp | 12 ++---------- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/GlosSITarget/util.h b/GlosSITarget/util.h index 8bc318d..3fd791a 100644 --- a/GlosSITarget/util.h +++ b/GlosSITarget/util.h @@ -14,6 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ #pragma once +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include namespace glossi_util { @@ -33,5 +37,40 @@ inline DWORD PidByName(const std::wstring& name) return 0; } +inline std::wstring GetProcName(DWORD pid) +{ + PROCESSENTRY32 processInfo; + processInfo.dwSize = sizeof(processInfo); + const HANDLE processesSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + if (processesSnapshot == INVALID_HANDLE_VALUE) { + spdlog::trace("util::GetProcName: can't get a process snapshot"); + return L""; + } + + for (BOOL bok = Process32First(processesSnapshot, &processInfo); + bok; + bok = Process32Next(processesSnapshot, &processInfo)) { + if (pid == processInfo.th32ProcessID) { + CloseHandle(processesSnapshot); + return processInfo.szExeFile; + } + } + CloseHandle(processesSnapshot); + return L""; +} + +inline bool KillProcess(DWORD pid) +{ + auto res = true; + if (const auto proc = OpenProcess(PROCESS_TERMINATE, FALSE, pid)) { + spdlog::debug("Terminating process: {}", pid); + res = TerminateProcess(proc, 0); + if (!res) { + spdlog::error("Failed to terminate process: {}", pid); + } + CloseHandle(proc); + } + return res; +} } // namespace glossi_util diff --git a/GlosSIWatchdog/dllmain.cpp b/GlosSIWatchdog/dllmain.cpp index 957d9df..c63ce7d 100644 --- a/GlosSIWatchdog/dllmain.cpp +++ b/GlosSIWatchdog/dllmain.cpp @@ -31,6 +31,7 @@ limitations under the License. #include "../version.hpp" #include "../GlosSITarget/Settings.h" #include "../GlosSITarget/HidHide.h" +#include "../GlosSITarget/util.h" bool IsProcessRunning(DWORD pid) { @@ -142,16 +143,7 @@ DWORD WINAPI watchdog(HMODULE hModule) } if (IsProcessRunning(pid)) { - if (const auto proc = OpenProcess(PROCESS_TERMINATE, FALSE, pid)) - { - spdlog::debug("Terminating process: {}", pid); - const auto terminate_res = TerminateProcess(proc, 0); - if (!terminate_res) - { - spdlog::error("Failed to terminate process: {}", pid); - } - CloseHandle(proc); - } + glossi_util::KillProcess(pid); } else { From eee739054782fd5c0cfb54fba2e4a2584960a099 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Tue, 11 Oct 2022 20:16:34 +0200 Subject: [PATCH 22/44] GlosSITarget: AppLauncher: Add processInfo/killer overlay element --- GlosSITarget/AppLauncher.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index 0b174a8..7327d8f 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -32,6 +32,7 @@ limitations under the License. #include +#include "Overlay.h" #include "UnhookUtil.h" #include "util.h" @@ -64,6 +65,22 @@ void AppLauncher::launchApp(const std::wstring& path, const std::wstring& args) spdlog::info("LaunchApp is Win32, launching..."); launchWin32App(path, args); } + Overlay::AddOverlayElem([this](bool has_focus, ImGuiID dockspace_id) { + ImGui::SetNextWindowDockID(dockspace_id, ImGuiCond_FirstUseEver); + if (ImGui::Begin("Launched Processes")) { + ImGui::BeginChild("Inner##LaunchedProcs", {0.f, ImGui::GetItemRectSize().y - 64}, true); + std::ranges::for_each(pids_, [](DWORD pid) { + ImGui::Text("%s | %d", std::wstring_convert>().to_bytes(glossi_util::GetProcName(pid)).c_str(), pid); + ImGui::SameLine(); + if (ImGui::Button((" Kill ##" + std::to_string(pid)).c_str())) { + glossi_util::KillProcess(pid); + } + }); + ImGui::EndChild(); + } + ImGui::End(); + + }); #endif } From 11e3029915e7e41a6094a5aa3341e4af219d0175 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Tue, 11 Oct 2022 20:29:06 +0200 Subject: [PATCH 23/44] GlosSITarget: Also find nested child PIDs --- GlosSITarget/AppLauncher.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index 7327d8f..7348886 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -172,6 +172,7 @@ void AppLauncher::getChildPids(DWORD parent_pid) spdlog::info("Found new child process with PID \"{}\"", pe.th32ProcessID); } pids_.push_back(pe.th32ProcessID); + getChildPids(pe.th32ProcessID); } } } while (Process32Next(hp, &pe)); From 138dd98c59041d12424f464b3003f1a180e9cef3 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Tue, 11 Oct 2022 20:32:56 +0200 Subject: [PATCH 24/44] GlosSITarget: Log process names in addition to PIDs --- GlosSITarget/AppLauncher.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index 7348886..7ecb695 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -97,7 +97,7 @@ void AppLauncher::update() getChildPids(pids_[0]); } if (!IsProcessRunning(pids_[0])) { - spdlog::info("Launched App with PID \"{}\" died", pids_[0]); + spdlog::info(L"Launched App \"{}\" with PID \"{}\" died", glossi_util::GetProcName(pids_[0]), pids_[0]); if (Settings::launch.closeOnExit && !Settings::launch.waitForChildProcs && Settings::launch.launch) { spdlog::info("Configured to close on exit. Shutting down..."); shutdown_(); @@ -112,7 +112,7 @@ void AppLauncher::update() } const auto running = IsProcessRunning(pid); if (!running) - spdlog::trace("Child process with PID \"{}\" died", pid); + spdlog::trace(L"Child process \"{}\" with PID \"{}\" died", glossi_util::GetProcName(pid), pid); return !running; }); if (Settings::launch.closeOnExit && pids_.empty() && Settings::launch.launch) { @@ -169,7 +169,7 @@ void AppLauncher::getChildPids(DWORD parent_pid) if (pe.th32ParentProcessID == parent_pid) { if (std::ranges::find(pids_, pe.th32ProcessID) == pids_.end()) { if (Settings::common.extendedLogging) { - spdlog::info("Found new child process with PID \"{}\"", pe.th32ProcessID); + spdlog::info(L"Found new child process \"{}\" with PID \"{}\"", glossi_util::GetProcName(pe.th32ProcessID), pe.th32ProcessID); } pids_.push_back(pe.th32ProcessID); getChildPids(pe.th32ProcessID); From 96d8f24c93e17296adb600c908823bc3c5edbd1a Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Tue, 11 Oct 2022 21:35:27 +0200 Subject: [PATCH 25/44] GlosSITarget: More EGS Hacks/workarounds CloseOnExit: Ignore EpicGamesLauncher.exe, EpicWebHelper.exe in detection if GlosSITarget should quit --- GlosSITarget/AppLauncher.cpp | 29 ++++++++++++++++++++++++----- GlosSITarget/AppLauncher.h | 7 +++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index 7ecb695..71e10a2 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -106,6 +106,7 @@ void AppLauncher::update() } } if (Settings::launch.waitForChildProcs) { + std::erase_if(pids_, [](auto pid) { if (pid == 0) { return true; @@ -115,9 +116,25 @@ void AppLauncher::update() spdlog::trace(L"Child process \"{}\" with PID \"{}\" died", glossi_util::GetProcName(pid), pid); return !running; }); - if (Settings::launch.closeOnExit && pids_.empty() && Settings::launch.launch) { - spdlog::info("Configured to close on all children exit. Shutting down..."); - shutdown_(); + + auto filtered_pids = pids_ | std::ranges::views::filter([](DWORD pid) { + return std::ranges::find(EGS_LAUNCHER_PROCNAMES_, glossi_util::GetProcName(pid)) == EGS_LAUNCHER_PROCNAMES_.end(); + }); + if (was_egs_launch_ && !filtered_pids.empty()) { + egs_has_launched_game_ = true; + } + if (Settings::launch.closeOnExit && Settings::launch.launch) { + if (was_egs_launch_) { + if (egs_has_launched_game_ && filtered_pids.empty()) { + spdlog::info("Configured to close on all children exit. Shutting down after game launched via EGS quit..."); + shutdown_(); + } + } else { + if (pids_.empty()) { + spdlog::info("Configured to close on all children exit. Shutting down..."); + shutdown_(); + } + } } } getProcessHwnds(); @@ -346,6 +363,9 @@ void AppLauncher::launchURL(const std::wstring& url, const std::wstring& args, c } CoUninitialize(); + if (url.find(L"epicgames.launcher") != std::wstring::npos) { + was_egs_launch_ = true; + } if (execute_info.hProcess != nullptr) { if (const auto pid = GetProcessId(execute_info.hProcess); pid > 0) { pid_mutex_.lock(); @@ -356,8 +376,7 @@ void AppLauncher::launchURL(const std::wstring& url, const std::wstring& args, c } } - if (url.find(L"epicgames.launcher") != std::wstring::npos) { - was_egs_launch_ = true; + if (was_egs_launch_) { spdlog::debug("Epic Games launch; Couldn't find egs launcher PID"); pid_mutex_.lock(); diff --git a/GlosSITarget/AppLauncher.h b/GlosSITarget/AppLauncher.h index 4b1472f..84563b9 100644 --- a/GlosSITarget/AppLauncher.h +++ b/GlosSITarget/AppLauncher.h @@ -22,6 +22,7 @@ limitations under the License. #endif #include #include +#include #include #include #include @@ -49,8 +50,14 @@ class AppLauncher { void getProcessHwnds(); std::vector& process_hwnds_; + static inline const std::array EGS_LAUNCHER_PROCNAMES_{ + L"EpicGamesLauncher.exe", + L"EpicWebHelper.exe", + }; + bool was_egs_launch_ = false; + bool egs_has_launched_game_ = false; bool findEgsPid(); From fc03e91c2bd546711ede04d0cbdeabcc2a765704 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Tue, 11 Oct 2022 21:57:11 +0200 Subject: [PATCH 26/44] GlosSIConfig: setting to ignore egs-launcher procs --- GlosSIConfig/UIModel.cpp | 1 + GlosSIConfig/qml/AdvancedTargetSettings.qml | 12 +++++++++++- GlosSIConfig/qml/ShortcutProps.qml | 5 ++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/GlosSIConfig/UIModel.cpp b/GlosSIConfig/UIModel.cpp index 70817a8..d0d2ff2 100644 --- a/GlosSIConfig/UIModel.cpp +++ b/GlosSIConfig/UIModel.cpp @@ -343,6 +343,7 @@ QVariantMap UIModel::getDefaultConf() const {"version", 1}, {"extendedLogging", false}, {"snapshotNotify", false}, + {"ignoreEGS", true}, { "controller", QJsonObject{ diff --git a/GlosSIConfig/qml/AdvancedTargetSettings.qml b/GlosSIConfig/qml/AdvancedTargetSettings.qml index 05c80ca..935095d 100644 --- a/GlosSIConfig/qml/AdvancedTargetSettings.qml +++ b/GlosSIConfig/qml/AdvancedTargetSettings.qml @@ -74,7 +74,7 @@ CollapsiblePane { } } Label { - text: qsTr("Recommended to disable for launcher-games") + text: qsTr("(Might cause issues with launcher-games)") wrapMode: Text.WordWrap width: parent.width leftPadding: 32 @@ -88,6 +88,16 @@ CollapsiblePane { shortcutInfo.launch.waitForChildProcs = checked } } + CheckBox { + height: subTitle != "" || (shortcutInfo.launch.launchPath || "").includes("epicgames.launcher") ? 32 : 0 + visible: subTitle != "" || (shortcutInfo.launch.launchPath || "").includes("epicgames.launcher") + id: ignoreEGS + text: qsTr("Ignore EpicGamesLauncher process") + checked: shortcutInfo.ignoreEGS + onCheckedChanged: function(){ + shortcutInfo.ignoreEGS = checked + } + } } Column { spacing: 2 diff --git a/GlosSIConfig/qml/ShortcutProps.qml b/GlosSIConfig/qml/ShortcutProps.qml index 985d2c5..ecf7b3a 100644 --- a/GlosSIConfig/qml/ShortcutProps.qml +++ b/GlosSIConfig/qml/ShortcutProps.qml @@ -190,7 +190,10 @@ Item { placeholderText: qsTr("...") enabled: launchApp.checked text: shortcutInfo.launch.launchPath || "" - onTextChanged: shortcutInfo.launch.launchPath = text + onTextChanged: function() { + shortcutInfo.launch.launchPath = text + shortcutInfo = shortcutInfo + } } } Button { From 18a960abab2a67f76baa1621fad9ed4df3c18a51 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Tue, 11 Oct 2022 22:07:06 +0200 Subject: [PATCH 27/44] GlosSITarget: parse and use "ignoreEGS" setting --- GlosSITarget/AppLauncher.cpp | 35 +++++++++++++++++++++++------------ GlosSITarget/Settings.h | 5 +++++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index 71e10a2..1e81d6a 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -25,7 +25,6 @@ limitations under the License. #include #include - #pragma comment(lib, "Shell32.lib") #endif #include "Settings.h" @@ -76,10 +75,9 @@ void AppLauncher::launchApp(const std::wstring& path, const std::wstring& args) glossi_util::KillProcess(pid); } }); - ImGui::EndChild(); + ImGui::EndChild(); } ImGui::End(); - }); #endif } @@ -118,21 +116,22 @@ void AppLauncher::update() }); auto filtered_pids = pids_ | std::ranges::views::filter([](DWORD pid) { - return std::ranges::find(EGS_LAUNCHER_PROCNAMES_, glossi_util::GetProcName(pid)) == EGS_LAUNCHER_PROCNAMES_.end(); - }); - if (was_egs_launch_ && !filtered_pids.empty()) { + return std::ranges::find(EGS_LAUNCHER_PROCNAMES_, glossi_util::GetProcName(pid)) == EGS_LAUNCHER_PROCNAMES_.end(); + }); + if (was_egs_launch_ && !filtered_pids.empty()) { egs_has_launched_game_ = true; } if (Settings::launch.closeOnExit && Settings::launch.launch) { - if (was_egs_launch_) { + if (was_egs_launch_ && Settings::common.ignoreEGS) { if (egs_has_launched_game_ && filtered_pids.empty()) { spdlog::info("Configured to close on all children exit. Shutting down after game launched via EGS quit..."); - shutdown_(); + shutdown_(); } - } else { + } + else { if (pids_.empty()) { spdlog::info("Configured to close on all children exit. Shutting down..."); - shutdown_(); + shutdown_(); } } } @@ -159,8 +158,20 @@ std::vector AppLauncher::launchedPids() pid_mutex_.lock(); std::vector res; res.reserve(pids_.size()); - std::ranges::copy(pids_.begin(), pids_.end(), - std::back_inserter(res)); + if (Settings::common.ignoreEGS) { + for (const auto& pid : pids_ | std::ranges::views::filter( + [](DWORD pid) { + return std::ranges::find( + EGS_LAUNCHER_PROCNAMES_, + glossi_util::GetProcName(pid)) == EGS_LAUNCHER_PROCNAMES_.end(); + })) { + res.push_back(pid); + } + } + else { + std::ranges::copy(pids_.begin(), pids_.end(), + std::back_inserter(res)); + } pid_mutex_.unlock(); return res; } diff --git a/GlosSITarget/Settings.h b/GlosSITarget/Settings.h index dfe0eb6..9227b1f 100644 --- a/GlosSITarget/Settings.h +++ b/GlosSITarget/Settings.h @@ -63,6 +63,7 @@ inline struct Common { bool no_uwp_overlay = false; bool disable_watchdog = false; bool extendedLogging = false; + bool ignoreEGS = true; std::wstring name; std::wstring icon; int version; @@ -194,6 +195,7 @@ inline void Parse(const nlohmann::basic_json<>& json) safeWStringParse(json, "name", common.name); safeWStringParse(json, "icon", common.icon); safeParseValue(json, "version", common.version); + safeParseValue(json, "ignoreEGS", common.ignoreEGS); if (launch.launch) { launch.isUWP = checkIsUwp(launch.launchPath); @@ -213,6 +215,9 @@ inline void Parse(const std::vector& args) else if (arg == L"-disablewatchdog") { common.disable_watchdog = true; } + else if (arg == L"-ignoreegs") { + common.ignoreEGS = true; + } else { configName += L" " + std::wstring(arg.begin(), arg.end()); } From cafb4e85a8030e7196d1cde44d157c9bfc2fd267 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Tue, 11 Oct 2022 22:34:28 +0200 Subject: [PATCH 28/44] GlosSITarget: Add API to insert launched-pids --- GlosSITarget/AppLauncher.cpp | 14 ++++++++++++++ GlosSITarget/AppLauncher.h | 1 + GlosSITarget/HttpServer.cpp | 30 ++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index 1e81d6a..7cf9179 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -176,6 +176,20 @@ std::vector AppLauncher::launchedPids() return res; } +void AppLauncher::addPids(const std::vector& pids) +{ + pid_mutex_.lock(); + for (const auto pid : pids) { + if (pid > 0 && std::ranges::find(pids_, pid) == pids_.end()) { + if (Settings::common.extendedLogging) { + spdlog::debug("Added PID {} via API", pid); + } + pids_.push_back(pid); + } + } + pid_mutex_.unlock(); +} + #ifdef _WIN32 bool AppLauncher::IsProcessRunning(DWORD pid) { diff --git a/GlosSITarget/AppLauncher.h b/GlosSITarget/AppLauncher.h index 84563b9..df4032e 100644 --- a/GlosSITarget/AppLauncher.h +++ b/GlosSITarget/AppLauncher.h @@ -38,6 +38,7 @@ class AppLauncher { void close(); std::vector launchedPids(); + void addPids(const std::vector& pids); private: std::function shutdown_; diff --git a/GlosSITarget/HttpServer.cpp b/GlosSITarget/HttpServer.cpp index 85466e1..c3acd0d 100644 --- a/GlosSITarget/HttpServer.cpp +++ b/GlosSITarget/HttpServer.cpp @@ -32,6 +32,36 @@ void HttpServer::run() res.set_content(j.dump(), "text/json"); }); + server_.Post("/launched-pids", [this](const httplib::Request& req, httplib::Response& res) { + try { + const nlohmann::json postbody = nlohmann::json::parse(req.body); + app_launcher_.addPids(postbody.get>()); + } catch (std::exception& e) { + res.status = 401; + res.set_content(nlohmann::json{ + {"code", 401}, + {"name", "Bad Request"}, + {"message", e.what()}, + } + .dump(), + "text/json"); + return; + } + catch (...) { + res.status = 500; + res.set_content(nlohmann::json{ + {"code", 500}, + {"name", "Internal Server Error"}, + {"message", "Unknown Error"}, + } + .dump(), + "text/json"); + return; + } + const nlohmann::json j = app_launcher_.launchedPids(); + res.set_content(j.dump(), "text/json"); + }); + server_.Get("/settings", [this](const httplib::Request& req, httplib::Response& res) { res.set_content(Settings::toJson().dump(), "text/json"); }); From 1a22423f56ee0a3561933e66c3aed122539ec250 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Wed, 12 Oct 2022 13:00:10 +0200 Subject: [PATCH 29/44] GlosSIConfig: Add "killEGS" settings --- GlosSIConfig/qml/AdvancedTargetSettings.qml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/GlosSIConfig/qml/AdvancedTargetSettings.qml b/GlosSIConfig/qml/AdvancedTargetSettings.qml index 935095d..29b46a2 100644 --- a/GlosSIConfig/qml/AdvancedTargetSettings.qml +++ b/GlosSIConfig/qml/AdvancedTargetSettings.qml @@ -31,8 +31,9 @@ CollapsiblePane { content: Column { - spacing: 16 - + spacing: 16 + id: contentColumn + height: subTitleLabel.height + 16 + advancedLaunchPane.height + 16 + deviceWindowRow.height + 16 + commonPane.height Label { id: subTitleLabel width: parent.width @@ -42,6 +43,7 @@ CollapsiblePane { } RPane { + id: advancedLaunchPane width: parent.width radius: 4 Material.elevation: 32 @@ -98,6 +100,16 @@ CollapsiblePane { shortcutInfo.ignoreEGS = checked } } + CheckBox { + height: subTitle != "" || (shortcutInfo.launch.launchPath || "").includes("epicgames.launcher") ? 32 : 0 + visible: subTitle != "" || (shortcutInfo.launch.launchPath || "").includes("epicgames.launcher") + id: killEGS + text: qsTr("Kill EpicGamesLauncher process on exit") + checked: shortcutInfo.killEGS + onCheckedChanged: function(){ + shortcutInfo.killEGS = checked + } + } } Column { spacing: 2 @@ -122,6 +134,7 @@ CollapsiblePane { Row { spacing: 16 width: parent.width + id: deviceWindowRow RPane { width: parent.width / 2 - 8 @@ -467,6 +480,7 @@ CollapsiblePane { radius: 4 Material.elevation: 32 bgOpacity: 0.97 + id: commonPane Column { spacing: 4 Row { From e56a53a0b73f592602c59d07bf0daac06cbc4ad0 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Wed, 12 Oct 2022 13:00:38 +0200 Subject: [PATCH 30/44] GlosSIConfig: auto-migrate (default) settings --- GlosSIConfig/UIModel.cpp | 104 ++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/GlosSIConfig/UIModel.cpp b/GlosSIConfig/UIModel.cpp index d0d2ff2..24e08f3 100644 --- a/GlosSIConfig/UIModel.cpp +++ b/GlosSIConfig/UIModel.cpp @@ -328,61 +328,64 @@ QVariantMap UIModel::getDefaultConf() const path /= "GlosSI"; path /= "default.json"; - if (std::filesystem::exists(path)) { - QFile file(QString::fromStdWString(path)); - if (file.open(QIODevice::ReadOnly)) { - const auto data = file.readAll(); - file.close(); - return QJsonDocument::fromJson(data).object().toVariantMap(); - } - } - - QJsonObject obj = { + QJsonObject defaults = { {"icon", QJsonValue::Null}, {"name", QJsonValue::Null}, {"version", 1}, {"extendedLogging", false}, {"snapshotNotify", false}, {"ignoreEGS", true}, - { - "controller", - QJsonObject{ - {"maxControllers", 1}, - {"emulateDS4", false}, - {"allowDesktopConfig", false} - } - }, - { - "devices", - QJsonObject{ - {"hideDevices", true}, - {"realDeviceIds", false}, - } - }, - { - "launch", - QJsonObject{ - {"closeOnExit", true}, - {"launch", false}, - {"launchAppArgs", QJsonValue::Null}, - {"launchPath", QJsonValue::Null}, - {"waitForChildProcs", true}, - } - }, - { - "window", - QJsonObject{ - {"disableOverlay", false}, - {"maxFps", QJsonValue::Null}, - {"scale", QJsonValue::Null}, - {"windowMode", false}, - } - }, + {"killEGS", false}, + {"controller", QJsonObject{{"maxControllers", 1}, {"emulateDS4", false}, {"allowDesktopConfig", false}}}, + {"devices", + QJsonObject{ + {"hideDevices", true}, + {"realDeviceIds", false}, + }}, + {"launch", + QJsonObject{ + {"closeOnExit", true}, + {"launch", false}, + {"launchAppArgs", QJsonValue::Null}, + {"launchPath", QJsonValue::Null}, + {"waitForChildProcs", true}, + }}, + {"window", + QJsonObject{ + {"disableOverlay", false}, + {"maxFps", QJsonValue::Null}, + {"scale", QJsonValue::Null}, + {"windowMode", false}, + }}, }; - saveDefaultConf(obj.toVariantMap()); + if (std::filesystem::exists(path)) { + QFile file(QString::fromStdWString(path)); + if (file.open(QIODevice::ReadOnly)) { + const auto data = file.readAll(); + file.close(); + auto json = QJsonDocument::fromJson(data).object(); + + const auto applyDefaults = [](QJsonObject obj, const QJsonObject& defaults, + auto applyDefaultsFn) -> QJsonObject { + for (const auto& key : defaults.keys()) { + qDebug() << key << ": " << obj[key]; + if ((obj[key].isUndefined() || obj[key].isNull()) && !defaults[key].isNull()) { + obj[key] = defaults.value(key); + } + if (obj.value(key).isObject()) { + obj[key] = applyDefaultsFn(obj[key].toObject(), defaults.value(key).toObject(), applyDefaultsFn); + } + } + return obj; + }; + json = applyDefaults(json, defaults, applyDefaults); + return json.toVariantMap(); + } + } + + saveDefaultConf(defaults.toVariantMap()); return getDefaultConf(); - } void UIModel::saveDefaultConf(QVariantMap conf) const @@ -497,9 +500,7 @@ void UIModel::onAvailFilesResponse(QNetworkReply* reply) const auto defaultConf = getDefaultConf(); bool snapshotNotify = - defaultConf.contains("snapshotNotify") - ? defaultConf["snapshotNotify"].toJsonValue().toBool() - : false; + defaultConf.contains("snapshotNotify") ? defaultConf["snapshotNotify"].toJsonValue().toBool() : false; struct VersionInfo { int major; @@ -512,8 +513,9 @@ void UIModel::onAvailFilesResponse(QNetworkReply* reply) std::vector> new_versions; for (const auto& info : json.keys() | std::ranges::views::filter([this, &json, snapshotNotify](const auto& key) { - return notify_on_snapshots_ ? true - : json[key].toObject().value("type") == (snapshotNotify ? "snapshot" : "release"); + return notify_on_snapshots_ + ? true + : json[key].toObject().value("type") == (snapshotNotify ? "snapshot" : "release"); }) | std::ranges::views::transform([&json](const auto& key) -> std::pair { const auto versionString = json[key].toObject().value("version").toString(); const auto cleanVersion = versionString.split("-")[0]; From 929abfe5f2ce44178526ec291831b8b67113eef4 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Wed, 12 Oct 2022 13:06:26 +0200 Subject: [PATCH 31/44] GlosSITarget: Parse and impl "killEGS" --- GlosSITarget/AppLauncher.cpp | 4 ++-- GlosSITarget/Settings.h | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index 7cf9179..71dae27 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -122,7 +122,7 @@ void AppLauncher::update() egs_has_launched_game_ = true; } if (Settings::launch.closeOnExit && Settings::launch.launch) { - if (was_egs_launch_ && Settings::common.ignoreEGS) { + if (was_egs_launch_ && (Settings::common.ignoreEGS || Settings::common.killEGS)) { if (egs_has_launched_game_ && filtered_pids.empty()) { spdlog::info("Configured to close on all children exit. Shutting down after game launched via EGS quit..."); shutdown_(); @@ -158,7 +158,7 @@ std::vector AppLauncher::launchedPids() pid_mutex_.lock(); std::vector res; res.reserve(pids_.size()); - if (Settings::common.ignoreEGS) { + if (!Settings::common.killEGS && Settings::common.ignoreEGS) { for (const auto& pid : pids_ | std::ranges::views::filter( [](DWORD pid) { return std::ranges::find( diff --git a/GlosSITarget/Settings.h b/GlosSITarget/Settings.h index 9227b1f..32b505b 100644 --- a/GlosSITarget/Settings.h +++ b/GlosSITarget/Settings.h @@ -64,6 +64,7 @@ inline struct Common { bool disable_watchdog = false; bool extendedLogging = false; bool ignoreEGS = true; + bool killEGS = false; std::wstring name; std::wstring icon; int version; @@ -196,6 +197,7 @@ inline void Parse(const nlohmann::basic_json<>& json) safeWStringParse(json, "icon", common.icon); safeParseValue(json, "version", common.version); safeParseValue(json, "ignoreEGS", common.ignoreEGS); + safeParseValue(json, "killEGS", common.killEGS); if (launch.launch) { launch.isUWP = checkIsUwp(launch.launchPath); From b568418891a76a9d75bc860a6121e2ef8e56fc06 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Fri, 14 Oct 2022 22:45:24 +0200 Subject: [PATCH 32/44] GlosSIConfig: Find and launch EGS games --- GlosSIConfig/GlosSIConfig.vcxproj | 1 + GlosSIConfig/GlosSIConfig.vcxproj.filters | 3 + GlosSIConfig/Resource.rc | 168 +++++++++++++++- GlosSIConfig/UIModel.cpp | 25 +++ GlosSIConfig/UIModel.h | 5 + GlosSIConfig/qml.qrc | 1 + GlosSIConfig/qml/AddSelectTypeDialog.qml | 11 +- GlosSIConfig/qml/EGSSelectDialog.qml | 230 ++++++++++++++++++++++ GlosSIConfig/qml/ShortcutProps.qml | 16 ++ GlosSIConfig/qml/main.qml | 3 + 10 files changed, 458 insertions(+), 5 deletions(-) create mode 100644 GlosSIConfig/qml/EGSSelectDialog.qml diff --git a/GlosSIConfig/GlosSIConfig.vcxproj b/GlosSIConfig/GlosSIConfig.vcxproj index 0ba8142..b649a7b 100644 --- a/GlosSIConfig/GlosSIConfig.vcxproj +++ b/GlosSIConfig/GlosSIConfig.vcxproj @@ -143,6 +143,7 @@ + diff --git a/GlosSIConfig/GlosSIConfig.vcxproj.filters b/GlosSIConfig/GlosSIConfig.vcxproj.filters index f7483fd..08d81b8 100644 --- a/GlosSIConfig/GlosSIConfig.vcxproj.filters +++ b/GlosSIConfig/GlosSIConfig.vcxproj.filters @@ -80,6 +80,9 @@ qml + + qml + diff --git a/GlosSIConfig/Resource.rc b/GlosSIConfig/Resource.rc index 9f6cc17..73c56a6 100644 --- a/GlosSIConfig/Resource.rc +++ b/GlosSIConfig/Resource.rc @@ -51,8 +51,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,0,9,1037005000102 - PRODUCTVERSION 0,0,9,1037005000102 + FILEVERSION 0,1,0,2033009290000 + PRODUCTVERSION 0,1,0,2033009290000 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -69,12 +69,12 @@ BEGIN BEGIN VALUE "CompanyName", "Peter Repukat - FlatspotSoftware" VALUE "FileDescription", "GlosSI - Config" - VALUE "FileVersion", "0.0.9.1-37-g5ebe102" + VALUE "FileVersion", "0.1.0.2-33-g929abfe" VALUE "InternalName", "GlosSIConfig" VALUE "LegalCopyright", "Copyright (C) 2021 Peter Repukat - FlatspotSoftware" VALUE "OriginalFilename", "GlosSIConfig.exe" VALUE "ProductName", "GlosSI" - VALUE "ProductVersion", "0.0.9.1-37-g5ebe102" + VALUE "ProductVersion", "0.1.0.2-33-g929abfe" END END BLOCK "VarFileInfo" @@ -1092,6 +1092,166 @@ IDI_ICON1 ICON "..\GlosSI_Icon.ico" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GlosSIConfig/UIModel.cpp b/GlosSIConfig/UIModel.cpp index 24e08f3..f4ba80a 100644 --- a/GlosSIConfig/UIModel.cpp +++ b/GlosSIConfig/UIModel.cpp @@ -416,6 +416,31 @@ void UIModel::saveDefaultConf(QVariantMap conf) const QVariantList UIModel::uwpApps() { return UWPFetch::UWPAppList(); } #endif +QVariantList UIModel::egsGamesList() const +{ + wchar_t* program_data_path_str; + std::filesystem::path path; + if (SHGetKnownFolderPath(FOLDERID_ProgramData, KF_FLAG_CREATE, NULL, &program_data_path_str) != S_OK) { + qDebug() << "Couldn't get ProgramDataPath"; + return {{"InstallLocation", "Error"}}; + } + path = std::filesystem::path(program_data_path_str); + path /= egs_games_json_path_; + + QFile file(path); + if (file.open(QIODevice::ReadOnly)) { + const auto data = file.readAll(); + file.close(); + auto json = QJsonDocument::fromJson(data).object(); + if (json["InstallationList"].isArray()) { + return json["InstallationList"].toVariant().toList(); + } + qDebug() << "InstallationList does not exist!"; + } + qDebug() << "Couldn't read EGS LauncherInstalled.dat " << path; + return {{"InstallLocation", "Error"}}; +} + bool UIModel::writeShortcutsVDF(const std::wstring& mode, const std::wstring& name, const std::wstring& shortcutspath, bool is_admin_try) const { diff --git a/GlosSIConfig/UIModel.h b/GlosSIConfig/UIModel.h index 8943d27..96eae0d 100644 --- a/GlosSIConfig/UIModel.h +++ b/GlosSIConfig/UIModel.h @@ -29,6 +29,7 @@ class UIModel : public QObject { Q_PROPERTY(bool hasAcrlyicEffect READ hasAcrylicEffect NOTIFY acrylicChanged) Q_PROPERTY(QVariantList targetList READ getTargetList NOTIFY targetListChanged) Q_PROPERTY(QVariantList uwpList READ uwpApps CONSTANT) + Q_PROPERTY(QVariantList egsList READ egsGamesList CONSTANT) Q_PROPERTY(bool foundSteam READ foundSteam CONSTANT) Q_PROPERTY(bool steamInputXboxSupportEnabled READ isSteamInputXboxSupportEnabled CONSTANT) @@ -60,6 +61,7 @@ class UIModel : public QObject { #ifdef _WIN32 Q_INVOKABLE QVariantList uwpApps(); #endif + Q_INVOKABLE QVariantList egsGamesList() const; [[nodiscard]] bool writeShortcutsVDF(const std::wstring& mode, const std::wstring& name, const std::wstring& shortcutspath, bool is_admin_try = false) const; @@ -92,6 +94,9 @@ class UIModel : public QObject { QString user_data_path_ = "/userdata/"; QString steam_executable_name_ = "steam.exe"; + const std::wstring_view egs_games_json_path_ = + L"Epic/UnrealEngineLauncher/LauncherInstalled.dat"; + QVariantList targets_; QString new_version_name_; diff --git a/GlosSIConfig/qml.qrc b/GlosSIConfig/qml.qrc index d3945e1..c9a5eaa 100644 --- a/GlosSIConfig/qml.qrc +++ b/GlosSIConfig/qml.qrc @@ -22,5 +22,6 @@ qml/AdvancedTargetSettings.qml qml/GlobalConf.qml svg/settings_fill_white_24dp.svg + qml/EGSSelectDialog.qml diff --git a/GlosSIConfig/qml/AddSelectTypeDialog.qml b/GlosSIConfig/qml/AddSelectTypeDialog.qml index 8ecc10e..42b36a3 100644 --- a/GlosSIConfig/qml/AddSelectTypeDialog.qml +++ b/GlosSIConfig/qml/AddSelectTypeDialog.qml @@ -89,7 +89,7 @@ Dialog { } } Button { - visible: uiModel.isWindows + visible: uiModel.isWindows text: qsTr("UWP App") highlighted: true onClicked: function(){ @@ -97,6 +97,15 @@ Dialog { confirmed("uwp") } } + Button { + visible: uiModel.isWindows + text: qsTr("EGS Game") + highlighted: true + onClicked: function(){ + close() + confirmed("egs") + } + } } } diff --git a/GlosSIConfig/qml/EGSSelectDialog.qml b/GlosSIConfig/qml/EGSSelectDialog.qml new file mode 100644 index 0000000..5fce0bd --- /dev/null +++ b/GlosSIConfig/qml/EGSSelectDialog.qml @@ -0,0 +1,230 @@ +/* +Copyright 2021-2022 Peter Repukat - FlatspotSoftware + +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. +*/ +import QtQuick 6.2 +import QtQuick.Controls 6.2 +import QtQuick.Layouts 6.2 +import QtQuick.Controls.Material 6.2 + +Dialog { + id: dlg + anchors.centerIn: parent + + signal confirmed(var param) + + visible: false + modal: true + dim: true + parent: Overlay.overlay + Overlay.modal: Rectangle { + color: Qt.rgba(0,0,0,0.4) + opacity: backdropOpacity + Behavior on opacity { + NumberAnimation { + duration: 300 + } + } + } + property real backdropOpacity: 1.0 + + property var unfilteredModel: null; + property var filteredModel: []; + + + onOpened: function() { + unfilteredModel = null; + unfilteredModel = uiModel.egsList; + listview.model = null; + filteredModel = []; + for(let i = 0; i < unfilteredModel.length; i++) + { + filteredModel.push(unfilteredModel[i]) + } + listview.model = filteredModel + } + + onClosed: function() { + listview.model = null; + unfilteredModel = null; + filteredModel = null; + } + + enter: Transition { + NumberAnimation{target: content; property: "y"; from: parent.height; to: 16; duration: 300; easing.type: Easing.OutQuad } + NumberAnimation{target: background; property: "y"; from: parent.height; to: 0; duration: 300; easing.type: Easing.OutQuad } + NumberAnimation{target: dlg; property: "backdropOpacity"; from: 0; to: 1; duration: 300; easing.type: Easing.OutQuad } + } + + exit: Transition { + NumberAnimation{target: content; property: "y"; from: 16; to: parent.height; duration: 300; easing.type: Easing.InQuad } + NumberAnimation{target: background; property: "y"; from: 0; to: parent.height; duration: 300; easing.type: Easing.InQuad } + NumberAnimation{target: dlg; property: "backdropOpacity"; from: 1; to: 0; duration: 300; easing.type: Easing.InQuad } + } + + background: RPane { + id: background + radius: 4 + Material.elevation: 64 + bgOpacity: 0.97 + } + contentItem: Item { + id: content + implicitWidth: listview.width + implicitHeight: listview.height + titlelabel.height + 16 + 64 + clip: true + Label { + id: titlelabel + text: qsTr("Select Epic Games Launcher Game...") + font.pixelSize: 24 + font.bold: true + } + + FluentTextInput { + width: listview.width - 2 + x: 1 + anchors.top: titlelabel.bottom + anchors.topMargin: 8 + id: searchBar + enabled: true + placeholderText: qsTr("Search...") + text: "" + onTextChanged: function() { + listview.model = null; + filteredModel = []; + for(let i = 0; i < unfilteredModel.length; i++) + { + if(unfilteredModel[i].AppName.toLowerCase().includes(searchBar.text.toLowerCase())) { + filteredModel.push(unfilteredModel[i]) + } + } + listview.model = filteredModel + } + } + + BusyIndicator { + running: visible + anchors.centerIn: parent + opacity: (!unfilteredModel || unfilteredModel.length == 0) ? 1 : 0 + Behavior on opacity { + NumberAnimation { + duration: 350 + easing.type: Easing.InOutQuad + } + } + visible: opacity == 0 ? false : true + } + + Button { + anchors.right: parent.right + anchors.top: listview.bottom + anchors.topMargin: 16 + anchors.rightMargin: 2 + text: qsTr("Cancel") + onClicked: dlg.close() + } + + ListView { + anchors.top: searchBar.bottom + anchors.topMargin: 16 + id: listview + width: window.width * 0.45 + height: window.height * 0.66 + spacing: 0 + clip: true + model: filteredModel + ScrollBar.vertical: ScrollBar { + } + + opacity: (!unfilteredModel || unfilteredModel.length == 0) ? 0 : 1 + Behavior on opacity { + ParallelAnimation { + NumberAnimation { + duration: 350 + easing.type: Easing.InOutQuad + } + PropertyAnimation { + target: listview + property: "anchors.topMargin" + from: window.height * 0.75 + to: 16 + duration: 350 + easing.type: Easing.InOutQuad + } + } + } + + + delegate: Item { + width: listview.width + height: textcolumn.implicitHeight > 72 ? 500 : 72 + + /*Image { + id: maybeIcon + width: textcolumn.implicitHeight > 72 ? 0 : 56 + height: 56 + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + source: "file:///" + modelData.IconPath + mipmap: true + smooth: true + }*/ + + Column { + id: textcolumn + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.verticalCenter: parent.verticalCenter + spacing: 2 + Label { + text: modelData.InstallLocation.split('/').pop().split('\\').pop() + font.pixelSize: 18 + font.bold: true + } + Label { + id: appNameLabel + text: "AppName: " + modelData.AppName + font.pixelSize: 12 + wrapMode: Text.WordWrap + width: parent.width + } + Label { + id: fullPathLabel + text: modelData.InstallLocation + font.pixelSize: 12 + color: '#888888' + wrapMode: Text.WordWrap + width: parent.width + } + } + + Rectangle { + anchors.bottom: parent.bottom + height: 1 + width: parent.width + color: Qt.rgba(1,1,1,0.25) + } + + MouseArea { + anchors.fill: parent + onClicked: function(){ + confirmed(modelData) + dlg.close(); + } + } + } + } + } +} \ No newline at end of file diff --git a/GlosSIConfig/qml/ShortcutProps.qml b/GlosSIConfig/qml/ShortcutProps.qml index ecf7b3a..30862f5 100644 --- a/GlosSIConfig/qml/ShortcutProps.qml +++ b/GlosSIConfig/qml/ShortcutProps.qml @@ -26,6 +26,7 @@ Item { property alias fileDialog: fileDialog property alias uwpSelectDialog: uwpSelectDialog + property alias egsSelectDialog: egsSelectDialog signal cancel() signal done(var shortcut) @@ -309,6 +310,21 @@ Item { launchApp.checked = true } } + EGSSelectDialog { + id: egsSelectDialog + onConfirmed: function(modelData) { + if (nameInput.text == "") { + nameInput.text = modelData.InstallLocation.split('/').pop().split('\\').pop() + } + pathInput.text = "com.epicgames.launcher://apps/" + + modelData.NamespaceId + + "%3A" + + modelData.ItemId + + "%3A" + + modelData.ArtifactId + "?action=launch&silent=true" + launchApp.checked = true + } + } InfoDialog { id: helpInfoDialog diff --git a/GlosSIConfig/qml/main.qml b/GlosSIConfig/qml/main.qml index 3894e96..579cdfb 100644 --- a/GlosSIConfig/qml/main.qml +++ b/GlosSIConfig/qml/main.qml @@ -387,6 +387,9 @@ Window { if (param == "uwp") { props.uwpSelectDialog.open(); } + if (param == "egs") { + props.egsSelectDialog.open(); + } } } } From 01efd8d02d37073fd27ecfd3e94eb0996620ed1f Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Sat, 15 Oct 2022 14:54:10 +0200 Subject: [PATCH 33/44] GlosSIConfig: QML: change shortcut Icon --- GlosSIConfig/Resource.rc | 8 +-- GlosSIConfig/qml.qrc | 1 + GlosSIConfig/qml/ShortcutCards.qml | 3 +- GlosSIConfig/qml/ShortcutProps.qml | 52 ++++++++++++++----- .../svg/add_photo_alternate_white_24dp.svg | 1 + 5 files changed, 46 insertions(+), 19 deletions(-) create mode 100644 GlosSIConfig/svg/add_photo_alternate_white_24dp.svg diff --git a/GlosSIConfig/Resource.rc b/GlosSIConfig/Resource.rc index 73c56a6..713a137 100644 --- a/GlosSIConfig/Resource.rc +++ b/GlosSIConfig/Resource.rc @@ -51,8 +51,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,1,0,2033009290000 - PRODUCTVERSION 0,1,0,2033009290000 + FILEVERSION 0,1,0,2034000568418 + PRODUCTVERSION 0,1,0,2034000568418 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -69,12 +69,12 @@ BEGIN BEGIN VALUE "CompanyName", "Peter Repukat - FlatspotSoftware" VALUE "FileDescription", "GlosSI - Config" - VALUE "FileVersion", "0.1.0.2-33-g929abfe" + VALUE "FileVersion", "0.1.0.2-34-gb568418" VALUE "InternalName", "GlosSIConfig" VALUE "LegalCopyright", "Copyright (C) 2021 Peter Repukat - FlatspotSoftware" VALUE "OriginalFilename", "GlosSIConfig.exe" VALUE "ProductName", "GlosSI" - VALUE "ProductVersion", "0.1.0.2-33-g929abfe" + VALUE "ProductVersion", "0.1.0.2-34-gb568418" END END BLOCK "VarFileInfo" diff --git a/GlosSIConfig/qml.qrc b/GlosSIConfig/qml.qrc index c9a5eaa..64cc986 100644 --- a/GlosSIConfig/qml.qrc +++ b/GlosSIConfig/qml.qrc @@ -23,5 +23,6 @@ qml/GlobalConf.qml svg/settings_fill_white_24dp.svg qml/EGSSelectDialog.qml + svg/add_photo_alternate_white_24dp.svg diff --git a/GlosSIConfig/qml/ShortcutCards.qml b/GlosSIConfig/qml/ShortcutCards.qml index 85593a2..830514b 100644 --- a/GlosSIConfig/qml/ShortcutCards.qml +++ b/GlosSIConfig/qml/ShortcutCards.qml @@ -215,10 +215,9 @@ GridView { ? modelData.icon.endsWith(".exe") ? "image://exe/" + modelData.icon : "file:///" + modelData.icon - : null + : 'qrc:/svg/add_photo_alternate_white_24dp.svg' width: 48 height: 48 - visible: !!modelData.icon } Label { diff --git a/GlosSIConfig/qml/ShortcutProps.qml b/GlosSIConfig/qml/ShortcutProps.qml index 30862f5..784fbc1 100644 --- a/GlosSIConfig/qml/ShortcutProps.qml +++ b/GlosSIConfig/qml/ShortcutProps.qml @@ -53,6 +53,14 @@ Item { if (advancedTargetSettings) { // advanced settings (collapsible container) advancedTargetSettings.shortcutInfo = shortcutInfo; } + if (maybeIcon) { + console.log("meh"); + maybeIcon.source = shortcutInfo.icon + ? shortcutInfo.icon.endsWith(".exe") + ? "image://exe/" + shortcutInfo.icon + : "file:///" + shortcutInfo.icon + : 'qrc:/svg/add_photo_alternate_white_24dp.svg'; + } } Flickable { @@ -153,24 +161,27 @@ Item { spacing: 4 anchors.left: parent.left anchors.right: parent.right - anchors.leftMargin: 32 - anchors.rightMargin: 32 - Image { - id: maybeIcon - source: shortcutInfo.icon - ? shortcutInfo.icon.endsWith(".exe") - ? "image://exe/" + shortcutInfo.icon - : "file:///" + shortcutInfo.icon - : '' - Layout.preferredWidth: 48 - Layout.preferredHeight: 48 - visible: shortcutInfo.icon + anchors.leftMargin: 8 + anchors.rightMargin: 16 + Button { + id: iconButton + Layout.preferredWidth: 56 + Layout.preferredHeight: 64 Layout.alignment: Qt.AlignVCenter + flat: true + contentItem: Image { + id: maybeIcon + source: shortcutInfo.icon + ? shortcutInfo.icon.endsWith(".exe") + ? "image://exe/" + shortcutInfo.icon + : "file:///" + shortcutInfo.icon + : 'qrc:/svg/add_photo_alternate_white_24dp.svg' + } + onClicked: iconFileDialog.open() } Item { Layout.preferredWidth: 8 Layout.preferredHeight: 8 - visible: shortcutInfo.icon } Item { Layout.preferredWidth: parent.width / 2 @@ -296,6 +307,21 @@ Item { } } + + FileDialog { + id: iconFileDialog + title: qsTr("Please choose an icon") + nameFilters: uiModel.isWindows ? ["Image/Executable (*.exe *.png *.ico *.jpg)"] : ["Image (*.png *.ico *.jpg)"] + onAccepted: { + if (iconFileDialog.selectedFile != null) { + shortcutInfo.icon = iconFileDialog.selectedFile.toString().replace("file:///", "") + shortcutInfo = shortcutInfo; + } + } + onRejected: { + + } + } UWPSelectDialog { id: uwpSelectDialog diff --git a/GlosSIConfig/svg/add_photo_alternate_white_24dp.svg b/GlosSIConfig/svg/add_photo_alternate_white_24dp.svg new file mode 100644 index 0000000..927af10 --- /dev/null +++ b/GlosSIConfig/svg/add_photo_alternate_white_24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file From be48ffee402dafab7379db78f16834bb9cc35d81 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Sat, 15 Oct 2022 15:50:04 +0200 Subject: [PATCH 34/44] Update SteamShortcuts on target update --- GlosSIConfig/UIModel.cpp | 25 ++++++++++++++++++++----- GlosSIConfig/UIModel.h | 2 +- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/GlosSIConfig/UIModel.cpp b/GlosSIConfig/UIModel.cpp index f4ba80a..82c0c4d 100644 --- a/GlosSIConfig/UIModel.cpp +++ b/GlosSIConfig/UIModel.cpp @@ -102,22 +102,37 @@ void UIModel::addTarget(QVariant shortcut) emit targetListChanged(); } -void UIModel::updateTarget(int index, QVariant shortcut) +bool UIModel::updateTarget(int index, QVariant shortcut) { const auto map = shortcut.toMap(); const auto json = QJsonObject::fromVariantMap(map); + auto oldSteamName = targets_[index].toMap()["name"].toString(); auto oldName = targets_[index].toMap()["name"].toString().replace(QRegularExpression("[\\\\/:*?\"<>|]"), "") + ".json"; - auto path = config_path_; - path /= config_dir_name_.toStdString(); - path /= (oldName).toStdString(); - std::filesystem::remove(path); + auto oldPath = config_path_; + oldPath /= config_dir_name_.toStdString(); + oldPath /= (oldName).toStdString(); + std::filesystem::remove(oldPath); writeTarget(json, map["name"].toString()); targets_.replace(index, QJsonDocument(json).toVariant()); emit targetListChanged(); + + auto path = config_path_; + path /= config_dir_name_.toStdString(); + path /= (map["name"].toString()).toStdString(); + + if (removeFromSteam(oldSteamName, QString::fromStdWString(path.wstring()))) { + if (!addToSteam(shortcut, QString::fromStdWString(path.wstring()))) { + qDebug() << "Couldn't add shortcut \"" << (map["name"].toString()) << "\" to Steam when updating"; + return false; + } + return true; + } + qDebug() << "Couldn't remove shortcut \"" << oldName << "\" from Steam when updating"; + return false; } void UIModel::deleteTarget(int index) diff --git a/GlosSIConfig/UIModel.h b/GlosSIConfig/UIModel.h index 96eae0d..ba38577 100644 --- a/GlosSIConfig/UIModel.h +++ b/GlosSIConfig/UIModel.h @@ -42,7 +42,7 @@ class UIModel : public QObject { Q_INVOKABLE void readTargetConfigs(); Q_INVOKABLE QVariantList getTargetList() const; Q_INVOKABLE void addTarget(QVariant shortcut); - Q_INVOKABLE void updateTarget(int index, QVariant shortcut); + Q_INVOKABLE bool updateTarget(int index, QVariant shortcut); Q_INVOKABLE void deleteTarget(int index); Q_INVOKABLE bool isInSteam(QVariant shortcut); Q_INVOKABLE bool addToSteam(QVariant shortcut, const QString& shortcutspath, bool from_cmd = false); From c4f238292804b957f84a5e52e5fee7cd4c81f91a Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Sat, 15 Oct 2022 15:50:40 +0200 Subject: [PATCH 35/44] refactor/fix stupid qml shit --- GlosSIConfig/Resource.rc | 8 +- GlosSIConfig/UIModel.cpp | 6 + GlosSIConfig/qml/AddSelectTypeDialog.qml | 3 + GlosSIConfig/qml/InfoDialog.qml | 3 + GlosSIConfig/qml/ShortcutCards.qml | 140 +-------------------- GlosSIConfig/qml/ShortcutProps.qml | 20 +-- GlosSIConfig/qml/SteamNotFoundDialog.qml | 14 +-- GlosSIConfig/qml/main.qml | 148 ++++++++++++++++++++++- 8 files changed, 184 insertions(+), 158 deletions(-) diff --git a/GlosSIConfig/Resource.rc b/GlosSIConfig/Resource.rc index 713a137..dc73243 100644 --- a/GlosSIConfig/Resource.rc +++ b/GlosSIConfig/Resource.rc @@ -51,8 +51,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,1,0,2034000568418 - PRODUCTVERSION 0,1,0,2034000568418 + FILEVERSION 0,1,0,2035000100080 + PRODUCTVERSION 0,1,0,2035000100080 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -69,12 +69,12 @@ BEGIN BEGIN VALUE "CompanyName", "Peter Repukat - FlatspotSoftware" VALUE "FileDescription", "GlosSI - Config" - VALUE "FileVersion", "0.1.0.2-34-gb568418" + VALUE "FileVersion", "0.1.0.2-35-g01efd8d" VALUE "InternalName", "GlosSIConfig" VALUE "LegalCopyright", "Copyright (C) 2021 Peter Repukat - FlatspotSoftware" VALUE "OriginalFilename", "GlosSIConfig.exe" VALUE "ProductName", "GlosSI" - VALUE "ProductVersion", "0.1.0.2-34-gb568418" + VALUE "ProductVersion", "0.1.0.2-35-g01efd8d" END END BLOCK "VarFileInfo" diff --git a/GlosSIConfig/UIModel.cpp b/GlosSIConfig/UIModel.cpp index 82c0c4d..ee5ad7e 100644 --- a/GlosSIConfig/UIModel.cpp +++ b/GlosSIConfig/UIModel.cpp @@ -20,6 +20,7 @@ limitations under the License. #include #include #include +#include #include @@ -60,6 +61,11 @@ UIModel::UIModel() : QObject(nullptr) parseShortcutVDF(); readTargetConfigs(); updateCheck(); + + auto font = QGuiApplication::font(); + font.setPointSize(11); + font.setFamily("Roboto"); + QGuiApplication::setFont(font); } void UIModel::readTargetConfigs() diff --git a/GlosSIConfig/qml/AddSelectTypeDialog.qml b/GlosSIConfig/qml/AddSelectTypeDialog.qml index 42b36a3..857981b 100644 --- a/GlosSIConfig/qml/AddSelectTypeDialog.qml +++ b/GlosSIConfig/qml/AddSelectTypeDialog.qml @@ -15,6 +15,9 @@ limitations under the License. */ import QtQuick 6.2 import QtQuick.Controls 6.2 +import QtQuick.Controls.Material 6.2 +import QtQuick.Dialogs 6.2 + Dialog { id: dlg anchors.centerIn: parent diff --git a/GlosSIConfig/qml/InfoDialog.qml b/GlosSIConfig/qml/InfoDialog.qml index 908e718..f635235 100644 --- a/GlosSIConfig/qml/InfoDialog.qml +++ b/GlosSIConfig/qml/InfoDialog.qml @@ -15,6 +15,9 @@ limitations under the License. */ import QtQuick 6.2 import QtQuick.Controls 6.2 +import QtQuick.Controls.Material 6.2 +import QtQuick.Dialogs 6.2 + Dialog { id: dlg anchors.centerIn: parent diff --git a/GlosSIConfig/qml/ShortcutCards.qml b/GlosSIConfig/qml/ShortcutCards.qml index 830514b..59d5126 100644 --- a/GlosSIConfig/qml/ShortcutCards.qml +++ b/GlosSIConfig/qml/ShortcutCards.qml @@ -13,11 +13,11 @@ 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. */ -import QtQuick 6.2 -import QtQuick.Layouts 6.2 -import QtQuick.Controls 6.2 -import QtQuick.Controls.Material 6.2 -import QtQuick.Dialogs 6.2 +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.Material +import QtQuick.Dialogs import Qt5Compat.GraphicalEffects GridView { @@ -40,8 +40,6 @@ GridView { model: uiModel.targetList; GridView.delayRemove: true - property var manualInfo: null - // TODO: animations only properly work with abstractListModel... grrr... addDisplaced: Transition { NumberAnimation { properties: "x,y"; duration: 300 } @@ -67,134 +65,6 @@ GridView { NumberAnimation { properties: "x,y"; duration: 300; easing.type: Easing.InQuad } } - Dialog { - id: manualAddDialog - anchors.centerIn: parent - visible: false - modal: true - dim: true - parent: Overlay.overlay - Overlay.modal: Rectangle { - color: Qt.rgba(0,0,0,0.4) - opacity: backdropOpacity - Behavior on opacity { - NumberAnimation { - duration: 300 - } - } - } - property real backdropOpacity: 1.0 - enter: Transition { - NumberAnimation{target: madcontent; property: "y"; from: parent.height; to: 16; duration: 300; easing.type: Easing.OutQuad } - NumberAnimation{target: madbackground; property: "y"; from: parent.height; to: 0; duration: 300; easing.type: Easing.OutQuad } - NumberAnimation{target: manualAddDialog; property: "backdropOpacity"; from: 0; to: 1; duration: 300; easing.type: Easing.OutQuad } - } - - exit: Transition { - NumberAnimation{target: madcontent; property: "y"; from: 16; to: parent.height; duration: 300; easing.type: Easing.InQuad } - NumberAnimation{target: madbackground; property: "y"; from: 0; to: parent.height; duration: 300; easing.type: Easing.InQuad } - NumberAnimation{target: manualAddDialog; property: "backdropOpacity"; from: 1; to: 0; duration: 300; easing.type: Easing.InQuad } - } - - background: RPane { - id: madbackground - radius: 4 - Material.elevation: 64 - bgOpacity: 0.97 - } - contentItem: Item { - id: madcontent - implicitWidth: steamscreener.width - implicitHeight: madtext.height + 16 + steamscreener.height + 16 + madrow.height - - Label { - id: madtext - text: qsTr("Add \"GlosSITarget\" as game to Steam and change it's properties (in Steam) to this:") - } - - Image { - anchors.top: madtext.bottom - anchors.left: madtext.left - anchors.topMargin: 16 - id: steamscreener - source: "qrc:/steamscreener.png" - } - - FluentTextInput { - id: madnameinput - text: manualInfo ? manualInfo.name : "" - anchors.top: steamscreener.top - anchors.left: steamscreener.left - anchors.topMargin: 72 - anchors.leftMargin: 92 - readOnly: true - background: Item {} - width: 550 - } - - FluentTextInput { - id: glossiPathInput - text: manualInfo ? manualInfo.launch : "" - anchors.top: steamscreener.top - anchors.left: steamscreener.left - anchors.topMargin: 192 - anchors.leftMargin: 24 - readOnly: true - background: Item {} - width: 550 - } - - FluentTextInput { - id: startDirInput - text: manualInfo ? manualInfo.launchDir : "" - anchors.top: steamscreener.top - anchors.left: steamscreener.left - anchors.topMargin: 266 - anchors.leftMargin: 24 - readOnly: true - background: Item {} - width: 550 - } - - FluentTextInput { - id: launchOptsInput - text: manualInfo ? manualInfo.config : "" - anchors.top: steamscreener.top - anchors.left: steamscreener.left - anchors.topMargin: 432 - anchors.leftMargin: 24 - readOnly: true - background: Item {} - width: 550 - } - - Row { - id: madrow - anchors.top: steamscreener.bottom - anchors.topMargin: 16 - spacing: 16 - - Button { - text: qsTr("OK") - onClicked: function(){ - manualAddDialog.close() - } - } - anchors.right: parent.right - } - } - } - - InfoDialog { - id: writeErrorDialog - titleText: qsTr("Error") - text: qsTr("Error writing shortcuts.vdf...\nPlease make sure Steam is running") - extraButton: true - extraButtonText: qsTr("Manual instructions") - onConfirmedExtra: function(data) { - manualAddDialog.open(); - } - } delegate: RPane { id: delegateRoot diff --git a/GlosSIConfig/qml/ShortcutProps.qml b/GlosSIConfig/qml/ShortcutProps.qml index 784fbc1..385a206 100644 --- a/GlosSIConfig/qml/ShortcutProps.qml +++ b/GlosSIConfig/qml/ShortcutProps.qml @@ -13,11 +13,12 @@ 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. */ -import QtQuick 6.2 -import QtQuick.Controls 6.2 -import QtQuick.Layouts 6.2 -import QtQuick.Controls.Material 6.2 -import QtQuick.Dialogs 6.2 +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.Material +import QtQuick.Dialogs +import Qt5Compat.GraphicalEffects Item { @@ -54,7 +55,6 @@ Item { advancedTargetSettings.shortcutInfo = shortcutInfo; } if (maybeIcon) { - console.log("meh"); maybeIcon.source = shortcutInfo.icon ? shortcutInfo.icon.endsWith(".exe") ? "image://exe/" + shortcutInfo.icon @@ -221,6 +221,13 @@ Item { visible: uiModel.isWindows onClicked: uwpSelectDialog.open(); } + Button { + Layout.preferredWidth: 64 + Layout.alignment: Qt.AlignBottom + text: qsTr("EGS") + visible: uiModel.isWindows + onClicked: egsSelectDialog.open(); + } Item { height: 1 Layout.preferredWidth: 12 @@ -255,7 +262,6 @@ Item { AdvancedTargetSettings { id: advancedTargetSettings - shortcutInfo: shortcutInfo } Item { diff --git a/GlosSIConfig/qml/SteamNotFoundDialog.qml b/GlosSIConfig/qml/SteamNotFoundDialog.qml index 3b254e6..1aba489 100644 --- a/GlosSIConfig/qml/SteamNotFoundDialog.qml +++ b/GlosSIConfig/qml/SteamNotFoundDialog.qml @@ -13,10 +13,12 @@ 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. */ -import QtQuick 6.2 -import QtQuick.Controls 6.2 -import QtQuick.Layouts 6.2 -import QtQuick.Controls.Material 6.2 +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.Material +import QtQuick.Dialogs +import Qt5Compat.GraphicalEffects Dialog { id: dlg @@ -80,10 +82,6 @@ Dialog { } Button { - anchors.right: parent.right - anchors.top: listview.bottom - anchors.topMargin: 16 - anchors.rightMargin: 2 text: qsTr("Ok") onClicked: dlg.close() } diff --git a/GlosSIConfig/qml/main.qml b/GlosSIConfig/qml/main.qml index 579cdfb..0126a17 100644 --- a/GlosSIConfig/qml/main.qml +++ b/GlosSIConfig/qml/main.qml @@ -13,9 +13,12 @@ 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. */ -import QtQuick 6.2 -import QtQuick.Layouts 6.2 -import QtQuick.Controls.Material 6.2 +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.Material +import QtQuick.Dialogs +import Qt5Compat.GraphicalEffects Window { id: window @@ -26,6 +29,7 @@ Window { Material.accent: Material.color(Material.Blue, Material.Shade900) property bool itemSelected: false; + property var manualInfo: null title: qsTr("GlosSI - Config") @@ -125,6 +129,135 @@ Window { extraButtonText: qsTr("Remind me later") visible: !!uiModel.newVersionName } + + Dialog { + id: manualAddDialog + anchors.centerIn: parent + visible: false + modal: true + dim: true + parent: Overlay.overlay + Overlay.modal: Rectangle { + color: Qt.rgba(0,0,0,0.4) + opacity: backdropOpacity + Behavior on opacity { + NumberAnimation { + duration: 300 + } + } + } + property real backdropOpacity: 1.0 + enter: Transition { + NumberAnimation{target: madcontent; property: "y"; from: parent.height; to: 16; duration: 300; easing.type: Easing.OutQuad } + NumberAnimation{target: madbackground; property: "y"; from: parent.height; to: 0; duration: 300; easing.type: Easing.OutQuad } + NumberAnimation{target: manualAddDialog; property: "backdropOpacity"; from: 0; to: 1; duration: 300; easing.type: Easing.OutQuad } + } + + exit: Transition { + NumberAnimation{target: madcontent; property: "y"; from: 16; to: parent.height; duration: 300; easing.type: Easing.InQuad } + NumberAnimation{target: madbackground; property: "y"; from: 0; to: parent.height; duration: 300; easing.type: Easing.InQuad } + NumberAnimation{target: manualAddDialog; property: "backdropOpacity"; from: 1; to: 0; duration: 300; easing.type: Easing.InQuad } + } + + background: RPane { + id: madbackground + radius: 4 + Material.elevation: 64 + bgOpacity: 0.97 + } + contentItem: Item { + id: madcontent + implicitWidth: steamscreener.width + implicitHeight: madtext.height + 16 + steamscreener.height + 16 + madrow.height + + Label { + id: madtext + text: qsTr("Add \"GlosSITarget\" as game to Steam and change it's properties (in Steam) to this:") + } + + Image { + anchors.top: madtext.bottom + anchors.left: madtext.left + anchors.topMargin: 16 + id: steamscreener + source: "qrc:/steamscreener.png" + } + + FluentTextInput { + id: madnameinput + text: manualInfo ? manualInfo.name : "" + anchors.top: steamscreener.top + anchors.left: steamscreener.left + anchors.topMargin: 72 + anchors.leftMargin: 92 + readOnly: true + background: Item {} + width: 550 + } + + FluentTextInput { + id: glossiPathInput + text: manualInfo ? manualInfo.launch : "" + anchors.top: steamscreener.top + anchors.left: steamscreener.left + anchors.topMargin: 192 + anchors.leftMargin: 24 + readOnly: true + background: Item {} + width: 550 + } + + FluentTextInput { + id: startDirInput + text: manualInfo ? manualInfo.launchDir : "" + anchors.top: steamscreener.top + anchors.left: steamscreener.left + anchors.topMargin: 266 + anchors.leftMargin: 24 + readOnly: true + background: Item {} + width: 550 + } + + FluentTextInput { + id: launchOptsInput + text: manualInfo ? manualInfo.config : "" + anchors.top: steamscreener.top + anchors.left: steamscreener.left + anchors.topMargin: 432 + anchors.leftMargin: 24 + readOnly: true + background: Item {} + width: 550 + } + + Row { + id: madrow + anchors.top: steamscreener.bottom + anchors.topMargin: 16 + spacing: 16 + + Button { + text: qsTr("OK") + onClicked: function(){ + manualAddDialog.close() + } + } + anchors.right: parent.right + } + } + } + + InfoDialog { + id: writeErrorDialog + titleText: qsTr("Error") + text: qsTr("Error writing shortcuts.vdf...\nPlease make sure Steam is running") + extraButton: true + extraButtonText: qsTr("Manual instructions") + onConfirmedExtra: function(data) { + manualAddDialog.open(); + } + } Rectangle { id: titleBar @@ -318,7 +451,14 @@ Window { if (windowContent.editedIndex < 0) { uiModel.addTarget(shortcut) } else { - uiModel.updateTarget(windowContent.editedIndex, shortcut) + if (uiModel.updateTarget(windowContent.editedIndex, shortcut)) { + if (steamShortcutsChanged == false) { + steamChangedDialog.open(); + } + } else { + manualInfo = uiModel.manualProps(shortcut); + writeErrorDialog.open(); + } } } } From 3458f907cabaf231be0ef032cdd8839874eaf964 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Sat, 15 Oct 2022 16:06:34 +0200 Subject: [PATCH 36/44] GlosSIConfig: Show non-Steam-Shortcut AppIDs in Debug builds --- GlosSIConfig/UIModel.cpp | 25 +++++++++++++++++++++++++ GlosSIConfig/UIModel.h | 3 +++ GlosSIConfig/qml/ShortcutCards.qml | 26 +++----------------------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/GlosSIConfig/UIModel.cpp b/GlosSIConfig/UIModel.cpp index ee5ad7e..0fe35a7 100644 --- a/GlosSIConfig/UIModel.cpp +++ b/GlosSIConfig/UIModel.cpp @@ -167,6 +167,22 @@ bool UIModel::isInSteam(QVariant shortcut) return false; } +uint32_t UIModel::getAppId(QVariant shortcut) +{ + if (!isInSteam(shortcut)) { + return 0; + } + const auto map = shortcut.toMap(); + for (auto& steam_shortcut : shortcuts_vdf_) { + if (map["name"].toString() == QString::fromStdString(steam_shortcut.appname)) { + if (QString::fromStdString(steam_shortcut.exe).toLower().contains("glossitarget.exe")) { + return steam_shortcut.appid; + } + } + } + return 0; +} + bool UIModel::addToSteam(QVariant shortcut, const QString& shortcutspath, bool from_cmd) { QDir appDir = QGuiApplication::applicationDirPath(); @@ -523,6 +539,15 @@ bool UIModel::writeShortcutsVDF(const std::wstring& mode, const std::wstring& na #endif } +bool UIModel::getIsDebug() const +{ +#ifdef _DEBUG + return true; +#else + return false; +#endif +} + bool UIModel::getIsWindows() const { return is_windows_; } bool UIModel::hasAcrylicEffect() const { return has_acrylic_affect_; } diff --git a/GlosSIConfig/UIModel.h b/GlosSIConfig/UIModel.h index ba38577..93548bb 100644 --- a/GlosSIConfig/UIModel.h +++ b/GlosSIConfig/UIModel.h @@ -25,6 +25,7 @@ class QNetworkReply; class UIModel : public QObject { Q_OBJECT + Q_PROPERTY(bool isDebug READ getIsDebug CONSTANT) Q_PROPERTY(bool isWindows READ getIsWindows CONSTANT) Q_PROPERTY(bool hasAcrlyicEffect READ hasAcrylicEffect NOTIFY acrylicChanged) Q_PROPERTY(QVariantList targetList READ getTargetList NOTIFY targetListChanged) @@ -45,6 +46,7 @@ class UIModel : public QObject { Q_INVOKABLE bool updateTarget(int index, QVariant shortcut); Q_INVOKABLE void deleteTarget(int index); Q_INVOKABLE bool isInSteam(QVariant shortcut); + Q_INVOKABLE uint32_t getAppId(QVariant shortcut); Q_INVOKABLE bool addToSteam(QVariant shortcut, const QString& shortcutspath, bool from_cmd = false); bool addToSteam(const QString& name, const QString& shortcutspath, bool from_cmd = false); Q_INVOKABLE bool removeFromSteam(const QString& name, const QString& shortcutspath, bool from_cmd = false); @@ -66,6 +68,7 @@ class UIModel : public QObject { [[nodiscard]] bool writeShortcutsVDF(const std::wstring& mode, const std::wstring& name, const std::wstring& shortcutspath, bool is_admin_try = false) const; + bool getIsDebug() const; bool getIsWindows() const; [[nodiscard]] bool hasAcrylicEffect() const; void setAcrylicEffect(bool has_acrylic_affect); diff --git a/GlosSIConfig/qml/ShortcutCards.qml b/GlosSIConfig/qml/ShortcutCards.qml index 59d5126..72e3f50 100644 --- a/GlosSIConfig/qml/ShortcutCards.qml +++ b/GlosSIConfig/qml/ShortcutCards.qml @@ -130,34 +130,14 @@ GridView { } } Row { - visible: false // TODO: dunno about this... + visible: uiModel.isDebug spacing: 4 Label { - text: qsTr("Is in") + text: qsTr("AppID: ") font.bold: true } - Image { - anchors.verticalCenter: parent.verticalCenter - source: "qrc:/svg/steam.svg" - width: 16 - height: 16 - smooth: true - mipmap: true - ColorOverlay { - anchors.fill: parent - source: parent - color: "white" - } - } - Item { - width: 4 - height: 1 - } Label { - anchors.verticalCenter: parent.verticalCenter - text: delegateRoot.isInSteam ? qsTr("Yes") : qsTr("No") - width: 292 - typeLabel.width - 72 - elide: Text.ElideRight + text: uiModel.getAppId(modelData) } } } From e5d7dd44328a9999102a814be0c945f3a0f52f2d Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Sat, 15 Oct 2022 17:48:50 +0200 Subject: [PATCH 37/44] GlosSIConfig: Show Steam grid background images on shortcutCards --- GlosSIConfig/UIModel.cpp | 36 ++++++++++++++++++++++++++---- GlosSIConfig/UIModel.h | 6 +++-- GlosSIConfig/qml/RPane.qml | 12 +++++++--- GlosSIConfig/qml/ShortcutCards.qml | 3 +++ GlosSIConfig/qml/ShortcutProps.qml | 1 + 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/GlosSIConfig/UIModel.cpp b/GlosSIConfig/UIModel.cpp index 0fe35a7..873304c 100644 --- a/GlosSIConfig/UIModel.cpp +++ b/GlosSIConfig/UIModel.cpp @@ -16,11 +16,11 @@ limitations under the License. #include "UIModel.h" #include +#include #include #include #include #include -#include #include @@ -153,7 +153,7 @@ void UIModel::deleteTarget(int index) emit targetListChanged(); } -bool UIModel::isInSteam(QVariant shortcut) +bool UIModel::isInSteam(QVariant shortcut) const { const auto map = shortcut.toMap(); for (auto& steam_shortcut : shortcuts_vdf_) { @@ -167,7 +167,7 @@ bool UIModel::isInSteam(QVariant shortcut) return false; } -uint32_t UIModel::getAppId(QVariant shortcut) +uint32_t UIModel::getAppId(QVariant shortcut) const { if (!isInSteam(shortcut)) { return 0; @@ -411,7 +411,8 @@ QVariantMap UIModel::getDefaultConf() const obj[key] = defaults.value(key); } if (obj.value(key).isObject()) { - obj[key] = applyDefaultsFn(obj[key].toObject(), defaults.value(key).toObject(), applyDefaultsFn); + obj[key] = + applyDefaultsFn(obj[key].toObject(), defaults.value(key).toObject(), applyDefaultsFn); } } return obj; @@ -478,6 +479,33 @@ QVariantList UIModel::egsGamesList() const return {{"InstallLocation", "Error"}}; } + +QString UIModel::getGridImagePath(QVariant shortcut) const +{ + if (!foundSteam()) { + return ""; + } + const auto& app_id = getAppId(shortcut); + if (app_id == 0) { + return ""; + } + + const std::filesystem::path grid_dir = + std::wstring(getSteamPath()) + user_data_path_.toStdWString() + getSteamUserId() + L"/config/grid"; + if (!std::filesystem::exists(grid_dir)) { + return ""; + } + const std::vector extensions = {".png", ".jpg"}; + for (const auto& entry : std::filesystem::directory_iterator(grid_dir)) { + if (entry.is_regular_file() + && std::ranges::find(extensions, entry.path().extension().string()) != extensions.end() + && entry.path().filename().string().find(std::to_string(app_id)) != std::string::npos) { + return QString::fromStdString(entry.path().string()); + } + } + return ""; +} + bool UIModel::writeShortcutsVDF(const std::wstring& mode, const std::wstring& name, const std::wstring& shortcutspath, bool is_admin_try) const { diff --git a/GlosSIConfig/UIModel.h b/GlosSIConfig/UIModel.h index 93548bb..fa4c944 100644 --- a/GlosSIConfig/UIModel.h +++ b/GlosSIConfig/UIModel.h @@ -45,8 +45,8 @@ class UIModel : public QObject { Q_INVOKABLE void addTarget(QVariant shortcut); Q_INVOKABLE bool updateTarget(int index, QVariant shortcut); Q_INVOKABLE void deleteTarget(int index); - Q_INVOKABLE bool isInSteam(QVariant shortcut); - Q_INVOKABLE uint32_t getAppId(QVariant shortcut); + Q_INVOKABLE bool isInSteam(QVariant shortcut) const; + Q_INVOKABLE uint32_t getAppId(QVariant shortcut) const; Q_INVOKABLE bool addToSteam(QVariant shortcut, const QString& shortcutspath, bool from_cmd = false); bool addToSteam(const QString& name, const QString& shortcutspath, bool from_cmd = false); Q_INVOKABLE bool removeFromSteam(const QString& name, const QString& shortcutspath, bool from_cmd = false); @@ -65,6 +65,8 @@ class UIModel : public QObject { #endif Q_INVOKABLE QVariantList egsGamesList() const; + Q_INVOKABLE QString getGridImagePath(QVariant shortcut) const; + [[nodiscard]] bool writeShortcutsVDF(const std::wstring& mode, const std::wstring& name, const std::wstring& shortcutspath, bool is_admin_try = false) const; diff --git a/GlosSIConfig/qml/RPane.qml b/GlosSIConfig/qml/RPane.qml index 56748f6..8ada4b0 100644 --- a/GlosSIConfig/qml/RPane.qml +++ b/GlosSIConfig/qml/RPane.qml @@ -23,6 +23,8 @@ Pane { property int radius: 0 property color color: control.Material.backgroundColor property real bgOpacity: 1 + property string bgImgSource: null + property real bgImgOpacity: -1 background: Rectangle { color: parent.color opacity: parent.bgOpacity @@ -31,15 +33,19 @@ Pane { layer.enabled: control.enabled && control.Material.elevation > 0 layer.effect: ElevationEffect { elevation: control.Material.elevation + clip: true } + clip: true Image { + id: bgImage anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom - source: "qrc:/noise.png" - fillMode: Image.Tile - opacity: 0.035 + source: bgImgSource ? bgImgSource : "qrc:/noise.png" + fillMode: bgImgSource ? Image.PreserveAspectCrop : Image.Tile + opacity: bgImgOpacity < 0 ? 0.035 : bgImgOpacity + clip: true } } } \ No newline at end of file diff --git a/GlosSIConfig/qml/ShortcutCards.qml b/GlosSIConfig/qml/ShortcutCards.qml index 72e3f50..e0f634b 100644 --- a/GlosSIConfig/qml/ShortcutCards.qml +++ b/GlosSIConfig/qml/ShortcutCards.qml @@ -76,6 +76,8 @@ GridView { Material.elevation: 4 clip: true property bool isInSteam: uiModel.isInSteam(modelData); + bgImgSource: isInSteam ? "file:///" + uiModel.getGridImagePath(modelData) : null + bgImgOpacity: isInSteam ? 0.12 : -1 Image { anchors.top: parent.top @@ -88,6 +90,7 @@ GridView { : 'qrc:/svg/add_photo_alternate_white_24dp.svg' width: 48 height: 48 + fillMode: Image.PreserveAspectFit } Label { diff --git a/GlosSIConfig/qml/ShortcutProps.qml b/GlosSIConfig/qml/ShortcutProps.qml index 385a206..e2f5f3d 100644 --- a/GlosSIConfig/qml/ShortcutProps.qml +++ b/GlosSIConfig/qml/ShortcutProps.qml @@ -171,6 +171,7 @@ Item { flat: true contentItem: Image { id: maybeIcon + fillMode: Image.PreserveAspectFit source: shortcutInfo.icon ? shortcutInfo.icon.endsWith(".exe") ? "image://exe/" + shortcutInfo.icon From 616650c04cb118d0a5d03dea3d070e44eac951f8 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Sat, 15 Oct 2022 18:12:52 +0200 Subject: [PATCH 38/44] GlosSIConfig: Show grid images on SHortcutProps background --- GlosSIConfig/qml/RPane.qml | 3 --- GlosSIConfig/qml/ShortcutProps.qml | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/GlosSIConfig/qml/RPane.qml b/GlosSIConfig/qml/RPane.qml index 8ada4b0..2705cd7 100644 --- a/GlosSIConfig/qml/RPane.qml +++ b/GlosSIConfig/qml/RPane.qml @@ -33,9 +33,7 @@ Pane { layer.enabled: control.enabled && control.Material.elevation > 0 layer.effect: ElevationEffect { elevation: control.Material.elevation - clip: true } - clip: true Image { id: bgImage anchors.top: parent.top @@ -45,7 +43,6 @@ Pane { source: bgImgSource ? bgImgSource : "qrc:/noise.png" fillMode: bgImgSource ? Image.PreserveAspectCrop : Image.Tile opacity: bgImgOpacity < 0 ? 0.035 : bgImgOpacity - clip: true } } } \ No newline at end of file diff --git a/GlosSIConfig/qml/ShortcutProps.qml b/GlosSIConfig/qml/ShortcutProps.qml index e2f5f3d..2c08779 100644 --- a/GlosSIConfig/qml/ShortcutProps.qml +++ b/GlosSIConfig/qml/ShortcutProps.qml @@ -62,6 +62,32 @@ Item { : 'qrc:/svg/add_photo_alternate_white_24dp.svg'; } } + + Image { + id: bgImage + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + fillMode: Image.PreserveAspectCrop + source: "file:///" + uiModel.getGridImagePath(shortcutInfo) + autoTransform: true + opacity: 0 + } + + LinearGradient { + id: mask + anchors.fill: bgImage + gradient: Gradient { + GradientStop { position: 0.0; color: "#afFFFFFF"} + GradientStop { position: 0.7; color: "transparent" } + GradientStop { position: 1; color: "transparent" } + } + } + OpacityMask { + source: bgImage + maskSource: mask + anchors.fill: bgImage + } Flickable { id: flickable From 93026dfb757f3c87b59ef9a956faab155df1f276 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Sat, 15 Oct 2022 18:23:08 +0200 Subject: [PATCH 39/44] GlosSIConfig: Fix Win32 Apps Icons --- GlosSIConfig/qml/ShortcutProps.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GlosSIConfig/qml/ShortcutProps.qml b/GlosSIConfig/qml/ShortcutProps.qml index 2c08779..9d66279 100644 --- a/GlosSIConfig/qml/ShortcutProps.qml +++ b/GlosSIConfig/qml/ShortcutProps.qml @@ -331,10 +331,11 @@ Item { pathInput.text = fileDialog.selectedFile.toString().replace("file:///", "") if (nameInput.text == "") { nameInput.text = pathInput.text.replace(/.*(\\|\/)/,"").replace(/\.[0-z]*$/, "") - shortcutInfo.icon = nameInput.text } + shortcutInfo.icon = pathInput.text launchApp.checked = true } + shortcutInfo = shortcutInfo; } onRejected: { From 6e69b6ec1c8d874f6dc69d74266e4c41d8be7d81 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Sun, 16 Oct 2022 00:04:33 +0200 Subject: [PATCH 40/44] GlosSIConfig: Integrate SteamGrid --- .gitignore | 2 + GlosSIConfig/GlosSIConfig.vcxproj | 2 + GlosSIConfig/GlosSIConfig.vcxproj.filters | 6 + GlosSIConfig/UIModel.cpp | 26 +++- GlosSIConfig/UIModel.h | 11 ++ GlosSIConfig/qml.qrc | 1 + GlosSIConfig/qml/SteamGridDialog.qml | 156 ++++++++++++++++++++++ GlosSIConfig/qml/UWPSelectDialog.qml | 18 +-- GlosSIConfig/qml/main.qml | 91 ++++++++----- GlosSITarget/Resource.rc | 8 +- bundle-zip.ps1 | 1 + prebuild.ps1 | 18 ++- 12 files changed, 292 insertions(+), 48 deletions(-) create mode 100644 GlosSIConfig/qml/SteamGridDialog.qml diff --git a/.gitignore b/.gitignore index 8553f87..96f406a 100644 --- a/.gitignore +++ b/.gitignore @@ -370,3 +370,5 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd .visuallint +GlosSIConfig/steamgrid_api_keys.h +steamgrid.zip diff --git a/GlosSIConfig/GlosSIConfig.vcxproj b/GlosSIConfig/GlosSIConfig.vcxproj index b649a7b..a79c58d 100644 --- a/GlosSIConfig/GlosSIConfig.vcxproj +++ b/GlosSIConfig/GlosSIConfig.vcxproj @@ -145,6 +145,7 @@ + @@ -163,6 +164,7 @@ + diff --git a/GlosSIConfig/GlosSIConfig.vcxproj.filters b/GlosSIConfig/GlosSIConfig.vcxproj.filters index 08d81b8..9811199 100644 --- a/GlosSIConfig/GlosSIConfig.vcxproj.filters +++ b/GlosSIConfig/GlosSIConfig.vcxproj.filters @@ -83,6 +83,9 @@ qml + + qml + @@ -102,6 +105,9 @@ Header Files + + Header Files + diff --git a/GlosSIConfig/UIModel.cpp b/GlosSIConfig/UIModel.cpp index 873304c..c23a67b 100644 --- a/GlosSIConfig/UIModel.cpp +++ b/GlosSIConfig/UIModel.cpp @@ -33,6 +33,7 @@ limitations under the License. #endif #include "../version.hpp" +#include "steamgrid_api_keys.h" UIModel::UIModel() : QObject(nullptr) { @@ -479,6 +480,17 @@ QVariantList UIModel::egsGamesList() const return {{"InstallLocation", "Error"}}; } +void UIModel::loadSteamGridImages() +{ + std::filesystem::path path = QCoreApplication::applicationDirPath().toStdWString(); + path /= "steamgrid.exe"; + + steamgrid_proc_.setProgram(path.string().c_str()); + steamgrid_proc_.setArguments({"-nonsteamonly", "--onlymissingartwork", "--steamgriddb", steamgridb_key}); + connect(&steamgrid_proc_, &QProcess::readyReadStandardOutput, this, &UIModel::onSteamGridReadReady); + steamgrid_proc_.start(); + steamgrid_proc_.write("\n"); +} QString UIModel::getGridImagePath(QVariant shortcut) const { @@ -497,9 +509,9 @@ QString UIModel::getGridImagePath(QVariant shortcut) const } const std::vector extensions = {".png", ".jpg"}; for (const auto& entry : std::filesystem::directory_iterator(grid_dir)) { - if (entry.is_regular_file() - && std::ranges::find(extensions, entry.path().extension().string()) != extensions.end() - && entry.path().filename().string().find(std::to_string(app_id)) != std::string::npos) { + if (entry.is_regular_file() && + std::ranges::find(extensions, entry.path().extension().string()) != extensions.end() && + entry.path().filename().string().find(std::to_string(app_id)) != std::string::npos) { return QString::fromStdString(entry.path().string()); } } @@ -586,6 +598,8 @@ void UIModel::setAcrylicEffect(bool has_acrylic_affect) emit acrylicChanged(); } +QStringList UIModel::getSteamgridOutput() const { return steamgrid_output_; } + void UIModel::onAvailFilesResponse(QNetworkReply* reply) { @@ -658,6 +672,12 @@ void UIModel::onAvailFilesResponse(QNetworkReply* reply) } } +void UIModel::onSteamGridReadReady() +{ + steamgrid_output_.push_back(QString::fromLocal8Bit(steamgrid_proc_.readAllStandardOutput())); + emit steamgridOutputChanged(); +} + void UIModel::writeTarget(const QJsonObject& json, const QString& name) const { auto path = config_path_; diff --git a/GlosSIConfig/UIModel.h b/GlosSIConfig/UIModel.h index fa4c944..b3cb982 100644 --- a/GlosSIConfig/UIModel.h +++ b/GlosSIConfig/UIModel.h @@ -17,6 +17,7 @@ limitations under the License. #include #include #include +#include #include #include @@ -37,6 +38,8 @@ class UIModel : public QObject { Q_PROPERTY(QString versionString READ getVersionString CONSTANT) Q_PROPERTY(QString newVersionName READ getNewVersionName NOTIFY newVersionAvailable) + Q_PROPERTY(QStringList steamgridOutput READ getSteamgridOutput NOTIFY steamgridOutputChanged) + public: UIModel(); @@ -65,6 +68,7 @@ class UIModel : public QObject { #endif Q_INVOKABLE QVariantList egsGamesList() const; + Q_INVOKABLE void loadSteamGridImages(); Q_INVOKABLE QString getGridImagePath(QVariant shortcut) const; [[nodiscard]] bool writeShortcutsVDF(const std::wstring& mode, const std::wstring& name, @@ -75,13 +79,17 @@ class UIModel : public QObject { [[nodiscard]] bool hasAcrylicEffect() const; void setAcrylicEffect(bool has_acrylic_affect); + QStringList getSteamgridOutput() const; + signals: void acrylicChanged(); void targetListChanged(); void newVersionAvailable(); + void steamgridOutputChanged(); public slots: void onAvailFilesResponse(QNetworkReply* reply); + void onSteamGridReadReady(); private: #ifdef _WIN32 @@ -107,6 +115,9 @@ class UIModel : public QObject { QString new_version_name_; bool notify_on_snapshots_ = false; + QProcess steamgrid_proc_; + QStringList steamgrid_output_; + std::vector shortcuts_vdf_; void writeTarget(const QJsonObject& json, const QString& name) const; diff --git a/GlosSIConfig/qml.qrc b/GlosSIConfig/qml.qrc index 64cc986..1d07c92 100644 --- a/GlosSIConfig/qml.qrc +++ b/GlosSIConfig/qml.qrc @@ -24,5 +24,6 @@ svg/settings_fill_white_24dp.svg qml/EGSSelectDialog.qml svg/add_photo_alternate_white_24dp.svg + qml/SteamGridDialog.qml diff --git a/GlosSIConfig/qml/SteamGridDialog.qml b/GlosSIConfig/qml/SteamGridDialog.qml new file mode 100644 index 0000000..e7d3b61 --- /dev/null +++ b/GlosSIConfig/qml/SteamGridDialog.qml @@ -0,0 +1,156 @@ +/* +Copyright 2021-2022 Peter Repukat - FlatspotSoftware + +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. +*/ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Controls.Material + +Dialog { + id: gridDialog + anchors.centerIn: parent + + signal confirmed(var param) + + visible: false + modal: true + dim: true + parent: Overlay.overlay + Overlay.modal: Rectangle { + color: Qt.rgba(0,0,0,0.4) + opacity: backdropOpacity + Behavior on opacity { + NumberAnimation { + duration: 300 + } + } + } + property real backdropOpacity: 1.0 + + property bool loading: true + + + onOpened: function() { + loading = true; + uiModel.loadSteamGridImages(); + } + + onClosed: function() { + } + + enter: Transition { + NumberAnimation{target: content; property: "y"; from: parent.height; to: 16; duration: 300; easing.type: Easing.OutQuad } + NumberAnimation{target: background; property: "y"; from: parent.height; to: 0; duration: 300; easing.type: Easing.OutQuad } + NumberAnimation{target: gridDialog; property: "backdropOpacity"; from: 0; to: 1; duration: 300; easing.type: Easing.OutQuad } + } + + exit: Transition { + NumberAnimation{target: content; property: "y"; from: 16; to: parent.height; duration: 300; easing.type: Easing.InQuad } + NumberAnimation{target: background; property: "y"; from: 0; to: parent.height; duration: 300; easing.type: Easing.InQuad } + NumberAnimation{target: gridDialog; property: "backdropOpacity"; from: 1; to: 0; duration: 300; easing.type: Easing.InQuad } + } + + background: RPane { + id: background + radius: 4 + Material.elevation: 64 + bgOpacity: 0.97 + } + contentItem: Item { + id: content + implicitWidth: listview.width + implicitHeight: listview.height + titlelabel.height + 16 + 64 + clip: true + Label { + id: titlelabel + text: qsTr("Loading Grid images...") + font.pixelSize: 24 + font.bold: true + } + + BusyIndicator { + id: busyIndicator + running: visible + anchors.top: titlelabel.bottom + anchors.topMargin: 8 + anchors.horizontalCenter: parent.horizontalCenter + opacity: loading ? 1 : 0 + height: loading ? 72 : 0 + Behavior on opacity { + NumberAnimation { + duration: 350 + easing.type: Easing.InOutQuad + } + } + visible: loading + } + + ListView { + anchors.top: busyIndicator.bottom + anchors.topMargin: 16 + anchors.bottom: parent.bottom + anchors.bottomMargin: 16 + id: listview + width: window.width * 0.45 + height: window.height * 0.66 + spacing: 0 + clip: true + model: uiModel.steamgridOutput + ScrollBar.vertical: ScrollBar { + } + onCountChanged: { + listview.positionViewAtIndex(listview.count - 1, ListView.Visible) + loading = !listview.model[listview.count - 1].includes("Press enter") + } + + Behavior on opacity { + ParallelAnimation { + NumberAnimation { + duration: 350 + easing.type: Easing.InOutQuad + } + PropertyAnimation { + target: listview + property: "anchors.topMargin" + from: window.height * 0.75 + to: 16 + duration: 350 + easing.type: Easing.InOutQuad + } + } + } + + + delegate: /* Item { + width: listview.width + height: outputLabel.implicitHeight */ + + Label { + id: outputLabel + text: modelData + } + // } + } + Button { + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: 2 + anchors.rightMargin: 2 + text: qsTr("Ok") + onClicked: gridDialog.close() + } + + } +} \ No newline at end of file diff --git a/GlosSIConfig/qml/UWPSelectDialog.qml b/GlosSIConfig/qml/UWPSelectDialog.qml index d450b04..3c12235 100644 --- a/GlosSIConfig/qml/UWPSelectDialog.qml +++ b/GlosSIConfig/qml/UWPSelectDialog.qml @@ -126,15 +126,6 @@ Dialog { visible: opacity == 0 ? false : true } - Button { - anchors.right: parent.right - anchors.top: listview.bottom - anchors.topMargin: 16 - anchors.rightMargin: 2 - text: qsTr("Cancel") - onClicked: dlg.close() - } - ListView { anchors.top: searchBar.bottom anchors.topMargin: 16 @@ -218,5 +209,14 @@ Dialog { } } } + + Button { + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: 2 + anchors.rightMargin: 2 + text: qsTr("Cancel") + onClicked: dlg.close() + } } } \ No newline at end of file diff --git a/GlosSIConfig/qml/main.qml b/GlosSIConfig/qml/main.qml index 0126a17..7e22a54 100644 --- a/GlosSIConfig/qml/main.qml +++ b/GlosSIConfig/qml/main.qml @@ -48,6 +48,12 @@ Window { } property bool steamShortcutsChanged: false + + onSteamShortcutsChanged: function() { + shouldShowLoadGridImagesButton = uiModel.targetList.some((shortcut) => uiModel.isInSteam(shortcut)) + } + + property bool shouldShowLoadGridImagesButton: false Component.onCompleted: function() { if (!uiModel.foundSteam) { @@ -57,6 +63,7 @@ Window { if (!uiModel.steamInputXboxSupportEnabled) { steamXboxDisabledDialog.open(); } + shouldShowLoadGridImagesButton = uiModel.targetList.some((shortcut) => uiModel.isInSteam(shortcut)) } Image { @@ -370,47 +377,64 @@ Window { windowContent.editedIndex = index; } } - Column { + Row { spacing: 8 anchors.right: parent.right anchors.bottom: parent.bottom anchors.margins: 24 - RoundButton { - id: optionsBtn - width: 64 - height: 64 - text: "" - contentItem: Item { - Image { + Column { + spacing: 8 + RoundButton { + id: optionsBtn + anchors.right: parent.right + width: 64 + height: 64 + text: "" + contentItem: Item { + Image { + anchors.centerIn: parent + source: "qrc:/svg/settings_fill_white_24dp.svg" + width: 24 + height: 24 + } + } + highlighted: true + onClicked: function() { + globalConf.opacity = 1; + homeContent.opacity = 0; + } + } + RoundButton { + id: addBtn + anchors.right: parent.right + width: 64 + height: 64 + text: "+" + contentItem: Label { anchors.centerIn: parent - source: "qrc:/svg/settings_fill_white_24dp.svg" - width: 24 - height: 24 + text: addBtn.text + font.pixelSize: 32 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight } - } - highlighted: true - onClicked: function() { - globalConf.opacity = 1; - homeContent.opacity = 0; + highlighted: true + onClicked: selectTypeDialog.open() } - } - RoundButton { - id: addBtn - width: 64 - height: 64 - text: "+" - contentItem: Label { - anchors.centerIn: parent - text: addBtn.text - font.pixelSize: 32 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight + Button { + visible: shouldShowLoadGridImagesButton || steamShortcutsChanged + id: loadGridImagesBtn + text: qsTr("🖼️ Load steam grid images") + highlighted: true + onClicked: function() { + steamGridDialog.open() + } } - highlighted: true - onClicked: selectTypeDialog.open() } + } + + } Item { @@ -532,5 +556,10 @@ Window { } } } + + SteamGridDialog { + id: steamGridDialog + } + } } diff --git a/GlosSITarget/Resource.rc b/GlosSITarget/Resource.rc index ccb5d81..009c048 100644 --- a/GlosSITarget/Resource.rc +++ b/GlosSITarget/Resource.rc @@ -51,8 +51,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,1,0,203000008020 - PRODUCTVERSION 0,1,0,203000008020 + FILEVERSION 0,1,0,2033009290000 + PRODUCTVERSION 0,1,0,2033009290000 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -69,12 +69,12 @@ BEGIN BEGIN VALUE "CompanyName", "Peter Repukat - FlatspotSoftware" VALUE "FileDescription", "GlosSI - SteamTarget" - VALUE "FileVersion", "0.1.0.2-3-gcea802b" + VALUE "FileVersion", "0.1.0.2-33-g929abfe" VALUE "InternalName", "GlosSITarget" VALUE "LegalCopyright", "Copyright (C) 2021-2022 Peter Repukat - FlatspotSoftware" VALUE "OriginalFilename", "GlosSITarget.exe" VALUE "ProductName", "GlosSI" - VALUE "ProductVersion", "0.1.0.2-3-gcea802b" + VALUE "ProductVersion", "0.1.0.2-33-g929abfe" END END BLOCK "VarFileInfo" diff --git a/bundle-zip.ps1 b/bundle-zip.ps1 index c6964e7..dffea33 100644 --- a/bundle-zip.ps1 +++ b/bundle-zip.ps1 @@ -18,6 +18,7 @@ Copy-Item "..\..\vc_redist.x64.exe" -Destination "." Copy-Item "..\..\LICENSE" -Destination "./LICENSE" Copy-Item "..\..\QT_License" -Destination "./QT_License" Copy-Item "..\..\THIRD_PARTY_LICENSES.txt" -Destination "./THIRD_PARTY_LICENSES.txt" +Copy-Item "..\..\steamgrid.exe" -Destination "./steamgrid.exe" Copy-Item "C:\Qt\Tools\OpenSSL\Win_x64\bin\libcrypto-1_1-x64.dll" -Destination "./libcrypto-1_1-x64.dll" Copy-Item "C:\Qt\Tools\OpenSSL\Win_x64\bin\libssl-1_1-x64.dll" -Destination "./libssl-1_1-x64.dll" diff --git a/prebuild.ps1 b/prebuild.ps1 index 23c6fff..b2ee11e 100644 --- a/prebuild.ps1 +++ b/prebuild.ps1 @@ -14,4 +14,20 @@ cd .\GlosSIConfig\ ..\version_help.ps1 -cd ../ \ No newline at end of file +$apiKeyText = " +/* Autogenerated version info file */ +#pragma once + +inline const char* steamgridb_key = ""$env:STEAMGRIDDB_KEY""; +" + +if (!(Test-Path 'steamgrid_api_keys.h')) { + New-Item -Path "." -Name "steamgrid_api_keys.h" -ItemType "file" -Value $apiKeyText +} + +cd ../ + +if (!(Test-Path 'steamgrid.exe')) { + Invoke-WebRequest -o steamgrid.zip https://github.com/boppreh/steamgrid/releases/download/v3.4.0/steamgrid_windows.zip + 7z e steamgrid.zip steamgrid.exe +} From 20e86ce8e47202a5abf58f854137c2803e17bf0d Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Sun, 16 Oct 2022 12:12:36 +0200 Subject: [PATCH 41/44] GlosSIConfig: Fix steam shortcuts updating when editing non added shortcuts --- GlosSIConfig/qml/main.qml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/GlosSIConfig/qml/main.qml b/GlosSIConfig/qml/main.qml index 7e22a54..27836a9 100644 --- a/GlosSIConfig/qml/main.qml +++ b/GlosSIConfig/qml/main.qml @@ -475,13 +475,15 @@ Window { if (windowContent.editedIndex < 0) { uiModel.addTarget(shortcut) } else { - if (uiModel.updateTarget(windowContent.editedIndex, shortcut)) { - if (steamShortcutsChanged == false) { - steamChangedDialog.open(); - } - } else { - manualInfo = uiModel.manualProps(shortcut); - writeErrorDialog.open(); + if (uiModel.isInSteam(shortcut)) { + if (uiModel.updateTarget(windowContent.editedIndex, shortcut)) { + if (steamShortcutsChanged == false) { + steamChangedDialog.open(); + } + } else { + manualInfo = uiModel.manualProps(shortcut); + writeErrorDialog.open(); + } } } } From 3df6cf510a9890b8d20b8d6b26e0924e02bd2347 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Sun, 16 Oct 2022 12:37:14 +0200 Subject: [PATCH 42/44] AppLauncher: Fix Win32 launch args --- GlosSITarget/AppLauncher.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index 71dae27..8a07887 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -287,10 +287,16 @@ void AppLauncher::launchWin32App(const std::wstring& path, const std::wstring& a // } else { // launch_dir = m[0]; // } - std::wstring args_cpy(args); + std::wstring args_cpy( + args.empty() + ? L"" + : ((native_seps_path.find(L" ") != std::wstring::npos + ? L"\"" + native_seps_path + L"\"" + : native_seps_path) + L" " + args) + ); spdlog::debug(L"Launching Win32App app \"{}\"; args \"{}\"", native_seps_path, args_cpy); if (CreateProcessW(native_seps_path.data(), - args_cpy.data(), + args_cpy.empty() ? nullptr : args_cpy.data(), nullptr, nullptr, watchdog ? FALSE : TRUE, From 63fdab16852bd2405b831b26e6042df963e1dde0 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Sun, 16 Oct 2022 13:11:14 +0200 Subject: [PATCH 43/44] GlosSIConfig: Reload grid images after steamgrid is done --- GlosSIConfig/qml/SteamGridDialog.qml | 5 ++++- GlosSIConfig/qml/main.qml | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/GlosSIConfig/qml/SteamGridDialog.qml b/GlosSIConfig/qml/SteamGridDialog.qml index e7d3b61..097947a 100644 --- a/GlosSIConfig/qml/SteamGridDialog.qml +++ b/GlosSIConfig/qml/SteamGridDialog.qml @@ -149,7 +149,10 @@ Dialog { anchors.bottomMargin: 2 anchors.rightMargin: 2 text: qsTr("Ok") - onClicked: gridDialog.close() + onClicked: function() { + gridDialog.close(); + confirmed(undefined); + } } } diff --git a/GlosSIConfig/qml/main.qml b/GlosSIConfig/qml/main.qml index 27836a9..479c2bd 100644 --- a/GlosSIConfig/qml/main.qml +++ b/GlosSIConfig/qml/main.qml @@ -561,6 +561,10 @@ Window { SteamGridDialog { id: steamGridDialog + onConfirmed: function() { + shortcutgrid.model = []; + shortcutgrid.model = uiModel.targetList; + } } } From 5528dfb733366fdadd78db05d9e6ba9fbf8120d6 Mon Sep 17 00:00:00 2001 From: Peter Repukat Date: Fri, 21 Oct 2022 21:18:25 +0200 Subject: [PATCH 44/44] Add configurable Launcher options / Launcher ProcessList --- GlosSIConfig/Resource.rc | 8 +- GlosSIConfig/UIModel.cpp | 10 +- GlosSIConfig/qml/AdvancedTargetSettings.qml | 101 +++++++++++++++++++- GlosSITarget/AppLauncher.cpp | 34 ++++--- GlosSITarget/AppLauncher.h | 12 +-- GlosSITarget/Resource.rc | 8 +- GlosSITarget/Settings.h | 32 ++++--- 7 files changed, 157 insertions(+), 48 deletions(-) diff --git a/GlosSIConfig/Resource.rc b/GlosSIConfig/Resource.rc index dc73243..f5db24c 100644 --- a/GlosSIConfig/Resource.rc +++ b/GlosSIConfig/Resource.rc @@ -51,8 +51,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,1,0,2035000100080 - PRODUCTVERSION 0,1,0,2035000100080 + FILEVERSION 0,1,0,2045006300001 + PRODUCTVERSION 0,1,0,2045006300001 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -69,12 +69,12 @@ BEGIN BEGIN VALUE "CompanyName", "Peter Repukat - FlatspotSoftware" VALUE "FileDescription", "GlosSI - Config" - VALUE "FileVersion", "0.1.0.2-35-g01efd8d" + VALUE "FileVersion", "0.1.0.2-45-g63fdab1" VALUE "InternalName", "GlosSIConfig" VALUE "LegalCopyright", "Copyright (C) 2021 Peter Repukat - FlatspotSoftware" VALUE "OriginalFilename", "GlosSIConfig.exe" VALUE "ProductName", "GlosSI" - VALUE "ProductVersion", "0.1.0.2-35-g01efd8d" + VALUE "ProductVersion", "0.1.0.2-45-g63fdab1" END END BLOCK "VarFileInfo" diff --git a/GlosSIConfig/UIModel.cpp b/GlosSIConfig/UIModel.cpp index c23a67b..ee67d6d 100644 --- a/GlosSIConfig/UIModel.cpp +++ b/GlosSIConfig/UIModel.cpp @@ -19,6 +19,7 @@ limitations under the License. #include #include #include +#include #include #include @@ -32,6 +33,8 @@ limitations under the License. #include #endif +#include "ExeImageProvider.h" +#include "ExeImageProvider.h" #include "../version.hpp" #include "steamgrid_api_keys.h" @@ -365,15 +368,13 @@ QVariantMap UIModel::getDefaultConf() const path /= "Roaming"; path /= "GlosSI"; path /= "default.json"; - + QJsonObject defaults = { {"icon", QJsonValue::Null}, {"name", QJsonValue::Null}, {"version", 1}, {"extendedLogging", false}, {"snapshotNotify", false}, - {"ignoreEGS", true}, - {"killEGS", false}, {"controller", QJsonObject{{"maxControllers", 1}, {"emulateDS4", false}, {"allowDesktopConfig", false}}}, {"devices", QJsonObject{ @@ -387,6 +388,9 @@ QVariantMap UIModel::getDefaultConf() const {"launchAppArgs", QJsonValue::Null}, {"launchPath", QJsonValue::Null}, {"waitForChildProcs", true}, + {"launcherProcesses", QJsonArray{}}, + {"ignoreLauncher", true}, + {"killLauncher", false}, }}, {"window", QJsonObject{ diff --git a/GlosSIConfig/qml/AdvancedTargetSettings.qml b/GlosSIConfig/qml/AdvancedTargetSettings.qml index 29b46a2..0c7ec8a 100644 --- a/GlosSIConfig/qml/AdvancedTargetSettings.qml +++ b/GlosSIConfig/qml/AdvancedTargetSettings.qml @@ -90,7 +90,7 @@ CollapsiblePane { shortcutInfo.launch.waitForChildProcs = checked } } - CheckBox { + /*CheckBox { height: subTitle != "" || (shortcutInfo.launch.launchPath || "").includes("epicgames.launcher") ? 32 : 0 visible: subTitle != "" || (shortcutInfo.launch.launchPath || "").includes("epicgames.launcher") id: ignoreEGS @@ -109,7 +109,7 @@ CollapsiblePane { onCheckedChanged: function(){ shortcutInfo.killEGS = checked } - } + }*/ } Column { spacing: 2 @@ -483,7 +483,104 @@ CollapsiblePane { id: commonPane Column { spacing: 4 + width: parent.width + Column { + width: parent.width + Row { + width: parent.width + Label { + text: qsTr("Launcher processes") + anchors.verticalCenter: parent.verticalCenter + } + RoundButton { + onClicked: () => { + helpInfoDialog.titleText = qsTr("Launcher processes") + helpInfoDialog.text = + qsTr("Tells GlosSI what processes should be treated as launchers") + + "\n" + qsTr("Only use executable name, not full path") + + "\n" + qsTr("One process per line") + + "\n" + + qsTr("List must be filled for \"") + + qsTr("Ignore launcher for close detection") + + qsTr("\" and \"") + qsTr("Close launcher on game exit.") + + qsTr("\" to work") + + helpInfoDialog.open() + } + width: 48 + height: 48 + Material.elevation: 0 + anchors.verticalCenter: parent.verticalCenter + Image { + anchors.centerIn: parent + source: "qrc:/svg/help_outline_white_24dp.svg" + width: 24 + height: 24 + } + } + } + RPane { + color: Qt.lighter(Material.background, 1.6) + bgOpacity: 0.3 + radius: 8 + width: parent.width + height: launcherProcessesTextArea.height + 16 + Flickable { + width: parent.width + height: parent.height + clip: true + ScrollBar.vertical: ScrollBar { + + } + contentWidth: parent.width + flickableDirection: Flickable.VerticalFlick + TextArea { + id: launcherProcessesTextArea + width: parent.width + TextArea.flickable: parent + text: ((shortcutInfo.launch.launcherProcesses || []).length == 0 && (shortcutInfo.launch.launchPath || "").includes("epicgames.launcher")) + ? "EpicGamesLauncher.exe\nEpicWebHelper.exe" + : (shortcutInfo.launch.launcherProcesses || [""]).reduce((acc, curr) => { + return acc + "\n" + curr; + }) + onTextChanged: function() { + shortcutInfo.launch.launcherProcesses = text.split("\n") + .map((e) => { + e = e.endsWith(".exe") ? e : e + ".exe" + return e.trim() + }) + .filter((e) => { + return e != "" && e != ".exe" + }); + } + } + + } + } + } + Row { + CheckBox { + id: ignoreLauncherCheckbox + text: qsTr("Ignore launcher for close detection") + checked: shortcutInfo.launch.ignoreLauncher + onCheckedChanged: function(){ + shortcutInfo.launch.ignoreLauncher = checked + } + } + CheckBox { + id: killLauncherCheckbox + text: qsTr("Close launcher on game exit.") + enabled: ignoreLauncherCheckbox.checked + checked: shortcutInfo.launch.killLauncher + onCheckedChanged: function(){ + shortcutInfo.launch.killLauncher = checked + } + } + } Row { + anchors.topMargin: 24 Row { CheckBox { id: extendedLogging diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index 8a07887..e9b007a 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -51,6 +51,12 @@ AppLauncher::AppLauncher( void AppLauncher::launchApp(const std::wstring& path, const std::wstring& args) { #ifdef _WIN32 + + if (!Settings::launch.launcherProcesses.empty()) { + has_extra_launchers_ = true; + spdlog::debug("Has extra launchers"); + } + if (Settings::launch.isUWP) { spdlog::info("LaunchApp is UWP, launching..."); launched_uwp_path_ = path; @@ -87,8 +93,8 @@ void AppLauncher::update() if (process_check_clock_.getElapsedTime().asMilliseconds() > 250) { pid_mutex_.lock(); #ifdef _WIN32 - if (was_egs_launch_ && pids_.empty()) { - findEgsPid(); + if (has_extra_launchers_ && pids_.empty()) { + findLauncherPids(); } if (!pids_.empty() && pids_[0] > 0) { if (Settings::launch.waitForChildProcs) { @@ -116,14 +122,14 @@ void AppLauncher::update() }); auto filtered_pids = pids_ | std::ranges::views::filter([](DWORD pid) { - return std::ranges::find(EGS_LAUNCHER_PROCNAMES_, glossi_util::GetProcName(pid)) == EGS_LAUNCHER_PROCNAMES_.end(); + return std::ranges::find(Settings::launch.launcherProcesses, glossi_util::GetProcName(pid)) == Settings::launch.launcherProcesses.end(); }); - if (was_egs_launch_ && !filtered_pids.empty()) { - egs_has_launched_game_ = true; + if (has_extra_launchers_ && !filtered_pids.empty()) { + launcher_has_launched_game_ = true; } if (Settings::launch.closeOnExit && Settings::launch.launch) { - if (was_egs_launch_ && (Settings::common.ignoreEGS || Settings::common.killEGS)) { - if (egs_has_launched_game_ && filtered_pids.empty()) { + if (has_extra_launchers_ && (Settings::launch.ignoreLauncher || Settings::launch.killLauncher)) { + if (launcher_has_launched_game_ && filtered_pids.empty()) { spdlog::info("Configured to close on all children exit. Shutting down after game launched via EGS quit..."); shutdown_(); } @@ -158,12 +164,12 @@ std::vector AppLauncher::launchedPids() pid_mutex_.lock(); std::vector res; res.reserve(pids_.size()); - if (!Settings::common.killEGS && Settings::common.ignoreEGS) { + if (!Settings::launch.killLauncher && Settings::launch.ignoreLauncher) { for (const auto& pid : pids_ | std::ranges::views::filter( [](DWORD pid) { return std::ranges::find( - EGS_LAUNCHER_PROCNAMES_, - glossi_util::GetProcName(pid)) == EGS_LAUNCHER_PROCNAMES_.end(); + Settings::launch.launcherProcesses, + glossi_util::GetProcName(pid)) == Settings::launch.launcherProcesses.end(); })) { res.push_back(pid); } @@ -255,7 +261,7 @@ void AppLauncher::getProcessHwnds() #endif #ifdef _WIN32 -bool AppLauncher::findEgsPid() +bool AppLauncher::findLauncherPids() { if (const auto pid = glossi_util::PidByName(L"EpicGamesLauncher.exe")) { spdlog::debug("Found EGS-Launcher running"); @@ -395,7 +401,7 @@ void AppLauncher::launchURL(const std::wstring& url, const std::wstring& args, c CoUninitialize(); if (url.find(L"epicgames.launcher") != std::wstring::npos) { - was_egs_launch_ = true; + has_extra_launchers_ = true; } if (execute_info.hProcess != nullptr) { if (const auto pid = GetProcessId(execute_info.hProcess); pid > 0) { @@ -407,12 +413,12 @@ void AppLauncher::launchURL(const std::wstring& url, const std::wstring& args, c } } - if (was_egs_launch_) { + if (has_extra_launchers_) { spdlog::debug("Epic Games launch; Couldn't find egs launcher PID"); pid_mutex_.lock(); const auto pid = glossi_util::PidByName(L"EpicGamesLauncher.exe"); - if (!findEgsPid()) { + if (!findLauncherPids()) { spdlog::debug("Did not find EGS-Launcher not running, retrying later..."); } pid_mutex_.unlock(); diff --git a/GlosSITarget/AppLauncher.h b/GlosSITarget/AppLauncher.h index df4032e..8293e8e 100644 --- a/GlosSITarget/AppLauncher.h +++ b/GlosSITarget/AppLauncher.h @@ -51,15 +51,9 @@ class AppLauncher { void getProcessHwnds(); std::vector& process_hwnds_; - static inline const std::array EGS_LAUNCHER_PROCNAMES_{ - L"EpicGamesLauncher.exe", - L"EpicWebHelper.exe", - }; - - - bool was_egs_launch_ = false; - bool egs_has_launched_game_ = false; - bool findEgsPid(); + bool has_extra_launchers_ = false; + bool launcher_has_launched_game_ = false; + bool findLauncherPids(); std::wstring launched_uwp_path_; diff --git a/GlosSITarget/Resource.rc b/GlosSITarget/Resource.rc index 009c048..edcf04e 100644 --- a/GlosSITarget/Resource.rc +++ b/GlosSITarget/Resource.rc @@ -51,8 +51,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,1,0,2033009290000 - PRODUCTVERSION 0,1,0,2033009290000 + FILEVERSION 0,1,0,2045006300001 + PRODUCTVERSION 0,1,0,2045006300001 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -69,12 +69,12 @@ BEGIN BEGIN VALUE "CompanyName", "Peter Repukat - FlatspotSoftware" VALUE "FileDescription", "GlosSI - SteamTarget" - VALUE "FileVersion", "0.1.0.2-33-g929abfe" + VALUE "FileVersion", "0.1.0.2-45-g63fdab1" VALUE "InternalName", "GlosSITarget" VALUE "LegalCopyright", "Copyright (C) 2021-2022 Peter Repukat - FlatspotSoftware" VALUE "OriginalFilename", "GlosSITarget.exe" VALUE "ProductName", "GlosSI" - VALUE "ProductVersion", "0.1.0.2-33-g929abfe" + VALUE "ProductVersion", "0.1.0.2-45-g63fdab1" END END BLOCK "VarFileInfo" diff --git a/GlosSITarget/Settings.h b/GlosSITarget/Settings.h index 32b505b..1acfaf5 100644 --- a/GlosSITarget/Settings.h +++ b/GlosSITarget/Settings.h @@ -39,6 +39,9 @@ inline struct Launch { bool closeOnExit = true; bool waitForChildProcs = true; bool isUWP = false; + bool ignoreLauncher = true; + bool killLauncher = false; + std::vector launcherProcesses{}; } launch; inline struct Devices { @@ -63,8 +66,6 @@ inline struct Common { bool no_uwp_overlay = false; bool disable_watchdog = false; bool extendedLogging = false; - bool ignoreEGS = true; - bool killEGS = false; std::wstring name; std::wstring icon; int version; @@ -165,6 +166,17 @@ inline void Parse(const nlohmann::basic_json<>& json) safeWStringParse(launchconf, "launchAppArgs", launch.launchAppArgs); safeParseValue(launchconf, "closeOnExit", launch.closeOnExit); safeParseValue(launchconf, "waitForChildProcs", launch.waitForChildProcs); + safeParseValue(launchconf, "killLauncher", launch.killLauncher); + safeParseValue(launchconf, "ignoreLauncher", launch.ignoreLauncher); + + if (auto launcherProcs = launchconf["launcherProcesses"]; + !launcherProcs.is_null() && !launcherProcs.empty() && launcherProcs.is_array()) { + launch.launcherProcesses.clear(); + launch.launcherProcesses.reserve(launcherProcs.size()); + for (auto& proc : launcherProcs) { + launch.launcherProcesses.push_back(std::wstring_convert>().from_bytes(proc)); + } + } } if (auto devconf = json["devices"]; !devconf.is_null() && !devconf.empty() && devconf.is_object()) { @@ -184,6 +196,10 @@ inline void Parse(const nlohmann::basic_json<>& json) safeParseValue(controllerConf, "allowDesktopConfig", controller.allowDesktopConfig); safeParseValue(controllerConf, "emulateDS4", controller.emulateDS4); } + safeParseValue(json, "extendedLogging", common.extendedLogging); + safeWStringParse(json, "name", common.name); + safeWStringParse(json, "icon", common.icon); + safeParseValue(json, "version", common.version); } catch (const nlohmann::json::exception& e) { spdlog::warn("Err parsing config: {}", e.what()); @@ -191,14 +207,6 @@ inline void Parse(const nlohmann::basic_json<>& json) catch (const std::exception& e) { spdlog::warn("Err parsing config: {}", e.what()); } - - safeParseValue(json, "extendedLogging", common.extendedLogging); - safeWStringParse(json, "name", common.name); - safeWStringParse(json, "icon", common.icon); - safeParseValue(json, "version", common.version); - safeParseValue(json, "ignoreEGS", common.ignoreEGS); - safeParseValue(json, "killEGS", common.killEGS); - if (launch.launch) { launch.isUWP = checkIsUwp(launch.launchPath); } @@ -217,8 +225,8 @@ inline void Parse(const std::vector& args) else if (arg == L"-disablewatchdog") { common.disable_watchdog = true; } - else if (arg == L"-ignoreegs") { - common.ignoreEGS = true; + else if (arg == L"-ignorelauncher") { + launch.ignoreLauncher = true; } else { configName += L" " + std::wstring(arg.begin(), arg.end());