mirror of
https://git.srhtnin.garden/sarah/SaraSuite.git
synced 2026-10-15 10:33:17 +02:00
SaraFem's file picker without a password where possible; clearer pkexec errors; portals.conf without a desktop name
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
70c9730017
commit
ce62249487
4 files changed
+76
-13
No files matched your search
@@ -13,6 +13,7 @@ picked for one Sara program looks the same in all of them.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from dataclasses import asdict, dataclass, field
|
||||
@@ -268,6 +269,26 @@ def ChildEnvironment() -> dict[str, str]:
|
||||
return Environment
|
||||
|
||||
|
||||
def UserPortalsSupported() -> bool:
|
||||
"""Whether xdg-desktop-portal reads portal files from
|
||||
~/.local/share/xdg-desktop-portal/portals (1.17 and newer), so installing
|
||||
one needs no password. True when it isn't installed either: then a
|
||||
portal file can't be used anywhere, so it might as well not need root."""
|
||||
import subprocess
|
||||
|
||||
for Candidate in ("/usr/libexec/xdg-desktop-portal", "/usr/lib/xdg-desktop-portal",
|
||||
"/usr/lib/x86_64-linux-gnu/xdg-desktop-portal", "/usr/local/libexec/xdg-desktop-portal"):
|
||||
if not Path(Candidate).is_file():
|
||||
continue
|
||||
try:
|
||||
Output = subprocess.run([Candidate, "--version"], capture_output=True, text=True, timeout=10).stdout
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return False
|
||||
Found = re.search(r"(\d+)\.(\d+)", Output)
|
||||
return bool(Found) and (int(Found[1]), int(Found[2])) >= (1, 17)
|
||||
return True
|
||||
|
||||
|
||||
def CurrentDesktop() -> str:
|
||||
"""First entry of $XDG_CURRENT_DESKTOP as-is (e.g. "COSMIC"), used for
|
||||
portal descriptors' UseIn= and the <desktop>-portals.conf file name.
|
||||
|
||||
+36
-6
@@ -21,8 +21,8 @@ from typing import Callable, Optional
|
||||
from ConfigMerge import ApplySet, MergeConfig, MergeResult
|
||||
import Services
|
||||
from Core import (
|
||||
AutostartDir, BackupsDir, ChildEnvironment, DownloadsDir, HasSystemd, IsWindows, LoadConfig, PristineDir,
|
||||
StartMenuDir, StartupDir,
|
||||
AutostartDir, BackupsDir, ChildEnvironment, DownloadsDir, HasSystemd, Home, IsWindows, LoadConfig,
|
||||
PristineDir, StartMenuDir, StartupDir,
|
||||
)
|
||||
from Manifests import Leftover, Program, Render
|
||||
from Receipts import IniRecord, LoadReceipt, PlacedFile, Receipt
|
||||
@@ -81,18 +81,47 @@ def _BackupName(Target: str) -> str:
|
||||
return Target.strip("/").replace("/", "__")
|
||||
|
||||
|
||||
def _Pkexec(Command: list[str], Log: LogFn, Check: bool = True) -> int:
|
||||
"""Run Command as root through pkexec, saying what went wrong in words."""
|
||||
if shutil.which("pkexec") is None:
|
||||
Code = 127
|
||||
else:
|
||||
Code = _Run(["pkexec", *Command], Log, Check=False)
|
||||
if Code == 0:
|
||||
return 0
|
||||
if Code == 126:
|
||||
Problem = "the password prompt was cancelled"
|
||||
elif shutil.which("pkexec") is None:
|
||||
Problem = "pkexec (polkit) isn't installed"
|
||||
else:
|
||||
Problem = ("no password prompt could be shown: polkit needs an authentication agent running "
|
||||
"in your session (e.g. polkit-gnome or lxqt-policykit), which window managers don't start themselves")
|
||||
Message = f"Couldn't do this as root ({Problem}). The command was: sudo {' '.join(Command)}"
|
||||
if Check:
|
||||
raise InstallError(Message)
|
||||
Log(f"Warning: {Message}")
|
||||
return Code
|
||||
|
||||
|
||||
def _NeedsRoot(Template, Target: str) -> bool:
|
||||
if Template.Root == "Auto":
|
||||
return not (Target == str(Home) or Target.startswith(f"{Home}{os.sep}"))
|
||||
return bool(Template.Root)
|
||||
|
||||
|
||||
def _WriteRoot(Target: str, Text: str, Log: LogFn) -> None:
|
||||
with tempfile.NamedTemporaryFile("w", delete=False, suffix=".sarasuite") as Temporary:
|
||||
Temporary.write(Text)
|
||||
os.chmod(Temporary.name, 0o644)
|
||||
try:
|
||||
_Run(["pkexec", "install", "-D", "-m", "644", Temporary.name, Target], Log)
|
||||
_Pkexec(["install", "-D", "-m", "644", Temporary.name, Target], Log)
|
||||
finally:
|
||||
os.unlink(Temporary.name)
|
||||
|
||||
|
||||
def _RemoveRoot(Target: str, Log: LogFn) -> None:
|
||||
_Run(["pkexec", "rm", "-f", Target], Log)
|
||||
# A leftover outside $HOME isn't worth failing an install or removal over.
|
||||
_Pkexec(["rm", "-f", Target], Log, Check=False)
|
||||
|
||||
|
||||
def _ReadIni(FilePath: Path) -> configparser.ConfigParser:
|
||||
@@ -381,7 +410,8 @@ def _ApplyTemplates(Program: Program, Context: dict, OldReceipt: Optional[Receip
|
||||
Target = Render(Template.Target, Context)
|
||||
Text = Render((Program.Folder / "Templates" / Template.Source).read_text(), Context)
|
||||
Previous = OldReceipt.FindFile(Target) if OldReceipt is not None else None
|
||||
Placed = PlacedFile(Target, "Template", Root=Template.Root, Displaced=Previous.Displaced if Previous else "")
|
||||
Root = _NeedsRoot(Template, Target)
|
||||
Placed = PlacedFile(Target, "Template", Root=Root, Displaced=Previous.Displaced if Previous else "")
|
||||
|
||||
# A file that's already there but wasn't placed by SaraSuite (e.g. a
|
||||
# hand-made desktop entry from development) is moved aside, not lost.
|
||||
@@ -393,7 +423,7 @@ def _ApplyTemplates(Program: Program, Context: dict, OldReceipt: Optional[Receip
|
||||
Log(f"Moved your existing {Target} aside (it comes back if {Program.Name} is removed)")
|
||||
|
||||
NewReceipt.Files.append(Placed)
|
||||
if Template.Root:
|
||||
if Root:
|
||||
if Path(Target).exists() and Path(Target).read_text() == Text:
|
||||
continue
|
||||
_WriteRoot(Target, Text, Log)
|
||||
|
||||
+12
-2
@@ -17,7 +17,7 @@ import tomlkit
|
||||
|
||||
from Core import (
|
||||
AutostartDir, CacheHome, ConfigHome, CurrentDesktop, DataHome, HasSystemd, Home, IsWindows, LoadConfig,
|
||||
Platform, ProgramsDir, SharedConfigDir, StartMenuDir, StartupDir,
|
||||
Platform, ProgramsDir, SharedConfigDir, StartMenuDir, StartupDir, UserPortalsSupported,
|
||||
)
|
||||
|
||||
# Never worth copying out of a source checkout, whatever Include says.
|
||||
@@ -55,7 +55,9 @@ class TemplateFile:
|
||||
Source: str # relative to the program's Templates/ folder
|
||||
Target: str
|
||||
Option: str = ""
|
||||
Root: bool = False # lives outside $HOME, written through pkexec
|
||||
# True: lives outside $HOME, written through pkexec. "Auto": only when its
|
||||
# Target turns out to be outside $HOME (e.g. {PortalsDir} on an old system).
|
||||
Root: bool | str = False
|
||||
Executable: bool = False
|
||||
|
||||
|
||||
@@ -239,6 +241,14 @@ class Program:
|
||||
AutostartDir=str(AutostartDir),
|
||||
ApplicationsDir=str(DataHome / "applications"),
|
||||
DBusServicesDir=str(DataHome / "dbus-1" / "services"),
|
||||
# xdg-desktop-portal: where a portal file goes (your own folder
|
||||
# where it's new enough, no password), the preferences file
|
||||
# (portals.conf without a desktop name, e.g. a window manager),
|
||||
# and the desktop's own entry in its list ("cosmic;" or "").
|
||||
PortalsDir=str(DataHome / "xdg-desktop-portal" / "portals") if UserPortalsSupported()
|
||||
else "/usr/share/xdg-desktop-portal/portals",
|
||||
PortalsConf=f"{Desktop.lower()}-portals.conf" if Desktop else "portals.conf",
|
||||
DesktopPortal=f"{Desktop.lower()};" if Desktop else "",
|
||||
)
|
||||
Context.update(Answers or {})
|
||||
return Context
|
||||
|
||||
@@ -18,7 +18,7 @@ RequirementsFile = "requirements.txt"
|
||||
[[Options]]
|
||||
Platform = "Linux"
|
||||
Id = "FilePicker"
|
||||
Label = "Use SaraFem as the system file picker (asks for your password: the portal file lives in /usr/share)"
|
||||
Label = "Use SaraFem as the system file picker"
|
||||
Default = true
|
||||
|
||||
[[Options]]
|
||||
@@ -48,16 +48,18 @@ Option = "FilePicker"
|
||||
[[Templates]]
|
||||
Platform = "Linux"
|
||||
Source = "SaraFem.portal"
|
||||
Target = "/usr/share/xdg-desktop-portal/portals/SaraFem.portal"
|
||||
# In your own folder where xdg-desktop-portal is new enough (1.17+); in
|
||||
# /usr/share, through pkexec, on older systems.
|
||||
Target = "{PortalsDir}/SaraFem.portal"
|
||||
Option = "FilePicker"
|
||||
Root = true
|
||||
Root = "Auto"
|
||||
|
||||
[[IniEdits]]
|
||||
Platform = "Linux"
|
||||
File = "{ConfigHome}/xdg-desktop-portal/{Desktop}-portals.conf"
|
||||
File = "{ConfigHome}/xdg-desktop-portal/{PortalsConf}"
|
||||
Section = "preferred"
|
||||
Key = "org.freedesktop.impl.portal.FileChooser"
|
||||
Value = "SaraFem;{Desktop};gtk;"
|
||||
Value = "SaraFem;{DesktopPortal}gtk;"
|
||||
Option = "FilePicker"
|
||||
|
||||
[[IniEdits]]
|
||||
|
||||
Reference in new issue
Block a user