Coverage for vibefpmgui/compat.py: 37%
73 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-01 20:51 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-01 20:51 +0000
1"""Small helpers that differ between macOS, Linux and Windows."""
3from __future__ import annotations
5import os
6import shlex
7import subprocess
8import sys
10WINDOWS = os.name == "nt"
13def split_args(text: str) -> list[str]:
14 """Split a command-line string typed by the user into arguments.
16 On Windows backslashes are path separators, not escapes, so POSIX
17 ``shlex.split`` would turn ``C:\\data\\in.txt`` into ``C:datain.txt``;
18 use non-POSIX splitting there and drop the surrounding quotes.
19 """
20 if not WINDOWS: 20 ↛ 22line 20 didn't jump to line 22 because the condition on line 20 was always true
21 return shlex.split(text)
22 parts = shlex.split(text, posix=False)
23 return [p[1:-1] if len(p) >= 2 and p[0] == p[-1] and p[0] in "\"'" else p for p in parts]
26def join_args(args: list[str]) -> str:
27 """A command line to show the user, quoted the way their shell expects."""
28 return subprocess.list2cmdline(args) if WINDOWS else shlex.join(args)
31def quote_arg(arg: str) -> str:
32 return subprocess.list2cmdline([arg]) if WINDOWS else shlex.quote(arg)
35def file_manager_name() -> str:
36 """What the platform's file manager is called (for menu items)."""
37 return {"darwin": "Finder", "win32": "Explorer"}.get(sys.platform, "the file manager")
40def reveal_label() -> str:
41 return {"darwin": "Show in Finder", "win32": "Show in Explorer"}.get(
42 sys.platform, "Open Containing Folder")
45def set_windows_app_id(app_id: str = "VibeFPMGUI.VibeFPMGUI"):
46 """On Windows, give the process its own taskbar identity, so the taskbar
47 shows the app's icon instead of grouping it under python.exe."""
48 if not WINDOWS: 48 ↛ 50line 48 didn't jump to line 50 because the condition on line 48 was always true
49 return
50 try:
51 import ctypes
52 ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(app_id)
53 except (AttributeError, OSError):
54 pass
57# ------------------------------------------------------------ macOS app name
58# Started as "python -m vibefpmgui" (not from an .app bundle), macOS would call
59# the app "python": in the menu bar, the Dock, and Qt's application menu
60# ("About __main__.py", "Quit __main__.py", from argv[0]). These give it its
61# own name; each does nothing elsewhere or if macOS doesn't allow it.
62def _objc():
63 """(objc_msgSend caller, class lookup, selector lookup), or None."""
64 import ctypes
65 import ctypes.util
67 objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc"))
68 ctypes.cdll.LoadLibrary(ctypes.util.find_library("Foundation"))
69 objc.objc_getClass.restype = ctypes.c_void_p
70 objc.sel_registerName.restype = ctypes.c_void_p
72 def send(restype, *argtypes):
73 return ctypes.cast(objc.objc_msgSend, ctypes.CFUNCTYPE(restype, ctypes.c_void_p, ctypes.c_void_p, *argtypes))
75 return send, lambda name: objc.objc_getClass(name.encode()), lambda name: objc.sel_registerName(name.encode())
78def set_macos_bundle_name(name: str) -> bool:
79 """Before Qt starts: the name in the menu bar (the bundle's CFBundleName,
80 which a python executable has none of). True if it was set."""
81 if sys.platform != "darwin": 81 ↛ 83line 81 didn't jump to line 83 because the condition on line 81 was always true
82 return False
83 try:
84 import ctypes
86 send, cls, sel = _objc()
88 def nsstring(text):
89 return send(ctypes.c_void_p, ctypes.c_char_p)(cls("NSString"), sel("stringWithUTF8String:"),
90 text.encode())
92 bundle = send(ctypes.c_void_p)(cls("NSBundle"), sel("mainBundle"))
93 info = send(ctypes.c_void_p)(bundle, sel("infoDictionary"))
94 if not info:
95 return False
96 send(None, ctypes.c_void_p, ctypes.c_void_p)(info, sel("setObject:forKey:"), nsstring(name),
97 nsstring("CFBundleName"))
98 return True
99 except (OSError, AttributeError, TypeError):
100 return False
103def set_macos_display_name(name: str) -> bool:
104 """After Qt starts: the name the Dock shows for the running app. A private
105 LaunchServices call (as Chromium uses for its helper processes): if a
106 macOS version lacks it, nothing happens. True if it was set."""
107 if sys.platform != "darwin": 107 ↛ 109line 107 didn't jump to line 109 because the condition on line 107 was always true
108 return False
109 try:
110 import ctypes
112 services = ctypes.cdll.LoadLibrary(
113 "/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices")
114 core = ctypes.cdll.LoadLibrary("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")
115 core.CFStringCreateWithCString.restype = ctypes.c_void_p
116 core.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32]
117 current = services._LSGetCurrentApplicationASN
118 current.restype = ctypes.c_void_p
119 set_item = services._LSSetApplicationInformationItem
120 set_item.restype = ctypes.c_int
121 set_item.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
122 key = ctypes.c_void_p.in_dll(services, "_kLSDisplayNameKey")
123 value = core.CFStringCreateWithCString(None, name.encode(), 0x08000100) # (UTF-8)
124 asn = current()
125 return bool(asn) and set_item(-2, asn, key, value, None) == 0
126 except (OSError, AttributeError, ValueError, TypeError):
127 return False