Skip to main content

Input devices

COMPOSITOR.input configures keyboards, pointers (mice), and touchpads. As with outputs, you register a factory that the compositor calls whenever the set of input devices changes; you mutate the draft it passes in.

COMPOSITOR.input.configure((input, context) => {
// Global defaults applied to all matching devices
input.global = {
touchpad: {
tapToClick: true,
naturalScroll: true,
scrollMethod: 'twoFinger',
disableWhileTyping: true,
scrollFactor: 0.3,
},
pointer: {
pointerAccel: 0.0,
accelProfile: 'flat',
},
keyboard: {
options: 'caps:ctrl_modifier',
repeatRate: 60,
repeatDelay: 250,
},
};

// Per-device override, keyed by device name
input.device['Razer Razer Blade Keyboard'] = {
keyboard: {layout: 'us'},
};
});

input.global applies to every device of that kind. input.device[name] overrides settings for one specific device (set it to null to clear an override). Each value is an InputDeviceConfig with optional keyboard, pointer, and touchpad sub-objects.

Keyboard settings​

keyboard — an InputDeviceConfig.keyboard object. The rules/model/ layout/variant/options fields are standard XKB settings.

FieldTypeMeaning
layoutstringXKB layout, e.g. "us", "jp", "de"
variantstringLayout variant, e.g. "dvorak"
optionsstringXKB options, e.g. "caps:ctrl_modifier", "ctrl:nocaps"
rulesstringXKB rules set
modelstringKeyboard model
repeatRatenumberKey repeats per second
repeatDelaynumberDelay (ms) before key repeat starts
input.global = {
keyboard: {layout: 'us', options: 'caps:ctrl_modifier', repeatRate: 60, repeatDelay: 250},
};

Reading the active keyboard layout​

Subscribe to the seat's actual XKB layout from TypeScript:

const unsubscribe = COMPOSITOR.event.onKeyboardLayoutChange((event) => {
console.log(event.index, event.name); // 0, "English (US)"
// Write your panel's JSON status or broadcast through your IPC server here.
});

index is the zero-based XKB layout index and name is its XKB display name. The event contains no key events or typed text. Register during config loading or onEnable to receive the initial layout on the first scheduler tick after enable, including after a config reload. Later ticks report changes to either field, including keymap changes; unchanged layouts emit nothing. Multiple changes before a tick are coalesced to the latest layout. The scheduler samples the seat at its existing cadence (up to 250 ms when idle).

Registration returns an unsubscribe function. The listener is synchronous; keep it short and handle errors from your chosen JSON/IPC transport. The compositor does not choose a transport or write a status file. Configurations using the earlier draft's shojiwm-$WAYLAND_DISPLAY-keyboard.json must now publish that file from TypeScript or migrate their panel to IPC.

Pointer (mouse) settings​

pointer — an InputDeviceConfig.pointer object.

FieldTypeMeaning
pointerAccelnumberAcceleration speed, -1.0…1.0
accelProfile"adaptive" | "flat"Acceleration curve ("flat" = 1:1, no accel)
naturalScrollbooleanReverse scroll direction
leftHandedbooleanSwap left/right buttons
middleEmulationbooleanEmulate middle click via left+right
input.global = {pointer: {accelProfile: 'flat', pointerAccel: 0.0}};

Touchpad settings​

touchpad — a TouchpadInputConfig. It extends the pointer settings above, so every pointer field is also valid here, plus:

FieldTypeMeaning
tapToClickbooleanTap the pad to click
tapButtonMap"leftRightMiddle" | "leftMiddleRight"Multi-finger tap → button mapping
clickMethod"buttonAreas" | "clickfinger"How physical clicks map to buttons
scrollMethod"none" | "twoFinger" | "edge" | "onButtonDown"How scrolling is triggered
scrollFactornumberScroll speed multiplier
disableWhileTypingbooleanIgnore the pad while typing

…plus all pointer fields (pointerAccel, accelProfile, naturalScroll, leftHanded, middleEmulation).

input.global = {
touchpad: {
tapToClick: true,
naturalScroll: true,
scrollMethod: 'twoFinger',
scrollFactor: 0.3,
disableWhileTyping: true,
},
};

Reading and targeting devices​

The factory's second argument (and the controller itself) exposes the connected devices, so you can apply settings conditionally by kind.

MemberReturns
devicesInputDeviceInfo[]
currentRecord<string, InputDeviceInfo>
get(key)InputDeviceInfo | undefined
find(predicate)first matching device
configure(factory)register a config factory
reconfigure()re-run all factories now

Each InputDeviceInfo has key, name, optional vendor/product, and a kind flag object (keyboard, pointer, touchpad, touch, tabletTool, tabletPad, gesture, switch).

COMPOSITOR.input.configure((input, ctx) => {
for (const device of ctx.devices) {
if (device.kind.touchpad) {
input.device[device.key] = {touchpad: {scrollMethod: 'twoFinger'}};
}
}
});