Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bottles/backend/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion bottles/backend/utils/imagemagick.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,4 @@ def convert(
cmd += " -flatten"

cmd += f" '{dest}'"
subprocess.Popen(["bash", "-c", cmd])
subprocess.run(["bash", "-c", cmd])
122 changes: 39 additions & 83 deletions bottles/backend/utils/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()


Expand Down Expand Up @@ -223,101 +227,53 @@ 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")
)
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):
Expand Down
24 changes: 10 additions & 14 deletions bottles/frontend/widgets/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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()
Expand Down
3 changes: 3 additions & 0 deletions data/data.gresource.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
<gresource prefix="/com/usebottles/bottles">
<file preprocess="xml-stripblanks" alias="appdata">@APP_ID@.metainfo.xml</file>
</gresource>
<gresource prefix="/com/usebottles/bottles/icons/scalable/apps">
<file preprocess="xml-stripblanks" alias="com.usebottles.bottles-program.svg">icons/hicolor/scalable/apps/com.usebottles.bottles-program.svg</file>
</gresource>
<gresource prefix="/com/usebottles/bottles/icons/scalable/actions">
<file preprocess="xml-stripblanks" alias="bottles-steam-symbolic.svg">icons/hicolor/symbolic/apps/bottles-steam-symbolic.svg</file>
<file preprocess="xml-stripblanks" alias="external-link-symbolic.svg">icons/hicolor/symbolic/actions/external-link-symbolic.svg</file>
Expand Down
Loading