Add Hyprland rofi action picker

This commit is contained in:
2026-05-13 02:23:21 -07:00
parent bd85161f7a
commit 996d02cc60
4 changed files with 189 additions and 4 deletions

View File

@@ -72,6 +72,7 @@ function M.setup(ctx)
bind(hyper .. " + K", exec("rofi_kill_process.sh"), desc("Open process kill menu")) 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 .. " + 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 .. " + 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 .. " + I", exec("rofi_select_input.hs"), desc("Open input selection menu"))
bind(hyper .. " + Y", exec("rofi_agentic_skill"), desc("Open agentic skill menu")) bind(hyper .. " + Y", exec("rofi_agentic_skill"), desc("Open agentic skill menu"))
end end

View File

@@ -15,10 +15,6 @@ function M.setup(ctx)
verify_config = command_line_contains("--verify-config") verify_config = command_line_contains("--verify-config")
local function bind(keys, dispatcher, opts)
hl.bind(keys, dispatcher, opts)
end
local function exec(command) local function exec(command)
return hl.dsp.exec_cmd(command) return hl.dsp.exec_cmd(command)
end end
@@ -27,6 +23,63 @@ function M.setup(ctx)
return hl.dispatch(dispatcher) return hl.dispatch(dispatcher)
end 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) local function shell_quote(value)
return "'" .. tostring(value):gsub("'", "'\\''") .. "'" return "'" .. tostring(value):gsub("'", "'\\''") .. "'"
end end

118
dotfiles/lib/bin/hypr_rofi_action Executable file
View 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())

View File

@@ -151,6 +151,18 @@
exec ${../dotfiles/lib/bin/hypr_rofi_layout} "$@" 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 = { hyprscratchSettings = {
daemon_options = "clean"; daemon_options = "clean";
global_options = ""; global_options = "";
@@ -303,6 +315,7 @@
# For scripts # For scripts
hyprRofiLayout hyprRofiLayout
hyprRofiAction
hyprRofiWindow hyprRofiWindow
hyprShellUi hyprShellUi
jq jq