Merge branch 'develop'

pull/192/head 0.1.1.0
Peter Repukat 2 years ago
commit 78a9bbdc9d

2
.gitignore vendored

@ -370,3 +370,5 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd
.visuallint
GlosSIConfig/steamgrid_api_keys.h
steamgrid.zip

3
.gitmodules vendored

@ -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

@ -143,7 +143,9 @@
<None Include="GetAUMIDs.ps1" />
<None Include="qml\AdvancedTargetSettings.qml" />
<None Include="qml\CollapsiblePane.qml" />
<None Include="qml\EGSSelectDialog.qml" />
<None Include="qml\GlobalConf.qml" />
<None Include="qml\SteamGridDialog.qml" />
<None Include="qml\SteamInputXboxDisabledDialog.qml" />
<None Include="qml\AddSelectTypeDialog.qml" />
<None Include="qml\FluentTextInput.qml" />
@ -162,6 +164,7 @@
<ItemGroup>
<ClInclude Include="ExeImageProvider.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="steamgrid_api_keys.h" />
<ClInclude Include="UWPFetch.h" />
<ClInclude Include="WinEventFilter.h" />
</ItemGroup>

@ -80,6 +80,12 @@
<None Include="qml\GlobalConf.qml">
<Filter>qml</Filter>
</None>
<None Include="qml\EGSSelectDialog.qml">
<Filter>qml</Filter>
</None>
<None Include="qml\SteamGridDialog.qml">
<Filter>qml</Filter>
</None>
</ItemGroup>
<ItemGroup>
<QtMoc Include="UIModel.h">
@ -99,6 +105,9 @@
<ClInclude Include="ExeImageProvider.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="steamgrid_api_keys.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Xml Include="manifest.xml">

@ -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"

@ -16,8 +16,10 @@ limitations under the License.
#include "UIModel.h"
#include <QDir>
#include <QFont>
#include <QGuiApplication>
#include <QJsonDocument>
#include <QJsonArray>
#include <QNetworkAccessManager>
#include <QNetworkReply>
@ -31,7 +33,10 @@ limitations under the License.
#include <shlobj.h>
#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<std::string> 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<std::pair<QString, VersionInfo>> 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<QString, VersionInfo> {
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_;

@ -17,6 +17,7 @@ limitations under the License.
#include <QJsonObject>
#include <QObject>
#include <QVariant>
#include <QProcess>
#include <filesystem>
#include <shortcuts_vdf.hpp>
@ -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<VDFParser::Shortcut> shortcuts_vdf_;
void writeTarget(const QJsonObject& json, const QString& name) const;

@ -22,5 +22,8 @@
<file>qml/AdvancedTargetSettings.qml</file>
<file>qml/GlobalConf.qml</file>
<file>svg/settings_fill_white_24dp.svg</file>
<file>qml/EGSSelectDialog.qml</file>
<file>svg/add_photo_alternate_white_24dp.svg</file>
<file>qml/SteamGridDialog.qml</file>
</qresource>
</RCC>

@ -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")
}
}
}
}

@ -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

@ -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();
}
}
}
}
}
}

@ -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

@ -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
}
}
}

@ -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)
}
}
}

@ -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

@ -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);
}
}
}
}

@ -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()
}

@ -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()
}
}
}

@ -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;
}
}
}
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#FFFFFF"><path d="M29.45 6v3H9v30h30V18.6h3V39q0 1.2-.9 2.1-.9.9-2.1.9H9q-1.2 0-2.1-.9Q6 40.2 6 39V9q0-1.2.9-2.1Q7.8 6 9 6ZM38 6v4.05h4.05v3H38v4.05h-3v-4.05h-4.05v-3H35V6ZM12 33.9h24l-7.2-9.6-6.35 8.35-4.7-6.2ZM9 9v30V9Z"/></svg>

After

Width:  |  Height:  |  Size: 299 B

