feat: sims cli as python package
This commit is contained in:
13
Makefile
13
Makefile
@@ -1,2 +1,15 @@
|
|||||||
run:
|
run:
|
||||||
python -m sims.main --config ./example-stylix-dev.yaml
|
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;
|
formatter = pkgs.nixfmt-rfc-style;
|
||||||
devShells.default = pkgs.callPackage ./nix/shell.nix { inherit pkgs; };
|
devShells.default = pkgs.callPackage ./nix/shell.nix { inherit pkgs; };
|
||||||
packages = {
|
packages = rec {
|
||||||
default = pkgs.callPackage ./nix/derivation.nix { inherit (pkgs) lib python3Packages; };
|
default = pkgs.callPackage ./nix/derivation.nix { inherit (pkgs) lib python3Packages; };
|
||||||
sims-cli = pkgs.writeShellApplication {
|
sims = default;
|
||||||
name = "sims-cli";
|
|
||||||
runtimeInputs = [ pkgs.dbus ];
|
|
||||||
text = builtins.readFile ./scripts/sims-cli.sh;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
apps.default = {
|
apps.default = {
|
||||||
type = "app";
|
type = "app";
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
notmuch,
|
notmuch,
|
||||||
khal,
|
khal,
|
||||||
emacs,
|
emacs,
|
||||||
|
dbus,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
@@ -64,6 +65,8 @@ python3Packages.buildPythonApplication {
|
|||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
cp scripts/launcher.py $out/bin/sims
|
cp scripts/launcher.py $out/bin/sims
|
||||||
chmod +x $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
|
runHook postInstall
|
||||||
@@ -71,7 +74,7 @@ python3Packages.buildPythonApplication {
|
|||||||
|
|
||||||
preFixup = ''
|
preFixup = ''
|
||||||
makeWrapperArgs+=("''${gappsWrapperArgs[@]}")
|
makeWrapperArgs+=("''${gappsWrapperArgs[@]}")
|
||||||
makeWrapperArgs+=(--prefix PATH : ${lib.makeBinPath [ khal notmuch emacs ]})
|
makeWrapperArgs+=(--prefix PATH : ${lib.makeBinPath [ khal notmuch emacs dbus ]})
|
||||||
'';
|
'';
|
||||||
|
|
||||||
passthru = {
|
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