diff --git a/bottles/backend/state.py b/bottles/backend/state.py
index faa0ac1ff9..5bb7fcb082 100644
--- a/bottles/backend/state.py
+++ b/bottles/backend/state.py
@@ -57,6 +57,9 @@ class Signals(Enum):
EagleStep = "Eagle.step" # data(Result): msg(str)
EagleFinished = "Eagle.finished" # data(Result): results(dict)
+ # Dynamic launcher portal
+ DesktopEntryCreated = "DesktopEntry.created"
+
class Status(Enum):
RUNNING = "running"
diff --git a/bottles/backend/utils/imagemagick.py b/bottles/backend/utils/imagemagick.py
index bdeda075e9..d44ed6f956 100644
--- a/bottles/backend/utils/imagemagick.py
+++ b/bottles/backend/utils/imagemagick.py
@@ -85,4 +85,4 @@ def convert(
cmd += " -flatten"
cmd += f" '{dest}'"
- subprocess.Popen(["bash", "-c", cmd])
+ subprocess.run(["bash", "-c", cmd])
diff --git a/bottles/backend/utils/manager.py b/bottles/backend/utils/manager.py
index 3c879c7186..c3b9ecfad5 100644
--- a/bottles/backend/utils/manager.py
+++ b/bottles/backend/utils/manager.py
@@ -17,13 +17,13 @@
import os
import shlex
import shutil
-from datetime import datetime
from gettext import gettext as _
-from glob import glob
from typing import Optional
import icoextract # type: ignore [import-untyped]
+from bottles.backend.params import APP_ID
+
from bottles.backend.globals import Paths
from bottles.backend.logger import Logger
from bottles.backend.models.config import BottleConfig
@@ -32,6 +32,10 @@
from bottles.backend.utils.generic import get_mime
from bottles.backend.utils.imagemagick import ImageMagickUtils
+from gi.repository import GLib, Gio, Xdp
+
+portal = Xdp.Portal()
+
logging = Logger()
@@ -223,22 +227,9 @@ def create_desktop_entry(
program: dict,
skip_icon: bool = False,
custom_icon: str = "",
- use_xdp: bool = False,
- ) -> bool:
- if not use_xdp:
- try:
- os.makedirs(Paths.applications, exist_ok=True)
- except OSError:
- return False
-
- cmd_legacy = "bottles"
- cmd_cli = "bottles-cli"
+ ):
icon = "com.usebottles.bottles-program"
- if "FLATPAK_ID" in os.environ:
- cmd_legacy = "flatpak run com.usebottles.bottles"
- cmd_cli = "flatpak run --command=bottles-cli com.usebottles.bottles"
-
if not skip_icon and not custom_icon:
icon = ManagerUtils.extract_icon(
config, program.get("name"), program.get("path")
@@ -246,78 +237,43 @@ def create_desktop_entry(
elif custom_icon:
icon = custom_icon
- if not use_xdp:
- file_name_template = "%s/%s--%s--%s.desktop"
- existing_files = glob(
- file_name_template
- % (Paths.applications, config.Name, program.get("name"), "*")
+ def prepare_install_cb (self, result):
+ ret = portal.dynamic_launcher_prepare_install_finish(result)
+ id = f"{config.get('Name')}.{program.get('name')}"
+ sum_type = GLib.ChecksumType.SHA1
+ exec = "bottles-cli run -p {} -b '{}' -- %u".format(
+ shlex.quote(program.get('name')), config.get('Name')
)
- desktop_file = file_name_template % (
- Paths.applications,
- config.Name,
- program.get("name"),
- datetime.now().timestamp(),
+ portal.dynamic_launcher_install(
+ ret["token"],
+ "{}.App_{}.desktop".format(
+ APP_ID, GLib.compute_checksum_for_string(sum_type, id, -1)
+ ),
+ """[Desktop Entry]
+ Exec={}
+ Type=Application
+ Terminal=false
+ Categories=Application;
+ Comment=Launch {} using Bottles.
+ StartupWMClass={}""".format(
+ exec, program.get("name"), program.get("name")
+ )
)
+ SignalManager.send(Signals.DesktopEntryCreated)
- if existing_files:
- for file in existing_files:
- os.remove(file)
-
- # [Bug-]issue #4247 (single- to double-quotes in Desktop Entry spec -> "The Exec key"):
- with open(desktop_file, "w") as f:
- f.write("[Desktop Entry]\n")
- f.write(f"Name={program.get('name')}\n")
- f.write(
- f"Exec={cmd_cli} run -p \"{program.get('name')}\" -b \"{config.get('Name')}\" -- %u\n"
- )
- f.write("Type=Application\n")
- f.write("Terminal=false\n")
- f.write("Categories=Application;\n")
- f.write(f"Icon={icon}\n")
- f.write(f"Comment=Launch {program.get('name')} using Bottles.\n")
- f.write(f"StartupWMClass={program.get('name')}\n")
- # Actions
- f.write("Actions=Configure;\n")
- f.write("[Desktop Action Configure]\n")
- f.write("Name=Configure in Bottles\n")
- f.write(f"Exec={cmd_legacy} -b \"{config.get('Name')}\"\n")
-
- return True
- '''
- WIP: the following code is not working yet, it raises an error:
- GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod
- import uuid
- from gi.repository import Gio, Xdp
-
- portal = Xdp.Portal()
if icon == "com.usebottles.bottles-program":
- _icon = Gio.BytesIcon.new(icon.encode("utf-8"))
+ icon += ".svg"
+ _icon = Gio.File.new_for_uri(
+ f"resource:/com/usebottles/bottles/icons/scalable/apps/{icon}"
+ )
else:
- _icon = Gio.FileIcon.new(Gio.File.new_for_path(icon))
- icon_v = _icon.serialize()
- token = portal.dynamic_launcher_request_install_token(program.get("name"), icon_v)
- portal.dynamic_launcher_install(
- token,
- f"com.usebottles.bottles.{config.get('Name')}.{program.get('name')}.{str(uuid.uuid4())}.desktop",
- """
- [Desktop Entry]
- Exec={}
- Type=Application
- Terminal=false
- Categories=Application;
- Comment=Launch {} using Bottles.
- Actions=Configure;
- [Desktop Action Configure]
- Name=Configure in Bottles
- Exec={}
- """.format(
- f"{cmd_cli} run -p {shlex.quote(program.get('name'))} -b '{config.get('Path')}'",
- program.get("name"),
- f"{cmd_legacy} -b '{config.get('Name')}'"
- ).encode("utf-8")
- )
- '''
- return False
+ _icon = Gio.File.new_for_path(icon)
+ icon_v = Gio.BytesIcon.new(_icon.load_bytes()[0]).serialize()
+ portal.dynamic_launcher_prepare_install(None,
+ program.get("name"), icon_v,
+ Xdp.LauncherType.APPLICATION,
+ None, True, False, None,
+ prepare_install_cb)
@staticmethod
def browse_wineprefix(wineprefix: dict):
diff --git a/bottles/frontend/widgets/program.py b/bottles/frontend/widgets/program.py
index 61542da9d0..5778029d51 100644
--- a/bottles/frontend/widgets/program.py
+++ b/bottles/frontend/widgets/program.py
@@ -23,6 +23,7 @@
from bottles.backend.managers.library import LibraryManager
from bottles.backend.managers.steam import SteamManager
from bottles.backend.models.result import Result
+from bottles.backend.state import SignalManager, Signals
from bottles.backend.utils.manager import ManagerUtils
from bottles.backend.utils.threading import RunAsync
from bottles.backend.wine.executor import WineExecutor
@@ -34,6 +35,7 @@
from bottles.frontend.windows.playtimegraph import PlaytimeGraphDialog
from bottles.frontend.windows.rename import RenameDialog
+from typing import Optional
# noinspection PyUnusedLocal
@Gtk.Template(resource_path="/com/usebottles/bottles/program-entry.ui")
@@ -348,27 +350,21 @@ def browse_program_folder(self, _widget):
self.pop_actions.popdown() # workaround #1640
def add_entry(self, _widget):
- @GtkUtils.run_in_main_loop
- def update(result, _error=False):
- if not result:
- webbrowser.open("https://docs.usebottles.com/bottles/programs#flatpak")
- return
-
- self.window.show_toast(
- _('Desktop Entry created for "{0}"').format(self.program["name"])
- )
-
- RunAsync(
- ManagerUtils.create_desktop_entry,
- callback=update,
+ ManagerUtils.create_desktop_entry(
config=self.config,
program={
"name": self.program["name"],
"executable": self.program["executable"],
"path": self.program["path"],
- },
+ }
)
+ def _on_desktop_entry_created(data: Optional[Result] = None) -> None:
+ self.window.show_toast(
+ _('Desktop Entry created for "{0}"').format(self.program["name"])
+ )
+ SignalManager.connect(Signals.DesktopEntryCreated, _on_desktop_entry_created)
+
def add_to_library(self, _widget):
def update(_result, _error=False):
self.window.update_library()
diff --git a/data/data.gresource.xml.in b/data/data.gresource.xml.in
index 13b85cd35f..c200be6c3d 100644
--- a/data/data.gresource.xml.in
+++ b/data/data.gresource.xml.in
@@ -3,6 +3,9 @@
@APP_ID@.metainfo.xml
+
+ icons/hicolor/scalable/apps/com.usebottles.bottles-program.svg
+
icons/hicolor/symbolic/apps/bottles-steam-symbolic.svg
icons/hicolor/symbolic/actions/external-link-symbolic.svg