Pointing Processor
Use PointingProcessor to turn sensor movement into mouse reports or arrow key taps. Choose one of the four modes below, then switch modes when a layer or key changes.
Modes
Choose the mode for the input you want to send to the host:
Cursor
Maps X/Y deltas directly to mouse cursor movement with optional scaling.
rmk::input_device::pointing::CursorConfig {
multiplier_x: 1, // scales X delta. 0 disables X.
multiplier_y: 1, // scales Y delta. 0 disables Y.
divisor_x: 1, // divides X motion. 0 disables X.
divisor_y: 1, // divides Y motion. 0 disables Y.
invert_x: false,
invert_y: false,
}
Each sample produces output = delta * multiplier / divisor, keeping the remainder for the next sample as scroll mode does, so a cursor slower than the sensor, such as multiplier: 2, divisor: 3, loses no movement. Set an axis multiplier or divisor to 0 to disable that axis.
Cursor mode can also accelerate: PointingProcessorConfig::acceleration, or acceleration in keyboard.toml, scales up motion faster than a threshold before the multiplier and divisor apply, so a slow, precise cursor still crosses the screen with a quick move.
Maps X to horizontal pan and Y to vertical wheel scrolling.
use rmk::input_device::pointing::{ScrollAxis, ScrollConfig};
ScrollConfig {
wheel: ScrollAxis {
multiplier: 1,
divisor: 8,
invert: false,
},
pan: ScrollAxis {
multiplier: 1,
divisor: 8,
invert: false,
},
}
Set an output's multiplier or divisor to 0 to disable it. By default, positive Y produces a negative wheel delta, and positive X produces a positive pan delta. Set invert to true to reverse an output.
Scroll mode keeps motion between samples. It computes total = remainder + delta * multiplier, emits total / divisor, and keeps the remainder for the next sample. A larger divisor requires more movement for each scroll step:
Sniper
Maps X/Y to cursor at reduced sensitivity for precision aiming.
rmk::input_device::pointing::SniperConfig {
multiplier: 1, // scales delta before divisor (same for both axes)
divisor: 4, // divides motion. 0 disables both axes.
invert_x: false,
invert_y: false,
}
Like Scroll mode, Sniper mode keeps fractional movement between samples. Increase divisor to reduce cursor sensitivity.
Caret
Maps X/Y to arrow key taps (up/down/left/right). No mouse report is generated.
use rmk::types::keycode::HidKeyCode;
rmk::input_device::pointing::CaretConfig {
disable_x: false, // disable horizontal caret taps
disable_y: false, // disable vertical caret taps
invert_x: false,
invert_y: false,
threshold: 100, // minimum accumulated motion (|dx|+|dy|) to trigger a tap
keycode_up: HidKeyCode::Up,
keycode_down: HidKeyCode::Down,
keycode_left: HidKeyCode::Left,
keycode_right: HidKeyCode::Right,
}
Caret mode accumulates movement until it exceeds threshold, then sends the configured arrow key taps. Increase the threshold to require more movement before each tap.
Note
Caret mode preserves held keys and modifiers, so holding Shift or Ctrl also modifies the arrow key taps. The configured keycodes bypass keymap, combo, and fork lookup. Direct inputs of the same keycode share press/release state.
Mode switching
To change modes when a layer or key changes, create a controller that receives those events and publishes PointingProcessorEvent. Set the event's device_id to match the sensor's ID. The example below targets device 0; change it if your sensor uses another ID.
Add the following module to your project. It selects Cursor on layer 0, Sniper on layer 1, Scroll on layer 2, and Caret on layer 3. Pressing a key bound to User0 also selects Caret.
Note
Reserve a subscriber slot for ActionEvent in keyboard.toml. Its default subscriber count is zero:
src/pointing_processor_controller.rs
use rmk::event::{ActionEvent, LayerChangeEvent, PointingProcessorEvent, publish_event};
use rmk::input_device::pointing::{PointingMode, ScrollAxis, ScrollConfig};
use rmk::macros::processor;
use rmk::types::action::Action;
use rmk::types::keycode::HidKeyCode;
#[processor(subscribe = [LayerChangeEvent, ActionEvent])]
pub struct PointingProcessorController;
impl PointingProcessorController {
pub fn new() -> Self {
Self
}
async fn on_layer_change_event(&mut self, event: LayerChangeEvent) {
match event.0 {
0 => {
publish_event(PointingProcessorEvent {
device_id: 0,
mode: PointingMode::Cursor(rmk::input_device::pointing::CursorConfig::default()),
});
}
1 => {
publish_event(PointingProcessorEvent {
device_id: 0,
mode: PointingMode::Sniper(rmk::input_device::pointing::SniperConfig {
multiplier: 1, // scales sensor delta before divisor
divisor: 8, // divides motion for precision aiming
invert_x: false,
invert_y: false,
}),
});
}
2 => {
publish_event(PointingProcessorEvent {
device_id: 0,
mode: PointingMode::Scroll(ScrollConfig {
wheel: ScrollAxis {
divisor: 16,
..Default::default()
},
pan: ScrollAxis {
divisor: 16,
..Default::default()
},
}),
});
}
3 => {
publish_event(PointingProcessorEvent {
device_id: 0,
mode: PointingMode::Caret(rmk::input_device::pointing::CaretConfig {
disable_x: false, // disable horizontal caret taps
disable_y: false, // disable vertical caret taps
invert_x: false,
invert_y: false,
threshold: 100, // minimum accumulated motion (|dx|+|dy|) to trigger a tap
keycode_up: HidKeyCode::Up,
keycode_down: HidKeyCode::Down,
keycode_left: HidKeyCode::Left,
keycode_right: HidKeyCode::Right,
}),
});
}
_ => {}
}
}
async fn on_action_event(&mut self, event: ActionEvent) {
if event.keyboard_event.pressed && event.action == Action::User(0) {
publish_event(PointingProcessorEvent {
device_id: 0,
mode: PointingMode::Caret(rmk::input_device::pointing::CaretConfig::default()),
});
}
}
}
Run the controller on the central, including when the sensor is connected to a peripheral. For a unibody keyboard, run it on the keyboard itself. Choose the setup that matches your project:
src/main.rs
#![no_main]
#![no_std]
mod pointing_processor_controller;
use pointing_processor_controller::PointingProcessorController;
use rmk::macros::rmk_keyboard;
#[rmk_keyboard]
mod keyboard {
#[register_processor]
fn pointing_processor_controller() -> PointingProcessorController {
PointingProcessorController::new()
}
}
Add the constructor to your existing keyboard module. For a split keyboard, use the central's #[rmk_central] module in src/central.rs instead. See running a processor.
Declare the module and import the controller in src/main.rs or src/central.rs:
mod pointing_processor_controller;
use pointing_processor_controller::PointingProcessorController;
use rmk::run_all;
After initializing your keyboard and transport, add the controller to the existing task list. Here, keyboard and transport are your firmware's existing instances:
let mut pointing_controller = PointingProcessorController::new();
run_all!(pointing_controller, keyboard, transport).await;