Client: macOS support

This commit is contained in:
sarah committed 2026-10-14 13:18:19 +02:00
1 parent 5c04a4a77d
commit d86e33fd4a
4 files changed
+80 -9

No files matched your search

+58 -6
View File
@@ -45,14 +45,19 @@ BundledFontFamily = "Fixedsys Excelsior"
ActiveFontFamily = BundledFontFamily # set by ApplyAppearance, read by BuildTheme
IsWindows = sys.platform == "win32"
IsMac = sys.platform == "darwin"
# Each system's own per-user folders, like SaraSuite's: ~/.config, ~/.cache
# and ~/.local/share on Linux; %APPDATA% and %LOCALAPPDATA% on Windows.
# and ~/.local/share on Linux; %APPDATA% and %LOCALAPPDATA% on Windows;
# ~/Library/Application Support and ~/Library/Caches on macOS.
Home = Path.home()
if IsWindows:
ConfigHome = Path(os.environ.get("APPDATA") or Home / "AppData" / "Roaming")
DataHome = Path(os.environ.get("LOCALAPPDATA") or Home / "AppData" / "Local")
CacheHome = DataHome
elif IsMac:
ConfigHome = DataHome = Home / "Library" / "Application Support"
CacheHome = Home / "Library" / "Caches"
else:
ConfigHome = Path(os.environ.get("XDG_CONFIG_HOME") or Home / ".config")
DataHome = Path(os.environ.get("XDG_DATA_HOME") or Home / ".local" / "share")
@@ -337,7 +342,7 @@ def OpenExternally(FilePath: Path) -> None:
if IsWindows:
os.startfile(str(FilePath))
return
subprocess.Popen(["xdg-open", str(FilePath)], env=ChildEnvironment(), start_new_session=True)
subprocess.Popen(["open" if IsMac else "xdg-open", str(FilePath)], env=ChildEnvironment(), start_new_session=True)
def _WindowsFonts() -> set[str]:
@@ -367,11 +372,58 @@ def _WindowsFonts() -> set[str]:
return Families
def _MacFonts() -> set[str]:
"""Font families on macOS (no fontconfig there), read from the font files'
name tables in the system, shared and personal Fonts folders. The
typographic family (name ID 16) where there is one, else ID 1."""
import struct
Families: set[str] = set()
for Folder in (Path("/System/Library/Fonts"), Path("/Library/Fonts"), Path.home() / "Library" / "Fonts"):
if not Folder.is_dir():
continue
for FilePath in Folder.rglob("*"):
if FilePath.suffix.lower() not in (".ttf", ".otf", ".ttc"):
continue
try:
Data = FilePath.read_bytes()
except OSError:
continue
Offsets = [0]
if Data[:4] == b"ttcf": # a collection: every font in it
Count = struct.unpack_from(">I", Data, 8)[0]
Offsets = list(struct.unpack_from(f">{Count}I", Data, 12))
for Offset in Offsets:
try:
for Index in range(struct.unpack_from(">H", Data, Offset + 4)[0]):
Tag, _Sum, Start, _Length = struct.unpack_from(">4sIII", Data, Offset + 12 + Index * 16)
if Tag != b"name":
continue
_Format, Count, Strings = struct.unpack_from(">HHH", Data, Start)
Found: dict[int, str] = {}
for Record in range(Count):
PlatformId, _Encoding, Language, NameId, Length, At = struct.unpack_from(
">6H", Data, Start + 6 + Record * 12)
if NameId not in (1, 16) or NameId in Found:
continue
Raw = Data[Start + Strings + At:Start + Strings + At + Length]
if PlatformId in (0, 3):
Found[NameId] = Raw.decode("utf-16-be", "replace")
elif PlatformId == 1 and Language == 0:
Found[NameId] = Raw.decode("mac-roman", "replace")
Name = (Found.get(16) or Found.get(1) or "").strip()
if Name and not Name.startswith("."): # ".SF NS" and co. are system-internal
Families.add(Name)
except struct.error:
continue
return Families
def InstalledFontFamilies() -> list[str]:
"""Font families fontconfig (or, on Windows, the registry) knows about,
with the bundled font first."""
if IsWindows:
Families = _WindowsFonts()
"""Font families fontconfig (or, on Windows, the registry; on macOS, the
font files themselves) knows about, with the bundled font first."""
if IsWindows or IsMac:
Families = _WindowsFonts() if IsWindows else _MacFonts()
Families.discard(BundledFontFamily)
return [BundledFontFamily] + sorted(Families, key=str.lower)
try: