Better handling for wine based shortcuts.

This commit is contained in:
Storm Dragon committed 2026-10-19 00:46:09 -04:00
1 parent d2c5695221
commit fb2e1cc55c
3 files changed
+91 -9

No files matched your search

+40 -4
View File
@@ -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.