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/.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/GlosSIConfig/GlosSIConfig.vcxproj b/GlosSIConfig/GlosSIConfig.vcxproj
index 0ba8142..a79c58d 100644
--- a/GlosSIConfig/GlosSIConfig.vcxproj
+++ b/GlosSIConfig/GlosSIConfig.vcxproj
@@ -143,7 +143,9 @@
+
+
@@ -162,6 +164,7 @@
+
diff --git a/GlosSIConfig/GlosSIConfig.vcxproj.filters b/GlosSIConfig/GlosSIConfig.vcxproj.filters
index f7483fd..9811199 100644
--- a/GlosSIConfig/GlosSIConfig.vcxproj.filters
+++ b/GlosSIConfig/GlosSIConfig.vcxproj.filters
@@ -80,6 +80,12 @@
qml
+
+ qml
+
+
+ qml
+
@@ -99,6 +105,9 @@
Header Files
+
+ Header Files
+
diff --git a/GlosSIConfig/Resource.rc b/GlosSIConfig/Resource.rc
index 9f6cc17..f5db24c 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,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.0.9.1-37-g5ebe102"
+ 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.0.9.1-37-g5ebe102"
+ VALUE "ProductVersion", "0.1.0.2-45-g63fdab1"
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 70817a8..ee67d6d 100644
--- a/GlosSIConfig/UIModel.cpp
+++ b/GlosSIConfig/UIModel.cpp
@@ -16,8 +16,10 @@ limitations under the License.
#include "UIModel.h"
#include
+#include
#include
#include
+#include
#include
#include
@@ -31,7 +33,10 @@ limitations under the License.
#include
#endif
+#include "ExeImageProvider.h"
+#include "ExeImageProvider.h"
#include "../version.hpp"
+#include "steamgrid_api_keys.h"
UIModel::UIModel() : QObject(nullptr)
{
@@ -60,6 +65,11 @@ UIModel::UIModel() : QObject(nullptr)
parseShortcutVDF();
readTargetConfigs();
updateCheck();
+
+ auto font = QGuiApplication::font();
+ font.setPointSize(11);
+ font.setFamily("Roboto");
+ QGuiApplication::setFont(font);
}
void UIModel::readTargetConfigs()
@@ -102,22 +112,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)
@@ -132,7 +157,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_) {
@@ -146,6 +171,22 @@ bool UIModel::isInSteam(QVariant shortcut)
return false;
}
+uint32_t UIModel::getAppId(QVariant shortcut) const
+{
+ 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();
@@ -327,61 +368,67 @@ 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},
+ {"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},
+ {"launcherProcesses", QJsonArray{}},
+ {"ignoreLauncher", true},
+ {"killLauncher", false},
+ }},
+ {"window",
+ QJsonObject{
+ {"disableOverlay", false},
+ {"maxFps", QJsonValue::Null},
+ {"scale", QJsonValue::Null},
+ {"windowMode", false},
+ }},
+ };
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();
+ 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();
}
}
- QJsonObject obj = {
- {"icon", QJsonValue::Null},
- {"name", QJsonValue::Null},
- {"version", 1},
- {"extendedLogging", false},
- {"snapshotNotify", 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());
+ saveDefaultConf(defaults.toVariantMap());
return getDefaultConf();
-
}
void UIModel::saveDefaultConf(QVariantMap conf) const
@@ -412,6 +459,69 @@ 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"}};
+}
+
+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
+{
+ 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
{
@@ -473,6 +583,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_; }
@@ -483,6 +602,8 @@ void UIModel::setAcrylicEffect(bool has_acrylic_affect)
emit acrylicChanged();
}
+QStringList UIModel::getSteamgridOutput() const { return steamgrid_output_; }
+
void UIModel::onAvailFilesResponse(QNetworkReply* reply)
{
@@ -496,9 +617,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;
@@ -511,8 +630,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];
@@ -556,6 +676,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 8943d27..b3cb982 100644
--- a/GlosSIConfig/UIModel.h
+++ b/GlosSIConfig/UIModel.h
@@ -17,6 +17,7 @@ limitations under the License.
#include
#include
#include
+#include
#include
#include
@@ -25,25 +26,30 @@ 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)
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)
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();
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 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);
@@ -60,21 +66,30 @@ class UIModel : public QObject {
#ifdef _WIN32
Q_INVOKABLE QVariantList uwpApps();
#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,
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);
+ QStringList getSteamgridOutput() const;
+
signals:
void acrylicChanged();
void targetListChanged();
void newVersionAvailable();
+ void steamgridOutputChanged();
public slots:
void onAvailFilesResponse(QNetworkReply* reply);
+ void onSteamGridReadReady();
private:
#ifdef _WIN32
@@ -92,11 +107,17 @@ 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_;
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 d3945e1..1d07c92 100644
--- a/GlosSIConfig/qml.qrc
+++ b/GlosSIConfig/qml.qrc
@@ -22,5 +22,8 @@
qml/AdvancedTargetSettings.qml
qml/GlobalConf.qml
svg/settings_fill_white_24dp.svg
+ qml/EGSSelectDialog.qml
+ svg/add_photo_alternate_white_24dp.svg
+ qml/SteamGridDialog.qml
diff --git a/GlosSIConfig/qml/AddSelectTypeDialog.qml b/GlosSIConfig/qml/AddSelectTypeDialog.qml
index 8ecc10e..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
@@ -89,7 +92,7 @@ Dialog {
}
}
Button {
- visible: uiModel.isWindows
+ visible: uiModel.isWindows
text: qsTr("UWP App")
highlighted: true
onClicked: function(){
@@ -97,6 +100,15 @@ Dialog {
confirmed("uwp")
}
}
+ Button {
+ visible: uiModel.isWindows
+ text: qsTr("EGS Game")
+ highlighted: true
+ onClicked: function(){
+ close()
+ confirmed("egs")
+ }
+ }
}
}
diff --git a/GlosSIConfig/qml/AdvancedTargetSettings.qml b/GlosSIConfig/qml/AdvancedTargetSettings.qml
index 8798ee0..0c7ec8a 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
@@ -62,7 +64,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
@@ -74,7 +76,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
@@ -82,12 +84,32 @@ 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
}
}
+ /*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
+ }
+ }
+ 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
@@ -112,6 +134,7 @@ CollapsiblePane {
Row {
spacing: 16
width: parent.width
+ id: deviceWindowRow
RPane {
width: parent.width / 2 - 8
@@ -457,9 +480,107 @@ CollapsiblePane {
radius: 4
Material.elevation: 32
bgOpacity: 0.97
+ 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/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/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/RPane.qml b/GlosSIConfig/qml/RPane.qml
index 56748f6..2705cd7 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
@@ -33,13 +35,14 @@ Pane {
elevation: control.Material.elevation
}
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
}
}
}
\ No newline at end of file
diff --git a/GlosSIConfig/qml/ShortcutCards.qml b/GlosSIConfig/qml/ShortcutCards.qml
index 85593a2..e0f634b 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
@@ -206,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
@@ -215,10 +87,10 @@ 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
+ fillMode: Image.PreserveAspectFit
}
Label {
@@ -261,34 +133,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)
}
}
}
diff --git a/GlosSIConfig/qml/ShortcutProps.qml b/GlosSIConfig/qml/ShortcutProps.qml
index 985d2c5..9d66279 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 {
@@ -26,6 +27,7 @@ Item {
property alias fileDialog: fileDialog
property alias uwpSelectDialog: uwpSelectDialog
+ property alias egsSelectDialog: egsSelectDialog
signal cancel()
signal done(var shortcut)
@@ -52,6 +54,39 @@ Item {
if (advancedTargetSettings) { // advanced settings (collapsible container)
advancedTargetSettings.shortcutInfo = shortcutInfo;
}
+ if (maybeIcon) {
+ maybeIcon.source = shortcutInfo.icon
+ ? shortcutInfo.icon.endsWith(".exe")
+ ? "image://exe/" + shortcutInfo.icon
+ : "file:///" + shortcutInfo.icon
+ : '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 {
@@ -152,24 +187,28 @@ 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
+ fillMode: Image.PreserveAspectFit
+ 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
@@ -190,7 +229,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 {
@@ -206,6 +248,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
@@ -240,7 +289,6 @@ Item {
AdvancedTargetSettings {
id: advancedTargetSettings
- shortcutInfo: shortcutInfo
}
Item {
@@ -283,9 +331,25 @@ 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: {
+
+ }
+ }
+
+ 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: {
@@ -306,6 +370,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/SteamGridDialog.qml b/GlosSIConfig/qml/SteamGridDialog.qml
new file mode 100644
index 0000000..097947a
--- /dev/null
+++ b/GlosSIConfig/qml/SteamGridDialog.qml
@@ -0,0 +1,159 @@
+/*
+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: function() {
+ gridDialog.close();
+ confirmed(undefined);
+ }
+ }
+
+ }
+}
\ No newline at end of file
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/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 3894e96..479c2bd 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")
@@ -44,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) {
@@ -53,6 +63,7 @@ Window {
if (!uiModel.steamInputXboxSupportEnabled) {
steamXboxDisabledDialog.open();
}
+ shouldShowLoadGridImagesButton = uiModel.targetList.some((shortcut) => uiModel.isInSteam(shortcut))
}
Image {
@@ -125,6 +136,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
@@ -237,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 {
@@ -318,7 +475,16 @@ Window {
if (windowContent.editedIndex < 0) {
uiModel.addTarget(shortcut)
} else {
- uiModel.updateTarget(windowContent.editedIndex, shortcut)
+ if (uiModel.isInSteam(shortcut)) {
+ if (uiModel.updateTarget(windowContent.editedIndex, shortcut)) {
+ if (steamShortcutsChanged == false) {
+ steamChangedDialog.open();
+ }
+ } else {
+ manualInfo = uiModel.manualProps(shortcut);
+ writeErrorDialog.open();
+ }
+ }
}
}
}
@@ -387,7 +553,19 @@ Window {
if (param == "uwp") {
props.uwpSelectDialog.open();
}
+ if (param == "egs") {
+ props.egsSelectDialog.open();
+ }
}
}
+
+ SteamGridDialog {
+ id: steamGridDialog
+ onConfirmed: function() {
+ shortcutgrid.model = [];
+ shortcutgrid.model = uiModel.targetList;
+ }
+ }
+
}
}
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
diff --git a/GlosSITarget/AppLauncher.cpp b/GlosSITarget/AppLauncher.cpp
index 92ebe32..e9b007a 100644
--- a/GlosSITarget/AppLauncher.cpp
+++ b/GlosSITarget/AppLauncher.cpp
@@ -23,6 +23,7 @@ limitations under the License.
#include
#include
#include
+#include
#pragma comment(lib, "Shell32.lib")
#endif
@@ -30,7 +31,9 @@ limitations under the License.
#include
+#include "Overlay.h"
#include "UnhookUtil.h"
+#include "util.h"
AppLauncher::AppLauncher(
std::vector& process_hwnds,
@@ -48,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;
@@ -61,19 +70,38 @@ 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
}
void AppLauncher::update()
{
if (process_check_clock_.getElapsedTime().asMilliseconds() > 250) {
+ pid_mutex_.lock();
#ifdef _WIN32
+ if (has_extra_launchers_ && pids_.empty()) {
+ findLauncherPids();
+ }
if (!pids_.empty() && pids_[0] > 0) {
if (Settings::launch.waitForChildProcs) {
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_();
@@ -82,22 +110,41 @@ 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);
+ 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(Settings::launch.launcherProcesses, glossi_util::GetProcName(pid)) == Settings::launch.launcherProcesses.end();
+ });
+ if (has_extra_launchers_ && !filtered_pids.empty()) {
+ launcher_has_launched_game_ = true;
+ }
+ if (Settings::launch.closeOnExit && Settings::launch.launch) {
+ 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_();
+ }
+ }
+ else {
+ if (pids_.empty()) {
+ spdlog::info("Configured to close on all children exit. Shutting down...");
+ shutdown_();
+ }
+ }
}
}
getProcessHwnds();
#endif
+ pid_mutex_.unlock();
process_check_clock_.restart();
}
}
@@ -112,6 +159,43 @@ void AppLauncher::close()
#endif
}
+std::vector AppLauncher::launchedPids()
+{
+ pid_mutex_.lock();
+ std::vector res;
+ res.reserve(pids_.size());
+ if (!Settings::launch.killLauncher && Settings::launch.ignoreLauncher) {
+ for (const auto& pid : pids_ | std::ranges::views::filter(
+ [](DWORD pid) {
+ return std::ranges::find(
+ Settings::launch.launcherProcesses,
+ glossi_util::GetProcName(pid)) == Settings::launch.launcherProcesses.end();
+ })) {
+ res.push_back(pid);
+ }
+ }
+ else {
+ std::ranges::copy(pids_.begin(), pids_.end(),
+ std::back_inserter(res));
+ }
+ pid_mutex_.unlock();
+ 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)
{
@@ -132,10 +216,11 @@ void AppLauncher::getChildPids(DWORD parent_pid)
do {
if (pe.th32ParentProcessID == parent_pid) {
if (std::ranges::find(pids_, pe.th32ProcessID) == pids_.end()) {
- if (Settings::extendedLogging) {
- spdlog::info("Found new child process with PID \"{}\"", pe.th32ProcessID);
+ if (Settings::common.extendedLogging) {
+ spdlog::info(L"Found new child process \"{}\" with PID \"{}\"", glossi_util::GetProcName(pe.th32ProcessID), pe.th32ProcessID);
}
pids_.push_back(pe.th32ProcessID);
+ getChildPids(pe.th32ProcessID);
}
}
} while (Process32Next(hp, &pe));
@@ -176,6 +261,16 @@ void AppLauncher::getProcessHwnds()
#endif
#ifdef _WIN32
+bool AppLauncher::findLauncherPids()
+{
+ 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...
@@ -198,10 +293,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,
@@ -213,7 +314,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 +346,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 +361,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);
@@ -296,13 +400,30 @@ void AppLauncher::launchURL(const std::wstring& url, const std::wstring& args, c
}
CoUninitialize();
+ if (url.find(L"epicgames.launcher") != std::wstring::npos) {
+ has_extra_launchers_ = true;
+ }
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;
}
}
+
+ 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 (!findLauncherPids()) {
+ 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 231a0bf..8293e8e 100644
--- a/GlosSITarget/AppLauncher.h
+++ b/GlosSITarget/AppLauncher.h
@@ -17,9 +17,12 @@ limitations under the License.
#ifdef _WIN32
#define NOMINMAX
+#define WIN32_LEAN_AND_MEAN
#include
#endif
#include
+#include
+#include
#include
#include
#include
@@ -33,10 +36,14 @@ class AppLauncher {
void launchApp(const std::wstring& path, const std::wstring& args = L"");
void update();
void close();
+
+ std::vector launchedPids();
+ void addPids(const std::vector& pids);
private:
std::function shutdown_;
sf::Clock process_check_clock_;
+ std::mutex pid_mutex_;
#ifdef _WIN32
static bool IsProcessRunning(DWORD pid);
@@ -44,6 +51,11 @@ class AppLauncher {
void getProcessHwnds();
std::vector& process_hwnds_;
+ bool has_extra_launchers_ = false;
+ bool launcher_has_launched_game_ = false;
+ bool findLauncherPids();
+
+
std::wstring launched_uwp_path_;
static void UnPatchValveHooks();
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..086383d 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
@@ -118,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);
});
@@ -126,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);
});
@@ -153,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);
});
@@ -201,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);
});
@@ -244,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);
});
@@ -351,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/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