diff --git a/data/gtk/details-window.blp b/data/gtk/details-window.blp index d00b899..6efa0f0 100644 --- a/data/gtk/details-window.blp +++ b/data/gtk/details-window.blp @@ -2,153 +2,159 @@ using Gtk 4.0; using Adw 1; template $DetailsWindow : Adw.Window { - default-width: 480; // Same as Nautilus' properties window - default-height: -1; - modal: true; + default-width: 480; // Same as Nautilus' properties window + default-height: -1; + modal: true; - ShortcutController { - Shortcut { - trigger: "Escape"; - action: "action(window.close)"; - } + ShortcutController { + Shortcut { + trigger: "Escape"; + action: "action(window.close)"; + } + } + + Box { + orientation: vertical; + + Adw.HeaderBar HeaderBar { + show-start-title-buttons: false; + show-end-title-buttons: false; + + [start] + Button cancel_button { + label: _("Cancel"); + action-name: "window.close"; + } + + [end] + Button apply_button { + styles [ + "suggested-action", + ] + } } - Box { - orientation: vertical; + Adw.PreferencesPage { + vexpand: true; - Adw.HeaderBar HeaderBar { - show-start-title-buttons: false; - show-end-title-buttons: false; + Adw.PreferencesGroup cover_group { + Adw.Clamp cover_clamp { + maximum-size: 200; - [start] - Button cancel_button { - label: _("Cancel"); - action-name: "window.close"; + Overlay { + [overlay] + Spinner spinner { + margin-start: 72; + margin-end: 72; } - [end] - Button apply_button { + Overlay cover_overlay { + halign: center; + valign: center; + + [overlay] + Button cover_button_edit { + icon-name: "document-edit-symbolic"; + tooltip-text: _("New Cover"); + halign: end; + valign: end; + margin-bottom: 6; + margin-end: 6; + styles [ - "suggested-action" + "circular", + "osd", ] + } + + [overlay] + Revealer cover_button_delete_revealer { + transition-type: crossfade; + margin-end: 40; + + Button cover_button_delete { + icon-name: "user-trash-symbolic"; + tooltip-text: _("Delete Cover"); + halign: end; + valign: end; + margin-bottom: 6; + margin-end: 6; + + styles [ + "circular", + "osd", + ] + } + } + + Picture cover { + width-request: 200; + height-request: 300; + + styles [ + "card", + ] + } } + } + } + } + + Adw.PreferencesGroup { + Adw.EntryRow name { + title: _("Title"); } - Adw.PreferencesPage { - vexpand: true; - - Adw.PreferencesGroup cover_group { - Adw.Clamp cover_clamp { - maximum-size: 200; - Overlay { - [overlay] - Spinner spinner { - margin-start: 72; - margin-end: 72; - } - - Overlay cover_overlay { - halign: center; - valign: center; - - [overlay] - Button cover_button_edit { - icon-name: "document-edit-symbolic"; - tooltip-text: _("New Cover"); - halign: end; - valign: end; - margin-bottom: 6; - margin-end: 6; - - styles [ - "circular", "osd" - ] - } - - [overlay] - Revealer cover_button_delete_revealer { - transition-type: crossfade; - margin-end: 40; - - Button cover_button_delete { - icon-name: "user-trash-symbolic"; - tooltip-text: _("Delete Cover"); - halign: end; - valign: end; - margin-bottom: 6; - margin-end: 6; - - styles [ - "circular", "osd" - ] - } - } - - Picture cover { - width-request: 200; - height-request: 300; - - styles [ - "card" - ] - } - } - } - } - } - - Adw.PreferencesGroup { - Adw.EntryRow name { - title: _("Title"); - } - Adw.EntryRow developer { - title: _("Developer (optional)"); - } - } - Adw.PreferencesGroup { - Adw.EntryRow executable { - title: _("Executable"); - - [suffix] - Gtk.Button file_chooser_button { - valign: center; - icon-name: "document-open-symbolic"; - tooltip-text: _("Select File"); - - styles [ - "flat" - ] - } - - [suffix] - Gtk.MenuButton exec_info_button { - valign: center; - icon-name: "help-about-symbolic"; - tooltip-text: _("More Info"); - - popover: Popover exec_info_popover { - focusable: true; - - Label exec_info_label { - use-markup: true; - wrap: true; - max-width-chars: 50; - halign: center; - valign: center; - margin-top: 6; - margin-bottom: 6; - margin-start: 6; - margin-end: 6; - } - }; - - styles [ - "flat" - ] - } - - } - } + Adw.EntryRow developer { + title: _("Developer (optional)"); } + } + + Adw.PreferencesGroup { + Adw.EntryRow executable { + title: _("Executable"); + + [suffix] + Button file_chooser_button { + valign: center; + icon-name: "document-open-symbolic"; + tooltip-text: _("Select File"); + + styles [ + "flat", + ] + } + + [suffix] + MenuButton exec_info_button { + valign: center; + icon-name: "help-about-symbolic"; + tooltip-text: _("More Info"); + popover: + Popover exec_info_popover { + focusable: true; + + Label exec_info_label { + use-markup: true; + wrap: true; + max-width-chars: 50; + halign: center; + valign: center; + margin-top: 6; + margin-bottom: 6; + margin-start: 6; + margin-end: 6; + } + } + + ; + + styles [ + "flat", + ] + } + } + } } -} \ No newline at end of file + } +} diff --git a/data/gtk/preferences.blp b/data/gtk/preferences.blp index bd564dd..bd33ce1 100644 --- a/data/gtk/preferences.blp +++ b/data/gtk/preferences.blp @@ -76,7 +76,6 @@ template $PreferencesWindow : Adw.PreferencesWindow { ] } } - } } @@ -311,6 +310,35 @@ template $PreferencesWindow : Adw.PreferencesWindow { } } } + + Adw.ExpanderRow desktop_expander_row { + title: _("Desktop Entries"); + show-enable-switch: true; + + Adw.ComboRow desktop_terminal_exec_row { + title: _("Terminal"); + subtitle: _("Used only by games that require one to run"); + model: StringList { + strings [ + _("Custom"), + "xdg-terminal-exec", + "GNOME Console", + "GNOME Terminal", + "Konsole", + "XTerm" + ] + }; + [suffix] + Revealer desktop_tereminal_custom_exec_revealer { + transition-type: slide_right; + + Entry desktop_tereminal_custom_exec { + valign: center; + placeholder-text: _("Executable"); + } + } + } + } } } diff --git a/data/gtk/window.blp b/data/gtk/window.blp index bf5b6db..c68e1ee 100644 --- a/data/gtk/window.blp +++ b/data/gtk/window.blp @@ -194,13 +194,19 @@ template $CartridgesWindow : Adw.ApplicationWindow { action-name: "app.edit_game"; tooltip-text: _("Edit"); - styles ["raised", "circular"] + styles [ + "raised", + "circular", + ] } Button details_view_hide_button { action-name: "app.hide_game"; - styles ["raised", "circular"] + styles [ + "raised", + "circular", + ] } Button { @@ -208,7 +214,10 @@ template $CartridgesWindow : Adw.ApplicationWindow { action-name: "app.remove_game"; tooltip-text: _("Remove"); - styles ["raised", "circular"] + styles [ + "raised", + "circular", + ] } MenuButton { @@ -216,7 +225,10 @@ template $CartridgesWindow : Adw.ApplicationWindow { menu-model: search; tooltip-text: _("Search"); - styles ["raised", "circular"] + styles [ + "raised", + "circular", + ] } styles [ diff --git a/data/hu.kramo.Cartridges.gschema.xml.in b/data/hu.kramo.Cartridges.gschema.xml.in index 9003a93..3da3a37 100644 --- a/data/hu.kramo.Cartridges.gschema.xml.in +++ b/data/hu.kramo.Cartridges.gschema.xml.in @@ -1,5 +1,15 @@ + + + + + + + + + + false @@ -76,6 +86,15 @@ "~/.var/app/org.libretro.RetroArch/config/retroarch/" + + true + + + "xdg-terminal-exec" + + + "" + true @@ -98,6 +117,7 @@ false + 1110 @@ -121,5 +141,8 @@ "[]" + + false + diff --git a/po/cartridges.pot b/po/cartridges.pot index 0d22dd4..fe22bf5 100644 --- a/po/cartridges.pot +++ b/po/cartridges.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Cartridges\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-08-19 12:45+0200\n" +"POT-Creation-Date: 2023-08-21 16:18+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -19,7 +19,7 @@ msgstr "" #: data/hu.kramo.Cartridges.desktop.in:3 #: data/hu.kramo.Cartridges.metainfo.xml.in:6 data/gtk/window.blp:47 -#: src/main.py:173 +#: src/main.py:174 msgid "Cartridges" msgstr "" @@ -58,7 +58,7 @@ msgstr "" msgid "Game Details" msgstr "" -#: data/hu.kramo.Cartridges.metainfo.xml.in:42 data/gtk/window.blp:418 +#: data/hu.kramo.Cartridges.metainfo.xml.in:42 data/gtk/window.blp:430 #: src/details_window.py:263 src/importer/importer.py:301 #: src/importer/importer.py:352 msgid "Preferences" @@ -68,31 +68,31 @@ msgstr "" msgid "Cancel" msgstr "" -#: data/gtk/details-window.blp:57 +#: data/gtk/details-window.blp:58 msgid "New Cover" msgstr "" -#: data/gtk/details-window.blp:75 +#: data/gtk/details-window.blp:77 msgid "Delete Cover" msgstr "" -#: data/gtk/details-window.blp:102 data/gtk/game.blp:80 +#: data/gtk/details-window.blp:105 data/gtk/game.blp:80 msgid "Title" msgstr "" -#: data/gtk/details-window.blp:105 +#: data/gtk/details-window.blp:109 msgid "Developer (optional)" msgstr "" -#: data/gtk/details-window.blp:110 +#: data/gtk/details-window.blp:115 data/gtk/preferences.blp:310 msgid "Executable" msgstr "" -#: data/gtk/details-window.blp:116 +#: data/gtk/details-window.blp:121 msgid "Select File" msgstr "" -#: data/gtk/details-window.blp:127 +#: data/gtk/details-window.blp:132 msgid "More Info" msgstr "" @@ -105,7 +105,7 @@ msgid "Hide" msgstr "" #: data/gtk/game.blp:112 data/gtk/game.blp:131 data/gtk/preferences.blp:56 -#: data/gtk/window.blp:209 +#: data/gtk/window.blp:215 msgid "Remove" msgstr "" @@ -121,8 +121,8 @@ msgstr "" msgid "Quit" msgstr "" -#: data/gtk/help-overlay.blp:19 data/gtk/window.blp:217 data/gtk/window.blp:257 -#: data/gtk/window.blp:324 +#: data/gtk/help-overlay.blp:19 data/gtk/window.blp:226 data/gtk/window.blp:269 +#: data/gtk/window.blp:336 msgid "Search" msgstr "" @@ -134,7 +134,7 @@ msgstr "" msgid "Shortcuts" msgstr "" -#: data/gtk/help-overlay.blp:34 src/game.py:105 src/preferences.py:122 +#: data/gtk/help-overlay.blp:34 src/game.py:105 src/preferences.py:128 #: src/importer/importer.py:376 msgid "Undo" msgstr "" @@ -163,8 +163,8 @@ msgstr "" msgid "Remove game" msgstr "" -#: data/gtk/preferences.blp:13 data/gtk/preferences.blp:89 -#: data/gtk/preferences.blp:304 +#: data/gtk/preferences.blp:13 data/gtk/preferences.blp:88 +#: data/gtk/preferences.blp:332 msgid "Behavior" msgstr "" @@ -200,114 +200,130 @@ msgstr "" msgid "Remove All Games" msgstr "" -#: data/gtk/preferences.blp:85 data/gtk/window.blp:27 data/gtk/window.blp:444 +#: data/gtk/preferences.blp:84 data/gtk/window.blp:27 data/gtk/window.blp:456 msgid "Import" msgstr "" -#: data/gtk/preferences.blp:92 +#: data/gtk/preferences.blp:91 msgid "Remove Uninstalled Games" msgstr "" -#: data/gtk/preferences.blp:102 +#: data/gtk/preferences.blp:101 msgid "Sources" msgstr "" -#: data/gtk/preferences.blp:105 +#: data/gtk/preferences.blp:104 msgid "Steam" msgstr "" -#: data/gtk/preferences.blp:109 data/gtk/preferences.blp:123 -#: data/gtk/preferences.blp:164 data/gtk/preferences.blp:214 -#: data/gtk/preferences.blp:228 data/gtk/preferences.blp:242 -#: data/gtk/preferences.blp:256 data/gtk/preferences.blp:270 +#: data/gtk/preferences.blp:108 data/gtk/preferences.blp:122 +#: data/gtk/preferences.blp:163 data/gtk/preferences.blp:213 +#: data/gtk/preferences.blp:227 data/gtk/preferences.blp:241 +#: data/gtk/preferences.blp:255 data/gtk/preferences.blp:269 msgid "Install Location" msgstr "" -#: data/gtk/preferences.blp:119 +#: data/gtk/preferences.blp:118 msgid "Lutris" msgstr "" -#: data/gtk/preferences.blp:132 +#: data/gtk/preferences.blp:131 msgid "Cache Location" msgstr "" -#: data/gtk/preferences.blp:141 +#: data/gtk/preferences.blp:140 msgid "Import Steam Games" msgstr "" -#: data/gtk/preferences.blp:150 +#: data/gtk/preferences.blp:149 msgid "Import Flatpak Games" msgstr "" -#: data/gtk/preferences.blp:160 +#: data/gtk/preferences.blp:159 msgid "Heroic" msgstr "" -#: data/gtk/preferences.blp:173 +#: data/gtk/preferences.blp:172 msgid "Import Epic Games" msgstr "" -#: data/gtk/preferences.blp:182 +#: data/gtk/preferences.blp:181 msgid "Import GOG Games" msgstr "" -#: data/gtk/preferences.blp:191 +#: data/gtk/preferences.blp:190 msgid "Import Amazon Games" msgstr "" -#: data/gtk/preferences.blp:200 +#: data/gtk/preferences.blp:199 msgid "Import Sideloaded Games" msgstr "" -#: data/gtk/preferences.blp:210 +#: data/gtk/preferences.blp:209 msgid "Bottles" msgstr "" -#: data/gtk/preferences.blp:224 +#: data/gtk/preferences.blp:223 msgid "itch" msgstr "" -#: data/gtk/preferences.blp:238 +#: data/gtk/preferences.blp:237 msgid "Legendary" msgstr "" -#: data/gtk/preferences.blp:252 +#: data/gtk/preferences.blp:251 msgid "RetroArch" msgstr "" -#: data/gtk/preferences.blp:266 +#: data/gtk/preferences.blp:265 msgid "Flatpak" msgstr "" -#: data/gtk/preferences.blp:279 +#: data/gtk/preferences.blp:278 msgid "Import Game Launchers" msgstr "" +#: data/gtk/preferences.blp:288 +msgid "Desktop Entries" +msgstr "" + #: data/gtk/preferences.blp:292 -msgid "SteamGridDB" +msgid "Terminal" +msgstr "" + +#: data/gtk/preferences.blp:293 +msgid "Used only by games that require one to run" msgstr "" #: data/gtk/preferences.blp:296 +msgid "Custom" +msgstr "" + +#: data/gtk/preferences.blp:320 +msgid "SteamGridDB" +msgstr "" + +#: data/gtk/preferences.blp:324 msgid "Authentication" msgstr "" -#: data/gtk/preferences.blp:299 +#: data/gtk/preferences.blp:327 msgid "API Key" msgstr "" -#: data/gtk/preferences.blp:307 +#: data/gtk/preferences.blp:335 msgid "Use SteamGridDB" msgstr "" -#: data/gtk/preferences.blp:308 +#: data/gtk/preferences.blp:336 msgid "Download images when adding or importing games" msgstr "" -#: data/gtk/preferences.blp:317 +#: data/gtk/preferences.blp:345 msgid "Prefer Over Official Images" msgstr "" -#: data/gtk/preferences.blp:326 +#: data/gtk/preferences.blp:354 msgid "Prefer Animated Images" msgstr "" @@ -335,7 +351,7 @@ msgstr "" msgid "Games you hide will appear here." msgstr "" -#: data/gtk/window.blp:64 data/gtk/window.blp:305 +#: data/gtk/window.blp:64 data/gtk/window.blp:317 msgid "Back" msgstr "" @@ -347,64 +363,64 @@ msgstr "" msgid "Play" msgstr "" -#: data/gtk/window.blp:243 data/gtk/window.blp:437 +#: data/gtk/window.blp:255 data/gtk/window.blp:449 msgid "Add Game" msgstr "" -#: data/gtk/window.blp:250 data/gtk/window.blp:317 +#: data/gtk/window.blp:262 data/gtk/window.blp:329 msgid "Main Menu" msgstr "" -#: data/gtk/window.blp:272 +#: data/gtk/window.blp:284 msgid "Search games" msgstr "" -#: data/gtk/window.blp:312 +#: data/gtk/window.blp:324 msgid "Hidden Games" msgstr "" -#: data/gtk/window.blp:339 +#: data/gtk/window.blp:351 msgid "Search hidden games" msgstr "" -#: data/gtk/window.blp:376 +#: data/gtk/window.blp:388 msgid "Sort" msgstr "" -#: data/gtk/window.blp:379 +#: data/gtk/window.blp:391 msgid "A-Z" msgstr "" -#: data/gtk/window.blp:385 +#: data/gtk/window.blp:397 msgid "Z-A" msgstr "" -#: data/gtk/window.blp:391 +#: data/gtk/window.blp:403 msgid "Newest" msgstr "" -#: data/gtk/window.blp:397 +#: data/gtk/window.blp:409 msgid "Oldest" msgstr "" -#: data/gtk/window.blp:403 +#: data/gtk/window.blp:415 msgid "Last Played" msgstr "" -#: data/gtk/window.blp:410 +#: data/gtk/window.blp:422 msgid "Show Hidden" msgstr "" -#: data/gtk/window.blp:423 +#: data/gtk/window.blp:435 msgid "Keyboard Shortcuts" msgstr "" -#: data/gtk/window.blp:428 +#: data/gtk/window.blp:440 msgid "About Cartridges" msgstr "" #. Translators: Replace this with your name for it to show up in the about window -#: src/main.py:192 +#: src/main.py:193 msgid "translator_credits" msgstr "" @@ -434,8 +450,7 @@ msgstr "" msgid "Add" msgstr "" -#. Gdk.Texture supports .svg but PIL doesn't -#: src/details_window.py:88 +#: src/details_window.py:91 msgid "Executables" msgstr "" @@ -508,28 +523,28 @@ msgstr "" msgid "{} removed" msgstr "" -#: src/preferences.py:121 +#: src/preferences.py:127 msgid "All games removed" msgstr "" -#: src/preferences.py:170 +#: src/preferences.py:207 msgid "" "An API key is required to use SteamGridDB. You can generate one {}here{}." msgstr "" -#: src/preferences.py:298 +#: src/preferences.py:335 msgid "Installation Not Found" msgstr "" -#: src/preferences.py:300 +#: src/preferences.py:337 msgid "Select a valid directory." msgstr "" -#: src/preferences.py:356 +#: src/preferences.py:393 msgid "Invalid Directory" msgstr "" -#: src/preferences.py:362 +#: src/preferences.py:399 msgid "Set Location" msgstr "" @@ -537,7 +552,7 @@ msgstr "" msgid "Dismiss" msgstr "" -#: src/importer/importer.py:135 +#: src/importer/importer.py:137 msgid "Importing Games…" msgstr "" diff --git a/src/importer/sources/bottles_source.py b/src/importer/sources/bottles_source.py index d85529c..5769077 100644 --- a/src/importer/sources/bottles_source.py +++ b/src/importer/sources/bottles_source.py @@ -73,7 +73,6 @@ class BottlesSourceIterable(SourceIterable): image_path = bottles_location / bottle_path / "grids" / image_name additional_data = {"local_image_path": image_path} - # Produce game yield (game, additional_data) diff --git a/src/importer/sources/desktop_source.py b/src/importer/sources/desktop_source.py new file mode 100644 index 0000000..6e78b0b --- /dev/null +++ b/src/importer/sources/desktop_source.py @@ -0,0 +1,214 @@ +# desktop_source.py +# +# Copyright 2023 kramo +# +# This program 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. +# +# This program 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 this program. If not, see . +# +# SPDX-License-Identifier: GPL-3.0-or-later + +import shlex +from hashlib import sha3_256 +from pathlib import Path +from time import time +from typing import NamedTuple + +from gi.repository import GLib, Gtk + +from src import shared +from src.game import Game +from src.importer.sources.source import Source, SourceIterable + + +class DesktopSourceIterable(SourceIterable): + source: "DesktopSource" + + def __iter__(self): + """Generator method producing games""" + + added_time = int(time()) + + icon_theme = Gtk.IconTheme.new() + + search_paths = [ + shared.home / ".local" / "share", + "/run/host/usr/local/share", + "/run/host/usr/share", + "/run/host/usr/share/pixmaps", + "/usr/share/pixmaps", + ] + GLib.get_system_data_dirs() + + for search_path in search_paths: + path = Path(search_path) + + if not str(search_path).endswith("/pixmaps"): + path = path / "icons" + + if not path.is_dir(): + continue + + if str(path).startswith("/app/"): + continue + + icon_theme.add_search_path(str(path)) + + terminal_exec = self.get_terminal_exec() + + for path in search_paths: + if str(path).startswith("/app/"): + continue + + path = Path(path) / "applications" + + if not path.is_dir(): + continue + + for entry in path.iterdir(): + if entry.suffix != ".desktop": + continue + + # Skip Lutris games + if str(entry.name).startswith("net.lutris."): + continue + + keyfile = GLib.KeyFile.new() + + try: + keyfile.load_from_file(str(entry), 0) + + if "Game" not in keyfile.get_string_list( + "Desktop Entry", "Categories" + ): + continue + + name = keyfile.get_string("Desktop Entry", "Name") + executable = keyfile.get_string("Desktop Entry", "Exec").split( + " %" + )[0] + except GLib.GError: + continue + + # Skip Steam games + if "steam://rungameid/" in executable: + continue + + # Skip Heroic games + if "heroic://launch/" in executable: + continue + + # Skip Bottles games + if "bottles-cli " in executable: + continue + + try: + if keyfile.get_boolean("Desktop Entry", "NoDisplay"): + continue + except GLib.GError: + pass + + try: + terminal = keyfile.get_boolean("Desktop Entry", "Terminal") + except GLib.GError: + terminal = False + + try: + cd_path = ( + "cd " + keyfile.get_string("Desktop Entry", "Path") + " && " + ) + except GLib.GError: + cd_path = "" + + values = { + "source": self.source.source_id, + "added": added_time, + "name": name, + "game_id": "desktop_" + + sha3_256( + str(entry).encode("utf-8"), usedforsecurity=False + ).hexdigest(), + "executable": cd_path + + ( + (terminal_exec + shlex.quote(executable)) + if terminal + else executable + ), + } + game = Game(values) + + additional_data = {} + + try: + icon_str = keyfile.get_string("Desktop Entry", "Icon") + except GLib.GError: + print("AAAAAAAAAAAAAAAAAAAAAAA") + yield game + continue + else: + if "/" in icon_str: + additional_data = {"local_icon_path": Path(icon_str)} + yield (game, additional_data) + continue + + try: + if ( + icon_path := icon_theme.lookup_icon( + icon_str, + None, + 512, + 1, + shared.win.get_direction(), + 0, + ) + .get_file() + .get_path() + ): + additional_data = {"local_icon_path": Path(icon_path)} + except GLib.GError: + pass + + yield (game, additional_data) + + def get_terminal_exec(self) -> str: + match shared.schema.get_enum("desktop-terminal"): + case 0: + terminal_exec = shared.schema.get_string("desktop-terminal-custom-exec") + case 1: + terminal_exec = "xdg-terminal-exec" + case 2: + terminal_exec = "kgx -e" + case 3: + terminal_exec = "gnome-terminal --" + case 4: + terminal_exec = "konsole -e" + case 5: + terminal_exec = "xterm -e" + return terminal_exec + " " + + +class DesktopLocations(NamedTuple): + pass + + +class DesktopSource(Source): + """Generic Flatpak source""" + + source_id = "desktop" + name = _("Desktop") + iterable_class = DesktopSourceIterable + available_on = {"linux"} + + locations: DesktopLocations + + def __init__(self) -> None: + super().__init__() + self.locations = DesktopLocations() diff --git a/src/importer/sources/flatpak_source.py b/src/importer/sources/flatpak_source.py index 66b8b57..c0840f2 100644 --- a/src/importer/sources/flatpak_source.py +++ b/src/importer/sources/flatpak_source.py @@ -109,7 +109,6 @@ class FlatpakSourceIterable(SourceIterable): except GLib.GError: pass - # Produce game yield (game, additional_data) diff --git a/src/importer/sources/lutris_source.py b/src/importer/sources/lutris_source.py index dd5defd..5e00ee5 100644 --- a/src/importer/sources/lutris_source.py +++ b/src/importer/sources/lutris_source.py @@ -77,7 +77,6 @@ class LutrisSourceIterable(SourceIterable): image_path = self.source.locations.cache["coverart"] / f"{row[2]}.jpg" additional_data = {"local_image_path": image_path} - # Produce game yield (game, additional_data) # Cleanup diff --git a/src/importer/sources/source.py b/src/importer/sources/source.py index e19b4dd..4f6be55 100644 --- a/src/importer/sources/source.py +++ b/src/importer/sources/source.py @@ -78,7 +78,6 @@ class Source(Iterable): def is_available(self) -> bool: return sys.platform in self.available_on - @abstractmethod def make_executable(self, *args, **kwargs) -> str: """ Create a game executable command. diff --git a/src/importer/sources/steam_source.py b/src/importer/sources/steam_source.py index 75b8f69..3d10aa4 100644 --- a/src/importer/sources/steam_source.py +++ b/src/importer/sources/steam_source.py @@ -105,7 +105,6 @@ class SteamSourceIterable(SourceIterable): ) additional_data = {"local_image_path": image_path, "steam_appid": appid} - # Produce game yield (game, additional_data) diff --git a/src/main.py b/src/main.py index 6c59d22..63aedc4 100644 --- a/src/main.py +++ b/src/main.py @@ -20,6 +20,8 @@ import json import lzma import os +import shlex +import subprocess import sys from typing import Any, Optional @@ -36,6 +38,7 @@ from src.details_window import DetailsWindow from src.game import Game from src.importer.importer import Importer from src.importer.sources.bottles_source import BottlesSource +from src.importer.sources.desktop_source import DesktopSource from src.importer.sources.flatpak_source import FlatpakSource from src.importer.sources.heroic_source import HeroicSource from src.importer.sources.itch_source import ItchSource @@ -72,6 +75,10 @@ class CartridgesApplication(Adw.Application): if os.name == "nt": migrate_files_v1_to_v2() + else: + if not shared.state_schema.get_boolean("terminal-check-done"): + self.check_desktop_terminals() + shared.state_schema.set_boolean("terminal-check-done", True) # Set fallback icon-name Gtk.Window.set_default_icon_name(shared.APP_ID) @@ -142,6 +149,22 @@ class CartridgesApplication(Adw.Application): self.win.present() + def check_desktop_terminals(self) -> None: + """Look for an installed terminal for desktop entries and set the relevant gsetting""" + terminals = ("xdg-terminal-exec", "kgx", "gnome-terminal", "konsole", "xterm") + + for index, command in enumerate(terminals): + command = f"type {command} &> /dev/null" + if os.getenv("FLATPAK_ID") == shared.APP_ID: + command = "flatpak-spawn --host /bin/sh -c " + shlex.quote(command) + + try: + subprocess.run(command, shell=True, check=True) + shared.schema.set_enum("desktop-terminal", index + 1) + return + except subprocess.CalledProcessError: + pass + def load_games_from_disk(self) -> None: if shared.games_dir.is_dir(): for game_file in shared.games_dir.iterdir(): @@ -155,9 +178,9 @@ class CartridgesApplication(Adw.Application): def on_about_action(self, *_args: Any) -> None: # Get the debug info from the log files debug_str = "" - for i, path in enumerate(shared.log_files): + for index, path in enumerate(shared.log_files): # Add a horizontal line between runs - if i > 0: + if index > 0: debug_str += "─" * 37 + "\n" # Add the run's logs log_file = ( @@ -241,6 +264,9 @@ class CartridgesApplication(Adw.Application): if shared.schema.get_boolean("flatpak"): shared.importer.add_source(FlatpakSource()) + if shared.schema.get_boolean("desktop"): + shared.importer.add_source(DesktopSource()) + if shared.schema.get_boolean("itch"): shared.importer.add_source(ItchSource()) diff --git a/src/preferences.py b/src/preferences.py index e801fcd..3111d6b 100644 --- a/src/preferences.py +++ b/src/preferences.py @@ -28,6 +28,7 @@ from gi.repository import Adw, Gio, GLib, Gtk from src import shared from src.game import Game from src.importer.sources.bottles_source import BottlesSource +from src.importer.sources.desktop_source import DesktopSource from src.importer.sources.flatpak_source import FlatpakSource from src.importer.sources.heroic_source import HeroicSource from src.importer.sources.itch_source import ItchSource @@ -97,6 +98,11 @@ class PreferencesWindow(Adw.PreferencesWindow): flatpak_data_file_chooser_button = Gtk.Template.Child() flatpak_import_launchers_switch = Gtk.Template.Child() + desktop_expander_row = Gtk.Template.Child() + desktop_terminal_exec_row = Gtk.Template.Child() + desktop_tereminal_custom_exec_revealer = Gtk.Template.Child() + desktop_tereminal_custom_exec = Gtk.Template.Child() + sgdb_key_group = Gtk.Template.Child() sgdb_key_entry_row = Gtk.Template.Child() sgdb_switch = Gtk.Template.Child() @@ -144,6 +150,7 @@ class PreferencesWindow(Adw.PreferencesWindow): for source_class in ( BottlesSource, FlatpakSource, + DesktopSource, HeroicSource, ItchSource, LegendarySource, @@ -158,6 +165,36 @@ class PreferencesWindow(Adw.PreferencesWindow): else: self.init_source_row(source) + # Desktop Terminal Executable + def set_terminal_exec(widget: Adw.ComboRow, _param: Any) -> None: + shared.schema.set_enum("desktop-terminal", widget.get_selected()) + self.desktop_tereminal_custom_exec_revealer.set_reveal_child( + widget.get_selected() == 0 + ) + + self.desktop_terminal_exec_row.connect("notify::selected", set_terminal_exec) + self.desktop_terminal_exec_row.set_selected( + terminal_value := shared.schema.get_enum("desktop-terminal") + ) + if not terminal_value: + set_terminal_exec( + self.desktop_terminal_exec_row, None + ) # The default value is supposed to be 4294967295, but it's 0 and I can't change it + + self.desktop_tereminal_custom_exec.set_text( + shared.schema.get_string("desktop-terminal-custom-exec") + ) + + def desktop_custom_exec_changed(*_args: Any) -> None: + shared.schema.set_string( + "desktop-terminal-custom-exec", + self.desktop_tereminal_custom_exec.get_text(), + ) + + self.desktop_tereminal_custom_exec.connect( + "changed", desktop_custom_exec_changed + ) + # SteamGridDB def sgdb_key_changed(*_args: Any) -> None: shared.schema.set_string("sgdb-key", self.sgdb_key_entry_row.get_text())