mirror of
https://git.srhtnin.garden/sarah/SaraTag.git
synced 2026-10-15 02:43:17 +02:00
macOS support
This commit is contained in:
1 parent
f2e8f620bd
commit
259f5caf3c
5 files changed
+72
-13
No files matched your search
+2
-2
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
@@ -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
@@ -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]):
|
||||
|
||||
Reference in new issue
Block a user