macOS support

This commit is contained in:
sarah committed 2026-10-14 13:18:18 +02:00
1 parent f2e8f620bd
commit 259f5caf3c
5 files changed
+72 -13

No files matched your search

+2 -2
View File
@@ -59,8 +59,8 @@ SaraTagRecordsPath = ApplicationDirectory / ".SaraTagRecords.toml"
SaraTagHistoryPath = ApplicationDirectory / ".SaraTagHistory.toml"
# Empty means the default app; Windows rarely has mpv.
DefaultVideoPlayer = "" if sys.platform == "win32" else "mpv"
# Empty means the default app; Windows and macOS rarely have mpv.
DefaultVideoPlayer = "mpv" if sys.platform.startswith("linux") else ""
DefaultSaraTagConf = f'''# SaraTag's main portable configuration file.
# Paths in this file are interpreted relative to the SaraTag directory
+5 -1
View File
@@ -24,13 +24,17 @@ 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 and
# ~/.local/share on Linux, %APPDATA% and %LOCALAPPDATA% on Windows.
# ~/.local/share on Linux, %APPDATA% and %LOCALAPPDATA% on Windows,
# ~/Library/Application Support (both) 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")
elif IsMac:
ConfigHome = DataHome = Home / "Library" / "Application Support"
else:
ConfigHome = Path(os.environ.get("XDG_CONFIG_HOME") or Home / ".config")
DataHome = Path(os.environ.get("XDG_DATA_HOME") or Home / ".local" / "share")
+8 -1
View File
@@ -1,6 +1,6 @@
# Kept in sync between SaraSuite/Programs/SaraTag/ and SaraTag/Packaging/SaraSuite/.
Name = "SaraTag"
Platforms = ["Linux", "Windows"]
Platforms = ["Linux", "Windows", "macOS"]
Description = "Tag files with Books (sidecars) defined by Lexicons: browse, filter and sort by tag, edit tags on many files at once."
[Version]
@@ -40,6 +40,13 @@ Name = "SaraTag"
Script = "Main.py"
Icon = "assets/Icon.png"
# macOS: an app bundle, ~/Applications/SaraTag.app (python Main.py, from the app folder).
[[Shortcuts]]
Platform = "macOS"
Name = "SaraTag"
Script = "Main.py"
Icon = "assets/Icon.png"
# Made by SaraTag itself. The Lexicons are your tag definitions: only tick
# "remove configs" if you don't need them any more.
[[Leftovers]]
+54 -7
View File
@@ -11,8 +11,8 @@ import flet as ft
import tomlkit
from Core import (
AppConfig, AvailableThemes, BundledFontFamily, ChildEnvironment, ChipButtonStyle, ExportSettings, Home,
ImportSettings, IsWindows, LoadSharedSettings, Palette, SharedSettings,
AppConfig, AvailableThemes, BundledFontFamily, ChildEnvironment, ChipButtonStyle, DisplayPath, ExportSettings,
Home, ImportSettings, IsMac, IsWindows, LoadSharedSettings, Palette, SharedConfigPath, SharedSettings,
)
from Library import ConfPath, ConfValue, DataDir, DataError, SaveConfValues
from Widgets import App
@@ -55,11 +55,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 _Fonts() -> list[str]:
"""Installed font families (fontconfig, or the registry on Windows), with
the bundled font first."""
if IsWindows:
Families = _WindowsFonts()
"""Installed font families (fontconfig, the registry on Windows, the font
files themselves on macOS), 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:
@@ -200,7 +247,7 @@ def ShowSettingsDialog(TheApp: App, OnSaved: Callable[[], None]) -> None:
Categories: dict[str, ft.Control] = {
"SaraSuite": ft.Column(
[
_Note("Shared by every SaraSuite program (~/.config/SaraSuite/Config.toml). "
_Note(f"Shared by every SaraSuite program ({DisplayPath(str(SharedConfigPath))}). "
"Other programs pick changes up the next time they start."),
UseSharedSwitch,
ft.Divider(height=1, color=Palette.Surface1),
+3 -2
View File
@@ -12,7 +12,7 @@ from typing import Callable, Optional
import flet as ft
from Core import AppConfig, ChildEnvironment, ChipButton, IsWindows, Note, NotificationStyle, Palette
from Core import AppConfig, ChildEnvironment, ChipButton, IsMac, IsWindows, Note, NotificationStyle, Palette
from Library import ConfValue
@@ -72,7 +72,8 @@ class App:
else to the default app."""
from Query import VideoExtensions
Command = None if IsWindows else ["xdg-open", str(TargetPath)] # Windows: os.startfile below
# Windows: os.startfile below.
Command = None if IsWindows else ["open" if IsMac else "xdg-open", str(TargetPath)]
if TargetPath.suffix.lower() in VideoExtensions:
Player = str(ConfValue("Media", "VideoPlayer", "") or "").strip()
if Player and shutil.which(Player.split()[0]):