Fork the desktop off Omarchy as a self-contained system
This commit is contained in:
Executable
+124
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# blob:summary=Pick files with the desktop file chooser
|
||||
# blob:args=[--title <title>] [--multiple] [--directory] [--extensions "<ext ext...>"]
|
||||
# blob:examples=blob file select --title "Send with Tailscale" --multiple | blob file select --title "Pick image" --extensions "png svg" | blob file select --title "Share folder" --directory
|
||||
|
||||
# Python rather than bash, alone among the commands here, because the portal
|
||||
# answers a request with a Response signal addressed to the connection that
|
||||
# asked, and D-Bus delivers a directed signal only to that connection. Every
|
||||
# shell-callable client — gdbus call, busctl call, dbus-send — opens its own
|
||||
# connection and exits before the answer arrives, and gdbus monitor registers
|
||||
# with AddMatch rather than BecomeMonitor, so it never sees one either. Holding
|
||||
# a single connection across both the call and the wait is the whole job, and
|
||||
# bash has no way to hold one.
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gio", "2.0")
|
||||
from gi.repository import Gio, GLib
|
||||
|
||||
# A dialog nobody ever answers would otherwise keep this process, and whatever
|
||||
# waits on its output, alive forever.
|
||||
ANSWER_TIMEOUT_SEC = 600
|
||||
|
||||
# Callers act on these: nothing picked is a decision, a chooser that never ran
|
||||
# is a fault, and the two want different handling.
|
||||
EXIT_NOTHING_PICKED = 1
|
||||
EXIT_CHOOSER_FAILED = 2
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument("--title", default="Select file")
|
||||
parser.add_argument("--multiple", action="store_true")
|
||||
parser.add_argument("--directory", action="store_true")
|
||||
parser.add_argument("--extensions", default="")
|
||||
args, unknown = parser.parse_known_args()
|
||||
|
||||
if unknown:
|
||||
print("blob-file-select: unknown option %s" % unknown[0], file=sys.stderr)
|
||||
return EXIT_CHOOSER_FAILED
|
||||
|
||||
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
||||
loop = GLib.MainLoop()
|
||||
uris = []
|
||||
|
||||
def on_response(connection, sender, path, interface, signal, params):
|
||||
code, results = params.unpack()
|
||||
if code == 0:
|
||||
uris.extend(results.get("uris", []))
|
||||
loop.quit()
|
||||
|
||||
def subscribe(path):
|
||||
bus.signal_subscribe(
|
||||
"org.freedesktop.portal.Desktop",
|
||||
"org.freedesktop.portal.Request",
|
||||
"Response",
|
||||
path,
|
||||
None,
|
||||
Gio.DBusSignalFlags.NONE,
|
||||
on_response,
|
||||
)
|
||||
|
||||
# The request path is derived from our bus name and the token we pass, so it
|
||||
# can be subscribed to up front. Asking first would race a dialog that gets
|
||||
# answered immediately.
|
||||
token = "blob%d" % os.getpid()
|
||||
sender = bus.get_unique_name()[1:].replace(".", "_")
|
||||
predicted = "/org/freedesktop/portal/desktop/request/%s/%s" % (sender, token)
|
||||
subscribe(predicted)
|
||||
|
||||
options = {
|
||||
"handle_token": GLib.Variant("s", token),
|
||||
"multiple": GLib.Variant("b", args.multiple),
|
||||
}
|
||||
|
||||
if args.directory:
|
||||
options["directory"] = GLib.Variant("b", True)
|
||||
|
||||
# Filters name file formats, which a directory chooser has no use for.
|
||||
if args.extensions and not args.directory:
|
||||
# Glob matching in the chooser is case-sensitive, so cover both cases.
|
||||
exts = [ext.lstrip(".").lower() for ext in args.extensions.split()]
|
||||
patterns = [(0, "*." + ext) for ext in exts] + [(0, "*." + ext.upper()) for ext in exts]
|
||||
label = " ".join("*." + ext for ext in exts)
|
||||
filters = GLib.Variant("a(sa(us))", [(label, patterns)])
|
||||
options["filters"] = filters
|
||||
options["current_filter"] = GLib.Variant("(sa(us))", (label, patterns))
|
||||
|
||||
handle = bus.call_sync(
|
||||
"org.freedesktop.portal.Desktop",
|
||||
"/org/freedesktop/portal/desktop",
|
||||
"org.freedesktop.portal.FileChooser",
|
||||
"OpenFile",
|
||||
GLib.Variant("(ssa{sv})", ("", args.title, options)),
|
||||
None,
|
||||
Gio.DBusCallFlags.NONE,
|
||||
-1,
|
||||
None,
|
||||
).unpack()[0]
|
||||
|
||||
# Portals predating the token convention answer on a path of their choosing.
|
||||
if handle != predicted:
|
||||
subscribe(handle)
|
||||
|
||||
GLib.timeout_add_seconds(ANSWER_TIMEOUT_SEC, loop.quit)
|
||||
loop.run()
|
||||
|
||||
for uri in uris:
|
||||
print(GLib.filename_from_uri(uri)[0])
|
||||
|
||||
return 0 if uris else EXIT_NOTHING_PICKED
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.exit(main())
|
||||
except GLib.Error as error:
|
||||
print("blob-file-select: %s" % error.message, file=sys.stderr)
|
||||
sys.exit(EXIT_CHOOSER_FAILED)
|
||||
Reference in New Issue
Block a user