From 2d791e46b04b92c4792ba3b8e30c87019ef8e5a5 Mon Sep 17 00:00:00 2001 From: kramo Date: Mon, 21 Aug 2023 14:09:41 +0200 Subject: [PATCH] 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())