@ -23,6 +23,7 @@ limitations under the License.
#include <tlhelp32.h>
#include <Propsys.h>
#include <propkey.h>
#include <shellapi.h>
#pragma comment(lib, "Shell32.lib")
#endif
@ -30,7 +31,9 @@ limitations under the License.
#include <regex>
#include "Overlay.h"
#include "UnhookUtil.h"
#include "util.h"
AppLauncher::AppLauncher(
std::vector<HWND>& 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<std::codecvt_utf8_utf16<wchar_t>>().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<DWORD> AppLauncher::launchedPids()
{
pid_mutex_.lock();
std::vector<DWORD> 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<DWORD>& 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

@ -17,9 +17,12 @@ limitations under the License.
#ifdef _WIN32
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif
#include <functional>
#include <mutex>
#include <array>
#include <string>
#include <unordered_set>
#include <SFML/System/Clock.hpp>
@ -33,10 +36,14 @@ class AppLauncher {
void launchApp(const std::wstring& path, const std::wstring& args = L"");
void update();
void close();
std::vector<DWORD> launchedPids();
void addPids(const std::vector<DWORD>& pids);
private:
std::function<void()> 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<HWND>& process_hwnds_;
bool has_extra_launchers_ = false;
bool launcher_has_launched_game_ = false;
bool findLauncherPids();
std::wstring launched_uwp_path_;
static void UnPatchValveHooks();

@ -80,7 +80,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<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;$(ExternalIncludePath)</ExternalIncludePath>
<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)</ExternalIncludePath>
<LibraryPath>..\deps\SFML\out\Debug\lib\Debug;..\deps\ViGEmClient\lib\debug\x64;$(LibraryPath)</LibraryPath>
<CopyLocalProjectReference>false</CopyLocalProjectReference>
<CopyLocalDeploymentContent>true</CopyLocalDeploymentContent>
@ -88,7 +88,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<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;$(ExternalIncludePath)</ExternalIncludePath>
<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)</ExternalIncludePath>
<LibraryPath>..\deps\SFML\out\Release\lib\RelWithDebInfo;..\deps\ViGEmClient\lib\release\x64;$(LibraryPath)</LibraryPath>
<CustomBuildBeforeTargets>ResourceCompile</CustomBuildBeforeTargets>
<CopyLocalDeploymentContent>true</CopyLocalDeploymentContent>
@ -188,6 +188,7 @@
<ClCompile Include="..\deps\traypp\tray\src\core\windows\tray.cpp" />
<ClCompile Include="AppLauncher.cpp" />
<ClCompile Include="HidHide.cpp" />
<ClCompile Include="HttpServer.cpp" />
<ClCompile Include="InputRedirector.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="Overlay.cpp" />
@ -204,6 +205,7 @@
<ClInclude Include="DllInjector.h" />
<ClInclude Include="GlosSI_logo.h" />
<ClInclude Include="HidHide.h" />
<ClInclude Include="HttpServer.h" />
<ClInclude Include="imconfig.h" />
<ClInclude Include="InputRedirector.h" />
<ClInclude Include="Overlay.h" />

@ -117,6 +117,9 @@
<ClCompile Include="UnhookUtil.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="HttpServer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="SteamTarget.h">
@ -185,6 +188,9 @@
<ClInclude Include="util.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="HttpServer.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\deps\SFML\out\Debug\lib\Debug\sfml-system-d-2.dll" />

@ -24,11 +24,10 @@ limitations under the License.
#include <spdlog/spdlog.h>
#include <vector>
#include <initguid.h>
// Device configuration related
#include <cfgmgr32.h>
#include <initguid.h>
//
#ifndef WATCHDOG
#include "Overlay.h"
#endif
@ -38,9 +37,14 @@ limitations under the License.
#include <devguid.h>
#include <devpkey.h>
#include <regex>
#include <cguid.h>
#include <atlbase.h>
#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);
}
}

@ -15,7 +15,10 @@ limitations under the License.
*/
#pragma once
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <ntddscsi.h>
#include <winioctl.h>
#include <hidsdi.h>
@ -25,6 +28,7 @@ limitations under the License.
#include <map>
#include <string>
#include <vector>
#ifndef WATCHDOG
#include <SFML/System/Clock.hpp>
#endif

