macOS support (code only, no build yet)

This commit is contained in:
sarah committed 2026-10-14 13:18:20 +02:00
1 parent 1feb6d4336
commit ca04dcd4a7
17 files changed
+572 -126

No files matched your search

+8 -1
View File
@@ -1,6 +1,6 @@
Name = "SaraFem"
Description = "Desktop file manager that understands SaraTag Books, and (on Linux) can stand in as the system file picker."
Platforms = ["Linux", "Windows"]
Platforms = ["Linux", "Windows", "macOS"]
# The portal reads its descriptors and preferences once at startup.
RestartUnits = ["xdg-desktop-portal.service"]
@@ -77,6 +77,13 @@ Name = "SaraFem"
Script = "Main.py"
Icon = "assets/Icon.png"
# macOS: an app bundle, ~/Applications/SaraFem.app (python Main.py, from the app folder).
[[Shortcuts]]
Platform = "macOS"
Name = "SaraFem"
Script = "Main.py"
Icon = "assets/Icon.png"
[[Leftovers]]
Path = "{CacheHome}/SaraFem"
Kind = "Runtime"
+8 -1
View File
@@ -1,7 +1,7 @@
# Kept in sync between SaraSuite/Programs/SaraSort/ and SaraSort/Packaging/SaraSuite/.
Name = "SaraSort"
Parent = "SaraSortd" # a GUI for SaraSortd, listed under it
Platforms = ["Linux", "Windows"]
Platforms = ["Linux", "Windows", "macOS"]
Description = "A GUI for configuring SaraSortd: a flowchart of what goes where, its service and logs, configs and templates."
[Source]
@@ -31,6 +31,13 @@ Name = "SaraSort"
Script = "Main.py"
Icon = "assets/Icon.png"
# macOS: an app bundle, ~/Applications/SaraSort.app (python Main.py, from the app folder).
[[Shortcuts]]
Platform = "macOS"
Name = "SaraSort"
Script = "Main.py"
Icon = "assets/Icon.png"
# Made by SaraSort itself: Config.toml, Layout.toml, Templates/.
[[Leftovers]]
Path = "{ConfigDir}"
+13 -5
View File
@@ -1,6 +1,6 @@
Name = "SaraSortd"
Description = "Sorts files from input folders into output folders by the rules in its configs. Runs in the background."
Platforms = ["Linux", "Windows"]
Platforms = ["Linux", "Windows", "macOS"]
[Version]
File = "SaraSortd.py"
@@ -46,10 +46,11 @@ Platform = "Linux"
Source = "SaraSortd.service"
Target = "{SystemdUserDir}/SaraSortd.service"
# Windows, and Linux without systemd (runit, OpenRC, s6, ...): run from
# ConfigDir by ServiceRun.py, which restarts it if it fails, at login through
# a shortcut in the Startup folder / an XDG autostart entry (SaraSuite's
# Services.py). On Linux the same service has a Unit for systemd too.
# Windows, macOS, and Linux without systemd (runit, OpenRC, s6, ...): run
# from ConfigDir by ServiceRun.py, which restarts it if it fails, at login
# through a shortcut in the Startup folder / a LaunchAgent / an XDG autostart
# entry (SaraSuite's Services.py). On Linux the same service has a Unit for
# systemd too.
[[Templates]]
Source = "../../ServiceRun.py"
Target = "{ProgramDir}/ServiceRun.py"
@@ -69,5 +70,12 @@ Script = "SaraSortd.py"
WorkingDir = "{ConfigDir}"
Option = "Autostart"
[[Services]]
Platform = "macOS"
Name = "SaraSortd"
Script = "SaraSortd.py"
WorkingDir = "{ConfigDir}"
Option = "Autostart"
# Everything SaraSortd makes inside the sorting folders is worked out from
# GlobalConf.toml by Hooks.py.
+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]]
+8 -1
View File
@@ -1,6 +1,6 @@
# Kept in sync between SaraSuite/Programs/SaraTune/ and SaraTune/Packaging/SaraSuite/.
Name = "SaraTune"
Platforms = ["Linux", "Windows"]
Platforms = ["Linux", "Windows", "macOS"]
Description = "A music player with two modes: Audio to open a file or folder and listen right away, Music for your library, a queue and saved playlists. Linux needs libmpv (Fedora: mpv-libs)."
[Source]
@@ -30,6 +30,13 @@ Name = "SaraTune"
Script = "Main.py"
Icon = "assets/Icon.png"
# macOS: an app bundle, ~/Applications/SaraTune.app (python Main.py, from the app folder).
[[Shortcuts]]
Platform = "macOS"
Name = "SaraTune"
Script = "Main.py"
Icon = "assets/Icon.png"
# Made by SaraTune itself.
[[Leftovers]]
Path = "{ConfigDir}"
+8 -1
View File
@@ -1,6 +1,6 @@
# Kept in sync between SaraSuite/Programs/SaraView/ and SaraView/Client/Packaging/SaraSuite/.
Name = "SaraView"
Platforms = ["Linux", "Windows"]
Platforms = ["Linux", "Windows", "macOS"]
Description = "SaraView's desktop client: view and edit files in a local folder, or on a SaraView server."
[Source]
@@ -32,6 +32,13 @@ Name = "SaraView"
Script = "Client/Main.py"
Icon = "Client/assets/Icon.png"
# macOS: an app bundle, ~/Applications/SaraView.app (python Client/Main.py, from the app folder).
[[Shortcuts]]
Platform = "macOS"
Name = "SaraView"
Script = "Client/Main.py"
Icon = "Client/assets/Icon.png"
# Made by SaraView itself: Config.toml and Session.toml (your saved login).
[[Leftovers]]
Path = "{ConfigDir}"
+17 -8
View File
@@ -1,8 +1,8 @@
"""Runs a SaraSuite program as a background service where there's no systemd
--user to do it: on Windows (started windowless with pythonw.exe), and on
Linux with another init system (runit on Void, OpenRC, s6, dinit, ...).
Started at login (Windows: a shortcut in the Startup folder; Linux: an XDG
autostart entry) and when SaraSuite or the program's own GUI (e.g. SaraSort
--user to do it: on Windows (started windowless with pythonw.exe), on macOS,
and on Linux with another init system (runit on Void, OpenRC, s6, dinit, ...).
Started at login (Windows: a shortcut in the Startup folder; macOS: a
LaunchAgent; Linux: an XDG autostart entry) and when SaraSuite or the program's own GUI (e.g. SaraSort
for SaraSortd) starts it:
python ServiceRun.py <Name> <Script> [<WorkingDir>]
@@ -12,7 +12,8 @@ seconds after it fails (an exception or a non-zero exit), not after a clean
exit or a stop; its output goes to <Name>.log. Its PID is in <Name>.pid, so
anything can tell whether it runs, and stop it, without admin rights. Both
files are in SaraSuite's Services folder: %LOCALAPPDATA%\\SaraSuite\\Services
on Windows, ~/.local/share/SaraSuite/Services on Linux.
on Windows, ~/Library/Application Support/SaraSuite/Services on macOS,
~/.local/share/SaraSuite/Services on Linux.
Installed next to each program that has a service (Programs/<Name>/Manifest.toml).
"""
@@ -20,6 +21,7 @@ Installed next to each program that has a service (Programs/<Name>/Manifest.toml
import os
import runpy
import signal
import subprocess
import sys
import time
import traceback
@@ -27,6 +29,7 @@ from datetime import datetime
from pathlib import Path
IsWindows = sys.platform == "win32"
IsMac = sys.platform == "darwin"
RestartSeconds = 10 # the unit's RestartSec
MaxLogBytes = 1_000_000 # then the log starts over, the old one kept as <Name>.log.1
Stopping = False
@@ -35,22 +38,28 @@ Stopping = False
def ServicesDir() -> Path:
if IsWindows:
Base = Path(os.environ.get("LOCALAPPDATA") or Path.home() / "AppData" / "Local")
elif IsMac:
Base = Path.home() / "Library" / "Application Support"
else:
Base = Path(os.environ.get("XDG_DATA_HOME") or Path.home() / ".local" / "share")
return Base / "SaraSuite" / "Services"
def _AlreadyRunning(PidFile: Path) -> bool:
"""Linux: whether the PID file's process is another ServiceRun.py still
running (not a reused PID). Windows relies on whoever starts it to check."""
"""Linux and macOS: whether the PID file's process is another
ServiceRun.py still running (not a reused PID). Windows relies on whoever
starts it to check."""
if IsWindows:
return False
try:
Pid = int(PidFile.read_text().strip())
if Pid == os.getpid():
return False
if IsMac: # no /proc
Output = subprocess.run(["ps", "-o", "command=", "-p", str(Pid)], capture_output=True, text=True, timeout=15).stdout
return "ServiceRun.py" in Output
return b"ServiceRun.py" in Path(f"/proc/{Pid}/cmdline").read_bytes()
except (OSError, ValueError):
except (OSError, ValueError, subprocess.SubprocessError):
return False