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 .. " + 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
|
||||||
|
|||||||
@@ -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
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} "$@"
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user