From ca13abc9b72f05e62e2e13373dde1f8b8c920b23 Mon Sep 17 00:00:00 2001 From: Bjorn Blomberg Date: Wed, 15 Jul 2026 20:15:17 +0200 Subject: [PATCH] Reliable letter-based default keybinds + kitty keyboard protocol alt+tab is grabbed by desktop WMs and alt+arrows are re-encoded or swallowed by several terminals, leaking plain arrow keys into the focused window. Primary defaults are now letter chords which every terminal delivers via ESC-prefix: alt+w (switcher), alt+h/alt+l (snap), alt+m (maximize). The alt+tab/arrow variants stay as extra bindings, and standalone/client now enable the kitty keyboard protocol (DISAMBIGUATE_ESCAPE_CODES) when the terminal supports it so even those are delivered unambiguously (Konsole 23.08+, kitty, foot, wezterm, ghostty). 34/34 integration checks. Co-Authored-By: Claude Fable 5 --- README.md | 4 ++-- assets/default-config.toml | 25 ++++++++++++++++++++----- config.toml | 31 ++++++++++++++++++++++++------- dist/linux/config.toml | 25 ++++++++++++++++++++----- dist/win/config.toml | 25 ++++++++++++++++++++----- src/client.rs | 12 ++++++++++++ src/config.rs | 28 +++++++++++++++++++++++----- src/main.rs | 26 +++++++++++++++++++++++--- tests/integration.py | 8 ++++++++ 9 files changed, 152 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index f9c3b6f..8d8f05f 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ En terminal-baserad fönsterhanterare med flytande fönster, full mussuport och - Flytande terminaler med fri positionering och storleksändring - Full mussuport: vänster/höger/mitten-klick, scroll, drag, ANSI-muse-protokoll vidarebefordras till appar - Scrollback-historik med scrollbar + copy-mode med sökning (`alt+c`) -- Fönsterväxlare i MRU-ordning (`alt+tab`/`alt+w`) -- Snap till halvskärm (`alt+left`/`alt+right` eller dra mot kanten) och maximera (`alt+up`/dubbelklick på titelraden) +- Fönsterväxlare i MRU-ordning (`alt+w`, även `alt+tab` där den inte fångas av skrivbordet) +- Snap till halvskärm (`alt+h`/`alt+l` eller dra mot kanten) och maximera (`alt+m`/dubbelklick på titelraden) — alt+pilar fungerar också i terminaler med kitty keyboard-protokoll - Teman i egna TOML-filer (`themes/`) + inbyggd inställningsdialog (kugghjulet `≡` i panelen) - Hot-reload: config.toml, panel- och temafiler laddas om live när de ändras - Panel (topp/botten) med klickbara knappar och dynamiska status-widgets diff --git a/assets/default-config.toml b/assets/default-config.toml index c141bae..45f5340 100644 --- a/assets/default-config.toml +++ b/assets/default-config.toml @@ -45,34 +45,49 @@ label = "Ny terminal" action = { type = "spawn_terminal" } [[keybind]] -key = "alt+tab" +key = "alt+w" scope = "global" label = "Fönsterväxlare" action = { type = "window_switcher" } [[keybind]] -key = "alt+w" +key = "alt+tab" scope = "global" action = { type = "window_switcher" } [[keybind]] -key = "alt+left" +key = "alt+h" scope = "global" label = "Fäst vänster" action = { type = "snap_left" } [[keybind]] -key = "alt+right" +key = "alt+left" +scope = "global" +action = { type = "snap_left" } + +[[keybind]] +key = "alt+l" scope = "global" label = "Fäst höger" action = { type = "snap_right" } [[keybind]] -key = "alt+up" +key = "alt+right" +scope = "global" +action = { type = "snap_right" } + +[[keybind]] +key = "alt+m" scope = "global" label = "Maximera/återställ" action = { type = "maximize" } +[[keybind]] +key = "alt+up" +scope = "global" +action = { type = "maximize" } + [[keybind]] key = "alt+c" scope = "global" diff --git a/config.toml b/config.toml index f79229b..67a4777 100644 --- a/config.toml +++ b/config.toml @@ -44,37 +44,54 @@ scope = "global" label = "Ny terminal" action = { type = "spawn_terminal" } -# Fönsterväxlare (Alt+Tab-overlay i MRU-ordning). -# OBS: vissa terminaler/skrivbord fångar alt+tab själva — alt+w är reserv. +# Fönsterväxlare (MRU-ordning). alt+w är primär — alt+tab fångas ofta +# av skrivbordsmiljön och alt+pilar av vissa terminaler; bokstavs-binds +# fungerar överallt. (I terminaler med kitty keyboard-protokoll +# levereras även alt+tab/pilar korrekt.) [[keybind]] -key = "alt+tab" +key = "alt+w" scope = "global" label = "Fönsterväxlare" action = { type = "window_switcher" } [[keybind]] -key = "alt+w" +key = "alt+tab" scope = "global" action = { type = "window_switcher" } # Fäst fokuserat fönster som vänster/höger halvskärm [[keybind]] -key = "alt+left" +key = "alt+h" scope = "global" label = "Fäst vänster" action = { type = "snap_left" } [[keybind]] -key = "alt+right" +key = "alt+left" +scope = "global" +action = { type = "snap_left" } + +[[keybind]] +key = "alt+l" scope = "global" label = "Fäst höger" action = { type = "snap_right" } +[[keybind]] +key = "alt+right" +scope = "global" +action = { type = "snap_right" } + # Maximera/återställ fokuserat fönster (även dubbelklick på titelraden) +[[keybind]] +key = "alt+m" +scope = "global" +label = "Maximera/återställ" +action = { type = "maximize" } + [[keybind]] key = "alt+up" scope = "global" -label = "Maximera/återställ" action = { type = "maximize" } # Copy-mode: bläddra/söka i fokuserat fönsters historik med tangentbordet diff --git a/dist/linux/config.toml b/dist/linux/config.toml index b8513d7..92c59c8 100644 --- a/dist/linux/config.toml +++ b/dist/linux/config.toml @@ -45,34 +45,49 @@ label = "Ny terminal" action = { type = "spawn_terminal" } [[keybind]] -key = "alt+tab" +key = "alt+w" scope = "global" label = "Fönsterväxlare" action = { type = "window_switcher" } [[keybind]] -key = "alt+w" +key = "alt+tab" scope = "global" action = { type = "window_switcher" } [[keybind]] -key = "alt+left" +key = "alt+h" scope = "global" label = "Fäst vänster" action = { type = "snap_left" } [[keybind]] -key = "alt+right" +key = "alt+left" +scope = "global" +action = { type = "snap_left" } + +[[keybind]] +key = "alt+l" scope = "global" label = "Fäst höger" action = { type = "snap_right" } [[keybind]] -key = "alt+up" +key = "alt+right" +scope = "global" +action = { type = "snap_right" } + +[[keybind]] +key = "alt+m" scope = "global" label = "Maximera/återställ" action = { type = "maximize" } +[[keybind]] +key = "alt+up" +scope = "global" +action = { type = "maximize" } + [[keybind]] key = "alt+c" scope = "global" diff --git a/dist/win/config.toml b/dist/win/config.toml index 6b8f9e4..3f2166e 100644 --- a/dist/win/config.toml +++ b/dist/win/config.toml @@ -45,34 +45,49 @@ label = "Ny PowerShell-terminal" action = { type = "spawn_terminal", shell = "powershell.exe" } [[keybind]] -key = "alt+tab" +key = "alt+w" scope = "global" label = "Fönsterväxlare" action = { type = "window_switcher" } [[keybind]] -key = "alt+w" +key = "alt+tab" scope = "global" action = { type = "window_switcher" } [[keybind]] -key = "alt+left" +key = "alt+h" scope = "global" label = "Fäst vänster" action = { type = "snap_left" } [[keybind]] -key = "alt+right" +key = "alt+left" +scope = "global" +action = { type = "snap_left" } + +[[keybind]] +key = "alt+l" scope = "global" label = "Fäst höger" action = { type = "snap_right" } [[keybind]] -key = "alt+up" +key = "alt+right" +scope = "global" +action = { type = "snap_right" } + +[[keybind]] +key = "alt+m" scope = "global" label = "Maximera/återställ" action = { type = "maximize" } +[[keybind]] +key = "alt+up" +scope = "global" +action = { type = "maximize" } + [[keybind]] key = "alt+c" scope = "global" diff --git a/src/client.rs b/src/client.rs index 228e633..edeb34e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -60,9 +60,21 @@ pub fn run(socket_path: &str) -> io::Result<()> { enable_raw_mode()?; let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture, crossterm::cursor::Hide)?; + let keyboard_enhanced = crossterm::terminal::supports_keyboard_enhancement().unwrap_or(false); + if keyboard_enhanced { + let _ = execute!( + io::stdout(), + crossterm::event::PushKeyboardEnhancementFlags( + crossterm::event::KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES + ) + ); + } let result = client_loop(&mut stdout, &mut writer, &frame_rx, term_w, term_h); + if keyboard_enhanced { + let _ = execute!(io::stdout(), crossterm::event::PopKeyboardEnhancementFlags); + } execute!( stdout, crossterm::cursor::Show, diff --git a/src/config.rs b/src/config.rs index 242d138..3ed7a36 100644 --- a/src/config.rs +++ b/src/config.rs @@ -173,35 +173,53 @@ fn default_keybinds() -> Vec { label: Some("Ny terminal".to_string()), }, KeybindConfig { - key: "alt+tab".to_string(), + key: "alt+w".to_string(), scope: KeybindScope::Global, action: MenuAction::WindowSwitcher, label: Some("Fönsterväxlare".to_string()), }, KeybindConfig { - key: "alt+w".to_string(), + key: "alt+tab".to_string(), scope: KeybindScope::Global, action: MenuAction::WindowSwitcher, label: None, }, KeybindConfig { - key: "alt+left".to_string(), + key: "alt+h".to_string(), scope: KeybindScope::Global, action: MenuAction::SnapLeft, label: Some("Fäst vänster".to_string()), }, KeybindConfig { - key: "alt+right".to_string(), + key: "alt+left".to_string(), + scope: KeybindScope::Global, + action: MenuAction::SnapLeft, + label: None, + }, + KeybindConfig { + key: "alt+l".to_string(), scope: KeybindScope::Global, action: MenuAction::SnapRight, label: Some("Fäst höger".to_string()), }, KeybindConfig { - key: "alt+up".to_string(), + key: "alt+right".to_string(), + scope: KeybindScope::Global, + action: MenuAction::SnapRight, + label: None, + }, + KeybindConfig { + key: "alt+m".to_string(), scope: KeybindScope::Global, action: MenuAction::Maximize, label: Some("Maximera/återställ".to_string()), }, + KeybindConfig { + key: "alt+up".to_string(), + scope: KeybindScope::Global, + action: MenuAction::Maximize, + label: None, + }, KeybindConfig { key: "alt+c".to_string(), scope: KeybindScope::Global, diff --git a/src/main.rs b/src/main.rs index edf7a70..8bd1c56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -170,12 +170,17 @@ KONFIGURATION (./config.toml, annars ~/.config/tui-wm/config.toml): TANGENTER (standard, ändras via [[keybind]] i config.toml): ctrl+space Kommandodialog — skriv t.ex. `htop` och tryck Enter alt+enter Ny terminal - alt+tab/alt+w Fönsterväxlare (MRU) — Tab stegar, Enter väljer - alt+left/right Fäst fönster som vänster/höger halvskärm - alt+up Maximera/återställ (även dubbelklick på titelraden) + alt+w Fönsterväxlare (MRU) — Tab stegar, Enter väljer + alt+h/alt+l Fäst fönster som vänster/höger halvskärm + alt+m Maximera/återställ (även dubbelklick på titelraden) alt+c Copy-mode: bläddra/söka i historiken (/ n N q) ctrl+q Avsluta (från en ansluten klient: kopplar bara ner den) + alt+tab och alt+pilar är också bundna men fångas ofta av + skrivbordsmiljön/terminalen — bokstavsvarianterna fungerar överallt. + I terminaler med kitty keyboard-protokoll (Konsole 23.08+, kitty, + foot, wezterm, ghostty) levereras även pil-varianterna korrekt. + TEMA & INSTÄLLNINGAR: theme = \"themes/dark.toml\" i config.toml väljer tema (egna teman = egna TOML-filer). Kugghjulet ≡ i panelen öppnar inbyggda @@ -203,6 +208,18 @@ fn run_standalone() -> io::Result<()> { enable_raw_mode()?; let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture, EnableBracketedPaste, EnableFocusChange)?; + // Kitty keyboard-protokoll (om terminalen stödjer det): gör att + // alt+pilar, alt+tab m.fl. levereras entydigt i stället för att + // kollidera med legacy-escape-sekvenser. + let keyboard_enhanced = crossterm::terminal::supports_keyboard_enhancement().unwrap_or(false); + if keyboard_enhanced { + let _ = execute!( + io::stdout(), + crossterm::event::PushKeyboardEnhancementFlags( + crossterm::event::KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES + ) + ); + } let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; @@ -212,6 +229,9 @@ fn run_standalone() -> io::Result<()> { let result = run_standalone_loop(&mut terminal, &mut app, ipc_rx, &socket_path); + if keyboard_enhanced { + let _ = execute!(io::stdout(), crossterm::event::PopKeyboardEnhancementFlags); + } disable_raw_mode()?; execute!(terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture, DisableBracketedPaste, DisableFocusChange)?; terminal.show_cursor()?; diff --git a/tests/integration.py b/tests/integration.py index 36dac3d..669c1bf 100644 --- a/tests/integration.py +++ b/tests/integration.py @@ -346,6 +346,14 @@ def main(): maxed = next(w for w in wmx["windows"] if w["id"] == sw_open["id"]) check("alt+up maximerar fönstret", maxed["width"] >= 95, f"geometri: {maxed}") + # Bokstavs-varianten: alt+m ska toggla tillbaka (återställa) + disp.send(key_event(ch="m", modifiers="ALT")) + time.sleep(0.3) + app.send({"type": "list_windows", "request_id": "lrest"}) + wre = app.recv_until("window_list") + restored = next(w for w in wre["windows"] if w["id"] == sw_open["id"]) + check("alt+m återställer (bokstavs-bind)", 45 <= restored["width"] <= 55, + f"geometri: {restored}") app.send({"type": "close_window", "window_id": sw_open["id"]}) # ── 4f. Copy-mode: alt+c, sök, avsluta ───────────────────────────