diff --git a/src/app.vala b/src/app.vala
index dd7a5ecc..1c3da098 100644
--- a/src/app.vala
+++ b/src/app.vala
@@ -145,15 +145,13 @@ namespace GameHub
Providers.ImageProviders = { new Providers.Images.SteamGridDB(), new Providers.Images.JinxSGVI() };
Providers.DataProviders = { new Providers.Data.IGDB() };
- CompatTool[] tools = { new Compat.WineWrap(), new Compat.Innoextract(), new Compat.DOSBox(), new Compat.ScummVM() };
- foreach(var appid in Compat.Proton.APPIDS)
- {
- tools += new Compat.Proton(appid);
- }
+ var proton_latest = new Compat.Proton(Compat.Proton.LATEST);
- CompatTools = tools;
+ CompatTools = { new Compat.WineWrap(), new Compat.Innoextract(), new Compat.DOSBox(), new Compat.ScummVM(), proton_latest };
- tools += new Compat.Proton(Compat.Proton.LATEST);
+ Compat.Proton.find_proton_versions();
+
+ CompatTool[] tools = CompatTools;
string[] wine_binaries = { "wine"/*, "wine64", "wine32"*/ };
string[] wine_arches = { "win64", "win32" };
@@ -173,6 +171,8 @@ namespace GameHub
CompatTools = tools;
+ proton_latest.init();
+
IconTheme.get_default().add_resource_path("/com/github/tkashkin/gamehub/icons");
screen = Screen.get_default();
diff --git a/src/data/compat/Proton.vala b/src/data/compat/Proton.vala
index b6f2af10..bd77a56b 100644
--- a/src/data/compat/Proton.vala
+++ b/src/data/compat/Proton.vala
@@ -16,28 +16,31 @@ You should have received a copy of the GNU General Public License
along with GameHub. If not, see .
*/
-using GameHub.Utils;
+using Gee;
using GameHub.Data.Sources.Steam;
+using GameHub.Utils;
namespace GameHub.Data.Compat
{
public class Proton: Wine
{
- public const string[] APPIDS = {"1054830", "996510", "961940", "930400", "858280"}; // 4.2, 3.16 Beta, 3.16, 3.7 Beta, 3.7
public const string LATEST = "latest";
- public string appid { get; construct; }
+ public string appid { get; construct set; }
+ public string? appname { get; construct set; }
+
+ public bool is_latest { get; construct set; default = false; }
- public Proton(string appid)
+ public Proton(string appid, string? appname=null)
{
- Object(appid: appid, binary: "", arch: "");
+ Object(appid: appid, appname: appname, is_latest: appid == LATEST, binary: "", arch: "");
}
construct
{
id = @"proton_$(appid)";
- name = "Proton";
+ name = appname ?? "Proton";
icon = "source-steam-symbolic";
installed = false;
@@ -64,8 +67,17 @@ namespace GameHub.Data.Compat
new CompatTool.BoolOption("/NOGUI", _("No GUI"), false)
};
- if(appid == Proton.LATEST)
+ if(!is_latest)
+ {
+ init();
+ }
+ }
+
+ public void init()
+ {
+ if(is_latest)
{
+ name = "Proton (latest)";
foreach(var tool in CompatTools)
{
if(tool is Proton)
@@ -74,7 +86,6 @@ namespace GameHub.Data.Compat
if(proton.installed)
{
appid = proton.appid;
- name = "Proton (latest)";
executable = proton.executable;
installed = true;
wine_binary = proton.wine_binary;
@@ -90,12 +101,16 @@ namespace GameHub.Data.Compat
{
if(proton_dir != null)
{
- name = proton_dir.get_basename();
+ name = appname ?? proton_dir.get_basename();
executable = proton_dir.get_child("proton");
installed = executable.query_exists();
wine_binary = proton_dir.get_child("dist/bin/wine");
}
}
+ else
+ {
+ name = appname ?? "Proton";
+ }
}
if(installed)
@@ -228,5 +243,60 @@ namespace GameHub.Data.Compat
yield Utils.run_thread({ executable.get_path(), "run", cmd.get_path() }, runnable.install_dir.get_path(), prepare_env(runnable), false, true);
}
}
+
+ public void install_app()
+ {
+ if(!is_latest && !installed)
+ {
+ Steam.install_app(appid);
+ }
+ }
+
+ public static void find_proton_versions()
+ {
+ if(Steam.instance == null) return;
+
+ Steam.instance.load_appinfo();
+
+ if(Steam.instance.appinfo == null) return;
+
+ ArrayList versions = new ArrayList();
+
+ foreach(var app_node in Steam.instance.appinfo.nodes.values)
+ {
+ if(app_node != null && app_node is BinaryVDF.ListNode)
+ {
+ var app = (BinaryVDF.ListNode) app_node;
+ var common = (BinaryVDF.ListNode) app.get_nested({"appinfo", "common"});
+
+ if(common != null)
+ {
+ var name = ((BinaryVDF.StringNode) common.get("name")).value;
+ var type = ((BinaryVDF.StringNode) common.get("type")).value;
+
+ if(type.down() == "tool" && name.down().has_prefix("proton "))
+ {
+ versions.add(new Proton(app.key, name));
+ }
+ }
+ }
+ }
+
+ if(versions.size > 0)
+ {
+ versions.sort((first, second) => {
+ return int.parse(second.appid) - int.parse(first.appid);
+ });
+
+ CompatTool[] tools = CompatTools;
+
+ foreach(var proton in versions)
+ {
+ tools += proton;
+ }
+
+ CompatTools = tools;
+ }
+ }
}
}
diff --git a/src/data/sources/steam/Steam.vala b/src/data/sources/steam/Steam.vala
index 2e806ae6..26a2013a 100644
--- a/src/data/sources/steam/Steam.vala
+++ b/src/data/sources/steam/Steam.vala
@@ -60,8 +60,8 @@ namespace GameHub.Data.Sources.Steam
private bool? installed = null;
- private BinaryVDF.ListNode? appinfo;
- private BinaryVDF.ListNode? packageinfo;
+ public BinaryVDF.ListNode? appinfo;
+ public BinaryVDF.ListNode? packageinfo;
public bool is_authenticated_in_steam_client
{
@@ -200,6 +200,18 @@ namespace GameHub.Data.Sources.Steam
return Settings.Auth.Steam.instance.authenticated && is_authenticated_in_steam_client;
}
+ public void load_appinfo()
+ {
+ if(appinfo == null)
+ {
+ appinfo = new AppInfoVDF(FSUtils.find_case_insensitive(FSUtils.file(FSUtils.Paths.Steam.Home), FSUtils.Paths.Steam.AppInfoVDF)).read();
+ }
+ if(packageinfo == null)
+ {
+ packageinfo = new PackageInfoVDF(FSUtils.find_case_insensitive(FSUtils.file(FSUtils.Paths.Steam.Home), FSUtils.Paths.Steam.PackageInfoVDF)).read();
+ }
+ }
+
private ArrayList _games = new ArrayList(Game.is_equal);
public override ArrayList games { get { return _games; } }
@@ -216,8 +228,7 @@ namespace GameHub.Data.Sources.Steam
Utils.thread("SteamLoading", () => {
_games.clear();
- appinfo = new AppInfoVDF(FSUtils.find_case_insensitive(FSUtils.file(FSUtils.Paths.Steam.Home), FSUtils.Paths.Steam.AppInfoVDF)).read();
- packageinfo = new PackageInfoVDF(FSUtils.find_case_insensitive(FSUtils.file(FSUtils.Paths.Steam.Home), FSUtils.Paths.Steam.PackageInfoVDF)).read();
+ load_appinfo();
var cached = Tables.Games.get_all(this);
games_count = 0;
@@ -313,6 +324,11 @@ namespace GameHub.Data.Sources.Steam
return pkgs;
}
+ public static void install_app(string appid)
+ {
+ Utils.open_uri(@"steam://install/$(appid)");
+ }
+
public static void install_multiple_apps(string[] appids)
{
if(instance.packageinfo == null) return;
diff --git a/src/data/sources/steam/SteamGame.vala b/src/data/sources/steam/SteamGame.vala
index 375f033f..485fd2ad 100644
--- a/src/data/sources/steam/SteamGame.vala
+++ b/src/data/sources/steam/SteamGame.vala
@@ -250,7 +250,7 @@ namespace GameHub.Data.Sources.Steam
public override async void install(Runnable.Installer.InstallMode install_mode=Runnable.Installer.InstallMode.INTERACTIVE)
{
- Utils.open_uri(@"steam://install/$(id)");
+ Steam.install_app(id);
update_status();
}
diff --git a/src/ui/dialogs/SettingsDialog/pages/sources/Steam.vala b/src/ui/dialogs/SettingsDialog/pages/sources/Steam.vala
index f87c0ccc..03e51ba8 100644
--- a/src/ui/dialogs/SettingsDialog/pages/sources/Steam.vala
+++ b/src/ui/dialogs/SettingsDialog/pages/sources/Steam.vala
@@ -18,6 +18,9 @@ along with GameHub. If not, see .
using Gtk;
+using GameHub.Data;
+using GameHub.Data.Compat;
+
using GameHub.Utils;
namespace GameHub.UI.Dialogs.SettingsDialog.Pages.Sources
@@ -26,6 +29,8 @@ namespace GameHub.UI.Dialogs.SettingsDialog.Pages.Sources
{
private Settings.Auth.Steam steam_auth;
+ private ListBox proton;
+
public Steam(SettingsDialog dlg)
{
Object(
@@ -41,15 +46,34 @@ namespace GameHub.UI.Dialogs.SettingsDialog.Pages.Sources
construct
{
+ root_grid.margin = 0;
+ header_grid.margin = 12;
+ header_grid.margin_bottom = 0;
+ content_area.margin = 0;
+
var paths = FSUtils.Paths.Settings.instance;
steam_auth = Settings.Auth.Steam.instance;
add_steam_apikey_entry();
- add_labeled_link(_("Steam API keys have limited number of uses per day"), _("Generate key"), "steam://openurl/https://steamcommunity.com/dev/apikey");
+ adjust_margins(add_labeled_link(_("Steam API keys have limited number of uses per day"), _("Generate key"), "steam://openurl/https://steamcommunity.com/dev/apikey"));
+
+ adjust_margins(add_separator());
+
+ adjust_margins(add_file_chooser(_("Installation directory"), FileChooserAction.SELECT_FOLDER, paths.steam_home, v => { paths.steam_home = v; request_restart(); }, false));
- add_separator();
- add_file_chooser(_("Installation directory"), FileChooserAction.SELECT_FOLDER, paths.steam_home, v => { paths.steam_home = v; request_restart(); }, false);
+ var proton_header = add_header("Proton");
+ proton_header.margin_start = proton_header.margin_end = 12;
+
+ proton = add_widget(new ListBox());
+ proton.selection_mode = SelectionMode.NONE;
+ proton.get_style_context().add_class(Gtk.STYLE_CLASS_FRAME);
+ proton.expand = true;
+
+ proton.margin_start = 7;
+ proton.margin_end = 3;
+ proton.margin_top = 0;
+ proton.margin_bottom = 6;
status_switch.active = steam_auth.enabled;
status_switch.notify["active"].connect(() => {
@@ -83,9 +107,97 @@ namespace GameHub.UI.Dialogs.SettingsDialog.Pages.Sources
{
status = description = steam.user_name != null ? _("Authenticated as %s").printf(steam.user_name) : _("Authenticated");
}
+
+ proton.foreach(r => {
+ if(r != null) r.destroy();
+ });
+
+ foreach(var tool in CompatTools)
+ {
+ if(tool is Proton)
+ {
+ var p = tool as Proton;
+ if(p != null && !p.is_latest)
+ {
+ proton.add(new ProtonRow(p, this));
+ }
+ }
+ }
+
+ proton.show_all();
}
- protected void add_steam_apikey_entry()
+ private class ProtonRow: ListBoxRow
+ {
+ public Proton proton { get; construct; }
+
+ public Steam page { private get; construct; }
+
+ public ProtonRow(Proton proton, Steam page)
+ {
+ Object(proton: proton, page: page);
+ }
+
+ construct
+ {
+ var grid = new Grid();
+ grid.column_spacing = 12;
+ grid.margin_start = grid.margin_end = 8;
+ grid.margin_top = grid.margin_bottom = 4;
+
+ var icon = new Image.from_icon_name("source-steam-symbolic", IconSize.LARGE_TOOLBAR);
+ icon.valign = Align.CENTER;
+
+ var name = new Label(proton.name);
+ name.get_style_context().add_class("category-label");
+ name.set_size_request(96, -1);
+ name.hexpand = false;
+ name.xalign = 0;
+ name.valign = Align.CENTER;
+
+ var appid = new Label(proton.appid);
+ appid.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
+ appid.hexpand = true;
+ appid.xalign = 0;
+ appid.valign = Align.CENTER;
+
+ var status = new Label(_("Not installed"));
+ status.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
+ status.hexpand = true;
+ status.ellipsize = Pango.EllipsizeMode.MIDDLE;
+ status.xalign = 0;
+ status.valign = Align.CENTER;
+
+ var install = new Button.with_label(_("Install"));
+ install.valign = Align.CENTER;
+ install.sensitive = false;
+
+ grid.attach(icon, 0, 0, 1, 2);
+ grid.attach(name, 1, 0);
+ grid.attach(appid, 2, 0);
+ grid.attach(status, 1, 1, 2, 1);
+
+ if(proton.installed && proton.executable != null)
+ {
+ status.label = status.tooltip_text = proton.executable.get_path();
+ }
+ else
+ {
+ install.sensitive = true;
+ grid.attach(install, 3, 0, 1, 2);
+
+ install.clicked.connect(() => {
+ install.sensitive = false;
+ page.request_restart();
+ proton.install_app();
+ });
+ }
+
+ child = grid;
+ }
+ }
+
+ protected Box add_steam_apikey_entry()
{
var steam_auth = Settings.Auth.Steam.instance;
@@ -117,6 +229,16 @@ namespace GameHub.UI.Dialogs.SettingsDialog.Pages.Sources
hbox.add(label);
hbox.add(entry);
add_widget(hbox);
+
+ adjust_margins(hbox);
+
+ return hbox;
+ }
+
+ private void adjust_margins(Widget w)
+ {
+ w.margin_start = 16;
+ w.margin_end = 12;
}
}
}
diff --git a/src/utils/BinaryVDF.vala b/src/utils/BinaryVDF.vala
index b10366ee..4bc885e3 100644
--- a/src/utils/BinaryVDF.vala
+++ b/src/utils/BinaryVDF.vala
@@ -158,9 +158,10 @@ namespace GameHub.Utils
public Node? get_nested(string[] keys, Node? def=null)
{
ListNode? list = this;
+ Node? node = null;
foreach(var key in keys)
{
- var node = list.get(key);
+ node = list.get(key);
if(node != null && node is ListNode)
{
list = (ListNode) node;
@@ -170,7 +171,7 @@ namespace GameHub.Utils
return node ?? def;
}
}
- return def;
+ return node ?? def;
}
public override void show(int indent=0)
diff --git a/src/utils/FSUtils.vala b/src/utils/FSUtils.vala
index 17c66208..e51362f6 100644
--- a/src/utils/FSUtils.vala
+++ b/src/utils/FSUtils.vala
@@ -265,7 +265,10 @@ namespace GameHub.Utils
{
foreach(var v in variables.entries)
{
- expanded_path = expanded_path.replace("${" + v.key + "}", v.value).replace("$" + v.key, v.value);
+ if(v.key != null && v.value != null)
+ {
+ expanded_path = expanded_path.replace("${" + v.key + "}", v.value).replace("$" + v.key, v.value);
+ }
}
}
expanded_path = Utils.replace_prefix(expanded_path, "~/.cache", Environment.get_user_cache_dir());