← Otto User GuideKeyboard Shortcuts

Every shortcut in Otto is defined in your config file, under [keyboard_shortcuts]. There are no built-in bindings apart from two escape hatches (see Always-on keys below) — if your config has no [keyboard_shortcuts] table, Otto starts with no shortcuts at all.

Default config

The shipped /etc/otto/config.toml (a copy of otto_config.example.toml) defines a full set. Those are the “defaults” referred to throughout this guide.

Binding syntax

Each entry maps a trigger on the left to an action on the right:

[keyboard_shortcuts]
"Ctrl+Esc"          = "Quit"
"Ctrl+Return"       = { run = { cmd = "terminator", args = [] } }
"Logo+B"            = { open_default = "browser" }
"Ctrl+1"            = { builtin = "Workspace", index = 0 }

Triggers

A trigger is zero or more modifiers followed by a key, joined by +:

Ctrl+Shift+Q
Logo+Space
XF86AudioMute

Modifiers (case-insensitive, with aliases):

ModifierAlso accepted as
CtrlControl, Primary
Alt
Shift
LogoSuper, Meta, Win, Command

Keys are XKB keysym names. Letters are case-insensitive (W and w bind the same physical key — use Shift+W if you mean shifted). Otto also accepts a few friendly aliases for names people actually write:

You can writeXKB name
EscEscape
ArrowUp / ArrowDown / ArrowLeft / ArrowRightUp / Down / Left / Right

