diff --git a/.darklua.json b/.darklua.json new file mode 100644 index 0000000..5cd0537 --- /dev/null +++ b/.darklua.json @@ -0,0 +1,26 @@ +{ + "generator": { "name": "dense", "column_span": 175}, + "bundle": { + "require_mode": { + "name" : "path", + "sources": { + "@Classes": "./src/Classes", + "@Configuration": "./src/Configuration", + "@Launcher" : "./src/Launcher", + "@Steam": "./src/Steam", + "@Utilities": "./src/Utilities", + "@Metadata": "./src/Metadata" + } + }, + "excludes": ["@lune/**"] + }, + "rules": [ + "remove_comments", + "remove_spaces", + "remove_nil_declaration", + "compute_expression", + "remove_unused_if_branch", + "filter_after_early_return", + "remove_empty_do" + ] +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..179e626 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# LF normalization +* text=auto diff --git a/.luaurc b/.luaurc new file mode 100644 index 0000000..dfc464c --- /dev/null +++ b/.luaurc @@ -0,0 +1,14 @@ +{ + "languageMode": "strict", + "lint": { + "*": false + }, + "aliases": { + "Classes": "./src/Classes", + "Configuration": "./src/Configuration", + "Launcher" : "./src/Launcher", + "Steam": "./src/Steam", + "Utilities": "./src/Utilities", + "Metadata": "./src/Metadata" + } +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b486f95 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "luau-lsp.require.mode": "relativeToFile", + "luau-lsp.require.directoryAliases": { + "@lune/": "~/.lune/.typedefs/0.8.8/", + "@Classes": "src/Classes", + "@Configuration": "src/Configuration", + "@Launcher" : "src/Launcher", + "@Steam": "src/Steam", + "@Utilities": "src/Utilities", + "@Metadata": "src/Metadata" +}, + "luau-lsp.sourcemap.enabled": false +} \ No newline at end of file diff --git a/Makefile b/Makefile index 970abf2..20c2f6b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Metadata -PROJECT_NAME=simplesteamtinker -INSTALL_FOLDER_NAME=SimpleSteamTinker +PROJECT_NAME=stweaks +INSTALL_FOLDER_NAME=STWeaks VERSION=indev ifeq ($(PREFIX),) @@ -8,56 +8,31 @@ ifeq ($(PREFIX),) endif BUILD_FOLDER := dist/ -.PHONY: system install uninstall clean local - -# -------------- System installation -------------- -# This will create a folder structure in dist that is compatible with most package managers. -system: - @echo "Packaging project for system installation..." -# Executable - mkdir -p $(BUILD_FOLDER)$(PREFIX)/bin - install -Dm755 sst $(BUILD_FOLDER)$(PREFIX)/bin -# Modules - mkdir -p $(BUILD_FOLDER)$(PREFIX)/share/$(INSTALL_FOLDER_NAME) - install -Dm644 main.lua $(BUILD_FOLDER)$(PREFIX)/share/$(INSTALL_FOLDER_NAME)/main.lua - cp -r modules $(BUILD_FOLDER)$(PREFIX)/share/$(INSTALL_FOLDER_NAME) -# UI - mkdir -p $(BUILD_FOLDER)$(PREFIX)/share/$(INSTALL_FOLDER_NAME)/ui - install -Dm644 ui/main.ui $(BUILD_FOLDER)$(PREFIX)/share/$(INSTALL_FOLDER_NAME)/ui/main.ui -# Desktop file - mkdir -p $(BUILD_FOLDER)$(PREFIX)/share/applications - install -Dm644 assets/desktop/system.desktop $(BUILD_FOLDER)$(PREFIX)/share/applications/$(PROJECT_NAME).desktop -# Icons - mkdir -p $(BUILD_FOLDER)$(PREFIX)/share/icons/hicolor/256x256/apps - mkdir -p $(BUILD_FOLDER)$(PREFIX)/share/icons/hicolor/scalable/apps - install -Dm644 assets/icons/256x256.png $(BUILD_FOLDER)$(PREFIX)/share/icons/hicolor/256x256/apps/$(PROJECT_NAME).png - install -Dm644 assets/icons/scalable.svg $(BUILD_FOLDER)$(PREFIX)/share/icons/hicolor/scalable/apps/$(PROJECT_NAME).svg - -# Should only be used as last resort, the end user should use a compatible package manager if possible instead. -# This is only here for testing purposes, and also as a general "manual" on how to install the project for package maintainers. -install: system - @echo "Installing project to system..." - cp -r $(BUILD_FOLDER)$(PREFIX)/bin/* $(PREFIX)/bin - cp -r $(BUILD_FOLDER)$(PREFIX)/share/$(INSTALL_FOLDER_NAME) $(PREFIX)/share - cp -r $(BUILD_FOLDER)$(PREFIX)/share/applications/* $(PREFIX)/share/applications - cp -r $(BUILD_FOLDER)$(PREFIX)/share/icons/hicolor/256x256/apps/* $(PREFIX)/share/icons/hicolor/256x256/apps - cp -r $(BUILD_FOLDER)$(PREFIX)/share/icons/hicolor/scalable/apps/* $(PREFIX)/share/icons/hicolor/scalable/apps -# Update icon cache - @echo "Updating icon cache..." - gtk-update-icon-cache $(PREFIX)/share/icons/hicolor -uninstall: - @echo "Uninstalling project from system..." - rm -ri $(PREFIX)/bin/sst - rm -rI $(PREFIX)/share/$(INSTALL_FOLDER_NAME) - rm -ri $(PREFIX)/share/applications/$(PROJECT_NAME).desktop - rm -ri $(PREFIX)/share/icons/hicolor/256x256/apps/$(PROJECT_NAME).png - rm -ri $(PREFIX)/share/icons/hicolor/scalable/apps/$(PROJECT_NAME).svg -# Update icon cache - @ echo "Updating icon cache..." - gtk-update-icon-cache $(PREFIX)/share/icons/hicolor - -# -------------- Cleaning -------------- +.PHONY: build run check style clean + +# -------------- Packaging -------------- +$(BUILD_FOLDER)stweaks.luau: clean + @echo "Using DarkLua to bundle Luau code..." + darklua process init.luau $(BUILD_FOLDER)stweaks.luau -v + +build: $(BUILD_FOLDER)stweaks.luau + @echo "Compiling project..." + lune build $(BUILD_FOLDER)stweaks.luau + +# -------------- Run -------------- +run: + @echo "Running project..." + lune run init + +# -------------- Extra -------------- +check: + @echo "Running linter..." + selene src/ + +style: + @echo "Running code formatter..." + stylua src/ clean: @echo "Cleaning up..." - rm -rf $(BUILD_FOLDER) + rm -rf "$(BUILD_FOLDER)" diff --git a/README.md b/README.md deleted file mode 100644 index c0c140e..0000000 --- a/README.md +++ /dev/null @@ -1,184 +0,0 @@ -> [!WARNING] -> This project may be deprecated in the future, as I have been recently working on a **massive** rewrite, avoiding LGI and focusing more on portability and stability this time around. -___ - -> [!IMPORTANT] -> SimpleSteamTinker is still in ***HEAVY*** development and very unfinished. -> Expect an incomplete interface and lots of missing/unstable features. -> -> If SteamTinkerLaunch is fast enough for you and fits your needs, you should not use this tool right now. -> -> With that being said, I'd still very much appreciate people testing SimpleSteamTinker and reporting [issues](https://github.com/JordanViknar/SimpleSteamTinker/issues) or contributing. - -
- -

SimpleSteamTinker

-

A work-in-progress simple, fast, and modern Adwaita alternative to SteamTinkerLaunch.

- - -
- - - -
- - -
- -## Description - -### Situation - -I like [SteamTinkerLaunch](https://github.com/sonic2kk/steamtinkerlaunch). It is for me one of the biggest tools to have ever been created for Linux gaming. - -However, it presents many flaws in my eyes : it is slow, impractical to use, and not user-friendly. - -I want Linux Gaming to be available to everyone, including any non-technical users. In my opinion, SteamTinkerLaunch's interface is not friendly to newcomers. - -Finally, my curiosity once led me to look inside its source code, only to be met with a single, extremely long (26k+ lines) Bash script. At that point, I decided there should be an alternative to it. - -*And thus came SimpleSteamTinker...* - -### Goal - -*TL;DR : Simple, Fast, Modern & Friendly* - -SimpleSteamTinker aims to be only an alternative solution to SteamTinkerLaunch, not to replace it : the more complex features it provides that aren't used by the average user won't be implemented. - -Using Lua, a fast and simple language, and Adwaita, a modern user-friendly interface system, the goal of this project is to have a clean and easy but *powerful* way of launching Steam games with custom options and tools. - -It takes inspiration from [Bottles](https://github.com/bottlesdevs/Bottles) and Adwaita applications in general. - -Additionally, it is considered SimpleSteamTinker should work *with* Steam rather than hack/replace its features. Anything that can be easily done through Steam will not be reimplemented (Proton version management for example. If you want to use GE-Proton, [ProtonPlus](https://github.com/Vysp3r/ProtonPlus) and [ProtonUp-Qt](https://github.com/DavidoTek/ProtonUp-Qt) are recommended). - -And finally, as a side goal : prove that Lua can *also* be used for developing modern applications, just like for example Python (which I often see in Adwaita apps). - -## Installation - -### Arch Linux-based distributions - -SimpleSteamTinker can be installed from Arch Linux, Manjaro, and variants through the provided [PKGBUILD](./install/archlinux/PKGBUILD). - -**Always** download the latest version of the PKGBUILD, then use **makepkg** in its directory. - -It will eventually be made available on the AUR once the program is complete enough. - -To enable a game in SimpleSteamTinker with this installation method, use this text as launch options in Steam : -```bash -sst %command% -``` - -### Packaging for other distributions - -Here are informations which might be useful to you : - -SimpleSteamTinker is meant to be installed in `/usr/share/SimpleSteamTinker` (except `sst` which goes into `/usr/bin`). -The system desktop file goes in `/usr/share/applications` and the icons go in `/usr/share/icons` like many applications. - -Most of the file structure installation steps can be achieved with the Makefile. -It is recommended to use it if possible in your packaging system. - -It depends on : -- Lua 5.4 -- [LGI](https://github.com/lgi-devs/lgi) (development version **specifically**) + [GTK4](https://www.gtk.org/) + [libadwaita](https://gnome.pages.gitlab.gnome.org/libadwaita/) -- Steam installation, with a user configured and games installed. -- [LuaFileSystem](https://github.com/lunarmodules/luafilesystem) -- [LuaSocket](https://github.com/lunarmodules/luasocket) + [LuaSec](https://github.com/lunarmodules/luasec) -- [dkjson](https://dkolf.de/src/dkjson-lua.fsl/home) -- [xclip](https://github.com/astrand/xclip) *(Might be replaced/made optional in the future.)* -- [libnotify](https://gitlab.gnome.org/GNOME/libnotify) - -And optionally (if they're missing, their related features will simply be disabled) : - -[GameMode](https://github.com/FeralInteractive/gamemode), [MangoHud](https://github.com/flightlessmango/MangoHud), [Mesa](https://www.mesa3d.org/) *(for Zink support)*, [switcheroo-control](https://gitlab.freedesktop.org/hadess/switcheroo-control) and [Gamescope](https://github.com/ValveSoftware/gamescope). - -## Development - -Simply install the project's dependencies described earlier and clone it. - -To test the launch of Steam games, insert in their launch options : -```bash -/PATH/TO/CLONED/REPO/sst %command% -``` -For example, in my case `/PATH/TO/CLONED/REPO/sst` is `/home/jordan/Programmation/SimpleSteamTinker/sst`. - -Additionally, if you want to perform UI-related changes, you'll need [Blueprint](https://gitlab.gnome.org/jwestman/blueprint-compiler). -If, in the future, LGI becomes stable enough with GTK4 to use it directly to generate the UI, Blueprint might be dropped. - -*Note : A desktop file won't be provided with this method, and icons won't be installed.* - -## Comparison - -### Development - -| Development | SteamTinkerLaunch | SimpleSteamTinker | -| --- | --- | --- | -| Language | Bash | Lua | -| Code | A single file with 26k lines | Organised in modules and commented | -| UI | Directly generated by the code | (Mostly) done in separate GTK Blueprint files | -| Interface system | yad + GTK3 | LGI + GTK4 + libadwaita | -| License | GPL-3.0 | MPL-2.0 | - -### Interface - -| Features (interface) | SteamTinkerLaunch | SimpleSteamTinker | -| --- | --- | --- | -| Launch speed (on primary laptop) | ❌ (25 seconds) | ✅ (1 second) | -| Game list | ❌ | ✅ | -| Game status | ❌ | ✅ | -| ProtonDB integration | ✅ | ✅ | -| Launch Button | ✅ | ✅ | -| Steam AppID | ✅ | ✅ | -| Notifications | ✅ | ✅ | -| Pre-game launch window | ✅ | ❌ *(Not implemented to speed up game startup and not get in the user's way. Might change in the future.)* | -| Categories | ✅ | ❌ | -| Custom default game config | ✅ | ❌ | -| Game location | ~ *(impractical)* | ✅ | -| Compatdata location | ❌ | ✅ | -| Help pages *(ProtonDB, PCGamingWiki, etc.)* | ~ *(impractical)* | ✅ | - -### Tools, settings, etc. - -*(Note : Native Linux features/tools/settings are prioritized over alternatives designed for Windows.)* - -| Features (tools, settings, etc.) | SteamTinkerLaunch | SimpleSteamTinker | -| --- | --- | --- | -| Non-Steam game support | ✅ | ❌ *([Bottles](https://github.com/bottlesdevs/Bottles) recommended)* | -| ~~Proton version management~~ | ✅ | ❌ *([ProtonPlus](https://github.com/Vysp3r/ProtonPlus) recommended)* | -| dGPU management | ✅ | ✅ | -| GameMode | ✅ | ✅ | -| Custom launch system | ✅ | 🚧 *(Planned)* | -| Winetricks | ✅ | ❌ *(Planned ?)* | -| Protontricks | ❌ | ❌ *(Planned ?)* | -| Proton settings | ✅ | ✅ | -| DXVK settings | ✅ | 🚧 *(Planned)* | -| VKD3D settings | ✅ | 🚧 *(Planned)* | -| MangoHud | ✅ | ✅ | -| Gamescope | ✅ | ✅ | -| Shader support (ReShade/vkBasalt) | ✅ | 🚧 *(Planned)* | -| SDL Wayland video driver | ✅ | ✅ | -| Zink | ✅ | ✅ | -| PulseAudio latency | ✅ | 🚧 *(Planned)* | -| Vortex | ✅ | ❌ | -| Mod Organizer 2 | ✅ | ❌ | -| HedgeModManager | ✅ | ❌ | -| geo-11 3D Driver | ✅ | ❌ | -| SpecialK | ✅ | ❌ | -| FlawlessWidescreen | ✅ | ❌ | -| Stereo3D | ✅ | ❌ | -| RADV Perftest Options | ✅ | ❌ *(Cannot test properly due to lack of an AMD GPU)* | -| Steam Linux Runtime toggle | ✅ | ❌ | -| steamwebhelper toggle | ✅ | ❌ | -| obs-gamecapture | ✅ | ✅ | -| ~~Nyrna~~ | ✅ | ❌ *(Waiting for Wayland support...)* | -| ~~ReplaySorcery~~ | ✅ | ❌ *(Unmaintaned)* | -| ~~Boxtron~~ | ✅ | ❌ *([ProtonPlus](https://github.com/Vysp3r/ProtonPlus) recommended)* | -| ~~Roberta~~ | ✅ | ❌ *([ProtonPlus](https://github.com/Vysp3r/ProtonPlus) recommended)* | -| ~~Luxtorpeda~~ | ✅ | ❌ *([ProtonPlus](https://github.com/Vysp3r/ProtonPlus) recommended)* | -| Network monitoring and control | ✅ | 🚧 *(Planned)* | -| Discord Rich Presence | ✅ | 🚧 *(Planned)* | - -## Bug Reports / Contributions / Suggestions -You can report bugs or suggest features by making an issue, or you can contribute to this program directly by forking it and then sending a pull request. - -Any help will be very much appreciated. Thank you. diff --git a/assets/desktop/system.desktop b/assets/desktop/system.desktop deleted file mode 100755 index c2e7d71..0000000 --- a/assets/desktop/system.desktop +++ /dev/null @@ -1,8 +0,0 @@ -[Desktop Entry] -Name=SimpleSteamTinker -Icon=simplesteamtinker -Comment=A work-in-progress simple, fast and modern Adwaita alternative to SteamTinkerLaunch. -Exec=/usr/bin/sst -Terminal=false -Type=Application -Categories=Game; diff --git a/assets/icons/256x256.png b/assets/icons/256x256.png deleted file mode 100644 index 1796bc0..0000000 Binary files a/assets/icons/256x256.png and /dev/null differ diff --git a/assets/icons/scalable.svg b/assets/icons/scalable.svg deleted file mode 100644 index 0cec0ac..0000000 --- a/assets/icons/scalable.svg +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Button - - OutsideCircle - - - - - DotCircle - - - InsideCircle - - InsideCircle - - - Middle - - - - Arm - - - Hand - - - - - \ No newline at end of file diff --git a/assets/screenshots/ViewLight.ora b/assets/screenshots/ViewLight.ora deleted file mode 100644 index 54dd31a..0000000 Binary files a/assets/screenshots/ViewLight.ora and /dev/null differ diff --git a/assets/screenshots/ViewLight.png b/assets/screenshots/ViewLight.png deleted file mode 100644 index 7e9f2f4..0000000 Binary files a/assets/screenshots/ViewLight.png and /dev/null differ diff --git a/docs/STEAM_PROBLEMS_LIST.md b/docs/STEAM_PROBLEMS_LIST.md deleted file mode 100644 index 779fa78..0000000 --- a/docs/STEAM_PROBLEMS_LIST.md +++ /dev/null @@ -1,47 +0,0 @@ -For *"fun"*, I have compiled a list of every single problem I consider shouldn't have happened that I've encountered anyways, going through Steam's configuration files. - -I put them in order, from ***most annoying*** to *least annoying*. - -This is meant to warn people to not create code that may cross these issues. - -___ - -# Problems - -## Linux games don't get removed from *compat.vdf* when going back from their Windows version. - -### Conclusion - -We have no way from Steam's configuration files to know if a game that ever had a Windows version installed now has its Linux version installed. - -I looked everywhere I could for an alternative configuration file (or a file that could be useful through another method), but came empty-handed. - -### Workaround - -We check for Linux executables or shell scripts in every Windows game's root directory. - -We previously used in a loop - -```lua -os.execute("file "..location.."/"..file.." | grep -e 'Linux' -e 'shell' &> /dev/null") -``` - -to detect those. - -However, ironically, this method led us into another problem later on, which required us to scrap the os.execute approach. - -## *libraryfolders.vdf* doesn't get immediately updated upon game installation/uninstallation. - -### Conclusion - -This problem effectively makes it completely unreliable (and thus useless) to gather the game IDs stored inside it. - -### Workaround - -As a workaround, we look in the library folders for *appmanifest_ID.acf* files, and use them to guess the installed game IDs and gather the data. - -## Steam can get stuck trying to start a game when using *os.execute* many times. - -### Workaround - -Use os.execute as little as possible if launching a game. diff --git a/init.luau b/init.luau new file mode 100644 index 0000000..bae5fd0 --- /dev/null +++ b/init.luau @@ -0,0 +1 @@ +return require("./src").start() diff --git a/install/archlinux/PKGBUILD b/install/archlinux/PKGBUILD deleted file mode 100644 index 658d7fb..0000000 --- a/install/archlinux/PKGBUILD +++ /dev/null @@ -1,48 +0,0 @@ -# Maintainer: JordanViknar - -pkgname=simplesteamtinker-git -pkgver=r44.84be422 -pkgrel=1 -pkgdesc="A work-in-progress simple, fast and modern Adwaita alternative to SteamTinkerLaunch." -arch=('x86_64' 'i686' 'armv7h' 'aarch64') -url=https://github.com/JordanViknar/SimpleSteamTinker -license=('MPL2') -makedepends=( - 'git' - 'make' -) -depends=( - 'lua' - 'lua-lgi-git' # Used for GTK (git version because latest release neither supports Libadwaita nor GTK4 and is pretty old) - 'libadwaita' - 'gtk4' - 'lua-filesystem' # Used for some filesystem operations - 'lua-socket' # Used for network operations - 'lua-sec' # Used for secure https interactions - 'lua-dkjson' # Used for json parsing - 'xclip' # Used for copying to clipboard - 'libnotify' # Used for notifications -) -optdepends=( - 'gamemode: Enable GameMode support' - 'mangohud: Enable MangoHud support' - 'mesa: Enable Zink support' - 'switcheroo-control: Launch games with your dedicated GPU' - 'gamescope: Enable GameScope support' - 'protonplus: Recommended to install with SimpleSteamTinker' -) -provides=("${pkgname%-git}") -conflicts=("${pkgname%-git}") -source=('git+https://github.com/JordanViknar/SimpleSteamTinker') -sha256sums=('SKIP') - -pkgver() { - cd SimpleSteamTinker - printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" -} - -package() { - cd SimpleSteamTinker - make system - cp -r dist/* "${pkgdir}/" -} diff --git a/main.lua b/main.lua deleted file mode 100644 index b9fe82f..0000000 --- a/main.lua +++ /dev/null @@ -1,131 +0,0 @@ --- Startup time -local totalStartupTimeVar = os.clock() - --- Internal Modules -local logSystem = require("modules.general.logSystem") -local fsUtils = require("modules.general.fsUtils") -local systemUtils = require("modules.general.systemUtils") -local steamUtils = require("modules.steam.steamUtils") -local programMetadata = require("modules.extra.programMetadata") -local gameLauncher = require("modules.gameLauncher") - -if programMetadata.version:find("dev") then - logSystem.log("warning", "DEVELOPMENT VERSION") -end - ---[[ - Chapter 0 : Preparing the environment - We need to create the config folder if it doesn't exist. -]] --- Create the config folder if it doesn't exist -fsUtils.createOrUseDirectory(programMetadata.folders.config) --- And the games folder inside of it -fsUtils.createOrUseDirectory(programMetadata.folders.gamesConfig) - --- Create the cache folder if it doesn't exist -fsUtils.createOrUseDirectory(programMetadata.folders.cache) - --- Put the arguments inside the cache folder for testing purposes -local cacheFile = io.open(programMetadata.folders.cache.."/lastArguments.txt", "w") -if not cacheFile then - logSystem.log("error", "Unable to open cache file.") -else - cacheFile:write(table.concat(arg, "\n")) - cacheFile:close() -end - ---[[ - Chapter 1 : Game management - What's the Steam config ? Is this being started through Steam or alone ? What to do ?! -]] - --- We retrieve games registered in Steam. -local steamGames = require("modules.steam.steamConfigProvider") -collectgarbage("collect") - --- We check if we're starting a Steam game or not through the arguments. -local gameStartStatus = steamUtils.isSteamArgs(arg) - -if gameStartStatus == "steamGame" then - local game = steamGames[arg[3]:gsub("AppId=", "")] - - -- We notify the user that SimpleSteamTinker is running. - logSystem.log("info", "Starting "..game.name.."...") - systemUtils.sendNotification(programMetadata.name.." is starting...", "Detected "..game.name, "information") - - -- For every argument that is a path, we put \ next to the spaces - for i, v in ipairs(arg) do - if fsUtils.exists(v) then - arg[i] = v:gsub(" ", "\\ ") - end - end - - -- We prepare the command - local command = table.concat(arg, " ") - - logSystem.log("info", "Applying settings for "..game.name.."...") - command = gameLauncher.prepareGameLaunch(game, command) - - if os.execute(command) then - os.exit(0) - else - systemUtils.sendNotification(programMetadata.name.." crashed !", "Something bad happened while running "..game.name..".", "error") - os.exit(1) - end -elseif gameStartStatus == "SteamTinkerLaunch" or (arg[3] and steamGames[arg[3]:gsub("AppId=", "")].status == "SteamTinkerLaunch") then - -- We notify the user that SteamTinkerLaunch is running. - logSystem.log("error", "Can't use "..programMetadata.name.." ! SteamTinkerLaunch detected.") - systemUtils.sendNotification("Can't use "..programMetadata.name.." !", "SteamTinkerLaunch detected.", "warning") - - -- For every argument that is a path, we put \ next to the spaces - for i, v in ipairs(arg) do - if fsUtils.exists(v) then - arg[i] = v:gsub(" ", "\\ ") - end - end - - -- We prepare the command - local command = table.concat(arg, " ") - - os.execute(command) - os.exit(0) -end -logSystem.log("info", "No game to be launched detected. Launching interface...") - - - ---[[ - Chapter 2 : The main window - No games started. Time to cook some Libadwaita goodness then. -]] - --- External Modules -local lgi = require("lgi") -local Adw = lgi.Adw - --- Application -local app = Adw.Application({ - application_id = programMetadata.developer.."."..programMetadata.name, -}) - --- Sort Steam games alphabetically -local newSteamGamesTable = {} -for _, v in pairs(steamGames) do - table.insert(newSteamGamesTable, v) -end -table.sort(newSteamGamesTable, function(a, b) return a.name < b.name end) -steamGames = newSteamGamesTable - --- Time to start the application -local timeStart = os.clock() -function app:on_startup() - require("modules.ui.mainWindow")(app, steamGames) -end - -function app:on_activate() - logSystem.log("speed", timeStart) - logSystem.log("info", "Startup time : "..os.clock()-totalStartupTimeVar.." seconds.") - self.active_window:present() -end - -return app:run(arg) diff --git a/modules/config/configManager.lua b/modules/config/configManager.lua deleted file mode 100644 index 31e02da..0000000 --- a/modules/config/configManager.lua +++ /dev/null @@ -1,125 +0,0 @@ --- Internal Modules -local gameConfigFolder = require("modules.extra.programMetadata").folders.gamesConfig -local fsUtils = require("modules.general.fsUtils") -local logSystem = require("modules.general.logSystem") - --- External Modules -local json = require("dkjson") - -local configManager = {} - ---[[ - Name : function configManager.createGameConfig(gameId) - Description : Creates a config file for a game. - Arg 1 : gameId (string) : The game's Steam ID. - Return : The game's config. -]] -function configManager.createGameConfig(gameId) - -- Grab the default template - local gameConfig = require("modules.config.defaultConfigTemplate") - gameConfig.steamGameId = gameId - - -- Write the new table to the file - local path = string.format("%s/%s.json", gameConfigFolder, gameId) - local file = assert(io.open(path, "w"), "Couldn't open " .. path .. " !") - file:write(json.encode(gameConfig, { indent = false })) - file:close() - - return gameConfig -end - ---[[ - Name : configManager.getGameConfig(gameId) - Description : Retrieves a game's config. - Arg 1 : gameId (string) : The game's Steam ID. -]] -function configManager.getGameConfig(gameId) - -- Check if the config exists - local path = string.format("%s/%s.json", gameConfigFolder, gameId) - if not fsUtils.exists(path) then - local warningMsg = "Config for game " .. gameId .. " at " .. path .. " doesn't exist. Creating it..." - logSystem.log("warning", warningMsg) - return configManager.createGameConfig(gameId) - end - - -- Read the file - local file = assert(io.open(path, "r"), "Couldn't open " .. path .. " !") - local fileContent = file:read("*a") - file:close() - - -- Parse the JSON - local parsed = json.decode(fileContent) - - -- Update the config to be based on defaultConfigTemplate recursively - local defaultConfig = require("modules.config.defaultConfigTemplate") - local function updateConfig(config, template) - for key, value in pairs(template) do - if config[key] == nil then - config[key] = value - elseif type(value) == "table" then - updateConfig(config[key], value) - end - end - end - updateConfig(parsed, defaultConfig) - - return parsed -end - ---[[ - Name : function configManager.modifyGameConfig(gameId, dataToChange) - Description : Modifies a game's config. - Arg 1 : gameId (string) : The game's Steam ID. - Arg 2 : dataToChange (string) : The data to change. Example : "steamGameId" - Arg 3 : value (any) : The value to set. - Return : The game's config. -]] -function configManager.modifyGameConfig(gameId, dataToChange, value) - -- Grab the original config data - local gameConfig = configManager.getGameConfig(gameId) - - -- Parse dataToChange - local keys = {} - for substring in dataToChange:gmatch("[^.]+") do - keys[#keys + 1] = substring - end - - -- Modify the table - local pointer = gameConfig - for i = 1, #keys - 1 do - pointer = pointer[keys[i]] or {} - end - pointer[keys[#keys]] = value - - -- Write the new table - local path = string.format("%s/%s.json", gameConfigFolder, gameId) - local file = assert(io.open(path, "w"), "Couldn't open " .. path .. " !") - - file:write(json.encode(gameConfig)) - file:close() - - return gameConfig -end - ---[[ - Name : function configManager.grabInTableFromString(table, string) - Description : Grabs a value in a table from a string. - Arg 1 : table (table) : The table to grab the value from. - Arg 2 : string (string) : The string to parse. Example : "gamescope.general.resolution.internal.width" - Return : The value. -]] -function configManager.grabInTableFromString(table, string) - local keys = {} - for substring in string:gmatch("[^.]+") do - keys[#keys + 1] = substring - end - - local pointer = table - for i = 1, #keys do - pointer = pointer[keys[i]] or {} - end - - return pointer -end - -return configManager diff --git a/modules/config/defaultConfigTemplate.lua b/modules/config/defaultConfigTemplate.lua deleted file mode 100644 index b605f27..0000000 --- a/modules/config/defaultConfigTemplate.lua +++ /dev/null @@ -1,90 +0,0 @@ -return - -{ - config_version = 1; - steamGameId = nil; - - dgpu = { - enabled = true; - }; - - -- Miscellaneous - misc = { - sdl_wayland = true; - }; - - -- Utilities - utilities = { - gamemode = { - enabled = true; - }, - mangohud = { - enabled = false; - }, - zink = { - enabled = false; - }, - obs_gamecapture = { - enabled = false; - }, - }, - - -- GameScope - gamescope = { - enabled = false; - general = { - resolution = { - enabled = false; - internal = { - width = 1280; - height = 720; - }; - external = { - width = 1920; - height = 1080; - }; - }; - frame_limit = { - enabled = false; - normal = 60; - unfocused = 5; - }; - fullscreen = false; - borderless = false; - }; - filtering = { - enabled = false; - filter = "FSR"; -- "Linear", "Nearest", "FSR", "NIS", "Pixel" - sharpness = 10; -- Ranges from 0 to 20 - } - }, - - -- Settings exclusive to Windows games - proton = { - direct3d = { - enable_direct3d9 = true; -- Exclusive to Proton GE - enable_direct3d10 = true; - enable_direct3d11 = true; - enable_direct3d12 = true; -- Exclusive to Proton GE - use_wined3d = false; - }; - sync = { - enable_esync = true; - enable_fsync = true; - }; - nvidia = { - enable_nvapi = false; - hide_nvidia_gpu = false; - }; - fsr = { -- Exclusive to Proton GE - enabled = false; - sharpness = 2; -- Between 0 and 5, 0 = maximum, 5 = minimum - upscaling_mode = "none"; -- Can be "none", "performance", "balanced", "quality" or "ultra" - resolution = { - enabled = false; - width = 1920; - height = 1080; - }; - }; - }; -} diff --git a/modules/extra/programMetadata.lua b/modules/extra/programMetadata.lua deleted file mode 100644 index 4df3476..0000000 --- a/modules/extra/programMetadata.lua +++ /dev/null @@ -1,17 +0,0 @@ -return { - name="SimpleSteamTinker", - description = "A work-in-progress fast, simple and modern Libadwaita alternative to SteamTinkerLaunch.", - executable="sst", - icon_name = "simplesteamtinker", - version="indev", - developer="JordanViknar", - url = "https://github.com/JordanViknar/SimpleSteamTinker", - installdir = os.getenv("SST_SCRIPT_PATH"), - folders = { - config = os.getenv("HOME").."/.config/SimpleSteamTinker", - gamesConfig = os.getenv("HOME").."/.config/SimpleSteamTinker/games", - - storage = os.getenv("HOME").."/.local/share/SimpleSteamTinker", - cache = os.getenv("HOME").."/.cache/SimpleSteamTinker" - } -} diff --git a/modules/gameLauncher.lua b/modules/gameLauncher.lua deleted file mode 100644 index 83f9fab..0000000 --- a/modules/gameLauncher.lua +++ /dev/null @@ -1,103 +0,0 @@ --- Internal Modules -local logSystem = require("modules.general.logSystem") -local systemUtils = require("modules.general.systemUtils") -local configManager = require("modules.config.configManager") -local programMetadata = require("modules.extra.programMetadata") - -local gameLauncher = {} - -function gameLauncher.prepareGameLaunch(game, command) - -- First we retrive the configuration - local gameConfig - local status, result = pcall(function(value) - gameConfig = configManager.getGameConfig(value) - end, game.id) - - if not status then - logSystem.log("error", "Couldn't retrieve configuration for "..game.name..". Error : "..result) - systemUtils.sendNotification("Couldn't retrieve configuration for "..game.name.." !", result, "error") - return command - end - - -- We get the list of utilities - local utilities = require("modules.utilitiesList")(gameConfig) - - -- Environment variables : Part 1 - local environmentVars = {} - - -- We iterate through the utilities and enable them if they are installed - for _, utility in pairs(utilities) do - if utility[2] --[[= Utility is enabled]] and utility[1].isInstalled then - if utility[1].toolType == "executable" then - command = utility[1].usage(command, gameConfig) - elseif utility[1].toolType == "environmentVariable" then - table.insert(environmentVars, utility[1].usage(command, gameConfig)) - end - end - end - - -- Special case for switcherooctl, some Steam launch desktop files force the dGPU on games even when disabled. - if gameConfig.dgpu.enabled == false and utilities[3][1].isInstalled then -- Should use names instead of indexes later - -- We add the environment variables to forcefully disable the dGPU - table.insert(environmentVars, "DRI_PRIME=0 __NV_PRIME_RENDER_OFFLOAD=0 __VK_LAYER_NV_optimus=none __GLX_VENDOR_LIBRARY_NAME=none") - end - - -- Environment variables not linked to a particular utility - if game.os_platform == "Linux" then - if gameConfig.misc.sdl_wayland == true then - table.insert(environmentVars, "SDL_VIDEODRIVER=\"wayland,x11\"") - else -- We force X11 to be used, just in case. - table.insert(environmentVars, "SDL_VIDEODRIVER=\"x11\"") - end - elseif game.os_platform == "Windows" then - -- Direct3D - if gameConfig.proton.direct3d.enable_direct3d9 == false then table.insert(environmentVars, "PROTON_NO_D3D9=1") end - if gameConfig.proton.direct3d.enable_direct3d10 == false then table.insert(environmentVars, "PROTON_NO_D3D10=1") end - if gameConfig.proton.direct3d.enable_direct3d11 == false then table.insert(environmentVars, "PROTON_NO_D3D11=1") end - if gameConfig.proton.direct3d.enable_direct3d12 == false then table.insert(environmentVars, "PROTON_NO_D3D12=1") end - if gameConfig.proton.direct3d.use_wined3d == true then table.insert(environmentVars, "PROTON_USE_WINED3D=1") end - -- Sync - if gameConfig.proton.sync.enable_esync == false then table.insert(environmentVars, "PROTON_NO_ESYNC=1") end - if gameConfig.proton.sync.enable_fsync == false then table.insert(environmentVars, "PROTON_NO_FSYNC=1") end - -- NVIDIA - if gameConfig.proton.nvidia.enable_nvapi == true then table.insert(environmentVars, "PROTON_ENABLE_NVAPI=1") end - if gameConfig.proton.nvidia.hide_nvidia_gpu == true then table.insert(environmentVars, "PROTON_HIDE_NVIDIA_GPU=1") end - -- FSR - if gameConfig.proton.fsr.enabled == true then - table.insert(environmentVars, string.format( - "WINE_FULLSCREEN_FSR=1 WINE_FULLSCREEN_FSR_STRENGTH=%s WINE_FULLSCREEN_FSR_MODE=%s", - gameConfig.proton.fsr.sharpness, - string.lower(gameConfig.proton.fsr.upscaling_mode) - )) - if gameConfig.proton.fsr.resolution.enabled == true then - table.insert(environmentVars, string.format( - "WINE_FULLSCREEN_FSR_CUSTOM_MODE=%sx%s", - gameConfig.proton.fsr.resolution.width, - gameConfig.proton.fsr.resolution.height - )) - end - else - -- Explictly disable FSR if it's not enabled, recent Proton GE versions enable it by default. - table.insert(environmentVars, "WINE_FULLSCREEN_FSR=0") - end - end - - -- We convert the environment variables table to a string - if environmentVars ~= {} then - local envVarString = table.concat(environmentVars, " ") - command = "env "..envVarString.." "..command - end - - -- Print the final command in cache for debugging purposes - local cacheFile = io.open(programMetadata.folders.cache.."/lastCommand.txt", "w") - if not cacheFile then - logSystem.log("error", "Unable to open cache file.") - else - cacheFile:write(command) - cacheFile:close() - end - - return command -end - -return gameLauncher diff --git a/modules/general/fsUtils.lua b/modules/general/fsUtils.lua deleted file mode 100644 index bbff7f5..0000000 --- a/modules/general/fsUtils.lua +++ /dev/null @@ -1,188 +0,0 @@ --- External Modules -local lfs = require("lfs") - --- Internal Modules -local logSystem = require("modules.general.logSystem") - --- Module -local fsUtils = {} - ---[[ - Name : function fsUtils.exists(path) - Description : Checks if a file or directory exists. - Arg 1 : path (string) : The path to check. - Return : true if the file or directory exists, false otherwise. -]] -function fsUtils.exists(path) - local attributes, err = lfs.attributes(path) - if attributes then - return true - else - return false, err - end -end - ---[[ - Name : function fsUtils.isDirectory(path) - Description : Checks if a path is a directory. - Arg 1 : path (string) : The path to check. - Return : true if the path is a directory, false otherwise. -]] -function fsUtils.isDirectory(path) - local attributes, err = lfs.attributes(path) - if attributes then - if attributes.mode == "directory" then - return true - else - return false - end - else - return false, err - end -end - ---[[ - Name : function fsUtils.createDirectory(path) - Description : Creates a directory. - Arg 1 : path (string) : The path to create. - Return : true if the directory was created, false otherwise. -]] -function fsUtils.createDirectory(path) - local success, err = lfs.mkdir(path) - if success then - return true - else - return false, err - end -end - ---[[ - Name : function fsUtils.createOrUseDirectory(path) - Description : Creates a directory if it doesn't exist, or uses it if it does. - Arg 1 : path (string) : The path to create or use. - Return : true if the directory was created or used, false otherwise. -]] -function fsUtils.createOrUseDirectory(path) - if not fsUtils.exists(path) then - logSystem.log("debug", "Directory "..path.." not found. Creating it...") - return fsUtils.createDirectory(path) - else - return true - end -end - ---[[ - Name : function fsUtils.getSize(path) - Description : Gets the size of a file or directory. - Arg 1 : path (string) : The path to get the size of. - Return : The size of the file or directory. -]] -local function trim(s) - return s:match'^()%s*$' and '' or s:match'^%s*(.*%S)' -end -function fsUtils.getSize(path) - local handle = io.popen("du -sb '"..path:gsub("'", "'\\''").."' | cut -f1") - if not handle then - return 0 - end - - local result = handle:read("*a") - handle:close() - - return tonumber(trim(result)) -end - ---[[ - Name : function fsUtils.sizeToUnit(size) - Description : Converts a size in bytes to a human-readable size. - Arg 1 : size (number) : The size to convert. - Return : The human-readable size. -]] -function fsUtils.sizeToUnit(size) - local unit = "B" - if size > 1024 then - size = size / 1024 - unit = "KB" - end - if size > 1024 then - size = size / 1024 - unit = "MB" - end - if size > 1024 then - size = size / 1024 - unit = "GB" - end - if size > 1024 then - size = size / 1024 - unit = "TB" - end - return string.format("%.2f", size).." "..unit -end - ---[[ - Name : function fsUtils.directoryContainsLinuxData(location) - Description : Checks if a directory contains Linux data. - Arg 1 : location (string) : The directory to check. - Return : true if the directory contains Linux data, false otherwise. - - Note : Used to for game platform detection to work around Steam not cleaning up the compat.vdf file when going back from the Windows version of a game to the Linux version. - Note 2 : The reason we don't use os.execute("file ...") is because Steam can get stuck while starting a game for whatever reason. -]] --- Alternative to using file command -local function checkFileForKeywords(location, file) - local filepath = location .. "/" .. file - - local fileHandle = io.open(filepath, "r") - if not fileHandle then - return false - end - - local foundKeywords = false - - for line in fileHandle:lines() do - if line:find("Linux") or line:find("shell") then - foundKeywords = true - break - end - end - - fileHandle:close() - - return foundKeywords -end - -function fsUtils.directoryContainsLinuxData(location) - for file in lfs.dir(location) do - if (string.find(file,".sh") or not string.find(file,".")) and not fsUtils.isDirectory(location.."/"..file) then - local containsKeywords = checkFileForKeywords(location, file) - if containsKeywords then - return true - end - end - end - - return false -end - ---[[ - Name : function fsUtils.getFilenamePatternInDirectory(directory, pattern) - Description : Returns a table containing every filename matching a pattern in a directory. - Arg 1 : directory (string) : The directory to search in. - Arg 2 : pattern (string) : The pattern to search for. - Return : A table containing every filename matching the pattern. - - Note : Used to for game detection to work around Steam not updating the libraryfolders.vdf file immediately. -]] -function fsUtils.getFilenamePatternInDirectory(directory, pattern) - local result = {} - - for file in lfs.dir(directory) do - if file:match(pattern) then - table.insert(result, file) - end - end - - return result -end - -return fsUtils diff --git a/modules/general/logSystem.lua b/modules/general/logSystem.lua deleted file mode 100644 index 409a586..0000000 --- a/modules/general/logSystem.lua +++ /dev/null @@ -1,73 +0,0 @@ -local logSystem = {} - ---[[ - Name : local function color(colorId, message) - Description : Colors a message. - Arg 1 : colorId (string) : The color's id. - Arg 2 : message (string) : The message to color. - Return : The colored message. -]] -local function color(colorId, message) - local colorList = { - ["red"] = "\27[31m", - ["green"] = "\27[32m", - ["yellow"] = "\27[33m", - ["blue"] = "\27[34m", - ["magenta"] = "\27[35m", - ["cyan"] = "\27[36m", - ["white"] = "\27[37m", - ["grey"] = "\27[90m", - ["orange"] = "\27[91m", - ["reset"] = "\27[0m" - } - - return colorList[colorId]..message..colorList["reset"] -end - ---[[ - Name : function logSystem.log(type, message) - Description : Logs a message. - Arg 1 : type (string) : The log type. - Arg 2 : message (string) : The message to log. - Return : nil -]] -function logSystem.log(type, message) - local logReactions = { - ["info"] = function(text) - print("["..color("blue","Info").."] "..text) - end, - ["warning"] = function(text) - print("["..color("yellow","Warning").."] "..color("yellow",text)) - end, - ["error"] = function(text) - print(color("red","[Error]").." "..color("red",text)) - end, - ["debug"] = function(text) - print("["..color("grey","Debug").."] "..color("grey",text)) - end, - ["switch"] = function (text) - print(color("green","[Output Switch] "..text)) - print("--------------------------") - end, - ["switchEnd"] = function (text) - print("--------------------------") - end, - ["download"] = function (text) - print("["..color("green","Download").."] "..text) - end, - ["fileRead"] = function (text) - print("["..color("magenta","Data").."] "..text) - end, - ["speed"] = function (startTime) - print("["..color("grey","Speed").."] ".."Done in "..os.clock()-startTime.." seconds.") - end - } - - if (logReactions[type]) then - logReactions[type](message) - else - error("Unknown log type : "..type) - end -end - -return logSystem diff --git a/modules/general/systemUtils.lua b/modules/general/systemUtils.lua deleted file mode 100644 index c75b843..0000000 --- a/modules/general/systemUtils.lua +++ /dev/null @@ -1,72 +0,0 @@ --- Custom modules -local logSystem = require("modules.general.logSystem") -local fsUtils = require("modules.general.fsUtils") -local programMetadata = require("modules.extra.programMetadata") - --- External modules -local lgi = require("lgi") -local Notify = lgi.Notify -Notify.init(programMetadata.name) - --- Module -local systemUtils = {} - ---[[ - Name : function systemUtils.sendNotification(title, message, urgency, transient, time) - Description : Sends a notification using notify-send. - Arg 1 : title (string) : The notification's title. - Arg 2 : message (string) : The notification's message. - Arg 3 : type (string) : The notification's type. Used only for icons right now. Can be "warning", "error" or "information". - Return : nil -]] -function systemUtils.sendNotification(title, message, type) - -- Set icons - local icon - if type == "warning" then - icon = "dialog-warning-symbolic" - elseif type == "error" then - icon = "dialog-error-symbolic" - else - icon = "dialog-information" - end - - local notification = Notify.Notification.new(title, message, icon) - notification:show() -end - ---[[ - Name : function systemUtils.copyToClipboard(text) - Description : Copies a text to the clipboard using xclip. - Arg 1 : text (string) : The text to copy. - Return : true if successful, false otherwise. -]] -function systemUtils.copyToClipboard(text) - local command = string.format("echo -n %q | xclip -selection clipboard", text) - - if not os.execute(command) then - logSystem.log("error", "Unable to copy to clipboard using command : "..command) - return false - else - return true - end -end - ---[[ - Name : function systemUtils.isInstalled(command) - Description : Checks if an command is installed. - Arg 1 : command (string) : The command to check. - Return : true if installed, false otherwise. -]] -function systemUtils.isInstalled(command) - if command:sub(1, 1) == "/" then - return fsUtils.exists(command) - else - if os.execute("which "..command.." &> /dev/null") then - return true - else - return false - end - end -end - -return systemUtils diff --git a/modules/objects/steamGameObject.lua b/modules/objects/steamGameObject.lua deleted file mode 100644 index 77e2b07..0000000 --- a/modules/objects/steamGameObject.lua +++ /dev/null @@ -1,62 +0,0 @@ --- Internal Modules -local fsUtils = require("modules.general.fsUtils") - --- Constants -local steamImagesLocation = os.getenv("HOME").."/.local/share/Steam/appcache/librarycache/" - --- Extra function -local function ifExists(path) - if fsUtils.exists(path) then - return path - else - return nil - end -end - --- Object -SteamGame = { - metatable = { - __index = SteamGame - } -} -function SteamGame:new (id, name, library, location) - -- Current way of detecting if an app is a game or a tool... not the greatest. - local type = "game" - local libraryIconPath = steamImagesLocation..id.."_library_600x900.jpg" - if not fsUtils.exists(libraryIconPath) then - libraryIconPath = nil - type = "software" - end - if name:find("Proton") then - type = "proton" - end - - -- The actual OS detection is in steamConfigProvider.lua instead. This is here because Linux games can have compatdata left over from previously using Proton. - local protonConfig = {} - if fsUtils.exists(library.."/steamapps/compatdata/"..id) then - protonConfig.compatdata = library.."/steamapps/compatdata/"..id - end - - return setmetatable({ - id = id, - name = name, - library = library, - location = location, - size = nil, -- Slow, done in UI - protondb_data = nil, -- VERY slow, done in UI - type = type, - os_platform = "Linux", -- Assume Linux first, normally gets reconfigured as Windows later - proton_config = protonConfig, - tool_status = false, -- Assume the game is not setup to use the tool, normally gets reconfigured true later - images = { - header = ifExists(steamImagesLocation..id.."_header.jpg"), - icon = ifExists(steamImagesLocation..id.."_icon.jpg"), - logo = ifExists(steamImagesLocation..id.."_logo.png"), - library = libraryIconPath, - library_hero = ifExists(steamImagesLocation..id.."_library_hero.jpg"), - library_hero_blur = ifExists(steamImagesLocation..id.."_library_hero_blur.jpg"), - } - }, SteamGame.metatable) -end - -return SteamGame diff --git a/modules/objects/toolObject.lua b/modules/objects/toolObject.lua deleted file mode 100644 index 0f81668..0000000 --- a/modules/objects/toolObject.lua +++ /dev/null @@ -1,24 +0,0 @@ --- Internal Modules -local systemUtils = require("modules.general.systemUtils") -local logSystem = require("modules.general.logSystem") - -Tool = { - metatable = { - __index = Tool - } -} - -function Tool:new (toolName, toolType, modification) - assert(toolName) - assert(toolType == "executable" or toolType == "environmentVariable", "Invalid tool type for tool "..toolName..".") - assert(type(modification) == "function", "Invalid modification for tool "..toolName..".") - - return setmetatable({ - toolName = toolName, - toolType = toolType, -- "executable" or "environmentVariable" - isInstalled = systemUtils.isInstalled(toolName), - usage = modification - }, Tool.metatable) -end - -return Tool diff --git a/modules/steam/protonDBManager.lua b/modules/steam/protonDBManager.lua deleted file mode 100644 index 1a5c448..0000000 --- a/modules/steam/protonDBManager.lua +++ /dev/null @@ -1,41 +0,0 @@ --- External Modules -local http = require("socket.http") -local ltn12 = require("ltn12") -local json = require("dkjson") - --- Internal Modules -local logSystem = require("modules.general.logSystem") - -local protonDBManager = {} - ---[[ - Name : protonDBManager.getAppInfo(appId) - Description : Retrieves the ProtonDB data for a given app ID. - Arg 1 : appId (string) : The app ID to retrieve the data for. - Return : The ProtonDB data for the given app ID. -]] -function protonDBManager.getAppInfo(appId) - local url = "https://www.protondb.com/api/v1/reports/summaries/"..appId..".json" - local response = {} - - local success, status, headers = http.request { - url = url, - method = "GET", - sink = ltn12.sink.table(response) - } - - if success then - local data = json.decode(table.concat(response)) -- Parse the JSON response - if data then - return data - else - logSystem.log("error", "Failed to parse JSON response from ProtonDB for app "..appId..".") - return "Not found" - end - else - logSystem.log("error", "Failed to retrieve ProtonDB data for app "..appId..", with error : "..status..".") - return "Unavailable" - end -end - -return protonDBManager diff --git a/modules/steam/steamConfigProvider.lua b/modules/steam/steamConfigProvider.lua deleted file mode 100644 index a922b79..0000000 --- a/modules/steam/steamConfigProvider.lua +++ /dev/null @@ -1,144 +0,0 @@ --- Internal Modules -local logSystem = require("modules.general.logSystem") -local vdfParser = require("modules.steam.vdfParser") -local steamUtils = require("modules.steam.steamUtils") -local programMetadata = require("modules.extra.programMetadata") -local fsUtils = require("modules.general.fsUtils") - --- Timer -local timeStart - ---[[ - Chapter 1 : We recover the Steam user config so we can get the last active user. - And also the SteamID3 to access their settings folder. -]] -logSystem.log("fileRead", "Detecting user config...") -timeStart = os.clock() -local userData = vdfParser.parseFile(os.getenv("HOME").."/.local/share/Steam/config/loginusers.vdf") - --- We add the SteamID3 to the user datas, and also grab the most recent user's ID while we're at it. -local activeUserID -for id,data in pairs(userData["users"]) do - -- SteamID3 - data["steamID3"] = steamUtils.convertToSteamID3(id) - - -- Most recent - if data["MostRecent"] == "1" then - activeUserID = id - end -end -logSystem.log("speed", timeStart) -logSystem.log("info", "Active user : "..userData["users"][activeUserID]["AccountName"]) - ---[[ - Chapter 2 : We recover the user config to get which games have the tool enabled. - This section would normally have quite an impact on performance. - Thanks to some optimizations, however, the time is cut from 0.5 seconds to 0.04 seconds on my computer. -]] -logSystem.log("fileRead", "Detecting active user game configs...") -timeStart = os.clock() - -local userAppSettings = vdfParser.parseFile( - os.getenv("HOME").."/.local/share/Steam/userdata/"..userData["users"][activeUserID]["steamID3"].."/config/localconfig.vdf", - {"UserLocalConfigStore","Software","Valve","Steam","apps"}, - {"CachedCommunityPreferences", - "UIStoreLocalState", - "CachedStorePreferences", - "CachedNotificationPreferences", - "SteamVoiceSettings_", - "UIStoreLocalSteamUIState", - "UIStoreLocalGamepadState", - "GetEquippedProfileItemsForUser", - "CTextFilterStore_strBannedPattern", - "CTextFilterStore_strCleanPattern", - "trendingstore_storage", - "playnextstore_storage", - "GetEquippedProfileItemsForUser"} -- Optimization : we remove big lines that we don't need. -)["UserLocalConfigStore"]["Software"]["Valve"]["Steam"]["apps"] -logSystem.log("speed", timeStart) - ---[[ - Chapter 3 : We recover the Steam library config to get the list of games. - We can't use just libraryfolders.vdf to get the game IDs, as Steam seems to not always update it immediately. -]] -logSystem.log("fileRead", "Detecting games...") -timeStart = os.clock() - -local libraryFolders = vdfParser.parseFile(os.getenv("HOME").."/.local/share/Steam/config/libraryfolders.vdf") - --- Steam Game Object -local SteamGame = require("modules.objects.steamGameObject") --- And list -local steamGames = {} - --- For each library -for _,folderData in pairs(libraryFolders["libraryfolders"]) do - -- We check if the folder exists - if not fsUtils.exists(folderData["path"].."/steamapps") then - logSystem.log("warning", "Steam library "..folderData["path"].." doesn't exist. Skipping...") - goto continue - end - - -- We get the list of every appmanifest_*.acf file in the folder - local appManifestsFileNames = fsUtils.getFilenamePatternInDirectory(folderData["path"].."/steamapps", "appmanifest_") - for _,appId in pairs(appManifestsFileNames) do - -- We convert the filename to the app ID - appId = appId:gsub("appmanifest_", ""):gsub(".acf", "") - - local appManifest = vdfParser.parseFile(folderData.path.."/steamapps/appmanifest_"..appId..".acf") - -- We create a SteamGame object - local steamGame = SteamGame:new( - appId, - appManifest["AppState"]["name"], - folderData["path"], - folderData.path.."/steamapps/common/"..appManifest["AppState"]["installdir"] - ) - -- We add it to the list - steamGames[appId] = steamGame - end - - ::continue:: -end - --- And now we merge user game data and game data -for gameID, data in pairs(userAppSettings) do - -- We check if the game is actually installed before attempting a merge. - if steamGames[gameID] ~= nil then - -- Tool status - if data["LaunchOptions"] and string.find(data["LaunchOptions"],programMetadata.executable) then - steamGames[gameID].status = true - end - if data["LaunchOptions"] and string.find(data["LaunchOptions"],"steamtinkerlaunch") then - steamGames[gameID].status = "SteamTinkerLaunch" - end - end -end -logSystem.log("speed", timeStart) - ---[[ - Chapter 4 : We check if the game is in the compat list -]] -logSystem.log("fileRead","Detecting games' platforms...") -timeStart = os.clock() - -local compatList = vdfParser.parseFile( - os.getenv("HOME").."/.local/share/Steam/userdata/"..userData["users"][activeUserID]["steamID3"].."/config/compat.vdf" -)["platform_overrides"] - -for gameID,_ in pairs(compatList) do - -- Proton Status - --[[ - WARNING : METHOD WRONGLY SETS WINDOWS STATUS FOR LINUX GAMES THAT WERE PREVIOUSLY USING PROTON - Workaround : Check if Windows version contains Linux executable or shell script. - ]] - if steamGames[gameID] and steamGames.type ~= "proton" then - if not fsUtils.directoryContainsLinuxData(steamGames[gameID].location) then - steamGames[gameID].os_platform = "Windows" - else - logSystem.log("warning", "Game "..steamGames[gameID].name.." was listed as Windows game, but contains Linux data. Assuming it runs with Linux...") - end - end -end -logSystem.log("speed", timeStart) - -return steamGames diff --git a/modules/steam/steamUtils.lua b/modules/steam/steamUtils.lua deleted file mode 100644 index ed19bbb..0000000 --- a/modules/steam/steamUtils.lua +++ /dev/null @@ -1,44 +0,0 @@ -local steamUtils = {} - ---[[ - This function checks if the arguments are from Steam or not. - I suppose this is where non-Steam game support will be added later. -]] -function steamUtils.isSteamArgs(arguments) - if arguments[7] ~= nil and arguments[7]:find("steamtinkerlaunch") then - return "steamtinkerlaunch" - elseif ( - --arguments[1] == os.getenv("HOME").."/.local/share/Steam/ubuntu12_32/reaper" and - arguments[2] == "SteamLaunch" and - arguments[3]:match("AppId=%d+") and - arguments[4] == "--" and - --arguments[5] == os.getenv("HOME").."/.local/share/Steam/ubuntu12_32/steam-launch-wrapper" and - arguments[6] == "--" - ) then - return "steamGame" - else - return false - end -end - ---[[ - Name : function steamUtils.convertToSteamID3(steamID) - Description : Converts a SteamID64 to a SteamID3 - Arg 1 : string steamID - Return : string steamID3 -]] -function steamUtils.convertToSteamID3(steamID) - local id_str = tostring(steamID) - - if tonumber(id_str) then - local offset_id = tonumber(id_str) - 76561197960265728 - local account_type = offset_id % 2 - local account_id = math.floor((offset_id - account_type) / 2) + account_type - - return (account_id * 2) - account_type - else - error("Unable to decode SteamID : "..id_str) - end -end - -return steamUtils diff --git a/modules/steam/vdfParser.lua b/modules/steam/vdfParser.lua deleted file mode 100644 index 30e67f6..0000000 --- a/modules/steam/vdfParser.lua +++ /dev/null @@ -1,167 +0,0 @@ -local vdfParser = {} - ---[[ - Name : function vdfParser.printArray(data, indent) - Description : Prints a table in a human-readable way. Used for testing. - Arg 1 : data (table) : The table to print. - Arg 2 : indent (number) : The indentation level. Default : 0 -]] -function vdfParser.printArray(data, indent) - indent = indent or 0 - local spaces = string.rep(" ", indent) - - for key, value in pairs(data) do - if type(value) == "table" then - print(spaces .. key .. ": {") - vdfParser.printArray(value, indent + 1) - print(spaces .. "}") - else - print(spaces .. key .. ": " .. tostring(value)) - end - end -end - ---[[ - Name : function vdfParser.parseString(input, stopKeyList) - Description : Parses a VDF string into a table. - Arg 1 : input (string) : The VDF string to parse. - Arg 2 : stopKeyList (table) : A list of keys to stop the parsing at. Default : nil - Return : result (table) : The parsed table. -]] -function vdfParser.parseString(input, stopKeyList) - local result = {} - - local status = "keyWait" - - -- Temp Values - local tempKey, tempValue, tempRecursiveInput = "", "", "" - - local indent = 0 - local previousChar = nil - - -- Until we reached the end of the file - for i = 1, #input do - local char = input:sub(i, i) - - -- Status : - -- keyWait : We wait for a key - -- readingKey : We are reading a key - -- valueWait : We wait for a value - -- readingValue : We are reading a value - -- readingTableValue : We are reading a table value - - -- String management - if char == '"' and previousChar ~= "\\" then - if status == "keyWait" then - status = "readingKey" - elseif status == "readingKey" then - status = "valueWait" - elseif status == "valueWait" then - status = "readingValue" - elseif status == "readingValue" then - result[tempKey] = tempValue - -- We stop the parsing if we reached the stopKeyList entry - if stopKeyList and tempKey == stopKeyList[1] then - return result - end - tempKey = "" - tempValue = "" - status = "keyWait" - elseif status == "readingTableValue" then - tempRecursiveInput = tempRecursiveInput..char - end - - -- Subtable management - elseif char == '{' and previousChar ~= "\\" and (status == "valueWait" or status == "readingTableValue") then - if status == "valueWait" then - status = "readingTableValue" - elseif status == "readingTableValue" then - tempRecursiveInput = tempRecursiveInput..char - indent = indent + 1 - end - elseif char == '}' and previousChar ~= "\\" and status == "readingTableValue" then - if indent == 0 then - local newStopKeyList = nil - if stopKeyList then - newStopKeyList = {table.unpack(stopKeyList, 2)} - if next(newStopKeyList) == nil then - newStopKeyList = nil - end - end - - if stopKeyList == nil or stopKeyList[1] == nil or tempKey == stopKeyList[1] then - result[tempKey] = vdfParser.parseString(tempRecursiveInput, newStopKeyList) - end - - if stopKeyList and tempKey == stopKeyList[1] then - return result - end - - tempKey = "" - tempValue = "" - tempRecursiveInput = "" - status = "keyWait" - else - tempRecursiveInput = tempRecursiveInput..char - indent = indent - 1 - end - - -- While reading a key - elseif status == "readingKey" then - tempKey = tempKey..char - -- While reading a value - elseif status == "readingValue" and (stopKeyList == nil or stopKeyList[1] == nil or tempKey == stopKeyList[1]) then - tempValue = tempValue..char - -- While reading a table value, before sending it through the function again to parse it - elseif status == "readingTableValue" and (stopKeyList == nil or stopKeyList[1] == nil or tempKey == stopKeyList[1]) then - tempRecursiveInput = tempRecursiveInput..char - end - - previousChar = char - end - - return result -end - ---[[ - Name : function vdfParser.parseFile(path, stopKeyList, wordsFromLinesToRemove) - Description : Parses a VDF file into a table. - Arg 1 : path (string) : The path to the file to parse. - Arg 2 : stopKeyList (table) : A list of keys to stop the parsing at. Default : nil - Arg 3 : wordsFromLinesToRemove (table) : A list of words that, if found in a line, will remove the line. Default : nil - Return : result (table) : The parsed table. - - Note : Arg 2 and Arg 3 are critical for optimization in big files. Do not hesitate to use them. - Results such as going from 0.5 seconds to 0.04 seconds are possible. -]] -function vdfParser.parseFile(path, stopKeyList, wordsFromLinesToRemove) - local file = io.open(path, "r") - if not file then - error("File not found : "..path) - end - - local content = file:read("*all") - file:close() - - -- Remove every line that contains a word from wordsFromLinesToRemove - if wordsFromLinesToRemove then - local lines = {} - for line in content:gmatch("[^\r\n]+") do - local removeLine = false - for _, word in pairs(wordsFromLinesToRemove) do - if line:find(word) then - removeLine = true - break - end - end - if not removeLine then - table.insert(lines, line) - end - end - content = table.concat(lines, "\n") - end - - return vdfParser.parseString(content, stopKeyList) -end - -return vdfParser diff --git a/modules/tools/gamemode.lua b/modules/tools/gamemode.lua deleted file mode 100644 index de54ee9..0000000 --- a/modules/tools/gamemode.lua +++ /dev/null @@ -1,10 +0,0 @@ --- Internal Modules -local toolObject = require("modules.objects.toolObject") - -local name = "gamemoderun" -local type = "executable" -local usage = function(command) - return "gamemoderun "..command -end - -return toolObject:new(name, type, usage) diff --git a/modules/tools/gamescope.lua b/modules/tools/gamescope.lua deleted file mode 100644 index bc34396..0000000 --- a/modules/tools/gamescope.lua +++ /dev/null @@ -1,59 +0,0 @@ --- Internal Modules -local toolObject = require("modules.objects.toolObject") - -local name = "gamescope" -local type = "executable" -local usage = function(command, config) - local options = "" - - -- Nested resolution - if - config.gamescope.general.resolution.enabled == true - then - options = string.format( - "%s --nested-width %s --nested-height %s --output-width %s --output-height %s", - options, - config.gamescope.general.resolution.internal.width, - config.gamescope.general.resolution.internal.height, - config.gamescope.general.resolution.external.width, - config.gamescope.general.resolution.external.height - ) - end - - -- Frame limit - if config.gamescope.general.frame_limit.enabled == true then - options = string.format( - "%s -r %s -o %s", - options, - config.gamescope.general.frame_limit.normal, - config.gamescope.general.frame_limit.unfocused - ) - end - - -- Fullscreen - if config.gamescope.general.fullscreen == true then - options = options.." -f" - end - if config.gamescope.general.borderless == true then - options = options.." -b" - end - - -- Filtering - if config.gamescope.filtering.enabled == true then - options = string.format( - "%s --filter %s --sharpness %s", - options, - string.lower(config.gamescope.filtering.filter), - config.gamescope.filtering.sharpness -- gamescope's sharpness is inverted - ) - end - - -- The command - return string.format( - "gamescope %s %s", - options, - command - ) -end - -return toolObject:new(name, type, usage) diff --git a/modules/tools/mangohud.lua b/modules/tools/mangohud.lua deleted file mode 100644 index 59bb082..0000000 --- a/modules/tools/mangohud.lua +++ /dev/null @@ -1,10 +0,0 @@ --- Internal Modules -local toolObject = require("modules.objects.toolObject") - -local name = "mangohud" -local type = "executable" -local usage = function(command) - return "mangohud "..command -end - -return toolObject:new(name, type, usage) diff --git a/modules/tools/obs-gamecapture.lua b/modules/tools/obs-gamecapture.lua deleted file mode 100644 index b127f52..0000000 --- a/modules/tools/obs-gamecapture.lua +++ /dev/null @@ -1,17 +0,0 @@ --- Internal Modules -local toolObject = require("modules.objects.toolObject") - ---[[ - I was tempted to separate this tool in 2 options : one for the utility (supports Vulkan & OpenGL), - and another for the environment variable which can be used with Vulkan games. - However, I chose to stay simple and use what works for all : the utility. - This is seemingly also the approach SteamTinkerLaunch goes with, judging from its wiki. -]] - -local name = "obs-gamecapture" -local type = "executable" -local usage = function(command) - return "obs-gamecapture "..command -end - -return toolObject:new(name, type, usage) diff --git a/modules/tools/switcherooctl.lua b/modules/tools/switcherooctl.lua deleted file mode 100644 index 2aa25df..0000000 --- a/modules/tools/switcherooctl.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Internal Modules -local toolObject = require("modules.objects.toolObject") - -local name = "switcherooctl" -local type = "executable" -local usage = function(command, config) - if config.utilities.zink.enabled == true then - -- We let Zink handle the dGPU - return command - else - return "switcherooctl launch "..command - end -end - -return toolObject:new(name, type, usage) diff --git a/modules/tools/zink.lua b/modules/tools/zink.lua deleted file mode 100644 index 3058c2a..0000000 --- a/modules/tools/zink.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Internal Modules -local toolObject = require("modules.objects.toolObject") - -local name = "/usr/lib/dri/zink_dri.so" -local toolType = "environmentVariable" -local usage = function(command, config) - if config.dgpu.enabled and require("modules.tools.switcherooctl").isInstalled then - -- We override switcherooctl (in its function) and let Zink handle the dGPU - return "DRI_PRIME=1 __GLX_VENDOR_LIBRARY_NAME=mesa MESA_LOADER_DRIVER_OVERRIDE=zink GALLIUM_DRIVER=zink" - else - return "MESA_LOADER_DRIVER_OVERRIDE=zink" - end -end - -return toolObject:new(name, toolType, usage) diff --git a/modules/ui/UItoSettingsList.lua b/modules/ui/UItoSettingsList.lua deleted file mode 100644 index 29a6f8d..0000000 --- a/modules/ui/UItoSettingsList.lua +++ /dev/null @@ -1,52 +0,0 @@ -return { - -- Settings - ["dGPU_Switch"] = {type= "Switch", tool = "switcherooctl", setting = "dgpu.enabled"}, - ["SDL_Wayland_Switch"] = {type= "Switch", setting = "misc.sdl_wayland", os_platform = "Linux"}, - - -- Utilities - ["gamemode_Switch"] = {type= "Switch", tool = "gamemoderun", setting = "utilities.gamemode.enabled"}, - ["mangohud_Switch"] = {type= "Switch", tool = "mangohud", setting = "utilities.mangohud.enabled"}, - ["zink_Switch"] = {type= "Switch", tool = "/usr/lib/dri/zink_dri.so", setting = "utilities.zink.enabled"}, - ["obs_gamecapture_Switch"] = {type= "Switch", tool = "obs-gamecapture", setting = "utilities.obs_gamecapture.enabled"}, - - -- Gamescope - ["gamescope_Switch"] = {type= "Switch", tool = "gamescope", setting = "gamescope.enabled"}, - -- Resolution - ["gamescope_Resolution_Switch"] = {type= "Switch", tool = "gamescope", setting = "gamescope.general.resolution.enabled"}, - ["gamescope_Resolution_Internal_Width_SpinRow"] = {type= "SpinRow", tool = "gamescope", setting = "gamescope.general.resolution.internal.width"}, - ["gamescope_Resolution_Internal_Height_SpinRow"] = {type= "SpinRow", tool = "gamescope", setting = "gamescope.general.resolution.internal.height"}, - ["gamescope_Resolution_External_Width_SpinRow"] = {type= "SpinRow", tool = "gamescope", setting = "gamescope.general.resolution.external.width"}, - ["gamescope_Resolution_External_Height_SpinRow"] = {type= "SpinRow", tool = "gamescope", setting = "gamescope.general.resolution.external.height"}, - -- Framerate - ["gamescope_Framerate_Switch"] = {type= "Switch", tool = "gamescope", setting = "gamescope.general.frame_limit.enabled"}, - ["gamescope_Framerate_Normal_SpinRow"] = {type= "SpinRow", tool = "gamescope", setting = "gamescope.general.frame_limit.normal"}, - ["gamescope_Framerate_Unfocused_SpinRow"] = {type= "SpinRow", tool = "gamescope", setting = "gamescope.general.frame_limit.unfocused"}, - -- Filtering - ["gamescope_Filtering_Switch"] = {type= "Switch", tool = "gamescope", setting = "gamescope.filtering.enabled"}, - ["gamescope_Filtering_Sharpness_SpinRow"] = {type= "SpinRow", tool = "gamescope", setting = "gamescope.filtering.sharpness"}, - ["gamescope_Filtering_Filter_ComboRow"] = {type= "ComboRow", tool = "gamescope", setting = "gamescope.filtering.filter", items = {"Linear","Nearest","FSR","NIS","Pixel"}}, - -- Extras - ["gamescope_Borderless_Toggle"] = {type= "Toggle", tool = "gamescope", setting = "gamescope.general.borderless"}, - ["gamescope_Fullscreen_Toggle"] = {type= "Toggle", tool = "gamescope", setting = "gamescope.general.fullscreen"}, - - -- Proton - -- Direct3D - ["Direct3D9_Switch"] = {type= "Switch", setting = "proton.direct3d.enable_direct3d9", os_platform = "Windows"}, - ["Direct3D10_Switch"] = {type= "Switch", setting = "proton.direct3d.enable_direct3d10", os_platform = "Windows"}, - ["Direct3D11_Switch"] = {type= "Switch", setting = "proton.direct3d.enable_direct3d11", os_platform = "Windows"}, - ["Direct3D12_Switch"] = {type= "Switch", setting = "proton.direct3d.enable_direct3d12", os_platform = "Windows"}, - ["WineD3D_Switch"] = {type= "Switch", setting = "proton.direct3d.use_wined3d", os_platform = "Windows"}, - -- Sync - ["ESync_Toggle"] = {type= "Toggle", setting = "proton.sync.enable_esync", os_platform = "Windows"}, - ["FSync_Toggle"] = {type= "Toggle", setting = "proton.sync.enable_fsync", os_platform = "Windows"}, - -- NVIDIA - ["Hide_NVIDIA_GPU_Switch"] = {type= "Switch", setting = "proton.nvidia.hide_nvidia_gpu", os_platform = "Windows"}, - ["Enable_NVAPI_Switch"] = {type= "Switch", setting = "proton.nvidia.enable_nvapi", os_platform = "Windows"}, - -- FSR - ["Wine_FSR_Switch"] = {type= "Switch", setting = "proton.fsr.enabled", os_platform = "Windows"}, - ["Wine_FSR_Sharpness_SpinRow"] = {type= "SpinRow", setting = "proton.fsr.sharpness", os_platform = "Windows"}, - ["Wine_FSR_Upscaling_Resolution_Mode_ComboRow"] = {type = "ComboRow", setting = "proton.fsr.upscaling_mode", os_platform = "Windows", items = {"None", "Performance", "Balanced", "Quality", "Ultra"}}, - ["Wine_FSR_Resolution_Switch"] = {type = "Switch", setting = "proton.fsr.resolution.enabled", os_platform = "Windows"}, - ["Wine_FSR_Resolution_External_Width_SpinRow"] = {type= "SpinRow", setting = "proton.fsr.resolution.width", os_platform = "Windows"}, - ["Wine_FSR_Resolution_External_Height_SpinRow"] = {type= "SpinRow", setting = "proton.fsr.resolution.height", os_platform = "Windows"} -} diff --git a/modules/ui/aboutWindow.lua b/modules/ui/aboutWindow.lua deleted file mode 100644 index 8529e8b..0000000 --- a/modules/ui/aboutWindow.lua +++ /dev/null @@ -1,73 +0,0 @@ --- External Modules -local lgi = require("lgi") -local Adw = lgi.Adw -local Gtk = lgi.Gtk - --- Internal Modules -local programMetadata = require("modules.extra.programMetadata") - --- Translation fake function -local function _(text) return text end - -return function(application, interface, win) - local creditLauncher = interface:get_object("aboutLauncher") - creditLauncher.on_clicked = function() - -- Create window with main metadata already set - local creditWindow = Adw.AboutWindow{ - application = application, - modal = true, - - application_icon = programMetadata.icon_name, - application_name = programMetadata.name, - version = programMetadata.version, - - title = _("About ")..programMetadata.name, - developer_name = programMetadata.developer, - website = programMetadata.url, - issue_url = programMetadata.url.."/issues", - license_type = Gtk.License.MPL_2_0, - - developers = { - "JordanViknar https://github.com/JordanViknar", - }, - artists = { - "JordanViknar https://github.com/JordanViknar" - }, - documenters = { - "JordanViknar https://github.com/JordanViknar" - } - } - - -- Credits - creditWindow:add_acknowledgement_section(_("Inspired by"),{ - "SteamTinkerLaunch https://github.com/sonic2kk/steamtinkerlaunch", - "Bottles https://github.com/bottlesdevs/Bottles" - }) - creditWindow:add_acknowledgement_section(_("Powered by"),{ - "Lua https://www.lua.org/", - "LGI https://github.com/lgi-devs/lgi", - "GTK https://www.gtk.org/", - "libadwaita https://gnome.pages.gitlab.gnome.org/libadwaita/", - "LuaFileSystem https://github.com/lunarmodules/luafilesystem", - "LuaSocket https://github.com/lunarmodules/luasocket", - "dkjson http://dkolf.de/src/dkjson-lua.fsl/home" - }) - creditWindow:add_acknowledgement_section(_("Third-Party Tools and Special Thanks"),{ - "Steam https://store.steampowered.com/", - "Proton https://github.com/ValveSoftware/proton", - "GameMode https://github.com/FeralInteractive/gamemode", - "MangoHud https://github.com/flightlessmango/MangoHud", - "Zink https://docs.mesa3d.org/drivers/zink.html", - "ProtonDB https://www.protondb.com/", - "PCGamingWiki https://www.pcgamingwiki.com/", - "SteamDB https://steamdb.info/", - "switcheroo-control https://gitlab.freedesktop.org/hadess/switcheroo-control", - "Gamescope https://github.com/ValveSoftware/gamescope" - }) - - creditWindow:set_comments(_("A work-in-progress fast, simple and modern Libadwaita alternative to SteamTinkerLaunch.")) - - creditWindow:set_transient_for(win) - creditWindow:present() - end -end diff --git a/modules/ui/gameSettingsOverview.lua b/modules/ui/gameSettingsOverview.lua deleted file mode 100644 index 82064dd..0000000 --- a/modules/ui/gameSettingsOverview.lua +++ /dev/null @@ -1,167 +0,0 @@ --- Internal Modules -local lgiHelper = require("modules.ui.lgiHelper") -local protonDBManager = require("modules.steam.protonDBManager") -local fsUtils = require("modules.general.fsUtils") -local logSystem = require("modules.general.logSystem") -local systemUtils = require("modules.general.systemUtils") - --- External Modules -local lgi = require("lgi") -local Adw = lgi.Adw -local Gio = lgi.Gio - -return function(application, interface, steamGameData) - --[[ - SIDEBAR - ]] - -- Set the game banner in the sidebar - interface:get_object("Sidebar_Banner"):set_filename(steamGameData.images.header) - - --[[ - OVERVIEW - ]] - -- Sets the game image in the overview area - interface:get_object("Overview_Picture"):set_filename(steamGameData.images.library) - -- Sets the game title in the overview area - interface:get_object("gameTitle").label = steamGameData.name - - -- ProtonDB rating - local protonRating_Label = interface:get_object("protonDBRating_Label") - protonRating_Label.label = "Loading..." - protonRating_Label.css_classes = {"dim-label"} - if steamGameData.os_platform == "Windows" then - - local function loadProtonDBrating() - -- If the game's rating hasn't been retrieved yet, we retrieve it. - -- The reason why this is done HERE is because it would slow down startup otherwise. - if steamGameData.protondb_data == nil then - logSystem.log("download", "Loading ProtonDB rating for "..steamGameData.name.."...") - steamGameData.protondb_data = protonDBManager.getAppInfo(steamGameData.id) - end - - -- We set the rating label - if steamGameData.protondb_data == "Not found" then - protonRating_Label.css_classes = {"error"} - elseif steamGameData.protondb_data == "Unavailable" then - protonRating_Label.css_classes = {"warning"} - elseif steamGameData.protondb_data["tier"] == "platinum" then - protonRating_Label.css_classes = {"success"} - elseif steamGameData.protondb_data["tier"] == "silver" or "gold" then - protonRating_Label.css_classes = {"warning"} - else - protonRating_Label.css_classes = {"error"} - end - local rating = steamGameData.protondb_data["tier"] or steamGameData.protondb_data - -- Set label and capitalize the first letter - protonRating_Label.label = rating:sub(1, 1):upper()..rating:sub(2) - end - Gio.Async.start(loadProtonDBrating)() - - else - protonRating_Label.label = "Native" - protonRating_Label.css_classes = {"success"} - end - - -- Sets up the game start button - local gameLaunchButton = interface:get_object("gameLaunchButton") - lgiHelper.replaceSignal(gameLaunchButton, "on_clicked", function() - os.execute("xdg-open steam://rungameid/"..steamGameData.id.." &> /dev/null") - end) - - -- Sets up the game's SimpleSteamTinker status - local gameStatus, gameColor - if steamGameData.status == true then - gameStatus = "Enabled" - gameColor = "success" - elseif steamGameData.status == "SteamTinkerLaunch" then - gameStatus = "SteamTinkerLaunch" - gameColor = "warning" - else - gameStatus = "Disabled" - gameColor = "error" - end - local gameStatus_Label = interface:get_object("gameStatus_Label") - gameStatus_Label.label = gameStatus - gameStatus_Label.css_classes = {gameColor} - - -- Sets the game ID in the overview area - interface:get_object("gameID_Label").label = steamGameData.id - -- Sets up the game ID copy button in the overview area - local toastSystem = interface:get_object("toastSystem") - local gameIDCopyButton = interface:get_object("gameID_copyButton") - lgiHelper.replaceSignal(gameIDCopyButton, "on_clicked", function() - if systemUtils.copyToClipboard(steamGameData.id) then - toastSystem:add_toast( - Adw.Toast.new("Game ID copied to clipboard !") - ) - end - end) - -- Sets the game platform in the overview area - interface:get_object("gamePlatform_Label").label = steamGameData.os_platform - - -- Sets the game size in the overview area - local gameSize_Label = interface:get_object("gameSize_Label") - gameSize_Label.label = "Loading..." - if not steamGameData.size then - logSystem.log("fileRead", "Detecting size for "..steamGameData.name.."...") - - local function insertGameSize() - steamGameData.size = fsUtils.sizeToUnit(fsUtils.getSize(steamGameData.location)) - gameSize_Label.label = steamGameData.size - end - Gio.Async.start(insertGameSize)() - - else - gameSize_Label.label = steamGameData.size - end - - -- Sets the game folder in the overview area - interface:get_object("gameLocation_ActionRow").subtitle = steamGameData.location - -- Modifies the button to get to the game's folder - local gameFolderButton = interface:get_object("gameLocationButton") - lgiHelper.replaceSignal(gameFolderButton, "on_clicked", function() - os.execute("xdg-open '"..steamGameData.location:gsub("'", "'\\''").."' &> /dev/null") - end) - - -- Sets the compatdata folder in the overview area - local gameCompatdata_ActionRow = interface:get_object("gameCompatdata_ActionRow") - if steamGameData.os_platform == "Windows" then - gameCompatdata_ActionRow.visible = true - if steamGameData.proton_config.compatdata then - gameCompatdata_ActionRow:set_sensitive(true) - gameCompatdata_ActionRow.subtitle = steamGameData.proton_config.compatdata - -- Modifies the button to get to the game's compatdata folder - lgiHelper.replaceSignal(interface:get_object("gameCompatdataButton"), "on_clicked", function() - os.execute("xdg-open '"..steamGameData.proton_config.compatdata:gsub("'", "'\\''").."' &> /dev/null") - end) - else - gameCompatdata_ActionRow:set_sensitive(false) - gameCompatdata_ActionRow.subtitle = "Not found. Try launching the game at least once ?" - end - else - gameCompatdata_ActionRow.visible = false - end - - --[[ - LINKS - ]] - local protonDBPage_Button = interface:get_object("protonDBPage_Button") - lgiHelper.replaceSignal(protonDBPage_Button, "on_clicked", function() - os.execute("xdg-open 'https://www.protondb.com/app/"..steamGameData.id.."' &> /dev/null") - end) - - local steamDBPage_Button = interface:get_object("steamDBPage_Button") - lgiHelper.replaceSignal(steamDBPage_Button, "on_clicked", function() - os.execute("xdg-open 'https://steamdb.info/app/"..steamGameData.id.."' &> /dev/null") - end) - - local PCGamingWikiPage_Button = interface:get_object("PCGamingWikiPage_Button") - lgiHelper.replaceSignal(PCGamingWikiPage_Button, "on_clicked", function() - os.execute("xdg-open 'https://www.pcgamingwiki.com/wiki/"..steamGameData.name:gsub(" ", "_").."' &> /dev/null") - end) - - local SteambasePage_Button = interface:get_object("SteambasePage_Button") - lgiHelper.replaceSignal(SteambasePage_Button, "on_clicked", function() - os.execute("xdg-open 'https://steambase.io/apps/"..steamGameData.id.."' &> /dev/null") - end) -end diff --git a/modules/ui/lgiHelper.lua b/modules/ui/lgiHelper.lua deleted file mode 100644 index 75b1830..0000000 --- a/modules/ui/lgiHelper.lua +++ /dev/null @@ -1,77 +0,0 @@ --- External Modules -local lgi = require("lgi") -local GObject = lgi.GObject - --- Internal Modules -local systemUtils = require("modules.general.systemUtils") -local configManager = require("modules.config.configManager") - -local lgiHelper = {} - ---[[ - Name : function lgiHelper.replaceSignal(object, signal, action) - Description : This function helps replacing signals on widgets. - For example, if you want to replace the "on_clicked" signal of a button. - - It avoids bugs like opening a game's directory also opening the previous ones. - Arg 1 : The object to modify. - Arg 2 : The signal to replace. - Arg 3 : The function to run (when clicking for example). -]] -local signalList = {} -function lgiHelper.replaceSignal(object, signal, action) - lgiHelper.removeSignal(object, signal) - - if signalList[object] == nil then signalList[object] = {} end - signalList[object][signal] = object[signal]:connect(action) -end - ---[[ - Name : function lgiHelper.removeSignal(object, signal) - Description : Simply deactivate the function of a button. - Arg 1 : The object to modify. - Arg 2 : The signal to remove. -]] -function lgiHelper.removeSignal(object, signal) - -- We create a table for the object if it doesn't exist. - if signalList[object] == nil then signalList[object] = {} end - -- If the object already has an event connected to the same signal, we disconnect it. - if signalList[object][signal] then - GObject.signal_handler_disconnect(object, signalList[object][signal]) - signalList[object][signal] = nil - end -end - ---[[ - Name : function lgiHelper.connectUtilityToButton(id, button, utility, property, setting) - Description : Connects a utility to a button. - Arg 1 : string id : The game's ID. - Arg 2 : The button to connect. - Arg 3 : string utility : The utility's name. - Arg 4 : string property : The property to connect. - Arg 5 : string setting : The setting to modify. -]] -function lgiHelper.connectUtilityToButton(id, button, utility, property, setting) - local signal = "on_state_set" - lgiHelper.removeSignal(button, signal) - - -- If utility begins with /, then it's a file path. - local isPresent = systemUtils.isInstalled(utility) - - if isPresent then - button:set_sensitive(true) - button:set_active(property) - - if signalList[button] == nil then signalList[button] = {} end - signalList[button][signal] = button[signal]:connect(function() - configManager.modifyGameConfig(id, setting, button:get_active()) - end) - else - button:set_sensitive(false) - button:set_active(false) - button.has_tooltip = true - button.tooltip_text = "'"..utility.."' is not present on your system." - end -end - -return lgiHelper diff --git a/modules/ui/mainWindow.lua b/modules/ui/mainWindow.lua deleted file mode 100644 index da8014a..0000000 --- a/modules/ui/mainWindow.lua +++ /dev/null @@ -1,236 +0,0 @@ --- Internal Modules -local programMetadata = require("modules.extra.programMetadata") -local configManager = require("modules.config.configManager") -local systemUtils = require("modules.general.systemUtils") -local lgiHelper = require("modules.ui.lgiHelper") - --- External Modules -local lgi = require("lgi") -local Gtk = lgi.require("Gtk") -local Adw = lgi.Adw - --- Install checks -local installedList = {} - -return function(app, steamGames) - -- We create the window - local builder = Gtk.Builder.new_from_file(programMetadata.installdir.."ui/main.ui") - local win = builder:get_object("mainWindow") - win.application = app - win.title = programMetadata.name - win.startup_id = programMetadata.name - - -- Check for dev version and add relevant theme - if programMetadata.version:find("dev") or programMetadata.version:find("a") or programMetadata.version:find("b") then - win:add_css_class("devel") - end - - --[[ - UI ELEMENTS - ]] - -- Page management - local mainView = builder:get_object("mainView") - local gameList = builder:get_object("gameList") - - local gameSettingsInterface = builder:get_object("gameSettings") - - -- Stack test - local stack = builder:get_object("gameSettingsStack") - - -- For every game that has SimpleSteamMod enabled, we add a button in this list - for _, game in pairs(steamGames) do - if game.type == "game" then - local statusIcon, statusLabel, statusColor - -- Status label and icon - if game.status == true then - statusIcon = "emblem-default-symbolic" - statusLabel = programMetadata.name.." is enabled for this game." - statusColor = "success" - elseif game.status == "SteamTinkerLaunch" then - statusIcon = "emblem-important-symbolic" - statusLabel = "SteamTinkerLaunch is enabled for this game instead of "..programMetadata.name.."." - statusColor = "warning" - else - statusIcon = "dialog-error-symbolic" - statusLabel = programMetadata.name.." is disabled for this game." - statusColor = "" - end - - -- OS Label - local osLabel, osColor, osTooltip - if game.os_platform == "Linux" then - osLabel = "Native" - osColor = "success" - osTooltip = "This is a native Linux game." - elseif game.os_platform == "Windows" then - osLabel = "Proton" - osColor = "accent" - osTooltip = "This is a Windows game running through Proton." - end - - -- The row - local row = Adw.ActionRow { - title = game.name, - use_markup = false, -- Used to escape the ampersand - activatable = true, - subtitle_lines = 1 - } - - -- The game's image - local image = Gtk.Image { - file = game.images.icon, - icon_size = Gtk.IconSize.LARGE - } - row:add_prefix(image) - - local box = Gtk.Box { - orientation = Gtk.Orientation.HORIZONTAL, - spacing = 12, - Gtk.Label { - label = osLabel, - css_classes = { osColor, "dim-label" }, - has_tooltip = true, - tooltip_text = osTooltip - }, - Gtk.Image { - icon_name = statusIcon, - valign = Gtk.Align.CENTER, - halign = Gtk.Align.CENTER, - has_tooltip = true, - tooltip_text = statusLabel, - css_classes = { statusColor } - } - } - local button = Gtk.Button { - id = "button", - icon_name = "go-next-symbolic", - has_frame = false, - valign = Gtk.Align.CENTER, - halign = Gtk.Align.CENTER, - css_classes = { "circular", "flat", "image-button" }, - on_clicked = function() - -- We setup the gameSettings Overview UI for this game - require("modules.ui.gameSettingsOverview")(app, builder, game) - - -- We connect the UI elements to the game settings - local UIelements = require("modules.ui.UItoSettingsList") - local gameSettings = configManager.getGameConfig(game.id) - - -- We iterate through the UI elements - for widgetname, data in pairs(UIelements) do - local widget = builder:get_object(widgetname) - - -- Split the setting into keys - local keys = {} - for substring in data.setting:gmatch("[^.]+") do - keys[#keys + 1] = substring - end - -- Connect to the settings table - local pointer = gameSettings - for i = 1, #keys - 1 do - pointer = pointer[keys[i]] or {} - end - - -- We determine the right signal to use - local signal - if data.type == "Switch" then signal = "on_state_set" - elseif data.type == "SpinRow" then signal = "on_changed" - elseif data.type == "Toggle" then signal = "on_toggled" - elseif data.type == "ComboRow" then - signal = "on_notify" -- Not optimal, but it works. Isn't there a better way to do this ? - else - error("Unknown signal to use with type '"..data.type.."' for UI element '"..widgetname.."'") - end - - lgiHelper.removeSignal(widget, signal) - - -- We hide the option if it doesn't fit the platform - if data.os_platform and data.os_platform ~= game.os_platform then - widget.visible = false - goto skip - elseif widget.visible ~= true then - widget.visible = true - end - - -- Should they be active ? Is the related tool installed ? - if data.tool then - if not installedList[data.tool] then - installedList[data.tool] = systemUtils.isInstalled(data.tool) - end - if installedList[data.tool] == false then - widget:set_sensitive(false) - widget.has_tooltip = true - widget.tooltip_text = data.tool.." is not present on your system." - goto skip - else - widget:set_sensitive(true) - end - end - - -- We connect the UI element to the setting - if data.type == "Switch" or data.type == "Toggle" then - widget:set_active(pointer[keys[#keys]]) - lgiHelper.replaceSignal(widget, signal,function() configManager.modifyGameConfig(game.id, data.setting, widget:get_active()) end) - elseif data.type == "SpinRow" then - widget.value = pointer[keys[#keys]] - lgiHelper.replaceSignal(widget, signal,function() configManager.modifyGameConfig(game.id, data.setting, math.floor(widget:get_value())) end) - elseif data.type == "ComboRow" then - -- We grab the model used - local model = widget:get_model() - - -- We select the item that's already configured in the settings - for index, filter in ipairs(data.items) do - local currentFilter = configManager.grabInTableFromString(gameSettings, data.setting) - if currentFilter == filter then - widget:set_selected(index - 1) - break -- No need to continue - end - end - - -- We connect to the setting - lgiHelper.replaceSignal(widget, signal, function() - local selected = model:get_string(widget:get_selected()) - configManager.modifyGameConfig(game.id, data.setting, selected) - end) - else - error("Unknown type '"..data.type.."' for UI element '"..widgetname.."'") - end - - ::skip:: - end - - -- Windows only pages - local windowsPages = {"protonPage"} - for _, page in ipairs(windowsPages) do - page = builder:get_object(page) - if game.os_platform == "Windows" then - page.visible = true - else - page.visible = false - end - end - - -- We finally push the settings page to the user. - mainView:push(gameSettingsInterface) - end - } - box:append(button) - row:add_suffix(box) - row.activatable_widget = button - - gameList:add(row) - end - end - - --Topbar management - builder:get_object("mainPageTopbar"):set_top_bar_style(Adw.ToolbarStyle.RAISED_BORDER) - builder:get_object("gameSettingsTopbar"):set_top_bar_style(Adw.ToolbarStyle.RAISED_BORDER) - - -- Overview restoration when we exit out of the settings - gameSettingsInterface.on_hidden = function() - stack:set_visible_child_name("overviewPage") - end - - -- Credits - require("modules.ui.aboutWindow")(app, builder, win) -end diff --git a/modules/utilitiesList.lua b/modules/utilitiesList.lua deleted file mode 100644 index e31d503..0000000 --- a/modules/utilitiesList.lua +++ /dev/null @@ -1,15 +0,0 @@ -return function (gameConfig) - return - - -- Executables are ordered by priority (first only applies to the game, last applies to the whole sequence) - - { - {require("modules.tools.zink"),gameConfig.utilities.zink.enabled}, - {require("modules.tools.gamescope"),gameConfig.gamescope.enabled}, - {require("modules.tools.switcherooctl"),gameConfig.dgpu.enabled}, - {require("modules.tools.mangohud"),gameConfig.utilities.mangohud.enabled}, - {require("modules.tools.obs-gamecapture"),gameConfig.utilities.obs_gamecapture.enabled}, - {require("modules.tools.gamemode"),gameConfig.utilities.gamemode.enabled} - } - -end diff --git a/rokit.toml b/rokit.toml new file mode 100644 index 0000000..f98c9ba --- /dev/null +++ b/rokit.toml @@ -0,0 +1,10 @@ +# This file lists tools managed by Rokit, a toolchain manager for Roblox projects. +# For more information, see https://github.com/rojo-rbx/rokit + +# New tools can be added by running `rokit add ` in a terminal. + +[tools] +lune = "lune-org/lune@0.8.8" +darklua = "seaofvoices/darklua@0.13.1" +selene = "Kampfkarren/selene@0.27.1" +StyLua = "JohnnyMorganz/StyLua@0.20.0" diff --git a/selene.toml b/selene.toml new file mode 100644 index 0000000..49bce28 --- /dev/null +++ b/selene.toml @@ -0,0 +1,2 @@ +std = "luau" +exclude = ["luneTypes.d.luau"] diff --git a/src/Classes/SteamApp.luau b/src/Classes/SteamApp.luau new file mode 100644 index 0000000..2559cd0 --- /dev/null +++ b/src/Classes/SteamApp.luau @@ -0,0 +1,77 @@ +local fs = require("@lune/fs") +local process = require("@lune/process") + +local Metadata = require("@Metadata/") + +local SteamApp = {} + +SteamApp.Interface = {} +SteamApp.Prototype = {} + +function SteamApp.Interface.new( + appID: number, + name: string, + library: string, + location: string, + platform: ("windows" | "linux")?, + launchArguments: string? +): SteamApp + -- Is the app configured to use STweaks ? + local tweaksEnabled = false + if launchArguments and launchArguments:find(Metadata.executable .. " ") then + tweaksEnabled = true + end + + -- Does the app manifest specify the platform ? + -- Could be simplified + local osPlatform: ("Windows" | "Linux")? + if platform == "windows" then + osPlatform = "Windows" + elseif platform == "linux" then + osPlatform = "Linux" + end + + -- What type is the app ? + -- Current way of detecting if an app is a game or a tool... not the greatest. + local type: "Game" | "Other" | "Proton" = "Game" + if not fs.metadata(process.env.HOME .. "/.local/share/Steam/appcache/librarycache/" .. appID .. "_library_600x900.jpg").exists then + type = "Other" + if name:find("Proton") then + type = "Proton" + end + end + + -- We return the object + return { + appID = appID, + name = name, + library = library, + location = location, + size = nil, + protonDBData = nil, + type = type, + osPlatform = osPlatform, + protonConfig = nil, + tweaksEnabled = tweaksEnabled, + } +end + +export type SteamApp = { + appID: number, + name: string, + tweaksEnabled: boolean?, + library: string, + location: string, + type: "Game" | "Other" | "Proton", + osPlatform: ("Windows" | "Linux")?, + size: number?, -- Slow, done in UI (Currently unused) + protonDBData: { any }?, -- Slow, done in UI (Currently unused) + protonConfig: ProtonConfig?, -- (Currently unused) +} + +type ProtonConfig = { + compatdata_path: string, + version: string?, -- Another file needs to be checked for this information, currently unused +} + +return SteamApp.Interface diff --git a/src/Configuration/Games/Default.luau b/src/Configuration/Games/Default.luau new file mode 100644 index 0000000..f0b6cea --- /dev/null +++ b/src/Configuration/Games/Default.luau @@ -0,0 +1,99 @@ +local SteamApp = require("@Classes/SteamApp") + +export type GameConfiguration = { + appID : number, + version : number, + + settings : { + dgpu : boolean, + zink : boolean, + sdl_wayland : boolean + }, + + utilities : { + gamemode : boolean, + mangohud : { + enabled : boolean, + }, + gamescope : { + enabled : boolean, + general : { + resolution : { + enabled : boolean; + internal : { + width : number, + height : number, + }, + external : { + width : number, + height : number, + }, + }, + frame_limit : { + enabled : boolean, + normal : number, + unfocused : number, + }, + fullscreen : boolean, + borderless : boolean + }, + filtering : { + enabled : boolean, + filter : "Linear" | "Nearest" | "FSR" | "NIS" | "Pixel", + sharpness : number, + } + }, + } +} + +local Default = {} + +function Default.getDefaultGameConfiguration(appData : SteamApp.SteamApp) : GameConfiguration + return { + appID = appData.appID, + version = 1, + + settings = { + dgpu = true, + zink = false, + sdl_wayland = true + }, + + utilities = { + gamemode = true, + mangohud = { + enabled = true, + }, + gamescope = { + enabled = false, + general = { + resolution = { + enabled = false, + internal = { + width = 1280, + height = 720, + }, + external = { + width = 1920, + height = 1080, + }, + }, + frame_limit = { + enabled = false, + normal = 60, + unfocused = 5, + }, + fullscreen = false, + borderless = false + }, + filtering = { + enabled = false, + filter = "FSR", + sharpness = 5, + } + }, + } + } +end + +return Default diff --git a/src/Configuration/Games/Functions.luau b/src/Configuration/Games/Functions.luau new file mode 100644 index 0000000..049bb5b --- /dev/null +++ b/src/Configuration/Games/Functions.luau @@ -0,0 +1,41 @@ +local fs = require("@lune/fs") +local serde = require("@lune/serde") + +local SteamApp = require("@Classes/SteamApp") +local Metadata = require("@Metadata/") +local Logging = require("@Utilities/Logging") + +local Default = require("./Default") + +local function createGameConfiguration(appData : SteamApp.SteamApp) : Default.GameConfiguration + local newConfig = Default.getDefaultGameConfiguration(appData) + + Logging.write("info", "Creating default configuration for game " .. appData.name .. " (" .. appData.appID .. ")...") + local configString = serde.encode("toml", newConfig, true) + fs.writeFile(string.format( + "%s/%s.toml", + Metadata.folders.gamesConfig, + tostring(appData.appID) + ), configString) + + return newConfig +end + +local GameConfigurations = {} + +function GameConfigurations.getGameConfiguration(appData : SteamApp.SteamApp) : Default.GameConfiguration + local configPath = string.format( + "%s/%s.toml", + Metadata.folders.gamesConfig, + tostring(appData.appID) + ) + + if fs.metadata(configPath).exists then + local configString = fs.readFile(configPath) + return serde.decode("toml", configString) + else + return createGameConfiguration(appData) + end +end + +return GameConfigurations \ No newline at end of file diff --git a/src/Configuration/init.luau b/src/Configuration/init.luau new file mode 100644 index 0000000..61a264c --- /dev/null +++ b/src/Configuration/init.luau @@ -0,0 +1,5 @@ +return { + Games = { + Functions = require("./Games/Functions") + } +} \ No newline at end of file diff --git a/src/Launcher/Functions.luau b/src/Launcher/Functions.luau new file mode 100644 index 0000000..f9a13aa --- /dev/null +++ b/src/Launcher/Functions.luau @@ -0,0 +1,17 @@ +local SteamApp = require("../Classes/SteamApp") + +local GameConfiguration = require("@Configuration/").Games.Functions + +local Launcher = {} + +function Launcher.prepareLaunchCommand(appData: SteamApp.SteamApp): { string } + -- First and foremost, we recover the configuration of the game. + local ok, appConfig = pcall(GameConfiguration.getGameConfiguration, appData) + if not ok then + error("Failed to get game configuration for " .. appData.name .. " (" .. appData.appID .. ").") + end + + error("Not implemented.") +end + +return Launcher diff --git a/src/Metadata.luau b/src/Metadata.luau new file mode 100644 index 0000000..eec65ec --- /dev/null +++ b/src/Metadata.luau @@ -0,0 +1,14 @@ +local process = require("@lune/process") + +return { + name = "STweaks", + description = "A work-in-progress fast, simple and modern Libadwaita alternative to SteamTinkerLaunch.", + version = "indev", + executable = "stweaks", + folders = { + config = process.env.HOME .. "/.config/Stweaks", + gamesConfig = process.env.HOME .. "/.config/Stweaks/Games", + storage = process.env.HOME .. "/.local/share/Stweaks", + cache = process.env.HOME .. "/.cache/Stweaks", + }, +} diff --git a/src/Steam/Configuration.luau b/src/Steam/Configuration.luau new file mode 100644 index 0000000..210405a --- /dev/null +++ b/src/Steam/Configuration.luau @@ -0,0 +1,277 @@ +local fs = require("@lune/fs") +local process = require("@lune/process") + +local Logging = require("@Utilities/Logging") +local Async = require("@Utilities/Async") +local Filesystem = require("@Utilities/Filesystem") +local SteamUtilities = require("./Utilities") +local VDFParser = require("./VDFParser") +local SteamApp = require("@Classes/SteamApp") + +local function VDFHandler(filePath: string, errorMessage: string, logging: boolean?): any + if logging == nil then + logging = true + end + + local timeStart + if logging then + timeStart = os.clock() + Logging.write("data", "Parsing VDF file : " .. filePath) + end + + local result, parsedData = pcall(VDFParser.parseFile, filePath) + if not result then + error(errorMessage) + else + if logging then + Logging.write("speed", timeStart) + end + return parsedData + end +end + +local function getMostRecentUserID(userData: loginusers_vdf): number + for id, data in pairs(userData.users) do + if data.MostRecent == 1 then + return id + end + end + error("No most recent user found in loginusers.vdf. What ?!") +end + +-- --------------------------- Definitions ----------------------------- + +-- Note : Most of the entries are unused and simply for informative purposes. + +type loginusers_vdf = { + users: { + [number]: { + AccountName: string, + PersonaName: string, + RememberPassword: number, + WantsOfflineMode: number, + SkipOfflineModeWarning: number, + AllowAutoLogon: number, + MostRecent: number, + Timestamp: number, + }, + }, +} +type localconfig_vdf = { + UserLocalConfigStore: { + Software: { + Valve: { + Steam: { + apps: { + [number]: { + cloud: { + last_sync_state: ("synchronized" | "changesincloud")?, + quota_bytes: number?, + quota_files: number?, + used_bytes: number?, + used_files: number?, + }?, + LastPlayed: number?, + Playtime: number?, + Playtime2wks: number?, + LaunchOptions: string?, -- IMPORTANT ! + autocloud: { + lastlaunch: number, + lastexit: number, + }?, + ViewedSteamPlay: number?, + BadgeData: string?, + }, + }, + }, + }, + }, + }, +} +type libraryfolders_vdf = { + libraryfolders: { + [number]: { + path: string, + label: string?, + contentid: number, + totalsize: number, + update_clean_bytes_tally: number, + time_last_update_corruption: number, + apps: { + [number]: number, + }, + }?, + }, +} + +type appmanifest_vdf = { + AppState: { + appid: number, + Universe: number, + name: string, + StateFlags: number, + installdir: string, + LastUpdated: number, + LastPlayed: number, + SizeOnDisk: number, + StagingSize: number, + buildid: number, + LastOwner: number, + UpdateResult: number?, + BytesToDownload: number?, + BytesDownloaded: number?, + BytesToStage: number?, + BytesStaged: number?, + TargetBuildID: number?, + AutoUpdateBehavior: number, + AllowOtherDownloadsWhileRunning: number, + ScheduledAutoUpdate: number, + InstalledDepots: { + [number]: { + manifest: number, + size: number, + }, + }, + InstallScripts: { + [number]: number, + }?, + SharedDepots: { + [number]: number, + }?, + UserConfig: { + language: string?, + BetaKey: string?, + platform_override_dest: "linux"?, + platform_override_source: ("linux" | "windows")?, + }, + MountedConfig: { + language: string?, + BetaKey: string?, + platform_override_dest: "linux"?, + platform_override_source: ("linux" | "windows")?, -- IMPORTANT ! (Too bad it seems to never be present in Linux games, and sometimes not in Windows games... :/) + }, + }, +} + +type compat_vdf = { + platform_overrides: { + [number]: { + dest: "windows" | "linux", + src: "windows" | "linux", + }, + }, +} + +-- --------------------------- Script ----------------------------- + +local Configuration = {} + +function Configuration.getSteamConfiguration() + local timeStart = os.clock() + + --[[ + Chapter 1 : We recover the Steam user config so we can get the last active user. + And also the SteamID3 to access their settings folder. + ]] + local userConfiguration: loginusers_vdf = + VDFHandler(process.env.HOME .. "/.local/share/Steam/config/loginusers.vdf", "Failed to parse loginusers.vdf file.") + local activeUserSteamID3 = SteamUtilities.convertToSteamID3(getMostRecentUserID(userConfiguration)) + Logging.write("info", "Active user SteamID3 : " .. activeUserSteamID3) + Logging.separator() + + --[[ + Chapter 2 : We recover the user config to get which games have the tool enabled. + + Note : Error handling should be added for the scenario where the configuration can't be loaded. + ]] + local localconfig_vdf: localconfig_vdf = VDFHandler( + process.env.HOME .. "/.local/share/Steam/userdata/" .. activeUserSteamID3 .. "/config/localconfig.vdf", + "Failed to parse active user localconfig.vdf file with SteamID3: " .. activeUserSteamID3 + ) + local userGameConfigurations = localconfig_vdf.UserLocalConfigStore.Software.Valve.Steam.apps + Logging.write("info", "User game configurations loaded.") + Logging.separator() + + --[[ + Chapter 2.5 : We recover compat.vdf to know if a game has ever used its Windows version. + We can't use it directly to get the platform details, as games that have previously used Proton will still be set to "windows" here. + Logically, a game completely absent from this list is a native Linux game. + ]] + local compat_vdf: compat_vdf = VDFHandler( + process.env.HOME .. "/.local/share/Steam/userdata/" .. activeUserSteamID3 .. "/config/compat.vdf", + "Failed to parse compat.vdf file." + ) + Logging.write("info", "Compatibility data loaded.") + Logging.separator() + + --[[ + Chapter 3 : We recover the Steam library config to get the list of games. + We can't use just libraryfolders.vdf to get the game IDs, as Steam seems to not always update it immediately. + ]] + local libraries: libraryfolders_vdf = + VDFHandler(process.env.HOME .. "/.local/share/Steam/config/libraryfolders.vdf", "Failed to parse libraryfolders.vdf file.") + Logging.write("info", "Steam libraries loaded.") + Logging.separator() + + local steamApps = {} + local steamAppCount = 0 + + Async.asyncForEach(libraries.libraryfolders, function(_, library) + -- We check if the folder exists + if not fs.metadata(library.path).exists then + Logging.write("warn", "Steam library " .. library.path .. " doesn't exist. Skipping...") + else + Logging.write("data", "Checking library " .. library.path) + local appManifestFilenames = Filesystem.getFilenamePatternInDirectory(library.path .. "/steamapps", "appmanifest_") + + Async.asyncForEach(appManifestFilenames, function(_, appManifestFilename) + local appManifestPath: string = library.path .. "/steamapps/" .. appManifestFilename + local appManifest: appmanifest_vdf = VDFHandler(appManifestPath, "Failed to parse " .. appManifestPath .. ".", false) + + Logging.write("info", 'Found "' .. appManifest.AppState.name .. '" (' .. appManifest.AppState.appid .. ")") + + local location = library.path .. "/steamapps/common/" .. appManifest.AppState.installdir + + -- Does the game have launch options set ? + local configuration = userGameConfigurations[appManifest.AppState.appid] + local launchOptions + if configuration then + launchOptions = configuration.LaunchOptions + end + + -- Do we know the game's platform + local osPlatform: ("windows" | "linux")? = appManifest.AppState.MountedConfig.platform_override_source + if not osPlatform then + local compat = compat_vdf.platform_overrides[appManifest.AppState.appid] + -- The app being absent from the compat.vdf list means it can only be a native Linux game + if not compat then + osPlatform = "linux" + else + -- Gah ! We still don't have the platform... time to check manually. + Logging.write( + "warn", + 'Platform not found for "' .. appManifest.AppState.name .. '" (' .. appManifest.AppState.appid .. "). Using manual check..." + ) + if not Filesystem.directoryContainsLinuxData(location) then + osPlatform = "windows" + else + osPlatform = "linux" + end + end + end + + local steamApp = SteamApp.new(appManifest.AppState.appid, appManifest.AppState.name, library.path, location, osPlatform, launchOptions) + steamApps[steamApp.appID] = steamApp + steamAppCount += 1 + end) + end + end) + + Logging.write("info", "Found " .. steamAppCount .. " Steam apps.") + Logging.write("speed", timeStart) + Logging.separator() + + return steamApps +end + +return Configuration diff --git a/src/Steam/Utilities.luau b/src/Steam/Utilities.luau new file mode 100644 index 0000000..1b40e30 --- /dev/null +++ b/src/Steam/Utilities.luau @@ -0,0 +1,31 @@ +local process = require("@lune/process") + +local Utilities = {} + +function Utilities.isSteamLaunch(): number? + local appIDArgumentIndex + for index, argument in ipairs(process.args) do + -- Locate the SteamLaunch argument, our AppID will be the next argument. + if argument == "SteamLaunch" then + appIDArgumentIndex = index + 1 + break + end + end + + if appIDArgumentIndex then + return tonumber(process.args[appIDArgumentIndex]:match("AppId=(%d+)")) + else + return nil + end +end + +function Utilities.convertToSteamID3(steamID64: number): number + local offset_id = steamID64 - 76561197960265728 + local account_type = offset_id % 2 + local account_id = math.floor((offset_id - account_type) / 2) + account_type + + -- The -1 is meant to be a fix, but could also cause bugs by itself. Keep an eye out. + return (account_id * 2) - account_type - 1 +end + +return Utilities diff --git a/src/Steam/VDFParser.luau b/src/Steam/VDFParser.luau new file mode 100644 index 0000000..16a9734 --- /dev/null +++ b/src/Steam/VDFParser.luau @@ -0,0 +1,148 @@ +local fs = require("@lune/fs") + +type VDFTable = { [string]: VDFValue } +type VDFValue = string | VDFTable + +local function parseVDFData(input: string): VDFTable + -- Trim function to remove whitespace + local function trim(s: string): string? + return s:match("^%s*(.-)%s*$") + end + + -- Handle escape sequences + local function parseString(string: string, pos: number): (string, number) + local result = "" + local len = #string + while pos <= len do + local char = string:sub(pos, pos) + if char == "\\" then + pos = pos + 1 + if pos <= len then + local nextChar = string:sub(pos, pos) + if nextChar == '"' then + result = result .. '"' + elseif nextChar == "\\" then + result = result .. "\\" + elseif nextChar == "n" then + result = result .. "\n" + elseif nextChar == "t" then + result = result .. "\t" + else + result = result .. "\\" .. nextChar + end + end + elseif char == '"' then + break + else + result = result .. char + end + pos = pos + 1 + end + return result, pos + end + + -- Parsing logic + local pos = 1 + local len = #input + local stack: { VDFValue } = {} + local currentTable: VDFTable = {} + local currentKey: string? = nil + + while pos <= len do + local char = input:sub(pos, pos) + + if char == '"' then + -- Extract the key or value with escape sequence handling + pos = pos + 1 + local parsedString + parsedString, pos = parseString(input, pos) + parsedString = trim(parsedString) or parsedString + pos = pos + 1 -- Skip the closing quote + + if currentKey == nil then + -- It's a key + currentKey = parsedString + else + -- It's a value + currentTable[currentKey] = parsedString + currentKey = nil + end + elseif char == "{" then + -- Start a new table + local newTable: VDFTable = {} + if currentKey ~= nil then + currentTable[currentKey] = newTable + currentKey = nil + else + error("Malformed VDF: encountered '{' without a preceding key") + end + table.insert(stack, currentTable) + currentTable = newTable + elseif char == "}" then + -- End the current table + if #stack == 0 then + error("Malformed VDF: encountered '}' without a matching '{'") + end + + local data = table.remove(stack) + if not data or typeof(data) == "string" then + error("Malformed VDF") + end + currentTable = data + elseif not (char == "\n" or char == "\t" or char == " ") then + error("Malformed VDF: unexpected character '" .. char .. "'") + end + + pos = pos + 1 + end + + if #stack > 0 then + error("Malformed VDF: unclosed '{'") + end + + return currentTable +end + +local function sanitizeVDFTable(input: VDFTable) + local function convertValue(value: any): any + if type(value) == "string" then + -- Check for number values (integers or floats) + local numberValue = tonumber(value) + if numberValue ~= nil then + return numberValue + end + -- Check for empty strings + if value == "" then + return nil + end + -- If it's neither, return the original string + return value + elseif type(value) == "table" then + -- Recursively sanitize nested tables + return sanitizeVDFTable(value) + else + -- Return the value as-is for unsupported types (shouldn't happen in a VDF) + return value + end + end + + local result = {} + for key, value in pairs(input) do + result[convertValue(key)] = convertValue(value) + end + + return result +end + + +local VDFParser = {} + +function VDFParser.parseFile(path: string) + return VDFParser.parseString(fs.readFile(path)) +end + +function VDFParser.parseString(input: string) + return sanitizeVDFTable(parseVDFData(input)) +end + +return VDFParser diff --git a/src/Utilities/Async.luau b/src/Utilities/Async.luau new file mode 100644 index 0000000..2ea81ff --- /dev/null +++ b/src/Utilities/Async.luau @@ -0,0 +1,22 @@ +local task = require("@lune/task") + +local Async = {} + +function Async.asyncForEach(tbl: { any }, func: (any, any) -> ()) + local total = 0 + local completed = 0 + + for key, item in pairs(tbl) do + total += 1 + task.spawn(function() + func(key, item) + completed += 1 + end) + end + + while completed < total do + task.wait() + end +end + +return Async diff --git a/src/Utilities/Filesystem.luau b/src/Utilities/Filesystem.luau new file mode 100644 index 0000000..f5d5e34 --- /dev/null +++ b/src/Utilities/Filesystem.luau @@ -0,0 +1,78 @@ +local fs = require("@lune/fs") +local process = require("@lune/process") + +local Logging = require("./Logging") + +local Filesystem = {} + +function Filesystem.createOrUseDirectory(path: string): string + if not fs.isDir(path) then + Logging.write("debug", "Directory " .. path .. " not found. Creating it...") + fs.writeDir(path) + end + return path +end + +function Filesystem.getSize(path: string): number + path = path:gsub("'", "'\\''") + + local handle = process.spawn("du", { "-sb", path }) + + if not handle.ok then + error("Failed to get size of path " .. path .. ": " .. handle.stderr) + end + + local result = tonumber(handle.stdout:match("^(%d+)")) -- Grab only the number + if not result then + error("Failed to parse size for output (" .. handle.stdout .. ")") + else + return result + end +end + +function Filesystem.sizeToUnit(size: number): string + local units = { "B", "KB", "MB", "GB", "TB" } + local unit = units[1] -- Default to "B" (bytes) + + for i = 2, #units do + if size >= 1024 then + size = size / 1024 + unit = units[i] + else + break + end + end + + return string.format("%.2f", size) .. " " .. unit +end + +function Filesystem.getFilenamePatternInDirectory(directory: string, pattern: string): { string } + local result = {} + + for _, file in fs.readDir(directory) do + if file:match(pattern) then + table.insert(result, file) + end + end + + return result +end + +function Filesystem.directoryContainsLinuxData(path: string): boolean + local files = fs.readDir(path) + for _, file in ipairs(files) do + if not fs.isDir(path) and (file:find(".sh") or not file:find(".")) then + file = path .. "/" .. file + + Logging.write("data", "Checking file " .. file) + local contents = fs.readFile(file) + + if contents:find("Linux") or contents:find("shell") then + return true + end + end + end + return false +end + +return Filesystem diff --git a/src/Utilities/Logging.luau b/src/Utilities/Logging.luau new file mode 100644 index 0000000..4cf9aab --- /dev/null +++ b/src/Utilities/Logging.luau @@ -0,0 +1,50 @@ +local stdio = require("@lune/stdio") + +type LogColor = "black" | "blue" | "cyan" | "green" | "purple" | "red" | "reset" | "white" | "yellow" +type LogType = "info" | "warn" | "error" | "debug" | "data" | "speed" | "special" | "success" + +local function color(colorName: LogColor, text: string): string + return stdio.color(colorName) .. text .. stdio.color("reset") +end + +local Logging = {} + +function Logging.write(type: LogType, message: string | number) + local time = os.date("%H:%M:%S") + + local logFunctions = { + info = function(text) + print(time .. " - [" .. color("blue", "Info") .. "] : " .. text) + end, + warn = function(text) + print(time .. " - [" .. color("yellow", "Warn") .. "] : " .. color("yellow", text)) + end, + error = function(text) + print(time .. " - " .. color("red", "[Error] : " .. text)) + end, + debug = function(text) + print(time .. " - [" .. color("white", "Debug") .. "] : " .. color("white", text)) + end, + data = function(text) + print(time .. " - [" .. color("purple", "Data") .. "] : " .. text) + end, + speed = function(startTime) + print(time .. " - [" .. color("white", "Speed") .. "] : " .. "Done in " .. os.clock() - startTime .. " seconds.") + end, + success = function(text) + print(time .. " - [" .. color("green", "Success") .. "] : " .. text) + end, + -- Might be used for an easter egg feature in the future... + special = function(text) + print(time .. " - [" .. color("red", "!!!!") .. "] : " .. text) + end, + } + + logFunctions[type](message) +end + +function Logging.separator() + print("---------------------------------------------------------------------------------------------") +end + +return Logging diff --git a/src/Utilities/System.luau b/src/Utilities/System.luau new file mode 100644 index 0000000..47c6946 --- /dev/null +++ b/src/Utilities/System.luau @@ -0,0 +1,32 @@ +local process = require("@lune/process") + +local Logging = require("./Logging") + +local System = {} + +function System.sendNotification(title: string, message: string, urgency: ("normal" | "critical")?, transient: boolean?, time: number?) + local arguments = { + title, + message, + } + if transient then + table.insert(arguments, "-e") + end + + if urgency then + table.insert(arguments, "-u") + table.insert(arguments, urgency) + end + if time then + table.insert(arguments, "-t") + table.insert(arguments, tostring(time)) + end + + local execution = process.spawn("notify-send", arguments) + + if not execution.ok then + Logging.write("error", "Unable to send notification : " .. execution.stderr) + end +end + +return System diff --git a/src/init.luau b/src/init.luau new file mode 100644 index 0000000..fc0bd39 --- /dev/null +++ b/src/init.luau @@ -0,0 +1,71 @@ +local fs = require("@lune/fs") +local process = require("@lune/process") + +local Filesystem = require("@Utilities/Filesystem") +local Launcher = require("@Launcher/Functions") +local Logging = require("@Utilities/Logging") +local Metadata = require("./Metadata") +local Steam = { + Configuration = require("@Steam/Configuration"), + Utilities = require("@Steam/Utilities"), +} +local System = require("@Utilities/System") + +local Application = {} + +function Application.start() + -- Prepare folders if necessary + for _, folder in ipairs({ + Metadata.folders.config, + Metadata.folders.gamesConfig, + Metadata.folders.cache, + }) do + Filesystem.createOrUseDirectory(folder) + end + + -- Development version detection + if Metadata.version:find("dev") then + Logging.write("warn", "This is a development version of " .. Metadata.name .. " !") + -- Put the arguments inside the cache folder for testing purposes + fs.writeFile(Metadata.folders.cache .. "/arguments.txt", table.concat(process.args, "\n")) + end + + -- Check if we're launched through Steam, start the game with its settings if so + local launchedAppID = Steam.Utilities.isSteamLaunch() + if launchedAppID then + Logging.write("info", "Launched through Steam (AppID " .. launchedAppID .. ").") + Logging.separator() + + -- Load the Steam config + local ok, steamConfig = pcall(Steam.Configuration.getSteamConfiguration) + if not ok then + Logging.write("error", "Failed to load Steam games.") + System.sendNotification(Metadata.name, "Failed to load Steam games.", "critical", true) + process.exit(1) + end + + -- Load the game's data + local gameData = steamConfig[launchedAppID] + if not gameData then + Logging.write("error", "No data found for game with AppID " .. launchedAppID .. ".") + System.sendNotification(Metadata.name, "No data found for game with AppID " .. launchedAppID .. ".", "critical", true) + process.exit(1) + end + + -- Tell the user we found the game + Logging.write("success", 'Detected "' .. gameData.name .. '" (' .. gameData.appID .. ") !") + System.sendNotification(Metadata.name, "Detected " .. gameData.name .. ".", "normal", true) + + -- Go into the Game Launcher module. + local command: { string } = Launcher.Functions.prepareLaunchCommand(gameData) + print(command) + else + Logging.write("info", "Launched without Steam.") + + -- :( - Lune FFI is not implemented yet, so no interface. + System.sendNotification(Metadata.name, "No graphical interface implemented yet.", "critical", true) + error("No graphical interface implemented yet.") + end +end + +return Application diff --git a/sst b/sst deleted file mode 100755 index 53f5969..0000000 --- a/sst +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh - -# Check if installed locally (main.lua is in the same directory as this script) or in the system (main.lua is in /usr/share/simplesteammod) -if [ -f "$(dirname $0)/main.lua" ]; then - # Installed locally - SCRIPT_PATH="$(dirname $0)" -elif [ -f "/usr/share/SimpleSteamTinker/main.lua" ]; then - # Installed in the system - SCRIPT_PATH="/usr/share/SimpleSteamTinker" -else - echo "main.lua not found. Please install SimpleSteamTinker and try again." - exit 1 -fi - -# Set LUA_PATH to avoid missing require issues, using the detected directory -export LUA_PATH="$SCRIPT_PATH/?.lua;$SCRIPT_PATH/?/init.lua;;" - -# Provide script directory -export SST_SCRIPT_PATH="$SCRIPT_PATH/" - -# Check if Lua is installed and run main.lua, accounting for the script path -if [ -x "$(command -v lua)" ]; then - lua $SCRIPT_PATH/main.lua "$@" -else - echo "No Lua installation found. Please install Lua and try again." - exit 1 -fi diff --git a/stylua.toml b/stylua.toml new file mode 100644 index 0000000..9dcacf6 --- /dev/null +++ b/stylua.toml @@ -0,0 +1,9 @@ +column_width = 160 +line_endings = "Unix" +indent_type = "Tabs" +indent_width = 4 +quote_style = "AutoPreferDouble" +call_parentheses = "Always" + +[sort_requires] +enabled = true diff --git a/ui/definitions/cleanerPage.blp b/ui/definitions/cleanerPage.blp deleted file mode 100644 index 79fa5f4..0000000 --- a/ui/definitions/cleanerPage.blp +++ /dev/null @@ -1,10 +0,0 @@ -using Gtk 4.0; - -StackPage cleaner { //[MODIFIED IN LUA]: This page is only shown if the game is using Proton. - title: _("Cleaner"); - child: - Label { - label: "In the future, this page will allow you to do some cleaning up."; - wrap: true; - }; -} diff --git a/ui/definitions/commandPage.blp b/ui/definitions/commandPage.blp deleted file mode 100644 index e9b1707..0000000 --- a/ui/definitions/commandPage.blp +++ /dev/null @@ -1,10 +0,0 @@ -using Gtk 4.0; - -StackPage commandPage { - title: _("Launch options"); - child: - Label { - label: "In the future, this will be a page for configuring launch options."; - wrap: true; - }; -} diff --git a/ui/definitions/gamescopePage.blp b/ui/definitions/gamescopePage.blp deleted file mode 100644 index b5a11cb..0000000 --- a/ui/definitions/gamescopePage.blp +++ /dev/null @@ -1,215 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -StackPage gamescopePage { //[MODIFIED IN LUA]: This page is only shown if the game is using Proton. - title: "Gamescope"; - child: - Gtk.ScrolledWindow { - Adw.Clamp { - Box { - margin-bottom: 16; - margin-end: 12; - margin-start: 12; - margin-top: 16; - orientation: vertical; - spacing: 24; - vexpand: true; - vexpand-set: true; - - Adw.PreferencesGroup { - title: "Gamescope"; - description: _("Gamescope is a tool from Valve that allows for games to run in an isolated XWayland instance."); - Adw.ActionRow { - title : _("Enable Gamescope"); - subtitle: _("May cause compatibility issues with some utilities and Wayland-native games."); - activatable-widget: gamescope_Switch; - - sensitive: bind gamescope_Switch.sensitive bidirectional; - has-tooltip: bind gamescope_Switch.has-tooltip bidirectional; - tooltip-text: bind gamescope_Switch.tooltip-text bidirectional; - - Gtk.Switch gamescope_Switch { - active: false; - sensitive: false; - valign: center; - } - } - } - - Adw.PreferencesGroup { - title: _("Resolution"); - description: _("Resolution settings for both the game and the Gamescope window."); - - sensitive: bind gamescope_Switch.active; - - Adw.ExpanderRow { - title: _("Game resolution"); - subtitle: _("The resolution the game will run at."); - - sensitive: bind gamescope_Resolution_Switch.active; - - Adw.SpinRow gamescope_Resolution_Internal_Width_SpinRow { - title: _("Width"); - subtitle: _("The width of the game's internal resolution."); - numeric: true; - adjustment : - Gtk.Adjustment { - lower: 0; - upper: 7680; - step-increment: 10; - }; - } - Adw.SpinRow gamescope_Resolution_Internal_Height_SpinRow { - title: _("Height"); - subtitle: _("The height of the game's internal resolution."); - numeric: true; - adjustment : - Gtk.Adjustment { - lower: 0; - upper: 7680; - step-increment: 10; - }; - } - } - - Adw.ExpanderRow { - title: _("Window resolution"); - subtitle: _("The resolution of the Gamescope window."); - - sensitive: bind gamescope_Resolution_Switch.active; - - Adw.SpinRow gamescope_Resolution_External_Width_SpinRow { - title: _("Width"); - subtitle: _("The width of the Gamescope window."); - numeric: true; - adjustment : - Gtk.Adjustment { - lower: 0; - upper: 7680; - step-increment: 10; - }; - } - Adw.SpinRow gamescope_Resolution_External_Height_SpinRow { - title: _("Height"); - subtitle: _("The height of the Gamescope window."); - numeric: true; - adjustment : - Gtk.Adjustment { - lower: 0; - upper: 7680; - step-increment: 10; - }; - } - } - - [header-suffix] - Switch gamescope_Resolution_Switch { - active: false; - sensitive: bind gamescope_Switch.active; - valign: center; - } - } - - - Adw.PreferencesGroup { - title: _("Framerate"); - description: _("The maximum framerate the game will run at."); - - sensitive: bind gamescope_Switch.active; - - Adw.SpinRow gamescope_Framerate_Normal_SpinRow { - title: _("Normal"); - subtitle: _("The framerate when the game is focused."); - sensitive: bind gamescope_Framerate_Switch.active; - numeric: true; - adjustment : - Gtk.Adjustment { - lower: 0; - upper: 244; - step-increment: 1; - }; - } - Adw.SpinRow gamescope_Framerate_Unfocused_SpinRow { - title: _("Unfocused"); - subtitle: _("The framerate when the game is not focused."); - sensitive: bind gamescope_Framerate_Switch.active; - numeric: true; - adjustment : - Gtk.Adjustment { - lower: 0; - upper: 244; - step-increment: 1; - }; - } - - [header-suffix] - Switch gamescope_Framerate_Switch { - active: false; - sensitive: bind gamescope_Switch.active; - valign: center; - } - } - - Adw.PreferencesGroup { - title: _("Filtering"); - description: _("Makes the game look smoother at lower resolutions."); - - sensitive: bind gamescope_Switch.active; - - Adw.ComboRow gamescope_Filtering_Filter_ComboRow { - title: _("Filter"); - subtitle: _("The filter to use. (NIS = Nvidia Image Scaling, FSR = FidelityFX Super Resolution)"); - sensitive: bind gamescope_Filtering_Switch.active; - model: Gtk.StringList { - strings ["Linear", "Nearest", "FSR", "NIS", "Pixel"] - }; - } - Adw.SpinRow gamescope_Filtering_Sharpness_SpinRow { - title: _("Sharpness"); - subtitle: _("Sets the sharpness of the filter. (0 = maximum sharpness, 20 = minimum)"); - sensitive: bind gamescope_Filtering_Switch.active; - numeric: true; - adjustment : - Gtk.Adjustment { - lower: 0; - upper: 20; - step-increment: 1; - }; - } - - [header-suffix] - Switch gamescope_Filtering_Switch { - active: false; - sensitive: bind gamescope_Switch.active; - valign: center; - } - } - - Adw.PreferencesGroup { - title: _("Extras"); - description: _("Additional settings for GameScope."); - - sensitive: bind gamescope_Switch.active; - - Adw.ActionRow { - title: _("Window type"); - subtitle: _("The type of window to use for the Gamescope window."); - - Box { - styles ["linked"] - - ToggleButton gamescope_Borderless_Toggle { - label: _("Borderless"); - valign: center; - } - ToggleButton gamescope_Fullscreen_Toggle { - label: _("Fullscreen"); - valign: center; - } - } - } - } - } - } - }; -} diff --git a/ui/definitions/mainWindow.blp b/ui/definitions/mainWindow.blp deleted file mode 100644 index b7b88ae..0000000 --- a/ui/definitions/mainWindow.blp +++ /dev/null @@ -1,147 +0,0 @@ -/* - Why not use LGI directly instead ? - - Because LGI doesn't support well in GTK4 many things that can be easily defined here. - Still, both are by FAR a much better solution than using XML directly. - Plus, I feel like using a universal standard would be a good idea to make it easier for people to contribute. -*/ - -using Gtk 4.0; -using Adw 1; - -Adw.ApplicationWindow mainWindow { - default-height: 500; - default-width: 700; - height-request: 400; - show-menubar: true; - width-request: 360; - - // AdwBreakpoint used to dynamically make the sidebar appear or disappear depending on the window's width. - Adw.Breakpoint { - condition ("max-width: 600sp") - setters { - sidebarToggleButton.visible: true; - sidebarToggleButton.active: false; - gameSettings_SplitView.collapsed: true; - } - } - - [content] - Adw.ToastOverlay toastSystem { //[INTERACTS WITH LUA] - Adw.NavigationView mainView { - /* - MAIN PAGE - This is the page the program starts on when it's launched. - */ - Adw.NavigationPage mainPage { - title: _("SimpleSteamTinker"); - Adw.ToolbarView mainPageTopbar { - [top] - Adw.HeaderBar { - styles ["titlebar"] - - [end] - Button aboutLauncher { - icon-name: "help-about-symbolic"; - styles ["image-button"] - } - } - - // This is where the games are listed. - ScrolledWindow { - hscrollbar-policy: never; - - Adw.Clamp { - margin-bottom: 16; - margin-end: 12; - margin-start: 12; - margin-top: 16; - - Adw.PreferencesGroup gameList { //[MODIFIED IN LUA] - description: _("Insert \"sst %command%\" as a game\'s launch options to enable it here.\nCommand may change depending on installation method."); - title: _("Detected Steam games"); - } - } - } - } - } - - // This is the page that shows up when you click on a game. - Adw.NavigationPage gameSettings { //[INTERACTS THROUGH LUA] - title: _("Game settings"); - Adw.ToolbarView gameSettingsTopbar { - [top] - Adw.HeaderBar { - styles ["titlebar"] - - // Button that allows you to toggle the sidebar in the game settings page. - ToggleButton sidebarToggleButton { //[MODIFIED IN LUA][INTERACTS THROUGH LUA] - active: true; - visible: false; - has-frame: false; - icon-name: "view-sidebar-start-symbolic"; - } - } - - Adw.OverlaySplitView gameSettings_SplitView { - /* - SIDEBAR - */ - min-sidebar-width: 200; - show-sidebar: bind sidebarToggleButton.active bidirectional; - sidebar: - Adw.NavigationPage gameSettingsSidebar { - title: _("Sidebar"); - child: - Adw.ToolbarView { - Box { - orientation: vertical; - vexpand: true; - vexpand-set: true; - - // This is the header of the sidebar of the game settings page. - Picture Sidebar_Banner { - halign: fill; - valign: fill; - } - - // This is the list of pages in the sidebar of the game settings page. - StackSidebar { - vexpand: true; - vexpand-set: true; - stack: gameSettingsStack; - } - } - }; - }; - - /* - CONTENT - */ - content: - Adw.NavigationPage { - title: _("Game settings"); - Stack gameSettingsStack { - transition-type: crossfade; - - //[overviewPage.blp] - - //[settingsPage.blp] - - //[utilitiesPage.blp] - - //[commandPage.blp] - - //[protonPage.blp] - - //[gamescopePage.blp] - - //[cleanerPage.blp] - } - }; - } - } - } - } - } -} \ No newline at end of file diff --git a/ui/definitions/overviewPage.blp b/ui/definitions/overviewPage.blp deleted file mode 100644 index be9264d..0000000 --- a/ui/definitions/overviewPage.blp +++ /dev/null @@ -1,279 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -StackPage overviewPage { //[INTERACTS IN LUA] - name: "overviewPage"; - title: _("Overview"); - - child: - ScrolledWindow { - hscrollbar-policy: never; - styles ["background"] - - Box { - orientation: vertical; - - Picture Overview_Picture { //[MODIFIED IN LUA] - halign: fill; - valign: fill; - height-request: 256; - } - - Adw.Clamp { - Box { - margin-bottom: 16; - margin-end: 12; - margin-start: 12; - margin-top: 16; - orientation: vertical; - spacing: 24; - vexpand: true; - vexpand-set: true; - - Box { - orientation: vertical; - spacing: 8; - halign: center; - - // Game title - Label gameTitle { //[MODIFIED IN LUA] - halign: center; - valign: end; - wrap: true; - - styles [ - "title-1" - ] - } - - // ProtonDB Rating - Box protonDBRating_Box { - orientation: horizontal; - spacing: 8; - halign: center; - Label { - label: _("ProtonDB Rating :"); - halign: center; - valign: end; - - styles [ - "dim-label" - ] - } - Label protonDBRating_Label { //[MODIFIED IN LUA] - label: _("Unavailable"); - halign: center; - valign: end; - - styles [ - "dim-label" - ] - } - } - } - - // Launch button - Button gameLaunchButton { //[INTERACTS THROUGH LUA] - halign: center; - valign: end; - - Adw.ButtonContent { - icon-name: "media-playback-start-symbolic"; - label: _("Launch"); - } - - styles [ - "suggested-action", - "image-button", - "pill" - ] - } - - // SimpleSteamTinker Status - Adw.PreferencesGroup { - Adw.ActionRow { - title: _("SimpleSteamTinker Status"); - subtitle: _("Based on the game's launch options."); - subtitle-lines: 1; - - [suffix] - Label gameStatus_Label { //[MODIFIED IN LUA] - label: _("Placeholder"); - halign: end; - wrap: true; - styles [ - "dim-label" - ] - } - } - } - - // Game details - Adw.PreferencesGroup { - title: _("Details"); - description: _("Information about the game."); - - Adw.ActionRow { - title: _("Steam AppID"); - subtitle: _("The unique ID of the game that identifies it in Steam."); - activatable-widget: gameID_copyButton; - [suffix] - Box { - spacing: 8; - Label gameID_Label { //[MODIFIED IN LUA] - halign: end; - wrap: true; - selectable: true; - styles [ - "dim-label" - ] - } - Button gameID_copyButton { //[MODIFIED IN LUA][INTERACTS WITH LUA] - halign: end; - icon-name: "edit-copy-symbolic"; - styles [ - "image-button", - "flat", - "circular" - ] - } - } - } - Adw.ActionRow { - title: _("Platform"); - subtitle: _("The intended platform for the installed game version."); - - [suffix] - Label gamePlatform_Label { //[MODIFIED IN LUA] - halign: end; - wrap: true; - styles [ - "dim-label" - ] - } - } - Adw.ActionRow { - title: _("Size"); - subtitle: _("The amount of space the game takes up on the system."); - - [suffix] - Label gameSize_Label { //[MODIFIED IN LUA] - halign: end; - lines: 1; - styles [ - "dim-label" - ] - } - } - Adw.ActionRow gameLocation_ActionRow { //[MODIFIED IN LUA] - title: _("Install location"); - tooltip-text: _("The location of the game's files on the system."); - has-tooltip: true; - activatable-widget: gameLocationButton; - subtitle-lines: 2; - [suffix] - Button gameLocationButton { //[MODIFIED IN LUA] - Adw.ButtonContent { - icon-name: "folder-open-symbolic"; - } - styles [ - "image-button", - "flat", - "circular" - ] - } - } - Adw.ActionRow gameCompatdata_ActionRow { //[MODIFIED IN LUA] - title: _("Virtual filesystem location"); - tooltip-text: _("The location of the game's virtual filesystem assigned by Steam. (Only applies to Windows games)"); - has-tooltip: true; - activatable-widget: gameCompatdataButton; - subtitle-lines: 2; - [suffix] - Button gameCompatdataButton { //[MODIFIED IN LUA] - Adw.ButtonContent { - icon-name: "folder-open-symbolic"; - } - styles [ - "image-button", - "flat", - "circular" - ] - } - } - } - - // Links - Adw.PreferencesGroup { - title: _("Online help"); - description: _("Links to useful pages related to the game."); - - Adw.ActionRow { - title: _("ProtonDB"); - activatable-widget: protonDBPage_Button; - [suffix] - Button protonDBPage_Button { //[MODIFIED IN LUA] - Adw.ButtonContent { - icon-name: "help-browser-symbolic"; - } - styles [ - "image-button", - "flat", - "circular" - ] - } - } - - Adw.ActionRow { - title: _("PCGamingWiki"); - activatable-widget: PCGamingWikiPage_Button; - [suffix] - Button PCGamingWikiPage_Button { //[MODIFIED IN LUA] - Adw.ButtonContent { - icon-name: "help-browser-symbolic"; - } - styles [ - "image-button", - "flat", - "circular" - ] - } - } - - Adw.ActionRow { - title: _("SteamDB"); - activatable-widget: steamDBPage_Button; - [suffix] - Button steamDBPage_Button { //[MODIFIED IN LUA] - Adw.ButtonContent { - icon-name: "help-browser-symbolic"; - } - styles [ - "image-button", - "flat", - "circular" - ] - } - } - - Adw.ActionRow { - title: _("Steambase"); - activatable-widget: SteambasePage_Button; - [suffix] - Button SteambasePage_Button { //[MODIFIED IN LUA] - Adw.ButtonContent { - icon-name: "help-browser-symbolic"; - } - styles [ - "image-button", - "flat", - "circular" - ] - } - } - } - } - } - } - }; -} diff --git a/ui/definitions/protonPage.blp b/ui/definitions/protonPage.blp deleted file mode 100644 index 551d6c9..0000000 --- a/ui/definitions/protonPage.blp +++ /dev/null @@ -1,225 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -StackPage protonPage { //[MODIFIED IN LUA]: This page is only shown if the game is using Proton. - title: "Proton / Wine"; - - child: - Gtk.ScrolledWindow { - Adw.Clamp { - Box { - margin-bottom: 16; - margin-end: 12; - margin-start: 12; - margin-top: 16; - orientation: vertical; - spacing: 24; - vexpand: true; - vexpand-set: true; - - Adw.PreferencesGroup { - title: "Direct3D"; - description: _("Configure Direct3D settings."); - - Adw.ExpanderRow { - title: _("Versions"); - subtitle: _("Control which versions of Direct3D can be used by this game. If you are unsure, leave all versions enabled."); - - Adw.ActionRow { - title: _("Direct3D 9"); - subtitle: _("Only applies to GE Proton."); - activatable-widget: Direct3D9_Switch; - sensitive: bind Direct3D9_Switch.sensitive bidirectional; - has-tooltip: bind Direct3D9_Switch.has-tooltip bidirectional; - tooltip-text: bind Direct3D9_Switch.tooltip-text bidirectional; - - Gtk.Switch Direct3D9_Switch { - active: true; - valign: center; - } - } - Adw.ActionRow { - title: _("Direct3D 10"); - activatable-widget: Direct3D10_Switch; - sensitive: bind Direct3D10_Switch.sensitive bidirectional; - has-tooltip: bind Direct3D10_Switch.has-tooltip bidirectional; - tooltip-text: bind Direct3D10_Switch.tooltip-text bidirectional; - - Gtk.Switch Direct3D10_Switch { - active: true; - valign: center; - } - } - Adw.ActionRow { - title: _("Direct3D 11"); - activatable-widget: Direct3D11_Switch; - sensitive: bind Direct3D11_Switch.sensitive bidirectional; - has-tooltip: bind Direct3D11_Switch.has-tooltip bidirectional; - tooltip-text: bind Direct3D11_Switch.tooltip-text bidirectional; - - Gtk.Switch Direct3D11_Switch { - active: true; - valign: center; - } - } - Adw.ActionRow { - title: _("Direct3D 12"); - subtitle: _("Only applies to GE Proton."); - activatable-widget: Direct3D12_Switch; - sensitive: bind Direct3D12_Switch.sensitive bidirectional; - has-tooltip: bind Direct3D12_Switch.has-tooltip bidirectional; - tooltip-text: bind Direct3D12_Switch.tooltip-text bidirectional; - - Gtk.Switch Direct3D12_Switch { - active: true; - valign: center; - } - } - } - - Adw.ActionRow { - title: _("Use WineD3D instead of DXVK"); - subtitle: _("Use OpenGL-based WineD3D instead of Vulkan-based DXVK for D3D11 and D3D10."); - activatable-widget: WineD3D_Switch; - sensitive: bind WineD3D_Switch.sensitive bidirectional; - has-tooltip: bind WineD3D_Switch.has-tooltip bidirectional; - tooltip-text: bind WineD3D_Switch.tooltip-text bidirectional; - - Gtk.Switch WineD3D_Switch { - active: false; - valign: center; - } - } - } - - Adw.PreferencesGroup { - title: "AMD FidelityFX Super Resolution 1.0"; - description: _("Note : this feature requires GE Proton, and is not restricted to AMD GPUs."); - - [header-suffix] - Switch Wine_FSR_Switch { - active: false; - sensitive: true; - valign: center; - } - - Adw.SpinRow Wine_FSR_Sharpness_SpinRow { - title: _("Sharpness"); - subtitle: _("The strength of the filter. (0 = strongest, 5 = weakest)"); - sensitive: bind Wine_FSR_Switch.active; - numeric: true; - adjustment : - Gtk.Adjustment { - lower: 0; - upper: 5; - step-increment: 1; - }; - } - - Adw.ComboRow Wine_FSR_Upscaling_Resolution_Mode_ComboRow { - title: _("Upscaling resolution mode"); - subtitle: _("Depending on what preset value you choose and your system, Proton will set an upscaling resolution automatically."); - sensitive: bind Wine_FSR_Switch.active; - model: Gtk.StringList { - strings ["None", "Performance", "Balanced", "Quality", "Ultra"] - }; - } - - Adw.ExpanderRow { - title: _("Custom resolution"); - subtitle: _("Note : overrides the upscaling resolution mode. This shouldn't be needed in most cases."); - sensitive: bind Wine_FSR_Switch.active; - - Adw.ActionRow { - title: _("Enable the usage of a custom resolution"); - Switch Wine_FSR_Resolution_Switch { - active: false; - sensitive: bind Wine_FSR_Switch.active; - valign: center; - } - } - Adw.SpinRow Wine_FSR_Resolution_External_Width_SpinRow { - title: _("Width"); - sensitive: bind Wine_FSR_Resolution_Switch.active; - numeric: true; - adjustment : - Gtk.Adjustment { - lower: 0; - upper: 7680; - step-increment: 10; - }; - } - Adw.SpinRow Wine_FSR_Resolution_External_Height_SpinRow { - title: _("Height"); - sensitive: bind Wine_FSR_Resolution_Switch.active; - numeric: true; - adjustment : - Gtk.Adjustment { - lower: 0; - upper: 7680; - step-increment: 10; - }; - } - } - } - - Adw.PreferencesGroup { - title: _("NVIDIA features"); - - Adw.ActionRow { - title : _("Disguise NVIDIA GPUs as AMD"); - subtitle: _("Force NVIDIA GPUs to always be reported as AMD GPUs. Some games require this if they depend on Windows-only NVIDIA driver functionality."); - subtitle-lines: 3; - - activatable-widget: Hide_NVIDIA_GPU_Switch; - sensitive: bind Hide_NVIDIA_GPU_Switch.sensitive bidirectional; - has-tooltip: bind Hide_NVIDIA_GPU_Switch.has-tooltip bidirectional; - tooltip-text: bind Hide_NVIDIA_GPU_Switch.tooltip-text bidirectional; - - Gtk.Switch Hide_NVIDIA_GPU_Switch { - active: false; - valign: center; - } - } - Adw.ActionRow { - title : _("Enable NVAPI"); - subtitle: _("Enable NVIDIA's NVAPI GPU support library."); - subtitle-lines: 3; - - activatable-widget: Enable_NVAPI_Switch; - sensitive: bind Enable_NVAPI_Switch.sensitive bidirectional; - has-tooltip: bind Enable_NVAPI_Switch.has-tooltip bidirectional; - tooltip-text: bind Enable_NVAPI_Switch.tooltip-text bidirectional; - - Gtk.Switch Enable_NVAPI_Switch { - active: false; - valign: center; - } - } - } - - Adw.PreferencesGroup { - title: "Wine"; - - Adw.ActionRow { - title: _("Synchronization"); - Box { - styles ["linked"] - - ToggleButton ESync_Toggle { - label: "ESync"; - valign: center; - active: true; - } - ToggleButton FSync_Toggle { - label: "FSync"; - valign: center; - active: true; - } - } - } - } - } - } - }; -} diff --git a/ui/definitions/settingsPage.blp b/ui/definitions/settingsPage.blp deleted file mode 100644 index 6c5669b..0000000 --- a/ui/definitions/settingsPage.blp +++ /dev/null @@ -1,59 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -StackPage settingsPage { //[MODIFIED IN LUA]: This page is only shown if the game is using Proton. - title: _("Settings"); - child: - Gtk.ScrolledWindow { - Adw.Clamp { - Box { - margin-bottom: 16; - margin-end: 12; - margin-start: 12; - margin-top: 16; - orientation: vertical; - spacing: 24; - vexpand: true; - vexpand-set: true; - - Adw.PreferencesGroup { - title: "Recommended"; - description: _("Settings recommended for the best experience."); - Adw.ActionRow { - title : _("Use dedicated graphics card"); - subtitle: _("Use your dedicated graphics card to boost performance by a lot, in exchange for higher power consumption."); - subtitle-lines: 3; - - activatable-widget: dGPU_Switch; - sensitive: bind dGPU_Switch.sensitive bidirectional; - has-tooltip: bind dGPU_Switch.has-tooltip bidirectional; - tooltip-text: bind dGPU_Switch.tooltip-text bidirectional; - - Gtk.Switch dGPU_Switch { - active: false; - sensitive: false; - valign: center; - } - } - Adw.ActionRow { - title : _("Enable SDL's Wayland driver"); - subtitle: _("This option can give you smoother gameplay and performance in SDL-based games on Wayland. Useless on X11."); - subtitle-lines: 3; - - activatable-widget: SDL_Wayland_Switch; - sensitive: bind SDL_Wayland_Switch.sensitive bidirectional; - visible: bind SDL_Wayland_Switch.visible bidirectional; - has-tooltip: bind SDL_Wayland_Switch.has-tooltip bidirectional; - tooltip-text: bind SDL_Wayland_Switch.tooltip-text bidirectional; - - Gtk.Switch SDL_Wayland_Switch { - active: false; - sensitive: true; - valign: center; - } - } - } - } - } - }; -} diff --git a/ui/definitions/utilitiesPage.blp b/ui/definitions/utilitiesPage.blp deleted file mode 100644 index 5e87575..0000000 --- a/ui/definitions/utilitiesPage.blp +++ /dev/null @@ -1,93 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -StackPage utilitiesPage { - title: _("In-game utilities"); - - child: - Adw.ClampScrollable { - Box { - margin-bottom: 16; - margin-end: 12; - margin-start: 12; - margin-top: 16; - orientation: vertical; - spacing: 24; - - Adw.PreferencesGroup { - title: "Recommended"; - description: _("Utilities recommended for the best experience."); - Adw.ActionRow { - subtitle: _("GameMode is a tool to optimize Linux system performance on demand."); - title: "Feral GameMode"; - activatable-widget: gamemode_Switch; - has-tooltip: bind gamemode_Switch.has-tooltip bidirectional; - tooltip-text: bind gamemode_Switch.tooltip-text bidirectional; - sensitive: bind gamemode_Switch.sensitive bidirectional; - - Switch gamemode_Switch { //[MODIFIED IN LUA][INTERACTS WITH LUA] - active: false; - sensitive: false; - valign: center; - } - } - } - - Adw.PreferencesGroup { - title: _("Utilities"); - description: _("Utilities that can be used while the game is running, depending on your needs."); - - // Adw.SwitchRow seem to be buggy-ish. The value gets inverted, and clicking directly on the button - // doesn't send the signal I need to run the functions. - - Adw.ActionRow { - subtitle: _("A Vulkan and OpenGL overlay for monitoring FPS, temperatures, CPU/GPU load and more... May not work on some native OpenGL games."); - subtitle-lines: 2; - title: "MangoHud"; - activatable-widget: mangohud_Switch; - has-tooltip: bind mangohud_Switch.has-tooltip bidirectional; - tooltip-text: bind mangohud_Switch.tooltip-text bidirectional; - sensitive: bind mangohud_Switch.sensitive bidirectional; - - Switch mangohud_Switch { //[MODIFIED IN LUA][INTERACTS WITH LUA] - active: false; - sensitive: false; - valign: center; - } - } - - Adw.ActionRow { - subtitle: _("Convert OpenGL games to Vulkan."); - subtitle-lines: 2; - title: "Zink"; - activatable-widget: zink_Switch; - has-tooltip: bind zink_Switch.has-tooltip bidirectional; - tooltip-text: bind zink_Switch.tooltip-text bidirectional; - sensitive: bind zink_Switch.sensitive bidirectional; - - Switch zink_Switch { //[MODIFIED IN LUA][INTERACTS WITH LUA] - active: false; - sensitive: false; - valign: center; - } - } - - Adw.ActionRow { - subtitle: _("obs-gamecapture is a tool that captures a game window and plugs it into OBS Studio as a source with minimal overhead."); - subtitle-lines: 2; - title: _("OBS Game Capture"); - activatable-widget: obs_gamecapture_Switch; - has-tooltip: bind obs_gamecapture_Switch.has-tooltip bidirectional; - tooltip-text: bind obs_gamecapture_Switch.tooltip-text bidirectional; - sensitive: bind obs_gamecapture_Switch.sensitive bidirectional; - - Switch obs_gamecapture_Switch { //[MODIFIED IN LUA][INTERACTS WITH LUA] - active: false; - sensitive: false; - valign: center; - } - } - } - } - }; -} diff --git a/ui/main.ui b/ui/main.ui deleted file mode 100644 index e9d7cb2..0000000 --- a/ui/main.ui +++ /dev/null @@ -1,1190 +0,0 @@ - - - - - 500 - 700 - 400 - true - 360 - - - max-width: 600sp - true - false - true - - - - - - - - - SimpleSteamTinker - - - - - - - - help-about-symbolic - - - - - - - - 2 - - - 16 - 12 - 12 - 16 - - - Insert "sst %command%" as a game's launch options to enable it here. -Command may change depending on installation method. - Detected Steam games - - - - - - - - - - - - - Game settings - - - - - - - - true - false - false - view-sidebar-start-symbolic - - - - - - - 200 - - - - Sidebar - - - - - 1 - true - true - - - 0 - 0 - - - - - true - true - gameSettingsStack - - - - - - - - - - - Game settings - - - 1 - - - overviewPage - Overview - - - 2 - - - - 1 - - - 0 - 0 - 256 - - - - - - - 16 - 12 - 12 - 16 - 1 - 24 - true - true - - - 1 - 8 - 3 - - - 3 - 2 - true - - - - - - 0 - 8 - 3 - - - ProtonDB Rating : - 3 - 2 - - - - - - Unavailable - 3 - 2 - - - - - - - - - - 3 - 2 - - - media-playback-start-symbolic - Launch - - - - - - - - - - SimpleSteamTinker Status - Based on the game's launch options. - 1 - - - Placeholder - 2 - true - - - - - - - - - - Details - Information about the game. - - - Steam AppID - The unique ID of the game that identifies it in Steam. - gameID_copyButton - - - 8 - - - 2 - true - true - - - - - - 2 - edit-copy-symbolic - - - - - - - - - - Platform - The intended platform for the installed game version. - - - 2 - true - - - - - - - - Size - The amount of space the game takes up on the system. - - - 2 - 1 - - - - - - - - Install location - The location of the game's files on the system. - true - gameLocationButton - 2 - - - - - folder-open-symbolic - - - - - - - - - - Virtual filesystem location - The location of the game's virtual filesystem assigned by Steam. (Only applies to Windows games) - true - gameCompatdataButton - 2 - - - - - folder-open-symbolic - - - - - - - - - - - - Online help - Links to useful pages related to the game. - - - ProtonDB - protonDBPage_Button - - - - - help-browser-symbolic - - - - - - - - - - PCGamingWiki - PCGamingWikiPage_Button - - - - - help-browser-symbolic - - - - - - - - - - SteamDB - steamDBPage_Button - - - - - help-browser-symbolic - - - - - - - - - - Steambase - SteambasePage_Button - - - - - help-browser-symbolic - - - - - - - - - - - - - - - - - - - - - - Settings - - - - - - - 16 - 12 - 12 - 16 - 1 - 24 - true - true - - - Recommended - Settings recommended for the best experience. - - - Use dedicated graphics card - Use your dedicated graphics card to boost performance by a lot, in exchange for higher power consumption. - 3 - dGPU_Switch - - - - - - false - false - 3 - - - - - - - Enable SDL's Wayland driver - This option can give you smoother gameplay and performance in SDL-based games on Wayland. Useless on X11. - 3 - SDL_Wayland_Switch - - - - - - - false - true - 3 - - - - - - - - - - - - - - - - - In-game utilities - - - - - 16 - 12 - 12 - 16 - 1 - 24 - - - Recommended - Utilities recommended for the best experience. - - - GameMode is a tool to optimize Linux system performance on demand. - Feral GameMode - gamemode_Switch - - - - - - false - false - 3 - - - - - - - - - Utilities - Utilities that can be used while the game is running, depending on your needs. - - - A Vulkan and OpenGL overlay for monitoring FPS, temperatures, CPU/GPU load and more... May not work on some native OpenGL games. - 2 - MangoHud - mangohud_Switch - - - - - - false - false - 3 - - - - - - - Convert OpenGL games to Vulkan. - 2 - Zink - zink_Switch - - - - - - false - false - 3 - - - - - - - obs-gamecapture is a tool that captures a game window and plugs it into OBS Studio as a source with minimal overhead. - 2 - OBS Game Capture - obs_gamecapture_Switch - - - - - - false - false - 3 - - - - - - - - - - - - - - - Launch options - - - In the future, this will be a page for configuring launch options. - true - - - - - - - Proton / Wine - - - - - - - 16 - 12 - 12 - 16 - 1 - 24 - true - true - - - Direct3D - Configure Direct3D settings. - - - Versions - Control which versions of Direct3D can be used by this game. If you are unsure, leave all versions enabled. - - - Direct3D 9 - Only applies to GE Proton. - Direct3D9_Switch - - - - - - true - 3 - - - - - - - Direct3D 10 - Direct3D10_Switch - - - - - - true - 3 - - - - - - - Direct3D 11 - Direct3D11_Switch - - - - - - true - 3 - - - - - - - Direct3D 12 - Only applies to GE Proton. - Direct3D12_Switch - - - - - - true - 3 - - - - - - - - - Use WineD3D instead of DXVK - Use OpenGL-based WineD3D instead of Vulkan-based DXVK for D3D11 and D3D10. - WineD3D_Switch - - - - - - false - 3 - - - - - - - - - AMD FidelityFX Super Resolution 1.0 - Note : this feature requires GE Proton, and is not restricted to AMD GPUs. - - - false - true - 3 - - - - - Sharpness - The strength of the filter. (0 = strongest, 5 = weakest) - - true - - - 0 - 5 - 1 - - - - - - - Upscaling resolution mode - Depending on what preset value you choose and your system, Proton will set an upscaling resolution automatically. - - - - - None - Performance - Balanced - Quality - Ultra - - - - - - - - Custom resolution - Note : overrides the upscaling resolution mode. This shouldn't be needed in most cases. - - - - Enable the usage of a custom resolution - - - false - - 3 - - - - - - - Width - - true - - - 0 - 7680 - 10 - - - - - - - Height - - true - - - 0 - 7680 - 10 - - - - - - - - - - - NVIDIA features - - - Disguise NVIDIA GPUs as AMD - Force NVIDIA GPUs to always be reported as AMD GPUs. Some games require this if they depend on Windows-only NVIDIA driver functionality. - 3 - Hide_NVIDIA_GPU_Switch - - - - - - false - 3 - - - - - - - Enable NVAPI - Enable NVIDIA's NVAPI GPU support library. - 3 - Enable_NVAPI_Switch - - - - - - false - 3 - - - - - - - - - Wine - - - Synchronization - - - - - - ESync - 3 - true - - - - - FSync - 3 - true - - - - - - - - - - - - - - - - - - - Gamescope - - - - - - - 16 - 12 - 12 - 16 - 1 - 24 - true - true - - - Gamescope - Gamescope is a tool from Valve that allows for games to run in an isolated XWayland instance. - - - Enable Gamescope - May cause compatibility issues with some utilities and Wayland-native games. - gamescope_Switch - - - - - - false - false - 3 - - - - - - - - - Resolution - Resolution settings for both the game and the Gamescope window. - - - - Game resolution - The resolution the game will run at. - - - - Width - The width of the game's internal resolution. - true - - - 0 - 7680 - 10 - - - - - - - Height - The height of the game's internal resolution. - true - - - 0 - 7680 - 10 - - - - - - - - - Window resolution - The resolution of the Gamescope window. - - - - Width - The width of the Gamescope window. - true - - - 0 - 7680 - 10 - - - - - - - Height - The height of the Gamescope window. - true - - - 0 - 7680 - 10 - - - - - - - - - false - - 3 - - - - - - - Framerate - The maximum framerate the game will run at. - - - - Normal - The framerate when the game is focused. - - true - - - 0 - 244 - 1 - - - - - - - Unfocused - The framerate when the game is not focused. - - true - - - 0 - 244 - 1 - - - - - - - false - - 3 - - - - - - - Filtering - Makes the game look smoother at lower resolutions. - - - - Filter - The filter to use. (NIS = Nvidia Image Scaling, FSR = FidelityFX Super Resolution) - - - - - Linear - Nearest - FSR - NIS - Pixel - - - - - - - - Sharpness - Sets the sharpness of the filter. (0 = maximum sharpness, 20 = minimum) - - true - - - 0 - 20 - 1 - - - - - - - false - - 3 - - - - - - - Extras - Additional settings for GameScope. - - - - Window type - The type of window to use for the Gamescope window. - - - - - - Borderless - 3 - - - - - Fullscreen - 3 - - - - - - - - - - - - - - - - - - - Cleaner - - - In the future, this page will allow you to do some cleaning up. - true - - - - - - - - - - - - - - - - - - - - diff --git a/ui/update-ui.lua b/ui/update-ui.lua deleted file mode 100644 index b61c29e..0000000 --- a/ui/update-ui.lua +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/lua - ---[[ - This script should be used after modifying any of the ui_definitions files. - It will compile the ui_definitions files into ui files that GTK can use. - If you do not, the modifications you've made to the UI outside of Lua code will not be applied. - - This is basically a workaround for LGI not working well with Gtk templates or to assemble widgets together in general right now. -]] - --- Define the list of files to compile -local baseFile = "mainWindow.blp" -local files = { - [baseFile] = false, - ["overviewPage.blp"] = false, - ["utilitiesPage.blp"] = false, - ["settingsPage.blp"] = false, - ["commandPage.blp"] = false, - ["gamescopePage.blp"] = false, - ["protonPage.blp"] = false, - ["cleanerPage.blp"] = false, -} - --- Add their contents to the list -for file, _ in pairs(files) do - local f = io.open("ui/definitions/"..file, "r") - if not f then - print("Error: Could not open file "..file) - os.exit(1) - end - files[file] = f:read("*all") - f:close() -end - -local function removeUsageLines(content) - local lines = {} - for line in content:gmatch("[^\r\n]+") do - if not line:find("using ") or not line:find(";") then - table.insert(lines, line) - end - end - return table.concat(lines, "\n") -end - --- Inside the contents of each file, replace comments written like "//[FILENAME]" with the respective content -for file, content in pairs(files) do - local newContent = {} - for line in content:gmatch("[^\r\n]+") do - - local fileName = line:match("//%[(.+)%]") - if fileName then - local fileContent = files[fileName] - if fileContent then - print("Inserting "..fileName.." into "..file..".") - table.insert(newContent, removeUsageLines(fileContent)) - else - table.insert(newContent, line) - end - else - table.insert(newContent, line) - end - - end - files[file] = table.concat(newContent, "\n") -end - --- Write it into a location -local location = "/tmp/main.blp" -local f = io.open(location, "w") -if not f then - print("Error: Could not open file "..location) - os.exit(1) -end -f:write(files[baseFile]) -f:close() - --- Make the blueprint with blueprint-compiler -local command = "blueprint-compiler compile "..location.." > ui/main.ui" -print(command) -os.execute(command) -os.remove(location) \ No newline at end of file