From 87422afae38ae330411ec729ab77b00b98351d2c Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Tue, 9 Jun 2026 15:57:12 -0700 Subject: [PATCH] hypr: make Super+Alt+C toggle a runtime-selectable AI scratchpad Super+Alt+C now toggles whichever AI app is currently selected (Codex or Claude Desktop) instead of always Codex. The selection is read from $XDG_STATE_HOME/hypr/ai-scratchpad at keypress time, so switching is dynamic and needs no Hyprland reload. Hyper+C opens a rofi chooser (rofi_ai_scratchpad.sh) to pick between the two, replacing the old "Open Codex session menu" binding. It writes the choice and brings the selected scratchpad into view. Co-Authored-By: Claude Opus 4.8 --- dotfiles/config/hypr/hyprland/binds.lua | 6 +-- dotfiles/config/hypr/hyprland/scratchpads.lua | 51 +++++++++++++++++++ dotfiles/lib/bin/rofi_ai_scratchpad.sh | 39 ++++++++++++++ 3 files changed, 92 insertions(+), 4 deletions(-) create mode 100755 dotfiles/lib/bin/rofi_ai_scratchpad.sh diff --git a/dotfiles/config/hypr/hyprland/binds.lua b/dotfiles/config/hypr/hyprland/binds.lua index ff231676..8550b2bf 100644 --- a/dotfiles/config/hypr/hyprland/binds.lua +++ b/dotfiles/config/hypr/hyprland/binds.lua @@ -68,7 +68,7 @@ function M.setup(ctx) bind(hyper .. " + V", exec([[cliphist list | rofi -dmenu -p "Clipboard" | cliphist decode | wl-copy]]), desc("Open clipboard history")) bind(hyper .. " + P", exec("rofi-pass"), desc("Open password menu")) bind(hyper .. " + N", exec("rofi_codex_desktop_project.sh"), desc("Start Codex Desktop thread from project")) - bind(hyper .. " + C", exec("rofi_tmcodex.sh"), desc("Open Codex session menu")) + bind(hyper .. " + C", exec("rofi_ai_scratchpad.sh"), desc("Choose AI scratchpad (Codex/Claude)")) bind(hyper .. " + SHIFT + C", exec("rofi_tmcodex.sh resume"), desc("Resume Codex session")) bind(hyper .. " + L", exec("hypr_rofi_layout"), desc("Open Hyprland layout menu")) bind(hyper .. " + K", exec("rofi_kill_process.sh"), desc("Open process kill menu")) @@ -264,9 +264,7 @@ function M.setup(ctx) local function setup_scratchpad_bindings() bind(main_mod .. " + SHIFT + X", hl.dsp.workspace.toggle_special("NSP"), desc("Toggle NSP special workspace")) - bind(mod_alt .. " + C", function() - toggle_scratchpad("codex") - end, desc("Toggle Codex scratchpad")) + bind(mod_alt .. " + C", toggle_active_ai_scratchpad, desc("Toggle AI scratchpad (Codex/Claude)")) bind(mod_alt .. " + D", function() toggle_scratchpad("discord") end, desc("Toggle Discord scratchpad")) diff --git a/dotfiles/config/hypr/hyprland/scratchpads.lua b/dotfiles/config/hypr/hyprland/scratchpads.lua index fa060078..a13c99f0 100644 --- a/dotfiles/config/hypr/hyprland/scratchpads.lua +++ b/dotfiles/config/hypr/hyprland/scratchpads.lua @@ -17,6 +17,11 @@ function M.setup(ctx) class = "codex-desktop", allow_tiling = true, }, + claude = { + command = "claude-desktop", + class = "claude-desktop", + allow_tiling = true, + }, htop = { command = "alacritty --class htop-scratch --title htop -e htop", class = "htop-scratch", @@ -517,6 +522,49 @@ function M.setup(ctx) end end + -- Which AI scratchpad SUPER+ALT+C targets. Selected at runtime (no reload) + -- by rofi_ai_scratchpad.sh, which writes the chosen name to this file. + local ai_scratchpad_default = "codex" + + local function ai_scratchpad_state_path() + local base = os.getenv("XDG_STATE_HOME") or ((os.getenv("HOME") or "") .. "/.local/state") + return base .. "/hypr/ai-scratchpad" + end + + local function active_ai_scratchpad() + local file = io.open(ai_scratchpad_state_path(), "r") + if not file then + return ai_scratchpad_default + end + + local value = file:read("*l") + file:close() + value = value and value:gsub("%s+", "") + if scratchpads[value] then + return value + end + return ai_scratchpad_default + end + + local function toggle_active_ai_scratchpad() + toggle_scratchpad(active_ai_scratchpad()) + end + + -- Used by rofi_ai_scratchpad.sh after a selection: bring the chosen + -- scratchpad into view if it isn't already, without hiding it when it is. + local function show_active_ai_scratchpad() + local name = active_ai_scratchpad() + for _, window in ipairs(matching_scratchpad_windows(name)) do + if scratchpad_is_visible(window) then + return + end + end + toggle_scratchpad(name) + end + + _G.im_hyprland_toggle_ai_scratchpad = toggle_active_ai_scratchpad + _G.im_hyprland_show_ai_scratchpad = show_active_ai_scratchpad + ctx.lower_contains = lower_contains ctx.lower_contains_any = lower_contains_any ctx.scratchpad_window_matches = scratchpad_window_matches @@ -552,6 +600,9 @@ function M.setup(ctx) ctx.refresh_shell_workarea_and_scratchpads = refresh_shell_workarea_and_scratchpads ctx.adopt_matching_scratchpad_window = adopt_matching_scratchpad_window ctx.toggle_scratchpad = toggle_scratchpad + ctx.active_ai_scratchpad = active_ai_scratchpad + ctx.toggle_active_ai_scratchpad = toggle_active_ai_scratchpad + ctx.show_active_ai_scratchpad = show_active_ai_scratchpad end return M diff --git a/dotfiles/lib/bin/rofi_ai_scratchpad.sh b/dotfiles/lib/bin/rofi_ai_scratchpad.sh new file mode 100755 index 00000000..d9cd16a4 --- /dev/null +++ b/dotfiles/lib/bin/rofi_ai_scratchpad.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env zsh +set -euo pipefail + +# Choose which AI app SUPER+ALT+C toggles as a scratchpad: Codex or Claude +# Desktop. The choice is written to a state file that the Hyprland Lua config +# reads at keypress time, so switching is dynamic and needs no reload. + +state_file="${XDG_STATE_HOME:-$HOME/.local/state}/hypr/ai-scratchpad" +mkdir -p "${state_file:h}" + +names=(codex claude) +labels=("Codex" "Claude Desktop") + +current=codex +[[ -r "$state_file" ]] && current="$(<"$state_file")" + +menu="" +for i in {1..${#names}}; do + marker=" " + [[ "${names[$i]}" == "$current" ]] && marker="● " + menu+="${marker}${labels[$i]}\n" +done + +index="$(printf "$menu" | rofi -dmenu -i -p "AI scratchpad" -format i)" || exit 0 +[[ -n "$index" ]] || exit 0 + +selected="${names[$((index + 1))]}" +[[ -n "$selected" ]] || exit 0 + +print -r -- "$selected" > "$state_file" + +# Bring the freshly selected scratchpad into view (no-op if already visible). +if command -v hyprctl >/dev/null 2>&1; then + hyprctl -q eval "_G.im_hyprland_show_ai_scratchpad()" >/dev/null 2>&1 || true +fi + +if command -v notify-send >/dev/null 2>&1; then + notify-send "AI scratchpad" "Super+Alt+C now toggles ${labels[$((index + 1))]}" || true +fi