feat: sims cli as python package
This commit is contained in:
13
Makefile
13
Makefile
@@ -1,2 +1,15 @@
|
||||
run:
|
||||
python -m sims.main --config ./example-stylix-dev.yaml
|
||||
|
||||
# Talk to the running sims daemon over DBus.
|
||||
# Usage: make cli list
|
||||
# make cli finder
|
||||
# make cli screenrec stop
|
||||
# make cli ARGS="list --json" # for flags (make eats leading dashes)
|
||||
ifeq (cli,$(firstword $(MAKECMDGOALS)))
|
||||
CLI_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
|
||||
$(eval $(CLI_ARGS):;@:)
|
||||
endif
|
||||
|
||||
cli:
|
||||
@python -m sims.cli $(if $(ARGS),$(ARGS),$(CLI_ARGS))
|
||||
|
||||
@@ -32,13 +32,9 @@
|
||||
{
|
||||
formatter = pkgs.nixfmt-rfc-style;
|
||||
devShells.default = pkgs.callPackage ./nix/shell.nix { inherit pkgs; };
|
||||
packages = {
|
||||
packages = rec {
|
||||
default = pkgs.callPackage ./nix/derivation.nix { inherit (pkgs) lib python3Packages; };
|
||||
sims-cli = pkgs.writeShellApplication {
|
||||
name = "sims-cli";
|
||||
runtimeInputs = [ pkgs.dbus ];
|
||||
text = builtins.readFile ./scripts/sims-cli.sh;
|
||||
};
|
||||
sims = default;
|
||||
};
|
||||
apps.default = {
|
||||
type = "app";
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
notmuch,
|
||||
khal,
|
||||
emacs,
|
||||
dbus,
|
||||
...
|
||||
}:
|
||||
|
||||
@@ -64,6 +65,8 @@ python3Packages.buildPythonApplication {
|
||||
mkdir -p $out/bin
|
||||
cp scripts/launcher.py $out/bin/sims
|
||||
chmod +x $out/bin/sims
|
||||
cp scripts/cli_launcher.py $out/bin/sims-cli
|
||||
chmod +x $out/bin/sims-cli
|
||||
|
||||
|
||||
runHook postInstall
|
||||
@@ -71,7 +74,7 @@ python3Packages.buildPythonApplication {
|
||||
|
||||
preFixup = ''
|
||||
makeWrapperArgs+=("''${gappsWrapperArgs[@]}")
|
||||
makeWrapperArgs+=(--prefix PATH : ${lib.makeBinPath [ khal notmuch emacs ]})
|
||||
makeWrapperArgs+=(--prefix PATH : ${lib.makeBinPath [ khal notmuch emacs dbus ]})
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
|
||||
20
scripts/cli_launcher.py
Normal file
20
scripts/cli_launcher.py
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
site_packages_dir = os.path.join(
|
||||
script_dir,
|
||||
os.pardir,
|
||||
"lib",
|
||||
f"python{sys.version_info.major}.{sys.version_info.minor}",
|
||||
"site-packages",
|
||||
)
|
||||
|
||||
if site_packages_dir not in sys.path:
|
||||
sys.path.insert(0, site_packages_dir)
|
||||
|
||||
from sims.cli import main
|
||||
|
||||
sys.argv[0] = os.path.join(script_dir, os.path.basename(__file__))
|
||||
sys.exit(main())
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# sims-cli — talk to the running `sims` status bar over DBus.
|
||||
set -euo pipefail
|
||||
|
||||
DEST=org.Fabric.fabric.sims
|
||||
OBJ=/org/Fabric/fabric
|
||||
IFACE=org.Fabric.fabric
|
||||
|
||||
invoke() {
|
||||
dbus-send --session --print-reply --dest="$DEST" "$OBJ" \
|
||||
"$IFACE.InvokeAction" "string:$1" "array:string:" >/dev/null
|
||||
}
|
||||
|
||||
list_actions() {
|
||||
dbus-send --session --print-reply --dest="$DEST" "$OBJ" \
|
||||
org.freedesktop.DBus.Properties.Get \
|
||||
"string:$IFACE" string:Actions
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF' >&2
|
||||
usage: sims-cli <command> [args]
|
||||
finder open window finder
|
||||
apps open application launcher
|
||||
power open power menu
|
||||
screenshot open screenshot menu
|
||||
notmuch-refresh refresh unread mail count
|
||||
screenrec menu open screenrec menu (auto-detects state)
|
||||
screenrec start-monitor start recording the focused monitor
|
||||
screenrec start-region start recording a slurp-selected region
|
||||
screenrec stop stop active recording
|
||||
list list registered actions
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
finder) invoke open-finder ;;
|
||||
apps) invoke open-app-launcher ;;
|
||||
power) invoke open-power-menu ;;
|
||||
screenshot) invoke open-screenshot-menu ;;
|
||||
notmuch-refresh) invoke refresh-notmuch ;;
|
||||
screenrec)
|
||||
case "${2:-}" in
|
||||
menu) invoke open-screenrec-menu ;;
|
||||
start-monitor) invoke screenrec-start-monitor ;;
|
||||
start-region) invoke screenrec-start-region ;;
|
||||
stop) invoke screenrec-stop ;;
|
||||
*) usage ;;
|
||||
esac ;;
|
||||
list) list_actions ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
132
sims/cli.py
Normal file
132
sims/cli.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""sims-cli — talk to the running sims status bar over DBus."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
|
||||
DEST = "org.Fabric.fabric.sims"
|
||||
OBJ = "/org/Fabric/fabric"
|
||||
IFACE = "org.Fabric.fabric"
|
||||
|
||||
|
||||
def _dbus_send(*args: str) -> str:
|
||||
proc = subprocess.run(
|
||||
["dbus-send", "--session", "--print-reply", f"--dest={DEST}", OBJ, *args],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
sys.stderr.write(proc.stderr)
|
||||
sys.exit(proc.returncode)
|
||||
return proc.stdout
|
||||
|
||||
|
||||
def invoke_action(action: str) -> None:
|
||||
_dbus_send(f"{IFACE}.InvokeAction", f"string:{action}", "array:string:")
|
||||
|
||||
|
||||
_ACTION_RE = re.compile(r'dict entry\(\s*string "([^"]+)"')
|
||||
|
||||
|
||||
def list_actions() -> list[str]:
|
||||
out = _dbus_send(
|
||||
"org.freedesktop.DBus.Properties.Get",
|
||||
f"string:{IFACE}",
|
||||
"string:Actions",
|
||||
)
|
||||
return _ACTION_RE.findall(out)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Command:
|
||||
name: str
|
||||
help: str
|
||||
run: Callable[[argparse.Namespace], None]
|
||||
|
||||
|
||||
def _action(name: str) -> Callable[[argparse.Namespace], None]:
|
||||
return lambda _ns: invoke_action(name)
|
||||
|
||||
|
||||
COMMANDS: list[Command] = [
|
||||
Command("finder", "open window finder", _action("open-finder")),
|
||||
Command("apps", "open application launcher", _action("open-app-launcher")),
|
||||
Command("power", "open power menu", _action("open-power-menu")),
|
||||
Command("screenshot", "open screenshot menu", _action("open-screenshot-menu")),
|
||||
Command("notmuch-refresh", "refresh unread mail count", _action("refresh-notmuch")),
|
||||
]
|
||||
|
||||
|
||||
def _cmd_screenrec(ns: argparse.Namespace) -> None:
|
||||
mapping = {
|
||||
"menu": "open-screenrec-menu",
|
||||
"start-monitor": "screenrec-start-monitor",
|
||||
"start-region": "screenrec-start-region",
|
||||
"stop": "screenrec-stop",
|
||||
}
|
||||
invoke_action(mapping[ns.screenrec_cmd])
|
||||
|
||||
|
||||
def _cmd_list(ns: argparse.Namespace) -> None:
|
||||
actions = list_actions()
|
||||
if ns.json:
|
||||
json.dump(actions, sys.stdout, indent=2)
|
||||
sys.stdout.write("\n")
|
||||
else:
|
||||
for a in actions:
|
||||
print(a)
|
||||
|
||||
|
||||
def _cmd_invoke(ns: argparse.Namespace) -> None:
|
||||
invoke_action(ns.action)
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="sims-cli",
|
||||
description="Talk to the running sims status bar over DBus.",
|
||||
)
|
||||
sub = parser.add_subparsers(dest="cmd", required=True, metavar="COMMAND")
|
||||
|
||||
for cmd in COMMANDS:
|
||||
p = sub.add_parser(cmd.name, help=cmd.help)
|
||||
p.set_defaults(func=cmd.run)
|
||||
|
||||
rec = sub.add_parser("screenrec", help="screen recording controls")
|
||||
rec_sub = rec.add_subparsers(dest="screenrec_cmd", required=True, metavar="ACTION")
|
||||
for sub_name, sub_help in [
|
||||
("menu", "open screenrec menu (auto-detects state)"),
|
||||
("start-monitor", "start recording the focused monitor"),
|
||||
("start-region", "start recording a slurp-selected region"),
|
||||
("stop", "stop active recording"),
|
||||
]:
|
||||
rec_sub.add_parser(sub_name, help=sub_help)
|
||||
rec.set_defaults(func=_cmd_screenrec)
|
||||
|
||||
lst = sub.add_parser("list", help="list registered actions")
|
||||
lst.add_argument("--json", action="store_true", help="emit JSON array")
|
||||
lst.set_defaults(func=_cmd_list)
|
||||
|
||||
inv = sub.add_parser(
|
||||
"invoke",
|
||||
help="invoke a raw action by name (escape hatch for actions without a dedicated subcommand)",
|
||||
)
|
||||
inv.add_argument("action", help="dbus action name, e.g. open-finder")
|
||||
inv.set_defaults(func=_cmd_invoke)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = build_parser()
|
||||
ns = parser.parse_args()
|
||||
ns.func(ns)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user