Merge pull request #174 from kra-mo/desktop-file-importer

Desktop file importer
This commit is contained in:
kramo
2023-08-25 21:53:26 +02:00
committed by GitHub
13 changed files with 574 additions and 218 deletions

View File

@@ -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)

View File

@@ -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 <http://www.gnu.org/licenses/>.
#
# 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()

View File

@@ -109,7 +109,6 @@ class FlatpakSourceIterable(SourceIterable):
except GLib.GError:
pass
# Produce game
yield (game, additional_data)

View File

@@ -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

View File

@@ -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.

View File

@@ -105,7 +105,6 @@ class SteamSourceIterable(SourceIterable):
)
additional_data = {"local_image_path": image_path, "steam_appid": appid}
# Produce game
yield (game, additional_data)

View File

@@ -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())

View File

@@ -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())