Better handling for wine based shortcuts.
This commit is contained in:
1 parent
d2c5695221
commit
fb2e1cc55c
3 files changed
+91
-9
No files matched your search
@@ -12,8 +12,8 @@ gi.require_version("Gdk", "3.0")
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gdk, GLib, Gtk
|
||||
|
||||
from shortcuts import (clipboard_target, create_shortcut, fetch_page_title,
|
||||
installed_applications, parse_target)
|
||||
from shortcuts import (clipboard_target, create_shortcut, detect_wine_prefix, fetch_page_title,
|
||||
installed_applications, parse_target, validate_wine_prefix)
|
||||
|
||||
|
||||
class ApplicationDialog(Gtk.Dialog):
|
||||
@@ -132,6 +132,15 @@ class ShortcutWindow(Gtk.Window):
|
||||
self.add_field(box, "_Target (path, program, or web URL):", self.targetEntry)
|
||||
self.nameEntry = Gtk.Entry()
|
||||
self.add_field(box, "Shortcut _name:", self.nameEntry)
|
||||
self.wineBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||
self.wineBox.set_no_show_all(True)
|
||||
self.wineEntry = Gtk.Entry()
|
||||
self.wineEntry.set_activates_default(True)
|
||||
self.add_field(self.wineBox, "_Wine prefix (bottle) folder:", self.wineEntry)
|
||||
wineButton = Gtk.Button.new_with_mnemonic("_Browse for Wine Prefix")
|
||||
wineButton.connect("clicked", self.on_wine_prefix)
|
||||
self.wineBox.pack_start(wineButton, False, False, 0)
|
||||
box.pack_start(self.wineBox, False, False, 0)
|
||||
self.targetEntry.connect("changed", self.on_target_changed)
|
||||
self.targetEntry.connect("activate", self.on_target_activate)
|
||||
self.nameEntry.connect("changed", self.on_name_changed)
|
||||
@@ -181,6 +190,8 @@ class ShortcutWindow(Gtk.Window):
|
||||
self.createButton.set_sensitive(False)
|
||||
self.terminalCheck.set_active(False)
|
||||
self.terminalCheck.set_sensitive(False)
|
||||
self.wineBox.hide()
|
||||
self.wineEntry.set_text("")
|
||||
self.statusLabel.set_text("Press Enter to check the target, or choose a source above.")
|
||||
|
||||
def on_name_changed(self, _entry):
|
||||
@@ -191,10 +202,21 @@ class ShortcutWindow(Gtk.Window):
|
||||
self.targetEntry.set_text(target.value)
|
||||
self.target = target
|
||||
self.nameEntry.set_text(target.name)
|
||||
self.terminalCheck.set_sensitive(target.kind == "Program")
|
||||
self.terminalCheck.set_sensitive(target.kind in ("Program", "Windows program"))
|
||||
self.statusLabel.set_text("{}: {}".format(target.kind, target.value))
|
||||
self.nameEntry.grab_focus()
|
||||
self.nameEntry.select_region(0, -1)
|
||||
if target.kind == "Windows program":
|
||||
winePrefix = detect_wine_prefix(target.value)
|
||||
self.wineEntry.set_text(winePrefix or "")
|
||||
self.wineBox.set_no_show_all(False)
|
||||
self.wineBox.show_all()
|
||||
self.wineBox.set_no_show_all(True)
|
||||
if winePrefix:
|
||||
self.statusLabel.set_text("Windows program: {}. Wine prefix: {}".format(target.value, winePrefix))
|
||||
else:
|
||||
self.statusLabel.set_text("Choose the Wine prefix (bottle) folder before creating this shortcut.")
|
||||
self.wineEntry.grab_focus()
|
||||
if target.kind == "Web link":
|
||||
requestNumber = self.requestNumber
|
||||
nameVersion = self.nameVersion
|
||||
@@ -289,6 +311,18 @@ class ShortcutWindow(Gtk.Window):
|
||||
def on_folder(self, _button):
|
||||
self.choose_path(True)
|
||||
|
||||
def on_wine_prefix(self, _button):
|
||||
dialog = Gtk.FileChooserDialog(title="Choose Wine prefix (bottle) folder",
|
||||
transient_for=self, modal=True,
|
||||
action=Gtk.FileChooserAction.SELECT_FOLDER)
|
||||
dialog.add_buttons("_Cancel", Gtk.ResponseType.CANCEL, "_Select", Gtk.ResponseType.OK)
|
||||
dialog.set_local_only(True)
|
||||
dialog.set_show_hidden(True)
|
||||
if dialog.run() == Gtk.ResponseType.OK:
|
||||
self.wineEntry.set_text(dialog.get_filename())
|
||||
dialog.destroy()
|
||||
self.wineEntry.grab_focus()
|
||||
|
||||
def on_application(self, _button):
|
||||
self.requestNumber += 1
|
||||
try:
|
||||
@@ -303,9 +337,17 @@ class ShortcutWindow(Gtk.Window):
|
||||
def on_create(self, _button):
|
||||
if self.target is None:
|
||||
return
|
||||
winePrefix = None
|
||||
if self.target.kind == "Windows program":
|
||||
try:
|
||||
winePrefix = validate_wine_prefix(self.wineEntry.get_text())
|
||||
except ValueError as error:
|
||||
self.show_message(str(error), error=True)
|
||||
self.wineEntry.grab_focus()
|
||||
return
|
||||
try:
|
||||
destination = create_shortcut(self.target, self.nameEntry.get_text(), self.fileManager,
|
||||
self.terminalCheck.get_active())
|
||||
self.terminalCheck.get_active(), winePrefix=winePrefix)
|
||||
except (ValueError, OSError, GLib.Error) as error:
|
||||
self.show_message(str(error), error=True)
|
||||
return
|
||||
|
||||
+40
-4
@@ -143,11 +143,39 @@ def parse_target(value):
|
||||
icon = app.get_icon()
|
||||
return Target(str(targetPath), app.get_display_name(), "Application",
|
||||
icon.to_string() if icon else "application-x-executable")
|
||||
if targetPath.suffix.lower() == ".exe":
|
||||
return Target(str(targetPath), targetPath.stem, "Windows program", "application-x-executable")
|
||||
if os.access(str(targetPath), os.X_OK):
|
||||
return Target(str(targetPath), targetPath.name, "Program", "application-x-executable")
|
||||
return Target(str(targetPath), targetPath.name, "File", "text-x-generic")
|
||||
|
||||
|
||||
def is_wine_prefix(path):
|
||||
return ((path / "drive_c").is_dir() and (path / "dosdevices").is_dir()
|
||||
and (path / "system.reg").is_file())
|
||||
|
||||
|
||||
def detect_wine_prefix(value):
|
||||
targetPath = Path(value).expanduser()
|
||||
# Check the supplied path first: drive_c itself can be a symlink. Then
|
||||
# resolve links so an executable linked onto the desktop also works.
|
||||
for programPath in (targetPath, targetPath.resolve()):
|
||||
for parent in programPath.parents:
|
||||
if is_wine_prefix(parent):
|
||||
return str(parent)
|
||||
defaultPrefix = Path.home() / ".wine"
|
||||
return str(defaultPrefix) if is_wine_prefix(defaultPrefix) else None
|
||||
|
||||
|
||||
def validate_wine_prefix(value):
|
||||
if not value or any(ord(character) < 32 or ord(character) == 127 for character in value):
|
||||
raise ValueError("Choose the Wine prefix (bottle) folder for this Windows program.")
|
||||
prefixPath = Path(value).expanduser()
|
||||
if not prefixPath.is_absolute() or not is_wine_prefix(prefixPath):
|
||||
raise ValueError("Choose an existing Wine prefix containing drive_c, dosdevices, and system.reg.")
|
||||
return str(prefixPath)
|
||||
|
||||
|
||||
def clipboard_target(data, targetType):
|
||||
if isinstance(data, bytes):
|
||||
data = data.decode("utf-8")
|
||||
@@ -178,7 +206,7 @@ def exec_argument(value):
|
||||
return '"' + escaped + '"'
|
||||
|
||||
|
||||
def shortcut_data(target, name, fileManager="", terminal=False):
|
||||
def shortcut_data(target, name, fileManager="", terminal=False, winePrefix=None):
|
||||
name = name.strip()
|
||||
if not name or any(ord(character) < 32 or ord(character) == 127 for character in name):
|
||||
raise ValueError("Enter a shortcut name without line breaks or control characters.")
|
||||
@@ -192,6 +220,12 @@ def shortcut_data(target, name, fileManager="", terminal=False):
|
||||
if "=" in target.value:
|
||||
raise ValueError("Desktop launchers cannot run a program with '=' in its path.")
|
||||
arguments = [target.value]
|
||||
elif target.kind == "Windows program":
|
||||
winePrefix = validate_wine_prefix(winePrefix if winePrefix is not None else
|
||||
detect_wine_prefix(target.value))
|
||||
if shutil.which("wine") is None:
|
||||
raise ValueError("Required program is unavailable: wine")
|
||||
arguments = ["env", "WINEPREFIX=" + winePrefix, "wine", target.value]
|
||||
elif target.kind == "Application":
|
||||
# Launch the original entry so D-Bus identity, field codes, terminal and
|
||||
# working-directory settings survive a renamed desktop shortcut.
|
||||
@@ -204,12 +238,14 @@ def shortcut_data(target, name, fileManager="", terminal=False):
|
||||
for key, value in (("Type", "Application"), ("Name", name), ("Icon", target.icon),
|
||||
("Exec", " ".join(exec_argument(argument) for argument in arguments))):
|
||||
keyFile.set_string("Desktop Entry", key, value)
|
||||
keyFile.set_boolean("Desktop Entry", "Terminal", terminal and target.kind == "Program")
|
||||
keyFile.set_boolean("Desktop Entry", "Terminal", terminal and target.kind in ("Program", "Windows program"))
|
||||
if target.kind == "Windows program":
|
||||
keyFile.set_string("Desktop Entry", "Path", str(Path(target.value).resolve().parent))
|
||||
return keyFile.to_data()[0]
|
||||
|
||||
|
||||
def create_shortcut(target, name, fileManager="", terminal=False, desktopPath=None):
|
||||
data = shortcut_data(target, name, fileManager, terminal)
|
||||
def create_shortcut(target, name, fileManager="", terminal=False, desktopPath=None, winePrefix=None):
|
||||
data = shortcut_data(target, name, fileManager, terminal, winePrefix)
|
||||
desktopPath = Path(desktopPath) if desktopPath is not None else desktop_directory()
|
||||
desktopPath.mkdir(parents=True, exist_ok=True)
|
||||
# Keep long names below common NAME_MAX limits, including numbered suffixes.
|
||||
|
||||
Reference in new issue
Block a user