Everything else uses the real keysym name: Return, space, Tab, Prior (Page Up), Next (Page Down), grave (`), ISO_Left_Tab (Shift+Tab), XF86AudioRaiseVolume, and so on. xkbcli list and man xkeyboard-config are the references; the repo also ships scripts/show-keys.sh to print the keysym for whatever you press.

A trigger Otto cannot parse is skipped with a warning in the log, not an error — so a typo silently costs you that one binding. If a shortcut “does nothing”, check the log first.

Two triggers that resolve to the same key combination collide; the later one (alphabetically, since the table is sorted) wins, and a warning is logged.

Action forms

There are four ways to write an action:

1. A built-in, by name:

"Ctrl+Tab" = "ApplicationSwitchNext"

2. A built-in that takes an index:

"Ctrl+1" = { builtin = "Workspace", index = 0 }
"Ctrl+F2" = { builtin = "Screen", index = 1 }

3. Run a command:

"Ctrl+Return" = { run = { cmd = "alacritty", args = [] } }
"Logo+Shift+B" = { run = { cmd = "firefox", args = ["--private-window"] } }

The command is spawned fire-and-forget. It is not run through a shell, so pipes, globs and && do not work — call sh -c explicitly if you need them.

4. Open the user’s default application for a role:

"Logo+B"     = { open_default = "browser" }
"Logo+Space" = { open_default = "file_manager" }
"Logo+T"     = { open_default = { role = "terminal", fallback = "alacritty" } }

This resolves through your mimeapps.list defaults, so it launches whatever you have set as the handler rather than a hard-coded program. Recognised roles:

RoleResolved via
browserx-scheme-handler/https, then http, then text/html
file_manager / filesinode/directory
terminal / shellx-scheme-handler/terminal, then application/x-terminal
anything containing /used directly as a MIME type
anything elsex-scheme-handler/<role>

You can also give a desktop file id directly (open_default = "firefox.desktop"). The optional fallback is used when the role resolves to nothing; it may be a desktop id or a plain command line.

Built-in actions

Session

ActionEffect
QuitExit Otto, ending the session
LockSessionLaunch the configured locker — see Lock Screen

Windows

ActionEffect
CloseWindowAsk the focused window to close
ToggleMaximizeWindowMaximize / restore the focused window (animated)
TileWindowLeftSnap the focused window to the left half of its monitor
TileWindowRightSnap the focused window to the right half
ToggleDecorationsFlip every window between client-side and server-side decoration mode

Applications

ActionEffect
ApplicationSwitchNextOpen the app switcher / move forward
ApplicationSwitchPrevMove backward in the app switcher
ApplicationSwitchNextWindowCycle windows within the highlighted app
ApplicationSwitchQuitQuit the highlighted app

The switcher stays up as long as you hold the modifier that opened it, and commits when you release it. See Exposé & App Switcher.

Workspaces and overview

ActionEffect
Workspace (needs index)Switch to workspace N, zero-based
ExposeShowAllToggle the exposé grid of all windows
ExposeShowDesktopPush all windows aside to reveal the desktop

Displays

ActionEffect
Screen (needs index)Warp the pointer to the center of monitor N, zero-based
ScaleUpIncrease the scale factor of the monitor under the pointer
ScaleDownDecrease it
RotateOutputRotate the monitor under the pointer by 90°

ScaleUp, ScaleDown and RotateOutput change the live session only; they do not write anything back to the config. Use display profiles to make a scale or rotation stick.

Hardware keys

ActionEffect
BrightnessUp / BrightnessDownScreen backlight
VolumeUp / VolumeDown / VolumeMuteAudio volume
MediaPlayPause / MediaNext / MediaPrev / MediaStopMedia player control (MPRIS)

Volume and brightness changes surface in the dynamic island and can play a feedback sound — see Audio.

Debugging

ActionEffect
SceneSnapshot (alias ExportSceneJson)Dump the scene graph to scene.json in the working directory
SkpSnapshot (alias ExportSceneSkp)Dump the focused monitor’s render tree as a Skia .skp picture

Useful when reporting a rendering bug.

Always-on keys

These are handled before the config is consulted and cannot be rebound or disabled. They exist so a locked, grabbed or misconfigured session is never a dead end.

KeysEffect
Logo+QQuit Otto immediately
Ctrl+Alt+BackspaceQuit Otto immediately
Ctrl+Alt+F1F12Switch virtual terminal — works even while locked
Ctrl+Alt+EscapeLock the session — works whatever holds the keyboard
Power buttonRuns power_management.on_power_button — see Power Management

Ctrl+Alt+Escape and the power button are read from raw hardware key codes, so they work regardless of your keyboard layout and regardless of which client has grabbed the keyboard — including a fullscreen game or a lock screen.

If you are testing something over a remote session, note that Logo+Q and Ctrl+Alt+Backspace will kill the compositor instantly. Avoid them.

While the session is locked, all configured shortcuts are inactive: the locker owns the keyboard. Only the always-on keys above still work.

The shipped default set

For reference, this is what /etc/otto/config.toml binds:

[keyboard_shortcuts]
"Ctrl+Return"               = { open_default = { role = "terminal", fallback = "xdg-terminal-exec" } }
"Ctrl+Space"                = { run = { cmd = "otto-launcher", args = [] } }
"Ctrl+Shift+P"              = { run = { cmd = "otto-launcher", args = ["--windows"] } }

"Ctrl+1"                    = { builtin = "Workspace", index = 0 }
"Ctrl+2"                    = { builtin = "Workspace", index = 1 }
"Ctrl+3"                    = { builtin = "Workspace", index = 2 }
"Ctrl+4"                    = { builtin = "Workspace", index = 3 }

"Ctrl+Tab"                  = "ApplicationSwitchNext"
"Ctrl+Shift+ISO_Left_Tab"   = "ApplicationSwitchPrev"
"Ctrl+grave"                = "ApplicationSwitchNextWindow"
"Ctrl+q"                    = "ApplicationSwitchQuit"

"Ctrl+ArrowUp"              = "ToggleMaximizeWindow"
"Ctrl+ArrowLeft"            = "TileWindowLeft"
"Ctrl+ArrowRight"           = "TileWindowRight"

"Prior"                     = "ExposeShowAll"       # Page Up
"Next"                      = "ExposeShowDesktop"   # Page Down

"XF86MonBrightnessUp"       = "BrightnessUp"
"XF86MonBrightnessDown"     = "BrightnessDown"
"XF86AudioRaiseVolume"      = "VolumeUp"
"XF86AudioLowerVolume"      = "VolumeDown"
"XF86AudioMute"             = "VolumeMute"
"XF86AudioPlay"             = "MediaPlayPause"
"XF86AudioNext"             = "MediaNext"
"XF86AudioPrev"             = "MediaPrev"
"XF86AudioStop"             = "MediaStop"

Quitting the session is not in the table: Logo+Q and Ctrl+Alt+Backspace are always on (see above). Note that Ctrl+Q quits the highlighted app in the switcher, which is easy to hit by accident; rebinding it is a reasonable first customization.

Shortcuts and applications

Clients can request a keyboard-shortcuts inhibitor (via keyboard-shortcuts-inhibit), which suspends Otto’s bindings while that window is focused. Remote-desktop clients, virtual machines and terminal multiplexers use this so that, for example, Ctrl+Tab reaches the guest instead of Otto. The always-on keys above are never inhibited.

Inside Otto’s own applications

Settings, Files and the launcher can be driven entirely from the keyboard — Tab between controls, Space or Enter to operate one, arrows for sliders and lists, Esc to close a pop-up. None of it is configurable here: these keys belong to the application, not to the compositor, and they are the same in every Otto application. The table is in Accessibility, which is also where to look if you use a screen reader.

See also