mirror of
https://git.srhtnin.garden/sarah/SaraView.git
synced 2026-10-14 18:38:11 +02:00
Client: macOS support
This commit is contained in:
1 parent
5c04a4a77d
commit
d86e33fd4a
4 files changed
+80
-9
No files matched your search
+58
-6
@@ -45,14 +45,19 @@ BundledFontFamily = "Fixedsys Excelsior"
|
|||||||
ActiveFontFamily = BundledFontFamily # set by ApplyAppearance, read by BuildTheme
|
ActiveFontFamily = BundledFontFamily # set by ApplyAppearance, read by BuildTheme
|
||||||
|
|
||||||
IsWindows = sys.platform == "win32"
|
IsWindows = sys.platform == "win32"
|
||||||
|
IsMac = sys.platform == "darwin"
|
||||||
|
|
||||||
# Each system's own per-user folders, like SaraSuite's: ~/.config, ~/.cache
|
# 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()
|
Home = Path.home()
|
||||||
if IsWindows:
|
if IsWindows:
|
||||||
ConfigHome = Path(os.environ.get("APPDATA") or Home / "AppData" / "Roaming")
|
ConfigHome = Path(os.environ.get("APPDATA") or Home / "AppData" / "Roaming")
|
||||||
DataHome = Path(os.environ.get("LOCALAPPDATA") or Home / "AppData" / "Local")
|
DataHome = Path(os.environ.get("LOCALAPPDATA") or Home / "AppData" / "Local")
|
||||||
CacheHome = DataHome
|
CacheHome = DataHome
|
||||||
|
elif IsMac:
|
||||||
|
ConfigHome = DataHome = Home / "Library" / "Application Support"
|
||||||
|
CacheHome = Home / "Library" / "Caches"
|
||||||
else:
|
else:
|
||||||
ConfigHome = Path(os.environ.get("XDG_CONFIG_HOME") or Home / ".config")
|
ConfigHome = Path(os.environ.get("XDG_CONFIG_HOME") or Home / ".config")
|
||||||
DataHome = Path(os.environ.get("XDG_DATA_HOME") or Home / ".local" / "share")
|
DataHome = Path(os.environ.get("XDG_DATA_HOME") or Home / ".local" / "share")
|
||||||
@@ -337,7 +342,7 @@ def OpenExternally(FilePath: Path) -> None:
|
|||||||
if IsWindows:
|
if IsWindows:
|
||||||
os.startfile(str(FilePath))
|
os.startfile(str(FilePath))
|
||||||
return
|
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]:
|
def _WindowsFonts() -> set[str]:
|
||||||
@@ -367,11 +372,58 @@ def _WindowsFonts() -> set[str]:
|
|||||||
return Families
|
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]:
|
def InstalledFontFamilies() -> list[str]:
|
||||||
"""Font families fontconfig (or, on Windows, the registry) knows about,
|
"""Font families fontconfig (or, on Windows, the registry; on macOS, the
|
||||||
with the bundled font first."""
|
font files themselves) knows about, with the bundled font first."""
|
||||||
if IsWindows:
|
if IsWindows or IsMac:
|
||||||
Families = _WindowsFonts()
|
Families = _WindowsFonts() if IsWindows else _MacFonts()
|
||||||
Families.discard(BundledFontFamily)
|
Families.discard(BundledFontFamily)
|
||||||
return [BundledFontFamily] + sorted(Families, key=str.lower)
|
return [BundledFontFamily] + sorted(Families, key=str.lower)
|
||||||
try:
|
try:
|
||||||
|
|||||||
+13
-1
@@ -31,7 +31,7 @@ from typing import Optional
|
|||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from Core import AssetsDir, ChildEnvironment, DataHome, AppName, IsWindows
|
from Core import AssetsDir, ChildEnvironment, DataHome, AppName, Home, IsMac, IsWindows
|
||||||
|
|
||||||
ToolsDir = DataHome / AppName / "DiagramTools"
|
ToolsDir = DataHome / AppName / "DiagramTools"
|
||||||
PythonDir = ToolsDir / "Python"
|
PythonDir = ToolsDir / "Python"
|
||||||
@@ -53,6 +53,12 @@ WindowsBrowsers = [
|
|||||||
r"Google\Chrome\Application\chrome.exe",
|
r"Google\Chrome\Application\chrome.exe",
|
||||||
r"BraveSoftware\Brave-Browser\Application\brave.exe",
|
r"BraveSoftware\Brave-Browser\Application\brave.exe",
|
||||||
]
|
]
|
||||||
|
MacBrowsers = [ # in /Applications or ~/Applications
|
||||||
|
"Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||||
|
"Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
||||||
|
"Brave Browser.app/Contents/MacOS/Brave Browser",
|
||||||
|
"Chromium.app/Contents/MacOS/Chromium",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class DiagramError(Exception):
|
class DiagramError(Exception):
|
||||||
@@ -169,6 +175,12 @@ def _InstalledBrowser() -> Optional[str]:
|
|||||||
if Base and (Path(Base) / Relative).is_file():
|
if Base and (Path(Base) / Relative).is_file():
|
||||||
return str(Path(Base) / Relative)
|
return str(Path(Base) / Relative)
|
||||||
return None
|
return None
|
||||||
|
if IsMac:
|
||||||
|
for Base in (Path("/Applications"), Home / "Applications"):
|
||||||
|
for Relative in MacBrowsers:
|
||||||
|
if (Base / Relative).is_file():
|
||||||
|
return str(Base / Relative)
|
||||||
|
return None
|
||||||
return next((Found for Name in BrowserNames if (Found := shutil.which(Name))), None)
|
return next((Found for Name in BrowserNames if (Found := shutil.which(Name))), None)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ from Settings import ShowSettingsDialog
|
|||||||
def _SetCursorThemeFallback() -> None:
|
def _SetCursorThemeFallback() -> None:
|
||||||
"""GTK reads XCURSOR_THEME directly and doesn't always inherit it (shows as
|
"""GTK reads XCURSOR_THEME directly and doesn't always inherit it (shows as
|
||||||
'Unable to load ... from the cursor theme'). Same fix as SaraFem's."""
|
'Unable to load ... from the cursor theme'). Same fix as SaraFem's."""
|
||||||
if os.environ.get("XCURSOR_THEME") or sys.platform == "win32": # GTK/GNOME only
|
if os.environ.get("XCURSOR_THEME") or not sys.platform.startswith("linux"): # GTK/GNOME only
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
Result = subprocess.run(["gsettings", "get", "org.gnome.desktop.interface", "cursor-theme"],
|
Result = subprocess.run(["gsettings", "get", "org.gnome.desktop.interface", "cursor-theme"],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Kept in sync between SaraSuite/Programs/SaraView/ and SaraView/Client/Packaging/SaraSuite/.
|
# Kept in sync between SaraSuite/Programs/SaraView/ and SaraView/Client/Packaging/SaraSuite/.
|
||||||
Name = "SaraView"
|
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."
|
Description = "SaraView's desktop client: view and edit files in a local folder, or on a SaraView server."
|
||||||
|
|
||||||
[Source]
|
[Source]
|
||||||
@@ -32,6 +32,13 @@ Name = "SaraView"
|
|||||||
Script = "Client/Main.py"
|
Script = "Client/Main.py"
|
||||||
Icon = "Client/assets/Icon.png"
|
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).
|
# Made by SaraView itself: Config.toml and Session.toml (your saved login).
|
||||||
[[Leftovers]]
|
[[Leftovers]]
|
||||||
Path = "{ConfigDir}"
|
Path = "{ConfigDir}"
|
||||||
|
|||||||
Reference in new issue
Block a user