Bump version to 0.15.0
[GameHub.git] / src / app.vala
blobb690bda138ef72a2f672aafd27da3f5396abf852
1 /*
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/>.
19 using Gtk;
20 using Gdk;
21 using Gee;
23 using GameHub.Data;
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;
29 using GameHub.Utils;
31 namespace GameHub
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 },
94 { 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" },
101 { null }
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" },
108 { null }
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 },
117 { null }
120 construct
122 application_id = ProjectConfig.PROJECT_NAME;
123 flags = ApplicationFlags.HANDLES_COMMAND_LINE;
124 instance = this;
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;
134 private void init()
136 if(GameSources != null && CompatTools != null) return;
138 FSUtils.make_dirs();
139 ImageCache.init();
140 Database.create();
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();
171 CompatTools = tools;
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);
193 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);
213 init();
215 #if MANETTE
216 GameHub.Utils.Gamepad.init();
217 #endif
219 main_window = new GameHub.UI.Windows.MainWindow(this);
220 main_window.show_all();
224 public static int main(string[] args)
226 #if MANETTE
227 X.init_threads();
228 #endif
230 var app = new Application();
232 Utils.Logger.init();
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);
248 return group;
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);
256 return group;
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);
273 catch(Error e)
275 warning(e.message);
278 if(opt_show_version)
280 print_version(true);
281 exit_status = 0;
282 return true;
285 if(opt_help)
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));
296 exit_status = 0;
297 return true;
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")
311 cmd_args += arg;
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",
334 "-ex", "run",
335 "-ex", "thread apply all bt" + (opt_gdb_bt_full ? " full" : ""),
336 current_args[0]
339 info("Restarting with GDB");
340 Utils.run(exec_cmd, Environment.get_current_dir());
341 exit_status = 0;
342 return true;
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);
364 catch(Error e)
366 warning(e.message);
369 Logger.DisplayLevel = opt_debug_log ? Logger.LogLevel.DEBUG : Logger.LogLevel.INFO;
371 init();
373 if(opt_show || (opt_run == null && opt_game_details == null && opt_game_properties == null))
375 activate();
376 main_window.present();
379 if(opt_settings)
381 activate_action(ACTION_SETTINGS, null);
384 if(opt_about)
386 activate_action(ACTION_ABOUT, null);
389 if(opt_run != 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));
404 opt_run = null;
405 opt_game_details = null;
406 opt_game_properties = null;
408 opt_show_compat = false;
409 opt_show = false;
410 opt_settings = false;
412 return 0;
415 [PrintfFormat]
416 private void println(bool plain, string format, ...)
418 var line = format.vprintf(va_list());
419 if(plain)
421 print(line + "\n");
423 else
425 info(line);
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");
440 #if OS_LINUX
441 println(plain, " Distro: %s", Utils.get_distro());
442 println(plain, " DE: %s", Utils.get_desktop_environment() ?? "unknown");
443 #else
444 println(plain, " OS: %s", Utils.get_distro());
445 #endif
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();
449 if(settings != null)
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;
471 string? path = 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;
481 switch(action.name)
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]);
489 if(game != null)
491 var loop = new MainLoop();
492 var dlg = new UI.Dialogs.CorruptedInstallerDialog(game, file);
493 dlg.destroy.connect(() => {
494 loop.quit();
496 loop.run();
499 break;
501 case ACTION_CORRUPTED_INSTALLER_SHOW:
502 Utils.open_uri(file.get_parent().get_uri());
503 break;
505 case ACTION_CORRUPTED_INSTALLER_BACKUP:
506 file.move(FSUtils.file(path + ".backup"), FileCopyFlags.BACKUP);
507 break;
509 case ACTION_CORRUPTED_INSTALLER_REMOVE:
510 file.delete();
511 break;
514 catch(Error e)
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]);
531 if(game != null)
533 var loop = new MainLoop();
534 game.update_game_info.begin((obj, res) => {
535 game.update_game_info.end(res);
536 switch(action.name)
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);
543 loop.quit();
545 break;
547 case ACTION_GAME_DETAILS:
548 var dlg = new UI.Dialogs.GameDetailsDialog(game);
549 dlg.destroy.connect(() => {
550 loop.quit();
552 break;
554 case ACTION_GAME_PROPERTIES:
555 var dlg = new UI.Dialogs.GamePropertiesDialog(game);
556 dlg.destroy.connect(() => {
557 loop.quit();
559 break;
562 loop.run();
564 else
566 error("Game with id `%s` from source `%s` is not found", id_parts[1], id_parts[0]);
569 else
571 error("`%s` is not a fully-qualified game id", opt_run);