@ -0,0 +1,83 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "HttpServer.h"
#include <spdlog/spdlog.h>
#include <nlohmann/json.hpp>
#include "AppLauncher.h"
#include "Settings.h"
HttpServer::HttpServer(AppLauncher& app_launcher) : app_launcher_(app_launcher)
{
}
void HttpServer::run()
{
server_.Get("/launched-pids", [this](const httplib::Request& req, httplib::Response& res) {
const nlohmann::json j = app_launcher_.launchedPids();
res.set_content(j.dump(), "text/json");
});
server_.Post("/launched-pids", [this](const httplib::Request& req, httplib::Response& res) {
try {
const nlohmann::json postbody = nlohmann::json::parse(req.body);
app_launcher_.addPids(postbody.get<std::vector<DWORD>>());
} catch (std::exception& e) {
res.status = 401;
res.set_content(nlohmann::json{
{"code", 401},
{"name", "Bad Request"},
{"message", e.what()},
}
.dump(),
"text/json");
return;
}
catch (...) {
res.status = 500;
res.set_content(nlohmann::json{
{"code", 500},
{"name", "Internal Server Error"},
{"message", "Unknown Error"},
}
.dump(),
"text/json");
return;
}
const nlohmann::json j = app_launcher_.launchedPids();
res.set_content(j.dump(), "text/json");
});
server_.Get("/settings", [this](const httplib::Request& req, httplib::Response& res) {
res.set_content(Settings::toJson().dump(), "text/json");
});
server_thread_ = std::thread([this]() {
if (!server_.listen("0.0.0.0", port_)) {
spdlog::error("Couldn't start http-server");
return;
}
spdlog::debug("Started http-server on port {}", static_cast<int>(port_));
});
}
void HttpServer::stop()
{
server_.stop();
if (server_thread_.joinable())
server_thread_.join();
}

@ -0,0 +1,37 @@
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <thread>
#include <httplib.h>
class AppLauncher;
class HttpServer {
public:
explicit HttpServer(AppLauncher& app_launcher);
void run();
void stop();
private:
httplib::Server server_;
std::thread server_thread_;
uint16_t port_ = 8756;
AppLauncher& app_launcher_;
};

@ -17,6 +17,7 @@ limitations under the License.
#include <thread>
#ifdef _WIN32
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <Xinput.h>
#include <ViGEm/Client.h>

@ -199,7 +199,7 @@ void Overlay::update()
Settings::StoreSettings();
}
}
ImGui::Checkbox("Extended logging", &Settings::extendedLogging);
ImGui::Checkbox("Extended logging", &Settings::common.extendedLogging);
ImGuiID dockspace_id = ImGui::GetID("GlosSI-DockSpace");
ImGui::DockSpace(dockspace_id);

@ -51,8 +51,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,0,9,1040000008386
PRODUCTVERSION 0,0,9,1040000008386
FILEVERSION 0,1,0,2045006300001
PRODUCTVERSION 0,1,0,2045006300001
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -69,12 +69,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "Peter Repukat - FlatspotSoftware"
VALUE "FileDescription", "GlosSI - SteamTarget"
VALUE "FileVersion", "0.0.9.1-40-gdae8386"
VALUE "FileVersion", "0.1.0.2-45-g63fdab1"
VALUE "InternalName", "GlosSITarget"
VALUE "LegalCopyright", "Copyright (C) 2021-2022 Peter Repukat - FlatspotSoftware"
VALUE "OriginalFilename", "GlosSITarget.exe"
VALUE "ProductName", "GlosSI"
VALUE "ProductVersion", "0.0.9.1-40-gdae8386"
VALUE "ProductVersion", "0.1.0.2-45-g63fdab1"
END
END
BLOCK "VarFileInfo"
@ -160,6 +160,66 @@ IDI_ICON1 ICON "..\\GlosSI_Icon.ico"

