Add effective desktop style controls
This commit is contained in:
+98
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Report installed cursor and icon themes from the standard XDG roots.
|
||||
|
||||
This helper is intentionally read-only and argument-free. Theme paths come
|
||||
only from XDG_DATA_HOME and XDG_DATA_DIRS; directory symlinks are not followed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def icon_roots() -> list[Path]:
|
||||
home = Path(os.environ.get("HOME") or "/nonexistent")
|
||||
data_home = Path(os.environ.get("XDG_DATA_HOME") or home / ".local/share")
|
||||
data_dirs = os.environ.get("XDG_DATA_DIRS") or "/usr/local/share:/usr/share"
|
||||
|
||||
roots = [data_home / "icons"]
|
||||
roots.extend(Path(directory) / "icons" for directory in data_dirs.split(":") if directory)
|
||||
# XDG paths are required to be absolute. Ignoring malformed relative
|
||||
# entries also prevents the helper's working directory becoming an
|
||||
# accidental caller-controlled search root.
|
||||
return [root for root in roots if root.is_absolute()]
|
||||
|
||||
|
||||
def is_real_directory(path: Path) -> bool:
|
||||
try:
|
||||
return stat.S_ISDIR(path.lstat().st_mode)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def is_real_file(path: Path) -> bool:
|
||||
try:
|
||||
return stat.S_ISREG(path.lstat().st_mode)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def has_icon_directories(index_path: Path) -> bool:
|
||||
try:
|
||||
with index_path.open(encoding="utf-8", errors="replace") as handle:
|
||||
for raw_line in handle:
|
||||
line = raw_line.strip()
|
||||
if line.startswith(("#", ";")) or "=" not in line:
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
if key.strip() == "Directories":
|
||||
return bool(value.strip())
|
||||
except OSError:
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def catalog() -> dict[str, list[str]]:
|
||||
cursor_themes: set[str] = set()
|
||||
icon_themes: set[str] = set()
|
||||
|
||||
for root in icon_roots():
|
||||
if not is_real_directory(root):
|
||||
continue
|
||||
try:
|
||||
entries = list(os.scandir(root))
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
for entry in entries:
|
||||
if not entry.is_dir(follow_symlinks=False):
|
||||
continue
|
||||
theme = Path(entry.path)
|
||||
if is_real_directory(theme / "cursors"):
|
||||
cursor_themes.add(entry.name)
|
||||
|
||||
index_path = theme / "index.theme"
|
||||
if is_real_file(index_path) and has_icon_directories(index_path):
|
||||
icon_themes.add(entry.name)
|
||||
|
||||
return {
|
||||
"cursorThemes": sorted(cursor_themes, key=str.casefold),
|
||||
"iconThemes": sorted(icon_themes, key=str.casefold),
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 1:
|
||||
print("panama-desktop-style takes no arguments", file=sys.stderr)
|
||||
return 2
|
||||
print(json.dumps(catalog(), ensure_ascii=False, separators=(",", ":")))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user