Add Hyprland rofi action picker
This commit is contained in:
@@ -72,6 +72,7 @@ function M.setup(ctx)
|
||||
bind(hyper .. " + K", exec("rofi_kill_process.sh"), desc("Open process kill menu"))
|
||||
bind(hyper .. " + SHIFT + K", exec("rofi_kill_all.sh"), desc("Open kill-all menu"))
|
||||
bind(hyper .. " + R", exec("rofi-systemd"), desc("Open systemd unit menu"))
|
||||
bind(hyper .. " + X", exec("hypr_rofi_action"), desc("Open Hyprland action menu"))
|
||||
bind(hyper .. " + I", exec("rofi_select_input.hs"), desc("Open input selection menu"))
|
||||
bind(hyper .. " + Y", exec("rofi_agentic_skill"), desc("Open agentic skill menu"))
|
||||
end
|
||||
|
||||
@@ -15,10 +15,6 @@ function M.setup(ctx)
|
||||
|
||||
verify_config = command_line_contains("--verify-config")
|
||||
|
||||
local function bind(keys, dispatcher, opts)
|
||||
hl.bind(keys, dispatcher, opts)
|
||||
end
|
||||
|
||||
local function exec(command)
|
||||
return hl.dsp.exec_cmd(command)
|
||||
end
|
||||
@@ -27,6 +23,63 @@ function M.setup(ctx)
|
||||
return hl.dispatch(dispatcher)
|
||||
end
|
||||
|
||||
local action_registry = {}
|
||||
|
||||
local function action_text(value)
|
||||
return tostring(value or ""):gsub("[\t\r\n]", " "):gsub(" +", " "):match("^%s*(.-)%s*$")
|
||||
end
|
||||
|
||||
local function action_registry_path()
|
||||
local runtime_dir = os.getenv("XDG_RUNTIME_DIR") or "/tmp"
|
||||
return runtime_dir .. "/hyprland-actions.tsv"
|
||||
end
|
||||
|
||||
local function register_action(keys, dispatcher, opts)
|
||||
local description = opts and opts.description
|
||||
if not description or description == "" then
|
||||
return
|
||||
end
|
||||
|
||||
local id = tostring(#action_registry + 1)
|
||||
action_registry[#action_registry + 1] = {
|
||||
id = id,
|
||||
keys = action_text(keys),
|
||||
description = action_text(description),
|
||||
dispatcher = dispatcher,
|
||||
}
|
||||
end
|
||||
|
||||
local function bind(keys, dispatcher, opts)
|
||||
hl.bind(keys, dispatcher, opts)
|
||||
register_action(keys, dispatcher, opts)
|
||||
end
|
||||
|
||||
_G.im_hyprland_write_actions = function()
|
||||
local actions_file = io.open(action_registry_path(), "w")
|
||||
if not actions_file then
|
||||
return
|
||||
end
|
||||
|
||||
for _, action in ipairs(action_registry) do
|
||||
actions_file:write(action.id, "\t", action.description, "\t", action.keys, "\n")
|
||||
end
|
||||
|
||||
actions_file:close()
|
||||
end
|
||||
|
||||
_G.im_hyprland_run_action = function(id)
|
||||
local action = action_registry[tonumber(id)]
|
||||
if not action then
|
||||
return
|
||||
end
|
||||
|
||||
if type(action.dispatcher) == "function" then
|
||||
action.dispatcher()
|
||||
else
|
||||
dispatch(action.dispatcher)
|
||||
end
|
||||
end
|
||||
|
||||
local function shell_quote(value)
|
||||
return "'" .. tostring(value):gsub("'", "'\\''") .. "'"
|
||||
end
|
||||
|
||||
118
dotfiles/lib/bin/hypr_rofi_action
Executable file
118
dotfiles/lib/bin/hypr_rofi_action
Executable file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PROMPT = "Hyprland action"
|
||||
|
||||
|
||||
def ensure_hyprland_instance():
|
||||
if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
|
||||
return
|
||||
|
||||
runtime = Path(os.environ.get("XDG_RUNTIME_DIR", f"/run/user/{os.getuid()}"))
|
||||
hypr_dir = runtime / "hypr"
|
||||
if not hypr_dir.is_dir():
|
||||
return
|
||||
|
||||
sockets = []
|
||||
for socket in hypr_dir.glob("*/.socket.sock"):
|
||||
try:
|
||||
sockets.append((socket.stat().st_mtime, socket.parent.name))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if sockets:
|
||||
os.environ["HYPRLAND_INSTANCE_SIGNATURE"] = max(sockets)[1]
|
||||
|
||||
|
||||
def actions_path():
|
||||
return Path(os.environ.get("XDG_RUNTIME_DIR", f"/run/user/{os.getuid()}")) / "hyprland-actions.tsv"
|
||||
|
||||
|
||||
def rofi_index(entries):
|
||||
menu = "".join(f"{entry}\n" for entry in entries)
|
||||
result = subprocess.run(
|
||||
["rofi", "-dmenu", "-i", "-p", PROMPT, "-format", "i"],
|
||||
input=menu,
|
||||
check=False,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return None
|
||||
|
||||
try:
|
||||
return int(result.stdout.strip())
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def notify(message):
|
||||
if shutil.which("notify-send"):
|
||||
subprocess.run(["notify-send", "Hyprland action", message], check=False)
|
||||
else:
|
||||
sys.stderr.write(message + "\n")
|
||||
|
||||
|
||||
def hyprland_eval(expression):
|
||||
return subprocess.run(["hyprctl", "-q", "eval", expression], check=False)
|
||||
|
||||
|
||||
def request_actions_file():
|
||||
ensure_hyprland_instance()
|
||||
result = hyprland_eval("_G.im_hyprland_write_actions()")
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def read_actions():
|
||||
if not request_actions_file():
|
||||
return []
|
||||
|
||||
actions_file = actions_path()
|
||||
if not actions_file.is_file():
|
||||
return []
|
||||
|
||||
actions = []
|
||||
try:
|
||||
with actions_file.open() as handle:
|
||||
for line in handle:
|
||||
action_id, description, keys = (line.rstrip("\n").split("\t", 2) + ["", ""])[:3]
|
||||
if action_id and description:
|
||||
actions.append({"id": action_id, "description": description, "keys": keys})
|
||||
except OSError:
|
||||
return []
|
||||
|
||||
actions.sort(key=lambda action: (action["description"].lower(), action["keys"].lower()))
|
||||
return actions
|
||||
|
||||
|
||||
def dispatch(action):
|
||||
result = hyprland_eval(f'_G.im_hyprland_run_action("{action["id"]}")')
|
||||
if result.returncode != 0:
|
||||
notify(f"Failed to run action: {action['description']}")
|
||||
return result.returncode
|
||||
|
||||
|
||||
def main():
|
||||
actions = read_actions()
|
||||
if not actions:
|
||||
notify("No registered Hyprland actions found.")
|
||||
return 1
|
||||
|
||||
width = min(max(len(action["description"]) for action in actions), 48)
|
||||
labels = [f"{action['description']:<{width}} {action['keys']}" for action in actions]
|
||||
index = rofi_index(labels)
|
||||
if index is None:
|
||||
return 0
|
||||
if index < 0 or index >= len(actions):
|
||||
return 1
|
||||
|
||||
return dispatch(actions[index])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -151,6 +151,18 @@
|
||||
exec ${../dotfiles/lib/bin/hypr_rofi_layout} "$@"
|
||||
'';
|
||||
};
|
||||
hyprRofiAction = pkgs.writeShellApplication {
|
||||
name = "hypr_rofi_action";
|
||||
runtimeInputs = [
|
||||
pkgs.libnotify
|
||||
pkgs.python3
|
||||
pkgs.rofi
|
||||
hyprlandPackage
|
||||
];
|
||||
text = ''
|
||||
exec python3 ${../dotfiles/lib/bin/hypr_rofi_action} "$@"
|
||||
'';
|
||||
};
|
||||
hyprscratchSettings = {
|
||||
daemon_options = "clean";
|
||||
global_options = "";
|
||||
@@ -303,6 +315,7 @@
|
||||
|
||||
# For scripts
|
||||
hyprRofiLayout
|
||||
hyprRofiAction
|
||||
hyprRofiWindow
|
||||
hyprShellUi
|
||||
jq
|
||||
|
||||
Reference in New Issue
Block a user