diff --git a/GlosSIConfig/.clang-format b/GlosSIConfig/.clang-format index dc813fd..7cc5c0d 100644 --- a/GlosSIConfig/.clang-format +++ b/GlosSIConfig/.clang-format @@ -1,4 +1,5 @@ UseTab: false +BasedOnStyle: LLVM IndentWidth: 4 BreakBeforeBraces: "Stroustrup" AllowShortIfStatementsOnASingleLine: false diff --git a/GlosSIConfig/GlosSIConfig.vcxproj b/GlosSIConfig/GlosSIConfig.vcxproj index 2033835..025235d 100644 --- a/GlosSIConfig/GlosSIConfig.vcxproj +++ b/GlosSIConfig/GlosSIConfig.vcxproj @@ -137,6 +137,8 @@ + + @@ -144,6 +146,7 @@ + diff --git a/GlosSIConfig/GlosSIConfig.vcxproj.filters b/GlosSIConfig/GlosSIConfig.vcxproj.filters index e1d59d1..4b2ed94 100644 --- a/GlosSIConfig/GlosSIConfig.vcxproj.filters +++ b/GlosSIConfig/GlosSIConfig.vcxproj.filters @@ -65,6 +65,15 @@ + + qml + + + qml + + + qml + diff --git a/GlosSIConfig/Resource.rc b/GlosSIConfig/Resource.rc index ee15a05..315c1a0 100644 --- a/GlosSIConfig/Resource.rc +++ b/GlosSIConfig/Resource.rc @@ -51,8 +51,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,0,8,003002880001 - PRODUCTVERSION 0,0,8,003002880001 + FILEVERSION 0,0,8,1023005406006 + PRODUCTVERSION 0,0,8,1023005406006 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.8.0-3-g288bba1" + VALUE "FileVersion", "0.0.8.1-23-g54e6bf6" VALUE "InternalName", "GlosSIConfig" VALUE "LegalCopyright", "Copyright (C) 2021 Peter Repukat - FlatspotSoftware" VALUE "OriginalFilename", "GlosSIConfig.exe" VALUE "ProductName", "GlosSI" - VALUE "ProductVersion", "0.0.8.0-3-g288bba1" + VALUE "ProductVersion", "0.0.8.1-23-g54e6bf6" END END BLOCK "VarFileInfo" @@ -756,6 +756,342 @@ IDI_ICON1 ICON "..\GloSC_Icon.ico" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GlosSIConfig/UIModel.cpp b/GlosSIConfig/UIModel.cpp index a1c2451..f75ac62 100644 --- a/GlosSIConfig/UIModel.cpp +++ b/GlosSIConfig/UIModel.cpp @@ -39,16 +39,16 @@ UIModel::UIModel() : QObject(nullptr) std::filesystem::create_directories(path); config_path_ = path; - config_dir_name_ = QString::fromStdWString((path /= "Targets").wstring().data()); + config_dir_name_ = QString::fromStdWString((path /= "Targets").wstring()); if (!std::filesystem::exists(path)) std::filesystem::create_directories(path); parseShortcutVDF(); - readConfigs(); + readTargetConfigs(); } -void UIModel::readConfigs() +void UIModel::readTargetConfigs() { QDir dir(config_dir_name_); auto entries = dir.entryList(QDir::Files, QDir::SortFlag::Name); @@ -68,29 +68,13 @@ void UIModel::readConfigs() const auto data = file.readAll(); file.close(); const auto jsondoc = QJsonDocument::fromJson(data); - const auto filejson = jsondoc.object(); - - QJsonObject json; - json["version"] = filejson["version"]; - json["icon"] = filejson["icon"]; - json["launch"] = filejson["launch"]["launch"]; - json["launchPath"] = filejson["launch"]["launchPath"]; - json["launchAppArgs"] = filejson["launch"]["launchAppArgs"]; - json["closeOnExit"] = filejson["launch"]["closeOnExit"]; - json["waitForChildProcs"] = filejson["launch"]["waitForChildProcs"]; - json["hideDevices"] = filejson["devices"]["hideDevices"]; - json["realDeviceIds"] = filejson["devices"]["realDeviceIds"]; - json["windowMode"] = filejson["window"]["windowMode"]; - json["maxFps"] = filejson["window"]["maxFps"]; - json["scale"] = filejson["window"]["scale"]; - json["disableOverlay"] = filejson["window"]["disableOverlay"]; - json["maxControllers"] = filejson["controller"]["maxControllers"]; - json["allowDesktopConfig"] = filejson["controller"]["allowDesktopConfig"]; - json["emulateDS4"] = filejson["controller"]["emulateDS4"]; - - json["name"] = filejson.contains("name") ? filejson["name"] : QString(name).replace(QRegularExpression("\\.json"), ""); - - targets_.append(json.toVariantMap()); + auto filejson = jsondoc.object(); + + filejson["name"] = filejson.contains("name") + ? filejson["name"].toString() + : QString(name).replace(QRegularExpression("\\.json"), ""); + + targets_.append(filejson.toVariantMap()); }); emit targetListChanged(); @@ -237,6 +221,46 @@ QVariantMap UIModel::manualProps(QVariant shortcut) return res; } +void UIModel::enableSteamInputXboxSupport() +{ + if (foundSteam()) { + const std::filesystem::path config_path = std::wstring(getSteamPath()) + user_data_path_.toStdWString() + getSteamUserId() + user_config_file_.toStdWString(); + if (!std::filesystem::exists(config_path)) { + qDebug() << "localconfig.vdf does not exist."; + } + QFile file(config_path); + if (file.open(QIODevice::Text | QIODevice::ReadOnly)) { + QTextStream in(&file); + QStringList lines; + QString line = in.readLine(); + // simple approach is enough... + while (!in.atEnd()) { + if (line.contains("SteamController_XBoxSupport")) { + if (line.contains("1")) { + qDebug() << "\"SteamController_XBoxSupport\" is already enabled! aborting write..."; + file.close(); + return; + } + qDebug() << "found \"SteamController_XBoxSupport\" line, replacing value..."; + line.replace("0", "1"); + } + lines.push_back(line); + line = in.readLine(); + } + file.close(); + QFile updatedFile(config_path); + if (updatedFile.open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) { + qDebug() << "writing localconfig.vdf..."; + QTextStream out(&updatedFile); + for (const auto& l : lines) { + out << l << "\n"; + } + } + updatedFile.close(); + } + } +} + #ifdef _WIN32 QVariantList UIModel::uwpApps() { @@ -319,60 +343,41 @@ void UIModel::setAcrylicEffect(bool has_acrylic_affect) emit acrylicChanged(); } -void UIModel::writeTarget(const QJsonObject& json, const QString& name) +void UIModel::writeTarget(const QJsonObject& json, const QString& name) const { auto path = config_path_; path /= config_dir_name_.toStdWString(); path /= (QString(name).replace(QRegularExpression("[\\\\/:*?\"<>|]"), "") + ".json").toStdWString(); QFile file(path); if (!file.open(QIODevice::Text | QIODevice::ReadWrite)) { - // meh + qDebug() << "Couldn't open file for writing: " << path; return; } - QJsonObject fileJson; - fileJson["version"] = json["version"]; - fileJson["icon"] = json["icon"]; - fileJson["name"] = json["name"]; - - QJsonObject launchObject; - launchObject["launch"] = json["launch"]; - launchObject["launchPath"] = json["launchPath"]; - launchObject["launchAppArgs"] = json["launchAppArgs"]; - launchObject["closeOnExit"] = json["closeOnExit"]; - launchObject["waitForChildProcs"] = json["waitForChildProcs"]; - fileJson["launch"] = launchObject; - - QJsonObject devicesObject; - devicesObject["hideDevices"] = json["hideDevices"]; - devicesObject["realDeviceIds"] = json["realDeviceIds"]; - fileJson["devices"] = devicesObject; - - QJsonObject windowObject; - windowObject["windowMode"] = json["windowMode"]; - windowObject["maxFps"] = json["maxFps"]; - windowObject["scale"] = json["scale"]; - windowObject["disableOverlay"] = json["disableOverlay"]; - fileJson["window"] = windowObject; - - QJsonObject controllerObject; - controllerObject["maxControllers"] = json["maxControllers"]; - controllerObject["allowDesktopConfig"] = json["allowDesktopConfig"]; - controllerObject["emulateDS4"] = json["emulateDS4"]; - fileJson["controller"] = controllerObject; - - auto wtf = QString(QJsonDocument(fileJson).toJson(QJsonDocument::Indented)).toStdString(); - file.write(wtf.data()); + + file.write( + QString(QJsonDocument(json).toJson(QJsonDocument::Indented)) + .toStdString() + .data() + ); file.close(); } std::filesystem::path UIModel::getSteamPath() const { + try { #ifdef _WIN32 - // TODO: check if keys/value exist - // steam should always be open and have written reg values... - winreg::RegKey key{HKEY_CURRENT_USER, L"SOFTWARE\\Valve\\Steam"}; - const auto res = key.GetStringValue(L"SteamPath"); - return res; + // TODO: check if keys/value exist + // steam should always be open and have written reg values... + winreg::RegKey key{HKEY_CURRENT_USER, L"SOFTWARE\\Valve\\Steam"}; + if (!key.IsValid()) { + return ""; + } + const auto res = key.GetStringValue(L"SteamPath"); + return res; + } + catch (...) { + return ""; + } #else return L""; // TODO LINUX #endif @@ -381,22 +386,46 @@ std::filesystem::path UIModel::getSteamPath() const std::wstring UIModel::getSteamUserId() const { #ifdef _WIN32 - // TODO: check if keys/value exist - // steam should always be open and have written reg values... - winreg::RegKey key{HKEY_CURRENT_USER, L"SOFTWARE\\Valve\\Steam\\ActiveProcess"}; - const auto res = std::to_wstring(key.GetDwordValue(L"ActiveUser")); - if (res == L"0") { - qDebug() << "Steam not open?"; + try { + // TODO: check if keys/value exist + // steam should always be open and have written reg values... + winreg::RegKey key{HKEY_CURRENT_USER, L"SOFTWARE\\Valve\\Steam\\ActiveProcess"}; + if (!key.IsValid()) { + return L"0"; + } + const auto res = std::to_wstring(key.GetDwordValue(L"ActiveUser")); + if (res == L"0") { + qDebug() << "Steam not open?"; + } + return res; + } catch(...) { + return L"0"; } - return res; #else return L""; // TODO LINUX #endif } +bool UIModel::foundSteam() const +{ + if (getSteamPath() == "" || getSteamUserId() == L"0") { + return false; + } + const std::filesystem::path user_config_dir = std::wstring(getSteamPath()) + user_data_path_.toStdWString() + getSteamUserId(); + if (!std::filesystem::exists(user_config_dir)) { + return false; + } + return true; +} + void UIModel::parseShortcutVDF() { const std::filesystem::path config_path = std::wstring(getSteamPath()) + user_data_path_.toStdWString() + getSteamUserId() + shortcutsfile_.toStdWString(); + if (!std::filesystem::exists(config_path)) { + qDebug() << "Shortcuts file does not exist."; + return; + } + try { shortcuts_vdf_ = VDFParser::Parser::parseShortcuts(config_path, qDebug()); } @@ -404,3 +433,39 @@ void UIModel::parseShortcutVDF() qDebug() << "Error parsing VDF: " << e.what(); } } + +bool UIModel::isSteamInputXboxSupportEnabled() const +{ + // return true as default to not bug the user in error cases. + if (foundSteam()) { + const std::filesystem::path config_path = std::wstring(getSteamPath()) + user_data_path_.toStdWString() + getSteamUserId() + user_config_file_.toStdWString(); + if (!std::filesystem::exists(config_path)) { + qDebug() << "localconfig.vdf does not exist."; + return true; + } + QFile file(config_path); + if (file.open(QIODevice::Text | QIODevice::ReadOnly)) { + QTextStream in(&file); + QString line = in.readLine(); + // simple, regex approach should be enough... + while (!in.atEnd()) { + if (line.contains("SteamController_XBoxSupport")) { + file.close(); + if (line.contains("1")) { + qDebug() << "\"SteamController_XBoxSupport\" is enabled!"; + return true; + } + qDebug() << "\"SteamController_XBoxSupport\" is disabled!"; + return false; + } + line = in.readLine(); + } + qDebug() << "couldn't find \"SteamController_XBoxSupport\" in localconfig.vdf"; + file.close(); + } + else { + qDebug() << "could not open localconfig.vdf"; + } + } + return true; +} diff --git a/GlosSIConfig/UIModel.h b/GlosSIConfig/UIModel.h index d9c1595..4ec241a 100644 --- a/GlosSIConfig/UIModel.h +++ b/GlosSIConfig/UIModel.h @@ -27,11 +27,13 @@ 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(bool foundSteam READ foundSteam CONSTANT) + Q_PROPERTY(bool steamInputXboxSupportEnabled READ isSteamInputXboxSupportEnabled CONSTANT) public: UIModel(); - Q_INVOKABLE void readConfigs(); + Q_INVOKABLE void readTargetConfigs(); Q_INVOKABLE QVariantList getTargetList() const; Q_INVOKABLE void addTarget(QVariant shortcut); Q_INVOKABLE void updateTarget(int index, QVariant shortcut); @@ -41,6 +43,7 @@ class UIModel : public QObject { 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); Q_INVOKABLE QVariantMap manualProps(QVariant shortcut); + Q_INVOKABLE void enableSteamInputXboxSupport(); #ifdef _WIN32 Q_INVOKABLE QVariantList uwpApps(); #endif @@ -61,25 +64,30 @@ class UIModel : public QObject { void targetListChanged(); private: +#ifdef _WIN32 + bool is_windows_ = true; +#else + bool is_windows_ = false; +#endif + bool has_acrylic_affect_ = false; + std::filesystem::path config_path_; QString config_dir_name_; - - void writeTarget(const QJsonObject& json, const QString& name); - std::filesystem::path getSteamPath() const; - std::wstring getSteamUserId() const; - void parseShortcutVDF(); QString shortcutsfile_ = "/config/shortcuts.vdf"; + QString user_config_file_ = "/config/localconfig.vdf"; QString user_data_path_ = "/userdata/"; QVariantList targets_; std::vector shortcuts_vdf_; + + void writeTarget(const QJsonObject& json, const QString& name) const; -#ifdef _WIN32 - bool is_windows_ = true; -#else - bool is_windows_ = false; -#endif - bool has_acrylic_affect_ = false; + std::filesystem::path getSteamPath() const; + std::wstring getSteamUserId() const; + bool foundSteam() const; + void parseShortcutVDF(); + + bool isSteamInputXboxSupportEnabled() const; }; diff --git a/GlosSIConfig/qml.qrc b/GlosSIConfig/qml.qrc index 80ff2b9..b273ad9 100644 --- a/GlosSIConfig/qml.qrc +++ b/GlosSIConfig/qml.qrc @@ -15,5 +15,9 @@ noise.png GloSC_Icon_small.png svg/help_outline_white_24dp.svg + qml/SteamNotFoundDialog.qml + qml/SteamInputXboxDisabledDialog.qml + qml/CollapsiblePane.qml + svg/expand_more_white_24dp.svg diff --git a/GlosSIConfig/qml/CollapsiblePane.qml b/GlosSIConfig/qml/CollapsiblePane.qml new file mode 100644 index 0000000..6dea9e0 --- /dev/null +++ b/GlosSIConfig/qml/CollapsiblePane.qml @@ -0,0 +1,97 @@ +/* +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 2.9 +import QtQuick.Controls 2.9 +import QtQuick.Controls.Material 2.9 +import QtQuick.Controls.Material.impl 2.9 + + +RPane { + property alias title: paneTitle.text + width: parent.width + + property alias content: ldr.sourceComponent + clip: true + height: paneHeader.height + collapseColumn.spacing + property bool collapsed: true + id: collapsePane + + Behavior on height { + NumberAnimation { + duration: 300 + easing.type: Easing.InOutQuad + } + } + + Column { + id: collapseColumn + width: parent.width + spacing: 16 + Item { + id: paneHeader + width: parent.width + height: paneTitle.height + 32 + Label { + id: paneTitle + anchors.left: parent.left + anchors.leftMargin: 4 + font.bold: true + font.pixelSize: 24 + anchors.top: parent.top + anchors.topMargin: 14 + } + RoundButton { + width: 48 + height: 48 + Material.elevation: 0 + anchors.rightMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + onClicked: function(){ + collapsed = !collapsed; + if (collapsed) { + collapsePane.height = paneHeader.height + collapseColumn.spacing + } else { + collapsePane.height = paneHeader.height + collapseColumn.spacing * 3 + ldr.item.height + } + } + Image { + id: arrowImg + anchors.centerIn: parent + source: "qrc:/svg/expand_more_white_24dp.svg" + width: 24 + height: 24 + transform: Rotation{ + angle: collapsed ? 0 : 180 + origin.x: arrowImg.width/2 + origin.y: arrowImg.height/2 + Behavior on angle { + NumberAnimation { + duration: 125 + easing.type: Easing.InOutQuad + } + } + } + } + anchors.right: parent.right + } + } + Loader { + id: ldr + width: parent.width + } + } +} diff --git a/GlosSIConfig/qml/ShortcutProps.qml b/GlosSIConfig/qml/ShortcutProps.qml index 14ba84c..8d2a7da 100644 --- a/GlosSIConfig/qml/ShortcutProps.qml +++ b/GlosSIConfig/qml/ShortcutProps.qml @@ -29,62 +29,62 @@ Item { signal cancel() signal done(var shortcut) - property var shortcutInfo: ({ - version: 1, - name: null, - launch: false, - launchPath: null, - launchAppArgs: null, - closeOnExit: true, - waitForChildProcs: true, - hideDevices: true, - windowMode: false, - maxFps: null, - scale: null, - icon: null, - maxControllers: 4, - disableOverlay: false, - realDeviceIds: false, - allowDesktopConfig: false, - emulateDS4: false, - }) + property var shortcutInfo: ({}) function resetInfo() { shortcutInfo = ({ - version: 1, - name: null, - launch: false, - launchPath: null, - launchAppArgs: null, - closeOnExit: true, - waitForChildProcs: true, - hideDevices: true, - windowMode: false, - maxFps: null, - scale: null, - icon: null, - maxControllers: 4, - disableOverlay: false, - realDeviceIds: false, - allowDesktopConfig: false, - emulateDS4: false, + "controller": { + "maxControllers": 1, + "emulateDS4": false, + "allowDesktopConfig": false + }, + "devices": { + "hideDevices": true, + "realDeviceIds": false + }, + "icon": null, + "launch": { + "closeOnExit": true, + "launch": false, + "launchAppArgs": null, + "launchPath": null, + "waitForChildProcs": true + }, + "name": null, + "version": 1, + "window": { + "disableOverlay": false, + "maxFps": null, + "scale": null, + "windowMode": false + }, + "extendedLogging": false }) } + + Component.onCompleted: function() { + resetInfo() + } onShortcutInfoChanged: function() { nameInput.text = shortcutInfo.name || "" - launchApp.checked = shortcutInfo.launch || false - pathInput.text = shortcutInfo.launchPath || "" - argsInput.text = shortcutInfo.launchAppArgs || "" - closeOnExit.checked = shortcutInfo.closeOnExit || false - waitForChildren.checked = shortcutInfo.waitForChildProcs - hideDevices.checked = shortcutInfo.hideDevices || false - windowMode.checked = shortcutInfo.windowMode || false - maxControllersSpinBox.value = shortcutInfo.maxControllers - disableOverlayCheckbox.checked = shortcutInfo.disableOverlay || false - realDeviceIds.checked = shortcutInfo.realDeviceIds || false - allowDesktopConfig.checked = shortcutInfo.allowDesktopConfig || false - emulateDS4.checked = shortcutInfo.emulateDS4 || false + if (extendedLogging) { + extendedLogging.checked = shortcutInfo.extendedLogging || false + } + launchApp.checked = shortcutInfo.launch.launch + pathInput.text = shortcutInfo.launch.launchPath || "" + argsInput.text = shortcutInfo.launch.launchAppArgs || "" + closeOnExit.checked = shortcutInfo.launch.closeOnExit + waitForChildren.checked = shortcutInfo.launch.waitForChildProcs + hideDevices.checked = shortcutInfo.devices.hideDevices + realDeviceIds.checked = shortcutInfo.devices.realDeviceIds + windowMode.checked = shortcutInfo.window.windowMode + disableOverlayCheckbox.checked = shortcutInfo.window.disableOverlay + scaleSpinBox.value = shortcutInfo.window.scale + maxFPSSpinBox.value = shortcutInfo.window.maxFps + maxControllersSpinBox.value = shortcutInfo.controller.maxControllers + allowDesktopConfig.checked = shortcutInfo.controller.allowDesktopConfig + emulateDS4.checked = shortcutInfo.controller.emulateDS4 } Flickable { @@ -144,7 +144,6 @@ Item { } RPane { width: parent.width - height: 248 radius: 4 Material.elevation: 32 bgOpacity: 0.97 @@ -155,17 +154,18 @@ Item { Row { spacing: 32 width: parent.width - height: closeOnExitCol.height CheckBox { id: launchApp text: qsTr("Launch app") - checked: shortcutInfo.launch + checked: shortcutInfo.launch.launch onCheckedChanged: function() { - shortcutInfo.launch = checked + shortcutInfo.launch.launch = checked if (checked) { - closeOnExit.enabled = true; - if (closeOnExit.checked) { - waitForChildren.enabled = true; + if (closeOnExit) { + closeOnExit.enabled = true; + if (closeOnExit.checked) { + waitForChildren.enabled = true; + } } allowDesktopConfig.enabled = true; } else { @@ -175,54 +175,6 @@ Item { } } } - Column { - id: closeOnExitCol - spacing: 2 - CheckBox { - id: closeOnExit - text: qsTr("Close when launched app quits") - checked: shortcutInfo.closeOnExit - onCheckedChanged: function() { - shortcutInfo.closeOnExit = checked - if (checked) { - waitForChildren.enabled = true; - } else { - waitForChildren.enabled = false; - } - } - } - Label { - text: qsTr("Recommended to disable for launcher-games") - wrapMode: Text.WordWrap - width: parent.width - leftPadding: 32 - topPadding: -8 - } - CheckBox { - id: waitForChildren - text: qsTr("Wait for child processes") - checked: shortcutInfo.waitForChildProcs - onCheckedChanged: function(){ - shortcutInfo.waitForChildProcs = checked - } - } - } - Column { - spacing: 2 - CheckBox { - id: allowDesktopConfig - text: qsTr("Allow desktop-config") - checked: shortcutInfo.allowDesktopConfig - onCheckedChanged: function(){ - shortcutInfo.allowDesktopConfig = checked - } - } - Label { - text: qsTr("Use desktop-config if launched application is not focused") - leftPadding: 32 - topPadding: -8 - } - } } Item { width: 1 @@ -231,14 +183,17 @@ Item { RowLayout { id: launchlayout spacing: 4 - width: parent.width + 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 - : null + : '' Layout.preferredWidth: 48 Layout.preferredHeight: 48 visible: shortcutInfo.icon @@ -267,8 +222,8 @@ Item { id: pathInput placeholderText: qsTr("...") enabled: launchApp.checked - text: shortcutInfo.launchPath || "" - onTextChanged: shortcutInfo.launchPath = text + text: shortcutInfo.launch.launchPath || "" + onTextChanged: shortcutInfo.launch.launchPath = text } } Button { @@ -304,8 +259,8 @@ Item { anchors.topMargin: 4 id: argsInput enabled: launchApp.checked - text: shortcutInfo.launchAppArgs - onTextChanged: shortcutInfo.launchAppArgs = text + text: shortcutInfo.launch.launchAppArgs + onTextChanged: shortcutInfo.launch.launchAppArgs = text } } } @@ -315,183 +270,467 @@ Item { width: 1 height: 8 } - Row { - spacing: 16 - width: parent.width - RPane { - width: parent.width / 2 - 8 - height: 264 - radius: 4 - Material.elevation: 32 - bgOpacity: 0.97 + CollapsiblePane { + radius: 4 + Material.elevation: 32 + bgOpacity: 0.97 + title: qsTr("Advanced") + content: + Column { + spacing: 16 - Column { - spacing: 2 + RPane { width: parent.width - Row { - CheckBox { - id: hideDevices - text: qsTr("Hide (Real) Controllers") - checked: shortcutInfo.hideDevices - onCheckedChanged: shortcutInfo.hideDevices = checked - } - RoundButton { - onClicked: () => { - helpInfoDialog.titleText = qsTr("Hide (Real) Controllers") - helpInfoDialog.text = - qsTr("Hides real game controllers from the system\nThis may prevent doubled inputs") - + "\n" - + qsTr("You can change this setting and which devices are hidden in the GlosSI overlay") - - helpInfoDialog.open() + radius: 4 + Material.elevation: 32 + bgOpacity: 0.97 + height: advancedLaunchCol.height + 24 + Column { + id: advancedLaunchCol + spacing: 4 + height: advancedLaunchedRow.height + Row { + id: advancedLaunchedRow + spacing: 32 + width: parent.width + height: closeOnExitCol.height + Column { + id: closeOnExitCol + spacing: 2 + CheckBox { + id: closeOnExit + text: qsTr("Close when launched app quits") + checked: shortcutInfo.launch.closeOnExit + onCheckedChanged: function() { + shortcutInfo.launch.closeOnExit = checked + if (checked) { + waitForChildren.enabled = true; + } else { + waitForChildren.enabled = false; + } + } + } + Label { + text: qsTr("Recommended to disable for launcher-games") + wrapMode: Text.WordWrap + width: parent.width + leftPadding: 32 + topPadding: -8 + } + CheckBox { + id: waitForChildren + text: qsTr("Wait for child processes") + checked: shortcutInfo.launch.waitForChildProcs + onCheckedChanged: function(){ + shortcutInfo.launch.waitForChildProcs = checked + } + } } - width: 48 - height: 48 - Material.elevation: 0 - anchors.topMargin: 16 - Image { - anchors.centerIn: parent - source: "qrc:/svg/help_outline_white_24dp.svg" - width: 24 - height: 24 + Column { + spacing: 2 + CheckBox { + id: allowDesktopConfig + text: qsTr("Allow desktop-config") + checked: shortcutInfo.controller.allowDesktopConfig + onCheckedChanged: function(){ + shortcutInfo.controller.allowDesktopConfig = checked + } + } + Label { + text: qsTr("Allow desktop-config if launched application is not focused") + leftPadding: 32 + topPadding: -8 + } } } } - Item { - width: 1 - height: 4 - } - Row { - CheckBox { - id: realDeviceIds - text: qsTr("Use real device (USB)-IDs") - checked: shortcutInfo.realDeviceIds - onCheckedChanged: shortcutInfo.realDeviceIds = checked - } - RoundButton { - onClicked: () => { - helpInfoDialog.titleText = qsTr("Use real device (USB)-IDs") - helpInfoDialog.text = - qsTr("Only enable if input's are not recognized by the game") - + "\n" - + qsTr("If enabled, device-hiding won't work.\nUse the \"Max. Controller count\" setting!") - - helpInfoDialog.open() + } + + Row { + spacing: 16 + width: parent.width + + RPane { + width: parent.width / 2 - 8 + height: 248 + radius: 4 + Material.elevation: 32 + bgOpacity: 0.97 + + Column { + spacing: 0 + width: parent.width + Row { + CheckBox { + id: hideDevices + text: qsTr("Hide (Real) Controllers") + checked: shortcutInfo.devices.hideDevices + onCheckedChanged: shortcutInfo.devices.hideDevices = checked + } + RoundButton { + onClicked: () => { + helpInfoDialog.titleText = qsTr("Hide (Real) Controllers") + helpInfoDialog.text = + qsTr("Hides real game controllers from the system\nThis may prevent doubled inputs") + + "\n" + + qsTr("You can change this setting and which devices are hidden in the GlosSI overlay") + + helpInfoDialog.open() + } + width: 48 + height: 48 + Material.elevation: 0 + anchors.topMargin: 16 + Image { + anchors.centerIn: parent + source: "qrc:/svg/help_outline_white_24dp.svg" + width: 24 + height: 24 + } + } } - width: 48 - height: 48 - Material.elevation: 0 - anchors.topMargin: 16 - Image { - anchors.centerIn: parent - source: "qrc:/svg/help_outline_white_24dp.svg" - width: 24 - height: 24 + Item { + width: 1 + height: 4 } - } - } - Item { - width: 1 - height: 4 - } - Row { - CheckBox { - id: emulateDS4 - text: qsTr("Emulate DS4") - checked: shortcutInfo.emulateDS4 - onCheckedChanged: shortcutInfo.emulateDS4 = checked - } - RoundButton { - onClicked: () => { - helpInfoDialog.titleText = qsTr("Emulate DS4") - helpInfoDialog.text = - qsTr("Instead of X360 Pad") - + "\n" - + qsTr("Disable \"Playstation Configuration support\" in Steam") - helpInfoDialog.open() + Row { + CheckBox { + id: realDeviceIds + text: qsTr("Use real device (USB)-IDs") + checked: shortcutInfo.devices.realDeviceIds + onCheckedChanged: shortcutInfo.devices.realDeviceIds = checked + } + RoundButton { + onClicked: () => { + helpInfoDialog.titleText = qsTr("Use real device (USB)-IDs") + helpInfoDialog.text = + qsTr("Only enable if input's are not recognized by the game") + + "\n" + + qsTr("If enabled, device-hiding won't work.\nUse the \"Max. Controller count\" setting!") + + helpInfoDialog.open() + } + width: 48 + height: 48 + Material.elevation: 0 + anchors.topMargin: 16 + Image { + anchors.centerIn: parent + source: "qrc:/svg/help_outline_white_24dp.svg" + width: 24 + height: 24 + } + } + } + Item { + width: 1 + height: 4 + } + Row { + CheckBox { + id: emulateDS4 + text: qsTr("Emulate DS4") + checked: shortcutInfo.controller.emulateDS4 || false + onCheckedChanged: shortcutInfo.controller.emulateDS4 = checked + } + RoundButton { + onClicked: () => { + helpInfoDialog.titleText = qsTr("Emulate DS4") + helpInfoDialog.text = + qsTr("Emulates a DS4 instead of X360 Pad") + + "\n" + qsTr("for usage with, for example, PSNow") + + "\n" + + qsTr("If enabled you have to disable \"Playstation Configuration support\" in Steam") + helpInfoDialog.open() + } + width: 48 + height: 48 + Material.elevation: 0 + anchors.topMargin: 16 + Image { + anchors.centerIn: parent + source: "qrc:/svg/help_outline_white_24dp.svg" + width: 24 + height: 24 + } + } } - width: 48 - height: 48 - Material.elevation: 0 - anchors.topMargin: 16 - Image { - anchors.centerIn: parent - source: "qrc:/svg/help_outline_white_24dp.svg" - width: 24 - height: 24 + Item { + width: 1 + height: 4 + } + Row { + leftPadding: 16 + Label { + text: qsTr("Max. emulated controllers") + topPadding: 16 + } + SpinBox { + id: maxControllersSpinBox + width: 128 + editable: true + value: shortcutInfo.controller.maxControllers + from: 0 + to: 4 + onValueChanged: shortcutInfo.controller.maxControllers = value + } + RoundButton { + onClicked: () => { + helpInfoDialog.titleText = qsTr("Max. emulated controllers") + helpInfoDialog.text = + qsTr("GlosSI will only provide [NUMBER] of controllers") + + "\n" + + qsTr("Required to set to actually connected controller count when using \"real device IDs\" ") + helpInfoDialog.open() + } + width: 48 + height: 48 + Material.elevation: 0 + anchors.topMargin: 16 + Image { + anchors.centerIn: parent + source: "qrc:/svg/help_outline_white_24dp.svg" + width: 24 + height: 24 + } + } } } } - Item { - width: 1 - height: 4 - } - Row { - leftPadding: 16 - Label { - text: qsTr("Max. emulated controllers") - topPadding: 16 - } - SpinBox { - id: maxControllersSpinBox - width: 128 - value: 4 - from: 0 - to: 4 - onValueChanged: shortcutInfo.maxControllers = value + RPane { + width: parent.width / 2 - 8 + height: 248 + radius: 4 + Material.elevation: 32 + bgOpacity: 0.97 + Column { + spacing: 0 + width: parent.width + Row { + CheckBox { + id: windowMode + text: qsTr("Steam/GlosSI overlay as separate window") + checked: shortcutInfo.window.windowMode + onCheckedChanged: shortcutInfo.window.windowMode = checked + } + RoundButton { + onClicked: () => { + helpInfoDialog.titleText = qsTr("Steam/GlosSI overlay as separate window") + helpInfoDialog.text = + qsTr("Doesn't show overlay on top, but as separate window") + + "\n" + + qsTr("Use if blackscreen-issues are encountered.") + + helpInfoDialog.open() + } + width: 48 + height: 48 + Material.elevation: 0 + anchors.topMargin: 16 + Image { + anchors.centerIn: parent + source: "qrc:/svg/help_outline_white_24dp.svg" + width: 24 + height: 24 + } + } + } + Item { + width: 1 + height: 4 + } + + Row { + CheckBox { + id: disableOverlayCheckbox + text: qsTr("Disable Steam/GlosSI overlay") + checked: shortcutInfo.window.disableOverlay + onCheckedChanged: shortcutInfo.window.disableOverlay = checked + } + RoundButton { + onClicked: () => { + helpInfoDialog.titleText = qsTr("Disable Steam/GlosSI overlay") + helpInfoDialog.text = + qsTr("Only controller emulation - No extra window") + + "\n" + + qsTr("Might help with Steam remote play.") + + helpInfoDialog.open() + } + width: 48 + height: 48 + Material.elevation: 0 + anchors.topMargin: 16 + Image { + anchors.centerIn: parent + source: "qrc:/svg/help_outline_white_24dp.svg" + width: 24 + height: 24 + } + } + } + Item { + width: 1 + height: 4 + } + Row { + leftPadding: 16 + Label { + text: qsTr("GlosSI-Overlay scale") + topPadding: 16 + } + SpinBox { + id: scaleSpinBox + width: 172 + from: -100 + value: shortcutInfo.window.scale * 100 || 0 + to: 350 + stepSize: 10 + editable: true + + property int decimals: 2 + property real realValue: value / 100 + + validator: DoubleValidator { + bottom: Math.min(scaleSpinBox.from, scaleSpinBox.to) + top: Math.max(scaleSpinBox.from, scaleSpinBox.to) + } + + textFromValue: function(value, locale) { + return Number(value / 100).toLocaleString(locale, 'f', scaleSpinBox.decimals) + } + + valueFromText: function(text, locale) { + return Number.fromLocaleString(locale, text) * 100 + } + onValueChanged: function() { + if (value <= 0) { + shortcutInfo.window.scale = null + return + } + shortcutInfo.window.scale = value / 100 + } + } + RoundButton { + onClicked: () => { + helpInfoDialog.titleText = qsTr("GloSI-Overlay scaling") + helpInfoDialog.text = + qsTr("Scales the elements of the GlosSI-Overlay (not Steam Overlay)") + + "\n" + + qsTr(" <= 0.0 to use auto-detection") + + helpInfoDialog.open() + } + width: 48 + height: 48 + Material.elevation: 0 + anchors.topMargin: 16 + Image { + anchors.centerIn: parent + source: "qrc:/svg/help_outline_white_24dp.svg" + width: 24 + height: 24 + } + } + } + Item { + width: 1 + height: 4 + } + Row { + leftPadding: 16 + Label { + text: qsTr("Max. Overlay FPS") + topPadding: 16 + } + SpinBox { + id: maxFPSSpinBox + width: 172 + from: -1 + value: shortcutInfo.window.maxFps || 0 + to: 244 + stepSize: 5 + editable: true + + onValueChanged: function() { + if (value <= 0) { + shortcutInfo.window.maxFps = null + return + } + shortcutInfo.window.maxFps = value + } + } + RoundButton { + onClicked: () => { + helpInfoDialog.titleText = qsTr("Max. Overlay FPS") + helpInfoDialog.text = + qsTr("Restricts the FPS of the overlay to the given value") + + "\n" + + qsTr(" <= 0.0 to use screen refresh rate") + + helpInfoDialog.open() + } + width: 48 + height: 48 + Material.elevation: 0 + anchors.topMargin: 16 + Image { + anchors.centerIn: parent + source: "qrc:/svg/help_outline_white_24dp.svg" + width: 24 + height: 24 + } + } + } } } } - } - RPane { - width: parent.width / 2 - 8 - height: 264 - radius: 4 - Material.elevation: 32 - bgOpacity: 0.97 - Column { - spacing: 2 + + RPane { width: parent.width - CheckBox { - id: windowMode - text: qsTr("Steam/GlosSI overlay as separate window") - checked: shortcutInfo.windowMode - onCheckedChanged: shortcutInfo.windowMode = checked - } - Label { - text: qsTr("Doesn't show overlay on top, but as separate window") - wrapMode: Text.WordWrap - width: parent.width - leftPadding: 32 - topPadding: -8 - } - Label { - text: qsTr("Use if blackscreen-issues are encountered.") - wrapMode: Text.WordWrap - width: parent.width - leftPadding: 32 - } - Item { - width: 1 - height: 4 - } - CheckBox { - id: disableOverlayCheckbox - text: qsTr("Disable Steam/GlosSI overlay") - checked: shortcutInfo.disableOverlay - onCheckedChanged: shortcutInfo.disableOverlay = checked - } - Label { - text: qsTr("Only controller emulation - No extra window") - wrapMode: Text.WordWrap - width: parent.width - leftPadding: 32 - topPadding: -8 + radius: 4 + Material.elevation: 32 + bgOpacity: 0.97 + Column { + spacing: 4 + Row { + Row { + CheckBox { + id: extendedLogging + text: qsTr("Extended Logging") + checked: shortcutInfo.extendedLogging + onCheckedChanged: shortcutInfo.extendedLogging = checked + } + // RoundButton { + // onClicked: () => { + // helpInfoDialog.titleText = qsTr("Hide (Real) Controllers") + // helpInfoDialog.text = + // qsTr("Hides real game controllers from the system\nThis may prevent doubled inputs") + // + "\n" + // + qsTr("You can change this setting and which devices are hidden in the GlosSI overlay") + + // helpInfoDialog.open() + // } + // width: 48 + // height: 48 + // Material.elevation: 0 + // anchors.topMargin: 16 + // Image { + // anchors.centerIn: parent + // source: "qrc:/svg/help_outline_white_24dp.svg" + // width: 24 + // height: 24 + // } + // } + } + } } } } } + Item { id: bottomspacing width: 1 diff --git a/GlosSIConfig/qml/SteamInputXboxDisabledDialog.qml b/GlosSIConfig/qml/SteamInputXboxDisabledDialog.qml new file mode 100644 index 0000000..7e2350c --- /dev/null +++ b/GlosSIConfig/qml/SteamInputXboxDisabledDialog.qml @@ -0,0 +1,111 @@ +/* +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 + + 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 + clip: true + Column { + spacing: 4 + bottomPadding: 96 + Label { + id: titlelabel + text: qsTr("Steam Input Xbox support disabled") + font.pixelSize: 24 + font.bold: true + } + Item { + height: 32 + } + Label { + text: qsTr("Please enable \"Xbox configuration support\" in Steams controller settings.\n\nGlosSI cannot function properly with this setting disabled\n\nEnable now?") + wrapMode: Text.WordWrap + width: parent.width + } + Row { + anchors.right: parent.right + anchors.topMargin: 16 + anchors.rightMargin: 2 + spacing: 8 + Button { + id: noBtn + text: qsTr("No") + onClicked: dlg.close() + } + Button { + id: yesBtn + text: qsTr("Yes") + onClicked: function() { + uiModel.enableSteamInputXboxSupport(); + dlg.close(); + steamChangedDialog2.open(); + } + } + } + } + InfoDialog { + id: steamChangedDialog2 + titleText: qsTr("Steam config changed!") + text: qsTr("Please restart Steam to reload your changes!") + onConfirmed: function (callback) { + callback(); + } + } + } +} \ No newline at end of file diff --git a/GlosSIConfig/qml/SteamNotFoundDialog.qml b/GlosSIConfig/qml/SteamNotFoundDialog.qml new file mode 100644 index 0000000..3b254e6 --- /dev/null +++ b/GlosSIConfig/qml/SteamNotFoundDialog.qml @@ -0,0 +1,92 @@ +/* +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 + + 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 + clip: true + Column { + spacing: 4 + bottomPadding: 24 + Label { + id: titlelabel + text: qsTr("Could not detect Steam") + font.pixelSize: 24 + font.bold: true + } + Item { + height: 24 + } + Label { + text: qsTr("Please make sure that Steam is running and you are logged in.") + wrapMode: Text.WordWrap + width: parent.width + } + + Button { + anchors.right: parent.right + anchors.top: listview.bottom + anchors.topMargin: 16 + anchors.rightMargin: 2 + text: qsTr("Ok") + onClicked: dlg.close() + } + } + } +} \ No newline at end of file diff --git a/GlosSIConfig/qml/main.qml b/GlosSIConfig/qml/main.qml index 3322295..3be4d08 100644 --- a/GlosSIConfig/qml/main.qml +++ b/GlosSIConfig/qml/main.qml @@ -45,6 +45,16 @@ Window { property bool steamShortcutsChanged: false + Component.onCompleted: function() { + if (!uiModel.foundSteam) { + steamNotFoundDialog.open(); + return; + } + if (!uiModel.steamInputXboxSupportEnabled) { + steamXboxDisabledDialog.open(); + } + } + Image { anchors.top: parent.top anchors.left: parent.left @@ -55,10 +65,18 @@ Window { opacity: 0.033 } + SteamNotFoundDialog { + id: steamNotFoundDialog + } + SteamInputXboxDisabledDialog { + id: steamXboxDisabledDialog + } + + InfoDialog { id: steamChangedDialog - titleText: qsTr("Attention!") - text: qsTr("Please restart Steam to reload your changes!") + titleText: qsTr("Steam shortcuts changed!") + text: qsTr("Please restart Steam to reload your changes") onConfirmed: function (callback) { callback(); } diff --git a/GlosSIConfig/svg/expand_more_white_24dp.svg b/GlosSIConfig/svg/expand_more_white_24dp.svg new file mode 100644 index 0000000..9c420ca --- /dev/null +++ b/GlosSIConfig/svg/expand_more_white_24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/GlosSITarget/.clang-format b/GlosSITarget/.clang-format index b5e5b98..1c78926 100644 --- a/GlosSITarget/.clang-format +++ b/GlosSITarget/.clang-format @@ -1,3 +1,4 @@ +BasedOnStyle: LLVM UseTab: false IndentWidth: 4 BreakBeforeBraces: "Stroustrup" diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp index 7bc3f25..e660331 100644 --- a/GlosSITarget/AppLauncher.cpp +++ b/GlosSITarget/AppLauncher.cpp @@ -77,6 +77,9 @@ void AppLauncher::update() } if (Settings::launch.waitForChildProcs) { std::erase_if(pids_, [](auto pid) { + if (pid == 0) { + return true; + } const auto running = IsProcessRunning(pid); if (!running) spdlog::trace("Child process with PID \"{}\" died", pid); diff --git a/GlosSITarget/HidHide.cpp b/GlosSITarget/HidHide.cpp index 52bf1bf..1655e32 100644 --- a/GlosSITarget/HidHide.cpp +++ b/GlosSITarget/HidHide.cpp @@ -102,9 +102,19 @@ void HidHide::hideDevices(const std::filesystem::path& steam_path) whitelist.push_back(path); } } + if (Settings::extendedLogging) { + std::ranges::for_each(whitelist, [](const auto& exe) { + spdlog::trace(L"Whitelisted executable: {}", exe); + }); + } setAppWhiteList(whitelist); avail_devices_ = GetHidDeviceList(); + if (Settings::extendedLogging) { + std::ranges::for_each(avail_devices_, [](const auto& dev) { + spdlog::trace(L"AvailDevice device: {}", dev.name); + }); + } blacklisted_devices_ = getBlackListDevices(); for (const auto& dev : avail_devices_) { @@ -112,11 +122,11 @@ void HidHide::hideDevices(const std::filesystem::path& steam_path) return blackdev == dev.device_instance_path || blackdev == dev.base_container_device_instance_path; })) { // Valve emulated gamepad PID/VID; mirrord by ViGEm - if (!(dev.vendor_id == 0x28de && dev.product_id == 0x11FF)) { + if (!(dev.vendor_id == 0x28de && (dev.product_id == 0x11FF || dev.product_id == 0x028E))) { if (!dev.device_instance_path.empty()) { blacklisted_devices_.push_back(dev.device_instance_path); } - if (!dev.device_instance_path.empty()) { + if (!dev.base_container_device_instance_path.empty()) { blacklisted_devices_.push_back(dev.base_container_device_instance_path); } } @@ -127,6 +137,11 @@ 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) { + std::ranges::for_each(blacklisted_devices_, [](const auto& dev) { + spdlog::trace(L"Blacklisted device: {}", dev); + }); + } enableOverlayElement(); } closeCtrlDevice(); @@ -148,7 +163,7 @@ void HidHide::UnPatchValveHooks() // need to load addresses that way.. Otherwise we land before some jumps... if (const auto setupapidll = GetModuleHandle(L"setupapi.dll")) { UnPatchHook("SetupDiEnumDeviceInfo", setupapidll); - //UnPatchHook("SetupDiGetClassDevsW", setupapidll); + UnPatchHook("SetupDiGetClassDevsW", setupapidll); } if (const auto hiddll = GetModuleHandle(L"hid.dll")) { for (const auto& name : ORIGINAL_BYTES | std::views::keys) { @@ -182,56 +197,70 @@ void HidHide::UnPatchHook(const std::string& name, HMODULE module) void HidHide::enableOverlayElement() { - Overlay::AddOverlayElem([this](bool window_has_focus) { - if (window_has_focus && (overlay_elem_clock_.getElapsedTime().asSeconds() > OVERLAY_ELEM_REFRESH_INTERVAL_S_)) { - openCtrlDevice(); - bool hidehide_state_store = hidhide_active_; - if (hidhide_active_) { - setActive(false); - } - avail_devices_ = GetHidDeviceList(); - blacklisted_devices_ = getBlackListDevices(); - if (hidehide_state_store) { - setActive(true); - } - closeCtrlDevice(); - overlay_elem_clock_.restart(); - } - ImGui::SetNextWindowPos({650, 100}, ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSizeConstraints({400, 270}, {1000, 1000}); - ImGui::Begin("Hidden Devices"); - ImGui::BeginChild("Inner", {0.f, ImGui::GetItemRectSize().y - 64}, true); - std::ranges::for_each(avail_devices_, [this](const auto& device) { - std::string label = (std::string(device.name.begin(), std::ranges::find(device.name, L'\0')) + "##" + std::string(device.device_instance_path.begin(), device.device_instance_path.end())); - const auto findDeviceFn = [&device](const auto& blackdev) { - return device.device_instance_path == blackdev || device.base_container_device_instance_path == blackdev; - }; - bool hidden = std::ranges::find_if(blacklisted_devices_, findDeviceFn) != blacklisted_devices_.end(); - if (ImGui::Checkbox(label.data(), &hidden)) { + Overlay::AddOverlayElem([this](bool window_has_focus, ImGuiID dockspace_id) { + ImGui::SetNextWindowDockID(dockspace_id, ImGuiCond_FirstUseEver); + if (ImGui::Begin("Hidden Devices")) { + if (window_has_focus && (overlay_elem_clock_.getElapsedTime().asSeconds() > OVERLAY_ELEM_REFRESH_INTERVAL_S_)) { + // UnPatchValveHooks(); openCtrlDevice(); - if (hidden) { - if (std::ranges::none_of(blacklisted_devices_, findDeviceFn)) { - if (!device.device_instance_path.empty()) { - blacklisted_devices_.push_back(device.device_instance_path); - } - if (!device.device_instance_path.empty()) { - blacklisted_devices_.push_back(device.base_container_device_instance_path); + bool hidehide_state_store = hidhide_active_; + if (Settings::extendedLogging) { + spdlog::debug("Refreshing HID devices"); + } + if (hidhide_active_) { + setActive(false); + } + avail_devices_ = GetHidDeviceList(); + if (Settings::extendedLogging) { + std::ranges::for_each(avail_devices_, [](const auto& dev) { + spdlog::trace(L"AvailDevice device: {}", dev.name); + }); + } + blacklisted_devices_ = getBlackListDevices(); + if (hidehide_state_store) { + setActive(true); + } + closeCtrlDevice(); + overlay_elem_clock_.restart(); + } + ImGui::BeginChild("Inner", {0.f, ImGui::GetItemRectSize().y - 64}, true); + std::ranges::for_each(avail_devices_, [this](const auto& device) { + std::string label = (std::string(device.name.begin(), std::ranges::find(device.name, L'\0')) + "##" + std::string(device.device_instance_path.begin(), device.device_instance_path.end())); + const auto findDeviceFn = [&device](const auto& blackdev) { + return device.device_instance_path == blackdev || device.base_container_device_instance_path == blackdev; + }; + bool hidden = std::ranges::find_if(blacklisted_devices_, findDeviceFn) != blacklisted_devices_.end(); + if (ImGui::Checkbox(label.data(), &hidden)) { + openCtrlDevice(); + if (hidden) { + if (std::ranges::none_of(blacklisted_devices_, findDeviceFn)) { + if (!device.device_instance_path.empty()) { + blacklisted_devices_.push_back(device.device_instance_path); + } + if (!device.device_instance_path.empty()) { + blacklisted_devices_.push_back(device.base_container_device_instance_path); + } } } + else { + blacklisted_devices_.erase(std::ranges::remove_if(blacklisted_devices_, findDeviceFn).begin(), + blacklisted_devices_.end()); + } + setBlacklistDevices(blacklisted_devices_); + if (Settings::extendedLogging) { + std::ranges::for_each(blacklisted_devices_, [](const auto& dev) { + spdlog::trace(L"Blacklisted device: {}", dev); + }); + } + closeCtrlDevice(); } - else { - blacklisted_devices_.erase(std::ranges::remove_if(blacklisted_devices_, findDeviceFn).begin(), - blacklisted_devices_.end()); - } - setBlacklistDevices(blacklisted_devices_); + }); + ImGui::EndChild(); + if (ImGui::Checkbox("Devices Hidden", &hidhide_active_)) { + openCtrlDevice(); + setActive(hidhide_active_); closeCtrlDevice(); } - }); - ImGui::EndChild(); - if (ImGui::Checkbox("Devices Hidden", &hidhide_active_)) { - openCtrlDevice(); - setActive(hidhide_active_); - closeCtrlDevice(); } ImGui::End(); }); @@ -316,6 +345,9 @@ void HidHide::setActive(bool active) return; } hidhide_active_ = active; + if (Settings::extendedLogging) { + spdlog::debug("HidHide State set to {}", active); + } } DWORD HidHide::getRequiredOutputBufferSize(IOCTL_TYPE type) const diff --git a/GlosSITarget/InputRedirector.cpp b/GlosSITarget/InputRedirector.cpp index 2645b10..498ce05 100644 --- a/GlosSITarget/InputRedirector.cpp +++ b/GlosSITarget/InputRedirector.cpp @@ -53,9 +53,8 @@ void InputRedirector::run() max_controller_count_ = Settings::controller.maxControllers; use_real_vid_pid_ = Settings::devices.realDeviceIds; #ifdef _WIN32 - Overlay::AddOverlayElem([this](bool window_has_focus) { - ImGui::SetNextWindowPos({650, 450}, ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSizeConstraints({400, 270}, {1000, 1000}); + Overlay::AddOverlayElem([this](bool window_has_focus, ImGuiID dockspace_id) { + ImGui::SetNextWindowDockID(dockspace_id, ImGuiCond_FirstUseEver); ImGui::Begin("Controller Emulation"); int countcopy = max_controller_count_; ImGui::Text("Max. controller count"); @@ -163,7 +162,11 @@ void InputRedirector::runLoop() // Multiple controllers can be worked around with by setting max count. if (!use_real_vid_pid_) { vigem_target_set_vid(vt_pad_[i], 0x28de); //VALVE_DIRECTINPUT_GAMEPAD_VID - // vigem_target_set_pid(vt_pad_[i], 0x11FF); //VALVE_DIRECTINPUT_GAMEPAD_PID + //vigem_target_set_pid(vt_pad_[i], 0x11FF); //VALVE_DIRECTINPUT_GAMEPAD_PID + vigem_target_set_pid(vt_pad_[i], 0x028E); // XBOX 360 Controller + } else { + vigem_target_set_vid(vt_pad_[i], 0x045E); // MICROSOFT + vigem_target_set_pid(vt_pad_[i], 0x028E); // XBOX 360 Controller } // TODO: MAYBE!: In a future version, use something like OpenXInput //and filter out emulated controllers to support a greater amount of controllers simultaneously @@ -178,7 +181,11 @@ void InputRedirector::runLoop() } } if (target_add_res == VIGEM_ERROR_NONE) { - spdlog::info("Plugged in controller {}, {}", i, vigem_target_get_index(vt_pad_[i])); + spdlog::info("Plugged in controller {}, {}; VID: {:x}; PID: {:x}", + i, + vigem_target_get_index(vt_pad_[i]), + vigem_target_get_vid(vt_pad_[i]), + vigem_target_get_pid(vt_pad_[i])); if (Settings::controller.emulateDS4) { const auto callback_register_res = vigem_target_ds4_register_notification( diff --git a/GlosSITarget/Overlay.cpp b/GlosSITarget/Overlay.cpp index d317175..a952dcb 100644 --- a/GlosSITarget/Overlay.cpp +++ b/GlosSITarget/Overlay.cpp @@ -21,6 +21,7 @@ limitations under the License. #include #include "Roboto.h" +#include "Settings.h" Overlay::Overlay( sf::RenderWindow& window, @@ -37,12 +38,14 @@ Overlay::Overlay( ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + io.Fonts->Clear(); // clear fonts if you loaded some before (even if only default one was loaded) auto fontconf = ImFontConfig{}; fontconf.FontDataOwnedByAtlas = false; io.Fonts->AddFontFromMemoryTTF(Roboto_Regular_ttf.data(), Roboto_Regular_ttf.size(), 24, &fontconf); - ImGui::SFML::UpdateFontTexture(); // important call: updates font texture + ImGui::SFML::UpdateFontTexture(); #ifdef _WIN32 auto config_path = std::filesystem::temp_directory_path() @@ -55,6 +58,7 @@ Overlay::Overlay( if (!std::filesystem::exists(config_path)) std::filesystem::create_directories(config_path); config_path /= "imgui.ini"; + // This assumes that char is utf8 and wchar_t is utf16, which is guaranteed on Windows. config_file_name_ = std::wstring_convert>().to_bytes(config_path.wstring()); io.IniFilename = config_file_name_.data(); @@ -151,14 +155,31 @@ void Overlay::update() { ImGui::SFML::Update(window_, update_clock_.restart()); - showLogs(); + showLogs(0); + if (enabled_ || force_enable_) { + // Create a DockSpace node where any window can be docked + ImGui::SetNextWindowSize({ImGui::GetMainViewport()->Size.x * 0.6f, ImGui::GetMainViewport()->Size.y * 0.7f}, ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos({ImGui::GetMainViewport()->Size.x * 0.25f, 100 }, ImGuiCond_FirstUseEver); + ImGui::Begin("GlosSI Settings"); + if (Settings::settings_path_ != "") { + if (ImGui::Button("Save shortcut settings", {256, 32})) { + Settings::StoreSettings(); + } + } + ImGuiID dockspace_id = ImGui::GetID("GlosSI-DockSpace"); + ImGui::DockSpace(dockspace_id); + window_.clear(sf::Color(0, 0, 0, 128)); // make window slightly dim screen with overlay - std::ranges::for_each(OVERLAY_ELEMS_, [this](const auto& elem) { - elem.second(window_.hasFocus()); + + + std::ranges::for_each(OVERLAY_ELEMS_, [this, &dockspace_id](const auto& elem) { + elem.second(window_.hasFocus(), dockspace_id); }); + ImGui::End(); + // ImGui::ShowDemoWindow(); if (closeButton()) { @@ -186,7 +207,7 @@ void Overlay::AddLog(const spdlog::details::log_msg& msg) LOG_MSGS_.push_back({.time = msg.time, .level = msg.level, .payload = msg.payload.data()}); } -int Overlay::AddOverlayElem(const std::function& elem_fn) +int Overlay::AddOverlayElem(const std::function& elem_fn) { OVERLAY_ELEMS_.insert({overlay_element_id_, elem_fn}); // keep this non confusing, but longer... @@ -200,20 +221,22 @@ void Overlay::RemoveOverlayElem(int id) OVERLAY_ELEMS_.erase(id); } -void Overlay::showLogs() +void Overlay::showLogs(ImGuiID dockspace_id) { std::vector logs; if (!enabled_ && !log_expanded_) { return; } + bool logs_contain_warn_or_worse = false; if (enabled_) { logs = LOG_MSGS_; } else { std::ranges::copy_if(LOG_MSGS_, std::back_inserter(logs), - [](const auto& log) { - return ( + [&logs_contain_warn_or_worse](const auto& log) { + + const auto res = ( log.time.time_since_epoch() + std::chrono::seconds( LOG_RETENTION_TIME_) > std::chrono::system_clock::now().time_since_epoch()) @@ -221,9 +244,13 @@ void Overlay::showLogs() && (log.level > spdlog::level::debug) #endif ; + if (res && log.level > spdlog::level::warn) { + logs_contain_warn_or_worse = true; + } + return res; }); } - if (logs.empty()) + if (logs.empty() || ( !enabled_ && !logs_contain_warn_or_worse && time_since_start_clock_.getElapsedTime().asSeconds() > HIDE_NORMAL_LOGS_AFTER_S)) return; ImGui::SetNextWindowSizeConstraints({150, 150}, {1000, window_.getSize().y - 250.f}); if (!enabled_) { @@ -232,6 +259,9 @@ void Overlay::showLogs() ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoTitleBar); } else { + //ImGui::SetNextWindowDockID(dockspace_id, ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize({ImGui::GetMainViewport()->Size.x * 0.2f, ImGui::GetMainViewport()->Size.y * 0.7f}, ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos({ImGui::GetMainViewport()->Size.x * 0.05f, 100}, ImGuiCond_FirstUseEver); log_expanded_ = ImGui::Begin("Log"); } if (log_expanded_) { diff --git a/GlosSITarget/Overlay.h b/GlosSITarget/Overlay.h index 46cda4f..1f0ebf1 100644 --- a/GlosSITarget/Overlay.h +++ b/GlosSITarget/Overlay.h @@ -38,7 +38,7 @@ class Overlay { static void Shutdown(); static void AddLog(const spdlog::details::log_msg& msg); - static int AddOverlayElem(const std::function& elem_fn); + static int AddOverlayElem(const std::function& elem_fn); static void RemoveOverlayElem(int id); private: @@ -47,11 +47,12 @@ class Overlay { bool enabled_ = true; std::function on_close_; std::function trigger_state_change_; - void showLogs(); + void showLogs(ImGuiID dockspace_id); bool closeOverlayButton() const; [[nodiscard]] bool closeButton() const; bool force_enable_ = false; bool log_expanded_ = true; + sf::Clock time_since_start_clock_; struct Log { std::chrono::system_clock::time_point time; @@ -60,9 +61,10 @@ class Overlay { }; static inline std::vector LOG_MSGS_; static constexpr int LOG_RETENTION_TIME_ = 5; + static constexpr int HIDE_NORMAL_LOGS_AFTER_S = 20; static inline int overlay_element_id_ = 0; - static inline std::map> OVERLAY_ELEMS_; + static inline std::map> OVERLAY_ELEMS_; #ifdef _WIN32 std::string config_file_name_; diff --git a/GlosSITarget/ProcessPriority.h b/GlosSITarget/ProcessPriority.h index 5357a77..60528e8 100644 --- a/GlosSITarget/ProcessPriority.h +++ b/GlosSITarget/ProcessPriority.h @@ -12,35 +12,41 @@ static int current_priority = HIGH_PRIORITY_CLASS; inline void init() { SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); + spdlog::trace("Set process priority to HIGH_PRIORITY_CLASS"); - Overlay::AddOverlayElem([](bool window_has_focus) { - ImGui::SetNextWindowPos({913, 418}, ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSizeConstraints({170, 325}, {1000, 1000}); + Overlay::AddOverlayElem([](bool window_has_focus, ImGuiID dockspace_id) { + ImGui::SetNextWindowDockID(dockspace_id, ImGuiCond_FirstUseEver); ImGui::Begin("Process Priority"); ImGui::Text("Might help with input-lag or bad game performance"); if (ImGui::RadioButton("Realtime", current_priority == REALTIME_PRIORITY_CLASS)) { SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); current_priority = REALTIME_PRIORITY_CLASS; + spdlog::trace("Set process priority to REALTIME_PRIORITY_CLASS"); } if (ImGui::RadioButton("High", current_priority == HIGH_PRIORITY_CLASS)) { SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); current_priority = HIGH_PRIORITY_CLASS; + spdlog::trace("Set process priority to HIGH_PRIORITY_CLASS"); } if (ImGui::RadioButton("Above Normal", current_priority == ABOVE_NORMAL_PRIORITY_CLASS)) { SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); current_priority = ABOVE_NORMAL_PRIORITY_CLASS; + spdlog::trace("Set process priority to ABOVE_NORMAL_PRIORITY_CLASS"); } if (ImGui::RadioButton("Normal", current_priority == NORMAL_PRIORITY_CLASS)) { SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); current_priority = NORMAL_PRIORITY_CLASS; + spdlog::trace("Set process priority to NORMAL_PRIORITY_CLASS"); } if (ImGui::RadioButton("Below Normal", current_priority == BELOW_NORMAL_PRIORITY_CLASS)) { SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS); current_priority = BELOW_NORMAL_PRIORITY_CLASS; + spdlog::trace("Set process priority to BELOW_NORMAL_PRIORITY_CLASS"); } if (ImGui::RadioButton("Low", current_priority == IDLE_PRIORITY_CLASS)) { SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS); current_priority = IDLE_PRIORITY_CLASS; + spdlog::trace("Set process priority to IDLE_PRIORITY_CLASS"); } ImGui::End(); }); diff --git a/GlosSITarget/Resource.rc b/GlosSITarget/Resource.rc index e37e0b4..0bece18 100644 --- a/GlosSITarget/Resource.rc +++ b/GlosSITarget/Resource.rc @@ -51,8 +51,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,0,7,1018000020006 - PRODUCTVERSION 0,0,7,1018000020006 + FILEVERSION 0,0,8,1031000051035 + PRODUCTVERSION 0,0,8,1031000051035 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.7.1-18-g0f2bac6" + VALUE "FileVersion", "0.0.8.1-31-gda51d35" VALUE "InternalName", "GlosSITarget" VALUE "LegalCopyright", "Copyright (C) 2021 Peter Repukat - FlatspotSoftware" VALUE "OriginalFilename", "GlosSITarget.exe" VALUE "ProductName", "GlosSI" - VALUE "ProductVersion", "0.0.7.1-18-g0f2bac6" + VALUE "ProductVersion", "0.0.8.1-31-gda51d35" END END BLOCK "VarFileInfo" @@ -168,6 +168,234 @@ IDI_ICON1 ICON "GloSC_Icon.ico" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GlosSITarget/Settings.h b/GlosSITarget/Settings.h index 6b80fd0..9e25bc9 100644 --- a/GlosSITarget/Settings.h +++ b/GlosSITarget/Settings.h @@ -47,11 +47,15 @@ inline struct Window { } window; inline struct Controller { - int maxControllers = 4; + int maxControllers = 1; bool allowDesktopConfig = false; bool emulateDS4 = false; } controller; +inline bool extendedLogging = false; + +inline std::filesystem::path settings_path_ = ""; + inline bool checkIsUwp(const std::wstring& launch_path) { if (launch_path.find(L"://") != std::wstring::npos) { @@ -87,14 +91,9 @@ inline void Parse(std::wstring arg1) spdlog::error(L"Couldn't open settings file {}", path.wstring()); return; } - const auto json = nlohmann::json::parse(json_file); - if (json["version"] != 1) { // TODO: versioning stuff - spdlog::warn("Config version doesn't match application version."); - } + settings_path_ = path; - // TODO: make this as much generic as fits in about the same amount of code if one would parse every value separately. - - auto safeParseValue = [](const auto& object, const auto& key, auto& value) { + 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()) { return; @@ -102,10 +101,10 @@ inline void Parse(std::wstring arg1) value = object[key]; } catch (const nlohmann::json::exception& e) { - spdlog::error("Err parsing \"{}\"; {}", key, e.what()); + spdlog::warn("Err parsing \"{}\"; {}", key, e.what()); } catch (const std::exception& e) { - spdlog::error("Err parsing \"{}\"; {}", key, e.what()); + spdlog::warn("Err parsing \"{}\"; {}", key, e.what()); } }; @@ -118,6 +117,15 @@ inline void Parse(std::wstring arg1) } }; + const auto json = nlohmann::json::parse(json_file); + int version; + safeParseValue(json, "version" ,version); + if (version != 1) { // TODO: versioning stuff + spdlog::warn("Config version doesn't match application version."); + } + + // TODO: make this as much generic as fits in about the same amount of code if one would parse every value separately. + if (auto launchconf = json["launch"]; launchconf.is_object()) { safeParseValue(launchconf, "launch", launch.launch); safeWStringParse(launchconf, "launchPath", launch.launchPath); @@ -144,13 +152,47 @@ inline void Parse(std::wstring arg1) safeParseValue(controllerConf, "emulateDS4", controller.emulateDS4); } + safeParseValue(json, "extendedLogging", extendedLogging); + json_file.close(); - spdlog::debug(L"Read config file \"{}\"", path.wstring()); + // c++ is stupid... + spdlog::debug(L"Read config file \"{}\"; config: {}", path.wstring(), std::filesystem::path(json.dump()).wstring()); if (launch.launch) { launch.isUWP = checkIsUwp(launch.launchPath); } } +inline void StoreSettings() +{ + nlohmann::json json; + json["version"] = 1; + json["launch"]["launch"] = launch.launch; + json["launch"]["launchPath"] = std::wstring_convert>().to_bytes(launch.launchPath); + json["launch"]["launchAppArgs"] = std::wstring_convert>().to_bytes(launch.launchAppArgs); + json["launch"]["closeOnExit"] = launch.closeOnExit; + json["launch"]["waitForChildProcs"] = launch.waitForChildProcs; + json["devices"]["hideDevices"] = devices.hideDevices; + json["devices"]["realDeviceIds"] = devices.realDeviceIds; + json["window"]["windowMode"] = window.windowMode; + json["window"]["maxFps"] = window.maxFps; + json["window"]["scale"] = window.scale; + json["window"]["disableOverlay"] = window.disableOverlay; + json["controller"]["maxControllers"] = controller.maxControllers; + json["controller"]["allowDesktopConfig"] = controller.allowDesktopConfig; + json["controller"]["emulateDS4"] = controller.emulateDS4; + + json["extendedLogging"] = extendedLogging; + + std::ofstream json_file; + json_file.open(settings_path_); + if (!json_file.is_open()) { + spdlog::error(L"Couldn't open settings file {}", settings_path_.wstring()); + return; + } + json_file << json.dump(4); + json_file.close(); +} + } // namespace Settings diff --git a/GlosSITarget/SteamOverlayDetector.cpp b/GlosSITarget/SteamOverlayDetector.cpp index 003a1e6..12fdf86 100644 --- a/GlosSITarget/SteamOverlayDetector.cpp +++ b/GlosSITarget/SteamOverlayDetector.cpp @@ -17,6 +17,8 @@ limitations under the License. #include +#include "Settings.h" + #ifdef _WIN32 #define NOMINMAX #include @@ -44,6 +46,11 @@ void SteamOverlayDetector::update() // okey to use nullptr as hwnd. get EVERY message 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) { + spdlog::trace("PeekMessage: Window msg: {}", msg.message); + } + if (msg.message < 1000 && msg.message > 0) { return; } diff --git a/GlosSITarget/SteamTarget.cpp b/GlosSITarget/SteamTarget.cpp index b11a51a..89feee7 100644 --- a/GlosSITarget/SteamTarget.cpp +++ b/GlosSITarget/SteamTarget.cpp @@ -181,6 +181,9 @@ void SteamTarget::toggleGlossiOverlay() void SteamTarget::focusWindow(WindowHandle hndl) { + if (reinterpret_cast(hndl) == 0) { + return; + } #ifdef _WIN32 if (hndl == target_window_handle_) { spdlog::debug("Bring own window to foreground"); diff --git a/GlosSITarget/TargetWindow.cpp b/GlosSITarget/TargetWindow.cpp index f2eb178..32790c6 100644 --- a/GlosSITarget/TargetWindow.cpp +++ b/GlosSITarget/TargetWindow.cpp @@ -50,9 +50,9 @@ TargetWindow::TargetWindow( { createWindow(Settings::window.windowMode); - Overlay::AddOverlayElem([this](bool window_has_focus) { + Overlay::AddOverlayElem([this](bool window_has_focus, ImGuiID dockspace_id) { + ImGui::SetNextWindowDockID(dockspace_id, ImGuiCond_FirstUseEver); bool windowed_copy = windowed_; - ImGui::SetNextWindowPos({window_.getSize().x - 370.f, 100}, ImGuiCond_FirstUseEver); ImGui::Begin("Window mode"); if (ImGui::Checkbox("Window mode", &windowed_copy)) { toggle_window_mode_after_frame_ = true; @@ -254,14 +254,26 @@ void TargetWindow::createWindow(bool window_mode) #ifdef _WIN32 // For some completely odd reason, the Background becomes black when enabled dpi-awareness and making the window desktop-size. // Scaling down by 1px each direction is barely noticeable and works. + + // Due to some other issue, the (Steam) overlay might get blurred when doing this + // as a workaround, start in full size, and scale down later... spdlog::info("Creating Overlay window (Borderless Fullscreen)..."); - window_.create(sf::VideoMode(desktop_mode.width - 1, desktop_mode.height - 1, 32), "GlosSITarget", sf::Style::None); + window_.create(sf::VideoMode(desktop_mode.width -1, desktop_mode.height -1, 32), "GlosSITarget", sf::Style::None); + + // get size of all monitors combined + const auto screenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); + const auto screenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); + spdlog::debug("Full screen size: {}x{}", screenWidth, screenHeight); + + spdlog::debug("Primary monitor size: {}x{}", desktop_mode.width, desktop_mode.height); + #else window_.create(desktop_mode, "GlosSITarget", sf::Style::None); #endif windowed_ = false; } window_.setActive(true); + spdlog::debug("Window position: {}x{}", window_.getPosition().x, window_.getPosition().y); #ifdef _WIN32 HWND hwnd = window_.getSystemHandle(); @@ -320,6 +332,9 @@ void TargetWindow::createWindow(bool window_mode) else { spdlog::warn("Not applying too low screen scale setting"); } + + // window_.setSize({desktop_mode.width - 1, desktop_mode.height - 1 }); + on_window_changed_(); #ifdef _WIN32 diff --git a/GlosSITarget/UWPOverlayEnabler.h b/GlosSITarget/UWPOverlayEnabler.h index 110b329..02e63fa 100644 --- a/GlosSITarget/UWPOverlayEnabler.h +++ b/GlosSITarget/UWPOverlayEnabler.h @@ -60,10 +60,8 @@ inline void EnableUwpOverlay() inline void AddUwpOverlayOvWidget() { - Overlay::AddOverlayElem([](bool window_has_focus) { - ImGui::SetNextWindowPos({1200, 250}, ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSizeConstraints({170, 325}, {1000, 1000}); - ImGui::SetNextWindowCollapsed(true, ImGuiCond_FirstUseEver); + Overlay::AddOverlayElem([](bool window_has_focus, ImGuiID dockspace_id) { + ImGui::SetNextWindowDockID(dockspace_id, ImGuiCond_FirstUseEver); ImGui::Begin("UWP-Overlay"); ImGui::Text("To enable the overlay on top of \"fullscreen\" UWP-Apps,"); ImGui::Text("a .dll has to be injected into explorer.exe"); diff --git a/UWPOverlayEnablerDLL/UWPOverlayEnablerDLL.vcxproj b/UWPOverlayEnablerDLL/UWPOverlayEnablerDLL.vcxproj index 9d0ad5a..34e5567 100644 --- a/UWPOverlayEnablerDLL/UWPOverlayEnablerDLL.vcxproj +++ b/UWPOverlayEnablerDLL/UWPOverlayEnablerDLL.vcxproj @@ -78,11 +78,11 @@ true - ..\deps\subhook;$(IncludePath) + ..\deps\subhook;..\deps\spdlog\include;$(IncludePath) false - ..\deps\subhook;$(IncludePath) + ..\deps\subhook;..\deps\spdlog\include;$(IncludePath) @@ -126,6 +126,7 @@ true NotUsing pch.h + stdcpp20 Windows @@ -143,6 +144,7 @@ true NotUsing pch.h + stdcpp20 Windows diff --git a/UWPOverlayEnablerDLL/dllmain.cpp b/UWPOverlayEnablerDLL/dllmain.cpp index fe4dd04..2e2f1a3 100644 --- a/UWPOverlayEnablerDLL/dllmain.cpp +++ b/UWPOverlayEnablerDLL/dllmain.cpp @@ -47,8 +47,15 @@ There are two (known to me, at time of writing) ways to get a working overlay fo #define SUBHOOK_STATIC #include +#include #include +#include +#include +#include + +#include + enum ZBID { ZBID_DEFAULT = 0, @@ -71,6 +78,7 @@ enum ZBID ZBID_LOCK = 17, ZBID_ABOVELOCK_UX = 18, }; + typedef BOOL(WINAPI* fSetWindowBand)(HWND hWnd, HWND hwndInsertAfter, DWORD dwBand); @@ -79,18 +87,22 @@ fSetWindowBand SetWindowBand; std::atomic allow_exit = false; +std::atomic to_set_window_band = ZBID_SYSTEM_TOOLS; + BOOL WINAPI SetGlosSIWindowBand(HWND hWnd, HWND hwndInsertAfter, DWORD dwBand) { subhook::ScopedHookRemove remove(&SetWindowBandHook); const auto glossi_hwnd = FindWindowA(nullptr, "GlosSITarget"); if (glossi_hwnd) { + spdlog::info("Found GlosSI Window"); // Most window bands don't really seem to work. // However, notification and system_tools does! // use system tools, as that allows the steam overlay to be interacted with // without UWP apps minimizing - SetWindowBand(glossi_hwnd, nullptr, ZBID_SYSTEM_TOOLS); + auto success = SetWindowBand(glossi_hwnd, nullptr, to_set_window_band); allow_exit = true; + spdlog::info("Set GlosSI Window Band to {}; success: {}", static_cast(to_set_window_band), success); } return SetWindowBand(hWnd, hwndInsertAfter, dwBand); } @@ -102,7 +114,10 @@ DWORD WINAPI WaitThread(HMODULE hModule) Sleep(10); } if (SetWindowBandHook.IsInstalled()) + { + spdlog::debug("Uninstalling SetWindowBand hook"); SetWindowBandHook.Remove(); + } FreeLibraryAndExitThread(hModule, 0); } @@ -113,17 +128,104 @@ BOOL APIENTRY DllMain( HMODULE hModule, { if (ul_reason_for_call == DLL_PROCESS_ATTACH) { + + auto configDirPath = std::filesystem::temp_directory_path() + .parent_path() + .parent_path() + .parent_path(); + + configDirPath /= "Roaming"; + configDirPath /= "GlosSI"; + if (!std::filesystem::exists(configDirPath)) + std::filesystem::create_directories(configDirPath); + + + + auto logPath = configDirPath; + logPath /= "UWPOverlayEnabler.log"; + const auto file_sink = std::make_shared(logPath.string(), true); + std::vector sinks{ file_sink }; + auto logger = std::make_shared("log", sinks.begin(), sinks.end()); + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::trace); + spdlog::set_default_logger(logger); + + spdlog::info("UWPOverlayEnabler loaded"); + + auto configPath = configDirPath; + configPath /= "UWPOverlayEnabler.cfg"; + if (std::filesystem::exists(configPath)) + { + std::ifstream config(configPath); + std::string line; + while (std::getline(config, line)) + { + // github copilot, lol + // i take it! + if (line == "ZBID_DEFAULT") + to_set_window_band = ZBID_DEFAULT; + else if (line == "ZBID_DESKTOP") + to_set_window_band = ZBID_DESKTOP; + else if (line == "ZBID_UIACCESS") + to_set_window_band = ZBID_UIACCESS; + else if (line == "ZBID_IMMERSIVE_IHM") + to_set_window_band = ZBID_IMMERSIVE_IHM; + else if (line == "ZBID_IMMERSIVE_NOTIFICATION") + to_set_window_band = ZBID_IMMERSIVE_NOTIFICATION; + else if (line == "ZBID_IMMERSIVE_APPCHROME") + to_set_window_band = ZBID_IMMERSIVE_APPCHROME; + else if (line == "ZBID_IMMERSIVE_MOGO") + to_set_window_band = ZBID_IMMERSIVE_MOGO; + else if (line == "ZBID_IMMERSIVE_EDGY") + to_set_window_band = ZBID_IMMERSIVE_EDGY; + else if (line == "ZBID_IMMERSIVE_INACTIVEMOBODY") + to_set_window_band = ZBID_IMMERSIVE_INACTIVEMOBODY; + else if (line == "ZBID_IMMERSIVE_INACTIVEDOCK") + to_set_window_band = ZBID_IMMERSIVE_INACTIVEDOCK; + else if (line == "ZBID_IMMERSIVE_ACTIVEMOBODY") + to_set_window_band = ZBID_IMMERSIVE_ACTIVEMOBODY; + else if (line == "ZBID_IMMERSIVE_ACTIVEDOCK") + to_set_window_band = ZBID_IMMERSIVE_ACTIVEDOCK; + else if (line == "ZBID_IMMERSIVE_BACKGROUND") + to_set_window_band = ZBID_IMMERSIVE_BACKGROUND; + else if (line == "ZBID_IMMERSIVE_SEARCH") + to_set_window_band = ZBID_IMMERSIVE_SEARCH; + else if (line == "ZBID_GENUINE_WINDOWS") + to_set_window_band = ZBID_GENUINE_WINDOWS; + else if (line == "ZBID_IMMERSIVE_RESTRICTED") + to_set_window_band = ZBID_IMMERSIVE_RESTRICTED; + else if (line == "ZBID_SYSTEM_TOOLS") + to_set_window_band = ZBID_SYSTEM_TOOLS; + else if (line == "ZBID_LOCK") + to_set_window_band = ZBID_LOCK; + else if (line == "ZBID_ABOVELOCK_UX") + to_set_window_band = ZBID_ABOVELOCK_UX; + + } + spdlog::info("Read window band from config: {}", static_cast(to_set_window_band)); + } + const auto hpath = LoadLibrary(L"user32.dll"); if (hpath) { + spdlog::debug("Loaded user32.dll"); + spdlog::debug("Installing SetWindowBand hook"); SetWindowBand = reinterpret_cast(GetProcAddress(hpath, "SetWindowBand")); SetWindowBandHook.Install(GetProcAddress(hpath, "SetWindowBand"), &SetGlosSIWindowBand, subhook::HookFlags::HookFlag64BitOffset); + spdlog::debug("Creating wait thread"); CloseHandle(CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)WaitThread, hModule, 0, nullptr)); + } else + { + spdlog::error("Loaded user32.dll"); } } else if (ul_reason_for_call == DLL_PROCESS_DETACH) { + spdlog::info("unloading UWPOverlayEnabler"); if (SetWindowBandHook.IsInstalled()) + { + spdlog::debug("Uninstalling SetWindowBand hook"); SetWindowBandHook.Remove(); + } } return TRUE; } diff --git a/deps/Shortcuts_VDF b/deps/Shortcuts_VDF index 2816b31..59108a7 160000 --- a/deps/Shortcuts_VDF +++ b/deps/Shortcuts_VDF @@ -1 +1 @@ -Subproject commit 2816b31c8e777c2920e1f0881ce10c5c66e30c63 +Subproject commit 59108a7f9a938911e1cc237003a396c886be85f8 diff --git a/deps/imgui b/deps/imgui index 9aae45e..9cd9c2e 160000 --- a/deps/imgui +++ b/deps/imgui @@ -1 +1 @@ -Subproject commit 9aae45eb4a05a5a1f96be1ef37eb503a12ceb889 +Subproject commit 9cd9c2eff99877a3f10a7f9c2a3a5b9c15ea36c6