From 95d104bd97e9769b75b4d3f94df496e0c748bff9 Mon Sep 17 00:00:00 2001 From: tkashkin Date: Mon, 10 Aug 2020 07:08:18 +0300 Subject: [PATCH] Tweaks: refactoring (#355) Tweak options: saving/restoring Tweak states (`enabled`/`disabled`/`global`) Former-commit-id: 86e59c33dd9100d1ae35c447e30616cb55e792eb --- ...com.github.tkashkin.gamehub.gschema.xml.in | 10 +- res/styles/common/dialogs/SettingsDialog.css | 47 +- src/app.vala | 8 +- src/data/Emulator.vala | 6 +- src/data/adapters/GamesAdapter.vala | 8 +- src/data/compat/CustomScript.vala | 12 +- src/data/compat/DOSBox.vala | 6 +- src/data/compat/Innoextract.vala | 4 +- src/data/compat/Proton.vala | 12 +- src/data/compat/RetroArch.vala | 2 +- src/data/compat/ScummVM.vala | 6 +- src/data/compat/Wine.vala | 14 +- src/data/compat/WineWrap.vala | 14 +- src/data/db/tables/Games.vala | 2 +- .../runnables/tasks/install/Installer.vala | 2 +- .../tasks/install/InstallerType.vala | 4 +- .../runnables/traits/HasExecutableFile.vala | 6 +- .../runnables/traits/game/SupportsTweaks.vala | 14 +- src/data/sources/gog/GOGGame.vala | 6 +- src/data/sources/humble/HumbleGame.vala | 4 +- src/data/sources/steam/Steam.vala | 16 +- src/data/sources/user/UserGame.vala | 4 +- src/data/tweaks/ApplicabilityOptions.vala | 181 ++++++ src/data/tweaks/Option.vala | 205 +++++++ src/data/tweaks/Requirements.vala | 167 ++++++ src/data/tweaks/Tweak.vala | 520 +----------------- src/data/tweaks/TweakOptions.vala | 278 ++++++++++ src/data/tweaks/TweakSet.vala | 131 +++++ src/meson.build | 11 + src/settings/Tweaks.vala | 21 +- .../GamePropertiesDialog.vala | 2 +- .../GamePropertiesDialog/tabs/Overlays.vala | 2 +- .../GamePropertiesDialog/tabs/Tweaks.vala | 54 ++ src/ui/dialogs/ImportEmulatedGamesDialog.vala | 6 +- .../SettingsDialog/SettingsDialog.vala | 1 + .../dialogs/SettingsDialog/pages/About.vala | 6 +- .../pages/general/CompatTools.vala | 47 ++ src/ui/views/GamesView/GamesView.vala | 10 +- src/ui/views/GamesView/grid/GameCard.vala | 2 +- src/ui/views/GamesView/list/GameListRow.vala | 2 +- src/ui/views/WelcomeView.vala | 4 +- src/ui/widgets/CompatToolOptions.vala | 12 +- src/ui/widgets/compat/CompatToolsList.vala | 54 ++ .../widgets/tweaks/TweakOptionsPopover.vala | 99 ++-- src/ui/widgets/tweaks/TweakRow.vala | 24 +- src/ui/widgets/tweaks/TweaksList.vala | 2 +- src/utils/ExecTask.vala | 278 ++++++++++ src/utils/Gamepad.vala | 2 +- src/utils/OS.vala | 121 ++++ src/utils/Utils.vala | 330 +---------- src/utils/fs/FS.vala | 2 +- src/utils/fs/FSOverlay.vala | 6 +- 52 files changed, 1764 insertions(+), 1023 deletions(-) create mode 100644 src/data/tweaks/ApplicabilityOptions.vala create mode 100644 src/data/tweaks/Option.vala create mode 100644 src/data/tweaks/Requirements.vala create mode 100644 src/data/tweaks/TweakOptions.vala create mode 100644 src/data/tweaks/TweakSet.vala create mode 100644 src/ui/dialogs/GamePropertiesDialog/tabs/Tweaks.vala create mode 100644 src/ui/dialogs/SettingsDialog/pages/general/CompatTools.vala create mode 100644 src/ui/widgets/compat/CompatToolsList.vala create mode 100644 src/utils/ExecTask.vala create mode 100644 src/utils/OS.vala diff --git a/data/com.github.tkashkin.gamehub.gschema.xml.in b/data/com.github.tkashkin.gamehub.gschema.xml.in index 67609d84..434fa4a3 100644 --- a/data/com.github.tkashkin.gamehub.gschema.xml.in +++ b/data/com.github.tkashkin.gamehub.gschema.xml.in @@ -82,7 +82,7 @@ - + @@ -93,7 +93,7 @@ Dark theme - "Theme-based" + "Automatic" Icon style @@ -375,9 +375,9 @@ - - [] - Global tweaks list + + '' + Global tweak options diff --git a/res/styles/common/dialogs/SettingsDialog.css b/res/styles/common/dialogs/SettingsDialog.css index 74dc3ed1..ee42b914 100644 --- a/res/styles/common/dialogs/SettingsDialog.css +++ b/res/styles/common/dialogs/SettingsDialog.css @@ -7,38 +7,51 @@ padding: 4px; } +.settings-dialog > .dialog-vbox:dir(ltr) +{ + margin-left: -2px; +} + .settings-dialog:dir(ltr) { background-image: linear-gradient(to right, - shade(@theme_bg_color, 0.95) 202px, - @borders 202px, @borders 203px, - transparent 203px, transparent); + shade(@theme_bg_color, 0.95) 200px, + @borders 200px, @borders 201px, + transparent 201px, transparent); } + .settings-dialog > headerbar:dir(ltr) { background-image: linear-gradient(to right, - alpha(shade(@theme_bg_color, 0.1), 0.05) 202px, - @borders 202px, @borders 203px, - transparent 203px, transparent); + alpha(shade(@theme_bg_color, 0.1), 0.05) 200px, + @borders 200px, @borders 201px, + transparent 201px, transparent); } -.settings-dialog:dir(rtl) + +.settings-dialog > headerbar > .vertical:dir(ltr) { - background-image: linear-gradient(to left, - shade(@theme_bg_color, 0.95) 202px, - @borders 202px, @borders 203px, - transparent 203px, transparent); + padding-left: 200px; } -.settings-dialog > headerbar:dir(rtl) + +.settings-dialog > .dialog-vbox:dir(rtl) +{ + margin-right: -2px; +} + +.settings-dialog:dir(rtl) { background-image: linear-gradient(to left, - alpha(shade(@theme_bg_color, 0.1), 0.05) 202px, - @borders 202px, @borders 203px, - transparent 203px, transparent); + shade(@theme_bg_color, 0.95) 200px, + @borders 200px, @borders 201px, + transparent 201px, transparent); } -.settings-dialog > headerbar > .vertical:dir(ltr) +.settings-dialog > headerbar:dir(rtl) { - padding-left: 200px; + background-image: linear-gradient(to left, + alpha(shade(@theme_bg_color, 0.1), 0.05) 200px, + @borders 200px, @borders 201px, + transparent 201px, transparent); } .settings-dialog > headerbar > .vertical:dir(rtl) diff --git a/src/app.vala b/src/app.vala index 41ef7f77..4038f383 100644 --- a/src/app.vala +++ b/src/app.vala @@ -339,7 +339,7 @@ namespace GameHub }; info("Restarting with GDB"); - Utils.run(exec_cmd).dir(Environment.get_current_dir()).run_sync(); + Utils.exec(exec_cmd).dir(Environment.get_current_dir()).sync(); exit_status = 0; return true; } @@ -440,10 +440,10 @@ namespace GameHub println(plain, "- Environment"); #if OS_LINUX - println(plain, " Distro: %s", Utils.get_distro()); - println(plain, " DE: %s", Utils.get_desktop_environment() ?? "unknown"); + println(plain, " Distro: %s", OS.get_distro()); + println(plain, " DE: %s", OS.get_desktop_environment() ?? "unknown"); #else - println(plain, " OS: %s", Utils.get_distro()); + println(plain, " OS: %s", OS.get_distro()); #endif println(plain, " GTK: %u.%u.%u", Gtk.get_major_version(), Gtk.get_minor_version(), Gtk.get_micro_version()); diff --git a/src/data/Emulator.vala b/src/data/Emulator.vala index 930db47e..5675de2a 100644 --- a/src/data/Emulator.vala +++ b/src/data/Emulator.vala @@ -175,7 +175,7 @@ namespace GameHub.Data { Runnable.IsLaunched = is_running = true; - yield Utils.run(get_args(null, executable)).dir(work_dir.get_path()).override_runtime(true).run_sync_thread(); + yield Utils.exec(get_args(null, executable)).dir(work_dir.get_path()).override_runtime(true).sync_thread(); Timeout.add_seconds(1, () => { Runnable.IsLaunched = is_running = false; @@ -204,12 +204,12 @@ namespace GameHub.Data var dir = game != null && launch_in_game_dir ? game.work_dir : work_dir; - var task = Utils.run(get_args(game, executable)).dir(dir.get_path()).override_runtime(true); + var task = Utils.exec(get_args(game, executable)).dir(dir.get_path()).override_runtime(true); if(game != null && game is TweakableGame) { task.tweaks(((TweakableGame) game).get_enabled_tweaks()); } - yield task.run_sync_thread(); + yield task.sync_thread(); Timeout.add_seconds(1, () => { Runnable.IsLaunched = is_running = false; diff --git a/src/data/adapters/GamesAdapter.vala b/src/data/adapters/GamesAdapter.vala index 934ee04b..ddd65ce1 100644 --- a/src/data/adapters/GamesAdapter.vala +++ b/src/data/adapters/GamesAdapter.vala @@ -91,10 +91,10 @@ namespace GameHub.Data.Adapters if(this.grid != null) { this.grid.set_filter_func(c => { - return filter((c as GameCard).game); + return filter(((GameCard) c).game); }); this.grid.set_sort_func((c, c2) => { - return sort((c as GameCard).game, (c2 as GameCard).game); + return sort(((GameCard) c).game, ((GameCard) c2).game); }); add_cached_views(false); } @@ -106,10 +106,10 @@ namespace GameHub.Data.Adapters if(this.list != null) { this.list.set_filter_func(r => { - return filter((r as GameListRow).game); + return filter(((GameListRow) r).game); }); this.list.set_sort_func((r, r2) => { - return sort((r as GameListRow).game, (r2 as GameListRow).game); + return sort(((GameListRow) r).game, ((GameListRow) r2).game); }); this.list.set_header_func(list_header); add_cached_views(false); diff --git a/src/data/compat/CustomScript.vala b/src/data/compat/CustomScript.vala index fd91f23d..b784215e 100644 --- a/src/data/compat/CustomScript.vala +++ b/src/data/compat/CustomScript.vala @@ -81,7 +81,7 @@ namespace GameHub.Data.Compat var script = gh_dir.get_child(SCRIPT); if(script.query_exists()) { - var task = Utils.run({ script.get_path() }).dir(runnable.install_dir.get_path()); + var task = Utils.exec({ script.get_path() }).dir(runnable.install_dir.get_path()); runnable.cast(game => { task.env_var("GH_INSTALL_DIR", game.install_dir.get_path()) @@ -107,7 +107,7 @@ namespace GameHub.Data.Compat runnable.cast(game => { task.tweaks(game.get_enabled_tweaks(this)); }); - yield task.run_sync_thread(); + yield task.sync_thread(); } else { @@ -122,17 +122,17 @@ namespace GameHub.Data.Compat var script = gh_dir.get_child(SCRIPT); if(script.query_exists()) { - Utils.run({"chmod", "+x", script.get_path()}).run_sync(); + Utils.exec({"chmod", "+x", script.get_path()}).sync(); var executable_path = emu.executable != null ? emu.executable.get_path() : "null"; var game_executable_path = game != null && game.executable != null ? game.executable.get_path() : "null"; string[] cmd = { script.get_path(), executable_path, emu.id, emu.name, game_executable_path, game.id, game.full_id, game.name, game.escaped_name }; var dir = game != null && launch_in_game_dir ? game.work_dir : emu.work_dir; - var task = Utils.run(cmd).dir(dir.get_path()); + var task = Utils.exec(cmd).dir(dir.get_path()); runnable.cast(game => { task.tweaks(game.get_enabled_tweaks(this)); }); - yield task.run_sync_thread(); + yield task.sync_thread(); } else { @@ -157,7 +157,7 @@ namespace GameHub.Data.Compat warning("[CustomScript.edit_script] %s", e.message); } } - Utils.run({"chmod", "+x", script.get_path()}).run_sync(); + Utils.exec({"chmod", "+x", script.get_path()}).sync(); Utils.open_uri(script.get_uri()); } } diff --git a/src/data/compat/DOSBox.vala b/src/data/compat/DOSBox.vala index 18e99d44..4d3e9dc3 100644 --- a/src/data/compat/DOSBox.vala +++ b/src/data/compat/DOSBox.vala @@ -141,7 +141,7 @@ namespace GameHub.Data.Compat private static bool is_dos_executable(File? file) { if(file == null || !file.query_exists()) return false; - var type = Utils.run({"file", "-b", file.get_path()}).log(false).run_sync(true).output; + var type = Utils.exec({"file", "-b", file.get_path()}).log(false).sync(true).output; if(type != null && type.length > 0) { return "DOS" in type; @@ -216,11 +216,11 @@ namespace GameHub.Data.Compat wdir = bundled_win_dosbox.get_parent(); } - var task = Utils.run(combine_cmd_with_args(cmd, runnable)).dir(wdir.get_path()); + var task = Utils.exec(combine_cmd_with_args(cmd, runnable)).dir(wdir.get_path()); runnable.cast(game => { task.tweaks(game.get_enabled_tweaks(this)); }); - yield task.run_sync_thread(); + yield task.sync_thread(); } } } diff --git a/src/data/compat/Innoextract.vala b/src/data/compat/Innoextract.vala index 6deafcb0..28092364 100644 --- a/src/data/compat/Innoextract.vala +++ b/src/data/compat/Innoextract.vala @@ -49,7 +49,7 @@ namespace GameHub.Data.Compat if(installed) { - version = Utils.run({executable.get_path(), "-v", "-q", "-c", "0"}).log(false).run_sync(true).output.replace(id, "").strip(); + version = Utils.exec({executable.get_path(), "-v", "-q", "-c", "0"}).log(false).sync(true).output.replace(id, "").strip(); name = name + " (" + version + ")"; if(Utils.compare_versions(Utils.parse_version(version), Innoextract.MIN_SUPPORTED_VERSION) < 0) @@ -76,7 +76,7 @@ namespace GameHub.Data.Compat string[] cmd = { executable.get_path(), "-e", "-m", "-d", runnable.install_dir.get_path() }; if(runnable is Sources.GOG.GOGGame) cmd += "--gog"; cmd += installer.get_path(); - yield Utils.run(cmd).dir(installer.get_parent().get_path()).run_sync_thread(); + yield Utils.exec(cmd).dir(installer.get_parent().get_path()).sync_thread(); do { diff --git a/src/data/compat/Proton.vala b/src/data/compat/Proton.vala index 12aa9629..9c50fdc1 100644 --- a/src/data/compat/Proton.vala +++ b/src/data/compat/Proton.vala @@ -134,11 +134,11 @@ namespace GameHub.Data.Compat { cmd = { executable.get_path(), "run", "msiexec", "/i", file.get_path() }; } - var task = Utils.run(combine_cmd_with_args(cmd, runnable, args)).dir(dir.get_path()).env(prepare_env(runnable, parse_opts)); + var task = Utils.exec(combine_cmd_with_args(cmd, runnable, args)).dir(dir.get_path()).env(prepare_env(runnable, parse_opts)); runnable.cast(game => { task.tweaks(game.get_enabled_tweaks(this)); }); - yield task.run_sync_thread(); + yield task.sync_thread(); } public override File get_default_wineprefix(Traits.SupportsCompatTools runnable) @@ -150,7 +150,7 @@ namespace GameHub.Data.Compat if(FS.file(install_dir.get_path(), @"$(FS.GAMEHUB_DIR)/$(binary)_$(arch)").query_exists()) { - Utils.run({"bash", "-c", @"mv -f $(FS.GAMEHUB_DIR)/$(binary)_$(arch) $(FS.GAMEHUB_DIR)/$(FS.COMPAT_DATA_DIR)/$(id)"}).dir(install_dir.get_path()).run_sync(); + Utils.exec({"bash", "-c", @"mv -f $(FS.GAMEHUB_DIR)/$(binary)_$(arch) $(FS.GAMEHUB_DIR)/$(FS.COMPAT_DATA_DIR)/$(id)"}).dir(install_dir.get_path()).sync(); FS.rm(dosdevices.get_child("d:").get_path()); } @@ -184,7 +184,7 @@ namespace GameHub.Data.Compat { if(!dosdevices.get_child(@"$(letter):").query_exists() && !dosdevices.get_child(@"$(letter)::").query_exists()) { - Utils.run({"ln", "-nsf", "../../../../../", @"$(letter):"}).dir(dosdevices.get_path()).run_sync(); + Utils.exec({"ln", "-nsf", "../../../../../", @"$(letter):"}).dir(dosdevices.get_path()).sync(); break; } } @@ -269,10 +269,10 @@ namespace GameHub.Data.Compat if(!cmd.query_exists()) { - yield Utils.run({executable.get_path(), "run", cmd.get_path(), "/c", "exit"}) + yield Utils.exec({executable.get_path(), "run", cmd.get_path(), "/c", "exit"}) .dir(runnable.install_dir.get_path()) .env(prepare_env(runnable)) - .run_sync_thread(true); + .sync_thread(true); } } diff --git a/src/data/compat/RetroArch.vala b/src/data/compat/RetroArch.vala index 689edbb2..fd1afafe 100644 --- a/src/data/compat/RetroArch.vala +++ b/src/data/compat/RetroArch.vala @@ -117,7 +117,7 @@ namespace GameHub.Data.Compat string[] cmd = { executable.get_path(), "-L", core, runnable.executable.get_path() }; - yield Utils.run(combine_cmd_with_args(cmd, runnable)).dir(runnable.work_dir.get_path()).run_sync_thread(); + yield Utils.exec(combine_cmd_with_args(cmd, runnable)).dir(runnable.work_dir.get_path()).sync_thread(); } } } diff --git a/src/data/compat/ScummVM.vala b/src/data/compat/ScummVM.vala index 5501d704..1479e7fa 100644 --- a/src/data/compat/ScummVM.vala +++ b/src/data/compat/ScummVM.vala @@ -48,7 +48,7 @@ namespace GameHub.Data.Compat { if(dir != null && dir.query_exists()) { - var output = Utils.run({executable.get_path(), "--detect"}).dir(dir.get_path()).log(false).run_sync(true).output; + var output = Utils.exec({executable.get_path(), "--detect"}).dir(dir.get_path()).log(false).sync(true).output; return !(SCUMMVM_NO_GAMES_WARNING in output); } return false; @@ -74,11 +74,11 @@ namespace GameHub.Data.Compat string[] cmd = { executable.get_path(), "--auto-detect" }; - var task = Utils.run(combine_cmd_with_args(cmd, runnable)).dir(dir.get_path()); + var task = Utils.exec(combine_cmd_with_args(cmd, runnable)).dir(dir.get_path()); runnable.cast(game => { task.tweaks(game.get_enabled_tweaks(this)); }); - yield task.run_sync_thread(); + yield task.sync_thread(); } } } diff --git a/src/data/compat/Wine.vala b/src/data/compat/Wine.vala index a835c4dd..ab9ffc18 100644 --- a/src/data/compat/Wine.vala +++ b/src/data/compat/Wine.vala @@ -171,11 +171,11 @@ namespace GameHub.Data.Compat { cmd = { executable.get_path(), "msiexec", "/i", file.get_path() }; } - var task = Utils.run(combine_cmd_with_args(cmd, runnable, args)).dir(dir.get_path()).env(prepare_env(runnable, parse_opts)); + var task = Utils.exec(combine_cmd_with_args(cmd, runnable, args)).dir(dir.get_path()).env(prepare_env(runnable, parse_opts)); runnable.cast(game => { task.tweaks(game.get_enabled_tweaks(this)); }); - yield task.run_sync_thread(); + yield task.sync_thread(); } public virtual File get_default_wineprefix(Traits.SupportsCompatTools runnable) @@ -187,7 +187,7 @@ namespace GameHub.Data.Compat if(FS.file(install_dir.get_path(), @"$(FS.GAMEHUB_DIR)/$(binary)_$(arch)").query_exists()) { - Utils.run({"bash", "-c", @"mv -f $(FS.GAMEHUB_DIR)/$(binary)_$(arch) $(FS.GAMEHUB_DIR)/$(FS.COMPAT_DATA_DIR)/$(binary)_$(arch)"}).dir(install_dir.get_path()).run_sync(); + Utils.exec({"bash", "-c", @"mv -f $(FS.GAMEHUB_DIR)/$(binary)_$(arch) $(FS.GAMEHUB_DIR)/$(FS.COMPAT_DATA_DIR)/$(binary)_$(arch)"}).dir(install_dir.get_path()).sync(); FS.rm(dosdevices.get_child("d:").get_path()); } @@ -221,7 +221,7 @@ namespace GameHub.Data.Compat { if(!dosdevices.get_child(@"$(letter):").query_exists() && !dosdevices.get_child(@"$(letter)::").query_exists()) { - Utils.run({"ln", "-nsf", "../../../../", @"$(letter):"}).dir(dosdevices.get_path()).run_sync(); + Utils.exec({"ln", "-nsf", "../../../../", @"$(letter):"}).dir(dosdevices.get_path()).sync(); break; } } @@ -316,17 +316,17 @@ namespace GameHub.Data.Compat } } - yield Utils.run(cmd).dir(runnable.install_dir.get_path()).env(prepare_env(runnable)).run_sync_thread(); + yield Utils.exec(cmd).dir(runnable.install_dir.get_path()).env(prepare_env(runnable)).sync_thread(); } protected async void winetricks(Traits.SupportsCompatTools runnable) { - yield Utils.run({"winetricks"}).dir(runnable.install_dir.get_path()).env(prepare_env(runnable)).run_sync_thread(); + yield Utils.exec({"winetricks"}).dir(runnable.install_dir.get_path()).env(prepare_env(runnable)).sync_thread(); } public async string convert_path(Traits.SupportsCompatTools runnable, File path) { - var win_path = (yield Utils.run({wine_binary.get_path(), "winepath", "-w", path.get_path()}).env(prepare_env(runnable)).log(false).run_sync_thread(true)).output.strip(); + var win_path = (yield Utils.exec({wine_binary.get_path(), "winepath", "-w", path.get_path()}).env(prepare_env(runnable)).log(false).sync_thread(true)).output.strip(); debug("[Wine.convert_path] '%s' -> '%s'", path.get_path(), win_path); return win_path; } diff --git a/src/data/compat/WineWrap.vala b/src/data/compat/WineWrap.vala index 6d5f12ec..50c94a5c 100644 --- a/src/data/compat/WineWrap.vala +++ b/src/data/compat/WineWrap.vala @@ -86,7 +86,7 @@ namespace GameHub.Data.Compat public override bool can_install(Traits.SupportsCompatTools runnable, InstallTask task) { - return installed && runnable != null && runnable is GOGGame && wrappers.has_key(runnable.id); + return can_run(runnable); } public override async void install(Traits.SupportsCompatTools runnable, InstallTask task, File installer) @@ -107,7 +107,7 @@ namespace GameHub.Data.Compat if(wrapper == null || !wrapper.query_exists()) return; string[] cmd = { "tar", "xf", wrapper.get_path(), "-C", wrapper_dir.get_path(), "--strip-components=1" }; - yield Utils.run(cmd).dir(wrapper_dir.get_path()).run_sync_thread(); + yield Utils.exec(cmd).dir(wrapper_dir.get_path()).sync_thread(); var winewrap_env = Environ.get(); winewrap_env = Environ.set_variable(winewrap_env, "WINEWRAP_RESPATH", installer.get_parent().get_path()); @@ -116,8 +116,8 @@ namespace GameHub.Data.Compat FS.rm(runnable.install_dir.get_path(), null, "-rf"); - cmd = { "bash", "-c", "./*_wine.sh -dirname=" + (runnable as GOGGame).name_escaped }; - yield Utils.run(cmd).dir(wrapper_dir.get_path()).env(winewrap_env).run_sync_thread(); + cmd = { "bash", "-c", "./*_wine.sh -dirname=" + runnable.name_escaped }; + yield Utils.exec(cmd).dir(wrapper_dir.get_path()).env(winewrap_env).sync_thread(); runnable.executable = runnable.install_dir.get_child("start.sh"); } @@ -129,7 +129,7 @@ namespace GameHub.Data.Compat public override bool can_run(Traits.SupportsCompatTools runnable) { - return can_install(runnable, null) || runnable.compat_tool == id; + return installed && runnable != null && runnable is GOGGame && wrappers.has_key(runnable.id); } public override async void run(Traits.SupportsCompatTools runnable) @@ -143,11 +143,11 @@ namespace GameHub.Data.Compat string[] cmd = { runnable.install_dir.get_child("start.sh").get_path(), action }; - var task = Utils.run(combine_cmd_with_args(cmd, runnable)).dir(runnable.work_dir.get_path()); + var task = Utils.exec(combine_cmd_with_args(cmd, runnable)).dir(runnable.work_dir.get_path()); runnable.cast(game => { task.tweaks(game.get_enabled_tweaks(this)); }); - yield task.run_sync_thread(); + yield task.sync_thread(); } } } diff --git a/src/data/db/tables/Games.vala b/src/data/db/tables/Games.vala index 1e41f5ba..e5233154 100644 --- a/src/data/db/tables/Games.vala +++ b/src/data/db/tables/Games.vala @@ -220,7 +220,7 @@ namespace GameHub.Data.DB.Tables game.cast(game => { if(game.tweaks != null) { - tweaks = string.joinv(",", game.tweaks); + tweaks = Json.to_string(game.tweaks.to_json(), false); } }); diff --git a/src/data/runnables/tasks/install/Installer.vala b/src/data/runnables/tasks/install/Installer.vala index 48b1f415..38044ec7 100644 --- a/src/data/runnables/tasks/install/Installer.vala +++ b/src/data/runnables/tasks/install/Installer.vala @@ -94,7 +94,7 @@ namespace GameHub.Data.Runnables.Tasks.Install { try { - return (yield Utils.run(cmd).run_async()).check_status(); + return (yield Utils.exec(cmd).async()).check_status(); } catch(Error e) { diff --git a/src/data/runnables/tasks/install/InstallerType.vala b/src/data/runnables/tasks/install/InstallerType.vala index 5a65b39a..aab38d24 100644 --- a/src/data/runnables/tasks/install/InstallerType.vala +++ b/src/data/runnables/tasks/install/InstallerType.vala @@ -41,7 +41,7 @@ namespace GameHub.Data.Runnables.Tasks.Install var type = yield guess_from_mime_or_extension(file, can_be_data); if(type == InstallerType.WINDOWS_EXECUTABLE) { - var desc = Utils.run({"file", "-b", file.get_path()}).log(false).run_sync(true).output; + var desc = Utils.exec({"file", "-b", file.get_path()}).log(false).sync(true).output; if(desc != null && DESC_NSIS_INSTALLER in desc) { return InstallerType.WINDOWS_NSIS_INSTALLER; @@ -61,7 +61,7 @@ namespace GameHub.Data.Runnables.Tasks.Install if(type != null) return type; - var info = Utils.run({"file", "-bi", file.get_path()}).log(false).run_sync(true).output; + var info = Utils.exec({"file", "-bi", file.get_path()}).log(false).sync(true).output; if(info != null) { mime = info.split(";")[0]; diff --git a/src/data/runnables/traits/HasExecutableFile.vala b/src/data/runnables/traits/HasExecutableFile.vala index 1bac8cb6..db797ab7 100644 --- a/src/data/runnables/traits/HasExecutableFile.vala +++ b/src/data/runnables/traits/HasExecutableFile.vala @@ -123,7 +123,7 @@ namespace GameHub.Data.Runnables.Traits yield pre_run(); - yield prepare_run_task().run_sync_thread(); + yield prepare_exec_task().sync_thread(); yield post_run(); @@ -149,7 +149,7 @@ namespace GameHub.Data.Runnables.Traits return variables; } - protected virtual RunTask prepare_run_task() + protected virtual ExecTask prepare_exec_task() { string[] cmd = _cmdline; string[] full_cmd = cmd; @@ -185,7 +185,7 @@ namespace GameHub.Data.Runnables.Traits } } - var task = Utils.run(full_cmd).override_runtime(true); + var task = Utils.exec(full_cmd).override_runtime(true); if(work_dir != null && work_dir.query_exists()) task.dir(work_dir.get_path()); return task; diff --git a/src/data/runnables/traits/game/SupportsTweaks.vala b/src/data/runnables/traits/game/SupportsTweaks.vala index aa95e785..e5497bc6 100644 --- a/src/data/runnables/traits/game/SupportsTweaks.vala +++ b/src/data/runnables/traits/game/SupportsTweaks.vala @@ -22,20 +22,20 @@ using GameHub.Data; using GameHub.Data.DB; using GameHub.Data.Tweaks; using GameHub.Data.Runnables; +using GameHub.Utils; namespace GameHub.Data.Runnables.Traits.Game { public interface SupportsTweaks: Runnables.Game { - public abstract string[]? tweaks { get; set; default = null; } + public abstract TweakSet? tweaks { get; set; default = null; } protected void dbinit_tweaks(Sqlite.Statement s) { - var tweaks_string = Tables.Games.TWEAKS.get(s); - if(tweaks_string != null) - { - tweaks = tweaks_string.split(","); - } + tweaks = new TweakSet.from_json(false, Parser.parse_json(Tables.Games.TWEAKS.get(s))); + tweaks.changed.connect(() => { + save(); + }); } public Tweak[] get_enabled_tweaks(CompatTool? tool=null) @@ -44,7 +44,7 @@ namespace GameHub.Data.Runnables.Traits.Game var all_tweaks = Tweak.load_tweaks(); foreach(var tweak in all_tweaks.values) { - if(tweak.is_enabled(this) && tweak.is_applicable_to(this, tool)) + if(tweaks.is_enabled(tweak.id) && tweak.is_applicable_to(this, tool)) { enabled_tweaks += tweak; } diff --git a/src/data/sources/gog/GOGGame.vala b/src/data/sources/gog/GOGGame.vala index ecad03c2..b1d4c71e 100644 --- a/src/data/sources/gog/GOGGame.vala +++ b/src/data/sources/gog/GOGGame.vala @@ -21,6 +21,8 @@ using Gee; using GameHub.Data.DB; using GameHub.Data.Runnables; using GameHub.Data.Runnables.Tasks.Install; +using GameHub.Data.Tweaks; + using GameHub.Utils; using GameHub.Utils.FS; @@ -51,7 +53,7 @@ namespace GameHub.Data.Sources.GOG protected override string? fs_overlay_last_options { get; set; } // Traits.Game.SupportsTweaks - public override string[]? tweaks { get; set; default = null; } + public override TweakSet? tweaks { get; set; default = null; } public ArrayList? bonus_content { get; protected set; default = null; } public ArrayList? dlc { get; protected set; default = null; } @@ -316,7 +318,7 @@ namespace GameHub.Data.Sources.GOG { uninstaller = FS.expand(install_dir.get_path(), uninstaller); debug("[GOGGame] Running uninstaller '%s'...", uninstaller); - yield Utils.run({uninstaller, "--noprompt", "--force"}).override_runtime(true).run_sync_thread(); + yield Utils.exec({uninstaller, "--noprompt", "--force"}).override_runtime(true).sync_thread(); } else { diff --git a/src/data/sources/humble/HumbleGame.vala b/src/data/sources/humble/HumbleGame.vala index 5a72458a..3955a700 100644 --- a/src/data/sources/humble/HumbleGame.vala +++ b/src/data/sources/humble/HumbleGame.vala @@ -21,6 +21,8 @@ using Gee; using GameHub.Data.DB; using GameHub.Data.Runnables; using GameHub.Data.Runnables.Tasks.Install; +using GameHub.Data.Tweaks; + using GameHub.Utils; using GameHub.Utils.FS; @@ -45,7 +47,7 @@ namespace GameHub.Data.Sources.Humble protected override string? fs_overlay_last_options { get; set; } // Traits.Game.SupportsTweaks - public override string[]? tweaks { get; set; default = null; } + public override TweakSet? tweaks { get; set; default = null; } public string order_id; diff --git a/src/data/sources/steam/Steam.vala b/src/data/sources/steam/Steam.vala index a936519b..335c9180 100644 --- a/src/data/sources/steam/Steam.vala +++ b/src/data/sources/steam/Steam.vala @@ -82,19 +82,21 @@ namespace GameHub.Data.Sources.Steam return (!) installed; } - var distro = Utils.get_distro().down(); + #if OS_LINUX + var distro = OS.get_distro().down(); if("ubuntu" in distro || "elementary" in distro || "pop!_os" in distro) { - installed = Utils.is_package_installed("steam") - || Utils.is_package_installed("steam64") - || Utils.is_package_installed("steam-launcher") - || Utils.is_package_installed("steam-installer") + installed = OS.is_package_installed("steam") + || OS.is_package_installed("steam64") + || OS.is_package_installed("steam-launcher") + || OS.is_package_installed("steam-installer") || FS.file(GameHub.Settings.Paths.Steam.instance.home).query_exists(); } else { installed = FS.file(GameHub.Settings.Paths.Steam.instance.home).query_exists(); } + #endif return (!) installed; } @@ -123,11 +125,13 @@ namespace GameHub.Data.Sources.Steam public override async bool install() { - var distro = Utils.get_distro().down(); + #if OS_LINUX + var distro = OS.get_distro().down(); if("elementary" in distro || "pop!_os" in distro) { Utils.open_uri("appstream://steam.desktop"); } + #endif return true; } diff --git a/src/data/sources/user/UserGame.vala b/src/data/sources/user/UserGame.vala index c3ccf727..11f0bdf0 100644 --- a/src/data/sources/user/UserGame.vala +++ b/src/data/sources/user/UserGame.vala @@ -21,6 +21,8 @@ using Gee; using GameHub.Data.DB; using GameHub.Data.Runnables; using GameHub.Data.Runnables.Tasks.Install; +using GameHub.Data.Tweaks; + using GameHub.Utils; using GameHub.Utils.FS; @@ -45,7 +47,7 @@ namespace GameHub.Data.Sources.User protected override string? fs_overlay_last_options { get; set; } // Traits.Game.SupportsTweaks - public override string[]? tweaks { get; set; default = null; } + public override TweakSet? tweaks { get; set; default = null; } private bool is_removed = false; public signal void removed(); diff --git a/src/data/tweaks/ApplicabilityOptions.vala b/src/data/tweaks/ApplicabilityOptions.vala new file mode 100644 index 00000000..6cb11331 --- /dev/null +++ b/src/data/tweaks/ApplicabilityOptions.vala @@ -0,0 +1,181 @@ +/* +This file is part of GameHub. +Copyright (C) 2018-2019 Anatoliy Kashkin + +GameHub is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GameHub is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GameHub. If not, see . +*/ + +using Gee; + +using GameHub.Utils; +using GameHub.Data.Runnables; + +namespace GameHub.Data.Tweaks +{ + public class ApplicabilityOptions: Object + { + public ArrayList? platforms { get; protected set; default = null; } + public ArrayList? compat_tool_ids { get; protected set; default = null; } + + public ApplicabilityOptions(ArrayList? platforms, ArrayList? compat_tool_ids) + { + Object(platforms: platforms, compat_tool_ids: compat_tool_ids); + } + + public ApplicabilityOptions.from_json(Json.Node? json) + { + ArrayList? platforms = null; + ArrayList? compat_tool_ids = null; + + if(json != null && json.get_node_type() == Json.NodeType.OBJECT) + { + var obj = json.get_object(); + + if(obj.has_member("platforms")) + { + var platforms_array = obj.get_array_member("platforms"); + if(platforms_array.get_length() > 0) + { + platforms = new ArrayList(); + foreach(var platform_node in platforms_array.get_elements()) + { + if(platform_node.get_node_type() == Json.NodeType.VALUE) + { + var platform_id = platform_node.get_string(); + if(platform_id != null) + { + foreach(var p in Platform.PLATFORMS) + { + if(platform_id == p.id() && !(p in platforms)) + { + platforms.add(p); + break; + } + } + } + } + } + } + } + + if(obj.has_member("compat")) + { + var tools_array = obj.get_array_member("compat"); + if(tools_array.get_length() > 0) + { + compat_tool_ids = new ArrayList(); + foreach(var tool_node in tools_array.get_elements()) + { + if(tool_node.get_node_type() == Json.NodeType.VALUE) + { + var tool_id = tool_node.get_string(); + if(tool_id != null && !(tool_id in compat_tool_ids)) + { + compat_tool_ids.add(tool_id); + } + } + } + } + } + } + + Object(platforms: platforms, compat_tool_ids: compat_tool_ids); + } + + public bool is_applicable_to(Traits.Game.SupportsTweaks game, CompatTool? compat_tool=null) + { + if(platforms != null) + { + var has_platform = false; + foreach(var platform in platforms) + { + if(platform in game.platforms) + { + has_platform = true; + break; + } + } + if(!has_platform) return false; + } + + string? compat_tool_id = null; + if(compat_tool != null) + { + compat_tool_id = compat_tool.id; + } + else + { + game.cast(game => { + if(game.use_compat) + { + compat_tool_id = game.compat_tool; + } + }); + } + + if(compat_tool_ids != null) + { + var has_tool = false; + if(compat_tool_id != null) + { + foreach(var id in compat_tool_ids) + { + if(compat_tool_id == id || compat_tool_id.has_prefix(@"$(id)_")) + { + has_tool = true; + break; + } + } + } + if(!has_tool) return false; + } + + return true; + } + + public Json.Node? to_json() + { + if((platforms == null || platforms.size == 0) && (compat_tool_ids == null || compat_tool_ids.size == 0)) + { + return null; + } + + var node = new Json.Node(Json.NodeType.OBJECT); + var obj = new Json.Object(); + + if(platforms != null && platforms.size > 0) + { + var platforms_array = new Json.Array.sized(platforms.size); + foreach(var platform in platforms) + { + platforms_array.add_string_element(platform.id()); + } + obj.set_array_member("platforms", platforms_array); + } + + if(compat_tool_ids != null && compat_tool_ids.size > 0) + { + var tools_array = new Json.Array.sized(compat_tool_ids.size); + foreach(var tool_id in compat_tool_ids) + { + tools_array.add_string_element(tool_id); + } + obj.set_array_member("compat", tools_array); + } + + node.set_object(obj); + return node; + } + } +} diff --git a/src/data/tweaks/Option.vala b/src/data/tweaks/Option.vala new file mode 100644 index 00000000..a5f3d9d1 --- /dev/null +++ b/src/data/tweaks/Option.vala @@ -0,0 +1,205 @@ +/* +This file is part of GameHub. +Copyright (C) 2018-2019 Anatoliy Kashkin + +GameHub is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GameHub is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GameHub. If not, see . +*/ + +using Gee; + +using GameHub.Utils; +using GameHub.Data.Runnables; + +namespace GameHub.Data.Tweaks +{ + public class Option: Object + { + public string id { get; protected set; } + public string? name { get; protected set; default = null; } + public string? description { get; protected set; default = null; } + + public Type option_type { get; protected set; default = Type.LIST; } + public string list_separator { get; protected set; default = DEFAULT_LIST_SEPARATOR; } + public string? string_value { get; protected set; default = null; } + + public HashMap? values { get; protected set; default = null; } + public ArrayList? presets { get; protected set; default = null; } + + public Option.from_json(string id, Json.Node? json) + { + string? name = null; + string? description = null; + + Type option_type = Type.LIST; + string list_separator = DEFAULT_LIST_SEPARATOR; + string? string_value = null; + + HashMap? values = null; + ArrayList? presets = null; + + if(json != null && json.get_node_type() == Json.NodeType.OBJECT) + { + var obj = json.get_object(); + + if(obj.has_member("name")) name = obj.get_string_member("name"); + if(obj.has_member("description")) description = obj.get_string_member("description"); + + if(obj.has_member("type")) option_type = Type.from_string(obj.get_string_member("type")); + if(obj.has_member("separator")) list_separator = obj.get_string_member("separator"); + if(obj.has_member("value")) string_value = obj.get_string_member("value"); + + if(obj.has_member("values")) + { + values = new HashMap(); + var values_obj = obj.get_object_member("values"); + foreach(var value in values_obj.get_members()) + { + values.set(value, values_obj.get_string_member(value)); + } + } + + if(obj.has_member("presets")) + { + presets = new ArrayList(); + var presets_obj = obj.get_object_member("presets"); + foreach(var preset_id in presets_obj.get_members()) + { + presets.add(new Preset.from_json(preset_id, presets_obj.get_member(preset_id))); + } + } + } + + Object(id: id, name: name, description: description, option_type: option_type, list_separator: list_separator, string_value: string_value, values: values, presets: presets); + } + + public Preset? get_preset(string id) + { + if(presets == null || presets.size == 0) return null; + foreach(var preset in presets) + { + if(preset.id == id) return preset; + } + return null; + } + + public Json.Node? to_json() + { + var node = new Json.Node(Json.NodeType.OBJECT); + var obj = new Json.Object(); + + obj.set_string_member("id", id); + if(name != null) obj.set_string_member("name", name); + if(description != null) obj.set_string_member("description", description); + + obj.set_string_member("type", option_type.to_string()); + if(list_separator != DEFAULT_LIST_SEPARATOR) obj.set_string_member("separator", list_separator); + if(string_value != null) obj.set_string_member("value", string_value); + + if(values != null && values.size > 0) + { + var values_obj = new Json.Object(); + foreach(var value in values.entries) + { + values_obj.set_string_member(value.key, value.value); + } + obj.set_object_member("values", values_obj); + } + + if(presets != null && presets.size > 0) + { + var presets_obj = new Json.Object(); + foreach(var preset in presets) + { + presets_obj.set_member(preset.id, preset.to_json()); + } + obj.set_object_member("presets", presets_obj); + } + + node.set_object(obj); + return node; + } + + public class Preset: Object + { + public string id { get; protected set; } + public string value { get; protected set; } + public string? name { get; protected set; } + public string? description { get; protected set; } + + public Preset(string id, string value, string? name, string? description) + { + Object(id: id, value: value, name: name, description: description); + } + + public Preset.from_json(string id, Json.Node? json) + { + string value = ""; + string? name = null; + string? description = null; + + if(json != null && json.get_node_type() == Json.NodeType.OBJECT) + { + var obj = json.get_object(); + + if(obj.has_member("value")) value = obj.get_string_member("value"); + if(obj.has_member("name")) name = obj.get_string_member("name"); + if(obj.has_member("description")) description = obj.get_string_member("description"); + } + + Object(id: id, value: value, name: name, description: description); + } + + public Json.Node? to_json() + { + var node = new Json.Node(Json.NodeType.OBJECT); + var obj = new Json.Object(); + + obj.set_string_member("id", id); + obj.set_string_member("value", value); + if(name != null) obj.set_string_member("name", name); + if(description != null) obj.set_string_member("description", description); + + node.set_object(obj); + return node; + } + } + + public enum Type + { + LIST, STRING; + + public string to_string() + { + switch(this) + { + case LIST: return "list"; + case STRING: return "string"; + } + assert_not_reached(); + } + + public static Type from_string(string type) + { + switch(type) + { + case "list": return LIST; + case "string": return STRING; + } + assert_not_reached(); + } + } + + private static string DEFAULT_LIST_SEPARATOR = ","; + } +} diff --git a/src/data/tweaks/Requirements.vala b/src/data/tweaks/Requirements.vala new file mode 100644 index 00000000..e3ae14ed --- /dev/null +++ b/src/data/tweaks/Requirements.vala @@ -0,0 +1,167 @@ +/* +This file is part of GameHub. +Copyright (C) 2018-2019 Anatoliy Kashkin + +GameHub is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GameHub is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GameHub. If not, see . +*/ + +using Gee; + +using GameHub.Utils; +using GameHub.Data.Runnables; + +namespace GameHub.Data.Tweaks +{ + public class Requirements: Object + { + public ArrayList? executables { get; protected set; default = null; } + public ArrayList? kernel_modules { get; protected set; default = null; } + + public Requirements(ArrayList? executables, ArrayList? kernel_modules) + { + Object(executables: executables, kernel_modules: kernel_modules); + } + + public Requirements.from_json(Json.Node? json) + { + ArrayList? executables = null; + ArrayList? kernel_modules = null; + + if(json != null && json.get_node_type() == Json.NodeType.OBJECT) + { + var obj = json.get_object(); + + if(obj.has_member("executables")) + { + var executables_array = obj.get_array_member("executables"); + if(executables_array.get_length() > 0) + { + executables = new ArrayList(); + foreach(var executable_node in executables_array.get_elements()) + { + if(executable_node.get_node_type() == Json.NodeType.VALUE) + { + var executable = executable_node.get_string(); + if(executable != null) + { + executables.add(executable); + } + } + } + } + } + + if(obj.has_member("kernel_modules")) + { + var kmods_array = obj.get_array_member("kernel_modules"); + if(kmods_array.get_length() > 0) + { + kernel_modules = new ArrayList(); + foreach(var kmod_node in kmods_array.get_elements()) + { + if(kmod_node.get_node_type() == Json.NodeType.VALUE) + { + var kmod = kmod_node.get_string(); + if(kmod != null) + { + kernel_modules.add(kmod); + } + } + } + } + } + } + + Object(executables: executables, kernel_modules: kernel_modules); + } + + public Requirements? get_unavailable() + { + var reqs = new Requirements(null, null); + + if(executables != null && executables.size > 0) + { + var has_executable = false; + foreach(var executable in executables) + { + var file = Utils.find_executable(executable); + if(file != null && file.query_exists()) + { + has_executable = true; + break; + } + } + if(!has_executable) + { + reqs.executables = executables; + } + } + + #if OS_LINUX + if(kernel_modules != null && kernel_modules.size > 0) + { + var has_kmod = false; + foreach(var kmod in kernel_modules) + { + if(OS.is_kernel_module_loaded(kmod)) + { + has_kmod = true; + break; + } + } + if(!has_kmod) + { + reqs.kernel_modules = kernel_modules; + } + } + #endif + + return ((reqs.executables == null || reqs.executables.size == 0) && (reqs.kernel_modules == null || reqs.kernel_modules.size == 0)) ? null : reqs; + } + + public Json.Node? to_json() + { + if((executables == null || executables.size == 0) && (kernel_modules == null || kernel_modules.size == 0)) + { + return null; + } + + var node = new Json.Node(Json.NodeType.OBJECT); + var obj = new Json.Object(); + + if(executables != null && executables.size > 0) + { + var executables_array = new Json.Array.sized(executables.size); + foreach(var executable in executables) + { + executables_array.add_string_element(executable); + } + obj.set_array_member("executables", executables_array); + } + + if(kernel_modules != null && kernel_modules.size > 0) + { + var kmods_array = new Json.Array.sized(kernel_modules.size); + foreach(var kmod in kernel_modules) + { + kmods_array.add_string_element(kmod); + } + obj.set_array_member("kernel_modules", kmods_array); + } + + node.set_object(obj); + return node; + } + } +} diff --git a/src/data/tweaks/Tweak.vala b/src/data/tweaks/Tweak.vala index 3e504d55..405e8795 100644 --- a/src/data/tweaks/Tweak.vala +++ b/src/data/tweaks/Tweak.vala @@ -152,58 +152,14 @@ namespace GameHub.Data.Tweaks return requirements.get_unavailable(); } - public bool is_enabled(Traits.Game.SupportsTweaks? game=null) + public Option? get_option(string id) { - if(game == null || game.tweaks == null) + if(options == null || options.size == 0) return null; + foreach(var option in options) { - return id in Settings.Tweaks.instance.global; - } - else - { - return id in game.tweaks; - } - } - - public void set_enabled(bool enabled, Traits.Game.SupportsTweaks? game=null) - { - if(game == null) - { - var global = Settings.Tweaks.instance.global; - if(!enabled && id in global) - { - string[] new_global = {}; - foreach(var t in global) - { - if(t != id) new_global += t; - } - Settings.Tweaks.instance.global = new_global; - } - else if(enabled && !(id in global)) - { - global += id; - Settings.Tweaks.instance.global = global; - } - } - else - { - var game_tweaks = game.tweaks ?? Settings.Tweaks.instance.global; - if(!enabled && id in game_tweaks) - { - string[] new_game_tweaks = {}; - foreach(var t in game_tweaks) - { - if(t != id) new_game_tweaks += t; - } - game.tweaks = new_game_tweaks; - game.save(); - } - else if(enabled && !(id in game_tweaks)) - { - game_tweaks += id; - game.tweaks = game_tweaks; - game.save(); - } + if(option.id == id) return option; } + return null; } public string icon @@ -372,471 +328,5 @@ namespace GameHub.Data.Tweaks return obj; } - - public class ApplicabilityOptions: Object - { - public ArrayList? platforms { get; protected set; default = null; } - public ArrayList? compat_tool_ids { get; protected set; default = null; } - - public ApplicabilityOptions(ArrayList? platforms, ArrayList? compat_tool_ids) - { - Object(platforms: platforms, compat_tool_ids: compat_tool_ids); - } - - public ApplicabilityOptions.from_json(Json.Node? json) - { - ArrayList? platforms = null; - ArrayList? compat_tool_ids = null; - - if(json != null && json.get_node_type() == Json.NodeType.OBJECT) - { - var obj = json.get_object(); - - if(obj.has_member("platforms")) - { - var platforms_array = obj.get_array_member("platforms"); - if(platforms_array.get_length() > 0) - { - platforms = new ArrayList(); - foreach(var platform_node in platforms_array.get_elements()) - { - if(platform_node.get_node_type() == Json.NodeType.VALUE) - { - var platform_id = platform_node.get_string(); - if(platform_id != null) - { - foreach(var p in Platform.PLATFORMS) - { - if(platform_id == p.id() && !(p in platforms)) - { - platforms.add(p); - break; - } - } - } - } - } - } - } - - if(obj.has_member("compat")) - { - var tools_array = obj.get_array_member("compat"); - if(tools_array.get_length() > 0) - { - compat_tool_ids = new ArrayList(); - foreach(var tool_node in tools_array.get_elements()) - { - if(tool_node.get_node_type() == Json.NodeType.VALUE) - { - var tool_id = tool_node.get_string(); - if(tool_id != null && !(tool_id in compat_tool_ids)) - { - compat_tool_ids.add(tool_id); - } - } - } - } - } - } - - Object(platforms: platforms, compat_tool_ids: compat_tool_ids); - } - - public bool is_applicable_to(Traits.Game.SupportsTweaks game, CompatTool? compat_tool=null) - { - if(platforms != null) - { - var has_platform = false; - foreach(var platform in platforms) - { - if(platform in game.platforms) - { - has_platform = true; - break; - } - } - if(!has_platform) return false; - } - - string? compat_tool_id = null; - if(compat_tool != null) - { - compat_tool_id = compat_tool.id; - } - else - { - game.cast(game => { - if(game.use_compat) - { - compat_tool_id = game.compat_tool; - } - }); - } - - if(compat_tool_ids != null) - { - var has_tool = false; - if(compat_tool_id != null) - { - foreach(var id in compat_tool_ids) - { - if(compat_tool_id == id || compat_tool_id.has_prefix(@"$(id)_")) - { - has_tool = true; - break; - } - } - } - if(!has_tool) return false; - } - - return true; - } - - public Json.Node? to_json() - { - if((platforms == null || platforms.size == 0) && (compat_tool_ids == null || compat_tool_ids.size == 0)) - { - return null; - } - - var node = new Json.Node(Json.NodeType.OBJECT); - var obj = new Json.Object(); - - if(platforms != null && platforms.size > 0) - { - var platforms_array = new Json.Array.sized(platforms.size); - foreach(var platform in platforms) - { - platforms_array.add_string_element(platform.id()); - } - obj.set_array_member("platforms", platforms_array); - } - - if(compat_tool_ids != null && compat_tool_ids.size > 0) - { - var tools_array = new Json.Array.sized(compat_tool_ids.size); - foreach(var tool_id in compat_tool_ids) - { - tools_array.add_string_element(tool_id); - } - obj.set_array_member("compat", tools_array); - } - - node.set_object(obj); - return node; - } - } - - public class Requirements: Object - { - public ArrayList? executables { get; protected set; default = null; } - public ArrayList? kernel_modules { get; protected set; default = null; } - - public Requirements(ArrayList? executables, ArrayList? kernel_modules) - { - Object(executables: executables, kernel_modules: kernel_modules); - } - - public Requirements.from_json(Json.Node? json) - { - ArrayList? executables = null; - ArrayList? kernel_modules = null; - - if(json != null && json.get_node_type() == Json.NodeType.OBJECT) - { - var obj = json.get_object(); - - if(obj.has_member("executables")) - { - var executables_array = obj.get_array_member("executables"); - if(executables_array.get_length() > 0) - { - executables = new ArrayList(); - foreach(var executable_node in executables_array.get_elements()) - { - if(executable_node.get_node_type() == Json.NodeType.VALUE) - { - var executable = executable_node.get_string(); - if(executable != null) - { - executables.add(executable); - } - } - } - } - } - - if(obj.has_member("kernel_modules")) - { - var kmods_array = obj.get_array_member("kernel_modules"); - if(kmods_array.get_length() > 0) - { - kernel_modules = new ArrayList(); - foreach(var kmod_node in kmods_array.get_elements()) - { - if(kmod_node.get_node_type() == Json.NodeType.VALUE) - { - var kmod = kmod_node.get_string(); - if(kmod != null) - { - kernel_modules.add(kmod); - } - } - } - } - } - } - - Object(executables: executables, kernel_modules: kernel_modules); - } - - public Requirements? get_unavailable() - { - var reqs = new Requirements(null, null); - - if(executables != null && executables.size > 0) - { - var has_executable = false; - foreach(var executable in executables) - { - var file = Utils.find_executable(executable); - if(file != null && file.query_exists()) - { - has_executable = true; - break; - } - } - if(!has_executable) - { - reqs.executables = executables; - } - } - - if(kernel_modules != null && kernel_modules.size > 0) - { - var has_kmod = false; - foreach(var kmod in kernel_modules) - { - if(Utils.is_kernel_module_loaded(kmod)) - { - has_kmod = true; - break; - } - } - if(!has_kmod) - { - reqs.kernel_modules = kernel_modules; - } - } - - return ((reqs.executables == null || reqs.executables.size == 0) && (reqs.kernel_modules == null || reqs.kernel_modules.size == 0)) ? null : reqs; - } - - public Json.Node? to_json() - { - if((executables == null || executables.size == 0) && (kernel_modules == null || kernel_modules.size == 0)) - { - return null; - } - - var node = new Json.Node(Json.NodeType.OBJECT); - var obj = new Json.Object(); - - if(executables != null && executables.size > 0) - { - var executables_array = new Json.Array.sized(executables.size); - foreach(var executable in executables) - { - executables_array.add_string_element(executable); - } - obj.set_array_member("executables", executables_array); - } - - if(kernel_modules != null && kernel_modules.size > 0) - { - var kmods_array = new Json.Array.sized(kernel_modules.size); - foreach(var kmod in kernel_modules) - { - kmods_array.add_string_element(kmod); - } - obj.set_array_member("kernel_modules", kmods_array); - } - - node.set_object(obj); - return node; - } - } - - public class Option: Object - { - public string id { get; protected set; } - public string? name { get; protected set; default = null; } - public string? description { get; protected set; default = null; } - - public Type option_type { get; protected set; default = Type.LIST; } - public string list_separator { get; protected set; default = DEFAULT_LIST_SEPARATOR; } - public string? string_value { get; protected set; default = null; } - - public HashMap? values { get; protected set; default = null; } - public ArrayList? presets { get; protected set; default = null; } - - public Option.from_json(string id, Json.Node? json) - { - string? name = null; - string? description = null; - - Type option_type = Type.LIST; - string list_separator = DEFAULT_LIST_SEPARATOR; - string? string_value = null; - - HashMap? values = null; - ArrayList? presets = null; - - if(json != null && json.get_node_type() == Json.NodeType.OBJECT) - { - var obj = json.get_object(); - - if(obj.has_member("name")) name = obj.get_string_member("name"); - if(obj.has_member("description")) description = obj.get_string_member("description"); - - if(obj.has_member("type")) option_type = Type.from_string(obj.get_string_member("type")); - if(obj.has_member("separator")) list_separator = obj.get_string_member("separator"); - if(obj.has_member("value")) string_value = obj.get_string_member("value"); - - if(obj.has_member("values")) - { - values = new HashMap(); - var values_obj = obj.get_object_member("values"); - foreach(var value in values_obj.get_members()) - { - values.set(value, values_obj.get_string_member(value)); - } - } - - if(obj.has_member("presets")) - { - presets = new ArrayList(); - var presets_obj = obj.get_object_member("presets"); - foreach(var preset_id in presets_obj.get_members()) - { - presets.add(new Preset.from_json(preset_id, presets_obj.get_member(preset_id))); - } - } - } - - Object(id: id, name: name, description: description, option_type: option_type, list_separator: list_separator, string_value: string_value, values: values, presets: presets); - } - - public Json.Node? to_json() - { - var node = new Json.Node(Json.NodeType.OBJECT); - var obj = new Json.Object(); - - obj.set_string_member("id", id); - if(name != null) obj.set_string_member("name", name); - if(description != null) obj.set_string_member("description", description); - - obj.set_string_member("type", option_type.to_string()); - if(list_separator != DEFAULT_LIST_SEPARATOR) obj.set_string_member("separator", list_separator); - if(string_value != null) obj.set_string_member("value", string_value); - - if(values != null && values.size > 0) - { - var values_obj = new Json.Object(); - foreach(var value in values.entries) - { - values_obj.set_string_member(value.key, value.value); - } - obj.set_object_member("values", values_obj); - } - - if(presets != null && presets.size > 0) - { - var presets_obj = new Json.Object(); - foreach(var preset in presets) - { - presets_obj.set_member(preset.id, preset.to_json()); - } - obj.set_object_member("presets", presets_obj); - } - - node.set_object(obj); - return node; - } - - public class Preset: Object - { - public string id { get; protected set; } - public string value { get; protected set; } - public string? name { get; protected set; } - public string? description { get; protected set; } - - public Preset(string id, string value, string? name, string? description) - { - Object(id: id, value: value, name: name, description: description); - } - - public Preset.from_json(string id, Json.Node? json) - { - string value = ""; - string? name = null; - string? description = null; - - if(json != null && json.get_node_type() == Json.NodeType.OBJECT) - { - var obj = json.get_object(); - - if(obj.has_member("value")) value = obj.get_string_member("value"); - if(obj.has_member("name")) name = obj.get_string_member("name"); - if(obj.has_member("description")) description = obj.get_string_member("description"); - } - - Object(id: id, value: value, name: name, description: description); - } - - public Json.Node? to_json() - { - var node = new Json.Node(Json.NodeType.OBJECT); - var obj = new Json.Object(); - - obj.set_string_member("id", id); - obj.set_string_member("value", value); - if(name != null) obj.set_string_member("name", name); - if(description != null) obj.set_string_member("description", description); - - node.set_object(obj); - return node; - } - } - - public enum Type - { - LIST, STRING; - - public string to_string() - { - switch(this) - { - case LIST: return "list"; - case STRING: return "string"; - } - assert_not_reached(); - } - - public static Type from_string(string type) - { - switch(type) - { - case "list": return LIST; - case "string": return STRING; - } - assert_not_reached(); - } - } - - private static string DEFAULT_LIST_SEPARATOR = ","; - } } } diff --git a/src/data/tweaks/TweakOptions.vala b/src/data/tweaks/TweakOptions.vala new file mode 100644 index 00000000..98925a3b --- /dev/null +++ b/src/data/tweaks/TweakOptions.vala @@ -0,0 +1,278 @@ +/* +This file is part of GameHub. +Copyright (C) 2018-2019 Anatoliy Kashkin + +GameHub is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GameHub is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GameHub. If not, see . +*/ + +using Gee; + +using GameHub.Utils; +using GameHub.Data.Runnables; + +namespace GameHub.Data.Tweaks +{ + public class TweakOptions: Object + { + public Tweak tweak { get; set; } + public State state { get; set; default = State.GLOBAL; } + public ArrayList? properties { get; protected set; default = null; } + + public TweakOptions(Tweak tweak, State state = State.GLOBAL, ArrayList? properties = null) + { + Object(tweak: tweak, state: state, properties: properties); + } + + public TweakOptions.from_json(Tweak tweak, Json.Node? json) + { + State state = State.GLOBAL; + ArrayList? properties = null; + + if(json != null && json.get_node_type() == Json.NodeType.OBJECT) + { + var obj = json.get_object(); + + if(obj.has_member("state")) state = State.from_string(obj.get_string_member("state")); + + if(obj.has_member("options")) + { + properties = new ArrayList(); + obj.get_object_member("options").foreach_member((obj, id, node) => { + var option = tweak.get_option(id); + if(option != null) + { + properties.add(new OptionProperties.from_json(option, node)); + } + }); + } + } + + Object(tweak: tweak, state: state, properties: properties); + } + + public OptionProperties? get_properties_for_id(string option_id) + { + if(properties == null || properties.size == 0) return null; + foreach(var opt_props in properties) + { + if(opt_props.option.id == option_id) return opt_props; + } + return null; + } + + public void set_properties_for_id(string option_id, OptionProperties? opt_props) + { + var old_props = get_properties_for_id(option_id); + if(old_props != null) + { + properties.remove(old_props); + } + if(opt_props != null) + { + if(properties == null) + { + properties = new ArrayList(); + } + properties.add(opt_props); + } + } + + public OptionProperties? get_properties_for_option(Option option) + { + return get_properties_for_id(option.id); + } + + public void set_properties_for_option(Option option, OptionProperties? opt_props) + { + set_properties_for_id(option.id, opt_props); + } + + public OptionProperties get_or_create_properties(Option option) + { + var props = get_properties_for_option(option); + if(props == null) + { + props = new OptionProperties(option, option.presets != null && option.presets.size > 0 ? option.presets.first() : null); + } + return props; + } + + public string expand(string value) + { + var result = value; + if(properties != null && properties.size > 0) + { + foreach(var option_props in properties) + { + var option_value = option_props.value; + result = result.replace("${option:%s}".printf(option_props.option.id), option_props.value); + } + } + return result; + } + + public Json.Node? to_json() + { + var node = new Json.Node(Json.NodeType.OBJECT); + var obj = new Json.Object(); + + obj.set_string_member("state", state.to_string()); + + if(properties != null && properties.size > 0) + { + var options = new Json.Object(); + foreach(var opt_props in properties) + { + options.set_member(opt_props.option.id, opt_props.to_json()); + } + obj.set_object_member("options", options); + } + + node.set_object(obj); + return node; + } + + public class OptionProperties: Object + { + public Option option { get; set; } + public Option.Preset? preset { get; set; default = null; } + public string[]? selected_values { get; set; default = null; } + public string? string_value { get; set; default = null; } + + public OptionProperties(Option option, Option.Preset? preset = null, string[]? selected_values = null, string? string_value = null) + { + Object(option: option, preset: preset, selected_values: selected_values, string_value: string_value); + } + + public OptionProperties.from_json(Option option, Json.Node? json) + { + Option.Preset? preset = null; + string[]? selected_values = null; + string? string_value = null; + + if(json != null && json.get_node_type() == Json.NodeType.OBJECT) + { + var obj = json.get_object(); + + if(option.presets != null && option.presets.size > 0 && obj.has_member("preset")) + { + preset = option.get_preset(obj.get_string_member("preset")); + } + + if(option.option_type == Option.Type.LIST && option.values != null && option.values.size > 0 && obj.has_member("values")) + { + selected_values = {}; + var value_nodes = obj.get_array_member("values").get_elements(); + foreach(var node in value_nodes) + { + if(node.get_node_type() == Json.NodeType.VALUE) + { + selected_values += node.get_string(); + } + } + } + else if(option.option_type == Option.Type.STRING && obj.has_member("value")) + { + string_value = obj.get_string_member("value"); + } + } + + Object(option: option, preset: preset, selected_values: selected_values, string_value: string_value); + } + + public string? value + { + owned get + { + if(preset != null) + { + return preset.value; + } + else + { + switch(option.option_type) + { + case Option.Type.LIST: + if(selected_values != null) + { + return string.joinv(option.list_separator, selected_values); + } + break; + + case Option.Type.STRING: + if(string_value != null) + { + return option.string_value.replace("${value}", string_value).replace("$value", string_value); + } + break; + } + } + return null; + } + } + + public Json.Node to_json() + { + var node = new Json.Node(Json.NodeType.OBJECT); + var obj = new Json.Object(); + + if(preset != null) obj.set_string_member("preset", preset.id); + + if(option.option_type == Option.Type.LIST && option.values != null && option.values.size > 0 && selected_values != null) + { + var values = new Json.Array(); + foreach(var value in selected_values) + { + values.add_string_element(value); + } + obj.set_array_member("values", values); + } + else if(option.option_type == Option.Type.STRING && string_value != null) + { + obj.set_string_member("value", string_value); + } + + node.set_object(obj); + return node; + } + } + + public enum State + { + ENABLED, DISABLED, GLOBAL; + + public string to_string() + { + switch(this) + { + case ENABLED: return "enabled"; + case DISABLED: return "disabled"; + case GLOBAL: return "global"; + } + assert_not_reached(); + } + + public static State from_string(string state) + { + switch(state) + { + case "enabled": return ENABLED; + case "disabled": return DISABLED; + case "global": return GLOBAL; + } + assert_not_reached(); + } + } + } +} diff --git a/src/data/tweaks/TweakSet.vala b/src/data/tweaks/TweakSet.vala new file mode 100644 index 00000000..f07b9907 --- /dev/null +++ b/src/data/tweaks/TweakSet.vala @@ -0,0 +1,131 @@ +/* +This file is part of GameHub. +Copyright (C) 2018-2019 Anatoliy Kashkin + +GameHub is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GameHub is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GameHub. If not, see . +*/ + +using Gee; + +using GameHub.Utils; +using GameHub.Data.Runnables; + +namespace GameHub.Data.Tweaks +{ + public class TweakSet: Object + { + public bool is_global { get; construct; default = false; } + public ArrayList? tweaks { get; set; default = null; } + + public signal void changed(); + + public TweakSet.from_json(bool is_global, Json.Node? json) + { + ArrayList tweaks = null; + + if(json != null && json.get_node_type() == Json.NodeType.OBJECT) + { + var all_tweaks = Tweak.load_tweaks(); + tweaks = new ArrayList(); + + json.get_object().foreach_member((obj, tweak_id, node) => { + foreach(var tweak in all_tweaks.values) + { + if(tweak.id == tweak_id) + { + tweaks.add(new TweakOptions.from_json(tweak, node)); + } + } + }); + } + + Object(is_global: is_global, tweaks: tweaks); + } + + public TweakOptions? get_options_for_id(string tweak_id) + { + if(tweaks == null || tweaks.size == 0) return null; + foreach(var tweak_options in tweaks) + { + if(tweak_options.tweak.id == tweak_id) return tweak_options; + } + return null; + } + + public void set_options_for_id(string tweak_id, TweakOptions? tweak_options) + { + var old_opts = get_options_for_id(tweak_id); + if(old_opts != null) + { + tweaks.remove(old_opts); + } + if(tweak_options != null) + { + if(tweaks == null) + { + tweaks = new ArrayList(); + } + tweaks.add(tweak_options); + } + changed(); + } + + public TweakOptions? get_options_for_tweak(Tweak tweak) + { + return get_options_for_id(tweak.id); + } + + public void set_options_for_tweak(Tweak tweak, TweakOptions? tweak_options) + { + set_options_for_id(tweak.id, tweak_options); + } + + public TweakOptions get_or_create_options(Tweak tweak) + { + return get_options_for_tweak(tweak) ?? new TweakOptions(tweak); + } + + public bool is_enabled(string tweak_id) + { + var opts = get_options_for_id(tweak_id); + if(is_global) + { + return opts != null && opts.state == TweakOptions.State.ENABLED; + } + if(opts != null && opts.state != TweakOptions.State.GLOBAL) + { + return opts.state == TweakOptions.State.ENABLED; + } + var global_opts = GameHub.Settings.Tweaks.global_tweakset.get_options_for_id(tweak_id); + return global_opts != null && global_opts.state == TweakOptions.State.ENABLED; + } + + public Json.Node? to_json() + { + var node = new Json.Node(Json.NodeType.OBJECT); + var obj = new Json.Object(); + + if(tweaks != null && tweaks.size > 0) + { + foreach(var tweak_options in tweaks) + { + obj.set_member(tweak_options.tweak.id, tweak_options.to_json()); + } + } + + node.set_object(obj); + return node; + } + } +} diff --git a/src/meson.build b/src/meson.build index 27b8724c..9fca70fd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -111,6 +111,11 @@ sources = [ 'data/providers/data/IGDB.vala', 'data/tweaks/Tweak.vala', + 'data/tweaks/Option.vala', + 'data/tweaks/ApplicabilityOptions.vala', + 'data/tweaks/Requirements.vala', + 'data/tweaks/TweakOptions.vala', + 'data/tweaks/TweakSet.vala', 'ui/windows/MainWindow.vala', 'ui/windows/WebAuthWindow.vala', @@ -121,6 +126,7 @@ sources = [ 'ui/dialogs/SettingsDialog/pages/ui/Appearance.vala', 'ui/dialogs/SettingsDialog/pages/ui/Behavior.vala', 'ui/dialogs/SettingsDialog/pages/general/Collection.vala', + 'ui/dialogs/SettingsDialog/pages/general/CompatTools.vala', 'ui/dialogs/SettingsDialog/pages/general/Tweaks.vala', 'ui/dialogs/SettingsDialog/pages/sources/Steam.vala', 'ui/dialogs/SettingsDialog/pages/sources/GOG.vala', @@ -138,6 +144,7 @@ sources = [ 'ui/dialogs/GamePropertiesDialog/GamePropertiesDialog.vala', 'ui/dialogs/GamePropertiesDialog/tabs/General.vala', + 'ui/dialogs/GamePropertiesDialog/tabs/Tweaks.vala', 'ui/dialogs/GamePropertiesDialog/tabs/Overlays.vala', 'ui/dialogs/GameDetailsDialog.vala', @@ -192,6 +199,8 @@ sources = [ 'ui/widgets/Welcome.vala', 'ui/widgets/DirectoriesList.vala', + 'ui/widgets/compat/CompatToolsList.vala', + 'ui/widgets/settings/SettingsSidebar.vala', 'ui/widgets/settings/SettingsGroup.vala', 'ui/widgets/settings/Settings.vala', @@ -201,6 +210,8 @@ sources = [ 'ui/widgets/tweaks/TweakOptionsPopover.vala', 'utils/Utils.vala', + 'utils/ExecTask.vala', + 'utils/OS.vala', 'utils/fs/FS.vala', 'utils/fs/FSOverlay.vala', diff --git a/src/settings/Tweaks.vala b/src/settings/Tweaks.vala index 3aafa1f7..11197080 100644 --- a/src/settings/Tweaks.vala +++ b/src/settings/Tweaks.vala @@ -16,13 +16,14 @@ You should have received a copy of the GNU General Public License along with GameHub. If not, see . */ - +using GameHub.Data.Tweaks; +using GameHub.Utils; namespace GameHub.Settings { public class Tweaks: SettingsSchema { - public string[] global { get; set; } + public string global { get; set; } public Tweaks() { @@ -41,5 +42,21 @@ namespace GameHub.Settings return _instance; } } + + private static TweakSet? _global_tweakset; + public static unowned TweakSet global_tweakset + { + get + { + if(_global_tweakset == null) + { + _global_tweakset = new TweakSet.from_json(true, Parser.parse_json(instance.global)); + _global_tweakset.changed.connect(() => { + instance.global = Json.to_string(_global_tweakset.to_json(), false); + }); + } + return _global_tweakset; + } + } } } diff --git a/src/ui/dialogs/GamePropertiesDialog/GamePropertiesDialog.vala b/src/ui/dialogs/GamePropertiesDialog/GamePropertiesDialog.vala index ecc42362..bfbaa4b9 100644 --- a/src/ui/dialogs/GamePropertiesDialog/GamePropertiesDialog.vala +++ b/src/ui/dialogs/GamePropertiesDialog/GamePropertiesDialog.vala @@ -99,8 +99,8 @@ namespace GameHub.UI.Dialogs.GamePropertiesDialog game.cast(game => add_tab(new DummyTab(_("Executable")))); game.cast(game => add_tab(new DummyTab(_("Compatibility")))); - game.cast(game => add_tab(new DummyTab(_("Tweaks")))); + game.cast(game => add_tab(new Tabs.Tweaks(game))); game.cast(game => add_tab(new Tabs.Overlays(game))); tabs.show_tabs = tabs.get_n_pages() > 1; diff --git a/src/ui/dialogs/GamePropertiesDialog/tabs/Overlays.vala b/src/ui/dialogs/GamePropertiesDialog/tabs/Overlays.vala index d9f62e29..dacfbefc 100644 --- a/src/ui/dialogs/GamePropertiesDialog/tabs/Overlays.vala +++ b/src/ui/dialogs/GamePropertiesDialog/tabs/Overlays.vala @@ -116,7 +116,7 @@ namespace GameHub.UI.Dialogs.GamePropertiesDialog.Tabs enable_btn = new Button.with_label(_("Enable overlays")); enable_btn.get_style_context().add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION); - enable_btn.grab_default(); + //enable_btn.grab_default(); /*response.connect((source, response_id) => { switch(response_id) diff --git a/src/ui/dialogs/GamePropertiesDialog/tabs/Tweaks.vala b/src/ui/dialogs/GamePropertiesDialog/tabs/Tweaks.vala new file mode 100644 index 00000000..0ebfc413 --- /dev/null +++ b/src/ui/dialogs/GamePropertiesDialog/tabs/Tweaks.vala @@ -0,0 +1,54 @@ +/* +This file is part of GameHub. +Copyright (C) 2018-2019 Anatoliy Kashkin + +GameHub is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GameHub is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GameHub. If not, see . +*/ + +using Gtk; + +using GameHub.Data; +using GameHub.Data.Runnables; + +using GameHub.Utils; +using GameHub.Utils.FS; + +using GameHub.UI.Widgets; +using GameHub.UI.Widgets.Tweaks; +using GameHub.UI.Widgets.Settings; + +namespace GameHub.UI.Dialogs.GamePropertiesDialog.Tabs +{ + private class Tweaks: GamePropertiesDialogTab + { + public Traits.Game.SupportsTweaks game { get; construct; } + + public Tweaks(Traits.Game.SupportsTweaks game) + { + Object( + game: game, + title: _("Tweaks"), + orientation: Orientation.HORIZONTAL + ); + } + + construct + { + var sgrp_tweaks = new SettingsGroupBox(); + sgrp_tweaks.container.get_style_context().remove_class(Gtk.STYLE_CLASS_VIEW); + sgrp_tweaks.add_widget(new TweaksList(game)); + add(sgrp_tweaks); + } + } +} diff --git a/src/ui/dialogs/ImportEmulatedGamesDialog.vala b/src/ui/dialogs/ImportEmulatedGamesDialog.vala index 88f548bb..b3ececdb 100644 --- a/src/ui/dialogs/ImportEmulatedGamesDialog.vala +++ b/src/ui/dialogs/ImportEmulatedGamesDialog.vala @@ -266,7 +266,7 @@ namespace GameHub.UI.Dialogs sp = sp.substring(sp.index_of_nth_char(2)); } - var files_list = Utils.run({"find", root.get_path(), "-path", "*/" + sp, "-type", "f"}).log(false).run_sync(true).output; + var files_list = Utils.exec({"find", root.get_path(), "-path", "*/" + sp, "-type", "f"}).log(false).sync(true).output; var files = files_list.split("\n"); foreach(var file_path in files) @@ -383,7 +383,7 @@ namespace GameHub.UI.Dialogs ext = ext.strip(); if(ext in ignored_extensions) continue; - var files_list = Utils.run({"find", root.get_path(), "-path", "*/*." + ext, "-type", "f"}).log(false).run_sync(true).output; + var files_list = Utils.exec({"find", root.get_path(), "-path", "*/*." + ext, "-type", "f"}).log(false).sync(true).output; var files = files_list.split("\n"); foreach(var file_path in files) @@ -629,7 +629,7 @@ namespace GameHub.UI.Dialogs sp = sp.replace("${basename}", basename).replace("$basename", basename); - var files_list = Utils.run({"find", directory.get_path(), "-path", "*/" + sp}).log(false).run_sync(true).output; + var files_list = Utils.exec({"find", directory.get_path(), "-path", "*/" + sp}).log(false).sync(true).output; var files = files_list.split("\n"); foreach(var file_path in files) diff --git a/src/ui/dialogs/SettingsDialog/SettingsDialog.vala b/src/ui/dialogs/SettingsDialog/SettingsDialog.vala index 86707340..1283dd9a 100644 --- a/src/ui/dialogs/SettingsDialog/SettingsDialog.vala +++ b/src/ui/dialogs/SettingsDialog/SettingsDialog.vala @@ -103,6 +103,7 @@ namespace GameHub.UI.Dialogs.SettingsDialog #if MANETTE add_page("general/controller", new Pages.General.Controller(this)); #endif + add_page("general/compat", new Pages.General.CompatTools(this)); add_page("general/tweaks", new Pages.General.Tweaks(this)); add_page("sources/steam", new Pages.Sources.Steam(this)); diff --git a/src/ui/dialogs/SettingsDialog/pages/About.vala b/src/ui/dialogs/SettingsDialog/pages/About.vala index ee39ac84..697540b3 100644 --- a/src/ui/dialogs/SettingsDialog/pages/About.vala +++ b/src/ui/dialogs/SettingsDialog/pages/About.vala @@ -138,10 +138,10 @@ namespace GameHub.UI.Dialogs.SettingsDialog.Pages info += "- Environment\n"; #if OS_LINUX - info += " Distro: %s\n".printf(Utils.get_distro()); - info += " DE: %s\n".printf(Utils.get_desktop_environment() ?? "unknown"); + info += " Distro: %s\n".printf(OS.get_distro()); + info += " DE: %s\n".printf(OS.get_desktop_environment() ?? "unknown"); #else - info += " OS: %s\n".printf(Utils.get_distro()); + info += " OS: %s\n".printf(OS.get_distro()); #endif info += " GTK: %u.%u.%u\n".printf(Gtk.get_major_version(), Gtk.get_minor_version(), Gtk.get_micro_version()); diff --git a/src/ui/dialogs/SettingsDialog/pages/general/CompatTools.vala b/src/ui/dialogs/SettingsDialog/pages/general/CompatTools.vala new file mode 100644 index 00000000..6f2c9b67 --- /dev/null +++ b/src/ui/dialogs/SettingsDialog/pages/general/CompatTools.vala @@ -0,0 +1,47 @@ +/* +This file is part of GameHub. +Copyright (C) 2018-2019 Anatoliy Kashkin + +GameHub is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GameHub is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GameHub. If not, see . +*/ + +using Gtk; +using GameHub.UI.Widgets; +using GameHub.UI.Widgets.Compat; +using GameHub.UI.Widgets.Settings; + +using GameHub.Utils; + +namespace GameHub.UI.Dialogs.SettingsDialog.Pages.General +{ + public class CompatTools: SettingsDialogPage + { + public CompatTools(SettingsDialog dlg) + { + Object( + dialog: dlg, + title: _("Compatibility layers"), + icon_name: "application-x-executable-symbolic" + ); + } + + construct + { + var sgrp_compat = new SettingsGroupBox(); + sgrp_compat.container.get_style_context().remove_class(Gtk.STYLE_CLASS_VIEW); + sgrp_compat.add_widget(new CompatToolsList()); + add_widget(sgrp_compat); + } + } +} diff --git a/src/ui/views/GamesView/GamesView.vala b/src/ui/views/GamesView/GamesView.vala index af441fd8..3e5987ec 100644 --- a/src/ui/views/GamesView/GamesView.vala +++ b/src/ui/views/GamesView/GamesView.vala @@ -244,11 +244,11 @@ namespace GameHub.UI.Views.GamesView search.activate.connect(search_run_first_matching_game); ui_settings.notify["icon-style"].connect(() => { - (filters.image as Image).icon_name = "tag" + Settings.UI.Appearance.symbolic_icon_suffix; - (add_game_button.image as Image).icon_name = "list-add" + Settings.UI.Appearance.symbolic_icon_suffix; - (downloads.image as Image).icon_name = "folder-download" + Settings.UI.Appearance.symbolic_icon_suffix; - (settings.image as Image).icon_name = "open-menu" + Settings.UI.Appearance.symbolic_icon_suffix; - (filters.image as Image).icon_size = (add_game_button.image as Image).icon_size = (downloads.image as Image).icon_size = (settings.image as Image).icon_size = Settings.UI.Appearance.headerbar_icon_size; + ((Image) filters.image).icon_name = "tag" + Settings.UI.Appearance.symbolic_icon_suffix; + ((Image) add_game_button.image).icon_name = "list-add" + Settings.UI.Appearance.symbolic_icon_suffix; + ((Image) downloads.image).icon_name = "folder-download" + Settings.UI.Appearance.symbolic_icon_suffix; + ((Image) settings.image).icon_name = "open-menu" + Settings.UI.Appearance.symbolic_icon_suffix; + ((Image) filters.image).icon_size = ((Image) add_game_button.image).icon_size = ((Image) downloads.image).icon_size = ((Image) settings.image).icon_size = Settings.UI.Appearance.headerbar_icon_size; }); filters_popover.filters_changed.connect(() => { diff --git a/src/ui/views/GamesView/grid/GameCard.vala b/src/ui/views/GamesView/grid/GameCard.vala index 867a88be..99095f8c 100644 --- a/src/ui/views/GamesView/grid/GameCard.vala +++ b/src/ui/views/GamesView/grid/GameCard.vala @@ -458,7 +458,7 @@ namespace GameHub.UI.Views.GamesView.Grid private void updates_handler() { Idle.add(() => { - updated_icon.visible = game is GameHub.Data.Sources.GOG.GOGGame && (game as GameHub.Data.Sources.GOG.GOGGame).has_updates; + updated_icon.visible = game is GameHub.Data.Sources.GOG.GOGGame && ((GameHub.Data.Sources.GOG.GOGGame) game).has_updates; return Source.REMOVE; }, Priority.LOW); } diff --git a/src/ui/views/GamesView/list/GameListRow.vala b/src/ui/views/GamesView/list/GameListRow.vala index c71ff54c..4eb3240d 100644 --- a/src/ui/views/GamesView/list/GameListRow.vala +++ b/src/ui/views/GamesView/list/GameListRow.vala @@ -286,7 +286,7 @@ namespace GameHub.UI.Views.GamesView.List private void updates_handler() { Idle.add(() => { - updated_icon.visible = game is GameHub.Data.Sources.GOG.GOGGame && (game as GameHub.Data.Sources.GOG.GOGGame).has_updates; + updated_icon.visible = game is GameHub.Data.Sources.GOG.GOGGame && ((GameHub.Data.Sources.GOG.GOGGame) game).has_updates; return Source.REMOVE; }, Priority.LOW); } diff --git a/src/ui/views/WelcomeView.vala b/src/ui/views/WelcomeView.vala index 1029a4ab..611d0749 100644 --- a/src/ui/views/WelcomeView.vala +++ b/src/ui/views/WelcomeView.vala @@ -80,8 +80,8 @@ namespace GameHub.UI.Views settings.action_name = Application.ACTION_PREFIX + Application.ACTION_SETTINGS; ui_settings.notify["symbolic-icons"].connect(() => { - (settings.image as Image).icon_name = "open-menu" + Settings.UI.Appearance.symbolic_icon_suffix; - (settings.image as Image).icon_size = Settings.UI.Appearance.headerbar_icon_size; + ((Image) settings.image).icon_name = "open-menu" + Settings.UI.Appearance.symbolic_icon_suffix; + ((Image) settings.image).icon_size = Settings.UI.Appearance.headerbar_icon_size; }); empty_alert.action_activated.connect(() => settings.clicked()); diff --git a/src/ui/widgets/CompatToolOptions.vala b/src/ui/widgets/CompatToolOptions.vala index 25803050..ccfe81ac 100644 --- a/src/ui/widgets/CompatToolOptions.vala +++ b/src/ui/widgets/CompatToolOptions.vala @@ -34,7 +34,7 @@ namespace GameHub.UI.Widgets public CompatToolOptions(Traits.SupportsCompatTools runnable, CompatToolPicker picker, bool install = false) { - this.runnable = runnable; + /*this.runnable = runnable; this.compat_tool_picker = picker; this.install = install; this.settings_key = install ? "install_options" : "options"; @@ -42,12 +42,12 @@ namespace GameHub.UI.Widgets get_style_context().add_class("tags-list"); selection_mode = SelectionMode.NONE; update_options(); - compat_tool_picker.notify["selected"].connect(update_options); + compat_tool_picker.notify["selected"].connect(update_options);*/ } public void update_options() { - this.foreach(r => r.destroy()); + /*this.foreach(r => r.destroy()); visible = false; if(compat_tool_picker == null || compat_tool_picker.selected == null) return; @@ -99,12 +99,12 @@ namespace GameHub.UI.Widgets add(new OptionRow(opt)); } - show_all(); + show_all();*/ } public void save_options() { - runnable.compat_options_saved = true; + /*runnable.compat_options_saved = true; if(compat_tool_picker == null || compat_tool_picker.selected == null) return; var options = install ? compat_tool_picker.selected.install_options : compat_tool_picker.selected.options; @@ -157,7 +157,7 @@ namespace GameHub.UI.Widgets } } tool_settings.set_object_member(settings_key, ts_options); - runnable.set_compat_settings(compat_tool_picker.selected, tool_settings); + runnable.set_compat_settings(compat_tool_picker.selected, tool_settings);*/ } public class OptionRow: ListBoxRow diff --git a/src/ui/widgets/compat/CompatToolsList.vala b/src/ui/widgets/compat/CompatToolsList.vala new file mode 100644 index 00000000..7b21095c --- /dev/null +++ b/src/ui/widgets/compat/CompatToolsList.vala @@ -0,0 +1,54 @@ +/* +This file is part of GameHub. +Copyright (C) 2018-2019 Anatoliy Kashkin + +GameHub is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GameHub is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GameHub. If not, see . +*/ + +using Gtk; +using Gdk; +using Gee; + +using GameHub.UI.Widgets; +using GameHub.UI.Widgets.Settings; + +using GameHub.Data; +using GameHub.Data.Tweaks; +using GameHub.Data.Runnables; + +namespace GameHub.UI.Widgets.Compat +{ + public class CompatToolsList: Notebook + { + public Runnable? runnable { get; construct; default = null; } + + public CompatToolsList(Runnable? runnable = null) + { + Object(runnable: runnable, show_border: false, expand: true, scrollable: true); + } + + construct + { + update(); + } + + private void update() + { + this.foreach(w => w.destroy()); + + append_page(new Box(Orientation.VERTICAL, 0), new Label("Wine")); + append_page(new Box(Orientation.VERTICAL, 0), new Label("Proton")); + } + } +} diff --git a/src/ui/widgets/tweaks/TweakOptionsPopover.vala b/src/ui/widgets/tweaks/TweakOptionsPopover.vala index 1fc92744..d2c4cfcf 100644 --- a/src/ui/widgets/tweaks/TweakOptionsPopover.vala +++ b/src/ui/widgets/tweaks/TweakOptionsPopover.vala @@ -32,9 +32,11 @@ namespace GameHub.UI.Widgets.Tweaks public class TweakOptionsPopover: Popover { public Tweak tweak { get; construct; } + public TweakSet tweakset { get; construct; } + public TweakOptions tweak_options { get; construct; } - private Tweak.Option? selected_option; - private Tweak.Option.Preset? selected_preset; + private Option? selected_option; + private Option.Preset? selected_preset; private Box option_details_vbox; private Box? presets_vbox; @@ -42,9 +44,9 @@ namespace GameHub.UI.Widgets.Tweaks private ListBox? values_list; private Entry? string_value_entry; - public TweakOptionsPopover(Tweak tweak) + public TweakOptionsPopover(Tweak tweak, TweakSet tweakset) { - Object(tweak: tweak); + Object(tweak: tweak, tweakset: tweakset, tweak_options: tweakset.get_or_create_options(tweak)); } construct @@ -118,7 +120,7 @@ namespace GameHub.UI.Widgets.Tweaks select_option(tweak.options.first()); } - private void select_option(Tweak.Option? option) + private void select_option(Option? option) { selected_option = option; selected_preset = null; @@ -132,9 +134,11 @@ namespace GameHub.UI.Widgets.Tweaks if(option == null) return; + var option_properties = tweak_options.get_or_create_properties(option); + var has_presets = option.presets != null && option.presets.size > 0; - var has_values_list = option.option_type == Tweak.Option.Type.LIST && option.values != null && option.values.size > 0; - var has_string_value = option.option_type == Tweak.Option.Type.STRING; + var has_values_list = option.option_type == Option.Type.LIST && option.values != null && option.values.size > 0; + var has_string_value = option.option_type == Option.Type.STRING; if(has_presets) { @@ -162,6 +166,10 @@ namespace GameHub.UI.Widgets.Tweaks row.notify["selected"].connect(() => { if(row.selected) select_preset(row.preset); }); + if(option_properties.preset == preset) + { + row.toggle(); + } } if(has_values_list || has_string_value) @@ -172,10 +180,13 @@ namespace GameHub.UI.Widgets.Tweaks row.notify["selected"].connect(() => { if(row.selected) select_preset(row.preset); }); + if(option_properties.preset == null) + { + row.toggle(); + } } presets_list.row_activated.connect(r => ((PresetRow) r).toggle()); - option_details_vbox.add(presets_vbox); } @@ -206,6 +217,7 @@ namespace GameHub.UI.Widgets.Tweaks { var row = new ValueRow(value.key, value.value); values_list.add(row); + row.selected = option_properties.selected_values != null && value.key in option_properties.selected_values; row.notify["selected"].connect(update_option_value); } @@ -235,10 +247,12 @@ namespace GameHub.UI.Widgets.Tweaks } option_details_vbox.show_all(); + + select_preset(option_properties.preset); update_option_value(); } - private void select_preset(Tweak.Option.Preset? preset) + private void select_preset(Option.Preset? preset) { selected_preset = preset; if(selected_option != null && values_vbox != null) @@ -252,55 +266,42 @@ namespace GameHub.UI.Widgets.Tweaks { if(selected_option == null) return; - warning("[TweakOptionsPopover.update_option_value] Option: '%s'", selected_option.id); - warning("[TweakOptionsPopover.update_option_value] Preset: %s", selected_preset != null ? "'%s'".printf(selected_preset.id) : "custom"); - - string? value = null; + var properties = new TweakOptions.OptionProperties(selected_option, selected_preset); - if(selected_preset != null) + switch(selected_option.option_type) { - value = selected_preset.value; - } - else - { - switch(selected_option.option_type) - { - case Tweak.Option.Type.LIST: - if(values_list != null) - { - string[] values = {}; - values_list.foreach(r => { - var row = (ValueRow) r; - if(row.selected) - { - values += row.value; - } - }); - value = string.joinv(selected_option.list_separator, values); - } - break; - - case Tweak.Option.Type.STRING: - if(string_value_entry != null) - { - value = string_value_entry.text; - if(selected_option.string_value != null) + case Option.Type.LIST: + if(values_list != null) + { + string[] values = {}; + values_list.foreach(r => { + var row = (ValueRow) r; + if(row.selected) { - value = selected_option.string_value.replace("${value}", value).replace("$value", value); + values += row.value; } - } - break; - } + }); + properties.selected_values = values; + } + break; + + case Option.Type.STRING: + if(string_value_entry != null) + { + properties.string_value = string_value_entry.text; + } + break; } - warning("[TweakOptionsPopover.update_option_value] Value: %s", value != null ? "'%s'".printf(value) : "null"); + tweak_options.set_properties_for_option(selected_option, properties); + tweakset.set_options_for_tweak(tweak, tweak_options); } private class OptionRow: ListBoxRow { - public Tweak.Option option { get; construct; } + public Option option { get; construct; } - public OptionRow(Tweak.Option option) + public OptionRow(Option option) { Object(option: option); } @@ -338,12 +339,12 @@ namespace GameHub.UI.Widgets.Tweaks private class PresetRow: ListBoxRow { - public Tweak.Option.Preset? preset { get; construct; } + public Option.Preset? preset { get; construct; } public bool selected { get; set; } public RadioButton? radio { get; construct; } - public PresetRow(Tweak.Option.Preset? preset, RadioButton? prev_radio = null) + public PresetRow(Option.Preset? preset, RadioButton? prev_radio = null) { Object(preset: preset, radio: new RadioButton.from_widget(prev_radio), selectable: false, activatable: true); } diff --git a/src/ui/widgets/tweaks/TweakRow.vala b/src/ui/widgets/tweaks/TweakRow.vala index 8ff63cab..e34eecb9 100644 --- a/src/ui/widgets/tweaks/TweakRow.vala +++ b/src/ui/widgets/tweaks/TweakRow.vala @@ -32,14 +32,14 @@ namespace GameHub.UI.Widgets.Tweaks public class TweakRow: ListBoxRow, ActivatableSetting { public Tweak tweak { get; construct; } - public Traits.Game.SupportsTweaks? game { get; construct; default = null; } + public TweakSet tweakset { get; construct; } - public Tweak.Requirements? unavailable_reqs { get; private set; } + public Requirements? unavailable_reqs { get; private set; } public bool is_available { get { return unavailable_reqs == null; } } - public TweakRow(Tweak tweak, Traits.Game.SupportsTweaks? game=null) + public TweakRow(Tweak tweak, TweakSet tweakset) { - Object(tweak: tweak, game: game, activatable: true, selectable: false); + Object(tweak: tweak, tweakset: tweakset, activatable: true, selectable: false); } construct @@ -65,7 +65,7 @@ namespace GameHub.UI.Widgets.Tweaks install.sensitive = false; var enabled = new Switch(); - enabled.active = tweak.is_enabled(game); + enabled.active = tweakset.is_enabled(tweak.id); enabled.valign = Align.CENTER; var buttons_hbox = new Box(Orientation.HORIZONTAL, 0); @@ -122,7 +122,7 @@ namespace GameHub.UI.Widgets.Tweaks MenuButton? options = null; if(tweak.options != null && tweak.options.size > 0) { - var options_popover = new TweakOptionsPopover(tweak); + var options_popover = new TweakOptionsPopover(tweak, tweakset); options = new MenuButton(); options.tooltip_text = _("Options"); options.get_style_context().add_class(Gtk.STYLE_CLASS_FLAT); @@ -178,8 +178,18 @@ namespace GameHub.UI.Widgets.Tweaks } else { + if(!tweakset.is_global && tweakset.get_or_create_options(tweak).state == TweakOptions.State.GLOBAL) + { + enabled.opacity = 0.6; + enabled.tooltip_text = enabled.active ? _("Enabled globally") : _("Disabled globally"); + } + enabled.notify["active"].connect(() => { - tweak.set_enabled(enabled.active, game); + var tweak_opts = tweakset.get_or_create_options(tweak); + tweak_opts.state = enabled.active ? TweakOptions.State.ENABLED : TweakOptions.State.DISABLED; + tweakset.set_options_for_tweak(tweak, tweak_opts); + enabled.opacity = 1; + enabled.tooltip_text = null; }); setting_activated.connect(() => { enabled.activate(); diff --git a/src/ui/widgets/tweaks/TweaksList.vala b/src/ui/widgets/tweaks/TweaksList.vala index 620ccc7b..9433244f 100644 --- a/src/ui/widgets/tweaks/TweaksList.vala +++ b/src/ui/widgets/tweaks/TweaksList.vala @@ -98,7 +98,7 @@ namespace GameHub.UI.Widgets.Tweaks { if(game == null || tweak.is_applicable_to(game, compat_tool)) { - tweaks_list.add(new TweakRow(tweak, game)); + tweaks_list.add(new TweakRow(tweak, game == null ? GameHub.Settings.Tweaks.global_tweakset : game.tweaks)); } } } diff --git a/src/utils/ExecTask.vala b/src/utils/ExecTask.vala new file mode 100644 index 00000000..3edc3c3a --- /dev/null +++ b/src/utils/ExecTask.vala @@ -0,0 +1,278 @@ +/* +This file is part of GameHub. +Copyright (C) 2018-2019 Anatoliy Kashkin + +GameHub is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GameHub is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GameHub. If not, see . +*/ + +using Gee; + +using GameHub.Data; +using GameHub.Data.Tweaks; + +namespace GameHub.Utils +{ + public class ExecTask + { + private string[] _cmd; + private string? _dir = null; + private string[]? _env = null; + private HashMap? _env_vars = null; + private bool _override_runtime = false; + private bool _log = true; + private Tweak[]? _tweaks = null; + private TweakOptions? _tweak_options = null; + + public ExecTask(string[] cmd) { _cmd = cmd; } + + public ExecTask cmd(string[] cmd) { _cmd = cmd; return this; } + public ExecTask dir(string? dir=null) { _dir = dir; return this; } + public ExecTask env(string[]? env=null) { _env = env; return this; } + public ExecTask env_var(string name, string? value=null) { if(_env_vars == null) _env_vars = new HashMap(); _env_vars.set(name, value); return this; } + public ExecTask override_runtime(bool override_runtime=false) { _override_runtime = override_runtime; return this; } + public ExecTask log(bool log=true) { _log = log; return this; } + public ExecTask tweaks(Tweak[]? tweaks=null, TweakOptions? tweak_options=null) { _tweaks = tweaks; _tweak_options = tweak_options; return this; } + + private string expand_options(string value) + { + if(_tweak_options != null) + { + return _tweak_options.expand(value); + } + return value; + } + + private bool _expanded = false; + private void expand() + { + if(_expanded) return; + _expanded = true; + + var cmd_expanded = false; + + if(_log) debug("[ExecTask] {'%s'}", string.joinv("' '", _cmd)); + + _dir = _dir ?? Environment.get_home_dir(); + _env = _env ?? Environ.get(); + + #if PKG_APPIMAGE + _env = Environ.unset_variable(_env, "LD_LIBRARY_PATH"); + _env = Environ.unset_variable(_env, "LD_PRELOAD"); + #endif + + if(_env_vars != null) + { + foreach(var env_var in _env_vars.entries) + { + if(env_var.value != null) + { + _env = Environ.set_variable(_env, env_var.key, expand_options(env_var.value)); + } + else + { + _env = Environ.unset_variable(_env, env_var.key); + } + } + } + + if(_tweaks != null) + { + foreach(var tweak in _tweaks) + { + if(tweak.env != null) + { + foreach(var env_var in tweak.env.entries) + { + if(env_var.value != null) + { + _env = Environ.set_variable(_env, env_var.key, expand_options(env_var.value)); + } + else + { + _env = Environ.unset_variable(_env, env_var.key); + } + } + } + + if(tweak.command != null && tweak.command.length > 0) + { + string[] tweaked_cmd = _cmd; + var tweak_cmd = Utils.parse_args(tweak.command); + if(tweak_cmd != null) + { + if("$command" in tweak_cmd || "${command}" in tweak_cmd) + { + tweaked_cmd = {}; + } + foreach(var arg in tweak_cmd) + { + if(arg == "$command" || arg == "${command}") + { + foreach(var a in _cmd) + { + tweaked_cmd += a; + } + } + else + { + tweaked_cmd += expand_options(arg); + } + } + cmd_expanded = true; + } + _cmd = tweaked_cmd; + } + } + } + + #if PKG_FLATPAK + if(_override_runtime && ProjectConfig.RUNTIME.length > 0) + { + _env = Environ.set_variable(_env, "LD_LIBRARY_PATH", ProjectConfig.RUNTIME); + } + string[] cmd = { "flatpak-spawn", "--host" }; + foreach(var arg in _cmd) + { + cmd += arg; + } + _cmd = cmd; + cmd_expanded = true; + #endif + + if(_log && GameHub.Application.log_verbose) + { + if(cmd_expanded) debug(" cmd: {'%s'}", string.joinv("' '", _cmd)); + debug(" dir: '%s'", _dir); + + string[] env_diff = {}; + string[] env_clean = Environ.get(); + foreach(var env_var in _env) + { + if(!(env_var in env_clean)) + { + env_diff += env_var; + } + } + if(env_diff.length > 0) + { + debug(" env: {\n '%s'\n }", string.joinv("'\n '", env_diff)); + } + } + } + + public Result? sync(bool capture_output=false) + { + var is_called_from_thread = _expanded; + expand(); + try + { + if(_log && !is_called_from_thread) debug(" .sync()"); + int status; + if(capture_output) + { + string sout; + string serr; + Process.spawn_sync(_dir, _cmd, _env, SpawnFlags.SEARCH_PATH, null, out sout, out serr, out status); + sout = sout.strip(); + serr = serr.strip(); + if(_log) + { + if(sout.length > 0) print(sout + "\n"); + if(serr.length > 0) warning(serr); + } + return new Result(status, sout, serr); + } + else + { + Process.spawn_sync(_dir, _cmd, _env, SpawnFlags.SEARCH_PATH | SpawnFlags.CHILD_INHERITS_STDIN | SpawnFlags.STDERR_TO_DEV_NULL, null, null, null, out status); + return new Result(status); + } + } + catch (Error e) + { + warning("[ExecTask.sync] %s", e.message); + } + return null; + } + + public async Result? sync_thread(bool capture_output=false) + { + expand(); + Result? result = null; + Utils.thread("ExecTask.sync_thread", () => { + if(_log) debug(" .sync_thread()"); + result = sync(capture_output); + Idle.add(sync_thread.callback); + }, _log); + yield; + return result; + } + + public async Result? async(bool wait=true) + { + expand(); + Result? result = null; + try + { + if(_log) debug(" .async()"); + Pid pid; + Process.spawn_async(_dir, _cmd, _env, SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD, null, out pid); + ChildWatch.add(pid, (pid, status) => { + Process.close_pid(pid); + result = new Result(status); + Idle.add(async.callback); + }); + } + catch (Error e) + { + warning("[ExecTask.async] %s", e.message); + } + if(wait) yield; + return result; + } + + public class Result + { + public int? status; + public int? exit_code; + public string? output; + public string? errors; + + public Result(int? status, string? output=null, string? errors=null) + { + this.status = status; + this.output = output; + this.errors = errors; + if(this.status != null) + { + this.exit_code = Process.exit_status(this.status); + } + } + + public bool check_status() throws Error + { + if(this.status != null) + { + return Process.check_exit_status(this.status); + } + return true; + } + } + } + + public static ExecTask exec(string[] cmd) + { + return new ExecTask(cmd); + } +} diff --git a/src/utils/Gamepad.vala b/src/utils/Gamepad.vala index b2d914b1..efe097f4 100644 --- a/src/utils/Gamepad.vala +++ b/src/utils/Gamepad.vala @@ -260,7 +260,7 @@ namespace GameHub.Utils.Gamepad var display = wnd.screen.get_display(); if(display is Gdk.X11.Display) { - return (wnd.screen.get_display() as Gdk.X11.Display).get_xdisplay(); + return ((Gdk.X11.Display) wnd.screen.get_display()).get_xdisplay(); } } } diff --git a/src/utils/OS.vala b/src/utils/OS.vala new file mode 100644 index 00000000..8c602384 --- /dev/null +++ b/src/utils/OS.vala @@ -0,0 +1,121 @@ +/* +This file is part of GameHub. +Copyright (C) 2018-2019 Anatoliy Kashkin + +GameHub is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GameHub is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GameHub. If not, see . +*/ + +using Gtk; +using Gee; + +using GameHub.Data; +using GameHub.Data.Tweaks; + +namespace GameHub.Utils.OS +{ + private static string? distro = null; + public static string get_distro() + { + if(distro != null) return distro; + + #if OS_LINUX + distro = Utils.exec({"bash", "-c", "lsb_release -ds 2>/dev/null || cat /etc/*release 2>/dev/null | head -n1 || uname -om"}).log(false).sync(true).output.replace("\"", ""); + #if PKG_APPIMAGE + distro = "[AppImage] " + distro; + #elif PKG_FLATPAK + distro = "[Flatpak] " + distro; + #endif + #elif OS_WINDOWS + distro = "Windows " + win32_get_os_version(); + #elif OS_MACOS + distro = "macOS"; + #else + distro = "unknown"; + #endif + + return distro; + } + + #if OS_LINUX + + public static string? get_desktop_environment() + { + return Environment.get_variable("XDG_CURRENT_DESKTOP"); + } + + public static bool is_package_installed(string package) + { + #if PKG_APPIMAGE || PKG_FLATPAK + return false; + #elif PM_APT + var output = Utils.exec({"dpkg-query", "-W", "-f=${Status}", package}).log(false).sync(true).output; + return "install ok installed" in output; + #else + return false; + #endif + } + + private static string[]? kmods = null; + public static bool is_kernel_module_loaded(string? name) + { + if(name == null || name.length == 0) return true; + if(kmods == null) + { + try + { + kmods = {}; + string proc_modules; + FileUtils.get_contents("/proc/modules", out proc_modules); + var module_lines = proc_modules.split("\n"); + foreach(var line in module_lines) + { + kmods += line.split(" ")[0]; + } + } + catch(Error e) + { + warning("[Utils.OS.is_kernel_module_loaded] Error while reading kernel modules list: %s", e.message); + } + } + return name in kmods; + } + + #elif OS_WINDOWS + + private struct win32_OSVERSIONINFOW + { + uint size; + uint major; + uint minor; + uint build; + uint platform; + uint16 sp_version[128]; + } + [CCode(cname="RtlGetVersion")] + private static extern uint32 win32_rtl_get_version(out win32_OSVERSIONINFOW ver); + public static string? win32_get_os_version() + { + win32_OSVERSIONINFOW ver = new win32_OSVERSIONINFOW(); + ver.size = (uint) sizeof(win32_OSVERSIONINFOW); + win32_rtl_get_version(out ver); + var result = "%u.%u.%u".printf(ver.major, ver.minor, ver.build); + if(ver.sp_version[0] != 0) + { + result += " " + ((string) ver.sp_version); + } + return result; + } + + #endif +} diff --git a/src/utils/Utils.vala b/src/utils/Utils.vala index 3fdba1f3..7b320b02 100644 --- a/src/utils/Utils.vala +++ b/src/utils/Utils.vala @@ -82,254 +82,10 @@ namespace GameHub.Utils return null; } - public class RunTask - { - private string[] _cmd; - private string? _dir = null; - private string[]? _env = null; - private HashMap? _env_vars = null; - private bool _override_runtime = false; - private bool _log = true; - private Tweak[]? _tweaks = null; - - public RunTask(string[] cmd) { _cmd = cmd; } - - public RunTask cmd(string[] cmd) { _cmd = cmd; return this; } - public RunTask dir(string? dir=null) { _dir = dir; return this; } - public RunTask env(string[]? env=null) { _env = env; return this; } - public RunTask env_var(string name, string? value=null) { if(_env_vars == null) _env_vars = new HashMap(); _env_vars.set(name, value); return this; } - public RunTask override_runtime(bool override_runtime=false) { _override_runtime = override_runtime; return this; } - public RunTask log(bool log=true) { _log = log; return this; } - public RunTask tweaks(Tweak[]? tweaks=null) { _tweaks = tweaks; return this; } - - private bool _expanded = false; - private void expand() - { - if(_expanded) return; - _expanded = true; - - var cmd_expanded = false; - - if(_log) debug("[RunTask] {'%s'}", string.joinv("' '", _cmd)); - - _dir = _dir ?? Environment.get_home_dir(); - _env = _env ?? Environ.get(); - - #if PKG_APPIMAGE - _env = Environ.unset_variable(_env, "LD_LIBRARY_PATH"); - _env = Environ.unset_variable(_env, "LD_PRELOAD"); - #endif - - if(_env_vars != null) - { - foreach(var env_var in _env_vars.entries) - { - if(env_var.value != null) - { - _env = Environ.set_variable(_env, env_var.key, env_var.value); - } - else - { - _env = Environ.unset_variable(_env, env_var.key); - } - } - } - - if(_tweaks != null) - { - foreach(var tweak in _tweaks) - { - if(tweak.env != null) - { - foreach(var env_var in tweak.env.entries) - { - if(env_var.value != null) - { - _env = Environ.set_variable(_env, env_var.key, env_var.value); - } - else - { - _env = Environ.unset_variable(_env, env_var.key); - } - } - } - - if(tweak.command != null && tweak.command.length > 0) - { - string[] tweaked_cmd = _cmd; - var tweak_cmd = Utils.parse_args(tweak.command); - if(tweak_cmd != null) - { - if("$command" in tweak_cmd || "${command}" in tweak_cmd) - { - tweaked_cmd = {}; - } - foreach(var arg in tweak_cmd) - { - if(arg == "$command" || arg == "${command}") - { - foreach(var a in _cmd) - { - tweaked_cmd += a; - } - } - else - { - tweaked_cmd += arg; - } - } - cmd_expanded = true; - } - _cmd = tweaked_cmd; - } - } - } - - #if PKG_FLATPAK - if(_override_runtime && ProjectConfig.RUNTIME.length > 0) - { - _env = Environ.set_variable(_env, "LD_LIBRARY_PATH", ProjectConfig.RUNTIME); - } - string[] cmd = { "flatpak-spawn", "--host" }; - foreach(var arg in _cmd) - { - cmd += arg; - } - _cmd = cmd; - cmd_expanded = true; - #endif - - if(_log && GameHub.Application.log_verbose) - { - if(cmd_expanded) debug(" cmd: {'%s'}", string.joinv("' '", _cmd)); - debug(" dir: '%s'", _dir); - - string[] env_diff = {}; - string[] env_clean = Environ.get(); - foreach(var env_var in _env) - { - if(!(env_var in env_clean)) - { - env_diff += env_var; - } - } - if(env_diff.length > 0) - { - debug(" env: {\n '%s'\n }", string.joinv("'\n '", env_diff)); - } - } - - } - - public Result? run_sync(bool capture_output=false) - { - var is_called_from_thread = _expanded; - expand(); - try - { - if(_log && !is_called_from_thread) debug(" .run_sync()"); - int status; - if(capture_output) - { - string sout; - string serr; - Process.spawn_sync(_dir, _cmd, _env, SpawnFlags.SEARCH_PATH, null, out sout, out serr, out status); - sout = sout.strip(); - serr = serr.strip(); - if(_log) - { - if(sout.length > 0) print(sout + "\n"); - if(serr.length > 0) warning(serr); - } - return new Result(status, sout, serr); - } - else - { - Process.spawn_sync(_dir, _cmd, _env, SpawnFlags.SEARCH_PATH | SpawnFlags.CHILD_INHERITS_STDIN | SpawnFlags.STDERR_TO_DEV_NULL, null, null, null, out status); - return new Result(status); - } - } - catch (Error e) - { - warning("[RunTask.run_sync] %s", e.message); - } - return null; - } - - public async Result? run_sync_thread(bool capture_output=false) - { - expand(); - Result? result = null; - Utils.thread("RunTask.run_sync_thread", () => { - if(_log) debug(" .run_sync_thread()"); - result = run_sync(capture_output); - Idle.add(run_sync_thread.callback); - }, _log); - yield; - return result; - } - - public async Result? run_async(bool wait=true) - { - expand(); - Result? result = null; - try - { - if(_log) debug(" .run_async()"); - Pid pid; - Process.spawn_async(_dir, _cmd, _env, SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD, null, out pid); - ChildWatch.add(pid, (pid, status) => { - Process.close_pid(pid); - result = new Result(status); - Idle.add(run_async.callback); - }); - } - catch (Error e) - { - warning("[RunTask.run_async] %s", e.message); - } - if(wait) yield; - return result; - } - - public class Result - { - public int? status; - public int? exit_code; - public string? output; - public string? errors; - - public Result(int? status, string? output=null, string? errors=null) - { - this.status = status; - this.output = output; - this.errors = errors; - if(this.status != null) - { - this.exit_code = Process.exit_status(this.status); - } - } - - public bool check_status() throws Error - { - if(this.status != null) - { - return Process.check_exit_status(this.status); - } - return true; - } - } - } - - public static RunTask run(string[] cmd) - { - return new RunTask(cmd); - } - public static File? find_executable(string? name) { if(name == null || name.length == 0) return null; - var which = Environment.find_program_in_path(name) ?? run({"which", name}).log(false).run_sync(true).output; + var which = Environment.find_program_in_path(name); if(which == null || which.length == 0 || !which.has_prefix("/")) { return null; @@ -353,60 +109,6 @@ namespace GameHub.Utils } } - private static string? distro = null; - public static string get_distro() - { - if(distro != null) return distro; - - #if OS_LINUX - distro = Utils.run({"bash", "-c", "lsb_release -ds 2>/dev/null || cat /etc/*release 2>/dev/null | head -n1 || uname -om"}).log(false).run_sync(true).output.replace("\"", ""); - #if PKG_APPIMAGE - distro = "[AppImage] " + distro; - #elif PKG_FLATPAK - distro = "[Flatpak] " + distro; - #endif - #elif OS_WINDOWS - distro = "Windows " + win32_get_os_version(); - #elif OS_MACOS - distro = "macOS"; - #else - distro = "unknown"; - #endif - - return distro; - } - - #if OS_WINDOWS - private struct win32_OSVERSIONINFOW - { - uint size; - uint major; - uint minor; - uint build; - uint platform; - uint16 sp_version[128]; - } - [CCode(cname="RtlGetVersion")] - private static extern uint32 win32_rtl_get_version(out win32_OSVERSIONINFOW ver); - public static string? win32_get_os_version() - { - win32_OSVERSIONINFOW ver = new win32_OSVERSIONINFOW(); - ver.size = (uint) sizeof(win32_OSVERSIONINFOW); - win32_rtl_get_version(out ver); - var result = "%u.%u.%u".printf(ver.major, ver.minor, ver.build); - if(ver.sp_version[0] != 0) - { - result += " " + ((string) ver.sp_version); - } - return result; - } - #endif - - public static string? get_desktop_environment() - { - return Environment.get_variable("XDG_CURRENT_DESKTOP"); - } - public static string get_language_name() { #if OS_LINUX @@ -416,36 +118,6 @@ namespace GameHub.Utils #endif } - public static bool is_package_installed(string package) - { - #if PKG_APPIMAGE || PKG_FLATPAK - return false; - #elif PM_APT - var output = Utils.run({"dpkg-query", "-W", "-f=${Status}", package}).log(false).run_sync(true).output; - return "install ok installed" in output; - #else - return false; - #endif - } - - private static string[]? kmods = null; - public static bool is_kernel_module_loaded(string? name) - { - if(name == null || name.length == 0) return true; - if(kmods == null) - { - kmods = {}; - string proc_modules; - FileUtils.get_contents("/proc/modules", out proc_modules); - var module_lines = proc_modules.split("\n"); - foreach(var line in module_lines) - { - kmods += line.split(" ")[0]; - } - } - return name in kmods; - } - public static async void sleep_async(uint interval, int priority=GLib.Priority.DEFAULT) { Timeout.add(interval, () => { diff --git a/src/utils/fs/FS.vala b/src/utils/fs/FS.vala index aed062da..d8016598 100644 --- a/src/utils/fs/FS.vala +++ b/src/utils/fs/FS.vala @@ -244,7 +244,7 @@ namespace GameHub.Utils.FS public static void rm(string path, string? file=null, string flags="-f", HashMap? variables=null) { - Utils.run({"bash", "-c", "rm " + flags + " " + expand(path, file, variables).replace(" ", "\\ ")}).run_sync(); + Utils.exec({"bash", "-c", "rm " + flags + " " + expand(path, file, variables).replace(" ", "\\ ")}).sync(); } public static void mv_up(File? path, string dirname) diff --git a/src/utils/fs/FSOverlay.vala b/src/utils/fs/FSOverlay.vala index d788b1f4..4badf0ad 100644 --- a/src/utils/fs/FSOverlay.vala +++ b/src/utils/fs/FSOverlay.vala @@ -101,16 +101,16 @@ namespace GameHub.Utils.FS yield polkit_authenticate(); - yield Utils.run({"pkexec", POLKIT_HELPER, "mount", id, options, target.get_path()}).log(GameHub.Application.log_verbose).run_sync_thread(); + yield Utils.exec({"pkexec", POLKIT_HELPER, "mount", id, options, target.get_path()}).log(GameHub.Application.log_verbose).sync_thread(); } public async void umount() { yield polkit_authenticate(); - while(id in (yield Utils.run({"mount"}).log(false).run_sync_thread(true)).output) + while(id in (yield Utils.exec({"mount"}).log(false).sync_thread(true)).output) { - yield Utils.run({"pkexec", POLKIT_HELPER, "umount", id}).log(GameHub.Application.log_verbose).run_sync_thread(); + yield Utils.exec({"pkexec", POLKIT_HELPER, "umount", id}).log(GameHub.Application.log_verbose).sync_thread(); yield Utils.sleep_async(500); }