@ -39,6 +39,9 @@ inline struct Launch {
bool closeOnExit = true;
bool waitForChildProcs = true;
bool isUWP = false;
bool ignoreLauncher = true;
bool killLauncher = false;
std::vector<std::wstring> launcherProcesses{};
} launch;
inline struct Devices {
@ -59,7 +62,14 @@ inline struct Controller {
bool emulateDS4 = false;
} controller;
inline bool extendedLogging = false;
inline struct Common {
bool no_uwp_overlay = false;
bool disable_watchdog = false;
bool extendedLogging = false;
std::wstring name;
std::wstring icon;
int version;
} common;
inline std::filesystem::path settings_path_ = "";
@ -114,45 +124,8 @@ inline void checkWinVer()
}
#endif
inline void Parse(std::wstring arg1)
inline void Parse(const nlohmann::basic_json<>& json)
{
const auto config_specified = !std::views::filter(arg1, [](const auto& ch) {
return ch != ' ';
}).empty();
if (config_specified) {
if (!arg1.ends_with(L".json")) {
arg1 += L".json";
}
}
wchar_t* localAppDataFolder;
std::filesystem::path path;
if (SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, NULL, &localAppDataFolder) != S_OK) {
path = std::filesystem::temp_directory_path().parent_path().parent_path().parent_path();
}
else {
path = std::filesystem::path(localAppDataFolder).parent_path();
}
path /= "Roaming";
path /= "GlosSI";
if (config_specified) {
path /= "Targets";
path /= arg1;
}
else {
spdlog::info("No config file specified, using default");
path /= "default.json";
}
std::ifstream json_file;
json_file.open(path);
if (!json_file.is_open()) {
spdlog::error(L"Couldn't open settings file {}", path.wstring());
spdlog::debug(L"Using sane defaults...");
return;
}
settings_path_ = path;
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()) {
@ -179,7 +152,6 @@ 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
@ -194,6 +166,17 @@ inline void Parse(std::wstring arg1)
safeWStringParse(launchconf, "launchAppArgs", launch.launchAppArgs);
safeParseValue(launchconf, "closeOnExit", launch.closeOnExit);
safeParseValue(launchconf, "waitForChildProcs", launch.waitForChildProcs);
safeParseValue(launchconf, "killLauncher", launch.killLauncher);
safeParseValue(launchconf, "ignoreLauncher", launch.ignoreLauncher);
if (auto launcherProcs = launchconf["launcherProcesses"];
!launcherProcs.is_null() && !launcherProcs.empty() && launcherProcs.is_array()) {
launch.launcherProcesses.clear();
launch.launcherProcesses.reserve(launcherProcs.size());
for (auto& proc : launcherProcs) {
launch.launcherProcesses.push_back(std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(proc));
}
}
}
if (auto devconf = json["devices"]; !devconf.is_null() && !devconf.empty() && devconf.is_object()) {
@ -213,6 +196,10 @@ inline void Parse(std::wstring arg1)
safeParseValue(controllerConf, "allowDesktopConfig", controller.allowDesktopConfig);
safeParseValue(controllerConf, "emulateDS4", controller.emulateDS4);
}
safeParseValue(json, "extendedLogging", common.extendedLogging);
safeWStringParse(json, "name", common.name);
safeWStringParse(json, "icon", common.icon);
safeParseValue(json, "version", common.version);
}
catch (const nlohmann::json::exception& e) {
spdlog::warn("Err parsing config: {}", e.what());
@ -220,19 +207,75 @@ inline void Parse(std::wstring arg1)
catch (const std::exception& e) {
spdlog::warn("Err parsing config: {}", e.what());
}
if (launch.launch) {
launch.isUWP = checkIsUwp(launch.launchPath);
}
}
safeParseValue(json, "extendedLogging", extendedLogging);
json_file.close();
inline void Parse(const std::vector<std::wstring>& args)
{
std::wstring configName;
for (const auto& arg : args) {
if (arg.empty()) {
continue;
}
if (arg == L"-disableuwpoverlay") {
common.no_uwp_overlay = true;
}
else if (arg == L"-disablewatchdog") {
common.disable_watchdog = true;
}
else if (arg == L"-ignorelauncher") {
launch.ignoreLauncher = true;
}
else {
configName += L" " + std::wstring(arg.begin(), arg.end());
}
}
if (!configName.empty()) {
if (configName[0] == L' ') {
configName.erase(configName.begin());
}
if (!configName.ends_with(L".json")) {
configName += L".json";
}
}
wchar_t* localAppDataFolder;
std::filesystem::path path;
if (SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, NULL, &localAppDataFolder) != S_OK) {
path = std::filesystem::temp_directory_path().parent_path().parent_path().parent_path();
}
else {
path = std::filesystem::path(localAppDataFolder).parent_path();
}
spdlog::debug("Read config file \"{}\"; config: {}", path.string(), json.dump());
path /= "Roaming";
path /= "GlosSI";
if (!configName.empty()) {
path /= "Targets";
path /= configName;
}
else {
spdlog::info("No config file specified, using default");
path /= "default.json";
}
if (launch.launch) {
launch.isUWP = checkIsUwp(launch.launchPath);
std::ifstream json_file;
json_file.open(path);
if (!json_file.is_open()) {
spdlog::error(L"Couldn't open settings file {}", path.wstring());
spdlog::debug(L"Using sane defaults...");
return;
}
settings_path_ = path;
const auto& json = nlohmann::json::parse(json_file);
Parse(json);
spdlog::debug("Read config file \"{}\"; config: {}", path.string(), json.dump());
json_file.close();
}
inline void StoreSettings()
inline nlohmann::json toJson()
{
nlohmann::json json;
json["version"] = 1;
@ -251,7 +294,16 @@ inline void StoreSettings()
json["controller"]["allowDesktopConfig"] = controller.allowDesktopConfig;
json["controller"]["emulateDS4"] = controller.emulateDS4;
json["extendedLogging"] = extendedLogging;
json["extendedLogging"] = common.extendedLogging;
json["name"] = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(common.name);
json["icon"] = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(common.icon);
json["version"] = common.version;
return json;
}
inline void StoreSettings()
{
const auto& json = toJson();
std::ofstream json_file;
json_file.open(settings_path_);

@ -47,7 +47,7 @@ void SteamOverlayDetector::update()
if (PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE)) {
// filter out some messages as not all get altered by steam...
if (Settings::extendedLogging && msg.message != 512 && msg.message != 5374) {
if (Settings::common.extendedLogging && msg.message != 512 && msg.message != 5374) {
spdlog::trace("PeekMessage: Window msg: {}", msg.message);
}

@ -30,6 +30,7 @@ limitations under the License.
#include <tray.hpp>
#endif
SteamTarget::SteamTarget()
: window_(
[this] { run_ = false; },
@ -44,15 +45,16 @@ SteamTarget::SteamTarget()
launcher_(force_config_hwnds_, [this] {
delayed_shutdown_ = true;
delay_shutdown_clock_.restart();
})
}),
server_(launcher_)
{
target_window_handle_ = window_.getSystemHandle();
#ifdef _WIN32
if (Settings::launch.isUWP) {
UWPOverlayEnabler::EnableUwpOverlay();
if (Settings::common.no_uwp_overlay) {
UWPOverlayEnabler::AddUwpOverlayOvWidget();
}
else {
UWPOverlayEnabler::AddUwpOverlayOvWidget();
UWPOverlayEnabler::EnableUwpOverlay();
}
#endif
}
@ -76,12 +78,14 @@ Application will not function!");
steam_overlay_present_ = true;
#ifdef WIN32
wchar_t buff[MAX_PATH];
GetModuleFileName(GetModuleHandle(NULL), buff, MAX_PATH);
std::wstring watchDogPath(buff);
watchDogPath = watchDogPath.substr(0, 1 + watchDogPath.find_last_of(L'\\')) + L"GlosSIWatchdog.dll";
if (!Settings::common.disable_watchdog) {
wchar_t buff[MAX_PATH];
GetModuleFileName(GetModuleHandle(NULL), buff, MAX_PATH);
std::wstring watchDogPath(buff);
watchDogPath = watchDogPath.substr(0, 1 + watchDogPath.find_last_of(L'\\')) + L"GlosSIWatchdog.dll";
DllInjector::injectDllInto(watchDogPath, L"explorer.exe");
DllInjector::injectDllInto(watchDogPath, L"explorer.exe");
}
#endif
}
getXBCRebindingEnabled();
@ -123,6 +127,8 @@ Application will not function!");
run_ = false;
}});
server_.run();
while (run_) {
detector_.update();
overlayHotkeyWorkaround();
@ -139,6 +145,7 @@ Application will not function!");
}
tray.exit();
server_.stop();
#ifdef _WIN32
input_redirector_.stop();
hidhide_.disableHidHide();

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#define WIN32_LEAN_AND_MEAN
#include "SteamOverlayDetector.h"
@ -27,6 +28,8 @@ limitations under the License.
#include "AppLauncher.h"
#include "Overlay.h"
#include "HttpServer.h"
#include <filesystem>
@ -81,6 +84,7 @@ class SteamTarget {
std::weak_ptr<Overlay> overlay_;
SteamOverlayDetector detector_;
AppLauncher launcher_;
HttpServer server_;
WindowHandle last_foreground_window_ = nullptr;
static inline WindowHandle target_window_handle_ = nullptr;

@ -14,11 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <DbgHelp.h>
#include <ShlObj.h>
#include <shellapi.h>
#endif
#include <httplib.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
@ -162,12 +165,20 @@ int main(int argc, char* argv[])
auto exit = 1;
try {
#ifdef _WIN32
auto existingwindow = FindWindowA(nullptr, "GlosSITarget");
if (existingwindow) {
spdlog::error("GlosSITarget is already running!");
return 1;
}
int numArgs;
LPWSTR* args = CommandLineToArgvW(GetCommandLine(), &numArgs);
std::wstring argsv = L"";
std::vector<std::wstring> argsv;
argsv.reserve(numArgs);
if (numArgs > 1) {
for (int i = 1; i < numArgs; i++)
argsv += i == 1 ? args[i] : std::wstring(L" ") + args[i];
argsv.emplace_back(args[i]);
}
Settings::Parse(argsv);
Settings::checkWinVer();

@ -1,6 +1,23 @@
#pragma once
/*
Copyright 2021-2022 Peter Repukat - FlatspotSoftware
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
#include "DllInjector.h"
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <tlhelp32.h>
namespace glossi_util {
@ -20,5 +37,40 @@ inline DWORD PidByName(const std::wstring& name)
return 0;
}
inline std::wstring GetProcName(DWORD pid)
{
PROCESSENTRY32 processInfo;
processInfo.dwSize = sizeof(processInfo);
const HANDLE processesSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (processesSnapshot == INVALID_HANDLE_VALUE) {
spdlog::trace("util::GetProcName: can't get a process snapshot");
return L"";
}
for (BOOL bok = Process32First(processesSnapshot, &processInfo);
bok;
bok = Process32Next(processesSnapshot, &processInfo)) {
if (pid == processInfo.th32ProcessID) {
CloseHandle(processesSnapshot);
return processInfo.szExeFile;
}
}
CloseHandle(processesSnapshot);
return L"";
}
inline bool KillProcess(DWORD pid)
{
auto res = true;
if (const auto proc = OpenProcess(PROCESS_TERMINATE, FALSE, pid)) {
spdlog::debug("Terminating process: {}", pid);
res = TerminateProcess(proc, 0);
if (!res) {
spdlog::error("Failed to terminate process: {}", pid);
}
CloseHandle(proc);
}
return res;
}
} // namespace glossi_util

@ -71,10 +71,10 @@
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IncludePath>..\deps\spdlog\include;..\deps\json\include;$(IncludePath)</IncludePath>
<IncludePath>..\deps\spdlog\include;..\deps\json\include;..\deps\cpp-httplib;$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<IncludePath>..\deps\spdlog\include;..\deps\json\include;$(IncludePath)</IncludePath>
<IncludePath>..\deps\spdlog\include;..\deps\json\include;..\deps\cpp-httplib;$(IncludePath)</IncludePath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>

@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
#include <httplib.h>
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <ShlObj.h>
@ -24,9 +26,43 @@ limitations under the License.
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include <nlohmann/json.hpp>
#include "../version.hpp"
#include "../GlosSITarget/Settings.h"
#include "../GlosSITarget/HidHide.h"
#include "../GlosSITarget/util.h"
bool IsProcessRunning(DWORD pid)
{
const HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, pid);
if (process == nullptr)
return false;
const DWORD ret = WaitForSingleObject(process, 0);
CloseHandle(process);
return ret == WAIT_TIMEOUT;
}
void fetchSettings(httplib::Client& http_client, int retried_count = 0) {
http_client.set_connection_timeout(1 + (retried_count > 0 ? 2 : 0));
auto http_res = http_client.Get("/settings");
if (http_res.error() == httplib::Error::Success && http_res->status == 200)
{
const auto json = nlohmann::json::parse(http_res->body);
spdlog::debug("Received settings from GlosSITarget: {}", json.dump());
Settings::Parse(json);
}
else
{
spdlog::error("Couldn't get settings from GlosSITarget. Error: {}", (int)http_res.error());
if (retried_count < 2)
{
spdlog::info("Retrying... {}", retried_count);
fetchSettings(http_client, retried_count + 1);
}
}
}
DWORD WINAPI watchdog(HMODULE hModule)
{
@ -60,18 +96,65 @@ DWORD WINAPI watchdog(HMODULE hModule)
if (!glossi_hwnd)
{
spdlog::error("Couldn't find GlosSITarget window. Exiting...");
FreeLibraryAndExitThread(hModule, 1);
return 1;
}
spdlog::debug("Found GlosSITarget window; Starting watch loop");
httplib::Client http_client("http://localhost:8756");
fetchSettings(http_client);
std::vector<DWORD> pids;
while (glossi_hwnd)
{
http_client.set_connection_timeout(120);
const auto http_res = http_client.Get("/launched-pids");
if (http_res.error() == httplib::Error::Success && http_res->status == 200)
{
const auto json = nlohmann::json::parse(http_res->body);
if (Settings::common.extendedLogging)
{
spdlog::trace("Received pids: {}", json.dump());
}
pids = json.get<std::vector<DWORD>>();
}
else {
spdlog::error("Couldn't fetch launched PIDs: {}", (int)http_res.error());
}
glossi_hwnd = FindWindowA(nullptr, "GlosSITarget");
Sleep(1337);
Sleep(333);
}
spdlog::info("GlosSITarget was closed. Cleaning up...");
spdlog::info("GlosSITarget was closed. Resetting HidHide state...");
HidHide hidhide;
hidhide.disableHidHide();
if (Settings::launch.closeOnExit)
{
spdlog::info("Closing launched processes");
for (const auto pid : pids)
{
if (Settings::common.extendedLogging)
{
spdlog::debug("Checking if process {} is running", pid);
}
if (IsProcessRunning(pid))
{
glossi_util::KillProcess(pid);
}
else
{
if (Settings::common.extendedLogging)
{
spdlog::debug("Process {} is not running", pid);
}
}
}
}
spdlog::info("Unloading Watchdog...");
FreeLibraryAndExitThread(hModule, 0);
}
@ -89,5 +172,5 @@ BOOL APIENTRY DllMain(HMODULE hModule,
}
return TRUE;
return 0;
return 0;
}

@ -18,6 +18,7 @@ Copy-Item "..\..\vc_redist.x64.exe" -Destination "."
Copy-Item "..\..\LICENSE" -Destination "./LICENSE"
Copy-Item "..\..\QT_License" -Destination "./QT_License"
Copy-Item "..\..\THIRD_PARTY_LICENSES.txt" -Destination "./THIRD_PARTY_LICENSES.txt"
Copy-Item "..\..\steamgrid.exe" -Destination "./steamgrid.exe"
Copy-Item "C:\Qt\Tools\OpenSSL\Win_x64\bin\libcrypto-1_1-x64.dll" -Destination "./libcrypto-1_1-x64.dll"
Copy-Item "C:\Qt\Tools\OpenSSL\Win_x64\bin\libssl-1_1-x64.dll" -Destination "./libssl-1_1-x64.dll"

1
deps/cpp-httplib vendored

@ -0,0 +1 @@
Subproject commit cae5a8be1c9ef8399f45b89f6fc55afb122808cb

@ -14,4 +14,20 @@ cd .\GlosSIConfig\
..\version_help.ps1
cd ../
$apiKeyText = "
/* Autogenerated version info file */
#pragma once
inline const char* steamgridb_key = ""$env:STEAMGRIDDB_KEY"";
"
if (!(Test-Path 'steamgrid_api_keys.h')) {
New-Item -Path "." -Name "steamgrid_api_keys.h" -ItemType "file" -Value $apiKeyText
}
cd ../
if (!(Test-Path 'steamgrid.exe')) {
Invoke-WebRequest -o steamgrid.zip https://github.com/boppreh/steamgrid/releases/download/v3.4.0/steamgrid_windows.zip
7z e steamgrid.zip steamgrid.exe
}

Loading…
Cancel
Save