Skip to content

Add Linux keyboard support via evdev#9

Open
killerra wants to merge 5 commits into
memflow:mainfrom
killerra:linux-keyboard
Open

Add Linux keyboard support via evdev#9
killerra wants to merge 5 commits into
memflow:mainfrom
killerra:linux-keyboard

Conversation

@killerra

@killerra killerra commented Jun 11, 2026

Copy link
Copy Markdown

Stacked on #8 — review only the last two commits (Add Linux keyboard support via evdev, Implement Keyboard::set_down on Linux and Windows); the earlier commits belong to #8 and this branch will be rebased once it merges.

What

Implements OsKeyboard for LinuxOs, bringing the Linux backend to feature parity with the Windows backend's keyboard support — and implements the previously-stubbed set_down (input injection) on both platforms:

  • LinuxKeyboard / LinuxKeyboardState poll the pressed-key bitmap via the EVIOCGKEY ioctl (evdev's get_key_state) on /dev/input/event* — the direct analog of GetKeyboardState, reading state without consuming input events.
  • is_down() accepts Microsoft virtual-key codes, same as the Windows backend, translated through a static VK → evdev table (US layout), so cross-platform consumers work unchanged. Unmapped VKs cleanly report not-pressed.
    • Side-agnostic modifiers (VK_SHIFT/VK_CONTROL/VK_MENU) check both left and right keys; VK_RETURN covers main and keypad enter.
    • Mouse buttons are supported like GetKeyState does, including mouse4/mouse5 in both evdev report styles (BTN_SIDE/BTN_EXTRA and BTN_BACK/BTN_FORWARD).
  • State is ORed across all keyboard-capable devices (filtered by a representative key set, so power buttons/lid switches are excluded; pointer devices are admitted for the mouse-button VKs). Unplugged devices are dropped on the fly with a one-shot re-enumeration when none remain.

set_down (key/button injection)

  • Linux: writes an EV_KEY + SYN_REPORT pair to the first open device that advertises the key (the kernel drops injected events a device doesn't support), via evdev's send_events — no /dev/uinput needed, the same device access as reading suffices. Side-agnostic VKs press their left-side variant.
  • Windows: SendInput — keyboard keys as KEYBDINPUT with the scancode filled in via MapVirtualKeyW (raw-input/DirectInput consumers see the key too) and KEYEVENTF_EXTENDEDKEY applied to the extended-range set; mouse-button VKs as MOUSEINPUT with the matching MOUSEEVENTF flags, including X buttons.
  • The trait's set_down has no error channel, so failures are logged; unmapped VKs are ignored cleanly, matching is_down.

Permissions

Reading /dev/input/event* requires root or membership in the input group (udev default: root:input 0660); injection additionally needs write access, which the same group membership grants. With no readable device, keyboard() returns a descriptive NotFound error stating exactly that.

Testing

  • Unit tests for the VK→evdev table and the state bitset (cargo test --all).
  • cargo build with --all-features and --no-default-features, cargo fmt --check, cargo clippy --all-targets --all-features all clean.
  • Windows side type-checked via cargo check --target x86_64-pc-windows-gnu (and clippy; the only warnings on that target are pre-existing on main). Runtime testing on a Windows machine is still outstanding — review of the SendInput path appreciated.
  • Note for CI: runners have no input devices, so no non-ignored test touches /dev/input.

killerra added 5 commits June 10, 2026 19:03
- Implement Process::state() via /proc/<pid>/stat: zombie/dead states map
  to ProcessState::Dead (with exit_code when readable), a vanished PID
  maps to Dead, and permission errors stay Unknown.
- Use the module base address as the opaque module handle instead of an
  index into a freshly parsed maps list, eliminating the race between
  module_address_list_callback and module_by_address.
- Resolve primary_module_address() through /proc/<pid>/exe instead of
  assuming the 0th mapping, falling back to the first mapping.
- Report real in-process addresses for environment variables using
  env_start from /proc/<pid>/stat, and return the env block address from
  environment_block_address() (plugin API 2).
After a partial process_vm_readv/writev, the retry syscall resumes at
iov offset `offset`, but the result-dispatch loop iterated the local/
remote iovecs and temp_meta from index 0. On every pass after the first,
already-reported entries were re-reported with the wrong metadata and
local slices, the entries the retry actually transferred were never
reported, byte accounting used the wrong iov_lens, and out_fail flagged
the wrong element. This corrupted result attribution for any batched
read/write spanning an unmapped hole.

Align dispatch and accounting with the syscall window by skipping the
first `win` entries on all three iterators. Add a regression test that
batches [valid, unmapped, valid] reads against our own PID; it fails
against the previous code (first region duplicated, third dropped).
Follow-ups from the Linux backend soundness audit:

- Decode the waitpid(2)-style status word from /proc/<pid>/stat into a
  real exit code in Process::state(): normal exits report the exit(3)
  code, signal deaths report the negated signal number. Previously the
  raw status word leaked through (exit(3) surfaced as Dead(768)).
- Derive OsInfo.arch and ProcessInfo::{sys_arch,proc_arch} from the
  compile target (x86_64/x86/aarch64) instead of hardcoding x86-64, and
  make the process module list callback emit the same arch field that
  module_by_address keys on.
- Replace OS kernel-module handles (indices into a name-sorted snapshot
  that shift on module load/unload) with a stable hash of the module
  name (std DefaultHasher, fixed-seeded), resolved against the live
  snapshot.
- Report the real process state in process_info_by_pid via the shared
  process_state() helper instead of hardcoding Alive.
Implement OsKeyboard for LinuxOs with LinuxKeyboard/LinuxKeyboardState,
polling key state through the EVIOCGKEY ioctl (evdev's get_key_state) on
/dev/input/event* devices - the direct analog of GetKeyboardState on
Windows. Requires root or membership in the 'input' group.

is_down() accepts Microsoft virtual-key codes like the Windows backend,
translated through a static VK -> evdev table (US layout). Side-agnostic
modifiers check both sides, VK_RETURN covers both enter keys, and mouse
buttons cover both side-button report styles (BTN_SIDE/BTN_EXTRA and
BTN_BACK/BTN_FORWARD). Unmapped VKs report not-pressed, matching Windows.

Key state is ORed across all keyboard-capable devices; unplugged devices
are dropped on the fly with a one-shot re-enumeration when none remain.
Linux injects an EV_KEY + SYN_REPORT pair into the first open device
that advertises the key (the kernel drops events a device does not
support), using evdev's send_events on the already-open device nodes.
Side-agnostic virtual-key codes press their left-side variant. Injection
needs write access to /dev/input, which the root/'input'-group
requirement already grants.

Windows uses SendInput: keyboard keys as KEYBDINPUT with the scancode
filled in via MapVirtualKeyW (so raw-input/DirectInput consumers see the
key) and KEYEVENTF_EXTENDEDKEY applied to the extended-range set; mouse
button VKs as MOUSEINPUT with the matching MOUSEEVENTF flags, including
X buttons.

The trait's set_down returns nothing, so failures are logged instead of
propagated. Unmapped or out-of-range keycodes are ignored cleanly,
matching is_down.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant