From 2d791e46b04b92c4792ba3b8e30c87019ef8e5a5 Mon Sep 17 00:00:00 2001 From: kramo Date: Mon, 21 Aug 2023 14:09:41 +0200 Subject: [PATCH 01/11] Basic support for Desktop Entry source --- data/gtk/preferences.blp | 31 ++++- data/hu.kramo.Cartridges.gschema.xml.in | 20 +++ src/importer/sources/bottles_source.py | 1 - src/importer/sources/desktop_source.py | 169 ++++++++++++++++++++++++ src/importer/sources/flatpak_source.py | 1 - src/importer/sources/lutris_source.py | 1 - src/importer/sources/steam_source.py | 1 - src/main.py | 4 + src/preferences.py | 37 ++++++ 9 files changed, 260 insertions(+), 5 deletions(-) create mode 100644 src/importer/sources/desktop_source.py diff --git a/data/gtk/preferences.blp b/data/gtk/preferences.blp index c779347..6537941 100644 --- a/data/gtk/preferences.blp +++ b/data/gtk/preferences.blp @@ -275,7 +275,7 @@ template $PreferencesWindow : Adw.PreferencesWindow { } } - Adw.ActionRow flatpak_import_launchers_row { + Adw.ActionRow flatpak_import_launchers_row { title: _("Import Game Launchers"); activatable-widget: flatpak_import_launchers_switch; @@ -284,6 +284,35 @@ template $PreferencesWindow : Adw.PreferencesWindow { } } } + + Adw.ExpanderRow desktop_expander_row { + title: _("Desktop Files"); + 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_left; + + Entry desktop_tereminal_custom_exec { + valign: center; + placeholder-text: _("Executable"); + } + } + } + } } } diff --git a/data/hu.kramo.Cartridges.gschema.xml.in b/data/hu.kramo.Cartridges.gschema.xml.in index 9003a93..c64dc02 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 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..9213634 --- /dev/null +++ b/src/importer/sources/desktop_source.py @@ -0,0 +1,169 @@ +# desktop_source.py +# +# Copyright 2022-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 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 ExecutableFormatSource, SourceIterable + + +class DesktopSourceIterable(SourceIterable): + source: "DesktopSource" + + def __iter__(self): + """Generator method producing games""" + + added_time = int(time()) + + icon_theme = Gtk.IconTheme.new() + + search_paths = GLib.get_system_data_dirs() + [ + "/run/host/usr/share", + "/run/host/usr/local/share", + shared.home / ".local" / "share", + ] + + for search_path in search_paths: + if not (path := Path(search_path) / "icons").exists(): + continue + + if str(path).startswith("/app/"): + continue + + icon_theme.add_search_path(str(path)) + + 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" + 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 + + 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") + except GLib.GError: + continue + + 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" + executable.replace(" ", "_"), + "executable": self.source.executable_format.format( + exec=cd_path + + ( + (terminal_exec + shlex.quote(executable)) + if terminal + else executable + ) + ), + } + game = Game(values) + + additional_data = {} + + try: + if ( + icon_path := icon_theme.lookup_icon( + keyfile.get_string("Desktop Entry", "Icon"), + None, + 512, + 1, + shared.win.get_direction(), + 0, + ) + .get_file() + .get_path() + ): + additional_data = {"local_icon_path": Path(icon_path)} + else: + pass + except GLib.GError: + pass + + yield (game, additional_data) + + +class DesktopLocations(NamedTuple): + pass + + +class DesktopSource(ExecutableFormatSource): + """Generic Flatpak source""" + + source_id = "desktop" + name = _("Desktop") + iterable_class = DesktopSourceIterable + executable_format = "{exec}" + 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/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..5aa393f 100644 --- a/src/main.py +++ b/src/main.py @@ -36,6 +36,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 @@ -241,6 +242,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 9adf84b..494e3f9 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 Exec + 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()) From 1e2d85b50d76a42af424fe03e019378eea68124d Mon Sep 17 00:00:00 2001 From: kramo Date: Mon, 21 Aug 2023 15:23:29 +0200 Subject: [PATCH 02/11] Proper game_id for desktop entries --- src/importer/sources/desktop_source.py | 6 +++++- src/main.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/importer/sources/desktop_source.py b/src/importer/sources/desktop_source.py index 9213634..6c0dbd8 100644 --- a/src/importer/sources/desktop_source.py +++ b/src/importer/sources/desktop_source.py @@ -18,6 +18,7 @@ # 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 @@ -113,7 +114,10 @@ class DesktopSourceIterable(SourceIterable): "source": self.source.source_id, "added": added_time, "name": name, - "game_id": "desktop" + executable.replace(" ", "_"), + "game_id": "desktop_" + + sha3_256( + str(path).encode("utf-8"), usedforsecurity=False + ).hexdigest(), "executable": self.source.executable_format.format( exec=cd_path + ( diff --git a/src/main.py b/src/main.py index 5aa393f..8a89973 100644 --- a/src/main.py +++ b/src/main.py @@ -156,9 +156,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 = ( From 20f1ce6e83fc145cf430565efc9451418c256d1d Mon Sep 17 00:00:00 2001 From: kramo Date: Mon, 21 Aug 2023 16:04:46 +0200 Subject: [PATCH 03/11] Deskop importer cleanups --- src/importer/sources/desktop_source.py | 67 ++++++++++++++++++++------ 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/src/importer/sources/desktop_source.py b/src/importer/sources/desktop_source.py index 6c0dbd8..32a2dab 100644 --- a/src/importer/sources/desktop_source.py +++ b/src/importer/sources/desktop_source.py @@ -83,6 +83,10 @@ class DesktopSourceIterable(SourceIterable): if entry.suffix != ".desktop": continue + # Skip Lutris games + if str(entry.name).startswith("net.lutris."): + continue + keyfile = GLib.KeyFile.new() try: @@ -94,10 +98,30 @@ class DesktopSourceIterable(SourceIterable): continue name = keyfile.get_string("Desktop Entry", "Name") - executable = keyfile.get_string("Desktop Entry", "Exec") + 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: @@ -116,7 +140,7 @@ class DesktopSourceIterable(SourceIterable): "name": name, "game_id": "desktop_" + sha3_256( - str(path).encode("utf-8"), usedforsecurity=False + str(entry).encode("utf-8"), usedforsecurity=False ).hexdigest(), "executable": self.source.executable_format.format( exec=cd_path @@ -130,26 +154,37 @@ class DesktopSourceIterable(SourceIterable): game = Game(values) additional_data = {} + icon_name = None try: - if ( - icon_path := icon_theme.lookup_icon( - keyfile.get_string("Desktop Entry", "Icon"), - None, - 512, - 1, - shared.win.get_direction(), - 0, - ) - .get_file() - .get_path() - ): - additional_data = {"local_icon_path": Path(icon_path)} + icon_str = keyfile.get_string("Desktop Entry", "Icon") + if "/" in icon_str: + additional_data = {"local_icon_path": Path(icon_str)} else: - pass + icon_name = icon_str except GLib.GError: pass + if icon_name: + try: + if ( + icon_path := icon_theme.lookup_icon( + icon_name, + None, + 512, + 1, + shared.win.get_direction(), + 0, + ) + .get_file() + .get_path() + ): + additional_data = {"local_icon_path": Path(icon_path)} + else: + pass + except GLib.GError: + pass + yield (game, additional_data) From 70b8493e72842ba31fbddece079684086c063d39 Mon Sep 17 00:00:00 2001 From: kramo Date: Mon, 21 Aug 2023 16:08:47 +0200 Subject: [PATCH 04/11] Preferences cleanups --- data/gtk/preferences.blp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/gtk/preferences.blp b/data/gtk/preferences.blp index 6537941..e4e8639 100644 --- a/data/gtk/preferences.blp +++ b/data/gtk/preferences.blp @@ -299,12 +299,12 @@ template $PreferencesWindow : Adw.PreferencesWindow { "GNOME Console", "GNOME Terminal", "Konsole", - "xterm" + "XTerm" ] }; [suffix] Revealer desktop_tereminal_custom_exec_revealer { - transition-type: slide_left; + transition-type: slide_right; Entry desktop_tereminal_custom_exec { valign: center; From 9883cc6b6bd93b5716d223b884044c95d6b04b6e Mon Sep 17 00:00:00 2001 From: kramo Date: Mon, 21 Aug 2023 16:13:31 +0200 Subject: [PATCH 05/11] Blueprint cleanups --- data/gtk/details-window.blp | 278 ++++++++++++++++++------------------ data/gtk/preferences.blp | 3 +- data/gtk/window.blp | 20 ++- 3 files changed, 159 insertions(+), 142 deletions(-) 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 e4e8639..d1d8e93 100644 --- a/data/gtk/preferences.blp +++ b/data/gtk/preferences.blp @@ -76,7 +76,6 @@ template $PreferencesWindow : Adw.PreferencesWindow { ] } } - } } @@ -275,7 +274,7 @@ template $PreferencesWindow : Adw.PreferencesWindow { } } - Adw.ActionRow flatpak_import_launchers_row { + Adw.ActionRow flatpak_import_launchers_row { title: _("Import Game Launchers"); activatable-widget: flatpak_import_launchers_switch; 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 [ From ab128ab4619c1903868a7c2f8e6cd79be97e15d1 Mon Sep 17 00:00:00 2001 From: kramo Date: Mon, 21 Aug 2023 16:18:44 +0200 Subject: [PATCH 06/11] Update translations --- data/gtk/preferences.blp | 2 +- po/cartridges.pot | 155 +++++++++++++++++++++------------------ src/preferences.py | 2 +- 3 files changed, 87 insertions(+), 72 deletions(-) diff --git a/data/gtk/preferences.blp b/data/gtk/preferences.blp index d1d8e93..a93d60b 100644 --- a/data/gtk/preferences.blp +++ b/data/gtk/preferences.blp @@ -285,7 +285,7 @@ template $PreferencesWindow : Adw.PreferencesWindow { } Adw.ExpanderRow desktop_expander_row { - title: _("Desktop Files"); + title: _("Desktop Entries"); show-enable-switch: true; Adw.ComboRow desktop_terminal_exec_row { 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/preferences.py b/src/preferences.py index 494e3f9..10e18ca 100644 --- a/src/preferences.py +++ b/src/preferences.py @@ -165,7 +165,7 @@ class PreferencesWindow(Adw.PreferencesWindow): else: self.init_source_row(source) - # Desktop Terminal Exec + # 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( From 489cb543dea54c84d334bdf355f5e669c37f0cd4 Mon Sep 17 00:00:00 2001 From: kramo <93832451+kra-mo@users.noreply.github.com> Date: Mon, 21 Aug 2023 16:22:06 +0200 Subject: [PATCH 07/11] Fix copyright header --- src/importer/sources/desktop_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/importer/sources/desktop_source.py b/src/importer/sources/desktop_source.py index 32a2dab..a9fdcf5 100644 --- a/src/importer/sources/desktop_source.py +++ b/src/importer/sources/desktop_source.py @@ -1,6 +1,6 @@ # desktop_source.py # -# Copyright 2022-2023 kramo +# 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 From a9196c1e55b214960b27f57accbe84d25bc57cc4 Mon Sep 17 00:00:00 2001 From: kramo Date: Mon, 21 Aug 2023 16:49:31 +0200 Subject: [PATCH 08/11] Auto-detect terminal on first run --- data/hu.kramo.Cartridges.gschema.xml.in | 5 ++++- src/main.py | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/data/hu.kramo.Cartridges.gschema.xml.in b/data/hu.kramo.Cartridges.gschema.xml.in index c64dc02..3da3a37 100644 --- a/data/hu.kramo.Cartridges.gschema.xml.in +++ b/data/hu.kramo.Cartridges.gschema.xml.in @@ -89,7 +89,7 @@ true - + "xdg-terminal-exec" @@ -141,5 +141,8 @@ "[]" + + false + diff --git a/src/main.py b/src/main.py index 8a89973..c04aae5 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 @@ -73,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) @@ -143,6 +149,22 @@ class CartridgesApplication(Adw.Application): self.win.present() + def check_desktop_terminals(self) -> None: + """Look for an installed terminal for desktop entries""" + 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(): From 01f1f31f10abe8fd1e09462da1f65c5afef05ca4 Mon Sep 17 00:00:00 2001 From: kramo Date: Tue, 22 Aug 2023 12:01:21 +0200 Subject: [PATCH 09/11] Add pixmaps to icon search paths --- src/importer/sources/desktop_source.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/importer/sources/desktop_source.py b/src/importer/sources/desktop_source.py index a9fdcf5..8ea7dbf 100644 --- a/src/importer/sources/desktop_source.py +++ b/src/importer/sources/desktop_source.py @@ -41,13 +41,20 @@ class DesktopSourceIterable(SourceIterable): icon_theme = Gtk.IconTheme.new() search_paths = GLib.get_system_data_dirs() + [ + "/usr/share/pixmaps", "/run/host/usr/share", "/run/host/usr/local/share", + "/run/host/usr/share/pixmaps", shared.home / ".local" / "share", ] for search_path in search_paths: - if not (path := Path(search_path) / "icons").exists(): + 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/"): From 8c5f4f178022d2d895a0a22b8241298ee1aa8a93 Mon Sep 17 00:00:00 2001 From: kramo Date: Tue, 22 Aug 2023 13:32:56 +0200 Subject: [PATCH 10/11] Lists are ordered and I'm silly --- src/importer/sources/desktop_source.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/importer/sources/desktop_source.py b/src/importer/sources/desktop_source.py index 8ea7dbf..618d89e 100644 --- a/src/importer/sources/desktop_source.py +++ b/src/importer/sources/desktop_source.py @@ -40,13 +40,13 @@ class DesktopSourceIterable(SourceIterable): icon_theme = Gtk.IconTheme.new() - search_paths = GLib.get_system_data_dirs() + [ - "/usr/share/pixmaps", - "/run/host/usr/share", - "/run/host/usr/local/share", - "/run/host/usr/share/pixmaps", + 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) From e287ec1986b6964c684bab992cc47357e723e872 Mon Sep 17 00:00:00 2001 From: kramo Date: Fri, 25 Aug 2023 21:50:23 +0200 Subject: [PATCH 11/11] Desktop source cleanups --- src/importer/sources/desktop_source.py | 93 +++++++++++++------------- src/importer/sources/source.py | 1 - src/main.py | 2 +- 3 files changed, 47 insertions(+), 49 deletions(-) diff --git a/src/importer/sources/desktop_source.py b/src/importer/sources/desktop_source.py index 618d89e..6e78b0b 100644 --- a/src/importer/sources/desktop_source.py +++ b/src/importer/sources/desktop_source.py @@ -27,7 +27,7 @@ from gi.repository import GLib, Gtk from src import shared from src.game import Game -from src.importer.sources.source import ExecutableFormatSource, SourceIterable +from src.importer.sources.source import Source, SourceIterable class DesktopSourceIterable(SourceIterable): @@ -62,20 +62,7 @@ class DesktopSourceIterable(SourceIterable): icon_theme.add_search_path(str(path)) - 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" - terminal_exec += " " + terminal_exec = self.get_terminal_exec() for path in search_paths: if str(path).startswith("/app/"): @@ -149,63 +136,75 @@ class DesktopSourceIterable(SourceIterable): + sha3_256( str(entry).encode("utf-8"), usedforsecurity=False ).hexdigest(), - "executable": self.source.executable_format.format( - exec=cd_path - + ( - (terminal_exec + shlex.quote(executable)) - if terminal - else executable - ) + "executable": cd_path + + ( + (terminal_exec + shlex.quote(executable)) + if terminal + else executable ), } game = Game(values) additional_data = {} - icon_name = None 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)} - else: - icon_name = 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 - if icon_name: - try: - if ( - icon_path := icon_theme.lookup_icon( - icon_name, - None, - 512, - 1, - shared.win.get_direction(), - 0, - ) - .get_file() - .get_path() - ): - additional_data = {"local_icon_path": Path(icon_path)} - else: - pass - 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(ExecutableFormatSource): +class DesktopSource(Source): """Generic Flatpak source""" source_id = "desktop" name = _("Desktop") iterable_class = DesktopSourceIterable - executable_format = "{exec}" available_on = {"linux"} locations: DesktopLocations 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/main.py b/src/main.py index c04aae5..63aedc4 100644 --- a/src/main.py +++ b/src/main.py @@ -150,7 +150,7 @@ class CartridgesApplication(Adw.Application): self.win.present() def check_desktop_terminals(self) -> None: - """Look for an installed terminal for desktop entries""" + """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):