Open a new one with SUPER, go to the old one with SUPER+ALT
Making the plain application keys focus an existing window was the wrong call. It reads well in a demo and it is what macOS does, but it made "give me another terminal" the awkward case -- and on a tiling desktop a second terminal beside the first is the normal way to work, not an edge case. Reaching for the launcher to open a second file manager is not an improvement on anything. So the plain keys do what they always did, and SUPER+ALT is the new capability rather than a tax on the old one: go to the terminal, editor, browser, files, calculator or mail you already have, wherever it is, and start one only if there is none. ALT rather than SHIFT because SUPER+SHIFT is already the window-manipulation space -- Files, Neovim and Settings would have collided with Focus session, Taller and Shorter, and breaking two keys out of the eight-key resize set to make room is the worse trade. Also fixes a real trap found while using it. The Alt-Tab overlay commits on SUPER release, which is a compositor bind running an IPC call; if that call ever fails to land, the overlay stayed up with no keyboard focus, no Escape handler and nothing clickable, so the only way out was an IPC call typed into a terminal it was covering. Clicking outside now dismisses it, clicking a row switches to that window -- which is the obvious thing to try and did nothing -- and an abandoned switch closes itself after ten seconds. Keyboard focus still stays with the compositor, because taking it mid-switch is what would break stepping.
This commit is contained in:
@@ -285,6 +285,7 @@ The mental model is unchanged from Forge:
|
||||
| `SUPER + SHIFT + P` | Color picker |
|
||||
| `CTRL + ALT + L` · `SUPER + Backspace` | Lock (SUPER+L is "focus right") |
|
||||
| `SUPER + /` | Every shortcut, on screen. Reads the live keymap, so a rebind shows here |
|
||||
| `SUPER + ALT + T/N/W/F/C/E` | Go to that application if it is open, rather than starting another |
|
||||
| `CTRL + ALT + Delete` | Power menu |
|
||||
|
||||
### Apps
|
||||
|
||||
@@ -136,23 +136,35 @@ local function write_categories()
|
||||
file:close()
|
||||
end
|
||||
|
||||
-- Focus it if it is already open, start it if it is not -- which is what the
|
||||
-- application keys do on macOS and Windows, and what pressing the browser key
|
||||
-- twice ought to do. bin/panama-launch matches on window class, narrowed by
|
||||
-- title where the class alone cannot tell two things apart: the terminal and
|
||||
-- the editor are both kitty here, so only the title distinguishes them.
|
||||
-- SUPER opens a new one. SUPER+ALT goes to the one you already have.
|
||||
--
|
||||
-- That order matters and was chosen deliberately after trying the reverse.
|
||||
-- Making the plain key focus an existing window reads well in a demo and is
|
||||
-- what macOS does, but it makes "give me another terminal" the awkward case --
|
||||
-- and on a tiling desktop, opening a second terminal beside the first is not
|
||||
-- an edge case, it is the normal way to work. So the plain key keeps doing
|
||||
-- what it has always done, and the modifier is the new capability rather than
|
||||
-- a tax on the old one.
|
||||
--
|
||||
-- ALT rather than SHIFT because SUPER+SHIFT is already the window-manipulation
|
||||
-- space: Files, Neovim and Settings would have collided with Focus session,
|
||||
-- Taller and Shorter, and breaking two keys out of the eight-key resize set to
|
||||
-- make room is a worse trade than borrowing a modifier.
|
||||
--
|
||||
-- The go-to binds still launch when nothing is open. A key that silently does
|
||||
-- nothing is worse than one that does the obvious thing.
|
||||
--
|
||||
-- Patterns are regular expressions and are anchored. An unanchored "mail"
|
||||
-- would match gmail-notifier, and the mail key would raise somebody's notifier
|
||||
-- instead. Single-quoted for the shell so a backslash reaches the matcher
|
||||
-- rather than being eaten on the way.
|
||||
-- would match gmail-notifier, and the go-to-mail key would raise somebody's
|
||||
-- notifier instead. Single-quoted for the shell so a backslash reaches the
|
||||
-- matcher rather than being eaten on the way.
|
||||
local function shell_quote(value)
|
||||
return "'" .. value:gsub("'", "'\\''") .. "'"
|
||||
end
|
||||
|
||||
local launcher_bin = "$HOME/.local/share/Panama/bin/panama-launch"
|
||||
|
||||
local function launch_or_focus(class, command, title)
|
||||
local function go_to(class, command, title)
|
||||
local parts = { launcher_bin, "--class", shell_quote(class) }
|
||||
if title then
|
||||
parts[#parts + 1] = "--title"
|
||||
@@ -164,12 +176,26 @@ local function launch_or_focus(class, command, title)
|
||||
end
|
||||
|
||||
category("Applications")
|
||||
bind(mod .. " + T", hl.dsp.exec_cmd(launch_or_focus("^kitty$", terminal)), { description = "Terminal" })
|
||||
bind(mod .. " + N", hl.dsp.exec_cmd(launch_or_focus("^kitty$", editor, "nvim")), { description = "Neovim" })
|
||||
bind(mod .. " + W", hl.dsp.exec_cmd(launch_or_focus("^helium", browser)), { description = "Browser" })
|
||||
bind(mod .. " + F", hl.dsp.exec_cmd(launch_or_focus("^org\\.gnome\\.Nautilus$", files)), { description = "Files" })
|
||||
bind(mod .. " + C", hl.dsp.exec_cmd(launch_or_focus("^org\\.gnome\\.Calculator$", calculator)), { description = "Calculator" })
|
||||
bind(mod .. " + E", hl.dsp.exec_cmd(launch_or_focus("^org\\.mozilla\\.thunderbird", mail)), { description = "Mail" })
|
||||
bind(mod .. " + T", hl.dsp.exec_cmd(terminal), { description = "Terminal" })
|
||||
bind(mod .. " + N", hl.dsp.exec_cmd(editor), { description = "Neovim" })
|
||||
bind(mod .. " + W", hl.dsp.exec_cmd(browser), { description = "Browser" })
|
||||
bind(mod .. " + F", hl.dsp.exec_cmd(files), { description = "Files" })
|
||||
bind(mod .. " + C", hl.dsp.exec_cmd(calculator), { description = "Calculator" })
|
||||
bind(mod .. " + E", hl.dsp.exec_cmd(mail), { description = "Mail" })
|
||||
|
||||
-- Go to the one already open, or start it if there is none.
|
||||
bind(mod .. " + ALT + T", hl.dsp.exec_cmd(go_to("^kitty$", terminal)),
|
||||
{ description = "Go to terminal" })
|
||||
bind(mod .. " + ALT + N", hl.dsp.exec_cmd(go_to("^kitty$", editor, "nvim")),
|
||||
{ description = "Go to Neovim" })
|
||||
bind(mod .. " + ALT + W", hl.dsp.exec_cmd(go_to("^helium", browser)),
|
||||
{ description = "Go to browser" })
|
||||
bind(mod .. " + ALT + F", hl.dsp.exec_cmd(go_to("^org\\.gnome\\.Nautilus$", files)),
|
||||
{ description = "Go to files" })
|
||||
bind(mod .. " + ALT + C", hl.dsp.exec_cmd(go_to("^org\\.gnome\\.Calculator$", calculator)),
|
||||
{ description = "Go to calculator" })
|
||||
bind(mod .. " + ALT + E", hl.dsp.exec_cmd(go_to("^org\\.mozilla\\.thunderbird", mail)),
|
||||
{ description = "Go to mail" })
|
||||
bind(mod .. " + I", hl.dsp.exec_cmd(settings), { description = "Settings" })
|
||||
bind("CTRL + SHIFT + Escape", hl.dsp.exec_cmd(sysmonitor), { description = "System monitor" })
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ once.
|
||||
| `Super` | The window you are looking at |
|
||||
| `Alt` | Workspaces |
|
||||
| `Super + Ctrl` | The layout itself: splitting, floating, swapping |
|
||||
| `Super + Alt` | The application you already have open |
|
||||
|
||||
Almost every shortcut follows from this. `Super + H` moves focus left.
|
||||
`Alt + H` moves to the workspace on the left. `Super + Ctrl + H` swaps the
|
||||
@@ -28,12 +29,25 @@ would rather not.
|
||||
Add `Shift` to move the window rather than the focus. `Super + Shift + L`
|
||||
takes the current window and moves it to the right.
|
||||
|
||||
## Opening versus going to
|
||||
|
||||
`Super + T` opens a terminal. It opens another one every time, which on a
|
||||
tiling desktop is the normal way to work rather than an accident.
|
||||
|
||||
`Super + Alt + T` goes to a terminal you already have, wherever it is, and
|
||||
opens one only if there is none. The same pair works for the editor, browser,
|
||||
files, calculator and mail.
|
||||
|
||||
So the plain key means "give me one" and adding `Alt` means "take me to the
|
||||
one I have".
|
||||
|
||||
## The handful worth memorising first
|
||||
|
||||
| Keys | What it does |
|
||||
|---|---|
|
||||
| `Super + Space` | Find and open anything |
|
||||
| `Super + T` | Terminal |
|
||||
| `Super + T` | Terminal (another one) |
|
||||
| `Super + Alt + T` | The terminal you already have |
|
||||
| `Super + Q` | Close the window |
|
||||
| `Super + backtick` | Every window, every workspace |
|
||||
| `Super + I` | Settings |
|
||||
|
||||
@@ -34,15 +34,37 @@ Loader {
|
||||
// Overlay so it sits above the focused window it is describing.
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.namespace: "qs-switcher"
|
||||
// Nothing here is clickable: the gesture is driven entirely from the
|
||||
// keyboard, and taking input would steal focus from the compositor
|
||||
// mid-switch, which is the one thing that would break it.
|
||||
// Keyboard focus stays with the compositor: the gesture is driven by
|
||||
// binds, and taking focus mid-switch is the one thing that would break
|
||||
// stepping. Pointer input is a different matter -- see below.
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
color: "transparent"
|
||||
|
||||
anchors { top: true; bottom: true; left: true; right: true }
|
||||
|
||||
// Anywhere outside the card puts the switcher away.
|
||||
//
|
||||
// This is recovery, not decoration. The gesture commits on SUPER
|
||||
// release, which is a compositor bind running `qs ipc call` -- and if
|
||||
// that call ever fails to land, the overlay used to stay up with no
|
||||
// keyboard focus, no Escape handler and nothing clickable, which meant
|
||||
// the only way out was an IPC call from a terminal the overlay was
|
||||
// covering.
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: WindowSwitcherState.cancel()
|
||||
}
|
||||
|
||||
// A switch nobody finished. Ten seconds is far longer than the gesture
|
||||
// takes and far shorter than "forever", so a lost commit costs a pause
|
||||
// rather than the session.
|
||||
Timer {
|
||||
running: WindowSwitcherState.open
|
||||
interval: 10000
|
||||
onTriggered: WindowSwitcherState.cancel()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(560, parent.width - 96)
|
||||
@@ -78,7 +100,18 @@ Loader {
|
||||
height: 44
|
||||
radius: 10
|
||||
border.width: 0
|
||||
color: row.current ? Theme.alpha(Theme.accent, 0.20) : "transparent"
|
||||
color: rowHover.hovered || row.current
|
||||
? Theme.alpha(Theme.accent, row.current ? 0.20 : 0.10)
|
||||
: "transparent"
|
||||
|
||||
// Pointing at a window and clicking it is the obvious
|
||||
// thing to try, and it did nothing.
|
||||
HoverHandler { id: rowHover }
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: WindowSwitcherState.selectAt(row.index)
|
||||
}
|
||||
|
||||
Image {
|
||||
id: icon
|
||||
|
||||
@@ -106,6 +106,15 @@ Singleton {
|
||||
target.wayland.activate();
|
||||
}
|
||||
|
||||
// Commit to a specific entry rather than the one stepping landed on.
|
||||
// Clicking a row is the obvious thing to try with a list on screen.
|
||||
function selectAt(position: int): void {
|
||||
if (!root.open || position < 0 || position >= root.windows.length)
|
||||
return;
|
||||
root.index = position;
|
||||
root.commit();
|
||||
}
|
||||
|
||||
function cancel(): void {
|
||||
root.open = false;
|
||||
root.windows = [];
|
||||
|
||||
Reference in New Issue
Block a user