Input Devices
Keyboard, cursor, and hardware-switch configuration
sweets.keyboard
sweets.keyboard({
numlock = true,
repeat_rate = 25,
repeat_delay = 600,
layout = "us,lv",
variant = ",apostrophe",
options = "grp:alt_shift_toggle,caps:escape",
model = "pc105",
rules = "evdev",
})| Field | Type | Default | Description |
|---|---|---|---|
numlock | boolean | true | Turn Num Lock on for each keyboard as it connects |
repeat_rate | integer | 25 | Repeats per second, 0–100; 0 disables repeat |
repeat_delay | integer | 200 | Milliseconds before repeat starts, 0–5000 |
layout | string | XKB default | Layout, or a list such as us,lv |
variant | string | XKB default | Variant; leave an entry empty for none, as in ,apostrophe |
options | string | XKB default | Comma-separated XKB options; "" clears them |
model | string | XKB default | Usually pc105 |
rules | string | XKB default | Usually evdev |
track_layout | "global" or "window" | "global" | Remember the active layout for each window |
For a faster backspace, try repeat_rate = 50 with repeat_delay = 250.
Omitted XKB fields keep the system default, normally from XKB_DEFAULT_*.
Sweets compiles the keymap before accepting a reload, so a bad layout keeps the
active configuration. Virtual keyboards keep their own keymap.
Switching layout
Bind keyboard_layout to switch between the layouts in layout. The argument
is next, prev, or a position in the list starting at 1.
sweets.keyboard({ layout = "us,lv", variant = ",apostrophe" })
sweets.bind("MOD+space", "keyboard_layout", "next")
sweets.bind("MOD+F1", "keyboard_layout", 1)
sweets.bind("MOD+F2", "keyboard_layout", 2)Your other bindings keep working in every layout. MOD+D still reaches its
action while a non-Latin layout is active.
XKB compiles at most four layouts and drops any after the fourth, so an index
above 4 is rejected.
To keep separate layouts in different windows, set track_layout = "window":
sweets.keyboard({ layout = "us,lv", track_layout = "window" })
sweets.bind("MOD+space", "keyboard_layout", "next")New windows start in the first layout. Focusing a window restores its last
layout, including changes made by an XKB group-switch option. The default,
"global", keeps the active layout across focus changes. Layer surfaces and
the lock screen use the current layout without their own saved layout.
Changing track_layout on reload resets saved layouts; a keymap with fewer
groups clamps a saved group when its window next receives focus.
A keyboard_layout binding does not work on the lock screen. Use an XKB
option such as options = "grp:alt_shift_toggle" if you need to change
layout while locked.
sweets.native
-- Optional native DRM workarounds; both default to false.
sweets.native({ disable_cursor_plane = true, disable_direct_scanout = true })| Field | Type | Default | Description |
|---|---|---|---|
disable_cursor_plane | boolean | false | On native DRM, compose the cursor instead of using a hardware cursor plane |
disable_direct_scanout | boolean | false | On native DRM, compose fullscreen windows instead of scanning them out directly |
This can work around a missing, corrupted, or incorrectly scaled hardware cursor. It applies on reload and forces a fresh frame so an assigned cursor plane is removed. Session lock still disables cursor planes independently. Nested and headless backends keep their normal composited cursors. A visible software cursor can prevent fullscreen direct scanout because it must be drawn over the window. Capture requests keep their own cursor setting.
Use disable_direct_scanout when fullscreen direct scanout fails or to compare
it with ordinary composition. It applies on reload and forces a fresh frame.
The cursor-plane setting is independent. Turning direct scanout back on only
restores eligibility; the scene and driver still decide if it is used.
sweets.cursor
sweets.cursor({
size = 24,
theme = "Adwaita",
hide_on_key_press = false,
inactive_timeout = 0,
})| Field | Type | Default | Description |
|---|---|---|---|
size | integer | 24 | Cursor size in logical pixels, 1–256 |
theme | string | backend | Installed xcursor theme name |
hide_on_key_press | boolean | false | Hide the cursor while you type until you use the mouse |
inactive_timeout | integer | 0 | Seconds without mouse input before hiding; 0 never hides |
All apply on reload. An unknown theme falls back to whatever xcursor resolves.
hide_on_key_press
Set this to keep the pointer out of the way while you type. Any key press on a real keyboard hides the cursor. Moving the mouse, clicking, or scrolling brings it back.
Typing does not move the pointer, so clicking where it was hidden still works.
Keys sent by a virtual keyboard, such as an on-screen keyboard or a remote-control tool, do not hide the cursor.
inactive_timeout
Set this to a number of seconds to hide a cursor you have left alone. Moving the mouse, clicking, or scrolling brings it back and restarts the countdown.
Typing does not count, so the cursor still disappears while you write even
without hide_on_key_press. Use hide_on_key_press instead when you want it
gone the moment you start typing.
Keys and pointer events sent by virtual devices do not restart the countdown.
pointer.hide_when_following hides the cursor for a third reason, after
Sweets moves it for you. All three are independent, and turning one off
leaves a cursor hidden by another alone.
How clients pick this up
Modern clients ask the compositor for a named cursor shape through
cursor-shape-v1. Sweets draws those from your theme at the right size and
per-monitor scale, so they always match and update on reload.
Clients that draw their own cursor read XCURSOR_SIZE and XCURSOR_THEME.
Sweets exports both, but they are read at startup, so only newly launched
programs see a change.
Some GTK, Electron, and Firefox builds read GSettings instead. Set the same values there if an application ignores both:
gsettings set org.gnome.desktop.interface cursor-theme 'breeze_cursors'
gsettings set org.gnome.desktop.interface cursor-size 24Sweets does not write GSettings itself. A client drawing its own cursor on a
fractionally scaled monitor may not scale XCURSOR_SIZE, so its cursor can
look small. cursor-shape-v1 clients never have this problem.
sweets.bind_switch
Hardware switches do nothing by default. Bind a command to a transition:
sweets.bind_switch("lid:closed", "spawn", { "veila", "lock" })
sweets.bind_switch("lid:open", "spawn", { "notify-send", "Lid opened" })
sweets.bind_switch("tablet_mode:on", "spawn", { "wvkbd-mobintl" })
sweets.bind_switch("tablet_mode:off", "spawn", { "pkill", "wvkbd-mobintl" })| Selector | Trigger |
|---|---|
lid:closed / lid:open | The laptop lid closes or opens |
tablet_mode:on / tablet_mode:off | The tablet-mode switch changes |
Only spawn is accepted, using the same no-shell argument array as a key
binding. Several bindings may match one event and run in declaration order.
They keep working while the session is locked, so a lid policy still applies.
The state at startup does not run a command, and repeated reports are ignored.
A lid opened during suspend still runs lid:open when the laptop wakes. A reload affects later
transitions only. sweets msg inputs shows the switches
Sweets can see. The nested backend has no physical switches.
Sweets never locks, suspends, or powers off displays on its own. Use one locker or policy command so locking is established before the display changes.
Touchscreen, tablet tool, and tablet pad routing are not implemented yet.