2 This file is part of GameHub.
3 Copyright (C) 2018-2019 Anatoliy Kashkin
5 GameHub is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 GameHub is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with GameHub. If not, see <https://www.gnu.org/licenses/>.
24 using GameHub
.Data
.DB
;
25 using GameHub
.Data
.Sources
.Steam
;
26 using GameHub
.Data
.Sources
.GOG
;
27 using GameHub
.Data
.Sources
.Humble
;
28 using GameHub
.Data
.Sources
.User
;
33 public class Application
: Gtk
.Application
35 public static Application instance
;
37 public static bool log_auth
= false;
38 public static bool log_downloader
= false;
39 public static bool log_workers
= false;
40 public static bool log_no_filters
= false;
41 public static bool log_verbose
= false;
43 private static bool opt_help
= false;
44 private static bool opt_debug_log
= false;
45 private static bool opt_show_version
= false;
47 private static string? opt_run
= null;
48 private static string? opt_game_details
= null;
49 private static string? opt_game_properties
= null;
51 private static bool opt_show_compat
= false;
52 private static bool opt_show
= false;
53 private static bool opt_settings
= false;
54 private static bool opt_about
= false;
55 private static bool opt_gdb
= false;
56 private static bool opt_gdb_bt_full
= false;
57 private static bool opt_gdb_fatal_criticals
= false;
59 public static int worker_threads
= -1;
61 private GameHub
.UI
.Windows
.MainWindow? main_window
;
63 public const string ACTION_PREFIX
= "app.";
64 public const string ACTION_SETTINGS
= "settings";
65 public const string ACTION_ABOUT
= "about";
66 public const string ACTION_CORRUPTED_INSTALLER_PICK_ACTION
= "corrupted-installer.pick-action";
67 public const string ACTION_CORRUPTED_INSTALLER_SHOW
= "corrupted-installer.show";
68 public const string ACTION_CORRUPTED_INSTALLER_BACKUP
= "corrupted-installer.backup";
69 public const string ACTION_CORRUPTED_INSTALLER_REMOVE
= "corrupted-installer.remove";
70 public const string ACTION_GAME_RUN
= "game.run";
71 public const string ACTION_GAME_DETAILS
= "game.details";
72 public const string ACTION_GAME_PROPERTIES
= "game.properties";
74 public const string ACCEL_SETTINGS
= "<Control>S";
76 private const GLib
.ActionEntry
[] action_entries
= {
77 { ACTION_SETTINGS
, action_settings
},
78 { ACTION_ABOUT
, action_about
},
79 { ACTION_CORRUPTED_INSTALLER_PICK_ACTION
, action_corrupted_installer
, "(ss)" },
80 { ACTION_CORRUPTED_INSTALLER_SHOW
, action_corrupted_installer
, "(ss)" },
81 { ACTION_CORRUPTED_INSTALLER_BACKUP
, action_corrupted_installer
, "(ss)" },
82 { ACTION_CORRUPTED_INSTALLER_REMOVE
, action_corrupted_installer
, "(ss)" },
83 { ACTION_GAME_RUN
, action_game
, "s" },
84 { ACTION_GAME_DETAILS
, action_game
, "s" },
85 { ACTION_GAME_PROPERTIES
, action_game
, "s" }
88 private const OptionEntry
[] local_options
= {
89 { "help", 'h', 0, OptionArg
.NONE
, out opt_help
, N_("Show help"), null },
90 { "version", 'v', 0, OptionArg
.NONE
, out opt_show_version
, N_("Show application version and exit"), null },
91 { "gdb", 0, 0, OptionArg
.NONE
, out opt_gdb
, N_("Restart with GDB debugger attached"), null },
92 { "gdb-bt-full", 0, 0, OptionArg
.NONE
, out opt_gdb_bt_full
, N_("Show full GDB backtrace"), null },
93 { "gdb-fatal-criticals", 0, 0, OptionArg
.NONE
, out opt_gdb_fatal_criticals
, N_("Treat fatal errors as criticals and crash application"), null },
96 private const OptionEntry
[] options
= {
97 { "show", 's', 0, OptionArg
.NONE
, out opt_show
, N_("Show main window"), null },
98 { "settings", 0, 0, OptionArg
.NONE
, out opt_settings
, N_("Show application settings dialog"), null },
99 { "about", 0, 0, OptionArg
.NONE
, out opt_about
, N_("Show about dialog"), null },
100 { "worker-threads", 'j', 0, OptionArg
.INT
, out worker_threads
, N_("Maximum number of background worker threads"), "THREADS" },
103 private const OptionEntry
[] options_game
= {
104 { "run", 'r', 0, OptionArg
.STRING
, out opt_run
, N_("Run game"), "GAME" },
105 { "show-compat", 'c', 0, OptionArg
.NONE
, out opt_show_compat
, N_("Show compatibility options dialog"), null },
106 { "details", 0, 0, OptionArg
.STRING
, out opt_game_details
, N_("Open game details"), "GAME" },
107 { "properties", 0, 0, OptionArg
.STRING
, out opt_game_properties
, N_("Open game properties"), "GAME" },
110 private const OptionEntry
[] options_log
= {
111 { "debug", 'd', 0, OptionArg
.NONE
, out opt_debug_log
, N_("Enable debug logging"), null },
112 { "log-auth", 0, 0, OptionArg
.NONE
, out log_auth
, N_("Log authentication process and sensitive information like authentication tokens"), null },
113 { "log-downloader", 0, 0, OptionArg
.NONE
, out log_downloader
, N_("Log download manager"), null },
114 { "log-workers", 0, 0, OptionArg
.NONE
, out log_workers
, N_("Log background workers start/stop"), null },
115 { "log-no-filters", 0, 0, OptionArg
.NONE
, out log_no_filters
, N_("Disable log messages filtering"), null },
116 { "verbose", 0, 0, OptionArg
.NONE
, out log_verbose
, N_("Verbose logging"), null },
122 application_id
= ProjectConfig
.PROJECT_NAME
;
123 flags
= ApplicationFlags
.HANDLES_COMMAND_LINE
;
125 add_action_entries(action_entries
, this
);
126 set_accels_for_action(ACTION_PREFIX
+ ACTION_SETTINGS
, { ACCEL_SETTINGS
});
129 private const string[] THEME_SPECIFIC_STYLES
= { "elementary" };
130 private Screen screen
;
131 private Gtk
.Settings gtk_settings
;
132 private HashMap
<string, CssProvider
> theme_providers
;
136 if(GameSources
!= null && CompatTools
!= null) return;
142 GameSources
= { new
Steam(), new
GOG(), new
Humble(), new
Trove(), new
User() };
144 Providers
.ImageProviders
= { new Providers
.Images
.SteamGridDB(), new Providers
.Images
.JinxSGVI() };
145 Providers
.DataProviders
= { new Providers
.Data
.IGDB() };
147 var proton_latest
= new Compat
.Proton(Compat
.Proton
.LATEST
);
149 CompatTools
= { new Compat
.WineWrap(), new Compat
.Innoextract(), new Compat
.DOSBox(), new Compat
.ScummVM(), proton_latest
};
151 Compat
.Proton
.find_proton_versions();
153 CompatTool
[] tools
= CompatTools
;
155 string[] wine_binaries
= { "wine"/*, "wine64", "wine32"*/ };
156 string[] wine_arches
= { "win64", "win32" };
158 foreach(var wine_binary
in wine_binaries
)
160 foreach(var wine_arch
in wine_arches
)
162 if(wine_binary
== "wine32" && wine_arch
== "win64") continue;
163 tools
+= new Compat
.Wine(wine_binary
, wine_arch
);
167 tools
+= new Compat
.CustomEmulator();
168 tools
+= new Compat
.RetroArch();
169 tools
+= new Compat
.CustomScript();
173 proton_latest
.init();
175 IconTheme
.get_default().add_resource_path("/com/github/tkashkin/gamehub/icons");
177 screen
= Screen
.get_default();
179 var app_provider
= new
CssProvider();
180 app_provider
.load_from_resource("/com/github/tkashkin/gamehub/css/app.css");
181 StyleContext
.add_provider_for_screen(screen
, app_provider
, Gtk
.STYLE_PROVIDER_PRIORITY_APPLICATION
);
183 theme_providers
= new HashMap
<string, CssProvider
>();
184 foreach(var theme
in THEME_SPECIFIC_STYLES
)
186 var provider
= new
CssProvider();
187 provider
.load_from_resource(@
"/com/github/tkashkin/gamehub/css/themes/$(theme).css");
188 theme_providers
.set(theme
, provider
);
191 gtk_settings
= Gtk
.Settings
.get_for_screen(screen
);
192 gtk_settings
.notify
["gtk-theme-name"].connect(gtk_theme_handler
);
196 private void gtk_theme_handler()
198 foreach(var provider
in theme_providers
.values
)
200 StyleContext
.remove_provider_for_screen(screen
, provider
);
202 if(theme_providers
.has_key(gtk_settings
.gtk_theme_name
))
204 StyleContext
.add_provider_for_screen(screen
, theme_providers
.get(gtk_settings
.gtk_theme_name
), Gtk
.STYLE_PROVIDER_PRIORITY_APPLICATION
);
208 protected override void activate()
210 if(main_window
== null)
212 print_version(false);
216 GameHub
.Utils
.Gamepad
.init();
219 main_window
= new GameHub
.UI
.Windows
.MainWindow(this
);
220 main_window
.show_all();
224 public static int main(string[] args
)
230 var app
= new
Application();
234 var lang
= Environment
.get_variable("LC_ALL") ??
"";
235 Intl
.setlocale(LocaleCategory
.ALL
, lang
);
236 Intl
.bindtextdomain(ProjectConfig
.GETTEXT_PACKAGE
, ProjectConfig
.GETTEXT_DIR
);
237 Intl
.bind_textdomain_codeset(ProjectConfig
.GETTEXT_PACKAGE
, "UTF-8");
238 Intl
.textdomain(ProjectConfig
.GETTEXT_PACKAGE
);
240 return app
.run(args
);
243 private OptionGroup
get_game_option_group()
245 var group
= new
OptionGroup("game", _("Game Options:"), _("Show game options help"));
246 group
.add_entries(options_game
);
247 group
.set_translation_domain(ProjectConfig
.GETTEXT_PACKAGE
);
251 private OptionGroup
get_log_option_group()
253 var group
= new
OptionGroup("log", _("Logging Options:"), _("Show logging options help"));
254 group
.add_entries(options_log
);
255 group
.set_translation_domain(ProjectConfig
.GETTEXT_PACKAGE
);
259 public override bool local_command_line(ref weak string[] arguments
, out int exit_status
)
261 OptionContext local_option_context
= new
OptionContext();
262 local_option_context
.set_ignore_unknown_options(true);
263 local_option_context
.set_help_enabled(false);
264 local_option_context
.add_main_entries(local_options
, ProjectConfig
.GETTEXT_PACKAGE
);
265 local_option_context
.add_group(get_log_option_group());
266 local_option_context
.set_translation_domain(ProjectConfig
.GETTEXT_PACKAGE
);
270 unowned
string[] args
= arguments
;
271 local_option_context
.parse(ref args
);
287 OptionContext help_option_context
= new
OptionContext();
288 help_option_context
.set_help_enabled(false);
289 help_option_context
.add_main_entries(local_options
, ProjectConfig
.GETTEXT_PACKAGE
);
290 help_option_context
.add_main_entries(options
, ProjectConfig
.GETTEXT_PACKAGE
);
291 help_option_context
.add_group(get_game_option_group());
292 help_option_context
.add_group(get_log_option_group());
293 help_option_context
.add_group(Gtk
.get_option_group(true));
294 help_option_context
.set_translation_domain(ProjectConfig
.GETTEXT_PACKAGE
);
295 print(help_option_context
.get_help(false, null));
300 Logger
.DisplayLevel
= opt_debug_log ? Logger
.LogLevel
.DEBUG
: Logger
.LogLevel
.INFO
;
302 if(opt_gdb
|| opt_gdb_bt_full
|| opt_gdb_fatal_criticals
)
304 string[] current_args
= arguments
;
305 string[] cmd_args
= {};
306 for(int i
= 1; i
< current_args
.length
; i
++)
308 var arg
= current_args
[i
];
309 if(arg
!= "--gdb" && arg
!= "--gdb-bt-full" && arg
!= "--gdb-fatal-criticals")
314 if(!("--debug" in cmd_args
) && !("-d" in cmd_args
))
316 cmd_args
+= "--debug";
318 string cmd_args_string
= string.joinv(" ", cmd_args
);
320 string[] exec_cmd
= {
321 "gdb", "-q", "--batch",
322 "-ex", @
"set args $cmd_args_string",
323 "-ex", (opt_gdb_fatal_criticals ?
"set env G_DEBUG = fatal-criticals" : "unset env G_DEBUG"),
324 "-ex", "set pagination off",
325 "-ex", "handle SIGHUP nostop pass",
326 "-ex", "handle SIGQUIT nostop pass",
327 "-ex", "handle SIGPIPE nostop pass",
328 "-ex", "handle SIGALRM nostop pass",
329 "-ex", "handle SIGTERM nostop pass",
330 "-ex", "handle SIGUSR1 nostop pass",
331 "-ex", "handle SIGUSR2 nostop pass",
332 "-ex", "handle SIGCHLD nostop pass",
333 "-ex", "set print thread-events off",
335 "-ex", "thread apply all bt" + (opt_gdb_bt_full ?
" full" : ""),
339 info("Restarting with GDB");
340 Utils
.run(exec_cmd
, Environment
.get_current_dir());
345 return base.local_command_line(ref arguments
, out exit_status
);
348 public override int command_line(ApplicationCommandLine cmd
)
350 string[] oargs
= cmd
.get_arguments();
351 unowned
string[] args
= oargs
;
353 var option_context
= new
OptionContext();
354 option_context
.add_main_entries(options
, ProjectConfig
.GETTEXT_PACKAGE
);
355 option_context
.add_group(get_game_option_group());
356 option_context
.add_group(get_log_option_group());
357 option_context
.add_group(Gtk
.get_option_group(true));
358 option_context
.set_translation_domain(ProjectConfig
.GETTEXT_PACKAGE
);
362 option_context
.parse(ref args
);
369 Logger
.DisplayLevel
= opt_debug_log ? Logger
.LogLevel
.DEBUG
: Logger
.LogLevel
.INFO
;
373 if(opt_show
|| (opt_run
== null && opt_game_details
== null && opt_game_properties
== null))
376 main_window
.present();
381 activate_action(ACTION_SETTINGS
, null);
386 activate_action(ACTION_ABOUT
, null);
391 activate_action(ACTION_GAME_RUN
, new Variant
.string(opt_run
));
394 if(opt_game_details
!= null)
396 activate_action(ACTION_GAME_DETAILS
, new Variant
.string(opt_game_details
));
399 if(opt_game_properties
!= null)
401 activate_action(ACTION_GAME_PROPERTIES
, new Variant
.string(opt_game_properties
));
405 opt_game_details
= null;
406 opt_game_properties
= null;
408 opt_show_compat
= false;
410 opt_settings
= false;
416 private void println(bool plain
, string format
, ...)
418 var line
= format
.vprintf(va_list());
429 private void print_version(bool plain
)
431 println(plain
, "- GameHub");
432 println(plain
, " Version: %s", ProjectConfig
.VERSION
);
433 println(plain
, " Branch: %s", ProjectConfig
.GIT_BRANCH
);
434 if(ProjectConfig
.GIT_COMMIT
!= null && ProjectConfig
.GIT_COMMIT
.length
> 0)
436 println(plain
, " Commit: %s", ProjectConfig
.GIT_COMMIT
);
439 println(plain
, "- Environment");
441 println(plain
, " Distro: %s", Utils
.get_distro());
442 println(plain
, " DE: %s", Utils
.get_desktop_environment() ??
"unknown");
444 println(plain
, " OS: %s", Utils
.get_distro());
446 println(plain
, " GTK: %u.%u.%u", Gtk
.get_major_version(), Gtk
.get_minor_version(), Gtk
.get_micro_version());
448 var settings
= Gtk
.Settings
.get_default();
451 println(plain
, " Themes: %s | %s", settings
.gtk_theme_name
, settings
.gtk_icon_theme_name
);
455 private static void action_settings(SimpleAction action
, Variant? args
)
457 new GameHub
.UI
.Dialogs
.SettingsDialog
.SettingsDialog();
460 private static void action_about(SimpleAction action
, Variant? args
)
462 new GameHub
.UI
.Dialogs
.SettingsDialog
.SettingsDialog("about");
465 private static void action_corrupted_installer(SimpleAction action
, Variant? args
)
467 if(args
== null) return;
469 var args_iter
= args
.iterator();
470 string? game_id
= null;
472 args_iter
.next("s", &game_id
);
473 args_iter
.next("s", &path
);
475 if(game_id
== null || path
== null) return;
477 var file
= FSUtils
.file(path
);
478 if(file
== null || !file
.query_exists()) return;
483 case ACTION_CORRUPTED_INSTALLER_PICK_ACTION
:
484 game_id
= game_id
.strip();
485 if(game_id
.length
> 0 && ":" in game_id
)
487 var id_parts
= game_id
.split(":");
488 var game
= GameHub
.Data
.DB
.Tables
.Games
.get(id_parts
[0], id_parts
[1]);
491 var loop
= new
MainLoop();
492 var dlg
= new UI
.Dialogs
.CorruptedInstallerDialog(game
, file
);
493 dlg
.destroy
.connect(() => {
501 case ACTION_CORRUPTED_INSTALLER_SHOW
:
502 Utils
.open_uri(file
.get_parent().get_uri());
505 case ACTION_CORRUPTED_INSTALLER_BACKUP
:
506 file
.move(FSUtils
.file(path
+ ".backup"), FileCopyFlags
.BACKUP
);
509 case ACTION_CORRUPTED_INSTALLER_REMOVE
:
516 warning("[app.installer_action] %s", e
.message
);
520 private static void action_game(SimpleAction action
, Variant? args
)
522 if(args
== null) return;
523 string? game_id
= args
.get_string();
524 if(game_id
== null) return;
526 game_id
= game_id
.strip();
527 if(game_id
.length
> 0 && ":" in game_id
)
529 var id_parts
= game_id
.split(":");
530 var game
= GameHub
.Data
.DB
.Tables
.Games
.get(id_parts
[0], id_parts
[1]);
533 var loop
= new
MainLoop();
534 game
.update_game_info
.begin((obj
, res
) => {
535 game
.update_game_info
.end(res
);
538 case ACTION_GAME_RUN
:
539 info("Starting `%s`", game
.name
);
540 game
.run_or_install
.begin(opt_show_compat
, (obj
, res
) => {
541 game
.run_or_install
.end(res
);
542 info("`%s` finished", game
.name
);
547 case ACTION_GAME_DETAILS
:
548 var dlg
= new UI
.Dialogs
.GameDetailsDialog(game
);
549 dlg
.destroy
.connect(() => {
554 case ACTION_GAME_PROPERTIES
:
555 var dlg
= new UI
.Dialogs
.GamePropertiesDialog(game
);
556 dlg
.destroy
.connect(() => {
566 error("Game with id `%s` from source `%s` is not found", id_parts
[1], id_parts
[0]);
571 error("`%s` is not a fully-qualified game id", opt_run
);