From 775e33f022111bb008bc3ea18a40e6482b69a60f Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 02:34:17 -0700 Subject: [PATCH 01/42] feat(hidpp): add ForceSensingButton and EnableHiddenFeatures wrappers 0x19c0 ForceSensingButton is the MX Master 4 Action Ring pad; its function map is undocumented, so the wrapper exposes a raw probe surface plus the one call confirmed on hardware: setForceThreshold (fn 3), the write that arms the dormant pad. 0x1e00 EnableHiddenFeatures gates hidden-feature access. Device::raw_feature_call sends one short-form call to any feature by ID for reverse-engineering diagnostics. --- crates/openlogi-hidpp/src/device.rs | 22 ++++++- .../src/feature/enable_hidden_features/mod.rs | 52 +++++++++++++++ .../src/feature/force_sensing_button/mod.rs | 64 +++++++++++++++++++ crates/openlogi-hidpp/src/feature/mod.rs | 2 + crates/openlogi-hidpp/src/feature/registry.rs | 5 +- 5 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 crates/openlogi-hidpp/src/feature/enable_hidden_features/mod.rs create mode 100644 crates/openlogi-hidpp/src/feature/force_sensing_button/mod.rs diff --git a/crates/openlogi-hidpp/src/device.rs b/crates/openlogi-hidpp/src/device.rs index 877ae54f..a8e4e242 100755 --- a/crates/openlogi-hidpp/src/device.rs +++ b/crates/openlogi-hidpp/src/device.rs @@ -7,7 +7,7 @@ use thiserror::Error; use crate::{ channel::{ChannelError, HidppChannel}, feature::{ - self, CreatableFeature, Feature, + self, CreatableFeature, Feature, FeatureEndpoint, feature_set::{FeatureInformation, FeatureSetFeature}, root::RootFeature, }, @@ -121,6 +121,26 @@ impl Device { .and_then(|feat| Arc::downcast::(feat).ok()) } + /// Resolves `feature_id` at runtime and sends one short-form call to it, + /// returning the 16-byte response payload, or `Ok(None)` if the device + /// does not expose the feature. + /// + /// Reverse-engineering aid for features without typed wrappers — the + /// caller owns interpretation of the payload. Typed wrappers remain the + /// path for anything shipping. + pub async fn raw_feature_call( + &self, + feature_id: u16, + function: u8, + args: [u8; 3], + ) -> Result, Hidpp20Error> { + let Some(info) = self.root().get_feature(feature_id).await? else { + return Ok(None); + }; + let endpoint = FeatureEndpoint::new(Arc::clone(&self.chan), self.device_index, info.index); + Ok(Some(endpoint.call(function, args).await?.extend_payload())) + } + /// Tries to detect all features supported by the device and add /// implementations for them using [`feature::registry::lookup_version`]. /// diff --git a/crates/openlogi-hidpp/src/feature/enable_hidden_features/mod.rs b/crates/openlogi-hidpp/src/feature/enable_hidden_features/mod.rs new file mode 100644 index 00000000..553757f7 --- /dev/null +++ b/crates/openlogi-hidpp/src/feature/enable_hidden_features/mod.rs @@ -0,0 +1,52 @@ +//! Implements the `EnableHiddenFeatures` feature (`0x1e00`). +//! +//! A one-byte gate for engineering/optional functionality: some devices keep +//! auxiliary event sources dormant until a host enables this feature. Vendor +//! software (Logi Options+) is believed to enable it at session start; the +//! function layout below (get = function 0, set = function 1, enabled flag in +//! byte 0) matches the de-facto layout used by Solaar and libratbag. + +use std::sync::Arc; + +use crate::{ + channel::HidppChannel, + feature::{CreatableFeature, Feature, FeatureEndpoint}, + protocol::v20::Hidpp20Error, +}; + +/// Implements `EnableHiddenFeatures` / `0x1e00`. +#[derive(Clone)] +pub struct EnableHiddenFeaturesFeature { + /// The endpoint this feature talks to. + endpoint: FeatureEndpoint, +} + +impl CreatableFeature for EnableHiddenFeaturesFeature { + const ID: u16 = 0x1e00; + const STARTING_VERSION: u8 = 0; + + fn new(chan: Arc, device_index: u8, feature_index: u8) -> Self { + Self { + endpoint: FeatureEndpoint::new(chan, device_index, feature_index), + } + } +} + +impl Feature for EnableHiddenFeaturesFeature {} + +impl EnableHiddenFeaturesFeature { + /// Reads whether hidden features are currently enabled (`getEnabled`, + /// function `0`). + pub async fn get_enabled(&self) -> Result { + let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload(); + Ok(payload[0] != 0) + } + + /// Enables or disables hidden features (`setEnabled`, function `1`). + /// + /// The device does not persist this across power cycles. + pub async fn set_enabled(&self, enabled: bool) -> Result<(), Hidpp20Error> { + self.endpoint.call(1, [u8::from(enabled), 0, 0]).await?; + Ok(()) + } +} diff --git a/crates/openlogi-hidpp/src/feature/force_sensing_button/mod.rs b/crates/openlogi-hidpp/src/feature/force_sensing_button/mod.rs new file mode 100644 index 00000000..829e45e2 --- /dev/null +++ b/crates/openlogi-hidpp/src/feature/force_sensing_button/mod.rs @@ -0,0 +1,64 @@ +//! Implements the `ForceSensingButton` feature (`0x19c0`) — the +//! force-sensitive pad first seen as the MX Master 4's Action Ring button. +//! +//! The function map of this feature is NOT publicly documented. Everything +//! here is reverse-engineered against real hardware; this wrapper therefore +//! exposes a deliberately raw probe surface so callers can map the function +//! table, and typed accessors should replace [`Self::raw_call`] as layouts are +//! confirmed on hardware. Keep the reverse-engineering annotations honest. + +use std::sync::Arc; + +use crate::{ + channel::HidppChannel, + feature::{CreatableFeature, Feature, FeatureEndpoint}, + protocol::v20::Hidpp20Error, +}; + +/// Implements `ForceSensingButton` / `0x19c0`. +#[derive(Clone)] +pub struct ForceSensingButtonFeature { + /// The endpoint this feature talks to. + endpoint: FeatureEndpoint, +} + +impl CreatableFeature for ForceSensingButtonFeature { + const ID: u16 = 0x19c0; + const STARTING_VERSION: u8 = 0; + + fn new(chan: Arc, device_index: u8, feature_index: u8) -> Self { + Self { + endpoint: FeatureEndpoint::new(chan, device_index, feature_index), + } + } +} + +impl Feature for ForceSensingButtonFeature {} + +impl ForceSensingButtonFeature { + /// Raw short-form call: sends `function` with three argument bytes and + /// returns the 16-byte response payload verbatim. + /// + /// Reverse-engineering aid — the caller owns interpretation. A device + /// rejecting an unknown function returns the HID++ 2.0 `InvalidFunction` + /// error rather than garbage, so probing function IDs is safe. + pub async fn raw_call(&self, function: u8, args: [u8; 3]) -> Result<[u8; 16], Hidpp20Error> { + Ok(self.endpoint.call(function, args).await?.extend_payload()) + } + + /// Sets `button`'s force-activation threshold (function `3`). + /// + /// Reverse-engineered on a real MX Master 4 (`wpid=b042`): the pad is + /// dormant until a threshold is written, and Options+ writes `0x15a3` for + /// button `0` at startup — the call that arms the Action Ring pad. + /// Function `2` with the same button index reads the value back. + pub async fn set_force_threshold( + &self, + button: u8, + threshold: u16, + ) -> Result<(), Hidpp20Error> { + let [hi, lo] = threshold.to_be_bytes(); + self.endpoint.call(3, [button, hi, lo]).await?; + Ok(()) + } +} diff --git a/crates/openlogi-hidpp/src/feature/mod.rs b/crates/openlogi-hidpp/src/feature/mod.rs index ec39e990..fbf6ed16 100755 --- a/crates/openlogi-hidpp/src/feature/mod.rs +++ b/crates/openlogi-hidpp/src/feature/mod.rs @@ -20,11 +20,13 @@ pub mod device_type_and_name; pub mod disable_keys; pub mod disable_keys_by_usage; pub mod dual_platform; +pub mod enable_hidden_features; pub mod equalizer; pub mod extended_dpi; pub mod extended_report_rate; pub mod feature_set; pub mod fn_inversion; +pub mod force_sensing_button; pub mod hires_wheel; pub mod hosts_info; pub mod illumination; diff --git a/crates/openlogi-hidpp/src/feature/registry.rs b/crates/openlogi-hidpp/src/feature/registry.rs index 83897373..249016f5 100755 --- a/crates/openlogi-hidpp/src/feature/registry.rs +++ b/crates/openlogi-hidpp/src/feature/registry.rs @@ -24,11 +24,13 @@ use crate::{ disable_keys::DisableKeysFeature, disable_keys_by_usage::DisableKeysByUsageFeature, dual_platform::DualPlatformFeature, + enable_hidden_features::EnableHiddenFeaturesFeature, equalizer::EqualizerFeature, extended_dpi::ExtendedDpiFeature, extended_report_rate::ExtendedReportRateFeature, feature_set::FeatureSetFeature, fn_inversion::{FnInversionMultiHostFeature, FnInversionWithDefaultStateFeature}, + force_sensing_button::ForceSensingButtonFeature, hires_wheel::HiResWheelFeature, hosts_info::HostsInfoFeature, illumination::IlluminationFeature, @@ -169,7 +171,7 @@ static KNOWN_FEATURES: LazyLock> = LazyLock::new(|| { 0x1983 "Backlight3", 0x1990 "Illumination" => IlluminationFeature, 0x19b0 "HapticFeedback", - 0x19c0 "ForceSensingButton", + 0x19c0 "ForceSensingButton" => ForceSensingButtonFeature, 0x1a00 "PresenterControl", 0x1a01 "Sensor3D", 0x1b00 "ReprogControls", @@ -181,6 +183,7 @@ static KNOWN_FEATURES: LazyLock> = LazyLock::new(|| { 0x1c00 "PersistentRemappableAction" => PersistentRemappableActionFeature, 0x1d4b "WirelessDeviceStatus" => WirelessDeviceStatusFeature, 0x1df0 "RemainingPairings", + 0x1e00 "EnableHiddenFeatures" => EnableHiddenFeaturesFeature, 0x1f1f "FirmwareProperties", 0x1f20 "AdcMeasurement", 0x2001 "SwapLeftRightButton", From 17a12fb4e701ab1835563bb5a8259057bdf81eaa Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 02:34:26 -0700 Subject: [PATCH 02/42] fix(hid): declare Win32_System_IO for WriteFile OVERLAPPED windows_hid.rs calls WriteFile, whose signature references OVERLAPPED from Win32_System_IO; the build only worked through feature unification with sibling crates. Declare what the crate itself uses. --- crates/openlogi-hid/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/openlogi-hid/Cargo.toml b/crates/openlogi-hid/Cargo.toml index 43251c44..d501066e 100644 --- a/crates/openlogi-hid/Cargo.toml +++ b/crates/openlogi-hid/Cargo.toml @@ -34,6 +34,9 @@ windows-sys = { workspace = true, features = [ # exposed when Win32_Security is also enabled. "Win32_Security", "Win32_Storage_FileSystem", + # WriteFile's signature references OVERLAPPED, so it is only exposed when + # Win32_System_IO is also enabled. + "Win32_System_IO", ] } [lints] From e1703719bdcc3a2d44532e3754898d5f16addb8b Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 02:34:27 -0700 Subject: [PATCH 03/42] feat(hid): add Action Ring panel diagnostics module hidden_features.rs carries the route-level diag plumbing proven on real hardware: watch_panel arms the MX Master 4 Action Ring with the Options+ recipe (0x19c0 force threshold + 0x1b04 analyticsKeyEvents reporting on the panel CIDs, re-applied on a cadence for cold BTLE links) and decodes each press/release; plus raw probe helpers for 0x1e00/0x19c0 and arbitrary features. Errors stay in a local type so the IPC wire format is untouched. --- crates/openlogi-hid/src/hidden_features.rs | 266 +++++++++++++++++++++ crates/openlogi-hid/src/lib.rs | 1 + 2 files changed, 267 insertions(+) create mode 100644 crates/openlogi-hid/src/hidden_features.rs diff --git a/crates/openlogi-hid/src/hidden_features.rs b/crates/openlogi-hid/src/hidden_features.rs new file mode 100644 index 00000000..fbedd066 --- /dev/null +++ b/crates/openlogi-hid/src/hidden_features.rs @@ -0,0 +1,266 @@ +//! Route-level diagnostics for `0x1e00 EnableHiddenFeatures` and the +//! `0x19c0 ForceSensingButton` probe surface (MX Master 4 Action Ring panel). +//! +//! Diag-only plumbing for the CLI: errors stay in a local type and never +//! cross the agent↔GUI IPC, so [`crate::write::WriteError`] (wire format) +//! stays untouched. + +use std::future::Future; +use std::sync::Arc; +use std::time::Duration; + +use hidpp::device::Device; +use hidpp::feature::enable_hidden_features::EnableHiddenFeaturesFeature; +use hidpp::feature::force_sensing_button::ForceSensingButtonFeature; +use hidpp::feature::reprog_controls::{ + CidReportingChange, ReprogControlsEvent, decode_event as decode_reprog_event, +}; +use hidpp::protocol::v20; +use thiserror::Error; + +use crate::reprog_controls::{FEATURE_ID as REPROG_FEATURE_ID, ReprogControlsV4}; +use crate::route::{DeviceRoute, open_route_channel}; +use crate::write::open_feature; + +/// Control IDs the MX Master 4 Action Ring panel reports through, confirmed on +/// real hardware 2026-07-20: with analytics reporting enabled the panel emits +/// `0x1b04` `analyticsKeyEvents` (`event` `0x01` = press, `0x00` = release). +/// +/// **`0x01a0` is the Action Ring pad itself** — it is the only control in the +/// device's `0x1b04` table advertising `analytics-events`, and it carries the +/// tap. `0x0050`/`0x0051` are companion controls Options+ also arms (observed +/// firing on firm/held presses). `0x00d7` ("Virtual Gesture Button", +/// `force-raw-xy`) is NOT involved — diverting it only steals the sensor +/// stream and freezes the cursor. +pub const PANEL_ANALYTICS_CIDS: [u16; 3] = [0x01a0, 0x0050, 0x0051]; + +/// Hard wall-clock budget for one whole diagnostic (open + calls). A cold +/// BTLE link can swallow a request without ever answering, and the underlying +/// channel read has no timeout of its own — without this bound a single call +/// can hang a diagnostic process forever (observed on real hardware). +const DIAG_BUDGET: Duration = Duration::from_secs(8); + +async fn bounded( + fut: impl Future>, +) -> Result { + tokio::time::timeout(DIAG_BUDGET, fut) + .await + .map_err(|_| HiddenDiagError::Hidpp("call timed out (link cold?)".into()))? +} + +/// Failure modes for the hidden-features / force-button diagnostics. +#[derive(Debug, Error)] +pub enum HiddenDiagError { + /// No HID node matched the route. + #[error("no connected device matched the route")] + DeviceNotFound, + /// Transport-level failure opening the route. + #[error("HID transport error: {0}")] + Hid(String), + /// The node opened but the HID++ device index did not answer. + #[error("device at index {index:#04x} did not respond to HID++")] + DeviceUnreachable { + /// HID++ device index that failed to answer. + index: u8, + }, + /// A feature lookup or call failed. + #[error("HID++ error: {0}")] + Hidpp(String), +} + +async fn open_device(route: &DeviceRoute) -> Result { + let chan = open_route_channel(route) + .await + .map_err(|e| HiddenDiagError::Hid(format!("{e:?}")))? + .ok_or(HiddenDiagError::DeviceNotFound)?; + let index = route.device_index(); + Device::new(Arc::clone(&chan), index) + .await + .map_err(|_| HiddenDiagError::DeviceUnreachable { index }) +} + +/// Reads the current `0x1e00` enabled state. +pub async fn hidden_features_enabled(route: &DeviceRoute) -> Result { + bounded(async { + let mut device = open_device(route).await?; + let feature = open_feature::(&mut device) + .await + .map_err(|e| HiddenDiagError::Hidpp(e.to_string()))?; + feature + .get_enabled() + .await + .map_err(|e| HiddenDiagError::Hidpp(format!("{e:?}"))) + }) + .await +} + +/// Writes the `0x1e00` enabled state and returns the read-back value. +pub async fn set_hidden_features_enabled( + route: &DeviceRoute, + enabled: bool, +) -> Result { + bounded(async { + let mut device = open_device(route).await?; + let feature = open_feature::(&mut device) + .await + .map_err(|e| HiddenDiagError::Hidpp(e.to_string()))?; + feature + .set_enabled(enabled) + .await + .map_err(|e| HiddenDiagError::Hidpp(format!("{e:?}")))?; + feature + .get_enabled() + .await + .map_err(|e| HiddenDiagError::Hidpp(format!("{e:?}"))) + }) + .await +} + +/// Arm the Action Ring panel with the Options+ recipe — `analyticsKeyEvents` +/// reporting on [`PANEL_ANALYTICS_CIDS`], no diversion — then listen for +/// `seconds` and invoke `on_event(cid, event_code)` for each analytics entry +/// (`event_code`: non-zero = press, `0` = release). Restores reporting on exit. +/// +/// This is the reverse-engineered path that makes the panel emit at all; the +/// production ring in `gesture.rs` should adopt the same config + decode. +pub async fn watch_panel( + route: &DeviceRoute, + seconds: u64, + on_event: impl Fn(u16, u8) + Send + Sync + 'static, +) -> Result<(), HiddenDiagError> { + let chan = open_route_channel(route) + .await + .map_err(|e| HiddenDiagError::Hid(format!("{e:?}")))? + .ok_or(HiddenDiagError::DeviceNotFound)?; + let device_index = route.device_index(); + let mut device = Device::new(Arc::clone(&chan), device_index) + .await + .map_err(|_| HiddenDiagError::DeviceUnreachable { + index: device_index, + })?; + let info = device + .root() + .get_feature(REPROG_FEATURE_ID) + .await + .map_err(|e| HiddenDiagError::Hidpp(format!("{e:?}")))? + .ok_or_else(|| HiddenDiagError::Hidpp("device does not expose 0x1b04".into()))?; + let feature_index = info.index; + eprintln!("[watch_panel] 0x1b04 feature index = {feature_index}"); + let rc = ReprogControlsV4::new(Arc::clone(&chan), device_index, feature_index); + + // The physical force pad is dormant until its threshold is written. Options+ + // sends 0x19c0 (ForceSensingButton) fn3 with `00 15 a3` at init; without it + // the pad generates no 0x0050 control events for analytics to report. + let fsb = open_feature::(&mut device) + .await + .ok(); + eprintln!( + "[watch_panel] force-sensing feature present = {}", + fsb.is_some() + ); + + // Listen before arming so nothing is missed once the config lands. + let on_event = Arc::new(on_event); + let listener = chan.add_msg_listener_guarded({ + let on_event = Arc::clone(&on_event); + move |raw, matched| { + if matched { + return; + } + let msg = v20::Message::from(raw); + if let Some(ReprogControlsEvent::AnalyticsKeyEvents(entries)) = + decode_reprog_event(&msg, device_index, feature_index) + { + for entry in entries { + let cid: u16 = entry.cid.into(); + if cid != 0 { + on_event(cid, entry.event); + } + } + } + } + }); + + // Re-arm on a short cadence: a single arm at t=0 is lost if the BTLE link + // is asleep, and the config does not survive the device sleeping mid-run. + // Re-applying every few seconds guarantees it lands once the link is hot. + let arm_on = CidReportingChange { + analytics_key_events: Some(true), + ..CidReportingChange::default() + }; + let ticks = seconds.div_ceil(3).max(1); + for tick in 0..ticks { + // Arm the force pad (threshold 0x15a3, button 0), then enable analytics + // reporting on its control IDs — the full Options+ activation order. + if let Some(fsb) = &fsb { + match fsb.raw_call(3, [0x00, 0x15, 0xa3]).await { + Ok(r) if tick == 0 => { + eprintln!("[watch_panel] force threshold set -> {:02x?}", &r[..4]); + } + Err(e) if tick == 0 => eprintln!("[watch_panel] force threshold set failed: {e:?}"), + _ => {} + } + } + for cid in PANEL_ANALYTICS_CIDS { + match rc.set_cid_reporting_full(cid, arm_on).await { + Ok(_) if tick == 0 => eprintln!("[watch_panel] analytics armed on 0x{cid:04x}"), + Err(e) if tick == 0 => eprintln!("[watch_panel] arm 0x{cid:04x} failed: {e:?}"), + _ => {} + } + } + tokio::time::sleep(Duration::from_secs(3)).await; + } + + drop(listener); + for cid in PANEL_ANALYTICS_CIDS { + let _ = rc + .set_cid_reporting_full( + cid, + CidReportingChange { + analytics_key_events: Some(false), + ..CidReportingChange::default() + }, + ) + .await; + } + Ok(()) +} + +/// Sends one raw short-form call to ANY HID++ 2.0 feature by ID. Returns +/// `Ok(None)` when the device does not expose the feature. +/// Reverse-engineering aid; interpretation is the caller's. +pub async fn raw_feature_call( + route: &DeviceRoute, + feature_id: u16, + function: u8, + args: [u8; 3], +) -> Result, HiddenDiagError> { + bounded(async { + let device = open_device(route).await?; + device + .raw_feature_call(feature_id, function, args) + .await + .map_err(|e| HiddenDiagError::Hidpp(format!("{e:?}"))) + }) + .await +} + +/// Sends one raw `0x19c0 ForceSensingButton` call and returns the 16-byte +/// response payload. Reverse-engineering aid; interpretation is the caller's. +pub async fn force_button_raw_call( + route: &DeviceRoute, + function: u8, + args: [u8; 3], +) -> Result<[u8; 16], HiddenDiagError> { + bounded(async { + let mut device = open_device(route).await?; + let feature = open_feature::(&mut device) + .await + .map_err(|e| HiddenDiagError::Hidpp(e.to_string()))?; + feature + .raw_call(function, args) + .await + .map_err(|e| HiddenDiagError::Hidpp(format!("{e:?}"))) + }) + .await +} diff --git a/crates/openlogi-hid/src/lib.rs b/crates/openlogi-hid/src/lib.rs index b6cf1547..88bd7647 100644 --- a/crates/openlogi-hid/src/lib.rs +++ b/crates/openlogi-hid/src/lib.rs @@ -20,6 +20,7 @@ mod transport; mod windows_hid; pub mod gesture; +pub mod hidden_features; mod hires_wheel; pub mod hotplug; pub mod inventory; From c812febaa006c2cb3367f992dc616298ac2d32ba Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 02:34:35 -0700 Subject: [PATCH 04/42] feat(cli): add reverse-engineering diag subcommands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit diag panel arms the MX Master 4 Action Ring and prints each press/release — the hardware exerciser behind the production ring capture. diag hidden/fsb probe 0x1e00 and 0x19c0, diag call sends one raw call to any feature by ID, diag hidsniff hex-dumps Logitech HID interfaces, and diag rawinput taps RawInput to observe OS-owned collections (Windows only). --- Cargo.lock | 3 + crates/openlogi-cli/Cargo.toml | 15 + crates/openlogi-cli/src/cmd/diag.rs | 24 ++ crates/openlogi-cli/src/cmd/diag/call.rs | 59 ++++ crates/openlogi-cli/src/cmd/diag/fsb.rs | 66 +++++ crates/openlogi-cli/src/cmd/diag/hidden.rs | 49 ++++ crates/openlogi-cli/src/cmd/diag/hidsniff.rs | 118 ++++++++ crates/openlogi-cli/src/cmd/diag/panel.rs | 53 ++++ crates/openlogi-cli/src/cmd/diag/rawinput.rs | 283 +++++++++++++++++++ 9 files changed, 670 insertions(+) create mode 100644 crates/openlogi-cli/src/cmd/diag/call.rs create mode 100644 crates/openlogi-cli/src/cmd/diag/fsb.rs create mode 100644 crates/openlogi-cli/src/cmd/diag/hidden.rs create mode 100644 crates/openlogi-cli/src/cmd/diag/hidsniff.rs create mode 100644 crates/openlogi-cli/src/cmd/diag/panel.rs create mode 100644 crates/openlogi-cli/src/cmd/diag/rawinput.rs diff --git a/Cargo.lock b/Cargo.lock index 2f7cf1ef..dd997089 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5047,13 +5047,16 @@ name = "openlogi-cli" version = "0.6.21" dependencies = [ "anyhow", + "async-hid", "clap", + "futures-lite", "openlogi-assets", "openlogi-core", "openlogi-hid", "tokio", "tracing", "tracing-subscriber", + "windows-sys 0.61.2", ] [[package]] diff --git a/crates/openlogi-cli/Cargo.toml b/crates/openlogi-cli/Cargo.toml index 4f87d318..4c4d726d 100644 --- a/crates/openlogi-cli/Cargo.toml +++ b/crates/openlogi-cli/Cargo.toml @@ -16,9 +16,24 @@ openlogi-hid = { path = "../openlogi-hid", version = "0.6.21" } openlogi-assets = { path = "../openlogi-assets", version = "0.6.21" } clap = { workspace = true } anyhow = { workspace = true } +async-hid = { workspace = true } +futures-lite = { workspace = true } tokio = { workspace = true, features = ["rt", "macros", "sync", "time"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } +# RawInput tap for `diag rawinput` — the only user-mode way to observe HID +# collections the OS input stack holds exclusively (see diag/rawinput.rs). +[target.'cfg(target_os = "windows")'.dependencies] +windows-sys = { workspace = true, features = [ + "Win32_Foundation", + # WNDCLASSW references HBRUSH, so the window-class APIs are only exposed + # when Win32_Graphics_Gdi is also enabled. + "Win32_Graphics_Gdi", + "Win32_System_LibraryLoader", + "Win32_UI_Input", + "Win32_UI_WindowsAndMessaging", +] } + [lints] workspace = true diff --git a/crates/openlogi-cli/src/cmd/diag.rs b/crates/openlogi-cli/src/cmd/diag.rs index 3a9e0a74..13027c54 100644 --- a/crates/openlogi-cli/src/cmd/diag.rs +++ b/crates/openlogi-cli/src/cmd/diag.rs @@ -10,10 +10,16 @@ use anyhow::{Result, anyhow}; use clap::Subcommand; use openlogi_hid::{DeviceRoute, dump_features}; +pub mod call; pub mod controls; pub mod dpi; pub mod features; +pub mod fsb; +pub mod hidden; +pub mod hidsniff; pub mod lighting; +pub mod panel; +pub mod rawinput; pub mod smartshift; pub mod wheel; @@ -31,6 +37,18 @@ pub enum DiagCmd { Lighting(lighting::LightingArgs), /// Read or set the HID++ 0x2121 wheel reporting resolution. Wheel(wheel::WheelArgs), + /// Passively hex-dump raw input reports from every Logitech HID interface. + Hidsniff(hidsniff::HidSniffArgs), + /// Read or set the 0x1e00 EnableHiddenFeatures gate. + Hidden(hidden::HiddenArgs), + /// Raw-probe the 0x19c0 ForceSensingButton feature (Action Ring panel). + Fsb(fsb::FsbArgs), + /// OS-level RawInput tap: dump reports even from OS-owned HID collections. + Rawinput(rawinput::RawInputArgs), + /// Send one raw call to any HID++ 2.0 feature by ID (reverse-engineering). + Call(call::CallArgs), + /// Arm the Action Ring panel (Options+ recipe) and print its press events. + Panel(panel::PanelArgs), } impl DiagCmd { @@ -42,6 +60,12 @@ impl DiagCmd { Self::Smartshift(args) => smartshift::run(args).await, Self::Lighting(args) => lighting::run(args).await, Self::Wheel(args) => wheel::run(args).await, + Self::Hidsniff(args) => hidsniff::run(args).await, + Self::Hidden(args) => hidden::run(args).await, + Self::Fsb(args) => fsb::run(args).await, + Self::Rawinput(args) => rawinput::run(args).await, + Self::Call(args) => call::run(args).await, + Self::Panel(args) => panel::run(args).await, } } } diff --git a/crates/openlogi-cli/src/cmd/diag/call.rs b/crates/openlogi-cli/src/cmd/diag/call.rs new file mode 100644 index 00000000..110bda4a --- /dev/null +++ b/crates/openlogi-cli/src/cmd/diag/call.rs @@ -0,0 +1,59 @@ +//! `openlogi diag call` — send one raw call to ANY HID++ 2.0 feature by ID. +//! +//! Generic reverse-engineering probe: resolves the feature at runtime and +//! hex-dumps the 16-byte response. `InvalidFunctionId` marks the end of a +//! feature's function table; `InvalidArgument` means the function exists but +//! wants different arguments. + +use anyhow::{Context, Result, bail}; +use clap::Args; + +use crate::cmd::diag::select_device; + +#[derive(Debug, Args)] +pub struct CallArgs { + /// Feature ID, hex (e.g. 1e02). + #[arg(long, value_name = "HEX")] + pub feature: String, + + /// Function ID to call (0-15). + #[arg(long, default_value_t = 0)] + pub function: u8, + + /// Three comma-separated hex argument bytes, e.g. `01,00,00`. + #[arg(long, value_name = "AA,BB,CC")] + pub args: Option, + + /// Run against the device whose name contains this string + /// (case-insensitive) instead of auto-selecting. + #[arg(long, value_name = "NAME")] + pub device: Option, +} + +pub async fn run(args: CallArgs) -> Result<()> { + let feature = u16::from_str_radix(args.feature.trim_start_matches("0x"), 16) + .context("--feature must be hex, e.g. 1e02")?; + if args.function > 0x0f { + bail!("function must be 0-15 (got {})", args.function); + } + let call_args = super::fsb::parse_args(args.args.as_deref())?; + + let (route, name) = select_device(args.device.as_deref(), &[feature]).await?; + println!("device: {name} ({route})"); + println!( + "feature 0x{feature:04x} fn={} args={:02x} {:02x} {:02x}", + args.function, call_args[0], call_args[1], call_args[2] + ); + + match openlogi_hid::hidden_features::raw_feature_call(&route, feature, args.function, call_args) + .await + { + Ok(Some(payload)) => { + let hex: String = payload.iter().map(|b| format!("{b:02x} ")).collect(); + println!("response: {hex}"); + } + Ok(None) => println!("feature not exposed by this device"), + Err(e) => println!("error: {e}"), + } + Ok(()) +} diff --git a/crates/openlogi-cli/src/cmd/diag/fsb.rs b/crates/openlogi-cli/src/cmd/diag/fsb.rs new file mode 100644 index 00000000..dbcc2104 --- /dev/null +++ b/crates/openlogi-cli/src/cmd/diag/fsb.rs @@ -0,0 +1,66 @@ +//! `openlogi diag fsb` — raw-probe the `0x19c0 ForceSensingButton` feature +//! (the MX Master 4's Action Ring panel). +//! +//! The feature's function map is undocumented; this sends one function call +//! with caller-chosen argument bytes and hex-dumps the response so the map +//! can be reverse-engineered on real hardware. An `InvalidFunction` HID++ +//! error is the expected "past the end of the table" response. + +use anyhow::{Context, Result, bail}; +use clap::Args; + +use crate::cmd::diag::select_device; + +#[derive(Debug, Args)] +pub struct FsbArgs { + /// Function ID to call (0-15). + #[arg(long, default_value_t = 0)] + pub function: u8, + + /// Three comma-separated hex argument bytes, e.g. `01,00,00`. + #[arg(long, value_name = "AA,BB,CC")] + pub args: Option, + + /// Run against the device whose name contains this string + /// (case-insensitive) instead of auto-selecting. + #[arg(long, value_name = "NAME")] + pub device: Option, +} + +pub async fn run(args: FsbArgs) -> Result<()> { + if args.function > 0x0f { + bail!("function must be 0-15 (got {})", args.function); + } + let call_args = parse_args(args.args.as_deref())?; + + let (route, name) = select_device(args.device.as_deref(), &[0x19c0]).await?; + println!("device: {name} ({route})"); + println!( + "0x19c0 fn={} args={:02x} {:02x} {:02x}", + args.function, call_args[0], call_args[1], call_args[2] + ); + + match openlogi_hid::hidden_features::force_button_raw_call(&route, args.function, call_args) + .await + { + Ok(payload) => { + let hex: String = payload.iter().map(|b| format!("{b:02x} ")).collect(); + println!("response: {hex}"); + } + Err(e) => println!("error: {e}"), + } + Ok(()) +} + +pub(crate) fn parse_args(raw: Option<&str>) -> Result<[u8; 3]> { + let Some(raw) = raw else { return Ok([0; 3]) }; + let parts: Vec = raw + .split(',') + .map(|p| u8::from_str_radix(p.trim().trim_start_matches("0x"), 16)) + .collect::>() + .context("--args must be hex bytes like 01,00,00")?; + if parts.len() != 3 { + bail!("--args needs exactly 3 bytes (got {})", parts.len()); + } + Ok([parts[0], parts[1], parts[2]]) +} diff --git a/crates/openlogi-cli/src/cmd/diag/hidden.rs b/crates/openlogi-cli/src/cmd/diag/hidden.rs new file mode 100644 index 00000000..4fcae8ee --- /dev/null +++ b/crates/openlogi-cli/src/cmd/diag/hidden.rs @@ -0,0 +1,49 @@ +//! `openlogi diag hidden` — read or set the `0x1e00 EnableHiddenFeatures` +//! gate. Some devices keep auxiliary event sources dormant until a host +//! enables this; the setting does not survive a device power cycle. + +use anyhow::{Context, Result, bail}; +use clap::Args; + +use crate::cmd::diag::select_device; + +#[derive(Debug, Args)] +pub struct HiddenArgs { + /// Enable hidden features (write `1`). + #[arg(long, conflicts_with = "disable")] + pub enable: bool, + + /// Disable hidden features (write `0`). + #[arg(long)] + pub disable: bool, + + /// Run against the device whose name contains this string + /// (case-insensitive) instead of auto-selecting. + #[arg(long, value_name = "NAME")] + pub device: Option, +} + +pub async fn run(args: HiddenArgs) -> Result<()> { + let (route, name) = select_device(args.device.as_deref(), &[0x1e00]).await?; + println!("device: {name} ({route})"); + + let before = openlogi_hid::hidden_features::hidden_features_enabled(&route) + .await + .context("read 0x1e00 enabled state")?; + println!("hidden features enabled: {before}"); + + let target = match (args.enable, args.disable) { + (true, _) => true, + (_, true) => false, + _ => return Ok(()), + }; + + let after = openlogi_hid::hidden_features::set_hidden_features_enabled(&route, target) + .await + .context("write 0x1e00 enabled state")?; + println!("hidden features enabled (read-back): {after}"); + if after != target { + bail!("write not applied: requested {target}, device reports {after}"); + } + Ok(()) +} diff --git a/crates/openlogi-cli/src/cmd/diag/hidsniff.rs b/crates/openlogi-cli/src/cmd/diag/hidsniff.rs new file mode 100644 index 00000000..5f998066 --- /dev/null +++ b/crates/openlogi-cli/src/cmd/diag/hidsniff.rs @@ -0,0 +1,118 @@ +//! `openlogi diag hidsniff` — passively hex-dump raw input reports from every +//! Logitech HID interface for a bounded window. +//! +//! Diagnosis tool for locating input that does NOT arrive via HID++ divert — +//! e.g. the MX Master 4 Action Ring panel, whose events are invisible on +//! `0x1b04` and suspected to flow through the Bolt receiver's touch-pad +//! collection (`MI_03`) instead. Read-only: opens interfaces for input +//! reports, writes nothing, and interfaces the OS holds exclusively (mouse / +//! keyboard boot collections) simply report their open error and are skipped. + +use std::sync::Arc; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::time::{Duration, Instant}; + +use anyhow::{Context, Result}; +use async_hid::{AsyncHidRead, HidBackend}; +use clap::Args; +use futures_lite::StreamExt; + +/// Per-interface cap on individually printed reports, so an unexpectedly +/// chatty collection (sensor stream) can't drown the interesting ones; counts +/// keep accumulating after the mute. +const PRINT_CAP: u64 = 300; + +#[derive(Debug, Args)] +pub struct HidSniffArgs { + /// Vendor ID to filter on, hex (default: Logitech). + #[arg(long, default_value = "046d")] + pub vid: String, + + /// How long to listen for reports, in seconds. + #[arg(long, default_value_t = 30)] + pub seconds: u64, +} + +pub async fn run(args: HidSniffArgs) -> Result<()> { + let vid = u16::from_str_radix(args.vid.trim_start_matches("0x"), 16) + .context("--vid must be hex, e.g. 046d")?; + + let devices: Vec = HidBackend::default() + .enumerate() + .await + .context("HID enumeration failed")? + .collect() + .await; + + let started = Instant::now(); + let deadline = started + Duration::from_secs(args.seconds); + let mut tasks = Vec::new(); + + for (n, dev) in devices.iter().filter(|d| d.vendor_id == vid).enumerate() { + println!( + "iface #{n}: pid={:04x} usage={:#06x}/{:#04x} name={:?} id={:?}", + dev.product_id, dev.usage_page, dev.usage_id, dev.name, dev.id + ); + match dev.open().await { + Ok((mut reader, _writer)) => { + println!(" -> open OK, listening"); + let label = format!("#{n} pid={:04x} up={:#06x}", dev.product_id, dev.usage_page); + let task_label = label.clone(); + let count = Arc::new(AtomicU64::new(0)); + let task_count = Arc::clone(&count); + let task = tokio::spawn(async move { + let label = task_label; + // 1 KiB: Windows rejects reads whose buffer is smaller than + // the interface's max input report (seen on the Bolt + // receiver's Haptics collection, > 64 bytes). + let mut buf = [0u8; 1024]; + loop { + let remain = deadline.saturating_duration_since(Instant::now()); + if remain.is_zero() { + break; + } + match tokio::time::timeout(remain, reader.read_input_report(&mut buf)).await + { + Ok(Ok(len)) => { + let seen = task_count.fetch_add(1, Ordering::Relaxed) + 1; + if seen <= PRINT_CAP { + let ms = started.elapsed().as_millis(); + let hex: String = + buf[..len].iter().map(|b| format!("{b:02x} ")).collect(); + println!("[{ms:>7}ms] {label} len={len}: {hex}"); + } else if seen == PRINT_CAP + 1 { + println!("[{label}] print cap reached — counting silently"); + } + } + Ok(Err(e)) => { + println!("[{label}] read error: {e:?}"); + break; + } + Err(_) => break, // deadline reached + } + } + }); + tasks.push((label, count, task)); + } + Err(e) => println!(" -> open failed: {e:?}"), + } + } + + if tasks.is_empty() { + anyhow::bail!("no vid={vid:04x} interface could be opened"); + } + + println!( + "\nlistening on {} interface(s) for {} s — exercise the control under test now\n", + tasks.len(), + args.seconds + ); + for (label, count, task) in tasks { + let _ = task.await; + println!( + "summary {label}: {} report(s)", + count.load(Ordering::Relaxed) + ); + } + Ok(()) +} diff --git a/crates/openlogi-cli/src/cmd/diag/panel.rs b/crates/openlogi-cli/src/cmd/diag/panel.rs new file mode 100644 index 00000000..97f1b51d --- /dev/null +++ b/crates/openlogi-cli/src/cmd/diag/panel.rs @@ -0,0 +1,53 @@ +//! `openlogi diag panel` — arm the MX Master 4 Action Ring panel with the +//! reverse-engineered Options+ recipe and print each press/release. +//! +//! Proves OpenLogi can drive the panel: it enables `0x1b04` +//! `analyticsKeyEvents` on the panel CIDs (no diversion) and decodes the +//! events the panel emits. This is the empirical basis for the production +//! ring; keep the mouse moving so the (BTLE) link is hot while arming. + +use std::sync::Arc; +use std::sync::atomic::{AtomicU32, Ordering}; + +use anyhow::{Context, Result}; +use clap::Args; + +use crate::cmd::diag::select_device; + +#[derive(Debug, Args)] +pub struct PanelArgs { + /// How long to listen, in seconds. + #[arg(long, default_value_t = 45)] + pub seconds: u64, + + /// Run against the device whose name contains this string + /// (case-insensitive) instead of auto-selecting. + #[arg(long, value_name = "NAME")] + pub device: Option, +} + +pub async fn run(args: PanelArgs) -> Result<()> { + let (route, name) = select_device(args.device.as_deref(), &[0x1b04]).await?; + println!("device: {name} ({route})"); + println!( + "arming Action Ring panel (analytics on CIDs 0x0050/0x0051) — press it now, {}s window\n", + args.seconds + ); + + let count = Arc::new(AtomicU32::new(0)); + let tally = Arc::clone(&count); + openlogi_hid::hidden_features::watch_panel(&route, args.seconds, move |cid, event| { + let n = tally.fetch_add(1, Ordering::Relaxed) + 1; + let state = if event != 0 { "PRESS " } else { "RELEASE" }; + println!(" [{n:>3}] panel cid=0x{cid:04x} {state} (event=0x{event:02x})"); + }) + .await + .context("watch panel")?; + + let total = count.load(Ordering::Relaxed); + println!("\n{total} panel event(s) captured."); + if total == 0 { + println!("none seen — was the mouse moving while arming? is Options+ holding the device?"); + } + Ok(()) +} diff --git a/crates/openlogi-cli/src/cmd/diag/rawinput.rs b/crates/openlogi-cli/src/cmd/diag/rawinput.rs new file mode 100644 index 00000000..f487110b --- /dev/null +++ b/crates/openlogi-cli/src/cmd/diag/rawinput.rs @@ -0,0 +1,283 @@ +//! `openlogi diag rawinput` — OS-level input tap via the Win32 RawInput API. +//! +//! Registers whole HID usage pages (digitizer `0x0D`, haptics `0x0E`, the +//! Logitech vendor pages `0xFF00`/`0xFF43`) with `RIDEV_INPUTSINK` and +//! hex-dumps every report Windows delivers — INCLUDING reports consumed by +//! exclusive OS owners like the Precision Touchpad stack. This is the only +//! user-mode way to observe the Bolt receiver's touch-pad collection +//! (`0x000D/0x05`), whose node `CreateFile` cannot open. Windows-only; +//! read-only (registration does not steal input from its owners). + +use anyhow::Result; +use clap::Args; + +#[derive(Debug, Args)] +pub struct RawInputArgs { + /// How long to listen, in seconds. + #[arg(long, default_value_t = 45)] + pub seconds: u64, +} + +#[cfg(not(target_os = "windows"))] +pub async fn run(_args: RawInputArgs) -> Result<()> { + anyhow::bail!("`diag rawinput` is Windows-only") +} + +#[cfg(target_os = "windows")] +pub async fn run(args: RawInputArgs) -> Result<()> { + // RawInput delivery is synchronous window messaging; the pump owns a + // dedicated blocking thread rather than the async runtime. + let seconds = args.seconds; + tokio::task::spawn_blocking(move || windows_impl::listen(seconds)).await? +} + +#[cfg(target_os = "windows")] +#[expect( + unsafe_code, + reason = "raw Win32 RawInput FFI — a read-only diagnostic input tap with no safe wrapper available" +)] +mod windows_impl { + use std::collections::HashMap; + use std::mem::{size_of, zeroed}; + use std::ptr::{null, null_mut}; + use std::sync::{Mutex, OnceLock}; + use std::time::Instant; + + use anyhow::{Result, bail}; + use windows_sys::Win32::Foundation::{GetLastError, HWND, LPARAM, LRESULT, WPARAM}; + use windows_sys::Win32::System::LibraryLoader::GetModuleHandleW; + use windows_sys::Win32::UI::Input::{ + GetRawInputData, GetRawInputDeviceInfoW, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, + RAWINPUTHEADER, RID_INPUT, RIDEV_INPUTSINK, RIDEV_PAGEONLY, RIDI_DEVICENAME, RIM_TYPEHID, + RegisterRawInputDevices, + }; + use windows_sys::Win32::UI::WindowsAndMessaging::{ + CreateWindowExW, DefWindowProcW, DispatchMessageW, GetMessageW, MSG, PostQuitMessage, + RegisterClassW, SetTimer, TranslateMessage, WM_INPUT, WM_TIMER, WNDCLASSW, + }; + + /// Set once at listen start; the window proc reads it for timestamps. + static START: OnceLock = OnceLock::new(); + + fn device_names() -> &'static Mutex> { + static NAMES: OnceLock>> = OnceLock::new(); + NAMES.get_or_init(Mutex::default) + } + + pub fn listen(seconds: u64) -> Result<()> { + let _ = START.set(Instant::now()); + + let class_name: Vec = "openlogi_rawinput\0".encode_utf16().collect(); + let window_name: Vec = "openlogi rawinput tap\0".encode_utf16().collect(); + + // SAFETY: plain Win32 window bootstrap; all pointers passed are either + // valid locals or null where the API documents null as acceptable. + let hwnd = unsafe { + let hinstance = GetModuleHandleW(null()); + let mut wc: WNDCLASSW = zeroed(); + wc.lpfnWndProc = Some(wndproc); + wc.hInstance = hinstance; + wc.lpszClassName = class_name.as_ptr(); + // Re-registration in one process returns 0 with + // ERROR_CLASS_ALREADY_EXISTS — harmless for this tool's lifetime. + RegisterClassW(&wc); + + CreateWindowExW( + 0, + class_name.as_ptr(), + window_name.as_ptr(), + 0, + 0, + 0, + 0, + 0, + null_mut(), + null_mut(), + hinstance, + null(), + ) + }; + if hwnd.is_null() { + // SAFETY: trivial error-code read. + bail!("CreateWindowExW failed: {}", unsafe { GetLastError() }); + } + + let flags = RIDEV_INPUTSINK | RIDEV_PAGEONLY; + let registrations = [ + // Digitizer page — the receiver's touch-pad collection lives here. + rid(0x000d, flags, hwnd), + // Haptics page — the receiver's 0x000E collection. + rid(0x000e, flags, hwnd), + // Logitech vendor pages (HID++ short/long and friends). + rid(0xff00, flags, hwnd), + rid(0xff43, flags, hwnd), + ]; + // SAFETY: registrations array outlives the call; cbSize matches. + let ok = unsafe { + RegisterRawInputDevices( + registrations.as_ptr(), + registrations.len() as u32, + size_of::() as u32, + ) + }; + if ok == 0 { + // SAFETY: trivial error-code read. + bail!("RegisterRawInputDevices failed: {}", unsafe { + GetLastError() + }); + } + + println!( + "raw-input tap live for {seconds} s — pages 0x0D digitizer, 0x0E haptics, 0xFF00/0xFF43 vendor" + ); + + // SAFETY: standard message pump on the window created above; the + // timer id is private to this window. + unsafe { + SetTimer( + hwnd, + 1, + u32::try_from(seconds * 1000).unwrap_or(45_000), + None, + ); + let mut msg: MSG = zeroed(); + while GetMessageW(&mut msg, null_mut(), 0, 0) > 0 { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + println!("raw-input tap closed"); + Ok(()) + } + + fn rid(usage_page: u16, flags: u32, hwnd: HWND) -> RAWINPUTDEVICE { + RAWINPUTDEVICE { + usUsagePage: usage_page, + usUsage: 0, // RIDEV_PAGEONLY requires usage 0 + dwFlags: flags, + hwndTarget: hwnd, + } + } + + unsafe extern "system" fn wndproc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, + ) -> LRESULT { + match msg { + WM_INPUT => { + dump_input(lparam as HRAWINPUT); + // WM_INPUT must still reach DefWindowProc for cleanup. + // SAFETY: forwarding the untouched arguments. + unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) } + } + WM_TIMER => { + // SAFETY: ends this thread's message loop. + unsafe { PostQuitMessage(0) }; + 0 + } + // SAFETY: default handling for everything else. + _ => unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }, + } + } + + fn dump_input(handle: HRAWINPUT) { + let header_size = size_of::() as u32; + let mut size = 0u32; + // SAFETY: sizing call per RawInput protocol (null buffer, then fetch). + unsafe { + GetRawInputData(handle, RID_INPUT, null_mut(), &mut size, header_size); + } + if size == 0 { + return; + } + let mut buf = vec![0u8; size as usize]; + // SAFETY: buffer is exactly the size Windows requested. + let got = unsafe { + GetRawInputData( + handle, + RID_INPUT, + buf.as_mut_ptr().cast(), + &mut size, + header_size, + ) + }; + if got == 0 || got == u32::MAX { + return; + } + // SAFETY: Windows filled `buf` with a RAWINPUT structure of at least + // `got` bytes; the buffer is aligned by Vec only to 1, so read + // fields via raw pointer without constructing a reference... a u8 Vec + // is sufficiently aligned in practice for RAWINPUT on x86-64 heap + // allocations (16-byte aligned), and this mirrors the canonical + // RawInput usage pattern. + let raw = buf.as_ptr().cast::(); + // SAFETY: header fields are plain integers within the filled buffer. + let (dw_type, h_device) = unsafe { ((*raw).header.dwType, (*raw).header.hDevice) }; + if dw_type != RIM_TYPEHID { + return; + } + // SAFETY: dwType == RIM_TYPEHID guarantees the union holds RAWHID. + let (each, count, data_ptr) = unsafe { + let hid = &(*raw).data.hid; + ( + hid.dwSizeHid as usize, + hid.dwCount as usize, + hid.bRawData.as_ptr(), + ) + }; + let name = lookup_name(h_device as usize); + let ms = START.get().map_or(0, |start| start.elapsed().as_millis()); + for i in 0..count { + // SAFETY: bRawData holds dwCount packed reports of dwSizeHid bytes + // inside the buffer Windows sized for us. + let report = unsafe { std::slice::from_raw_parts(data_ptr.add(i * each), each) }; + let hex: String = report.iter().map(|b| format!("{b:02x} ")).collect(); + println!("[{ms:>7}ms] {name} len={each}: {hex}"); + } + } + + fn lookup_name(handle: usize) -> String { + if let Some(name) = device_names() + .lock() + .ok() + .and_then(|map| map.get(&handle).cloned()) + { + return name; + } + let mut len = 0u32; + // SAFETY: sizing call, then bounded fetch into a matching buffer. + let name = unsafe { + GetRawInputDeviceInfoW(handle as _, RIDI_DEVICENAME, null_mut(), &mut len); + let mut buf = vec![0u16; len as usize]; + let got = GetRawInputDeviceInfoW( + handle as _, + RIDI_DEVICENAME, + buf.as_mut_ptr().cast(), + &mut len, + ); + if got == u32::MAX || got == 0 { + format!("hdev={handle:x}") + } else { + let full = String::from_utf16_lossy(&buf[..got as usize]); + let full = full.trim_end_matches('\0'); + // Announce the full path once; per-report lines carry the + // discriminating VID/PID/interface segment. + println!("device hdev={handle:x}: {full}"); + let core: String = full + .split('#') + .nth(1) + .unwrap_or(full) + .chars() + .take(40) + .collect(); + core + } + }; + if let Ok(mut map) = device_names().lock() { + map.insert(handle, name.clone()); + } + name + } +} From 0e9b8bc36acdb9a75b0de3982aed7df4568106fc Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 02:34:35 -0700 Subject: [PATCH 05/42] docs(hid): add MX Master 4 Action Ring panel captures The empirical record behind the panel protocol: the Options+ cold-start wiretap the arming recipe came from, the analytics event stream under Options+, and the confirming clean-state capture (48/48 press/release events from a power-cycled mouse with no Logitech software running). --- .../elan-touchpad-positive-control.txt | 1015 +++ .../openlogi-diag-panel-arm-confirmed.txt | 14 + .../openlogi-panel-confirmed-clean.txt | 62 + .../optionsplus-coldstart-full.txt | 315 + .../optionsplus-rawinput.txt | 135 + .../panel-analytics-stream.txt | 115 + .../panel-force-rawxy-stream.log | 5797 +++++++++++++++++ 7 files changed, 7453 insertions(+) create mode 100644 docs/mx-master-4-panel-captures/elan-touchpad-positive-control.txt create mode 100644 docs/mx-master-4-panel-captures/openlogi-diag-panel-arm-confirmed.txt create mode 100644 docs/mx-master-4-panel-captures/openlogi-panel-confirmed-clean.txt create mode 100644 docs/mx-master-4-panel-captures/optionsplus-coldstart-full.txt create mode 100644 docs/mx-master-4-panel-captures/optionsplus-rawinput.txt create mode 100644 docs/mx-master-4-panel-captures/panel-analytics-stream.txt create mode 100644 docs/mx-master-4-panel-captures/panel-force-rawxy-stream.log diff --git a/docs/mx-master-4-panel-captures/elan-touchpad-positive-control.txt b/docs/mx-master-4-panel-captures/elan-touchpad-positive-control.txt new file mode 100644 index 00000000..1ec28e84 --- /dev/null +++ b/docs/mx-master-4-panel-captures/elan-touchpad-positive-control.txt @@ -0,0 +1,1015 @@ +raw-input tap live for 120 s — pages 0x0D digitizer, 0x0E haptics, 0xFF00/0xFF43 vendor +device hdev=1005e: \\?\HID#VEN_0488&Col02#5&2b581ffa&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030} +[ 15431ms] VEN_0488&Col02 len=20: 01 03 a3 02 49 03 00 00 00 00 00 00 00 00 00 00 31 31 01 00 +[ 15432ms] VEN_0488&Col02 len=20: 01 03 a3 02 49 03 00 00 00 00 00 00 00 00 00 00 77 31 01 00 +[ 15434ms] VEN_0488&Col02 len=20: 01 03 a3 02 49 03 07 a1 04 43 03 00 00 00 00 00 bd 31 02 00 +[ 15441ms] VEN_0488&Col02 len=20: 01 03 a3 02 49 03 07 a1 04 43 03 00 00 00 00 00 03 32 02 00 +[ 15449ms] VEN_0488&Col02 len=20: 01 03 a3 02 49 03 07 a1 04 43 03 0b 85 03 0e 03 49 32 03 00 +[ 15460ms] VEN_0488&Col02 len=20: 01 03 a3 02 49 03 07 a1 04 43 03 0b 85 03 0e 03 8f 32 03 00 +[ 15463ms] VEN_0488&Col02 len=20: 01 03 a3 02 46 03 07 a1 04 41 03 0b 85 03 0e 03 d5 32 03 00 +[ 15470ms] VEN_0488&Col02 len=20: 01 03 a4 02 3f 03 07 a2 04 3c 03 0b 85 03 0c 03 1b 33 03 00 +[ 15477ms] VEN_0488&Col02 len=20: 01 03 a5 02 31 03 07 a5 04 31 03 0b 85 03 05 03 61 33 03 00 +[ 15485ms] VEN_0488&Col02 len=20: 01 03 a9 02 1c 03 07 a9 04 1f 03 0b 86 03 fa 02 a7 33 03 00 +[ 15492ms] VEN_0488&Col02 len=20: 01 03 ab 02 0e 03 07 aa 04 13 03 0b 87 03 e0 02 ed 33 03 00 +[ 15499ms] VEN_0488&Col02 len=20: 01 03 ad 02 00 03 07 ab 04 09 03 0b 88 03 cc 02 33 34 03 00 +[ 15506ms] VEN_0488&Col02 len=20: 01 03 af 02 ef 02 07 ab 04 fe 02 0b 88 03 c1 02 79 34 03 00 +[ 15513ms] VEN_0488&Col02 len=20: 01 03 b2 02 e2 02 07 ab 04 f1 02 0b 8b 03 b0 02 bf 34 03 00 +[ 15520ms] VEN_0488&Col02 len=20: 01 03 b4 02 d4 02 07 ac 04 e4 02 0b 8c 03 a0 02 05 35 03 00 +[ 15527ms] VEN_0488&Col02 len=20: 01 03 b6 02 c7 02 07 ae 04 d9 02 0b 8c 03 92 02 4b 35 03 00 +[ 15535ms] VEN_0488&Col02 len=20: 01 03 b7 02 b9 02 07 ae 04 cd 02 0b 8e 03 86 02 91 35 03 00 +[ 15541ms] VEN_0488&Col02 len=20: 01 03 b9 02 a9 02 07 ae 04 c1 02 0b 91 03 77 02 d7 35 03 00 +[ 15549ms] VEN_0488&Col02 len=20: 01 03 bb 02 98 02 07 ae 04 b4 02 0b 94 03 67 02 1d 36 03 00 +[ 15557ms] VEN_0488&Col02 len=20: 01 03 bd 02 8b 02 07 ae 04 a5 02 0b 95 03 5a 02 63 36 03 00 +[ 15564ms] VEN_0488&Col02 len=20: 01 03 be 02 7c 02 07 af 04 97 02 0b 98 03 4b 02 a9 36 03 00 +[ 15571ms] VEN_0488&Col02 len=20: 01 03 bf 02 6f 02 07 af 04 8c 02 0b 99 03 3d 02 ef 36 03 00 +[ 15578ms] VEN_0488&Col02 len=20: 01 03 c0 02 5f 02 07 af 04 81 02 0b 9a 03 2f 02 35 37 03 00 +[ 15585ms] VEN_0488&Col02 len=20: 01 03 c1 02 55 02 07 af 04 74 02 0b 9c 03 22 02 7b 37 03 00 +[ 15592ms] VEN_0488&Col02 len=20: 01 03 c2 02 4b 02 07 af 04 67 02 0b 9e 03 14 02 c1 37 03 00 +[ 15599ms] VEN_0488&Col02 len=20: 01 03 c2 02 44 02 07 ae 04 5f 02 0b 9e 03 0c 02 07 38 03 00 +[ 15606ms] VEN_0488&Col02 len=20: 01 03 c2 02 3c 02 07 ae 04 55 02 0b 9f 03 02 02 4d 38 03 00 +[ 15613ms] VEN_0488&Col02 len=20: 01 03 c2 02 34 02 07 ae 04 4c 02 0b a0 03 f9 01 93 38 03 00 +[ 15620ms] VEN_0488&Col02 len=20: 01 03 c1 02 2c 02 07 ae 04 42 02 0b a0 03 ee 01 d9 38 03 00 +[ 15627ms] VEN_0488&Col02 len=20: 01 03 c1 02 23 02 07 ac 04 38 02 0b a1 03 e3 01 1f 39 03 00 +[ 15634ms] VEN_0488&Col02 len=20: 01 03 c1 02 19 02 07 ab 04 2e 02 0b a1 03 d8 01 65 39 03 00 +[ 15641ms] VEN_0488&Col02 len=20: 01 03 c1 02 0e 02 07 ab 04 21 02 0b a1 03 ce 01 ab 39 03 00 +[ 15649ms] VEN_0488&Col02 len=20: 01 03 c1 02 02 02 07 aa 04 16 02 0b a1 03 c3 01 f1 39 03 00 +[ 15655ms] VEN_0488&Col02 len=20: 01 03 c1 02 f6 01 07 a9 04 0c 02 0b a1 03 b8 01 37 3a 03 00 +[ 15662ms] VEN_0488&Col02 len=20: 01 03 c1 02 e8 01 07 a7 04 01 02 0b a1 03 ab 01 7d 3a 03 00 +[ 15669ms] VEN_0488&Col02 len=20: 01 03 c1 02 d7 01 07 a6 04 f4 01 0b a1 03 9c 01 c3 3a 03 00 +[ 15677ms] VEN_0488&Col02 len=20: 01 03 c2 02 c9 01 07 a5 04 e6 01 0b a2 03 8d 01 09 3b 03 00 +[ 15684ms] VEN_0488&Col02 len=20: 01 03 c4 02 bb 01 07 a5 04 da 01 0b a2 03 80 01 4f 3b 03 00 +[ 15692ms] VEN_0488&Col02 len=20: 01 03 c4 02 af 01 07 a4 04 d0 01 0b a2 03 72 01 95 3b 03 00 +[ 15701ms] VEN_0488&Col02 len=20: 01 03 c5 02 a1 01 07 a2 04 c5 01 0b a2 03 63 01 db 3b 03 00 +[ 15706ms] VEN_0488&Col02 len=20: 01 03 c6 02 96 01 07 a1 04 bc 01 0b a2 03 58 01 21 3c 03 00 +[ 15713ms] VEN_0488&Col02 len=20: 01 03 c7 02 8e 01 07 9f 04 b2 01 0b a2 03 4f 01 67 3c 03 00 +[ 15720ms] VEN_0488&Col02 len=20: 01 03 c7 02 84 01 07 9e 04 a8 01 0b a2 03 44 01 ad 3c 03 00 +[ 15727ms] VEN_0488&Col02 len=20: 01 03 c7 02 7a 01 07 9d 04 9e 01 0b a2 03 39 01 f3 3c 03 00 +[ 15734ms] VEN_0488&Col02 len=20: 01 03 c7 02 70 01 07 9a 04 93 01 0b a2 03 2e 01 39 3d 03 00 +[ 15741ms] VEN_0488&Col02 len=20: 01 03 c6 02 68 01 07 98 04 89 01 0b a2 03 24 01 7f 3d 03 00 +[ 15748ms] VEN_0488&Col02 len=20: 01 03 c6 02 61 01 07 96 04 81 01 0b a2 03 1b 01 c5 3d 03 00 +[ 15755ms] VEN_0488&Col02 len=20: 01 03 c5 02 58 01 07 93 04 77 01 0b a2 03 11 01 0b 3e 03 00 +[ 15762ms] VEN_0488&Col02 len=20: 01 03 c4 02 50 01 07 91 04 6b 01 0b a1 03 07 01 51 3e 03 00 +[ 15769ms] VEN_0488&Col02 len=20: 01 03 c2 02 48 01 07 90 04 60 01 0b a1 03 fe 00 97 3e 03 00 +[ 15776ms] VEN_0488&Col02 len=20: 01 03 c1 02 3f 01 07 8e 04 55 01 0b a0 03 f3 00 dd 3e 03 00 +[ 15783ms] VEN_0488&Col02 len=20: 01 03 bf 02 36 01 07 8c 04 4b 01 0b a0 03 e8 00 23 3f 03 00 +[ 15791ms] VEN_0488&Col02 len=20: 01 03 bf 02 2d 01 07 8b 04 42 01 0b a0 03 de 00 69 3f 03 00 +[ 15798ms] VEN_0488&Col02 len=20: 01 03 bf 02 22 01 07 8a 04 36 01 0b a0 03 d4 00 af 3f 03 00 +[ 15805ms] VEN_0488&Col02 len=20: 01 03 bf 02 1a 01 07 88 04 2c 01 0b 9f 03 cc 00 f5 3f 03 00 +[ 15812ms] VEN_0488&Col02 len=20: 01 03 bf 02 13 01 07 88 04 25 01 0b 9f 03 c5 00 3b 40 03 00 +[ 15819ms] VEN_0488&Col02 len=20: 01 03 bf 02 0c 01 07 88 04 1c 01 0b 9f 03 bd 00 81 40 03 00 +[ 15827ms] VEN_0488&Col02 len=20: 01 03 c0 02 04 01 07 89 04 14 01 0b a0 03 b3 00 c7 40 03 00 +[ 15833ms] VEN_0488&Col02 len=20: 01 03 c1 02 fe 00 07 89 04 0e 01 0b a1 03 ab 00 0d 41 03 00 +[ 15841ms] VEN_0488&Col02 len=20: 01 03 c1 02 f5 00 07 8a 04 05 01 0b a2 03 a3 00 53 41 03 00 +[ 15848ms] VEN_0488&Col02 len=20: 01 03 c2 02 ed 00 07 8a 04 fc 00 0b a2 03 9b 00 99 41 03 00 +[ 15855ms] VEN_0488&Col02 len=20: 01 03 c2 02 e7 00 07 8a 04 f5 00 0b a2 03 94 00 df 41 03 00 +[ 15862ms] VEN_0488&Col02 len=20: 01 03 c3 02 e0 00 07 8b 04 eb 00 0b a4 03 8c 00 25 42 03 00 +[ 15870ms] VEN_0488&Col02 len=20: 01 03 c3 02 da 00 07 8c 04 e1 00 0b a6 03 84 00 6b 42 03 00 +[ 15877ms] VEN_0488&Col02 len=20: 01 03 c3 02 d5 00 07 8d 04 d9 00 0b a7 03 7e 00 b1 42 03 00 +[ 15884ms] VEN_0488&Col02 len=20: 01 03 c4 02 d0 00 07 8e 04 d1 00 0b a7 03 77 00 f7 42 03 00 +[ 15892ms] VEN_0488&Col02 len=20: 01 03 c5 02 cb 00 07 90 04 ca 00 0b a8 03 6f 00 3d 43 03 00 +[ 15899ms] VEN_0488&Col02 len=20: 01 03 c7 02 c8 00 07 91 04 c3 00 0b aa 03 6a 00 83 43 03 00 +[ 15906ms] VEN_0488&Col02 len=20: 01 01 c7 02 c8 00 07 92 04 b9 00 0b ad 03 65 00 c9 43 03 00 +[ 15913ms] VEN_0488&Col02 len=20: 01 07 92 04 ba 00 09 ad 03 65 00 00 00 00 00 00 0f 44 02 00 +[ 15921ms] VEN_0488&Col02 len=20: 01 05 92 04 ba 00 00 00 00 00 00 00 00 00 00 00 55 44 01 00 +[ 16684ms] VEN_0488&Col02 len=20: 01 03 df 03 83 00 00 00 00 00 00 00 00 00 00 00 54 62 01 00 +[ 16691ms] VEN_0488&Col02 len=20: 01 03 df 03 83 00 07 f3 02 f0 00 00 00 00 00 00 9a 62 02 00 +[ 16698ms] VEN_0488&Col02 len=20: 01 03 df 03 83 00 07 f3 02 f0 00 00 00 00 00 00 e0 62 02 00 +[ 16706ms] VEN_0488&Col02 len=20: 01 03 df 03 83 00 07 f3 02 f0 00 00 00 00 00 00 26 63 02 00 +[ 16713ms] VEN_0488&Col02 len=20: 01 03 df 03 83 00 07 f3 02 f0 00 0b ce 04 c4 00 6c 63 03 00 +[ 16720ms] VEN_0488&Col02 len=20: 01 03 df 03 83 00 07 f3 02 f0 00 0b ce 04 c4 00 b2 63 03 00 +[ 16727ms] VEN_0488&Col02 len=20: 01 03 df 03 83 00 07 f3 02 f0 00 0b ce 04 c4 00 f8 63 03 00 +[ 16734ms] VEN_0488&Col02 len=20: 01 03 df 03 83 00 07 f3 02 f0 00 0b ce 04 c4 00 3e 64 03 00 +[ 16742ms] VEN_0488&Col02 len=20: 01 03 df 03 83 00 07 f3 02 f0 00 0b ce 04 c4 00 84 64 03 00 +[ 16748ms] VEN_0488&Col02 len=20: 01 03 df 03 84 00 07 f3 02 f1 00 0b ce 04 c4 00 ca 64 03 00 +[ 16756ms] VEN_0488&Col02 len=20: 01 03 df 03 86 00 07 f3 02 f4 00 0b ce 04 c4 00 10 65 03 00 +[ 16762ms] VEN_0488&Col02 len=20: 01 03 e0 03 8a 00 07 f5 02 f9 00 0b cf 04 c6 00 56 65 03 00 +[ 16770ms] VEN_0488&Col02 len=20: 01 03 e1 03 91 00 07 f7 02 01 01 0b d0 04 ca 00 9c 65 03 00 +[ 16777ms] VEN_0488&Col02 len=20: 01 03 e5 03 9d 00 07 fc 02 0e 01 0b d0 04 d0 00 e2 65 03 00 +[ 16784ms] VEN_0488&Col02 len=20: 01 03 e6 03 a6 00 07 fd 02 15 01 0b d2 04 dc 00 28 66 03 00 +[ 16791ms] VEN_0488&Col02 len=20: 01 03 e7 03 af 00 07 fe 02 1c 01 0b d2 04 ea 00 6e 66 03 00 +[ 16798ms] VEN_0488&Col02 len=20: 01 03 e8 03 bb 00 07 ff 02 24 01 0b d2 04 f4 00 b4 66 03 00 +[ 16805ms] VEN_0488&Col02 len=20: 01 03 ea 03 c5 00 07 01 03 2d 01 0b d1 04 fe 00 fa 66 03 00 +[ 16813ms] VEN_0488&Col02 len=20: 01 03 ec 03 ce 00 07 03 03 36 01 0b d1 04 08 01 40 67 03 00 +[ 16820ms] VEN_0488&Col02 len=20: 01 03 ed 03 d7 00 07 04 03 42 01 0b d0 04 12 01 86 67 03 00 +[ 16826ms] VEN_0488&Col02 len=20: 01 03 ed 03 e2 00 07 04 03 4d 01 0b cf 04 1b 01 cc 67 03 00 +[ 16834ms] VEN_0488&Col02 len=20: 01 03 eb 03 eb 00 07 04 03 56 01 0b ce 04 22 01 12 68 03 00 +[ 16840ms] VEN_0488&Col02 len=20: 01 03 ea 03 f9 00 07 04 03 60 01 0b cd 04 2e 01 58 68 03 00 +[ 16847ms] VEN_0488&Col02 len=20: 01 03 e9 03 05 01 07 04 03 6a 01 0b cc 04 3a 01 9e 68 03 00 +[ 16855ms] VEN_0488&Col02 len=20: 01 03 e7 03 11 01 07 03 03 73 01 0b ca 04 46 01 e4 68 03 00 +[ 16862ms] VEN_0488&Col02 len=20: 01 03 e6 03 1a 01 07 02 03 80 01 0b c7 04 51 01 2a 69 03 00 +[ 16869ms] VEN_0488&Col02 len=20: 01 03 e4 03 25 01 07 01 03 8d 01 0b c5 04 5c 01 70 69 03 00 +[ 16876ms] VEN_0488&Col02 len=20: 01 03 e1 03 31 01 07 ff 02 99 01 0b c3 04 65 01 b6 69 03 00 +[ 16883ms] VEN_0488&Col02 len=20: 01 03 de 03 3f 01 07 fd 02 a2 01 0b c1 04 70 01 fc 69 03 00 +[ 16890ms] VEN_0488&Col02 len=20: 01 03 dc 03 4a 01 07 fc 02 ab 01 0b bf 04 7b 01 42 6a 03 00 +[ 16897ms] VEN_0488&Col02 len=20: 01 03 d9 03 56 01 07 fa 02 b4 01 0b bd 04 88 01 88 6a 03 00 +[ 16904ms] VEN_0488&Col02 len=20: 01 03 d6 03 60 01 07 f8 02 c2 01 0b bb 04 93 01 ce 6a 03 00 +[ 16911ms] VEN_0488&Col02 len=20: 01 03 d4 03 69 01 07 f7 02 cd 01 0b b8 04 9d 01 14 6b 03 00 +[ 16918ms] VEN_0488&Col02 len=20: 01 03 d2 03 74 01 07 f5 02 d9 01 0b b7 04 a5 01 5a 6b 03 00 +[ 16925ms] VEN_0488&Col02 len=20: 01 03 d1 03 81 01 07 f3 02 e3 01 0b b5 04 af 01 a0 6b 03 00 +[ 16932ms] VEN_0488&Col02 len=20: 01 03 ce 03 8e 01 07 f2 02 ee 01 0b b4 04 ba 01 e6 6b 03 00 +[ 16939ms] VEN_0488&Col02 len=20: 01 03 cb 03 9a 01 07 ee 02 f9 01 0b b1 04 c7 01 2c 6c 03 00 +[ 16946ms] VEN_0488&Col02 len=20: 01 03 c9 03 a4 01 07 ec 02 06 02 0b b0 04 d3 01 72 6c 03 00 +[ 16953ms] VEN_0488&Col02 len=20: 01 03 c8 03 ad 01 07 e9 02 13 02 0b af 04 dd 01 b8 6c 03 00 +[ 16960ms] VEN_0488&Col02 len=20: 01 03 c6 03 ba 01 07 e8 02 20 02 0b ad 04 e7 01 fe 6c 03 00 +[ 16967ms] VEN_0488&Col02 len=20: 01 03 c4 03 c7 01 07 e4 02 2c 02 0b aa 04 f1 01 44 6d 03 00 +[ 16975ms] VEN_0488&Col02 len=20: 01 03 c2 03 d4 01 07 e1 02 36 02 0b a8 04 fc 01 8a 6d 03 00 +[ 16982ms] VEN_0488&Col02 len=20: 01 03 c0 03 df 01 07 df 02 42 02 0b a6 04 0b 02 d0 6d 03 00 +[ 16989ms] VEN_0488&Col02 len=20: 01 03 be 03 eb 01 07 db 02 51 02 0b a4 04 18 02 16 6e 03 00 +[ 16996ms] VEN_0488&Col02 len=20: 01 03 bc 03 f6 01 07 d8 02 5d 02 0b a2 04 23 02 5c 6e 03 00 +[ 17004ms] VEN_0488&Col02 len=20: 01 03 ba 03 05 02 07 d4 02 69 02 0b a0 04 2c 02 a2 6e 03 00 +[ 17010ms] VEN_0488&Col02 len=20: 01 03 b9 03 11 02 07 d2 02 72 02 0b 9e 04 36 02 e8 6e 03 00 +[ 17018ms] VEN_0488&Col02 len=20: 01 03 b9 03 1c 02 07 d1 02 7a 02 0b 9c 04 41 02 2e 6f 03 00 +[ 17024ms] VEN_0488&Col02 len=20: 01 03 b8 03 25 02 07 d0 02 86 02 0b 9b 04 4c 02 74 6f 03 00 +[ 17032ms] VEN_0488&Col02 len=20: 01 03 b7 03 2d 02 07 ce 02 90 02 0b 99 04 56 02 ba 6f 03 00 +[ 17039ms] VEN_0488&Col02 len=20: 01 03 b5 03 36 02 07 cd 02 9a 02 0b 98 04 5f 02 00 70 03 00 +[ 17046ms] VEN_0488&Col02 len=20: 01 03 b4 03 3f 02 07 cc 02 a3 02 0b 98 04 66 02 46 70 03 00 +[ 17053ms] VEN_0488&Col02 len=20: 01 03 b3 03 49 02 07 ca 02 ab 02 0b 97 04 6d 02 8c 70 03 00 +[ 17060ms] VEN_0488&Col02 len=20: 01 03 b3 03 53 02 07 ca 02 b3 02 0b 95 04 73 02 d2 70 03 00 +[ 17067ms] VEN_0488&Col02 len=20: 01 03 b2 03 5c 02 07 ca 02 ba 02 0b 95 04 7c 02 18 71 03 00 +[ 17075ms] VEN_0488&Col02 len=20: 01 03 b1 03 64 02 07 ca 02 c3 02 0b 94 04 85 02 5e 71 03 00 +[ 17081ms] VEN_0488&Col02 len=20: 01 03 b0 03 6b 02 07 ca 02 cd 02 0b 92 04 8d 02 a4 71 03 00 +[ 17089ms] VEN_0488&Col02 len=20: 01 03 af 03 74 02 07 ca 02 d8 02 0b 90 04 97 02 ea 71 03 00 +[ 17096ms] VEN_0488&Col02 len=20: 01 03 ac 03 7d 02 07 c9 02 e1 02 0b 8d 04 9f 02 30 72 03 00 +[ 17103ms] VEN_0488&Col02 len=20: 01 03 aa 03 85 02 07 c8 02 e9 02 0b 8b 04 a6 02 76 72 03 00 +[ 17110ms] VEN_0488&Col02 len=20: 01 03 a8 03 91 02 07 c6 02 f2 02 0b 8a 04 ae 02 bc 72 03 00 +[ 17117ms] VEN_0488&Col02 len=20: 01 03 a6 03 9b 02 07 c4 02 f9 02 0b 88 04 b5 02 02 73 03 00 +[ 17124ms] VEN_0488&Col02 len=20: 01 03 a5 03 a2 02 07 c3 02 fe 02 0b 86 04 bb 02 48 73 03 00 +[ 17131ms] VEN_0488&Col02 len=20: 01 03 a3 03 ab 02 07 c2 02 08 03 0b 84 04 c3 02 8e 73 03 00 +[ 17139ms] VEN_0488&Col02 len=20: 01 03 a2 03 b2 02 07 c1 02 0f 03 0b 82 04 cb 02 d4 73 03 00 +[ 17146ms] VEN_0488&Col02 len=20: 01 03 a1 03 ba 02 07 c0 02 17 03 0b 81 04 d3 02 1a 74 03 00 +[ 17153ms] VEN_0488&Col02 len=20: 01 03 a0 03 c1 02 07 be 02 1f 03 0b 80 04 d9 02 60 74 03 00 +[ 17160ms] VEN_0488&Col02 len=20: 01 03 9e 03 c9 02 07 bc 02 27 03 0b 7e 04 df 02 a6 74 03 00 +[ 17167ms] VEN_0488&Col02 len=20: 01 03 9d 03 d2 02 07 bb 02 2e 03 0b 7d 04 e5 02 ec 74 03 00 +[ 17174ms] VEN_0488&Col02 len=20: 01 03 9d 03 dc 02 07 ba 02 34 03 0b 7d 04 eb 02 32 75 03 00 +[ 17181ms] VEN_0488&Col02 len=20: 01 03 9c 03 e5 02 07 b9 02 3a 03 0b 7c 04 f1 02 78 75 03 00 +[ 17188ms] VEN_0488&Col02 len=20: 01 03 9a 03 ed 02 07 b8 02 3f 03 0b 7a 04 f7 02 be 75 03 00 +[ 17195ms] VEN_0488&Col02 len=20: 01 03 99 03 f5 02 07 b7 02 45 03 0b 7a 04 fc 02 04 76 03 00 +[ 17202ms] VEN_0488&Col02 len=20: 01 03 98 03 fd 02 07 b6 02 4d 03 0b 7a 04 03 03 4a 76 03 00 +[ 17209ms] VEN_0488&Col02 len=20: 01 03 97 03 07 03 07 b6 02 54 03 0b 7a 04 0a 03 90 76 03 00 +[ 17219ms] VEN_0488&Col02 len=20: 01 03 96 03 13 03 07 b6 02 5d 03 0b 78 04 13 03 d6 76 03 00 +[ 17227ms] VEN_0488&Col02 len=20: 01 03 93 03 1f 03 07 b6 02 65 03 0b 77 04 1b 03 1c 77 03 00 +[ 17231ms] VEN_0488&Col02 len=20: 01 03 90 03 2a 03 07 b6 02 6d 03 0b 76 04 22 03 62 77 03 00 +[ 17238ms] VEN_0488&Col02 len=20: 01 03 90 03 37 03 07 b6 02 77 03 0b 75 04 2b 03 a8 77 03 00 +[ 17245ms] VEN_0488&Col02 len=20: 01 03 8d 03 42 03 07 b6 02 80 03 0b 74 04 34 03 ee 77 03 00 +[ 17252ms] VEN_0488&Col02 len=20: 01 03 8b 03 4e 03 07 b6 02 89 03 0b 73 04 3f 03 34 78 03 00 +[ 17259ms] VEN_0488&Col02 len=20: 01 03 88 03 58 03 07 b7 02 94 03 0b 72 04 4a 03 7a 78 03 00 +[ 17266ms] VEN_0488&Col02 len=20: 01 03 86 03 63 03 07 b7 02 9e 03 0b 71 04 55 03 c0 78 03 00 +[ 17273ms] VEN_0488&Col02 len=20: 01 03 84 03 6e 03 07 b7 02 a7 03 0b 70 04 5d 03 06 79 03 00 +[ 17281ms] VEN_0488&Col02 len=20: 01 01 84 03 6e 03 05 b7 02 a7 03 09 70 04 5d 03 4c 79 03 00 +[ 17732ms] VEN_0488&Col02 len=20: 01 03 5b 04 49 03 00 00 00 00 00 00 00 00 00 00 e9 8a 01 00 +[ 17739ms] VEN_0488&Col02 len=20: 01 03 5b 04 49 03 00 00 00 00 00 00 00 00 00 00 2f 8b 01 00 +[ 17747ms] VEN_0488&Col02 len=20: 01 03 5b 04 49 03 07 70 03 21 03 00 00 00 00 00 75 8b 02 00 +[ 17753ms] VEN_0488&Col02 len=20: 01 03 5b 04 49 03 07 70 03 21 03 0b 9a 02 40 03 bb 8b 03 00 +[ 17760ms] VEN_0488&Col02 len=20: 01 03 5b 04 49 03 07 70 03 21 03 0b 9a 02 40 03 01 8c 03 00 +[ 17768ms] VEN_0488&Col02 len=20: 01 03 5b 04 49 03 07 70 03 21 03 0b 9a 02 40 03 47 8c 03 00 +[ 17775ms] VEN_0488&Col02 len=20: 01 03 5b 04 49 03 07 70 03 21 03 0b 9a 02 40 03 8d 8c 03 00 +[ 17782ms] VEN_0488&Col02 len=20: 01 03 5c 04 47 03 07 71 03 1e 03 0b 9b 02 3d 03 d3 8c 03 00 +[ 17789ms] VEN_0488&Col02 len=20: 01 03 5d 04 43 03 07 74 03 1a 03 0b 9c 02 37 03 19 8d 03 00 +[ 17796ms] VEN_0488&Col02 len=20: 01 03 60 04 3c 03 07 78 03 11 03 0b 9f 02 2b 03 5f 8d 03 00 +[ 17803ms] VEN_0488&Col02 len=20: 01 03 65 04 31 03 07 7f 03 02 03 0b a4 02 1a 03 a5 8d 03 00 +[ 17810ms] VEN_0488&Col02 len=20: 01 03 69 04 28 03 07 84 03 f6 02 0b a9 02 0d 03 eb 8d 03 00 +[ 17817ms] VEN_0488&Col02 len=20: 01 03 69 04 23 03 07 85 03 f2 02 0b ab 02 0a 03 31 8e 03 00 +[ 17824ms] VEN_0488&Col02 len=20: 01 03 6b 04 1e 03 07 86 03 ee 02 0b ad 02 05 03 77 8e 03 00 +[ 17831ms] VEN_0488&Col02 len=20: 01 03 6c 04 19 03 07 87 03 ea 02 0b ae 02 00 03 bd 8e 03 00 +[ 17838ms] VEN_0488&Col02 len=20: 01 03 6d 04 13 03 07 87 03 e6 02 0b ae 02 fd 02 03 8f 03 00 +[ 17845ms] VEN_0488&Col02 len=20: 01 03 70 04 0e 03 07 89 03 e0 02 0b ae 02 f7 02 49 8f 03 00 +[ 17852ms] VEN_0488&Col02 len=20: 01 03 71 04 0a 03 07 8a 03 db 02 0b ae 02 f2 02 8f 8f 03 00 +[ 17859ms] VEN_0488&Col02 len=20: 01 03 70 04 03 03 07 89 03 d4 02 0b ae 02 ec 02 d5 8f 03 00 +[ 17867ms] VEN_0488&Col02 len=20: 01 03 71 04 fe 02 07 89 03 ce 02 0b ae 02 e6 02 1b 90 03 00 +[ 17874ms] VEN_0488&Col02 len=20: 01 03 71 04 f8 02 07 89 03 c8 02 0b ae 02 e0 02 61 90 03 00 +[ 17880ms] VEN_0488&Col02 len=20: 01 03 71 04 f1 02 07 89 03 c1 02 0b ae 02 db 02 a7 90 03 00 +[ 17889ms] VEN_0488&Col02 len=20: 01 03 71 04 ea 02 07 87 03 bb 02 0b ae 02 d4 02 ed 90 03 00 +[ 17895ms] VEN_0488&Col02 len=20: 01 03 71 04 e4 02 07 86 03 b5 02 0b ae 02 ce 02 33 91 03 00 +[ 17902ms] VEN_0488&Col02 len=20: 01 03 71 04 df 02 07 86 03 af 02 0b ae 02 c7 02 79 91 03 00 +[ 17910ms] VEN_0488&Col02 len=20: 01 03 71 04 da 02 07 85 03 a8 02 0b af 02 bf 02 bf 91 03 00 +[ 17916ms] VEN_0488&Col02 len=20: 01 03 71 04 d4 02 07 85 03 a2 02 0b af 02 b8 02 05 92 03 00 +[ 17923ms] VEN_0488&Col02 len=20: 01 03 71 04 ce 02 07 85 03 9d 02 0b af 02 b3 02 4b 92 03 00 +[ 17931ms] VEN_0488&Col02 len=20: 01 03 71 04 c8 02 07 85 03 97 02 0b b0 02 ad 02 91 92 03 00 +[ 17937ms] VEN_0488&Col02 len=20: 01 03 71 04 c3 02 07 85 03 91 02 0b b0 02 a8 02 d7 92 03 00 +[ 17945ms] VEN_0488&Col02 len=20: 01 03 70 04 bc 02 07 85 03 8b 02 0b b0 02 a1 02 1d 93 03 00 +[ 17951ms] VEN_0488&Col02 len=20: 01 03 70 04 b6 02 07 85 03 85 02 0b b0 02 9b 02 63 93 03 00 +[ 17959ms] VEN_0488&Col02 len=20: 01 03 70 04 b2 02 07 85 03 81 02 0b b0 02 96 02 a9 93 03 00 +[ 17966ms] VEN_0488&Col02 len=20: 01 03 70 04 ac 02 07 85 03 7b 02 0b b0 02 90 02 ef 93 03 00 +[ 17972ms] VEN_0488&Col02 len=20: 01 03 70 04 a6 02 07 85 03 77 02 0b b0 02 8c 02 35 94 03 00 +[ 17979ms] VEN_0488&Col02 len=20: 01 03 70 04 a1 02 07 85 03 72 02 0b b0 02 86 02 7b 94 03 00 +[ 17987ms] VEN_0488&Col02 len=20: 01 03 70 04 9c 02 07 85 03 6d 02 0b b0 02 80 02 c1 94 03 00 +[ 17994ms] VEN_0488&Col02 len=20: 01 03 6f 04 97 02 07 85 03 67 02 0b b1 02 7b 02 07 95 03 00 +[ 18000ms] VEN_0488&Col02 len=20: 01 03 6e 04 93 02 07 85 03 63 02 0b b1 02 77 02 4d 95 03 00 +[ 18008ms] VEN_0488&Col02 len=20: 01 03 6e 04 8f 02 07 84 03 60 02 0b b1 02 74 02 93 95 03 00 +[ 18015ms] VEN_0488&Col02 len=20: 01 03 6e 04 8b 02 07 84 03 5c 02 0b b0 02 70 02 d9 95 03 00 +[ 18022ms] VEN_0488&Col02 len=20: 01 03 6e 04 86 02 07 83 03 57 02 0b af 02 6c 02 1f 96 03 00 +[ 18029ms] VEN_0488&Col02 len=20: 01 03 6d 04 82 02 07 82 03 52 02 0b af 02 67 02 65 96 03 00 +[ 18036ms] VEN_0488&Col02 len=20: 01 03 6c 04 7f 02 07 82 03 4d 02 0b ae 02 63 02 ab 96 03 00 +[ 18043ms] VEN_0488&Col02 len=20: 01 03 6b 04 7c 02 07 82 03 49 02 0b ad 02 60 02 f1 96 03 00 +[ 18050ms] VEN_0488&Col02 len=20: 01 03 6b 04 77 02 07 81 03 44 02 0b aa 02 5b 02 37 97 03 00 +[ 18057ms] VEN_0488&Col02 len=20: 01 03 69 04 72 02 07 80 03 40 02 0b a9 02 56 02 7d 97 03 00 +[ 18064ms] VEN_0488&Col02 len=20: 01 03 69 04 6d 02 07 7f 03 3c 02 0b a8 02 51 02 c3 97 03 00 +[ 18071ms] VEN_0488&Col02 len=20: 01 03 69 04 6a 02 07 7d 03 37 02 0b a8 02 4c 02 09 98 03 00 +[ 18079ms] VEN_0488&Col02 len=20: 01 03 68 04 65 02 07 7d 03 33 02 0b a7 02 47 02 4f 98 03 00 +[ 18086ms] VEN_0488&Col02 len=20: 01 03 67 04 61 02 07 7c 03 2f 02 0b a6 02 42 02 95 98 03 00 +[ 18093ms] VEN_0488&Col02 len=20: 01 03 67 04 5d 02 07 7c 03 2b 02 0b a6 02 3e 02 db 98 03 00 +[ 18100ms] VEN_0488&Col02 len=20: 01 03 66 04 58 02 07 7b 03 25 02 0b a6 02 38 02 21 99 03 00 +[ 18107ms] VEN_0488&Col02 len=20: 01 03 66 04 54 02 07 7a 03 20 02 0b a6 02 34 02 67 99 03 00 +[ 18114ms] VEN_0488&Col02 len=20: 01 03 66 04 50 02 07 7a 03 1c 02 0b a6 02 30 02 ad 99 03 00 +[ 18121ms] VEN_0488&Col02 len=20: 01 03 65 04 4b 02 07 79 03 17 02 0b a6 02 2c 02 f3 99 03 00 +[ 18128ms] VEN_0488&Col02 len=20: 01 03 65 04 46 02 07 79 03 12 02 0b a6 02 28 02 39 9a 03 00 +[ 18135ms] VEN_0488&Col02 len=20: 01 03 65 04 42 02 07 79 03 0c 02 0b a6 02 24 02 7f 9a 03 00 +[ 18143ms] VEN_0488&Col02 len=20: 01 03 65 04 3a 02 07 78 03 05 02 0b a6 02 1f 02 c5 9a 03 00 +[ 18149ms] VEN_0488&Col02 len=20: 01 03 65 04 34 02 07 77 03 ff 01 0b a6 02 1a 02 0b 9b 03 00 +[ 18156ms] VEN_0488&Col02 len=20: 01 03 66 04 2e 02 07 77 03 f8 01 0b a6 02 13 02 51 9b 03 00 +[ 18163ms] VEN_0488&Col02 len=20: 01 03 66 04 28 02 07 77 03 f1 01 0b a6 02 0d 02 97 9b 03 00 +[ 18170ms] VEN_0488&Col02 len=20: 01 03 66 04 21 02 07 77 03 eb 01 0b a6 02 07 02 dd 9b 03 00 +[ 18177ms] VEN_0488&Col02 len=20: 01 03 66 04 1a 02 07 78 03 e4 01 0b a6 02 01 02 23 9c 03 00 +[ 18184ms] VEN_0488&Col02 len=20: 01 03 65 04 14 02 07 78 03 dd 01 0b a7 02 f9 01 69 9c 03 00 +[ 18191ms] VEN_0488&Col02 len=20: 01 03 64 04 0e 02 07 77 03 d8 01 0b a8 02 f3 01 af 9c 03 00 +[ 18199ms] VEN_0488&Col02 len=20: 01 03 64 04 09 02 07 76 03 d3 01 0b a9 02 ef 01 f5 9c 03 00 +[ 18206ms] VEN_0488&Col02 len=20: 01 03 64 04 03 02 07 75 03 cc 01 0b a9 02 e9 01 3b 9d 03 00 +[ 18213ms] VEN_0488&Col02 len=20: 01 03 64 04 fc 01 07 74 03 c5 01 0b a9 02 e3 01 81 9d 03 00 +[ 18219ms] VEN_0488&Col02 len=20: 01 03 64 04 f5 01 07 74 03 bc 01 0b a9 02 dd 01 c7 9d 03 00 +[ 18227ms] VEN_0488&Col02 len=20: 01 03 65 04 ee 01 07 74 03 b5 01 0b a9 02 d7 01 0d 9e 03 00 +[ 18234ms] VEN_0488&Col02 len=20: 01 03 65 04 e5 01 07 74 03 ac 01 0b a9 02 cf 01 53 9e 03 00 +[ 18241ms] VEN_0488&Col02 len=20: 01 03 64 04 de 01 07 74 03 a6 01 0b a8 02 c9 01 99 9e 03 00 +[ 18248ms] VEN_0488&Col02 len=20: 01 03 63 04 d7 01 07 74 03 9d 01 0b a8 02 c0 01 df 9e 03 00 +[ 18255ms] VEN_0488&Col02 len=20: 01 03 63 04 d0 01 07 74 03 96 01 0b a8 02 b9 01 25 9f 03 00 +[ 18262ms] VEN_0488&Col02 len=20: 01 03 63 04 c9 01 07 74 03 8e 01 0b a8 02 b2 01 6b 9f 03 00 +[ 18269ms] VEN_0488&Col02 len=20: 01 03 63 04 c2 01 07 72 03 86 01 0b a8 02 aa 01 b1 9f 03 00 +[ 18276ms] VEN_0488&Col02 len=20: 01 03 63 04 bb 01 07 71 03 7e 01 0b a8 02 a4 01 f7 9f 03 00 +[ 18283ms] VEN_0488&Col02 len=20: 01 03 63 04 b4 01 07 71 03 75 01 0b a8 02 9e 01 3d a0 03 00 +[ 18290ms] VEN_0488&Col02 len=20: 01 03 63 04 ac 01 07 72 03 6d 01 0b a8 02 99 01 83 a0 03 00 +[ 18297ms] VEN_0488&Col02 len=20: 01 03 63 04 a5 01 07 73 03 66 01 0b a8 02 93 01 c9 a0 03 00 +[ 18304ms] VEN_0488&Col02 len=20: 01 03 63 04 a0 01 07 73 03 5f 01 0b a8 02 8b 01 0f a1 03 00 +[ 18312ms] VEN_0488&Col02 len=20: 01 03 63 04 98 01 07 73 03 58 01 0b a8 02 84 01 55 a1 03 00 +[ 18319ms] VEN_0488&Col02 len=20: 01 03 63 04 91 01 07 73 03 51 01 0b a8 02 7c 01 9b a1 03 00 +[ 18327ms] VEN_0488&Col02 len=20: 01 03 63 04 8c 01 07 73 03 4b 01 0b a8 02 76 01 e1 a1 03 00 +[ 18333ms] VEN_0488&Col02 len=20: 01 03 62 04 86 01 07 71 03 44 01 0b a9 02 70 01 27 a2 03 00 +[ 18341ms] VEN_0488&Col02 len=20: 01 03 62 04 80 01 07 70 03 3d 01 0b aa 02 69 01 6d a2 03 00 +[ 18348ms] VEN_0488&Col02 len=20: 01 03 62 04 7a 01 07 70 03 37 01 0b aa 02 63 01 b3 a2 03 00 +[ 18355ms] VEN_0488&Col02 len=20: 01 03 63 04 74 01 07 71 03 30 01 0b aa 02 5e 01 f9 a2 03 00 +[ 18362ms] VEN_0488&Col02 len=20: 01 03 63 04 6e 01 07 72 03 2a 01 0b aa 02 59 01 3f a3 03 00 +[ 18369ms] VEN_0488&Col02 len=20: 01 03 63 04 68 01 07 72 03 24 01 0b aa 02 55 01 85 a3 03 00 +[ 18377ms] VEN_0488&Col02 len=20: 01 03 64 04 63 01 07 73 03 1e 01 0b aa 02 4e 01 cb a3 03 00 +[ 18384ms] VEN_0488&Col02 len=20: 01 03 64 04 5c 01 07 73 03 18 01 0b aa 02 48 01 11 a4 03 00 +[ 18391ms] VEN_0488&Col02 len=20: 01 03 63 04 56 01 07 73 03 12 01 0b aa 02 42 01 57 a4 03 00 +[ 18398ms] VEN_0488&Col02 len=20: 01 03 63 04 50 01 07 72 03 0d 01 0b aa 02 3b 01 9d a4 03 00 +[ 18406ms] VEN_0488&Col02 len=20: 01 03 63 04 4a 01 07 71 03 07 01 0b aa 02 34 01 e3 a4 03 00 +[ 18412ms] VEN_0488&Col02 len=20: 01 03 63 04 44 01 07 71 03 01 01 0b aa 02 2e 01 29 a5 03 00 +[ 18420ms] VEN_0488&Col02 len=20: 01 03 63 04 3e 01 07 71 03 fb 00 0b aa 02 28 01 6f a5 03 00 +[ 18427ms] VEN_0488&Col02 len=20: 01 03 63 04 38 01 07 71 03 f5 00 0b aa 02 23 01 b5 a5 03 00 +[ 18434ms] VEN_0488&Col02 len=20: 01 03 63 04 32 01 07 71 03 ef 00 0b aa 02 1d 01 fb a5 03 00 +[ 18442ms] VEN_0488&Col02 len=20: 01 03 63 04 2c 01 07 71 03 ea 00 0b aa 02 19 01 41 a6 03 00 +[ 18448ms] VEN_0488&Col02 len=20: 01 03 63 04 27 01 07 72 03 e5 00 0b aa 02 15 01 87 a6 03 00 +[ 18456ms] VEN_0488&Col02 len=20: 01 03 63 04 22 01 07 72 03 df 00 0b aa 02 10 01 cd a6 03 00 +[ 18463ms] VEN_0488&Col02 len=20: 01 03 62 04 1b 01 07 73 03 da 00 0b aa 02 09 01 13 a7 03 00 +[ 18470ms] VEN_0488&Col02 len=20: 01 03 62 04 16 01 07 73 03 d6 00 0b aa 02 04 01 59 a7 03 00 +[ 18477ms] VEN_0488&Col02 len=20: 01 03 62 04 12 01 07 73 03 d1 00 0b aa 02 00 01 9f a7 03 00 +[ 18484ms] VEN_0488&Col02 len=20: 01 03 62 04 0c 01 07 73 03 cc 00 0b aa 02 f9 00 e5 a7 03 00 +[ 18492ms] VEN_0488&Col02 len=20: 01 03 61 04 07 01 07 72 03 c7 00 0b aa 02 f4 00 2b a8 03 00 +[ 18499ms] VEN_0488&Col02 len=20: 01 03 61 04 03 01 07 72 03 c3 00 0b aa 02 ef 00 71 a8 03 00 +[ 18506ms] VEN_0488&Col02 len=20: 01 03 61 04 fc 00 07 72 03 bf 00 0b aa 02 eb 00 b7 a8 03 00 +[ 18513ms] VEN_0488&Col02 len=20: 01 03 61 04 f7 00 07 72 03 ba 00 0b ab 02 e6 00 fd a8 03 00 +[ 18520ms] VEN_0488&Col02 len=20: 01 03 61 04 f3 00 07 72 03 b7 00 0b ab 02 e2 00 43 a9 03 00 +[ 18528ms] VEN_0488&Col02 len=20: 01 03 61 04 ee 00 07 72 03 b2 00 0b ab 02 dd 00 89 a9 03 00 +[ 18535ms] VEN_0488&Col02 len=20: 01 03 62 04 e9 00 07 72 03 ae 00 0b ab 02 da 00 cf a9 03 00 +[ 18542ms] VEN_0488&Col02 len=20: 01 03 62 04 e4 00 07 72 03 ab 00 0b ab 02 d7 00 15 aa 03 00 +[ 18549ms] VEN_0488&Col02 len=20: 01 03 61 04 e0 00 07 72 03 a6 00 0b ab 02 d4 00 5b aa 03 00 +[ 18557ms] VEN_0488&Col02 len=20: 01 03 61 04 db 00 07 73 03 a1 00 0b ab 02 d0 00 a1 aa 03 00 +[ 18564ms] VEN_0488&Col02 len=20: 01 03 61 04 d6 00 07 73 03 9d 00 0b ab 02 cc 00 e7 aa 03 00 +[ 18571ms] VEN_0488&Col02 len=20: 01 03 61 04 d1 00 07 74 03 98 00 0b ab 02 c7 00 2d ab 03 00 +[ 18578ms] VEN_0488&Col02 len=20: 01 03 60 04 cb 00 07 74 03 92 00 0b ab 02 c2 00 73 ab 03 00 +[ 18585ms] VEN_0488&Col02 len=20: 01 03 60 04 c7 00 07 74 03 8e 00 0b ab 02 bd 00 b9 ab 03 00 +[ 18592ms] VEN_0488&Col02 len=20: 01 03 60 04 c0 00 07 73 03 89 00 0b ab 02 b8 00 ff ab 03 00 +[ 18600ms] VEN_0488&Col02 len=20: 01 03 60 04 bb 00 07 72 03 83 00 0b ab 02 b1 00 45 ac 03 00 +[ 18607ms] VEN_0488&Col02 len=20: 01 03 60 04 b6 00 07 72 03 7e 00 0b ab 02 ac 00 8b ac 03 00 +[ 18614ms] VEN_0488&Col02 len=20: 01 03 61 04 ae 00 07 72 03 76 00 0b ab 02 a6 00 d1 ac 03 00 +[ 18622ms] VEN_0488&Col02 len=20: 01 03 62 04 a7 00 07 73 03 6f 00 0b ab 02 a1 00 17 ad 03 00 +[ 18629ms] VEN_0488&Col02 len=20: 01 03 62 04 a1 00 07 74 03 69 00 0b ab 02 9e 00 5d ad 03 00 +[ 18636ms] VEN_0488&Col02 len=20: 01 03 61 04 98 00 07 75 03 64 00 0b ab 02 9d 00 a3 ad 03 00 +[ 18643ms] VEN_0488&Col02 len=20: 01 01 61 04 98 00 05 75 03 64 00 09 ab 02 9d 00 e9 ad 03 00 +[ 18826ms] VEN_0488&Col02 len=20: 01 03 97 04 85 00 00 00 00 00 00 00 00 00 00 00 51 b5 01 00 +[ 18833ms] VEN_0488&Col02 len=20: 01 03 97 04 85 00 00 00 00 00 00 00 00 00 00 00 97 b5 01 00 +[ 18841ms] VEN_0488&Col02 len=20: 01 03 97 04 85 00 07 b0 03 41 00 00 00 00 00 00 dd b5 02 00 +[ 18848ms] VEN_0488&Col02 len=20: 01 03 97 04 85 00 07 b0 03 41 00 00 00 00 00 00 23 b6 02 00 +[ 18855ms] VEN_0488&Col02 len=20: 01 03 97 04 85 00 07 b0 03 41 00 0b d3 02 b2 00 69 b6 03 00 +[ 18862ms] VEN_0488&Col02 len=20: 01 03 97 04 85 00 07 b0 03 41 00 0b d3 02 b2 00 af b6 03 00 +[ 18870ms] VEN_0488&Col02 len=20: 01 03 97 04 85 00 07 b0 03 41 00 0b d3 02 b2 00 f5 b6 03 00 +[ 18877ms] VEN_0488&Col02 len=20: 01 03 97 04 85 00 07 b0 03 41 00 0b d3 02 b2 00 3b b7 03 00 +[ 18884ms] VEN_0488&Col02 len=20: 01 03 98 04 87 00 07 b1 03 43 00 0b d3 02 b2 00 81 b7 03 00 +[ 18891ms] VEN_0488&Col02 len=20: 01 03 99 04 8a 00 07 b2 03 46 00 0b d3 02 b3 00 c7 b7 03 00 +[ 18898ms] VEN_0488&Col02 len=20: 01 03 9b 04 8f 00 07 b4 03 4c 00 0b d3 02 b6 00 0d b8 03 00 +[ 18905ms] VEN_0488&Col02 len=20: 01 03 9f 04 9b 00 07 b8 03 57 00 0b d4 02 bc 00 53 b8 03 00 +[ 18912ms] VEN_0488&Col02 len=20: 01 03 a2 04 a8 00 07 bb 03 64 00 0b d5 02 c7 00 99 b8 03 00 +[ 18919ms] VEN_0488&Col02 len=20: 01 03 a4 04 b1 00 07 bd 03 6d 00 0b d8 02 d8 00 df b8 03 00 +[ 18926ms] VEN_0488&Col02 len=20: 01 03 a6 04 bc 00 07 bf 03 79 00 0b da 02 e1 00 25 b9 03 00 +[ 18934ms] VEN_0488&Col02 len=20: 01 03 a9 04 c7 00 07 c2 03 84 00 0b dc 02 ea 00 6b b9 03 00 +[ 18941ms] VEN_0488&Col02 len=20: 01 03 aa 04 d0 00 07 c3 03 8d 00 0b dd 02 f4 00 b1 b9 03 00 +[ 18948ms] VEN_0488&Col02 len=20: 01 03 ac 04 d8 00 07 c5 03 96 00 0b df 02 ff 00 f7 b9 03 00 +[ 18954ms] VEN_0488&Col02 len=20: 01 03 ae 04 e1 00 07 c6 03 9f 00 0b e1 02 0b 01 3d ba 03 00 +[ 18962ms] VEN_0488&Col02 len=20: 01 03 af 04 ef 00 07 c8 03 ac 00 0b e2 02 16 01 83 ba 03 00 +[ 18969ms] VEN_0488&Col02 len=20: 01 03 b0 04 fe 00 07 ca 03 bb 00 0b e3 02 23 01 c9 ba 03 00 +[ 18976ms] VEN_0488&Col02 len=20: 01 03 b0 04 0b 01 07 ca 03 c8 00 0b e3 02 2e 01 0f bb 03 00 +[ 18983ms] VEN_0488&Col02 len=20: 01 03 b0 04 18 01 07 c9 03 d5 00 0b e4 02 3d 01 55 bb 03 00 +[ 18991ms] VEN_0488&Col02 len=20: 01 03 b0 04 24 01 07 c9 03 e2 00 0b e4 02 4c 01 9b bb 03 00 +[ 18998ms] VEN_0488&Col02 len=20: 01 03 b0 04 30 01 07 c9 03 f0 00 0b e3 02 5b 01 e1 bb 03 00 +[ 19005ms] VEN_0488&Col02 len=20: 01 03 ad 04 41 01 07 c8 03 01 01 0b e1 02 68 01 27 bc 03 00 +[ 19012ms] VEN_0488&Col02 len=20: 01 03 ac 04 4f 01 07 c6 03 0f 01 0b df 02 75 01 6d bc 03 00 +[ 19019ms] VEN_0488&Col02 len=20: 01 03 ab 04 5b 01 07 c4 03 1a 01 0b dd 02 82 01 b3 bc 03 00 +[ 19026ms] VEN_0488&Col02 len=20: 01 03 a9 04 67 01 07 c3 03 27 01 0b da 02 92 01 f9 bc 03 00 +[ 19033ms] VEN_0488&Col02 len=20: 01 03 a7 04 74 01 07 c1 03 35 01 0b d8 02 a1 01 3f bd 03 00 +[ 19040ms] VEN_0488&Col02 len=20: 01 03 a4 04 82 01 07 bf 03 45 01 0b d5 02 ae 01 85 bd 03 00 +[ 19047ms] VEN_0488&Col02 len=20: 01 03 a1 04 91 01 07 bc 03 52 01 0b d2 02 ba 01 cb bd 03 00 +[ 19054ms] VEN_0488&Col02 len=20: 01 03 9f 04 9d 01 07 ba 03 5e 01 0b d0 02 c7 01 11 be 03 00 +[ 19061ms] VEN_0488&Col02 len=20: 01 03 9c 04 a9 01 07 b8 03 6b 01 0b ce 02 d8 01 57 be 03 00 +[ 19068ms] VEN_0488&Col02 len=20: 01 03 98 04 b4 01 07 b5 03 79 01 0b cb 02 e4 01 9d be 03 00 +[ 19075ms] VEN_0488&Col02 len=20: 01 03 95 04 bf 01 07 b3 03 86 01 0b c9 02 ef 01 e3 be 03 00 +[ 19082ms] VEN_0488&Col02 len=20: 01 03 92 04 cd 01 07 b0 03 95 01 0b c6 02 fc 01 29 bf 03 00 +[ 19090ms] VEN_0488&Col02 len=20: 01 03 8e 04 d9 01 07 ad 03 a1 01 0b c2 02 0b 02 6f bf 03 00 +[ 19097ms] VEN_0488&Col02 len=20: 01 03 8a 04 e3 01 07 ab 03 ac 01 0b bf 02 19 02 b5 bf 03 00 +[ 19104ms] VEN_0488&Col02 len=20: 01 03 88 04 ea 01 07 a9 03 b7 01 0b bd 02 25 02 fb bf 03 00 +[ 19111ms] VEN_0488&Col02 len=20: 01 03 85 04 f4 01 07 a5 03 c4 01 0b bb 02 2f 02 41 c0 03 00 +[ 19118ms] VEN_0488&Col02 len=20: 01 03 82 04 00 02 07 a2 03 d2 01 0b b8 02 3a 02 87 c0 03 00 +[ 19125ms] VEN_0488&Col02 len=20: 01 03 80 04 0e 02 07 a0 03 dd 01 0b b4 02 49 02 cd c0 03 00 +[ 19132ms] VEN_0488&Col02 len=20: 01 03 7d 04 19 02 07 9d 03 e9 01 0b b1 02 57 02 13 c1 03 00 +[ 19139ms] VEN_0488&Col02 len=20: 01 03 7a 04 23 02 07 9a 03 f4 01 0b af 02 63 02 59 c1 03 00 +[ 19146ms] VEN_0488&Col02 len=20: 01 03 78 04 2a 02 07 98 03 ff 01 0b ad 02 6d 02 9f c1 03 00 +[ 19153ms] VEN_0488&Col02 len=20: 01 03 76 04 33 02 07 96 03 0c 02 0b ab 02 77 02 e5 c1 03 00 +[ 19160ms] VEN_0488&Col02 len=20: 01 03 75 04 3c 02 07 93 03 17 02 0b a8 02 80 02 2b c2 03 00 +[ 19167ms] VEN_0488&Col02 len=20: 01 03 73 04 46 02 07 90 03 21 02 0b a5 02 8a 02 71 c2 03 00 +[ 19174ms] VEN_0488&Col02 len=20: 01 03 71 04 50 02 07 8e 03 2a 02 0b a3 02 96 02 b7 c2 03 00 +[ 19181ms] VEN_0488&Col02 len=20: 01 03 70 04 58 02 07 8c 03 31 02 0b a2 02 a0 02 fd c2 03 00 +[ 19189ms] VEN_0488&Col02 len=20: 01 03 6f 04 61 02 07 8c 03 3b 02 0b a1 02 aa 02 43 c3 03 00 +[ 19196ms] VEN_0488&Col02 len=20: 01 03 6e 04 69 02 07 8a 03 47 02 0b 9f 02 b3 02 89 c3 03 00 +[ 19203ms] VEN_0488&Col02 len=20: 01 03 6d 04 70 02 07 88 03 53 02 0b 9e 02 bb 02 cf c3 03 00 +[ 19210ms] VEN_0488&Col02 len=20: 01 03 6b 04 7b 02 07 86 03 60 02 0b 9e 02 c8 02 15 c4 03 00 +[ 19217ms] VEN_0488&Col02 len=20: 01 03 69 04 85 02 07 84 03 6c 02 0b 9d 02 d5 02 5b c4 03 00 +[ 19224ms] VEN_0488&Col02 len=20: 01 03 67 04 90 02 07 82 03 77 02 0b 9b 02 e3 02 a1 c4 03 00 +[ 19231ms] VEN_0488&Col02 len=20: 01 03 66 04 9b 02 07 80 03 86 02 0b 99 02 f0 02 e7 c4 03 00 +[ 19238ms] VEN_0488&Col02 len=20: 01 03 64 04 a5 02 07 7d 03 94 02 0b 97 02 fb 02 2d c5 03 00 +[ 19245ms] VEN_0488&Col02 len=20: 01 03 62 04 b0 02 07 7a 03 a4 02 0b 96 02 07 03 73 c5 03 00 +[ 19252ms] VEN_0488&Col02 len=20: 01 03 61 04 ba 02 07 77 03 b2 02 0b 95 02 13 03 b9 c5 03 00 +[ 19259ms] VEN_0488&Col02 len=20: 01 03 5f 04 c5 02 07 75 03 c1 02 0b 93 02 1f 03 ff c5 03 00 +[ 19266ms] VEN_0488&Col02 len=20: 01 03 5f 04 d0 02 07 73 03 d0 02 0b 92 02 2a 03 45 c6 03 00 +[ 19273ms] VEN_0488&Col02 len=20: 01 03 5f 04 dc 02 07 71 03 e3 02 0b 92 02 34 03 8b c6 03 00 +[ 19280ms] VEN_0488&Col02 len=20: 01 03 5e 04 e7 02 07 70 03 f4 02 0b 93 02 3f 03 d1 c6 03 00 +[ 19288ms] VEN_0488&Col02 len=20: 01 03 5e 04 f4 02 07 70 03 06 03 0b 95 02 49 03 17 c7 03 00 +[ 19294ms] VEN_0488&Col02 len=20: 01 01 5e 04 f4 02 05 70 03 06 03 09 95 02 49 03 5d c7 03 00 +[ 19563ms] VEN_0488&Col02 len=20: 01 03 28 04 d2 01 00 00 00 00 00 00 00 00 00 00 ce d1 01 00 +[ 19570ms] VEN_0488&Col02 len=20: 01 03 28 04 d2 01 00 00 00 00 00 00 00 00 00 00 14 d2 01 00 +[ 19577ms] VEN_0488&Col02 len=20: 01 03 28 04 d2 01 00 00 00 00 00 00 00 00 00 00 5a d2 01 00 +[ 19584ms] VEN_0488&Col02 len=20: 01 03 28 04 d2 01 00 00 00 00 00 00 00 00 00 00 a0 d2 01 00 +[ 19591ms] VEN_0488&Col02 len=20: 01 03 26 04 d3 01 00 00 00 00 00 00 00 00 00 00 e6 d2 01 00 +[ 19599ms] VEN_0488&Col02 len=20: 01 03 1d 04 d6 01 00 00 00 00 00 00 00 00 00 00 2c d3 01 00 +[ 19606ms] VEN_0488&Col02 len=20: 01 03 08 04 db 01 00 00 00 00 00 00 00 00 00 00 72 d3 01 00 +[ 19613ms] VEN_0488&Col02 len=20: 01 03 fb 03 de 01 00 00 00 00 00 00 00 00 00 00 b8 d3 01 00 +[ 19620ms] VEN_0488&Col02 len=20: 01 03 ed 03 e0 01 00 00 00 00 00 00 00 00 00 00 fe d3 01 00 +[ 19627ms] VEN_0488&Col02 len=20: 01 03 e4 03 e2 01 00 00 00 00 00 00 00 00 00 00 44 d4 01 00 +[ 19634ms] VEN_0488&Col02 len=20: 01 03 d7 03 e4 01 00 00 00 00 00 00 00 00 00 00 8a d4 01 00 +[ 19641ms] VEN_0488&Col02 len=20: 01 03 c7 03 e5 01 00 00 00 00 00 00 00 00 00 00 d0 d4 01 00 +[ 19648ms] VEN_0488&Col02 len=20: 01 03 bc 03 e7 01 00 00 00 00 00 00 00 00 00 00 16 d5 01 00 +[ 19656ms] VEN_0488&Col02 len=20: 01 03 b1 03 e9 01 00 00 00 00 00 00 00 00 00 00 5c d5 01 00 +[ 19663ms] VEN_0488&Col02 len=20: 01 03 a6 03 eb 01 00 00 00 00 00 00 00 00 00 00 a2 d5 01 00 +[ 19670ms] VEN_0488&Col02 len=20: 01 03 97 03 ec 01 00 00 00 00 00 00 00 00 00 00 e8 d5 01 00 +[ 19677ms] VEN_0488&Col02 len=20: 01 03 88 03 ed 01 00 00 00 00 00 00 00 00 00 00 2e d6 01 00 +[ 19684ms] VEN_0488&Col02 len=20: 01 03 7c 03 ef 01 00 00 00 00 00 00 00 00 00 00 74 d6 01 00 +[ 19691ms] VEN_0488&Col02 len=20: 01 03 6f 03 f1 01 00 00 00 00 00 00 00 00 00 00 ba d6 01 00 +[ 19698ms] VEN_0488&Col02 len=20: 01 03 61 03 f2 01 00 00 00 00 00 00 00 00 00 00 00 d7 01 00 +[ 19705ms] VEN_0488&Col02 len=20: 01 03 51 03 f3 01 00 00 00 00 00 00 00 00 00 00 46 d7 01 00 +[ 19712ms] VEN_0488&Col02 len=20: 01 03 42 03 f3 01 00 00 00 00 00 00 00 00 00 00 8c d7 01 00 +[ 19719ms] VEN_0488&Col02 len=20: 01 03 35 03 f4 01 00 00 00 00 00 00 00 00 00 00 d2 d7 01 00 +[ 19726ms] VEN_0488&Col02 len=20: 01 03 26 03 f5 01 00 00 00 00 00 00 00 00 00 00 18 d8 01 00 +[ 19733ms] VEN_0488&Col02 len=20: 01 03 16 03 f6 01 00 00 00 00 00 00 00 00 00 00 5e d8 01 00 +[ 19740ms] VEN_0488&Col02 len=20: 01 03 07 03 f6 01 00 00 00 00 00 00 00 00 00 00 a4 d8 01 00 +[ 19747ms] VEN_0488&Col02 len=20: 01 03 fa 02 f7 01 00 00 00 00 00 00 00 00 00 00 ea d8 01 00 +[ 19754ms] VEN_0488&Col02 len=20: 01 03 ee 02 f7 01 00 00 00 00 00 00 00 00 00 00 30 d9 01 00 +[ 19761ms] VEN_0488&Col02 len=20: 01 03 dd 02 f8 01 00 00 00 00 00 00 00 00 00 00 76 d9 01 00 +[ 19768ms] VEN_0488&Col02 len=20: 01 03 ce 02 f8 01 00 00 00 00 00 00 00 00 00 00 bc d9 01 00 +[ 19775ms] VEN_0488&Col02 len=20: 01 03 bf 02 f8 01 00 00 00 00 00 00 00 00 00 00 02 da 01 00 +[ 19782ms] VEN_0488&Col02 len=20: 01 03 b2 02 f8 01 00 00 00 00 00 00 00 00 00 00 48 da 01 00 +[ 19789ms] VEN_0488&Col02 len=20: 01 03 a2 02 f8 01 00 00 00 00 00 00 00 00 00 00 8e da 01 00 +[ 19797ms] VEN_0488&Col02 len=20: 01 03 94 02 f8 01 00 00 00 00 00 00 00 00 00 00 d4 da 01 00 +[ 19803ms] VEN_0488&Col02 len=20: 01 03 88 02 f8 01 00 00 00 00 00 00 00 00 00 00 1a db 01 00 +[ 19811ms] VEN_0488&Col02 len=20: 01 03 7d 02 f7 01 00 00 00 00 00 00 00 00 00 00 60 db 01 00 +[ 19818ms] VEN_0488&Col02 len=20: 01 03 70 02 f7 01 00 00 00 00 00 00 00 00 00 00 a6 db 01 00 +[ 19825ms] VEN_0488&Col02 len=20: 01 03 65 02 f6 01 00 00 00 00 00 00 00 00 00 00 ec db 01 00 +[ 19832ms] VEN_0488&Col02 len=20: 01 03 59 02 f6 01 00 00 00 00 00 00 00 00 00 00 32 dc 01 00 +[ 19839ms] VEN_0488&Col02 len=20: 01 03 4d 02 f5 01 00 00 00 00 00 00 00 00 00 00 78 dc 01 00 +[ 19846ms] VEN_0488&Col02 len=20: 01 03 43 02 f5 01 00 00 00 00 00 00 00 00 00 00 be dc 01 00 +[ 19853ms] VEN_0488&Col02 len=20: 01 03 39 02 f5 01 00 00 00 00 00 00 00 00 00 00 04 dd 01 00 +[ 19860ms] VEN_0488&Col02 len=20: 01 03 2c 02 f5 01 00 00 00 00 00 00 00 00 00 00 4a dd 01 00 +[ 19867ms] VEN_0488&Col02 len=20: 01 03 23 02 f5 01 00 00 00 00 00 00 00 00 00 00 90 dd 01 00 +[ 19874ms] VEN_0488&Col02 len=20: 01 03 18 02 f4 01 00 00 00 00 00 00 00 00 00 00 d6 dd 01 00 +[ 19882ms] VEN_0488&Col02 len=20: 01 03 0d 02 f2 01 00 00 00 00 00 00 00 00 00 00 1c de 01 00 +[ 19889ms] VEN_0488&Col02 len=20: 01 03 04 02 f2 01 00 00 00 00 00 00 00 00 00 00 62 de 01 00 +[ 19896ms] VEN_0488&Col02 len=20: 01 03 fb 01 f2 01 00 00 00 00 00 00 00 00 00 00 a8 de 01 00 +[ 19903ms] VEN_0488&Col02 len=20: 01 03 f0 01 f1 01 00 00 00 00 00 00 00 00 00 00 ee de 01 00 +[ 19910ms] VEN_0488&Col02 len=20: 01 03 e5 01 f1 01 00 00 00 00 00 00 00 00 00 00 34 df 01 00 +[ 19917ms] VEN_0488&Col02 len=20: 01 03 d9 01 f0 01 00 00 00 00 00 00 00 00 00 00 7a df 01 00 +[ 19924ms] VEN_0488&Col02 len=20: 01 03 cf 01 ef 01 00 00 00 00 00 00 00 00 00 00 c0 df 01 00 +[ 19931ms] VEN_0488&Col02 len=20: 01 03 c7 01 ef 01 00 00 00 00 00 00 00 00 00 00 06 e0 01 00 +[ 19938ms] VEN_0488&Col02 len=20: 01 03 be 01 ef 01 00 00 00 00 00 00 00 00 00 00 4c e0 01 00 +[ 19945ms] VEN_0488&Col02 len=20: 01 03 b4 01 ef 01 00 00 00 00 00 00 00 00 00 00 92 e0 01 00 +[ 19952ms] VEN_0488&Col02 len=20: 01 03 a9 01 ee 01 00 00 00 00 00 00 00 00 00 00 d8 e0 01 00 +[ 19959ms] VEN_0488&Col02 len=20: 01 03 9e 01 ee 01 00 00 00 00 00 00 00 00 00 00 1e e1 01 00 +[ 19966ms] VEN_0488&Col02 len=20: 01 03 93 01 ee 01 00 00 00 00 00 00 00 00 00 00 64 e1 01 00 +[ 19973ms] VEN_0488&Col02 len=20: 01 03 8a 01 ee 01 00 00 00 00 00 00 00 00 00 00 aa e1 01 00 +[ 19980ms] VEN_0488&Col02 len=20: 01 03 81 01 ee 01 00 00 00 00 00 00 00 00 00 00 f0 e1 01 00 +[ 19987ms] VEN_0488&Col02 len=20: 01 03 77 01 ef 01 00 00 00 00 00 00 00 00 00 00 36 e2 01 00 +[ 19994ms] VEN_0488&Col02 len=20: 01 03 6e 01 ef 01 00 00 00 00 00 00 00 00 00 00 7c e2 01 00 +[ 20001ms] VEN_0488&Col02 len=20: 01 03 64 01 f0 01 00 00 00 00 00 00 00 00 00 00 c2 e2 01 00 +[ 20008ms] VEN_0488&Col02 len=20: 01 03 5b 01 f0 01 00 00 00 00 00 00 00 00 00 00 08 e3 01 00 +[ 20016ms] VEN_0488&Col02 len=20: 01 03 53 01 f1 01 00 00 00 00 00 00 00 00 00 00 4e e3 01 00 +[ 20023ms] VEN_0488&Col02 len=20: 01 03 4d 01 f1 01 00 00 00 00 00 00 00 00 00 00 94 e3 01 00 +[ 20030ms] VEN_0488&Col02 len=20: 01 03 4c 01 f1 01 00 00 00 00 00 00 00 00 00 00 da e3 01 00 +[ 20037ms] VEN_0488&Col02 len=20: 01 03 4a 01 f1 01 00 00 00 00 00 00 00 00 00 00 20 e4 01 00 +[ 20044ms] VEN_0488&Col02 len=20: 01 03 4a 01 f1 01 00 00 00 00 00 00 00 00 00 00 66 e4 01 00 +[ 20051ms] VEN_0488&Col02 len=20: 01 03 4a 01 f1 01 00 00 00 00 00 00 00 00 00 00 ac e4 01 00 +[ 20058ms] VEN_0488&Col02 len=20: 01 03 4a 01 f1 01 00 00 00 00 00 00 00 00 00 00 f2 e4 01 00 +[ 20065ms] VEN_0488&Col02 len=20: 01 03 4b 01 f1 01 00 00 00 00 00 00 00 00 00 00 38 e5 01 00 +[ 20072ms] VEN_0488&Col02 len=20: 01 03 4b 01 f1 01 00 00 00 00 00 00 00 00 00 00 7e e5 01 00 +[ 20079ms] VEN_0488&Col02 len=20: 01 03 4c 01 f1 01 00 00 00 00 00 00 00 00 00 00 c4 e5 01 00 +[ 20086ms] VEN_0488&Col02 len=20: 01 03 4c 01 f1 01 00 00 00 00 00 00 00 00 00 00 0a e6 01 00 +[ 20094ms] VEN_0488&Col02 len=20: 01 03 4d 01 f1 01 00 00 00 00 00 00 00 00 00 00 50 e6 01 00 +[ 20101ms] VEN_0488&Col02 len=20: 01 03 4f 01 f1 01 00 00 00 00 00 00 00 00 00 00 96 e6 01 00 +[ 20107ms] VEN_0488&Col02 len=20: 01 03 54 01 f0 01 00 00 00 00 00 00 00 00 00 00 dc e6 01 00 +[ 20114ms] VEN_0488&Col02 len=20: 01 03 5d 01 ef 01 00 00 00 00 00 00 00 00 00 00 22 e7 01 00 +[ 20121ms] VEN_0488&Col02 len=20: 01 03 6d 01 ed 01 00 00 00 00 00 00 00 00 00 00 68 e7 01 00 +[ 20129ms] VEN_0488&Col02 len=20: 01 03 7e 01 eb 01 00 00 00 00 00 00 00 00 00 00 ae e7 01 00 +[ 20136ms] VEN_0488&Col02 len=20: 01 03 8d 01 e8 01 00 00 00 00 00 00 00 00 00 00 f4 e7 01 00 +[ 20143ms] VEN_0488&Col02 len=20: 01 03 9a 01 e7 01 00 00 00 00 00 00 00 00 00 00 3a e8 01 00 +[ 20150ms] VEN_0488&Col02 len=20: 01 03 ac 01 e6 01 00 00 00 00 00 00 00 00 00 00 80 e8 01 00 +[ 20157ms] VEN_0488&Col02 len=20: 01 03 bf 01 e4 01 00 00 00 00 00 00 00 00 00 00 c6 e8 01 00 +[ 20164ms] VEN_0488&Col02 len=20: 01 03 d1 01 e3 01 00 00 00 00 00 00 00 00 00 00 0c e9 01 00 +[ 20171ms] VEN_0488&Col02 len=20: 01 03 e6 01 e1 01 00 00 00 00 00 00 00 00 00 00 52 e9 01 00 +[ 20178ms] VEN_0488&Col02 len=20: 01 03 fd 01 e0 01 00 00 00 00 00 00 00 00 00 00 98 e9 01 00 +[ 20185ms] VEN_0488&Col02 len=20: 01 03 12 02 de 01 00 00 00 00 00 00 00 00 00 00 de e9 01 00 +[ 20192ms] VEN_0488&Col02 len=20: 01 03 26 02 dc 01 00 00 00 00 00 00 00 00 00 00 24 ea 01 00 +[ 20199ms] VEN_0488&Col02 len=20: 01 03 3d 02 db 01 00 00 00 00 00 00 00 00 00 00 6a ea 01 00 +[ 20206ms] VEN_0488&Col02 len=20: 01 03 4f 02 d9 01 00 00 00 00 00 00 00 00 00 00 b0 ea 01 00 +[ 20213ms] VEN_0488&Col02 len=20: 01 03 68 02 d8 01 00 00 00 00 00 00 00 00 00 00 f6 ea 01 00 +[ 20220ms] VEN_0488&Col02 len=20: 01 03 80 02 d7 01 00 00 00 00 00 00 00 00 00 00 3c eb 01 00 +[ 20227ms] VEN_0488&Col02 len=20: 01 03 97 02 d5 01 00 00 00 00 00 00 00 00 00 00 82 eb 01 00 +[ 20234ms] VEN_0488&Col02 len=20: 01 03 af 02 d2 01 00 00 00 00 00 00 00 00 00 00 c8 eb 01 00 +[ 20241ms] VEN_0488&Col02 len=20: 01 03 c8 02 cf 01 00 00 00 00 00 00 00 00 00 00 0e ec 01 00 +[ 20248ms] VEN_0488&Col02 len=20: 01 03 e1 02 cd 01 00 00 00 00 00 00 00 00 00 00 54 ec 01 00 +[ 20256ms] VEN_0488&Col02 len=20: 01 03 fb 02 cb 01 00 00 00 00 00 00 00 00 00 00 9a ec 01 00 +[ 20262ms] VEN_0488&Col02 len=20: 01 03 11 03 c8 01 00 00 00 00 00 00 00 00 00 00 e0 ec 01 00 +[ 20270ms] VEN_0488&Col02 len=20: 01 03 2a 03 c5 01 00 00 00 00 00 00 00 00 00 00 26 ed 01 00 +[ 20277ms] VEN_0488&Col02 len=20: 01 03 42 03 c2 01 00 00 00 00 00 00 00 00 00 00 6c ed 01 00 +[ 20284ms] VEN_0488&Col02 len=20: 01 03 5a 03 bf 01 00 00 00 00 00 00 00 00 00 00 b2 ed 01 00 +[ 20291ms] VEN_0488&Col02 len=20: 01 03 74 03 bb 01 00 00 00 00 00 00 00 00 00 00 f8 ed 01 00 +[ 20298ms] VEN_0488&Col02 len=20: 01 03 8a 03 b8 01 00 00 00 00 00 00 00 00 00 00 3e ee 01 00 +[ 20305ms] VEN_0488&Col02 len=20: 01 03 a2 03 b6 01 00 00 00 00 00 00 00 00 00 00 84 ee 01 00 +[ 20312ms] VEN_0488&Col02 len=20: 01 03 ba 03 b2 01 00 00 00 00 00 00 00 00 00 00 ca ee 01 00 +[ 20319ms] VEN_0488&Col02 len=20: 01 03 cd 03 b1 01 00 00 00 00 00 00 00 00 00 00 10 ef 01 00 +[ 20326ms] VEN_0488&Col02 len=20: 01 03 e2 03 af 01 00 00 00 00 00 00 00 00 00 00 56 ef 01 00 +[ 20333ms] VEN_0488&Col02 len=20: 01 03 f7 03 ad 01 00 00 00 00 00 00 00 00 00 00 9c ef 01 00 +[ 20341ms] VEN_0488&Col02 len=20: 01 03 09 04 ab 01 00 00 00 00 00 00 00 00 00 00 e2 ef 01 00 +[ 20348ms] VEN_0488&Col02 len=20: 01 03 1f 04 a9 01 00 00 00 00 00 00 00 00 00 00 28 f0 01 00 +[ 20355ms] VEN_0488&Col02 len=20: 01 03 33 04 a7 01 00 00 00 00 00 00 00 00 00 00 6e f0 01 00 +[ 20362ms] VEN_0488&Col02 len=20: 01 03 41 04 a5 01 00 00 00 00 00 00 00 00 00 00 b4 f0 01 00 +[ 20369ms] VEN_0488&Col02 len=20: 01 03 51 04 a4 01 00 00 00 00 00 00 00 00 00 00 fa f0 01 00 +[ 20376ms] VEN_0488&Col02 len=20: 01 03 5f 04 a3 01 00 00 00 00 00 00 00 00 00 00 40 f1 01 00 +[ 20383ms] VEN_0488&Col02 len=20: 01 03 6c 04 a2 01 00 00 00 00 00 00 00 00 00 00 86 f1 01 00 +[ 20390ms] VEN_0488&Col02 len=20: 01 03 75 04 a1 01 00 00 00 00 00 00 00 00 00 00 cc f1 01 00 +[ 20397ms] VEN_0488&Col02 len=20: 01 03 7c 04 a1 01 00 00 00 00 00 00 00 00 00 00 12 f2 01 00 +[ 20404ms] VEN_0488&Col02 len=20: 01 03 80 04 a1 01 00 00 00 00 00 00 00 00 00 00 58 f2 01 00 +[ 20411ms] VEN_0488&Col02 len=20: 01 03 84 04 a1 01 00 00 00 00 00 00 00 00 00 00 9e f2 01 00 +[ 20418ms] VEN_0488&Col02 len=20: 01 03 84 04 a1 01 00 00 00 00 00 00 00 00 00 00 e4 f2 01 00 +[ 20425ms] VEN_0488&Col02 len=20: 01 03 84 04 a1 01 00 00 00 00 00 00 00 00 00 00 2a f3 01 00 +[ 20432ms] VEN_0488&Col02 len=20: 01 03 84 04 a1 01 00 00 00 00 00 00 00 00 00 00 70 f3 01 00 +[ 20439ms] VEN_0488&Col02 len=20: 01 03 83 04 a2 01 00 00 00 00 00 00 00 00 00 00 b6 f3 01 00 +[ 20447ms] VEN_0488&Col02 len=20: 01 03 82 04 a2 01 00 00 00 00 00 00 00 00 00 00 fc f3 01 00 +[ 20454ms] VEN_0488&Col02 len=20: 01 03 82 04 a3 01 00 00 00 00 00 00 00 00 00 00 42 f4 01 00 +[ 20461ms] VEN_0488&Col02 len=20: 01 03 81 04 a4 01 00 00 00 00 00 00 00 00 00 00 88 f4 01 00 +[ 20468ms] VEN_0488&Col02 len=20: 01 03 7f 04 a4 01 00 00 00 00 00 00 00 00 00 00 ce f4 01 00 +[ 20475ms] VEN_0488&Col02 len=20: 01 03 7e 04 a5 01 00 00 00 00 00 00 00 00 00 00 14 f5 01 00 +[ 20482ms] VEN_0488&Col02 len=20: 01 03 7a 04 a5 01 00 00 00 00 00 00 00 00 00 00 5a f5 01 00 +[ 20489ms] VEN_0488&Col02 len=20: 01 03 75 04 a8 01 00 00 00 00 00 00 00 00 00 00 a0 f5 01 00 +[ 20497ms] VEN_0488&Col02 len=20: 01 03 6a 04 a9 01 00 00 00 00 00 00 00 00 00 00 e6 f5 01 00 +[ 20503ms] VEN_0488&Col02 len=20: 01 03 5b 04 aa 01 00 00 00 00 00 00 00 00 00 00 2c f6 01 00 +[ 20510ms] VEN_0488&Col02 len=20: 01 03 4c 04 ac 01 00 00 00 00 00 00 00 00 00 00 72 f6 01 00 +[ 20517ms] VEN_0488&Col02 len=20: 01 03 3e 04 ad 01 00 00 00 00 00 00 00 00 00 00 b8 f6 01 00 +[ 20524ms] VEN_0488&Col02 len=20: 01 03 33 04 ae 01 00 00 00 00 00 00 00 00 00 00 fe f6 01 00 +[ 20531ms] VEN_0488&Col02 len=20: 01 03 24 04 af 01 00 00 00 00 00 00 00 00 00 00 44 f7 01 00 +[ 20538ms] VEN_0488&Col02 len=20: 01 03 14 04 b1 01 00 00 00 00 00 00 00 00 00 00 8a f7 01 00 +[ 20545ms] VEN_0488&Col02 len=20: 01 03 02 04 b1 01 00 00 00 00 00 00 00 00 00 00 d0 f7 01 00 +[ 20552ms] VEN_0488&Col02 len=20: 01 03 f3 03 b1 01 00 00 00 00 00 00 00 00 00 00 16 f8 01 00 +[ 20559ms] VEN_0488&Col02 len=20: 01 03 e4 03 b2 01 00 00 00 00 00 00 00 00 00 00 5c f8 01 00 +[ 20567ms] VEN_0488&Col02 len=20: 01 03 d3 03 b3 01 00 00 00 00 00 00 00 00 00 00 a2 f8 01 00 +[ 20574ms] VEN_0488&Col02 len=20: 01 03 c0 03 b2 01 00 00 00 00 00 00 00 00 00 00 e8 f8 01 00 +[ 20581ms] VEN_0488&Col02 len=20: 01 03 b0 03 b3 01 00 00 00 00 00 00 00 00 00 00 2e f9 01 00 +[ 20588ms] VEN_0488&Col02 len=20: 01 03 9f 03 b4 01 00 00 00 00 00 00 00 00 00 00 74 f9 01 00 +[ 20595ms] VEN_0488&Col02 len=20: 01 03 8a 03 b4 01 00 00 00 00 00 00 00 00 00 00 ba f9 01 00 +[ 20602ms] VEN_0488&Col02 len=20: 01 03 79 03 b4 01 00 00 00 00 00 00 00 00 00 00 00 fa 01 00 +[ 20609ms] VEN_0488&Col02 len=20: 01 03 67 03 b4 01 00 00 00 00 00 00 00 00 00 00 46 fa 01 00 +[ 20616ms] VEN_0488&Col02 len=20: 01 03 53 03 b5 01 00 00 00 00 00 00 00 00 00 00 8c fa 01 00 +[ 20623ms] VEN_0488&Col02 len=20: 01 03 3f 03 b5 01 00 00 00 00 00 00 00 00 00 00 d2 fa 01 00 +[ 20630ms] VEN_0488&Col02 len=20: 01 03 2d 03 b5 01 00 00 00 00 00 00 00 00 00 00 18 fb 01 00 +[ 20637ms] VEN_0488&Col02 len=20: 01 03 1a 03 b6 01 00 00 00 00 00 00 00 00 00 00 5e fb 01 00 +[ 20644ms] VEN_0488&Col02 len=20: 01 03 07 03 b7 01 00 00 00 00 00 00 00 00 00 00 a4 fb 01 00 +[ 20651ms] VEN_0488&Col02 len=20: 01 03 f6 02 b7 01 00 00 00 00 00 00 00 00 00 00 ea fb 01 00 +[ 20658ms] VEN_0488&Col02 len=20: 01 03 e4 02 b9 01 00 00 00 00 00 00 00 00 00 00 30 fc 01 00 +[ 20665ms] VEN_0488&Col02 len=20: 01 03 d1 02 b9 01 00 00 00 00 00 00 00 00 00 00 76 fc 01 00 +[ 20672ms] VEN_0488&Col02 len=20: 01 03 be 02 b9 01 00 00 00 00 00 00 00 00 00 00 bc fc 01 00 +[ 20679ms] VEN_0488&Col02 len=20: 01 03 ad 02 b9 01 00 00 00 00 00 00 00 00 00 00 02 fd 01 00 +[ 20686ms] VEN_0488&Col02 len=20: 01 03 97 02 ba 01 00 00 00 00 00 00 00 00 00 00 48 fd 01 00 +[ 20694ms] VEN_0488&Col02 len=20: 01 03 84 02 ba 01 00 00 00 00 00 00 00 00 00 00 8e fd 01 00 +[ 20701ms] VEN_0488&Col02 len=20: 01 03 75 02 bb 01 00 00 00 00 00 00 00 00 00 00 d4 fd 01 00 +[ 20708ms] VEN_0488&Col02 len=20: 01 03 63 02 bc 01 00 00 00 00 00 00 00 00 00 00 1a fe 01 00 +[ 20715ms] VEN_0488&Col02 len=20: 01 03 50 02 bd 01 00 00 00 00 00 00 00 00 00 00 60 fe 01 00 +[ 20722ms] VEN_0488&Col02 len=20: 01 03 3d 02 bf 01 00 00 00 00 00 00 00 00 00 00 a6 fe 01 00 +[ 20729ms] VEN_0488&Col02 len=20: 01 03 2c 02 c0 01 00 00 00 00 00 00 00 00 00 00 ec fe 01 00 +[ 20736ms] VEN_0488&Col02 len=20: 01 03 18 02 c2 01 00 00 00 00 00 00 00 00 00 00 32 ff 01 00 +[ 20743ms] VEN_0488&Col02 len=20: 01 03 07 02 c3 01 00 00 00 00 00 00 00 00 00 00 78 ff 01 00 +[ 20750ms] VEN_0488&Col02 len=20: 01 03 f7 01 c6 01 00 00 00 00 00 00 00 00 00 00 be ff 01 00 +[ 20757ms] VEN_0488&Col02 len=20: 01 03 e6 01 c9 01 00 00 00 00 00 00 00 00 00 00 04 00 01 00 +[ 20764ms] VEN_0488&Col02 len=20: 01 03 d4 01 cc 01 00 00 00 00 00 00 00 00 00 00 4a 00 01 00 +[ 20771ms] VEN_0488&Col02 len=20: 01 03 c5 01 ce 01 00 00 00 00 00 00 00 00 00 00 90 00 01 00 +[ 20778ms] VEN_0488&Col02 len=20: 01 03 b7 01 d1 01 00 00 00 00 00 00 00 00 00 00 d6 00 01 00 +[ 20785ms] VEN_0488&Col02 len=20: 01 03 a5 01 d4 01 00 00 00 00 00 00 00 00 00 00 1c 01 01 00 +[ 20792ms] VEN_0488&Col02 len=20: 01 03 93 01 d7 01 00 00 00 00 00 00 00 00 00 00 62 01 01 00 +[ 20800ms] VEN_0488&Col02 len=20: 01 03 84 01 da 01 00 00 00 00 00 00 00 00 00 00 a8 01 01 00 +[ 20807ms] VEN_0488&Col02 len=20: 01 03 73 01 db 01 00 00 00 00 00 00 00 00 00 00 ee 01 01 00 +[ 20814ms] VEN_0488&Col02 len=20: 01 03 61 01 dd 01 00 00 00 00 00 00 00 00 00 00 34 02 01 00 +[ 20821ms] VEN_0488&Col02 len=20: 01 03 53 01 e1 01 00 00 00 00 00 00 00 00 00 00 7a 02 01 00 +[ 20828ms] VEN_0488&Col02 len=20: 01 03 47 01 e4 01 00 00 00 00 00 00 00 00 00 00 c0 02 01 00 +[ 20835ms] VEN_0488&Col02 len=20: 01 03 37 01 e6 01 00 00 00 00 00 00 00 00 00 00 06 03 01 00 +[ 20842ms] VEN_0488&Col02 len=20: 01 03 28 01 e9 01 00 00 00 00 00 00 00 00 00 00 4c 03 01 00 +[ 20849ms] VEN_0488&Col02 len=20: 01 03 1c 01 eb 01 00 00 00 00 00 00 00 00 00 00 92 03 01 00 +[ 20856ms] VEN_0488&Col02 len=20: 01 03 12 01 ee 01 00 00 00 00 00 00 00 00 00 00 d8 03 01 00 +[ 20863ms] VEN_0488&Col02 len=20: 01 03 0a 01 ee 01 00 00 00 00 00 00 00 00 00 00 1e 04 01 00 +[ 20870ms] VEN_0488&Col02 len=20: 01 03 06 01 f2 01 00 00 00 00 00 00 00 00 00 00 64 04 01 00 +[ 20877ms] VEN_0488&Col02 len=20: 01 03 01 01 f3 01 00 00 00 00 00 00 00 00 00 00 aa 04 01 00 +[ 20884ms] VEN_0488&Col02 len=20: 01 03 00 01 f3 01 00 00 00 00 00 00 00 00 00 00 f0 04 01 00 +[ 20891ms] VEN_0488&Col02 len=20: 01 03 00 01 f4 01 00 00 00 00 00 00 00 00 00 00 36 05 01 00 +[ 20898ms] VEN_0488&Col02 len=20: 01 03 00 01 f4 01 00 00 00 00 00 00 00 00 00 00 7c 05 01 00 +[ 20905ms] VEN_0488&Col02 len=20: 01 03 00 01 f4 01 00 00 00 00 00 00 00 00 00 00 c2 05 01 00 +[ 20913ms] VEN_0488&Col02 len=20: 01 03 01 01 f4 01 00 00 00 00 00 00 00 00 00 00 08 06 01 00 +[ 20920ms] VEN_0488&Col02 len=20: 01 03 01 01 f4 01 00 00 00 00 00 00 00 00 00 00 4e 06 01 00 +[ 20927ms] VEN_0488&Col02 len=20: 01 03 02 01 f4 01 00 00 00 00 00 00 00 00 00 00 94 06 01 00 +[ 20934ms] VEN_0488&Col02 len=20: 01 03 04 01 f4 01 00 00 00 00 00 00 00 00 00 00 da 06 01 00 +[ 20941ms] VEN_0488&Col02 len=20: 01 03 07 01 f4 01 00 00 00 00 00 00 00 00 00 00 20 07 01 00 +[ 20948ms] VEN_0488&Col02 len=20: 01 03 0b 01 f4 01 00 00 00 00 00 00 00 00 00 00 66 07 01 00 +[ 20955ms] VEN_0488&Col02 len=20: 01 03 14 01 f4 01 00 00 00 00 00 00 00 00 00 00 ac 07 01 00 +[ 20963ms] VEN_0488&Col02 len=20: 01 03 21 01 f4 01 00 00 00 00 00 00 00 00 00 00 f2 07 01 00 +[ 20969ms] VEN_0488&Col02 len=20: 01 03 31 01 f4 01 00 00 00 00 00 00 00 00 00 00 38 08 01 00 +[ 20977ms] VEN_0488&Col02 len=20: 01 03 43 01 f2 01 00 00 00 00 00 00 00 00 00 00 7e 08 01 00 +[ 20983ms] VEN_0488&Col02 len=20: 01 03 52 01 f1 01 00 00 00 00 00 00 00 00 00 00 c4 08 01 00 +[ 20990ms] VEN_0488&Col02 len=20: 01 03 64 01 f0 01 00 00 00 00 00 00 00 00 00 00 0a 09 01 00 +[ 20997ms] VEN_0488&Col02 len=20: 01 03 78 01 ee 01 00 00 00 00 00 00 00 00 00 00 50 09 01 00 +[ 21005ms] VEN_0488&Col02 len=20: 01 03 8c 01 ec 01 00 00 00 00 00 00 00 00 00 00 96 09 01 00 +[ 21011ms] VEN_0488&Col02 len=20: 01 03 9f 01 eb 01 00 00 00 00 00 00 00 00 00 00 dc 09 01 00 +[ 21018ms] VEN_0488&Col02 len=20: 01 03 b3 01 e8 01 00 00 00 00 00 00 00 00 00 00 22 0a 01 00 +[ 21025ms] VEN_0488&Col02 len=20: 01 03 cc 01 e6 01 00 00 00 00 00 00 00 00 00 00 68 0a 01 00 +[ 21032ms] VEN_0488&Col02 len=20: 01 03 e7 01 e5 01 00 00 00 00 00 00 00 00 00 00 ae 0a 01 00 +[ 21040ms] VEN_0488&Col02 len=20: 01 03 03 02 e3 01 00 00 00 00 00 00 00 00 00 00 f4 0a 01 00 +[ 21047ms] VEN_0488&Col02 len=20: 01 03 19 02 e2 01 00 00 00 00 00 00 00 00 00 00 3a 0b 01 00 +[ 21054ms] VEN_0488&Col02 len=20: 01 03 32 02 e0 01 00 00 00 00 00 00 00 00 00 00 80 0b 01 00 +[ 21061ms] VEN_0488&Col02 len=20: 01 03 4a 02 df 01 00 00 00 00 00 00 00 00 00 00 c6 0b 01 00 +[ 21068ms] VEN_0488&Col02 len=20: 01 03 65 02 dd 01 00 00 00 00 00 00 00 00 00 00 0c 0c 01 00 +[ 21075ms] VEN_0488&Col02 len=20: 01 03 80 02 dc 01 00 00 00 00 00 00 00 00 00 00 52 0c 01 00 +[ 21082ms] VEN_0488&Col02 len=20: 01 03 99 02 db 01 00 00 00 00 00 00 00 00 00 00 98 0c 01 00 +[ 21089ms] VEN_0488&Col02 len=20: 01 03 b3 02 da 01 00 00 00 00 00 00 00 00 00 00 de 0c 01 00 +[ 21096ms] VEN_0488&Col02 len=20: 01 03 cc 02 d8 01 00 00 00 00 00 00 00 00 00 00 24 0d 01 00 +[ 21103ms] VEN_0488&Col02 len=20: 01 03 e6 02 d6 01 00 00 00 00 00 00 00 00 00 00 6a 0d 01 00 +[ 21111ms] VEN_0488&Col02 len=20: 01 03 02 03 d5 01 00 00 00 00 00 00 00 00 00 00 b0 0d 01 00 +[ 21118ms] VEN_0488&Col02 len=20: 01 03 1a 03 d2 01 00 00 00 00 00 00 00 00 00 00 f6 0d 01 00 +[ 21125ms] VEN_0488&Col02 len=20: 01 03 36 03 d2 01 00 00 00 00 00 00 00 00 00 00 3c 0e 01 00 +[ 21132ms] VEN_0488&Col02 len=20: 01 03 4c 03 d0 01 00 00 00 00 00 00 00 00 00 00 82 0e 01 00 +[ 21139ms] VEN_0488&Col02 len=20: 01 03 65 03 ce 01 00 00 00 00 00 00 00 00 00 00 c8 0e 01 00 +[ 21146ms] VEN_0488&Col02 len=20: 01 03 7e 03 cd 01 00 00 00 00 00 00 00 00 00 00 0e 0f 01 00 +[ 21153ms] VEN_0488&Col02 len=20: 01 03 94 03 cb 01 00 00 00 00 00 00 00 00 00 00 54 0f 01 00 +[ 21160ms] VEN_0488&Col02 len=20: 01 03 ac 03 ca 01 00 00 00 00 00 00 00 00 00 00 9a 0f 01 00 +[ 21167ms] VEN_0488&Col02 len=20: 01 03 c4 03 c9 01 00 00 00 00 00 00 00 00 00 00 e0 0f 01 00 +[ 21174ms] VEN_0488&Col02 len=20: 01 03 d7 03 c8 01 00 00 00 00 00 00 00 00 00 00 26 10 01 00 +[ 21182ms] VEN_0488&Col02 len=20: 01 03 ed 03 c6 01 00 00 00 00 00 00 00 00 00 00 6c 10 01 00 +[ 21188ms] VEN_0488&Col02 len=20: 01 03 02 04 c5 01 00 00 00 00 00 00 00 00 00 00 b2 10 01 00 +[ 21196ms] VEN_0488&Col02 len=20: 01 03 17 04 c4 01 00 00 00 00 00 00 00 00 00 00 f8 10 01 00 +[ 21202ms] VEN_0488&Col02 len=20: 01 03 2d 04 c3 01 00 00 00 00 00 00 00 00 00 00 3e 11 01 00 +[ 21210ms] VEN_0488&Col02 len=20: 01 03 40 04 c2 01 00 00 00 00 00 00 00 00 00 00 84 11 01 00 +[ 21217ms] VEN_0488&Col02 len=20: 01 03 51 04 c1 01 00 00 00 00 00 00 00 00 00 00 ca 11 01 00 +[ 21224ms] VEN_0488&Col02 len=20: 01 03 62 04 c0 01 00 00 00 00 00 00 00 00 00 00 10 12 01 00 +[ 21231ms] VEN_0488&Col02 len=20: 01 03 72 04 bf 01 00 00 00 00 00 00 00 00 00 00 56 12 01 00 +[ 21238ms] VEN_0488&Col02 len=20: 01 03 7d 04 bf 01 00 00 00 00 00 00 00 00 00 00 9c 12 01 00 +[ 21245ms] VEN_0488&Col02 len=20: 01 03 8b 04 bf 01 00 00 00 00 00 00 00 00 00 00 e2 12 01 00 +[ 21252ms] VEN_0488&Col02 len=20: 01 03 95 04 bf 01 00 00 00 00 00 00 00 00 00 00 28 13 01 00 +[ 21259ms] VEN_0488&Col02 len=20: 01 03 a0 04 be 01 00 00 00 00 00 00 00 00 00 00 6e 13 01 00 +[ 21266ms] VEN_0488&Col02 len=20: 01 03 a7 04 be 01 00 00 00 00 00 00 00 00 00 00 b4 13 01 00 +[ 21274ms] VEN_0488&Col02 len=20: 01 03 ad 04 bd 01 00 00 00 00 00 00 00 00 00 00 fa 13 01 00 +[ 21280ms] VEN_0488&Col02 len=20: 01 03 b2 04 bd 01 00 00 00 00 00 00 00 00 00 00 40 14 01 00 +[ 21287ms] VEN_0488&Col02 len=20: 01 03 b5 04 bd 01 00 00 00 00 00 00 00 00 00 00 86 14 01 00 +[ 21294ms] VEN_0488&Col02 len=20: 01 03 b6 04 bd 01 00 00 00 00 00 00 00 00 00 00 cc 14 01 00 +[ 21302ms] VEN_0488&Col02 len=20: 01 03 b7 04 be 01 00 00 00 00 00 00 00 00 00 00 12 15 01 00 +[ 21309ms] VEN_0488&Col02 len=20: 01 03 b7 04 be 01 00 00 00 00 00 00 00 00 00 00 58 15 01 00 +[ 21316ms] VEN_0488&Col02 len=20: 01 03 b7 04 bf 01 00 00 00 00 00 00 00 00 00 00 9e 15 01 00 +[ 21323ms] VEN_0488&Col02 len=20: 01 03 b6 04 bf 01 00 00 00 00 00 00 00 00 00 00 e4 15 01 00 +[ 21330ms] VEN_0488&Col02 len=20: 01 03 b5 04 bf 01 00 00 00 00 00 00 00 00 00 00 2a 16 01 00 +[ 21337ms] VEN_0488&Col02 len=20: 01 03 b5 04 c0 01 00 00 00 00 00 00 00 00 00 00 70 16 01 00 +[ 21344ms] VEN_0488&Col02 len=20: 01 03 b5 04 c1 01 00 00 00 00 00 00 00 00 00 00 b6 16 01 00 +[ 21351ms] VEN_0488&Col02 len=20: 01 03 b4 04 c2 01 00 00 00 00 00 00 00 00 00 00 fc 16 01 00 +[ 21358ms] VEN_0488&Col02 len=20: 01 03 b3 04 c2 01 00 00 00 00 00 00 00 00 00 00 42 17 01 00 +[ 21365ms] VEN_0488&Col02 len=20: 01 03 b2 04 c3 01 00 00 00 00 00 00 00 00 00 00 88 17 01 00 +[ 21372ms] VEN_0488&Col02 len=20: 01 03 ae 04 c4 01 00 00 00 00 00 00 00 00 00 00 ce 17 01 00 +[ 21379ms] VEN_0488&Col02 len=20: 01 03 a6 04 c5 01 00 00 00 00 00 00 00 00 00 00 14 18 01 00 +[ 21386ms] VEN_0488&Col02 len=20: 01 03 99 04 c8 01 00 00 00 00 00 00 00 00 00 00 5a 18 01 00 +[ 21393ms] VEN_0488&Col02 len=20: 01 03 88 04 cb 01 00 00 00 00 00 00 00 00 00 00 a0 18 01 00 +[ 21401ms] VEN_0488&Col02 len=20: 01 03 7a 04 cb 01 00 00 00 00 00 00 00 00 00 00 e6 18 01 00 +[ 21407ms] VEN_0488&Col02 len=20: 01 03 6e 04 cc 01 00 00 00 00 00 00 00 00 00 00 2c 19 01 00 +[ 21414ms] VEN_0488&Col02 len=20: 01 03 61 04 cd 01 00 00 00 00 00 00 00 00 00 00 72 19 01 00 +[ 21421ms] VEN_0488&Col02 len=20: 01 03 50 04 d0 01 00 00 00 00 00 00 00 00 00 00 b8 19 01 00 +[ 21429ms] VEN_0488&Col02 len=20: 01 03 3f 04 d3 01 00 00 00 00 00 00 00 00 00 00 fe 19 01 00 +[ 21436ms] VEN_0488&Col02 len=20: 01 03 2e 04 d2 01 00 00 00 00 00 00 00 00 00 00 44 1a 01 00 +[ 21443ms] VEN_0488&Col02 len=20: 01 03 1b 04 d5 01 00 00 00 00 00 00 00 00 00 00 8a 1a 01 00 +[ 21450ms] VEN_0488&Col02 len=20: 01 03 06 04 d8 01 00 00 00 00 00 00 00 00 00 00 d0 1a 01 00 +[ 21457ms] VEN_0488&Col02 len=20: 01 03 f4 03 d8 01 00 00 00 00 00 00 00 00 00 00 16 1b 01 00 +[ 21464ms] VEN_0488&Col02 len=20: 01 03 e0 03 d8 01 00 00 00 00 00 00 00 00 00 00 5c 1b 01 00 +[ 21471ms] VEN_0488&Col02 len=20: 01 03 cb 03 db 01 00 00 00 00 00 00 00 00 00 00 a2 1b 01 00 +[ 21478ms] VEN_0488&Col02 len=20: 01 03 b7 03 dd 01 00 00 00 00 00 00 00 00 00 00 e8 1b 01 00 +[ 21485ms] VEN_0488&Col02 len=20: 01 03 a2 03 dd 01 00 00 00 00 00 00 00 00 00 00 2e 1c 01 00 +[ 21492ms] VEN_0488&Col02 len=20: 01 03 8b 03 e0 01 00 00 00 00 00 00 00 00 00 00 74 1c 01 00 +[ 21499ms] VEN_0488&Col02 len=20: 01 03 76 03 e0 01 00 00 00 00 00 00 00 00 00 00 ba 1c 01 00 +[ 21506ms] VEN_0488&Col02 len=20: 01 03 5f 03 e1 01 00 00 00 00 00 00 00 00 00 00 00 1d 01 00 +[ 21513ms] VEN_0488&Col02 len=20: 01 03 44 03 e4 01 00 00 00 00 00 00 00 00 00 00 46 1d 01 00 +[ 21520ms] VEN_0488&Col02 len=20: 01 03 2d 03 e4 01 00 00 00 00 00 00 00 00 00 00 8c 1d 01 00 +[ 21527ms] VEN_0488&Col02 len=20: 01 03 15 03 e5 01 00 00 00 00 00 00 00 00 00 00 d2 1d 01 00 +[ 21535ms] VEN_0488&Col02 len=20: 01 03 fe 02 e8 01 00 00 00 00 00 00 00 00 00 00 18 1e 01 00 +[ 21542ms] VEN_0488&Col02 len=20: 01 03 e5 02 ea 01 00 00 00 00 00 00 00 00 00 00 5e 1e 01 00 +[ 21549ms] VEN_0488&Col02 len=20: 01 03 cc 02 ec 01 00 00 00 00 00 00 00 00 00 00 a4 1e 01 00 +[ 21556ms] VEN_0488&Col02 len=20: 01 03 b6 02 ee 01 00 00 00 00 00 00 00 00 00 00 ea 1e 01 00 +[ 21563ms] VEN_0488&Col02 len=20: 01 03 9d 02 f0 01 00 00 00 00 00 00 00 00 00 00 30 1f 01 00 +[ 21570ms] VEN_0488&Col02 len=20: 01 03 83 02 f0 01 00 00 00 00 00 00 00 00 00 00 76 1f 01 00 +[ 21577ms] VEN_0488&Col02 len=20: 01 03 6e 02 f1 01 00 00 00 00 00 00 00 00 00 00 bc 1f 01 00 +[ 21584ms] VEN_0488&Col02 len=20: 01 03 56 02 f2 01 00 00 00 00 00 00 00 00 00 00 02 20 01 00 +[ 21591ms] VEN_0488&Col02 len=20: 01 03 41 02 f3 01 00 00 00 00 00 00 00 00 00 00 48 20 01 00 +[ 21598ms] VEN_0488&Col02 len=20: 01 03 2c 02 f4 01 00 00 00 00 00 00 00 00 00 00 8e 20 01 00 +[ 21605ms] VEN_0488&Col02 len=20: 01 03 17 02 f5 01 00 00 00 00 00 00 00 00 00 00 d4 20 01 00 +[ 21613ms] VEN_0488&Col02 len=20: 01 03 01 02 f6 01 00 00 00 00 00 00 00 00 00 00 1a 21 01 00 +[ 21619ms] VEN_0488&Col02 len=20: 01 03 ef 01 f9 01 00 00 00 00 00 00 00 00 00 00 60 21 01 00 +[ 21627ms] VEN_0488&Col02 len=20: 01 03 dc 01 f9 01 00 00 00 00 00 00 00 00 00 00 a6 21 01 00 +[ 21634ms] VEN_0488&Col02 len=20: 01 03 cb 01 f9 01 00 00 00 00 00 00 00 00 00 00 ec 21 01 00 +[ 21641ms] VEN_0488&Col02 len=20: 01 03 bc 01 f9 01 00 00 00 00 00 00 00 00 00 00 32 22 01 00 +[ 21648ms] VEN_0488&Col02 len=20: 01 03 ab 01 fc 01 00 00 00 00 00 00 00 00 00 00 78 22 01 00 +[ 21655ms] VEN_0488&Col02 len=20: 01 03 99 01 fc 01 00 00 00 00 00 00 00 00 00 00 be 22 01 00 +[ 21662ms] VEN_0488&Col02 len=20: 01 03 89 01 fc 01 00 00 00 00 00 00 00 00 00 00 04 23 01 00 +[ 21669ms] VEN_0488&Col02 len=20: 01 03 7a 01 fc 01 00 00 00 00 00 00 00 00 00 00 4a 23 01 00 +[ 21676ms] VEN_0488&Col02 len=20: 01 03 6d 01 fd 01 00 00 00 00 00 00 00 00 00 00 90 23 01 00 +[ 21683ms] VEN_0488&Col02 len=20: 01 03 5f 01 fe 01 00 00 00 00 00 00 00 00 00 00 d6 23 01 00 +[ 21690ms] VEN_0488&Col02 len=20: 01 03 52 01 fe 01 00 00 00 00 00 00 00 00 00 00 1c 24 01 00 +[ 21697ms] VEN_0488&Col02 len=20: 01 03 49 01 fe 01 00 00 00 00 00 00 00 00 00 00 62 24 01 00 +[ 21705ms] VEN_0488&Col02 len=20: 01 03 3f 01 fd 01 00 00 00 00 00 00 00 00 00 00 a8 24 01 00 +[ 21711ms] VEN_0488&Col02 len=20: 01 03 36 01 fc 01 00 00 00 00 00 00 00 00 00 00 ee 24 01 00 +[ 21718ms] VEN_0488&Col02 len=20: 01 03 2f 01 fb 01 00 00 00 00 00 00 00 00 00 00 34 25 01 00 +[ 21725ms] VEN_0488&Col02 len=20: 01 03 29 01 fa 01 00 00 00 00 00 00 00 00 00 00 7a 25 01 00 +[ 21732ms] VEN_0488&Col02 len=20: 01 03 25 01 f9 01 00 00 00 00 00 00 00 00 00 00 c0 25 01 00 +[ 21739ms] VEN_0488&Col02 len=20: 01 03 23 01 f8 01 00 00 00 00 00 00 00 00 00 00 06 26 01 00 +[ 21747ms] VEN_0488&Col02 len=20: 01 03 23 01 f7 01 00 00 00 00 00 00 00 00 00 00 4c 26 01 00 +[ 21753ms] VEN_0488&Col02 len=20: 01 03 23 01 f6 01 00 00 00 00 00 00 00 00 00 00 92 26 01 00 +[ 21761ms] VEN_0488&Col02 len=20: 01 03 23 01 f6 01 00 00 00 00 00 00 00 00 00 00 d8 26 01 00 +[ 21768ms] VEN_0488&Col02 len=20: 01 03 23 01 f5 01 00 00 00 00 00 00 00 00 00 00 1e 27 01 00 +[ 21775ms] VEN_0488&Col02 len=20: 01 03 24 01 f5 01 00 00 00 00 00 00 00 00 00 00 64 27 01 00 +[ 21782ms] VEN_0488&Col02 len=20: 01 03 25 01 f5 01 00 00 00 00 00 00 00 00 00 00 aa 27 01 00 +[ 21789ms] VEN_0488&Col02 len=20: 01 03 26 01 f5 01 00 00 00 00 00 00 00 00 00 00 f0 27 01 00 +[ 21796ms] VEN_0488&Col02 len=20: 01 03 29 01 f5 01 00 00 00 00 00 00 00 00 00 00 36 28 01 00 +[ 21803ms] VEN_0488&Col02 len=20: 01 03 29 01 f5 01 00 00 00 00 00 00 00 00 00 00 7c 28 01 00 +[ 21810ms] VEN_0488&Col02 len=20: 01 03 2c 01 f5 01 00 00 00 00 00 00 00 00 00 00 c2 28 01 00 +[ 21817ms] VEN_0488&Col02 len=20: 01 03 34 01 f5 01 00 00 00 00 00 00 00 00 00 00 08 29 01 00 +[ 21824ms] VEN_0488&Col02 len=20: 01 03 40 01 f1 01 00 00 00 00 00 00 00 00 00 00 4e 29 01 00 +[ 21831ms] VEN_0488&Col02 len=20: 01 03 4d 01 f0 01 00 00 00 00 00 00 00 00 00 00 94 29 01 00 +[ 21838ms] VEN_0488&Col02 len=20: 01 03 59 01 f0 01 00 00 00 00 00 00 00 00 00 00 da 29 01 00 +[ 21845ms] VEN_0488&Col02 len=20: 01 03 6a 01 f0 01 00 00 00 00 00 00 00 00 00 00 20 2a 01 00 +[ 21852ms] VEN_0488&Col02 len=20: 01 03 7c 01 ee 01 00 00 00 00 00 00 00 00 00 00 66 2a 01 00 +[ 21859ms] VEN_0488&Col02 len=20: 01 03 8c 01 ec 01 00 00 00 00 00 00 00 00 00 00 ac 2a 01 00 +[ 21866ms] VEN_0488&Col02 len=20: 01 03 9b 01 eb 01 00 00 00 00 00 00 00 00 00 00 f2 2a 01 00 +[ 21873ms] VEN_0488&Col02 len=20: 01 03 b1 01 e9 01 00 00 00 00 00 00 00 00 00 00 38 2b 01 00 +[ 21880ms] VEN_0488&Col02 len=20: 01 03 c6 01 e8 01 00 00 00 00 00 00 00 00 00 00 7e 2b 01 00 +[ 21888ms] VEN_0488&Col02 len=20: 01 03 da 01 e7 01 00 00 00 00 00 00 00 00 00 00 c4 2b 01 00 +[ 21895ms] VEN_0488&Col02 len=20: 01 03 f5 01 e5 01 00 00 00 00 00 00 00 00 00 00 0a 2c 01 00 +[ 21902ms] VEN_0488&Col02 len=20: 01 03 0d 02 e4 01 00 00 00 00 00 00 00 00 00 00 50 2c 01 00 +[ 21909ms] VEN_0488&Col02 len=20: 01 03 23 02 e3 01 00 00 00 00 00 00 00 00 00 00 96 2c 01 00 +[ 21916ms] VEN_0488&Col02 len=20: 01 03 3b 02 e1 01 00 00 00 00 00 00 00 00 00 00 dc 2c 01 00 +[ 21923ms] VEN_0488&Col02 len=20: 01 03 50 02 e0 01 00 00 00 00 00 00 00 00 00 00 22 2d 01 00 +[ 21930ms] VEN_0488&Col02 len=20: 01 03 6b 02 df 01 00 00 00 00 00 00 00 00 00 00 68 2d 01 00 +[ 21937ms] VEN_0488&Col02 len=20: 01 03 83 02 de 01 00 00 00 00 00 00 00 00 00 00 ae 2d 01 00 +[ 21944ms] VEN_0488&Col02 len=20: 01 03 9a 02 dd 01 00 00 00 00 00 00 00 00 00 00 f4 2d 01 00 +[ 21951ms] VEN_0488&Col02 len=20: 01 03 b4 02 dc 01 00 00 00 00 00 00 00 00 00 00 3a 2e 01 00 +[ 21959ms] VEN_0488&Col02 len=20: 01 03 ca 02 da 01 00 00 00 00 00 00 00 00 00 00 80 2e 01 00 +[ 21965ms] VEN_0488&Col02 len=20: 01 03 e2 02 d9 01 00 00 00 00 00 00 00 00 00 00 c6 2e 01 00 +[ 21972ms] VEN_0488&Col02 len=20: 01 03 fc 02 d8 01 00 00 00 00 00 00 00 00 00 00 0c 2f 01 00 +[ 21980ms] VEN_0488&Col02 len=20: 01 03 13 03 d7 01 00 00 00 00 00 00 00 00 00 00 52 2f 01 00 +[ 21987ms] VEN_0488&Col02 len=20: 01 03 2c 03 d6 01 00 00 00 00 00 00 00 00 00 00 98 2f 01 00 +[ 21994ms] VEN_0488&Col02 len=20: 01 03 42 03 d5 01 00 00 00 00 00 00 00 00 00 00 de 2f 01 00 +[ 22001ms] VEN_0488&Col02 len=20: 01 03 5b 03 d3 01 00 00 00 00 00 00 00 00 00 00 24 30 01 00 +[ 22008ms] VEN_0488&Col02 len=20: 01 03 75 03 d3 01 00 00 00 00 00 00 00 00 00 00 6a 30 01 00 +[ 22015ms] VEN_0488&Col02 len=20: 01 03 8a 03 d0 01 00 00 00 00 00 00 00 00 00 00 b0 30 01 00 +[ 22022ms] VEN_0488&Col02 len=20: 01 03 a1 03 d0 01 00 00 00 00 00 00 00 00 00 00 f6 30 01 00 +[ 22029ms] VEN_0488&Col02 len=20: 01 03 b9 03 d0 01 00 00 00 00 00 00 00 00 00 00 3c 31 01 00 +[ 22036ms] VEN_0488&Col02 len=20: 01 03 cf 03 cf 01 00 00 00 00 00 00 00 00 00 00 82 31 01 00 +[ 22043ms] VEN_0488&Col02 len=20: 01 03 e6 03 ce 01 00 00 00 00 00 00 00 00 00 00 c8 31 01 00 +[ 22050ms] VEN_0488&Col02 len=20: 01 03 fd 03 cd 01 00 00 00 00 00 00 00 00 00 00 0e 32 01 00 +[ 22057ms] VEN_0488&Col02 len=20: 01 03 13 04 cd 01 00 00 00 00 00 00 00 00 00 00 54 32 01 00 +[ 22064ms] VEN_0488&Col02 len=20: 01 03 2b 04 cc 01 00 00 00 00 00 00 00 00 00 00 9a 32 01 00 +[ 22072ms] VEN_0488&Col02 len=20: 01 03 41 04 cb 01 00 00 00 00 00 00 00 00 00 00 e0 32 01 00 +[ 22079ms] VEN_0488&Col02 len=20: 01 03 54 04 ca 01 00 00 00 00 00 00 00 00 00 00 26 33 01 00 +[ 22086ms] VEN_0488&Col02 len=20: 01 03 68 04 c9 01 00 00 00 00 00 00 00 00 00 00 6c 33 01 00 +[ 22093ms] VEN_0488&Col02 len=20: 01 03 7b 04 c8 01 00 00 00 00 00 00 00 00 00 00 b2 33 01 00 +[ 22100ms] VEN_0488&Col02 len=20: 01 03 8d 04 c7 01 00 00 00 00 00 00 00 00 00 00 f8 33 01 00 +[ 22107ms] VEN_0488&Col02 len=20: 01 03 a0 04 c4 01 00 00 00 00 00 00 00 00 00 00 3e 34 01 00 +[ 22114ms] VEN_0488&Col02 len=20: 01 03 b3 04 c3 01 00 00 00 00 00 00 00 00 00 00 84 34 01 00 +[ 22121ms] VEN_0488&Col02 len=20: 01 03 c1 04 c3 01 00 00 00 00 00 00 00 00 00 00 ca 34 01 00 +[ 22128ms] VEN_0488&Col02 len=20: 01 03 d2 04 c2 01 00 00 00 00 00 00 00 00 00 00 10 35 01 00 +[ 22135ms] VEN_0488&Col02 len=20: 01 03 e3 04 c1 01 00 00 00 00 00 00 00 00 00 00 56 35 01 00 +[ 22143ms] VEN_0488&Col02 len=20: 01 03 f0 04 c0 01 00 00 00 00 00 00 00 00 00 00 9c 35 01 00 +[ 22149ms] VEN_0488&Col02 len=20: 01 03 fb 04 c0 01 00 00 00 00 00 00 00 00 00 00 e2 35 01 00 +[ 22156ms] VEN_0488&Col02 len=20: 01 03 04 05 c1 01 00 00 00 00 00 00 00 00 00 00 28 36 01 00 +[ 22163ms] VEN_0488&Col02 len=20: 01 03 0b 05 c1 01 00 00 00 00 00 00 00 00 00 00 6e 36 01 00 +[ 22170ms] VEN_0488&Col02 len=20: 01 03 0f 05 c2 01 00 00 00 00 00 00 00 00 00 00 b4 36 01 00 +[ 22177ms] VEN_0488&Col02 len=20: 01 03 0f 05 c2 01 00 00 00 00 00 00 00 00 00 00 fa 36 01 00 +[ 22184ms] VEN_0488&Col02 len=20: 01 03 0f 05 c3 01 00 00 00 00 00 00 00 00 00 00 40 37 01 00 +[ 22191ms] VEN_0488&Col02 len=20: 01 03 0f 05 c4 01 00 00 00 00 00 00 00 00 00 00 86 37 01 00 +[ 22199ms] VEN_0488&Col02 len=20: 01 03 0e 05 c4 01 00 00 00 00 00 00 00 00 00 00 cc 37 01 00 +[ 22206ms] VEN_0488&Col02 len=20: 01 03 0d 05 c5 01 00 00 00 00 00 00 00 00 00 00 12 38 01 00 +[ 22212ms] VEN_0488&Col02 len=20: 01 03 0b 05 c6 01 00 00 00 00 00 00 00 00 00 00 58 38 01 00 +[ 22219ms] VEN_0488&Col02 len=20: 01 03 07 05 c9 01 00 00 00 00 00 00 00 00 00 00 9e 38 01 00 +[ 22227ms] VEN_0488&Col02 len=20: 01 03 fb 04 cc 01 00 00 00 00 00 00 00 00 00 00 e4 38 01 00 +[ 22234ms] VEN_0488&Col02 len=20: 01 03 eb 04 cf 01 00 00 00 00 00 00 00 00 00 00 2a 39 01 00 +[ 22241ms] VEN_0488&Col02 len=20: 01 03 d8 04 d4 01 00 00 00 00 00 00 00 00 00 00 70 39 01 00 +[ 22248ms] VEN_0488&Col02 len=20: 01 03 c2 04 d8 01 00 00 00 00 00 00 00 00 00 00 b6 39 01 00 +[ 22255ms] VEN_0488&Col02 len=20: 01 03 af 04 db 01 00 00 00 00 00 00 00 00 00 00 fc 39 01 00 +[ 22262ms] VEN_0488&Col02 len=20: 01 03 9c 04 db 01 00 00 00 00 00 00 00 00 00 00 42 3a 01 00 +[ 22269ms] VEN_0488&Col02 len=20: 01 03 84 04 de 01 00 00 00 00 00 00 00 00 00 00 88 3a 01 00 +[ 22276ms] VEN_0488&Col02 len=20: 01 03 6f 04 de 01 00 00 00 00 00 00 00 00 00 00 ce 3a 01 00 +[ 22283ms] VEN_0488&Col02 len=20: 01 03 59 04 df 01 00 00 00 00 00 00 00 00 00 00 14 3b 01 00 +[ 22290ms] VEN_0488&Col02 len=20: 01 03 41 04 df 01 00 00 00 00 00 00 00 00 00 00 5a 3b 01 00 +[ 22297ms] VEN_0488&Col02 len=20: 01 03 2c 04 df 01 00 00 00 00 00 00 00 00 00 00 a0 3b 01 00 +[ 22304ms] VEN_0488&Col02 len=20: 01 03 14 04 df 01 00 00 00 00 00 00 00 00 00 00 e6 3b 01 00 +[ 22311ms] VEN_0488&Col02 len=20: 01 03 fc 03 df 01 00 00 00 00 00 00 00 00 00 00 2c 3c 01 00 +[ 22319ms] VEN_0488&Col02 len=20: 01 03 e6 03 df 01 00 00 00 00 00 00 00 00 00 00 72 3c 01 00 +[ 22325ms] VEN_0488&Col02 len=20: 01 03 ce 03 de 01 00 00 00 00 00 00 00 00 00 00 b8 3c 01 00 +[ 22333ms] VEN_0488&Col02 len=20: 01 03 b8 03 dd 01 00 00 00 00 00 00 00 00 00 00 fe 3c 01 00 +[ 22340ms] VEN_0488&Col02 len=20: 01 03 a4 03 da 01 00 00 00 00 00 00 00 00 00 00 44 3d 01 00 +[ 22347ms] VEN_0488&Col02 len=20: 01 03 8c 03 da 01 00 00 00 00 00 00 00 00 00 00 8a 3d 01 00 +[ 22354ms] VEN_0488&Col02 len=20: 01 03 77 03 d9 01 00 00 00 00 00 00 00 00 00 00 d0 3d 01 00 +[ 22361ms] VEN_0488&Col02 len=20: 01 03 60 03 d6 01 00 00 00 00 00 00 00 00 00 00 16 3e 01 00 +[ 22368ms] VEN_0488&Col02 len=20: 01 03 48 03 d6 01 00 00 00 00 00 00 00 00 00 00 5c 3e 01 00 +[ 22375ms] VEN_0488&Col02 len=20: 01 03 33 03 d3 01 00 00 00 00 00 00 00 00 00 00 a2 3e 01 00 +[ 22382ms] VEN_0488&Col02 len=20: 01 03 1e 03 d2 01 00 00 00 00 00 00 00 00 00 00 e8 3e 01 00 +[ 22389ms] VEN_0488&Col02 len=20: 01 03 06 03 d2 01 00 00 00 00 00 00 00 00 00 00 2e 3f 01 00 +[ 22396ms] VEN_0488&Col02 len=20: 01 03 f2 02 cf 01 00 00 00 00 00 00 00 00 00 00 74 3f 01 00 +[ 22403ms] VEN_0488&Col02 len=20: 01 03 dc 02 cf 01 00 00 00 00 00 00 00 00 00 00 ba 3f 01 00 +[ 22410ms] VEN_0488&Col02 len=20: 01 03 c5 02 ce 01 00 00 00 00 00 00 00 00 00 00 00 40 01 00 +[ 22417ms] VEN_0488&Col02 len=20: 01 03 b1 02 cb 01 00 00 00 00 00 00 00 00 00 00 46 40 01 00 +[ 22424ms] VEN_0488&Col02 len=20: 01 03 9b 02 cb 01 00 00 00 00 00 00 00 00 00 00 8c 40 01 00 +[ 22431ms] VEN_0488&Col02 len=20: 01 03 85 02 cb 01 00 00 00 00 00 00 00 00 00 00 d2 40 01 00 +[ 22438ms] VEN_0488&Col02 len=20: 01 03 73 02 c8 01 00 00 00 00 00 00 00 00 00 00 18 41 01 00 +[ 22446ms] VEN_0488&Col02 len=20: 01 03 5f 02 c8 01 00 00 00 00 00 00 00 00 00 00 5e 41 01 00 +[ 22453ms] VEN_0488&Col02 len=20: 01 03 4b 02 c8 01 00 00 00 00 00 00 00 00 00 00 a4 41 01 00 +[ 22460ms] VEN_0488&Col02 len=20: 01 03 3a 02 c8 01 00 00 00 00 00 00 00 00 00 00 ea 41 01 00 +[ 22467ms] VEN_0488&Col02 len=20: 01 03 27 02 c7 01 00 00 00 00 00 00 00 00 00 00 30 42 01 00 +[ 22474ms] VEN_0488&Col02 len=20: 01 03 15 02 c8 01 00 00 00 00 00 00 00 00 00 00 76 42 01 00 +[ 22481ms] VEN_0488&Col02 len=20: 01 03 03 02 c8 01 00 00 00 00 00 00 00 00 00 00 bc 42 01 00 +[ 22488ms] VEN_0488&Col02 len=20: 01 03 f4 01 c8 01 00 00 00 00 00 00 00 00 00 00 02 43 01 00 +[ 22495ms] VEN_0488&Col02 len=20: 01 03 e4 01 c9 01 00 00 00 00 00 00 00 00 00 00 48 43 01 00 +[ 22502ms] VEN_0488&Col02 len=20: 01 03 d4 01 ca 01 00 00 00 00 00 00 00 00 00 00 8e 43 01 00 +[ 22509ms] VEN_0488&Col02 len=20: 01 03 c9 01 cb 01 00 00 00 00 00 00 00 00 00 00 d4 43 01 00 +[ 22516ms] VEN_0488&Col02 len=20: 01 03 be 01 cc 01 00 00 00 00 00 00 00 00 00 00 1a 44 01 00 +[ 22523ms] VEN_0488&Col02 len=20: 01 03 b2 01 cd 01 00 00 00 00 00 00 00 00 00 00 60 44 01 00 +[ 22530ms] VEN_0488&Col02 len=20: 01 03 a6 01 cd 01 00 00 00 00 00 00 00 00 00 00 a6 44 01 00 +[ 22537ms] VEN_0488&Col02 len=20: 01 03 9d 01 ce 01 00 00 00 00 00 00 00 00 00 00 ec 44 01 00 +[ 22544ms] VEN_0488&Col02 len=20: 01 03 96 01 cf 01 00 00 00 00 00 00 00 00 00 00 32 45 01 00 +[ 22551ms] VEN_0488&Col02 len=20: 01 03 91 01 cf 01 00 00 00 00 00 00 00 00 00 00 78 45 01 00 +[ 22559ms] VEN_0488&Col02 len=20: 01 03 8f 01 cf 01 00 00 00 00 00 00 00 00 00 00 be 45 01 00 +[ 22566ms] VEN_0488&Col02 len=20: 01 03 8e 01 d0 01 00 00 00 00 00 00 00 00 00 00 04 46 01 00 +[ 22573ms] VEN_0488&Col02 len=20: 01 03 8e 01 d0 01 00 00 00 00 00 00 00 00 00 00 4a 46 01 00 +[ 22580ms] VEN_0488&Col02 len=20: 01 03 8e 01 d0 01 00 00 00 00 00 00 00 00 00 00 90 46 01 00 +[ 22587ms] VEN_0488&Col02 len=20: 01 03 8e 01 d0 01 00 00 00 00 00 00 00 00 00 00 d6 46 01 00 +[ 22594ms] VEN_0488&Col02 len=20: 01 03 8f 01 d1 01 00 00 00 00 00 00 00 00 00 00 1c 47 01 00 +[ 22601ms] VEN_0488&Col02 len=20: 01 03 90 01 d1 01 00 00 00 00 00 00 00 00 00 00 62 47 01 00 +[ 22608ms] VEN_0488&Col02 len=20: 01 03 91 01 d2 01 00 00 00 00 00 00 00 00 00 00 a8 47 01 00 +[ 22615ms] VEN_0488&Col02 len=20: 01 03 92 01 d2 01 00 00 00 00 00 00 00 00 00 00 ee 47 01 00 +[ 22622ms] VEN_0488&Col02 len=20: 01 03 94 01 d3 01 00 00 00 00 00 00 00 00 00 00 34 48 01 00 +[ 22629ms] VEN_0488&Col02 len=20: 01 03 98 01 d3 01 00 00 00 00 00 00 00 00 00 00 7a 48 01 00 +[ 22636ms] VEN_0488&Col02 len=20: 01 03 a1 01 d5 01 00 00 00 00 00 00 00 00 00 00 c0 48 01 00 +[ 22643ms] VEN_0488&Col02 len=20: 01 03 ac 01 d7 01 00 00 00 00 00 00 00 00 00 00 06 49 01 00 +[ 22650ms] VEN_0488&Col02 len=20: 01 03 bb 01 d9 01 00 00 00 00 00 00 00 00 00 00 4c 49 01 00 +[ 22657ms] VEN_0488&Col02 len=20: 01 03 c9 01 da 01 00 00 00 00 00 00 00 00 00 00 92 49 01 00 +[ 22664ms] VEN_0488&Col02 len=20: 01 03 d7 01 db 01 00 00 00 00 00 00 00 00 00 00 d8 49 01 00 +[ 22671ms] VEN_0488&Col02 len=20: 01 03 eb 01 dc 01 00 00 00 00 00 00 00 00 00 00 1e 4a 01 00 +[ 22679ms] VEN_0488&Col02 len=20: 01 03 fd 01 de 01 00 00 00 00 00 00 00 00 00 00 64 4a 01 00 +[ 22686ms] VEN_0488&Col02 len=20: 01 03 0e 02 df 01 00 00 00 00 00 00 00 00 00 00 aa 4a 01 00 +[ 22692ms] VEN_0488&Col02 len=20: 01 03 22 02 df 01 00 00 00 00 00 00 00 00 00 00 f0 4a 01 00 +[ 22700ms] VEN_0488&Col02 len=20: 01 03 37 02 e1 01 00 00 00 00 00 00 00 00 00 00 36 4b 01 00 +[ 22707ms] VEN_0488&Col02 len=20: 01 03 4c 02 e1 01 00 00 00 00 00 00 00 00 00 00 7c 4b 01 00 +[ 22714ms] VEN_0488&Col02 len=20: 01 03 64 02 e2 01 00 00 00 00 00 00 00 00 00 00 c2 4b 01 00 +[ 22721ms] VEN_0488&Col02 len=20: 01 03 7f 02 e3 01 00 00 00 00 00 00 00 00 00 00 08 4c 01 00 +[ 22728ms] VEN_0488&Col02 len=20: 01 03 96 02 e4 01 00 00 00 00 00 00 00 00 00 00 4e 4c 01 00 +[ 22735ms] VEN_0488&Col02 len=20: 01 03 b0 02 e4 01 00 00 00 00 00 00 00 00 00 00 94 4c 01 00 +[ 22742ms] VEN_0488&Col02 len=20: 01 03 ca 02 e5 01 00 00 00 00 00 00 00 00 00 00 da 4c 01 00 +[ 22749ms] VEN_0488&Col02 len=20: 01 03 e5 02 e5 01 00 00 00 00 00 00 00 00 00 00 20 4d 01 00 +[ 22756ms] VEN_0488&Col02 len=20: 01 03 01 03 e4 01 00 00 00 00 00 00 00 00 00 00 66 4d 01 00 +[ 22763ms] VEN_0488&Col02 len=20: 01 03 1c 03 e4 01 00 00 00 00 00 00 00 00 00 00 ac 4d 01 00 +[ 22770ms] VEN_0488&Col02 len=20: 01 03 39 03 e3 01 00 00 00 00 00 00 00 00 00 00 f2 4d 01 00 +[ 22777ms] VEN_0488&Col02 len=20: 01 03 54 03 e3 01 00 00 00 00 00 00 00 00 00 00 38 4e 01 00 +[ 22784ms] VEN_0488&Col02 len=20: 01 03 71 03 e2 01 00 00 00 00 00 00 00 00 00 00 7e 4e 01 00 +[ 22792ms] VEN_0488&Col02 len=20: 01 03 8c 03 e1 01 00 00 00 00 00 00 00 00 00 00 c4 4e 01 00 +[ 22798ms] VEN_0488&Col02 len=20: 01 03 aa 03 e1 01 00 00 00 00 00 00 00 00 00 00 0a 4f 01 00 +[ 22806ms] VEN_0488&Col02 len=20: 01 03 c4 03 e0 01 00 00 00 00 00 00 00 00 00 00 50 4f 01 00 +[ 22813ms] VEN_0488&Col02 len=20: 01 03 e0 03 e0 01 00 00 00 00 00 00 00 00 00 00 96 4f 01 00 +[ 22820ms] VEN_0488&Col02 len=20: 01 03 fa 03 e0 01 00 00 00 00 00 00 00 00 00 00 dc 4f 01 00 +[ 22827ms] VEN_0488&Col02 len=20: 01 03 15 04 e0 01 00 00 00 00 00 00 00 00 00 00 22 50 01 00 +[ 22834ms] VEN_0488&Col02 len=20: 01 03 33 04 df 01 00 00 00 00 00 00 00 00 00 00 68 50 01 00 +[ 22841ms] VEN_0488&Col02 len=20: 01 03 4c 04 de 01 00 00 00 00 00 00 00 00 00 00 ae 50 01 00 +[ 22848ms] VEN_0488&Col02 len=20: 01 03 65 04 de 01 00 00 00 00 00 00 00 00 00 00 f4 50 01 00 +[ 22855ms] VEN_0488&Col02 len=20: 01 03 7d 04 de 01 00 00 00 00 00 00 00 00 00 00 3a 51 01 00 +[ 22862ms] VEN_0488&Col02 len=20: 01 03 93 04 dd 01 00 00 00 00 00 00 00 00 00 00 80 51 01 00 +[ 22869ms] VEN_0488&Col02 len=20: 01 03 aa 04 dd 01 00 00 00 00 00 00 00 00 00 00 c6 51 01 00 +[ 22876ms] VEN_0488&Col02 len=20: 01 03 be 04 dd 01 00 00 00 00 00 00 00 00 00 00 0c 52 01 00 +[ 22884ms] VEN_0488&Col02 len=20: 01 03 d1 04 dd 01 00 00 00 00 00 00 00 00 00 00 52 52 01 00 +[ 22891ms] VEN_0488&Col02 len=20: 01 03 e6 04 dd 01 00 00 00 00 00 00 00 00 00 00 98 52 01 00 +[ 22898ms] VEN_0488&Col02 len=20: 01 03 f7 04 dd 01 00 00 00 00 00 00 00 00 00 00 de 52 01 00 +[ 22905ms] VEN_0488&Col02 len=20: 01 03 09 05 dd 01 00 00 00 00 00 00 00 00 00 00 24 53 01 00 +[ 22912ms] VEN_0488&Col02 len=20: 01 03 1a 05 dd 01 00 00 00 00 00 00 00 00 00 00 6a 53 01 00 +[ 22919ms] VEN_0488&Col02 len=20: 01 03 29 05 dd 01 00 00 00 00 00 00 00 00 00 00 b0 53 01 00 +[ 22926ms] VEN_0488&Col02 len=20: 01 03 33 05 dd 01 00 00 00 00 00 00 00 00 00 00 f6 53 01 00 +[ 22933ms] VEN_0488&Col02 len=20: 01 03 3d 05 dd 01 00 00 00 00 00 00 00 00 00 00 3c 54 01 00 +[ 22940ms] VEN_0488&Col02 len=20: 01 03 46 05 dd 01 00 00 00 00 00 00 00 00 00 00 82 54 01 00 +[ 22947ms] VEN_0488&Col02 len=20: 01 03 4c 05 dc 01 00 00 00 00 00 00 00 00 00 00 c8 54 01 00 +[ 22954ms] VEN_0488&Col02 len=20: 01 03 51 05 dc 01 00 00 00 00 00 00 00 00 00 00 0e 55 01 00 +[ 22961ms] VEN_0488&Col02 len=20: 01 03 55 05 dc 01 00 00 00 00 00 00 00 00 00 00 54 55 01 00 +[ 22968ms] VEN_0488&Col02 len=20: 01 03 56 05 db 01 00 00 00 00 00 00 00 00 00 00 9a 55 01 00 +[ 22975ms] VEN_0488&Col02 len=20: 01 03 56 05 db 01 00 00 00 00 00 00 00 00 00 00 e0 55 01 00 +[ 22982ms] VEN_0488&Col02 len=20: 01 03 56 05 db 01 00 00 00 00 00 00 00 00 00 00 26 56 01 00 +[ 22989ms] VEN_0488&Col02 len=20: 01 03 56 05 db 01 00 00 00 00 00 00 00 00 00 00 6c 56 01 00 +[ 22996ms] VEN_0488&Col02 len=20: 01 03 55 05 db 01 00 00 00 00 00 00 00 00 00 00 b2 56 01 00 +[ 23003ms] VEN_0488&Col02 len=20: 01 03 55 05 db 01 00 00 00 00 00 00 00 00 00 00 f8 56 01 00 +[ 23011ms] VEN_0488&Col02 len=20: 01 03 54 05 db 01 00 00 00 00 00 00 00 00 00 00 3e 57 01 00 +[ 23017ms] VEN_0488&Col02 len=20: 01 03 53 05 db 01 00 00 00 00 00 00 00 00 00 00 84 57 01 00 +[ 23024ms] VEN_0488&Col02 len=20: 01 03 52 05 db 01 00 00 00 00 00 00 00 00 00 00 ca 57 01 00 +[ 23032ms] VEN_0488&Col02 len=20: 01 03 51 05 dc 01 00 00 00 00 00 00 00 00 00 00 10 58 01 00 +[ 23039ms] VEN_0488&Col02 len=20: 01 03 4f 05 dc 01 00 00 00 00 00 00 00 00 00 00 56 58 01 00 +[ 23046ms] VEN_0488&Col02 len=20: 01 03 4d 05 dc 01 00 00 00 00 00 00 00 00 00 00 9c 58 01 00 +[ 23053ms] VEN_0488&Col02 len=20: 01 03 49 05 db 01 00 00 00 00 00 00 00 00 00 00 e2 58 01 00 +[ 23060ms] VEN_0488&Col02 len=20: 01 03 40 05 da 01 00 00 00 00 00 00 00 00 00 00 28 59 01 00 +[ 23067ms] VEN_0488&Col02 len=20: 01 03 34 05 da 01 00 00 00 00 00 00 00 00 00 00 6e 59 01 00 +[ 23075ms] VEN_0488&Col02 len=20: 01 03 25 05 d9 01 00 00 00 00 00 00 00 00 00 00 b4 59 01 00 +[ 23081ms] VEN_0488&Col02 len=20: 01 03 15 05 d8 01 00 00 00 00 00 00 00 00 00 00 fa 59 01 00 +[ 23088ms] VEN_0488&Col02 len=20: 01 03 04 05 d7 01 00 00 00 00 00 00 00 00 00 00 40 5a 01 00 +[ 23095ms] VEN_0488&Col02 len=20: 01 03 f3 04 d7 01 00 00 00 00 00 00 00 00 00 00 86 5a 01 00 +[ 23102ms] VEN_0488&Col02 len=20: 01 03 e4 04 d6 01 00 00 00 00 00 00 00 00 00 00 cc 5a 01 00 +[ 23109ms] VEN_0488&Col02 len=20: 01 03 cf 04 d5 01 00 00 00 00 00 00 00 00 00 00 12 5b 01 00 +[ 23116ms] VEN_0488&Col02 len=20: 01 03 b9 04 d5 01 00 00 00 00 00 00 00 00 00 00 58 5b 01 00 +[ 23123ms] VEN_0488&Col02 len=20: 01 03 a6 04 d5 01 00 00 00 00 00 00 00 00 00 00 9e 5b 01 00 +[ 23130ms] VEN_0488&Col02 len=20: 01 03 91 04 d5 01 00 00 00 00 00 00 00 00 00 00 e4 5b 01 00 +[ 23138ms] VEN_0488&Col02 len=20: 01 03 7a 04 d5 01 00 00 00 00 00 00 00 00 00 00 2a 5c 01 00 +[ 23144ms] VEN_0488&Col02 len=20: 01 03 66 04 d5 01 00 00 00 00 00 00 00 00 00 00 70 5c 01 00 +[ 23152ms] VEN_0488&Col02 len=20: 01 03 50 04 d4 01 00 00 00 00 00 00 00 00 00 00 b6 5c 01 00 +[ 23159ms] VEN_0488&Col02 len=20: 01 03 3a 04 d4 01 00 00 00 00 00 00 00 00 00 00 fc 5c 01 00 +[ 23166ms] VEN_0488&Col02 len=20: 01 03 24 04 d4 01 00 00 00 00 00 00 00 00 00 00 42 5d 01 00 +[ 23173ms] VEN_0488&Col02 len=20: 01 03 0d 04 d4 01 00 00 00 00 00 00 00 00 00 00 88 5d 01 00 +[ 23180ms] VEN_0488&Col02 len=20: 01 03 f6 03 d3 01 00 00 00 00 00 00 00 00 00 00 ce 5d 01 00 +[ 23187ms] VEN_0488&Col02 len=20: 01 03 e0 03 d2 01 00 00 00 00 00 00 00 00 00 00 14 5e 01 00 +[ 23194ms] VEN_0488&Col02 len=20: 01 03 c8 03 d1 01 00 00 00 00 00 00 00 00 00 00 5a 5e 01 00 +[ 23201ms] VEN_0488&Col02 len=20: 01 03 b2 03 d0 01 00 00 00 00 00 00 00 00 00 00 a0 5e 01 00 +[ 23208ms] VEN_0488&Col02 len=20: 01 03 9c 03 cf 01 00 00 00 00 00 00 00 00 00 00 e6 5e 01 00 +[ 23215ms] VEN_0488&Col02 len=20: 01 03 85 03 cf 01 00 00 00 00 00 00 00 00 00 00 2c 5f 01 00 +[ 23222ms] VEN_0488&Col02 len=20: 01 03 6f 03 cd 01 00 00 00 00 00 00 00 00 00 00 72 5f 01 00 +[ 23229ms] VEN_0488&Col02 len=20: 01 03 57 03 cc 01 00 00 00 00 00 00 00 00 00 00 b8 5f 01 00 +[ 23236ms] VEN_0488&Col02 len=20: 01 03 3f 03 cc 01 00 00 00 00 00 00 00 00 00 00 fe 5f 01 00 +[ 23243ms] VEN_0488&Col02 len=20: 01 03 28 03 cb 01 00 00 00 00 00 00 00 00 00 00 44 60 01 00 +[ 23250ms] VEN_0488&Col02 len=20: 01 03 11 03 ca 01 00 00 00 00 00 00 00 00 00 00 8a 60 01 00 +[ 23257ms] VEN_0488&Col02 len=20: 01 03 fd 02 c9 01 00 00 00 00 00 00 00 00 00 00 d0 60 01 00 +[ 23264ms] VEN_0488&Col02 len=20: 01 03 e5 02 c8 01 00 00 00 00 00 00 00 00 00 00 16 61 01 00 +[ 23272ms] VEN_0488&Col02 len=20: 01 03 cd 02 c7 01 00 00 00 00 00 00 00 00 00 00 5c 61 01 00 +[ 23279ms] VEN_0488&Col02 len=20: 01 03 b4 02 c6 01 00 00 00 00 00 00 00 00 00 00 a2 61 01 00 +[ 23286ms] VEN_0488&Col02 len=20: 01 03 99 02 c6 01 00 00 00 00 00 00 00 00 00 00 e8 61 01 00 +[ 23293ms] VEN_0488&Col02 len=20: 01 03 80 02 c4 01 00 00 00 00 00 00 00 00 00 00 2e 62 01 00 +[ 23300ms] VEN_0488&Col02 len=20: 01 03 6a 02 c3 01 00 00 00 00 00 00 00 00 00 00 74 62 01 00 +[ 23307ms] VEN_0488&Col02 len=20: 01 03 52 02 c2 01 00 00 00 00 00 00 00 00 00 00 ba 62 01 00 +[ 23314ms] VEN_0488&Col02 len=20: 01 03 3b 02 c1 01 00 00 00 00 00 00 00 00 00 00 00 63 01 00 +[ 23321ms] VEN_0488&Col02 len=20: 01 03 25 02 c1 01 00 00 00 00 00 00 00 00 00 00 46 63 01 00 +[ 23328ms] VEN_0488&Col02 len=20: 01 03 0e 02 bf 01 00 00 00 00 00 00 00 00 00 00 8c 63 01 00 +[ 23335ms] VEN_0488&Col02 len=20: 01 03 fa 01 bf 01 00 00 00 00 00 00 00 00 00 00 d2 63 01 00 +[ 23342ms] VEN_0488&Col02 len=20: 01 03 e2 01 c0 01 00 00 00 00 00 00 00 00 00 00 18 64 01 00 +[ 23349ms] VEN_0488&Col02 len=20: 01 03 ce 01 c0 01 00 00 00 00 00 00 00 00 00 00 5e 64 01 00 +[ 23356ms] VEN_0488&Col02 len=20: 01 03 be 01 c0 01 00 00 00 00 00 00 00 00 00 00 a4 64 01 00 +[ 23363ms] VEN_0488&Col02 len=20: 01 03 ab 01 c1 01 00 00 00 00 00 00 00 00 00 00 ea 64 01 00 +[ 23370ms] VEN_0488&Col02 len=20: 01 03 9b 01 c4 01 00 00 00 00 00 00 00 00 00 00 30 65 01 00 +[ 23378ms] VEN_0488&Col02 len=20: 01 03 8d 01 c7 01 00 00 00 00 00 00 00 00 00 00 76 65 01 00 +[ 23385ms] VEN_0488&Col02 len=20: 01 03 81 01 c6 01 00 00 00 00 00 00 00 00 00 00 bc 65 01 00 +[ 23391ms] VEN_0488&Col02 len=20: 01 03 77 01 c7 01 00 00 00 00 00 00 00 00 00 00 02 66 01 00 +[ 23399ms] VEN_0488&Col02 len=20: 01 03 70 01 c7 01 00 00 00 00 00 00 00 00 00 00 48 66 01 00 +[ 23406ms] VEN_0488&Col02 len=20: 01 03 6c 01 c8 01 00 00 00 00 00 00 00 00 00 00 8e 66 01 00 +[ 23413ms] VEN_0488&Col02 len=20: 01 03 6c 01 c8 01 00 00 00 00 00 00 00 00 00 00 d4 66 01 00 +[ 23420ms] VEN_0488&Col02 len=20: 01 03 6c 01 c7 01 00 00 00 00 00 00 00 00 00 00 1a 67 01 00 +[ 23427ms] VEN_0488&Col02 len=20: 01 03 6c 01 c7 01 00 00 00 00 00 00 00 00 00 00 60 67 01 00 +[ 23434ms] VEN_0488&Col02 len=20: 01 03 6d 01 c7 01 00 00 00 00 00 00 00 00 00 00 a6 67 01 00 +[ 23441ms] VEN_0488&Col02 len=20: 01 03 6d 01 c7 01 00 00 00 00 00 00 00 00 00 00 ec 67 01 00 +[ 23448ms] VEN_0488&Col02 len=20: 01 03 6e 01 c7 01 00 00 00 00 00 00 00 00 00 00 32 68 01 00 +[ 23455ms] VEN_0488&Col02 len=20: 01 03 71 01 c7 01 00 00 00 00 00 00 00 00 00 00 78 68 01 00 +[ 23462ms] VEN_0488&Col02 len=20: 01 03 72 01 c7 01 00 00 00 00 00 00 00 00 00 00 be 68 01 00 +[ 23469ms] VEN_0488&Col02 len=20: 01 03 75 01 c7 01 00 00 00 00 00 00 00 00 00 00 04 69 01 00 +[ 23476ms] VEN_0488&Col02 len=20: 01 03 78 01 c7 01 00 00 00 00 00 00 00 00 00 00 4a 69 01 00 +[ 23483ms] VEN_0488&Col02 len=20: 01 03 81 01 c7 01 00 00 00 00 00 00 00 00 00 00 90 69 01 00 +[ 23490ms] VEN_0488&Col02 len=20: 01 03 8e 01 c7 01 00 00 00 00 00 00 00 00 00 00 d6 69 01 00 +[ 23497ms] VEN_0488&Col02 len=20: 01 03 9a 01 c6 01 00 00 00 00 00 00 00 00 00 00 1c 6a 01 00 +[ 23504ms] VEN_0488&Col02 len=20: 01 03 ab 01 c6 01 00 00 00 00 00 00 00 00 00 00 62 6a 01 00 +[ 23512ms] VEN_0488&Col02 len=20: 01 03 bc 01 c6 01 00 00 00 00 00 00 00 00 00 00 a8 6a 01 00 +[ 23519ms] VEN_0488&Col02 len=20: 01 03 cf 01 c6 01 00 00 00 00 00 00 00 00 00 00 ee 6a 01 00 +[ 23526ms] VEN_0488&Col02 len=20: 01 03 e0 01 c6 01 00 00 00 00 00 00 00 00 00 00 34 6b 01 00 +[ 23533ms] VEN_0488&Col02 len=20: 01 03 f6 01 c5 01 00 00 00 00 00 00 00 00 00 00 7a 6b 01 00 +[ 23540ms] VEN_0488&Col02 len=20: 01 03 0b 02 c5 01 00 00 00 00 00 00 00 00 00 00 c0 6b 01 00 +[ 23547ms] VEN_0488&Col02 len=20: 01 03 1c 02 c5 01 00 00 00 00 00 00 00 00 00 00 06 6c 01 00 +[ 23554ms] VEN_0488&Col02 len=20: 01 03 32 02 c4 01 00 00 00 00 00 00 00 00 00 00 4c 6c 01 00 +[ 23561ms] VEN_0488&Col02 len=20: 01 03 44 02 c3 01 00 00 00 00 00 00 00 00 00 00 92 6c 01 00 +[ 23568ms] VEN_0488&Col02 len=20: 01 03 57 02 c3 01 00 00 00 00 00 00 00 00 00 00 d8 6c 01 00 +[ 23575ms] VEN_0488&Col02 len=20: 01 03 70 02 c3 01 00 00 00 00 00 00 00 00 00 00 1e 6d 01 00 +[ 23582ms] VEN_0488&Col02 len=20: 01 03 86 02 c2 01 00 00 00 00 00 00 00 00 00 00 64 6d 01 00 +[ 23589ms] VEN_0488&Col02 len=20: 01 03 9b 02 c2 01 00 00 00 00 00 00 00 00 00 00 aa 6d 01 00 +[ 23596ms] VEN_0488&Col02 len=20: 01 03 b3 02 c1 01 00 00 00 00 00 00 00 00 00 00 f0 6d 01 00 +[ 23603ms] VEN_0488&Col02 len=20: 01 03 cc 02 bf 01 00 00 00 00 00 00 00 00 00 00 36 6e 01 00 +[ 23611ms] VEN_0488&Col02 len=20: 01 03 e4 02 bf 01 00 00 00 00 00 00 00 00 00 00 7c 6e 01 00 +[ 23617ms] VEN_0488&Col02 len=20: 01 03 ff 02 bd 01 00 00 00 00 00 00 00 00 00 00 c2 6e 01 00 +[ 23625ms] VEN_0488&Col02 len=20: 01 03 16 03 bd 01 00 00 00 00 00 00 00 00 00 00 08 6f 01 00 +[ 23632ms] VEN_0488&Col02 len=20: 01 03 31 03 bd 01 00 00 00 00 00 00 00 00 00 00 4e 6f 01 00 +[ 23639ms] VEN_0488&Col02 len=20: 01 03 4a 03 bd 01 00 00 00 00 00 00 00 00 00 00 94 6f 01 00 +[ 23646ms] VEN_0488&Col02 len=20: 01 03 63 03 bd 01 00 00 00 00 00 00 00 00 00 00 da 6f 01 00 +[ 23653ms] VEN_0488&Col02 len=20: 01 03 7e 03 bd 01 00 00 00 00 00 00 00 00 00 00 20 70 01 00 +[ 23660ms] VEN_0488&Col02 len=20: 01 03 97 03 bd 01 00 00 00 00 00 00 00 00 00 00 66 70 01 00 +[ 23667ms] VEN_0488&Col02 len=20: 01 03 b4 03 bc 01 00 00 00 00 00 00 00 00 00 00 ac 70 01 00 +[ 23674ms] VEN_0488&Col02 len=20: 01 03 ce 03 bc 01 00 00 00 00 00 00 00 00 00 00 f2 70 01 00 +[ 23681ms] VEN_0488&Col02 len=20: 01 03 e9 03 bc 01 00 00 00 00 00 00 00 00 00 00 38 71 01 00 +[ 23688ms] VEN_0488&Col02 len=20: 01 03 03 04 bb 01 00 00 00 00 00 00 00 00 00 00 7e 71 01 00 +[ 23695ms] VEN_0488&Col02 len=20: 01 03 1d 04 bb 01 00 00 00 00 00 00 00 00 00 00 c4 71 01 00 +[ 23702ms] VEN_0488&Col02 len=20: 01 03 39 04 bb 01 00 00 00 00 00 00 00 00 00 00 0a 72 01 00 +[ 23709ms] VEN_0488&Col02 len=20: 01 03 51 04 bb 01 00 00 00 00 00 00 00 00 00 00 50 72 01 00 +[ 23716ms] VEN_0488&Col02 len=20: 01 03 68 04 bb 01 00 00 00 00 00 00 00 00 00 00 96 72 01 00 +[ 23724ms] VEN_0488&Col02 len=20: 01 03 7e 04 bb 01 00 00 00 00 00 00 00 00 00 00 dc 72 01 00 +[ 23731ms] VEN_0488&Col02 len=20: 01 03 95 04 bb 01 00 00 00 00 00 00 00 00 00 00 22 73 01 00 +[ 23738ms] VEN_0488&Col02 len=20: 01 03 ad 04 bb 01 00 00 00 00 00 00 00 00 00 00 68 73 01 00 +[ 23745ms] VEN_0488&Col02 len=20: 01 03 c1 04 bb 01 00 00 00 00 00 00 00 00 00 00 ae 73 01 00 +[ 23752ms] VEN_0488&Col02 len=20: 01 03 d4 04 bb 01 00 00 00 00 00 00 00 00 00 00 f4 73 01 00 +[ 23759ms] VEN_0488&Col02 len=20: 01 03 e9 04 bb 01 00 00 00 00 00 00 00 00 00 00 3a 74 01 00 +[ 23766ms] VEN_0488&Col02 len=20: 01 03 fa 04 bc 01 00 00 00 00 00 00 00 00 00 00 80 74 01 00 +[ 23773ms] VEN_0488&Col02 len=20: 01 03 0a 05 bc 01 00 00 00 00 00 00 00 00 00 00 c6 74 01 00 +[ 23780ms] VEN_0488&Col02 len=20: 01 03 17 05 bc 01 00 00 00 00 00 00 00 00 00 00 0c 75 01 00 +[ 23787ms] VEN_0488&Col02 len=20: 01 03 24 05 bc 01 00 00 00 00 00 00 00 00 00 00 52 75 01 00 +[ 23794ms] VEN_0488&Col02 len=20: 01 03 2f 05 bc 01 00 00 00 00 00 00 00 00 00 00 98 75 01 00 +[ 23801ms] VEN_0488&Col02 len=20: 01 03 35 05 bc 01 00 00 00 00 00 00 00 00 00 00 de 75 01 00 +[ 23808ms] VEN_0488&Col02 len=20: 01 03 3c 05 bc 01 00 00 00 00 00 00 00 00 00 00 24 76 01 00 +[ 23815ms] VEN_0488&Col02 len=20: 01 03 3f 05 bc 01 00 00 00 00 00 00 00 00 00 00 6a 76 01 00 +[ 23822ms] VEN_0488&Col02 len=20: 01 03 40 05 bc 01 00 00 00 00 00 00 00 00 00 00 b0 76 01 00 +[ 23829ms] VEN_0488&Col02 len=20: 01 03 41 05 bc 01 00 00 00 00 00 00 00 00 00 00 f6 76 01 00 +[ 23836ms] VEN_0488&Col02 len=20: 01 03 41 05 bc 01 00 00 00 00 00 00 00 00 00 00 3c 77 01 00 +[ 23843ms] VEN_0488&Col02 len=20: 01 03 41 05 bc 01 00 00 00 00 00 00 00 00 00 00 82 77 01 00 +[ 23850ms] VEN_0488&Col02 len=20: 01 03 40 05 bc 01 00 00 00 00 00 00 00 00 00 00 c8 77 01 00 +[ 23858ms] VEN_0488&Col02 len=20: 01 03 3f 05 bc 01 00 00 00 00 00 00 00 00 00 00 0e 78 01 00 +[ 23865ms] VEN_0488&Col02 len=20: 01 03 3e 05 bc 01 00 00 00 00 00 00 00 00 00 00 54 78 01 00 +[ 23872ms] VEN_0488&Col02 len=20: 01 03 3d 05 bc 01 00 00 00 00 00 00 00 00 00 00 9a 78 01 00 +[ 23879ms] VEN_0488&Col02 len=20: 01 03 3a 05 bd 01 00 00 00 00 00 00 00 00 00 00 e0 78 01 00 +[ 23886ms] VEN_0488&Col02 len=20: 01 03 38 05 bd 01 00 00 00 00 00 00 00 00 00 00 26 79 01 00 +[ 23893ms] VEN_0488&Col02 len=20: 01 03 35 05 bd 01 00 00 00 00 00 00 00 00 00 00 6c 79 01 00 +[ 23900ms] VEN_0488&Col02 len=20: 01 03 2f 05 bd 01 00 00 00 00 00 00 00 00 00 00 b2 79 01 00 +[ 23907ms] VEN_0488&Col02 len=20: 01 03 22 05 bd 01 00 00 00 00 00 00 00 00 00 00 f8 79 01 00 +[ 23914ms] VEN_0488&Col02 len=20: 01 03 0f 05 bd 01 00 00 00 00 00 00 00 00 00 00 3e 7a 01 00 +[ 23921ms] VEN_0488&Col02 len=20: 01 03 fa 04 bd 01 00 00 00 00 00 00 00 00 00 00 84 7a 01 00 +[ 23929ms] VEN_0488&Col02 len=20: 01 03 e9 04 be 01 00 00 00 00 00 00 00 00 00 00 ca 7a 01 00 +[ 23935ms] VEN_0488&Col02 len=20: 01 03 d3 04 be 01 00 00 00 00 00 00 00 00 00 00 10 7b 01 00 +[ 23942ms] VEN_0488&Col02 len=20: 01 03 ba 04 be 01 00 00 00 00 00 00 00 00 00 00 56 7b 01 00 +[ 23949ms] VEN_0488&Col02 len=20: 01 03 a4 04 bf 01 00 00 00 00 00 00 00 00 00 00 9c 7b 01 00 +[ 23957ms] VEN_0488&Col02 len=20: 01 03 88 04 bf 01 00 00 00 00 00 00 00 00 00 00 e2 7b 01 00 +[ 23963ms] VEN_0488&Col02 len=20: 01 03 6f 04 c0 01 00 00 00 00 00 00 00 00 00 00 28 7c 01 00 +[ 23971ms] VEN_0488&Col02 len=20: 01 03 56 04 c0 01 00 00 00 00 00 00 00 00 00 00 6e 7c 01 00 +[ 23978ms] VEN_0488&Col02 len=20: 01 03 3c 04 c0 01 00 00 00 00 00 00 00 00 00 00 b4 7c 01 00 +[ 23985ms] VEN_0488&Col02 len=20: 01 03 24 04 c1 01 00 00 00 00 00 00 00 00 00 00 fa 7c 01 00 +[ 23992ms] VEN_0488&Col02 len=20: 01 03 0a 04 c1 01 00 00 00 00 00 00 00 00 00 00 40 7d 01 00 +[ 23999ms] VEN_0488&Col02 len=20: 01 03 f2 03 c1 01 00 00 00 00 00 00 00 00 00 00 86 7d 01 00 +[ 24006ms] VEN_0488&Col02 len=20: 01 03 de 03 c1 01 00 00 00 00 00 00 00 00 00 00 cc 7d 01 00 +[ 24013ms] VEN_0488&Col02 len=20: 01 03 c7 03 c1 01 00 00 00 00 00 00 00 00 00 00 12 7e 01 00 +[ 24020ms] VEN_0488&Col02 len=20: 01 03 b5 03 c0 01 00 00 00 00 00 00 00 00 00 00 58 7e 01 00 +[ 24027ms] VEN_0488&Col02 len=20: 01 03 a4 03 bf 01 00 00 00 00 00 00 00 00 00 00 9e 7e 01 00 +[ 24034ms] VEN_0488&Col02 len=20: 01 03 93 03 bc 01 00 00 00 00 00 00 00 00 00 00 e4 7e 01 00 +[ 24041ms] VEN_0488&Col02 len=20: 01 03 85 03 ba 01 00 00 00 00 00 00 00 00 00 00 2a 7f 01 00 +[ 24048ms] VEN_0488&Col02 len=20: 01 03 78 03 b8 01 00 00 00 00 00 00 00 00 00 00 70 7f 01 00 +[ 24055ms] VEN_0488&Col02 len=20: 01 03 6d 03 b5 01 00 00 00 00 00 00 00 00 00 00 b6 7f 01 00 +[ 24062ms] VEN_0488&Col02 len=20: 01 03 64 03 b5 01 00 00 00 00 00 00 00 00 00 00 fc 7f 01 00 +[ 24069ms] VEN_0488&Col02 len=20: 01 03 5d 03 b2 01 00 00 00 00 00 00 00 00 00 00 42 80 01 00 +[ 24076ms] VEN_0488&Col02 len=20: 01 03 59 03 b2 01 00 00 00 00 00 00 00 00 00 00 88 80 01 00 +[ 24084ms] VEN_0488&Col02 len=20: 01 03 56 03 b1 01 00 00 00 00 00 00 00 00 00 00 ce 80 01 00 +[ 24091ms] VEN_0488&Col02 len=20: 01 03 56 03 b0 01 00 00 00 00 00 00 00 00 00 00 14 81 01 00 +[ 24098ms] VEN_0488&Col02 len=20: 01 03 56 03 af 01 00 00 00 00 00 00 00 00 00 00 5a 81 01 00 +[ 24105ms] VEN_0488&Col02 len=20: 01 03 56 03 af 01 00 00 00 00 00 00 00 00 00 00 a0 81 01 00 +[ 24112ms] VEN_0488&Col02 len=20: 01 03 56 03 ae 01 00 00 00 00 00 00 00 00 00 00 e6 81 01 00 +[ 24119ms] VEN_0488&Col02 len=20: 01 03 57 03 ae 01 00 00 00 00 00 00 00 00 00 00 2c 82 01 00 +[ 24126ms] VEN_0488&Col02 len=20: 01 03 58 03 ae 01 00 00 00 00 00 00 00 00 00 00 72 82 01 00 +[ 24133ms] VEN_0488&Col02 len=20: 01 03 59 03 ae 01 00 00 00 00 00 00 00 00 00 00 b8 82 01 00 +[ 24140ms] VEN_0488&Col02 len=20: 01 03 5c 03 ae 01 00 00 00 00 00 00 00 00 00 00 fe 82 01 00 +[ 24147ms] VEN_0488&Col02 len=20: 01 03 5e 03 af 01 00 00 00 00 00 00 00 00 00 00 44 83 01 00 +[ 24154ms] VEN_0488&Col02 len=20: 01 03 61 03 b0 01 00 00 00 00 00 00 00 00 00 00 8a 83 01 00 +[ 24161ms] VEN_0488&Col02 len=20: 01 03 65 03 b3 01 00 00 00 00 00 00 00 00 00 00 d0 83 01 00 +[ 24168ms] VEN_0488&Col02 len=20: 01 03 6d 03 b6 01 00 00 00 00 00 00 00 00 00 00 16 84 01 00 +[ 24175ms] VEN_0488&Col02 len=20: 01 03 7d 03 b9 01 00 00 00 00 00 00 00 00 00 00 5c 84 01 00 +[ 24182ms] VEN_0488&Col02 len=20: 01 03 90 03 c0 01 00 00 00 00 00 00 00 00 00 00 a2 84 01 00 +[ 24189ms] VEN_0488&Col02 len=20: 01 03 a8 03 c7 01 00 00 00 00 00 00 00 00 00 00 e8 84 01 00 +[ 24197ms] VEN_0488&Col02 len=20: 01 03 c2 03 d2 01 00 00 00 00 00 00 00 00 00 00 2e 85 01 00 +[ 24204ms] VEN_0488&Col02 len=20: 01 03 dd 03 dd 01 00 00 00 00 00 00 00 00 00 00 74 85 01 00 +[ 24211ms] VEN_0488&Col02 len=20: 01 03 fc 03 ea 01 00 00 00 00 00 00 00 00 00 00 ba 85 01 00 +[ 24218ms] VEN_0488&Col02 len=20: 01 01 fc 03 ea 01 00 00 00 00 00 00 00 00 00 00 00 86 01 00 +raw-input tap closed diff --git a/docs/mx-master-4-panel-captures/openlogi-diag-panel-arm-confirmed.txt b/docs/mx-master-4-panel-captures/openlogi-diag-panel-arm-confirmed.txt new file mode 100644 index 00000000..c6219301 --- /dev/null +++ b/docs/mx-master-4-panel-captures/openlogi-diag-panel-arm-confirmed.txt @@ -0,0 +1,14 @@ +2026-07-20T08:13:11.228787Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T08:13:14.270630Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T08:13:17.287740Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +device: MX Master 4 (slot 2 on receiver 97B76948A846C55A) +arming Action Ring panel (analytics on CIDs 0x0050/0x0051) — press it now, 75s window + +[watch_panel] 0x1b04 feature index = 13 +[watch_panel] force-sensing feature present = true +[watch_panel] force threshold set -> [00, 15, a3, 00] +[watch_panel] analytics armed on 0x0050 +[watch_panel] analytics armed on 0x0051 + +0 panel event(s) captured. +none seen — was the mouse moving while arming? is Options+ holding the device? diff --git a/docs/mx-master-4-panel-captures/openlogi-panel-confirmed-clean.txt b/docs/mx-master-4-panel-captures/openlogi-panel-confirmed-clean.txt new file mode 100644 index 00000000..93216a05 --- /dev/null +++ b/docs/mx-master-4-panel-captures/openlogi-panel-confirmed-clean.txt @@ -0,0 +1,62 @@ +2026-07-20T09:07:57.848262Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T09:08:00.880293Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T09:08:03.885247Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +device: MX Master 4 (slot 2 on receiver 97B76948A846C55A) +arming Action Ring panel (analytics on CIDs 0x0050/0x0051) — press it now, 150s window + +[watch_panel] 0x1b04 feature index = 13 +[watch_panel] force-sensing feature present = true +[watch_panel] force threshold set -> [00, 15, a3, 00] +[watch_panel] analytics armed on 0x01a0 +[watch_panel] analytics armed on 0x0050 +[watch_panel] analytics armed on 0x0051 + [ 1] panel cid=0x0050 PRESS (event=0x01) + [ 2] panel cid=0x0050 RELEASE (event=0x00) + [ 3] panel cid=0x0050 PRESS (event=0x01) + [ 4] panel cid=0x0050 RELEASE (event=0x00) + [ 5] panel cid=0x0050 PRESS (event=0x01) + [ 6] panel cid=0x0050 RELEASE (event=0x00) + [ 7] panel cid=0x0050 PRESS (event=0x01) + [ 8] panel cid=0x0050 RELEASE (event=0x00) + [ 9] panel cid=0x0050 PRESS (event=0x01) + [ 10] panel cid=0x0050 RELEASE (event=0x00) + [ 11] panel cid=0x0050 PRESS (event=0x01) + [ 12] panel cid=0x0050 RELEASE (event=0x00) + [ 13] panel cid=0x0050 PRESS (event=0x01) + [ 14] panel cid=0x0050 RELEASE (event=0x00) + [ 15] panel cid=0x0050 PRESS (event=0x01) + [ 16] panel cid=0x0050 RELEASE (event=0x00) + [ 17] panel cid=0x0050 PRESS (event=0x01) + [ 18] panel cid=0x0050 RELEASE (event=0x00) + [ 19] panel cid=0x01a0 PRESS (event=0x01) + [ 20] panel cid=0x01a0 RELEASE (event=0x00) + [ 21] panel cid=0x01a0 PRESS (event=0x01) + [ 22] panel cid=0x01a0 RELEASE (event=0x00) + [ 23] panel cid=0x01a0 PRESS (event=0x01) + [ 24] panel cid=0x01a0 RELEASE (event=0x00) + [ 25] panel cid=0x01a0 PRESS (event=0x01) + [ 26] panel cid=0x01a0 RELEASE (event=0x00) + [ 27] panel cid=0x01a0 PRESS (event=0x01) + [ 28] panel cid=0x01a0 RELEASE (event=0x00) + [ 29] panel cid=0x01a0 PRESS (event=0x01) + [ 30] panel cid=0x01a0 RELEASE (event=0x00) + [ 31] panel cid=0x01a0 PRESS (event=0x01) + [ 32] panel cid=0x01a0 RELEASE (event=0x00) + [ 33] panel cid=0x01a0 PRESS (event=0x01) + [ 34] panel cid=0x01a0 RELEASE (event=0x00) + [ 35] panel cid=0x01a0 PRESS (event=0x01) + [ 36] panel cid=0x01a0 RELEASE (event=0x00) + [ 37] panel cid=0x01a0 PRESS (event=0x01) + [ 38] panel cid=0x01a0 RELEASE (event=0x00) + [ 39] panel cid=0x01a0 PRESS (event=0x01) + [ 40] panel cid=0x01a0 RELEASE (event=0x00) + [ 41] panel cid=0x01a0 PRESS (event=0x01) + [ 42] panel cid=0x01a0 RELEASE (event=0x00) + [ 43] panel cid=0x01a0 PRESS (event=0x01) + [ 44] panel cid=0x01a0 RELEASE (event=0x00) + [ 45] panel cid=0x01a0 PRESS (event=0x01) + [ 46] panel cid=0x01a0 RELEASE (event=0x00) + [ 47] panel cid=0x01a0 PRESS (event=0x01) + [ 48] panel cid=0x01a0 RELEASE (event=0x00) + +48 panel event(s) captured. diff --git a/docs/mx-master-4-panel-captures/optionsplus-coldstart-full.txt b/docs/mx-master-4-panel-captures/optionsplus-coldstart-full.txt new file mode 100644 index 00000000..0cc5830e --- /dev/null +++ b/docs/mx-master-4-panel-captures/optionsplus-coldstart-full.txt @@ -0,0 +1,315 @@ +raw-input tap live for 75 s — pages 0x0D digitizer, 0x0E haptics, 0xFF00/0xFF43 vendor +device hdev=10077: \\?\HID#VID_046D&PID_C548&MI_02&Col02#8&1ce49569&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030} +[ 163ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 282ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 1107ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 1266ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 2023ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 2218ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 2713ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 2826ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 3261ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 3359ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 4116ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 4205ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 5474ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 5541ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 5623ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 6021ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 6419ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 6486ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 6568ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 6981ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 7415ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 7475ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 7850ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 7963ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 8308ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 8420ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 8990ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +device hdev=10094f: \\?\HID#VID_046D&PID_C539&MI_02&Col01#7&2071e0a2&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} +[ 9000ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 81 00 00 01 00 +[ 9001ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 81 00 00 01 00 +device hdev=1006f: \\?\HID#VID_046D&PID_C548&MI_02&Col01#8&1ce49569&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} +[ 9001ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 81 00 00 09 00 +[ 9003ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 81 02 00 01 00 +[ 9004ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 81 00 00 09 00 +[ 9005ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 01 41 0c 62 79 40 +[ 9006ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 80 02 00 00 00 +[ 9011ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 81 02 00 01 00 +[ 9019ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 02 41 10 02 42 b0 +[ 9021ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 80 02 00 00 00 +device hdev=1006b: \\?\HID#VID_046D&PID_C339&MI_01&Col03#9&17be02e2&0&0002#{4d1e55b2-f16f-11cf-88cb-001111000030} +[ 9027ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 00 1c 04 02 39 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 9029ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 02 0c 02 35 31 47 07 00 08 c3 39 00 00 00 00 04 00 00 +[ 9032ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 02 1c 00 55 31 20 13 04 00 19 01 c3 39 00 00 00 00 00 +[ 9035ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 02 1c 00 55 31 20 13 04 00 19 01 c3 39 00 00 00 00 00 +[ 9038ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff ff 02 2c 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 9052ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff ff 02 2c 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 9056ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 02 0c 02 35 31 47 07 00 08 c3 39 00 00 00 00 04 00 00 +[ 9058ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 02 1c 00 55 31 20 13 04 00 19 01 c3 39 00 00 00 00 00 +[ 9061ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 02 1c 00 55 31 20 13 04 00 19 01 c3 39 00 00 00 00 00 +[ 9064ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 02 1c 01 42 4f 54 14 00 c4 b1 00 aa d0 00 00 00 00 00 +[ 9066ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 8f 00 1c 01 00 +device hdev=27909d3: \\?\HID#VID_046D&PID_C539&MI_02&Col02#7&2071e0a2&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030} +[ 9069ms] VID_046D&PID_C539&MI_02&Col02 len=20: 11 ff 83 b5 03 4b f5 94 dc 01 01 07 00 00 00 00 00 00 00 00 +[ 9071ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 81 f1 01 39 06 +[ 9073ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 81 f1 02 00 40 +[ 9089ms] VID_046D&PID_C539&MI_02&Col02 len=20: 11 ff 83 b5 40 06 47 20 50 72 6f 20 00 00 00 00 00 00 00 00 +[ 9093ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 8f 00 1c 01 00 +[ 9096ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 fb 39 37 42 37 36 39 34 38 41 38 34 36 43 35 35 41 +[ 9098ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 01 30 01 00 10 6e 3d 3d a0 00 00 00 00 00 00 00 +[ 9101ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 00 05 05 00 30 18 6a 26 ba 00 00 00 00 00 00 00 +[ 9102ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 05 00 00 01 3e 00 00 00 00 00 00 00 00 00 00 00 +[ 9105ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 02 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 9108ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 8f 83 f4 0b 00 +[ 9110ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 9112ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 fc 03 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 9125ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 12710ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 12785ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 13739ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 13829ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 15531ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 15605ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 16754ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 16829ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 17871ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 17930ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 18576ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 18636ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 19888ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 20000ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 20624ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 20728ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 21261ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 21366ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 21936ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 22041ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 22971ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 23046ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 24929ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 26999ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 27741ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 28289ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 32470ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 32473ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 32476ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 fb 39 37 42 37 36 39 34 38 41 38 34 36 43 35 35 41 +[ 32479ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 01 30 01 00 10 6e 3d 3d a0 00 00 00 00 00 00 00 +[ 32482ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 00 05 05 00 30 18 6a 26 ba 00 00 00 00 00 00 00 +[ 32486ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 05 00 00 01 3e 00 00 00 00 00 00 00 00 00 00 00 +[ 32487ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 02 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 32491ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 8f 83 f4 0b 00 +[ 32510ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1c 04 05 39 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 32534ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 0c 04 12 c1 78 4d 00 02 b0 42 00 00 00 00 01 01 00 +[ 32556ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1c 00 52 42 4d 27 03 00 19 05 b0 42 03 ce 08 d8 00 +[ 32579ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1c 01 4c 44 00 04 00 00 00 00 00 00 64 2b a1 a1 00 +[ 32601ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1c 00 52 42 4d 27 00 00 15 00 b0 42 f2 1a 37 9a 00 +[ 32616ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1c 00 52 42 4d 27 03 00 19 05 b0 42 03 ce 08 d8 00 +[ 32645ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1c 01 4c 44 00 04 00 00 00 00 00 00 64 2b a1 a1 00 +[ 32661ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 32675ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1c 00 52 42 4d 27 00 00 15 00 b0 42 f2 1a 37 9a 00 +[ 32699ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1c 00 52 42 4d 27 03 00 19 05 b0 42 03 ce 08 d8 00 +[ 32729ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0e 0c 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 32743ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 06 0c 0d 33 21 0b e2 5e 89 8a b5 66 aa 24 cf f7 60 db +[ 32765ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 06 1c b2 c0 e0 33 ff f1 fb c7 19 47 5b 71 f8 79 a1 22 +[ 32778ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 b5 52 02 42 b0 12 c1 78 4d 02 01 80 14 01 00 00 00 +[ 32804ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 0c 04 12 c1 78 4d 00 02 b0 42 00 00 00 00 01 01 00 +[ 32826ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1c 01 4c 44 00 04 00 00 00 00 00 00 64 2b a1 a1 00 +[ 32849ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1c 00 52 42 4d 27 00 00 15 00 b0 42 f2 1a 37 9a 00 +[ 32871ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1c 00 52 42 4d 27 03 00 19 05 b0 42 03 ce 08 d8 00 +[ 32893ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1c 01 4c 44 00 04 00 00 00 00 00 00 64 2b a1 a1 00 +[ 32915ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1c 00 52 42 4d 27 00 00 15 00 b0 42 f2 1a 37 9a 00 +[ 32939ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1c 00 52 42 4d 27 03 00 19 05 b0 42 03 ce 08 d8 00 +[ 32961ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1c 02 48 57 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 32983ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0a 0c 01 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33006ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 16 0c 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33062ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff ff 02 2c 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33066ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff ff 02 2c 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33076ms] VID_046D&PID_C539&MI_02&Col02 len=20: 11 ff 83 b5 03 4b f5 94 dc 01 01 07 00 00 00 00 00 00 00 00 +[ 33079ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 8f 83 f5 02 00 +[ 33094ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 fb 39 37 42 37 36 39 34 38 41 38 34 36 43 35 35 41 +[ 33099ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33119ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 09 0c 0f 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33148ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33218ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 01 30 01 00 10 6e 3d 3d a0 00 00 00 00 00 00 00 +[ 33220ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 00 05 05 00 30 18 6a 26 ba 00 00 00 00 00 00 00 +[ 33222ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 05 00 00 01 3e 00 00 00 00 00 00 00 00 00 00 00 +[ 33225ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 02 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33228ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 8f 83 f4 0b 00 +[ 33230ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 01 30 01 00 10 6e 3d 3d a0 00 00 00 00 00 00 00 +[ 33232ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 00 05 05 00 30 18 6a 26 ba 00 00 00 00 00 00 00 +[ 33235ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 05 00 00 01 3e 00 00 00 00 00 00 00 00 00 00 00 +[ 33237ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 02 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33240ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 8f 83 f4 0b 00 +[ 33243ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 01 30 01 00 10 6e 3d 3d a0 00 00 00 00 00 00 00 +[ 33244ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 00 05 05 00 30 18 6a 26 ba 00 00 00 00 00 00 00 +[ 33246ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 05 00 00 01 3e 00 00 00 00 00 00 00 00 00 00 00 +[ 33250ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 02 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33252ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 8f 83 f4 0b 00 +[ 33256ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 01 30 01 00 10 6e 3d 3d a0 00 00 00 00 00 00 00 +[ 33259ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 00 05 05 00 30 18 6a 26 ba 00 00 00 00 00 00 00 +[ 33262ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 05 00 00 01 3e 00 00 00 00 00 00 00 00 00 00 00 +[ 33265ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 02 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33267ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 8f 83 f4 0b 00 +[ 33271ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 fc 03 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33913ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 0c 00 14 00 78 00 03 03 e8 00 00 00 00 00 00 00 00 +[ 33935ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 1c 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33950ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 0c 01 0a 4b 0e 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33965ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 12 3c 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33980ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 12 1c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33996ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 14 2c 00 03 e8 03 e8 00 00 00 00 00 00 00 00 00 00 00 +[ 34019ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0e 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34034ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0e 0c 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34049ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0e 0c 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34139ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 81 00 00 09 00 +[ 34153ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 05 0c 36 8f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34168ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 05 1c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34184ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 05 1c 00 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34199ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 52 22 00 52 0b 00 00 00 00 00 00 00 00 00 00 +[ 34220ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 53 22 00 53 0b 00 00 00 00 00 00 00 00 00 00 +[ 34235ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 56 22 00 56 0b 00 00 00 00 00 00 00 00 00 00 +[ 34250ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 c3 22 00 c3 0b 00 00 00 00 00 00 00 00 00 00 +[ 34274ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 c4 22 00 c4 0b 00 00 00 00 00 00 00 00 00 00 +[ 34295ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 01 a0 22 01 a0 0b 00 00 00 00 00 00 00 00 00 00 +[ 34319ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 2c 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34333ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 50 00 00 00 03 00 00 00 00 00 00 00 00 00 00 +[ 34348ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 2c 00 51 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34371ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 51 00 00 00 03 00 00 00 00 00 00 00 00 00 00 +[ 34386ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 14 3c 00 03 e8 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34400ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 0c 13 08 03 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34424ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 1c 00 01 05 01 05 18 00 00 00 00 00 00 00 00 00 00 +[ 34445ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 1c 00 01 05 01 05 18 00 00 00 00 00 00 00 00 00 00 +[ 34469ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 3c 00 00 55 4c 54 52 41 00 00 00 00 00 00 00 00 00 +[ 34491ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 7c 00 01 0b 00 00 66 58 00 00 00 00 00 00 00 00 00 +[ 34514ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 1c 01 00 00 00 00 18 00 00 00 00 00 00 00 00 00 00 +[ 34536ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 1c 01 00 00 00 00 18 00 00 00 00 00 00 00 00 00 00 +[ 34559ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 ff 0f 7c 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34581ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 1c 02 00 00 00 00 18 00 00 00 00 00 00 00 00 00 00 +[ 34604ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 1c 02 00 00 00 00 18 00 00 00 00 00 00 00 00 00 00 +[ 34625ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 ff 0f 7c 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34648ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 8c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34671ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 0c 13 08 03 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34694ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 1c 00 01 05 01 05 18 00 00 00 00 00 00 00 00 00 00 +[ 34715ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 7c 00 01 0b 00 00 66 58 00 00 00 00 00 00 00 00 00 +[ 34739ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 1c 01 00 00 00 00 18 00 00 00 00 00 00 00 00 00 00 +[ 34760ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 ff 0f 7c 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34784ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0f 1c 02 00 00 00 00 18 00 00 00 00 00 00 00 00 00 00 +[ 34806ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 ff 0f 7c 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34835ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 09 1c 1e 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34858ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0e 0c 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34873ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 09 1c 1e 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34889ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 09 1c 1e 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35218ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0c 3c 00 15 a3 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35240ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 53 23 00 53 0b 00 00 00 00 00 00 00 00 00 00 +[ 35264ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0b 2c 01 3c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35285ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 56 23 00 56 0b 00 00 00 00 00 00 00 00 00 00 +[ 35308ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 12 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35331ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 c3 33 00 c3 0b 00 00 00 00 00 00 00 00 00 00 +[ 35354ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 c4 23 00 c4 0b 00 00 00 00 00 00 00 00 00 00 +[ 35376ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 01 a0 23 01 a0 0b 00 00 00 00 00 00 00 00 00 00 +[ 35391ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 2c 00 50 00 00 00 01 00 00 00 00 00 00 00 00 00 00 +[ 35406ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 50 00 00 00 03 00 00 00 00 00 00 00 00 00 00 +[ 35429ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 2c 00 51 00 00 00 01 00 00 00 00 00 00 00 00 00 00 +[ 35444ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 51 00 00 00 03 00 00 00 00 00 00 00 00 00 00 +[ 35458ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 0c 01 0a 4b 0e 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35481ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 2c 00 50 00 00 00 01 00 00 00 00 00 00 00 00 00 00 +[ 35495ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 1c 02 0c 4b 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35511ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 50 00 00 00 03 00 00 00 00 00 00 00 00 00 00 +[ 35526ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 0c 01 0a 4b 0e 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35541ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 2c 00 51 00 00 00 01 00 00 00 00 00 00 00 00 00 00 +[ 35563ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 1c 02 0c 4b 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35579ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 51 00 00 00 03 00 00 00 00 00 00 00 00 00 00 +[ 35594ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 1c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35616ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 2c 00 50 00 00 00 01 00 00 00 00 00 00 00 00 00 00 +[ 35639ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35660ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 50 00 00 00 03 00 00 00 00 00 00 00 00 00 00 +[ 35683ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 1c 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35705ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 2c 00 51 00 00 00 01 00 00 00 00 00 00 00 00 00 00 +[ 35729ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 1c 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35744ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 51 00 00 00 03 00 00 00 00 00 00 00 00 00 00 +[ 35758ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0c 3c 00 15 a3 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35781ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 2c 00 50 00 00 00 01 00 00 00 00 00 00 00 00 00 00 +[ 35804ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0b 2c 01 3c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35826ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 50 00 00 00 03 00 00 00 00 00 00 00 00 00 00 +[ 35849ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 12 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35864ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 2c 00 51 00 00 00 01 00 00 00 00 00 00 00 00 00 00 +[ 35878ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 3c 00 51 00 00 00 03 00 00 00 00 00 00 00 00 00 00 +[ 35901ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 0c 01 0a 4b 0e 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35923ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 1c 02 0c 4b 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35946ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 0c 01 0a 4b 0e 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35969ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 1c 02 0c 4b 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35991ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 1c 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36013ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36029ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 1c 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36044ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 1c 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36066ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0c 3c 00 15 a3 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36089ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0b 2c 01 3c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36111ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 12 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36134ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 0c 01 0a 4b 0e 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36149ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 1c 02 0c 4b 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36163ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 0c 01 0a 4b 0e 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36185ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 1c 02 0c 4b 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36208ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 1c 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36231ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36253ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 1c 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36276ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 1c 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36298ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0c 3c 00 15 a3 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36320ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0b 2c 01 3c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36343ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 12 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36365ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 0c 01 0a 4b 0e 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36388ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 1c 02 0c 4b 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36410ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 0c 01 0a 4b 0e 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36433ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 11 1c 02 0c 4b 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36448ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 1c 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36463ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36486ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 1c 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36500ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 1c 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 37055ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 +[ 37176ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 41196ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 41204ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 +[ 41248ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 13 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 42854ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 42929ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 43454ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 43596ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 43971ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 44061ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 44594ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 44684ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 45688ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 45756ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 46311ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 46409ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 46656ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 46761ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 52514ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 52595ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 53593ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 53699ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 54959ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 55018ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 56910ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 56976ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 57170ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 57284ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 59107ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 59182ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 63786ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 63892ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 66336ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 66426ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 67327ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 67372ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 67507ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 67567ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 67836ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 67927ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 70604ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 70687ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 72104ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 72217ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 73297ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 73394ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +raw-input tap closed diff --git a/docs/mx-master-4-panel-captures/optionsplus-rawinput.txt b/docs/mx-master-4-panel-captures/optionsplus-rawinput.txt new file mode 100644 index 00000000..5847da41 --- /dev/null +++ b/docs/mx-master-4-panel-captures/optionsplus-rawinput.txt @@ -0,0 +1,135 @@ +raw-input tap live for 300 s — pages 0x0D digitizer, 0x0E haptics, 0xFF00/0xFF43 vendor +device hdev=10094f: \\?\HID#VID_046D&PID_C539&MI_02&Col01#7&2071e0a2&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} +[ 9656ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 81 00 00 01 00 +device hdev=1006f: \\?\HID#VID_046D&PID_C548&MI_02&Col01#8&1ce49569&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} +[ 9656ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 81 00 00 01 00 +[ 9657ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 81 00 00 01 00 +[ 9659ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 81 02 00 01 00 +[ 9659ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 81 00 00 01 00 +[ 9661ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 01 41 0c 62 79 40 +[ 9662ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 80 02 00 00 00 +[ 9663ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 81 02 00 01 00 +[ 9676ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 02 41 10 02 42 b0 +[ 9677ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 80 02 00 00 00 +device hdev=1006b: \\?\HID#VID_046D&PID_C339&MI_01&Col03#9&17be02e2&0&0002#{4d1e55b2-f16f-11cf-88cb-001111000030} +[ 9696ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 00 1d 04 02 39 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 9699ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 02 0d 02 35 31 47 07 00 08 c3 39 00 00 00 00 04 00 00 +[ 9702ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 02 1d 00 55 31 20 13 04 00 19 01 c3 39 00 00 00 00 00 +[ 9705ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 02 1d 00 55 31 20 13 04 00 19 01 c3 39 00 00 00 00 00 +[ 9708ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff ff 02 2d 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 9722ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff ff 02 2d 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 9727ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 02 0d 02 35 31 47 07 00 08 c3 39 00 00 00 00 04 00 00 +[ 9730ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 02 1d 00 55 31 20 13 04 00 19 01 c3 39 00 00 00 00 00 +[ 9733ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 02 1d 00 55 31 20 13 04 00 19 01 c3 39 00 00 00 00 00 +[ 9736ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff 02 1d 01 42 4f 54 14 00 c4 b1 00 aa d0 00 00 00 00 00 +[ 9738ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 8f 00 1d 01 00 +device hdev=27909d3: \\?\HID#VID_046D&PID_C539&MI_02&Col02#7&2071e0a2&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030} +[ 9742ms] VID_046D&PID_C539&MI_02&Col02 len=20: 11 ff 83 b5 03 4b f5 94 dc 01 01 07 00 00 00 00 00 00 00 00 +[ 9744ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 81 f1 01 39 06 +[ 9746ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 81 f1 02 00 40 +[ 9761ms] VID_046D&PID_C539&MI_02&Col02 len=20: 11 ff 83 b5 40 06 47 20 50 72 6f 20 00 00 00 00 00 00 00 00 +[ 9764ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 8f 00 1d 01 00 +device hdev=10077: \\?\HID#VID_046D&PID_C548&MI_02&Col02#8&1ce49569&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030} +[ 9767ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 fb 39 37 42 37 36 39 34 38 41 38 34 36 43 35 35 41 +[ 9769ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 01 30 01 00 10 6e 3d 3d a0 00 00 00 00 00 00 00 +[ 9772ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 00 05 05 00 30 18 6a 26 ba 00 00 00 00 00 00 00 +[ 9774ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 05 00 00 01 3e 00 00 00 00 00 00 00 00 00 00 00 +[ 9775ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 02 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 9779ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 8f 83 f4 0b 00 +[ 9782ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 9784ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 fc 03 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 111626ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 111630ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 111633ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 fb 39 37 42 37 36 39 34 38 41 38 34 36 43 35 35 41 +[ 111635ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 01 30 01 00 10 6e 3d 3d a0 00 00 00 00 00 00 00 +[ 111638ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 00 05 05 00 30 18 6a 26 ba 00 00 00 00 00 00 00 +[ 111640ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 05 00 00 01 3e 00 00 00 00 00 00 00 00 00 00 00 +[ 111641ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 02 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 111644ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 8f 83 f4 0b 00 +[ 112309ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112332ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 39 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112354ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 0d 02 00 08 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112377ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 0d 04 12 c1 78 4d 00 02 b0 42 00 00 00 00 01 01 00 +[ 112399ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 01 4c 44 00 04 00 00 00 00 00 00 64 2b a1 a1 00 +[ 112422ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 00 52 42 4d 27 00 00 15 00 b0 42 f2 1a 37 9a 00 +[ 112437ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 00 52 42 4d 27 03 00 19 05 b0 42 03 ce 08 d8 00 +[ 112452ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112475ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 0d 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112497ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 6e 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112519ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 0d 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112543ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 01 0d 2d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112565ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 01 4c 44 00 04 00 00 00 00 00 00 64 2b a1 a1 00 +[ 112587ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 00 52 42 4d 27 00 00 15 00 b0 42 f2 1a 37 9a 00 +[ 112609ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 00 52 42 4d 27 03 00 19 05 b0 42 03 ce 08 d8 00 +[ 112633ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 2d 32 35 33 30 5a 41 4c 35 51 47 4e 38 00 00 00 00 +[ 112654ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 74 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112669ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112692ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 e1 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112714ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 0d 03 00 05 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112737ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 03 2d 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112759ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 03 0d 0b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112782ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 03 1d 4d 58 20 4d 61 73 74 65 72 20 34 00 00 00 00 00 +[ 112804ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 8c 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112828ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112850ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 d6 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112872ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112902ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 01 4c 44 00 04 00 00 00 00 00 00 64 2b a1 a1 00 +[ 112925ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 00 52 42 4d 27 00 00 15 00 b0 42 f2 1a 37 9a 00 +[ 112940ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 00 52 42 4d 27 03 00 19 05 b0 42 03 ce 08 d8 00 +[ 112954ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 5e 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112977ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 0d 0e 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 112999ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0e 0d 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113022ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 62 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113044ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 0d 16 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113067ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 f0 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113089ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 0d 06 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113104ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 c9 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113128ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 0d 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113142ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 06 0d 0d 33 21 0b e2 5e 89 8a b5 66 aa 24 cf f7 60 db +[ 113164ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 06 1d b2 c0 e0 33 ff f1 fb c7 19 47 5b 71 f8 79 a1 22 +[ 113177ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 b5 52 02 42 b0 12 c1 78 4d 02 01 80 14 01 00 00 00 +[ 113194ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 0d 04 12 c1 78 4d 00 02 b0 42 00 00 00 00 01 01 00 +[ 113210ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 01 4c 44 00 04 00 00 00 00 00 00 64 2b a1 a1 00 +[ 113224ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 00 52 42 4d 27 00 00 15 00 b0 42 f2 1a 37 9a 00 +[ 113247ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 00 52 42 4d 27 03 00 19 05 b0 42 03 ce 08 d8 00 +[ 113269ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 01 4c 44 00 04 00 00 00 00 00 00 64 2b a1 a1 00 +[ 113292ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 00 52 42 4d 27 00 00 15 00 b0 42 f2 1a 37 9a 00 +[ 113314ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 00 52 42 4d 27 03 00 19 05 b0 42 03 ce 08 d8 00 +[ 113337ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 02 48 57 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113359ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0a 0d 01 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113374ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 16 0d 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113448ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff ff 02 2d 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113454ms] VID_046D&PID_C339&MI_01&Col03 len=20: 11 ff ff 02 2d 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113463ms] VID_046D&PID_C539&MI_02&Col02 len=20: 11 ff 83 b5 03 4b f5 94 dc 01 01 07 00 00 00 00 00 00 00 00 +[ 113467ms] VID_046D&PID_C539&MI_02&Col01 len=7: 10 ff 8f 83 f5 02 00 +[ 113486ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 fb 39 37 42 37 36 39 34 38 41 38 34 36 43 35 35 41 +[ 113490ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113517ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 02 1d 02 48 57 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113539ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 51 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113562ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113577ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 11 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113592ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 0d 09 00 05 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113614ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 1d 04 05 2b 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113637ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 00 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113659ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 09 0d 0f 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113771ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 01 30 01 00 10 6e 3d 3d a0 00 00 00 00 00 00 00 +[ 113773ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 00 05 05 00 30 18 6a 26 ba 00 00 00 00 00 00 00 +[ 113776ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 05 00 00 01 3e 00 00 00 00 00 00 00 00 00 00 00 +[ 113779ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 02 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113782ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 8f 83 f4 0b 00 +[ 113784ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 01 30 01 00 10 6e 3d 3d a0 00 00 00 00 00 00 00 +[ 113786ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 00 05 05 00 30 18 6a 26 ba 00 00 00 00 00 00 00 +[ 113789ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 05 00 00 01 3e 00 00 00 00 00 00 00 00 00 00 00 +[ 113793ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 02 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113795ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 8f 83 f4 0b 00 +[ 113798ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 01 30 01 00 10 6e 3d 3d a0 00 00 00 00 00 00 00 +[ 113800ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 00 05 05 00 30 18 6a 26 ba 00 00 00 00 00 00 00 +[ 113801ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 05 00 00 01 3e 00 00 00 00 00 00 00 00 00 00 00 +[ 113803ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 02 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113806ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 8f 83 f4 0b 00 +[ 113809ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 01 30 01 00 10 6e 3d 3d a0 00 00 00 00 00 00 00 +[ 113812ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 00 05 05 00 30 18 6a 26 ba 00 00 00 00 00 00 00 +[ 113814ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 05 00 00 01 3e 00 00 00 00 00 00 00 00 00 00 00 +[ 113816ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 f4 02 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 113819ms] VID_046D&PID_C548&MI_02&Col01 len=7: 10 ff 8f 83 f4 0b 00 +[ 113823ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 ff 83 fc 03 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 diff --git a/docs/mx-master-4-panel-captures/panel-analytics-stream.txt b/docs/mx-master-4-panel-captures/panel-analytics-stream.txt new file mode 100644 index 00000000..55e4ee0a --- /dev/null +++ b/docs/mx-master-4-panel-captures/panel-analytics-stream.txt @@ -0,0 +1,115 @@ +raw-input tap live for 90 s — pages 0x0D digitizer, 0x0E haptics, 0xFF00/0xFF43 vendor +device hdev=10077: \\?\HID#VID_046D&PID_C548&MI_02&Col02#8&1ce49569&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030} +[ 490ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 551ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 1819ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 1939ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 2464ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 2524ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 3101ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 3184ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 3986ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 4106ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 5645ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 5726ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 7205ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 7280ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 8179ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 8262ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 8697ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 8787ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 10197ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 10302ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 11082ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 11157ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 11884ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 11982ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 12454ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 12544ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 13602ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 13722ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 16160ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 16272ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 17637ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 17697ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 21477ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 21545ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 26697ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 26795ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 28040ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 28145ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 29292ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 29337ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 29435ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 29480ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 29570ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 29637ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 29907ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 29990ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 30305ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 30725ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 30867ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 30920ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 31235ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 31715ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 31850ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 31917ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 32360ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 32772ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 32892ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 32975ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33297ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33642ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33785ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 33912ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34287ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34550ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34662ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 34730ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35007ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35067ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 35960ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 36042ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 37257ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 37340ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 37572ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 37677ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 41570ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 41637ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 42042ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 42117ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 42560ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 42620ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 44052ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 44210ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 46010ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 46100ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 51207ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 51297ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 56585ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 56645ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 58482ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 58527ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 59841ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 59907ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 60425ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 60515ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 68090ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 71563ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 75860ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 75943ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 77360ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 77428ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 78845ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 78928ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 80060ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 80129ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 80736ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 80826ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 81741ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 81853ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 85896ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 86023ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 86698ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 01 00 00 00 00 00 00 00 00 00 00 00 00 00 +[ 86803ms] VID_046D&PID_C548&MI_02&Col02 len=20: 11 02 0d 20 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +raw-input tap closed diff --git a/docs/mx-master-4-panel-captures/panel-force-rawxy-stream.log b/docs/mx-master-4-panel-captures/panel-force-rawxy-stream.log new file mode 100644 index 00000000..1a9b9e03 --- /dev/null +++ b/docs/mx-master-4-panel-captures/panel-force-rawxy-stream.log @@ -0,0 +1,5797 @@ +2026-07-20T04:56:38.090954Z DEBUG openlogi_core::single_instance: single-instance lock acquired path=C:\Users\ultra\.config/openlogi\agent.lock +2026-07-20T04:56:38.092452Z DEBUG openlogi_agent::launch_agent: agent autostart registry value already absent +2026-07-20T04:56:38.092691Z INFO openlogi_agent: openlogi-agent started +2026-07-20T04:56:38.092728Z INFO openlogi_agent: accessibility granted — installing OS mouse hook +2026-07-20T04:56:38.092799Z INFO openlogi_agent::server: IPC server listening +2026-07-20T04:56:38.093241Z INFO openlogi_agent_core::hook_runtime: OS mouse hook installed +2026-07-20T04:56:38.093280Z DEBUG openlogi_agent_core::watchers::foreground_app: frontmost app changed current=Some("c:\\program files (x86)\\steam\\steamapps\\common\\command & conquer generals - zero hour\\game.dat") last=None +2026-07-20T04:56:38.102239Z INFO openlogi_agent::tray_windows: tray icon installed +2026-07-20T04:56:38.209488Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:56:38.209519Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:56:38.209522Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:38.209524Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:38.209526Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:38.209528Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:56:38.209530Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:56:38.209532Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:56:38.209534Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:38.209536Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:38.209538Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:38.209540Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:56:38.209542Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:56:38.209545Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:56:38.209547Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:56:38.209549Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:56:38.209558Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=2 +2026-07-20T04:56:38.247873Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=PRO Gaming Keyboard pid=c339 supports_short=false supports_long=true +2026-07-20T04:56:38.283015Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c548 +2026-07-20T04:56:38.283038Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c548 supports_short=true supports_long=true +2026-07-20T04:56:38.292313Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:56:38.348425Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:56:39.806737Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:56:39.808447Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:39.823349Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:56:40.825834Z DEBUG openlogi_hid::inventory::probe: Bolt slot probe timed out; using cached data if available slot=2 budget=1s +2026-07-20T04:56:40.828450Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:40.831341Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:40.833376Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:40.835436Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:40.835517Z DEBUG openlogi_agent_core::hardware: no target device — wheel mode write skipped resolution=None inverted=None +2026-07-20T04:56:40.910311Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:56:40.910365Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:56:40.910382Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:40.910384Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:40.910387Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:40.910389Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:56:40.910391Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:56:40.910393Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:56:40.910395Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:40.910397Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:40.910399Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:40.910402Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:56:40.910404Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:56:40.910406Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:56:40.910408Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:56:40.910410Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:56:40.951363Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=PRO Gaming Keyboard pid=c339 supports_short=false supports_long=true +2026-07-20T04:56:40.995321Z DEBUG openlogi_hid::write::lighting: set keyboard colour via typed 0x8070 index=255 zone_count=2 zones_to_write=2 r=52 g=199 b=89 +2026-07-20T04:56:40.995611Z DEBUG openlogi_agent_core::hardware: lighting written to keyboard r=52 g=199 b=89 +2026-07-20T04:56:41.141850Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:56:41.141873Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:56:41.141876Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:41.141879Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:41.141881Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:41.141883Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:56:41.141885Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:56:41.141887Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:56:41.141889Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:41.141891Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:41.141893Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:41.141895Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:56:41.141898Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:56:41.141900Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:56:41.141902Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:56:41.141904Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:56:41.187983Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=PRO Gaming Keyboard pid=c339 supports_short=false supports_long=true +2026-07-20T04:56:41.193385Z DEBUG openlogi_hid::gesture: no capturable controls — idle session slot=255 +2026-07-20T04:56:41.193406Z INFO openlogi_hid::gesture: control capture active index=255 gesture=false dpi_buttons=0 thumbwheel=false +2026-07-20T04:56:42.891082Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:56:42.891102Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:56:42.891105Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:42.891107Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:42.891109Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:42.891111Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:56:42.891113Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:56:42.891117Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:56:42.891121Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:42.891123Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:42.891125Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:42.891127Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:56:42.891129Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:56:42.891131Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:56:42.891133Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:56:42.891135Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:56:42.891141Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=2 +2026-07-20T04:56:42.891152Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:56:42.898424Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:56:44.412641Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:56:44.414436Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:44.428488Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:56:45.437360Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:45.440439Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:45.442329Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:45.445356Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:45.445491Z DEBUG openlogi_agent_core::hardware: no target device — wheel mode write skipped resolution=None inverted=None +2026-07-20T04:56:45.574293Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:56:45.574320Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:56:45.574323Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:45.574325Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:45.574327Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:45.574329Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:56:45.574331Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:56:45.574333Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:56:45.574379Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:45.574384Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:45.574386Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:45.574388Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:56:45.574390Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:56:45.574392Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:56:45.574394Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:56:45.574396Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:56:45.578684Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:56:45.578703Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:56:45.578706Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:45.578708Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:45.578710Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:45.578712Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:56:45.578714Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:56:45.578716Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:56:45.578718Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:45.578720Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:45.578722Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:45.578724Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:56:45.578726Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:56:45.578731Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:56:45.578733Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:56:45.578735Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:56:45.632366Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=PRO Gaming Keyboard pid=c339 supports_short=false supports_long=true +2026-07-20T04:56:45.635409Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 0, function_id: U4(1), software_id: U4(1) }, [4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:45.635909Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=PRO Gaming Keyboard pid=c339 supports_short=false supports_long=true +2026-07-20T04:56:45.638482Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 0, function_id: U4(0), software_id: U4(1) }, [13, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:45.641381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 13, function_id: U4(0), software_id: U4(1) }, [2, 0, 1, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:45.644361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 13, function_id: U4(3), software_id: U4(1) }, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:45.656328Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 13, function_id: U4(3), software_id: U4(1) }, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:45.669545Z DEBUG openlogi_hid::write::lighting: set keyboard colour via typed 0x8070 index=255 zone_count=2 zones_to_write=2 r=52 g=199 b=89 +2026-07-20T04:56:45.669733Z DEBUG openlogi_agent_core::hardware: lighting written to keyboard r=52 g=199 b=89 +2026-07-20T04:56:45.680171Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c548 +2026-07-20T04:56:45.680185Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c548 supports_short=true supports_long=true +2026-07-20T04:56:45.756442Z DEBUG openlogi_hid::hires_wheel: native wheel mode already set; skipping index=2 desired=ScrollWheelMode { resolution: Low, inverted: false, target: Native } +2026-07-20T04:56:45.756603Z DEBUG openlogi_agent_core::hardware: native wheel mode written index=2 resolution=None inverted=Some(false) reused=false +2026-07-20T04:56:46.100538Z DEBUG openlogi_hid::gesture: control capture stopped index=255 +2026-07-20T04:56:47.142293Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:56:47.142318Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:56:47.142321Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:47.142323Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:47.142325Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:47.142327Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:56:47.142329Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:56:47.142331Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:56:47.142333Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:47.142335Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:47.142337Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:47.142339Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:56:47.142341Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:56:47.142348Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:56:47.142352Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:56:47.142354Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:56:47.182951Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=PRO Gaming Keyboard pid=c339 supports_short=false supports_long=true +2026-07-20T04:56:47.220414Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c548 +2026-07-20T04:56:47.220432Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c548 supports_short=true supports_long=true +2026-07-20T04:56:47.495743Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:56:47.495761Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:56:47.495764Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:47.495766Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:47.495768Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:47.495770Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:56:47.495772Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:56:47.495774Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:56:47.495776Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:47.495778Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:47.495780Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:47.495782Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:56:47.495784Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:56:47.495786Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:56:47.495788Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:56:47.495790Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:56:47.495795Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=2 +2026-07-20T04:56:47.495806Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:56:47.496425Z INFO openlogi_hid::gesture: haptic sense panel diverted (with force-raw-xy) +2026-07-20T04:56:47.503337Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:56:47.515409Z INFO openlogi_hid::gesture: control capture active index=2 gesture=true dpi_buttons=1 thumbwheel=false +2026-07-20T04:56:47.631414Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.631455Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:47.639381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.639396Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:47.646361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.646375Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-2 +2026-07-20T04:56:47.654313Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.654331Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-6 +2026-07-20T04:56:47.661405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.661438Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-5 +2026-07-20T04:56:47.669423Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.669454Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-6 +2026-07-20T04:56:47.676426Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.676434Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-8 +2026-07-20T04:56:47.684643Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.684658Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-6 +2026-07-20T04:56:47.691459Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.691491Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-7 +2026-07-20T04:56:47.699486Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.699500Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-8 +2026-07-20T04:56:47.706385Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.706398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-7 +2026-07-20T04:56:47.714445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.714458Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-8 +2026-07-20T04:56:47.721372Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.721398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-8 +2026-07-20T04:56:47.729393Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.729530Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-6 +2026-07-20T04:56:47.736521Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.736535Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-5 +2026-07-20T04:56:47.744345Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.744360Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-3 +2026-07-20T04:56:47.751318Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.751333Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-4 +2026-07-20T04:56:47.759384Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.759397Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-2 +2026-07-20T04:56:47.766432Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.766466Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-4 +2026-07-20T04:56:47.774370Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.774384Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-3 +2026-07-20T04:56:47.781454Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:47.781470Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.021469Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.021500Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.029430Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.029450Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:48.036436Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.036458Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:48.044383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.044399Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:48.051458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.051494Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:48.059438Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.059454Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.066460Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.066503Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.074383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.074399Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.096382Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.096405Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.119511Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.119535Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.126451Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.126474Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.141448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.141478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.149493Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.149503Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:48.156458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.156478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.164389Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.164403Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:48.171368Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.171384Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.179477Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.179516Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:48.186433Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.186461Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:48.194529Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.194559Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:48.201360Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.201424Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:56:48.209477Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.209491Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:56:48.216494Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.216531Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:48.224486Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.224502Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:48.231493Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.231522Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-2 +2026-07-20T04:56:48.239522Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.239539Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:48.246393Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.246407Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.254401Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.254414Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:48.261418Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.261456Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:48.269440Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.269458Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:48.276458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.276471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.284423Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.284434Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.291426Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.291436Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.299397Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.299486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.306310Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.306322Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.344368Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.352639Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.381468Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.381487Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:56:48.389399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.389415Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.779474Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.779491Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:48.786382Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.786398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:48.794467Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.794482Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-2 +2026-07-20T04:56:48.801419Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.801451Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-6 +2026-07-20T04:56:48.809326Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.809341Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-2 +2026-07-20T04:56:48.816372Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.816413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-3 +2026-07-20T04:56:48.824377Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.824395Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-3 +2026-07-20T04:56:48.831446Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.831460Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-4 +2026-07-20T04:56:48.839454Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.839473Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-4 +2026-07-20T04:56:48.846404Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.846427Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-4 +2026-07-20T04:56:48.854454Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.854475Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-4 +2026-07-20T04:56:48.861468Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.861486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-4 +2026-07-20T04:56:48.869437Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.869477Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-5 +2026-07-20T04:56:48.876431Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.876449Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-6 +2026-07-20T04:56:48.884479Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.884490Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-8 +2026-07-20T04:56:48.891473Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.891486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-7 +2026-07-20T04:56:48.899381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.899406Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-10 +2026-07-20T04:56:48.906391Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.906404Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-9 +2026-07-20T04:56:48.914430Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.914445Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-9 +2026-07-20T04:56:48.921416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.921442Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-13 +2026-07-20T04:56:48.929505Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.929546Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-8 +2026-07-20T04:56:48.936384Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.936398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-8 +2026-07-20T04:56:48.944420Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.944439Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-8 +2026-07-20T04:56:48.951406Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.951435Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-7 +2026-07-20T04:56:48.959431Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.959445Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-4 +2026-07-20T04:56:48.966457Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.966471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-4 +2026-07-20T04:56:48.974396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.974412Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-3 +2026-07-20T04:56:48.981424Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:48.981438Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:49.025311Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:56:49.027326Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:49.027445Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:49.037408Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:56:49.041338Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:56:49.041400Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:56:49.326321Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.326344Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:56:49.334334Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.334353Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:56:49.341318Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.341335Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:56:49.349341Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.353392Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:49.353451Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:49.355388Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:49.355397Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:49.356335Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.356347Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-3 +2026-07-20T04:56:49.358351Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:49.358440Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:49.361350Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:49.361362Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:49.364321Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.364337Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-7 +2026-07-20T04:56:49.371329Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.371364Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-5 +2026-07-20T04:56:49.386381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.386404Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-7 +2026-07-20T04:56:49.394329Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.394354Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-16 +2026-07-20T04:56:49.409357Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.409409Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-10 +2026-07-20T04:56:49.416289Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.416307Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-22 +2026-07-20T04:56:49.431304Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.431328Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-9 +2026-07-20T04:56:49.439299Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 229, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.439319Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-27 +2026-07-20T04:56:49.446393Z DEBUG openlogi_hid::hires_wheel: native wheel mode already set; skipping index=2 desired=ScrollWheelMode { resolution: Low, inverted: false, target: Native } +2026-07-20T04:56:49.446422Z DEBUG openlogi_agent_core::hardware: native wheel mode written index=2 resolution=None inverted=Some(false) reused=true +2026-07-20T04:56:49.454349Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.454370Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-11 +2026-07-20T04:56:49.461387Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.461406Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-20 +2026-07-20T04:56:49.469308Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.469340Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-14 +2026-07-20T04:56:49.476307Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.476328Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-9 +2026-07-20T04:56:49.484445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.484467Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-8 +2026-07-20T04:56:49.491356Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.491375Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-7 +2026-07-20T04:56:49.499321Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.499338Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-5 +2026-07-20T04:56:49.506426Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.506458Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-5 +2026-07-20T04:56:49.514455Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.514472Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-3 +2026-07-20T04:56:49.521535Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.521555Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-3 +2026-07-20T04:56:49.529426Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.529444Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-2 +2026-07-20T04:56:49.536431Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.536448Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-1 +2026-07-20T04:56:49.544496Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.544526Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-1 +2026-07-20T04:56:49.551396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.551413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=0 +2026-07-20T04:56:49.559432Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.559450Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=0 +2026-07-20T04:56:49.566366Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.566382Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-1 +2026-07-20T04:56:49.754350Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.754378Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:56:49.761440Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.761473Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:49.769384Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.769414Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-2 +2026-07-20T04:56:49.776471Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.776487Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-1 +2026-07-20T04:56:49.784561Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.784602Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=0 +2026-07-20T04:56:49.799402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.799420Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-2 +2026-07-20T04:56:49.806373Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.806390Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=0 +2026-07-20T04:56:49.814478Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.814494Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-1 +2026-07-20T04:56:49.821450Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.821475Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-1 +2026-07-20T04:56:49.829420Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.829432Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-1 +2026-07-20T04:56:49.836452Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.836497Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-1 +2026-07-20T04:56:49.844408Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.844424Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-1 +2026-07-20T04:56:49.851483Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.851511Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-1 +2026-07-20T04:56:49.859443Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.859459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-2 +2026-07-20T04:56:49.866433Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.866460Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-3 +2026-07-20T04:56:49.874375Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.874393Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-2 +2026-07-20T04:56:49.881492Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.881508Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-2 +2026-07-20T04:56:49.889505Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.889519Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-2 +2026-07-20T04:56:49.896499Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.896511Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-2 +2026-07-20T04:56:49.904419Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.904434Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-2 +2026-07-20T04:56:49.911368Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.911408Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-2 +2026-07-20T04:56:49.919421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.919436Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-6 +2026-07-20T04:56:49.926523Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.926538Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-3 +2026-07-20T04:56:49.934415Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.934437Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-2 +2026-07-20T04:56:49.941448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.941464Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-1 +2026-07-20T04:56:49.949324Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.949357Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:49.956408Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.956424Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:56:49.971432Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.971448Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=2 +2026-07-20T04:56:49.979349Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.979365Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=1 +2026-07-20T04:56:49.994402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:49.994420Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=2 +2026-07-20T04:56:50.271491Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.271538Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.294402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.294436Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.301403Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.301424Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.309401Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.309416Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.316461Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.316478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.324426Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.324452Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.331394Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.331412Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:50.339454Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.339492Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.346399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.346415Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:50.354417Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.354432Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.361411Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.361426Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:56:50.369416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.369431Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.376416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.376443Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:56:50.384462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.384482Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:50.391331Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.391348Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-3 +2026-07-20T04:56:50.399405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.399420Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-3 +2026-07-20T04:56:50.406349Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.406378Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:56:50.414365Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.414378Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-3 +2026-07-20T04:56:50.421415Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.421431Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:56:50.429369Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.429383Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-5 +2026-07-20T04:56:50.436330Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.436372Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-7 +2026-07-20T04:56:50.444464Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.444486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-5 +2026-07-20T04:56:50.451412Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.451426Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:56:50.459426Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.459445Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-5 +2026-07-20T04:56:50.466399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.466414Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-5 +2026-07-20T04:56:50.474417Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.474429Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-5 +2026-07-20T04:56:50.481459Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.481481Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-5 +2026-07-20T04:56:50.489425Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.489434Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-6 +2026-07-20T04:56:50.496384Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.496400Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-2 +2026-07-20T04:56:50.504329Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.504342Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-5 +2026-07-20T04:56:50.511470Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.511500Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-4 +2026-07-20T04:56:50.519314Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.519329Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-2 +2026-07-20T04:56:50.526442Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.526473Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-3 +2026-07-20T04:56:50.534370Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.534390Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-2 +2026-07-20T04:56:50.541371Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.541386Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.556366Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.556380Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.789451Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.789472Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.796414Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.796432Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:50.804325Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.804338Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-3 +2026-07-20T04:56:50.811391Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.811419Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:50.819388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.819403Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-3 +2026-07-20T04:56:50.826337Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.826349Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-3 +2026-07-20T04:56:50.834301Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.834311Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:50.841320Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.841334Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-2 +2026-07-20T04:56:50.849337Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.849364Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-3 +2026-07-20T04:56:50.856374Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.856404Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:50.864405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.864447Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:50.871386Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.871398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.879413Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.879426Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.894409Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.894439Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.901399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.901411Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:50.909462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.909489Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.931370Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.931387Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:50.954440Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:50.954473Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:51.480697Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:56:51.480717Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:56:51.480721Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:51.480723Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:51.480725Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:51.480727Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:56:51.480729Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:56:51.480731Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:56:51.480733Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:51.480735Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:51.480737Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:51.480740Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:56:51.480742Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:56:51.480746Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:56:51.480750Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:56:51.480752Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:56:51.480758Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=2 +2026-07-20T04:56:51.480769Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:56:51.484408Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:56:51.488355Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:56:51.488382Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:56:51.494444Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:56:51.496391Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:56:52.274447Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.274490Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:52.281538Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.281551Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:56:52.289501Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.289530Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:56:52.296439Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.296454Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-10 +2026-07-20T04:56:52.304414Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.304428Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-5 +2026-07-20T04:56:52.311479Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.311492Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-4 +2026-07-20T04:56:52.319468Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.319494Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-4 +2026-07-20T04:56:52.326458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.326505Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-5 +2026-07-20T04:56:52.334383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.334398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:52.341429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.341442Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-4 +2026-07-20T04:56:52.349445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.349459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:56:52.356386Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.356405Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-3 +2026-07-20T04:56:52.364422Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.364434Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-3 +2026-07-20T04:56:52.371371Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.371389Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-3 +2026-07-20T04:56:52.379380Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.379406Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-3 +2026-07-20T04:56:52.386616Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.386660Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:52.394424Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.394452Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:52.671460Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:52.671485Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=0 +2026-07-20T04:56:53.008901Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:56:53.012318Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:53.012380Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:53.021336Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:56:53.027303Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:56:53.027327Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:56:53.219430Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.219453Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-3 +2026-07-20T04:56:53.226400Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.226432Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-3 +2026-07-20T04:56:53.234384Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.234411Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-3 +2026-07-20T04:56:53.241471Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.244468Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:53.244484Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:53.247369Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:53.247401Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:53.249446Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:53.249464Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:53.251424Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.251440Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-15 +2026-07-20T04:56:53.253446Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:53.253459Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:53.256399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.256410Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=-14 +2026-07-20T04:56:53.264498Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.264513Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-6 +2026-07-20T04:56:53.271471Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.271486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=-10 +2026-07-20T04:56:53.279373Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.279390Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-6 +2026-07-20T04:56:53.286446Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.286475Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=-8 +2026-07-20T04:56:53.294456Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.294468Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-7 +2026-07-20T04:56:53.301448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.301478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-7 +2026-07-20T04:56:53.309413Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.309428Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-5 +2026-07-20T04:56:53.316354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.316361Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-3 +2026-07-20T04:56:53.324459Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.324489Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-1 +2026-07-20T04:56:53.331449Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.331462Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-2 +2026-07-20T04:56:53.339406Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.339418Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-2 +2026-07-20T04:56:53.346475Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.346493Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:53.676419Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.676444Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=0 +2026-07-20T04:56:53.684393Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.684426Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-1 +2026-07-20T04:56:53.691437Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.691471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=0 +2026-07-20T04:56:53.706361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.706409Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:56:53.714503Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.714546Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:56:53.721485Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.721517Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:53.729488Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.729534Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-5 +2026-07-20T04:56:53.736421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.736436Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-8 +2026-07-20T04:56:53.744393Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.744411Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-6 +2026-07-20T04:56:53.751435Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.751450Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-8 +2026-07-20T04:56:53.759473Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.759503Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-9 +2026-07-20T04:56:53.766333Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.766348Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-10 +2026-07-20T04:56:53.774385Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.774400Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-12 +2026-07-20T04:56:53.781400Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.781432Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-8 +2026-07-20T04:56:53.789573Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.792661Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-14 +2026-07-20T04:56:53.796390Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.796404Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-10 +2026-07-20T04:56:53.804404Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.804418Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-17 +2026-07-20T04:56:53.811458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.811475Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-11 +2026-07-20T04:56:53.819458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.819478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-12 +2026-07-20T04:56:53.826396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.826411Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-17 +2026-07-20T04:56:53.834342Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.834356Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-13 +2026-07-20T04:56:53.841344Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.841358Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-12 +2026-07-20T04:56:53.849394Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.849410Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-12 +2026-07-20T04:56:53.856415Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.856431Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-12 +2026-07-20T04:56:53.864435Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.864465Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-12 +2026-07-20T04:56:53.871488Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.871504Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-12 +2026-07-20T04:56:53.879357Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.879367Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-12 +2026-07-20T04:56:53.886369Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.886401Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-5 +2026-07-20T04:56:53.894376Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.894392Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-9 +2026-07-20T04:56:53.901414Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.901430Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-9 +2026-07-20T04:56:53.909453Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.909489Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-9 +2026-07-20T04:56:53.916398Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.916432Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-7 +2026-07-20T04:56:53.924471Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.924487Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-4 +2026-07-20T04:56:53.931564Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.931580Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-6 +2026-07-20T04:56:53.939482Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.939519Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-6 +2026-07-20T04:56:53.946489Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.946498Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-4 +2026-07-20T04:56:53.954415Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.954433Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-6 +2026-07-20T04:56:53.961471Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.961488Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-3 +2026-07-20T04:56:53.969384Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.969400Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-5 +2026-07-20T04:56:53.976409Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.976425Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-6 +2026-07-20T04:56:53.984482Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.984499Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-3 +2026-07-20T04:56:53.991478Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.991495Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-3 +2026-07-20T04:56:53.999491Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:53.999507Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-5 +2026-07-20T04:56:54.006389Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.006404Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-3 +2026-07-20T04:56:54.014458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.014479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-5 +2026-07-20T04:56:54.021438Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.021454Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-3 +2026-07-20T04:56:54.029467Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.029499Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-5 +2026-07-20T04:56:54.036408Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.036425Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-2 +2026-07-20T04:56:54.044351Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.044367Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-6 +2026-07-20T04:56:54.051548Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.051566Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-2 +2026-07-20T04:56:54.059427Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.059443Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-3 +2026-07-20T04:56:54.066395Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.066424Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-5 +2026-07-20T04:56:54.074429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.074441Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-2 +2026-07-20T04:56:54.081453Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.081490Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-3 +2026-07-20T04:56:54.089484Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.089505Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-7 +2026-07-20T04:56:54.096397Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.096413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-3 +2026-07-20T04:56:54.104471Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.104486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-3 +2026-07-20T04:56:54.111429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.111447Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-6 +2026-07-20T04:56:54.119413Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.119426Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-3 +2026-07-20T04:56:54.126381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.126397Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-3 +2026-07-20T04:56:54.134448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.134466Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-3 +2026-07-20T04:56:54.141441Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.141455Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-2 +2026-07-20T04:56:54.149415Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.149428Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-4 +2026-07-20T04:56:54.156416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.156439Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-2 +2026-07-20T04:56:54.164398Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.164411Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:54.171508Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.171537Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:54.179492Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.179505Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-2 +2026-07-20T04:56:54.186526Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.186539Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:54.194468Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.194478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:54.201518Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.201547Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:54.209363Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.209392Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:54.216455Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.216473Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-2 +2026-07-20T04:56:54.224388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.224400Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-2 +2026-07-20T04:56:54.231391Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.231403Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-4 +2026-07-20T04:56:54.239584Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.239606Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-2 +2026-07-20T04:56:54.269386Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.269421Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:56:54.284465Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.284494Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=5 +2026-07-20T04:56:54.291457Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.291474Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=6 +2026-07-20T04:56:54.299529Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.299543Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=10 +2026-07-20T04:56:54.306475Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.306504Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=8 +2026-07-20T04:56:54.314420Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.314430Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=11 +2026-07-20T04:56:54.321426Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.321435Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=14 +2026-07-20T04:56:54.329447Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.329468Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=14 +2026-07-20T04:56:54.336416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.336445Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=19 +2026-07-20T04:56:54.344451Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.344490Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=14 +2026-07-20T04:56:54.351390Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.351427Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=22 +2026-07-20T04:56:54.359446Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.359459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=15 +2026-07-20T04:56:54.366421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.366459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=22 +2026-07-20T04:56:54.374467Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.374479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=22 +2026-07-20T04:56:54.381469Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.381493Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=15 +2026-07-20T04:56:54.389455Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.389479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=21 +2026-07-20T04:56:54.396397Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.396409Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=14 +2026-07-20T04:56:54.404330Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.404344Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=18 +2026-07-20T04:56:54.411418Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.411431Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=11 +2026-07-20T04:56:54.419453Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.419465Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=16 +2026-07-20T04:56:54.426381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.426395Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=8 +2026-07-20T04:56:54.434382Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.434395Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=5 +2026-07-20T04:56:54.441487Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 27, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.441522Z INFO openlogi_hid::gesture: SPIKE raw xy dx=27 dy=5 +2026-07-20T04:56:54.449443Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.449520Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=1 +2026-07-20T04:56:54.456506Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.456537Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=-1 +2026-07-20T04:56:54.464449Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 30, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.464463Z INFO openlogi_hid::gesture: SPIKE raw xy dx=30 dy=-3 +2026-07-20T04:56:54.471510Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 26, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.471543Z INFO openlogi_hid::gesture: SPIKE raw xy dx=26 dy=-4 +2026-07-20T04:56:54.479405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 26, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.479419Z INFO openlogi_hid::gesture: SPIKE raw xy dx=26 dy=-4 +2026-07-20T04:56:54.486387Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.486401Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=-4 +2026-07-20T04:56:54.494375Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.494389Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=-5 +2026-07-20T04:56:54.501357Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.501372Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=-5 +2026-07-20T04:56:54.509398Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.509436Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=-4 +2026-07-20T04:56:54.516518Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.516544Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=-5 +2026-07-20T04:56:54.524409Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.524424Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=-4 +2026-07-20T04:56:54.531464Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.531474Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-3 +2026-07-20T04:56:54.539326Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.539349Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-2 +2026-07-20T04:56:54.546344Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.546359Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-2 +2026-07-20T04:56:54.554398Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.554425Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=0 +2026-07-20T04:56:54.561458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.561474Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=0 +2026-07-20T04:56:54.569460Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.569482Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=0 +2026-07-20T04:56:54.576432Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.576445Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:56:54.584354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.584366Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:56:54.591352Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.591365Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=2 +2026-07-20T04:56:54.599522Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.599537Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=3 +2026-07-20T04:56:54.606511Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.606549Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=7 +2026-07-20T04:56:54.614504Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.614534Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=7 +2026-07-20T04:56:54.621456Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.621491Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=9 +2026-07-20T04:56:54.629453Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.629466Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=9 +2026-07-20T04:56:54.636356Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.636382Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=9 +2026-07-20T04:56:54.644477Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.644495Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=8 +2026-07-20T04:56:54.651392Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.651420Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=8 +2026-07-20T04:56:54.659419Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.659432Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=5 +2026-07-20T04:56:54.666466Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.666479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:56:54.689401Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.689431Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:54.696383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.696397Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:56:54.704389Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.704412Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-3 +2026-07-20T04:56:54.711456Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.711469Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-19 +2026-07-20T04:56:54.719418Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.719432Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-18 +2026-07-20T04:56:54.726467Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.726479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-14 +2026-07-20T04:56:54.734477Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.734490Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=-17 +2026-07-20T04:56:54.741345Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.741373Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=-18 +2026-07-20T04:56:54.749347Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 236, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.749362Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-20 dy=-19 +2026-07-20T04:56:54.756317Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 234, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.756331Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-22 dy=-20 +2026-07-20T04:56:54.764370Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.764384Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=-19 +2026-07-20T04:56:54.771586Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 231, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.771620Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-25 dy=-19 +2026-07-20T04:56:54.779395Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 224, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.779421Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-32 dy=-20 +2026-07-20T04:56:54.786370Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.786383Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=-11 +2026-07-20T04:56:54.801521Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 222, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.801552Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-34 dy=-12 +2026-07-20T04:56:54.809531Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 226, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.809569Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-30 dy=-6 +2026-07-20T04:56:54.816441Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 187, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.816471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-69 dy=-4 +2026-07-20T04:56:54.824421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.824450Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-25 dy=0 +2026-07-20T04:56:54.831347Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 227, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.831365Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-29 dy=3 +2026-07-20T04:56:54.839427Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 223, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.839450Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-33 dy=7 +2026-07-20T04:56:54.846428Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.846458Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=9 +2026-07-20T04:56:54.854374Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.854413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=12 +2026-07-20T04:56:54.861426Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.861448Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=13 +2026-07-20T04:56:54.869451Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 236, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.869477Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-20 dy=20 +2026-07-20T04:56:54.876414Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.876437Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=18 +2026-07-20T04:56:54.884443Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.884475Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=22 +2026-07-20T04:56:54.891481Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.891495Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=25 +2026-07-20T04:56:54.899445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.899459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=25 +2026-07-20T04:56:54.906352Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.906365Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=27 +2026-07-20T04:56:54.914299Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.914313Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=19 +2026-07-20T04:56:54.921386Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.921410Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=23 +2026-07-20T04:56:54.929417Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.929431Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=15 +2026-07-20T04:56:54.936436Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.936450Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=14 +2026-07-20T04:56:54.944373Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.944386Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=13 +2026-07-20T04:56:54.951311Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.951327Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=11 +2026-07-20T04:56:54.959381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.959398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=9 +2026-07-20T04:56:54.966375Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.966386Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=7 +2026-07-20T04:56:54.974410Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.974420Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=4 +2026-07-20T04:56:54.981416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.981433Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=1 +2026-07-20T04:56:54.989510Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.989536Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=0 +2026-07-20T04:56:54.996408Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:54.996421Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-1 +2026-07-20T04:56:55.004415Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.004428Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-2 +2026-07-20T04:56:55.011524Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.011554Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-2 +2026-07-20T04:56:55.019433Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.019470Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-4 +2026-07-20T04:56:55.026351Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.026357Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-1 +2026-07-20T04:56:55.041359Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.041373Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:55.049708Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.049720Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:55.199492Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.199554Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:56:55.206421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.206429Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:56:55.214465Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.214483Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-4 +2026-07-20T04:56:55.221464Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.221497Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-5 +2026-07-20T04:56:55.236372Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.236386Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-6 +2026-07-20T04:56:55.244454Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.244468Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=-10 +2026-07-20T04:56:55.251457Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.251486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-9 +2026-07-20T04:56:55.259495Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.259513Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=-15 +2026-07-20T04:56:55.266452Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.266466Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-13 +2026-07-20T04:56:55.274480Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.274519Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-22 +2026-07-20T04:56:55.281490Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.281530Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-17 +2026-07-20T04:56:55.289485Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.289498Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-17 +2026-07-20T04:56:55.296373Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.296386Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-26 +2026-07-20T04:56:55.304374Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.304400Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-24 +2026-07-20T04:56:55.311403Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.311443Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=-20 +2026-07-20T04:56:55.319428Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.319460Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=-15 +2026-07-20T04:56:55.326419Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.326458Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=-11 +2026-07-20T04:56:55.331982Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:56:55.331995Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:56:55.331997Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:55.331999Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:55.332001Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:55.332003Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:56:55.332005Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:56:55.332007Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:56:55.332009Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:55.332011Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:55.332013Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:55.332015Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:56:55.332017Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:56:55.332019Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:56:55.332021Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:56:55.332023Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:56:55.332028Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=2 +2026-07-20T04:56:55.332042Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:56:55.333408Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:56:55.334344Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 234, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.334378Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-22 dy=-5 +2026-07-20T04:56:55.340392Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:56:55.340493Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:56:55.341354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 222, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.341367Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-34 dy=-2 +2026-07-20T04:56:55.348325Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:56:55.349435Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:56:55.351282Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 221, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.351294Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-35 dy=0 +2026-07-20T04:56:55.356338Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 220, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.356373Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-36 dy=5 +2026-07-20T04:56:55.364381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 214, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.364397Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-42 dy=9 +2026-07-20T04:56:55.371416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 218, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.371429Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-38 dy=13 +2026-07-20T04:56:55.379497Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 219, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.379538Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-37 dy=14 +2026-07-20T04:56:55.386325Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 221, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.386345Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-35 dy=18 +2026-07-20T04:56:55.394421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 216, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.394463Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-40 dy=27 +2026-07-20T04:56:55.401507Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.401520Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=24 +2026-07-20T04:56:55.409339Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 230, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.409351Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-26 dy=29 +2026-07-20T04:56:55.416337Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 233, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.416350Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-23 dy=33 +2026-07-20T04:56:55.424484Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.424498Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=29 +2026-07-20T04:56:55.431522Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.431545Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=26 +2026-07-20T04:56:55.439390Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.439407Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=31 +2026-07-20T04:56:55.446355Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.446368Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=21 +2026-07-20T04:56:55.454383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.454407Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=18 +2026-07-20T04:56:55.461427Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.461443Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=16 +2026-07-20T04:56:55.469455Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 23, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.469492Z INFO openlogi_hid::gesture: SPIKE raw xy dx=23 dy=12 +2026-07-20T04:56:55.476402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 35, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.476415Z INFO openlogi_hid::gesture: SPIKE raw xy dx=35 dy=10 +2026-07-20T04:56:55.484383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 34, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.484396Z INFO openlogi_hid::gesture: SPIKE raw xy dx=34 dy=6 +2026-07-20T04:56:55.491467Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 37, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.491504Z INFO openlogi_hid::gesture: SPIKE raw xy dx=37 dy=4 +2026-07-20T04:56:55.499453Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.499467Z INFO openlogi_hid::gesture: SPIKE raw xy dx=40 dy=0 +2026-07-20T04:56:55.506511Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 48, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.506544Z INFO openlogi_hid::gesture: SPIKE raw xy dx=48 dy=-3 +2026-07-20T04:56:55.514420Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 45, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.514436Z INFO openlogi_hid::gesture: SPIKE raw xy dx=45 dy=-8 +2026-07-20T04:56:55.521472Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 37, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.521485Z INFO openlogi_hid::gesture: SPIKE raw xy dx=37 dy=-9 +2026-07-20T04:56:55.529435Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 45, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.529454Z INFO openlogi_hid::gesture: SPIKE raw xy dx=45 dy=-15 +2026-07-20T04:56:55.536412Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 35, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.536431Z INFO openlogi_hid::gesture: SPIKE raw xy dx=35 dy=-15 +2026-07-20T04:56:55.544535Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 31, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.544548Z INFO openlogi_hid::gesture: SPIKE raw xy dx=31 dy=-19 +2026-07-20T04:56:55.551419Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 255, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.551432Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=-25 +2026-07-20T04:56:55.559487Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.559500Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-24 +2026-07-20T04:56:55.566418Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 255, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.566432Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=-26 +2026-07-20T04:56:55.574469Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 229, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.574554Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-27 +2026-07-20T04:56:55.581452Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 229, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.581465Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-27 +2026-07-20T04:56:55.589465Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.589479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-26 +2026-07-20T04:56:55.596354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.596377Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-24 +2026-07-20T04:56:55.604451Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 236, 255, 229, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.604488Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-20 dy=-27 +2026-07-20T04:56:55.611395Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 236, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.611408Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-20 dy=-18 +2026-07-20T04:56:55.619401Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 225, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.619417Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-31 dy=-19 +2026-07-20T04:56:55.626404Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 226, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.626419Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-30 dy=-14 +2026-07-20T04:56:55.634486Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 216, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.634504Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-40 dy=-11 +2026-07-20T04:56:55.641474Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 220, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.641491Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-36 dy=-5 +2026-07-20T04:56:55.649453Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 212, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.649485Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-44 dy=-2 +2026-07-20T04:56:55.656341Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.656355Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-36 dy=0 +2026-07-20T04:56:55.664466Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 222, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.664480Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-34 dy=3 +2026-07-20T04:56:55.671310Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 224, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.671323Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-32 dy=6 +2026-07-20T04:56:55.679478Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 222, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.679491Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-34 dy=8 +2026-07-20T04:56:55.686396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 234, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.686417Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-22 dy=8 +2026-07-20T04:56:55.694363Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.694378Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=8 +2026-07-20T04:56:55.701421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.701433Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=9 +2026-07-20T04:56:55.709410Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.709427Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=8 +2026-07-20T04:56:55.716357Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.716379Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=8 +2026-07-20T04:56:55.724475Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.724489Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=7 +2026-07-20T04:56:55.731403Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.731417Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=9 +2026-07-20T04:56:55.739402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.739417Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=7 +2026-07-20T04:56:55.746427Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.746440Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=9 +2026-07-20T04:56:55.754388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.754401Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=5 +2026-07-20T04:56:55.761419Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 32, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.761444Z INFO openlogi_hid::gesture: SPIKE raw xy dx=32 dy=6 +2026-07-20T04:56:55.769330Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 26, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.769347Z INFO openlogi_hid::gesture: SPIKE raw xy dx=26 dy=3 +2026-07-20T04:56:55.776417Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 33, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.776438Z INFO openlogi_hid::gesture: SPIKE raw xy dx=33 dy=3 +2026-07-20T04:56:55.784345Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 40, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.784359Z INFO openlogi_hid::gesture: SPIKE raw xy dx=40 dy=4 +2026-07-20T04:56:55.791375Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 34, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.791388Z INFO openlogi_hid::gesture: SPIKE raw xy dx=34 dy=3 +2026-07-20T04:56:55.799634Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 30, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.799648Z INFO openlogi_hid::gesture: SPIKE raw xy dx=30 dy=2 +2026-07-20T04:56:55.806550Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.806579Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=3 +2026-07-20T04:56:55.814445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 27, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.814459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=27 dy=3 +2026-07-20T04:56:55.821495Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 26, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.821508Z INFO openlogi_hid::gesture: SPIKE raw xy dx=26 dy=4 +2026-07-20T04:56:55.829468Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.829480Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=3 +2026-07-20T04:56:55.836376Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.836392Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=5 +2026-07-20T04:56:55.844457Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.844474Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=3 +2026-07-20T04:56:55.851291Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.851305Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=4 +2026-07-20T04:56:55.859399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.859425Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=4 +2026-07-20T04:56:55.866436Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.866457Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=3 +2026-07-20T04:56:55.874381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.874396Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=4 +2026-07-20T04:56:55.881456Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.881469Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=4 +2026-07-20T04:56:55.889344Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.889361Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=4 +2026-07-20T04:56:55.896429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.896448Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=4 +2026-07-20T04:56:55.904390Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.904413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=2 +2026-07-20T04:56:55.919486Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.919524Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=2 +2026-07-20T04:56:55.926491Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 224, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.926512Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-32 dy=1 +2026-07-20T04:56:55.934441Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.934454Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=0 +2026-07-20T04:56:55.941464Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.941496Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=0 +2026-07-20T04:56:55.949354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.949369Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=-2 +2026-07-20T04:56:55.964417Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.964439Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=0 +2026-07-20T04:56:55.971543Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.971566Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-1 +2026-07-20T04:56:55.979421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.979439Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=0 +2026-07-20T04:56:55.986472Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:55.986492Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:56.001487Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.001521Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=0 +2026-07-20T04:56:56.009434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.009464Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=0 +2026-07-20T04:56:56.016476Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.016510Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=0 +2026-07-20T04:56:56.024432Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.024480Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=0 +2026-07-20T04:56:56.031422Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.031447Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=0 +2026-07-20T04:56:56.046374Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.046403Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=1 +2026-07-20T04:56:56.054537Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.054568Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=0 +2026-07-20T04:56:56.061380Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.061396Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:56:56.069416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.069450Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:56:56.121410Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.121430Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:56:56.129422Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.129440Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=2 +2026-07-20T04:56:56.136407Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.136421Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=3 +2026-07-20T04:56:56.144394Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.144407Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=3 +2026-07-20T04:56:56.151407Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.151422Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=3 +2026-07-20T04:56:56.159378Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.159405Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=3 +2026-07-20T04:56:56.166372Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.166403Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=1 +2026-07-20T04:56:56.174485Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.174499Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=4 +2026-07-20T04:56:56.181418Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.181443Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=3 +2026-07-20T04:56:56.189476Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.189501Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=4 +2026-07-20T04:56:56.196455Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.196469Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=5 +2026-07-20T04:56:56.204462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.204475Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=5 +2026-07-20T04:56:56.211497Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.211533Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=2 +2026-07-20T04:56:56.219430Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.219443Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=6 +2026-07-20T04:56:56.226427Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.226441Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=5 +2026-07-20T04:56:56.234379Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.234393Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=3 +2026-07-20T04:56:56.241435Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.241451Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=6 +2026-07-20T04:56:56.249497Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.249514Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=7 +2026-07-20T04:56:56.256395Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.256426Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=2 +2026-07-20T04:56:56.264462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.264471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=5 +2026-07-20T04:56:56.271406Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.271417Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=2 +2026-07-20T04:56:56.279354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.279381Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=3 +2026-07-20T04:56:56.286399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.286429Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=1 +2026-07-20T04:56:56.294445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.294472Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=1 +2026-07-20T04:56:56.301390Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.301402Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=0 +2026-07-20T04:56:56.309407Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.309444Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-1 +2026-07-20T04:56:56.324404Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.324422Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-2 +2026-07-20T04:56:56.331396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.331420Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-4 +2026-07-20T04:56:56.339444Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 34, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.339479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=34 dy=-12 +2026-07-20T04:56:56.346403Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.346418Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=-7 +2026-07-20T04:56:56.354464Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.354479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=-7 +2026-07-20T04:56:56.361397Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.361413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=-8 +2026-07-20T04:56:56.369458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.369472Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-7 +2026-07-20T04:56:56.376355Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.376368Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=-5 +2026-07-20T04:56:56.384496Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.384536Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-7 +2026-07-20T04:56:56.391398Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.391413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-5 +2026-07-20T04:56:56.399445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.399459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-3 +2026-07-20T04:56:56.414467Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.414481Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-2 +2026-07-20T04:56:56.421395Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.421419Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-2 +2026-07-20T04:56:56.429398Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.429418Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=-7 +2026-07-20T04:56:56.436481Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.436526Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-5 +2026-07-20T04:56:56.444340Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 228, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.444352Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-28 dy=-7 +2026-07-20T04:56:56.451394Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.451408Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=-6 +2026-07-20T04:56:56.459479Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 223, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.459494Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-33 dy=-10 +2026-07-20T04:56:56.466395Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 227, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.466408Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-29 dy=-8 +2026-07-20T04:56:56.474447Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 228, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.474459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-28 dy=-10 +2026-07-20T04:56:56.481489Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 231, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.481503Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-25 dy=-9 +2026-07-20T04:56:56.489468Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.489495Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=-13 +2026-07-20T04:56:56.496521Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.496562Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=-9 +2026-07-20T04:56:56.504468Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.504496Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-12 +2026-07-20T04:56:56.511466Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.511479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-7 +2026-07-20T04:56:56.519493Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.526502Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-8 +2026-07-20T04:56:56.526545Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.526555Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-9 +2026-07-20T04:56:56.534498Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.534513Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-7 +2026-07-20T04:56:56.541424Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.541436Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-9 +2026-07-20T04:56:56.549549Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.549562Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-6 +2026-07-20T04:56:56.556304Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 24, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.556333Z INFO openlogi_hid::gesture: SPIKE raw xy dx=24 dy=-6 +2026-07-20T04:56:56.564400Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 21, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.564427Z INFO openlogi_hid::gesture: SPIKE raw xy dx=21 dy=-2 +2026-07-20T04:56:56.571387Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.571413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=29 dy=0 +2026-07-20T04:56:56.579335Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 29, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.579348Z INFO openlogi_hid::gesture: SPIKE raw xy dx=29 dy=4 +2026-07-20T04:56:56.586485Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 37, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.586523Z INFO openlogi_hid::gesture: SPIKE raw xy dx=37 dy=10 +2026-07-20T04:56:56.594361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 32, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.594375Z INFO openlogi_hid::gesture: SPIKE raw xy dx=32 dy=13 +2026-07-20T04:56:56.601373Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 34, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.601388Z INFO openlogi_hid::gesture: SPIKE raw xy dx=34 dy=19 +2026-07-20T04:56:56.609515Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.609531Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=21 +2026-07-20T04:56:56.616419Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.616456Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=24 +2026-07-20T04:56:56.625498Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.625528Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=21 +2026-07-20T04:56:56.631413Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.631437Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=29 +2026-07-20T04:56:56.639471Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.639485Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=21 +2026-07-20T04:56:56.646473Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.646511Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=25 +2026-07-20T04:56:56.654331Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.654347Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=17 +2026-07-20T04:56:56.661377Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.661396Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=19 +2026-07-20T04:56:56.669390Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.669404Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=10 +2026-07-20T04:56:56.676373Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.676386Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=7 +2026-07-20T04:56:56.684464Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.684494Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=4 +2026-07-20T04:56:56.691480Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.691492Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=0 +2026-07-20T04:56:56.699438Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 225, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.699446Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-31 dy=-6 +2026-07-20T04:56:56.706388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 231, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.706401Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-25 dy=-7 +2026-07-20T04:56:56.714456Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 224, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.714469Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-32 dy=-14 +2026-07-20T04:56:56.721461Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 220, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.721475Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-36 dy=-19 +2026-07-20T04:56:56.729468Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 230, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.729488Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-26 dy=-18 +2026-07-20T04:56:56.736371Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.736386Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=-20 +2026-07-20T04:56:56.744367Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 255, 235, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.744380Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=-21 +2026-07-20T04:56:56.751346Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.751374Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=-18 +2026-07-20T04:56:56.759456Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 255, 233, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.759494Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=-23 +2026-07-20T04:56:56.766341Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.766353Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-15 +2026-07-20T04:56:56.774342Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.774363Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-20 +2026-07-20T04:56:56.781343Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.781377Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-12 +2026-07-20T04:56:56.789333Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.789348Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-12 +2026-07-20T04:56:56.796359Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.796375Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-6 +2026-07-20T04:56:56.804493Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.804515Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-1 +2026-07-20T04:56:56.811492Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.811522Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=1 +2026-07-20T04:56:56.819358Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.819371Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=4 +2026-07-20T04:56:56.826387Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.826400Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=9 +2026-07-20T04:56:56.834514Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.834545Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=9 +2026-07-20T04:56:56.841434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 21, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.841479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=21 dy=12 +2026-07-20T04:56:56.849498Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 23, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.849538Z INFO openlogi_hid::gesture: SPIKE raw xy dx=23 dy=16 +2026-07-20T04:56:56.856445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.856463Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=18 +2026-07-20T04:56:56.858505Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:56:56.861415Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:56.861485Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:56.870397Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:56:56.871454Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.871467Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=13 +2026-07-20T04:56:56.878385Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:56:56.878422Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:56:56.879451Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.879477Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=18 +2026-07-20T04:56:56.880462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.880475Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=13 +2026-07-20T04:56:56.887339Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.887358Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=10 +2026-07-20T04:56:56.894331Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.894344Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=8 +2026-07-20T04:56:56.901388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.903425Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:56.903495Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:56.906352Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:56.906352Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:56.908461Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:56.908473Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:56.909508Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.909521Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=5 +2026-07-20T04:56:56.911459Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:56:56.911476Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:56:56.916445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.916458Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=3 +2026-07-20T04:56:56.924399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 236, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.924412Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-20 dy=-2 +2026-07-20T04:56:56.931453Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.931466Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-6 +2026-07-20T04:56:56.939418Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.939430Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=-10 +2026-07-20T04:56:56.946503Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.946533Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=-10 +2026-07-20T04:56:56.954466Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.954481Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-13 +2026-07-20T04:56:56.961396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.961422Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-15 +2026-07-20T04:56:56.969402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.969420Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=-15 +2026-07-20T04:56:56.976345Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.976366Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-15 +2026-07-20T04:56:56.984368Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.984382Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-12 +2026-07-20T04:56:56.991460Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.991472Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-8 +2026-07-20T04:56:56.999498Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:56.999525Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-4 +2026-07-20T04:56:57.006392Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.006407Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-2 +2026-07-20T04:56:57.014418Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.014433Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=0 +2026-07-20T04:56:57.021390Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.021403Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=0 +2026-07-20T04:56:57.029510Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.029522Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=4 +2026-07-20T04:56:57.036465Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.036478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=6 +2026-07-20T04:56:57.044581Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.044626Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=11 +2026-07-20T04:56:57.051532Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.051553Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=10 +2026-07-20T04:56:57.059472Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.059505Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=14 +2026-07-20T04:56:57.066416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.066430Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=9 +2026-07-20T04:56:57.074407Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.074423Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=17 +2026-07-20T04:56:57.081366Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.081403Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=11 +2026-07-20T04:56:57.089469Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.089477Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=12 +2026-07-20T04:56:57.096398Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.096435Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=11 +2026-07-20T04:56:57.104446Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.104459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=7 +2026-07-20T04:56:57.111464Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.111486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=5 +2026-07-20T04:56:57.119354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.119367Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=4 +2026-07-20T04:56:57.126366Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.126379Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=2 +2026-07-20T04:56:57.134370Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.134390Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=0 +2026-07-20T04:56:57.141319Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.141332Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=-2 +2026-07-20T04:56:57.149371Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.149384Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-3 +2026-07-20T04:56:57.156290Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.156304Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=-5 +2026-07-20T04:56:57.164421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.164433Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-5 +2026-07-20T04:56:57.171515Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.171550Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=-8 +2026-07-20T04:56:57.179410Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.179432Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-6 +2026-07-20T04:56:57.186444Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.186464Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-9 +2026-07-20T04:56:57.194392Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.194423Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-8 +2026-07-20T04:56:57.201430Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.201449Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-5 +2026-07-20T04:56:57.209539Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.209562Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-9 +2026-07-20T04:56:57.216423Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.216446Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-8 +2026-07-20T04:56:57.224478Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.224514Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-4 +2026-07-20T04:56:57.231411Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.231428Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-5 +2026-07-20T04:56:57.239386Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.239399Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=-2 +2026-07-20T04:56:57.246335Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.246350Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=0 +2026-07-20T04:56:57.254347Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.254373Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=0 +2026-07-20T04:56:57.261392Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.261409Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=3 +2026-07-20T04:56:57.269380Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.269406Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=3 +2026-07-20T04:56:57.276385Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.276403Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=7 +2026-07-20T04:56:57.284342Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.284367Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=6 +2026-07-20T04:56:57.291382Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.291394Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=7 +2026-07-20T04:56:57.299344Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.299371Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=7 +2026-07-20T04:56:57.306351Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.306357Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=8 +2026-07-20T04:56:57.314380Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.314393Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=5 +2026-07-20T04:56:57.321389Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.321402Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=6 +2026-07-20T04:56:57.329444Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.329497Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=3 +2026-07-20T04:56:57.336375Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.336413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=2 +2026-07-20T04:56:57.344396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.344409Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=2 +2026-07-20T04:56:57.351384Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.351398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=2 +2026-07-20T04:56:57.359423Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.359442Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=1 +2026-07-20T04:56:57.366393Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.366405Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=0 +2026-07-20T04:56:57.374405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.374437Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=0 +2026-07-20T04:56:57.381397Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.381423Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=0 +2026-07-20T04:56:57.389338Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.389351Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-1 +2026-07-20T04:56:57.396383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.396396Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-2 +2026-07-20T04:56:57.404342Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.404363Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-3 +2026-07-20T04:56:57.411454Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.411469Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-2 +2026-07-20T04:56:57.419395Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.419423Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-3 +2026-07-20T04:56:57.426352Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.426365Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-2 +2026-07-20T04:56:57.434605Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.434620Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-4 +2026-07-20T04:56:57.441365Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.441378Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-3 +2026-07-20T04:56:57.449508Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.449521Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-3 +2026-07-20T04:56:57.456413Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.456427Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-2 +2026-07-20T04:56:57.464482Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.464495Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-1 +2026-07-20T04:56:57.471522Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.471542Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-2 +2026-07-20T04:56:57.479441Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.479480Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-1 +2026-07-20T04:56:57.486408Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.486421Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:57.501366Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.501380Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-1 +2026-07-20T04:56:57.509298Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.509310Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:57.531474Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:57.531500Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:58.927376Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:58.927442Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:56:58.934313Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:58.934336Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:56:58.941312Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:56:58.949307Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:58.949331Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-8 +2026-07-20T04:56:58.956309Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:58.956334Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-18 +2026-07-20T04:56:58.964315Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:56:58.971362Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:58.971401Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-10 +2026-07-20T04:56:58.979368Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:58.979402Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=-18 +2026-07-20T04:56:58.986329Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:58.986466Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-10 +2026-07-20T04:56:58.994317Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:58.994344Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-11 +2026-07-20T04:56:59.001302Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.001332Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-8 +2026-07-20T04:56:59.010336Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.010408Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=-15 +2026-07-20T04:56:59.017388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.017420Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-11 +2026-07-20T04:56:59.024308Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.024332Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=-11 +2026-07-20T04:56:59.032317Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.032344Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=-13 +2026-07-20T04:56:59.034336Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:56:59.034367Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:56:59.034376Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:59.034383Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:59.034389Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:59.034395Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:56:59.034401Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:56:59.034407Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:56:59.034413Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:56:59.034419Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:59.034425Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:56:59.034431Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:56:59.034437Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:56:59.034548Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:56:59.034557Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:56:59.034564Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:56:59.034576Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=2 +2026-07-20T04:56:59.034595Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:56:59.037305Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:56:59.043329Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:56:59.043409Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:56:59.045294Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.045312Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=-8 +2026-07-20T04:56:59.052425Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:56:59.053311Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:56:59.054288Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.054315Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=-8 +2026-07-20T04:56:59.056326Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.056353Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=-4 +2026-07-20T04:56:59.061339Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 231, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.061370Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-25 dy=-6 +2026-07-20T04:56:59.069339Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.069374Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-3 +2026-07-20T04:56:59.076300Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.076323Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=-4 +2026-07-20T04:56:59.084439Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.084463Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-2 +2026-07-20T04:56:59.091357Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.091380Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-1 +2026-07-20T04:56:59.099353Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.099479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=0 +2026-07-20T04:56:59.103905Z DEBUG openlogi_agent_core::watchers::foreground_app: frontmost app changed current=Some("c:\\program files\\windowsapps\\claude_1.22209.3.0_x64__pzs8sxrjxfjjc\\app\\claude.exe") last=Some("c:\\program files (x86)\\steam\\steamapps\\common\\command & conquer generals - zero hour\\game.dat") +2026-07-20T04:56:59.106300Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.106319Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=0 +2026-07-20T04:56:59.114325Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.114349Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=2 +2026-07-20T04:56:59.121313Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.121338Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=3 +2026-07-20T04:56:59.129311Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.129337Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=3 +2026-07-20T04:56:59.136330Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.136362Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=5 +2026-07-20T04:56:59.144317Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.144349Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=7 +2026-07-20T04:56:59.151324Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.151356Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=8 +2026-07-20T04:56:59.159406Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.159475Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=8 +2026-07-20T04:56:59.166312Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.166337Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=9 +2026-07-20T04:56:59.174340Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.174370Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=16 +2026-07-20T04:56:59.181342Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.181367Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=12 +2026-07-20T04:56:59.189351Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.189467Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=14 +2026-07-20T04:56:59.196355Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.196388Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=15 +2026-07-20T04:56:59.204345Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.204367Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=18 +2026-07-20T04:56:59.211324Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.211344Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=18 +2026-07-20T04:56:59.219332Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.219380Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=18 +2026-07-20T04:56:59.226388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.226414Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=18 +2026-07-20T04:56:59.234322Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.234352Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=19 +2026-07-20T04:56:59.241317Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.241349Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=27 +2026-07-20T04:56:59.249307Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.249340Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=17 +2026-07-20T04:56:59.256334Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.256368Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=16 +2026-07-20T04:56:59.264349Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.264398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=15 +2026-07-20T04:56:59.271322Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.271344Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=16 +2026-07-20T04:56:59.279386Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.279410Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=8 +2026-07-20T04:56:59.286303Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.286329Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=12 +2026-07-20T04:56:59.294335Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.294361Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=7 +2026-07-20T04:56:59.301374Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.301418Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=4 +2026-07-20T04:56:59.309371Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.309402Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=4 +2026-07-20T04:56:59.316309Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.316332Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=2 +2026-07-20T04:56:59.324370Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.324396Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=0 +2026-07-20T04:56:59.331368Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.331386Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=0 +2026-07-20T04:56:59.339398Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.339413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-3 +2026-07-20T04:56:59.346303Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 27, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.346360Z INFO openlogi_hid::gesture: SPIKE raw xy dx=27 dy=-4 +2026-07-20T04:56:59.354359Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 23, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.354376Z INFO openlogi_hid::gesture: SPIKE raw xy dx=23 dy=-6 +2026-07-20T04:56:59.361392Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 26, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.361409Z INFO openlogi_hid::gesture: SPIKE raw xy dx=26 dy=-8 +2026-07-20T04:56:59.369350Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 21, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.369404Z INFO openlogi_hid::gesture: SPIKE raw xy dx=21 dy=-7 +2026-07-20T04:56:59.376374Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 23, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.376403Z INFO openlogi_hid::gesture: SPIKE raw xy dx=23 dy=-8 +2026-07-20T04:56:59.384367Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 26, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.384415Z INFO openlogi_hid::gesture: SPIKE raw xy dx=26 dy=-11 +2026-07-20T04:56:59.391295Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 26, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.391319Z INFO openlogi_hid::gesture: SPIKE raw xy dx=26 dy=-13 +2026-07-20T04:56:59.399340Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.399374Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=-11 +2026-07-20T04:56:59.406331Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.406362Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-10 +2026-07-20T04:56:59.414360Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.414387Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=-18 +2026-07-20T04:56:59.421313Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.421337Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-13 +2026-07-20T04:56:59.429305Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.429324Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-13 +2026-07-20T04:56:59.436307Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 21, 255, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.436329Z INFO openlogi_hid::gesture: SPIKE raw xy dx=21 dy=-22 +2026-07-20T04:56:59.444315Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.444348Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-8 +2026-07-20T04:56:59.451364Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 255, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.451382Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=-24 +2026-07-20T04:56:59.459382Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.459406Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-13 +2026-07-20T04:56:59.466292Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.466307Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-19 +2026-07-20T04:56:59.474330Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.474346Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-14 +2026-07-20T04:56:59.481383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.481401Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-15 +2026-07-20T04:56:59.489360Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.489396Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-15 +2026-07-20T04:56:59.496381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.496413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-9 +2026-07-20T04:56:59.504453Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.504487Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-15 +2026-07-20T04:56:59.511564Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.511619Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-9 +2026-07-20T04:56:59.519367Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.519395Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-8 +2026-07-20T04:56:59.526355Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.526387Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-9 +2026-07-20T04:56:59.534435Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.534474Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=-7 +2026-07-20T04:56:59.541374Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.541406Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-3 +2026-07-20T04:56:59.549393Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.549424Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=-3 +2026-07-20T04:56:59.556368Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.556392Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=-2 +2026-07-20T04:56:59.564406Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.564429Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=-2 +2026-07-20T04:56:59.571336Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.571364Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-1 +2026-07-20T04:56:59.579402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.579428Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-2 +2026-07-20T04:56:59.586455Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.586489Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=-1 +2026-07-20T04:56:59.594461Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.594490Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-1 +2026-07-20T04:56:59.601365Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.601390Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=-2 +2026-07-20T04:56:59.609309Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.609356Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-1 +2026-07-20T04:56:59.616323Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.616345Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=-2 +2026-07-20T04:56:59.624358Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.624394Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=-1 +2026-07-20T04:56:59.631317Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.631338Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=-2 +2026-07-20T04:56:59.639304Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.639330Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=0 +2026-07-20T04:56:59.646323Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.646359Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=-1 +2026-07-20T04:56:59.654292Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.654315Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=0 +2026-07-20T04:56:59.661333Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.661356Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=0 +2026-07-20T04:56:59.669292Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.669327Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=1 +2026-07-20T04:56:59.676284Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.676303Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=1 +2026-07-20T04:56:59.684414Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.684430Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=2 +2026-07-20T04:56:59.691325Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.691340Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=2 +2026-07-20T04:56:59.699323Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.699341Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=1 +2026-07-20T04:56:59.706460Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.706477Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=2 +2026-07-20T04:56:59.714428Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.714443Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=3 +2026-07-20T04:56:59.721331Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.721349Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=3 +2026-07-20T04:56:59.729374Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.729396Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=5 +2026-07-20T04:56:59.736414Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.736437Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=6 +2026-07-20T04:56:59.744517Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.744534Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=5 +2026-07-20T04:56:59.751466Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.751495Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=10 +2026-07-20T04:56:59.759506Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.759528Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=8 +2026-07-20T04:56:59.766464Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.766487Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=9 +2026-07-20T04:56:59.774526Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.774559Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=15 +2026-07-20T04:56:59.781401Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.781427Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=11 +2026-07-20T04:56:59.789561Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.789608Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=11 +2026-07-20T04:56:59.796499Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.796529Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=6 +2026-07-20T04:56:59.804585Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.804615Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=13 +2026-07-20T04:56:59.811580Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.811611Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=12 +2026-07-20T04:56:59.819483Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.819513Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=14 +2026-07-20T04:56:59.826388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.826424Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=14 +2026-07-20T04:56:59.834597Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.834636Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=9 +2026-07-20T04:56:59.841576Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.841620Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=14 +2026-07-20T04:56:59.849377Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.849396Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=10 +2026-07-20T04:56:59.856421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.856452Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=11 +2026-07-20T04:56:59.864494Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.864511Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=10 +2026-07-20T04:56:59.871392Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.871413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=15 +2026-07-20T04:56:59.879391Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.879420Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=9 +2026-07-20T04:56:59.886669Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.886713Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=9 +2026-07-20T04:56:59.894577Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.894609Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=12 +2026-07-20T04:56:59.901498Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.901518Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=9 +2026-07-20T04:56:59.909528Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.909546Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=8 +2026-07-20T04:56:59.916578Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.916615Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=7 +2026-07-20T04:56:59.924405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.924419Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=8 +2026-07-20T04:56:59.931488Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.931505Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=7 +2026-07-20T04:56:59.939462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.939503Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=6 +2026-07-20T04:56:59.946332Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.946367Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=5 +2026-07-20T04:56:59.954486Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.954527Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=4 +2026-07-20T04:56:59.961486Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.961527Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=4 +2026-07-20T04:56:59.969493Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.969539Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=4 +2026-07-20T04:56:59.976651Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.976686Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=4 +2026-07-20T04:56:59.984362Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.984400Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=4 +2026-07-20T04:56:59.991507Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.991517Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=3 +2026-07-20T04:56:59.999489Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:56:59.999506Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=1 +2026-07-20T04:57:00.006362Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.006382Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=2 +2026-07-20T04:57:00.014516Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.014548Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=0 +2026-07-20T04:57:00.021405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.021434Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-2 +2026-07-20T04:57:00.029377Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.029400Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:57:00.036390Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.036426Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-3 +2026-07-20T04:57:00.044511Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.044544Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-5 +2026-07-20T04:57:00.051491Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.051532Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-6 +2026-07-20T04:57:00.059374Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.059394Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-8 +2026-07-20T04:57:00.066486Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.066522Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=-9 +2026-07-20T04:57:00.074481Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.074517Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-7 +2026-07-20T04:57:00.081482Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.081515Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=-12 +2026-07-20T04:57:00.089480Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.089516Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=-9 +2026-07-20T04:57:00.096469Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.096499Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=-9 +2026-07-20T04:57:00.104412Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.104438Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-8 +2026-07-20T04:57:00.111477Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.111495Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=-11 +2026-07-20T04:57:00.119532Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.119557Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=-6 +2026-07-20T04:57:00.126357Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 30, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.126373Z INFO openlogi_hid::gesture: SPIKE raw xy dx=30 dy=-7 +2026-07-20T04:57:00.134449Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.134472Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=-4 +2026-07-20T04:57:00.141308Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.141321Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=-2 +2026-07-20T04:57:00.149512Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 27, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.149550Z INFO openlogi_hid::gesture: SPIKE raw xy dx=27 dy=-3 +2026-07-20T04:57:00.156377Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.156389Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=-1 +2026-07-20T04:57:00.164505Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.164518Z INFO openlogi_hid::gesture: SPIKE raw xy dx=21 dy=0 +2026-07-20T04:57:00.171465Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.171484Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=-1 +2026-07-20T04:57:00.179588Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.179610Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=0 +2026-07-20T04:57:00.186365Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.186384Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=0 +2026-07-20T04:57:00.194477Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.194515Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=0 +2026-07-20T04:57:00.201532Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.201549Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=1 +2026-07-20T04:57:00.209402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.209424Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:57:00.404412Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.404449Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:57:00.419625Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.419661Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-2 +2026-07-20T04:57:00.426644Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.426679Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-3 +2026-07-20T04:57:00.434776Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.434846Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-2 +2026-07-20T04:57:00.441518Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.441538Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-6 +2026-07-20T04:57:00.449393Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.449411Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-5 +2026-07-20T04:57:00.456472Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.456506Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=-10 +2026-07-20T04:57:00.464438Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.464474Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-8 +2026-07-20T04:57:00.471436Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.471471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-9 +2026-07-20T04:57:00.479513Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.479550Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-12 +2026-07-20T04:57:00.486439Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.486482Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-12 +2026-07-20T04:57:00.494350Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.494379Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=-20 +2026-07-20T04:57:00.501321Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.501336Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-14 +2026-07-20T04:57:00.509335Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.509375Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-14 +2026-07-20T04:57:00.516412Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.516446Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-16 +2026-07-20T04:57:00.524439Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.524470Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-19 +2026-07-20T04:57:00.531412Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.531430Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-15 +2026-07-20T04:57:00.539432Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.539454Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-14 +2026-07-20T04:57:00.546434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.546466Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-15 +2026-07-20T04:57:00.554436Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.554462Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-10 +2026-07-20T04:57:00.561510Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.561546Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=-8 +2026-07-20T04:57:00.567680Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:00.569518Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.569551Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=-6 +2026-07-20T04:57:00.571448Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:00.571573Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:00.582527Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:00.583437Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.583461Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=-6 +2026-07-20T04:57:00.590474Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:00.590539Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:00.591590Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.591619Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=-3 +2026-07-20T04:57:00.592449Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 236, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.592476Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-20 dy=-1 +2026-07-20T04:57:00.599434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 231, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.608391Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-25 dy=-1 +2026-07-20T04:57:00.608492Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 218, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.608501Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-38 dy=1 +2026-07-20T04:57:00.614437Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.617386Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:00.617474Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:00.619467Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:00.619549Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:00.620468Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:00.620485Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:00.621453Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 226, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.621490Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-30 dy=3 +2026-07-20T04:57:00.622468Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:00.622470Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:00.629359Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 191, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.629385Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-65 dy=12 +2026-07-20T04:57:00.636481Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 217, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.636500Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-39 dy=9 +2026-07-20T04:57:00.644643Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 228, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.644669Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-28 dy=7 +2026-07-20T04:57:00.651503Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 218, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.651528Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-38 dy=11 +2026-07-20T04:57:00.659472Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 230, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.659497Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-26 dy=9 +2026-07-20T04:57:00.666458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 222, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.666478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-34 dy=13 +2026-07-20T04:57:00.674588Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 231, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.674625Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-25 dy=10 +2026-07-20T04:57:00.681493Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 233, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.681514Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-23 dy=11 +2026-07-20T04:57:00.689366Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 234, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.689389Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-22 dy=11 +2026-07-20T04:57:00.696443Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.696481Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=8 +2026-07-20T04:57:00.704503Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.704539Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=10 +2026-07-20T04:57:00.711429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.711464Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=6 +2026-07-20T04:57:00.719553Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.719578Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=5 +2026-07-20T04:57:00.726643Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.726681Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=3 +2026-07-20T04:57:00.734559Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.734595Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=2 +2026-07-20T04:57:00.741527Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.741552Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=0 +2026-07-20T04:57:00.749489Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.749523Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=0 +2026-07-20T04:57:00.756428Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.756455Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=0 +2026-07-20T04:57:00.764560Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.764586Z INFO openlogi_hid::gesture: SPIKE raw xy dx=23 dy=0 +2026-07-20T04:57:00.771555Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.771572Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=0 +2026-07-20T04:57:00.779469Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.779516Z INFO openlogi_hid::gesture: SPIKE raw xy dx=32 dy=0 +2026-07-20T04:57:00.786395Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.786410Z INFO openlogi_hid::gesture: SPIKE raw xy dx=31 dy=0 +2026-07-20T04:57:00.794478Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 39, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.794505Z INFO openlogi_hid::gesture: SPIKE raw xy dx=39 dy=2 +2026-07-20T04:57:00.801700Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 40, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.801714Z INFO openlogi_hid::gesture: SPIKE raw xy dx=40 dy=4 +2026-07-20T04:57:00.809509Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 38, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.809522Z INFO openlogi_hid::gesture: SPIKE raw xy dx=38 dy=5 +2026-07-20T04:57:00.816549Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 36, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.816574Z INFO openlogi_hid::gesture: SPIKE raw xy dx=36 dy=6 +2026-07-20T04:57:00.824487Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 44, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.824517Z INFO openlogi_hid::gesture: SPIKE raw xy dx=44 dy=10 +2026-07-20T04:57:00.831557Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 34, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.831573Z INFO openlogi_hid::gesture: SPIKE raw xy dx=34 dy=9 +2026-07-20T04:57:00.839592Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 29, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.839606Z INFO openlogi_hid::gesture: SPIKE raw xy dx=29 dy=10 +2026-07-20T04:57:00.846489Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 36, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.846505Z INFO openlogi_hid::gesture: SPIKE raw xy dx=36 dy=12 +2026-07-20T04:57:00.854673Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.854726Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=11 +2026-07-20T04:57:00.861692Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 27, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.861743Z INFO openlogi_hid::gesture: SPIKE raw xy dx=27 dy=14 +2026-07-20T04:57:00.869767Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.869815Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=16 +2026-07-20T04:57:00.876421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 23, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.876442Z INFO openlogi_hid::gesture: SPIKE raw xy dx=23 dy=18 +2026-07-20T04:57:00.884489Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.884510Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=14 +2026-07-20T04:57:00.891512Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.891535Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=16 +2026-07-20T04:57:00.899509Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.899548Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=15 +2026-07-20T04:57:00.906556Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.906589Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=10 +2026-07-20T04:57:00.914521Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.914564Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=13 +2026-07-20T04:57:00.921656Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.921745Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=10 +2026-07-20T04:57:00.929455Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.929473Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=8 +2026-07-20T04:57:00.936557Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.936599Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=9 +2026-07-20T04:57:00.944542Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.944557Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=8 +2026-07-20T04:57:00.951465Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.951500Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=6 +2026-07-20T04:57:00.959785Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.959819Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=4 +2026-07-20T04:57:00.966518Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 227, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.966557Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-29 dy=2 +2026-07-20T04:57:00.974473Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 219, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.974525Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-37 dy=2 +2026-07-20T04:57:00.981476Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 228, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.981497Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-28 dy=0 +2026-07-20T04:57:00.989425Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 218, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.989464Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-38 dy=-1 +2026-07-20T04:57:00.996412Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 218, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:00.996449Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-38 dy=-3 +2026-07-20T04:57:01.004498Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 227, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.004522Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-29 dy=-4 +2026-07-20T04:57:01.011444Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 218, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.011465Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-38 dy=-7 +2026-07-20T04:57:01.019487Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 220, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.019509Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-36 dy=-10 +2026-07-20T04:57:01.026463Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 230, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.026500Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-26 dy=-10 +2026-07-20T04:57:01.034471Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 230, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.034484Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-26 dy=-12 +2026-07-20T04:57:01.041654Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 230, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.041693Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-26 dy=-16 +2026-07-20T04:57:01.049557Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.049587Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=-20 +2026-07-20T04:57:01.056530Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.056569Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=-20 +2026-07-20T04:57:01.064403Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 235, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.064423Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-21 +2026-07-20T04:57:01.071460Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 255, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.071486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=-25 +2026-07-20T04:57:01.079486Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 233, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.079513Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-23 +2026-07-20T04:57:01.086460Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.086497Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-20 +2026-07-20T04:57:01.094415Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.094444Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-22 +2026-07-20T04:57:01.101433Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.101464Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-24 +2026-07-20T04:57:01.109382Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.109410Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-26 +2026-07-20T04:57:01.116526Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.116545Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-16 +2026-07-20T04:57:01.124401Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 227, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.124428Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-29 +2026-07-20T04:57:01.131464Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.131476Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-14 +2026-07-20T04:57:01.139512Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.139533Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=-17 +2026-07-20T04:57:01.146474Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.146497Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=-11 +2026-07-20T04:57:01.154376Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.154408Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-7 +2026-07-20T04:57:01.161489Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 26, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.161549Z INFO openlogi_hid::gesture: SPIKE raw xy dx=26 dy=-7 +2026-07-20T04:57:01.169665Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.169715Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=-2 +2026-07-20T04:57:01.176620Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.176669Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=-1 +2026-07-20T04:57:01.184527Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.184571Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=2 +2026-07-20T04:57:01.191440Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 29, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.191461Z INFO openlogi_hid::gesture: SPIKE raw xy dx=29 dy=5 +2026-07-20T04:57:01.199432Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.199452Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=8 +2026-07-20T04:57:01.206446Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.206478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=12 +2026-07-20T04:57:01.214419Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.214435Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=13 +2026-07-20T04:57:01.221468Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.221484Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=20 +2026-07-20T04:57:01.229458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.229480Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=23 +2026-07-20T04:57:01.236494Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 21, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.236536Z INFO openlogi_hid::gesture: SPIKE raw xy dx=21 dy=21 +2026-07-20T04:57:01.244605Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.244653Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=35 +2026-07-20T04:57:01.251383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.251422Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=19 +2026-07-20T04:57:01.259420Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.259433Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=29 +2026-07-20T04:57:01.266384Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.266397Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=29 +2026-07-20T04:57:01.274498Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.274545Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=21 +2026-07-20T04:57:01.281504Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.281540Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=27 +2026-07-20T04:57:01.289383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.289407Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=20 +2026-07-20T04:57:01.296394Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.296417Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=20 +2026-07-20T04:57:01.304322Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.304345Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=15 +2026-07-20T04:57:01.311650Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.311687Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=11 +2026-07-20T04:57:01.319418Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.319440Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=13 +2026-07-20T04:57:01.326543Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.326591Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=7 +2026-07-20T04:57:01.334551Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.334583Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=5 +2026-07-20T04:57:01.341499Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.341527Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=5 +2026-07-20T04:57:01.349490Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.349514Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=1 +2026-07-20T04:57:01.356431Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 223, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.356464Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-33 dy=-2 +2026-07-20T04:57:01.364541Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 233, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.364573Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-23 dy=-4 +2026-07-20T04:57:01.371546Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 230, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.371579Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-26 dy=-7 +2026-07-20T04:57:01.379441Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 233, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.379480Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-23 dy=-8 +2026-07-20T04:57:01.386688Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.386739Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=-12 +2026-07-20T04:57:01.394491Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.394533Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-12 +2026-07-20T04:57:01.401475Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.401502Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=-14 +2026-07-20T04:57:01.409499Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.409530Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-14 +2026-07-20T04:57:01.416391Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.416410Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-15 +2026-07-20T04:57:01.424436Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.424479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-12 +2026-07-20T04:57:01.431432Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.431458Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-13 +2026-07-20T04:57:01.439445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.439478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-9 +2026-07-20T04:57:01.446401Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.446445Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-9 +2026-07-20T04:57:01.454500Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.454526Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=-10 +2026-07-20T04:57:01.461418Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.461443Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-6 +2026-07-20T04:57:01.469483Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.469509Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=-7 +2026-07-20T04:57:01.476630Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 24, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.476667Z INFO openlogi_hid::gesture: SPIKE raw xy dx=24 dy=-3 +2026-07-20T04:57:01.484481Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.484532Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=-1 +2026-07-20T04:57:01.491687Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.491781Z INFO openlogi_hid::gesture: SPIKE raw xy dx=31 dy=0 +2026-07-20T04:57:01.499621Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 29, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.499656Z INFO openlogi_hid::gesture: SPIKE raw xy dx=29 dy=2 +2026-07-20T04:57:01.508582Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 29, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.508620Z INFO openlogi_hid::gesture: SPIKE raw xy dx=29 dy=5 +2026-07-20T04:57:01.514438Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 38, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.514471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=38 dy=8 +2026-07-20T04:57:01.521529Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.521551Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=7 +2026-07-20T04:57:01.529379Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 27, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.529404Z INFO openlogi_hid::gesture: SPIKE raw xy dx=27 dy=9 +2026-07-20T04:57:01.536334Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.536384Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=7 +2026-07-20T04:57:01.544448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 29, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.544486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=29 dy=13 +2026-07-20T04:57:01.551415Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.551447Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=9 +2026-07-20T04:57:01.559459Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.559488Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=10 +2026-07-20T04:57:01.566518Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.566553Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=12 +2026-07-20T04:57:01.574543Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.574582Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=10 +2026-07-20T04:57:01.581841Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.581910Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=8 +2026-07-20T04:57:01.589590Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.589641Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=8 +2026-07-20T04:57:01.596520Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.596565Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=9 +2026-07-20T04:57:01.604502Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.604531Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=10 +2026-07-20T04:57:01.611425Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.611455Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=6 +2026-07-20T04:57:01.619557Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.619587Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=9 +2026-07-20T04:57:01.626423Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.626452Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=6 +2026-07-20T04:57:01.634434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.634459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=5 +2026-07-20T04:57:01.641380Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.641406Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=5 +2026-07-20T04:57:01.649487Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.649514Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=5 +2026-07-20T04:57:01.656668Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.656705Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=4 +2026-07-20T04:57:01.664481Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.664513Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=4 +2026-07-20T04:57:01.671628Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.671663Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=2 +2026-07-20T04:57:01.679474Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.679493Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=0 +2026-07-20T04:57:01.686401Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.686425Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=0 +2026-07-20T04:57:01.694416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.694431Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-4 +2026-07-20T04:57:01.701486Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.701500Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-4 +2026-07-20T04:57:01.709350Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.709391Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=-8 +2026-07-20T04:57:01.716333Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.716350Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-7 +2026-07-20T04:57:01.724349Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.724371Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-10 +2026-07-20T04:57:01.731309Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.731333Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-11 +2026-07-20T04:57:01.739341Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.739364Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-15 +2026-07-20T04:57:01.746291Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.746317Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-18 +2026-07-20T04:57:01.754373Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.754395Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-12 +2026-07-20T04:57:01.761349Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.761365Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-13 +2026-07-20T04:57:01.769322Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 235, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.769344Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-21 +2026-07-20T04:57:01.776314Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.776353Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-13 +2026-07-20T04:57:01.784355Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.784384Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-14 +2026-07-20T04:57:01.791363Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.791393Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-12 +2026-07-20T04:57:01.799396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.799422Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-11 +2026-07-20T04:57:01.806349Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.806370Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-8 +2026-07-20T04:57:01.814346Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.814368Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-7 +2026-07-20T04:57:01.821347Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.821371Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-5 +2026-07-20T04:57:01.829295Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.829331Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-5 +2026-07-20T04:57:01.836297Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.836331Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-3 +2026-07-20T04:57:01.844491Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.844511Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-2 +2026-07-20T04:57:01.851424Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.851447Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-1 +2026-07-20T04:57:01.859455Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.859482Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-1 +2026-07-20T04:57:01.866353Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:01.866383Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=0 +2026-07-20T04:57:02.061529Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.061559Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=1 +2026-07-20T04:57:02.069382Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.069400Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=1 +2026-07-20T04:57:02.076347Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.076362Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=1 +2026-07-20T04:57:02.084532Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.084544Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=0 +2026-07-20T04:57:02.091292Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.091305Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=0 +2026-07-20T04:57:02.099494Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.099520Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=0 +2026-07-20T04:57:02.106527Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.106536Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=0 +2026-07-20T04:57:02.114462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.114478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-1 +2026-07-20T04:57:02.121397Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.121426Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-1 +2026-07-20T04:57:02.129492Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.129506Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-2 +2026-07-20T04:57:02.136443Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.136462Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-3 +2026-07-20T04:57:02.144496Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.144512Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-3 +2026-07-20T04:57:02.151499Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.151512Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-5 +2026-07-20T04:57:02.159546Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.159559Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-2 +2026-07-20T04:57:02.166549Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.166585Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-6 +2026-07-20T04:57:02.174459Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.174471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-6 +2026-07-20T04:57:02.181435Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.181446Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-7 +2026-07-20T04:57:02.189441Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.189455Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-3 +2026-07-20T04:57:02.196445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.196458Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-7 +2026-07-20T04:57:02.204544Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.204555Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-7 +2026-07-20T04:57:02.211549Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.211564Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-4 +2026-07-20T04:57:02.219538Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.219558Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-7 +2026-07-20T04:57:02.226549Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.226562Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-6 +2026-07-20T04:57:02.234468Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.234513Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-6 +2026-07-20T04:57:02.241449Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.241480Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-5 +2026-07-20T04:57:02.249426Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.249441Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-2 +2026-07-20T04:57:02.256386Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.256406Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-5 +2026-07-20T04:57:02.264438Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.264464Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=-3 +2026-07-20T04:57:02.271450Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.271474Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-2 +2026-07-20T04:57:02.279331Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.279350Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=-1 +2026-07-20T04:57:02.286365Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.286393Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=0 +2026-07-20T04:57:02.294465Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.294480Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=2 +2026-07-20T04:57:02.301486Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.301506Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=4 +2026-07-20T04:57:02.309388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.309406Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=4 +2026-07-20T04:57:02.316501Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.316530Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=7 +2026-07-20T04:57:02.324620Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.324656Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=8 +2026-07-20T04:57:02.331552Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.331590Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=9 +2026-07-20T04:57:02.339526Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.339561Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=12 +2026-07-20T04:57:02.346386Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.346399Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=9 +2026-07-20T04:57:02.354414Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.354428Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=19 +2026-07-20T04:57:02.361442Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.361457Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=12 +2026-07-20T04:57:02.369606Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.369625Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=21 +2026-07-20T04:57:02.376501Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.376526Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=17 +2026-07-20T04:57:02.384457Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.384496Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=27 +2026-07-20T04:57:02.391452Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.391472Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=15 +2026-07-20T04:57:02.399440Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.399462Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=22 +2026-07-20T04:57:02.406376Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.406398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=27 +2026-07-20T04:57:02.414350Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.414423Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=19 +2026-07-20T04:57:02.421506Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.421521Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=17 +2026-07-20T04:57:02.429328Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.429367Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=14 +2026-07-20T04:57:02.436457Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.436495Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=15 +2026-07-20T04:57:02.444567Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.444595Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=11 +2026-07-20T04:57:02.451610Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 21, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.451656Z INFO openlogi_hid::gesture: SPIKE raw xy dx=21 dy=15 +2026-07-20T04:57:02.459491Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.459505Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=7 +2026-07-20T04:57:02.466528Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 21, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.466543Z INFO openlogi_hid::gesture: SPIKE raw xy dx=21 dy=10 +2026-07-20T04:57:02.474527Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.474545Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=8 +2026-07-20T04:57:02.481389Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.481406Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=5 +2026-07-20T04:57:02.489483Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.489499Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=3 +2026-07-20T04:57:02.496464Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.496495Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=1 +2026-07-20T04:57:02.504521Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 32, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.504539Z INFO openlogi_hid::gesture: SPIKE raw xy dx=32 dy=-1 +2026-07-20T04:57:02.511518Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.511539Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-4 +2026-07-20T04:57:02.519405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.519423Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=-4 +2026-07-20T04:57:02.526399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.526415Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-6 +2026-07-20T04:57:02.534495Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.534519Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=-9 +2026-07-20T04:57:02.541576Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.541590Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-9 +2026-07-20T04:57:02.549462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.549512Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=-12 +2026-07-20T04:57:02.556671Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.556716Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=-9 +2026-07-20T04:57:02.564395Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.564410Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=-16 +2026-07-20T04:57:02.571383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.571415Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-14 +2026-07-20T04:57:02.579493Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 255, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.579528Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=-22 +2026-07-20T04:57:02.586533Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.586546Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-17 +2026-07-20T04:57:02.594509Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.594524Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-24 +2026-07-20T04:57:02.601435Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.601450Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-20 +2026-07-20T04:57:02.609429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.609446Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-22 +2026-07-20T04:57:02.616357Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.616388Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-16 +2026-07-20T04:57:02.624536Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.624553Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-25 +2026-07-20T04:57:02.631672Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.631688Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-24 +2026-07-20T04:57:02.639413Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.639434Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-17 +2026-07-20T04:57:02.646392Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.646410Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-17 +2026-07-20T04:57:02.654404Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.654440Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=-17 +2026-07-20T04:57:02.661431Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.661465Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=-18 +2026-07-20T04:57:02.669565Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.669586Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=-12 +2026-07-20T04:57:02.676330Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 233, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.676345Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-23 dy=-19 +2026-07-20T04:57:02.686321Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.686358Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-11 +2026-07-20T04:57:02.691508Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 236, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.691539Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-20 dy=-10 +2026-07-20T04:57:02.699344Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 225, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.699358Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-31 dy=-14 +2026-07-20T04:57:02.707353Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.707366Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=-8 +2026-07-20T04:57:02.712446Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:02.712460Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:02.712465Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:02.712467Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:02.712469Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:02.712471Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:02.712473Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:02.712475Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:02.712477Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:02.712479Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:02.712481Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:02.712483Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:02.712485Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:02.712486Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:02.712488Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:02.712490Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:02.712495Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=2 +2026-07-20T04:57:02.712505Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:02.713317Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:02.714412Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 226, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.714441Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-30 dy=-9 +2026-07-20T04:57:02.721414Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:02.721425Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:02.722428Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.722441Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-4 +2026-07-20T04:57:02.728425Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:02.729407Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:02.731398Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.731425Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=-4 +2026-07-20T04:57:02.736326Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.736341Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=-2 +2026-07-20T04:57:02.744543Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.744563Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-1 +2026-07-20T04:57:02.751480Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.751502Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-25 dy=0 +2026-07-20T04:57:02.759526Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.759548Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=2 +2026-07-20T04:57:02.766565Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.766596Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=2 +2026-07-20T04:57:02.774736Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.774771Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=4 +2026-07-20T04:57:02.781721Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.781759Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=8 +2026-07-20T04:57:02.789707Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.789748Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=7 +2026-07-20T04:57:02.796714Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 230, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.796752Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-26 dy=13 +2026-07-20T04:57:02.804586Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.804621Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=11 +2026-07-20T04:57:02.811448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.811485Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=20 +2026-07-20T04:57:02.819425Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.819477Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=12 +2026-07-20T04:57:02.826517Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.826536Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=23 +2026-07-20T04:57:02.834573Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.834588Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=19 +2026-07-20T04:57:02.841404Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.841417Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=23 +2026-07-20T04:57:02.849386Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.849402Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=16 +2026-07-20T04:57:02.856544Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.856567Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=26 +2026-07-20T04:57:02.864382Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.864424Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=25 +2026-07-20T04:57:02.871405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.871436Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=18 +2026-07-20T04:57:02.879681Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.879700Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=17 +2026-07-20T04:57:02.886421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.886458Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=10 +2026-07-20T04:57:02.894400Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.894414Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=7 +2026-07-20T04:57:02.901441Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.901458Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=4 +2026-07-20T04:57:02.909334Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.909353Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=3 +2026-07-20T04:57:02.916611Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.916661Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=2 +2026-07-20T04:57:02.924461Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.924503Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=1 +2026-07-20T04:57:02.931599Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.931626Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=2 +2026-07-20T04:57:02.939349Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.939381Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=3 +2026-07-20T04:57:02.946354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.946388Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=3 +2026-07-20T04:57:02.954420Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.954440Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=4 +2026-07-20T04:57:02.961603Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.961625Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=1 +2026-07-20T04:57:02.969550Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:02.969595Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=1 +2026-07-20T04:57:03.824450Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.824489Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:57:03.831308Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.831328Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-1 +2026-07-20T04:57:03.839338Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.839365Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:57:03.846321Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.846335Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-4 +2026-07-20T04:57:03.854338Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.854389Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-2 +2026-07-20T04:57:03.861434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.861465Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-2 +2026-07-20T04:57:03.869337Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.869349Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-2 +2026-07-20T04:57:03.876294Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.876312Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-2 +2026-07-20T04:57:03.884289Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.884307Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-1 +2026-07-20T04:57:03.891477Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.891506Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-2 +2026-07-20T04:57:03.899409Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.899433Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-2 +2026-07-20T04:57:03.906363Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.906392Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-2 +2026-07-20T04:57:03.914466Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.914489Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=0 +2026-07-20T04:57:03.921513Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.921543Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-1 +2026-07-20T04:57:03.929418Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.929452Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=0 +2026-07-20T04:57:03.936498Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:03.936528Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:57:04.004630Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.004666Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:57:04.094572Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.094613Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:57:04.101630Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.101651Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-3 +2026-07-20T04:57:04.109611Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.109661Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-3 +2026-07-20T04:57:04.124613Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.124687Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-6 +2026-07-20T04:57:04.132478Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.132506Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-4 +2026-07-20T04:57:04.139581Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.139610Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-8 +2026-07-20T04:57:04.147713Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.147748Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-5 +2026-07-20T04:57:04.154389Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.154427Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-13 +2026-07-20T04:57:04.162591Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.162612Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-7 +2026-07-20T04:57:04.169476Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.169514Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-16 +2026-07-20T04:57:04.177440Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.177454Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-19 +2026-07-20T04:57:04.184608Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.184622Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-10 +2026-07-20T04:57:04.192503Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.192516Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-19 +2026-07-20T04:57:04.199429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.199444Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-9 +2026-07-20T04:57:04.207421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.207434Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-16 +2026-07-20T04:57:04.214553Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.214567Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-12 +2026-07-20T04:57:04.222392Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.222414Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-5 +2026-07-20T04:57:04.229480Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.229497Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-8 +2026-07-20T04:57:04.237409Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.237447Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-3 +2026-07-20T04:57:04.244289Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:04.244405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.244432Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-4 +2026-07-20T04:57:04.247536Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:04.247538Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:04.257545Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:04.258617Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.258649Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-3 +2026-07-20T04:57:04.266472Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:04.266493Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:04.268664Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.268696Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=0 +2026-07-20T04:57:04.269506Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.269537Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=0 +2026-07-20T04:57:04.274436Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.274470Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=1 +2026-07-20T04:57:04.282423Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.282438Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=4 +2026-07-20T04:57:04.289367Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.292379Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:04.292411Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:04.294320Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:04.294377Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:04.295302Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:04.295339Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:04.296380Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:04.296402Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:04.297399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.297412Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=6 +2026-07-20T04:57:04.304401Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 231, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.304415Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-25 dy=12 +2026-07-20T04:57:04.312698Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.312725Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=10 +2026-07-20T04:57:04.319571Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.319608Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=12 +2026-07-20T04:57:04.327520Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.327583Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=6 +2026-07-20T04:57:04.334456Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.334476Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=12 +2026-07-20T04:57:04.342411Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.342425Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=13 +2026-07-20T04:57:04.349485Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.349498Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=6 +2026-07-20T04:57:04.357445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.357465Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=12 +2026-07-20T04:57:04.364525Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.364547Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=11 +2026-07-20T04:57:04.372515Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.372533Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=8 +2026-07-20T04:57:04.379489Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.379527Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=3 +2026-07-20T04:57:04.387448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.387467Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=4 +2026-07-20T04:57:04.394432Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.394471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=2 +2026-07-20T04:57:04.402377Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.402398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=1 +2026-07-20T04:57:04.409462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.409479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:57:04.657529Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.657571Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:57:04.664429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.664466Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-4 +2026-07-20T04:57:04.679483Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.679503Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-5 +2026-07-20T04:57:04.687420Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.687446Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-8 +2026-07-20T04:57:04.694321Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.703126Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-7 +2026-07-20T04:57:04.703187Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.703195Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-7 +2026-07-20T04:57:04.709462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.709481Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-7 +2026-07-20T04:57:04.717332Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.717365Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-7 +2026-07-20T04:57:04.724481Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.724511Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-7 +2026-07-20T04:57:04.732487Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.732507Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-7 +2026-07-20T04:57:04.739369Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.739385Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-6 +2026-07-20T04:57:04.747450Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.747491Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-6 +2026-07-20T04:57:04.754426Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.754450Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-6 +2026-07-20T04:57:04.762534Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.762558Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-5 +2026-07-20T04:57:04.769485Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.769527Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-4 +2026-07-20T04:57:04.777444Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.777474Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-5 +2026-07-20T04:57:04.784490Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.784518Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-5 +2026-07-20T04:57:04.792660Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.792694Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-3 +2026-07-20T04:57:04.799486Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.799519Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-4 +2026-07-20T04:57:04.807446Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.807487Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-3 +2026-07-20T04:57:04.814450Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.814482Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-5 +2026-07-20T04:57:04.822458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.822473Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-3 +2026-07-20T04:57:04.829322Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.829337Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-2 +2026-07-20T04:57:04.837469Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.837487Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-1 +2026-07-20T04:57:04.844721Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.844754Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=0 +2026-07-20T04:57:04.852795Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.852846Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=1 +2026-07-20T04:57:04.859620Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.859656Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=3 +2026-07-20T04:57:04.867471Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.867510Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=5 +2026-07-20T04:57:04.874705Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.874755Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=7 +2026-07-20T04:57:04.882535Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.882582Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=13 +2026-07-20T04:57:04.889707Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.889743Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=10 +2026-07-20T04:57:04.897833Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.897895Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=10 +2026-07-20T04:57:04.904685Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.904699Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=18 +2026-07-20T04:57:04.912533Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.912550Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=12 +2026-07-20T04:57:04.919471Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.919484Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=12 +2026-07-20T04:57:04.927389Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.927409Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=19 +2026-07-20T04:57:04.934444Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.934476Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=12 +2026-07-20T04:57:04.942388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.942423Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=12 +2026-07-20T04:57:04.949467Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.949488Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=14 +2026-07-20T04:57:04.957399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.957420Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=22 +2026-07-20T04:57:04.964413Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.964425Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=15 +2026-07-20T04:57:04.972359Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.972398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=15 +2026-07-20T04:57:04.979498Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 23, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.979510Z INFO openlogi_hid::gesture: SPIKE raw xy dx=23 dy=20 +2026-07-20T04:57:04.987497Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.987510Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=13 +2026-07-20T04:57:04.994845Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:04.994908Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=11 +2026-07-20T04:57:05.002410Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.002445Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=14 +2026-07-20T04:57:05.009541Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 27, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.009557Z INFO openlogi_hid::gesture: SPIKE raw xy dx=27 dy=11 +2026-07-20T04:57:05.017450Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.017477Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=8 +2026-07-20T04:57:05.024469Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.024508Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=9 +2026-07-20T04:57:05.032463Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.032480Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=8 +2026-07-20T04:57:05.039574Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 21, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.039589Z INFO openlogi_hid::gesture: SPIKE raw xy dx=21 dy=7 +2026-07-20T04:57:05.047438Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.047473Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=6 +2026-07-20T04:57:05.054476Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.054493Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=4 +2026-07-20T04:57:05.062420Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.062440Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=3 +2026-07-20T04:57:05.069382Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.069398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=0 +2026-07-20T04:57:05.077388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.077415Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-2 +2026-07-20T04:57:05.084359Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.084376Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-3 +2026-07-20T04:57:05.092330Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.092350Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-5 +2026-07-20T04:57:05.099371Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.099387Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-11 +2026-07-20T04:57:05.107392Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.107410Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-10 +2026-07-20T04:57:05.114562Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.114666Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-13 +2026-07-20T04:57:05.122554Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 235, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.122587Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-21 +2026-07-20T04:57:05.129422Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.129436Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-16 +2026-07-20T04:57:05.137382Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 233, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.137409Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-23 +2026-07-20T04:57:05.144396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.144412Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-16 +2026-07-20T04:57:05.152489Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.152508Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-25 +2026-07-20T04:57:05.159520Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 233, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.159543Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-23 +2026-07-20T04:57:05.167414Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 235, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.167442Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-21 +2026-07-20T04:57:05.174433Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 235, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.174457Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-21 +2026-07-20T04:57:05.182386Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.182409Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-19 +2026-07-20T04:57:05.189329Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.189349Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-17 +2026-07-20T04:57:05.197395Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.197419Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-15 +2026-07-20T04:57:05.204341Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.204364Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-9 +2026-07-20T04:57:05.212475Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.212502Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=-11 +2026-07-20T04:57:05.219457Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.219489Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=-7 +2026-07-20T04:57:05.227504Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.227541Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-3 +2026-07-20T04:57:05.234510Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.234544Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=-2 +2026-07-20T04:57:05.242522Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.242543Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=0 +2026-07-20T04:57:05.249518Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.249537Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=1 +2026-07-20T04:57:05.257498Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 234, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.257514Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-22 dy=4 +2026-07-20T04:57:05.264582Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.264598Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=4 +2026-07-20T04:57:05.272647Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 233, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.272671Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-23 dy=8 +2026-07-20T04:57:05.279534Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 234, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.279554Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-22 dy=12 +2026-07-20T04:57:05.287401Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 236, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.287458Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-20 dy=11 +2026-07-20T04:57:05.294480Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 236, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.294509Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-20 dy=14 +2026-07-20T04:57:05.302601Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.302665Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=9 +2026-07-20T04:57:05.309616Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.309640Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=16 +2026-07-20T04:57:05.317408Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.317434Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=15 +2026-07-20T04:57:05.324538Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.324558Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=9 +2026-07-20T04:57:05.332374Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.332395Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=13 +2026-07-20T04:57:05.339523Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.339539Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=7 +2026-07-20T04:57:05.347479Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.347520Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=7 +2026-07-20T04:57:05.354569Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.354591Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=4 +2026-07-20T04:57:05.362690Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.362755Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=3 +2026-07-20T04:57:05.369568Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.369584Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=1 +2026-07-20T04:57:05.377514Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 27, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.377552Z INFO openlogi_hid::gesture: SPIKE raw xy dx=27 dy=2 +2026-07-20T04:57:05.384607Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.384621Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=4 +2026-07-20T04:57:05.392488Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 35, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.392505Z INFO openlogi_hid::gesture: SPIKE raw xy dx=35 dy=5 +2026-07-20T04:57:05.399491Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 30, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.399513Z INFO openlogi_hid::gesture: SPIKE raw xy dx=30 dy=5 +2026-07-20T04:57:05.407387Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 29, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.407401Z INFO openlogi_hid::gesture: SPIKE raw xy dx=29 dy=7 +2026-07-20T04:57:05.415625Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 27, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.415667Z INFO openlogi_hid::gesture: SPIKE raw xy dx=27 dy=9 +2026-07-20T04:57:05.422563Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.422596Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=10 +2026-07-20T04:57:05.429624Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.429661Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=9 +2026-07-20T04:57:05.437468Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.437506Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=12 +2026-07-20T04:57:05.444380Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.444427Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=7 +2026-07-20T04:57:05.452672Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.452718Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=9 +2026-07-20T04:57:05.459635Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.459686Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=5 +2026-07-20T04:57:05.467471Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.467506Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=6 +2026-07-20T04:57:05.474410Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.474444Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=4 +2026-07-20T04:57:05.482897Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 233, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.482934Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-23 dy=2 +2026-07-20T04:57:05.489759Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.489794Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-20 dy=0 +2026-07-20T04:57:05.497482Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 226, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.497516Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-30 dy=-2 +2026-07-20T04:57:05.504557Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 227, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.504599Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-29 dy=-4 +2026-07-20T04:57:05.512526Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 225, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.512567Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-31 dy=-6 +2026-07-20T04:57:05.519473Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 223, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.519489Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-33 dy=-9 +2026-07-20T04:57:05.527432Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 218, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.527457Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-38 dy=-12 +2026-07-20T04:57:05.534559Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 227, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.534607Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-29 dy=-11 +2026-07-20T04:57:05.542546Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 233, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.542560Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-23 dy=-11 +2026-07-20T04:57:05.549463Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 233, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.549478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-23 dy=-13 +2026-07-20T04:57:05.557395Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.557415Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=-17 +2026-07-20T04:57:05.564448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.564477Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-14 +2026-07-20T04:57:05.572561Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.572600Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-15 +2026-07-20T04:57:05.579495Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.579511Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-12 +2026-07-20T04:57:05.587389Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.587413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-17 +2026-07-20T04:57:05.594574Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.594617Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-13 +2026-07-20T04:57:05.602459Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.602476Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=-13 +2026-07-20T04:57:05.609469Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.609501Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=-11 +2026-07-20T04:57:05.617374Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.617402Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=-10 +2026-07-20T04:57:05.624384Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.624409Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=-5 +2026-07-20T04:57:05.632612Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 29, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.632650Z INFO openlogi_hid::gesture: SPIKE raw xy dx=29 dy=-1 +2026-07-20T04:57:05.639601Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.639650Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=0 +2026-07-20T04:57:05.647707Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.647756Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=3 +2026-07-20T04:57:05.654419Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 38, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.654459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=38 dy=7 +2026-07-20T04:57:05.662756Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 32, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.662805Z INFO openlogi_hid::gesture: SPIKE raw xy dx=32 dy=9 +2026-07-20T04:57:05.669708Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 30, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.669748Z INFO openlogi_hid::gesture: SPIKE raw xy dx=30 dy=11 +2026-07-20T04:57:05.677445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 35, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.677487Z INFO openlogi_hid::gesture: SPIKE raw xy dx=35 dy=17 +2026-07-20T04:57:05.684457Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 26, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.684481Z INFO openlogi_hid::gesture: SPIKE raw xy dx=26 dy=15 +2026-07-20T04:57:05.692443Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.692464Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=15 +2026-07-20T04:57:05.699467Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 23, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.699488Z INFO openlogi_hid::gesture: SPIKE raw xy dx=23 dy=21 +2026-07-20T04:57:05.707410Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.707439Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=15 +2026-07-20T04:57:05.714491Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.714526Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=17 +2026-07-20T04:57:05.722430Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.722455Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=16 +2026-07-20T04:57:05.729380Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.729412Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=14 +2026-07-20T04:57:05.737447Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.737472Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=12 +2026-07-20T04:57:05.744380Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.744399Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=10 +2026-07-20T04:57:05.752416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.752436Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=7 +2026-07-20T04:57:05.759484Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.759508Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=6 +2026-07-20T04:57:05.767391Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.767414Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=3 +2026-07-20T04:57:05.774343Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.774365Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=2 +2026-07-20T04:57:05.782366Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.782382Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=0 +2026-07-20T04:57:05.789425Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.789438Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-2 +2026-07-20T04:57:05.797302Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.797337Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=-5 +2026-07-20T04:57:05.804448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.804466Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-5 +2026-07-20T04:57:05.812769Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.812813Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-7 +2026-07-20T04:57:05.819603Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.819639Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-10 +2026-07-20T04:57:05.827461Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.827499Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-11 +2026-07-20T04:57:05.834429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.834455Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-14 +2026-07-20T04:57:05.842435Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.842465Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-22 +2026-07-20T04:57:05.849370Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.849390Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-15 +2026-07-20T04:57:05.857436Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.857448Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-15 +2026-07-20T04:57:05.864448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 233, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.864464Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-23 +2026-07-20T04:57:05.872455Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.872471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-15 +2026-07-20T04:57:05.879487Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.879506Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-18 +2026-07-20T04:57:05.887439Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.887459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-11 +2026-07-20T04:57:05.894462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.894480Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=-15 +2026-07-20T04:57:05.902416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.902439Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-9 +2026-07-20T04:57:05.909556Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.909571Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=-11 +2026-07-20T04:57:05.917403Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.917426Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-5 +2026-07-20T04:57:05.924376Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.924404Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=-4 +2026-07-20T04:57:05.932366Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.932390Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=-1 +2026-07-20T04:57:05.939597Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.939640Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=0 +2026-07-20T04:57:05.947523Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.947556Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=2 +2026-07-20T04:57:05.954728Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.954762Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=6 +2026-07-20T04:57:05.962568Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.962600Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=4 +2026-07-20T04:57:05.969584Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 231, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.969610Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-25 dy=9 +2026-07-20T04:57:05.977444Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.977465Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=7 +2026-07-20T04:57:05.984520Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 234, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.984557Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-22 dy=11 +2026-07-20T04:57:05.992651Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.992695Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=11 +2026-07-20T04:57:05.999724Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:05.999764Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=12 +2026-07-20T04:57:06.007537Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.007583Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=14 +2026-07-20T04:57:06.014532Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.014579Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=11 +2026-07-20T04:57:06.022509Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.022547Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=15 +2026-07-20T04:57:06.029709Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.029745Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=16 +2026-07-20T04:57:06.037339Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.037394Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=11 +2026-07-20T04:57:06.044320Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.044342Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=14 +2026-07-20T04:57:06.052365Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.052381Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=13 +2026-07-20T04:57:06.059436Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.059455Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=7 +2026-07-20T04:57:06.067342Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.067363Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=8 +2026-07-20T04:57:06.074427Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.074447Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=3 +2026-07-20T04:57:06.082509Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 23, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.082579Z INFO openlogi_hid::gesture: SPIKE raw xy dx=23 dy=2 +2026-07-20T04:57:06.089481Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.089516Z INFO openlogi_hid::gesture: SPIKE raw xy dx=24 dy=0 +2026-07-20T04:57:06.097548Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.097601Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=-2 +2026-07-20T04:57:06.104405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 27, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.104469Z INFO openlogi_hid::gesture: SPIKE raw xy dx=27 dy=-4 +2026-07-20T04:57:06.112595Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 31, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.112661Z INFO openlogi_hid::gesture: SPIKE raw xy dx=31 dy=-6 +2026-07-20T04:57:06.119466Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 23, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.119520Z INFO openlogi_hid::gesture: SPIKE raw xy dx=23 dy=-6 +2026-07-20T04:57:06.127411Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 26, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.127474Z INFO openlogi_hid::gesture: SPIKE raw xy dx=26 dy=-8 +2026-07-20T04:57:06.134460Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 27, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.134518Z INFO openlogi_hid::gesture: SPIKE raw xy dx=27 dy=-10 +2026-07-20T04:57:06.142458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.142519Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-8 +2026-07-20T04:57:06.149537Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.149588Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=-12 +2026-07-20T04:57:06.157528Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.157567Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-10 +2026-07-20T04:57:06.164577Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.164599Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-12 +2026-07-20T04:57:06.172441Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.172455Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-12 +2026-07-20T04:57:06.179486Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.179499Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-12 +2026-07-20T04:57:06.187353Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.187397Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-11 +2026-07-20T04:57:06.194478Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.194486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-10 +2026-07-20T04:57:06.202448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.202465Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-10 +2026-07-20T04:57:06.209411Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.209433Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-6 +2026-07-20T04:57:06.217417Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.217452Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-9 +2026-07-20T04:57:06.224497Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.224536Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-6 +2026-07-20T04:57:06.232497Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.232525Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-4 +2026-07-20T04:57:06.239464Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.239487Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-1 +2026-07-20T04:57:06.247403Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.247427Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-22 dy=0 +2026-07-20T04:57:06.254465Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.254487Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=1 +2026-07-20T04:57:06.262459Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.262496Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=4 +2026-07-20T04:57:06.269432Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.269450Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=4 +2026-07-20T04:57:06.277362Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 234, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.277387Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-22 dy=9 +2026-07-20T04:57:06.284505Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.284534Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=7 +2026-07-20T04:57:06.292446Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.292470Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=12 +2026-07-20T04:57:06.299496Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.299533Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=13 +2026-07-20T04:57:06.307434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.307463Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=9 +2026-07-20T04:57:06.314389Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.314426Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=12 +2026-07-20T04:57:06.322458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.322481Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=9 +2026-07-20T04:57:06.329494Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.329527Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=7 +2026-07-20T04:57:06.337458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.337487Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=7 +2026-07-20T04:57:06.344441Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.344457Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=9 +2026-07-20T04:57:06.352518Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.352532Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=6 +2026-07-20T04:57:06.359581Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.359600Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=5 +2026-07-20T04:57:06.367376Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.367387Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=5 +2026-07-20T04:57:06.374480Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.374492Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=2 +2026-07-20T04:57:06.382327Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.382345Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=1 +2026-07-20T04:57:06.393489Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.393511Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=0 +2026-07-20T04:57:06.397698Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.397721Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-1 +2026-07-20T04:57:06.404596Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 21, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.404617Z INFO openlogi_hid::gesture: SPIKE raw xy dx=21 dy=-2 +2026-07-20T04:57:06.412339Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.412381Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=-2 +2026-07-20T04:57:06.420366Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.420387Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-4 +2026-07-20T04:57:06.425683Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:06.425706Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:06.425711Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:06.425714Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:06.425717Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:06.425721Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:06.425724Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:06.425727Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:06.425731Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:06.425734Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:06.425737Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:06.425741Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:06.425744Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:06.425747Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:06.425751Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:06.425754Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:06.425762Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=2 +2026-07-20T04:57:06.425776Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:06.427322Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.427336Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-2 +2026-07-20T04:57:06.429397Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:06.434341Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:06.434462Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:06.436284Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.436302Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-4 +2026-07-20T04:57:06.442449Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:06.444399Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:06.445641Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.445657Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-2 +2026-07-20T04:57:06.449558Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.449578Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-2 +2026-07-20T04:57:06.457429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.457451Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-2 +2026-07-20T04:57:06.464533Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.464557Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-1 +2026-07-20T04:57:06.472510Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:06.472531Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:57:07.199503Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.199538Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-1 +2026-07-20T04:57:07.207626Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.207693Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-2 +2026-07-20T04:57:07.214688Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.214724Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-6 +2026-07-20T04:57:07.222763Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.222856Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-7 +2026-07-20T04:57:07.229630Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.229665Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=-9 +2026-07-20T04:57:07.237437Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.237471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=-9 +2026-07-20T04:57:07.244818Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.244859Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=-9 +2026-07-20T04:57:07.252413Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.252452Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-11 +2026-07-20T04:57:07.259482Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.259497Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=-13 +2026-07-20T04:57:07.267385Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.267403Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=-11 +2026-07-20T04:57:07.274459Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.274476Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-14 +2026-07-20T04:57:07.282383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.282400Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=-15 +2026-07-20T04:57:07.289473Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.289487Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=-18 +2026-07-20T04:57:07.297473Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.297512Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-13 +2026-07-20T04:57:07.304505Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.304522Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=-19 +2026-07-20T04:57:07.312575Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.312598Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-12 +2026-07-20T04:57:07.319466Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.319505Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-15 +2026-07-20T04:57:07.327447Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.327473Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-12 +2026-07-20T04:57:07.334396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.334422Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-8 +2026-07-20T04:57:07.342329Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.342346Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-4 +2026-07-20T04:57:07.349353Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.349368Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-5 +2026-07-20T04:57:07.357396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.357416Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-2 +2026-07-20T04:57:07.364476Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.364500Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-2 +2026-07-20T04:57:07.372433Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.372457Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:57:07.379368Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.379398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-1 +2026-07-20T04:57:07.387383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.387412Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=0 +2026-07-20T04:57:07.394435Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.394462Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=3 +2026-07-20T04:57:07.402484Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.402528Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=5 +2026-07-20T04:57:07.409502Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.409522Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=12 +2026-07-20T04:57:07.417322Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.417345Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=14 +2026-07-20T04:57:07.424491Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.433664Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=15 +2026-07-20T04:57:07.433694Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.433701Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=21 +2026-07-20T04:57:07.439383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.439406Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=27 +2026-07-20T04:57:07.447416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 21, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.447464Z INFO openlogi_hid::gesture: SPIKE raw xy dx=21 dy=27 +2026-07-20T04:57:07.454462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 24, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.454484Z INFO openlogi_hid::gesture: SPIKE raw xy dx=24 dy=33 +2026-07-20T04:57:07.462458Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.462480Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=29 +2026-07-20T04:57:07.469469Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.469496Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=30 +2026-07-20T04:57:07.477423Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.477453Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=27 +2026-07-20T04:57:07.484495Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.484531Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=26 +2026-07-20T04:57:07.492486Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.492520Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=23 +2026-07-20T04:57:07.499494Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.499531Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=26 +2026-07-20T04:57:07.507502Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.507539Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=28 +2026-07-20T04:57:07.514482Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.514517Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=21 +2026-07-20T04:57:07.522816Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.522844Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=18 +2026-07-20T04:57:07.529605Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.529637Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=16 +2026-07-20T04:57:07.537383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.537406Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=11 +2026-07-20T04:57:07.544343Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.544367Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=13 +2026-07-20T04:57:07.552408Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.552437Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=6 +2026-07-20T04:57:07.559614Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.559631Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=3 +2026-07-20T04:57:07.567434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 217, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.567453Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-39 dy=0 +2026-07-20T04:57:07.574534Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 228, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.574559Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-28 dy=-1 +2026-07-20T04:57:07.582472Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 216, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.582495Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-40 dy=-6 +2026-07-20T04:57:07.589515Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 217, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.589537Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-39 dy=-8 +2026-07-20T04:57:07.597369Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 219, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.597405Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-37 dy=-9 +2026-07-20T04:57:07.604620Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 211, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.604656Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-45 dy=-13 +2026-07-20T04:57:07.612608Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 219, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.612632Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-37 dy=-13 +2026-07-20T04:57:07.619506Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 220, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.619528Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-36 dy=-14 +2026-07-20T04:57:07.627403Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 217, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.627429Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-39 dy=-17 +2026-07-20T04:57:07.634456Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 217, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.634500Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-39 dy=-20 +2026-07-20T04:57:07.642419Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.642434Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=-16 +2026-07-20T04:57:07.649543Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 228, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.649576Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-28 dy=-18 +2026-07-20T04:57:07.657400Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 227, 255, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.657424Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-29 dy=-25 +2026-07-20T04:57:07.664643Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 255, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.664679Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=-22 +2026-07-20T04:57:07.672595Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.672608Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=-18 +2026-07-20T04:57:07.679594Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.679628Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-20 +2026-07-20T04:57:07.687397Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.687421Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-15 +2026-07-20T04:57:07.694640Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.694653Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-16 +2026-07-20T04:57:07.702581Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.702594Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-18 +2026-07-20T04:57:07.709383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.709397Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-12 +2026-07-20T04:57:07.717354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.717377Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-13 +2026-07-20T04:57:07.724304Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.724332Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-12 +2026-07-20T04:57:07.732485Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.732500Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-13 +2026-07-20T04:57:07.739534Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.739550Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-11 +2026-07-20T04:57:07.747388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.747404Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=-10 +2026-07-20T04:57:07.754512Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.754531Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=-9 +2026-07-20T04:57:07.762427Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.762446Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=-7 +2026-07-20T04:57:07.769467Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.769486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=-6 +2026-07-20T04:57:07.777428Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.777461Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=-2 +2026-07-20T04:57:07.784500Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 27, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.784539Z INFO openlogi_hid::gesture: SPIKE raw xy dx=27 dy=-4 +2026-07-20T04:57:07.792499Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 26, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.792530Z INFO openlogi_hid::gesture: SPIKE raw xy dx=26 dy=-1 +2026-07-20T04:57:07.799446Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.799471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-1 +2026-07-20T04:57:07.807602Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.807623Z INFO openlogi_hid::gesture: SPIKE raw xy dx=24 dy=0 +2026-07-20T04:57:07.814331Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.814346Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=0 +2026-07-20T04:57:07.822529Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.822543Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=0 +2026-07-20T04:57:07.829489Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.829514Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=1 +2026-07-20T04:57:07.837376Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.837391Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=1 +2026-07-20T04:57:07.852461Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.852491Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=0 +2026-07-20T04:57:07.919519Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.919548Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:57:07.951086Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:07.954384Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:07.954433Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:07.963422Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:07.970383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:07.970432Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:07.987448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:07.991393Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:07.991410Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:07.993380Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:07.993382Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:07.995324Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:07.995351Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:07.998375Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:07.998445Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:08.144437Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.144479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=0 +2026-07-20T04:57:08.152434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.152470Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=0 +2026-07-20T04:57:08.159581Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.159614Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:57:08.167416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.167437Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=-2 +2026-07-20T04:57:08.174517Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.174537Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-1 +2026-07-20T04:57:08.182469Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.182486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=-2 +2026-07-20T04:57:08.189434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.189452Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=-2 +2026-07-20T04:57:08.197400Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.197423Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=-2 +2026-07-20T04:57:08.204447Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.204494Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-1 +2026-07-20T04:57:08.212442Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.212457Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-1 +2026-07-20T04:57:08.219517Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 27, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.219532Z INFO openlogi_hid::gesture: SPIKE raw xy dx=27 dy=-2 +2026-07-20T04:57:08.227307Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.227324Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=0 +2026-07-20T04:57:08.234452Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.234481Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=1 +2026-07-20T04:57:08.242456Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 24, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.242480Z INFO openlogi_hid::gesture: SPIKE raw xy dx=24 dy=5 +2026-07-20T04:57:08.249337Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.249381Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=6 +2026-07-20T04:57:08.257378Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.257408Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=9 +2026-07-20T04:57:08.264435Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.264459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=8 +2026-07-20T04:57:08.272361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.272372Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=11 +2026-07-20T04:57:08.279517Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.279528Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=9 +2026-07-20T04:57:08.287399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.287418Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=15 +2026-07-20T04:57:08.294366Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.294392Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=17 +2026-07-20T04:57:08.302460Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.302476Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=13 +2026-07-20T04:57:08.309395Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.309413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=22 +2026-07-20T04:57:08.317432Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.317458Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=19 +2026-07-20T04:57:08.324520Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.324547Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=13 +2026-07-20T04:57:08.332399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.332431Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=19 +2026-07-20T04:57:08.339288Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.339320Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=19 +2026-07-20T04:57:08.347331Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.347371Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=10 +2026-07-20T04:57:08.354354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.354391Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=14 +2026-07-20T04:57:08.362596Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.362621Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=8 +2026-07-20T04:57:08.369508Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.369531Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=4 +2026-07-20T04:57:08.377551Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 236, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.377576Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-20 dy=1 +2026-07-20T04:57:08.384460Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.384508Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=-2 +2026-07-20T04:57:08.392511Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.392529Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=-5 +2026-07-20T04:57:08.399741Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.399754Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-7 +2026-07-20T04:57:08.407327Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.407340Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-9 +2026-07-20T04:57:08.414575Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.414588Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-8 +2026-07-20T04:57:08.422528Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.422543Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-7 +2026-07-20T04:57:08.429396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.429444Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=-13 +2026-07-20T04:57:08.437363Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.437395Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-9 +2026-07-20T04:57:08.444399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.444463Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-6 +2026-07-20T04:57:08.452654Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.452692Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-8 +2026-07-20T04:57:08.459577Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.459611Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-8 +2026-07-20T04:57:08.467341Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.467383Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-5 +2026-07-20T04:57:08.474352Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.474376Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:57:08.482488Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.482509Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-4 +2026-07-20T04:57:08.497332Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.497353Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-2 +2026-07-20T04:57:08.504460Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.504479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-2 +2026-07-20T04:57:08.512462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.512485Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:57:08.519389Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.519415Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:57:08.527402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:08.527424Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:57:09.022540Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.022576Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:57:09.029753Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.029787Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=0 +2026-07-20T04:57:09.037377Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.037410Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=0 +2026-07-20T04:57:09.044380Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.044407Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=0 +2026-07-20T04:57:09.052314Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.052331Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-1 +2026-07-20T04:57:09.059277Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.059295Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-1 +2026-07-20T04:57:09.067278Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.067293Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=0 +2026-07-20T04:57:09.074455Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.074471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-1 +2026-07-20T04:57:09.082493Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.082506Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-1 +2026-07-20T04:57:09.089393Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.089417Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-1 +2026-07-20T04:57:09.097361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.097389Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-1 +2026-07-20T04:57:09.104409Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.104431Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-1 +2026-07-20T04:57:09.112637Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.112664Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=0 +2026-07-20T04:57:09.119442Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.119455Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-1 +2026-07-20T04:57:09.127326Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.127341Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=0 +2026-07-20T04:57:09.134413Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.134441Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=0 +2026-07-20T04:57:09.322531Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.322586Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:57:09.329582Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.329626Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-3 +2026-07-20T04:57:09.337504Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.337556Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:57:09.344474Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.344520Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=-14 +2026-07-20T04:57:09.352481Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.352500Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-9 +2026-07-20T04:57:09.359354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.359370Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-9 +2026-07-20T04:57:09.367468Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.367488Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-6 +2026-07-20T04:57:09.374381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.374411Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-7 +2026-07-20T04:57:09.382401Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.382424Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-6 +2026-07-20T04:57:09.389453Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.389472Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-5 +2026-07-20T04:57:09.397419Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.397457Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-3 +2026-07-20T04:57:09.404480Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.404495Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=-2 +2026-07-20T04:57:09.412424Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.412466Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=0 +2026-07-20T04:57:09.419428Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.419441Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=1 +2026-07-20T04:57:09.427320Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.427333Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=6 +2026-07-20T04:57:09.434457Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.434475Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=7 +2026-07-20T04:57:09.442371Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.442405Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=12 +2026-07-20T04:57:09.449429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.449445Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=14 +2026-07-20T04:57:09.457379Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.457398Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=16 +2026-07-20T04:57:09.464475Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.464517Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=15 +2026-07-20T04:57:09.472522Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.472540Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=17 +2026-07-20T04:57:09.479429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.479467Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=18 +2026-07-20T04:57:09.487443Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.487460Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=12 +2026-07-20T04:57:09.494409Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.494436Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=18 +2026-07-20T04:57:09.502285Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.502298Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=16 +2026-07-20T04:57:09.509364Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.509376Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=12 +2026-07-20T04:57:09.517318Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.517334Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=13 +2026-07-20T04:57:09.524331Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.524346Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=7 +2026-07-20T04:57:09.532434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.532448Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=11 +2026-07-20T04:57:09.539409Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.539444Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=10 +2026-07-20T04:57:09.547357Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.547384Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=9 +2026-07-20T04:57:09.554488Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.554535Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=8 +2026-07-20T04:57:09.562402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.562431Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=4 +2026-07-20T04:57:09.569381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.569415Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=3 +2026-07-20T04:57:09.577338Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.577359Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=1 +2026-07-20T04:57:09.584471Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.584491Z INFO openlogi_hid::gesture: SPIKE raw xy dx=13 dy=0 +2026-07-20T04:57:09.592418Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.592438Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=-1 +2026-07-20T04:57:09.599443Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.599462Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=-3 +2026-07-20T04:57:09.607354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.607367Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=-2 +2026-07-20T04:57:09.614457Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.614475Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-2 +2026-07-20T04:57:09.622399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.622413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=-3 +2026-07-20T04:57:09.629405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.629444Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=0 +2026-07-20T04:57:09.644386Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.644405Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=0 +2026-07-20T04:57:09.652375Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.652396Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=1 +2026-07-20T04:57:09.659292Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.659311Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=3 +2026-07-20T04:57:09.667354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:09.667369Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=0 +2026-07-20T04:57:10.085488Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:10.085518Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:10.085521Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:10.085523Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:10.085539Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:10.085541Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:10.085544Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:10.085546Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:10.085549Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:10.085551Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:10.085553Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:10.085556Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:10.085559Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:10.085561Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:10.085564Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:10.085566Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:10.085572Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=2 +2026-07-20T04:57:10.085590Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:10.087394Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:10.093302Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:10.093310Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:10.101428Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:10.102374Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:11.212573Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.212604Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=0 +2026-07-20T04:57:11.219583Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.219601Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-1 +2026-07-20T04:57:11.227330Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.227346Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-1 +2026-07-20T04:57:11.234481Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:57:11.242478Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.242493Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-2 +2026-07-20T04:57:11.249569Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.249609Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-7 +2026-07-20T04:57:11.257351Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:57:11.264558Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.264574Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-5 +2026-07-20T04:57:11.276714Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.276747Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-10 +2026-07-20T04:57:11.279491Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.279505Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-3 +2026-07-20T04:57:11.287411Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.287423Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-4 +2026-07-20T04:57:11.294370Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.294410Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-4 +2026-07-20T04:57:11.302516Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.302584Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-5 +2026-07-20T04:57:11.309546Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.309559Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=-6 +2026-07-20T04:57:11.317354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.317381Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-4 +2026-07-20T04:57:11.324429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.324442Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=-9 +2026-07-20T04:57:11.332437Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.332453Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=-9 +2026-07-20T04:57:11.339448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.339476Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=-8 +2026-07-20T04:57:11.347446Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 231, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.347463Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-25 dy=-11 +2026-07-20T04:57:11.354308Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.354320Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=-8 +2026-07-20T04:57:11.362372Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 228, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.362404Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-28 dy=-12 +2026-07-20T04:57:11.369392Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 228, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.369408Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-28 dy=-11 +2026-07-20T04:57:11.377438Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 227, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.377469Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-29 dy=-11 +2026-07-20T04:57:11.384490Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 219, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.384504Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-37 dy=-11 +2026-07-20T04:57:11.392436Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.392459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=-6 +2026-07-20T04:57:11.399564Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 223, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.399597Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-33 dy=-8 +2026-07-20T04:57:11.407332Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 228, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.407345Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-28 dy=-5 +2026-07-20T04:57:11.414482Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.414492Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=-5 +2026-07-20T04:57:11.422414Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 230, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.422429Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-26 dy=-3 +2026-07-20T04:57:11.429445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 225, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.429484Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-31 dy=-1 +2026-07-20T04:57:11.437361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 235, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.437384Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-21 dy=0 +2026-07-20T04:57:11.444442Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.444471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=3 +2026-07-20T04:57:11.452395Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.452408Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=5 +2026-07-20T04:57:11.459543Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 236, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.459572Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-20 dy=9 +2026-07-20T04:57:11.467372Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.467390Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=11 +2026-07-20T04:57:11.474465Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.474506Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=14 +2026-07-20T04:57:11.482540Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.482554Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=11 +2026-07-20T04:57:11.489543Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.489558Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=21 +2026-07-20T04:57:11.497482Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.497533Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=15 +2026-07-20T04:57:11.504530Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.504570Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=23 +2026-07-20T04:57:11.512538Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.512572Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=22 +2026-07-20T04:57:11.519403Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.519419Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=23 +2026-07-20T04:57:11.527469Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.527492Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=27 +2026-07-20T04:57:11.534493Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.534516Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=19 +2026-07-20T04:57:11.542369Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.542391Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=25 +2026-07-20T04:57:11.549509Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.549543Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=19 +2026-07-20T04:57:11.557396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.557418Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=23 +2026-07-20T04:57:11.564618Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.564638Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=17 +2026-07-20T04:57:11.572476Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.572498Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=17 +2026-07-20T04:57:11.579479Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.579494Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=15 +2026-07-20T04:57:11.587411Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.587434Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=9 +2026-07-20T04:57:11.594466Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.594501Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=12 +2026-07-20T04:57:11.602472Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.602501Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=8 +2026-07-20T04:57:11.609465Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.609504Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=3 +2026-07-20T04:57:11.615826Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:11.617449Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 21, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.617479Z INFO openlogi_hid::gesture: SPIKE raw xy dx=21 dy=2 +2026-07-20T04:57:11.619425Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:11.619498Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:11.630455Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:11.631376Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.631406Z INFO openlogi_hid::gesture: SPIKE raw xy dx=20 dy=0 +2026-07-20T04:57:11.638416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:11.638515Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:11.639490Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.639498Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=-1 +2026-07-20T04:57:11.641369Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.641382Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=-3 +2026-07-20T04:57:11.647413Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 23, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.647437Z INFO openlogi_hid::gesture: SPIKE raw xy dx=23 dy=-5 +2026-07-20T04:57:11.654424Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 23, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.654447Z INFO openlogi_hid::gesture: SPIKE raw xy dx=23 dy=-7 +2026-07-20T04:57:11.662321Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.665424Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:11.665523Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:11.668480Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:11.668546Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:11.669550Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 28, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.669564Z INFO openlogi_hid::gesture: SPIKE raw xy dx=28 dy=-9 +2026-07-20T04:57:11.671482Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:11.671511Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:11.674489Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:11.674492Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:11.677415Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 47, 255, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.677428Z INFO openlogi_hid::gesture: SPIKE raw xy dx=47 dy=-22 +2026-07-20T04:57:11.684445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.684462Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=-12 +2026-07-20T04:57:11.692374Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.692394Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=-16 +2026-07-20T04:57:11.699406Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.699426Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=-16 +2026-07-20T04:57:11.707385Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.707415Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-18 +2026-07-20T04:57:11.714396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 238, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.714426Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-18 +2026-07-20T04:57:11.722321Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.722362Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=-19 +2026-07-20T04:57:11.729315Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.729340Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-20 +2026-07-20T04:57:11.737492Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.737528Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-20 +2026-07-20T04:57:11.744394Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.744444Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-14 +2026-07-20T04:57:11.752434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 235, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.752460Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-21 +2026-07-20T04:57:11.759407Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.759427Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-20 +2026-07-20T04:57:11.767327Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.767342Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-20 +2026-07-20T04:57:11.774366Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.774379Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-19 +2026-07-20T04:57:11.782543Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.782557Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-19 +2026-07-20T04:57:11.789522Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.789543Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-19 +2026-07-20T04:57:11.797368Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.797402Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-19 +2026-07-20T04:57:11.804455Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.804485Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-12 +2026-07-20T04:57:11.812493Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.812508Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-17 +2026-07-20T04:57:11.819418Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.819435Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-11 +2026-07-20T04:57:11.827433Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.827492Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-9 +2026-07-20T04:57:11.834438Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.834457Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-10 +2026-07-20T04:57:11.842452Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.842478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-5 +2026-07-20T04:57:11.849351Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.849374Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=-4 +2026-07-20T04:57:11.857361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.857391Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-3 +2026-07-20T04:57:11.864381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.864404Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-2 +2026-07-20T04:57:11.872469Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.872490Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-1 +2026-07-20T04:57:11.879453Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.879476Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-1 +2026-07-20T04:57:11.887524Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.887570Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=0 +2026-07-20T04:57:11.894473Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.894522Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=0 +2026-07-20T04:57:11.902528Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.902551Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=0 +2026-07-20T04:57:11.909423Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.909448Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=1 +2026-07-20T04:57:11.924400Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.924419Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=1 +2026-07-20T04:57:11.932434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.932451Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=4 +2026-07-20T04:57:11.939581Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.939596Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=2 +2026-07-20T04:57:11.947396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.947438Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:57:11.954386Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.954422Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=2 +2026-07-20T04:57:11.962587Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.962618Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=2 +2026-07-20T04:57:11.969617Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.969635Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:57:11.984561Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.984635Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:57:11.992613Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.992627Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=0 +2026-07-20T04:57:11.999649Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:11.999684Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:57:12.007401Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:12.007457Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:57:12.014453Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:12.014469Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:57:12.029462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:12.029478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:57:12.037388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:12.037430Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:57:12.059293Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:12.059314Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:57:12.067425Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:12.067444Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:57:12.074399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:12.074413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:57:12.089461Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:12.089486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=2 +2026-07-20T04:57:12.097464Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:12.097492Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:57:12.104381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:12.104411Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=2 +2026-07-20T04:57:12.112430Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:12.112454Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=1 +2026-07-20T04:57:12.119493Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:12.119510Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=1 +2026-07-20T04:57:12.127355Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:12.127381Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=0 +2026-07-20T04:57:12.337420Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:12.337450Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=1 +2026-07-20T04:57:13.797364Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:13.797397Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:13.797401Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:13.797404Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:13.797407Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:13.797410Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:13.797413Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:13.797415Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:13.797418Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:13.797421Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:13.797424Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:13.797427Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:13.797430Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:13.797433Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:13.797440Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:13.797445Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:13.797452Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=2 +2026-07-20T04:57:13.797466Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:13.798373Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:13.805400Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:13.805470Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:13.812508Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:13.813434Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:15.202703Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:57:15.224804Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:57:15.316421Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:15.319498Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:15.319522Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:15.328456Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:15.336380Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:15.336381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:15.359504Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:15.362445Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:15.362509Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:15.365449Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:15.365634Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:15.367393Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:15.367440Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:15.369357Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:15.369416Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:17.457849Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:17.457898Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:17.457903Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:17.457907Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:17.457911Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:17.457915Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:17.457919Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:17.457923Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:17.457927Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:17.457931Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:17.457935Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:17.457939Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:17.457944Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:17.457948Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:17.457952Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:17.457956Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:17.457966Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=2 +2026-07-20T04:57:17.457987Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:17.459360Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:17.465351Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:17.465442Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:17.472412Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:17.474400Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:18.990205Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:18.992452Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:18.992512Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:19.002492Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:19.008465Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:19.008468Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:19.402388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:57:19.410295Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:19.413332Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:19.413393Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:19.416316Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:19.416345Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:19.419330Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:19.419429Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:19.422353Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:19.422400Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:19.422445Z DEBUG openlogi_agent_core::watchers::inventory: hotplug event — enumerating early event=Connected +2026-07-20T04:57:19.425384Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:57:19.877396Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:19.877421Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:19.877424Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:19.877427Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:57:19.877429Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:19.877431Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:19.877438Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:19.877442Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:19.877444Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:19.877446Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:19.877448Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:19.877451Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:19.877453Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:19.877455Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:19.877457Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:19.877460Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:19.877462Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:19.877464Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:19.877467Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:19.877469Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:19.877472Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:57:19.877474Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:19.877476Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:19.877479Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:19.877481Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:19.877488Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:57:19.925832Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:57:19.925855Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:57:19.925973Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:19.927237Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:57:19.927256Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:57:19.928525Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:19.934340Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:19.934394Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:19.941418Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:19.943331Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:21.455407Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:21.456369Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:21.456415Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:21.466307Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:21.472449Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:21.472461Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:21.735779Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:21.735814Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=0 +2026-07-20T04:57:21.742472Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:21.742511Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-1 +2026-07-20T04:57:21.750660Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:57:21.757478Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:21.757510Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-1 +2026-07-20T04:57:21.765730Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:21.769780Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:21.769804Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:21.771487Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:21.771510Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:21.772437Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 180, 255, 217, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:21.772468Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-76 dy=-39 +2026-07-20T04:57:21.774365Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:21.774397Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:21.777272Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:21.777348Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:21.780331Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:57:21.787321Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 186, 255, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:21.787335Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-70 dy=-72 +2026-07-20T04:57:21.795343Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 179, 255, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:21.795378Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-77 dy=-104 +2026-07-20T04:57:21.802569Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 217, 255, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:21.802601Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-39 dy=-55 +2026-07-20T04:57:21.810564Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 217, 255, 206, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:21.810584Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-39 dy=-50 +2026-07-20T04:57:21.817356Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 219, 255, 215, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:21.817388Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-37 dy=-41 +2026-07-20T04:57:21.825340Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 225, 255, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:21.825353Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-31 dy=-32 +2026-07-20T04:57:22.042509Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 255, 233, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.042555Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=-23 +2026-07-20T04:57:22.050481Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 255, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.050511Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=-33 +2026-07-20T04:57:22.057437Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.057468Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-6 +2026-07-20T04:57:22.065359Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 182, 255, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.065386Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-74 dy=-72 +2026-07-20T04:57:22.072413Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 223, 255, 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.072446Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-33 dy=-31 +2026-07-20T04:57:22.080385Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 228, 255, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.080419Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-28 dy=-24 +2026-07-20T04:57:22.087432Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 255, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.087459Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=-22 +2026-07-20T04:57:22.095360Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.095386Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=-17 +2026-07-20T04:57:22.102433Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 233, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.102458Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-23 dy=-14 +2026-07-20T04:57:22.110435Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.110537Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=-10 +2026-07-20T04:57:22.117421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 230, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.117436Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-26 dy=-11 +2026-07-20T04:57:22.125348Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.125373Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=-7 +2026-07-20T04:57:22.132530Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 234, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.132552Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-22 dy=-10 +2026-07-20T04:57:22.140369Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 241, 255, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.140395Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-15 dy=-6 +2026-07-20T04:57:22.147381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.147397Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=-4 +2026-07-20T04:57:22.155470Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.155492Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=-4 +2026-07-20T04:57:22.162548Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.162576Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-4 +2026-07-20T04:57:22.170464Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.170599Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-2 +2026-07-20T04:57:22.177380Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.177404Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-3 +2026-07-20T04:57:22.185414Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.185433Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-1 +2026-07-20T04:57:22.192469Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.192492Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-1 +2026-07-20T04:57:22.200446Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.200463Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=0 +2026-07-20T04:57:22.207362Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.207383Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=0 +2026-07-20T04:57:22.215331Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.215348Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=0 +2026-07-20T04:57:22.477461Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:22.477522Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-1 +2026-07-20T04:57:23.826685Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:23.826711Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:23.826714Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:23.826716Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:57:23.826718Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:23.826720Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:23.826722Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:23.826724Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:23.826726Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:23.826728Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:23.826730Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:23.826735Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:23.826739Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:23.826741Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:23.826743Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:23.826745Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:23.826747Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:23.826749Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:23.826751Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:23.826753Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:23.826755Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:57:23.826757Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:23.826759Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:23.826761Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:23.826763Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:23.826769Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:57:23.826800Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:23.828324Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:57:23.828355Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:57:23.828407Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:23.834279Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:23.834304Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:23.841383Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:23.842273Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:25.351692Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:25.354484Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:25.354576Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:25.363460Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:25.371343Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:25.371374Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:25.807334Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:57:25.815459Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:25.819300Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:25.819356Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:25.821339Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:25.821403Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:25.824296Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:25.824371Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:25.827334Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:25.827372Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:25.827550Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:57:25.827584Z DEBUG openlogi_agent_core::watchers::inventory: hotplug event — enumerating early event=Disconnected +2026-07-20T04:57:25.830376Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:57:26.287953Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:26.287980Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:26.287983Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:26.287986Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:57:26.287989Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:26.287991Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:26.287994Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:26.287996Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:26.288004Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:26.288009Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:26.288012Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:26.288015Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:26.288017Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:26.288020Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:26.288023Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:26.288025Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:26.288028Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:26.288031Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:26.288033Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:26.288036Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:26.288038Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:57:26.288041Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:26.288043Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:26.288046Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:26.288049Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:26.288058Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:57:26.341588Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:57:26.341617Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:57:26.341712Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:26.343235Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:57:26.343255Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:57:26.344323Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:26.347287Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:26.347387Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:26.354320Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:26.355327Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:27.867389Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:27.869464Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:27.869482Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:27.887412Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:27.892364Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:27.892413Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:28.357340Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:57:28.372343Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:28.374323Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:28.374329Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:28.377296Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:28.377296Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:28.379538Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:28.379632Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:28.381288Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:57:28.383291Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:28.383292Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:28.383465Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:57:30.516708Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:30.516732Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:30.516736Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:30.516738Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:57:30.516745Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:30.516749Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:30.516752Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:30.516755Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:30.516757Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:30.516760Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:30.516762Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:30.516765Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:30.516767Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:30.516770Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:30.516772Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:30.516775Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:30.516778Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:30.516780Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:30.516783Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:30.516785Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:30.516788Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:57:30.516790Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:30.516793Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:30.516795Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:30.516798Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:30.516806Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:57:30.561683Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:57:30.561703Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:57:30.561789Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:30.563225Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:57:30.563240Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:57:30.564294Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:30.569308Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:30.569350Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:30.576340Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:30.577335Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:32.082859Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:32.084381Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:32.084397Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:32.093453Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:32.099283Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:32.099313Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:32.430343Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:57:32.437323Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:32.439284Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:32.439304Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:32.441335Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:32.441355Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:32.444319Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:32.444353Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:32.446281Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:32.446315Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:32.446461Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:57:32.452300Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:57:34.534330Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:34.534364Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:34.534367Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:34.534369Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:57:34.534371Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:34.534373Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:34.534375Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:34.534377Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:34.534379Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:34.534381Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:34.534383Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:34.534385Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:34.534387Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:34.534389Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:34.534390Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:34.534392Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:34.534394Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:34.534396Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:34.534398Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:34.534401Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:34.534402Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:57:34.534404Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:34.534406Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:34.534408Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:34.534410Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:34.534417Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:57:34.581693Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:57:34.581723Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:57:34.583381Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:57:34.583402Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:57:34.585286Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:34.589568Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:34.589593Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:34.596444Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:34.597459Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:34.647426Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:36.099495Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:36.102353Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:36.102415Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:36.110538Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:36.117354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:36.117441Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:36.494309Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:57:36.502345Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:36.504349Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:36.504446Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:36.507391Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:36.507411Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:36.508333Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:36.508365Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:36.509269Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:36.509297Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:36.509411Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:57:36.510282Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:57:38.630922Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:38.630950Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:38.630953Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:38.630955Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:57:38.630957Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:38.630959Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:38.630961Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:38.630963Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:38.630965Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:38.630968Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:38.630969Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:38.630971Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:38.630973Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:38.630975Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:38.630977Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:38.630980Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:38.630982Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:38.630984Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:38.630986Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:38.630988Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:38.630990Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:57:38.630992Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:38.630997Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:38.631000Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:38.631002Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:38.631008Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:57:38.675350Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:57:38.675374Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:57:38.675459Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:38.676325Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:38.677284Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:57:38.677304Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:57:38.682444Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:38.682449Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:38.687293Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:38.688311Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:40.201549Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:40.204415Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:40.204447Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:40.213350Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:40.220415Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:40.220421Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:40.559365Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:57:40.567446Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 0, function_id: U4(1), software_id: U4(1) }, [4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.582487Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:57:40.589584Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 0, function_id: U4(0), software_id: U4(1) }, [1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.613774Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(0), software_id: U4(1) }, [45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.627507Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.642528Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 3, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.664361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 5, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.687420Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [29, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.709514Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 32, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.732455Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 33, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.754309Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.777405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.792379Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [16, 4, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.807375Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [23, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.829407Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [25, 176, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.844405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [25, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.859352Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [27, 4, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.882402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 20, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.897365Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 21, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.912542Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [34, 80, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.927470Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [33, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.942349Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [33, 33, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.964334Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [33, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.979352Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [34, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:40.994434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [34, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:41.017388Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 209, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:41.039426Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 2, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:41.062448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 3, 112, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:41.084369Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 7, 112, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:41.107351Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 22, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:41.129404Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 5, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:41.144576Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 48, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:41.159309Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 145, 104, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:41.182361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 161, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:41.197384Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [30, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:41.212397Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [30, 2, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:41.226390Z DEBUG openlogi_hid::inventory::probe: Bolt slot probe timed out; using cached data if available slot=2 budget=1s +2026-07-20T04:57:41.228493Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:41.228531Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:41.230437Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:41.230545Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:41.232441Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:41.232461Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:41.233335Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:41.233410Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:41.233510Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:57:41.234422Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [30, 34, 112, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.318965Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:43.318993Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:43.318996Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:43.318998Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:57:43.319000Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:43.319002Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:43.319004Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:43.319005Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:43.319007Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:43.319009Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:43.319011Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:43.319013Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:43.319015Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:43.319017Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:43.319019Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:43.319021Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:43.319023Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:43.319024Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:43.319026Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:43.319028Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:43.319033Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:57:43.319038Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:43.319040Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:43.319041Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:43.319043Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:43.319049Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:57:43.362800Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:57:43.362824Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:57:43.362946Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:43.364200Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:57:43.364217Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:57:43.364352Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:43.371339Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:43.371371Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:43.378408Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:43.379332Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:43.883559Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.883590Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-1 +2026-07-20T04:57:43.889555Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.889614Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=-10 +2026-07-20T04:57:43.897382Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 7, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.897431Z INFO openlogi_hid::gesture: SPIKE raw xy dx=7 dy=-4 +2026-07-20T04:57:43.904362Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 42, 255, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.904405Z INFO openlogi_hid::gesture: SPIKE raw xy dx=42 dy=-32 +2026-07-20T04:57:43.912580Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.912620Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=-16 +2026-07-20T04:57:43.919583Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.919614Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=-15 +2026-07-20T04:57:43.927810Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.927841Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=-11 +2026-07-20T04:57:43.934688Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.934713Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-7 +2026-07-20T04:57:43.942422Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.942449Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=-2 +2026-07-20T04:57:43.949494Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.949527Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-1 +2026-07-20T04:57:43.957466Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.957489Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=0 +2026-07-20T04:57:43.964376Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.964422Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=1 +2026-07-20T04:57:43.972537Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 244, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.972558Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-12 dy=1 +2026-07-20T04:57:43.979542Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.979573Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=3 +2026-07-20T04:57:43.987440Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.987464Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=4 +2026-07-20T04:57:43.994540Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:43.994570Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=3 +2026-07-20T04:57:44.002553Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.002586Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=8 +2026-07-20T04:57:44.009526Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 242, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.009551Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-14 dy=9 +2026-07-20T04:57:44.017425Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.017520Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=8 +2026-07-20T04:57:44.024621Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.024640Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=11 +2026-07-20T04:57:44.032402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.032439Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=10 +2026-07-20T04:57:44.039462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.039494Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=12 +2026-07-20T04:57:44.047386Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.047419Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=9 +2026-07-20T04:57:44.054438Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.054462Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=8 +2026-07-20T04:57:44.062493Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.062527Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=10 +2026-07-20T04:57:44.069389Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.069422Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=7 +2026-07-20T04:57:44.077400Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 8, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.077420Z INFO openlogi_hid::gesture: SPIKE raw xy dx=8 dy=8 +2026-07-20T04:57:44.084414Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.084436Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=7 +2026-07-20T04:57:44.092549Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.092573Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=8 +2026-07-20T04:57:44.099506Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.099543Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=4 +2026-07-20T04:57:44.107429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.107451Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=6 +2026-07-20T04:57:44.114548Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.114619Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=3 +2026-07-20T04:57:44.122376Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 22, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.122411Z INFO openlogi_hid::gesture: SPIKE raw xy dx=22 dy=3 +2026-07-20T04:57:44.129378Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 24, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.129394Z INFO openlogi_hid::gesture: SPIKE raw xy dx=24 dy=1 +2026-07-20T04:57:44.137424Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.137449Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=0 +2026-07-20T04:57:44.144444Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 27, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.144461Z INFO openlogi_hid::gesture: SPIKE raw xy dx=27 dy=-4 +2026-07-20T04:57:44.152519Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 24, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.152564Z INFO openlogi_hid::gesture: SPIKE raw xy dx=24 dy=-5 +2026-07-20T04:57:44.159381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 24, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.159399Z INFO openlogi_hid::gesture: SPIKE raw xy dx=24 dy=-7 +2026-07-20T04:57:44.167327Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 23, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.167348Z INFO openlogi_hid::gesture: SPIKE raw xy dx=23 dy=-8 +2026-07-20T04:57:44.174462Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 25, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.174483Z INFO openlogi_hid::gesture: SPIKE raw xy dx=25 dy=-12 +2026-07-20T04:57:44.182401Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 255, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.182420Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=-11 +2026-07-20T04:57:44.189465Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 17, 255, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.189487Z INFO openlogi_hid::gesture: SPIKE raw xy dx=17 dy=-13 +2026-07-20T04:57:44.197429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 255, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.197478Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=-15 +2026-07-20T04:57:44.204511Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 12, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.204544Z INFO openlogi_hid::gesture: SPIKE raw xy dx=12 dy=-16 +2026-07-20T04:57:44.212693Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 255, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.212715Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=-17 +2026-07-20T04:57:44.219374Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 4, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.219416Z INFO openlogi_hid::gesture: SPIKE raw xy dx=4 dy=-12 +2026-07-20T04:57:44.227459Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.236659Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-16 +2026-07-20T04:57:44.236730Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.236740Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-14 +2026-07-20T04:57:44.242347Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.242390Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-8 +2026-07-20T04:57:44.249332Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.249383Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-10 +2026-07-20T04:57:44.257391Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.257408Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=-5 +2026-07-20T04:57:44.264581Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 237, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.264600Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-19 dy=-8 +2026-07-20T04:57:44.272381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 233, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.272399Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-23 dy=-5 +2026-07-20T04:57:44.279525Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 239, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.279557Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-17 dy=-2 +2026-07-20T04:57:44.287317Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.287335Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=-2 +2026-07-20T04:57:44.294467Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.294486Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-26 dy=0 +2026-07-20T04:57:44.302673Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 231, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.302698Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-25 dy=3 +2026-07-20T04:57:44.309716Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 232, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.309740Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-24 dy=5 +2026-07-20T04:57:44.317421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 229, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.317444Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-27 dy=7 +2026-07-20T04:57:44.324421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.324441Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=7 +2026-07-20T04:57:44.332417Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 238, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.332438Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-18 dy=8 +2026-07-20T04:57:44.339383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 240, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.339403Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-16 dy=11 +2026-07-20T04:57:44.347453Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.347487Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=11 +2026-07-20T04:57:44.354434Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.354465Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=13 +2026-07-20T04:57:44.362546Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.362580Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=14 +2026-07-20T04:57:44.369531Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 254, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.369551Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-2 dy=14 +2026-07-20T04:57:44.377354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.377372Z INFO openlogi_hid::gesture: SPIKE raw xy dx=0 dy=14 +2026-07-20T04:57:44.384592Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.384613Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=13 +2026-07-20T04:57:44.392572Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 6, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.392590Z INFO openlogi_hid::gesture: SPIKE raw xy dx=6 dy=13 +2026-07-20T04:57:44.399446Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.399482Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=12 +2026-07-20T04:57:44.407353Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 9, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.407389Z INFO openlogi_hid::gesture: SPIKE raw xy dx=9 dy=8 +2026-07-20T04:57:44.414321Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 16, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.414345Z INFO openlogi_hid::gesture: SPIKE raw xy dx=16 dy=10 +2026-07-20T04:57:44.422344Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.422364Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=8 +2026-07-20T04:57:44.429343Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.429377Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=4 +2026-07-20T04:57:44.437345Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 21, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.437361Z INFO openlogi_hid::gesture: SPIKE raw xy dx=21 dy=4 +2026-07-20T04:57:44.444447Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 15, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.444464Z INFO openlogi_hid::gesture: SPIKE raw xy dx=15 dy=2 +2026-07-20T04:57:44.452477Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.452495Z INFO openlogi_hid::gesture: SPIKE raw xy dx=21 dy=0 +2026-07-20T04:57:44.459292Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.459337Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=-2 +2026-07-20T04:57:44.467352Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 19, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.467370Z INFO openlogi_hid::gesture: SPIKE raw xy dx=19 dy=-5 +2026-07-20T04:57:44.474323Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.474346Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-7 +2026-07-20T04:57:44.482343Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 18, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.482361Z INFO openlogi_hid::gesture: SPIKE raw xy dx=18 dy=-9 +2026-07-20T04:57:44.489592Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.489614Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-8 +2026-07-20T04:57:44.497422Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 11, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.497476Z INFO openlogi_hid::gesture: SPIKE raw xy dx=11 dy=-12 +2026-07-20T04:57:44.504429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 5, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.504461Z INFO openlogi_hid::gesture: SPIKE raw xy dx=5 dy=-10 +2026-07-20T04:57:44.512518Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.512546Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-10 +2026-07-20T04:57:44.519311Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 1, 255, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.519345Z INFO openlogi_hid::gesture: SPIKE raw xy dx=1 dy=-16 +2026-07-20T04:57:44.527365Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.527384Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-9 +2026-07-20T04:57:44.534511Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 247, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.534528Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-9 +2026-07-20T04:57:44.542389Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.542419Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-8 +2026-07-20T04:57:44.549508Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.549525Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-10 +2026-07-20T04:57:44.557335Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.557352Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-5 +2026-07-20T04:57:44.564546Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.564563Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-4 +2026-07-20T04:57:44.572573Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.572609Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-3 +2026-07-20T04:57:44.579297Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.579328Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-3 +2026-07-20T04:57:44.587360Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.587379Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-2 +2026-07-20T04:57:44.594410Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.594430Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-1 +2026-07-20T04:57:44.602477Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.602518Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-1 +2026-07-20T04:57:44.609398Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.609435Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=0 +2026-07-20T04:57:44.617465Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 253, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.617500Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-3 dy=-1 +2026-07-20T04:57:44.624504Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:44.624533Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=0 +2026-07-20T04:57:44.893484Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:44.895328Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:44.895381Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:44.905391Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:44.910320Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:44.910380Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:45.659509Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 0, function_id: U4(1), software_id: U4(1) }, [4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.674423Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 0, function_id: U4(0), software_id: U4(1) }, [1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.689351Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(0), software_id: U4(1) }, [45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.712381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.727396Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 3, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.742485Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 5, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.764633Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [29, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.787428Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 32, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.809589Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 33, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.824425Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.839443Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.862621Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [16, 4, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.877420Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [23, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.892560Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [25, 176, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.914449Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [25, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:45.914548Z DEBUG openlogi_hid::inventory::probe: Bolt slot probe timed out; using cached data if available slot=2 budget=1s +2026-07-20T04:57:45.920488Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:45.920524Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:45.921433Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:45.921434Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:45.923552Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:45.923559Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:45.925489Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:45.925498Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:45.925615Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:57:45.929405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [27, 4, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:47.962610Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:57:47.984354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:57:48.050357Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:48.050380Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:48.050383Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:48.050385Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:57:48.050387Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:48.050390Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:48.050395Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:48.050399Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:48.050401Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:48.050403Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:48.050405Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:48.050407Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:48.050409Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:48.050411Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:48.050413Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:48.050415Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:48.050417Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:48.050419Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:48.050421Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:48.050423Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:48.050425Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:57:48.050427Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:48.050429Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:48.050431Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:48.050433Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:48.050440Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:57:48.097607Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:57:48.097630Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:57:48.097727Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:48.099280Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:57:48.099288Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:57:48.100344Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:48.104352Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:48.104366Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:48.111308Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:48.113411Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:49.622294Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:49.624400Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:49.624429Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:49.633438Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:49.641545Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:49.641599Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:50.017406Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:57:50.025430Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 0, function_id: U4(1), software_id: U4(1) }, [4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.039410Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:57:50.047363Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 0, function_id: U4(0), software_id: U4(1) }, [1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.069548Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(0), software_id: U4(1) }, [45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.092402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.114474Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 3, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.137468Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 5, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.159392Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [29, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.182392Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 32, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.204483Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 33, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.219445Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.234354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.257361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [16, 4, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.272394Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [23, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.287345Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [25, 176, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.309504Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [25, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.332455Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [27, 4, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.354549Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 20, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.377344Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 21, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.399405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [34, 80, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.422315Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [33, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.444515Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [33, 33, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.467403Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [33, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.489530Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [34, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.504664Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [34, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.519471Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 209, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.542485Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 2, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.557438Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 3, 112, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.572582Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 7, 112, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.594460Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 22, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.617437Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 5, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.639558Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 48, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:50.643374Z DEBUG openlogi_hid::inventory::probe: Bolt slot probe timed out; using cached data if available slot=2 budget=1s +2026-07-20T04:57:50.645406Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:50.645421Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:50.646443Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:50.646531Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:50.649585Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:50.649604Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:50.652391Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:50.652444Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:50.652660Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:57:50.662629Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 145, 104, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:52.737968Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:52.737996Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:52.737999Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:52.738001Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:57:52.738003Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:52.738005Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:52.738007Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:52.738009Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:52.738011Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:52.738013Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:52.738018Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:52.738022Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:52.738024Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:52.738026Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:52.738029Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:52.738031Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:52.738033Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:52.738035Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:52.738037Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:52.738039Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:52.738041Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:57:52.738043Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:52.738045Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:52.738047Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:52.738049Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:52.738056Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:57:52.783088Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:57:52.783113Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:57:52.783220Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:52.784262Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:57:52.784270Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:57:52.785337Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:52.791437Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:52.791447Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:52.798522Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:52.799487Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:54.311514Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:54.313464Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:54.313493Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:54.321564Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:54.328566Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:54.328576Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:54.704348Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:57:54.712405Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 0, function_id: U4(1), software_id: U4(1) }, [4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.719402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:57:54.727399Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 0, function_id: U4(0), software_id: U4(1) }, [1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.749475Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(0), software_id: U4(1) }, [45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.764508Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.779542Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 3, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.802421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 5, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.817450Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [29, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.832383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 32, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.854534Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 33, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.877358Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.899555Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.914461Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [16, 4, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.929440Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [23, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.952398Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [25, 176, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.967483Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [25, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:54.982419Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [27, 4, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.004398Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 20, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.019435Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 21, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.034361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [34, 80, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.049383Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [33, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.064507Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [33, 33, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.087440Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [33, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.102424Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [34, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.117353Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [34, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.139596Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 209, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.162487Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 2, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.184475Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 3, 112, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.207307Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 7, 112, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.229579Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 22, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.259463Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 5, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.282621Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 48, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.304482Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 145, 104, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.319391Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 161, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.334505Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [30, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:55.334550Z DEBUG openlogi_hid::inventory::probe: Bolt slot probe timed out; using cached data if available slot=2 budget=1s +2026-07-20T04:57:55.338327Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:55.338356Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:55.341458Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:55.341474Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:55.344336Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:55.344366Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:55.346284Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:55.346310Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:55.346485Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:57:55.357403Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [30, 2, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:57.477750Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:57.477782Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:57.477785Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:57.477787Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:57:57.477789Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:57.477792Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:57.477794Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:57.477799Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:57.477801Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:57.477837Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:57:57.477840Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:57:57.477842Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:57:57.477844Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:57:57.477846Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:57:57.477848Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:57.477850Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:57.477853Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:57:57.477855Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:57.477857Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:57:57.477859Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:57:57.477861Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:57:57.477864Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:57:57.477866Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:57:57.477868Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:57:57.477870Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:57:57.477877Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:57:57.523906Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:57:57.523933Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:57:57.524015Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:57:57.525307Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:57:57.525326Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:57:57.526317Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:57:57.531314Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:57:57.531337Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:57:57.538298Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:57:57.539323Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:57:58.897389Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:57:58.919346Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:57:59.051151Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:57:59.053659Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:57:59.053717Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:57:59.063421Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:57:59.069391Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:57:59.069432Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:57:59.084459Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 0, function_id: U4(1), software_id: U4(1) }, [4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.099536Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 0, function_id: U4(0), software_id: U4(1) }, [1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.122640Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(0), software_id: U4(1) }, [45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.144450Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.159483Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 3, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.174571Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 5, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.189443Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [29, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.204512Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 32, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.228372Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 33, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.243452Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.258582Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.279589Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [16, 4, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.303711Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [23, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.324629Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [25, 176, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.339398Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [25, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.354477Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [27, 4, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.378363Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 20, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.393361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 21, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.408353Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [34, 80, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.429482Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [33, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.444542Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [33, 33, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.459494Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [33, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.474475Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [34, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.489606Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [34, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.513430Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [0, 209, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.528496Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 2, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.543520Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 3, 112, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.564366Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 7, 112, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.588419Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 22, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.609404Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 5, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.633578Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 48, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.654471Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 145, 104, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.678615Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 161, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.693316Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [30, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.708497Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [30, 2, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.729647Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [30, 34, 112, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.744346Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [30, 48, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.759402Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [22, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.782482Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [30, 176, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.804409Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 97, 112, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.827380Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [146, 5, 112, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.849581Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [146, 1, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.872517Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [147, 0, 112, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.894301Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [148, 1, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.909438Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [148, 2, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.924484Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [144, 1, 112, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.947370Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 177, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.962604Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 1, function_id: U4(1), software_id: U4(1) }, [24, 192, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.977353Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 18, function_id: U4(0), software_id: U4(1) }, [15, 28, 24, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:57:59.999349Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.022467Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(1) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:58:00.030414Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.030470Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-12 +2026-07-20T04:58:00.037427Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 3, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.037446Z INFO openlogi_hid::gesture: SPIKE raw xy dx=3 dy=-10 +2026-07-20T04:58:00.044626Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(2), software_id: U4(1) }, [50, 53, 51, 48, 90, 65, 76, 53, 81, 71, 78, 56, 0, 0, 0, 0]) +2026-07-20T04:58:00.052515Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 14, 255, 206, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.052558Z INFO openlogi_hid::gesture: SPIKE raw xy dx=14 dy=-50 +2026-07-20T04:58:00.060722Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 10, 255, 182, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.060750Z INFO openlogi_hid::gesture: SPIKE raw xy dx=10 dy=-74 +2026-07-20T04:58:00.067403Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 3, function_id: U4(2), software_id: U4(1) }, [3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.070432Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:00.070454Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:00.072393Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:00.072398Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:00.074582Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:00.074600Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:00.075346Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [0, 2, 255, 206, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.075364Z INFO openlogi_hid::gesture: SPIKE raw xy dx=2 dy=-50 +2026-07-20T04:58:00.078469Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:00.078492Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:00.078653Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:58:00.082349Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 255, 132, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.082362Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=-124 +2026-07-20T04:58:00.090440Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 190, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.090470Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-66 +2026-07-20T04:58:00.097440Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.097460Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-69 +2026-07-20T04:58:00.105504Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 180, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.105522Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-76 +2026-07-20T04:58:00.112430Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 255, 228, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.112448Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=-28 +2026-07-20T04:58:00.405587Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 251, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.405617Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-5 +2026-07-20T04:58:00.412538Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 255, 249, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.412572Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=-7 +2026-07-20T04:58:00.420554Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.420585Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-3 +2026-07-20T04:58:00.427448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 243, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.427471Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-13 dy=6 +2026-07-20T04:58:00.435711Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.435754Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=-1 +2026-07-20T04:58:00.442693Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 251, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.442710Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-5 dy=-10 +2026-07-20T04:58:00.450375Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 245, 255, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.450406Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-11 dy=-10 +2026-07-20T04:58:00.457363Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.457384Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-4 +2026-07-20T04:58:00.465381Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 247, 255, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.465413Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-9 dy=-2 +2026-07-20T04:58:00.472491Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 249, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.472510Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-7 dy=2 +2026-07-20T04:58:00.480604Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 246, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.480647Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-10 dy=5 +2026-07-20T04:58:00.487473Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 250, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.487517Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-6 dy=7 +2026-07-20T04:58:00.495515Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 248, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.495533Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-8 dy=8 +2026-07-20T04:58:00.502763Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 252, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.502798Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-4 dy=1 +2026-07-20T04:58:00.510380Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.510409Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=0 +2026-07-20T04:58:00.517436Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 13, function_id: U4(1), software_id: U4(0) }, [255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:00.517454Z INFO openlogi_hid::gesture: SPIKE raw xy dx=-1 dy=0 +2026-07-20T04:58:02.174350Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:02.174380Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:02.174383Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:02.174385Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:58:02.174387Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:02.174389Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:02.174391Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:02.174393Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:02.174395Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:02.174397Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:02.174402Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:58:02.174404Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:58:02.174406Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:02.174408Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:02.174410Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:02.174412Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:02.174414Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:02.174416Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:02.174418Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:58:02.174420Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:02.174422Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:58:02.174424Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:58:02.174426Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:02.174428Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:58:02.174430Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:02.174437Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:58:02.217920Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:58:02.217946Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:58:02.218030Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:58:02.219397Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:58:02.219449Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:58:02.219461Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:58:02.226345Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:58:02.226382Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:58:02.234405Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:58:02.235365Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:58:03.132830Z DEBUG openlogi_agent_core::watchers::foreground_app: frontmost app changed current=Some("c:\\program files (x86)\\steam\\steamapps\\common\\command & conquer generals - zero hour\\game.dat") last=Some("c:\\program files\\windowsapps\\claude_1.22209.3.0_x64__pzs8sxrjxfjjc\\app\\claude.exe") +2026-07-20T04:58:03.743980Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:58:03.747291Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:03.747359Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:03.756350Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:58:03.763373Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:58:03.763456Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:58:04.102601Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:58:04.117465Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:04.119394Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:04.119400Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:04.122396Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:04.122450Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:04.124327Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:04.124394Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:04.125477Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:58:04.127536Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:04.127547Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:04.127688Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:58:06.251959Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:06.251989Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:06.251992Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:06.251994Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:58:06.251999Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:06.252001Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:06.252003Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:06.252005Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:06.252007Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:06.252009Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:06.252011Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:58:06.252013Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:58:06.252015Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:06.252017Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:06.252019Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:06.252021Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:06.252023Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:06.252025Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:06.252027Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:58:06.252029Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:06.252031Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:58:06.252033Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:58:06.252035Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:06.252037Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:58:06.252039Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:06.252046Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:58:06.298902Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:58:06.298931Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:58:06.299015Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:58:06.300250Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:58:06.300320Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:58:06.300412Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:58:06.306394Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:58:06.306406Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:58:06.314372Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:58:06.315322Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:58:07.672441Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:58:07.695528Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:58:07.827526Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:58:07.829416Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:07.829469Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:07.838528Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:58:07.845426Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:58:07.845430Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:58:07.860492Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:07.862492Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:07.862510Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:07.865400Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:07.865461Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:07.866454Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:07.866536Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:07.868449Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:07.868470Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:07.868601Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:58:09.957318Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:09.957362Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:09.957368Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:09.957370Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:58:09.957373Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:09.957375Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:09.957377Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:09.957379Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:09.957382Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:09.957384Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:09.957386Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:58:09.957388Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:58:09.957391Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:09.957393Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:09.957408Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:09.957411Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:09.957413Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:09.957416Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:09.957418Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:58:09.957421Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:09.957423Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:58:09.957425Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:58:09.957428Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:09.957430Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:58:09.957432Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:09.957440Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:58:10.003801Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:58:10.003830Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:58:10.003916Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:58:10.005293Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:58:10.005308Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:58:10.006377Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:58:10.011383Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:58:10.011417Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:58:10.018408Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:58:10.019403Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:58:11.531853Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:58:11.533645Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:11.533662Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:11.542476Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:58:11.549448Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:58:11.549528Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:58:11.910435Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:58:11.918357Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:11.921448Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:11.921466Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:11.923333Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:11.923424Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:11.924405Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:11.924406Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:11.927385Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:11.927451Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:11.927625Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:58:11.933495Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:58:14.055253Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:14.055283Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:14.055287Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:14.055289Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:58:14.055292Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:14.055294Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:14.055297Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:14.055299Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:14.055302Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:14.055304Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:14.055307Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:58:14.055311Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:58:14.055313Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:14.055335Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:14.055338Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:14.055340Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:14.055343Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:14.055346Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:14.055348Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:58:14.055351Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:14.055353Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:58:14.055356Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:58:14.055367Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:14.055370Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:58:14.055372Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:14.055379Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:58:14.105023Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:58:14.105049Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:58:14.105153Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:58:14.106315Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:58:14.106333Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:58:14.107315Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:58:14.113311Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:58:14.113355Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:58:14.118403Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:58:14.120291Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:58:15.137313Z DEBUG openlogi_agent_core::watchers::foreground_app: frontmost app changed current=None last=Some("c:\\program files (x86)\\steam\\steamapps\\common\\command & conquer generals - zero hour\\game.dat") +2026-07-20T04:58:15.625511Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:58:15.627431Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:15.627432Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:15.636338Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:58:15.642382Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:58:15.642385Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:58:15.960345Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:58:15.975314Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:15.977342Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:15.977353Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:15.980365Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:15.980510Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:15.982322Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:15.982488Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:15.984437Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:58:15.986381Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:15.986413Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:15.986698Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:58:16.137808Z DEBUG openlogi_agent_core::watchers::foreground_app: frontmost app changed current=Some("c:\\program files\\windowsapps\\claude_1.22209.3.0_x64__pzs8sxrjxfjjc\\app\\claude.exe") last=None +2026-07-20T04:58:18.081084Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:18.081112Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:18.081115Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:18.081117Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:58:18.081119Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:18.081121Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:18.081123Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:18.081125Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:18.081127Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:18.081129Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:18.081131Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:58:18.081133Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:58:18.081135Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:18.081137Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:18.081139Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:18.081185Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:18.081190Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:18.081192Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:18.081194Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:58:18.081196Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:18.081198Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:58:18.081200Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:58:18.081203Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:18.081205Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:58:18.081207Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:18.081215Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:58:18.129390Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:58:18.129416Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:58:18.129495Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:58:18.131305Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:58:18.131346Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:58:18.132378Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:58:18.135338Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:58:18.135396Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:58:18.142438Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:58:18.143382Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:58:19.530354Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:58:19.553378Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:58:19.654282Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:58:19.657299Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:19.657313Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:19.668316Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:58:19.674333Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:58:19.674350Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:58:19.695380Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:19.698320Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:19.698356Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:19.701304Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:19.701415Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:19.703313Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:19.703342Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:19.706293Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:19.706326Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:19.706479Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:58:21.831701Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:21.831732Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:21.831735Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:21.831737Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:58:21.831739Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:21.831741Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:21.831743Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:21.831745Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:21.831747Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:21.831749Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:21.831751Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:58:21.831807Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:58:21.831811Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:21.831813Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:21.831815Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:21.831817Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:21.831819Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:21.831821Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:21.831823Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:58:21.831825Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:21.831827Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:58:21.831829Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:58:21.831831Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:21.831833Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:58:21.831835Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:21.831842Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:58:21.877551Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:58:21.877579Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:58:21.877664Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:58:21.879236Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:58:21.879254Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:58:21.880361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:58:21.885335Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:58:21.885380Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:58:21.892430Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:58:21.893402Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:58:23.141095Z DEBUG openlogi_agent_core::watchers::foreground_app: frontmost app changed current=Some("c:\\windows\\explorer.exe") last=Some("c:\\program files\\windowsapps\\claude_1.22209.3.0_x64__pzs8sxrjxfjjc\\app\\claude.exe") +2026-07-20T04:58:23.407254Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:58:23.410373Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:23.410397Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:23.420358Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:58:23.424330Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:58:23.424369Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:58:23.738362Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:58:23.745342Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:23.747338Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:23.747378Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:23.749318Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:23.749354Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:23.751354Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:23.751416Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:23.753333Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:58:23.754312Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:23.754353Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:23.754584Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:58:24.141724Z DEBUG openlogi_agent_core::watchers::foreground_app: frontmost app changed current=Some("c:\\program files\\google\\chrome\\application\\chrome.exe") last=Some("c:\\windows\\explorer.exe") +2026-07-20T04:58:25.855551Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:25.855594Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:25.855603Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:25.855619Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:58:25.855630Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:25.855636Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:25.855642Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:25.855648Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:25.855653Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:25.855659Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:25.855665Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:58:25.855671Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:58:25.855677Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:25.855683Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:25.855688Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:25.855695Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:25.855700Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:25.855706Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:25.855712Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:58:25.855719Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:25.855725Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:58:25.855730Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:58:25.855736Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:25.855742Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:58:25.855748Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:25.855764Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:58:25.906196Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:58:25.906232Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:58:25.906414Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:58:25.908303Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:58:25.908343Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:58:25.909459Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:58:25.915413Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:58:25.915448Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:58:25.920364Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:58:25.922400Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:58:27.435776Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:58:27.439368Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:27.439447Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:27.447428Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:58:27.455429Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:58:27.455474Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:58:27.795425Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:58:27.810376Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:27.813367Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:27.813415Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:27.816366Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:27.816418Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:27.818383Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:27.818438Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:27.820414Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:58:27.821347Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:27.821373Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:27.821643Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:58:29.961898Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:29.962114Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:29.962123Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:29.962128Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:58:29.962136Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:29.962141Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:29.962145Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:29.962149Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:29.962153Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:29.962157Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:29.962161Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:58:29.962165Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:58:29.962168Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:29.962172Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:29.962176Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:29.962181Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:29.962184Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:29.962188Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:29.962193Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:58:29.962197Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:29.962201Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:58:29.962204Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:58:29.962209Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:29.962213Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:58:29.962217Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:29.962231Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:58:30.014547Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:58:30.014652Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:58:30.014790Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:58:30.016323Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:58:30.016352Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:58:30.017357Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:58:30.022334Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:58:30.022424Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:58:30.030355Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:58:30.032347Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:58:31.351330Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:58:31.366364Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:58:31.539926Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:58:31.542493Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:31.542523Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:31.551517Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:58:31.559640Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:58:31.559644Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:58:31.575582Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:31.578561Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:31.578593Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:31.581536Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:31.581557Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:31.583545Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:31.583559Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:31.585462Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:31.585478Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:31.585817Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:58:33.688732Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:33.688763Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:33.688768Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:33.688772Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:58:33.688775Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:33.688779Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:33.688782Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:33.688785Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:33.688788Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:33.688792Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:33.688796Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:58:33.688799Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:58:33.688802Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:33.688806Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:33.688809Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:33.688813Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:33.688816Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:33.688820Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:33.688824Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:58:33.688827Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:33.688831Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:58:33.688835Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:58:33.688844Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:33.688851Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:58:33.688854Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:33.688868Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:58:33.740134Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:58:33.740166Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:58:33.740370Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:58:33.742324Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:58:33.742382Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:58:33.742397Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:58:33.749667Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:58:33.749757Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:58:33.757442Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:58:33.758498Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:58:35.265803Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:58:35.272407Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:35.272551Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:35.281450Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:58:35.287485Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:58:35.287544Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:58:35.617593Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:58:35.625516Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:35.628442Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:35.628489Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:35.631526Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:35.631559Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:35.632499Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:58:35.633568Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:35.633564Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:35.635343Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:35.635390Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:35.635600Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:58:37.774618Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:37.774637Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:37.774639Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:37.774642Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:58:37.774644Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:37.774647Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:37.774649Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:37.774652Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:37.774659Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:37.774666Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:37.774672Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:58:37.774679Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:58:37.774681Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:37.774684Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:37.774686Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:37.774688Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:37.774691Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:37.774696Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:37.774699Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:58:37.774703Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:37.774705Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:58:37.774707Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:58:37.774710Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:37.774712Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:58:37.774714Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:37.774722Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:58:37.828000Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:58:37.828027Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:58:37.829321Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:58:37.829348Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:58:37.829361Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:58:37.835396Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:58:37.835513Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:58:37.841369Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:58:37.843327Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:58:37.893601Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:58:39.357185Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:58:39.359429Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:39.359459Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:39.368350Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:58:39.374556Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:58:39.374561Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:58:39.676323Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:58:39.690416Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:39.692382Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:39.692383Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:39.694343Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:39.694351Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:39.697339Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:39.697352Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:39.698398Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:58:39.699318Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:39.699323Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:39.699462Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:58:41.798441Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:41.798464Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:41.798466Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:41.798469Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:58:41.798471Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:41.798474Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:41.798476Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:41.798478Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:41.798480Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:41.798482Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:41.798484Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:58:41.798486Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:58:41.798488Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:41.798493Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:41.798497Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:41.798499Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:41.798501Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:41.798504Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:41.798506Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:58:41.798508Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:41.798510Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:58:41.798512Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:58:41.798514Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:41.798517Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:58:41.798519Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:41.798527Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:58:41.849943Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:58:41.849981Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:58:41.850176Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:58:41.851423Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:58:41.851461Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:58:41.853356Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:58:41.857425Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:58:41.857503Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:58:41.864357Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:58:41.865479Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) +2026-07-20T04:58:43.379595Z DEBUG openlogi_hid::inventory::probe: drained device-arrival events events=1 +2026-07-20T04:58:43.383508Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=1 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:43.383579Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:43.393583Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [82, 2, 66, 176, 18, 193, 120, 77, 2, 1, 128, 20, 1, 0, 0, 0]) +2026-07-20T04:58:43.400477Z DEBUG openlogi_hid::inventory::probe: paired slot slot=2 online=true wpid=Some(45122) bolt_kind=Mouse has_event=true codename=Some("MX Master 4") +2026-07-20T04:58:43.400476Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(11), software_id: U4(5) }, [98, 1, 11, 77, 88, 32, 77, 97, 115, 116, 101, 114, 32, 52, 0, 0]) +2026-07-20T04:58:43.740524Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(0), software_id: U4(14) }, [4, 18, 193, 120, 77, 0, 2, 176, 66, 0, 0, 0, 0, 1, 1, 0]) +2026-07-20T04:58:43.748529Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 9, function_id: U4(1), software_id: U4(1) }, [30, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +2026-07-20T04:58:43.751369Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:43.751468Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=3 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:43.754488Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:43.754556Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=4 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:43.757350Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:43.757428Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=5 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:43.760440Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 143, function_id: U4(8), software_id: U4(3) }, [181, 8, 0]) +2026-07-20T04:58:43.760492Z DEBUG openlogi_hid::inventory::probe: slot empty or unreadable slot=6 error=Protocol(RegisterAccess(UnknownDevice)) +2026-07-20T04:58:43.760726Z WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick +2026-07-20T04:58:43.762369Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 2, feature_index: 2, function_id: U4(1), software_id: U4(14) }, [0, 82, 66, 77, 39, 3, 0, 25, 5, 176, 66, 3, 206, 8, 216, 0]) +2026-07-20T04:58:45.891969Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:45.892016Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:45.892024Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=c339 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:45.892030Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xffbc usage_id=0x0088 matched=false +2026-07-20T04:58:45.892036Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:45.892041Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:45.892046Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:45.892052Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:45.892057Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:45.892069Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0080 matched=false +2026-07-20T04:58:45.892078Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x0001 usage_id=0x0013 matched=false +2026-07-20T04:58:45.892084Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000e usage_id=0x0001 matched=false +2026-07-20T04:58:45.892089Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x0001 usage_id=0x0006 matched=false +2026-07-20T04:58:45.892094Z DEBUG openlogi_hid::transport: logitech HID node name=HID VHF Driver pid=4079 usage_page=0x0059 usage_id=0x0001 matched=false +2026-07-20T04:58:45.892099Z DEBUG openlogi_hid::transport: logitech HID node name=Logitech BRIO pid=085e usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:45.892105Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:45.892110Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0x000c usage_id=0x0001 matched=false +2026-07-20T04:58:45.892115Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:45.892120Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0602 matched=true +2026-07-20T04:58:45.892125Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0x0001 usage_id=0x0002 matched=false +2026-07-20T04:58:45.892131Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c539 usage_page=0xff00 usage_id=0x0004 matched=false +2026-07-20T04:58:45.892136Z DEBUG openlogi_hid::transport: logitech HID node name=PRO Gaming Keyboard pid=c339 usage_page=0xff43 usage_id=0x0604 matched=false +2026-07-20T04:58:45.892141Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0001 matched=false +2026-07-20T04:58:45.892146Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0x000d usage_id=0x0005 matched=false +2026-07-20T04:58:45.892151Z DEBUG openlogi_hid::transport: logitech HID node name=USB Receiver pid=c548 usage_page=0xff00 usage_id=0x0002 matched=true +2026-07-20T04:58:45.892165Z DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=3 +2026-07-20T04:58:45.940154Z DEBUG openlogi_hid::transport::windows: paired Windows HID++ short collection name=USB Receiver pid=c539 +2026-07-20T04:58:45.940183Z DEBUG openlogi_hid::transport::windows: opened Windows HID++ composite channel name=USB Receiver pid=c539 supports_short=true supports_long=true +2026-07-20T04:58:45.940344Z DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=PRO Gaming Keyboard +2026-07-20T04:58:45.941253Z DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=UnsupportedProtocolVersion +2026-07-20T04:58:45.941272Z DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=c539 has_model=false +2026-07-20T04:58:45.943467Z INFO openlogi_hid::gesture: SPIKE wire msg=Long(MessageHeader { device_index: 255, feature_index: 131, function_id: U4(15), software_id: U4(11) }, [57, 55, 66, 55, 54, 57, 52, 56, 65, 56, 52, 54, 67, 53, 53, 65]) +2026-07-20T04:58:45.948375Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 129, function_id: U4(0), software_id: U4(2) }, [0, 1, 0]) +2026-07-20T04:58:45.948481Z DEBUG openlogi_hid::inventory::probe: receiver reports pairing count pairing_count=Some(1) +2026-07-20T04:58:45.953368Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 2, feature_index: 65, function_id: U4(1), software_id: U4(0) }, [2, 66, 176]) +2026-07-20T04:58:45.954414Z INFO openlogi_hid::gesture: SPIKE wire msg=Short(MessageHeader { device_index: 255, feature_index: 128, function_id: U4(0), software_id: U4(2) }, [0, 0, 0]) From 498e6fb18d0ea131040044c1cd106746df7d48fa Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 05:36:28 -0700 Subject: [PATCH 06/42] fix(cli): make the reverse-engineering diag tools clippy-clean Raw-pointer borrows use the `&raw` forms, the RAWINPUT read buffer is u64-backed so the struct alignment is guaranteed rather than assumed from allocator behaviour, the two const-size u32 casts carry expect reasons, and the four hand-rolled hex dumps share one fold-based helper. --- crates/openlogi-cli/src/cmd/diag.rs | 10 +++++ crates/openlogi-cli/src/cmd/diag/call.rs | 2 +- crates/openlogi-cli/src/cmd/diag/fsb.rs | 2 +- crates/openlogi-cli/src/cmd/diag/hidsniff.rs | 3 +- crates/openlogi-cli/src/cmd/diag/rawinput.rs | 40 +++++++++++--------- 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/crates/openlogi-cli/src/cmd/diag.rs b/crates/openlogi-cli/src/cmd/diag.rs index 13027c54..11bdcd6b 100644 --- a/crates/openlogi-cli/src/cmd/diag.rs +++ b/crates/openlogi-cli/src/cmd/diag.rs @@ -10,6 +10,8 @@ use anyhow::{Result, anyhow}; use clap::Subcommand; use openlogi_hid::{DeviceRoute, dump_features}; +use std::fmt::Write as _; + pub mod call; pub mod controls; pub mod dpi; @@ -70,6 +72,14 @@ impl DiagCmd { } } +/// Space-separated lowercase hex (`"0a 1b "` style) for diag report dumps. +pub(crate) fn hex_dump(bytes: &[u8]) -> String { + bytes.iter().fold(String::new(), |mut s, b| { + let _ = write!(s, "{b:02x} "); + s + }) +} + /// One online, paired device discovered during enumeration, already resolved to /// the [`DeviceRoute`] needed to talk to it. Builds a Bolt route when the device /// is behind a receiver, a direct route otherwise (USB cable / Bluetooth). diff --git a/crates/openlogi-cli/src/cmd/diag/call.rs b/crates/openlogi-cli/src/cmd/diag/call.rs index 110bda4a..4006a963 100644 --- a/crates/openlogi-cli/src/cmd/diag/call.rs +++ b/crates/openlogi-cli/src/cmd/diag/call.rs @@ -49,7 +49,7 @@ pub async fn run(args: CallArgs) -> Result<()> { .await { Ok(Some(payload)) => { - let hex: String = payload.iter().map(|b| format!("{b:02x} ")).collect(); + let hex = super::hex_dump(&payload); println!("response: {hex}"); } Ok(None) => println!("feature not exposed by this device"), diff --git a/crates/openlogi-cli/src/cmd/diag/fsb.rs b/crates/openlogi-cli/src/cmd/diag/fsb.rs index dbcc2104..23aaa2ee 100644 --- a/crates/openlogi-cli/src/cmd/diag/fsb.rs +++ b/crates/openlogi-cli/src/cmd/diag/fsb.rs @@ -44,7 +44,7 @@ pub async fn run(args: FsbArgs) -> Result<()> { .await { Ok(payload) => { - let hex: String = payload.iter().map(|b| format!("{b:02x} ")).collect(); + let hex = super::hex_dump(&payload); println!("response: {hex}"); } Err(e) => println!("error: {e}"), diff --git a/crates/openlogi-cli/src/cmd/diag/hidsniff.rs b/crates/openlogi-cli/src/cmd/diag/hidsniff.rs index 5f998066..662013b0 100644 --- a/crates/openlogi-cli/src/cmd/diag/hidsniff.rs +++ b/crates/openlogi-cli/src/cmd/diag/hidsniff.rs @@ -77,8 +77,7 @@ pub async fn run(args: HidSniffArgs) -> Result<()> { let seen = task_count.fetch_add(1, Ordering::Relaxed) + 1; if seen <= PRINT_CAP { let ms = started.elapsed().as_millis(); - let hex: String = - buf[..len].iter().map(|b| format!("{b:02x} ")).collect(); + let hex = super::hex_dump(&buf[..len]); println!("[{ms:>7}ms] {label} len={len}: {hex}"); } else if seen == PRINT_CAP + 1 { println!("[{label}] print cap reached — counting silently"); diff --git a/crates/openlogi-cli/src/cmd/diag/rawinput.rs b/crates/openlogi-cli/src/cmd/diag/rawinput.rs index f487110b..52e56456 100644 --- a/crates/openlogi-cli/src/cmd/diag/rawinput.rs +++ b/crates/openlogi-cli/src/cmd/diag/rawinput.rs @@ -80,7 +80,7 @@ mod windows_impl { wc.lpszClassName = class_name.as_ptr(); // Re-registration in one process returns 0 with // ERROR_CLASS_ALREADY_EXISTS — harmless for this tool's lifetime. - RegisterClassW(&wc); + RegisterClassW(&raw const wc); CreateWindowExW( 0, @@ -113,6 +113,10 @@ mod windows_impl { rid(0xff43, flags, hwnd), ]; // SAFETY: registrations array outlives the call; cbSize matches. + #[expect( + clippy::cast_possible_truncation, + reason = "a four-element array length and a fixed struct size are far below u32::MAX" + )] let ok = unsafe { RegisterRawInputDevices( registrations.as_ptr(), @@ -141,9 +145,9 @@ mod windows_impl { None, ); let mut msg: MSG = zeroed(); - while GetMessageW(&mut msg, null_mut(), 0, 0) > 0 { - TranslateMessage(&msg); - DispatchMessageW(&msg); + while GetMessageW(&raw mut msg, null_mut(), 0, 0) > 0 { + TranslateMessage(&raw const msg); + DispatchMessageW(&raw const msg); } } println!("raw-input tap closed"); @@ -183,35 +187,37 @@ mod windows_impl { } fn dump_input(handle: HRAWINPUT) { + #[expect( + clippy::cast_possible_truncation, + reason = "a fixed struct size is far below u32::MAX" + )] let header_size = size_of::() as u32; let mut size = 0u32; // SAFETY: sizing call per RawInput protocol (null buffer, then fetch). unsafe { - GetRawInputData(handle, RID_INPUT, null_mut(), &mut size, header_size); + GetRawInputData(handle, RID_INPUT, null_mut(), &raw mut size, header_size); } if size == 0 { return; } - let mut buf = vec![0u8; size as usize]; - // SAFETY: buffer is exactly the size Windows requested. + // Backed by u64 words so the buffer meets RAWINPUT's 8-byte alignment + // — a Vec only guarantees 1. + let mut buf = vec![0u64; (size as usize).div_ceil(size_of::())]; + // SAFETY: buffer is at least the size Windows requested. let got = unsafe { GetRawInputData( handle, RID_INPUT, buf.as_mut_ptr().cast(), - &mut size, + &raw mut size, header_size, ) }; if got == 0 || got == u32::MAX { return; } - // SAFETY: Windows filled `buf` with a RAWINPUT structure of at least - // `got` bytes; the buffer is aligned by Vec only to 1, so read - // fields via raw pointer without constructing a reference... a u8 Vec - // is sufficiently aligned in practice for RAWINPUT on x86-64 heap - // allocations (16-byte aligned), and this mirrors the canonical - // RawInput usage pattern. + // SAFETY: Windows filled the buffer with a RAWINPUT structure of at + // least `got` bytes; the u64 backing guarantees its alignment. let raw = buf.as_ptr().cast::(); // SAFETY: header fields are plain integers within the filled buffer. let (dw_type, h_device) = unsafe { ((*raw).header.dwType, (*raw).header.hDevice) }; @@ -233,7 +239,7 @@ mod windows_impl { // SAFETY: bRawData holds dwCount packed reports of dwSizeHid bytes // inside the buffer Windows sized for us. let report = unsafe { std::slice::from_raw_parts(data_ptr.add(i * each), each) }; - let hex: String = report.iter().map(|b| format!("{b:02x} ")).collect(); + let hex = crate::cmd::diag::hex_dump(report); println!("[{ms:>7}ms] {name} len={each}: {hex}"); } } @@ -249,13 +255,13 @@ mod windows_impl { let mut len = 0u32; // SAFETY: sizing call, then bounded fetch into a matching buffer. let name = unsafe { - GetRawInputDeviceInfoW(handle as _, RIDI_DEVICENAME, null_mut(), &mut len); + GetRawInputDeviceInfoW(handle as _, RIDI_DEVICENAME, null_mut(), &raw mut len); let mut buf = vec![0u16; len as usize]; let got = GetRawInputDeviceInfoW( handle as _, RIDI_DEVICENAME, buf.as_mut_ptr().cast(), - &mut len, + &raw mut len, ); if got == u32::MAX || got == 0 { format!("hdev={handle:x}") From bf662f31863b55ae8285c0ee047b914fda8c1e71 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 05:36:40 -0700 Subject: [PATCH 07/42] feat(hid): capture Action Ring taps in the live session MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire the MX Master 4 Action Ring pad into the capture pipeline using the hardware-confirmed analytics recipe: - decode 0x1b04 analyticsKeyEvents into RawControlEvent::AnalyticsKeys instead of discarding them — the discard is what made the pad look dead - arm the pad in arm_controls when the control table advertises 0x01a0 with analytics-events: 0x19c0 force threshold 0x15a3, then analytics reporting on 0x01a0/0x0050/0x0051 (never diversion, never 0x00d7) - re-apply the recipe every 3 s from the session loop — a single arm is lost while the BTLE link sleeps — with each pass time-bounded so a cold link cannot wedge the shutdown path; the first pass runs after the message listener is registered so no event is missed - track taps with a rising edge across the pad CIDs in handle_reprog (hardware reports each tap on one of 0x01a0/0x0050, varying); log-only for now — emitting a CapturedInput needs the ButtonId append, which is a wire change and lands separately - retire the 0x00d7 divert experiment and document the dead end on HAPTIC_PANEL_CID; share the panel CID list from reprog_controls Non-MX4 devices skip the arming entirely (no 0x01a0 in their table). --- crates/openlogi-cli/src/cmd/diag/panel.rs | 2 +- crates/openlogi-hid/src/gesture.rs | 212 ++++++++++++++++-- crates/openlogi-hid/src/gesture/tests.rs | 107 +++++++++ crates/openlogi-hid/src/hidden_features.rs | 12 +- crates/openlogi-hid/src/reprog_controls.rs | 38 +++- .../openlogi-hid/src/reprog_controls/event.rs | 51 ++++- 6 files changed, 381 insertions(+), 41 deletions(-) diff --git a/crates/openlogi-cli/src/cmd/diag/panel.rs b/crates/openlogi-cli/src/cmd/diag/panel.rs index 97f1b51d..21983ef1 100644 --- a/crates/openlogi-cli/src/cmd/diag/panel.rs +++ b/crates/openlogi-cli/src/cmd/diag/panel.rs @@ -30,7 +30,7 @@ pub async fn run(args: PanelArgs) -> Result<()> { let (route, name) = select_device(args.device.as_deref(), &[0x1b04]).await?; println!("device: {name} ({route})"); println!( - "arming Action Ring panel (analytics on CIDs 0x0050/0x0051) — press it now, {}s window\n", + "arming Action Ring panel (analytics on CIDs 0x01a0/0x0050/0x0051) — press it now, {}s window\n", args.seconds ); diff --git a/crates/openlogi-hid/src/gesture.rs b/crates/openlogi-hid/src/gesture.rs index 1783494b..a968bea3 100644 --- a/crates/openlogi-hid/src/gesture.rs +++ b/crates/openlogi-hid/src/gesture.rs @@ -1,12 +1,14 @@ -//! Live control capture for one device: divert the MX dedicated gesture button, the -//! DPI/ModeShift button, and the thumb wheel over HID++ and turn their events -//! into [`CapturedInput`] the GUI can dispatch. +//! Live control capture for one device: divert the MX dedicated gesture button, +//! the DPI/ModeShift button, and the thumb wheel over HID++ — and arm the MX +//! Master 4 Action Ring pad for `analyticsKeyEvents` reporting — turning their +//! events into [`CapturedInput`] the GUI can dispatch. //! //! [`run_capture_session`] holds a single HID++ channel open for one device, -//! enables diversion on whichever of those controls it exposes, registers one -//! message listener, and restores every control's default mapping on shutdown. -//! Using one channel matters: a second channel to the same device would split -//! its input-report stream, so all captured controls share this session. +//! enables capture on whichever of those controls it exposes, registers one +//! message listener, and restores every control's default reporting on +//! shutdown. Using one channel matters: a second channel to the same device +//! would split its input-report stream, so all captured controls share this +//! session. //! //! The session is transport-only — it has no opinion on what an input *does*. //! The GUI maps each [`CapturedInput`] to the user's bound action and dispatches @@ -16,18 +18,43 @@ //! is therefore only diverted when its click is actually bound. use std::sync::{Arc, Mutex, PoisonError, RwLock}; +use std::time::Duration; -use hidpp::{channel::HidppChannel, device::Device, protocol::v20}; +use hidpp::{ + channel::HidppChannel, + device::Device, + feature::{ + force_sensing_button::ForceSensingButtonFeature, reprog_controls::CidReportingChange, + }, + protocol::v20, +}; use openlogi_core::binding::{ButtonId, GestureDirection, SwipeAccumulator}; use serde::{Deserialize, Serialize}; use thiserror::Error; use tokio::sync::{mpsc, oneshot}; +use tokio::time::MissedTickBehavior; use tracing::{debug, info, warn}; use crate::reprog_controls::{self, RawControlEvent, ReprogControlsV4}; use crate::route::{DeviceRoute, open_route_channel}; use crate::thumbwheel::{self, Thumbwheel}; -use crate::write::SharedChannel; +use crate::write::{SharedChannel, open_feature}; + +/// Force-activation threshold Options+ writes to the Action Ring pad's button +/// `0` at startup — the magic constant that wakes the physically dormant pad +/// (`docs/mx-master-4-panel-captures/optionsplus-coldstart-full.txt`). +const ACTION_RING_FORCE_THRESHOLD: u16 = 0x15a3; + +/// Cadence for re-applying the Action Ring arming recipe. A single arm at +/// session start is lost when the BTLE link is asleep, and the configuration +/// does not survive the device sleeping mid-session, so the recipe is +/// re-applied continuously (the cadence the confirming hardware run used). +const PANEL_REARM_PERIOD: Duration = Duration::from_secs(3); + +/// Budget for one Action Ring arming pass. Channel reads have no intrinsic +/// timeout, so without a bound a cold link could hold an arming call — and +/// with it the session's shutdown path — indefinitely. +const PANEL_ARM_BUDGET: Duration = Duration::from_secs(2); /// Shared slot holding the active capture session's open channel, so DPI / /// SmartShift writes can reuse it instead of opening a fresh one. `None` @@ -75,6 +102,9 @@ struct CaptureAccum { /// Whether any DPI/ModeShift control was held in the last event — for /// rising-edge press detection. dpi_down: bool, + /// Whether any Action Ring CID was down in the last analytics event — for + /// rising-edge press detection across the pad's multiple CIDs. + panel_down: bool, } /// Capture the gesture button, DPI/ModeShift button, and (when @@ -155,11 +185,40 @@ pub async fn run_capture_session( info!( index = device_index, gesture = armed.gesture_diverted, + action_ring = armed.panel.is_some(), dpi_buttons = armed.dpi_cids.len(), thumbwheel = armed.thumb.is_some(), "control capture active" ); - let _ = shutdown.await; + + // The Action Ring's arming does not survive the device sleeping, so a + // session with the pad re-applies the recipe on a cadence while waiting + // for shutdown. The interval's first tick fires immediately and doubles as + // the initial arm — deliberately after the listener above, so no event + // between arm and listen is lost. + match (&armed.panel, &armed.reprog) { + (Some(panel), Some((rc, _))) => { + let mut shutdown = shutdown; + let mut cadence = tokio::time::interval(PANEL_REARM_PERIOD); + cadence.set_missed_tick_behavior(MissedTickBehavior::Delay); + loop { + tokio::select! { + _ = &mut shutdown => break, + _ = cadence.tick() => { + if tokio::time::timeout(PANEL_ARM_BUDGET, panel.apply(rc)) + .await + .is_err() + { + debug!("action ring re-arm timed out (link cold?)"); + } + } + } + } + } + _ => { + let _ = shutdown.await; + } + } drop(listener); if let Ok(mut slot) = channel_slot.write() { @@ -177,6 +236,9 @@ struct ArmedControls { reprog: Option<(ReprogControlsV4, u8)>, /// Whether the gesture button is diverted with raw-XY reporting. gesture_diverted: bool, + /// Present when the device has the Action Ring pad — the arming recipe + /// [`run_capture_session`] re-applies on a cadence. + panel: Option, /// DPI/ModeShift CIDs diverted as plain buttons. dpi_cids: Vec, /// `0x2150` accessor + feature index, present when the thumb wheel is @@ -185,7 +247,7 @@ struct ArmedControls { } impl ArmedControls { - /// Restore every diverted control. Failures are logged, not propagated. + /// Restore every captured control. Failures are logged, not propagated. async fn disarm(&self) { if let Some((rc, _)) = self.reprog.as_ref() { if self.gesture_diverted { @@ -194,6 +256,16 @@ impl ArmedControls { .await; restore(r, "gesture button"); } + if self.panel.is_some() { + let off = CidReportingChange { + analytics_key_events: Some(false), + ..CidReportingChange::default() + }; + for cid in reprog_controls::ACTION_RING_ANALYTICS_CIDS { + let r = rc.set_cid_reporting_full(cid, off).await.map(|_| ()); + restore(r, "action ring pad"); + } + } for &cid in &self.dpi_cids { restore(rc.set_cid_reporting(cid, false, false).await, "DPI button"); } @@ -204,23 +276,61 @@ impl ArmedControls { } } -/// Resolve features off the device's root and divert the controls we capture: -/// the gesture button (raw-XY) and DPI/ModeShift buttons over `0x1b04`, and — -/// when `capture_thumbwheel` and the wheel reports a single tap — the thumb -/// wheel over `0x2150`. The root-feature lookup mirrors `write::open_feature`, -/// since hidpp 0.2's registry doesn't carry the features OpenLogi reimplements. +/// The Action Ring pad's arming recipe, mirroring what Options+ does at +/// startup: write the pad's force threshold (the pad is physically dormant +/// without one), then enable `analyticsKeyEvents` reporting on its CIDs — no +/// diversion, no raw-XY. Re-applied on [`PANEL_REARM_PERIOD`] because neither +/// write survives the device sleeping. +struct PanelArming { + /// `0x19c0` accessor for the force-threshold write; `None` when the device + /// does not expose it (analytics reporting is still armed). + fsb: Option>, +} + +impl PanelArming { + /// One arming pass. Failures are logged at debug and retried on the next + /// cadence tick — transient misses are expected while the BTLE link is + /// asleep. + async fn apply(&self, rc: &ReprogControlsV4) { + if let Some(fsb) = &self.fsb + && let Err(e) = fsb + .set_force_threshold(0, ACTION_RING_FORCE_THRESHOLD) + .await + { + debug!(error = ?e, "action ring force-threshold write failed"); + } + let on = CidReportingChange { + analytics_key_events: Some(true), + ..CidReportingChange::default() + }; + for cid in reprog_controls::ACTION_RING_ANALYTICS_CIDS { + if let Err(e) = rc.set_cid_reporting_full(cid, on).await { + debug!(cid, error = ?e, "action ring analytics arm failed"); + } + } + } +} + +/// Resolve features off the device's root and capture the controls we handle: +/// divert the gesture button (raw-XY) and DPI/ModeShift buttons over `0x1b04`, +/// resolve the Action Ring pad's arming recipe when the control table +/// advertises the pad, and — when `capture_thumbwheel` and the wheel reports a +/// single tap — divert the thumb wheel over `0x2150`. The root-feature lookup +/// mirrors `write::open_feature`, since hidpp 0.2's registry doesn't carry the +/// features OpenLogi reimplements. async fn arm_controls( chan: &Arc, slot: u8, capture_thumbwheel: bool, divert_gesture_button: bool, ) -> Result { - let device = Device::new(Arc::clone(chan), slot) + let mut device = Device::new(Arc::clone(chan), slot) .await .map_err(|_| GestureError::DeviceUnreachable(slot))?; let mut reprog: Option<(ReprogControlsV4, u8)> = None; let mut gesture_diverted = false; + let mut panel: Option = None; let mut dpi_cids: Vec = Vec::new(); if let Some(info) = device .root() @@ -243,6 +353,29 @@ async fn arm_controls( .map_err(|e| GestureError::Hidpp(format!("{e:?}")))?; gesture_diverted = true; } + + // The Action Ring pad advertises `analytics-events` in the control + // table — never divert it (and never touch 0x00d7; see + // [`reprog_controls::HAPTIC_PANEL_CID`]). Only resolve the recipe + // here: the actual arming runs on the session's cadence, after the + // message listener is registered. + if controls + .iter() + .any(|c| c.cid == reprog_controls::ACTION_RING_CID && c.supports_analytics_events()) + { + // 0x19c0 wakes the physical pad. Its absence is tolerated in case + // another device family reports analytics-capable controls + // without a force pad — analytics reporting is still armed. + let fsb = match open_feature::(&mut device).await { + Ok(fsb) => Some(fsb), + Err(e) => { + warn!(error = %e, "action ring: 0x19c0 unavailable, arming analytics only"); + None + } + }; + panel = Some(PanelArming { fsb }); + info!("action ring pad present — analytics capture arming on cadence"); + } for &cid in &reprog_controls::DPI_MODE_SHIFT_CIDS { if controls.iter().any(|c| c.cid == cid && c.is_divertable()) { rc.set_cid_reporting(cid, true, false) @@ -283,12 +416,13 @@ async fn arm_controls( } } - if !gesture_diverted && dpi_cids.is_empty() && thumb.is_none() { + if !gesture_diverted && panel.is_none() && dpi_cids.is_empty() && thumb.is_none() { debug!(slot, "no capturable controls — idle session"); } Ok(ArmedControls { reprog, gesture_diverted, + panel, dpi_cids, thumb, }) @@ -323,8 +457,9 @@ async fn enumerate_controls( /// Update `acc` and emit on a decoded `0x1b04` event: commit a gesture swipe the /// instant it crosses the threshold (mid-swipe, like Options+) rather than on -/// release, and emit a [`ButtonId::DpiToggle`] press on the rising edge of any -/// diverted DPI/ModeShift control. +/// release, emit a [`ButtonId::DpiToggle`] press on the rising edge of any +/// diverted DPI/ModeShift control, and track Action Ring taps from analytics +/// entries. fn handle_reprog( acc: &mut CaptureAccum, event: RawControlEvent, @@ -359,6 +494,43 @@ fn handle_reprog( let _ = sink.send(CapturedInput::Gesture(direction)); } } + RawControlEvent::AnalyticsKeys(entries) => { + // The Action Ring pad reports each tap as a press/release pair on + // ONE of its CIDs (0x01a0 or 0x0050, varying with the press), and + // a firm press can hold several CIDs at once — so track "any ring + // CID down" and emit on its rising edge: one physical tap, one + // press. The release is deliberately unused: the ring UI is a + // toggle (tap to open, tap to select), not press-and-hold. + let mut pressed = false; + let mut released = false; + for entry in entries { + let cid: u16 = entry.cid.into(); + if cid == 0 { + continue; + } + if reprog_controls::ACTION_RING_ANALYTICS_CIDS.contains(&cid) { + if entry.event == 0 { + released = true; + } else { + pressed = true; + } + } else { + debug!( + cid, + entry.event, "analytics event from an unhandled control" + ); + } + } + if pressed && !acc.panel_down { + acc.panel_down = true; + // Log-only until the Action Ring is a bindable control: turning + // this into a CapturedInput needs a ButtonId variant, which + // crosses the IPC wire (append-only, protocol version bump). + debug!("action ring pressed"); + } else if released && !pressed { + acc.panel_down = false; + } + } } } #[cfg(test)] diff --git a/crates/openlogi-hid/src/gesture/tests.rs b/crates/openlogi-hid/src/gesture/tests.rs index 035f2cda..619b450e 100644 --- a/crates/openlogi-hid/src/gesture/tests.rs +++ b/crates/openlogi-hid/src/gesture/tests.rs @@ -1,4 +1,5 @@ use super::*; +use crate::reprog_controls::{AnalyticsKeyEvent, ControlId}; fn press() -> RawControlEvent { RawControlEvent::DivertedButtons([reprog_controls::GESTURE_BUTTON_CID, 0, 0, 0]) @@ -8,6 +9,17 @@ fn release() -> RawControlEvent { RawControlEvent::DivertedButtons([0, 0, 0, 0]) } +/// One analytics batch with a single populated entry (the observed hardware +/// shape: one entry per message, four empty slots). +fn ring(cid: u16, event: u8) -> RawControlEvent { + let mut entries = [AnalyticsKeyEvent::default(); 5]; + entries[0] = AnalyticsKeyEvent { + cid: ControlId(cid), + event, + }; + RawControlEvent::AnalyticsKeys(entries) +} + #[test] fn quick_tap_is_a_click_even_while_the_cursor_moves() { let (tx, mut rx) = mpsc::unbounded_channel(); @@ -76,6 +88,101 @@ fn a_held_dpi_button_presses_once_on_the_rising_edge() { assert!(rx.try_recv().is_err(), "a held DPI button presses once"); } +#[test] +fn a_ring_tap_is_one_rising_edge_and_emits_nothing_until_bindable() { + let (tx, mut rx) = mpsc::unbounded_channel(); + let mut acc = CaptureAccum::default(); + + handle_reprog( + &mut acc, + ring(reprog_controls::ACTION_RING_CID, 0x01), + &[], + &tx, + ); + assert!(acc.panel_down, "a press entry arms the edge"); + // A companion CID firing while the pad is held is the same physical press. + handle_reprog(&mut acc, ring(0x0050, 0x01), &[], &tx); + assert!(acc.panel_down, "a companion press is not a second edge"); + handle_reprog( + &mut acc, + ring(reprog_controls::ACTION_RING_CID, 0x00), + &[], + &tx, + ); + assert!(!acc.panel_down, "a release entry clears the edge"); + assert!( + rx.try_recv().is_err(), + "analytics events carry no CapturedInput until the ring is a bindable control" + ); +} + +#[test] +fn a_ring_tap_re_arms_after_release() { + let (tx, mut rx) = mpsc::unbounded_channel(); + let mut acc = CaptureAccum::default(); + + handle_reprog(&mut acc, ring(0x0050, 0x01), &[], &tx); + handle_reprog(&mut acc, ring(0x0050, 0x00), &[], &tx); + assert!(!acc.panel_down); + // Taps arrive on either ring CID (both observed on hardware) — the edge + // logic must not care which one carried the previous tap. + handle_reprog( + &mut acc, + ring(reprog_controls::ACTION_RING_CID, 0x01), + &[], + &tx, + ); + assert!(acc.panel_down, "a release re-arms the rising edge"); + assert!(rx.try_recv().is_err()); +} + +#[test] +fn analytics_from_foreign_controls_do_not_touch_the_ring_edge() { + let (tx, mut rx) = mpsc::unbounded_channel(); + let mut acc = CaptureAccum::default(); + + handle_reprog( + &mut acc, + ring(reprog_controls::GESTURE_BUTTON_CID, 0x01), + &[], + &tx, + ); + assert!( + !acc.panel_down, + "a non-ring CID in an analytics batch is not the pad" + ); + assert!(rx.try_recv().is_err()); + assert!( + !acc.swipe.is_holding(), + "and it must not start a swipe either" + ); +} + +#[test] +fn a_batch_with_press_and_release_entries_arms_the_edge() { + // The wire format carries five entries per message; if a press and a + // (stale) release arrive together, the press wins — dropping a tap is + // worse than clearing the edge a message late. + let (tx, mut rx) = mpsc::unbounded_channel(); + let mut acc = CaptureAccum::default(); + + let mut entries = [AnalyticsKeyEvent::default(); 5]; + entries[0] = AnalyticsKeyEvent { + cid: ControlId(reprog_controls::ACTION_RING_CID), + event: 0x01, + }; + entries[1] = AnalyticsKeyEvent { + cid: ControlId(0x0050), + event: 0x00, + }; + handle_reprog(&mut acc, RawControlEvent::AnalyticsKeys(entries), &[], &tx); + assert!( + acc.panel_down, + "the press entry wins over the release entry" + ); + assert!(rx.try_recv().is_err()); +} + #[test] fn a_dpi_button_re_presses_after_a_release() { // Rising-edge detection must re-arm: press → release → press is two diff --git a/crates/openlogi-hid/src/hidden_features.rs b/crates/openlogi-hid/src/hidden_features.rs index fbedd066..b26176b9 100644 --- a/crates/openlogi-hid/src/hidden_features.rs +++ b/crates/openlogi-hid/src/hidden_features.rs @@ -22,17 +22,7 @@ use crate::reprog_controls::{FEATURE_ID as REPROG_FEATURE_ID, ReprogControlsV4}; use crate::route::{DeviceRoute, open_route_channel}; use crate::write::open_feature; -/// Control IDs the MX Master 4 Action Ring panel reports through, confirmed on -/// real hardware 2026-07-20: with analytics reporting enabled the panel emits -/// `0x1b04` `analyticsKeyEvents` (`event` `0x01` = press, `0x00` = release). -/// -/// **`0x01a0` is the Action Ring pad itself** — it is the only control in the -/// device's `0x1b04` table advertising `analytics-events`, and it carries the -/// tap. `0x0050`/`0x0051` are companion controls Options+ also arms (observed -/// firing on firm/held presses). `0x00d7` ("Virtual Gesture Button", -/// `force-raw-xy`) is NOT involved — diverting it only steals the sensor -/// stream and freezes the cursor. -pub const PANEL_ANALYTICS_CIDS: [u16; 3] = [0x01a0, 0x0050, 0x0051]; +pub use crate::reprog_controls::ACTION_RING_ANALYTICS_CIDS as PANEL_ANALYTICS_CIDS; /// Hard wall-clock budget for one whole diagnostic (open + calls). A cold /// BTLE link can swallow a request without ever answering, and the underlying diff --git a/crates/openlogi-hid/src/reprog_controls.rs b/crates/openlogi-hid/src/reprog_controls.rs index c25d4e3b..99dc75ba 100644 --- a/crates/openlogi-hid/src/reprog_controls.rs +++ b/crates/openlogi-hid/src/reprog_controls.rs @@ -37,12 +37,42 @@ pub const FEATURE_ID: u16 = 0x1b04; /// Control ID of the MX-line dedicated gesture button (`Mouse_Gesture_Button`, /// Logitech "App_Switch_Gesture"). /// -/// MX Master 4 also has a separate Haptic Sense Panel in the thumb area. That -/// panel is not this CID; it must be discovered from the device's `0x1b04` -/// control table and supported explicitly before OpenLogi treats it as a -/// bindable/capturable input. +/// The MX Master 4's force-sensitive thumb pad (the Action Ring) is a separate +/// control — see [`ACTION_RING_CID`]. pub const GESTURE_BUTTON_CID: u16 = 0x00c3; +/// Control ID `0x00d7` ("Virtual Gesture Button") on the MX Master 4 — +/// historically mistaken for the Action Ring pad's event path. **Do not divert +/// it.** +/// +/// It is the only control on the device advertising `force-raw-xy`, which made +/// it look like the force pad. On real hardware (`wpid=b042`), diverting it +/// with `force_raw_xy` hijacks the mouse's entire sensor stream and freezes +/// the cursor, and a plain divert + raw-XY produces no pad events at all. The +/// pad's real event path is `analyticsKeyEvents` on +/// [`ACTION_RING_ANALYTICS_CIDS`]. Kept documented so the dead end is not +/// rediscovered. +pub const HAPTIC_PANEL_CID: u16 = 0x00d7; + +/// Control ID of the MX Master 4 Action Ring pad — the force-sensitive ridged +/// thumb pad (eight dots in a ring) that drives the Actions Ring in Options+. +/// +/// The only control in the device's `0x1b04` table advertising +/// `analytics-events`; its presence there is how a capture session detects the +/// pad. The pad is dormant until its `0x19c0` force threshold is written and +/// silent until `analyticsKeyEvents` reporting is enabled — see +/// `gesture::arm_controls`. +pub const ACTION_RING_CID: u16 = 0x01a0; + +/// Control IDs the Action Ring pad reports through once armed (captures in +/// `docs/mx-master-4-panel-captures/`): each tap is an `analyticsKeyEvents` +/// press/release pair (`event` `0x01`/`0x00`) on **one** of these — +/// [`ACTION_RING_CID`] or `0x0050`, varying with the press. `0x0051` was never +/// observed firing, but Options+ arms it; armed for parity. Confirmed on real +/// hardware 2026-07-20 from a power-cycled mouse with no Logitech software +/// running. +pub const ACTION_RING_ANALYTICS_CIDS: [u16; 3] = [ACTION_RING_CID, 0x0050, 0x0051]; + /// Control IDs of the "DPI / ModeShift" button family. Whichever a device /// exposes (and can divert) is captured and mapped to /// [`ButtonId::DpiToggle`](openlogi_core::binding::ButtonId::DpiToggle): the MX diff --git a/crates/openlogi-hid/src/reprog_controls/event.rs b/crates/openlogi-hid/src/reprog_controls/event.rs index ed0e3970..376efd62 100644 --- a/crates/openlogi-hid/src/reprog_controls/event.rs +++ b/crates/openlogi-hid/src/reprog_controls/event.rs @@ -1,6 +1,6 @@ use hidpp::protocol::v20; -use super::{ControlId, ReprogControlsEvent, decode_full_event}; +use super::{AnalyticsKeyEvent, ControlId, ReprogControlsEvent, decode_full_event}; /// An unsolicited `0x1b04` event decoded for OpenLogi's gesture pipeline. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -17,6 +17,11 @@ pub enum RawControlEvent { /// Vertical delta (`+` = down, in the device's raw units). dy: i16, }, + /// `analyticsKeyEvents`: up to five `(cid, event)` entries from controls + /// armed for analytics reporting — the MX Master 4 Action Ring pad's event + /// path (`event` `0x01` = press, `0x00` = release). A zero CID is an empty + /// slot. + AnalyticsKeys([AnalyticsKeyEvent; 5]), } impl RawControlEvent { @@ -36,8 +41,8 @@ impl TryFrom for RawControlEvent { Ok(Self::DivertedButtons(cids.map(ControlId::into))) } ReprogControlsEvent::DivertedRawMouseXy { dx, dy } => Ok(Self::RawXy { dx, dy }), - ReprogControlsEvent::AnalyticsKeyEvents(_) - | ReprogControlsEvent::DivertedRawWheel { .. } => Err(event), + ReprogControlsEvent::AnalyticsKeyEvents(entries) => Ok(Self::AnalyticsKeys(entries)), + ReprogControlsEvent::DivertedRawWheel { .. } => Err(event), } } } @@ -106,13 +111,49 @@ mod tests { ); } + #[test] + fn decodes_analytics_press_and_release_entries() { + let mut p = [0u8; 16]; + // Entry 0: Action Ring pad press; entry 1: companion CID release. + p[0..2].copy_from_slice(&crate::reprog_controls::ACTION_RING_CID.to_be_bytes()); + p[2] = 0x01; + p[3..5].copy_from_slice(&0x0050u16.to_be_bytes()); + p[5] = 0x00; + let decoded = decode_event(&event(2, 0, p), 2, 7); + let Some(RawControlEvent::AnalyticsKeys(entries)) = decoded else { + panic!("analytics key events should decode, got {decoded:?}"); + }; + assert_eq!( + entries[0], + AnalyticsKeyEvent { + cid: ControlId(crate::reprog_controls::ACTION_RING_CID), + event: 0x01, + }, + "press entries carry event 0x01" + ); + assert_eq!( + entries[1], + AnalyticsKeyEvent { + cid: ControlId(0x0050), + event: 0x00, + }, + "release entries carry event 0x00" + ); + assert_eq!( + entries[2], + AnalyticsKeyEvent::default(), + "unused slots decode as zero CIDs" + ); + } + #[test] fn ignores_responses_and_foreign_messages() { let p = [0u8; 16]; // software_id != 0 marks a request response, not an event. assert_eq!(decode_event(&event(0, 5, p), 2, 7), None); - // Right device + feature, but an event outside the gesture-control path. - assert_eq!(decode_event(&event(2, 0, p), 2, 7), None); + // Right device + feature, but an event outside the gesture-control + // path (function 4 = raw wheel, which OpenLogi discards). + assert_eq!(decode_event(&event(4, 0, p), 2, 7), None); // Wrong feature index. assert_eq!(decode_event(&event(0, 0, p), 2, 9), None); } From 5d60f4717ff78e9403744b8802fe942cc8603f0b Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 05:53:43 -0700 Subject: [PATCH 08/42] feat(core): make the Action Ring a bindable control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ButtonId gains ActionRing (appended; ButtonId::ALL is now 11), and Binding gains a third untagged arm: Ring(BTreeMap) — eight compass-named sectors, clockwise from the top. The names are deliberately disjoint from every Action and GestureDirection variant, which is what keeps untagged TOML routing unambiguous; guard tests cover ring-keyed and sparse tables. ActionRing defaults to a full Ring map (opposing copy/paste + undo/redo pairs on the cardinals); its single-action projection is Action::None, so a ring-bound tap never double-fires an action in the agent. A user can instead bind the pad to a Single action, which dispatches through the existing HID++ button path. The capture session now emits ButtonPressed(ActionRing) on the pad's rising edge (verified on hardware: every tap = exactly one press event, including 210 ms double-taps). No IPC change: CapturedInput is consumed in-process by the agent-core gesture watcher and never crosses the tarpc wire, and config crosses via the file, not the socket. The GUI maps the MX Master 4 metadata slot ASSIGNMENT_NAME_SHOW_RADIAL_MENU to the ActionRing hotspot — Logi's own marker for the pad (its slotId suffix _c416 is CID 0x01a0, the control the session arms). "Action Ring" is inserted at the same position in all 20 locale files (parity test green). docs/CONFIGURATION.md is refreshed for the live v3 route-keyed schema and documents the ring table. --- crates/openlogi-core/src/binding.rs | 244 +++++++++++++++++- crates/openlogi-gui/locales/da.yml | 1 + crates/openlogi-gui/locales/de.yml | 1 + crates/openlogi-gui/locales/el.yml | 1 + crates/openlogi-gui/locales/en.yml | 1 + crates/openlogi-gui/locales/es.yml | 1 + crates/openlogi-gui/locales/fi.yml | 1 + crates/openlogi-gui/locales/fr.yml | 1 + crates/openlogi-gui/locales/it.yml | 1 + crates/openlogi-gui/locales/ja.yml | 1 + crates/openlogi-gui/locales/ko.yml | 1 + crates/openlogi-gui/locales/nb.yml | 1 + crates/openlogi-gui/locales/nl.yml | 1 + crates/openlogi-gui/locales/pl.yml | 1 + crates/openlogi-gui/locales/pt-BR.yml | 1 + crates/openlogi-gui/locales/pt-PT.yml | 1 + crates/openlogi-gui/locales/ru.yml | 1 + crates/openlogi-gui/locales/sv.yml | 1 + crates/openlogi-gui/locales/zh-CN.yml | 1 + crates/openlogi-gui/locales/zh-HK.yml | 1 + crates/openlogi-gui/locales/zh-TW.yml | 1 + .../openlogi-gui/src/mouse_model/geometry.rs | 4 + crates/openlogi-hid/src/gesture.rs | 4 +- crates/openlogi-hid/src/gesture/tests.rs | 23 +- docs/CONFIGURATION.md | 48 +++- 25 files changed, 311 insertions(+), 32 deletions(-) diff --git a/crates/openlogi-core/src/binding.rs b/crates/openlogi-core/src/binding.rs index 97ddea09..c4a01eac 100644 --- a/crates/openlogi-core/src/binding.rs +++ b/crates/openlogi-core/src/binding.rs @@ -51,13 +51,18 @@ pub enum ButtonId { /// The HID++ gesture button on MX-line devices. The press itself /// fires the bound action; swipe directions are P1.5 territory. GestureButton, + /// The MX Master 4 Action Ring pad — the force-sensitive ridged thumb pad, + /// captured over HID++ analytics events. With the default [`Binding::Ring`] + /// a tap opens the on-screen ring; bound to a [`Binding::Single`] action, a + /// tap fires that action directly instead. + ActionRing, } impl ButtonId { /// Every rebindable button in declaration (physical front-to-side) order — /// the iteration source for default-binding seeding and the popover /// trigger list. - pub const ALL: [ButtonId; 10] = [ + pub const ALL: [ButtonId; 11] = [ ButtonId::LeftClick, ButtonId::RightClick, ButtonId::MiddleClick, @@ -68,6 +73,7 @@ impl ButtonId { ButtonId::ThumbwheelScrollUp, ButtonId::ThumbwheelScrollDown, ButtonId::GestureButton, + ButtonId::ActionRing, ]; /// Whether this button is one the OS hook (macOS `CGEventTap` / Linux evdev) @@ -99,6 +105,7 @@ impl ButtonId { ButtonId::ThumbwheelScrollUp => "Thumb Wheel Up", ButtonId::ThumbwheelScrollDown => "Thumb Wheel Down", ButtonId::GestureButton => "Gesture Button", + ButtonId::ActionRing => "Action Ring", } } } @@ -174,6 +181,86 @@ impl fmt::Display for GestureDirection { } } +/// One of the eight sectors of the on-screen Action Ring, clockwise from the +/// top. The names are compass points — deliberately disjoint from every +/// [`Action`] variant name *and* every [`GestureDirection`] name, because the +/// untagged [`Binding`] routing distinguishes its arms purely by key names +/// (see the [`Binding`] serialization notes). +/// +/// Variant identifiers are TOML-stable: renames are migration events. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +pub enum RingSlot { + /// Straight up (12 o'clock). + North, + /// Up-right (1:30). + NorthEast, + /// Right (3 o'clock). + East, + /// Down-right (4:30). + SouthEast, + /// Straight down (6 o'clock). + South, + /// Down-left (7:30). + SouthWest, + /// Left (9 o'clock). + West, + /// Up-left (10:30). + NorthWest, +} + +impl RingSlot { + /// All eight slots, clockwise from the top — the iteration source for + /// default seeding and the overlay's sector layout. + pub const ALL: [RingSlot; 8] = [ + RingSlot::North, + RingSlot::NorthEast, + RingSlot::East, + RingSlot::SouthEast, + RingSlot::South, + RingSlot::SouthWest, + RingSlot::West, + RingSlot::NorthWest, + ]; + + /// Human-readable label for tooltips and the binding UI. + #[must_use] + pub fn label(self) -> &'static str { + match self { + RingSlot::North => "Top", + RingSlot::NorthEast => "Top Right", + RingSlot::East => "Right", + RingSlot::SouthEast => "Bottom Right", + RingSlot::South => "Bottom", + RingSlot::SouthWest => "Bottom Left", + RingSlot::West => "Left", + RingSlot::NorthWest => "Top Left", + } + } + + /// The slot's centre angle in degrees, measured clockwise from straight + /// up — the overlay's sector geometry (each sector spans ±22.5° around + /// this). + #[must_use] + pub fn angle_degrees(self) -> f32 { + match self { + RingSlot::North => 0.0, + RingSlot::NorthEast => 45.0, + RingSlot::East => 90.0, + RingSlot::SouthEast => 135.0, + RingSlot::South => 180.0, + RingSlot::SouthWest => 225.0, + RingSlot::West => 270.0, + RingSlot::NorthWest => 315.0, + } + } +} + +impl fmt::Display for RingSlot { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.label()) + } +} + /// Grouping for popover section headers. /// /// Used by [`Action::category`] and rendered as a small muted label above @@ -466,15 +553,18 @@ impl KeyCombo { /// table keyed by [`GestureDirection`] names (`Up`/`Down`/`Left`/`Right`/ /// `Click`). /// -/// The two arms are disambiguated by the **zero overlap** between [`Action`] -/// variant names and [`GestureDirection`] variant names — untagged tries -/// `Single(Action)` first, and a table keyed by `Up` etc. cannot parse as an -/// externally-tagged `Action`, so it falls through to `Gesture`. A payload -/// action like `{ SetDpiPreset = 2 }` is a valid externally-tagged `Action`, so -/// it stays `Single` and never reaches the `Gesture` arm. This invariant is the -/// entire safety basis for untagged routing; the `binding_untagged_*` tests -/// guard it (a future `Action` named `Up`/`Down`/`Left`/`Right`/`Click` would -/// silently mis-route, and those tests would fail). +/// The arms are disambiguated by the **zero overlap** between [`Action`] +/// variant names, [`GestureDirection`] variant names, and [`RingSlot`] variant +/// names — untagged tries `Single(Action)` first; a table keyed by `Up` etc. +/// cannot parse as an externally-tagged `Action`, so it falls through to +/// `Gesture`; a table keyed by compass names (`North` etc.) fails both and +/// lands on `Ring`. A payload action like `{ SetDpiPreset = 2 }` is a valid +/// externally-tagged `Action`, so it stays `Single` and never reaches the map +/// arms. This invariant is the entire safety basis for untagged routing; the +/// `binding_*_routes_*` tests guard it (a future `Action` named after a +/// direction or compass point — or a [`RingSlot`] named like a +/// [`GestureDirection`] — would silently mis-route, and those tests would +/// fail). #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum Binding { @@ -484,6 +574,12 @@ pub enum Binding { /// committed swipe direction, with [`GestureDirection::Click`] holding the /// plain-click (no-swipe) action. Gesture(BTreeMap), + /// Per-sector sub-bindings for the Action Ring pad: a tap opens the + /// on-screen ring and the second tap fires the hovered sector's action. + /// Appended after [`Gesture`](Binding::Gesture) — untagged tries arms in + /// order, so an **empty** table keeps routing to `Gesture` as it always + /// has (ring maps are seeded with all eight slots and are never empty). + Ring(BTreeMap), } impl Binding { @@ -493,6 +589,10 @@ impl Binding { /// when a gesture binding has no explicit `Click`. /// /// Lets the click-dispatch path stay binding-shape-agnostic. + /// + /// A [`Ring`](Binding::Ring) binding has no click action: the tap's job is + /// opening the on-screen ring (driven off the binding shape, not an + /// [`Action`]), so the single-action projection is [`Action::None`]. #[must_use] pub fn click_action(&self) -> Action { match self { @@ -501,19 +601,30 @@ impl Binding { .get(&GestureDirection::Click) .cloned() .unwrap_or(Action::None), + Binding::Ring(_) => Action::None, } } /// The action bound to `direction`, if this is a gesture binding. - /// [`Single`](Binding::Single) has no directions and returns `None`. + /// The other arms have no directions and return `None`. #[must_use] pub fn direction_action(&self, direction: GestureDirection) -> Option<&Action> { match self { - Binding::Single(_) => None, + Binding::Single(_) | Binding::Ring(_) => None, Binding::Gesture(map) => map.get(&direction), } } + /// The action bound to `slot`, if this is a ring binding. The other arms + /// have no slots and return `None`. + #[must_use] + pub fn ring_action(&self, slot: RingSlot) -> Option<&Action> { + match self { + Binding::Single(_) | Binding::Gesture(_) => None, + Binding::Ring(map) => map.get(&slot), + } + } + /// Whether this binding drives raw-XY swipe capture (the /// [`Gesture`](Binding::Gesture) arm). #[must_use] @@ -521,6 +632,13 @@ impl Binding { matches!(self, Binding::Gesture(_)) } + /// Whether a tap on this binding's button opens the on-screen ring (the + /// [`Ring`](Binding::Ring) arm). + #[must_use] + pub fn is_ring(&self) -> bool { + matches!(self, Binding::Ring(_)) + } + /// Promote a [`Single`](Binding::Single) binding in place to a /// [`Gesture`](Binding::Gesture), keeping its action as the /// [`GestureDirection::Click`] entry and leaving the swipe arms unbound. @@ -547,6 +665,19 @@ impl Binding { } } } + + /// Fill any unbound slots of a [`Ring`](Binding::Ring) binding with their + /// canonical [`default_ring_binding`], so the overlay always renders a full + /// eight-sector ring even from a hand-edited sparse config. A no-op on the + /// other arms and on slots already bound. + pub fn fill_ring_defaults(&mut self) { + if let Binding::Ring(map) = self { + for slot in RingSlot::ALL { + map.entry(slot) + .or_insert_with(|| default_ring_binding(slot)); + } + } + } } impl From for Binding { @@ -758,6 +889,10 @@ pub fn default_binding(button: ButtonId) -> Action { ButtonId::ThumbwheelScrollUp => Action::HorizontalScrollRight, ButtonId::ThumbwheelScrollDown => Action::HorizontalScrollLeft, ButtonId::GestureButton => Action::MissionControl, + // Vestigial like GestureButton's entry: the Action Ring defaults to + // Binding::Ring (see default_binding_for), whose tap opens the ring + // rather than firing a single action. + ButtonId::ActionRing => Action::None, } } @@ -775,6 +910,24 @@ pub fn default_gesture_binding(direction: GestureDirection) -> Action { } } +/// Per-sector defaults for the Action Ring: opposing pairs (copy/paste, +/// undo/redo) on the cardinal slots, with capture, media, and window controls +/// on the diagonals — a productivity set in the spirit of the Options+ ring, +/// shown on first run until the user rebinds the slots. +#[must_use] +pub fn default_ring_binding(slot: RingSlot) -> Action { + match slot { + RingSlot::North => Action::Copy, + RingSlot::NorthEast => Action::CaptureRegion, + RingSlot::East => Action::Redo, + RingSlot::SouthEast => Action::PlayPause, + RingSlot::South => Action::Paste, + RingSlot::SouthWest => Action::ShowDesktop, + RingSlot::West => Action::Undo, + RingSlot::NorthWest => Action::MissionControl, + } +} + /// The canonical default [`Binding`] for a fresh button in the merged model. /// /// [`ButtonId::GestureButton`] defaults to [`Binding::Gesture`] populated from @@ -797,6 +950,12 @@ pub fn default_binding_for(button: ButtonId) -> Binding { .map(|d| (d, default_gesture_binding(d))) .collect(), ), + ButtonId::ActionRing => Binding::Ring( + RingSlot::ALL + .into_iter() + .map(|s| (s, default_ring_binding(s))) + .collect(), + ), other => Binding::Single(default_binding(other)), } } @@ -919,6 +1078,67 @@ mod tests { } } + #[test] + fn binding_ring_roundtrips() { + let map: BTreeMap = RingSlot::ALL + .into_iter() + .map(|s| (s, default_ring_binding(s))) + .collect(); + let mut bindings = BTreeMap::new(); + bindings.insert(ButtonId::ActionRing, Binding::Ring(map.clone())); + let back = binding_roundtrip(bindings); + assert_eq!(back[&ButtonId::ActionRing], Binding::Ring(map)); + } + + /// The ring-side untagged-routing guard, including the sparse case: a table + /// keyed by ANY single [`RingSlot`] name must land on [`Binding::Ring`] — + /// never parse as a valid externally-tagged [`Action`] (which would route + /// `Single`) and never satisfy the [`GestureDirection`]-keyed map (which + /// would route `Gesture`). A collision in either namespace fails here. + #[test] + fn binding_ring_slot_keyed_table_routes_to_ring() { + for slot in RingSlot::ALL { + let toml = format!("bindings.ActionRing.{slot:?} = \"None\""); + let parsed = toml::from_str::(&toml).expect("deserialize"); + assert!( + matches!(parsed.bindings[&ButtonId::ActionRing], Binding::Ring(_)), + "a {slot:?}-keyed table must route to Ring, not Single/Gesture" + ); + } + } + + #[test] + fn ring_default_covers_every_slot_and_projects_no_click_action() { + let binding = default_binding_for(ButtonId::ActionRing); + let Binding::Ring(map) = &binding else { + panic!("ActionRing must default to a Ring binding, got {binding:?}"); + }; + assert_eq!(map.len(), RingSlot::ALL.len(), "all eight slots seeded"); + // The pad's tap opens the ring — the single-action projection the + // agent-side dispatch reads must be a no-op, or a tap would both open + // the ring and fire an action. + assert_eq!(binding.click_action(), Action::None); + assert!(binding.is_ring()); + assert_eq!( + binding.ring_action(RingSlot::North), + Some(&default_ring_binding(RingSlot::North)) + ); + } + + #[test] + fn fill_ring_defaults_completes_a_sparse_map_preserving_user_choices() { + let mut sparse = BTreeMap::new(); + sparse.insert(RingSlot::East, Action::LockScreen); + let mut binding = Binding::Ring(sparse); + binding.fill_ring_defaults(); + let Binding::Ring(map) = &binding else { + panic!("fill_ring_defaults must not change the arm"); + }; + assert_eq!(map.len(), RingSlot::ALL.len()); + assert_eq!(map[&RingSlot::East], Action::LockScreen, "user choice kept"); + assert_eq!(map[&RingSlot::North], default_ring_binding(RingSlot::North)); + } + /// The collision case: a payload [`Action`] also serializes as a single-key /// table, but untagged must keep it [`Binding::Single`] (it parses as a valid /// externally-tagged `Action` before the `Gesture` arm is tried). diff --git a/crates/openlogi-gui/locales/da.yml b/crates/openlogi-gui/locales/da.yml index cf5983fb..3e1f54e8 100644 --- a/crates/openlogi-gui/locales/da.yml +++ b/crates/openlogi-gui/locales/da.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "DPI-skift" "Thumb Wheel": "Tommelfingerhjul" "Gesture Button": "Bevægelsesknap" +"Action Ring": "Handlingsring" "Up": "Op" "Down": "Ned" "Left": "Venstre" diff --git a/crates/openlogi-gui/locales/de.yml b/crates/openlogi-gui/locales/de.yml index 203108dd..689a4653 100644 --- a/crates/openlogi-gui/locales/de.yml +++ b/crates/openlogi-gui/locales/de.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "DPI-Umschaltung" "Thumb Wheel": "Daumenrad" "Gesture Button": "Gestentaste" +"Action Ring": "Aktionsring" "Up": "Oben" "Down": "Unten" "Left": "Links" diff --git a/crates/openlogi-gui/locales/el.yml b/crates/openlogi-gui/locales/el.yml index 80d7156d..7c60e3a9 100644 --- a/crates/openlogi-gui/locales/el.yml +++ b/crates/openlogi-gui/locales/el.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "Εναλλαγή DPI" "Thumb Wheel": "Πλαϊνός τροχός" "Gesture Button": "Κουμπί χειρονομιών" +"Action Ring": "Δακτύλιος ενεργειών" "Up": "Πάνω" "Down": "Κάτω" "Left": "Αριστερά" diff --git a/crates/openlogi-gui/locales/en.yml b/crates/openlogi-gui/locales/en.yml index e574c8c5..f4ac457c 100644 --- a/crates/openlogi-gui/locales/en.yml +++ b/crates/openlogi-gui/locales/en.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "DPI Toggle" "Thumb Wheel": "Thumb Wheel" "Gesture Button": "Gesture Button" +"Action Ring": "Action Ring" "Up": "Up" "Down": "Down" "Left": "Left" diff --git a/crates/openlogi-gui/locales/es.yml b/crates/openlogi-gui/locales/es.yml index 37f0c3a2..68229912 100644 --- a/crates/openlogi-gui/locales/es.yml +++ b/crates/openlogi-gui/locales/es.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "Cambiar DPI" "Thumb Wheel": "Rueda lateral" "Gesture Button": "Botón de gestos" +"Action Ring": "Anillo de acciones" "Up": "Arriba" "Down": "Abajo" "Left": "Izquierda" diff --git a/crates/openlogi-gui/locales/fi.yml b/crates/openlogi-gui/locales/fi.yml index b14640ce..3ddd3791 100644 --- a/crates/openlogi-gui/locales/fi.yml +++ b/crates/openlogi-gui/locales/fi.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "DPI-vaihto" "Thumb Wheel": "Peukalorulla" "Gesture Button": "Eletoiminto" +"Action Ring": "Toimintorengas" "Up": "Ylös" "Down": "Alas" "Left": "Vasen" diff --git a/crates/openlogi-gui/locales/fr.yml b/crates/openlogi-gui/locales/fr.yml index cdb201d9..492e8ee3 100644 --- a/crates/openlogi-gui/locales/fr.yml +++ b/crates/openlogi-gui/locales/fr.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "Bascule DPI" "Thumb Wheel": "Molette latérale" "Gesture Button": "Bouton de gestes" +"Action Ring": "Anneau d'actions" "Up": "Haut" "Down": "Bas" "Left": "Gauche" diff --git a/crates/openlogi-gui/locales/it.yml b/crates/openlogi-gui/locales/it.yml index f35d58a8..5e4e05a4 100644 --- a/crates/openlogi-gui/locales/it.yml +++ b/crates/openlogi-gui/locales/it.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "Cambia DPI" "Thumb Wheel": "Volante da pollice" "Gesture Button": "Pulsante gesture" +"Action Ring": "Anello delle azioni" "Up": "Su" "Down": "Giù" "Left": "Sinistra" diff --git a/crates/openlogi-gui/locales/ja.yml b/crates/openlogi-gui/locales/ja.yml index ba765827..6dd32a1d 100644 --- a/crates/openlogi-gui/locales/ja.yml +++ b/crates/openlogi-gui/locales/ja.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "DPI 切り替え" "Thumb Wheel": "サムホイール" "Gesture Button": "ジェスチャーボタン" +"Action Ring": "アクションリング" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/ko.yml b/crates/openlogi-gui/locales/ko.yml index 1f6cfa17..89bf4265 100644 --- a/crates/openlogi-gui/locales/ko.yml +++ b/crates/openlogi-gui/locales/ko.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "DPI 전환" "Thumb Wheel": "썸휠" "Gesture Button": "제스처 버튼" +"Action Ring": "액션 링" "Up": "위" "Down": "아래" "Left": "왼쪽" diff --git a/crates/openlogi-gui/locales/nb.yml b/crates/openlogi-gui/locales/nb.yml index 3c74b79e..123a9a7e 100644 --- a/crates/openlogi-gui/locales/nb.yml +++ b/crates/openlogi-gui/locales/nb.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "DPI-veksling" "Thumb Wheel": "Tommelhjul" "Gesture Button": "Bevegelsesknapp" +"Action Ring": "Handlingsring" "Up": "Opp" "Down": "Ned" "Left": "Venstre" diff --git a/crates/openlogi-gui/locales/nl.yml b/crates/openlogi-gui/locales/nl.yml index f9e485dc..b915c0bb 100644 --- a/crates/openlogi-gui/locales/nl.yml +++ b/crates/openlogi-gui/locales/nl.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "DPI wisselen" "Thumb Wheel": "Duimwiel" "Gesture Button": "Gebarenknop" +"Action Ring": "Actiering" "Up": "Omhoog" "Down": "Omlaag" "Left": "Links" diff --git a/crates/openlogi-gui/locales/pl.yml b/crates/openlogi-gui/locales/pl.yml index e72d22b1..03c7666c 100644 --- a/crates/openlogi-gui/locales/pl.yml +++ b/crates/openlogi-gui/locales/pl.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "Przełącznik DPI" "Thumb Wheel": "Rolka kciukowa" "Gesture Button": "Przycisk gestów" +"Action Ring": "Pierścień akcji" "Up": "W górę" "Down": "W dół" "Left": "W lewo" diff --git a/crates/openlogi-gui/locales/pt-BR.yml b/crates/openlogi-gui/locales/pt-BR.yml index a298fe26..db26c0f4 100644 --- a/crates/openlogi-gui/locales/pt-BR.yml +++ b/crates/openlogi-gui/locales/pt-BR.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "Alternar DPI" "Thumb Wheel": "Roda do Polegar" "Gesture Button": "Botão de Gesto" +"Action Ring": "Anel de Ações" "Up": "Cima" "Down": "Baixo" "Left": "Esquerda" diff --git a/crates/openlogi-gui/locales/pt-PT.yml b/crates/openlogi-gui/locales/pt-PT.yml index dfe9346b..ff627501 100644 --- a/crates/openlogi-gui/locales/pt-PT.yml +++ b/crates/openlogi-gui/locales/pt-PT.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "Alternar DPI" "Thumb Wheel": "Roda de polegar" "Gesture Button": "Botão de gesto" +"Action Ring": "Anel de ações" "Up": "Cima" "Down": "Baixo" "Left": "Esquerda" diff --git a/crates/openlogi-gui/locales/ru.yml b/crates/openlogi-gui/locales/ru.yml index c50a9281..b5f1cd94 100644 --- a/crates/openlogi-gui/locales/ru.yml +++ b/crates/openlogi-gui/locales/ru.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "Переключение DPI" "Thumb Wheel": "Колесо под большой палец" "Gesture Button": "Кнопка жестов" +"Action Ring": "Кольцо действий" "Up": "Вверх" "Down": "Вниз" "Left": "Влево" diff --git a/crates/openlogi-gui/locales/sv.yml b/crates/openlogi-gui/locales/sv.yml index df82147f..cbf34599 100644 --- a/crates/openlogi-gui/locales/sv.yml +++ b/crates/openlogi-gui/locales/sv.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "DPI-växling" "Thumb Wheel": "Tumhjul" "Gesture Button": "Gestknapp" +"Action Ring": "Åtgärdsring" "Up": "Upp" "Down": "Ned" "Left": "Vänster" diff --git a/crates/openlogi-gui/locales/zh-CN.yml b/crates/openlogi-gui/locales/zh-CN.yml index 318d29eb..340f34e9 100644 --- a/crates/openlogi-gui/locales/zh-CN.yml +++ b/crates/openlogi-gui/locales/zh-CN.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "灵敏度切换" "Thumb Wheel": "拇指滚轮" "Gesture Button": "手势按钮" +"Action Ring": "操作环" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/zh-HK.yml b/crates/openlogi-gui/locales/zh-HK.yml index 65a363c5..c0383341 100644 --- a/crates/openlogi-gui/locales/zh-HK.yml +++ b/crates/openlogi-gui/locales/zh-HK.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "靈敏度切換" "Thumb Wheel": "拇指滾輪" "Gesture Button": "手勢按鈕" +"Action Ring": "操作環" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/zh-TW.yml b/crates/openlogi-gui/locales/zh-TW.yml index 7505b25a..e20d4d2a 100644 --- a/crates/openlogi-gui/locales/zh-TW.yml +++ b/crates/openlogi-gui/locales/zh-TW.yml @@ -156,6 +156,7 @@ _version: 1 "DPI Toggle": "靈敏度切換" "Thumb Wheel": "拇指滾輪" "Gesture Button": "手勢按鈕" +"Action Ring": "動作環" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/src/mouse_model/geometry.rs b/crates/openlogi-gui/src/mouse_model/geometry.rs index 7ad9b4ed..642c7da7 100644 --- a/crates/openlogi-gui/src/mouse_model/geometry.rs +++ b/crates/openlogi-gui/src/mouse_model/geometry.rs @@ -217,6 +217,10 @@ fn map_slot_name(name: &str) -> Option { "SLOT_NAME_MODESHIFT_BUTTON" => Some(ButtonId::DpiToggle), "SLOT_NAME_THUMBWHEEL" => Some(ButtonId::Thumbwheel), "SLOT_NAME_GESTURE_BUTTON" => Some(ButtonId::GestureButton), + // The MX Master 4 Action Ring pad. Logi's own marker for it: the + // slotId suffix is `_c416` = CID 0x01a0, the control the capture + // session arms for analytics events. + "ASSIGNMENT_NAME_SHOW_RADIAL_MENU" => Some(ButtonId::ActionRing), _ => None, } } diff --git a/crates/openlogi-hid/src/gesture.rs b/crates/openlogi-hid/src/gesture.rs index a968bea3..d8700460 100644 --- a/crates/openlogi-hid/src/gesture.rs +++ b/crates/openlogi-hid/src/gesture.rs @@ -523,10 +523,8 @@ fn handle_reprog( } if pressed && !acc.panel_down { acc.panel_down = true; - // Log-only until the Action Ring is a bindable control: turning - // this into a CapturedInput needs a ButtonId variant, which - // crosses the IPC wire (append-only, protocol version bump). debug!("action ring pressed"); + let _ = sink.send(CapturedInput::ButtonPressed(ButtonId::ActionRing)); } else if released && !pressed { acc.panel_down = false; } diff --git a/crates/openlogi-hid/src/gesture/tests.rs b/crates/openlogi-hid/src/gesture/tests.rs index 619b450e..a70f1ddd 100644 --- a/crates/openlogi-hid/src/gesture/tests.rs +++ b/crates/openlogi-hid/src/gesture/tests.rs @@ -89,7 +89,7 @@ fn a_held_dpi_button_presses_once_on_the_rising_edge() { } #[test] -fn a_ring_tap_is_one_rising_edge_and_emits_nothing_until_bindable() { +fn a_ring_tap_is_one_rising_edge_and_one_press() { let (tx, mut rx) = mpsc::unbounded_channel(); let mut acc = CaptureAccum::default(); @@ -110,9 +110,13 @@ fn a_ring_tap_is_one_rising_edge_and_emits_nothing_until_bindable() { &tx, ); assert!(!acc.panel_down, "a release entry clears the edge"); + assert_eq!( + rx.try_recv(), + Ok(CapturedInput::ButtonPressed(ButtonId::ActionRing)) + ); assert!( rx.try_recv().is_err(), - "analytics events carry no CapturedInput until the ring is a bindable control" + "one physical tap must emit exactly one press" ); } @@ -133,6 +137,15 @@ fn a_ring_tap_re_arms_after_release() { &tx, ); assert!(acc.panel_down, "a release re-arms the rising edge"); + assert_eq!( + rx.try_recv(), + Ok(CapturedInput::ButtonPressed(ButtonId::ActionRing)) + ); + assert_eq!( + rx.try_recv(), + Ok(CapturedInput::ButtonPressed(ButtonId::ActionRing)), + "a release re-arms: the second tap presses again" + ); assert!(rx.try_recv().is_err()); } @@ -180,7 +193,11 @@ fn a_batch_with_press_and_release_entries_arms_the_edge() { acc.panel_down, "the press entry wins over the release entry" ); - assert!(rx.try_recv().is_err()); + assert_eq!( + rx.try_recv(), + Ok(CapturedInput::ButtonPressed(ButtonId::ActionRing)) + ); + assert!(rx.try_recv().is_err(), "exactly one press for the batch"); } #[test] diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 555d169d..b30e82a9 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -11,15 +11,21 @@ Config is a TOML file, read on startup and written atomically on change: Most settings below are managed by the GUI (Settings window, action picker, DPI / SmartShift / lighting panels), but the file stays hand-editable; per-application overlays and custom shortcuts are currently authored there. -OpenLogi reloads it on startup. Older `schema_version = 1` files (separate -`button_bindings` / `gesture_bindings` tables) are migrated to the unified -`bindings` map on first load. +OpenLogi reloads it on startup. Older files migrate on first load: +`schema_version = 1` (separate `button_bindings` / `gesture_bindings` tables) +into the unified `bindings` map, and pre-v3 model-keyed device tables into +route-keyed ones. -Per-device settings are keyed by the HID++ identifier (e.g. `2b042` for an -MX Master 4): +The current schema is `schema_version = 3`. Per-device settings are keyed by +the device's **route identity** — `receiver::slot:` for a device +behind a Bolt/Unifying receiver, `direct:::unit:` for USB / +Bluetooth-direct — so two identical models never share settings. Each device +table also carries an `identity` block (model info and capabilities) that +OpenLogi maintains automatically; leave it alone when hand-editing. -- `bindings` — one entry per rebindable button: either a single action, or a - per-direction table for the gesture button. +- `bindings` — one entry per rebindable button: a single action, a + per-direction table for the gesture button, or a per-sector table for the + MX Master 4's Action Ring pad. - `per_app_bindings` — overlays keyed by application id (bundle id such as `com.microsoft.VSCode` on macOS, `WM_CLASS` on Linux/X11, or a lower-cased executable path on Windows) that take precedence while that app is @@ -42,8 +48,8 @@ the system locale); `thumbwheel_sensitivity` (default `14`); and the presentation settings. The theme and radius overrides are absent by default. ```toml -schema_version = 2 -selected_device = "2b042" +schema_version = 3 +selected_device = "receiver:97b76948a846c55a:slot:2" [app_settings] launch_at_login = true @@ -59,26 +65,40 @@ appearance = "system" # theme_dark = "OpenLogi Dark" # ui_radius = 6 -[devices.2b042] +[devices."receiver:97b76948a846c55a:slot:2"] dpi_presets = [800, 1600, 3200] -[devices.2b042.bindings] +[devices."receiver:97b76948a846c55a:slot:2".bindings] Back = "BrowserBack" Forward = "BrowserForward" # Gesture button: one action per swipe direction; Click = plain press. -[devices.2b042.bindings.GestureButton] +[devices."receiver:97b76948a846c55a:slot:2".bindings.GestureButton] Click = "MissionControl" Up = "MissionControl" Down = "AppExpose" Left = "PreviousDesktop" Right = "NextDesktop" +# Action Ring pad (MX Master 4): one action per ring sector, compass-named +# clockwise from the top. With this table a tap opens the on-screen ring; +# replace the whole table with a single action (ActionRing = "Copy") to make +# a tap fire that action directly instead. +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing] +North = "Copy" +NorthEast = "CaptureRegion" +East = "Redo" +SouthEast = "PlayPause" +South = "Paste" +SouthWest = "ShowDesktop" +West = "Undo" +NorthWest = "MissionControl" + # Per-app overlay: Back becomes Undo only while VS Code is frontmost. -[devices.2b042.per_app_bindings."com.microsoft.VSCode"] +[devices."receiver:97b76948a846c55a:slot:2".per_app_bindings."com.microsoft.VSCode"] Back = "Undo" -[devices.2b042.lighting] +[devices."receiver:97b76948a846c55a:slot:2".lighting] enabled = true color = "ff0000" brightness = 80 From a1dc1e9b985279f268027bccdbdd556a8018fb92 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 06:55:12 -0700 Subject: [PATCH 09/42] fix(hid): give first-contact Bolt slot walks a workable budget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BOLT_SLOT_PROBE (1 s) assumed "a healthy walk is well under a second" — true on a hot link, false for an MX Master 4 on the Windows BLE stack, where a first walk takes 1-3 s even in active use. With an empty cache every tick timed out, model_info stayed None, the device never entered the agent device list, and nothing that keys off it (capture, DPI, the Action Ring) ever saw the mouse; attach became a minutes-long lottery that only resolved when one walk happened to land under 1 s. A slot with no cached walk now gets a 3 s first-contact budget; once cached, the tight 1 s cap returns, keeping the #218 protection (a hung device falls back to its cache and cannot starve the receiver). Slots probe concurrently, so the worst case against PROBE_BUDGET is max, not sum: 1.5 s arrival drain + 3 s = 4.5 s still fits. Hardware-verified: attach went from ~8 minutes (or never) to within two ticks of agent start. --- crates/openlogi-hid/src/inventory.rs | 15 +++++++++++++++ crates/openlogi-hid/src/inventory/probe.rs | 21 ++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/crates/openlogi-hid/src/inventory.rs b/crates/openlogi-hid/src/inventory.rs index 394cfb22..563b20ee 100644 --- a/crates/openlogi-hid/src/inventory.rs +++ b/crates/openlogi-hid/src/inventory.rs @@ -72,6 +72,21 @@ const UNIFYING_SLOT_PROBE: Duration = Duration::from_millis(3500); /// arrival drain (1.5 + 3×1 = 4.5 s). const BOLT_SLOT_PROBE: Duration = Duration::from_secs(1); +/// First-contact budget for a Bolt slot with no cached feature walk yet. +/// +/// [`BOLT_SLOT_PROBE`]'s "a healthy walk is well under a second" holds for a +/// hot link, but not universally: on the Windows BLE stack an MX Master 4's +/// *first* walk takes 1–3 s even while the mouse is in active use, so the 1 s +/// cap can starve it forever — every tick times out, the cache never fills, +/// and the device never enters the inventory with capabilities (so nothing +/// that keys off the device list — capture, DPI, the Action Ring — ever sees +/// it). The first walk is the gateway to the device being usable at all, so +/// it gets more room; once cached, the tight cap returns (re-walks are rare +/// and a hung device falls back to its cache, keeping the #218 protection). +/// Slots probe concurrently, so the worst case against [`PROBE_BUDGET`] is +/// `max`, not sum: 1.5 s arrival drain + 3 s = 4.5 s still fits. +const BOLT_FIRST_CONTACT_PROBE: Duration = Duration::from_secs(3); + /// Errors raised while enumerating HID++ devices. #[derive(Debug, Error)] pub enum InventoryError { diff --git a/crates/openlogi-hid/src/inventory/probe.rs b/crates/openlogi-hid/src/inventory/probe.rs index ae78fb87..c72a6813 100644 --- a/crates/openlogi-hid/src/inventory/probe.rs +++ b/crates/openlogi-hid/src/inventory/probe.rs @@ -23,7 +23,9 @@ use crate::route::DIRECT_DEVICE_INDEX; use super::cache::{CacheKey, CacheOutcome, Cached, probe_or_reuse, seen}; use super::features::ProbedFeatures; -use super::{ARRIVAL_DRAIN, BOLT_SLOT_PROBE, MAX_BOLT_SLOTS, UNIFYING_SLOT_PROBE}; +use super::{ + ARRIVAL_DRAIN, BOLT_FIRST_CONTACT_PROBE, BOLT_SLOT_PROBE, MAX_BOLT_SLOTS, UNIFYING_SLOT_PROBE, +}; /// One probed node's contribution this tick: its inventory (if any), whether /// the node actually answered — the ledger replays the last snapshot when it @@ -281,17 +283,26 @@ async fn probe_bolt_slot( // burn the whole receiver's `PROBE_BUDGET` and time out `probe_one` — which // would drop *every* device on the receiver. A timed-out slot falls back to // its cached probe (its pairing-register identity above already read fine), - // mirroring the Unifying path (#218). + // mirroring the Unifying path (#218). A slot with no cached walk yet gets + // the longer first-contact budget — see [`BOLT_FIRST_CONTACT_PROBE`]. + let budget = if cached.is_none() { + BOLT_FIRST_CONTACT_PROBE + } else { + BOLT_SLOT_PROBE + }; let probe_result = timeout( - BOLT_SLOT_PROBE, + budget, probe_or_reuse(channel, slot, id.clone(), cached, online, tick), ) .await; let (probe, outcome) = if let Ok(r) = probe_result { r } else { - debug!(slot, budget = ?BOLT_SLOT_PROBE, - "Bolt slot probe timed out; using cached data if available"); + debug!( + slot, + ?budget, + "Bolt slot probe timed out; using cached data if available" + ); let probe = cached.map_or_else(ProbedFeatures::default, |c| c.probe.clone()); (probe, seen(id)) }; From a723bd1e6e836f1ea9f1fb2039f05f8b04594947 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 06:55:26 -0700 Subject: [PATCH 10/42] =?UTF-8?q?fix(hid):=20only=20the=20pad=20CID=20open?= =?UTF-8?q?s=20the=20ring=20=E2=80=94=200x0050/0x0051=20are=20clicks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The discovery notes called 0x0050/0x0051 "companions of the Action Ring pad". They are the LEFT and RIGHT mouse buttons: with analyticsKeyEvents enabled they report every physical click as a press/release pair — which is why Options+ arms them (click telemetry). With them armed for the ring, every click anywhere on the desktop opened the overlay (hardware-confirmed; the earlier captures'"taps on 0x0050" were stray real clicks misread as pad events). Production arming and the press edge now use ACTION_RING_CID (0x01a0) alone; analytics from any other CID are ignored with a debug note. The click CIDs stay as documented constants, and the diag panel keeps arming all three deliberately so the full analytics stream stays visible to diagnostics. --- crates/openlogi-hid/src/gesture.rs | 53 ++++++++++++---------- crates/openlogi-hid/src/gesture/tests.rs | 47 ++++++++++++++----- crates/openlogi-hid/src/hidden_features.rs | 2 +- crates/openlogi-hid/src/reprog_controls.rs | 33 ++++++++++---- 4 files changed, 90 insertions(+), 45 deletions(-) diff --git a/crates/openlogi-hid/src/gesture.rs b/crates/openlogi-hid/src/gesture.rs index d8700460..0cfad2cf 100644 --- a/crates/openlogi-hid/src/gesture.rs +++ b/crates/openlogi-hid/src/gesture.rs @@ -102,8 +102,8 @@ struct CaptureAccum { /// Whether any DPI/ModeShift control was held in the last event — for /// rising-edge press detection. dpi_down: bool, - /// Whether any Action Ring CID was down in the last analytics event — for - /// rising-edge press detection across the pad's multiple CIDs. + /// Whether the Action Ring pad was down in the last analytics event — for + /// rising-edge press detection. panel_down: bool, } @@ -261,10 +261,11 @@ impl ArmedControls { analytics_key_events: Some(false), ..CidReportingChange::default() }; - for cid in reprog_controls::ACTION_RING_ANALYTICS_CIDS { - let r = rc.set_cid_reporting_full(cid, off).await.map(|_| ()); - restore(r, "action ring pad"); - } + let r = rc + .set_cid_reporting_full(reprog_controls::ACTION_RING_CID, off) + .await + .map(|_| ()); + restore(r, "action ring pad"); } for &cid in &self.dpi_cids { restore(rc.set_cid_reporting(cid, false, false).await, "DPI button"); @@ -276,11 +277,13 @@ impl ArmedControls { } } -/// The Action Ring pad's arming recipe, mirroring what Options+ does at -/// startup: write the pad's force threshold (the pad is physically dormant -/// without one), then enable `analyticsKeyEvents` reporting on its CIDs — no -/// diversion, no raw-XY. Re-applied on [`PANEL_REARM_PERIOD`] because neither -/// write survives the device sleeping. +/// The Action Ring pad's arming recipe: write the pad's force threshold (the +/// pad is physically dormant without one), then enable `analyticsKeyEvents` +/// reporting on [`reprog_controls::ACTION_RING_CID`] **only** — no diversion, +/// no raw-XY, and deliberately NOT the click-telemetry CIDs Options+ also +/// arms (see [`reprog_controls::LEFT_BUTTON_CID`]: arming those turns every +/// physical click into a ring press). Re-applied on [`PANEL_REARM_PERIOD`] +/// because neither write survives the device sleeping. struct PanelArming { /// `0x19c0` accessor for the force-threshold write; `None` when the device /// does not expose it (analytics reporting is still armed). @@ -303,10 +306,11 @@ impl PanelArming { analytics_key_events: Some(true), ..CidReportingChange::default() }; - for cid in reprog_controls::ACTION_RING_ANALYTICS_CIDS { - if let Err(e) = rc.set_cid_reporting_full(cid, on).await { - debug!(cid, error = ?e, "action ring analytics arm failed"); - } + if let Err(e) = rc + .set_cid_reporting_full(reprog_controls::ACTION_RING_CID, on) + .await + { + debug!(error = ?e, "action ring analytics arm failed"); } } } @@ -495,12 +499,15 @@ fn handle_reprog( } } RawControlEvent::AnalyticsKeys(entries) => { - // The Action Ring pad reports each tap as a press/release pair on - // ONE of its CIDs (0x01a0 or 0x0050, varying with the press), and - // a firm press can hold several CIDs at once — so track "any ring - // CID down" and emit on its rising edge: one physical tap, one - // press. The release is deliberately unused: the ring UI is a - // toggle (tap to open, tap to select), not press-and-hold. + // The Action Ring pad reports each tap as an analytics + // press/release pair on ACTION_RING_CID — and ONLY that CID may + // drive the ring. Analytics for any other control (notably the + // left/right click-telemetry CIDs, if something else armed them) + // must be ignored, or every physical click opens the ring + // (hardware-confirmed failure mode, 2026-07-20). Rising-edge + // tracking makes one physical tap exactly one press; the release + // is deliberately unused (the ring UI is a toggle, not + // press-and-hold). let mut pressed = false; let mut released = false; for entry in entries { @@ -508,7 +515,7 @@ fn handle_reprog( if cid == 0 { continue; } - if reprog_controls::ACTION_RING_ANALYTICS_CIDS.contains(&cid) { + if cid == reprog_controls::ACTION_RING_CID { if entry.event == 0 { released = true; } else { @@ -517,7 +524,7 @@ fn handle_reprog( } else { debug!( cid, - entry.event, "analytics event from an unhandled control" + entry.event, "analytics event from a non-ring control — ignored" ); } } diff --git a/crates/openlogi-hid/src/gesture/tests.rs b/crates/openlogi-hid/src/gesture/tests.rs index a70f1ddd..6969fc7c 100644 --- a/crates/openlogi-hid/src/gesture/tests.rs +++ b/crates/openlogi-hid/src/gesture/tests.rs @@ -100,9 +100,6 @@ fn a_ring_tap_is_one_rising_edge_and_one_press() { &tx, ); assert!(acc.panel_down, "a press entry arms the edge"); - // A companion CID firing while the pad is held is the same physical press. - handle_reprog(&mut acc, ring(0x0050, 0x01), &[], &tx); - assert!(acc.panel_down, "a companion press is not a second edge"); handle_reprog( &mut acc, ring(reprog_controls::ACTION_RING_CID, 0x00), @@ -125,11 +122,19 @@ fn a_ring_tap_re_arms_after_release() { let (tx, mut rx) = mpsc::unbounded_channel(); let mut acc = CaptureAccum::default(); - handle_reprog(&mut acc, ring(0x0050, 0x01), &[], &tx); - handle_reprog(&mut acc, ring(0x0050, 0x00), &[], &tx); + handle_reprog( + &mut acc, + ring(reprog_controls::ACTION_RING_CID, 0x01), + &[], + &tx, + ); + handle_reprog( + &mut acc, + ring(reprog_controls::ACTION_RING_CID, 0x00), + &[], + &tx, + ); assert!(!acc.panel_down); - // Taps arrive on either ring CID (both observed on hardware) — the edge - // logic must not care which one carried the previous tap. handle_reprog( &mut acc, ring(reprog_controls::ACTION_RING_CID, 0x01), @@ -149,6 +154,26 @@ fn a_ring_tap_re_arms_after_release() { assert!(rx.try_recv().is_err()); } +#[test] +fn click_telemetry_cids_never_open_the_ring() { + // 0x0050/0x0051 are the LEFT/RIGHT mouse buttons' analytics CIDs, not the + // pad. If anything arms them (Options+ does, for telemetry), every + // physical click would arrive here — and must be ignored, or clicking + // anywhere opens the ring (hardware-confirmed failure, 2026-07-20). + let (tx, mut rx) = mpsc::unbounded_channel(); + let mut acc = CaptureAccum::default(); + + for cid in [ + reprog_controls::LEFT_BUTTON_CID, + reprog_controls::RIGHT_BUTTON_CID, + ] { + handle_reprog(&mut acc, ring(cid, 0x01), &[], &tx); + assert!(!acc.panel_down, "a click press must not arm the ring edge"); + handle_reprog(&mut acc, ring(cid, 0x00), &[], &tx); + } + assert!(rx.try_recv().is_err(), "clicks must emit nothing"); +} + #[test] fn analytics_from_foreign_controls_do_not_touch_the_ring_edge() { let (tx, mut rx) = mpsc::unbounded_channel(); @@ -173,9 +198,9 @@ fn analytics_from_foreign_controls_do_not_touch_the_ring_edge() { #[test] fn a_batch_with_press_and_release_entries_arms_the_edge() { - // The wire format carries five entries per message; if a press and a - // (stale) release arrive together, the press wins — dropping a tap is - // worse than clearing the edge a message late. + // The wire format carries five entries per message: a pad press plus an + // unrelated click-CID entry in the same batch must arm the edge off the + // pad entry alone, with the click entry ignored. let (tx, mut rx) = mpsc::unbounded_channel(); let mut acc = CaptureAccum::default(); @@ -185,7 +210,7 @@ fn a_batch_with_press_and_release_entries_arms_the_edge() { event: 0x01, }; entries[1] = AnalyticsKeyEvent { - cid: ControlId(0x0050), + cid: ControlId(reprog_controls::LEFT_BUTTON_CID), event: 0x00, }; handle_reprog(&mut acc, RawControlEvent::AnalyticsKeys(entries), &[], &tx); diff --git a/crates/openlogi-hid/src/hidden_features.rs b/crates/openlogi-hid/src/hidden_features.rs index b26176b9..cc05dc2d 100644 --- a/crates/openlogi-hid/src/hidden_features.rs +++ b/crates/openlogi-hid/src/hidden_features.rs @@ -22,7 +22,7 @@ use crate::reprog_controls::{FEATURE_ID as REPROG_FEATURE_ID, ReprogControlsV4}; use crate::route::{DeviceRoute, open_route_channel}; use crate::write::open_feature; -pub use crate::reprog_controls::ACTION_RING_ANALYTICS_CIDS as PANEL_ANALYTICS_CIDS; +pub use crate::reprog_controls::PANEL_DIAG_ANALYTICS_CIDS as PANEL_ANALYTICS_CIDS; /// Hard wall-clock budget for one whole diagnostic (open + calls). A cold /// BTLE link can swallow a request without ever answering, and the underlying diff --git a/crates/openlogi-hid/src/reprog_controls.rs b/crates/openlogi-hid/src/reprog_controls.rs index 99dc75ba..6a763430 100644 --- a/crates/openlogi-hid/src/reprog_controls.rs +++ b/crates/openlogi-hid/src/reprog_controls.rs @@ -50,7 +50,7 @@ pub const GESTURE_BUTTON_CID: u16 = 0x00c3; /// with `force_raw_xy` hijacks the mouse's entire sensor stream and freezes /// the cursor, and a plain divert + raw-XY produces no pad events at all. The /// pad's real event path is `analyticsKeyEvents` on -/// [`ACTION_RING_ANALYTICS_CIDS`]. Kept documented so the dead end is not +/// [`ACTION_RING_CID`]. Kept documented so the dead end is not /// rediscovered. pub const HAPTIC_PANEL_CID: u16 = 0x00d7; @@ -61,17 +61,30 @@ pub const HAPTIC_PANEL_CID: u16 = 0x00d7; /// `analytics-events`; its presence there is how a capture session detects the /// pad. The pad is dormant until its `0x19c0` force threshold is written and /// silent until `analyticsKeyEvents` reporting is enabled — see -/// `gesture::arm_controls`. +/// `gesture::arm_controls`. Pad taps arrive on **this CID only**; see +/// [`LEFT_BUTTON_CID`] for why the "companion" CIDs must never be armed in +/// production. pub const ACTION_RING_CID: u16 = 0x01a0; -/// Control IDs the Action Ring pad reports through once armed (captures in -/// `docs/mx-master-4-panel-captures/`): each tap is an `analyticsKeyEvents` -/// press/release pair (`event` `0x01`/`0x00`) on **one** of these — -/// [`ACTION_RING_CID`] or `0x0050`, varying with the press. `0x0051` was never -/// observed firing, but Options+ arms it; armed for parity. Confirmed on real -/// hardware 2026-07-20 from a power-cycled mouse with no Logitech software -/// running. -pub const ACTION_RING_ANALYTICS_CIDS: [u16; 3] = [ACTION_RING_CID, 0x0050, 0x0051]; +/// Left mouse button control ID. With `analyticsKeyEvents` reporting enabled +/// it reports **every physical left click** as a press/release pair — which is +/// why Options+ arms it (click telemetry) and why OpenLogi's ring arming must +/// not: with it armed, every click on the desktop opened the ring +/// (hardware-confirmed 2026-07-20). Earlier discovery notes calling +/// `0x0050`/`0x0051` "companions of the pad" were a misread of stray real +/// clicks in those captures. +pub const LEFT_BUTTON_CID: u16 = 0x0050; + +/// Right mouse button control ID — see [`LEFT_BUTTON_CID`]; same telemetry +/// behaviour for right clicks. +pub const RIGHT_BUTTON_CID: u16 = 0x0051; + +/// CIDs the *diagnostic* panel watcher arms — the pad plus the click-telemetry +/// CIDs Options+ also arms, so `openlogi diag panel` shows the full analytics +/// stream (pad taps AND clicks, each labeled by CID). Production ring capture +/// arms [`ACTION_RING_CID`] alone. +pub const PANEL_DIAG_ANALYTICS_CIDS: [u16; 3] = + [ACTION_RING_CID, LEFT_BUTTON_CID, RIGHT_BUTTON_CID]; /// Control IDs of the "DPI / ModeShift" button family. Whichever a device /// exposes (and can divert) is captured and mapped to From 00dbef65ef0cb34b9fffb8c6c92c274812752ce0 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 06:55:49 -0700 Subject: [PATCH 11/42] feat(ipc): stream Action Ring presses to the GUI and execute actions (v11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The overlay lives in the GUI, so a ring-shaped pad press must cross the IPC boundary fast. Two methods appended (order is wire format): next_ring_press long-polls the new agent-core RingChannel — held ~20 s, answered the instant a press queues, so open latency is one IPC round trip (~11 ms measured) — and execute_action fires the selected sector through the agent dispatch path, unbound from GUI window focus. The gesture watcher routes ButtonPressed(ActionRing) into the channel only while the effective binding is ring-shaped (ring_armed_for, published by the orchestrator on every rebuild; a per-app single-action override disarms the ring for that app). A Single-bound pad keeps dispatching in the agent as before. Presses carry a monotonic seq and a small drop-oldest queue so a backlog never replays ghost opens. PROTOCOL_VERSION 10 -> 11; goldens regenerated with the two request variants and RingPress varint encodings pinned. --- crates/openlogi-agent-core/Cargo.toml | 2 + crates/openlogi-agent-core/src/bindings.rs | 61 +++++++ crates/openlogi-agent-core/src/ipc.rs | 34 +++- crates/openlogi-agent-core/src/lib.rs | 1 + .../openlogi-agent-core/src/orchestrator.rs | 13 +- crates/openlogi-agent-core/src/ring.rs | 160 ++++++++++++++++++ .../src/watchers/gesture.rs | 17 ++ .../openlogi-agent-core/tests/wire_format.rs | 19 ++- crates/openlogi-agent/src/main.rs | 1 + crates/openlogi-agent/src/pairing.rs | 1 + crates/openlogi-agent/src/server.rs | 17 +- 11 files changed, 319 insertions(+), 7 deletions(-) create mode 100644 crates/openlogi-agent-core/src/ring.rs diff --git a/crates/openlogi-agent-core/Cargo.toml b/crates/openlogi-agent-core/Cargo.toml index 1a0f341f..448fbcb0 100644 --- a/crates/openlogi-agent-core/Cargo.toml +++ b/crates/openlogi-agent-core/Cargo.toml @@ -29,3 +29,5 @@ workspace = true bincode = "1.3" # Only to construct an `InventoryError::Hid` in the watcher's classify tests. async-hid = { workspace = true } +# `start_paused` virtual time for the ring long-poll timeout tests. +tokio = { workspace = true, features = ["test-util", "macros", "rt"] } diff --git a/crates/openlogi-agent-core/src/bindings.rs b/crates/openlogi-agent-core/src/bindings.rs index a416709c..ceba82cd 100644 --- a/crates/openlogi-agent-core/src/bindings.rs +++ b/crates/openlogi-agent-core/src/bindings.rs @@ -77,6 +77,25 @@ pub fn gesture_bindings_for( bindings } +/// Whether the Action Ring pad's effective binding opens the on-screen ring +/// (a [`Binding::Ring`]) rather than firing a single action. +/// +/// A device with no stored `ActionRing` binding gets the canonical default — +/// which *is* a ring — so a fresh MX Master 4 opens the ring out of the box. +/// A per-app overlay that demotes the pad to a single action disarms the ring +/// for that app, mirroring how a gesture owner is demoted per app. +#[must_use] +pub fn ring_armed_for(config: &Config, config_key: Option<&str>, app_bundle: Option<&str>) -> bool { + let Some(key) = config_key else { + // No config key yet (no device selected) — the default binding rules. + return true; + }; + config + .effective_bindings(key, app_bundle) + .remove(&ButtonId::ActionRing) + .is_none_or(|binding| binding.is_ring()) +} + /// Per-direction maps for the OS-hook gesture buttons (Middle/Back/Forward in /// gesture mode) on `config_key`, with `app_bundle`'s per-app overlay applied, /// for the OS hook to resolve a hold+swipe. @@ -160,6 +179,48 @@ mod tests { ); } + #[test] + fn ring_stays_armed_by_default_and_disarms_on_a_single_binding() { + let mut cfg = Config::default(); + // No stored binding: the canonical Ring default arms the overlay. + assert!(ring_armed_for(&cfg, Some("2b042"), None)); + assert!( + ring_armed_for(&cfg, None, None), + "no selected device still means the default ring binding" + ); + + // The user demotes the pad to a plain action — taps dispatch instead. + cfg.set_binding("2b042", ButtonId::ActionRing, Action::Copy.into()); + assert!(!ring_armed_for(&cfg, Some("2b042"), None)); + + // An explicit ring binding re-arms it. + cfg.set_binding( + "2b042", + ButtonId::ActionRing, + openlogi_core::binding::default_binding_for(ButtonId::ActionRing), + ); + assert!(ring_armed_for(&cfg, Some("2b042"), None)); + } + + #[test] + fn per_app_single_override_disarms_the_ring_for_that_app_only() { + let mut cfg = Config::default(); + cfg.set_per_app_binding( + "2b042", + "com.microsoft.VSCode", + ButtonId::ActionRing, + Some(Action::Paste), + ); + assert!( + !ring_armed_for(&cfg, Some("2b042"), Some("com.microsoft.VSCode")), + "the overlay must not open where the pad is a plain action" + ); + assert!( + ring_armed_for(&cfg, Some("2b042"), Some("com.other.App")), + "other apps keep the ring" + ); + } + #[test] fn oshook_gestures_collects_only_os_hook_gesture_buttons() { let mut cfg = Config::default(); diff --git a/crates/openlogi-agent-core/src/ipc.rs b/crates/openlogi-agent-core/src/ipc.rs index 9acdc281..de68386d 100644 --- a/crates/openlogi-agent-core/src/ipc.rs +++ b/crates/openlogi-agent-core/src/ipc.rs @@ -6,6 +6,7 @@ //! flow long-polls [`Agent::next_pairing`], which the agent holds open until a //! pairing event arrives or the request deadline elapses. +use openlogi_core::binding::Action; use openlogi_core::config::Lighting; use openlogi_core::device::DeviceInventory; use openlogi_hid::{ @@ -28,7 +29,22 @@ use serde::{Deserialize, Serialize}; /// v8: [`WriteError`] carries typed HID++ operation failures. /// v9: `poll_event_monitor` appended + [`MonitorEvent`] (live event monitor). /// v10: `Capabilities::hires_wheel` appended. -pub const PROTOCOL_VERSION: u32 = 10; +/// v11: `next_ring_press` + `execute_action` appended (Action Ring overlay). +pub const PROTOCOL_VERSION: u32 = 11; + +/// One Action Ring pad press, streamed to the GUI via +/// [`Agent::next_ring_press`] so the on-screen ring opens (or confirms a +/// selection) the moment the pad is tapped. +/// +/// bincode encodes struct fields positionally — fields are append-only, like +/// the [`Agent`] trait methods. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct RingPress { + /// Monotonic press counter within one agent run. Lets the GUI drop + /// stale presses that queued while it had no poll outstanding (e.g. + /// while reconnecting) instead of replaying a burst. + pub seq: u64, +} /// Where the agent's device enumeration stands. The distinction matters /// because an empty inventory list is ambiguous on its own: the GUI must keep @@ -272,7 +288,19 @@ pub trait Agent { /// Drain the events the hook has observed since the last poll, for the GUI's /// live event monitor. The first poll enables monitoring; the agent /// auto-disables it once polls stop (the GUI closed the panel or died), so - /// there is no explicit stop. Appended last — see the method-order note on - /// [`Agent::protocol_version`]. + /// there is no explicit stop. async fn poll_event_monitor() -> Vec; + /// Long-poll the next Action Ring pad press. The agent answers immediately + /// when a press is queued, otherwise holds the request until one arrives or + /// its hold window elapses (`None` — the GUI simply re-polls). Only presses + /// while the pad's effective binding is ring-shaped arrive here; a + /// single-action binding dispatches in the agent instead. Appended for + /// v11 — method order is wire-sensitive (see [`Self::protocol_version`]). + async fn next_ring_press() -> Option; + /// Execute one bound action through the agent's dispatch path — how the + /// ring overlay fires the sector the user selected. Runs in the agent so + /// synthesis works exactly like any button binding (and is not tied to the + /// GUI's window focus). Appended for v11 — see the method-order note on + /// [`Self::protocol_version`]. + async fn execute_action(action: Action); } diff --git a/crates/openlogi-agent-core/src/lib.rs b/crates/openlogi-agent-core/src/lib.rs index 435d848c..0638585b 100644 --- a/crates/openlogi-agent-core/src/lib.rs +++ b/crates/openlogi-agent-core/src/lib.rs @@ -15,6 +15,7 @@ pub mod hook_runtime; pub mod ipc; pub mod orchestrator; pub mod receiver_access; +pub mod ring; pub mod transport; pub mod watchers; diff --git a/crates/openlogi-agent-core/src/orchestrator.rs b/crates/openlogi-agent-core/src/orchestrator.rs index a3c26fdd..9f09eaa7 100644 --- a/crates/openlogi-agent-core/src/orchestrator.rs +++ b/crates/openlogi-agent-core/src/orchestrator.rs @@ -20,11 +20,12 @@ use openlogi_hid::{CaptureChannel, DeviceRoute}; use tracing::warn; use crate::DpiCycleState; -use crate::bindings::{bindings_for, gesture_bindings_for, oshook_gestures_for}; +use crate::bindings::{bindings_for, gesture_bindings_for, oshook_gestures_for, ring_armed_for}; use crate::device_order::DeviceStableId; use crate::hook_runtime::{HookMaps, SharedHookMaps}; use crate::ipc::InventoryHealth; use crate::receiver_access::ReceiverAccess; +use crate::ring::RingChannel; use crate::watchers::gesture::GestureBindings; /// The minimal per-device facts the agent needs: the config key (binding / @@ -61,6 +62,10 @@ pub struct SharedRuntime { /// Exclusive receiver access shared by HID++ capture and pairing. Capture /// and pairing must never open the same receiver HID node concurrently. pub receiver_access: ReceiverAccess, + /// Action Ring press channel: the gesture watcher pushes pad presses here + /// while the pad's effective binding is ring-shaped, and the IPC server's + /// `next_ring_press` long-poll drains them for the GUI overlay. + pub ring: RingChannel, } /// Owns the config + device selection and keeps [`SharedRuntime`] in sync. @@ -112,6 +117,7 @@ impl Orchestrator { )), capture_channel: Arc::new(RwLock::new(None)), receiver_access: ReceiverAccess::default(), + ring: RingChannel::default(), }; let orch = Self { config, @@ -183,6 +189,11 @@ impl Orchestrator { self.config.app_settings.thumbwheel_sensitivity, Ordering::Relaxed, ); + self.shared.ring.set_armed(ring_armed_for( + &self.config, + key, + self.current_app.as_deref(), + )); } /// Apply a fresh inventory snapshot. Always refreshes the snapshot the IPC diff --git a/crates/openlogi-agent-core/src/ring.rs b/crates/openlogi-agent-core/src/ring.rs new file mode 100644 index 00000000..60a04927 --- /dev/null +++ b/crates/openlogi-agent-core/src/ring.rs @@ -0,0 +1,160 @@ +//! Shared Action Ring press channel between the gesture watcher (producer) +//! and the IPC server's long-poll (consumer). +//! +//! When the pad's effective binding is ring-shaped, a tap must reach the GUI — +//! the process that draws the on-screen ring — instantly. The GUI keeps one +//! `next_ring_press` long-poll outstanding; the watcher pushes each press here +//! and the poll answers immediately, so open latency is one IPC round trip +//! rather than a poll interval. + +use std::collections::VecDeque; +use std::sync::{Arc, Mutex, PoisonError}; +use std::time::Duration; + +use tokio::sync::Notify; + +use crate::ipc::RingPress; + +/// How long an empty `next_ring_press` poll is held before answering `None`. +/// Mirrors the pairing long-poll: long enough that an idle GUI re-polls rarely, +/// short enough that the client's request deadline (25 s) never fires first. +const HOLD: Duration = Duration::from_secs(20); + +/// Presses buffered while the GUI has no poll outstanding. Deliberately small: +/// a backlog older than this is stale input (the user tapping at a dead +/// overlay), and replaying it would fire ghost open/select transitions. +const QUEUE_CAP: usize = 4; + +#[derive(Default)] +struct RingChannelState { + /// Whether the active device's effective Action Ring binding is + /// ring-shaped. Published by the orchestrator on every rebuild; the + /// gesture watcher routes a pad press here only while set, and to the + /// ordinary single-action dispatch otherwise. + armed: bool, + /// Monotonic press counter for [`RingPress::seq`]. + seq: u64, + pending: VecDeque, +} + +/// Shared press channel; cheap to clone (two `Arc`s). +#[derive(Clone, Default)] +pub struct RingChannel { + state: Arc>, + notify: Arc, +} + +impl RingChannel { + fn lock(&self) -> std::sync::MutexGuard<'_, RingChannelState> { + // Recover the guard even if a prior holder panicked — every critical + // section below is panic-free, so the data stays consistent. + self.state.lock().unwrap_or_else(PoisonError::into_inner) + } + + /// Publish whether the pad's effective binding opens the ring. + pub fn set_armed(&self, armed: bool) { + self.lock().armed = armed; + } + + /// Whether a pad press should be routed to the ring overlay. + #[must_use] + pub fn is_armed(&self) -> bool { + self.lock().armed + } + + /// Queue one pad press and wake the long-poll. Oldest press is dropped + /// when the queue is full — see [`QUEUE_CAP`]. + pub fn push_press(&self) { + { + let mut st = self.lock(); + st.seq += 1; + let seq = st.seq; + if st.pending.len() == QUEUE_CAP { + st.pending.pop_front(); + } + st.pending.push_back(RingPress { seq }); + } + // notify_one stores a permit when no poll is waiting, so a press that + // lands between a poll's queue check and its await is never lost. + self.notify.notify_one(); + } + + /// Long-poll: the oldest queued press, or `None` once [`HOLD`] elapses. + pub async fn next_press(&self) -> Option { + let deadline = tokio::time::Instant::now() + HOLD; + loop { + // Register interest before checking, so a push between the check + // and the await wakes the `notified` future instead of racing it. + let notified = self.notify.notified(); + if let Some(press) = self.lock().pending.pop_front() { + return Some(press); + } + tokio::select! { + () = notified => {} + () = tokio::time::sleep_until(deadline) => return None, + } + } + } +} + +#[cfg(test)] +#[allow(clippy::expect_used, reason = "expect/unwrap are idiomatic in tests")] +mod tests { + use super::*; + + #[tokio::test] + async fn queued_press_answers_immediately() { + let chan = RingChannel::default(); + chan.push_press(); + assert_eq!(chan.next_press().await, Some(RingPress { seq: 1 })); + } + + #[tokio::test] + async fn presses_deliver_in_order_with_monotonic_seq() { + let chan = RingChannel::default(); + chan.push_press(); + chan.push_press(); + assert_eq!(chan.next_press().await, Some(RingPress { seq: 1 })); + assert_eq!(chan.next_press().await, Some(RingPress { seq: 2 })); + } + + #[tokio::test] + async fn overflow_drops_the_oldest_press_not_the_newest() { + let chan = RingChannel::default(); + for _ in 0..QUEUE_CAP + 2 { + chan.push_press(); + } + // Seqs 1 and 2 were evicted; the survivors are the most recent CAP. + assert_eq!(chan.next_press().await, Some(RingPress { seq: 3 })); + } + + #[tokio::test(start_paused = true)] + async fn empty_poll_times_out_with_none() { + let chan = RingChannel::default(); + // Paused time: the sleep_until elapses instantly once polled. + assert_eq!(chan.next_press().await, None); + } + + #[tokio::test(start_paused = true)] + async fn press_during_a_held_poll_wakes_it() { + let chan = RingChannel::default(); + let waiter = tokio::spawn({ + let chan = chan.clone(); + async move { chan.next_press().await } + }); + // Let the poll reach its await before pushing. + tokio::task::yield_now().await; + chan.push_press(); + assert_eq!(waiter.await.expect("join"), Some(RingPress { seq: 1 })); + } + + #[tokio::test] + async fn armed_flag_round_trips() { + let chan = RingChannel::default(); + assert!(!chan.is_armed(), "unarmed until the orchestrator publishes"); + chan.set_armed(true); + assert!(chan.is_armed()); + chan.set_armed(false); + assert!(!chan.is_armed()); + } +} diff --git a/crates/openlogi-agent-core/src/watchers/gesture.rs b/crates/openlogi-agent-core/src/watchers/gesture.rs index 80659682..e966c66b 100644 --- a/crates/openlogi-agent-core/src/watchers/gesture.rs +++ b/crates/openlogi-agent-core/src/watchers/gesture.rs @@ -33,6 +33,7 @@ use tracing::{debug, warn}; use crate::DpiCycleState; use crate::hook_runtime::{self, SharedHookMaps}; use crate::receiver_access::ReceiverAccess; +use crate::ring::RingChannel; /// Shared gesture-direction binding map, mirrored from `AppState` (keyed by /// direction). The watcher reads it to map a captured swipe to a bound action. @@ -81,6 +82,7 @@ pub fn spawn( capture_channel: CaptureChannel, thumbwheel_sensitivity: ThumbwheelSensitivity, receiver_access: ReceiverAccess, + ring: RingChannel, ) { thread::spawn(move || { let runtime = match tokio::runtime::Builder::new_current_thread() @@ -100,6 +102,7 @@ pub fn spawn( capture_channel, thumbwheel_sensitivity, receiver_access, + ring, )); }); } @@ -151,6 +154,7 @@ async fn manage( capture_channel: CaptureChannel, thumbwheel_sensitivity: ThumbwheelSensitivity, receiver_access: ReceiverAccess, + ring: RingChannel, ) { let (tx, mut rx) = mpsc::unbounded_channel::(); // (route, capture_thumbwheel, divert_gesture_button) @@ -180,6 +184,7 @@ async fn manage( &dpi_cycle, &capture_channel, &thumbwheel_sensitivity, + &ring, ); } _ = ticker.tick() => { @@ -308,6 +313,10 @@ enum WheelOutput { } /// Route one captured input to its bound action (or re-synthesised scroll). +#[expect( + clippy::too_many_arguments, + reason = "the watcher's full shared-state set; bundling it would just relocate the list" +)] fn dispatch( input: CapturedInput, accumulators: &mut WheelAccumulators, @@ -316,6 +325,7 @@ fn dispatch( dpi_cycle: &Arc>, capture: &CaptureChannel, thumbwheel_sensitivity: &ThumbwheelSensitivity, + ring: &RingChannel, ) { match input { CapturedInput::Gesture(direction) => { @@ -330,6 +340,13 @@ fn dispatch( debug!(?direction, "gesture with no binding — ignored"); } } + // A ring-shaped Action Ring binding routes the press to the GUI + // overlay instead of the single-action path (whose projection for a + // ring binding is deliberately `Action::None`). + CapturedInput::ButtonPressed(ButtonId::ActionRing) if ring.is_armed() => { + debug!("action ring press → overlay long-poll"); + ring.push_press(); + } CapturedInput::ButtonPressed(button) => { let action = hook_maps .read() diff --git a/crates/openlogi-agent-core/tests/wire_format.rs b/crates/openlogi-agent-core/tests/wire_format.rs index c5b14e4c..b7d456b0 100644 --- a/crates/openlogi-agent-core/tests/wire_format.rs +++ b/crates/openlogi-agent-core/tests/wire_format.rs @@ -24,8 +24,9 @@ use std::fmt::Write; use bincode::Options; use openlogi_agent_core::ipc::{ AgentRequest, AgentSnapshot, AgentStatus, FoundDevice, InventoryHealth, MonitorEvent, - PROTOCOL_VERSION, PairingCommandError, PairingFailure, PairingUpdate, + PROTOCOL_VERSION, PairingCommandError, PairingFailure, PairingUpdate, RingPress, }; +use openlogi_core::binding::Action; use openlogi_core::config::Lighting; use openlogi_core::device::{ BatteryInfo, BatteryLevel, BatteryStatus, Capabilities, DeviceInventory, DeviceKind, @@ -61,7 +62,7 @@ fn assert_wire(value: &T, golden: &str) { /// that makes that visible in the same diff. #[test] fn protocol_version_is_pinned() { - assert_eq!(PROTOCOL_VERSION, 10); + assert_eq!(PROTOCOL_VERSION, 11); } /// tarpc encodes the request enum's variant index, so trait *method order* is @@ -83,6 +84,20 @@ fn request_variant_order() { assert_wire(&AgentRequest::NextPairing {}, "0d"); assert_wire(&AgentRequest::Snapshot {}, "0e"); assert_wire(&AgentRequest::PollEventMonitor {}, "0f"); + assert_wire(&AgentRequest::NextRingPress {}, "10"); + assert_wire( + &AgentRequest::ExecuteAction { + action: Action::Copy, + }, + "1106", + ); +} + +#[test] +fn ring_press() { + // Varint seq: single byte below 251, 0xfb + u16 LE above. + assert_wire(&RingPress { seq: 3 }, "03"); + assert_wire(&RingPress { seq: 300 }, "fb2c01"); } #[test] diff --git a/crates/openlogi-agent/src/main.rs b/crates/openlogi-agent/src/main.rs index b5e98729..91d7666d 100644 --- a/crates/openlogi-agent/src/main.rs +++ b/crates/openlogi-agent/src/main.rs @@ -162,6 +162,7 @@ async fn run(config: Config) { shared.capture_channel.clone(), shared.thumbwheel_sensitivity.clone(), shared.receiver_access.clone(), + shared.ring.clone(), ); let mut inventory_rx = watchers::inventory::spawn(Duration::from_secs(2)); diff --git a/crates/openlogi-agent/src/pairing.rs b/crates/openlogi-agent/src/pairing.rs index ef365590..fd5a047e 100644 --- a/crates/openlogi-agent/src/pairing.rs +++ b/crates/openlogi-agent/src/pairing.rs @@ -271,6 +271,7 @@ mod tests { thumbwheel_sensitivity: Arc::new(0.into()), capture_channel: Arc::new(RwLock::new(None)), receiver_access: ReceiverAccess::default(), + ring: openlogi_agent_core::ring::RingChannel::default(), } } diff --git a/crates/openlogi-agent/src/server.rs b/crates/openlogi-agent/src/server.rs index 4e69d75b..40720c70 100644 --- a/crates/openlogi-agent/src/server.rs +++ b/crates/openlogi-agent/src/server.rs @@ -10,12 +10,14 @@ use std::sync::atomic::{AtomicBool, Ordering}; use futures::StreamExt as _; use openlogi_agent_core::event_monitor::SharedEventMonitor; +use openlogi_agent_core::hook_runtime; use openlogi_agent_core::ipc::{ Agent, AgentSnapshot, AgentStatus, MonitorEvent, PROTOCOL_VERSION, PairingCommandError, - PairingUpdate, + PairingUpdate, RingPress, }; use openlogi_agent_core::orchestrator::{Orchestrator, SharedRuntime}; use openlogi_agent_core::{hardware, transport}; +use openlogi_core::binding::Action; use openlogi_core::config::{Config, Lighting}; use openlogi_core::device::DeviceInventory; use openlogi_hid::{ @@ -171,6 +173,19 @@ impl Agent for AgentServer { async fn poll_event_monitor(self, _: Context) -> Vec { self.event_monitor.poll() } + + async fn next_ring_press(self, _: Context) -> Option { + self.shared.ring.next_press().await + } + + async fn execute_action(self, _: Context, action: Action) { + info!(action = %action.label(), "ring overlay → action"); + hook_runtime::dispatch_action( + &action, + &self.shared.dpi_cycle, + &self.shared.capture_channel, + ); + } } /// Bind the agent's IPC socket and serve [`Agent`] requests until the process From c2ab68f9561ff42853b7d9833ae8a137080e5f25 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 06:55:49 -0700 Subject: [PATCH 12/42] =?UTF-8?q?feat(gui):=20Action=20Ring=20overlay=20?= =?UTF-8?q?=E2=80=94=20tap,=20move,=20tap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A pad tap opens a ring of eight sector buttons centred on the cursor; the pointer moves freely (never captured); a second tap fires the sector the cursor points toward. Cancel: the centre X, a second tap in the dead zone, Esc, or a click outside the ring. The window is gpui WindowKind::PopUp (borderless toolwindow) made transparent, and the bits the pinned backend does not expose go through the raw HWND (HasWindowHandle): WS_EX_NOACTIVATE so focus never leaves the app being acted on, HWND_TOPMOST + SW_SHOWNA / SW_HIDE for show/hide, and physical-pixel positioning at the cursor. The window is created once and reused — reopen latency measured at ~5 ms (first open ~200 ms including surface creation). Sector selection is pure geometry against the global cursor (the pointer may leave the little window entirely), polled at 33 ms while open by a watcher that also closes on Esc / outside-click — signals a no-activate window never receives as messages. Slots come from the device config (Binding::Ring, defaults filled); the selected action executes in the agent via execute_action. Non-Windows builds compile with stub platform plumbing (the pad stays captured and bindable; the overlay is Windows-first). Latency choice per the measured numbers: GUI draws (option A); the long-poll push path makes the IPC hop ~11 ms, far under a poll interval. --- Cargo.lock | 2 + crates/openlogi-gui/Cargo.toml | 14 + crates/openlogi-gui/src/ipc_client.rs | 55 ++- crates/openlogi-gui/src/main.rs | 11 + crates/openlogi-gui/src/ring.rs | 662 ++++++++++++++++++++++++++ crates/openlogi-gui/src/state.rs | 24 + 6 files changed, 763 insertions(+), 5 deletions(-) create mode 100644 crates/openlogi-gui/src/ring.rs diff --git a/Cargo.lock b/Cargo.lock index dd997089..4e4dbb0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5098,6 +5098,7 @@ dependencies = [ "openlogi-core", "openlogi-hid", "openlogi-hook", + "raw-window-handle", "rust-i18n", "serde", "serde_json", @@ -5111,6 +5112,7 @@ dependencies = [ "ureq", "url", "walkdir", + "windows-sys 0.61.2", ] [[package]] diff --git a/crates/openlogi-gui/Cargo.toml b/crates/openlogi-gui/Cargo.toml index 1e3f3e9d..8bae3fec 100644 --- a/crates/openlogi-gui/Cargo.toml +++ b/crates/openlogi-gui/Cargo.toml @@ -47,6 +47,9 @@ walkdir = "2.5.0" fluent-langneg = "0.14.2" backon.workspace = true opener = { workspace = true } +# The Action Ring overlay reads the raw HWND off the gpui window (same 0.6.x +# gpui itself uses, so the resolve adds an edge, not a crate). +raw-window-handle = "0.6" # Windows exe resources (app icon + VERSIONINFO) — see build.rs. The exact # version already in Cargo.lock as gpui's own build-dependency, so the resolve @@ -54,6 +57,17 @@ opener = { workspace = true } [build-dependencies] embed-resource = "3.0.9" +# Action Ring overlay window plumbing gpui doesn't expose: no-activate + +# topmost styles, show/hide without activation, the global cursor, and the +# Esc / outside-click cancel polls (see ring.rs::platform_win). +[target.'cfg(target_os = "windows")'.dependencies] +windows-sys = { workspace = true, features = [ + "Win32_Foundation", + "Win32_UI_HiDpi", + "Win32_UI_Input_KeyboardAndMouse", + "Win32_UI_WindowsAndMessaging", +] } + # Menu-bar (NSStatusItem) FFI — GPUI has no status-bar API. objc2 gives typed, # `Retained`-owned AppKit bindings so the status-item code can't leak (#99). [target.'cfg(target_os = "macos")'.dependencies] diff --git a/crates/openlogi-gui/src/ipc_client.rs b/crates/openlogi-gui/src/ipc_client.rs index 73040715..45e99ea0 100644 --- a/crates/openlogi-gui/src/ipc_client.rs +++ b/crates/openlogi-gui/src/ipc_client.rs @@ -21,8 +21,9 @@ use std::time::{Duration, Instant}; use openlogi_agent_core::ipc::{ AgentClient, AgentStatus, InventoryHealth, PROTOCOL_VERSION, PairingCommandError, - PairingFailure, PairingUpdate, + PairingFailure, PairingUpdate, RingPress, }; +use openlogi_core::binding::Action; use openlogi_core::config::Lighting; use openlogi_core::device::DeviceInventory; use openlogi_hid::{ @@ -101,15 +102,21 @@ pub enum Command { /// auto-disables it once polls stop. #[cfg(all(target_os = "macos", debug_assertions))] PollEventMonitor(oneshot::Sender>), + /// Fire one bound action through the agent's dispatch path — the ring + /// overlay's selected sector. Fire-and-forget like the other "apply now" + /// writes. + ExecuteAction(Action), } /// Handle the GUI holds to talk to the agent: a stream of poll updates, a -/// sender for device commands, and a stream of pairing events (long-polled on a -/// separate connection so a held pairing poll never stalls inventory). +/// sender for device commands, and streams of pairing events and Action Ring +/// presses (each long-polled on its own connection so a held poll never stalls +/// inventory — and a held pairing poll never delays a ring press). pub struct IpcClient { pub updates: mpsc::UnboundedReceiver, pub commands: mpsc::UnboundedSender, pub pairing: mpsc::UnboundedReceiver, + pub ring: mpsc::UnboundedReceiver, } /// Spawn the IPC client thread. Returns immediately; the thread connects (and @@ -119,6 +126,7 @@ pub fn spawn(poll_period: Duration) -> IpcClient { let (update_tx, updates) = mpsc::unbounded_channel(); let (commands, mut cmd_rx) = mpsc::unbounded_channel::(); let (pairing_tx, pairing) = mpsc::unbounded_channel(); + let (ring_tx, ring) = mpsc::unbounded_channel(); let spawn_result = std::thread::Builder::new() .name("openlogi-ipc-client".into()) @@ -134,9 +142,11 @@ pub fn spawn(poll_period: Duration) -> IpcClient { } }; rt.block_on(async move { - // Pairing events stream on their own connection + long-poll so - // a held next_pairing never delays the snapshot poll. + // Pairing events and ring presses stream on their own + // connections + long-polls so a held poll never delays the + // snapshot poll (or each other). tokio::spawn(pairing_poll(pairing_tx.clone())); + tokio::spawn(ring_poll(ring_tx)); poll_loop(poll_period, &update_tx, &pairing_tx, &mut cmd_rx).await; }); }); @@ -148,6 +158,7 @@ pub fn spawn(poll_period: Duration) -> IpcClient { updates, commands, pairing, + ring, } } @@ -299,6 +310,37 @@ async fn pairing_poll(tx: mpsc::UnboundedSender) { } } +/// Long-poll the agent's Action Ring press stream on a dedicated connection. +/// A press opens (or confirms a selection in) the overlay, so latency is the +/// whole point — the poll sits held at the agent and answers the instant the +/// pad is tapped. When the pad is idle the agent returns `None` at its hold +/// window and we re-poll. Runs for the client's lifetime. +async fn ring_poll(tx: mpsc::UnboundedSender) { + let mut client: Option = None; + loop { + let Ok(live) = ensure(&mut client).await else { + tokio::time::sleep(Duration::from_secs(1)).await; // agent not up yet + continue; + }; + // The agent holds the poll ~20s; give the request a bit longer so the + // agent answers (with a press or None) before the client deadline. + let mut ctx = context::current(); + ctx.deadline = Instant::now() + Duration::from_secs(25); + match live.next_ring_press(ctx).await { + Ok(Some(press)) => { + if tx.send(press).is_err() { + return; // GUI dropped the receiver → stop + } + } + Ok(None) => {} + Err(_) => { + client = None; // connection dropped (agent restart) — reconnect + tokio::time::sleep(Duration::from_secs(1)).await; + } + } + } +} + /// One pairing long-poll. `Ok(false)` means the GUI receiver is gone; `Err` a /// dropped connection the caller should reconnect. async fn poll_pairing_once( @@ -577,6 +619,9 @@ async fn handle( Command::PollEventMonitor(reply) => { let _ = reply.send(rpc_result(client.poll_event_monitor(ctx).await)?); } + Command::ExecuteAction(action) => { + client.execute_action(ctx, action).await.map_err(|_| ())?; + } } Ok(()) } diff --git a/crates/openlogi-gui/src/main.rs b/crates/openlogi-gui/src/main.rs index 07d07d06..78559d34 100644 --- a/crates/openlogi-gui/src/main.rs +++ b/crates/openlogi-gui/src/main.rs @@ -40,6 +40,7 @@ mod i18n; mod ipc_client; mod mouse_model; mod platform; +mod ring; mod state; mod theme; mod windows; @@ -156,6 +157,7 @@ fn main() -> Result<()> { updates: mut ipc_updates, commands: ipc_commands, pairing: mut ipc_pairing, + ring: mut ipc_ring, } = ipc_client::spawn(std::time::Duration::from_secs(2)); // Manual asset actions (Settings → Assets): Refresh / Clear cache. The @@ -201,6 +203,11 @@ fn main() -> Result<()> { // back into this global via the select loop below. cx.set_global(windows::add_device::PairingUi::Idle); + // The Action Ring overlay controller: pad presses from the agent's + // ring long-poll (select loop below) open it / confirm a selection, + // and it fires the chosen action back through the command channel. + ring::init(ipc_commands.clone(), cx); + // The Settings → Assets buttons drive the asset sync (which lives on // the select loop below) through this global. cx.set_global(AssetControl(asset_ctrl_tx)); @@ -482,6 +489,10 @@ fn main() -> Result<()> { windows::add_device::apply_update(cx, update); }); } + Some(press) = ipc_ring.recv() => { + tracing::debug!(seq = press.seq, "action ring press received"); + cx.update(ring::on_pad_press); + } Some(cmd) = gui_cmd_rx.recv() => { cx.update(|cx| dispatch_gui_command(cmd, cx)); } diff --git a/crates/openlogi-gui/src/ring.rs b/crates/openlogi-gui/src/ring.rs new file mode 100644 index 00000000..ff1697c3 --- /dev/null +++ b/crates/openlogi-gui/src/ring.rs @@ -0,0 +1,662 @@ +//! The on-screen Action Ring overlay. +//! +//! Interaction model (specified, not guessed): a pad tap opens the ring +//! centred at the cursor; the pointer then moves normally (never captured); +//! a second tap fires the sector the cursor points toward and closes the +//! ring. Cancel: the centre ✕ (click or second tap in the dead zone), `Esc`, +//! or a click outside the ring. +//! +//! The window is a borderless, transparent, always-on-top popup that never +//! takes focus — the action must land in the app the user was using, so +//! focus must not move. GPUI's `WindowKind::PopUp` provides the borderless +//! toolwindow; the no-activate + topmost bits and all show/hide/positioning +//! go through the raw HWND (see [`platform_win`]), because the pinned gpui +//! Windows backend exposes neither. The window is created once on first use +//! and then re-positioned and re-shown per open — no per-tap surface setup. +//! +//! Sector *selection* is plain cursor geometry: the angle from the ring's +//! centre to the global cursor picks the sector, the centre dead zone +//! cancels. The cursor may leave the little overlay window entirely ("move +//! toward" can overshoot) — that changes nothing, since the watcher reads the +//! global cursor, not window-local hover. + +use std::time::Duration; + +use gpui::{ + App, AppContext as _, Bounds, Context, InteractiveElement as _, IntoElement, + ParentElement as _, Point, Render, StatefulInteractiveElement as _, Styled as _, Window, + WindowBounds, WindowHandle, WindowKind, WindowOptions, div, point, px, size, +}; +use gpui_component::{ActiveTheme as _, Icon}; +use openlogi_core::binding::{Action, RingSlot}; +use tokio::sync::mpsc; +use tracing::{debug, info, warn}; + +use crate::ipc_client::Command; +use crate::mouse_model::picker::action_icon_path; +use crate::state::AppState; +use crate::theme; + +/// Logical size of the (square) overlay window. +const RING_WINDOW: f32 = 460.; +/// Radius of the circle the eight sector buttons sit on, from the centre. +const SECTOR_RADIUS: f32 = 150.; +/// Diameter of one sector's circular icon button. +const SECTOR_BUTTON: f32 = 52.; +/// Diameter of the centre ✕ cancel button. +const CENTER_BUTTON: f32 = 36.; +/// Cursor distance (logical px) below which a confirm means "cancel" — the +/// dead zone around the centre ✕. +const DEADZONE: f32 = 46.; +/// How often the open-ring watcher samples the global cursor (highlight) and +/// the Esc / outside-click cancel signals. +const WATCH_TICK: Duration = Duration::from_millis(33); + +/// What the cursor currently points at, driven from the global cursor by the +/// watcher so it works even when the pointer is outside the overlay window. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +enum Aim { + /// Inside the dead zone (or no cursor data): a confirm cancels. + #[default] + Center, + /// Toward a sector: a confirm fires its action. + Sector(RingSlot), +} + +/// Pick the aim for a cursor at `cursor` when the ring is centred at +/// `center`, both in the same (physical) coordinate space, with the dead +/// zone scaled by `scale`. Pure, so the sector math is unit-testable. +fn aim_for(center: Point, cursor: Point, scale: f32) -> Aim { + let dx = cursor.x - center.x; + let dy = cursor.y - center.y; + if (dx * dx + dy * dy).sqrt() < DEADZONE * scale { + return Aim::Center; + } + // Angle clockwise from straight up, matching `RingSlot::angle_degrees`. + let degrees = dx.atan2(-dy).to_degrees().rem_euclid(360.); + #[expect( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + reason = "degrees is in [0, 360), so the sector index is in 0..=8" + )] + let index = (((degrees + 22.5) / 45.).floor() as usize) % 8; + Aim::Sector(RingSlot::ALL[index]) +} + +/// The overlay's app-global controller: the singleton window, whether it is +/// currently shown, and the plumbing to fire the selected action. +pub struct RingOverlay { + commands: mpsc::UnboundedSender, + window: Option>, + open: bool, + /// Physical-pixel centre of the open ring, for global-cursor hit tests. + center: Point, + /// Physical-per-logical scale of the display the ring opened on. + scale: f32, + /// Generation counter; bumping it retires the previous open's watcher. + epoch: u64, +} + +impl gpui::Global for RingOverlay {} + +/// Install the controller. Call once at startup, before the first press can +/// arrive. +pub fn init(commands: mpsc::UnboundedSender, cx: &mut App) { + cx.set_global(RingOverlay { + commands, + window: None, + open: false, + center: point(0., 0.), + scale: 1., + epoch: 0, + }); +} + +/// Handle one Action Ring pad press from the agent: open the ring when it is +/// closed, confirm the aimed selection when it is open. +pub fn on_pad_press(cx: &mut App) { + if cx.global::().open { + confirm(cx); + } else { + open(cx); + } +} + +/// Open the ring centred at the cursor. +fn open(cx: &mut App) { + let Some(cursor) = platform_win::cursor_pos() else { + // Non-Windows builds have no overlay plumbing yet; the press is + // captured and bindable, the on-screen ring is Windows-first. + info!("action ring press ignored — no overlay support on this platform yet"); + return; + }; + + let slots = cx.global::().ring_slots_for_current(); + let window = match cx.global::().window { + Some(window) => { + let refreshed = window.update(cx, |view, _, cx| { + view.slots.clone_from(&slots); + view.aim = Aim::Center; + cx.notify(); + }); + if refreshed.is_err() { + // The window was closed out from under us (display change, + // gpui teardown) — recreate below. + cx.global_mut::().window = None; + } + match cx.global::().window { + Some(window) => window, + None => match create_window(slots, cx) { + Some(window) => window, + None => return, + }, + } + } + None => match create_window(slots, cx) { + Some(window) => window, + None => return, + }, + }; + + let Some(hwnd) = window_hwnd(&window, cx) else { + warn!("action ring window has no HWND — cannot show the overlay"); + return; + }; + let scale = platform_win::window_scale(hwnd); + platform_win::show_at(hwnd, cursor, RING_WINDOW * scale); + + let overlay = cx.global_mut::(); + overlay.window = Some(window); + overlay.open = true; + overlay.center = cursor; + overlay.scale = scale; + overlay.epoch += 1; + let epoch = overlay.epoch; + debug!(x = cursor.x, y = cursor.y, "action ring opened"); + + // While open: track the global cursor for the sector highlight, and close + // on Esc or a click outside the window — inputs a no-activate window + // never receives as messages. + cx.spawn(async move |cx| { + loop { + cx.background_executor().timer(WATCH_TICK).await; + if cx.update(|cx| watch_tick(epoch, hwnd, cx)) { + break; + } + } + }) + .detach(); +} + +/// One watcher pass. Returns `true` when this watcher is done (ring closed or +/// superseded by a newer open). +fn watch_tick(epoch: u64, hwnd: isize, cx: &mut App) -> bool { + { + let overlay = cx.global::(); + if !overlay.open || overlay.epoch != epoch { + return true; + } + } + if platform_win::escape_pressed() || platform_win::clicked_outside(hwnd) { + debug!("action ring cancelled (esc / outside click)"); + close(cx); + return true; + } + let (center, scale) = { + let overlay = cx.global::(); + (overlay.center, overlay.scale) + }; + let aim = + platform_win::cursor_pos().map_or(Aim::Center, |cursor| aim_for(center, cursor, scale)); + let window = cx.global::().window; + if let Some(window) = window { + let _ = window.update(cx, |view, _, cx| { + if view.aim != aim { + view.aim = aim; + cx.notify(); + } + }); + } + false +} + +/// Confirm the current aim: fire the aimed sector's action, or cancel from +/// the dead zone. +fn confirm(cx: &mut App) { + let (center, scale) = { + let overlay = cx.global::(); + (overlay.center, overlay.scale) + }; + let aim = + platform_win::cursor_pos().map_or(Aim::Center, |cursor| aim_for(center, cursor, scale)); + match aim { + Aim::Center => debug!("action ring cancelled (centre tap)"), + Aim::Sector(slot) => fire(slot, cx), + } + close(cx); +} + +/// Fire `slot`'s bound action through the agent. +fn fire(slot: RingSlot, cx: &mut App) { + let window = cx.global::().window; + let action = window.and_then(|window| { + window + .update(cx, |view, _, _| view.action_for(slot)) + .ok() + .flatten() + }); + let Some(action) = action else { + return; + }; + info!(slot = %slot, action = %action.label(), "action ring → action"); + if cx + .global::() + .commands + .send(Command::ExecuteAction(action)) + .is_err() + { + warn!("IPC client is gone — ring action dropped"); + } +} + +/// Hide the ring. +fn close(cx: &mut App) { + let overlay = cx.global_mut::(); + overlay.open = false; + overlay.epoch += 1; + let window = overlay.window; + if let Some(window) = window + && let Some(hwnd) = window_hwnd(&window, cx) + { + platform_win::hide(hwnd); + } +} + +/// Create the overlay window (hidden — [`platform_win::show_at`] reveals it). +fn create_window(slots: Vec<(RingSlot, Action)>, cx: &mut App) -> Option> { + let bounds = Bounds { + origin: point(px(0.), px(0.)), + size: size(px(RING_WINDOW), px(RING_WINDOW)), + }; + let options = WindowOptions { + window_bounds: Some(WindowBounds::Windowed(bounds)), + titlebar: None, + app_id: Some("openlogi".to_string()), + kind: WindowKind::PopUp, + is_movable: false, + focus: false, + show: false, + window_background: gpui::WindowBackgroundAppearance::Transparent, + ..WindowOptions::default() + }; + let opened = cx.open_window(options, |_, cx| { + cx.new(|_| RingView { + slots, + aim: Aim::Center, + }) + }); + match opened { + Ok(window) => { + if let Some(hwnd) = window_hwnd(&window, cx) { + platform_win::apply_overlay_style(hwnd); + } + Some(window) + } + Err(e) => { + warn!(error = %e, "could not open the action ring window"); + None + } + } +} + +/// The raw Win32 handle of a gpui window (`None` off Windows). +fn window_hwnd(window: &WindowHandle, cx: &mut App) -> Option { + window + .update(cx, |_, window, _| { + use raw_window_handle::{HasWindowHandle as _, RawWindowHandle}; + match window.window_handle().ok()?.as_raw() { + RawWindowHandle::Win32(handle) => Some(isize::from(handle.hwnd)), + _ => None, + } + }) + .ok() + .flatten() +} + +/// The ring's root view: eight sector buttons on a circle and the centre ✕. +pub struct RingView { + slots: Vec<(RingSlot, Action)>, + aim: Aim, +} + +impl RingView { + fn action_for(&self, slot: RingSlot) -> Option { + self.slots + .iter() + .find(|(s, _)| *s == slot) + .map(|(_, action)| action.clone()) + } +} + +impl Render for RingView { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let pal = theme::palette(cx); + let accent = cx.theme().primary; + let center = RING_WINDOW / 2.; + + let sectors = self.slots.iter().map(|(slot, action)| { + let angle = slot.angle_degrees().to_radians(); + let x = center + SECTOR_RADIUS * angle.sin() - SECTOR_BUTTON / 2.; + let y = center - SECTOR_RADIUS * angle.cos() - SECTOR_BUTTON / 2.; + let aimed = self.aim == Aim::Sector(*slot); + let slot_for_click = *slot; + div() + .absolute() + .left(px(x)) + .top(px(y)) + .w(px(SECTOR_BUTTON)) + .flex() + .flex_col() + .items_center() + .gap_1() + .child( + div() + .id(SharedStringId(slot.label())) + .size(px(SECTOR_BUTTON)) + .rounded_full() + .flex() + .items_center() + .justify_center() + .bg(if aimed { accent } else { pal.surface }) + .border_1() + .border_color(if aimed { accent } else { pal.border }) + .child( + Icon::empty() + .path(action_icon_path(action)) + .size_5() + .text_color(if aimed { + cx.theme().primary_foreground + } else { + pal.text_primary + }), + ) + .on_click(cx.listener(move |_, _, _, cx| { + cx.defer(move |cx| { + fire(slot_for_click, cx); + close(cx); + }); + })), + ) + .child( + div() + .max_w(px(96.)) + .text_xs() + .text_color(if aimed { + pal.text_primary + } else { + pal.text_muted + }) + .child(tr!(action.label())), + ) + }); + + div().size_full().relative().children(sectors).child( + // Centre ✕ — click (or a second pad tap in the dead zone) cancels. + div() + .id("ring-cancel") + .absolute() + .left(px(center - CENTER_BUTTON / 2.)) + .top(px(center - CENTER_BUTTON / 2.)) + .size(px(CENTER_BUTTON)) + .rounded_full() + .flex() + .items_center() + .justify_center() + .bg(if self.aim == Aim::Center { + pal.surface_hover + } else { + pal.surface + }) + .border_1() + .border_color(pal.border) + .text_color(pal.text_muted) + .child("✕") + .on_click(cx.listener(|_, _, _, cx| { + cx.defer(close); + })), + ) + } +} + +/// Ad-hoc `ElementId` source for per-slot stateful divs. +#[derive(Clone)] +struct SharedStringId(&'static str); + +impl From for gpui::ElementId { + fn from(id: SharedStringId) -> Self { + gpui::ElementId::Name(id.0.into()) + } +} + +/// Raw Win32 plumbing for the overlay: the style bits gpui doesn't expose +/// (no-activate, topmost), show/hide without activation, physical-pixel +/// positioning, the global cursor, and the Esc / outside-click cancel +/// signals a focusless window can't observe as messages. +#[cfg(target_os = "windows")] +#[expect( + unsafe_code, + reason = "raw Win32 window-style and cursor FFI — the pinned gpui backend exposes none of it" +)] +mod platform_win { + use gpui::Point; + use gpui::point; + use windows_sys::Win32::Foundation::{POINT, RECT}; + use windows_sys::Win32::UI::HiDpi::GetDpiForWindow; + use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ + GetAsyncKeyState, VK_ESCAPE, VK_LBUTTON, + }; + use windows_sys::Win32::UI::WindowsAndMessaging::{ + GWL_EXSTYLE, GetCursorPos, GetWindowLongPtrW, GetWindowRect, HWND_TOPMOST, SW_HIDE, + SWP_NOACTIVATE, SetWindowLongPtrW, SetWindowPos, ShowWindow, WS_EX_NOACTIVATE, + WS_EX_TOOLWINDOW, + }; + + /// Add the no-activate + toolwindow ex-style bits. Topmost is applied on + /// every [`show_at`] (a style bit alone does not raise the window). + pub fn apply_overlay_style(hwnd: isize) { + // SAFETY: plain style read-modify-write on a window this process owns. + unsafe { + let hwnd = hwnd as _; + let ex = GetWindowLongPtrW(hwnd, GWL_EXSTYLE); + SetWindowLongPtrW( + hwnd, + GWL_EXSTYLE, + ex | isize::from_ne_bytes( + ((WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW) as usize).to_ne_bytes(), + ), + ); + } + } + + /// Physical-per-logical scale of the display the window is on. + #[expect( + clippy::cast_precision_loss, + reason = "display DPI values are small integers — exact in f32" + )] + pub fn window_scale(hwnd: isize) -> f32 { + // SAFETY: DPI read on a window this process owns. + let dpi = unsafe { GetDpiForWindow(hwnd as _) }; + if dpi == 0 { 1. } else { dpi as f32 / 96. } + } + + /// Centre the window (of physical size `side`) at `cursor` (physical) and + /// show it topmost without activating it. + pub fn show_at(hwnd: isize, cursor: Point, side: f32) { + #[expect( + clippy::cast_possible_truncation, + reason = "screen coordinates are far inside i32 range" + )] + let (x, y, side) = ( + (cursor.x - side / 2.) as i32, + (cursor.y - side / 2.) as i32, + side as i32, + ); + // SAFETY: position + show of a window this process owns; SWP_NOACTIVATE + // keeps focus where the user is working. + unsafe { + SetWindowPos( + hwnd as _, + HWND_TOPMOST, + x, + y, + side, + side, + SWP_NOACTIVATE | windows_sys::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW, + ); + } + } + + /// Hide the window (kept alive for the next open). + pub fn hide(hwnd: isize) { + // SAFETY: hide of a window this process owns. + unsafe { + ShowWindow(hwnd as _, SW_HIDE); + } + } + + /// Global cursor position in physical pixels. + pub fn cursor_pos() -> Option> { + let mut p = POINT { x: 0, y: 0 }; + // SAFETY: out-pointer to a stack POINT. + #[expect( + clippy::cast_precision_loss, + reason = "screen coordinates are far inside f32's exact-integer range" + )] + if unsafe { GetCursorPos(&raw mut p) } != 0 { + Some(point(p.x as f32, p.y as f32)) + } else { + None + } + } + + /// Whether Esc is down right now (edge behaviour handled by the caller's + /// close-once state machine — the ring is gone before a repeat matters). + pub fn escape_pressed() -> bool { + // SAFETY: stateless key query. + unsafe { GetAsyncKeyState(i32::from(VK_ESCAPE)).cast_unsigned() & 0x8000 != 0 } + } + + /// Whether the left button is down with the cursor outside the window — + /// the "click outside cancels" signal, observed by polling because a + /// no-activate window receives no messages for clicks elsewhere. + pub fn clicked_outside(hwnd: isize) -> bool { + // SAFETY: stateless key query + window-rect read on an owned window. + unsafe { + if GetAsyncKeyState(i32::from(VK_LBUTTON)).cast_unsigned() & 0x8000 == 0 { + return false; + } + let mut rect = RECT { + left: 0, + top: 0, + right: 0, + bottom: 0, + }; + if GetWindowRect(hwnd as _, &raw mut rect) == 0 { + return false; + } + let mut p = POINT { x: 0, y: 0 }; + if GetCursorPos(&raw mut p) == 0 { + return false; + } + p.x < rect.left || p.x > rect.right || p.y < rect.top || p.y > rect.bottom + } + } +} + +/// Non-Windows stubs: the ring is captured and bindable everywhere, but the +/// overlay window itself is Windows-first (macOS/Linux land with their own +/// platform plumbing). +#[cfg(not(target_os = "windows"))] +mod platform_win { + use gpui::Point; + + pub fn apply_overlay_style(_hwnd: isize) {} + pub fn window_scale(_hwnd: isize) -> f32 { + 1. + } + pub fn show_at(_hwnd: isize, _cursor: Point, _side: f32) {} + pub fn hide(_hwnd: isize) {} + pub fn cursor_pos() -> Option> { + None + } + pub fn escape_pressed() -> bool { + false + } + pub fn clicked_outside(_hwnd: isize) -> bool { + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn sector(center: Point, dx: f32, dy: f32) -> Aim { + aim_for(center, point(center.x + dx, center.y + dy), 1.) + } + + #[test] + fn dead_zone_aims_center() { + let c = point(500., 500.); + assert_eq!(sector(c, 0., 0.), Aim::Center); + assert_eq!(sector(c, 30., -20.), Aim::Center, "inside the dead zone"); + } + + #[test] + fn cardinal_directions_pick_their_slots() { + let c = point(500., 500.); + assert_eq!(sector(c, 0., -200.), Aim::Sector(RingSlot::North)); + assert_eq!(sector(c, 200., 0.), Aim::Sector(RingSlot::East)); + assert_eq!(sector(c, 0., 200.), Aim::Sector(RingSlot::South)); + assert_eq!(sector(c, -200., 0.), Aim::Sector(RingSlot::West)); + } + + #[test] + fn diagonals_and_sector_boundaries_route_clockwise() { + let c = point(0., 0.); + assert_eq!(sector(c, 100., -100.), Aim::Sector(RingSlot::NorthEast)); + assert_eq!(sector(c, 100., 100.), Aim::Sector(RingSlot::SouthEast)); + assert_eq!(sector(c, -100., 100.), Aim::Sector(RingSlot::SouthWest)); + assert_eq!(sector(c, -100., -100.), Aim::Sector(RingSlot::NorthWest)); + // 22.4° off vertical still rounds to North; 22.6° tips into NorthEast. + let west_of_boundary = (22.4_f32).to_radians(); + let east_of_boundary = (22.6_f32).to_radians(); + assert_eq!( + sector( + c, + 200. * west_of_boundary.sin(), + -200. * west_of_boundary.cos() + ), + Aim::Sector(RingSlot::North) + ); + assert_eq!( + sector( + c, + 200. * east_of_boundary.sin(), + -200. * east_of_boundary.cos() + ), + Aim::Sector(RingSlot::NorthEast) + ); + } + + #[test] + fn scale_grows_the_dead_zone() { + let c = point(0., 0.); + // 60 px out: a sector at 1×, still the dead zone at 2×. + assert_eq!( + aim_for(c, point(0., -60.), 1.), + Aim::Sector(RingSlot::North) + ); + assert_eq!(aim_for(c, point(0., -60.), 2.), Aim::Center); + } +} diff --git a/crates/openlogi-gui/src/state.rs b/crates/openlogi-gui/src/state.rs index c6a74711..4c5c39b3 100644 --- a/crates/openlogi-gui/src/state.rs +++ b/crates/openlogi-gui/src/state.rs @@ -1130,6 +1130,30 @@ impl AppState { ) } + /// The eight Action Ring sector actions for the selected device, defaults + /// filled — what the ring overlay renders and fires. A non-ring stored + /// binding (the pad demoted to a single action) still yields the default + /// map: the agent only routes presses to the overlay while the binding is + /// ring-shaped, so this is display data, never dispatch policy. + #[must_use] + pub fn ring_slots_for_current(&self) -> Vec<(openlogi_core::binding::RingSlot, Action)> { + use openlogi_core::binding::{RingSlot, default_binding_for}; + let stored = self + .current_record() + .map(|r| r.config_key.clone()) + .and_then(|key| self.config.bindings_for(&key).remove(&ButtonId::ActionRing)) + .filter(Binding::is_ring); + let mut binding = stored.unwrap_or_else(|| default_binding_for(ButtonId::ActionRing)); + binding.fill_ring_defaults(); + RingSlot::ALL + .into_iter() + .map(|slot| { + let action = binding.ring_action(slot).cloned().unwrap_or(Action::None); + (slot, action) + }) + .collect() + } + fn gesture_bindings_for_current(&self) -> BTreeMap { let Some(key) = self.current_record().map(|r| r.config_key.as_str()) else { return BTreeMap::new(); From e6797356449f614f1029d7116e195f3b330d3458 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 09:00:34 -0700 Subject: [PATCH 13/42] =?UTF-8?q?fix(gui):=20ring=20overlay=20polish=20?= =?UTF-8?q?=E2=80=94=20true=20transparency,=20edge=20clamp,=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three rounds of on-hardware feedback: - The gpui Transparent background also enables the DWM accent (ACCENT_ENABLE_TRANSPARENTGRADIENT), which painted a faint veil over the whole window rect — the visible "box" behind the floating ring. DirectComposition already composites per-pixel alpha, so the accent is disabled via SetWindowCompositionAttribute (resolved from user32 at runtime like gpui does; not in the import library). - The window clamps into the cursor monitor''s work area, so a ring opened at the bottom of the screen slides up instead of losing its lower buttons; show_at returns the actual centre so sector aiming tracks the clamped position. - Options+-inspired look, inverted for a dark-mode desktop: bright circular icon buttons, dark label pills floating outside the button circle (above the top sectors, beside the side ones), small centre cancel button, in a wider-than-tall compact window. --- crates/openlogi-gui/Cargo.toml | 4 + crates/openlogi-gui/src/ring.rs | 306 +++++++++++++++++++++++--------- 2 files changed, 226 insertions(+), 84 deletions(-) diff --git a/crates/openlogi-gui/Cargo.toml b/crates/openlogi-gui/Cargo.toml index 8bae3fec..111e1d8f 100644 --- a/crates/openlogi-gui/Cargo.toml +++ b/crates/openlogi-gui/Cargo.toml @@ -63,6 +63,10 @@ embed-resource = "3.0.9" [target.'cfg(target_os = "windows")'.dependencies] windows-sys = { workspace = true, features = [ "Win32_Foundation", + # MonitorFromPoint / GetMonitorInfoW for the ring's work-area clamp. + "Win32_Graphics_Gdi", + # GetProcAddress for the undocumented SetWindowCompositionAttribute. + "Win32_System_LibraryLoader", "Win32_UI_HiDpi", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_WindowsAndMessaging", diff --git a/crates/openlogi-gui/src/ring.rs b/crates/openlogi-gui/src/ring.rs index ff1697c3..a2aac62d 100644 --- a/crates/openlogi-gui/src/ring.rs +++ b/crates/openlogi-gui/src/ring.rs @@ -25,7 +25,8 @@ use std::time::Duration; use gpui::{ App, AppContext as _, Bounds, Context, InteractiveElement as _, IntoElement, ParentElement as _, Point, Render, StatefulInteractiveElement as _, Styled as _, Window, - WindowBounds, WindowHandle, WindowKind, WindowOptions, div, point, px, size, + WindowBounds, WindowHandle, WindowKind, WindowOptions, div, point, prelude::FluentBuilder as _, + px, size, }; use gpui_component::{ActiveTheme as _, Icon}; use openlogi_core::binding::{Action, RingSlot}; @@ -35,23 +36,47 @@ use tracing::{debug, info, warn}; use crate::ipc_client::Command; use crate::mouse_model::picker::action_icon_path; use crate::state::AppState; -use crate::theme; -/// Logical size of the (square) overlay window. -const RING_WINDOW: f32 = 460.; +/// Logical size of the overlay window: wider than tall because side-sector +/// labels sit *beside* the ring (the Options+ layout). Compact enough to fit +/// near screen edges in most positions; when it can't, +/// [`platform_win::show_at`] clamps it into the work area. +const RING_W: f32 = 480.; +/// See [`RING_W`]. +const RING_H: f32 = 312.; /// Radius of the circle the eight sector buttons sit on, from the centre. -const SECTOR_RADIUS: f32 = 150.; +const SECTOR_RADIUS: f32 = 96.; /// Diameter of one sector's circular icon button. -const SECTOR_BUTTON: f32 = 52.; +const SECTOR_BUTTON: f32 = 44.; /// Diameter of the centre ✕ cancel button. -const CENTER_BUTTON: f32 = 36.; +const CENTER_BUTTON: f32 = 30.; +/// Gap between a sector button's outer edge and its label pill. +const LABEL_GAP: f32 = 8.; /// Cursor distance (logical px) below which a confirm means "cancel" — the /// dead zone around the centre ✕. -const DEADZONE: f32 = 46.; +const DEADZONE: f32 = 38.; /// How often the open-ring watcher samples the global cursor (highlight) and /// the Esc / outside-click cancel signals. const WATCH_TICK: Duration = Duration::from_millis(33); +// The ring's fixed palette, deliberately theme-independent and light-on-dark +// — the inverse of the Options+ light-mode reference, because the ring floats +// over a desktop that lives in dark mode: bright circles, dark label pills. +/// Sector button fill. +const PLATE: u32 = 0x00f7_f7f9; +/// Icon strokes on an unaimed button; label pill fill. +const INK: u32 = 0x001c_1c1e; +/// Aimed icon strokes and aimed-pill text. +const WHITE: u32 = 0x00ff_ffff; +/// Label pill text. +const PILL_TEXT: u32 = 0x00f2_f2f4; +/// Centre ✕ fill. +const CROSS_BG: u32 = 0x002c_2c2e; +/// Centre ✕ fill while the cursor aims at the dead zone. +const CROSS_BG_AIMED: u32 = 0x0045_4549; +/// Centre ✕ glyph. +const CROSS_TEXT: u32 = 0x00c9_c9ce; + /// What the cursor currently points at, driven from the global cursor by the /// watcher so it works even when the pointer is outside the overlay window. #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] @@ -163,16 +188,18 @@ fn open(cx: &mut App) { return; }; let scale = platform_win::window_scale(hwnd); - platform_win::show_at(hwnd, cursor, RING_WINDOW * scale); + // The centre can differ from the cursor near a screen edge (work-area + // clamp) — the hit-test must aim from the ring's real centre. + let center = platform_win::show_at(hwnd, cursor, RING_W * scale, RING_H * scale); let overlay = cx.global_mut::(); overlay.window = Some(window); overlay.open = true; - overlay.center = cursor; + overlay.center = center; overlay.scale = scale; overlay.epoch += 1; let epoch = overlay.epoch; - debug!(x = cursor.x, y = cursor.y, "action ring opened"); + debug!(x = center.x, y = center.y, "action ring opened"); // While open: track the global cursor for the sector highlight, and close // on Esc or a click outside the window — inputs a no-activate window @@ -276,7 +303,7 @@ fn close(cx: &mut App) { fn create_window(slots: Vec<(RingSlot, Action)>, cx: &mut App) -> Option> { let bounds = Bounds { origin: point(px(0.), px(0.)), - size: size(px(RING_WINDOW), px(RING_WINDOW)), + size: size(px(RING_W), px(RING_H)), }; let options = WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), @@ -299,6 +326,7 @@ fn create_window(slots: Vec<(RingSlot, Action)>, cx: &mut App) -> Option { if let Some(hwnd) = window_hwnd(&window, cx) { platform_win::apply_overlay_style(hwnd); + platform_win::clear_accent(hwnd); } Some(window) } @@ -340,64 +368,81 @@ impl RingView { impl Render for RingView { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let pal = theme::palette(cx); + // Fixed Options+-style identity in both themes: floating black circle + // buttons, white pill labels, no panel behind anything. The theme's + // primary marks the aimed sector. let accent = cx.theme().primary; - let center = RING_WINDOW / 2.; + let (cx0, cy0) = (RING_W / 2., RING_H / 2.); - let sectors = self.slots.iter().map(|(slot, action)| { + let sectors = self.slots.iter().flat_map(|(slot, action)| { let angle = slot.angle_degrees().to_radians(); - let x = center + SECTOR_RADIUS * angle.sin() - SECTOR_BUTTON / 2.; - let y = center - SECTOR_RADIUS * angle.cos() - SECTOR_BUTTON / 2.; + let sin = angle.sin(); + let bx = cx0 + SECTOR_RADIUS * sin - SECTOR_BUTTON / 2.; + let by = cy0 - SECTOR_RADIUS * angle.cos() - SECTOR_BUTTON / 2.; let aimed = self.aim == Aim::Sector(*slot); let slot_for_click = *slot; - div() + let button = div().absolute().left(px(bx)).top(px(by)).child( + div() + .id(SharedStringId(slot.label())) + .size(px(SECTOR_BUTTON)) + .rounded_full() + .flex() + .items_center() + .justify_center() + .bg(if aimed { + accent + } else { + gpui::rgb(PLATE).into() + }) + .shadow_md() + .child( + Icon::empty() + .path(action_icon_path(action)) + .size_5() + .text_color(if aimed { + gpui::rgb(WHITE) + } else { + gpui::rgb(INK) + }), + ) + .on_click(cx.listener(move |_, _, _, cx| { + cx.defer(move |cx| { + fire(slot_for_click, cx); + close(cx); + }); + })), + ); + // The label pill floats just outside the button along its angle: + // above the top sectors, beside the side ones (the Options+ + // layout). Anchored by direction so long labels grow outward. + let anchor_r = SECTOR_RADIUS + SECTOR_BUTTON / 2. + LABEL_GAP; + let ax = cx0 + anchor_r * sin; + let ay = cy0 - anchor_r * angle.cos(); + let pill = div() + .px_2() + .py_0p5() + .rounded_md() + .bg(gpui::rgb(INK)) + .shadow_md() + .text_xs() + .font_weight(gpui::FontWeight::MEDIUM) + .text_color(gpui::rgb(PILL_TEXT)) + .when(aimed, |pill| pill.bg(accent).text_color(gpui::rgb(WHITE))) + .child(tr!(action.label())); + let row = div() .absolute() - .left(px(x)) - .top(px(y)) - .w(px(SECTOR_BUTTON)) - .flex() - .flex_col() - .items_center() - .gap_1() - .child( - div() - .id(SharedStringId(slot.label())) - .size(px(SECTOR_BUTTON)) - .rounded_full() - .flex() - .items_center() - .justify_center() - .bg(if aimed { accent } else { pal.surface }) - .border_1() - .border_color(if aimed { accent } else { pal.border }) - .child( - Icon::empty() - .path(action_icon_path(action)) - .size_5() - .text_color(if aimed { - cx.theme().primary_foreground - } else { - pal.text_primary - }), - ) - .on_click(cx.listener(move |_, _, _, cx| { - cx.defer(move |cx| { - fire(slot_for_click, cx); - close(cx); - }); - })), - ) - .child( - div() - .max_w(px(96.)) - .text_xs() - .text_color(if aimed { - pal.text_primary - } else { - pal.text_muted - }) - .child(tr!(action.label())), - ) + .top(px(ay - 11.)) + .left(px(0.)) + .w(px(RING_W)) + .flex(); + let label = if sin > 0.35 { + row.justify_start().pl(px(ax)).child(pill) + } else if sin < -0.35 { + row.justify_end().pr(px(RING_W - ax)).child(pill) + } else { + row.justify_center().child(pill) + }; + [button, label] }); div().size_full().relative().children(sectors).child( @@ -405,21 +450,21 @@ impl Render for RingView { div() .id("ring-cancel") .absolute() - .left(px(center - CENTER_BUTTON / 2.)) - .top(px(center - CENTER_BUTTON / 2.)) + .left(px(cx0 - CENTER_BUTTON / 2.)) + .top(px(cy0 - CENTER_BUTTON / 2.)) .size(px(CENTER_BUTTON)) .rounded_full() .flex() .items_center() .justify_center() .bg(if self.aim == Aim::Center { - pal.surface_hover + gpui::rgb(CROSS_BG_AIMED) } else { - pal.surface + gpui::rgb(CROSS_BG) }) - .border_1() - .border_color(pal.border) - .text_color(pal.text_muted) + .shadow_sm() + .text_xs() + .text_color(gpui::rgb(CROSS_TEXT)) .child("✕") .on_click(cx.listener(|_, _, _, cx| { cx.defer(close); @@ -451,6 +496,10 @@ mod platform_win { use gpui::Point; use gpui::point; use windows_sys::Win32::Foundation::{POINT, RECT}; + use windows_sys::Win32::Graphics::Gdi::{ + GetMonitorInfoW, MONITOR_DEFAULTTONEAREST, MONITORINFO, MonitorFromPoint, + }; + use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress}; use windows_sys::Win32::UI::HiDpi::GetDpiForWindow; use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ GetAsyncKeyState, VK_ESCAPE, VK_LBUTTON, @@ -489,31 +538,117 @@ mod platform_win { if dpi == 0 { 1. } else { dpi as f32 / 96. } } - /// Centre the window (of physical size `side`) at `cursor` (physical) and - /// show it topmost without activating it. - pub fn show_at(hwnd: isize, cursor: Point, side: f32) { + /// Centre the window (physical size `w`×`h`) at `cursor` (physical), + /// clamped into the cursor's monitor **work area** so the ring never + /// hangs off a screen edge, and show it topmost without activating it. + /// Returns the window's actual centre — near an edge it differs from the + /// cursor, and the sector hit-test must aim from where the ring really is. + pub fn show_at(hwnd: isize, cursor: Point, w: f32, h: f32) -> Point { #[expect( clippy::cast_possible_truncation, reason = "screen coordinates are far inside i32 range" )] - let (x, y, side) = ( - (cursor.x - side / 2.) as i32, - (cursor.y - side / 2.) as i32, - side as i32, - ); - // SAFETY: position + show of a window this process owns; SWP_NOACTIVATE - // keeps focus where the user is working. + let (cx, cy, w, h) = (cursor.x as i32, cursor.y as i32, w as i32, h as i32); + let mut x = cx - w / 2; + let mut y = cy - h / 2; + // SAFETY: monitor lookup for the cursor's point, then position + show + // of a window this process owns; SWP_NOACTIVATE keeps focus where the + // user is working. unsafe { + let monitor = MonitorFromPoint(POINT { x: cx, y: cy }, MONITOR_DEFAULTTONEAREST); + let zero = RECT { + left: 0, + top: 0, + right: 0, + bottom: 0, + }; + #[expect( + clippy::cast_possible_truncation, + reason = "a fixed struct size is far below u32::MAX" + )] + let mut info = MONITORINFO { + cbSize: size_of::() as u32, + rcMonitor: zero, + rcWork: zero, + dwFlags: 0, + }; + if GetMonitorInfoW(monitor, &raw mut info) != 0 { + let work = info.rcWork; + x = x.max(work.left).min(work.right - w); + y = y.max(work.top).min(work.bottom - h); + } SetWindowPos( hwnd as _, HWND_TOPMOST, x, y, - side, - side, + w, + h, SWP_NOACTIVATE | windows_sys::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW, ); } + #[expect( + clippy::cast_precision_loss, + reason = "screen coordinates are far inside f32's exact-integer range" + )] + point((x + w / 2) as f32, (y + h / 2) as f32) + } + + /// Disable the window's DWM "accent" effect. gpui's `Transparent` + /// background enables `ACCENT_ENABLE_TRANSPARENTGRADIENT`, which paints a + /// faint veil over the whole window rect; DirectComposition already + /// composites the surface with per-pixel alpha, so the veil is pure + /// artifact — the visible "box" behind the floating ring. This calls the + /// same undocumented user32 API gpui itself uses, with the accent off. + pub fn clear_accent(hwnd: isize) { + #[repr(C)] + struct AccentPolicy { + state: u32, + flags: u32, + gradient_color: u32, + animation_id: u32, + } + #[repr(C)] + struct CompositionAttribData { + attrib: u32, + data: *mut core::ffi::c_void, + size: u32, + } + const WCA_ACCENT_POLICY: u32 = 19; + type SetWca = + unsafe extern "system" fn(hwnd: isize, data: *mut CompositionAttribData) -> i32; + + // SAFETY: resolve the export from user32 (it is not in the import + // library), then call it with a stack-valid attribute payload. + unsafe { + let user32: Vec = "user32.dll\0".encode_utf16().collect(); + let module = GetModuleHandleW(user32.as_ptr()); + if module.is_null() { + return; + } + let Some(addr) = + GetProcAddress(module, c"SetWindowCompositionAttribute".as_ptr().cast()) + else { + return; + }; + let set_wca: SetWca = core::mem::transmute(addr); + let mut policy = AccentPolicy { + state: 0, // ACCENT_DISABLED + flags: 0, + gradient_color: 0, + animation_id: 0, + }; + #[expect( + clippy::cast_possible_truncation, + reason = "a fixed struct size is far below u32::MAX" + )] + let mut data = CompositionAttribData { + attrib: WCA_ACCENT_POLICY, + data: (&raw mut policy).cast(), + size: size_of::() as u32, + }; + set_wca(hwnd, &raw mut data); + } } /// Hide the window (kept alive for the next open). @@ -584,7 +719,10 @@ mod platform_win { pub fn window_scale(_hwnd: isize) -> f32 { 1. } - pub fn show_at(_hwnd: isize, _cursor: Point, _side: f32) {} + pub fn show_at(_hwnd: isize, cursor: Point, _w: f32, _h: f32) -> Point { + cursor + } + pub fn clear_accent(_hwnd: isize) {} pub fn hide(_hwnd: isize) {} pub fn cursor_pos() -> Option> { None From db96b0805944f12b955f5c0ea9c553feda7aa4a6 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 09:00:34 -0700 Subject: [PATCH 14/42] feat(hid): capture the MX Master 4 side Back/Forward buttons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hardware-confirmed: with no Logitech software running, the MX Master 4 side buttons emit no native HID button events at all — they are dead until something drives them over HID++, which Options+ silently does. Scrolling and clicks are ordinary HID; only these buttons (and the gesture button and Action Ring) are software-defined. The capture session now diverts BACK_CID/FORWARD_CID (0x53/0x56 — decimal `_c83`/`_c86` in Logi''s own asset metadata) exactly like the DPI button, and dispatches ButtonPressed(Back/Forward) on the rising edge; the watcher routes them through the user''s bindings, whose defaults are BrowserBack/BrowserForward. Devices whose table lacks the CIDs are untouched. Verified in Chrome on hardware: navigation works again with only OpenLogi running. --- crates/openlogi-hid/src/gesture.rs | 79 +++++++++++++++++++--- crates/openlogi-hid/src/gesture/tests.rs | 57 ++++++++++++++++ crates/openlogi-hid/src/reprog_controls.rs | 11 +++ 3 files changed, 138 insertions(+), 9 deletions(-) diff --git a/crates/openlogi-hid/src/gesture.rs b/crates/openlogi-hid/src/gesture.rs index 0cfad2cf..8384424f 100644 --- a/crates/openlogi-hid/src/gesture.rs +++ b/crates/openlogi-hid/src/gesture.rs @@ -96,6 +96,10 @@ pub enum GestureError { /// Movement + button state accumulated across messages. Lives behind a `Mutex` /// because the channel's read thread invokes the listener by shared reference. #[derive(Default)] +#[expect( + clippy::struct_excessive_bools, + reason = "independent rising-edge latches, one per captured control — not a state machine" +)] struct CaptureAccum { /// Mid-swipe state for the diverted dedicated gesture button (raw-XY). swipe: SwipeAccumulator, @@ -105,6 +109,11 @@ struct CaptureAccum { /// Whether the Action Ring pad was down in the last analytics event — for /// rising-edge press detection. panel_down: bool, + /// Whether the diverted side "back" button was held in the last event — + /// rising-edge press detection, like [`Self::dpi_down`]. + back_down: bool, + /// Whether the diverted side "forward" button was held in the last event. + forward_down: bool, } /// Capture the gesture button, DPI/ModeShift button, and (when @@ -187,6 +196,7 @@ pub async fn run_capture_session( gesture = armed.gesture_diverted, action_ring = armed.panel.is_some(), dpi_buttons = armed.dpi_cids.len(), + nav_buttons = armed.nav_cids.len(), thumbwheel = armed.thumb.is_some(), "control capture active" ); @@ -241,6 +251,8 @@ struct ArmedControls { panel: Option, /// DPI/ModeShift CIDs diverted as plain buttons. dpi_cids: Vec, + /// Side Back/Forward CIDs diverted as plain buttons. + nav_cids: Vec, /// `0x2150` accessor + feature index, present when the thumb wheel is /// diverted. thumb: Option<(Thumbwheel, u8)>, @@ -270,6 +282,12 @@ impl ArmedControls { for &cid in &self.dpi_cids { restore(rc.set_cid_reporting(cid, false, false).await, "DPI button"); } + for &cid in &self.nav_cids { + restore( + rc.set_cid_reporting(cid, false, false).await, + "side nav button", + ); + } } if let Some((tw, _)) = self.thumb.as_ref() { restore(tw.set_reporting(false, false).await, "thumb wheel"); @@ -336,6 +354,7 @@ async fn arm_controls( let mut gesture_diverted = false; let mut panel: Option = None; let mut dpi_cids: Vec = Vec::new(); + let mut nav_cids: Vec = Vec::new(); if let Some(info) = device .root() .get_feature(reprog_controls::FEATURE_ID) @@ -380,14 +399,18 @@ async fn arm_controls( panel = Some(PanelArming { fsb }); info!("action ring pad present — analytics capture arming on cadence"); } - for &cid in &reprog_controls::DPI_MODE_SHIFT_CIDS { - if controls.iter().any(|c| c.cid == cid && c.is_divertable()) { - rc.set_cid_reporting(cid, true, false) - .await - .map_err(|e| GestureError::Hidpp(format!("{e:?}")))?; - dpi_cids.push(cid); - } - } + dpi_cids = + divert_plain_buttons(&rc, &controls, &reprog_controls::DPI_MODE_SHIFT_CIDS).await?; + // The side Back/Forward buttons are diverted like the DPI button and + // dispatched through their bindings. On the MX Master 4 they emit no + // native HID events at all, so without this they are dead buttons — + // the job Options+ quietly does on this device. + nav_cids = divert_plain_buttons( + &rc, + &controls, + &[reprog_controls::BACK_CID, reprog_controls::FORWARD_CID], + ) + .await?; reprog = Some((rc, info.index)); } @@ -420,7 +443,12 @@ async fn arm_controls( } } - if !gesture_diverted && panel.is_none() && dpi_cids.is_empty() && thumb.is_none() { + if !gesture_diverted + && panel.is_none() + && dpi_cids.is_empty() + && nav_cids.is_empty() + && thumb.is_none() + { debug!(slot, "no capturable controls — idle session"); } Ok(ArmedControls { @@ -428,10 +456,30 @@ async fn arm_controls( gesture_diverted, panel, dpi_cids, + nav_cids, thumb, }) } +/// Divert each of `candidates` that the control table lists as divertable, +/// as a plain button (no raw-XY); returns the CIDs actually diverted. +async fn divert_plain_buttons( + rc: &ReprogControlsV4, + controls: &[reprog_controls::CtrlIdInfo], + candidates: &[u16], +) -> Result, GestureError> { + let mut diverted = Vec::new(); + for &cid in candidates { + if controls.iter().any(|c| c.cid == cid && c.is_divertable()) { + rc.set_cid_reporting(cid, true, false) + .await + .map_err(|e| GestureError::Hidpp(format!("{e:?}")))?; + diverted.push(cid); + } + } + Ok(diverted) +} + /// Log (don't propagate) a failure to hand a control back to the firmware. fn restore(result: Result<(), E>, what: &str) { if let Err(e) = result { @@ -488,6 +536,19 @@ fn handle_reprog( let _ = sink.send(CapturedInput::ButtonPressed(ButtonId::DpiToggle)); } acc.dpi_down = dpi_down; + + // The diverted side buttons dispatch through their bindings on + // the rising edge, like the DPI button. + let back_down = cids.contains(&reprog_controls::BACK_CID); + if back_down && !acc.back_down { + let _ = sink.send(CapturedInput::ButtonPressed(ButtonId::Back)); + } + acc.back_down = back_down; + let forward_down = cids.contains(&reprog_controls::FORWARD_CID); + if forward_down && !acc.forward_down { + let _ = sink.send(CapturedInput::ButtonPressed(ButtonId::Forward)); + } + acc.forward_down = forward_down; } RawControlEvent::RawXy { dx, dy } => { // Commit the instant a clean direction emerges (mid-swipe, once per diff --git a/crates/openlogi-hid/src/gesture/tests.rs b/crates/openlogi-hid/src/gesture/tests.rs index 6969fc7c..e09d4bd9 100644 --- a/crates/openlogi-hid/src/gesture/tests.rs +++ b/crates/openlogi-hid/src/gesture/tests.rs @@ -154,6 +154,63 @@ fn a_ring_tap_re_arms_after_release() { assert!(rx.try_recv().is_err()); } +#[test] +fn diverted_side_buttons_press_on_the_rising_edge() { + // The MX Master 4's Back/Forward emit no native HID events; the session + // diverts them, so each hold must dispatch exactly one press. + let (tx, mut rx) = mpsc::unbounded_channel(); + let mut acc = CaptureAccum::default(); + let back = RawControlEvent::DivertedButtons([reprog_controls::BACK_CID, 0, 0, 0]); + let up = RawControlEvent::DivertedButtons([0, 0, 0, 0]); + + handle_reprog(&mut acc, back, &[], &tx); + handle_reprog(&mut acc, back, &[], &tx); // still held — no repeat + handle_reprog(&mut acc, up, &[], &tx); + handle_reprog( + &mut acc, + RawControlEvent::DivertedButtons([reprog_controls::FORWARD_CID, 0, 0, 0]), + &[], + &tx, + ); + + assert_eq!( + rx.try_recv(), + Ok(CapturedInput::ButtonPressed(ButtonId::Back)) + ); + assert_eq!( + rx.try_recv(), + Ok(CapturedInput::ButtonPressed(ButtonId::Forward)), + "a release re-arms; the other button has its own edge" + ); + assert!(rx.try_recv().is_err(), "a held button presses once"); +} + +#[test] +fn both_side_buttons_in_one_frame_press_both() { + let (tx, mut rx) = mpsc::unbounded_channel(); + let mut acc = CaptureAccum::default(); + handle_reprog( + &mut acc, + RawControlEvent::DivertedButtons([ + reprog_controls::BACK_CID, + reprog_controls::FORWARD_CID, + 0, + 0, + ]), + &[], + &tx, + ); + assert_eq!( + rx.try_recv(), + Ok(CapturedInput::ButtonPressed(ButtonId::Back)) + ); + assert_eq!( + rx.try_recv(), + Ok(CapturedInput::ButtonPressed(ButtonId::Forward)) + ); + assert!(rx.try_recv().is_err()); +} + #[test] fn click_telemetry_cids_never_open_the_ring() { // 0x0050/0x0051 are the LEFT/RIGHT mouse buttons' analytics CIDs, not the diff --git a/crates/openlogi-hid/src/reprog_controls.rs b/crates/openlogi-hid/src/reprog_controls.rs index 6a763430..c81115a5 100644 --- a/crates/openlogi-hid/src/reprog_controls.rs +++ b/crates/openlogi-hid/src/reprog_controls.rs @@ -86,6 +86,17 @@ pub const RIGHT_BUTTON_CID: u16 = 0x0051; pub const PANEL_DIAG_ANALYTICS_CIDS: [u16; 3] = [ACTION_RING_CID, LEFT_BUTTON_CID, RIGHT_BUTTON_CID]; +/// Control ID of the thumb-side "back" button (Logi's decimal `_c83` in the +/// asset metadata). On the MX Master 4 the side buttons emit **no native +/// HID button events at all** — hardware-confirmed 2026-07-20: with no +/// Logitech software running they do nothing in any app — so the capture +/// session diverts them and dispatches their bindings, the same job +/// Options+ quietly does on this device. +pub const BACK_CID: u16 = 0x0053; + +/// Control ID of the thumb-side "forward" button (`_c86`) — see [`BACK_CID`]. +pub const FORWARD_CID: u16 = 0x0056; + /// Control IDs of the "DPI / ModeShift" button family. Whichever a device /// exposes (and can divert) is captured and mapped to /// [`ButtonId::DpiToggle`](openlogi_core::binding::ButtonId::DpiToggle): the MX From a7747c3eea79f71a26ec547b606538fd7bd28690 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 09:42:37 -0700 Subject: [PATCH 15/42] =?UTF-8?q?fix(gui):=20frameless=20ring=20popup=20?= =?UTF-8?q?=E2=80=94=20window=20region,=20re-asserted=20transparency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two rounds of on-hardware feedback: - The visible "box" behind the ring had two causes. The app''s theme plumbing re-stamps window background appearance across every window when a theme applies, flipping the overlay back to Opaque (whose clear colour is solid white) — now re-asserted Transparent on every open. And regardless of what the compositor does with surface alpha, the window is now clipped to a region of exactly its nine circles (SetWindowRgn), so no rectangle exists to show a backdrop and clicks between the circles fall through to the app underneath. - The popup shows only the circles and the centre cancel button — the floating labels belong to the customization menu, not the popup — and the palette inverts to bright-on-dark (the user''s desktop lives in dark mode). The window shrinks to a tight square. --- crates/openlogi-gui/src/ring.rs | 142 +++++++++++++++++++------------- 1 file changed, 85 insertions(+), 57 deletions(-) diff --git a/crates/openlogi-gui/src/ring.rs b/crates/openlogi-gui/src/ring.rs index a2aac62d..b644aecc 100644 --- a/crates/openlogi-gui/src/ring.rs +++ b/crates/openlogi-gui/src/ring.rs @@ -25,8 +25,7 @@ use std::time::Duration; use gpui::{ App, AppContext as _, Bounds, Context, InteractiveElement as _, IntoElement, ParentElement as _, Point, Render, StatefulInteractiveElement as _, Styled as _, Window, - WindowBounds, WindowHandle, WindowKind, WindowOptions, div, point, prelude::FluentBuilder as _, - px, size, + WindowBounds, WindowHandle, WindowKind, WindowOptions, div, point, px, size, }; use gpui_component::{ActiveTheme as _, Icon}; use openlogi_core::binding::{Action, RingSlot}; @@ -37,21 +36,17 @@ use crate::ipc_client::Command; use crate::mouse_model::picker::action_icon_path; use crate::state::AppState; -/// Logical size of the overlay window: wider than tall because side-sector -/// labels sit *beside* the ring (the Options+ layout). Compact enough to fit -/// near screen edges in most positions; when it can't, -/// [`platform_win::show_at`] clamps it into the work area. -const RING_W: f32 = 480.; -/// See [`RING_W`]. -const RING_H: f32 = 312.; +/// Logical size of the (square) overlay window — just big enough for the +/// button circle plus shadows. The popup carries no labels, so it stays +/// tight; near a screen edge [`platform_win::show_at`] clamps it into the +/// work area. +const RING_WINDOW: f32 = 288.; /// Radius of the circle the eight sector buttons sit on, from the centre. const SECTOR_RADIUS: f32 = 96.; /// Diameter of one sector's circular icon button. const SECTOR_BUTTON: f32 = 44.; /// Diameter of the centre ✕ cancel button. const CENTER_BUTTON: f32 = 30.; -/// Gap between a sector button's outer edge and its label pill. -const LABEL_GAP: f32 = 8.; /// Cursor distance (logical px) below which a confirm means "cancel" — the /// dead zone around the centre ✕. const DEADZONE: f32 = 38.; @@ -64,12 +59,10 @@ const WATCH_TICK: Duration = Duration::from_millis(33); // over a desktop that lives in dark mode: bright circles, dark label pills. /// Sector button fill. const PLATE: u32 = 0x00f7_f7f9; -/// Icon strokes on an unaimed button; label pill fill. +/// Icon strokes on an unaimed button. const INK: u32 = 0x001c_1c1e; -/// Aimed icon strokes and aimed-pill text. +/// Aimed icon strokes. const WHITE: u32 = 0x00ff_ffff; -/// Label pill text. -const PILL_TEXT: u32 = 0x00f2_f2f4; /// Centre ✕ fill. const CROSS_BG: u32 = 0x002c_2c2e; /// Centre ✕ fill while the cursor aims at the dead zone. @@ -187,10 +180,21 @@ fn open(cx: &mut App) { warn!("action ring window has no HWND — cannot show the overlay"); return; }; + // The app's theme plumbing re-stamps window background appearance across + // every window when a theme applies, flipping this overlay back to Opaque + // — whose clear colour is solid white, i.e. the "box behind the ring". + // Re-assert transparency on every open, then strip the DWM accent veil + // the Transparent path re-enables (see [`platform_win::clear_accent`]). + let _ = window.update(cx, |_, window, _| { + window.set_background_appearance(gpui::WindowBackgroundAppearance::Transparent); + }); + platform_win::clear_accent(hwnd); + let scale = platform_win::window_scale(hwnd); + platform_win::apply_ring_region(hwnd, scale); // The centre can differ from the cursor near a screen edge (work-area // clamp) — the hit-test must aim from the ring's real centre. - let center = platform_win::show_at(hwnd, cursor, RING_W * scale, RING_H * scale); + let center = platform_win::show_at(hwnd, cursor, RING_WINDOW * scale, RING_WINDOW * scale); let overlay = cx.global_mut::(); overlay.window = Some(window); @@ -303,7 +307,7 @@ fn close(cx: &mut App) { fn create_window(slots: Vec<(RingSlot, Action)>, cx: &mut App) -> Option> { let bounds = Bounds { origin: point(px(0.), px(0.)), - size: size(px(RING_W), px(RING_H)), + size: size(px(RING_WINDOW), px(RING_WINDOW)), }; let options = WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), @@ -368,20 +372,20 @@ impl RingView { impl Render for RingView { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - // Fixed Options+-style identity in both themes: floating black circle - // buttons, white pill labels, no panel behind anything. The theme's + // The popup is just the eight circles and the centre ✕ — no labels + // (those belong to the customization page) and no panel behind + // anything. Fixed bright-on-dark identity in both themes; the theme's // primary marks the aimed sector. let accent = cx.theme().primary; - let (cx0, cy0) = (RING_W / 2., RING_H / 2.); + let (cx0, cy0) = (RING_WINDOW / 2., RING_WINDOW / 2.); - let sectors = self.slots.iter().flat_map(|(slot, action)| { + let sectors = self.slots.iter().map(|(slot, action)| { let angle = slot.angle_degrees().to_radians(); - let sin = angle.sin(); - let bx = cx0 + SECTOR_RADIUS * sin - SECTOR_BUTTON / 2.; + let bx = cx0 + SECTOR_RADIUS * angle.sin() - SECTOR_BUTTON / 2.; let by = cy0 - SECTOR_RADIUS * angle.cos() - SECTOR_BUTTON / 2.; let aimed = self.aim == Aim::Sector(*slot); let slot_for_click = *slot; - let button = div().absolute().left(px(bx)).top(px(by)).child( + div().absolute().left(px(bx)).top(px(by)).child( div() .id(SharedStringId(slot.label())) .size(px(SECTOR_BUTTON)) @@ -411,38 +415,7 @@ impl Render for RingView { close(cx); }); })), - ); - // The label pill floats just outside the button along its angle: - // above the top sectors, beside the side ones (the Options+ - // layout). Anchored by direction so long labels grow outward. - let anchor_r = SECTOR_RADIUS + SECTOR_BUTTON / 2. + LABEL_GAP; - let ax = cx0 + anchor_r * sin; - let ay = cy0 - anchor_r * angle.cos(); - let pill = div() - .px_2() - .py_0p5() - .rounded_md() - .bg(gpui::rgb(INK)) - .shadow_md() - .text_xs() - .font_weight(gpui::FontWeight::MEDIUM) - .text_color(gpui::rgb(PILL_TEXT)) - .when(aimed, |pill| pill.bg(accent).text_color(gpui::rgb(WHITE))) - .child(tr!(action.label())); - let row = div() - .absolute() - .top(px(ay - 11.)) - .left(px(0.)) - .w(px(RING_W)) - .flex(); - let label = if sin > 0.35 { - row.justify_start().pl(px(ax)).child(pill) - } else if sin < -0.35 { - row.justify_end().pr(px(RING_W - ax)).child(pill) - } else { - row.justify_center().child(pill) - }; - [button, label] + ) }); div().size_full().relative().children(sectors).child( @@ -497,7 +470,8 @@ mod platform_win { use gpui::point; use windows_sys::Win32::Foundation::{POINT, RECT}; use windows_sys::Win32::Graphics::Gdi::{ - GetMonitorInfoW, MONITOR_DEFAULTTONEAREST, MONITORINFO, MonitorFromPoint, + CombineRgn, CreateEllipticRgn, DeleteObject, GetMonitorInfoW, MONITOR_DEFAULTTONEAREST, + MONITORINFO, MonitorFromPoint, RGN_OR, SetWindowRgn, }; use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress}; use windows_sys::Win32::UI::HiDpi::GetDpiForWindow; @@ -594,6 +568,59 @@ mod platform_win { point((x + w / 2) as f32, (y + h / 2) as f32) } + /// Clip the window to just the ring's circles — the eight sector buttons + /// and the centre ✕. Whatever the compositor decides about the surface's + /// alpha, no window rectangle exists to show a backdrop (the same region + /// trick other overlay apps use on Windows), and clicks between the + /// circles fall through to whatever is underneath. Coordinates are + /// physical pixels; rebuilt per open because the display scale can change + /// between monitors. + pub fn apply_ring_region(hwnd: isize, scale: f32) { + use openlogi_core::binding::RingSlot; + + let c = super::RING_WINDOW / 2.; + // A little margin keeps each button's shadow from clipping flat. + let sector_r = super::SECTOR_BUTTON / 2. + 4.; + let center_r = super::CENTER_BUTTON / 2. + 3.; + #[expect( + clippy::cast_possible_truncation, + reason = "window-local coordinates are far inside i32 range" + )] + let p = |v: f32| (v * scale).round() as i32; + // SAFETY: plain GDI region construction on a window this process + // owns; SetWindowRgn takes ownership of the combined region on + // success, and each per-circle scratch region is deleted after it is + // OR-ed in. + unsafe { + let region = CreateEllipticRgn( + p(c - center_r), + p(c - center_r), + p(c + center_r), + p(c + center_r), + ); + if region.is_null() { + return; + } + for slot in RingSlot::ALL { + let angle = slot.angle_degrees().to_radians(); + let bx = c + super::SECTOR_RADIUS * angle.sin(); + let by = c - super::SECTOR_RADIUS * angle.cos(); + let circle = CreateEllipticRgn( + p(bx - sector_r), + p(by - sector_r), + p(bx + sector_r), + p(by + sector_r), + ); + if circle.is_null() { + continue; + } + CombineRgn(region, region, circle, RGN_OR); + DeleteObject(circle.cast()); + } + SetWindowRgn(hwnd as _, region, 1); + } + } + /// Disable the window's DWM "accent" effect. gpui's `Transparent` /// background enables `ACCENT_ENABLE_TRANSPARENTGRADIENT`, which paints a /// faint veil over the whole window rect; DirectComposition already @@ -722,6 +749,7 @@ mod platform_win { pub fn show_at(_hwnd: isize, cursor: Point, _w: f32, _h: f32) -> Point { cursor } + pub fn apply_ring_region(_hwnd: isize, _scale: f32) {} pub fn clear_accent(_hwnd: isize) {} pub fn hide(_hwnd: isize) {} pub fn cursor_pos() -> Option> { From a7f0ecb01f0803b6dbb9a40170356a5dd694d3a8 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Mon, 20 Jul 2026 09:42:38 -0700 Subject: [PATCH 16/42] feat(gui): Action Ring customization menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clicking the pad''s hotspot (or its side label, now summarized as "8 actions") opens a two-level menu mirroring the gesture button''s: a compass-grid navigator showing all eight slots in their on-screen arrangement, and a per-slot action-catalog flyout. Picking persists via Config::set_ring_slot — which seeds a complete default ring around the first edit and promotes a single-action pad back to a ring (guarded by set_ring_slot_seeds_a_complete_map) — and the agent reloads, so the next popup already uses the new layout. RingSlot gains compass glyphs; the seven new UI strings are inserted at the same position in all twenty locale files (parity test green). --- crates/openlogi-core/src/binding.rs | 16 ++ crates/openlogi-core/src/config.rs | 55 ++++++ crates/openlogi-gui/locales/da.yml | 7 + crates/openlogi-gui/locales/de.yml | 7 + crates/openlogi-gui/locales/el.yml | 7 + crates/openlogi-gui/locales/en.yml | 7 + crates/openlogi-gui/locales/es.yml | 7 + crates/openlogi-gui/locales/fi.yml | 7 + crates/openlogi-gui/locales/fr.yml | 7 + crates/openlogi-gui/locales/it.yml | 7 + crates/openlogi-gui/locales/ja.yml | 7 + crates/openlogi-gui/locales/ko.yml | 7 + crates/openlogi-gui/locales/nb.yml | 7 + crates/openlogi-gui/locales/nl.yml | 7 + crates/openlogi-gui/locales/pl.yml | 7 + crates/openlogi-gui/locales/pt-BR.yml | 7 + crates/openlogi-gui/locales/pt-PT.yml | 7 + crates/openlogi-gui/locales/ru.yml | 7 + crates/openlogi-gui/locales/sv.yml | 7 + crates/openlogi-gui/locales/zh-CN.yml | 7 + crates/openlogi-gui/locales/zh-HK.yml | 7 + crates/openlogi-gui/locales/zh-TW.yml | 7 + crates/openlogi-gui/src/mouse_model/picker.rs | 163 ++++++++++++++++++ crates/openlogi-gui/src/mouse_model/view.rs | 75 +++++++- crates/openlogi-gui/src/state.rs | 12 ++ 25 files changed, 460 insertions(+), 1 deletion(-) diff --git a/crates/openlogi-core/src/binding.rs b/crates/openlogi-core/src/binding.rs index c4a01eac..2bafdbf4 100644 --- a/crates/openlogi-core/src/binding.rs +++ b/crates/openlogi-core/src/binding.rs @@ -237,6 +237,22 @@ impl RingSlot { } } + /// Arrow glyph for compact list rendering, pointing at the slot's + /// position on the ring. + #[must_use] + pub fn glyph(self) -> &'static str { + match self { + RingSlot::North => "↑", + RingSlot::NorthEast => "↗", + RingSlot::East => "→", + RingSlot::SouthEast => "↘", + RingSlot::South => "↓", + RingSlot::SouthWest => "↙", + RingSlot::West => "←", + RingSlot::NorthWest => "↖", + } + } + /// The slot's centre angle in degrees, measured clockwise from straight /// up — the overlay's sector geometry (each sector spans ±22.5° around /// this). diff --git a/crates/openlogi-core/src/config.rs b/crates/openlogi-core/src/config.rs index dead22c3..ca96cf05 100644 --- a/crates/openlogi-core/src/config.rs +++ b/crates/openlogi-core/src/config.rs @@ -257,6 +257,33 @@ impl Config { } } + /// Records `action` for one `slot` of the Action Ring, creating the + /// device entry if needed. A missing — or single-action — `ActionRing` + /// binding is replaced by the canonical default ring first and any + /// unbound slots are seeded, so the edited map is always complete + /// (guarded by `set_ring_slot_seeds_a_complete_map`). + pub fn set_ring_slot( + &mut self, + device_key: &str, + slot: crate::binding::RingSlot, + action: Action, + ) { + let entry = self + .devices + .entry(device_key.to_string()) + .or_default() + .bindings + .entry(ButtonId::ActionRing) + .or_insert_with(|| default_binding_for(ButtonId::ActionRing)); + if !entry.is_ring() { + *entry = default_binding_for(ButtonId::ActionRing); + } + entry.fill_ring_defaults(); + if let Binding::Ring(map) = entry { + map.insert(slot, action); + } + } + /// Ensure `button` on `device_key` is a [`Binding::Gesture`], creating the /// device + a default binding if needed and upgrading a [`Binding::Single`] /// in place (its action kept as the [`GestureDirection::Click`]). Returns the @@ -609,6 +636,34 @@ mod tests { assert!(cfg.devices.is_empty()); } + #[test] + fn set_ring_slot_seeds_a_complete_map() { + use crate::binding::{RingSlot, default_ring_binding}; + + // Fresh device: one edit yields a full eight-slot ring around it. + let mut cfg = Config::default(); + cfg.set_ring_slot("2b042", RingSlot::SouthWest, Action::CaptureRegion); + let Some(Binding::Ring(map)) = cfg.bindings_for("2b042").remove(&ButtonId::ActionRing) + else { + panic!("ActionRing must be ring-shaped after a slot edit"); + }; + assert_eq!(map.len(), RingSlot::ALL.len(), "all slots seeded"); + assert_eq!(map[&RingSlot::SouthWest], Action::CaptureRegion); + assert_eq!(map[&RingSlot::North], default_ring_binding(RingSlot::North)); + + // A pad demoted to a single action is promoted back to a ring, and + // the whole map survives a save/load roundtrip. + cfg.set_binding("2b042", ButtonId::ActionRing, Action::Copy.into()); + cfg.set_ring_slot("2b042", RingSlot::East, Action::LockScreen); + let back = write_and_read(&cfg); + let Some(Binding::Ring(map)) = back.bindings_for("2b042").remove(&ButtonId::ActionRing) + else { + panic!("ring must survive the roundtrip"); + }; + assert_eq!(map[&RingSlot::East], Action::LockScreen); + assert_eq!(map.len(), RingSlot::ALL.len()); + } + #[test] fn lighting_roundtrips_per_device() { let mut cfg = Config::default(); diff --git a/crates/openlogi-gui/locales/da.yml b/crates/openlogi-gui/locales/da.yml index 3e1f54e8..c53a3d9e 100644 --- a/crates/openlogi-gui/locales/da.yml +++ b/crates/openlogi-gui/locales/da.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Tommelfingerhjul" "Gesture Button": "Bevægelsesknap" "Action Ring": "Handlingsring" +"8 actions": "8 handlinger" +"Top": "Øverst" +"Top Right": "Øverst til højre" +"Bottom Right": "Nederst til højre" +"Bottom": "Nederst" +"Bottom Left": "Nederst til venstre" +"Top Left": "Øverst til venstre" "Up": "Op" "Down": "Ned" "Left": "Venstre" diff --git a/crates/openlogi-gui/locales/de.yml b/crates/openlogi-gui/locales/de.yml index 689a4653..2dbb8396 100644 --- a/crates/openlogi-gui/locales/de.yml +++ b/crates/openlogi-gui/locales/de.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Daumenrad" "Gesture Button": "Gestentaste" "Action Ring": "Aktionsring" +"8 actions": "8 Aktionen" +"Top": "Oben" +"Top Right": "Oben rechts" +"Bottom Right": "Unten rechts" +"Bottom": "Unten" +"Bottom Left": "Unten links" +"Top Left": "Oben links" "Up": "Oben" "Down": "Unten" "Left": "Links" diff --git a/crates/openlogi-gui/locales/el.yml b/crates/openlogi-gui/locales/el.yml index 7c60e3a9..a54bffd3 100644 --- a/crates/openlogi-gui/locales/el.yml +++ b/crates/openlogi-gui/locales/el.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Πλαϊνός τροχός" "Gesture Button": "Κουμπί χειρονομιών" "Action Ring": "Δακτύλιος ενεργειών" +"8 actions": "8 ενέργειες" +"Top": "Επάνω" +"Top Right": "Επάνω δεξιά" +"Bottom Right": "Κάτω δεξιά" +"Bottom": "Κάτω" +"Bottom Left": "Κάτω αριστερά" +"Top Left": "Επάνω αριστερά" "Up": "Πάνω" "Down": "Κάτω" "Left": "Αριστερά" diff --git a/crates/openlogi-gui/locales/en.yml b/crates/openlogi-gui/locales/en.yml index f4ac457c..d21b173d 100644 --- a/crates/openlogi-gui/locales/en.yml +++ b/crates/openlogi-gui/locales/en.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Thumb Wheel" "Gesture Button": "Gesture Button" "Action Ring": "Action Ring" +"8 actions": "8 actions" +"Top": "Top" +"Top Right": "Top Right" +"Bottom Right": "Bottom Right" +"Bottom": "Bottom" +"Bottom Left": "Bottom Left" +"Top Left": "Top Left" "Up": "Up" "Down": "Down" "Left": "Left" diff --git a/crates/openlogi-gui/locales/es.yml b/crates/openlogi-gui/locales/es.yml index 68229912..1fe22638 100644 --- a/crates/openlogi-gui/locales/es.yml +++ b/crates/openlogi-gui/locales/es.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Rueda lateral" "Gesture Button": "Botón de gestos" "Action Ring": "Anillo de acciones" +"8 actions": "8 acciones" +"Top": "Arriba" +"Top Right": "Arriba a la derecha" +"Bottom Right": "Abajo a la derecha" +"Bottom": "Abajo" +"Bottom Left": "Abajo a la izquierda" +"Top Left": "Arriba a la izquierda" "Up": "Arriba" "Down": "Abajo" "Left": "Izquierda" diff --git a/crates/openlogi-gui/locales/fi.yml b/crates/openlogi-gui/locales/fi.yml index 3ddd3791..ebbb7b51 100644 --- a/crates/openlogi-gui/locales/fi.yml +++ b/crates/openlogi-gui/locales/fi.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Peukalorulla" "Gesture Button": "Eletoiminto" "Action Ring": "Toimintorengas" +"8 actions": "8 toimintoa" +"Top": "Ylhäällä" +"Top Right": "Oikealla ylhäällä" +"Bottom Right": "Oikealla alhaalla" +"Bottom": "Alhaalla" +"Bottom Left": "Vasemmalla alhaalla" +"Top Left": "Vasemmalla ylhäällä" "Up": "Ylös" "Down": "Alas" "Left": "Vasen" diff --git a/crates/openlogi-gui/locales/fr.yml b/crates/openlogi-gui/locales/fr.yml index 492e8ee3..89738f10 100644 --- a/crates/openlogi-gui/locales/fr.yml +++ b/crates/openlogi-gui/locales/fr.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Molette latérale" "Gesture Button": "Bouton de gestes" "Action Ring": "Anneau d'actions" +"8 actions": "8 actions" +"Top": "Haut" +"Top Right": "En haut à droite" +"Bottom Right": "En bas à droite" +"Bottom": "Bas" +"Bottom Left": "En bas à gauche" +"Top Left": "En haut à gauche" "Up": "Haut" "Down": "Bas" "Left": "Gauche" diff --git a/crates/openlogi-gui/locales/it.yml b/crates/openlogi-gui/locales/it.yml index 5e4e05a4..31511eb4 100644 --- a/crates/openlogi-gui/locales/it.yml +++ b/crates/openlogi-gui/locales/it.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Volante da pollice" "Gesture Button": "Pulsante gesture" "Action Ring": "Anello delle azioni" +"8 actions": "8 azioni" +"Top": "In alto" +"Top Right": "In alto a destra" +"Bottom Right": "In basso a destra" +"Bottom": "In basso" +"Bottom Left": "In basso a sinistra" +"Top Left": "In alto a sinistra" "Up": "Su" "Down": "Giù" "Left": "Sinistra" diff --git a/crates/openlogi-gui/locales/ja.yml b/crates/openlogi-gui/locales/ja.yml index 6dd32a1d..d40fc223 100644 --- a/crates/openlogi-gui/locales/ja.yml +++ b/crates/openlogi-gui/locales/ja.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "サムホイール" "Gesture Button": "ジェスチャーボタン" "Action Ring": "アクションリング" +"8 actions": "8つのアクション" +"Top": "上" +"Top Right": "右上" +"Bottom Right": "右下" +"Bottom": "下" +"Bottom Left": "左下" +"Top Left": "左上" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/ko.yml b/crates/openlogi-gui/locales/ko.yml index 89bf4265..7b838e4b 100644 --- a/crates/openlogi-gui/locales/ko.yml +++ b/crates/openlogi-gui/locales/ko.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "썸휠" "Gesture Button": "제스처 버튼" "Action Ring": "액션 링" +"8 actions": "8개 동작" +"Top": "위" +"Top Right": "오른쪽 위" +"Bottom Right": "오른쪽 아래" +"Bottom": "아래" +"Bottom Left": "왼쪽 아래" +"Top Left": "왼쪽 위" "Up": "위" "Down": "아래" "Left": "왼쪽" diff --git a/crates/openlogi-gui/locales/nb.yml b/crates/openlogi-gui/locales/nb.yml index 123a9a7e..364d469e 100644 --- a/crates/openlogi-gui/locales/nb.yml +++ b/crates/openlogi-gui/locales/nb.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Tommelhjul" "Gesture Button": "Bevegelsesknapp" "Action Ring": "Handlingsring" +"8 actions": "8 handlinger" +"Top": "Øverst" +"Top Right": "Øverst til høyre" +"Bottom Right": "Nederst til høyre" +"Bottom": "Nederst" +"Bottom Left": "Nederst til venstre" +"Top Left": "Øverst til venstre" "Up": "Opp" "Down": "Ned" "Left": "Venstre" diff --git a/crates/openlogi-gui/locales/nl.yml b/crates/openlogi-gui/locales/nl.yml index b915c0bb..5513582d 100644 --- a/crates/openlogi-gui/locales/nl.yml +++ b/crates/openlogi-gui/locales/nl.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Duimwiel" "Gesture Button": "Gebarenknop" "Action Ring": "Actiering" +"8 actions": "8 acties" +"Top": "Boven" +"Top Right": "Rechtsboven" +"Bottom Right": "Rechtsonder" +"Bottom": "Onder" +"Bottom Left": "Linksonder" +"Top Left": "Linksboven" "Up": "Omhoog" "Down": "Omlaag" "Left": "Links" diff --git a/crates/openlogi-gui/locales/pl.yml b/crates/openlogi-gui/locales/pl.yml index 03c7666c..89da7898 100644 --- a/crates/openlogi-gui/locales/pl.yml +++ b/crates/openlogi-gui/locales/pl.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Rolka kciukowa" "Gesture Button": "Przycisk gestów" "Action Ring": "Pierścień akcji" +"8 actions": "8 akcji" +"Top": "Góra" +"Top Right": "Prawy górny" +"Bottom Right": "Prawy dolny" +"Bottom": "Dół" +"Bottom Left": "Lewy dolny" +"Top Left": "Lewy górny" "Up": "W górę" "Down": "W dół" "Left": "W lewo" diff --git a/crates/openlogi-gui/locales/pt-BR.yml b/crates/openlogi-gui/locales/pt-BR.yml index db26c0f4..4a5ee1b0 100644 --- a/crates/openlogi-gui/locales/pt-BR.yml +++ b/crates/openlogi-gui/locales/pt-BR.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Roda do Polegar" "Gesture Button": "Botão de Gesto" "Action Ring": "Anel de Ações" +"8 actions": "8 ações" +"Top": "Superior" +"Top Right": "Superior direito" +"Bottom Right": "Inferior direito" +"Bottom": "Inferior" +"Bottom Left": "Inferior esquerdo" +"Top Left": "Superior esquerdo" "Up": "Cima" "Down": "Baixo" "Left": "Esquerda" diff --git a/crates/openlogi-gui/locales/pt-PT.yml b/crates/openlogi-gui/locales/pt-PT.yml index ff627501..1581e5b9 100644 --- a/crates/openlogi-gui/locales/pt-PT.yml +++ b/crates/openlogi-gui/locales/pt-PT.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Roda de polegar" "Gesture Button": "Botão de gesto" "Action Ring": "Anel de ações" +"8 actions": "8 ações" +"Top": "Superior" +"Top Right": "Superior direito" +"Bottom Right": "Inferior direito" +"Bottom": "Inferior" +"Bottom Left": "Inferior esquerdo" +"Top Left": "Superior esquerdo" "Up": "Cima" "Down": "Baixo" "Left": "Esquerda" diff --git a/crates/openlogi-gui/locales/ru.yml b/crates/openlogi-gui/locales/ru.yml index b5f1cd94..f3ad45d0 100644 --- a/crates/openlogi-gui/locales/ru.yml +++ b/crates/openlogi-gui/locales/ru.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Колесо под большой палец" "Gesture Button": "Кнопка жестов" "Action Ring": "Кольцо действий" +"8 actions": "8 действий" +"Top": "Верх" +"Top Right": "Справа сверху" +"Bottom Right": "Справа снизу" +"Bottom": "Низ" +"Bottom Left": "Слева снизу" +"Top Left": "Слева сверху" "Up": "Вверх" "Down": "Вниз" "Left": "Влево" diff --git a/crates/openlogi-gui/locales/sv.yml b/crates/openlogi-gui/locales/sv.yml index cbf34599..ece43eaa 100644 --- a/crates/openlogi-gui/locales/sv.yml +++ b/crates/openlogi-gui/locales/sv.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "Tumhjul" "Gesture Button": "Gestknapp" "Action Ring": "Åtgärdsring" +"8 actions": "8 åtgärder" +"Top": "Överst" +"Top Right": "Uppe till höger" +"Bottom Right": "Nere till höger" +"Bottom": "Nederst" +"Bottom Left": "Nere till vänster" +"Top Left": "Uppe till vänster" "Up": "Upp" "Down": "Ned" "Left": "Vänster" diff --git a/crates/openlogi-gui/locales/zh-CN.yml b/crates/openlogi-gui/locales/zh-CN.yml index 340f34e9..015279bd 100644 --- a/crates/openlogi-gui/locales/zh-CN.yml +++ b/crates/openlogi-gui/locales/zh-CN.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "拇指滚轮" "Gesture Button": "手势按钮" "Action Ring": "操作环" +"8 actions": "8 个操作" +"Top": "上" +"Top Right": "右上" +"Bottom Right": "右下" +"Bottom": "下" +"Bottom Left": "左下" +"Top Left": "左上" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/zh-HK.yml b/crates/openlogi-gui/locales/zh-HK.yml index c0383341..af2882b4 100644 --- a/crates/openlogi-gui/locales/zh-HK.yml +++ b/crates/openlogi-gui/locales/zh-HK.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "拇指滾輪" "Gesture Button": "手勢按鈕" "Action Ring": "操作環" +"8 actions": "8 個操作" +"Top": "上" +"Top Right": "右上" +"Bottom Right": "右下" +"Bottom": "下" +"Bottom Left": "左下" +"Top Left": "左上" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/zh-TW.yml b/crates/openlogi-gui/locales/zh-TW.yml index e20d4d2a..0a51d01b 100644 --- a/crates/openlogi-gui/locales/zh-TW.yml +++ b/crates/openlogi-gui/locales/zh-TW.yml @@ -157,6 +157,13 @@ _version: 1 "Thumb Wheel": "拇指滾輪" "Gesture Button": "手勢按鈕" "Action Ring": "動作環" +"8 actions": "8 個動作" +"Top": "上" +"Top Right": "右上" +"Bottom Right": "右下" +"Bottom": "下" +"Bottom Left": "左下" +"Top Left": "左上" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/src/mouse_model/picker.rs b/crates/openlogi-gui/src/mouse_model/picker.rs index cfdec04d..4e974673 100644 --- a/crates/openlogi-gui/src/mouse_model/picker.rs +++ b/crates/openlogi-gui/src/mouse_model/picker.rs @@ -34,6 +34,7 @@ use crate::data::mouse_buttons::{ use crate::mouse_model::view::MouseModelView; use crate::state::AppState; use crate::theme::{self, ACCENT_BLUE, Palette, SelectableStyle}; +use openlogi_core::binding::{RingSlot, default_ring_binding}; /// Floor width for the [`action_picker`] popover. The action labels drive the /// actual width; this only stops the list from collapsing too narrow. Matches @@ -107,6 +108,164 @@ pub fn gesture_overview( .into_any_element() } +/// Build the Action Ring's customization menu: a compass-grid navigator card +/// (level 1) showing all eight [`RingSlot`]s with their bound actions, and — +/// once a slot is activated — its action-list card (level 2) flown out beside +/// it. Mirrors [`gesture_overview`]'s two-card structure; the active slot is +/// scratch state on the [`MouseModelView`]. +pub fn ring_overview(view: &Entity, cx: &mut Context) -> AnyElement { + let pal = theme::palette(cx); + let active = view.read(cx).ring_selected_slot(); + h_flex() + .items_start() + .gap_2() + .child(ring_grid_card(view, active, pal, cx)) + .when_some(active, |row, slot| { + row.child(ring_flyout_card(slot, view, pal, cx)) + }) + .into_any_element() +} + +/// Level 1: the compass grid — three rows matching the ring's on-screen +/// layout, with the centre cell left empty (that's the popup's ✕). +fn ring_grid_card( + view: &Entity, + active: Option, + pal: Palette, + cx: &mut Context, +) -> AnyElement { + let actions: BTreeMap = cx + .try_global::() + .map(|s| s.ring_slots_for_current().into_iter().collect()) + .unwrap_or_default(); + + let cell = |slot: RingSlot| { + let action = actions + .get(&slot) + .cloned() + .unwrap_or_else(|| default_ring_binding(slot)); + ring_cell(slot, &action, active == Some(slot), view, pal) + }; + + menu_card(pal) + .gap_1p5() + .child( + h_flex() + .w_full() + .justify_center() + .gap_1p5() + .child(cell(RingSlot::NorthWest)) + .child(cell(RingSlot::North)) + .child(cell(RingSlot::NorthEast)), + ) + .child( + h_flex() + .w_full() + .justify_center() + .gap_1p5() + .child(cell(RingSlot::West)) + .child(div().w(px(GESTURE_CELL_W))) + .child(cell(RingSlot::East)), + ) + .child( + h_flex() + .w_full() + .justify_center() + .gap_1p5() + .child(cell(RingSlot::SouthWest)) + .child(cell(RingSlot::South)) + .child(cell(RingSlot::SouthEast)), + ) + .into_any_element() +} + +/// One slot's cell in the compass grid — the ring counterpart of +/// [`direction_cell`]. +fn ring_cell( + slot: RingSlot, + current: &Action, + active: bool, + view: &Entity, + pal: Palette, +) -> AnyElement { + let idx = RingSlot::ALL + .iter() + .position(|s| *s == slot) + .unwrap_or_default(); + let header = format!("{} {}", slot.glyph(), tr!(slot.label())); + let action_label = tr!(current.label()); + let is_default = *current == default_ring_binding(slot); + let view = view.clone(); + v_flex() + .id(("ring-cell", idx)) + .w(px(GESTURE_CELL_W)) + .gap(px(2.)) + .px_2() + .py_1p5() + .rounded_md() + .selected_border(active, pal) + .selected_fill(active) + .hover(move |s| s.bg(pal.surface_hover)) + .child(div().text_xs().text_color(pal.text_muted).child(header)) + .child( + div() + .text_sm() + .text_color(if is_default { + pal.text_muted + } else { + pal.text_primary + }) + .child(action_label), + ) + .on_click(move |_event, _window, cx| { + view.update(cx, |v, vcx| { + let next = (v.ring_selected_slot() != Some(slot)).then_some(slot); + v.set_ring_selected_slot(next); + vcx.notify(); + }); + }) + .into_any_element() +} + +/// Level 2: the `slot`'s action picker, flown out as its own card. Picking +/// commits (persist + agent reload — the next ring open shows it) and stays +/// open for further edits. +fn ring_flyout_card( + slot: RingSlot, + view: &Entity, + pal: Palette, + cx: &mut Context, +) -> AnyElement { + let current = cx + .try_global::() + .and_then(|s| { + s.ring_slots_for_current() + .into_iter() + .find(|(candidate, _)| *candidate == slot) + .map(|(_, action)| action) + }) + .unwrap_or_else(|| default_ring_binding(slot)); + + let view_pick = view.clone(); + let on_pick: PickFn = Rc::new(move |action, _window, cx| { + cx.update_global::(|state, _| state.commit_ring_binding(slot, action)); + view_pick.update(cx, |_, vcx| vcx.notify()); + }); + + menu_card(pal) + .min_w(px(POPOVER_W)) + .child(title( + format!("{} {}", slot.glyph(), tr!(slot.label())), + pal, + )) + .child(divider(pal)) + .child(scroll_list( + "ring-slot-scroll", + action_rows("ring-action", Some(¤t), &on_pick, pal), + )) + .into_any_element() +} + /// The shared floating-card surface for every binding menu — the button picker, /// the gesture plus navigator, and its action flyout — so they read as one /// consistent, app-branded panel instead of two different surfaces. @@ -290,6 +449,10 @@ fn grouped_catalog() -> Vec<(Category, Vec)> { /// bound action. pub(crate) const GESTURE_BUTTON_ICON: &str = "action-icons/move.svg"; +/// Icon for the Action Ring's label card — the dots grid echoes the pad's +/// eight-dot ring marking; the ring has no single bound action either. +pub(crate) const RING_BUTTON_ICON: &str = "action-icons/grid-3x3.svg"; + /// Asset path (served by [`crate::app_assets`]) of the vendored lucide glyph for /// an action — the leading icon in each action row and in the leader-line label /// card. Exhaustive on purpose: a new [`Action`] variant must pick an icon here diff --git a/crates/openlogi-gui/src/mouse_model/view.rs b/crates/openlogi-gui/src/mouse_model/view.rs index c25801d2..c21a42b0 100644 --- a/crates/openlogi-gui/src/mouse_model/view.rs +++ b/crates/openlogi-gui/src/mouse_model/view.rs @@ -22,10 +22,12 @@ use crate::mouse_model::leader_lines::{ Geometry as LeaderGeometry, Label, Side, paint as paint_leader_lines, }; use crate::mouse_model::picker::{ - GESTURE_BUTTON_ICON, action_icon_path, action_picker, gesture_overview, + GESTURE_BUTTON_ICON, RING_BUTTON_ICON, action_icon_path, action_picker, gesture_overview, + ring_overview, }; use crate::state::AppState; use crate::theme::{self, ACCENT_BLUE, Palette, SelectableStyle}; +use openlogi_core::binding::RingSlot; const SIDE_W: f32 = 180.; const SIDE_GAP: f32 = 24.; @@ -62,6 +64,10 @@ pub struct MouseModelView { /// state, so the popover's `on_open_change` — which runs outside paint — can /// reset it without tripping gpui's render-only guard. gesture_active_dir: Option, + /// Which ring slot the open Action Ring menu has activated (so its level-2 + /// flyout card shows) — the ring counterpart of + /// [`Self::gesture_active_dir`]. + ring_active_slot: Option, _state_obs: Subscription, } @@ -72,6 +78,7 @@ impl MouseModelView { Self { hovered: None, gesture_active_dir: None, + ring_active_slot: None, _state_obs: state_obs, } } @@ -86,6 +93,17 @@ impl MouseModelView { pub(crate) fn set_gesture_selected_dir(&mut self, dir: Option) { self.gesture_active_dir = dir; } + + /// The ring slot whose level-2 flyout is open, if any. + pub(crate) fn ring_selected_slot(&self) -> Option { + self.ring_active_slot + } + + /// Set (or clear, with `None`) the activated ring slot. Callers must + /// `cx.notify()` to re-render. + pub(crate) fn set_ring_selected_slot(&mut self, slot: Option) { + self.ring_active_slot = slot; + } } impl Render for MouseModelView { @@ -162,6 +180,15 @@ impl Render for MouseModelView { is_default: false, icon: Some(GESTURE_BUTTON_ICON), } + } else if label.id == ButtonId::ActionRing { + // The ring's card summarizes its slot map — its single- + // action projection is deliberately `None` and would + // mislabel the pad as "Do Nothing". + BindingLabel { + text: tr!("8 actions"), + is_default: false, + icon: Some(RING_BUTTON_ICON), + } } else { // `bindings` is seeded for every `ButtonId::ALL` (agent-core // `bindings_for`), so a rendered non-gesture button always @@ -456,6 +483,36 @@ where .content(move |_state, _window, cx| gesture_overview(&view, cx)) } +/// Wrap `trigger` in a left-click [`Popover`] hosting the Action Ring's +/// customization menu (see [`ring_overview`]) — the ring counterpart of +/// [`gesture_overview_popover`], resetting the activated slot on close so the +/// next open starts on the compass grid. +fn ring_overview_popover( + popover_id: impl Into, + anchor: Anchor, + trigger: Tr, + view: Entity, +) -> impl IntoElement +where + Tr: Selectable + IntoElement + 'static, +{ + let view_reset = view.clone(); + Popover::new(popover_id) + .appearance(false) + .mouse_button(MouseButton::Left) + .anchor(anchor) + .trigger(trigger) + .on_open_change(move |open, _window, cx| { + if !*open { + view_reset.update(cx, |v, vcx| { + v.set_ring_selected_slot(None); + vcx.notify(); + }); + } + }) + .content(move |_state, _window, cx| ring_overview(&view, cx)) +} + /// Position the popover wrapper at the label's slot in the side gutter and /// host a Popover whose trigger is the label card itself. Same picker /// content as the hotspot dot — clicking either entry point lands on the @@ -498,6 +555,14 @@ fn label_popover( view.clone(), ) .into_any_element() + } else if label.id == ButtonId::ActionRing { + ring_overview_popover( + ("label-popover", idx), + Anchor::TopLeft, + trigger, + view.clone(), + ) + .into_any_element() } else { Popover::new(("label-popover", idx)) // `action_picker` draws its own `menu_card` surface, matching the @@ -727,6 +792,14 @@ fn hotspot_popover( view.clone(), ) .into_any_element() + } else if hotspot.id == ButtonId::ActionRing { + ring_overview_popover( + ("hotspot-popover", idx), + Anchor::TopRight, + trigger, + view.clone(), + ) + .into_any_element() } else { Popover::new(("hotspot-popover", idx)) // `action_picker` draws its own `menu_card` surface, matching the diff --git a/crates/openlogi-gui/src/state.rs b/crates/openlogi-gui/src/state.rs index 4c5c39b3..a47315bd 100644 --- a/crates/openlogi-gui/src/state.rs +++ b/crates/openlogi-gui/src/state.rs @@ -1204,6 +1204,18 @@ impl AppState { /// Update a single gesture-button sub-binding in memory, on disk, and in the /// shared gesture map the watcher thread reads. + /// Persist one Action Ring slot for the selected device and have the + /// agent reload. The overlay reads its slots from this config on every + /// open, so the next ring already shows the change. + pub fn commit_ring_binding(&mut self, slot: openlogi_core::binding::RingSlot, action: Action) { + let Some(key) = self.current_record().map(|r| r.config_key.clone()) else { + debug!(?slot, "no active device key — ring binding edit ignored"); + return; + }; + self.config.set_ring_slot(&key, slot, action); + self.persist_and_reload("ring binding"); + } + pub fn commit_gesture_binding(&mut self, direction: GestureDirection, action: Action) { let Some(key) = self.current_record().map(|r| r.config_key.clone()) else { debug!( From 1bcbd83d350dfdcc62de2d1cd45e6720041ba7e4 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 16:07:06 -0700 Subject: [PATCH 17/42] feat(core): Run and PasteText payload actions Options+ parity for its "Run" and "Paste Text" assignments: `Run` opens a URL, file, or program (the payload carries an optional `||`-separated argument string, split by the new `split_run_target`), and `PasteText` types a fixed snippet. Both are appended after `CustomShortcut`, excluded from the picker catalog like it, and derive short readable labels (URL host / file stem / first snippet line). The untagged-routing guard gains cases proving their single-key tables still land on `Binding::Single`. --- crates/openlogi-core/src/binding.rs | 169 ++++++++++++++++++++++++++-- 1 file changed, 159 insertions(+), 10 deletions(-) diff --git a/crates/openlogi-core/src/binding.rs b/crates/openlogi-core/src/binding.rs index 2bafdbf4..1ef152df 100644 --- a/crates/openlogi-core/src/binding.rs +++ b/crates/openlogi-core/src/binding.rs @@ -466,6 +466,57 @@ pub enum Action { /// The `display` field is used by [`Action::label`] so the popover /// shows the user-friendly chord name. CustomShortcut(KeyCombo), + + /// Open a URL, file, or program — the Options+ "Run" action. + /// + /// The payload is the target optionally followed by `||` and an argument + /// string (`"C:\tool.exe||--flag value"`), the same convention Options+ + /// documents in its Run editor; split it with [`split_run_target`]. + /// On Windows `%VAR%` environment references in either half expand at + /// fire time. + Run(String), + + /// Type a fixed text snippet wherever the caret is — the Options+ + /// "Paste Text" action. Synthesized as Unicode key events rather than a + /// clipboard round-trip, so the user's clipboard is never clobbered. + PasteText(String), +} + +/// Split an [`Action::Run`] payload into its target and optional argument +/// string at the first `||`, trimming whitespace around both halves. An +/// empty argument half (`"app||"`) collapses to `None`. +#[must_use] +pub fn split_run_target(payload: &str) -> (&str, Option<&str>) { + match payload.split_once("||") { + Some((target, args)) => { + let args = args.trim(); + ( + target.trim(), + if args.is_empty() { None } else { Some(args) }, + ) + } + None => (payload.trim(), None), + } +} + +/// Short human name for a [`Action::Run`] target — the host for URLs, the +/// file stem for paths, the raw target otherwise — truncated so slot labels +/// stay readable. +fn run_target_short_name(payload: &str) -> String { + let (target, _) = split_run_target(payload); + let short = if let Some((_, rest)) = target.split_once("://") { + rest.split(['/', '?']).next().unwrap_or(rest) + } else { + std::path::Path::new(target) + .file_stem() + .and_then(|stem| stem.to_str()) + .unwrap_or(target) + }; + let mut out: String = short.chars().take(24).collect(); + if short.chars().count() > 24 { + out.push('…'); + } + out } /// A modifier + virtual-key keystroke captured by the P1.3 recorder UI or @@ -756,6 +807,22 @@ impl Action { Action::HorizontalScrollLeft => "Scroll Left".into(), Action::HorizontalScrollRight => "Scroll Right".into(), Action::CustomShortcut(combo) => combo.rendered_label(), + Action::Run(payload) => { + format!("Run: {}", run_target_short_name(payload)) + } + Action::PasteText(text) => { + let mut snippet: String = text + .lines() + .next() + .unwrap_or_default() + .chars() + .take(18) + .collect(); + if snippet.chars().count() < text.chars().count() { + snippet.push('…'); + } + format!("Paste: {snippet}") + } } } @@ -768,8 +835,9 @@ impl Action { | Action::MiddleClick | Action::MouseBack | Action::MouseForward => Category::Mouse, - // CustomShortcut is assigned to Editing so it doesn't need a - // separate arm (it's not in the picker catalog). + // CustomShortcut and PasteText are assigned to Editing, Run to + // System, so they don't need separate arms (none are in the + // picker catalog — they're entered via dedicated editor rows). Action::Copy | Action::Paste | Action::Cut @@ -778,7 +846,8 @@ impl Action { | Action::SelectAll | Action::Find | Action::Save - | Action::CustomShortcut(_) => Category::Editing, + | Action::CustomShortcut(_) + | Action::PasteText(_) => Category::Editing, Action::BrowserBack | Action::BrowserForward | Action::NewTab @@ -793,9 +862,11 @@ impl Action { | Action::NextDesktop | Action::ShowDesktop | Action::LaunchpadShow => Category::Navigation, - Action::None | Action::LockScreen | Action::Screenshot | Action::CaptureRegion => { - Category::System - } + Action::None + | Action::LockScreen + | Action::Screenshot + | Action::CaptureRegion + | Action::Run(_) => Category::System, Action::PlayPause | Action::NextTrack | Action::PrevTrack @@ -814,8 +885,10 @@ impl Action { /// All pickable actions in a deterministic order. /// - /// [`Action::CustomShortcut`] is intentionally excluded — it is opened via - /// "Record shortcut…" (P1.3), not selected from the catalog. + /// [`Action::CustomShortcut`], [`Action::Run`] and [`Action::PasteText`] + /// are intentionally excluded — they carry payloads and are entered via + /// their dedicated editor rows ("Record shortcut…", "Run…", "Paste + /// text…"), not selected from the catalog. #[must_use] pub fn catalog() -> Vec { vec![ @@ -1012,12 +1085,50 @@ mod tests { let catalog = Action::catalog(); for action in &catalog { assert!( - !matches!(action, Action::CustomShortcut(_)), - "catalog must not contain CustomShortcut" + !matches!( + action, + Action::CustomShortcut(_) | Action::Run(_) | Action::PasteText(_) + ), + "catalog must not contain payload-carrying editor actions" ); } } + #[test] + fn split_run_target_handles_args_and_whitespace() { + assert_eq!( + split_run_target("https://gemini.google.com/"), + ("https://gemini.google.com/", None) + ); + assert_eq!( + split_run_target(r"C:\tools\wispr.exe || --toggle voice"), + (r"C:\tools\wispr.exe", Some("--toggle voice")) + ); + // An empty argument half collapses to None. + assert_eq!(split_run_target("notepad.exe||"), ("notepad.exe", None)); + // Only the FIRST `||` splits — later ones belong to the arguments. + assert_eq!(split_run_target("a.exe||x||y"), ("a.exe", Some("x||y"))); + } + + #[test] + fn run_and_paste_labels_stay_short_and_readable() { + assert_eq!( + Action::Run("https://gemini.google.com/app?q=1".into()).label(), + "Run: gemini.google.com" + ); + assert_eq!( + Action::Run(r"C:\Program Files\Wispr\wispr.exe||--paste".into()).label(), + "Run: wispr" + ); + assert_eq!( + Action::PasteText("Build Phase Two".into()).label(), + "Paste: Build Phase Two" + ); + // Long / multi-line text truncates with an ellipsis marker. + let long = Action::PasteText("Please write a draft response to this\nsecond line".into()); + assert_eq!(long.label(), "Paste: Please write a dra…"); + } + // ── Binding (merged model) serde routing ────────────────────────────────── /// On-disk shape: a `ButtonId` → [`Binding`] map, as `DeviceConfig.bindings` @@ -1123,6 +1234,44 @@ mod tests { } } + /// The payload variants added for Options+ parity (`Run`, `PasteText`) + /// serialize as single-key tables (`{ Run = "…" }`) — the untagged router + /// must still land them on [`Binding::Single`], not mistake them for a + /// one-entry Gesture/Ring map. Their variant names live in the same + /// namespace as [`GestureDirection`] and [`RingSlot`] keys, so a rename + /// on either side shows up here. + #[test] + fn binding_payload_variant_tables_route_to_single() { + for toml in [ + "bindings.Forward = { Run = \"https://gemini.google.com/\" }", + "bindings.Forward = { PasteText = \"Build Phase Two\" }", + ] { + let parsed = toml::from_str::(toml).expect("deserialize"); + assert!( + matches!(parsed.bindings[&ButtonId::Forward], Binding::Single(_)), + "{toml} must route to Single" + ); + } + let roundtrip = binding_roundtrip(BTreeMap::from([ + ( + ButtonId::Back, + Binding::Single(Action::Run("notepad.exe||readme.txt".into())), + ), + ( + ButtonId::Forward, + Binding::Single(Action::PasteText("Master Spec".into())), + ), + ])); + assert_eq!( + roundtrip[&ButtonId::Back], + Binding::Single(Action::Run("notepad.exe||readme.txt".into())) + ); + assert_eq!( + roundtrip[&ButtonId::Forward], + Binding::Single(Action::PasteText("Master Spec".into())) + ); + } + #[test] fn ring_default_covers_every_slot_and_projects_no_click_action() { let binding = default_binding_for(ButtonId::ActionRing); From 74a880b1c4ab653388d5959eed4de3a9f77cb15d Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 16:07:06 -0700 Subject: [PATCH 18/42] feat(inject): dispatch Run and PasteText MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows: `Run` goes through `ShellExecuteW` (URLs launch the browser, documents their association, programs run with the `||` argument string) with cmd.exe-style `%VAR%` expansion, injected via a testable lookup; `PasteText` types the snippet as `KEYEVENTF_UNICODE` events — no clipboard round-trip — with line breaks as Enter and tabs as Tab. macOS/Linux run targets through `open` / `xdg-open` (children reaped off-thread); their PasteText is an honest logged stub pending platform Unicode typing. --- crates/openlogi-inject/Cargo.toml | 8 +- crates/openlogi-inject/src/inject/linux.rs | 35 ++++ crates/openlogi-inject/src/inject/macos.rs | 34 ++++ crates/openlogi-inject/src/inject/windows.rs | 170 ++++++++++++++++++- 4 files changed, 240 insertions(+), 7 deletions(-) diff --git a/crates/openlogi-inject/Cargo.toml b/crates/openlogi-inject/Cargo.toml index e3382139..14f96e96 100644 --- a/crates/openlogi-inject/Cargo.toml +++ b/crates/openlogi-inject/Cargo.toml @@ -37,6 +37,10 @@ objc2-app-kit = { workspace = true, features = [ objc2-core-graphics = { version = "0.3.2", features = ["CGEvent"] } objc2-foundation = { workspace = true } -# SendInput-based action synthesis (execute_windows). +# SendInput-based action synthesis (execute_windows); Win32_UI_Shell for +# ShellExecuteW, which backs the `Run` action. [target.'cfg(target_os = "windows")'.dependencies] -windows-sys = { workspace = true, features = ["Win32_UI_Input_KeyboardAndMouse"] } +windows-sys = { workspace = true, features = [ + "Win32_UI_Input_KeyboardAndMouse", + "Win32_UI_Shell", +] } diff --git a/crates/openlogi-inject/src/inject/linux.rs b/crates/openlogi-inject/src/inject/linux.rs index 15216e20..77c2875d 100644 --- a/crates/openlogi-inject/src/inject/linux.rs +++ b/crates/openlogi-inject/src/inject/linux.rs @@ -108,6 +108,41 @@ pub(super) fn execute(action: &Action) { }; press_key(&modifiers_to_keycodes(combo.modifiers), key); } + // ── Run / Paste Text ────────────────────────────────────────────── + // `xdg-open` routes URLs and documents to the right handler; an + // explicit `||` argument string execs the target directly instead, + // since xdg-open takes no arguments. + Action::Run(payload) => run_target(payload), + // Typing arbitrary Unicode through uinput needs a keymap round-trip + // (evdev codes are layout-relative); not wired up yet. + Action::PasteText(_) => { + tracing::warn!("PasteText is not implemented on Linux yet — press ignored"); + } + } +} + +/// Open a [`Action::Run`] target (see the match arm above for the split). +/// The child is reaped on a detached thread so short-lived openers don't +/// linger as zombies. +fn run_target(payload: &str) { + let (target, args) = openlogi_core::binding::split_run_target(payload); + if target.is_empty() { + tracing::warn!("Run action with an empty target; ignored"); + return; + } + let spawned = match args { + Some(args) => std::process::Command::new(target) + .args(args.split_whitespace()) + .spawn(), + None => std::process::Command::new("xdg-open").arg(target).spawn(), + }; + match spawned { + Ok(mut child) => { + std::thread::spawn(move || { + let _ = child.wait(); + }); + } + Err(e) => tracing::warn!(run_target = target, "Run target failed to spawn: {e}"), } } diff --git a/crates/openlogi-inject/src/inject/macos.rs b/crates/openlogi-inject/src/inject/macos.rs index a492d62a..b49f7ebe 100644 --- a/crates/openlogi-inject/src/inject/macos.rs +++ b/crates/openlogi-inject/src/inject/macos.rs @@ -146,6 +146,40 @@ pub(super) fn execute(action: &Action) { } post_key(combo.key_code, flags); } + // ── Run / Paste Text ────────────────────────────────────────────── + // `open` routes URLs, apps and documents through Launch Services; an + // explicit `||` argument string execs the target directly instead. + Action::Run(payload) => run_target(payload), + // Unicode typing (CGEvent keyboardSetUnicodeString) is not wired up + // yet. + Action::PasteText(_) => { + tracing::warn!("PasteText is not implemented on macOS yet — press ignored"); + } + } +} + +/// Open a [`Action::Run`] target (see the match arm above for the split). +/// The child is reaped on a detached thread so short-lived openers don't +/// linger as zombies. +fn run_target(payload: &str) { + let (target, args) = openlogi_core::binding::split_run_target(payload); + if target.is_empty() { + tracing::warn!("Run action with an empty target; ignored"); + return; + } + let spawned = match args { + Some(args) => std::process::Command::new(target) + .args(args.split_whitespace()) + .spawn(), + None => std::process::Command::new("open").arg(target).spawn(), + }; + match spawned { + Ok(mut child) => { + std::thread::spawn(move || { + let _ = child.wait(); + }); + } + Err(e) => tracing::warn!(run_target = target, "Run target failed to spawn: {e}"), } } diff --git a/crates/openlogi-inject/src/inject/windows.rs b/crates/openlogi-inject/src/inject/windows.rs index 74c74536..0525654b 100644 --- a/crates/openlogi-inject/src/inject/windows.rs +++ b/crates/openlogi-inject/src/inject/windows.rs @@ -4,13 +4,14 @@ use std::mem::size_of; use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ - INPUT, INPUT_0, INPUT_KEYBOARD, INPUT_MOUSE, KEYBDINPUT, KEYEVENTF_KEYUP, MOUSEEVENTF_HWHEEL, - MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP, - MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, MOUSEEVENTF_WHEEL, MOUSEEVENTF_XDOWN, - MOUSEEVENTF_XUP, MOUSEINPUT, SendInput, + INPUT, INPUT_0, INPUT_KEYBOARD, INPUT_MOUSE, KEYBDINPUT, KEYEVENTF_KEYUP, KEYEVENTF_UNICODE, + MOUSEEVENTF_HWHEEL, MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEDOWN, + MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, MOUSEEVENTF_WHEEL, + MOUSEEVENTF_XDOWN, MOUSEEVENTF_XUP, MOUSEINPUT, SendInput, }; +use windows_sys::Win32::UI::Shell::ShellExecuteW; -use openlogi_core::binding::{Action, KeyCombo}; +use openlogi_core::binding::{Action, KeyCombo, split_run_target}; const WHEEL_DELTA: i32 = 120; @@ -28,6 +29,7 @@ const VK_X: u16 = 0x58; const VK_Y: u16 = 0x59; const VK_Z: u16 = 0x5A; const VK_TAB: u16 = 0x09; +const VK_RETURN: u16 = 0x0D; const VK_LEFT: u16 = 0x25; const VK_RIGHT: u16 = 0x27; const VK_SHIFT: u16 = 0x10; @@ -124,10 +126,141 @@ pub(super) fn execute(action: &Action) { | Action::HorizontalScrollLeft | Action::HorizontalScrollRight => post_scroll(action), Action::CustomShortcut(combo) => post_custom_shortcut(combo), + Action::Run(payload) => run_target(payload), + Action::PasteText(text) => post_text(text), Action::None => {} } } +// SW_SHOWNORMAL from WinUser.h — windows-sys puts it behind the +// Win32_UI_WindowsAndMessaging feature; not worth enabling for one integer +// (same treatment as the VK_* codes above). +const SW_SHOWNORMAL: i32 = 1; + +/// Open a [`Action::Run`] target through the shell: URLs launch the default +/// browser, documents open their association, executables run with the `||` +/// argument string. `%VAR%` environment references expand in both halves. +fn run_target(payload: &str) { + let (target, args) = split_run_target(payload); + if target.is_empty() { + tracing::warn!("Run action with an empty target; ignored"); + return; + } + let target = expand_env(target); + let params = args.map(expand_env); + + let wide_target = wide(&target); + let wide_params = params.as_deref().map(wide); + let wide_verb = wide("open"); + // SAFETY: all pointers are NUL-terminated UTF-16 buffers that outlive the + // call; ShellExecuteW copies what it needs before returning. + let result = unsafe { + ShellExecuteW( + std::ptr::null_mut(), + wide_verb.as_ptr(), + wide_target.as_ptr(), + wide_params.as_ref().map_or(std::ptr::null(), Vec::as_ptr), + std::ptr::null(), + SW_SHOWNORMAL, + ) + }; + // ShellExecuteW's documented error contract: values ≤ 32 are error codes. + if result as usize <= 32 { + tracing::warn!(target = %target, code = result as usize, "Run target failed to open"); + } else { + tracing::debug!(target = %target, "Run target opened"); + } +} + +/// NUL-terminated UTF-16 for Win32 `W` APIs. +fn wide(text: &str) -> Vec { + text.encode_utf16().chain(std::iter::once(0)).collect() +} + +/// Expand `%NAME%` environment references: `%%` escapes a literal `%`, and +/// unknown names keep their literal spelling (cmd.exe behavior). +fn expand_env(value: &str) -> String { + expand_env_with(value, |name| std::env::var(name).ok()) +} + +/// [`expand_env`] with the variable source injected, so the parse is +/// deterministic under test. +fn expand_env_with(value: &str, lookup: impl Fn(&str) -> Option) -> String { + let mut out = String::with_capacity(value.len()); + let mut rest = value; + while let Some(start) = rest.find('%') { + out.push_str(&rest[..start]); + let after = &rest[start + 1..]; + if let Some(end) = after.find('%') { + let name = &after[..end]; + if name.is_empty() { + out.push('%'); + } else if let Some(resolved) = lookup(name) { + out.push_str(&resolved); + } else { + out.push('%'); + out.push_str(name); + out.push('%'); + } + rest = &after[end + 1..]; + } else { + // A lone trailing % is literal. + out.push('%'); + rest = after; + } + } + out.push_str(rest); + out +} + +/// Type [`Action::PasteText`]'s snippet as `KEYEVENTF_UNICODE` events — no +/// clipboard involvement, so the user's clipboard survives. Line breaks +/// become Enter and tabs become Tab presses so multi-line snippets land the +/// way they read; astral-plane characters ride as surrogate halves, which is +/// the documented `KEYEVENTF_UNICODE` convention. +fn post_text(text: &str) { + let mut inputs = Vec::with_capacity(text.len() * 2); + for ch in text.replace("\r\n", "\n").chars() { + match ch { + '\n' => { + inputs.push(key_input(VK_RETURN, false)); + inputs.push(key_input(VK_RETURN, true)); + } + '\t' => { + inputs.push(key_input(VK_TAB, false)); + inputs.push(key_input(VK_TAB, true)); + } + _ => { + let mut units = [0u16; 2]; + for unit in ch.encode_utf16(&mut units) { + inputs.push(unicode_input(*unit, false)); + inputs.push(unicode_input(*unit, true)); + } + } + } + } + send_inputs(&inputs); +} + +fn unicode_input(unit: u16, key_up: bool) -> INPUT { + let mut flags = KEYEVENTF_UNICODE; + if key_up { + flags |= KEYEVENTF_KEYUP; + } + INPUT { + r#type: INPUT_KEYBOARD, + Anonymous: INPUT_0 { + ki: KEYBDINPUT { + wVk: 0, + wScan: unit, + dwFlags: flags, + time: 0, + dwExtraInfo: 0, + }, + }, + } +} + fn post_click(button: MouseButton) { let (down, up, data) = match button { MouseButton::Left => (MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, 0), @@ -264,3 +397,30 @@ fn mouse_input(flags: u32, data: i32) -> INPUT { }, } } + +#[cfg(test)] +mod tests { + use super::expand_env_with; + + #[test] + fn expand_env_substitutes_known_and_keeps_unknown_names() { + let lookup = |name: &str| match name { + "USERPROFILE" => Some(r"C:\Users\demo".to_string()), + _ => None, + }; + assert_eq!( + expand_env_with(r"%USERPROFILE%\notes.txt", lookup), + r"C:\Users\demo\notes.txt" + ); + // Unknown names keep their literal spelling, cmd.exe style. + assert_eq!(expand_env_with(r"%UNSET%\x", lookup), r"%UNSET%\x"); + } + + #[test] + fn expand_env_treats_doubled_and_lone_percent_as_literals() { + let lookup = |_: &str| None; + assert_eq!(expand_env_with("100%% done", lookup), "100% done"); + assert_eq!(expand_env_with("50% off", lookup), "50% off"); + assert_eq!(expand_env_with("", lookup), ""); + } +} From 77709d04c62aaae39a62fbed463d4fb97cb9f2e8 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 16:07:06 -0700 Subject: [PATCH 19/42] =?UTF-8?q?feat(ipc):=20v12=20=E2=80=94=20Action=20g?= =?UTF-8?q?ains=20Run=20/=20PasteText=20on=20the=20wire?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The appended enum variants ride inside execute_action and config snapshots, so the strict-equality handshake must fence old/new pairs. Goldens pin the new variant encodings (varint index + length-prefixed payload). --- crates/openlogi-agent-core/src/ipc.rs | 4 +++- crates/openlogi-agent-core/tests/wire_format.rs | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/openlogi-agent-core/src/ipc.rs b/crates/openlogi-agent-core/src/ipc.rs index de68386d..8ab9d271 100644 --- a/crates/openlogi-agent-core/src/ipc.rs +++ b/crates/openlogi-agent-core/src/ipc.rs @@ -30,7 +30,9 @@ use serde::{Deserialize, Serialize}; /// v9: `poll_event_monitor` appended + [`MonitorEvent`] (live event monitor). /// v10: `Capabilities::hires_wheel` appended. /// v11: `next_ring_press` + `execute_action` appended (Action Ring overlay). -pub const PROTOCOL_VERSION: u32 = 11; +/// v12: [`Action`] gains the appended `Run` / `PasteText` variants (they ride +/// inside `execute_action` and config snapshots). +pub const PROTOCOL_VERSION: u32 = 12; /// One Action Ring pad press, streamed to the GUI via /// [`Agent::next_ring_press`] so the on-screen ring opens (or confirms a diff --git a/crates/openlogi-agent-core/tests/wire_format.rs b/crates/openlogi-agent-core/tests/wire_format.rs index b7d456b0..e51e6e9f 100644 --- a/crates/openlogi-agent-core/tests/wire_format.rs +++ b/crates/openlogi-agent-core/tests/wire_format.rs @@ -62,7 +62,7 @@ fn assert_wire(value: &T, golden: &str) { /// that makes that visible in the same diff. #[test] fn protocol_version_is_pinned() { - assert_eq!(PROTOCOL_VERSION, 11); + assert_eq!(PROTOCOL_VERSION, 12); } /// tarpc encodes the request enum's variant index, so trait *method order* is @@ -91,6 +91,20 @@ fn request_variant_order() { }, "1106", ); + // The appended v12 payload variants: variant index as a varint, then the + // usual length-prefixed UTF-8 payload. + assert_wire( + &AgentRequest::ExecuteAction { + action: Action::Run("a".into()), + }, + "112d0161", + ); + assert_wire( + &AgentRequest::ExecuteAction { + action: Action::PasteText("Hi".into()), + }, + "112e024869", + ); } #[test] From 8a0f724b1e60650fe698cd4443fe3d5f01a3eb7e Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 16:07:18 -0700 Subject: [PATCH 20/42] feat(gui): Run / Paste Text editor rows in the ring menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ring flyout grows a CUSTOM section with "Run Command…" and "Paste Text…" rows. Each opens a small payload-editor dialog — the Options+ Run editor shape: one input (seeded with the slot''s current payload for in-place edits), a hint line, Cancel / Save — committing through the same commit_ring_binding path as the plain rows. Editor strings land in all twenty locales at one position. --- crates/openlogi-gui/locales/da.yml | 7 + crates/openlogi-gui/locales/de.yml | 7 + crates/openlogi-gui/locales/el.yml | 7 + crates/openlogi-gui/locales/en.yml | 7 + crates/openlogi-gui/locales/es.yml | 7 + crates/openlogi-gui/locales/fi.yml | 7 + crates/openlogi-gui/locales/fr.yml | 7 + crates/openlogi-gui/locales/it.yml | 7 + crates/openlogi-gui/locales/ja.yml | 7 + crates/openlogi-gui/locales/ko.yml | 7 + crates/openlogi-gui/locales/nb.yml | 7 + crates/openlogi-gui/locales/nl.yml | 7 + crates/openlogi-gui/locales/pl.yml | 7 + crates/openlogi-gui/locales/pt-BR.yml | 7 + crates/openlogi-gui/locales/pt-PT.yml | 7 + crates/openlogi-gui/locales/ru.yml | 7 + crates/openlogi-gui/locales/sv.yml | 7 + crates/openlogi-gui/locales/zh-CN.yml | 7 + crates/openlogi-gui/locales/zh-HK.yml | 7 + crates/openlogi-gui/locales/zh-TW.yml | 7 + crates/openlogi-gui/src/mouse_model/picker.rs | 63 ++++- crates/openlogi-gui/src/windows.rs | 2 + .../src/windows/ring_action_editor.rs | 226 ++++++++++++++++++ 23 files changed, 425 insertions(+), 6 deletions(-) create mode 100644 crates/openlogi-gui/src/windows/ring_action_editor.rs diff --git a/crates/openlogi-gui/locales/da.yml b/crates/openlogi-gui/locales/da.yml index c53a3d9e..d1752fb1 100644 --- a/crates/openlogi-gui/locales/da.yml +++ b/crates/openlogi-gui/locales/da.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "Nederst" "Bottom Left": "Nederst til venstre" "Top Left": "Øverst til venstre" +"CUSTOM": "BRUGERDEFINERET" +"Run Command": "Kør kommando" +"Paste Text": "Indsæt tekst" +"Command or URL": "Kommando eller URL" +"Text to paste": "Tekst der skal indsættes" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "En URL, fil eller et program. Efter en programsti adskiller || argumenterne; %VAR%-miljøvariabler udvides." +"Typed at the cursor exactly as written.": "Skrives ved markøren præcis som skrevet." "Up": "Op" "Down": "Ned" "Left": "Venstre" diff --git a/crates/openlogi-gui/locales/de.yml b/crates/openlogi-gui/locales/de.yml index 2dbb8396..7b702ae1 100644 --- a/crates/openlogi-gui/locales/de.yml +++ b/crates/openlogi-gui/locales/de.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "Unten" "Bottom Left": "Unten links" "Top Left": "Oben links" +"CUSTOM": "BENUTZERDEFINIERT" +"Run Command": "Befehl ausführen" +"Paste Text": "Text einfügen" +"Command or URL": "Befehl oder URL" +"Text to paste": "Einzufügender Text" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Eine URL, Datei oder ein Programm. Nach einem Programmpfad trennt || die Argumente; %VAR%-Umgebungsvariablen werden expandiert." +"Typed at the cursor exactly as written.": "Wird an der Cursorposition genau wie geschrieben eingetippt." "Up": "Oben" "Down": "Unten" "Left": "Links" diff --git a/crates/openlogi-gui/locales/el.yml b/crates/openlogi-gui/locales/el.yml index a54bffd3..fb953818 100644 --- a/crates/openlogi-gui/locales/el.yml +++ b/crates/openlogi-gui/locales/el.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "Κάτω" "Bottom Left": "Κάτω αριστερά" "Top Left": "Επάνω αριστερά" +"CUSTOM": "ΠΡΟΣΑΡΜΟΣΜΕΝΑ" +"Run Command": "Εκτέλεση εντολής" +"Paste Text": "Επικόλληση κειμένου" +"Command or URL": "Εντολή ή URL" +"Text to paste": "Κείμενο για επικόλληση" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Ένα URL, αρχείο ή πρόγραμμα. Μετά τη διαδρομή προγράμματος, το || διαχωρίζει τα ορίσματα· οι μεταβλητές περιβάλλοντος %VAR% αναπτύσσονται." +"Typed at the cursor exactly as written.": "Πληκτρολογείται στον δρομέα ακριβώς όπως γράφτηκε." "Up": "Πάνω" "Down": "Κάτω" "Left": "Αριστερά" diff --git a/crates/openlogi-gui/locales/en.yml b/crates/openlogi-gui/locales/en.yml index d21b173d..007ebfb3 100644 --- a/crates/openlogi-gui/locales/en.yml +++ b/crates/openlogi-gui/locales/en.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "Bottom" "Bottom Left": "Bottom Left" "Top Left": "Top Left" +"CUSTOM": "CUSTOM" +"Run Command": "Run Command" +"Paste Text": "Paste Text" +"Command or URL": "Command or URL" +"Text to paste": "Text to paste" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand." +"Typed at the cursor exactly as written.": "Typed at the cursor exactly as written." "Up": "Up" "Down": "Down" "Left": "Left" diff --git a/crates/openlogi-gui/locales/es.yml b/crates/openlogi-gui/locales/es.yml index 1fe22638..9f94e328 100644 --- a/crates/openlogi-gui/locales/es.yml +++ b/crates/openlogi-gui/locales/es.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "Abajo" "Bottom Left": "Abajo a la izquierda" "Top Left": "Arriba a la izquierda" +"CUSTOM": "PERSONALIZADO" +"Run Command": "Ejecutar comando" +"Paste Text": "Pegar texto" +"Command or URL": "Comando o URL" +"Text to paste": "Texto a pegar" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Una URL, archivo o programa. Tras la ruta del programa, || separa los argumentos; las variables de entorno %VAR% se expanden." +"Typed at the cursor exactly as written.": "Se escribe en el cursor exactamente como está." "Up": "Arriba" "Down": "Abajo" "Left": "Izquierda" diff --git a/crates/openlogi-gui/locales/fi.yml b/crates/openlogi-gui/locales/fi.yml index ebbb7b51..b33f2099 100644 --- a/crates/openlogi-gui/locales/fi.yml +++ b/crates/openlogi-gui/locales/fi.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "Alhaalla" "Bottom Left": "Vasemmalla alhaalla" "Top Left": "Vasemmalla ylhäällä" +"CUSTOM": "MUKAUTETUT" +"Run Command": "Suorita komento" +"Paste Text": "Liitä teksti" +"Command or URL": "Komento tai URL" +"Text to paste": "Liitettävä teksti" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL, tiedosto tai ohjelma. Ohjelmapolun jälkeen || erottaa argumentit; %VAR%-ympäristömuuttujat laajennetaan." +"Typed at the cursor exactly as written.": "Kirjoitetaan kohdistimen kohtaan täsmälleen sellaisenaan." "Up": "Ylös" "Down": "Alas" "Left": "Vasen" diff --git a/crates/openlogi-gui/locales/fr.yml b/crates/openlogi-gui/locales/fr.yml index 89738f10..8c036fad 100644 --- a/crates/openlogi-gui/locales/fr.yml +++ b/crates/openlogi-gui/locales/fr.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "Bas" "Bottom Left": "En bas à gauche" "Top Left": "En haut à gauche" +"CUSTOM": "PERSONNALISÉ" +"Run Command": "Exécuter une commande" +"Paste Text": "Coller du texte" +"Command or URL": "Commande ou URL" +"Text to paste": "Texte à coller" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Une URL, un fichier ou un programme. Après le chemin du programme, || sépare les arguments ; les variables d'environnement %VAR% sont développées." +"Typed at the cursor exactly as written.": "Saisi au curseur exactement tel quel." "Up": "Haut" "Down": "Bas" "Left": "Gauche" diff --git a/crates/openlogi-gui/locales/it.yml b/crates/openlogi-gui/locales/it.yml index 31511eb4..b48e15fb 100644 --- a/crates/openlogi-gui/locales/it.yml +++ b/crates/openlogi-gui/locales/it.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "In basso" "Bottom Left": "In basso a sinistra" "Top Left": "In alto a sinistra" +"CUSTOM": "PERSONALIZZATO" +"Run Command": "Esegui comando" +"Paste Text": "Incolla testo" +"Command or URL": "Comando o URL" +"Text to paste": "Testo da incollare" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Un URL, file o programma. Dopo il percorso del programma, || separa gli argomenti; le variabili d'ambiente %VAR% vengono espanse." +"Typed at the cursor exactly as written.": "Digitato al cursore esattamente come scritto." "Up": "Su" "Down": "Giù" "Left": "Sinistra" diff --git a/crates/openlogi-gui/locales/ja.yml b/crates/openlogi-gui/locales/ja.yml index d40fc223..969abf19 100644 --- a/crates/openlogi-gui/locales/ja.yml +++ b/crates/openlogi-gui/locales/ja.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "下" "Bottom Left": "左下" "Top Left": "左上" +"CUSTOM": "カスタム" +"Run Command": "コマンドを実行" +"Paste Text": "テキストを貼り付け" +"Command or URL": "コマンドまたは URL" +"Text to paste": "貼り付けるテキスト" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL、ファイル、またはプログラム。プログラムのパスの後に || で引数を区切ります。%VAR% 環境変数は展開されます。" +"Typed at the cursor exactly as written.": "書かれたとおりにカーソル位置に入力されます。" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/ko.yml b/crates/openlogi-gui/locales/ko.yml index 7b838e4b..cc2b73a3 100644 --- a/crates/openlogi-gui/locales/ko.yml +++ b/crates/openlogi-gui/locales/ko.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "아래" "Bottom Left": "왼쪽 아래" "Top Left": "왼쪽 위" +"CUSTOM": "사용자 지정" +"Run Command": "명령 실행" +"Paste Text": "텍스트 붙여넣기" +"Command or URL": "명령 또는 URL" +"Text to paste": "붙여넣을 텍스트" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL, 파일 또는 프로그램. 프로그램 경로 뒤에 || 로 인수를 구분합니다. %VAR% 환경 변수는 확장됩니다." +"Typed at the cursor exactly as written.": "쓰인 그대로 커서 위치에 입력됩니다." "Up": "위" "Down": "아래" "Left": "왼쪽" diff --git a/crates/openlogi-gui/locales/nb.yml b/crates/openlogi-gui/locales/nb.yml index 364d469e..3ffac30a 100644 --- a/crates/openlogi-gui/locales/nb.yml +++ b/crates/openlogi-gui/locales/nb.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "Nederst" "Bottom Left": "Nederst til venstre" "Top Left": "Øverst til venstre" +"CUSTOM": "EGENDEFINERT" +"Run Command": "Kjør kommando" +"Paste Text": "Lim inn tekst" +"Command or URL": "Kommando eller URL" +"Text to paste": "Tekst som skal limes inn" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "En URL, fil eller et program. Etter en programsti skiller || argumentene; %VAR%-miljøvariabler utvides." +"Typed at the cursor exactly as written.": "Skrives ved markøren nøyaktig som skrevet." "Up": "Opp" "Down": "Ned" "Left": "Venstre" diff --git a/crates/openlogi-gui/locales/nl.yml b/crates/openlogi-gui/locales/nl.yml index 5513582d..4cf7d472 100644 --- a/crates/openlogi-gui/locales/nl.yml +++ b/crates/openlogi-gui/locales/nl.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "Onder" "Bottom Left": "Linksonder" "Top Left": "Linksboven" +"CUSTOM": "AANGEPAST" +"Run Command": "Opdracht uitvoeren" +"Paste Text": "Tekst plakken" +"Command or URL": "Opdracht of URL" +"Text to paste": "Te plakken tekst" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Een URL, bestand of programma. Na een programmapad scheidt || de argumenten; %VAR%-omgevingsvariabelen worden uitgebreid." +"Typed at the cursor exactly as written.": "Wordt bij de cursor exact zoals geschreven getypt." "Up": "Omhoog" "Down": "Omlaag" "Left": "Links" diff --git a/crates/openlogi-gui/locales/pl.yml b/crates/openlogi-gui/locales/pl.yml index 89da7898..7a20a494 100644 --- a/crates/openlogi-gui/locales/pl.yml +++ b/crates/openlogi-gui/locales/pl.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "Dół" "Bottom Left": "Lewy dolny" "Top Left": "Lewy górny" +"CUSTOM": "NIESTANDARDOWE" +"Run Command": "Uruchom polecenie" +"Paste Text": "Wklej tekst" +"Command or URL": "Polecenie lub URL" +"Text to paste": "Tekst do wklejenia" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL, plik lub program. Po ścieżce programu || oddziela argumenty; zmienne środowiskowe %VAR% są rozwijane." +"Typed at the cursor exactly as written.": "Wpisywany przy kursorze dokładnie tak, jak napisano." "Up": "W górę" "Down": "W dół" "Left": "W lewo" diff --git a/crates/openlogi-gui/locales/pt-BR.yml b/crates/openlogi-gui/locales/pt-BR.yml index 4a5ee1b0..66ab1302 100644 --- a/crates/openlogi-gui/locales/pt-BR.yml +++ b/crates/openlogi-gui/locales/pt-BR.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "Inferior" "Bottom Left": "Inferior esquerdo" "Top Left": "Superior esquerdo" +"CUSTOM": "PERSONALIZADO" +"Run Command": "Executar comando" +"Paste Text": "Colar texto" +"Command or URL": "Comando ou URL" +"Text to paste": "Texto a colar" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Um URL, arquivo ou programa. Após o caminho do programa, || separa os argumentos; variáveis de ambiente %VAR% são expandidas." +"Typed at the cursor exactly as written.": "Digitado no cursor exatamente como escrito." "Up": "Cima" "Down": "Baixo" "Left": "Esquerda" diff --git a/crates/openlogi-gui/locales/pt-PT.yml b/crates/openlogi-gui/locales/pt-PT.yml index 1581e5b9..2df7ecd4 100644 --- a/crates/openlogi-gui/locales/pt-PT.yml +++ b/crates/openlogi-gui/locales/pt-PT.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "Inferior" "Bottom Left": "Inferior esquerdo" "Top Left": "Superior esquerdo" +"CUSTOM": "PERSONALIZADO" +"Run Command": "Executar comando" +"Paste Text": "Colar texto" +"Command or URL": "Comando ou URL" +"Text to paste": "Texto a colar" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Um URL, ficheiro ou programa. Após o caminho do programa, || separa os argumentos; as variáveis de ambiente %VAR% são expandidas." +"Typed at the cursor exactly as written.": "Escrito no cursor exatamente como está." "Up": "Cima" "Down": "Baixo" "Left": "Esquerda" diff --git a/crates/openlogi-gui/locales/ru.yml b/crates/openlogi-gui/locales/ru.yml index f3ad45d0..2f48d52a 100644 --- a/crates/openlogi-gui/locales/ru.yml +++ b/crates/openlogi-gui/locales/ru.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "Низ" "Bottom Left": "Слева снизу" "Top Left": "Слева сверху" +"CUSTOM": "ПОЛЬЗОВАТЕЛЬСКИЕ" +"Run Command": "Выполнить команду" +"Paste Text": "Вставить текст" +"Command or URL": "Команда или URL" +"Text to paste": "Текст для вставки" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL, файл или программа. После пути к программе || отделяет аргументы; переменные окружения %VAR% раскрываются." +"Typed at the cursor exactly as written.": "Вводится у курсора точно как написано." "Up": "Вверх" "Down": "Вниз" "Left": "Влево" diff --git a/crates/openlogi-gui/locales/sv.yml b/crates/openlogi-gui/locales/sv.yml index ece43eaa..02620238 100644 --- a/crates/openlogi-gui/locales/sv.yml +++ b/crates/openlogi-gui/locales/sv.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "Nederst" "Bottom Left": "Nere till vänster" "Top Left": "Uppe till vänster" +"CUSTOM": "ANPASSAT" +"Run Command": "Kör kommando" +"Paste Text": "Klistra in text" +"Command or URL": "Kommando eller URL" +"Text to paste": "Text att klistra in" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "En URL, fil eller ett program. Efter en programsökväg avgränsar || argumenten; %VAR%-miljövariabler expanderas." +"Typed at the cursor exactly as written.": "Skrivs vid markören exakt som skrivet." "Up": "Upp" "Down": "Ned" "Left": "Vänster" diff --git a/crates/openlogi-gui/locales/zh-CN.yml b/crates/openlogi-gui/locales/zh-CN.yml index 015279bd..98e8fa40 100644 --- a/crates/openlogi-gui/locales/zh-CN.yml +++ b/crates/openlogi-gui/locales/zh-CN.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "下" "Bottom Left": "左下" "Top Left": "左上" +"CUSTOM": "自定义" +"Run Command": "运行命令" +"Paste Text": "粘贴文本" +"Command or URL": "命令或 URL" +"Text to paste": "要粘贴的文本" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL、文件或程序。在程序路径后用 || 分隔参数;%VAR% 环境变量会展开。" +"Typed at the cursor exactly as written.": "按原样在光标处输入。" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/zh-HK.yml b/crates/openlogi-gui/locales/zh-HK.yml index af2882b4..1eed8956 100644 --- a/crates/openlogi-gui/locales/zh-HK.yml +++ b/crates/openlogi-gui/locales/zh-HK.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "下" "Bottom Left": "左下" "Top Left": "左上" +"CUSTOM": "自訂" +"Run Command": "執行指令" +"Paste Text": "貼上文字" +"Command or URL": "指令或 URL" +"Text to paste": "要貼上的文字" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL、檔案或程式。在程式路徑後以 || 分隔參數;%VAR% 環境變數會展開。" +"Typed at the cursor exactly as written.": "按原樣在游標處輸入。" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/zh-TW.yml b/crates/openlogi-gui/locales/zh-TW.yml index 0a51d01b..88b430af 100644 --- a/crates/openlogi-gui/locales/zh-TW.yml +++ b/crates/openlogi-gui/locales/zh-TW.yml @@ -164,6 +164,13 @@ _version: 1 "Bottom": "下" "Bottom Left": "左下" "Top Left": "左上" +"CUSTOM": "自訂" +"Run Command": "執行命令" +"Paste Text": "貼上文字" +"Command or URL": "命令或 URL" +"Text to paste": "要貼上的文字" +"A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL、檔案或程式。在程式路徑後以 || 分隔參數;%VAR% 環境變數會展開。" +"Typed at the cursor exactly as written.": "按原樣在游標處輸入。" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/src/mouse_model/picker.rs b/crates/openlogi-gui/src/mouse_model/picker.rs index 4e974673..988749d0 100644 --- a/crates/openlogi-gui/src/mouse_model/picker.rs +++ b/crates/openlogi-gui/src/mouse_model/picker.rs @@ -34,6 +34,7 @@ use crate::data::mouse_buttons::{ use crate::mouse_model::view::MouseModelView; use crate::state::AppState; use crate::theme::{self, ACCENT_BLUE, Palette, SelectableStyle}; +use crate::windows::ring_action_editor::PayloadKind; use openlogi_core::binding::{RingSlot, default_ring_binding}; /// Floor width for the [`action_picker`] popover. The action labels drive the @@ -252,6 +253,17 @@ fn ring_flyout_card( view_pick.update(cx, |_, vcx| vcx.notify()); }); + // The payload actions (Run / Paste Text) close the list: they open the + // editor dialog instead of committing a fixed action. + let mut rows = action_rows("ring-action", Some(¤t), &on_pick, pal); + rows.push(section_header(&rust_i18n::t!("Custom"), pal)); + for (idx, kind) in [PayloadKind::Run, PayloadKind::PasteText] + .into_iter() + .enumerate() + { + rows.push(payload_editor_row(slot, kind, idx, ¤t, pal)); + } + menu_card(pal) .min_w(px(POPOVER_W)) .child(title( @@ -259,10 +271,47 @@ fn ring_flyout_card( pal, )) .child(divider(pal)) - .child(scroll_list( - "ring-slot-scroll", - action_rows("ring-action", Some(¤t), &on_pick, pal), - )) + .child(scroll_list("ring-slot-scroll", rows)) + .into_any_element() +} + +/// A "Run Command…" / "Paste Text…" flyout row. Unlike the catalog rows it +/// commits nothing itself — it opens the payload-editor dialog seeded from +/// the slot's current action. Selection styling still mirrors the catalog so +/// a slot bound to a payload action shows where its value lives. +fn payload_editor_row( + slot: RingSlot, + kind: PayloadKind, + idx: usize, + current: &Action, + pal: Palette, +) -> AnyElement { + let selected = kind.payload_of(current).is_some(); + let icon_path = action_icon_path(&kind.action(String::new())); + menu_row(("ring-payload", idx), pal, selected) + .child( + h_flex() + .items_center() + .gap_2() + .child( + svg() + .path(icon_path) + .size_4() + .flex_none() + .text_color(pal.text_muted), + ) + .child(div().child(format!("{}…", tr!(kind.title_key())))), + ) + .when(selected, |s| { + s.child( + Icon::new(IconName::Check) + .size_3() + .text_color(rgb(ACCENT_BLUE)), + ) + }) + .on_click(move |_event, _window, cx| { + crate::windows::ring_action_editor::open(slot, kind, cx); + }) .into_any_element() } @@ -467,7 +516,7 @@ pub(crate) fn action_icon_path(action: &Action) -> &'static str { Action::MouseBack => "action-icons/circle-arrow-left.svg", Action::MouseForward => "action-icons/circle-arrow-right.svg", Action::Copy => "action-icons/copy.svg", - Action::Paste => "action-icons/clipboard-paste.svg", + Action::Paste | Action::PasteText(_) => "action-icons/clipboard-paste.svg", Action::Cut => "action-icons/scissors.svg", Action::Undo => "action-icons/undo-2.svg", Action::Redo => "action-icons/redo-2.svg", @@ -485,7 +534,9 @@ pub(crate) fn action_icon_path(action: &Action) -> &'static str { Action::MissionControl => "action-icons/layout-grid.svg", Action::AppExpose => "action-icons/layers.svg", Action::PreviousDesktop => "action-icons/square-arrow-left.svg", - Action::NextDesktop => "action-icons/square-arrow-right.svg", + // Run shares the "launch outward" arrow — the closest glyph in the + // vendored set to Options+'s run icon. + Action::NextDesktop | Action::Run(_) => "action-icons/square-arrow-right.svg", Action::ShowDesktop => "action-icons/monitor.svg", Action::LaunchpadShow => "action-icons/grid-3x3.svg", Action::LockScreen => "action-icons/lock.svg", diff --git a/crates/openlogi-gui/src/windows.rs b/crates/openlogi-gui/src/windows.rs index 3a4ae9de..0cdc4989 100644 --- a/crates/openlogi-gui/src/windows.rs +++ b/crates/openlogi-gui/src/windows.rs @@ -10,6 +10,7 @@ //! [`crate::theme::apply_from_settings`]. pub mod add_device; +pub mod ring_action_editor; pub mod settings; pub mod update_consent; @@ -32,6 +33,7 @@ pub struct WindowRegistry { pub settings: Option>, pub add_device: Option>, pub update_consent: Option>, + pub ring_action_editor: Option>, } impl Global for WindowRegistry {} diff --git a/crates/openlogi-gui/src/windows/ring_action_editor.rs b/crates/openlogi-gui/src/windows/ring_action_editor.rs new file mode 100644 index 00000000..6dab8347 --- /dev/null +++ b/crates/openlogi-gui/src/windows/ring_action_editor.rs @@ -0,0 +1,226 @@ +//! Payload editor for the Action Ring's parameterized actions. +//! +//! `Run` and `PasteText` carry free text a picker row can't capture, so the +//! ring flyout's "Run Command…" / "Paste Text…" rows open this small dialog — +//! the same shape as the Options+ Run editor: one input, a hint line, Cancel +//! / Save. Saving commits the action to the slot through the same +//! [`AppState::commit_ring_binding`] path the plain rows use. + +use gpui::{ + App, AppContext as _, BorrowAppContext as _, Context, Entity, FocusHandle, FontWeight, + InteractiveElement, IntoElement, ParentElement as _, Render, Size, Styled as _, Subscription, + Window, div, prelude::FluentBuilder as _, px, +}; +use gpui_component::{ + button::{Button, ButtonVariants as _}, + h_flex, + input::{Input, InputState}, + v_flex, +}; +use openlogi_core::binding::{Action, RingSlot}; + +use crate::app_menu::{CloseWindow, Minimize, Zoom}; +use crate::state::AppState; +use crate::theme; +use crate::windows::{self, AuxWindow, WindowRegistry}; + +/// Which payload action this editor edits. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PayloadKind { + /// [`Action::Run`] — a URL, file, or program (`||` separates arguments). + Run, + /// [`Action::PasteText`] — a text snippet typed at the cursor. + PasteText, +} + +impl PayloadKind { + /// The dialog / flyout-row title (an i18n key). + pub fn title_key(self) -> &'static str { + match self { + PayloadKind::Run => "Run Command", + PayloadKind::PasteText => "Paste Text", + } + } + + fn placeholder_key(self) -> &'static str { + match self { + PayloadKind::Run => "Command or URL", + PayloadKind::PasteText => "Text to paste", + } + } + + fn hint_key(self) -> &'static str { + match self { + PayloadKind::Run => { + "A URL, file, or program. After a program path, use || to separate \ + arguments; %VAR% environment variables expand." + } + PayloadKind::PasteText => "Typed at the cursor exactly as written.", + } + } + + /// The payload of `action` when it already is this kind — the editor + /// seeds its input with it so reopening a configured slot edits in + /// place. + pub fn payload_of(self, action: &Action) -> Option<&str> { + match (self, action) { + (PayloadKind::Run, Action::Run(payload)) => Some(payload), + (PayloadKind::PasteText, Action::PasteText(text)) => Some(text), + _ => None, + } + } + + /// Wrap `payload` in this kind's [`Action`] variant. (With an empty + /// payload it also serves as the icon-lookup sample for the flyout row.) + pub fn action(self, payload: String) -> Action { + match self { + PayloadKind::Run => Action::Run(payload), + PayloadKind::PasteText => Action::PasteText(payload), + } + } +} + +/// Standalone payload-editor window root view. +pub struct RingActionEditorView { + focus_handle: FocusHandle, + #[allow(dead_code, reason = "held to keep the appearance observer alive")] + appearance_obs: Option, + slot: RingSlot, + kind: PayloadKind, + input: Entity, +} + +impl AuxWindow for RingActionEditorView { + fn set_appearance_obs(&mut self, sub: Subscription) { + self.appearance_obs = Some(sub); + } +} + +/// Open the editor for `slot`, seeded with the slot's current payload when it +/// already holds an action of `kind`. An editor left open for another slot is +/// closed first — focusing it would edit the wrong slot. +pub fn open(slot: RingSlot, kind: PayloadKind, cx: &mut App) { + if let Some(handle) = cx + .default_global::() + .ring_action_editor + .take() + { + let _ = handle.update(cx, |_, window, _| window.remove_window()); + } + + let seed: String = cx + .try_global::() + .and_then(|state| { + state + .ring_slots_for_current() + .into_iter() + .find(|(candidate, _)| *candidate == slot) + .and_then(|(_, action)| kind.payload_of(&action).map(str::to_owned)) + }) + .unwrap_or_default(); + + windows::open_or_focus( + |reg| &mut reg.ring_action_editor, + tr!(kind.title_key()), + Size::new(px(420.), px(240.)), + move |window, cx| { + let focus_handle = cx.focus_handle(); + let input = cx.new(|cx| { + InputState::new(window, cx) + .placeholder(tr!(kind.placeholder_key())) + .default_value(seed) + }); + // Type straight away — the input is the whole dialog. + input.update(cx, |state, cx| state.focus(window, cx)); + RingActionEditorView { + focus_handle, + appearance_obs: None, + slot, + kind, + input, + } + }, + cx, + ); +} + +impl Render for RingActionEditorView { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + let pal = theme::palette(cx); + let (slot, kind, input) = (self.slot, self.kind, self.input.clone()); + + v_flex() + .size_full() + .bg(pal.bg) + .text_color(pal.text_primary) + .track_focus(&self.focus_handle) + .on_action(|_: &CloseWindow, window, _| window.remove_window()) + .on_action(|_: &Minimize, window, _| window.minimize_window()) + .on_action(|_: &Zoom, window, _| window.zoom_window()) + .when(cfg!(target_os = "linux"), |this| { + this.child(windows::aux_title_bar(tr!(self.kind.title_key()), cx)) + }) + .child( + v_flex() + .flex_1() + .w_full() + .gap_3() + .p_5() + .child( + h_flex() + .items_center() + .gap_2() + .child( + div() + .text_lg() + .font_weight(FontWeight::SEMIBOLD) + .child(tr!(self.kind.title_key())), + ) + .child(div().text_sm().text_color(pal.text_muted).child(format!( + "{} {}", + self.slot.glyph(), + tr!(self.slot.label()) + ))), + ) + .child(Input::new(&self.input)) + .child( + div() + .text_sm() + .text_color(pal.text_muted) + .child(tr!(self.kind.hint_key())), + ) + .child( + h_flex() + .w_full() + .justify_end() + .gap_3() + .pt_1() + .child( + Button::new("ring-editor-cancel") + .outline() + .label(tr!("Cancel")) + .on_click(|_, window, _| window.remove_window()), + ) + .child( + Button::new("ring-editor-save") + .primary() + .label(tr!("Save")) + // An emptied input closes without + // committing — clearing a slot is what + // the plain rows ("Do Nothing") are for. + .on_click(move |_, window, cx| { + let payload = + input.read(cx).value().trim().to_owned(); + if !payload.is_empty() { + let action = kind.action(payload); + cx.update_global::(|state, _| { + state.commit_ring_binding(slot, action); + }); + } + window.remove_window(); + }), + ), + ), + ) + } +} From 0b5dc8d5dcfc6736c0d3aa7d30a389255dd1818e Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 16:07:18 -0700 Subject: [PATCH 21/42] feat(gui): tighten the ring to Options+ geometry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured against the real Options+ popup: sector circles grow to 48 at radius 86 so neighbours nearly touch, the centre ✕ becomes the 36px crimson cancel button, and the window shrinks to 240 logical px. The dead-zone test is now const-relative so a retune can''t silently rot it. --- crates/openlogi-gui/src/ring.rs | 45 +++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/openlogi-gui/src/ring.rs b/crates/openlogi-gui/src/ring.rs index b644aecc..9e266460 100644 --- a/crates/openlogi-gui/src/ring.rs +++ b/crates/openlogi-gui/src/ring.rs @@ -40,16 +40,17 @@ use crate::state::AppState; /// button circle plus shadows. The popup carries no labels, so it stays /// tight; near a screen edge [`platform_win::show_at`] clamps it into the /// work area. -const RING_WINDOW: f32 = 288.; +const RING_WINDOW: f32 = 240.; /// Radius of the circle the eight sector buttons sit on, from the centre. -const SECTOR_RADIUS: f32 = 96.; +/// Matched to the real Options+ ring: adjacent circles nearly touch. +const SECTOR_RADIUS: f32 = 86.; /// Diameter of one sector's circular icon button. -const SECTOR_BUTTON: f32 = 44.; +const SECTOR_BUTTON: f32 = 48.; /// Diameter of the centre ✕ cancel button. -const CENTER_BUTTON: f32 = 30.; +const CENTER_BUTTON: f32 = 36.; /// Cursor distance (logical px) below which a confirm means "cancel" — the /// dead zone around the centre ✕. -const DEADZONE: f32 = 38.; +const DEADZONE: f32 = 34.; /// How often the open-ring watcher samples the global cursor (highlight) and /// the Esc / outside-click cancel signals. const WATCH_TICK: Duration = Duration::from_millis(33); @@ -63,12 +64,12 @@ const PLATE: u32 = 0x00f7_f7f9; const INK: u32 = 0x001c_1c1e; /// Aimed icon strokes. const WHITE: u32 = 0x00ff_ffff; -/// Centre ✕ fill. -const CROSS_BG: u32 = 0x002c_2c2e; +/// Centre ✕ fill — the Options+ crimson cancel button. +const CROSS_BG: u32 = 0x00b0_2a38; /// Centre ✕ fill while the cursor aims at the dead zone. -const CROSS_BG_AIMED: u32 = 0x0045_4549; +const CROSS_BG_AIMED: u32 = 0x00c9_3a49; /// Centre ✕ glyph. -const CROSS_TEXT: u32 = 0x00c9_c9ce; +const CROSS_TEXT: u32 = 0x00ff_ffff; /// What the cursor currently points at, driven from the global cursor by the /// watcher so it works even when the pointer is outside the overlay window. @@ -128,6 +129,17 @@ pub fn init(commands: mpsc::UnboundedSender, cx: &mut App) { scale: 1., epoch: 0, }); + // Create the hidden overlay window eagerly rather than on the first tap: + // it removes window-construction latency from the first open, and — since + // it is never closed, only hidden — it keeps this on-demand GUI process + // alive past "quit when the last window closes", so the ring survives the + // user closing the settings window. Windows-only, like the overlay + // itself: elsewhere an invisible popup would still surface in window + // switchers. + #[cfg(target_os = "windows")] + if let Some(window) = create_window(Vec::new(), cx) { + cx.global_mut::().window = Some(window); + } } /// Handle one Action Ring pad press from the agent: open the ring when it is @@ -775,7 +787,20 @@ mod tests { fn dead_zone_aims_center() { let c = point(500., 500.); assert_eq!(sector(c, 0., 0.), Aim::Center); - assert_eq!(sector(c, 30., -20.), Aim::Center, "inside the dead zone"); + // Just inside the zone boundary, on a diagonal — const-relative so a + // DEADZONE retune doesn't silently rot this case. + let inside = (DEADZONE - 1.) / 2_f32.sqrt(); + assert_eq!( + sector(c, inside, -inside), + Aim::Center, + "inside the dead zone" + ); + let outside = (DEADZONE + 1.) / 2_f32.sqrt(); + assert_eq!( + sector(c, outside, -outside), + Aim::Sector(RingSlot::NorthEast), + "just past the dead zone" + ); } #[test] From 8392532fc63523877dc523fb76cbfc386d399b0e Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 16:07:18 -0700 Subject: [PATCH 22/42] feat(gui): --background flag and an always-resident ring window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `--background` (for login autostart) starts the GUI resident without opening the main window; the tray''s Show deeplink opens it on demand. The ring overlay window is now created eagerly and hidden at init — first-tap latency drops, and since it is hidden rather than closed, quit-on-last-window-close can no longer tear down the process that hosts the on-screen ring. --- crates/openlogi-gui/src/main.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/openlogi-gui/src/main.rs b/crates/openlogi-gui/src/main.rs index 78559d34..6e25e9c9 100644 --- a/crates/openlogi-gui/src/main.rs +++ b/crates/openlogi-gui/src/main.rs @@ -193,6 +193,11 @@ fn main() -> Result<()> { // Reopen the window when the app is relaunched with none open (dock click). app.on_reopen(|cx| open_main_window(&[], cx)); + // `--background` (login autostart): start resident for the Action Ring + // and the agent's tray, but do not open the main window — the tray's + // "Show Main Window" deeplink opens it on demand. + let background = std::env::args().any(|arg| arg == "--background"); + app.run(move |cx| { gpui_component::init(cx); theme::register_builtin_themes(cx); @@ -220,6 +225,9 @@ fn main() -> Result<()> { // On-demand GUI: quit when the last window closes. The agent stays // resident and keeps remapping (and hosts the menu-bar item from which // the GUI is reopened), so nothing needs the GUI process to linger. + // On Windows this never fires: the ring overlay's always-alive hidden + // window (created eagerly in `ring::init`) keeps the process resident, + // because the on-screen ring lives in this process, not the agent. cx.on_window_closed(|cx, _| { if cx.windows().is_empty() { cx.quit(); @@ -240,15 +248,20 @@ fn main() -> Result<()> { ipc_commands, )); } - open_main_window(&inventories, cx); + if !background { + open_main_window(&inventories, cx); + } }); // First launch only: offer to opt in to the update check, since it // defaults to off. Marked seen either way so it shows just once. + // Deferred in background mode (nothing may pop over the login + // desktop); it stays unseen and shows on the first real open. cx.update(|cx| { - let show = cx - .try_global::() - .is_some_and(|s| !s.app_settings().update_prompt_seen); + let show = !background + && cx + .try_global::() + .is_some_and(|s| !s.app_settings().update_prompt_seen); if show { windows::update_consent::open(cx); } From ac801eda85d5d4ef4bbc3aa0242ddebc4f04c2b3 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 16:07:18 -0700 Subject: [PATCH 23/42] docs: Run / PasteText in the ring binding example --- docs/CONFIGURATION.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index b30e82a9..7d5bcaff 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -85,13 +85,16 @@ Right = "NextDesktop" # replace the whole table with a single action (ActionRing = "Copy") to make # a tap fire that action directly instead. [devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing] -North = "Copy" +North = "PlayPause" NorthEast = "CaptureRegion" -East = "Redo" -SouthEast = "PlayPause" -South = "Paste" +# `Run` opens a URL, file, or program (`||` separates a program's arguments; +# %VAR% environment variables expand). `PasteText` types a snippet at the +# cursor. Both are also editable from the ring menu in the app. +East = { Run = "https://gemini.google.com/" } +SouthEast = "LockScreen" +South = { Run = 'C:\Windows\notepad.exe||%USERPROFILE%\notes.txt' } SouthWest = "ShowDesktop" -West = "Undo" +West = { PasteText = "Reviewed — looks good to me." } NorthWest = "MissionControl" # Per-app overlay: Back becomes Undo only while VS Code is frontmost. From c29429df456a060d2851833e8d2c6dfe65ccc9d4 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 16:44:20 -0700 Subject: [PATCH 24/42] feat(core): Win/Super modifier for CustomShortcut MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit KeyCombo carried only the four macOS modifiers, so Windows-first chords like Win+Ctrl+Space were unrepresentable. MOD_WIN is appended as bit 4 and renders as a leading ⊞ in derived labels. --- crates/openlogi-core/src/binding.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/openlogi-core/src/binding.rs b/crates/openlogi-core/src/binding.rs index 1ef152df..e43a1bd5 100644 --- a/crates/openlogi-core/src/binding.rs +++ b/crates/openlogi-core/src/binding.rs @@ -553,6 +553,12 @@ impl KeyCombo { pub const MOD_CTRL: u8 = 1 << 2; /// Bit for the ⌥ Option/Alt modifier in [`Self::modifiers`]. pub const MOD_OPTION: u8 = 1 << 3; + /// Bit for the ⊞ Windows / Super modifier in [`Self::modifiers`]. + /// + /// Appended for shortcuts like Win+Ctrl+Space that macOS's four + /// modifiers cannot express. Injection maps it to `VK_LWIN` on Windows + /// and Super/Meta on Linux; macOS treats it as ⌘ (the closest key). + pub const MOD_WIN: u8 = 1 << 4; /// Build the human-readable label from the modifier bitmask + key code. /// Falls back to `"⌘key 0xNN"` when the key code isn't one of the @@ -564,6 +570,9 @@ impl KeyCombo { return self.display.clone(); } let mut out = String::new(); + if self.modifiers & Self::MOD_WIN != 0 { + out.push('⊞'); + } if self.modifiers & Self::MOD_CTRL != 0 { out.push('⌃'); } @@ -1094,6 +1103,16 @@ mod tests { } } + #[test] + fn key_combo_win_modifier_renders_first() { + let combo = KeyCombo { + modifiers: KeyCombo::MOD_WIN | KeyCombo::MOD_CTRL, + key_code: 0x08, + display: String::new(), + }; + assert_eq!(combo.rendered_label(), "⊞⌃C"); + } + #[test] fn split_run_target_handles_args_and_whitespace() { assert_eq!( From 072c3dae6eb050b0e5ccf8092862a8128aea0d9a Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 16:44:20 -0700 Subject: [PATCH 25/42] feat(inject): map MOD_WIN to VK_LWIN / Meta / Command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows posts VK_LWIN, Linux KEY_LEFTMETA; macOS folds it into ⌘, the closest key it has. --- crates/openlogi-inject/src/inject/linux.rs | 3 +++ crates/openlogi-inject/src/inject/macos.rs | 4 ++++ crates/openlogi-inject/src/inject/windows.rs | 3 +++ 3 files changed, 10 insertions(+) diff --git a/crates/openlogi-inject/src/inject/linux.rs b/crates/openlogi-inject/src/inject/linux.rs index 77c2875d..fca8e269 100644 --- a/crates/openlogi-inject/src/inject/linux.rs +++ b/crates/openlogi-inject/src/inject/linux.rs @@ -320,6 +320,9 @@ fn modifiers_to_keycodes(modifiers: u8) -> Vec { if modifiers & KeyCombo::MOD_OPTION != 0 { mods.push(KeyCode::KEY_LEFTALT); } + if modifiers & KeyCombo::MOD_WIN != 0 { + mods.push(KeyCode::KEY_LEFTMETA); + } mods } diff --git a/crates/openlogi-inject/src/inject/macos.rs b/crates/openlogi-inject/src/inject/macos.rs index b49f7ebe..2546d1aa 100644 --- a/crates/openlogi-inject/src/inject/macos.rs +++ b/crates/openlogi-inject/src/inject/macos.rs @@ -144,6 +144,10 @@ pub(super) fn execute(action: &Action) { if combo.modifiers & KeyCombo::MOD_OPTION != 0 { flags |= CGEventFlags::CGEventFlagAlternate; } + // ⊞ has no macOS key; ⌘ is the conventional stand-in. + if combo.modifiers & KeyCombo::MOD_WIN != 0 { + flags |= CGEventFlags::CGEventFlagCommand; + } post_key(combo.key_code, flags); } // ── Run / Paste Text ────────────────────────────────────────────── diff --git a/crates/openlogi-inject/src/inject/windows.rs b/crates/openlogi-inject/src/inject/windows.rs index 0525654b..735aefbd 100644 --- a/crates/openlogi-inject/src/inject/windows.rs +++ b/crates/openlogi-inject/src/inject/windows.rs @@ -337,6 +337,9 @@ fn post_custom_shortcut(combo: &KeyCombo) { if combo.modifiers & KeyCombo::MOD_OPTION != 0 { modifiers.push(VK_MENU); } + if combo.modifiers & KeyCombo::MOD_WIN != 0 { + modifiers.push(VK_LWIN); + } post_key(vk, &modifiers); } From 5a852471bb88a61b25795456e9676b430d63a49a Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 16:44:20 -0700 Subject: [PATCH 26/42] feat(gui): full-page Action Ring editor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compass-grid popover read as a menu, not an editor. Clicking the ring hotspot or its label card now swaps the mouse diagram for the Options+ customization-page shape: a back arrow, the ring drawn large with every circle labelled by its bound action (selected slot accented), and a persistent action panel on the right hosting the catalog plus the Run / Paste Text editor rows. The popover path and its cards are removed — one editing surface, same commit_ring_binding flow. --- crates/openlogi-gui/src/mouse_model/mod.rs | 1 + crates/openlogi-gui/src/mouse_model/picker.rs | 178 +--------- .../src/mouse_model/ring_editor.rs | 304 ++++++++++++++++++ crates/openlogi-gui/src/mouse_model/view.rs | 101 +++--- 4 files changed, 366 insertions(+), 218 deletions(-) create mode 100644 crates/openlogi-gui/src/mouse_model/ring_editor.rs diff --git a/crates/openlogi-gui/src/mouse_model/mod.rs b/crates/openlogi-gui/src/mouse_model/mod.rs index e22dc885..3759b91c 100644 --- a/crates/openlogi-gui/src/mouse_model/mod.rs +++ b/crates/openlogi-gui/src/mouse_model/mod.rs @@ -6,4 +6,5 @@ mod geometry; pub mod leader_lines; pub mod picker; +pub mod ring_editor; pub mod view; diff --git a/crates/openlogi-gui/src/mouse_model/picker.rs b/crates/openlogi-gui/src/mouse_model/picker.rs index 988749d0..7a808c1b 100644 --- a/crates/openlogi-gui/src/mouse_model/picker.rs +++ b/crates/openlogi-gui/src/mouse_model/picker.rs @@ -35,7 +35,7 @@ use crate::mouse_model::view::MouseModelView; use crate::state::AppState; use crate::theme::{self, ACCENT_BLUE, Palette, SelectableStyle}; use crate::windows::ring_action_editor::PayloadKind; -use openlogi_core::binding::{RingSlot, default_ring_binding}; +use openlogi_core::binding::RingSlot; /// Floor width for the [`action_picker`] popover. The action labels drive the /// actual width; this only stops the list from collapsing too narrow. Matches @@ -109,177 +109,11 @@ pub fn gesture_overview( .into_any_element() } -/// Build the Action Ring's customization menu: a compass-grid navigator card -/// (level 1) showing all eight [`RingSlot`]s with their bound actions, and — -/// once a slot is activated — its action-list card (level 2) flown out beside -/// it. Mirrors [`gesture_overview`]'s two-card structure; the active slot is -/// scratch state on the [`MouseModelView`]. -pub fn ring_overview(view: &Entity, cx: &mut Context) -> AnyElement { - let pal = theme::palette(cx); - let active = view.read(cx).ring_selected_slot(); - h_flex() - .items_start() - .gap_2() - .child(ring_grid_card(view, active, pal, cx)) - .when_some(active, |row, slot| { - row.child(ring_flyout_card(slot, view, pal, cx)) - }) - .into_any_element() -} - -/// Level 1: the compass grid — three rows matching the ring's on-screen -/// layout, with the centre cell left empty (that's the popup's ✕). -fn ring_grid_card( - view: &Entity, - active: Option, - pal: Palette, - cx: &mut Context, -) -> AnyElement { - let actions: BTreeMap = cx - .try_global::() - .map(|s| s.ring_slots_for_current().into_iter().collect()) - .unwrap_or_default(); - - let cell = |slot: RingSlot| { - let action = actions - .get(&slot) - .cloned() - .unwrap_or_else(|| default_ring_binding(slot)); - ring_cell(slot, &action, active == Some(slot), view, pal) - }; - - menu_card(pal) - .gap_1p5() - .child( - h_flex() - .w_full() - .justify_center() - .gap_1p5() - .child(cell(RingSlot::NorthWest)) - .child(cell(RingSlot::North)) - .child(cell(RingSlot::NorthEast)), - ) - .child( - h_flex() - .w_full() - .justify_center() - .gap_1p5() - .child(cell(RingSlot::West)) - .child(div().w(px(GESTURE_CELL_W))) - .child(cell(RingSlot::East)), - ) - .child( - h_flex() - .w_full() - .justify_center() - .gap_1p5() - .child(cell(RingSlot::SouthWest)) - .child(cell(RingSlot::South)) - .child(cell(RingSlot::SouthEast)), - ) - .into_any_element() -} - -/// One slot's cell in the compass grid — the ring counterpart of -/// [`direction_cell`]. -fn ring_cell( - slot: RingSlot, - current: &Action, - active: bool, - view: &Entity, - pal: Palette, -) -> AnyElement { - let idx = RingSlot::ALL - .iter() - .position(|s| *s == slot) - .unwrap_or_default(); - let header = format!("{} {}", slot.glyph(), tr!(slot.label())); - let action_label = tr!(current.label()); - let is_default = *current == default_ring_binding(slot); - let view = view.clone(); - v_flex() - .id(("ring-cell", idx)) - .w(px(GESTURE_CELL_W)) - .gap(px(2.)) - .px_2() - .py_1p5() - .rounded_md() - .selected_border(active, pal) - .selected_fill(active) - .hover(move |s| s.bg(pal.surface_hover)) - .child(div().text_xs().text_color(pal.text_muted).child(header)) - .child( - div() - .text_sm() - .text_color(if is_default { - pal.text_muted - } else { - pal.text_primary - }) - .child(action_label), - ) - .on_click(move |_event, _window, cx| { - view.update(cx, |v, vcx| { - let next = (v.ring_selected_slot() != Some(slot)).then_some(slot); - v.set_ring_selected_slot(next); - vcx.notify(); - }); - }) - .into_any_element() -} - -/// Level 2: the `slot`'s action picker, flown out as its own card. Picking -/// commits (persist + agent reload — the next ring open shows it) and stays -/// open for further edits. -fn ring_flyout_card( - slot: RingSlot, - view: &Entity, - pal: Palette, - cx: &mut Context, -) -> AnyElement { - let current = cx - .try_global::() - .and_then(|s| { - s.ring_slots_for_current() - .into_iter() - .find(|(candidate, _)| *candidate == slot) - .map(|(_, action)| action) - }) - .unwrap_or_else(|| default_ring_binding(slot)); - - let view_pick = view.clone(); - let on_pick: PickFn = Rc::new(move |action, _window, cx| { - cx.update_global::(|state, _| state.commit_ring_binding(slot, action)); - view_pick.update(cx, |_, vcx| vcx.notify()); - }); - - // The payload actions (Run / Paste Text) close the list: they open the - // editor dialog instead of committing a fixed action. - let mut rows = action_rows("ring-action", Some(¤t), &on_pick, pal); - rows.push(section_header(&rust_i18n::t!("Custom"), pal)); - for (idx, kind) in [PayloadKind::Run, PayloadKind::PasteText] - .into_iter() - .enumerate() - { - rows.push(payload_editor_row(slot, kind, idx, ¤t, pal)); - } - - menu_card(pal) - .min_w(px(POPOVER_W)) - .child(title( - format!("{} {}", slot.glyph(), tr!(slot.label())), - pal, - )) - .child(divider(pal)) - .child(scroll_list("ring-slot-scroll", rows)) - .into_any_element() -} - -/// A "Run Command…" / "Paste Text…" flyout row. Unlike the catalog rows it +/// A "Run Command…" / "Paste Text…" action-list row. Unlike the catalog rows it /// commits nothing itself — it opens the payload-editor dialog seeded from /// the slot's current action. Selection styling still mirrors the catalog so /// a slot bound to a payload action shows where its value lives. -fn payload_editor_row( +pub(crate) fn payload_editor_row( slot: RingSlot, kind: PayloadKind, idx: usize, @@ -476,7 +310,7 @@ fn flyout_card( /// Commit callback invoked when a row is clicked. Boxed so the row builder can /// be shared between the button picker and any future custom picker, which /// differ only in what they do after committing. -type PickFn = Rc; +pub(crate) type PickFn = Rc; /// The action catalog grouped by [`Category`], preserving catalog order within /// each group and first-seen order across groups. @@ -561,7 +395,7 @@ pub(crate) fn action_icon_path(action: &Action) -> &'static str { /// icon, then its label; `current` adds a trailing accent check. Clicking any /// row invokes `on_pick`. `id_prefix` disambiguates element IDs between pickers /// that share this builder. -fn action_rows( +pub(crate) fn action_rows( id_prefix: &'static str, current: Option<&Action>, on_pick: &PickFn, @@ -642,7 +476,7 @@ fn menu_row( } /// Small uppercase muted group header. -fn section_header(label: &str, pal: Palette) -> AnyElement { +pub(crate) fn section_header(label: &str, pal: Palette) -> AnyElement { div() .w_full() .px_2() diff --git a/crates/openlogi-gui/src/mouse_model/ring_editor.rs b/crates/openlogi-gui/src/mouse_model/ring_editor.rs new file mode 100644 index 00000000..c055fced --- /dev/null +++ b/crates/openlogi-gui/src/mouse_model/ring_editor.rs @@ -0,0 +1,304 @@ +//! The Action Ring's full-page editor — the Options+ customization page +//! shape, not a popover: a back arrow, the ring drawn large with every +//! circle labelled by its bound action, and a persistent action panel on the +//! right editing the selected slot. +//! +//! Clicking the ring's hotspot or label card on the mouse diagram swaps the +//! diagram for this page (scratch state on [`MouseModelView`]); the back +//! arrow swaps back. Selection is the same `ring_active_slot` scratch state +//! the old flyout used, so the action panel and the canvas highlight always +//! agree. + +use std::collections::BTreeMap; +use std::rc::Rc; + +use gpui::{ + AnyElement, BorrowAppContext as _, Context, Entity, FontWeight, InteractiveElement, + IntoElement, ParentElement, StatefulInteractiveElement as _, Styled, div, px, rgb, svg, +}; +use gpui_component::{h_flex, v_flex}; +use openlogi_core::binding::{Action, RingSlot, default_ring_binding}; + +use crate::mouse_model::picker::{ + PickFn, action_icon_path, action_rows, payload_editor_row, section_header, +}; +use crate::mouse_model::view::{MouseModelView, localized_action_label}; +use crate::state::AppState; +use crate::theme::{ACCENT_BLUE, Palette}; +use crate::windows::ring_action_editor::PayloadKind; + +/// Radius of the circle the editor's slot buttons sit on. +const EDIT_RADIUS: f32 = 150.; +/// Diameter of one slot circle on the canvas. +const EDIT_CIRCLE: f32 = 56.; +/// Diameter of the decorative centre ✕. +const EDIT_CENTER: f32 = 40.; +/// Square canvas the ring is drawn in (labels hang outside the circle ring, +/// inside this box). +const CANVAS: f32 = 440.; +/// Width reserved for a floating label pill column beside a circle. +const PILL_W: f32 = 170.; +/// Gap between a circle and its label pill. +const PILL_GAP: f32 = 10.; +/// Fixed width of the right-hand action panel. +const PANEL_W: f32 = 260.; +/// Height cap for the action panel's scrolling list. +const PANEL_LIST_H: f32 = 420.; + +/// Build the full-page editor. Rendered by [`MouseModelView`] in place of the +/// mouse diagram while its `ring_editor_open` scratch flag is set. +pub(crate) fn page( + view: &Entity, + pal: Palette, + cx: &mut Context, +) -> AnyElement { + let selected = view + .read(cx) + .ring_selected_slot() + .unwrap_or(RingSlot::North); + let actions: BTreeMap = cx + .try_global::() + .map(|s| s.ring_slots_for_current().into_iter().collect()) + .unwrap_or_default(); + + let view_back = view.clone(); + let back = h_flex() + .items_center() + .gap_3() + .child( + div() + .id("ring-editor-back") + .p_1p5() + .rounded_md() + .cursor_pointer() + .hover(|s| s.bg(pal.surface)) + .on_click(move |_, _, cx| { + view_back.update(cx, |v, vcx| { + v.set_ring_editor_open(false); + v.set_ring_selected_slot(None); + vcx.notify(); + }); + }) + .child( + svg() + .path("action-icons/arrow-left.svg") + .size_5() + .text_color(pal.text_primary), + ), + ) + .child( + div() + .text_lg() + .font_weight(FontWeight::SEMIBOLD) + .child(tr!("Action Ring")), + ) + .child( + div() + .text_sm() + .text_color(pal.text_muted) + .child(tr!("8 actions")), + ); + + h_flex() + .size_full() + .items_start() + .gap_6() + .child( + v_flex().flex_1().gap_2().child(back).child( + div() + .flex_1() + .flex() + .items_center() + .justify_center() + .child(canvas(&actions, selected, view, pal)), + ), + ) + .child(action_panel(selected, &actions, view, pal)) + .into_any_element() +} + +/// The ring canvas: eight labelled slot circles on a ring, the decorative +/// centre ✕, and the selected slot accented — the Options+ customization +/// canvas. +fn canvas( + actions: &BTreeMap, + selected: RingSlot, + view: &Entity, + pal: Palette, +) -> AnyElement { + let c = CANVAS / 2.; + let mut layer = div().relative().w(px(CANVAS)).h(px(CANVAS)); + + // Decorative centre ✕ — the popup's cancel button, here just orientation. + layer = layer.child( + div() + .absolute() + .left(px(c - EDIT_CENTER / 2.)) + .top(px(c - EDIT_CENTER / 2.)) + .w(px(EDIT_CENTER)) + .h(px(EDIT_CENTER)) + .rounded_full() + .bg(pal.surface) + .border_1() + .border_color(pal.border) + .flex() + .items_center() + .justify_center() + .text_color(pal.text_muted) + .text_sm() + .child("✕"), + ); + + for (idx, slot) in RingSlot::ALL.into_iter().enumerate() { + let action = actions + .get(&slot) + .cloned() + .unwrap_or_else(|| default_ring_binding(slot)); + let angle = slot.angle_degrees().to_radians(); + let bx = c + EDIT_RADIUS * angle.sin() - EDIT_CIRCLE / 2.; + let by = c - EDIT_RADIUS * angle.cos() - EDIT_CIRCLE / 2.; + let is_selected = slot == selected; + + let view_pick = view.clone(); + let circle = div() + .id(("ring-editor-slot", idx)) + .absolute() + .left(px(bx)) + .top(px(by)) + .w(px(EDIT_CIRCLE)) + .h(px(EDIT_CIRCLE)) + .rounded_full() + .bg(pal.surface) + .border_2() + .border_color(if is_selected { + rgb(ACCENT_BLUE).into() + } else { + pal.border + }) + .cursor_pointer() + .hover(|s| s.border_color(rgb(ACCENT_BLUE))) + .flex() + .items_center() + .justify_center() + .on_click(move |_, _, cx| { + view_pick.update(cx, |v, vcx| { + v.set_ring_selected_slot(Some(slot)); + vcx.notify(); + }); + }) + .child( + svg() + .path(action_icon_path(&action)) + .size_6() + .text_color(pal.text_primary), + ); + layer = layer + .child(circle) + .child(label_pill(slot, &action, (bx, by), is_selected, pal)); + } + layer.into_any_element() +} + +/// The floating label pill beside a slot circle, placed outward from the +/// ring: east-side slots label to the right, west-side to the left, North +/// above and South below — matching the Options+ canvas. +fn label_pill( + slot: RingSlot, + action: &Action, + (bx, by): (f32, f32), + selected: bool, + pal: Palette, +) -> AnyElement { + let pill = div() + .px_3() + .py_1() + .rounded_md() + .bg(pal.surface) + .border_1() + .border_color(if selected { + rgb(ACCENT_BLUE).into() + } else { + pal.border + }) + .text_sm() + .text_color(pal.text_primary) + .child(localized_action_label(action)); + + let holder = div().absolute().w(px(PILL_W)).flex(); + let mid_y = by + EDIT_CIRCLE / 2. - 13.; + match slot { + RingSlot::NorthEast | RingSlot::East | RingSlot::SouthEast => holder + .left(px(bx + EDIT_CIRCLE + PILL_GAP)) + .top(px(mid_y)) + .justify_start(), + RingSlot::SouthWest | RingSlot::West | RingSlot::NorthWest => holder + .left(px(bx - PILL_GAP - PILL_W)) + .top(px(mid_y)) + .justify_end(), + RingSlot::North => holder + .left(px(bx + EDIT_CIRCLE / 2. - PILL_W / 2.)) + .top(px(by - 36.)) + .justify_center(), + RingSlot::South => holder + .left(px(bx + EDIT_CIRCLE / 2. - PILL_W / 2.)) + .top(px(by + EDIT_CIRCLE + PILL_GAP)) + .justify_center(), + } + .child(pill) + .into_any_element() +} + +/// The right-hand action panel for the selected slot: its compass name on +/// top, then the full categorized catalog plus the CUSTOM editor rows — +/// the same rows the popover flyout used, hosted persistently. +fn action_panel( + slot: RingSlot, + actions: &BTreeMap, + view: &Entity, + pal: Palette, +) -> AnyElement { + let current = actions + .get(&slot) + .cloned() + .unwrap_or_else(|| default_ring_binding(slot)); + + let view_pick = view.clone(); + let on_pick: PickFn = Rc::new(move |action, _window, cx| { + cx.update_global::(|state, _| state.commit_ring_binding(slot, action)); + view_pick.update(cx, |_, vcx| vcx.notify()); + }); + + let mut rows = action_rows("ring-editor-action", Some(¤t), &on_pick, pal); + rows.push(section_header(&rust_i18n::t!("CUSTOM"), pal)); + for (idx, kind) in [PayloadKind::Run, PayloadKind::PasteText] + .into_iter() + .enumerate() + { + rows.push(payload_editor_row(slot, kind, idx, ¤t, pal)); + } + + v_flex() + .w(px(PANEL_W)) + .flex_none() + .bg(pal.surface) + .border_1() + .border_color(pal.border) + .rounded_lg() + .p_1p5() + .child( + div() + .px_2() + .py_1p5() + .text_sm() + .font_weight(FontWeight::SEMIBOLD) + .child(format!("{} {}", slot.glyph(), tr!(slot.label()))), + ) + .child( + div() + .id("ring-editor-panel-scroll") + .max_h(px(PANEL_LIST_H)) + .overflow_y_scroll() + .child(v_flex().children(rows)), + ) + .into_any_element() +} diff --git a/crates/openlogi-gui/src/mouse_model/view.rs b/crates/openlogi-gui/src/mouse_model/view.rs index c21a42b0..01a8ecee 100644 --- a/crates/openlogi-gui/src/mouse_model/view.rs +++ b/crates/openlogi-gui/src/mouse_model/view.rs @@ -23,7 +23,6 @@ use crate::mouse_model::leader_lines::{ }; use crate::mouse_model::picker::{ GESTURE_BUTTON_ICON, RING_BUTTON_ICON, action_icon_path, action_picker, gesture_overview, - ring_overview, }; use crate::state::AppState; use crate::theme::{self, ACCENT_BLUE, Palette, SelectableStyle}; @@ -64,6 +63,9 @@ pub struct MouseModelView { /// state, so the popover's `on_open_change` — which runs outside paint — can /// reset it without tripping gpui's render-only guard. gesture_active_dir: Option, + /// Whether the Action Ring's full-page editor is shown in place of the + /// mouse diagram (see [`crate::mouse_model::ring_editor`]). + ring_editor_open: bool, /// Which ring slot the open Action Ring menu has activated (so its level-2 /// flyout card shows) — the ring counterpart of /// [`Self::gesture_active_dir`]. @@ -78,6 +80,7 @@ impl MouseModelView { Self { hovered: None, gesture_active_dir: None, + ring_editor_open: false, ring_active_slot: None, _state_obs: state_obs, } @@ -104,10 +107,32 @@ impl MouseModelView { pub(crate) fn set_ring_selected_slot(&mut self, slot: Option) { self.ring_active_slot = slot; } + + /// Show or hide the Action Ring's full-page editor. Callers must + /// `cx.notify()` to re-render. + pub(crate) fn set_ring_editor_open(&mut self, open: bool) { + self.ring_editor_open = open; + } + + /// The Action Ring's full-page editor, while its scratch flag is set — + /// rendered in place of the mouse diagram (the Options+ customization-page + /// shape, entered from the ring's hotspot / label card, left via its back + /// arrow). + fn ring_editor_page(&self, cx: &mut Context) -> Option { + self.ring_editor_open.then(|| { + let view = cx.entity(); + let pal = theme::palette(cx); + crate::mouse_model::ring_editor::page(&view, pal, cx) + }) + } } impl Render for MouseModelView { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + if let Some(page) = self.ring_editor_page(cx) { + return page; + } + let (asset, active, bindings, gesture_owner, glow) = cx .try_global::() .map(|s| { @@ -231,6 +256,7 @@ impl Render for MouseModelView { col.child(gesture_owner_selector(&capable, gesture_owner, &view, pal)) }) .child(canvas) + .into_any_element() } } @@ -483,36 +509,6 @@ where .content(move |_state, _window, cx| gesture_overview(&view, cx)) } -/// Wrap `trigger` in a left-click [`Popover`] hosting the Action Ring's -/// customization menu (see [`ring_overview`]) — the ring counterpart of -/// [`gesture_overview_popover`], resetting the activated slot on close so the -/// next open starts on the compass grid. -fn ring_overview_popover( - popover_id: impl Into, - anchor: Anchor, - trigger: Tr, - view: Entity, -) -> impl IntoElement -where - Tr: Selectable + IntoElement + 'static, -{ - let view_reset = view.clone(); - Popover::new(popover_id) - .appearance(false) - .mouse_button(MouseButton::Left) - .anchor(anchor) - .trigger(trigger) - .on_open_change(move |open, _window, cx| { - if !*open { - view_reset.update(cx, |v, vcx| { - v.set_ring_selected_slot(None); - vcx.notify(); - }); - } - }) - .content(move |_state, _window, cx| ring_overview(&view, cx)) -} - /// Position the popover wrapper at the label's slot in the side gutter and /// host a Popover whose trigger is the label card itself. Same picker /// content as the hotspot dot — clicking either entry point lands on the @@ -556,13 +552,20 @@ fn label_popover( ) .into_any_element() } else if label.id == ButtonId::ActionRing { - ring_overview_popover( - ("label-popover", idx), - Anchor::TopLeft, - trigger, - view.clone(), - ) - .into_any_element() + // The ring gets the full-page editor, not a popover: clicking the + // card swaps the diagram for it. + let view_open = view.clone(); + div() + .id(("label-ring-editor", idx)) + .cursor_pointer() + .on_click(move |_, _, cx| { + view_open.update(cx, |v, vcx| { + v.set_ring_editor_open(true); + vcx.notify(); + }); + }) + .child(trigger) + .into_any_element() } else { Popover::new(("label-popover", idx)) // `action_picker` draws its own `menu_card` surface, matching the @@ -712,7 +715,7 @@ impl RenderOnce for LabelTrigger { } } -fn localized_action_label(action: &Action) -> gpui::SharedString { +pub(crate) fn localized_action_label(action: &Action) -> gpui::SharedString { match action { Action::SetDpiPreset(index) => { tr!("DPI Preset %{index}", index => (index + 1).to_string()) @@ -793,13 +796,19 @@ fn hotspot_popover( ) .into_any_element() } else if hotspot.id == ButtonId::ActionRing { - ring_overview_popover( - ("hotspot-popover", idx), - Anchor::TopRight, - trigger, - view.clone(), - ) - .into_any_element() + // Same full-page editor entry as the ring's label card. + let view_open = view.clone(); + div() + .id(("hotspot-ring-editor", idx)) + .cursor_pointer() + .on_click(move |_, _, cx| { + view_open.update(cx, |v, vcx| { + v.set_ring_editor_open(true); + vcx.notify(); + }); + }) + .child(trigger) + .into_any_element() } else { Popover::new(("hotspot-popover", idx)) // `action_picker` draws its own `menu_card` surface, matching the From 3f3de935352e0586f3b36ef91707b7d6c616383e Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 17:12:58 -0700 Subject: [PATCH 27/42] =?UTF-8?q?feat(core):=20Folder=20action=20=E2=80=94?= =?UTF-8?q?=20ring=20sub-menus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Action::Folder(BTreeMap)` nests a sub-ring in a ring slot, one level deep by convention. Labels derive from the filled count (None entries are hidden positions), the catalog excludes it like the other payload actions, and the untagged-routing guard covers its table shape. Config grows `convert_ring_slot_to_folder` (a plain action survives as the folder''s North entry — conversion never silently discards a binding) and `set_ring_folder_slot` (implicit conversion; guarded by tests). --- crates/openlogi-core/src/binding.rs | 64 +++++++++++++++-- crates/openlogi-core/src/config.rs | 107 ++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 6 deletions(-) diff --git a/crates/openlogi-core/src/binding.rs b/crates/openlogi-core/src/binding.rs index e43a1bd5..87a30b66 100644 --- a/crates/openlogi-core/src/binding.rs +++ b/crates/openlogi-core/src/binding.rs @@ -480,6 +480,15 @@ pub enum Action { /// "Paste Text" action. Synthesized as Unicode key events rather than a /// clipboard round-trip, so the user's clipboard is never clobbered. PasteText(String), + + /// A sub-ring — the Options+ ring "Folder": firing this slot swaps the + /// on-screen ring to the nested actions instead of executing anything + /// (the overlay resolves it; it never reaches the injector). Sparse maps + /// are fine — empty positions just don't render. One level deep by + /// convention: the editor only offers folders at the top level, and the + /// overlay treats a folder nested inside a folder as a plain (warned, + /// ignored) leaf. + Folder(BTreeMap), } /// Split an [`Action::Run`] payload into its target and optional argument @@ -832,6 +841,13 @@ impl Action { } format!("Paste: {snippet}") } + Action::Folder(items) => { + let filled = items + .values() + .filter(|action| !matches!(action, Action::None)) + .count(); + format!("Folder ({filled})") + } } } @@ -875,7 +891,8 @@ impl Action { | Action::LockScreen | Action::Screenshot | Action::CaptureRegion - | Action::Run(_) => Category::System, + | Action::Run(_) + | Action::Folder(_) => Category::System, Action::PlayPause | Action::NextTrack | Action::PrevTrack @@ -894,10 +911,11 @@ impl Action { /// All pickable actions in a deterministic order. /// - /// [`Action::CustomShortcut`], [`Action::Run`] and [`Action::PasteText`] - /// are intentionally excluded — they carry payloads and are entered via - /// their dedicated editor rows ("Record shortcut…", "Run…", "Paste - /// text…"), not selected from the catalog. + /// [`Action::CustomShortcut`], [`Action::Run`], [`Action::PasteText`] + /// and [`Action::Folder`] are intentionally excluded — they carry + /// payloads and are entered via their dedicated editor rows ("Record + /// shortcut…", "Run…", "Paste text…", "Folder…"), not selected from the + /// catalog. #[must_use] pub fn catalog() -> Vec { vec![ @@ -1096,13 +1114,47 @@ mod tests { assert!( !matches!( action, - Action::CustomShortcut(_) | Action::Run(_) | Action::PasteText(_) + Action::CustomShortcut(_) + | Action::Run(_) + | Action::PasteText(_) + | Action::Folder(_) ), "catalog must not contain payload-carrying editor actions" ); } } + #[test] + fn folder_roundtrips_inside_a_ring_and_labels_by_filled_count() { + let folder = Action::Folder(BTreeMap::from([ + (RingSlot::North, Action::PasteText("4 Tracks".into())), + (RingSlot::East, Action::Run("https://chatgpt.com/".into())), + (RingSlot::South, Action::None), + ])); + assert_eq!(folder.label(), "Folder (2)", "None entries are hidden"); + + let mut ring: BTreeMap = RingSlot::ALL + .into_iter() + .map(|s| (s, default_ring_binding(s))) + .collect(); + ring.insert(RingSlot::NorthEast, folder.clone()); + let mut bindings = BTreeMap::new(); + bindings.insert(ButtonId::ActionRing, Binding::Ring(ring.clone())); + let back = binding_roundtrip(bindings); + assert_eq!(back[&ButtonId::ActionRing], Binding::Ring(ring)); + + // The untagged router must still land a bare `{ Folder = … }` table + // on Single, not mistake it for a one-entry Gesture/Ring map. + let parsed = toml::from_str::( + "bindings.Forward = { Folder = { North = \"Copy\" } }", + ) + .expect("deserialize"); + assert!(matches!( + parsed.bindings[&ButtonId::Forward], + Binding::Single(Action::Folder(_)) + )); + } + #[test] fn key_combo_win_modifier_renders_first() { let combo = KeyCombo { diff --git a/crates/openlogi-core/src/config.rs b/crates/openlogi-core/src/config.rs index ca96cf05..d4763392 100644 --- a/crates/openlogi-core/src/config.rs +++ b/crates/openlogi-core/src/config.rs @@ -284,6 +284,66 @@ impl Config { } } + /// Converts the Action Ring `slot` into a [`Action::Folder`], keeping a + /// plain action it previously held as the folder's North entry so the + /// conversion never silently discards a binding. Idempotent: a slot that + /// already holds a folder is left untouched. + pub fn convert_ring_slot_to_folder( + &mut self, + device_key: &str, + slot: crate::binding::RingSlot, + ) { + use crate::binding::RingSlot; + let current = self.ring_slot_action(device_key, slot); + if matches!(current, Some(Action::Folder(_))) { + return; + } + let mut items = std::collections::BTreeMap::new(); + if let Some(action) = current + && action != Action::None + { + items.insert(RingSlot::North, action); + } + self.set_ring_slot(device_key, slot, Action::Folder(items)); + } + + /// Records `action` for one `sub_slot` inside the folder at `slot`, + /// converting the slot into a folder first when it holds a plain action + /// (see [`Self::convert_ring_slot_to_folder`]). + pub fn set_ring_folder_slot( + &mut self, + device_key: &str, + slot: crate::binding::RingSlot, + sub_slot: crate::binding::RingSlot, + action: Action, + ) { + self.convert_ring_slot_to_folder(device_key, slot); + let entry = self + .devices + .entry(device_key.to_string()) + .or_default() + .bindings + .entry(ButtonId::ActionRing) + .or_insert_with(|| default_binding_for(ButtonId::ActionRing)); + if let Binding::Ring(map) = entry + && let Some(Action::Folder(items)) = map.get_mut(&slot) + { + items.insert(sub_slot, action); + } + } + + /// The action currently bound to one Action Ring `slot`, if the device + /// has a ring binding at all. + fn ring_slot_action(&self, device_key: &str, slot: crate::binding::RingSlot) -> Option { + self.devices + .get(device_key) + .and_then(|device| device.bindings.get(&ButtonId::ActionRing)) + .and_then(|binding| match binding { + Binding::Ring(map) => map.get(&slot).cloned(), + _ => None, + }) + } + /// Ensure `button` on `device_key` is a [`Binding::Gesture`], creating the /// device + a default binding if needed and upgrading a [`Binding::Single`] /// in place (its action kept as the [`GestureDirection::Click`]). Returns the @@ -636,6 +696,53 @@ mod tests { assert!(cfg.devices.is_empty()); } + #[test] + fn folder_conversion_keeps_the_old_action_and_edits_nest() { + use crate::binding::{Action, RingSlot}; + + let mut cfg = Config::default(); + cfg.set_ring_slot("2b042", RingSlot::East, Action::LockScreen); + cfg.convert_ring_slot_to_folder("2b042", RingSlot::East); + cfg.set_ring_folder_slot( + "2b042", + RingSlot::East, + RingSlot::SouthEast, + Action::Run("https://chatgpt.com/".into()), + ); + // Converting twice must not wipe the folder. + cfg.convert_ring_slot_to_folder("2b042", RingSlot::East); + + let Some(Binding::Ring(map)) = cfg.bindings_for("2b042").remove(&ButtonId::ActionRing) + else { + panic!("ActionRing must stay ring-shaped"); + }; + let Action::Folder(items) = &map[&RingSlot::East] else { + panic!("East must hold a folder, got {:?}", map[&RingSlot::East]); + }; + assert_eq!( + items[&RingSlot::North], + Action::LockScreen, + "the pre-conversion action survives as North" + ); + assert_eq!( + items[&RingSlot::SouthEast], + Action::Run("https://chatgpt.com/".into()) + ); + + // A folder edit on a slot holding a plain action converts implicitly. + cfg.set_ring_folder_slot("2b042", RingSlot::West, RingSlot::North, Action::Copy); + let Some(Binding::Ring(map)) = cfg.bindings_for("2b042").remove(&ButtonId::ActionRing) + else { + panic!("ActionRing must stay ring-shaped"); + }; + let Action::Folder(items) = &map[&RingSlot::West] else { + panic!("West must hold a folder"); + }; + // West's default (Undo) became the folder's North; the edit then + // overwrote North with Copy — dropping nothing silently. + assert_eq!(items[&RingSlot::North], Action::Copy); + } + #[test] fn set_ring_slot_seeds_a_complete_map() { use crate::binding::{RingSlot, default_ring_binding}; From 08d9ab161a2f070660a204e1b567ea5c21c5536e Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 17:12:58 -0700 Subject: [PATCH 28/42] =?UTF-8?q?feat(ipc):=20v13=20=E2=80=94=20Action=20g?= =?UTF-8?q?ains=20Folder=20on=20the=20wire?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/openlogi-agent-core/src/ipc.rs | 3 ++- crates/openlogi-agent-core/tests/wire_format.rs | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/openlogi-agent-core/src/ipc.rs b/crates/openlogi-agent-core/src/ipc.rs index 8ab9d271..898c8d2a 100644 --- a/crates/openlogi-agent-core/src/ipc.rs +++ b/crates/openlogi-agent-core/src/ipc.rs @@ -32,7 +32,8 @@ use serde::{Deserialize, Serialize}; /// v11: `next_ring_press` + `execute_action` appended (Action Ring overlay). /// v12: [`Action`] gains the appended `Run` / `PasteText` variants (they ride /// inside `execute_action` and config snapshots). -pub const PROTOCOL_VERSION: u32 = 12; +/// v13: [`Action`] gains the appended `Folder` variant (ring sub-menus). +pub const PROTOCOL_VERSION: u32 = 13; /// One Action Ring pad press, streamed to the GUI via /// [`Agent::next_ring_press`] so the on-screen ring opens (or confirms a diff --git a/crates/openlogi-agent-core/tests/wire_format.rs b/crates/openlogi-agent-core/tests/wire_format.rs index e51e6e9f..2d5eee78 100644 --- a/crates/openlogi-agent-core/tests/wire_format.rs +++ b/crates/openlogi-agent-core/tests/wire_format.rs @@ -62,7 +62,7 @@ fn assert_wire(value: &T, golden: &str) { /// that makes that visible in the same diff. #[test] fn protocol_version_is_pinned() { - assert_eq!(PROTOCOL_VERSION, 12); + assert_eq!(PROTOCOL_VERSION, 13); } /// tarpc encodes the request enum's variant index, so trait *method order* is @@ -105,6 +105,17 @@ fn request_variant_order() { }, "112e024869", ); + // v13's Folder: variant index, then the BTreeMap as varint length + + // (RingSlot variant, Action variant) pairs. + assert_wire( + &AgentRequest::ExecuteAction { + action: Action::Folder(std::collections::BTreeMap::from([( + openlogi_core::binding::RingSlot::North, + Action::Copy, + )])), + }, + "112f010006", + ); } #[test] From 61b6ab8423274dd6a4bbbf33813332f6c992377d Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 17:12:58 -0700 Subject: [PATCH 29/42] =?UTF-8?q?feat(inject):=20guard=20Folder=20?= =?UTF-8?q?=E2=80=94=20containers=20never=20execute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The overlay resolves folders; one reaching the injector is a routing bug surfaced as a warning, not a dispatch. --- crates/openlogi-inject/src/inject/linux.rs | 3 +++ crates/openlogi-inject/src/inject/macos.rs | 3 +++ crates/openlogi-inject/src/inject/windows.rs | 3 +++ 3 files changed, 9 insertions(+) diff --git a/crates/openlogi-inject/src/inject/linux.rs b/crates/openlogi-inject/src/inject/linux.rs index fca8e269..3b02a580 100644 --- a/crates/openlogi-inject/src/inject/linux.rs +++ b/crates/openlogi-inject/src/inject/linux.rs @@ -118,6 +118,9 @@ pub(super) fn execute(action: &Action) { Action::PasteText(_) => { tracing::warn!("PasteText is not implemented on Linux yet — press ignored"); } + Action::Folder(_) => { + tracing::warn!("folder reached the injector — containers are resolved by the ring"); + } } } diff --git a/crates/openlogi-inject/src/inject/macos.rs b/crates/openlogi-inject/src/inject/macos.rs index 2546d1aa..d35a3cca 100644 --- a/crates/openlogi-inject/src/inject/macos.rs +++ b/crates/openlogi-inject/src/inject/macos.rs @@ -159,6 +159,9 @@ pub(super) fn execute(action: &Action) { Action::PasteText(_) => { tracing::warn!("PasteText is not implemented on macOS yet — press ignored"); } + Action::Folder(_) => { + tracing::warn!("folder reached the injector — containers are resolved by the ring"); + } } } diff --git a/crates/openlogi-inject/src/inject/windows.rs b/crates/openlogi-inject/src/inject/windows.rs index 735aefbd..2cf3d78f 100644 --- a/crates/openlogi-inject/src/inject/windows.rs +++ b/crates/openlogi-inject/src/inject/windows.rs @@ -128,6 +128,9 @@ pub(super) fn execute(action: &Action) { Action::CustomShortcut(combo) => post_custom_shortcut(combo), Action::Run(payload) => run_target(payload), Action::PasteText(text) => post_text(text), + Action::Folder(_) => { + tracing::warn!("folder reached the injector — containers are resolved by the ring"); + } Action::None => {} } } From f269cc6f2aeb8b62fe340d0ebb2630c0d22d6e63 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 17:12:59 -0700 Subject: [PATCH 30/42] =?UTF-8?q?feat(gui):=20ring=20folders=20=E2=80=94?= =?UTF-8?q?=20overlay=20sub-rings=20and=20editor=20drill-in?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On-screen ring: firing a folder slot swaps the ring to its contents in place (sparse positions simply don''t render), the centre becomes a ← that steps back to the main ring, and Esc / outside-click still close outright. Editor: clicking an already-selected folder circle — or the panel''s "Open Folder" row — drills into the folder on the same canvas, empty positions render as dashed "+" add targets, a "Folder…" row converts a plain slot (keeping its action as North), and the payload dialog carries an EditTarget so Run / Paste Text commit to top-level slots and folder sub-slots alike. Editor polish in the same pass: the canvas is sized so label pills stay out of the action panel, pills are clickable selectors, and building the page no longer reads the view entity during its own render (a gpui panic that aborted the GUI the moment the editor opened). --- crates/openlogi-gui/action-icons/folder.svg | 1 + crates/openlogi-gui/locales/da.yml | 2 + crates/openlogi-gui/locales/de.yml | 2 + crates/openlogi-gui/locales/el.yml | 2 + crates/openlogi-gui/locales/en.yml | 2 + crates/openlogi-gui/locales/es.yml | 2 + crates/openlogi-gui/locales/fi.yml | 2 + crates/openlogi-gui/locales/fr.yml | 2 + crates/openlogi-gui/locales/it.yml | 2 + crates/openlogi-gui/locales/ja.yml | 2 + crates/openlogi-gui/locales/ko.yml | 2 + crates/openlogi-gui/locales/nb.yml | 2 + crates/openlogi-gui/locales/nl.yml | 2 + crates/openlogi-gui/locales/pl.yml | 2 + crates/openlogi-gui/locales/pt-BR.yml | 2 + crates/openlogi-gui/locales/pt-PT.yml | 2 + crates/openlogi-gui/locales/ru.yml | 2 + crates/openlogi-gui/locales/sv.yml | 2 + crates/openlogi-gui/locales/zh-CN.yml | 2 + crates/openlogi-gui/locales/zh-HK.yml | 2 + crates/openlogi-gui/locales/zh-TW.yml | 2 + crates/openlogi-gui/src/app_assets.rs | 1 + crates/openlogi-gui/src/mouse_model/picker.rs | 12 +- .../src/mouse_model/ring_editor.rs | 383 ++++++++++++++---- crates/openlogi-gui/src/mouse_model/view.rs | 28 +- crates/openlogi-gui/src/ring.rs | 122 ++++-- crates/openlogi-gui/src/state.rs | 28 ++ .../src/windows/ring_action_editor.rs | 102 +++-- docs/CONFIGURATION.md | 6 +- 29 files changed, 582 insertions(+), 141 deletions(-) create mode 100644 crates/openlogi-gui/action-icons/folder.svg diff --git a/crates/openlogi-gui/action-icons/folder.svg b/crates/openlogi-gui/action-icons/folder.svg new file mode 100644 index 00000000..47fe1aae --- /dev/null +++ b/crates/openlogi-gui/action-icons/folder.svg @@ -0,0 +1 @@ + diff --git a/crates/openlogi-gui/locales/da.yml b/crates/openlogi-gui/locales/da.yml index d1752fb1..ae8ada71 100644 --- a/crates/openlogi-gui/locales/da.yml +++ b/crates/openlogi-gui/locales/da.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Tekst der skal indsættes" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "En URL, fil eller et program. Efter en programsti adskiller || argumenterne; %VAR%-miljøvariabler udvides." "Typed at the cursor exactly as written.": "Skrives ved markøren præcis som skrevet." +"Folder": "Mappe" +"Open Folder": "Åbn mappe" "Up": "Op" "Down": "Ned" "Left": "Venstre" diff --git a/crates/openlogi-gui/locales/de.yml b/crates/openlogi-gui/locales/de.yml index 7b702ae1..0e742ad6 100644 --- a/crates/openlogi-gui/locales/de.yml +++ b/crates/openlogi-gui/locales/de.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Einzufügender Text" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Eine URL, Datei oder ein Programm. Nach einem Programmpfad trennt || die Argumente; %VAR%-Umgebungsvariablen werden expandiert." "Typed at the cursor exactly as written.": "Wird an der Cursorposition genau wie geschrieben eingetippt." +"Folder": "Ordner" +"Open Folder": "Ordner öffnen" "Up": "Oben" "Down": "Unten" "Left": "Links" diff --git a/crates/openlogi-gui/locales/el.yml b/crates/openlogi-gui/locales/el.yml index fb953818..652b6621 100644 --- a/crates/openlogi-gui/locales/el.yml +++ b/crates/openlogi-gui/locales/el.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Κείμενο για επικόλληση" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Ένα URL, αρχείο ή πρόγραμμα. Μετά τη διαδρομή προγράμματος, το || διαχωρίζει τα ορίσματα· οι μεταβλητές περιβάλλοντος %VAR% αναπτύσσονται." "Typed at the cursor exactly as written.": "Πληκτρολογείται στον δρομέα ακριβώς όπως γράφτηκε." +"Folder": "Φάκελος" +"Open Folder": "Άνοιγμα φακέλου" "Up": "Πάνω" "Down": "Κάτω" "Left": "Αριστερά" diff --git a/crates/openlogi-gui/locales/en.yml b/crates/openlogi-gui/locales/en.yml index 007ebfb3..3cfeb3e7 100644 --- a/crates/openlogi-gui/locales/en.yml +++ b/crates/openlogi-gui/locales/en.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Text to paste" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand." "Typed at the cursor exactly as written.": "Typed at the cursor exactly as written." +"Folder": "Folder" +"Open Folder": "Open Folder" "Up": "Up" "Down": "Down" "Left": "Left" diff --git a/crates/openlogi-gui/locales/es.yml b/crates/openlogi-gui/locales/es.yml index 9f94e328..544617e4 100644 --- a/crates/openlogi-gui/locales/es.yml +++ b/crates/openlogi-gui/locales/es.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Texto a pegar" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Una URL, archivo o programa. Tras la ruta del programa, || separa los argumentos; las variables de entorno %VAR% se expanden." "Typed at the cursor exactly as written.": "Se escribe en el cursor exactamente como está." +"Folder": "Carpeta" +"Open Folder": "Abrir carpeta" "Up": "Arriba" "Down": "Abajo" "Left": "Izquierda" diff --git a/crates/openlogi-gui/locales/fi.yml b/crates/openlogi-gui/locales/fi.yml index b33f2099..7842118d 100644 --- a/crates/openlogi-gui/locales/fi.yml +++ b/crates/openlogi-gui/locales/fi.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Liitettävä teksti" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL, tiedosto tai ohjelma. Ohjelmapolun jälkeen || erottaa argumentit; %VAR%-ympäristömuuttujat laajennetaan." "Typed at the cursor exactly as written.": "Kirjoitetaan kohdistimen kohtaan täsmälleen sellaisenaan." +"Folder": "Kansio" +"Open Folder": "Avaa kansio" "Up": "Ylös" "Down": "Alas" "Left": "Vasen" diff --git a/crates/openlogi-gui/locales/fr.yml b/crates/openlogi-gui/locales/fr.yml index 8c036fad..02eaed99 100644 --- a/crates/openlogi-gui/locales/fr.yml +++ b/crates/openlogi-gui/locales/fr.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Texte à coller" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Une URL, un fichier ou un programme. Après le chemin du programme, || sépare les arguments ; les variables d'environnement %VAR% sont développées." "Typed at the cursor exactly as written.": "Saisi au curseur exactement tel quel." +"Folder": "Dossier" +"Open Folder": "Ouvrir le dossier" "Up": "Haut" "Down": "Bas" "Left": "Gauche" diff --git a/crates/openlogi-gui/locales/it.yml b/crates/openlogi-gui/locales/it.yml index b48e15fb..f9e7b9cf 100644 --- a/crates/openlogi-gui/locales/it.yml +++ b/crates/openlogi-gui/locales/it.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Testo da incollare" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Un URL, file o programma. Dopo il percorso del programma, || separa gli argomenti; le variabili d'ambiente %VAR% vengono espanse." "Typed at the cursor exactly as written.": "Digitato al cursore esattamente come scritto." +"Folder": "Cartella" +"Open Folder": "Apri cartella" "Up": "Su" "Down": "Giù" "Left": "Sinistra" diff --git a/crates/openlogi-gui/locales/ja.yml b/crates/openlogi-gui/locales/ja.yml index 969abf19..29bc8b80 100644 --- a/crates/openlogi-gui/locales/ja.yml +++ b/crates/openlogi-gui/locales/ja.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "貼り付けるテキスト" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL、ファイル、またはプログラム。プログラムのパスの後に || で引数を区切ります。%VAR% 環境変数は展開されます。" "Typed at the cursor exactly as written.": "書かれたとおりにカーソル位置に入力されます。" +"Folder": "フォルダー" +"Open Folder": "フォルダーを開く" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/ko.yml b/crates/openlogi-gui/locales/ko.yml index cc2b73a3..ad2ee9e7 100644 --- a/crates/openlogi-gui/locales/ko.yml +++ b/crates/openlogi-gui/locales/ko.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "붙여넣을 텍스트" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL, 파일 또는 프로그램. 프로그램 경로 뒤에 || 로 인수를 구분합니다. %VAR% 환경 변수는 확장됩니다." "Typed at the cursor exactly as written.": "쓰인 그대로 커서 위치에 입력됩니다." +"Folder": "폴더" +"Open Folder": "폴더 열기" "Up": "위" "Down": "아래" "Left": "왼쪽" diff --git a/crates/openlogi-gui/locales/nb.yml b/crates/openlogi-gui/locales/nb.yml index 3ffac30a..293cc55a 100644 --- a/crates/openlogi-gui/locales/nb.yml +++ b/crates/openlogi-gui/locales/nb.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Tekst som skal limes inn" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "En URL, fil eller et program. Etter en programsti skiller || argumentene; %VAR%-miljøvariabler utvides." "Typed at the cursor exactly as written.": "Skrives ved markøren nøyaktig som skrevet." +"Folder": "Mappe" +"Open Folder": "Åpne mappe" "Up": "Opp" "Down": "Ned" "Left": "Venstre" diff --git a/crates/openlogi-gui/locales/nl.yml b/crates/openlogi-gui/locales/nl.yml index 4cf7d472..7e420a93 100644 --- a/crates/openlogi-gui/locales/nl.yml +++ b/crates/openlogi-gui/locales/nl.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Te plakken tekst" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Een URL, bestand of programma. Na een programmapad scheidt || de argumenten; %VAR%-omgevingsvariabelen worden uitgebreid." "Typed at the cursor exactly as written.": "Wordt bij de cursor exact zoals geschreven getypt." +"Folder": "Map" +"Open Folder": "Map openen" "Up": "Omhoog" "Down": "Omlaag" "Left": "Links" diff --git a/crates/openlogi-gui/locales/pl.yml b/crates/openlogi-gui/locales/pl.yml index 7a20a494..88a3a45f 100644 --- a/crates/openlogi-gui/locales/pl.yml +++ b/crates/openlogi-gui/locales/pl.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Tekst do wklejenia" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL, plik lub program. Po ścieżce programu || oddziela argumenty; zmienne środowiskowe %VAR% są rozwijane." "Typed at the cursor exactly as written.": "Wpisywany przy kursorze dokładnie tak, jak napisano." +"Folder": "Folder" +"Open Folder": "Otwórz folder" "Up": "W górę" "Down": "W dół" "Left": "W lewo" diff --git a/crates/openlogi-gui/locales/pt-BR.yml b/crates/openlogi-gui/locales/pt-BR.yml index 66ab1302..a98465e6 100644 --- a/crates/openlogi-gui/locales/pt-BR.yml +++ b/crates/openlogi-gui/locales/pt-BR.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Texto a colar" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Um URL, arquivo ou programa. Após o caminho do programa, || separa os argumentos; variáveis de ambiente %VAR% são expandidas." "Typed at the cursor exactly as written.": "Digitado no cursor exatamente como escrito." +"Folder": "Pasta" +"Open Folder": "Abrir pasta" "Up": "Cima" "Down": "Baixo" "Left": "Esquerda" diff --git a/crates/openlogi-gui/locales/pt-PT.yml b/crates/openlogi-gui/locales/pt-PT.yml index 2df7ecd4..7fc14a35 100644 --- a/crates/openlogi-gui/locales/pt-PT.yml +++ b/crates/openlogi-gui/locales/pt-PT.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Texto a colar" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "Um URL, ficheiro ou programa. Após o caminho do programa, || separa os argumentos; as variáveis de ambiente %VAR% são expandidas." "Typed at the cursor exactly as written.": "Escrito no cursor exatamente como está." +"Folder": "Pasta" +"Open Folder": "Abrir pasta" "Up": "Cima" "Down": "Baixo" "Left": "Esquerda" diff --git a/crates/openlogi-gui/locales/ru.yml b/crates/openlogi-gui/locales/ru.yml index 2f48d52a..3cc047dd 100644 --- a/crates/openlogi-gui/locales/ru.yml +++ b/crates/openlogi-gui/locales/ru.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Текст для вставки" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL, файл или программа. После пути к программе || отделяет аргументы; переменные окружения %VAR% раскрываются." "Typed at the cursor exactly as written.": "Вводится у курсора точно как написано." +"Folder": "Папка" +"Open Folder": "Открыть папку" "Up": "Вверх" "Down": "Вниз" "Left": "Влево" diff --git a/crates/openlogi-gui/locales/sv.yml b/crates/openlogi-gui/locales/sv.yml index 02620238..fed8e20a 100644 --- a/crates/openlogi-gui/locales/sv.yml +++ b/crates/openlogi-gui/locales/sv.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "Text att klistra in" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "En URL, fil eller ett program. Efter en programsökväg avgränsar || argumenten; %VAR%-miljövariabler expanderas." "Typed at the cursor exactly as written.": "Skrivs vid markören exakt som skrivet." +"Folder": "Mapp" +"Open Folder": "Öppna mapp" "Up": "Upp" "Down": "Ned" "Left": "Vänster" diff --git a/crates/openlogi-gui/locales/zh-CN.yml b/crates/openlogi-gui/locales/zh-CN.yml index 98e8fa40..d00ea06b 100644 --- a/crates/openlogi-gui/locales/zh-CN.yml +++ b/crates/openlogi-gui/locales/zh-CN.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "要粘贴的文本" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL、文件或程序。在程序路径后用 || 分隔参数;%VAR% 环境变量会展开。" "Typed at the cursor exactly as written.": "按原样在光标处输入。" +"Folder": "文件夹" +"Open Folder": "打开文件夹" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/zh-HK.yml b/crates/openlogi-gui/locales/zh-HK.yml index 1eed8956..a25ddbda 100644 --- a/crates/openlogi-gui/locales/zh-HK.yml +++ b/crates/openlogi-gui/locales/zh-HK.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "要貼上的文字" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL、檔案或程式。在程式路徑後以 || 分隔參數;%VAR% 環境變數會展開。" "Typed at the cursor exactly as written.": "按原樣在游標處輸入。" +"Folder": "資料夾" +"Open Folder": "開啟資料夾" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/zh-TW.yml b/crates/openlogi-gui/locales/zh-TW.yml index 88b430af..b8085e0e 100644 --- a/crates/openlogi-gui/locales/zh-TW.yml +++ b/crates/openlogi-gui/locales/zh-TW.yml @@ -171,6 +171,8 @@ _version: 1 "Text to paste": "要貼上的文字" "A URL, file, or program. After a program path, use || to separate arguments; %VAR% environment variables expand.": "URL、檔案或程式。在程式路徑後以 || 分隔參數;%VAR% 環境變數會展開。" "Typed at the cursor exactly as written.": "按原樣在游標處輸入。" +"Folder": "資料夾" +"Open Folder": "開啟資料夾" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/src/app_assets.rs b/crates/openlogi-gui/src/app_assets.rs index 28d730c2..76dd0cda 100644 --- a/crates/openlogi-gui/src/app_assets.rs +++ b/crates/openlogi-gui/src/app_assets.rs @@ -45,6 +45,7 @@ const ACTION_ICONS: &[(&str, &[u8])] = &[ ("action-icons/circle-arrow-right.svg", include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/action-icons/circle-arrow-right.svg"))), ("action-icons/clipboard-paste.svg", include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/action-icons/clipboard-paste.svg"))), ("action-icons/copy.svg", include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/action-icons/copy.svg"))), + ("action-icons/folder.svg", include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/action-icons/folder.svg"))), ("action-icons/gauge.svg", include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/action-icons/gauge.svg"))), ("action-icons/grid-3x3.svg", include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/action-icons/grid-3x3.svg"))), ("action-icons/keyboard.svg", include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/action-icons/keyboard.svg"))), diff --git a/crates/openlogi-gui/src/mouse_model/picker.rs b/crates/openlogi-gui/src/mouse_model/picker.rs index 7a808c1b..509d063b 100644 --- a/crates/openlogi-gui/src/mouse_model/picker.rs +++ b/crates/openlogi-gui/src/mouse_model/picker.rs @@ -34,8 +34,7 @@ use crate::data::mouse_buttons::{ use crate::mouse_model::view::MouseModelView; use crate::state::AppState; use crate::theme::{self, ACCENT_BLUE, Palette, SelectableStyle}; -use crate::windows::ring_action_editor::PayloadKind; -use openlogi_core::binding::RingSlot; +use crate::windows::ring_action_editor::{EditTarget, PayloadKind}; /// Floor width for the [`action_picker`] popover. The action labels drive the /// actual width; this only stops the list from collapsing too narrow. Matches @@ -111,10 +110,10 @@ pub fn gesture_overview( /// A "Run Command…" / "Paste Text…" action-list row. Unlike the catalog rows it /// commits nothing itself — it opens the payload-editor dialog seeded from -/// the slot's current action. Selection styling still mirrors the catalog so -/// a slot bound to a payload action shows where its value lives. +/// the target's current action. Selection styling still mirrors the catalog +/// so a slot bound to a payload action shows where its value lives. pub(crate) fn payload_editor_row( - slot: RingSlot, + target: EditTarget, kind: PayloadKind, idx: usize, current: &Action, @@ -144,7 +143,7 @@ pub(crate) fn payload_editor_row( ) }) .on_click(move |_event, _window, cx| { - crate::windows::ring_action_editor::open(slot, kind, cx); + crate::windows::ring_action_editor::open(target, kind, cx); }) .into_any_element() } @@ -388,6 +387,7 @@ pub(crate) fn action_icon_path(action: &Action) -> &'static str { Action::HorizontalScrollLeft => "action-icons/chevrons-left.svg", Action::HorizontalScrollRight => "action-icons/chevrons-right.svg", Action::CustomShortcut(_) => "action-icons/keyboard.svg", + Action::Folder(_) => "action-icons/folder.svg", } } diff --git a/crates/openlogi-gui/src/mouse_model/ring_editor.rs b/crates/openlogi-gui/src/mouse_model/ring_editor.rs index c055fced..8261923b 100644 --- a/crates/openlogi-gui/src/mouse_model/ring_editor.rs +++ b/crates/openlogi-gui/src/mouse_model/ring_editor.rs @@ -5,16 +5,18 @@ //! //! Clicking the ring's hotspot or label card on the mouse diagram swaps the //! diagram for this page (scratch state on [`MouseModelView`]); the back -//! arrow swaps back. Selection is the same `ring_active_slot` scratch state -//! the old flyout used, so the action panel and the canvas highlight always -//! agree. +//! arrow swaps back. Folders drill in: clicking an already-selected folder +//! circle (or the panel's "Open Folder" row) shows the folder's contents on +//! the same canvas — empty positions render as dashed "+" targets — and the +//! back arrow then pops to the top level first. use std::collections::BTreeMap; use std::rc::Rc; use gpui::{ AnyElement, BorrowAppContext as _, Context, Entity, FontWeight, InteractiveElement, - IntoElement, ParentElement, StatefulInteractiveElement as _, Styled, div, px, rgb, svg, + IntoElement, ParentElement, SharedString, StatefulInteractiveElement as _, Styled, div, + prelude::FluentBuilder as _, px, rgb, svg, }; use gpui_component::{h_flex, v_flex}; use openlogi_core::binding::{Action, RingSlot, default_ring_binding}; @@ -25,42 +27,97 @@ use crate::mouse_model::picker::{ use crate::mouse_model::view::{MouseModelView, localized_action_label}; use crate::state::AppState; use crate::theme::{ACCENT_BLUE, Palette}; -use crate::windows::ring_action_editor::PayloadKind; +use crate::windows::ring_action_editor::{EditTarget, PayloadKind}; /// Radius of the circle the editor's slot buttons sit on. -const EDIT_RADIUS: f32 = 150.; +const EDIT_RADIUS: f32 = 130.; /// Diameter of one slot circle on the canvas. -const EDIT_CIRCLE: f32 = 56.; -/// Diameter of the decorative centre ✕. -const EDIT_CENTER: f32 = 40.; -/// Square canvas the ring is drawn in (labels hang outside the circle ring, -/// inside this box). -const CANVAS: f32 = 440.; +const EDIT_CIRCLE: f32 = 52.; +/// Diameter of the decorative centre circle. +const EDIT_CENTER: f32 = 38.; /// Width reserved for a floating label pill column beside a circle. -const PILL_W: f32 = 170.; +const PILL_W: f32 = 150.; /// Gap between a circle and its label pill. const PILL_GAP: f32 = 10.; +/// Square canvas the ring is drawn in — sized so the side pills stay inside +/// it instead of spilling under the action panel. +const CANVAS: f32 = 2. * (EDIT_RADIUS + EDIT_CIRCLE / 2. + PILL_GAP + PILL_W); /// Fixed width of the right-hand action panel. const PANEL_W: f32 = 260.; /// Height cap for the action panel's scrolling list. const PANEL_LIST_H: f32 = 420.; -/// Build the full-page editor. Rendered by [`MouseModelView`] in place of the -/// mouse diagram while its `ring_editor_open` scratch flag is set. +/// One canvas position: the slot, what's bound there (`None` = an empty +/// folder position rendered as a dashed add target), and whether it is the +/// active selection. +struct CanvasSlot { + slot: RingSlot, + action: Option, + selected: bool, +} + +/// Build the full-page editor. Rendered by [`MouseModelView`] in place of +/// the mouse diagram while its `ring_editor_open` scratch flag is set. +/// +/// `selected` / `folder_open` come from the caller's `&self` — this runs +/// inside the view's own `render`, where `view.read(cx)` would panic +/// ("cannot read … while it is already being updated"); `view` is only +/// cloned into event closures. pub(crate) fn page( + selected: Option, + folder_open: Option, view: &Entity, pal: Palette, cx: &mut Context, ) -> AnyElement { - let selected = view - .read(cx) - .ring_selected_slot() - .unwrap_or(RingSlot::North); + let selected = selected.unwrap_or(RingSlot::North); let actions: BTreeMap = cx .try_global::() .map(|s| s.ring_slots_for_current().into_iter().collect()) .unwrap_or_default(); + // Drilled-in state survives the folder being replaced from elsewhere + // (another window, a config reload): fall back to the top level unless + // the slot still holds a folder. + let folder_open = + folder_open.filter(|slot| matches!(actions.get(slot), Some(Action::Folder(_)))); + let folder_items: Option<&BTreeMap> = + folder_open.and_then(|slot| match actions.get(&slot) { + Some(Action::Folder(items)) => Some(items), + _ => None, + }); + + let canvas_slots: Vec = RingSlot::ALL + .into_iter() + .map(|slot| CanvasSlot { + slot, + action: match folder_items { + Some(items) => items + .get(&slot) + .filter(|action| !matches!(action, Action::None)) + .cloned(), + None => Some( + actions + .get(&slot) + .cloned() + .unwrap_or_else(|| default_ring_binding(slot)), + ), + }, + selected: slot == selected, + }) + .collect(); + + let title: SharedString = match folder_open { + Some(slot) => format!( + "{} {} › {}", + tr!("Action Ring"), + slot.glyph(), + tr!("Folder") + ) + .into(), + None => tr!("Action Ring"), + }; + let view_back = view.clone(); let back = h_flex() .items_center() @@ -74,8 +131,15 @@ pub(crate) fn page( .hover(|s| s.bg(pal.surface)) .on_click(move |_, _, cx| { view_back.update(cx, |v, vcx| { - v.set_ring_editor_open(false); - v.set_ring_selected_slot(None); + // Pop the folder first; only a top-level back leaves + // the editor. + if folder_open.is_some() { + v.set_ring_folder_open(None); + v.set_ring_selected_slot(folder_open); + } else { + v.set_ring_editor_open(false); + v.set_ring_selected_slot(None); + } vcx.notify(); }); }) @@ -90,7 +154,7 @@ pub(crate) fn page( div() .text_lg() .font_weight(FontWeight::SEMIBOLD) - .child(tr!("Action Ring")), + .child(title), ) .child( div() @@ -110,26 +174,27 @@ pub(crate) fn page( .flex() .items_center() .justify_center() - .child(canvas(&actions, selected, view, pal)), + .child(canvas(&canvas_slots, folder_open, view, pal)), ), ) - .child(action_panel(selected, &actions, view, pal)) + .child(action_panel(selected, folder_open, &actions, view, pal)) .into_any_element() } -/// The ring canvas: eight labelled slot circles on a ring, the decorative -/// centre ✕, and the selected slot accented — the Options+ customization -/// canvas. +/// The ring canvas: eight slot circles on a ring plus the decorative centre. +/// Empty folder positions are dashed "+" add targets; the selected slot (and +/// its pill) is accented. fn canvas( - actions: &BTreeMap, - selected: RingSlot, + slots: &[CanvasSlot], + folder_open: Option, view: &Entity, pal: Palette, ) -> AnyElement { let c = CANVAS / 2.; let mut layer = div().relative().w(px(CANVAS)).h(px(CANVAS)); - // Decorative centre ✕ — the popup's cancel button, here just orientation. + // Decorative centre — the popup's ✕ (or the folder's ← back) is only in + // the on-screen ring; here it is orientation. layer = layer.child( div() .absolute() @@ -146,20 +211,33 @@ fn canvas( .justify_center() .text_color(pal.text_muted) .text_sm() - .child("✕"), + .child(if folder_open.is_some() { "←" } else { "✕" }), ); - for (idx, slot) in RingSlot::ALL.into_iter().enumerate() { - let action = actions - .get(&slot) - .cloned() - .unwrap_or_else(|| default_ring_binding(slot)); + for (idx, canvas_slot) in slots.iter().enumerate() { + let slot = canvas_slot.slot; + let is_selected = canvas_slot.selected; let angle = slot.angle_degrees().to_radians(); let bx = c + EDIT_RADIUS * angle.sin() - EDIT_CIRCLE / 2.; let by = c - EDIT_RADIUS * angle.cos() - EDIT_CIRCLE / 2.; - let is_selected = slot == selected; + // Clicking selects; clicking an already-selected folder drills in. + let drills = folder_open.is_none() + && is_selected + && matches!(canvas_slot.action, Some(Action::Folder(_))); let view_pick = view.clone(); + let on_activate = move |cx: &mut gpui::App| { + view_pick.update(cx, |v, vcx| { + if drills { + v.set_ring_folder_open(Some(slot)); + v.set_ring_selected_slot(Some(RingSlot::North)); + } else { + v.set_ring_selected_slot(Some(slot)); + } + vcx.notify(); + }); + }; + let circle = div() .id(("ring-editor-slot", idx)) .absolute() @@ -168,48 +246,76 @@ fn canvas( .w(px(EDIT_CIRCLE)) .h(px(EDIT_CIRCLE)) .rounded_full() - .bg(pal.surface) - .border_2() - .border_color(if is_selected { - rgb(ACCENT_BLUE).into() - } else { - pal.border - }) .cursor_pointer() - .hover(|s| s.border_color(rgb(ACCENT_BLUE))) .flex() .items_center() .justify_center() - .on_click(move |_, _, cx| { - view_pick.update(cx, |v, vcx| { - v.set_ring_selected_slot(Some(slot)); - vcx.notify(); - }); - }) - .child( - svg() - .path(action_icon_path(&action)) - .size_6() - .text_color(pal.text_primary), - ); - layer = layer - .child(circle) - .child(label_pill(slot, &action, (bx, by), is_selected, pal)); + .on_click({ + let on_activate = on_activate.clone(); + move |_, _, cx| on_activate(cx) + }); + let circle = match &canvas_slot.action { + Some(action) => circle + .bg(pal.surface) + .border_2() + .border_color(if is_selected { + rgb(ACCENT_BLUE).into() + } else { + pal.border + }) + .hover(|s| s.border_color(rgb(ACCENT_BLUE))) + .child( + svg() + .path(action_icon_path(action)) + .size_6() + .text_color(pal.text_primary), + ), + // An empty folder position: a dashed add target. + None => circle + .border_2() + .border_dashed() + .border_color(if is_selected { + rgb(ACCENT_BLUE).into() + } else { + pal.border + }) + .hover(|s| s.border_color(rgb(ACCENT_BLUE))) + .child(div().text_lg().text_color(pal.text_muted).child("+")), + }; + layer = layer.child(circle); + + if let Some(action) = &canvas_slot.action { + layer = layer.child(label_pill( + slot, + action, + (bx, by), + is_selected, + on_activate, + pal, + )); + } } layer.into_any_element() } /// The floating label pill beside a slot circle, placed outward from the /// ring: east-side slots label to the right, west-side to the left, North -/// above and South below — matching the Options+ canvas. +/// above and South below — matching the Options+ canvas. Clicking the pill +/// selects (or drills, same as its circle). fn label_pill( slot: RingSlot, action: &Action, (bx, by): (f32, f32), selected: bool, + on_activate: impl Fn(&mut gpui::App) + Clone + 'static, pal: Palette, ) -> AnyElement { + let idx = RingSlot::ALL + .iter() + .position(|s| *s == slot) + .unwrap_or_default(); let pill = div() + .id(("ring-editor-pill", idx)) .px_3() .py_1() .rounded_md() @@ -220,9 +326,15 @@ fn label_pill( } else { pal.border }) + .cursor_pointer() + .hover(|s| s.border_color(rgb(ACCENT_BLUE))) .text_sm() .text_color(pal.text_primary) - .child(localized_action_label(action)); + .whitespace_nowrap() + .overflow_hidden() + .max_w(px(PILL_W)) + .child(localized_action_label(action)) + .on_click(move |_, _, cx| on_activate(cx)); let holder = div().absolute().w(px(PILL_W)).flex(); let mid_y = by + EDIT_CIRCLE / 2. - 13.; @@ -249,32 +361,85 @@ fn label_pill( } /// The right-hand action panel for the selected slot: its compass name on -/// top, then the full categorized catalog plus the CUSTOM editor rows — -/// the same rows the popover flyout used, hosted persistently. +/// top, then the full categorized catalog plus the CUSTOM editor rows. At +/// the top level a selected folder gains an "Open Folder" row and a +/// "Folder…" row converts a plain slot; inside a folder the rows commit to +/// the folder's sub-slot (no nesting offered). fn action_panel( - slot: RingSlot, + selected: RingSlot, + folder_open: Option, actions: &BTreeMap, view: &Entity, pal: Palette, ) -> AnyElement { - let current = actions - .get(&slot) - .cloned() - .unwrap_or_else(|| default_ring_binding(slot)); + let target = match folder_open { + Some(folder) => EditTarget::FolderSlot { + folder, + sub: selected, + }, + None => EditTarget::Slot(selected), + }; + let current = match folder_open { + Some(folder) => match actions.get(&folder) { + Some(Action::Folder(items)) => items.get(&selected).cloned().unwrap_or(Action::None), + _ => Action::None, + }, + None => actions + .get(&selected) + .cloned() + .unwrap_or_else(|| default_ring_binding(selected)), + }; let view_pick = view.clone(); let on_pick: PickFn = Rc::new(move |action, _window, cx| { - cx.update_global::(|state, _| state.commit_ring_binding(slot, action)); + target.commit(action, cx); view_pick.update(cx, |_, vcx| vcx.notify()); }); - let mut rows = action_rows("ring-editor-action", Some(¤t), &on_pick, pal); + let mut rows: Vec = Vec::new(); + // A selected top-level folder: entering it is the panel's first offer. + if folder_open.is_none() && matches!(current, Action::Folder(_)) { + let view_open = view.clone(); + rows.push( + open_folder_row(pal, move |cx| { + view_open.update(cx, |v, vcx| { + v.set_ring_folder_open(Some(selected)); + v.set_ring_selected_slot(Some(RingSlot::North)); + vcx.notify(); + }); + }) + .into_any_element(), + ); + } + rows.extend(action_rows( + "ring-editor-action", + Some(¤t), + &on_pick, + pal, + )); rows.push(section_header(&rust_i18n::t!("CUSTOM"), pal)); for (idx, kind) in [PayloadKind::Run, PayloadKind::PasteText] .into_iter() .enumerate() { - rows.push(payload_editor_row(slot, kind, idx, ¤t, pal)); + rows.push(payload_editor_row(target, kind, idx, ¤t, pal)); + } + if folder_open.is_none() { + let view_convert = view.clone(); + let is_folder = matches!(current, Action::Folder(_)); + rows.push( + folder_convert_row(is_folder, pal, move |cx| { + cx.update_global::(|state, _| { + state.convert_ring_slot_to_folder(selected); + }); + view_convert.update(cx, |v, vcx| { + v.set_ring_folder_open(Some(selected)); + v.set_ring_selected_slot(Some(RingSlot::North)); + vcx.notify(); + }); + }) + .into_any_element(), + ); } v_flex() @@ -291,7 +456,7 @@ fn action_panel( .py_1p5() .text_sm() .font_weight(FontWeight::SEMIBOLD) - .child(format!("{} {}", slot.glyph(), tr!(slot.label()))), + .child(format!("{} {}", selected.glyph(), tr!(selected.label()))), ) .child( div() @@ -302,3 +467,75 @@ fn action_panel( ) .into_any_element() } + +/// "Open Folder →" — the drill-in row shown while a folder is selected. +fn open_folder_row(pal: Palette, on_open: impl Fn(&mut gpui::App) + 'static) -> impl IntoElement { + h_flex() + .id("ring-editor-open-folder") + .w_full() + .items_center() + .justify_between() + .px_2() + .py_1p5() + .rounded_md() + .cursor_pointer() + .text_sm() + .font_weight(FontWeight::SEMIBOLD) + .hover(move |s| s.bg(pal.surface_hover)) + .child( + h_flex() + .items_center() + .gap_2() + .child( + svg() + .path("action-icons/folder.svg") + .size_4() + .flex_none() + .text_color(rgb(ACCENT_BLUE)), + ) + .child(tr!("Open Folder")), + ) + .child(div().text_color(pal.text_muted).child("→")) + .on_click(move |_, _, cx| on_open(cx)) +} + +/// "Folder…" — converts the selected top-level slot into a folder (keeping +/// its action as the folder's North entry) and drills straight in. +fn folder_convert_row( + is_folder: bool, + pal: Palette, + on_convert: impl Fn(&mut gpui::App) + 'static, +) -> impl IntoElement { + h_flex() + .id("ring-editor-make-folder") + .w_full() + .items_center() + .justify_between() + .px_2() + .py_1p5() + .rounded_md() + .cursor_pointer() + .text_sm() + .hover(move |s| s.bg(pal.surface_hover)) + .child( + h_flex() + .items_center() + .gap_2() + .child( + svg() + .path("action-icons/folder.svg") + .size_4() + .flex_none() + .text_color(pal.text_muted), + ) + .child(format!("{}…", tr!("Folder"))), + ) + .when(is_folder, |s| { + s.child( + gpui_component::Icon::new(gpui_component::IconName::Check) + .size_3() + .text_color(rgb(ACCENT_BLUE)), + ) + }) + .on_click(move |_, _, cx| on_convert(cx)) +} diff --git a/crates/openlogi-gui/src/mouse_model/view.rs b/crates/openlogi-gui/src/mouse_model/view.rs index 01a8ecee..478dc66a 100644 --- a/crates/openlogi-gui/src/mouse_model/view.rs +++ b/crates/openlogi-gui/src/mouse_model/view.rs @@ -66,6 +66,9 @@ pub struct MouseModelView { /// Whether the Action Ring's full-page editor is shown in place of the /// mouse diagram (see [`crate::mouse_model::ring_editor`]). ring_editor_open: bool, + /// The top-level ring slot whose folder the editor has drilled into, if + /// any — the editor then shows and edits that folder's contents. + ring_folder_open: Option, /// Which ring slot the open Action Ring menu has activated (so its level-2 /// flyout card shows) — the ring counterpart of /// [`Self::gesture_active_dir`]. @@ -81,6 +84,7 @@ impl MouseModelView { hovered: None, gesture_active_dir: None, ring_editor_open: false, + ring_folder_open: None, ring_active_slot: None, _state_obs: state_obs, } @@ -97,11 +101,6 @@ impl MouseModelView { self.gesture_active_dir = dir; } - /// The ring slot whose level-2 flyout is open, if any. - pub(crate) fn ring_selected_slot(&self) -> Option { - self.ring_active_slot - } - /// Set (or clear, with `None`) the activated ring slot. Callers must /// `cx.notify()` to re-render. pub(crate) fn set_ring_selected_slot(&mut self, slot: Option) { @@ -109,9 +108,18 @@ impl MouseModelView { } /// Show or hide the Action Ring's full-page editor. Callers must - /// `cx.notify()` to re-render. + /// `cx.notify()` to re-render. Closing also pops any open folder. pub(crate) fn set_ring_editor_open(&mut self, open: bool) { self.ring_editor_open = open; + if !open { + self.ring_folder_open = None; + } + } + + /// Drill into (or, with `None`, back out of) a ring folder in the + /// editor. Callers must `cx.notify()` to re-render. + pub(crate) fn set_ring_folder_open(&mut self, slot: Option) { + self.ring_folder_open = slot; } /// The Action Ring's full-page editor, while its scratch flag is set — @@ -122,7 +130,13 @@ impl MouseModelView { self.ring_editor_open.then(|| { let view = cx.entity(); let pal = theme::palette(cx); - crate::mouse_model::ring_editor::page(&view, pal, cx) + crate::mouse_model::ring_editor::page( + self.ring_active_slot, + self.ring_folder_open, + &view, + pal, + cx, + ) }) } } diff --git a/crates/openlogi-gui/src/ring.rs b/crates/openlogi-gui/src/ring.rs index 9e266460..216e6a79 100644 --- a/crates/openlogi-gui/src/ring.rs +++ b/crates/openlogi-gui/src/ring.rs @@ -166,6 +166,7 @@ fn open(cx: &mut App) { Some(window) => { let refreshed = window.update(cx, |view, _, cx| { view.slots.clone_from(&slots); + view.in_folder = false; view.aim = Aim::Center; cx.notify(); }); @@ -263,8 +264,8 @@ fn watch_tick(epoch: u64, hwnd: isize, cx: &mut App) -> bool { false } -/// Confirm the current aim: fire the aimed sector's action, or cancel from -/// the dead zone. +/// Confirm the current aim: fire the aimed sector, or — from the dead zone — +/// step back out of an open folder / cancel from the main ring. fn confirm(cx: &mut App) { let (center, scale) = { let overlay = cx.global::(); @@ -273,35 +274,91 @@ fn confirm(cx: &mut App) { let aim = platform_win::cursor_pos().map_or(Aim::Center, |cursor| aim_for(center, cursor, scale)); match aim { - Aim::Center => debug!("action ring cancelled (centre tap)"), - Aim::Sector(slot) => fire(slot, cx), + Aim::Center => { + if view_in_folder(cx) { + back_to_main(cx); + } else { + debug!("action ring cancelled (centre tap)"); + close(cx); + } + } + Aim::Sector(slot) => activate(slot, cx), } - close(cx); } -/// Fire `slot`'s bound action through the agent. -fn fire(slot: RingSlot, cx: &mut App) { - let window = cx.global::().window; - let action = window.and_then(|window| { - window - .update(cx, |view, _, _| view.action_for(slot)) - .ok() - .flatten() - }); +/// Fire `slot`: a folder swaps the ring to its contents (staying open); a +/// leaf action goes to the agent and closes the ring; an empty sector — a +/// sparse folder position — does nothing. +fn activate(slot: RingSlot, cx: &mut App) { + let Some(window) = cx.global::().window else { + return; + }; + let Ok((action, in_folder)) = + window.update(cx, |view, _, _| (view.action_for(slot), view.in_folder)) + else { + return; + }; let Some(action) = action else { return; }; - info!(slot = %slot, action = %action.label(), "action ring → action"); - if cx - .global::() - .commands - .send(Command::ExecuteAction(action)) - .is_err() - { - warn!("IPC client is gone — ring action dropped"); + match action { + Action::Folder(items) if !in_folder => { + let items: Vec<(RingSlot, Action)> = items + .into_iter() + .filter(|(_, action)| !matches!(action, Action::None)) + .collect(); + if items.is_empty() { + debug!("empty folder — ignored"); + return; + } + info!(slot = %slot, "action ring → folder"); + let _ = window.update(cx, |view, _, cx| { + view.slots = items; + view.in_folder = true; + view.aim = Aim::Center; + cx.notify(); + }); + } + // One level deep by convention — a folder nested inside a folder is + // ignored rather than dispatched (the injector would only warn). + Action::Folder(_) => warn!("nested folder ignored"), + action => { + info!(slot = %slot, action = %action.label(), "action ring → action"); + if cx + .global::() + .commands + .send(Command::ExecuteAction(action)) + .is_err() + { + warn!("IPC client is gone — ring action dropped"); + } + close(cx); + } } } +/// Whether the open ring currently shows a folder's contents. +fn view_in_folder(cx: &mut App) -> bool { + cx.global::() + .window + .and_then(|window| window.update(cx, |view, _, _| view.in_folder).ok()) + .unwrap_or(false) +} + +/// Swap an open folder back to the main ring. +fn back_to_main(cx: &mut App) { + let slots = cx.global::().ring_slots_for_current(); + if let Some(window) = cx.global::().window { + let _ = window.update(cx, |view, _, cx| { + view.slots.clone_from(&slots); + view.in_folder = false; + view.aim = Aim::Center; + cx.notify(); + }); + } + debug!("action ring folder closed — back to the main ring"); +} + /// Hide the ring. fn close(cx: &mut App) { let overlay = cx.global_mut::(); @@ -335,6 +392,7 @@ fn create_window(slots: Vec<(RingSlot, Action)>, cx: &mut App) -> Option, cx: &mut App) -> Option { .flatten() } -/// The ring's root view: eight sector buttons on a circle and the centre ✕. +/// The ring's root view: sector buttons on a circle and the centre ✕ — or, +/// while a folder is open, the folder's (possibly sparse) contents with a +/// centre ← that steps back to the main ring. pub struct RingView { slots: Vec<(RingSlot, Action)>, + in_folder: bool, aim: Aim, } @@ -422,10 +483,7 @@ impl Render for RingView { }), ) .on_click(cx.listener(move |_, _, _, cx| { - cx.defer(move |cx| { - fire(slot_for_click, cx); - close(cx); - }); + cx.defer(move |cx| activate(slot_for_click, cx)); })), ) }); @@ -450,9 +508,13 @@ impl Render for RingView { .shadow_sm() .text_xs() .text_color(gpui::rgb(CROSS_TEXT)) - .child("✕") - .on_click(cx.listener(|_, _, _, cx| { - cx.defer(close); + .child(if self.in_folder { "←" } else { "✕" }) + .on_click(cx.listener(|view, _, _, cx| { + if view.in_folder { + cx.defer(back_to_main); + } else { + cx.defer(close); + } })), ) } diff --git a/crates/openlogi-gui/src/state.rs b/crates/openlogi-gui/src/state.rs index a47315bd..ff00aa60 100644 --- a/crates/openlogi-gui/src/state.rs +++ b/crates/openlogi-gui/src/state.rs @@ -1216,6 +1216,34 @@ impl AppState { self.persist_and_reload("ring binding"); } + /// Persist one sub-slot inside the folder at ring `slot` (converting the + /// slot into a folder first when needed) and have the agent reload. + pub fn commit_ring_folder_binding( + &mut self, + slot: openlogi_core::binding::RingSlot, + sub_slot: openlogi_core::binding::RingSlot, + action: Action, + ) { + let Some(key) = self.current_record().map(|r| r.config_key.clone()) else { + debug!(?slot, "no active device key — ring folder edit ignored"); + return; + }; + self.config + .set_ring_folder_slot(&key, slot, sub_slot, action); + self.persist_and_reload("ring folder binding"); + } + + /// Convert ring `slot` into a folder (its plain action kept as the + /// folder's North entry) and have the agent reload. + pub fn convert_ring_slot_to_folder(&mut self, slot: openlogi_core::binding::RingSlot) { + let Some(key) = self.current_record().map(|r| r.config_key.clone()) else { + debug!(?slot, "no active device key — folder conversion ignored"); + return; + }; + self.config.convert_ring_slot_to_folder(&key, slot); + self.persist_and_reload("ring folder conversion"); + } + pub fn commit_gesture_binding(&mut self, direction: GestureDirection, action: Action) { let Some(key) = self.current_record().map(|r| r.config_key.clone()) else { debug!( diff --git a/crates/openlogi-gui/src/windows/ring_action_editor.rs b/crates/openlogi-gui/src/windows/ring_action_editor.rs index 6dab8347..be574419 100644 --- a/crates/openlogi-gui/src/windows/ring_action_editor.rs +++ b/crates/openlogi-gui/src/windows/ring_action_editor.rs @@ -24,6 +24,69 @@ use crate::state::AppState; use crate::theme; use crate::windows::{self, AuxWindow, WindowRegistry}; +/// Where a ring edit lands: a top-level slot, or a sub-slot inside the +/// folder at `folder`. Carried by the payload dialog and the editor's +/// action panel so both commit through the right path. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum EditTarget { + /// A top-level Action Ring slot. + Slot(RingSlot), + /// A position inside the folder at the top-level slot `folder`. + FolderSlot { + /// The top-level slot holding the folder. + folder: RingSlot, + /// The position inside that folder. + sub: RingSlot, + }, +} + +impl EditTarget { + /// The action currently at this target, resolved against the selected + /// device's ring. + pub fn current_action(self, state: &AppState) -> Option { + let slots = state.ring_slots_for_current(); + match self { + EditTarget::Slot(slot) => slots + .into_iter() + .find(|(candidate, _)| *candidate == slot) + .map(|(_, action)| action), + EditTarget::FolderSlot { folder, sub } => slots + .into_iter() + .find(|(candidate, _)| *candidate == folder) + .and_then(|(_, action)| match action { + Action::Folder(items) => items.get(&sub).cloned(), + _ => None, + }), + } + } + + /// Commit `action` to this target through the matching [`AppState`] + /// path. + pub fn commit(self, action: Action, cx: &mut App) { + cx.update_global::(|state, _| match self { + EditTarget::Slot(slot) => state.commit_ring_binding(slot, action), + EditTarget::FolderSlot { folder, sub } => { + state.commit_ring_folder_binding(folder, sub, action); + } + }); + } + + /// Compass caption for dialog subtitles: `↗ Top Right` or + /// `↗ Top Right › ↓ Bottom`. + pub fn caption(self) -> String { + match self { + EditTarget::Slot(slot) => format!("{} {}", slot.glyph(), tr!(slot.label())), + EditTarget::FolderSlot { folder, sub } => format!( + "{} {} › {} {}", + folder.glyph(), + tr!(folder.label()), + sub.glyph(), + tr!(sub.label()) + ), + } + } +} + /// Which payload action this editor edits. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PayloadKind { @@ -85,7 +148,7 @@ pub struct RingActionEditorView { focus_handle: FocusHandle, #[allow(dead_code, reason = "held to keep the appearance observer alive")] appearance_obs: Option, - slot: RingSlot, + target: EditTarget, kind: PayloadKind, input: Entity, } @@ -96,10 +159,10 @@ impl AuxWindow for RingActionEditorView { } } -/// Open the editor for `slot`, seeded with the slot's current payload when it -/// already holds an action of `kind`. An editor left open for another slot is -/// closed first — focusing it would edit the wrong slot. -pub fn open(slot: RingSlot, kind: PayloadKind, cx: &mut App) { +/// Open the editor for `target`, seeded with its current payload when it +/// already holds an action of `kind`. An editor left open for another target +/// is closed first — focusing it would edit the wrong slot. +pub fn open(target: EditTarget, kind: PayloadKind, cx: &mut App) { if let Some(handle) = cx .default_global::() .ring_action_editor @@ -110,13 +173,8 @@ pub fn open(slot: RingSlot, kind: PayloadKind, cx: &mut App) { let seed: String = cx .try_global::() - .and_then(|state| { - state - .ring_slots_for_current() - .into_iter() - .find(|(candidate, _)| *candidate == slot) - .and_then(|(_, action)| kind.payload_of(&action).map(str::to_owned)) - }) + .and_then(|state| target.current_action(state)) + .and_then(|action| kind.payload_of(&action).map(str::to_owned)) .unwrap_or_default(); windows::open_or_focus( @@ -135,7 +193,7 @@ pub fn open(slot: RingSlot, kind: PayloadKind, cx: &mut App) { RingActionEditorView { focus_handle, appearance_obs: None, - slot, + target, kind, input, } @@ -147,7 +205,7 @@ pub fn open(slot: RingSlot, kind: PayloadKind, cx: &mut App) { impl Render for RingActionEditorView { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { let pal = theme::palette(cx); - let (slot, kind, input) = (self.slot, self.kind, self.input.clone()); + let (target, kind, input) = (self.target, self.kind, self.input.clone()); v_flex() .size_full() @@ -176,11 +234,12 @@ impl Render for RingActionEditorView { .font_weight(FontWeight::SEMIBOLD) .child(tr!(self.kind.title_key())), ) - .child(div().text_sm().text_color(pal.text_muted).child(format!( - "{} {}", - self.slot.glyph(), - tr!(self.slot.label()) - ))), + .child( + div() + .text_sm() + .text_color(pal.text_muted) + .child(self.target.caption()), + ), ) .child(Input::new(&self.input)) .child( @@ -212,10 +271,7 @@ impl Render for RingActionEditorView { let payload = input.read(cx).value().trim().to_owned(); if !payload.is_empty() { - let action = kind.action(payload); - cx.update_global::(|state, _| { - state.commit_ring_binding(slot, action); - }); + target.commit(kind.action(payload), cx); } window.remove_window(); }), diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 7d5bcaff..decfb26b 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -89,8 +89,10 @@ North = "PlayPause" NorthEast = "CaptureRegion" # `Run` opens a URL, file, or program (`||` separates a program's arguments; # %VAR% environment variables expand). `PasteText` types a snippet at the -# cursor. Both are also editable from the ring menu in the app. -East = { Run = "https://gemini.google.com/" } +# cursor. `Folder` nests a sub-ring (one level; sparse maps fine) that the +# on-screen ring opens in place. All are editable from the ring editor in +# the app. +East = { Folder = { North = { Run = "https://gemini.google.com/" }, South = "Copy" } } SouthEast = "LockScreen" South = { Run = 'C:\Windows\notepad.exe||%USERPROFILE%\notes.txt' } SouthWest = "ShowDesktop" From f4236eb7e88272869e034d43fadd94124b8a4eab Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 17:31:07 -0700 Subject: [PATCH 31/42] feat(core): parse human key chords into KeyCombo `KeyCombo::parse("Win+Ctrl+Space")`: case-insensitive modifier names joined with +, one trailing key (letters, digits, F1-F12, named keys), canonical re-rendered display. Rejects bare modifiers, double keys, unknown names. --- crates/openlogi-core/src/binding.rs | 155 ++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/crates/openlogi-core/src/binding.rs b/crates/openlogi-core/src/binding.rs index 87a30b66..3e247ce9 100644 --- a/crates/openlogi-core/src/binding.rs +++ b/crates/openlogi-core/src/binding.rs @@ -508,6 +508,86 @@ pub fn split_run_target(payload: &str) -> (&str, Option<&str>) { } } +/// Map one non-modifier chord token to its macOS virtual key code (the +/// [`KeyCombo`] storage format) plus its canonical display spelling. +fn parse_key_name(token: &str) -> Option<(u16, String)> { + let lower = token.to_ascii_lowercase(); + // Letters and digits: canonical form is the uppercase letter / digit. + if lower.len() == 1 { + let ch = lower.chars().next()?; + let code = match ch { + 'a' => 0x00, + 's' => 0x01, + 'd' => 0x02, + 'f' => 0x03, + 'h' => 0x04, + 'g' => 0x05, + 'z' => 0x06, + 'x' => 0x07, + 'c' => 0x08, + 'v' => 0x09, + 'b' => 0x0B, + 'q' => 0x0C, + 'w' => 0x0D, + 'e' => 0x0E, + 'r' => 0x0F, + 'y' => 0x10, + 't' => 0x11, + 'u' => 0x20, + 'i' => 0x22, + 'o' => 0x1F, + 'p' => 0x23, + 'j' => 0x26, + 'k' => 0x28, + 'l' => 0x25, + 'm' => 0x2E, + 'n' => 0x2D, + '0' => 0x1D, + '1' => 0x12, + '2' => 0x13, + '3' => 0x14, + '4' => 0x15, + '5' => 0x17, + '6' => 0x16, + '7' => 0x1A, + '8' => 0x1C, + '9' => 0x19, + _ => return None, + }; + return Some((code, ch.to_ascii_uppercase().to_string())); + } + let (code, canonical) = match lower.as_str() { + "space" => (0x31, "Space"), + "enter" | "return" => (0x24, "Enter"), + "tab" => (0x30, "Tab"), + "escape" | "esc" => (0x35, "Escape"), + "backspace" => (0x33, "Backspace"), + "delete" | "del" => (0x75, "Delete"), + "home" => (0x73, "Home"), + "end" => (0x77, "End"), + "pageup" => (0x74, "PageUp"), + "pagedown" => (0x79, "PageDown"), + "up" => (0x7E, "Up"), + "down" => (0x7D, "Down"), + "left" => (0x7B, "Left"), + "right" => (0x7C, "Right"), + "f1" => (0x7A, "F1"), + "f2" => (0x78, "F2"), + "f3" => (0x63, "F3"), + "f4" => (0x76, "F4"), + "f5" => (0x60, "F5"), + "f6" => (0x61, "F6"), + "f7" => (0x62, "F7"), + "f8" => (0x64, "F8"), + "f9" => (0x65, "F9"), + "f10" => (0x6D, "F10"), + "f11" => (0x67, "F11"), + "f12" => (0x6F, "F12"), + _ => return None, + }; + Some((code, canonical.to_string())) +} + /// Short human name for a [`Action::Run`] target — the host for URLs, the /// file stem for paths, the raw target otherwise — truncated so slot labels /// stay readable. @@ -554,6 +634,60 @@ pub struct KeyCombo { } impl KeyCombo { + /// Parse a human `"Win+Ctrl+Space"`-style chord: `+`-separated modifier + /// names (`Ctrl`/`Control`, `Shift`, `Alt`/`Option`, `Win`/`Super`/ + /// `Meta`, `Cmd`/`Command`; case-insensitive) followed by exactly one + /// key — a letter, digit, `F1`–`F12`, or a named key (`Space`, `Enter`/ + /// `Return`, `Tab`, `Escape`/`Esc`, `Backspace`, `Delete`, `Home`, + /// `End`, `PageUp`, `PageDown`, `Up`, `Down`, `Left`, `Right`). The + /// stored `display` is the canonical re-rendering of what was parsed, + /// so labels stay consistent regardless of input casing or order. + #[must_use] + pub fn parse(text: &str) -> Option { + let mut modifiers = 0u8; + let mut key: Option<(u16, String)> = None; + for token in text.split('+') { + let token = token.trim(); + if token.is_empty() { + return None; + } + match token.to_ascii_lowercase().as_str() { + "ctrl" | "control" => modifiers |= Self::MOD_CTRL, + "shift" => modifiers |= Self::MOD_SHIFT, + "alt" | "option" => modifiers |= Self::MOD_OPTION, + "win" | "windows" | "super" | "meta" => modifiers |= Self::MOD_WIN, + "cmd" | "command" => modifiers |= Self::MOD_CMD, + _ => { + // Exactly one non-modifier token, and it must be last. + if key.is_some() { + return None; + } + key = Some(parse_key_name(token)?); + } + } + } + let (key_code, canonical_key) = key?; + let mut display = String::new(); + for (bit, name) in [ + (Self::MOD_WIN, "Win"), + (Self::MOD_CTRL, "Ctrl"), + (Self::MOD_OPTION, "Alt"), + (Self::MOD_SHIFT, "Shift"), + (Self::MOD_CMD, "Cmd"), + ] { + if modifiers & bit != 0 { + display.push_str(name); + display.push('+'); + } + } + display.push_str(&canonical_key); + Some(Self { + modifiers, + key_code, + display, + }) + } + /// Bit for the ⌘ Command modifier in [`Self::modifiers`]. pub const MOD_CMD: u8 = 1 << 0; /// Bit for the ⇧ Shift modifier in [`Self::modifiers`]. @@ -1155,6 +1289,27 @@ mod tests { )); } + #[test] + fn key_combo_parse_accepts_chords_and_rejects_garbage() { + let combo = KeyCombo::parse("win + ctrl + space").expect("valid chord"); + assert_eq!(combo.modifiers, KeyCombo::MOD_WIN | KeyCombo::MOD_CTRL); + assert_eq!(combo.key_code, 0x31); + assert_eq!(combo.display, "Win+Ctrl+Space"); + + let combo = KeyCombo::parse("Alt+Shift+z").expect("valid chord"); + assert_eq!(combo.modifiers, KeyCombo::MOD_OPTION | KeyCombo::MOD_SHIFT); + assert_eq!(combo.key_code, 0x06); + assert_eq!(combo.display, "Alt+Shift+Z"); + + assert_eq!(KeyCombo::parse("F5").map(|c| c.key_code), Some(0x60)); + // Bare modifiers, two keys, unknown names, empties: all rejected. + assert!(KeyCombo::parse("Ctrl+Shift").is_none()); + assert!(KeyCombo::parse("A+B").is_none()); + assert!(KeyCombo::parse("Ctrl+Wibble").is_none()); + assert!(KeyCombo::parse("").is_none()); + assert!(KeyCombo::parse("Ctrl++A").is_none()); + } + #[test] fn key_combo_win_modifier_renders_first() { let combo = KeyCombo { From 3407f6900c945e5450a3b448594c29e4a6c862d9 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 17:31:07 -0700 Subject: [PATCH 32/42] feat(gui): editable keyboard shortcuts and a layout pass on the ring editor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CUSTOM section gains "Keyboard Shortcut…": the payload dialog accepts a typed chord, validates it through KeyCombo::parse (invalid input keeps the dialog open with an inline error instead of silently closing), and seeds from the slot''s current combo — so recovered bindings like Alt+Shift+Z are finally editable in place. Layout: the canvas becomes a wide, short rectangle (a square at pill width was taller than the content area and clipped), the header sits above the canvas+panel pair which centres as one block, and pills ellipsize instead of hard-clipping. --- crates/openlogi-gui/locales/da.yml | 4 + crates/openlogi-gui/locales/de.yml | 4 + crates/openlogi-gui/locales/el.yml | 4 + crates/openlogi-gui/locales/en.yml | 4 + crates/openlogi-gui/locales/es.yml | 4 + crates/openlogi-gui/locales/fi.yml | 4 + crates/openlogi-gui/locales/fr.yml | 4 + crates/openlogi-gui/locales/it.yml | 4 + crates/openlogi-gui/locales/ja.yml | 4 + crates/openlogi-gui/locales/ko.yml | 4 + crates/openlogi-gui/locales/nb.yml | 4 + crates/openlogi-gui/locales/nl.yml | 4 + crates/openlogi-gui/locales/pl.yml | 4 + crates/openlogi-gui/locales/pt-BR.yml | 4 + crates/openlogi-gui/locales/pt-PT.yml | 4 + crates/openlogi-gui/locales/ru.yml | 4 + crates/openlogi-gui/locales/sv.yml | 4 + crates/openlogi-gui/locales/zh-CN.yml | 4 + crates/openlogi-gui/locales/zh-HK.yml | 4 + crates/openlogi-gui/locales/zh-TW.yml | 4 + crates/openlogi-gui/src/mouse_model/picker.rs | 2 +- .../src/mouse_model/ring_editor.rs | 65 ++++++++------- .../src/windows/ring_action_editor.rs | 81 +++++++++++++++---- 23 files changed, 183 insertions(+), 45 deletions(-) diff --git a/crates/openlogi-gui/locales/da.yml b/crates/openlogi-gui/locales/da.yml index ae8ada71..e37dd852 100644 --- a/crates/openlogi-gui/locales/da.yml +++ b/crates/openlogi-gui/locales/da.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Skrives ved markøren præcis som skrevet." "Folder": "Mappe" "Open Folder": "Åbn mappe" +"Keyboard Shortcut": "Tastaturgenvej" +"e.g. Ctrl+Shift+P": "f.eks. Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modifikatorer adskilt med +, derefter én tast: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "Genvejen kunne ikke læses — brug formen Ctrl+Shift+P." "Up": "Op" "Down": "Ned" "Left": "Venstre" diff --git a/crates/openlogi-gui/locales/de.yml b/crates/openlogi-gui/locales/de.yml index 0e742ad6..e5a8e65a 100644 --- a/crates/openlogi-gui/locales/de.yml +++ b/crates/openlogi-gui/locales/de.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Wird an der Cursorposition genau wie geschrieben eingetippt." "Folder": "Ordner" "Open Folder": "Ordner öffnen" +"Keyboard Shortcut": "Tastenkombination" +"e.g. Ctrl+Shift+P": "z. B. Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modifikatoren mit + verbunden, dann eine Taste: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "Die Tastenkombination konnte nicht gelesen werden — verwende die Form Ctrl+Shift+P." "Up": "Oben" "Down": "Unten" "Left": "Links" diff --git a/crates/openlogi-gui/locales/el.yml b/crates/openlogi-gui/locales/el.yml index 652b6621..b08e774b 100644 --- a/crates/openlogi-gui/locales/el.yml +++ b/crates/openlogi-gui/locales/el.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Πληκτρολογείται στον δρομέα ακριβώς όπως γράφτηκε." "Folder": "Φάκελος" "Open Folder": "Άνοιγμα φακέλου" +"Keyboard Shortcut": "Συντόμευση πληκτρολογίου" +"e.g. Ctrl+Shift+P": "π.χ. Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Τροποποιητές ενωμένοι με +, μετά ένα πλήκτρο: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "Η συντόμευση δεν διαβάστηκε — χρησιμοποιήστε τη μορφή Ctrl+Shift+P." "Up": "Πάνω" "Down": "Κάτω" "Left": "Αριστερά" diff --git a/crates/openlogi-gui/locales/en.yml b/crates/openlogi-gui/locales/en.yml index 3cfeb3e7..edbda85f 100644 --- a/crates/openlogi-gui/locales/en.yml +++ b/crates/openlogi-gui/locales/en.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Typed at the cursor exactly as written." "Folder": "Folder" "Open Folder": "Open Folder" +"Keyboard Shortcut": "Keyboard Shortcut" +"e.g. Ctrl+Shift+P": "e.g. Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "That shortcut could not be read — use the Ctrl+Shift+P shape." "Up": "Up" "Down": "Down" "Left": "Left" diff --git a/crates/openlogi-gui/locales/es.yml b/crates/openlogi-gui/locales/es.yml index 544617e4..46448b7f 100644 --- a/crates/openlogi-gui/locales/es.yml +++ b/crates/openlogi-gui/locales/es.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Se escribe en el cursor exactamente como está." "Folder": "Carpeta" "Open Folder": "Abrir carpeta" +"Keyboard Shortcut": "Atajo de teclado" +"e.g. Ctrl+Shift+P": "p. ej. Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modificadores unidos con + y una tecla: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "No se pudo leer el atajo — usa la forma Ctrl+Shift+P." "Up": "Arriba" "Down": "Abajo" "Left": "Izquierda" diff --git a/crates/openlogi-gui/locales/fi.yml b/crates/openlogi-gui/locales/fi.yml index 7842118d..3bd931c2 100644 --- a/crates/openlogi-gui/locales/fi.yml +++ b/crates/openlogi-gui/locales/fi.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Kirjoitetaan kohdistimen kohtaan täsmälleen sellaisenaan." "Folder": "Kansio" "Open Folder": "Avaa kansio" +"Keyboard Shortcut": "Pikanäppäin" +"e.g. Ctrl+Shift+P": "esim. Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Muokkaimet yhdistettynä +-merkillä ja yksi näppäin: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "Pikanäppäintä ei voitu lukea — käytä muotoa Ctrl+Shift+P." "Up": "Ylös" "Down": "Alas" "Left": "Vasen" diff --git a/crates/openlogi-gui/locales/fr.yml b/crates/openlogi-gui/locales/fr.yml index 02eaed99..3edb065f 100644 --- a/crates/openlogi-gui/locales/fr.yml +++ b/crates/openlogi-gui/locales/fr.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Saisi au curseur exactement tel quel." "Folder": "Dossier" "Open Folder": "Ouvrir le dossier" +"Keyboard Shortcut": "Raccourci clavier" +"e.g. Ctrl+Shift+P": "p. ex. Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modificateurs reliés par +, puis une touche : Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "Le raccourci n'a pas pu être lu — utilisez la forme Ctrl+Shift+P." "Up": "Haut" "Down": "Bas" "Left": "Gauche" diff --git a/crates/openlogi-gui/locales/it.yml b/crates/openlogi-gui/locales/it.yml index f9e7b9cf..ca08a04a 100644 --- a/crates/openlogi-gui/locales/it.yml +++ b/crates/openlogi-gui/locales/it.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Digitato al cursore esattamente come scritto." "Folder": "Cartella" "Open Folder": "Apri cartella" +"Keyboard Shortcut": "Scorciatoia da tastiera" +"e.g. Ctrl+Shift+P": "es. Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modificatori uniti con + e un tasto: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "Impossibile leggere la scorciatoia — usa la forma Ctrl+Shift+P." "Up": "Su" "Down": "Giù" "Left": "Sinistra" diff --git a/crates/openlogi-gui/locales/ja.yml b/crates/openlogi-gui/locales/ja.yml index 29bc8b80..9ad53e7d 100644 --- a/crates/openlogi-gui/locales/ja.yml +++ b/crates/openlogi-gui/locales/ja.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "書かれたとおりにカーソル位置に入力されます。" "Folder": "フォルダー" "Open Folder": "フォルダーを開く" +"Keyboard Shortcut": "キーボードショートカット" +"e.g. Ctrl+Shift+P": "例: Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "修飾キーを + でつなぎ、最後にキーを 1 つ: Ctrl+Shift+P、Win+Ctrl+Space、Alt+F4。" +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "ショートカットを読み取れませんでした。Ctrl+Shift+P の形式で入力してください。" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/ko.yml b/crates/openlogi-gui/locales/ko.yml index ad2ee9e7..3340c156 100644 --- a/crates/openlogi-gui/locales/ko.yml +++ b/crates/openlogi-gui/locales/ko.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "쓰인 그대로 커서 위치에 입력됩니다." "Folder": "폴더" "Open Folder": "폴더 열기" +"Keyboard Shortcut": "키보드 단축키" +"e.g. Ctrl+Shift+P": "예: Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "보조 키를 + 로 잇고 마지막에 키 하나: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "단축키를 읽을 수 없습니다 — Ctrl+Shift+P 형식을 사용하세요." "Up": "위" "Down": "아래" "Left": "왼쪽" diff --git a/crates/openlogi-gui/locales/nb.yml b/crates/openlogi-gui/locales/nb.yml index 293cc55a..045d9c11 100644 --- a/crates/openlogi-gui/locales/nb.yml +++ b/crates/openlogi-gui/locales/nb.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Skrives ved markøren nøyaktig som skrevet." "Folder": "Mappe" "Open Folder": "Åpne mappe" +"Keyboard Shortcut": "Hurtigtast" +"e.g. Ctrl+Shift+P": "f.eks. Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modifikatorer koblet med +, deretter én tast: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "Hurtigtasten kunne ikke leses — bruk formen Ctrl+Shift+P." "Up": "Opp" "Down": "Ned" "Left": "Venstre" diff --git a/crates/openlogi-gui/locales/nl.yml b/crates/openlogi-gui/locales/nl.yml index 7e420a93..1ce95c0f 100644 --- a/crates/openlogi-gui/locales/nl.yml +++ b/crates/openlogi-gui/locales/nl.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Wordt bij de cursor exact zoals geschreven getypt." "Folder": "Map" "Open Folder": "Map openen" +"Keyboard Shortcut": "Sneltoets" +"e.g. Ctrl+Shift+P": "bijv. Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modificatietoetsen verbonden met +, dan één toets: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "De sneltoets kon niet worden gelezen — gebruik de vorm Ctrl+Shift+P." "Up": "Omhoog" "Down": "Omlaag" "Left": "Links" diff --git a/crates/openlogi-gui/locales/pl.yml b/crates/openlogi-gui/locales/pl.yml index 88a3a45f..3a7c5425 100644 --- a/crates/openlogi-gui/locales/pl.yml +++ b/crates/openlogi-gui/locales/pl.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Wpisywany przy kursorze dokładnie tak, jak napisano." "Folder": "Folder" "Open Folder": "Otwórz folder" +"Keyboard Shortcut": "Skrót klawiszowy" +"e.g. Ctrl+Shift+P": "np. Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modyfikatory połączone znakiem + i jeden klawisz: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "Nie udało się odczytać skrótu — użyj formy Ctrl+Shift+P." "Up": "W górę" "Down": "W dół" "Left": "W lewo" diff --git a/crates/openlogi-gui/locales/pt-BR.yml b/crates/openlogi-gui/locales/pt-BR.yml index a98465e6..95f594a7 100644 --- a/crates/openlogi-gui/locales/pt-BR.yml +++ b/crates/openlogi-gui/locales/pt-BR.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Digitado no cursor exatamente como escrito." "Folder": "Pasta" "Open Folder": "Abrir pasta" +"Keyboard Shortcut": "Atalho de teclado" +"e.g. Ctrl+Shift+P": "ex.: Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modificadores unidos com + e uma tecla: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "Não foi possível ler o atalho — use a forma Ctrl+Shift+P." "Up": "Cima" "Down": "Baixo" "Left": "Esquerda" diff --git a/crates/openlogi-gui/locales/pt-PT.yml b/crates/openlogi-gui/locales/pt-PT.yml index 7fc14a35..12ab847b 100644 --- a/crates/openlogi-gui/locales/pt-PT.yml +++ b/crates/openlogi-gui/locales/pt-PT.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Escrito no cursor exatamente como está." "Folder": "Pasta" "Open Folder": "Abrir pasta" +"Keyboard Shortcut": "Atalho de teclado" +"e.g. Ctrl+Shift+P": "p. ex. Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modificadores unidos com + e uma tecla: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "Não foi possível ler o atalho — utilize a forma Ctrl+Shift+P." "Up": "Cima" "Down": "Baixo" "Left": "Esquerda" diff --git a/crates/openlogi-gui/locales/ru.yml b/crates/openlogi-gui/locales/ru.yml index 3cc047dd..0a88990d 100644 --- a/crates/openlogi-gui/locales/ru.yml +++ b/crates/openlogi-gui/locales/ru.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Вводится у курсора точно как написано." "Folder": "Папка" "Open Folder": "Открыть папку" +"Keyboard Shortcut": "Сочетание клавиш" +"e.g. Ctrl+Shift+P": "напр. Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Модификаторы через + и одна клавиша: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "Не удалось прочитать сочетание — используйте форму Ctrl+Shift+P." "Up": "Вверх" "Down": "Вниз" "Left": "Влево" diff --git a/crates/openlogi-gui/locales/sv.yml b/crates/openlogi-gui/locales/sv.yml index fed8e20a..483e6e5d 100644 --- a/crates/openlogi-gui/locales/sv.yml +++ b/crates/openlogi-gui/locales/sv.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "Skrivs vid markören exakt som skrivet." "Folder": "Mapp" "Open Folder": "Öppna mapp" +"Keyboard Shortcut": "Kortkommando" +"e.g. Ctrl+Shift+P": "t.ex. Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modifierare sammanfogade med + och en tangent: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "Kortkommandot kunde inte läsas — använd formen Ctrl+Shift+P." "Up": "Upp" "Down": "Ned" "Left": "Vänster" diff --git a/crates/openlogi-gui/locales/zh-CN.yml b/crates/openlogi-gui/locales/zh-CN.yml index d00ea06b..a50ef198 100644 --- a/crates/openlogi-gui/locales/zh-CN.yml +++ b/crates/openlogi-gui/locales/zh-CN.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "按原样在光标处输入。" "Folder": "文件夹" "Open Folder": "打开文件夹" +"Keyboard Shortcut": "键盘快捷键" +"e.g. Ctrl+Shift+P": "例如 Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "修饰键用 + 连接,最后一个键:Ctrl+Shift+P、Win+Ctrl+Space、Alt+F4。" +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "无法读取该快捷键——请使用 Ctrl+Shift+P 的格式。" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/zh-HK.yml b/crates/openlogi-gui/locales/zh-HK.yml index a25ddbda..3fc910c8 100644 --- a/crates/openlogi-gui/locales/zh-HK.yml +++ b/crates/openlogi-gui/locales/zh-HK.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "按原樣在游標處輸入。" "Folder": "資料夾" "Open Folder": "開啟資料夾" +"Keyboard Shortcut": "鍵盤快速鍵" +"e.g. Ctrl+Shift+P": "例如 Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "修飾鍵以 + 連接,最後一個鍵:Ctrl+Shift+P、Win+Ctrl+Space、Alt+F4。" +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "無法讀取該快速鍵——請使用 Ctrl+Shift+P 的格式。" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/locales/zh-TW.yml b/crates/openlogi-gui/locales/zh-TW.yml index b8085e0e..6034cee2 100644 --- a/crates/openlogi-gui/locales/zh-TW.yml +++ b/crates/openlogi-gui/locales/zh-TW.yml @@ -173,6 +173,10 @@ _version: 1 "Typed at the cursor exactly as written.": "按原樣在游標處輸入。" "Folder": "資料夾" "Open Folder": "開啟資料夾" +"Keyboard Shortcut": "鍵盤快速鍵" +"e.g. Ctrl+Shift+P": "例如 Ctrl+Shift+P" +"Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "修飾鍵以 + 連接,最後一個鍵:Ctrl+Shift+P、Win+Ctrl+Space、Alt+F4。" +"That shortcut could not be read — use the Ctrl+Shift+P shape.": "無法讀取該快速鍵——請使用 Ctrl+Shift+P 的格式。" "Up": "上" "Down": "下" "Left": "左" diff --git a/crates/openlogi-gui/src/mouse_model/picker.rs b/crates/openlogi-gui/src/mouse_model/picker.rs index 509d063b..f4cecab6 100644 --- a/crates/openlogi-gui/src/mouse_model/picker.rs +++ b/crates/openlogi-gui/src/mouse_model/picker.rs @@ -120,7 +120,7 @@ pub(crate) fn payload_editor_row( pal: Palette, ) -> AnyElement { let selected = kind.payload_of(current).is_some(); - let icon_path = action_icon_path(&kind.action(String::new())); + let icon_path = kind.icon_path(); menu_row(("ring-payload", idx), pal, selected) .child( h_flex() diff --git a/crates/openlogi-gui/src/mouse_model/ring_editor.rs b/crates/openlogi-gui/src/mouse_model/ring_editor.rs index 8261923b..c7afc56e 100644 --- a/crates/openlogi-gui/src/mouse_model/ring_editor.rs +++ b/crates/openlogi-gui/src/mouse_model/ring_editor.rs @@ -30,22 +30,26 @@ use crate::theme::{ACCENT_BLUE, Palette}; use crate::windows::ring_action_editor::{EditTarget, PayloadKind}; /// Radius of the circle the editor's slot buttons sit on. -const EDIT_RADIUS: f32 = 130.; +const EDIT_RADIUS: f32 = 110.; /// Diameter of one slot circle on the canvas. -const EDIT_CIRCLE: f32 = 52.; +const EDIT_CIRCLE: f32 = 48.; /// Diameter of the decorative centre circle. -const EDIT_CENTER: f32 = 38.; +const EDIT_CENTER: f32 = 36.; /// Width reserved for a floating label pill column beside a circle. -const PILL_W: f32 = 150.; +const PILL_W: f32 = 140.; /// Gap between a circle and its label pill. const PILL_GAP: f32 = 10.; -/// Square canvas the ring is drawn in — sized so the side pills stay inside -/// it instead of spilling under the action panel. -const CANVAS: f32 = 2. * (EDIT_RADIUS + EDIT_CIRCLE / 2. + PILL_GAP + PILL_W); +/// Vertical room above / below the ring for the North / South pills. +const PILL_V: f32 = 44.; +/// The canvas is a wide, short rectangle — pills extend sideways, so the +/// height only needs the ring plus the North/South pill rows. A square at +/// pill width was taller than the content area and clipped. +const CANVAS_W: f32 = 2. * (EDIT_RADIUS + EDIT_CIRCLE / 2. + PILL_GAP + PILL_W); +const CANVAS_H: f32 = 2. * (EDIT_RADIUS + EDIT_CIRCLE / 2. + PILL_V); /// Fixed width of the right-hand action panel. const PANEL_W: f32 = 260.; /// Height cap for the action panel's scrolling list. -const PANEL_LIST_H: f32 = 420.; +const PANEL_LIST_H: f32 = 360.; /// One canvas position: the slot, what's bound there (`None` = an empty /// folder position rendered as a dashed add target), and whether it is the @@ -163,21 +167,21 @@ pub(crate) fn page( .child(tr!("8 actions")), ); - h_flex() + // Header on top; below it the canvas and panel side by side, the pair + // centred as one block so the page reads composed at any window size. + v_flex() .size_full() - .items_start() - .gap_6() + .gap_2() + .child(back) .child( - v_flex().flex_1().gap_2().child(back).child( - div() - .flex_1() - .flex() + div().flex_1().flex().items_center().justify_center().child( + h_flex() .items_center() - .justify_center() - .child(canvas(&canvas_slots, folder_open, view, pal)), + .gap_6() + .child(canvas(&canvas_slots, folder_open, view, pal)) + .child(action_panel(selected, folder_open, &actions, view, pal)), ), ) - .child(action_panel(selected, folder_open, &actions, view, pal)) .into_any_element() } @@ -190,16 +194,16 @@ fn canvas( view: &Entity, pal: Palette, ) -> AnyElement { - let c = CANVAS / 2.; - let mut layer = div().relative().w(px(CANVAS)).h(px(CANVAS)); + let (cw, ch) = (CANVAS_W / 2., CANVAS_H / 2.); + let mut layer = div().relative().flex_none().w(px(CANVAS_W)).h(px(CANVAS_H)); // Decorative centre — the popup's ✕ (or the folder's ← back) is only in // the on-screen ring; here it is orientation. layer = layer.child( div() .absolute() - .left(px(c - EDIT_CENTER / 2.)) - .top(px(c - EDIT_CENTER / 2.)) + .left(px(cw - EDIT_CENTER / 2.)) + .top(px(ch - EDIT_CENTER / 2.)) .w(px(EDIT_CENTER)) .h(px(EDIT_CENTER)) .rounded_full() @@ -218,8 +222,8 @@ fn canvas( let slot = canvas_slot.slot; let is_selected = canvas_slot.selected; let angle = slot.angle_degrees().to_radians(); - let bx = c + EDIT_RADIUS * angle.sin() - EDIT_CIRCLE / 2.; - let by = c - EDIT_RADIUS * angle.cos() - EDIT_CIRCLE / 2.; + let bx = cw + EDIT_RADIUS * angle.sin() - EDIT_CIRCLE / 2.; + let by = ch - EDIT_RADIUS * angle.cos() - EDIT_CIRCLE / 2.; // Clicking selects; clicking an already-selected folder drills in. let drills = folder_open.is_none() @@ -332,6 +336,7 @@ fn label_pill( .text_color(pal.text_primary) .whitespace_nowrap() .overflow_hidden() + .text_ellipsis() .max_w(px(PILL_W)) .child(localized_action_label(action)) .on_click(move |_, _, cx| on_activate(cx)); @@ -349,7 +354,7 @@ fn label_pill( .justify_end(), RingSlot::North => holder .left(px(bx + EDIT_CIRCLE / 2. - PILL_W / 2.)) - .top(px(by - 36.)) + .top(px(by - 34.)) .justify_center(), RingSlot::South => holder .left(px(bx + EDIT_CIRCLE / 2. - PILL_W / 2.)) @@ -418,9 +423,13 @@ fn action_panel( pal, )); rows.push(section_header(&rust_i18n::t!("CUSTOM"), pal)); - for (idx, kind) in [PayloadKind::Run, PayloadKind::PasteText] - .into_iter() - .enumerate() + for (idx, kind) in [ + PayloadKind::Run, + PayloadKind::PasteText, + PayloadKind::Shortcut, + ] + .into_iter() + .enumerate() { rows.push(payload_editor_row(target, kind, idx, ¤t, pal)); } diff --git a/crates/openlogi-gui/src/windows/ring_action_editor.rs b/crates/openlogi-gui/src/windows/ring_action_editor.rs index be574419..298bccbc 100644 --- a/crates/openlogi-gui/src/windows/ring_action_editor.rs +++ b/crates/openlogi-gui/src/windows/ring_action_editor.rs @@ -17,7 +17,7 @@ use gpui_component::{ input::{Input, InputState}, v_flex, }; -use openlogi_core::binding::{Action, RingSlot}; +use openlogi_core::binding::{Action, KeyCombo, RingSlot}; use crate::app_menu::{CloseWindow, Minimize, Zoom}; use crate::state::AppState; @@ -94,6 +94,9 @@ pub enum PayloadKind { Run, /// [`Action::PasteText`] — a text snippet typed at the cursor. PasteText, + /// [`Action::CustomShortcut`] — a chord entered as text + /// (`"Win+Ctrl+Space"`) and parsed by [`KeyCombo::parse`]. + Shortcut, } impl PayloadKind { @@ -102,6 +105,7 @@ impl PayloadKind { match self { PayloadKind::Run => "Run Command", PayloadKind::PasteText => "Paste Text", + PayloadKind::Shortcut => "Keyboard Shortcut", } } @@ -109,6 +113,7 @@ impl PayloadKind { match self { PayloadKind::Run => "Command or URL", PayloadKind::PasteText => "Text to paste", + PayloadKind::Shortcut => "e.g. Ctrl+Shift+P", } } @@ -119,26 +124,41 @@ impl PayloadKind { arguments; %VAR% environment variables expand." } PayloadKind::PasteText => "Typed at the cursor exactly as written.", + PayloadKind::Shortcut => { + "Modifier names joined with +, then one key: Ctrl+Shift+P, \ + Win+Ctrl+Space, Alt+F4." + } + } + } + + /// The vendored icon shown on this kind's editor row. + pub fn icon_path(self) -> &'static str { + match self { + PayloadKind::Run => "action-icons/square-arrow-right.svg", + PayloadKind::PasteText => "action-icons/clipboard-paste.svg", + PayloadKind::Shortcut => "action-icons/keyboard.svg", } } - /// The payload of `action` when it already is this kind — the editor - /// seeds its input with it so reopening a configured slot edits in - /// place. - pub fn payload_of(self, action: &Action) -> Option<&str> { + /// The editable payload of `action` when it already is this kind — the + /// editor seeds its input with it so reopening a configured slot edits + /// in place. + pub fn payload_of(self, action: &Action) -> Option { match (self, action) { - (PayloadKind::Run, Action::Run(payload)) => Some(payload), - (PayloadKind::PasteText, Action::PasteText(text)) => Some(text), + (PayloadKind::Run, Action::Run(payload)) => Some(payload.clone()), + (PayloadKind::PasteText, Action::PasteText(text)) => Some(text.clone()), + (PayloadKind::Shortcut, Action::CustomShortcut(combo)) => Some(combo.rendered_label()), _ => None, } } - /// Wrap `payload` in this kind's [`Action`] variant. (With an empty - /// payload it also serves as the icon-lookup sample for the flyout row.) - pub fn action(self, payload: String) -> Action { + /// Parse `payload` into this kind's [`Action`]. `None` means the input + /// is not valid for this kind (only `Shortcut` can fail). + pub fn to_action(self, payload: String) -> Option { match self { - PayloadKind::Run => Action::Run(payload), - PayloadKind::PasteText => Action::PasteText(payload), + PayloadKind::Run => Some(Action::Run(payload)), + PayloadKind::PasteText => Some(Action::PasteText(payload)), + PayloadKind::Shortcut => KeyCombo::parse(&payload).map(Action::CustomShortcut), } } } @@ -151,6 +171,9 @@ pub struct RingActionEditorView { target: EditTarget, kind: PayloadKind, input: Entity, + /// Set when Save could not parse the input (Shortcut only); renders an + /// inline error until the next attempt. + invalid: bool, } impl AuxWindow for RingActionEditorView { @@ -174,7 +197,7 @@ pub fn open(target: EditTarget, kind: PayloadKind, cx: &mut App) { let seed: String = cx .try_global::() .and_then(|state| target.current_action(state)) - .and_then(|action| kind.payload_of(&action).map(str::to_owned)) + .and_then(|action| kind.payload_of(&action)) .unwrap_or_default(); windows::open_or_focus( @@ -196,6 +219,7 @@ pub fn open(target: EditTarget, kind: PayloadKind, cx: &mut App) { target, kind, input, + invalid: false, } }, cx, @@ -206,6 +230,7 @@ impl Render for RingActionEditorView { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { let pal = theme::palette(cx); let (target, kind, input) = (self.target, self.kind, self.input.clone()); + let editor = cx.entity(); v_flex() .size_full() @@ -248,6 +273,16 @@ impl Render for RingActionEditorView { .text_color(pal.text_muted) .child(tr!(self.kind.hint_key())), ) + .when(self.invalid, |this| { + this.child( + div() + .text_sm() + .text_color(gpui::rgb(0x00d6_4541)) + .child(tr!( + "That shortcut could not be read — use the Ctrl+Shift+P shape." + )), + ) + }) .child( h_flex() .w_full() @@ -267,13 +302,27 @@ impl Render for RingActionEditorView { // An emptied input closes without // committing — clearing a slot is what // the plain rows ("Do Nothing") are for. + // Unparseable input (Shortcut) keeps the + // dialog open with an inline error. .on_click(move |_, window, cx| { let payload = input.read(cx).value().trim().to_owned(); - if !payload.is_empty() { - target.commit(kind.action(payload), cx); + if payload.is_empty() { + window.remove_window(); + return; + } + match kind.to_action(payload) { + Some(action) => { + target.commit(action, cx); + window.remove_window(); + } + None => { + editor.update(cx, |view, vcx| { + view.invalid = true; + vcx.notify(); + }); + } } - window.remove_window(); }), ), ), From 7fb3860f73e09a917a5181386231a6116d0b3c1c Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 17:42:52 -0700 Subject: [PATCH 33/42] feat(gui): record keyboard shortcuts by pressing them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shortcut dialog gains a "Press Keys" button: hold a chord, release, and it commits — recording is the save. A live readout shows the chord as it is held; Escape alone backs out; listening self-cancels after 15 s idle. Capture reads physical key state (GetAsyncKeyState on a 30 ms timer, the same technique as the ring overlay''s Esc watcher) rather than key events, so chords another app already owns as a global hotkey — Win+Ctrl+Space, for one — still record instead of being swallowed by their owner. The recorder emits canonical chord TEXT, so it and the typed field share one parser and one rendering; the field remains as the editable, non-Windows path. --- crates/openlogi-gui/locales/da.yml | 4 + crates/openlogi-gui/locales/de.yml | 4 + crates/openlogi-gui/locales/el.yml | 4 + crates/openlogi-gui/locales/en.yml | 4 + crates/openlogi-gui/locales/es.yml | 4 + crates/openlogi-gui/locales/fi.yml | 4 + crates/openlogi-gui/locales/fr.yml | 4 + crates/openlogi-gui/locales/it.yml | 4 + crates/openlogi-gui/locales/ja.yml | 4 + crates/openlogi-gui/locales/ko.yml | 4 + crates/openlogi-gui/locales/nb.yml | 4 + crates/openlogi-gui/locales/nl.yml | 4 + crates/openlogi-gui/locales/pl.yml | 4 + crates/openlogi-gui/locales/pt-BR.yml | 4 + crates/openlogi-gui/locales/pt-PT.yml | 4 + crates/openlogi-gui/locales/ru.yml | 4 + crates/openlogi-gui/locales/sv.yml | 4 + crates/openlogi-gui/locales/zh-CN.yml | 4 + crates/openlogi-gui/locales/zh-HK.yml | 4 + crates/openlogi-gui/locales/zh-TW.yml | 4 + crates/openlogi-gui/src/chord_recorder.rs | 204 ++++++++++++++ crates/openlogi-gui/src/main.rs | 1 + .../src/windows/ring_action_editor.rs | 254 +++++++++++++++--- 23 files changed, 495 insertions(+), 44 deletions(-) create mode 100644 crates/openlogi-gui/src/chord_recorder.rs diff --git a/crates/openlogi-gui/locales/da.yml b/crates/openlogi-gui/locales/da.yml index e37dd852..411f2a9f 100644 --- a/crates/openlogi-gui/locales/da.yml +++ b/crates/openlogi-gui/locales/da.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Mappe" "Open Folder": "Åbn mappe" "Keyboard Shortcut": "Tastaturgenvej" +"Press Keys": "Tryk på taster" +"Listening…": "Lytter…" +"Press a shortcut…": "Tryk en genvej…" +"Or record it: hold the keys, then let go.": "Eller optag den: hold tasterne nede, og slip så." "e.g. Ctrl+Shift+P": "f.eks. Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modifikatorer adskilt med +, derefter én tast: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "Genvejen kunne ikke læses — brug formen Ctrl+Shift+P." diff --git a/crates/openlogi-gui/locales/de.yml b/crates/openlogi-gui/locales/de.yml index e5a8e65a..9584b1a0 100644 --- a/crates/openlogi-gui/locales/de.yml +++ b/crates/openlogi-gui/locales/de.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Ordner" "Open Folder": "Ordner öffnen" "Keyboard Shortcut": "Tastenkombination" +"Press Keys": "Tasten drücken" +"Listening…": "Aufnahme…" +"Press a shortcut…": "Tastenkombination drücken…" +"Or record it: hold the keys, then let go.": "Oder aufnehmen: Tasten halten und dann loslassen." "e.g. Ctrl+Shift+P": "z. B. Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modifikatoren mit + verbunden, dann eine Taste: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "Die Tastenkombination konnte nicht gelesen werden — verwende die Form Ctrl+Shift+P." diff --git a/crates/openlogi-gui/locales/el.yml b/crates/openlogi-gui/locales/el.yml index b08e774b..f106e6e2 100644 --- a/crates/openlogi-gui/locales/el.yml +++ b/crates/openlogi-gui/locales/el.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Φάκελος" "Open Folder": "Άνοιγμα φακέλου" "Keyboard Shortcut": "Συντόμευση πληκτρολογίου" +"Press Keys": "Πατήστε πλήκτρα" +"Listening…": "Ακρόαση…" +"Press a shortcut…": "Πατήστε μια συντόμευση…" +"Or record it: hold the keys, then let go.": "Ή καταγράψτε την: κρατήστε τα πλήκτρα και μετά αφήστε τα." "e.g. Ctrl+Shift+P": "π.χ. Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Τροποποιητές ενωμένοι με +, μετά ένα πλήκτρο: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "Η συντόμευση δεν διαβάστηκε — χρησιμοποιήστε τη μορφή Ctrl+Shift+P." diff --git a/crates/openlogi-gui/locales/en.yml b/crates/openlogi-gui/locales/en.yml index edbda85f..8ab419d4 100644 --- a/crates/openlogi-gui/locales/en.yml +++ b/crates/openlogi-gui/locales/en.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Folder" "Open Folder": "Open Folder" "Keyboard Shortcut": "Keyboard Shortcut" +"Press Keys": "Press Keys" +"Listening…": "Listening…" +"Press a shortcut…": "Press a shortcut…" +"Or record it: hold the keys, then let go.": "Or record it: hold the keys, then let go." "e.g. Ctrl+Shift+P": "e.g. Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "That shortcut could not be read — use the Ctrl+Shift+P shape." diff --git a/crates/openlogi-gui/locales/es.yml b/crates/openlogi-gui/locales/es.yml index 46448b7f..dabf6c43 100644 --- a/crates/openlogi-gui/locales/es.yml +++ b/crates/openlogi-gui/locales/es.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Carpeta" "Open Folder": "Abrir carpeta" "Keyboard Shortcut": "Atajo de teclado" +"Press Keys": "Pulsar teclas" +"Listening…": "Escuchando…" +"Press a shortcut…": "Pulsa un atajo…" +"Or record it: hold the keys, then let go.": "O grábalo: mantén las teclas y luego suéltalas." "e.g. Ctrl+Shift+P": "p. ej. Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modificadores unidos con + y una tecla: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "No se pudo leer el atajo — usa la forma Ctrl+Shift+P." diff --git a/crates/openlogi-gui/locales/fi.yml b/crates/openlogi-gui/locales/fi.yml index 3bd931c2..aa873346 100644 --- a/crates/openlogi-gui/locales/fi.yml +++ b/crates/openlogi-gui/locales/fi.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Kansio" "Open Folder": "Avaa kansio" "Keyboard Shortcut": "Pikanäppäin" +"Press Keys": "Paina näppäimiä" +"Listening…": "Kuunnellaan…" +"Press a shortcut…": "Paina pikanäppäintä…" +"Or record it: hold the keys, then let go.": "Tai tallenna se: pidä näppäimiä pohjassa ja päästä irti." "e.g. Ctrl+Shift+P": "esim. Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Muokkaimet yhdistettynä +-merkillä ja yksi näppäin: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "Pikanäppäintä ei voitu lukea — käytä muotoa Ctrl+Shift+P." diff --git a/crates/openlogi-gui/locales/fr.yml b/crates/openlogi-gui/locales/fr.yml index 3edb065f..4db7f981 100644 --- a/crates/openlogi-gui/locales/fr.yml +++ b/crates/openlogi-gui/locales/fr.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Dossier" "Open Folder": "Ouvrir le dossier" "Keyboard Shortcut": "Raccourci clavier" +"Press Keys": "Appuyer sur les touches" +"Listening…": "Écoute…" +"Press a shortcut…": "Appuyez sur un raccourci…" +"Or record it: hold the keys, then let go.": "Ou enregistrez-le : maintenez les touches, puis relâchez." "e.g. Ctrl+Shift+P": "p. ex. Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modificateurs reliés par +, puis une touche : Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "Le raccourci n'a pas pu être lu — utilisez la forme Ctrl+Shift+P." diff --git a/crates/openlogi-gui/locales/it.yml b/crates/openlogi-gui/locales/it.yml index ca08a04a..4136cd63 100644 --- a/crates/openlogi-gui/locales/it.yml +++ b/crates/openlogi-gui/locales/it.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Cartella" "Open Folder": "Apri cartella" "Keyboard Shortcut": "Scorciatoia da tastiera" +"Press Keys": "Premi i tasti" +"Listening…": "In ascolto…" +"Press a shortcut…": "Premi una scorciatoia…" +"Or record it: hold the keys, then let go.": "Oppure registrala: tieni premuti i tasti, poi rilascia." "e.g. Ctrl+Shift+P": "es. Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modificatori uniti con + e un tasto: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "Impossibile leggere la scorciatoia — usa la forma Ctrl+Shift+P." diff --git a/crates/openlogi-gui/locales/ja.yml b/crates/openlogi-gui/locales/ja.yml index 9ad53e7d..3ed4b61c 100644 --- a/crates/openlogi-gui/locales/ja.yml +++ b/crates/openlogi-gui/locales/ja.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "フォルダー" "Open Folder": "フォルダーを開く" "Keyboard Shortcut": "キーボードショートカット" +"Press Keys": "キーを押す" +"Listening…": "入力待ち…" +"Press a shortcut…": "ショートカットを押してください…" +"Or record it: hold the keys, then let go.": "録音もできます: キーを押したまま、離してください。" "e.g. Ctrl+Shift+P": "例: Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "修飾キーを + でつなぎ、最後にキーを 1 つ: Ctrl+Shift+P、Win+Ctrl+Space、Alt+F4。" "That shortcut could not be read — use the Ctrl+Shift+P shape.": "ショートカットを読み取れませんでした。Ctrl+Shift+P の形式で入力してください。" diff --git a/crates/openlogi-gui/locales/ko.yml b/crates/openlogi-gui/locales/ko.yml index 3340c156..8c84849e 100644 --- a/crates/openlogi-gui/locales/ko.yml +++ b/crates/openlogi-gui/locales/ko.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "폴더" "Open Folder": "폴더 열기" "Keyboard Shortcut": "키보드 단축키" +"Press Keys": "키 누르기" +"Listening…": "대기 중…" +"Press a shortcut…": "단축키를 누르세요…" +"Or record it: hold the keys, then let go.": "또는 녹화하세요: 키를 누른 채 있다가 떼세요." "e.g. Ctrl+Shift+P": "예: Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "보조 키를 + 로 잇고 마지막에 키 하나: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "단축키를 읽을 수 없습니다 — Ctrl+Shift+P 형식을 사용하세요." diff --git a/crates/openlogi-gui/locales/nb.yml b/crates/openlogi-gui/locales/nb.yml index 045d9c11..6ea4dcd1 100644 --- a/crates/openlogi-gui/locales/nb.yml +++ b/crates/openlogi-gui/locales/nb.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Mappe" "Open Folder": "Åpne mappe" "Keyboard Shortcut": "Hurtigtast" +"Press Keys": "Trykk taster" +"Listening…": "Lytter…" +"Press a shortcut…": "Trykk en hurtigtast…" +"Or record it: hold the keys, then let go.": "Eller ta den opp: hold tastene, og slipp så." "e.g. Ctrl+Shift+P": "f.eks. Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modifikatorer koblet med +, deretter én tast: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "Hurtigtasten kunne ikke leses — bruk formen Ctrl+Shift+P." diff --git a/crates/openlogi-gui/locales/nl.yml b/crates/openlogi-gui/locales/nl.yml index 1ce95c0f..dfcb3146 100644 --- a/crates/openlogi-gui/locales/nl.yml +++ b/crates/openlogi-gui/locales/nl.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Map" "Open Folder": "Map openen" "Keyboard Shortcut": "Sneltoets" +"Press Keys": "Toetsen indrukken" +"Listening…": "Luisteren…" +"Press a shortcut…": "Druk een sneltoets in…" +"Or record it: hold the keys, then let go.": "Of neem hem op: houd de toetsen ingedrukt en laat los." "e.g. Ctrl+Shift+P": "bijv. Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modificatietoetsen verbonden met +, dan één toets: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "De sneltoets kon niet worden gelezen — gebruik de vorm Ctrl+Shift+P." diff --git a/crates/openlogi-gui/locales/pl.yml b/crates/openlogi-gui/locales/pl.yml index 3a7c5425..2679c91b 100644 --- a/crates/openlogi-gui/locales/pl.yml +++ b/crates/openlogi-gui/locales/pl.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Folder" "Open Folder": "Otwórz folder" "Keyboard Shortcut": "Skrót klawiszowy" +"Press Keys": "Naciśnij klawisze" +"Listening…": "Nasłuchiwanie…" +"Press a shortcut…": "Naciśnij skrót…" +"Or record it: hold the keys, then let go.": "Albo nagraj go: przytrzymaj klawisze, a potem puść." "e.g. Ctrl+Shift+P": "np. Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modyfikatory połączone znakiem + i jeden klawisz: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "Nie udało się odczytać skrótu — użyj formy Ctrl+Shift+P." diff --git a/crates/openlogi-gui/locales/pt-BR.yml b/crates/openlogi-gui/locales/pt-BR.yml index 95f594a7..d49baeb3 100644 --- a/crates/openlogi-gui/locales/pt-BR.yml +++ b/crates/openlogi-gui/locales/pt-BR.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Pasta" "Open Folder": "Abrir pasta" "Keyboard Shortcut": "Atalho de teclado" +"Press Keys": "Pressionar teclas" +"Listening…": "Ouvindo…" +"Press a shortcut…": "Pressione um atalho…" +"Or record it: hold the keys, then let go.": "Ou grave: segure as teclas e depois solte." "e.g. Ctrl+Shift+P": "ex.: Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modificadores unidos com + e uma tecla: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "Não foi possível ler o atalho — use a forma Ctrl+Shift+P." diff --git a/crates/openlogi-gui/locales/pt-PT.yml b/crates/openlogi-gui/locales/pt-PT.yml index 12ab847b..146d52fa 100644 --- a/crates/openlogi-gui/locales/pt-PT.yml +++ b/crates/openlogi-gui/locales/pt-PT.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Pasta" "Open Folder": "Abrir pasta" "Keyboard Shortcut": "Atalho de teclado" +"Press Keys": "Premir teclas" +"Listening…": "A ouvir…" +"Press a shortcut…": "Prima um atalho…" +"Or record it: hold the keys, then let go.": "Ou grave: mantenha as teclas premidas e depois solte." "e.g. Ctrl+Shift+P": "p. ex. Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modificadores unidos com + e uma tecla: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "Não foi possível ler o atalho — utilize a forma Ctrl+Shift+P." diff --git a/crates/openlogi-gui/locales/ru.yml b/crates/openlogi-gui/locales/ru.yml index 0a88990d..3ebad5c1 100644 --- a/crates/openlogi-gui/locales/ru.yml +++ b/crates/openlogi-gui/locales/ru.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Папка" "Open Folder": "Открыть папку" "Keyboard Shortcut": "Сочетание клавиш" +"Press Keys": "Нажмите клавиши" +"Listening…": "Ожидание…" +"Press a shortcut…": "Нажмите сочетание…" +"Or record it: hold the keys, then let go.": "Или запишите: удержите клавиши, затем отпустите." "e.g. Ctrl+Shift+P": "напр. Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Модификаторы через + и одна клавиша: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "Не удалось прочитать сочетание — используйте форму Ctrl+Shift+P." diff --git a/crates/openlogi-gui/locales/sv.yml b/crates/openlogi-gui/locales/sv.yml index 483e6e5d..9ed55da5 100644 --- a/crates/openlogi-gui/locales/sv.yml +++ b/crates/openlogi-gui/locales/sv.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "Mapp" "Open Folder": "Öppna mapp" "Keyboard Shortcut": "Kortkommando" +"Press Keys": "Tryck på tangenter" +"Listening…": "Lyssnar…" +"Press a shortcut…": "Tryck ett kortkommando…" +"Or record it: hold the keys, then let go.": "Eller spela in det: håll ner tangenterna och släpp sedan." "e.g. Ctrl+Shift+P": "t.ex. Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "Modifierare sammanfogade med + och en tangent: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4." "That shortcut could not be read — use the Ctrl+Shift+P shape.": "Kortkommandot kunde inte läsas — använd formen Ctrl+Shift+P." diff --git a/crates/openlogi-gui/locales/zh-CN.yml b/crates/openlogi-gui/locales/zh-CN.yml index a50ef198..a0f69e8b 100644 --- a/crates/openlogi-gui/locales/zh-CN.yml +++ b/crates/openlogi-gui/locales/zh-CN.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "文件夹" "Open Folder": "打开文件夹" "Keyboard Shortcut": "键盘快捷键" +"Press Keys": "按下按键" +"Listening…": "监听中…" +"Press a shortcut…": "请按下快捷键…" +"Or record it: hold the keys, then let go.": "也可以录制:按住按键,然后松开。" "e.g. Ctrl+Shift+P": "例如 Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "修饰键用 + 连接,最后一个键:Ctrl+Shift+P、Win+Ctrl+Space、Alt+F4。" "That shortcut could not be read — use the Ctrl+Shift+P shape.": "无法读取该快捷键——请使用 Ctrl+Shift+P 的格式。" diff --git a/crates/openlogi-gui/locales/zh-HK.yml b/crates/openlogi-gui/locales/zh-HK.yml index 3fc910c8..3c159f64 100644 --- a/crates/openlogi-gui/locales/zh-HK.yml +++ b/crates/openlogi-gui/locales/zh-HK.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "資料夾" "Open Folder": "開啟資料夾" "Keyboard Shortcut": "鍵盤快速鍵" +"Press Keys": "按下按鍵" +"Listening…": "聆聽中…" +"Press a shortcut…": "請按下快速鍵…" +"Or record it: hold the keys, then let go.": "也可以錄製:按住按鍵,然後放開。" "e.g. Ctrl+Shift+P": "例如 Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "修飾鍵以 + 連接,最後一個鍵:Ctrl+Shift+P、Win+Ctrl+Space、Alt+F4。" "That shortcut could not be read — use the Ctrl+Shift+P shape.": "無法讀取該快速鍵——請使用 Ctrl+Shift+P 的格式。" diff --git a/crates/openlogi-gui/locales/zh-TW.yml b/crates/openlogi-gui/locales/zh-TW.yml index 6034cee2..de926b15 100644 --- a/crates/openlogi-gui/locales/zh-TW.yml +++ b/crates/openlogi-gui/locales/zh-TW.yml @@ -174,6 +174,10 @@ _version: 1 "Folder": "資料夾" "Open Folder": "開啟資料夾" "Keyboard Shortcut": "鍵盤快速鍵" +"Press Keys": "按下按鍵" +"Listening…": "聆聽中…" +"Press a shortcut…": "請按下快速鍵…" +"Or record it: hold the keys, then let go.": "也可以錄製:按住按鍵,然後放開。" "e.g. Ctrl+Shift+P": "例如 Ctrl+Shift+P" "Modifier names joined with +, then one key: Ctrl+Shift+P, Win+Ctrl+Space, Alt+F4.": "修飾鍵以 + 連接,最後一個鍵:Ctrl+Shift+P、Win+Ctrl+Space、Alt+F4。" "That shortcut could not be read — use the Ctrl+Shift+P shape.": "無法讀取該快速鍵——請使用 Ctrl+Shift+P 的格式。" diff --git a/crates/openlogi-gui/src/chord_recorder.rs b/crates/openlogi-gui/src/chord_recorder.rs new file mode 100644 index 00000000..e0549a80 --- /dev/null +++ b/crates/openlogi-gui/src/chord_recorder.rs @@ -0,0 +1,204 @@ +//! Physical key-chord recorder for the shortcut editor. +//! +//! Reads the keyboard's **physical** state (`GetAsyncKeyState`) on a timer +//! rather than handling key events, for two reasons: the dialog must record +//! chords the OS or another app has already claimed as a global hotkey +//! (`Win+Ctrl+Space` is exactly such a case — the owner swallows the +//! message, but the physical state is still readable), and the same +//! technique already backs the ring overlay's Esc/outside-click watcher. +//! +//! The recorder emits the **canonical chord text** +//! ([`KeyCombo::parse`]'s input language, e.g. `"Win+Ctrl+Space"`) instead +//! of a [`KeyCombo`], so parsing and display keep exactly one source of +//! truth: whatever a user could type, the recorder produces, and vice versa. + +/// What one [`ChordRecorder::poll`] observed. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Poll { + /// Nothing pressed yet — keep listening. + Idle, + /// Keys are down; `0` is the chord so far (shown live in the dialog). + Holding(String), + /// Every key was released: `0` is the finished chord. + Done(String), + /// Escape alone — the user backed out of listening. + Cancelled, +} + +/// Accumulates the chord across polls: the modifiers held when a +/// non-modifier key goes down, finalized when everything is released. +#[derive(Default)] +pub struct ChordRecorder { + /// The best (most complete) chord seen while keys were held. + held: Option, +} + +impl ChordRecorder { + /// Sample the keyboard once. Call on a ~30 ms timer while listening. + pub fn poll(&mut self) -> Poll { + let (modifiers, key) = platform::sample(); + // Escape with no modifiers is the cancel gesture, never a binding — + // matching the ring overlay, where Esc means "back out". + if modifiers.is_empty() && key == Some("Escape") { + self.held = None; + return Poll::Cancelled; + } + match key { + Some(key) => { + let mut chord = String::new(); + for name in &modifiers { + chord.push_str(name); + chord.push('+'); + } + chord.push_str(key); + self.held = Some(chord.clone()); + Poll::Holding(chord) + } + // A bare modifier is not a chord; wait for the real key. + None if !modifiers.is_empty() => Poll::Idle, + // Everything is up: a chord that was held is now complete. + None => match self.held.take() { + Some(chord) => Poll::Done(chord), + None => Poll::Idle, + }, + } + } +} + +#[cfg(target_os = "windows")] +mod platform { + #![expect( + unsafe_code, + reason = "GetAsyncKeyState is the Win32 physical-key-state API" + )] + + use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetAsyncKeyState; + + /// Modifier virtual keys, in the canonical order [`super::ChordRecorder`] + /// emits them (matching `KeyCombo::parse`'s rendering). + const MODIFIERS: &[(i32, &str)] = &[ + (0x5B, "Win"), // VK_LWIN + (0x5C, "Win"), // VK_RWIN + (0x11, "Ctrl"), + (0x12, "Alt"), + (0x10, "Shift"), + ]; + + /// Named non-modifier keys. Letters, digits and F-keys are generated in + /// [`sample`] rather than listed. + const NAMED: &[(i32, &str)] = &[ + (0x20, "Space"), + (0x0D, "Enter"), + (0x09, "Tab"), + (0x1B, "Escape"), + (0x08, "Backspace"), + (0x2E, "Delete"), + (0x24, "Home"), + (0x23, "End"), + (0x21, "PageUp"), + (0x22, "PageDown"), + (0x26, "Up"), + (0x28, "Down"), + (0x25, "Left"), + (0x27, "Right"), + ]; + + /// Whether `vk` is physically down right now. + fn down(vk: i32) -> bool { + // SAFETY: GetAsyncKeyState takes a virtual-key code and only reads + // process-wide keyboard state; no pointers are involved. + let state = unsafe { GetAsyncKeyState(vk) }; + state.cast_unsigned() & 0x8000 != 0 + } + + /// The modifiers currently held (deduplicated, canonical order) and the + /// first non-modifier key held, if any. + pub fn sample() -> (Vec<&'static str>, Option<&'static str>) { + let mut modifiers: Vec<&'static str> = Vec::new(); + for (vk, name) in MODIFIERS { + if down(*vk) && !modifiers.contains(name) { + modifiers.push(name); + } + } + + // Letters (VK_A..VK_Z) and digits (VK_0..VK_9) share their ASCII + // codes, so their names come straight from the code. + for vk in 0x41..=0x5A_i32 { + if down(vk) { + let name = LETTERS[usize::try_from(vk - 0x41).unwrap_or(0)]; + return (modifiers, Some(name)); + } + } + for vk in 0x30..=0x39_i32 { + if down(vk) { + let name = DIGITS[usize::try_from(vk - 0x30).unwrap_or(0)]; + return (modifiers, Some(name)); + } + } + for vk in 0x70..=0x7B_i32 { + if down(vk) { + let name = FUNCTION_KEYS[usize::try_from(vk - 0x70).unwrap_or(0)]; + return (modifiers, Some(name)); + } + } + for (vk, name) in NAMED { + if down(*vk) { + return (modifiers, Some(name)); + } + } + (modifiers, None) + } + + /// `'static` names for VK_A..VK_Z, indexed by `vk - 0x41`. + const LETTERS: [&str; 26] = [ + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", + "S", "T", "U", "V", "W", "X", "Y", "Z", + ]; + /// `'static` names for VK_0..VK_9, indexed by `vk - 0x30`. + const DIGITS: [&str; 10] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; + /// `'static` names for VK_F1..VK_F12, indexed by `vk - 0x70`. + const FUNCTION_KEYS: [&str; 12] = [ + "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", + ]; +} + +#[cfg(not(target_os = "windows"))] +mod platform { + /// Recording is Windows-first, like the ring overlay itself; elsewhere + /// the dialog's typed-chord path is the way in. + pub fn sample() -> (Vec<&'static str>, Option<&'static str>) { + (Vec::new(), None) + } +} + +/// Whether this platform can record chords (gates the dialog's button). +#[must_use] +pub const fn is_supported() -> bool { + cfg!(target_os = "windows") +} + +#[cfg(test)] +mod tests { + use super::{ChordRecorder, Poll}; + + /// The state machine's contract, exercised without touching the + /// keyboard: nothing held finalizes nothing, and a finished chord is + /// emitted exactly once. + #[test] + fn idle_polls_never_finalize_a_chord() { + let mut recorder = ChordRecorder::default(); + // On a platform with no capture support `sample` reports nothing + // held, which must stay Idle rather than emitting an empty chord. + assert_eq!(recorder.poll(), Poll::Idle); + assert_eq!(recorder.poll(), Poll::Idle); + } + + #[test] + fn a_held_chord_is_emitted_once_on_release() { + let mut recorder = ChordRecorder { + held: Some("Ctrl+Shift+P".into()), + }; + assert_eq!(recorder.poll(), Poll::Done("Ctrl+Shift+P".into())); + assert_eq!(recorder.poll(), Poll::Idle, "not re-emitted"); + } +} diff --git a/crates/openlogi-gui/src/main.rs b/crates/openlogi-gui/src/main.rs index 6e25e9c9..25898ea7 100644 --- a/crates/openlogi-gui/src/main.rs +++ b/crates/openlogi-gui/src/main.rs @@ -33,6 +33,7 @@ mod app; mod app_assets; mod app_menu; mod asset; +mod chord_recorder; mod components; mod data; mod diagnostics; diff --git a/crates/openlogi-gui/src/windows/ring_action_editor.rs b/crates/openlogi-gui/src/windows/ring_action_editor.rs index 298bccbc..f666f125 100644 --- a/crates/openlogi-gui/src/windows/ring_action_editor.rs +++ b/crates/openlogi-gui/src/windows/ring_action_editor.rs @@ -12,7 +12,9 @@ use gpui::{ Window, div, prelude::FluentBuilder as _, px, }; use gpui_component::{ - button::{Button, ButtonVariants as _}, + // Named (not `as _`) because the record button selects its variant with + // `.when(cond, ButtonVariants::danger)`, which needs the trait's path. + button::{Button, ButtonVariants}, h_flex, input::{Input, InputState}, v_flex, @@ -20,10 +22,17 @@ use gpui_component::{ use openlogi_core::binding::{Action, KeyCombo, RingSlot}; use crate::app_menu::{CloseWindow, Minimize, Zoom}; +use crate::chord_recorder::{ChordRecorder, Poll}; use crate::state::AppState; use crate::theme; use crate::windows::{self, AuxWindow, WindowRegistry}; +/// How often the chord recorder samples the keyboard while listening. +const RECORD_TICK: std::time::Duration = std::time::Duration::from_millis(30); +/// Give up listening after this long with nothing pressed, so an abandoned +/// dialog doesn't leave a timer running forever. +const RECORD_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15); + /// Where a ring edit lands: a top-level slot, or a sub-slot inside the /// folder at `folder`. Carried by the payload dialog and the editor's /// action panel so both commit through the right path. @@ -174,6 +183,9 @@ pub struct RingActionEditorView { /// Set when Save could not parse the input (Shortcut only); renders an /// inline error until the next attempt. invalid: bool, + /// Live chord recording (Shortcut only): the chord held so far, or an + /// empty string right after "Press Keys" and before the first key. + listening: Option, } impl AuxWindow for RingActionEditorView { @@ -220,12 +232,149 @@ pub fn open(target: EditTarget, kind: PayloadKind, cx: &mut App) { kind, input, invalid: false, + listening: None, } }, cx, ); } +/// Start listening for a physical chord on `editor`, sampling until the +/// user releases a real chord (committed straight to `target` — recording +/// *is* the save), presses Escape alone, or the timeout elapses. +fn start_listening(editor: &Entity, target: EditTarget, cx: &mut App) { + editor.update(cx, |view, vcx| { + view.invalid = false; + view.listening = Some(String::new()); + vcx.notify(); + }); + + let editor = editor.clone(); + cx.spawn(async move |cx| { + let mut recorder = ChordRecorder::default(); + let mut idle_ticks = 0_u32; + let max_idle = RECORD_TIMEOUT.as_millis() / RECORD_TICK.as_millis().max(1); + loop { + cx.background_executor().timer(RECORD_TICK).await; + let done = cx.update(|cx| { + // The dialog closed (or a different one opened) — stop. + let still_listening = editor.read_with(cx, |view, _| view.listening.is_some()); + if !still_listening { + return true; + } + match recorder.poll() { + Poll::Idle => { + idle_ticks += 1; + if u128::from(idle_ticks) >= max_idle { + stop_listening(&editor, cx); + return true; + } + false + } + Poll::Holding(chord) => { + idle_ticks = 0; + editor.update(cx, |view, vcx| { + view.listening = Some(chord); + vcx.notify(); + }); + false + } + Poll::Cancelled => { + stop_listening(&editor, cx); + true + } + Poll::Done(chord) => { + // The recorder emits canonical chord text, so this + // parse is the same one a typed chord takes. + if let Some(action) = PayloadKind::Shortcut.to_action(chord) { + target.commit(action, cx); + close_editor_window(cx); + } else { + stop_listening(&editor, cx); + } + true + } + } + }); + if done { + break; + } + } + }) + .detach(); +} + +/// The "Press Keys" button plus its live readout. While listening the row +/// shows the chord as it is held (or a prompt before the first key); the +/// captured chord commits and closes on release, so recording *is* saving. +fn record_row( + listening: Option<&str>, + editor: &Entity, + target: EditTarget, + pal: crate::theme::Palette, +) -> impl IntoElement { + let editor_click = editor.clone(); + let is_listening = listening.is_some(); + let readout: gpui::SharedString = match listening { + Some("") => tr!("Press a shortcut…"), + Some(chord) => chord.to_owned().into(), + None => tr!("Or record it: hold the keys, then let go."), + }; + + h_flex() + .items_center() + .gap_3() + .child( + Button::new("ring-editor-record") + .when(is_listening, ButtonVariants::danger) + .when(!is_listening, Button::outline) + .label(if is_listening { + tr!("Listening…") + } else { + tr!("Press Keys") + }) + .on_click(move |_, _, cx| { + let listening = editor_click.read_with(cx, |view, _| view.listening.is_some()); + if listening { + stop_listening(&editor_click, cx); + } else { + start_listening(&editor_click, target, cx); + } + }), + ) + .child( + div() + .text_sm() + .when(is_listening, |s| s.font_weight(FontWeight::SEMIBOLD)) + .text_color(if is_listening { + pal.text_primary + } else { + pal.text_muted + }) + .child(readout), + ) +} + +/// Leave listening mode without committing. +fn stop_listening(editor: &Entity, cx: &mut App) { + editor.update(cx, |view, vcx| { + view.listening = None; + vcx.notify(); + }); +} + +/// Close the payload dialog from outside its own event handlers (the +/// recorder's timer has no `&mut Window`), via the registry handle. +fn close_editor_window(cx: &mut App) { + if let Some(handle) = cx + .default_global::() + .ring_action_editor + .take() + { + let _ = handle.update(cx, |_, window, _| window.remove_window()); + } +} + impl Render for RingActionEditorView { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { let pal = theme::palette(cx); @@ -266,6 +415,19 @@ impl Render for RingActionEditorView { .child(self.target.caption()), ), ) + // Recording is the primary path for a shortcut; the + // text field stays as the editable / accessible form. + .when( + kind == PayloadKind::Shortcut && crate::chord_recorder::is_supported(), + |this| { + this.child(record_row( + self.listening.as_deref(), + &editor, + target, + pal, + )) + }, + ) .child(Input::new(&self.input)) .child( div() @@ -283,49 +445,53 @@ impl Render for RingActionEditorView { )), ) }) - .child( - h_flex() - .w_full() - .justify_end() - .gap_3() - .pt_1() - .child( - Button::new("ring-editor-cancel") - .outline() - .label(tr!("Cancel")) - .on_click(|_, window, _| window.remove_window()), - ) - .child( - Button::new("ring-editor-save") - .primary() - .label(tr!("Save")) - // An emptied input closes without - // committing — clearing a slot is what - // the plain rows ("Do Nothing") are for. - // Unparseable input (Shortcut) keeps the - // dialog open with an inline error. - .on_click(move |_, window, cx| { - let payload = - input.read(cx).value().trim().to_owned(); - if payload.is_empty() { - window.remove_window(); - return; - } - match kind.to_action(payload) { - Some(action) => { - target.commit(action, cx); - window.remove_window(); - } - None => { - editor.update(cx, |view, vcx| { - view.invalid = true; - vcx.notify(); - }); - } - } - }), - ), - ), + .child(button_row(&input, &editor, target, kind)), ) } } + +/// Cancel / Save. An emptied input closes without committing — clearing a +/// slot is what the plain rows ("Do Nothing") are for — and input this kind +/// can't parse (Shortcut) keeps the dialog open with an inline error +/// instead of dropping the edit. +fn button_row( + input: &Entity, + editor: &Entity, + target: EditTarget, + kind: PayloadKind, +) -> impl IntoElement { + let (input, editor) = (input.clone(), editor.clone()); + h_flex() + .w_full() + .justify_end() + .gap_3() + .pt_1() + .child( + Button::new("ring-editor-cancel") + .outline() + .label(tr!("Cancel")) + .on_click(|_, window, _| window.remove_window()), + ) + .child( + Button::new("ring-editor-save") + .primary() + .label(tr!("Save")) + .on_click(move |_, window, cx| { + let payload = input.read(cx).value().trim().to_owned(); + if payload.is_empty() { + window.remove_window(); + return; + } + match kind.to_action(payload) { + Some(action) => { + target.commit(action, cx); + window.remove_window(); + } + None => editor.update(cx, |view, vcx| { + view.invalid = true; + vcx.notify(); + }), + } + }), + ) +} From 51342e715ccff189e7cd1b33bed8f0e19a3ca2a5 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 17:55:29 -0700 Subject: [PATCH 34/42] =?UTF-8?q?feat(core):=20Named=20actions=20=E2=80=94?= =?UTF-8?q?=20user=20labels=20for=20ring=20slots?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A chord or URL is what the machine does; `Named { name, action }` is what the user calls it ("Wispr Voice", not Win+Ctrl+Space). Purely presentational: `Action::inner()` strips the wrapper and every dispatcher (all three injectors, the overlay, icon lookup, categories) resolves through it, so naming can never change behaviour. `with_name("")` unwraps rather than storing a blank label, and re-naming replaces the wrapper instead of stacking one. IPC v14 with the appended-variant golden. --- crates/openlogi-agent-core/src/ipc.rs | 3 +- .../openlogi-agent-core/tests/wire_format.rs | 9 +- crates/openlogi-core/src/binding.rs | 111 ++++++++++++++++++ crates/openlogi-inject/src/inject/linux.rs | 5 +- crates/openlogi-inject/src/inject/macos.rs | 8 +- crates/openlogi-inject/src/inject/windows.rs | 6 +- 6 files changed, 134 insertions(+), 8 deletions(-) diff --git a/crates/openlogi-agent-core/src/ipc.rs b/crates/openlogi-agent-core/src/ipc.rs index 898c8d2a..b277b26d 100644 --- a/crates/openlogi-agent-core/src/ipc.rs +++ b/crates/openlogi-agent-core/src/ipc.rs @@ -33,7 +33,8 @@ use serde::{Deserialize, Serialize}; /// v12: [`Action`] gains the appended `Run` / `PasteText` variants (they ride /// inside `execute_action` and config snapshots). /// v13: [`Action`] gains the appended `Folder` variant (ring sub-menus). -pub const PROTOCOL_VERSION: u32 = 13; +/// v14: [`Action`] gains the appended `Named` variant (user action labels). +pub const PROTOCOL_VERSION: u32 = 14; /// One Action Ring pad press, streamed to the GUI via /// [`Agent::next_ring_press`] so the on-screen ring opens (or confirms a diff --git a/crates/openlogi-agent-core/tests/wire_format.rs b/crates/openlogi-agent-core/tests/wire_format.rs index 2d5eee78..f65eb6e8 100644 --- a/crates/openlogi-agent-core/tests/wire_format.rs +++ b/crates/openlogi-agent-core/tests/wire_format.rs @@ -62,7 +62,7 @@ fn assert_wire(value: &T, golden: &str) { /// that makes that visible in the same diff. #[test] fn protocol_version_is_pinned() { - assert_eq!(PROTOCOL_VERSION, 13); + assert_eq!(PROTOCOL_VERSION, 14); } /// tarpc encodes the request enum's variant index, so trait *method order* is @@ -116,6 +116,13 @@ fn request_variant_order() { }, "112f010006", ); + // v14's Named: variant index, the label, then the wrapped action. + assert_wire( + &AgentRequest::ExecuteAction { + action: Action::Copy.with_name("N"), + }, + "1130014e06", + ); } #[test] diff --git a/crates/openlogi-core/src/binding.rs b/crates/openlogi-core/src/binding.rs index 3e247ce9..ce73cde4 100644 --- a/crates/openlogi-core/src/binding.rs +++ b/crates/openlogi-core/src/binding.rs @@ -489,6 +489,57 @@ pub enum Action { /// overlay treats a folder nested inside a folder as a plain (warned, /// ignored) leaf. Folder(BTreeMap), + + /// A user-labelled wrapper — the Options+ card name. `Run`/`PasteText`/ + /// `CustomShortcut` payloads are what the machine does; this is what the + /// user calls it ("Wispr Voice", not `⊞⌃Space`). Purely presentational: + /// every consumer dispatches [`Action::inner`], so a name can be added + /// or removed without changing behaviour. + Named { + /// Display label. Empty falls back to the wrapped action's own + /// label, so a blanked name is not a blank slot. + name: String, + /// What actually runs. + action: Box, + }, +} + +impl Action { + /// The action stripped of any [`Action::Named`] wrappers — what every + /// dispatcher should execute. Loops rather than recurses so an + /// accidentally double-wrapped action still resolves. + #[must_use] + pub fn inner(&self) -> &Action { + let mut current = self; + while let Action::Named { action, .. } = current { + current = action; + } + current + } + + /// The user-chosen name, if this action carries one. + #[must_use] + pub fn display_name(&self) -> Option<&str> { + match self { + Action::Named { name, .. } if !name.is_empty() => Some(name), + _ => None, + } + } + + /// Wrap in (or, with an empty `name`, strip) a [`Action::Named`] label, + /// never stacking one wrapper on another. + #[must_use] + pub fn with_name(self, name: &str) -> Action { + let inner = self.inner().clone(); + if name.is_empty() { + inner + } else { + Action::Named { + name: name.to_owned(), + action: Box::new(inner), + } + } + } } /// Split an [`Action::Run`] payload into its target and optional argument @@ -982,6 +1033,13 @@ impl Action { .count(); format!("Folder ({filled})") } + Action::Named { name, action } => { + if name.is_empty() { + action.label() + } else { + name.clone() + } + } } } @@ -1040,6 +1098,8 @@ impl Action { | Action::ScrollDown | Action::HorizontalScrollLeft | Action::HorizontalScrollRight => Category::Scroll, + // A label doesn't change what an action is. + Action::Named { action, .. } => action.category(), } } @@ -1252,6 +1312,7 @@ mod tests { | Action::Run(_) | Action::PasteText(_) | Action::Folder(_) + | Action::Named { .. } ), "catalog must not contain payload-carrying editor actions" ); @@ -1289,6 +1350,56 @@ mod tests { )); } + #[test] + fn named_labels_by_name_but_stays_the_wrapped_action() { + let named = Action::Run("https://gemini.google.com/".into()).with_name("Rephrase"); + assert_eq!(named.label(), "Rephrase"); + assert_eq!(named.display_name(), Some("Rephrase")); + assert_eq!( + named.inner(), + &Action::Run("https://gemini.google.com/".into()), + "dispatch sees through the label" + ); + assert_eq!( + named.category(), + Action::Run(String::new()).category(), + "a label does not re-categorize" + ); + + // Renaming replaces the wrapper instead of stacking one. + let renamed = named.with_name("Gemini"); + assert_eq!(renamed.display_name(), Some("Gemini")); + assert_eq!( + renamed.inner(), + &Action::Run("https://gemini.google.com/".into()) + ); + // An empty name unwraps rather than showing a blank slot. + assert_eq!( + renamed.with_name(""), + Action::Run("https://gemini.google.com/".into()) + ); + // An empty name inside a stored action still renders something. + let blank = Action::Named { + name: String::new(), + action: Box::new(Action::Copy), + }; + assert_eq!(blank.label(), "Copy"); + assert_eq!(blank.display_name(), None); + } + + #[test] + fn named_roundtrips_through_toml() { + let bindings = BTreeMap::from([( + ButtonId::Forward, + Binding::Single(Action::PasteText("hi".into()).with_name("Greeting")), + )]); + let back = binding_roundtrip(bindings); + assert_eq!( + back[&ButtonId::Forward], + Binding::Single(Action::PasteText("hi".into()).with_name("Greeting")) + ); + } + #[test] fn key_combo_parse_accepts_chords_and_rejects_garbage() { let combo = KeyCombo::parse("win + ctrl + space").expect("valid chord"); diff --git a/crates/openlogi-inject/src/inject/linux.rs b/crates/openlogi-inject/src/inject/linux.rs index 3b02a580..403f2ec2 100644 --- a/crates/openlogi-inject/src/inject/linux.rs +++ b/crates/openlogi-inject/src/inject/linux.rs @@ -19,7 +19,8 @@ pub(super) fn execute(action: &Action) { let ctrl = KeyCode::KEY_LEFTCTRL; let shift = KeyCode::KEY_LEFTSHIFT; let alt = KeyCode::KEY_LEFTALT; - match action { + // A user-chosen label is presentation only — dispatch what it wraps. + match action.inner() { // ── Mouse clicks ────────────────────────────────────────────────── Action::LeftClick => click(KeyCode::BTN_LEFT), Action::RightClick => click(KeyCode::BTN_RIGHT), @@ -121,6 +122,8 @@ pub(super) fn execute(action: &Action) { Action::Folder(_) => { tracing::warn!("folder reached the injector — containers are resolved by the ring"); } + // `inner()` already stripped every label. + Action::Named { .. } => {} } } diff --git a/crates/openlogi-inject/src/inject/macos.rs b/crates/openlogi-inject/src/inject/macos.rs index d35a3cca..d26c7087 100644 --- a/crates/openlogi-inject/src/inject/macos.rs +++ b/crates/openlogi-inject/src/inject/macos.rs @@ -41,9 +41,11 @@ pub(super) fn execute(action: &Action) { let shift = CGEventFlags::CGEventFlagShift; let ctrl = CGEventFlags::CGEventFlagControl; - match action { - // Suppressed input: captured but deliberately produces no event. - Action::None => {} + // A user-chosen label is presentation only — dispatch what it wraps. + match action.inner() { + // Suppressed input: captured but deliberately produces no event; + // `inner()` already stripped every label. + Action::None | Action::Named { .. } => {} // ── Mouse clicks: synthesise a click at the cursor ──────────────── // Remapping a *different* button to a click lands here (e.g. Back → // MiddleClick). A button left on its own native click never reaches diff --git a/crates/openlogi-inject/src/inject/windows.rs b/crates/openlogi-inject/src/inject/windows.rs index 2cf3d78f..bbb23dcf 100644 --- a/crates/openlogi-inject/src/inject/windows.rs +++ b/crates/openlogi-inject/src/inject/windows.rs @@ -66,7 +66,8 @@ const XBUTTON2: i32 = 2; /// window-manager actions map to their Windows equivalents; `CustomShortcut` /// maps macOS `kVK_*` codes to Windows virtual-key codes (Cmd → Ctrl). pub(super) fn execute(action: &Action) { - match action { + // A user-chosen label is presentation only — dispatch what it wraps. + match action.inner() { Action::LeftClick => post_click(MouseButton::Left), Action::RightClick => post_click(MouseButton::Right), Action::MiddleClick => post_click(MouseButton::Middle), @@ -131,7 +132,8 @@ pub(super) fn execute(action: &Action) { Action::Folder(_) => { tracing::warn!("folder reached the injector — containers are resolved by the ring"); } - Action::None => {} + // `inner()` already stripped every label. + Action::Named { .. } | Action::None => {} } } From 75090295ef0f7f3342f7b1a319f98e8649c35130 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 17:55:29 -0700 Subject: [PATCH 35/42] feat(gui): name the action you are binding The payload dialog gains a Name field beside the payload, with captions on both so it reads as "this is the command, this is what I call it". The name is stored via Action::with_name, shown on the ring canvas and label pills in place of the raw payload, seeded when reopening a slot, and carried through the chord recorder''s auto-save path too. --- crates/openlogi-gui/locales/da.yml | 5 ++ crates/openlogi-gui/locales/de.yml | 5 ++ crates/openlogi-gui/locales/el.yml | 5 ++ crates/openlogi-gui/locales/en.yml | 5 ++ crates/openlogi-gui/locales/es.yml | 5 ++ crates/openlogi-gui/locales/fi.yml | 5 ++ crates/openlogi-gui/locales/fr.yml | 5 ++ crates/openlogi-gui/locales/it.yml | 5 ++ crates/openlogi-gui/locales/ja.yml | 5 ++ crates/openlogi-gui/locales/ko.yml | 5 ++ crates/openlogi-gui/locales/nb.yml | 5 ++ crates/openlogi-gui/locales/nl.yml | 5 ++ crates/openlogi-gui/locales/pl.yml | 5 ++ crates/openlogi-gui/locales/pt-BR.yml | 5 ++ crates/openlogi-gui/locales/pt-PT.yml | 5 ++ crates/openlogi-gui/locales/ru.yml | 5 ++ crates/openlogi-gui/locales/sv.yml | 5 ++ crates/openlogi-gui/locales/zh-CN.yml | 5 ++ crates/openlogi-gui/locales/zh-HK.yml | 5 ++ crates/openlogi-gui/locales/zh-TW.yml | 5 ++ crates/openlogi-gui/src/mouse_model/picker.rs | 7 +- crates/openlogi-gui/src/ring.rs | 4 +- .../src/windows/ring_action_editor.rs | 76 ++++++++++++++++--- 23 files changed, 173 insertions(+), 14 deletions(-) diff --git a/crates/openlogi-gui/locales/da.yml b/crates/openlogi-gui/locales/da.yml index 411f2a9f..29734ea5 100644 --- a/crates/openlogi-gui/locales/da.yml +++ b/crates/openlogi-gui/locales/da.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Åbn mappe" "Keyboard Shortcut": "Tastaturgenvej" "Press Keys": "Tryk på taster" +"Name": "Navn" +"Optional — shown on the ring": "Valgfrit — vises på ringen" +"Command": "Kommando" +"Text": "Tekst" +"Shortcut": "Genvej" "Listening…": "Lytter…" "Press a shortcut…": "Tryk en genvej…" "Or record it: hold the keys, then let go.": "Eller optag den: hold tasterne nede, og slip så." diff --git a/crates/openlogi-gui/locales/de.yml b/crates/openlogi-gui/locales/de.yml index 9584b1a0..d4d0c44c 100644 --- a/crates/openlogi-gui/locales/de.yml +++ b/crates/openlogi-gui/locales/de.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Ordner öffnen" "Keyboard Shortcut": "Tastenkombination" "Press Keys": "Tasten drücken" +"Name": "Name" +"Optional — shown on the ring": "Optional — wird im Ring angezeigt" +"Command": "Befehl" +"Text": "Text" +"Shortcut": "Tastenkombination" "Listening…": "Aufnahme…" "Press a shortcut…": "Tastenkombination drücken…" "Or record it: hold the keys, then let go.": "Oder aufnehmen: Tasten halten und dann loslassen." diff --git a/crates/openlogi-gui/locales/el.yml b/crates/openlogi-gui/locales/el.yml index f106e6e2..06fccc69 100644 --- a/crates/openlogi-gui/locales/el.yml +++ b/crates/openlogi-gui/locales/el.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Άνοιγμα φακέλου" "Keyboard Shortcut": "Συντόμευση πληκτρολογίου" "Press Keys": "Πατήστε πλήκτρα" +"Name": "Όνομα" +"Optional — shown on the ring": "Προαιρετικό — εμφανίζεται στον δακτύλιο" +"Command": "Εντολή" +"Text": "Κείμενο" +"Shortcut": "Συντόμευση" "Listening…": "Ακρόαση…" "Press a shortcut…": "Πατήστε μια συντόμευση…" "Or record it: hold the keys, then let go.": "Ή καταγράψτε την: κρατήστε τα πλήκτρα και μετά αφήστε τα." diff --git a/crates/openlogi-gui/locales/en.yml b/crates/openlogi-gui/locales/en.yml index 8ab419d4..42fa42e3 100644 --- a/crates/openlogi-gui/locales/en.yml +++ b/crates/openlogi-gui/locales/en.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Open Folder" "Keyboard Shortcut": "Keyboard Shortcut" "Press Keys": "Press Keys" +"Name": "Name" +"Optional — shown on the ring": "Optional — shown on the ring" +"Command": "Command" +"Text": "Text" +"Shortcut": "Shortcut" "Listening…": "Listening…" "Press a shortcut…": "Press a shortcut…" "Or record it: hold the keys, then let go.": "Or record it: hold the keys, then let go." diff --git a/crates/openlogi-gui/locales/es.yml b/crates/openlogi-gui/locales/es.yml index dabf6c43..df32db76 100644 --- a/crates/openlogi-gui/locales/es.yml +++ b/crates/openlogi-gui/locales/es.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Abrir carpeta" "Keyboard Shortcut": "Atajo de teclado" "Press Keys": "Pulsar teclas" +"Name": "Nombre" +"Optional — shown on the ring": "Opcional: se muestra en el anillo" +"Command": "Comando" +"Text": "Texto" +"Shortcut": "Atajo" "Listening…": "Escuchando…" "Press a shortcut…": "Pulsa un atajo…" "Or record it: hold the keys, then let go.": "O grábalo: mantén las teclas y luego suéltalas." diff --git a/crates/openlogi-gui/locales/fi.yml b/crates/openlogi-gui/locales/fi.yml index aa873346..abe04aba 100644 --- a/crates/openlogi-gui/locales/fi.yml +++ b/crates/openlogi-gui/locales/fi.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Avaa kansio" "Keyboard Shortcut": "Pikanäppäin" "Press Keys": "Paina näppäimiä" +"Name": "Nimi" +"Optional — shown on the ring": "Valinnainen — näkyy renkaassa" +"Command": "Komento" +"Text": "Teksti" +"Shortcut": "Pikanäppäin" "Listening…": "Kuunnellaan…" "Press a shortcut…": "Paina pikanäppäintä…" "Or record it: hold the keys, then let go.": "Tai tallenna se: pidä näppäimiä pohjassa ja päästä irti." diff --git a/crates/openlogi-gui/locales/fr.yml b/crates/openlogi-gui/locales/fr.yml index 4db7f981..d7cd9907 100644 --- a/crates/openlogi-gui/locales/fr.yml +++ b/crates/openlogi-gui/locales/fr.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Ouvrir le dossier" "Keyboard Shortcut": "Raccourci clavier" "Press Keys": "Appuyer sur les touches" +"Name": "Nom" +"Optional — shown on the ring": "Facultatif — affiché sur l'anneau" +"Command": "Commande" +"Text": "Texte" +"Shortcut": "Raccourci" "Listening…": "Écoute…" "Press a shortcut…": "Appuyez sur un raccourci…" "Or record it: hold the keys, then let go.": "Ou enregistrez-le : maintenez les touches, puis relâchez." diff --git a/crates/openlogi-gui/locales/it.yml b/crates/openlogi-gui/locales/it.yml index 4136cd63..bdc1d64d 100644 --- a/crates/openlogi-gui/locales/it.yml +++ b/crates/openlogi-gui/locales/it.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Apri cartella" "Keyboard Shortcut": "Scorciatoia da tastiera" "Press Keys": "Premi i tasti" +"Name": "Nome" +"Optional — shown on the ring": "Facoltativo — mostrato sull'anello" +"Command": "Comando" +"Text": "Testo" +"Shortcut": "Scorciatoia" "Listening…": "In ascolto…" "Press a shortcut…": "Premi una scorciatoia…" "Or record it: hold the keys, then let go.": "Oppure registrala: tieni premuti i tasti, poi rilascia." diff --git a/crates/openlogi-gui/locales/ja.yml b/crates/openlogi-gui/locales/ja.yml index 3ed4b61c..f2f73302 100644 --- a/crates/openlogi-gui/locales/ja.yml +++ b/crates/openlogi-gui/locales/ja.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "フォルダーを開く" "Keyboard Shortcut": "キーボードショートカット" "Press Keys": "キーを押す" +"Name": "名前" +"Optional — shown on the ring": "任意 — リングに表示されます" +"Command": "コマンド" +"Text": "テキスト" +"Shortcut": "ショートカット" "Listening…": "入力待ち…" "Press a shortcut…": "ショートカットを押してください…" "Or record it: hold the keys, then let go.": "録音もできます: キーを押したまま、離してください。" diff --git a/crates/openlogi-gui/locales/ko.yml b/crates/openlogi-gui/locales/ko.yml index 8c84849e..c85134f6 100644 --- a/crates/openlogi-gui/locales/ko.yml +++ b/crates/openlogi-gui/locales/ko.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "폴더 열기" "Keyboard Shortcut": "키보드 단축키" "Press Keys": "키 누르기" +"Name": "이름" +"Optional — shown on the ring": "선택 사항 — 링에 표시됩니다" +"Command": "명령" +"Text": "텍스트" +"Shortcut": "단축키" "Listening…": "대기 중…" "Press a shortcut…": "단축키를 누르세요…" "Or record it: hold the keys, then let go.": "또는 녹화하세요: 키를 누른 채 있다가 떼세요." diff --git a/crates/openlogi-gui/locales/nb.yml b/crates/openlogi-gui/locales/nb.yml index 6ea4dcd1..9abda612 100644 --- a/crates/openlogi-gui/locales/nb.yml +++ b/crates/openlogi-gui/locales/nb.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Åpne mappe" "Keyboard Shortcut": "Hurtigtast" "Press Keys": "Trykk taster" +"Name": "Navn" +"Optional — shown on the ring": "Valgfritt — vises på ringen" +"Command": "Kommando" +"Text": "Tekst" +"Shortcut": "Hurtigtast" "Listening…": "Lytter…" "Press a shortcut…": "Trykk en hurtigtast…" "Or record it: hold the keys, then let go.": "Eller ta den opp: hold tastene, og slipp så." diff --git a/crates/openlogi-gui/locales/nl.yml b/crates/openlogi-gui/locales/nl.yml index dfcb3146..b4546693 100644 --- a/crates/openlogi-gui/locales/nl.yml +++ b/crates/openlogi-gui/locales/nl.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Map openen" "Keyboard Shortcut": "Sneltoets" "Press Keys": "Toetsen indrukken" +"Name": "Naam" +"Optional — shown on the ring": "Optioneel — te zien op de ring" +"Command": "Opdracht" +"Text": "Tekst" +"Shortcut": "Sneltoets" "Listening…": "Luisteren…" "Press a shortcut…": "Druk een sneltoets in…" "Or record it: hold the keys, then let go.": "Of neem hem op: houd de toetsen ingedrukt en laat los." diff --git a/crates/openlogi-gui/locales/pl.yml b/crates/openlogi-gui/locales/pl.yml index 2679c91b..711178d4 100644 --- a/crates/openlogi-gui/locales/pl.yml +++ b/crates/openlogi-gui/locales/pl.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Otwórz folder" "Keyboard Shortcut": "Skrót klawiszowy" "Press Keys": "Naciśnij klawisze" +"Name": "Nazwa" +"Optional — shown on the ring": "Opcjonalnie — widoczne na pierścieniu" +"Command": "Polecenie" +"Text": "Tekst" +"Shortcut": "Skrót" "Listening…": "Nasłuchiwanie…" "Press a shortcut…": "Naciśnij skrót…" "Or record it: hold the keys, then let go.": "Albo nagraj go: przytrzymaj klawisze, a potem puść." diff --git a/crates/openlogi-gui/locales/pt-BR.yml b/crates/openlogi-gui/locales/pt-BR.yml index d49baeb3..5dfd739d 100644 --- a/crates/openlogi-gui/locales/pt-BR.yml +++ b/crates/openlogi-gui/locales/pt-BR.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Abrir pasta" "Keyboard Shortcut": "Atalho de teclado" "Press Keys": "Pressionar teclas" +"Name": "Nome" +"Optional — shown on the ring": "Opcional — exibido no anel" +"Command": "Comando" +"Text": "Texto" +"Shortcut": "Atalho" "Listening…": "Ouvindo…" "Press a shortcut…": "Pressione um atalho…" "Or record it: hold the keys, then let go.": "Ou grave: segure as teclas e depois solte." diff --git a/crates/openlogi-gui/locales/pt-PT.yml b/crates/openlogi-gui/locales/pt-PT.yml index 146d52fa..a06fc2e9 100644 --- a/crates/openlogi-gui/locales/pt-PT.yml +++ b/crates/openlogi-gui/locales/pt-PT.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Abrir pasta" "Keyboard Shortcut": "Atalho de teclado" "Press Keys": "Premir teclas" +"Name": "Nome" +"Optional — shown on the ring": "Opcional — apresentado no anel" +"Command": "Comando" +"Text": "Texto" +"Shortcut": "Atalho" "Listening…": "A ouvir…" "Press a shortcut…": "Prima um atalho…" "Or record it: hold the keys, then let go.": "Ou grave: mantenha as teclas premidas e depois solte." diff --git a/crates/openlogi-gui/locales/ru.yml b/crates/openlogi-gui/locales/ru.yml index 3ebad5c1..408b6432 100644 --- a/crates/openlogi-gui/locales/ru.yml +++ b/crates/openlogi-gui/locales/ru.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Открыть папку" "Keyboard Shortcut": "Сочетание клавиш" "Press Keys": "Нажмите клавиши" +"Name": "Имя" +"Optional — shown on the ring": "Необязательно — отображается в кольце" +"Command": "Команда" +"Text": "Текст" +"Shortcut": "Сочетание" "Listening…": "Ожидание…" "Press a shortcut…": "Нажмите сочетание…" "Or record it: hold the keys, then let go.": "Или запишите: удержите клавиши, затем отпустите." diff --git a/crates/openlogi-gui/locales/sv.yml b/crates/openlogi-gui/locales/sv.yml index 9ed55da5..8dd0bee8 100644 --- a/crates/openlogi-gui/locales/sv.yml +++ b/crates/openlogi-gui/locales/sv.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "Öppna mapp" "Keyboard Shortcut": "Kortkommando" "Press Keys": "Tryck på tangenter" +"Name": "Namn" +"Optional — shown on the ring": "Valfritt — visas på ringen" +"Command": "Kommando" +"Text": "Text" +"Shortcut": "Kortkommando" "Listening…": "Lyssnar…" "Press a shortcut…": "Tryck ett kortkommando…" "Or record it: hold the keys, then let go.": "Eller spela in det: håll ner tangenterna och släpp sedan." diff --git a/crates/openlogi-gui/locales/zh-CN.yml b/crates/openlogi-gui/locales/zh-CN.yml index a0f69e8b..8543db23 100644 --- a/crates/openlogi-gui/locales/zh-CN.yml +++ b/crates/openlogi-gui/locales/zh-CN.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "打开文件夹" "Keyboard Shortcut": "键盘快捷键" "Press Keys": "按下按键" +"Name": "名称" +"Optional — shown on the ring": "可选——显示在环上" +"Command": "命令" +"Text": "文本" +"Shortcut": "快捷键" "Listening…": "监听中…" "Press a shortcut…": "请按下快捷键…" "Or record it: hold the keys, then let go.": "也可以录制:按住按键,然后松开。" diff --git a/crates/openlogi-gui/locales/zh-HK.yml b/crates/openlogi-gui/locales/zh-HK.yml index 3c159f64..348459c6 100644 --- a/crates/openlogi-gui/locales/zh-HK.yml +++ b/crates/openlogi-gui/locales/zh-HK.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "開啟資料夾" "Keyboard Shortcut": "鍵盤快速鍵" "Press Keys": "按下按鍵" +"Name": "名稱" +"Optional — shown on the ring": "選填——顯示在圓環上" +"Command": "指令" +"Text": "文字" +"Shortcut": "快速鍵" "Listening…": "聆聽中…" "Press a shortcut…": "請按下快速鍵…" "Or record it: hold the keys, then let go.": "也可以錄製:按住按鍵,然後放開。" diff --git a/crates/openlogi-gui/locales/zh-TW.yml b/crates/openlogi-gui/locales/zh-TW.yml index de926b15..578f4ffc 100644 --- a/crates/openlogi-gui/locales/zh-TW.yml +++ b/crates/openlogi-gui/locales/zh-TW.yml @@ -175,6 +175,11 @@ _version: 1 "Open Folder": "開啟資料夾" "Keyboard Shortcut": "鍵盤快速鍵" "Press Keys": "按下按鍵" +"Name": "名稱" +"Optional — shown on the ring": "選填——顯示在圓環上" +"Command": "命令" +"Text": "文字" +"Shortcut": "快速鍵" "Listening…": "聆聽中…" "Press a shortcut…": "請按下快速鍵…" "Or record it: hold the keys, then let go.": "也可以錄製:按住按鍵,然後放開。" diff --git a/crates/openlogi-gui/src/mouse_model/picker.rs b/crates/openlogi-gui/src/mouse_model/picker.rs index f4cecab6..247f539b 100644 --- a/crates/openlogi-gui/src/mouse_model/picker.rs +++ b/crates/openlogi-gui/src/mouse_model/picker.rs @@ -340,8 +340,11 @@ pub(crate) const RING_BUTTON_ICON: &str = "action-icons/grid-3x3.svg"; /// card. Exhaustive on purpose: a new [`Action`] variant must pick an icon here /// (no catch-all fallback). pub(crate) fn action_icon_path(action: &Action) -> &'static str { - match action { - Action::None => "action-icons/ban.svg", + // A label doesn't change the glyph — look through it. + match action.inner() { + // `Named` is unreachable — `inner()` strips labels above — but it + // shares the neutral glyph rather than inventing an icon. + Action::None | Action::Named { .. } => "action-icons/ban.svg", Action::LeftClick | Action::RightClick => "action-icons/mouse-pointer-click.svg", Action::MiddleClick => "action-icons/mouse.svg", // Circled arrows: visually "back/forward as a button", distinct from diff --git a/crates/openlogi-gui/src/ring.rs b/crates/openlogi-gui/src/ring.rs index 216e6a79..75a01e3c 100644 --- a/crates/openlogi-gui/src/ring.rs +++ b/crates/openlogi-gui/src/ring.rs @@ -301,7 +301,9 @@ fn activate(slot: RingSlot, cx: &mut App) { let Some(action) = action else { return; }; - match action { + // A user label is presentation only: resolve folders and dispatch by + // what the action actually is. + match action.inner().clone() { Action::Folder(items) if !in_folder => { let items: Vec<(RingSlot, Action)> = items .into_iter() diff --git a/crates/openlogi-gui/src/windows/ring_action_editor.rs b/crates/openlogi-gui/src/windows/ring_action_editor.rs index f666f125..84e01935 100644 --- a/crates/openlogi-gui/src/windows/ring_action_editor.rs +++ b/crates/openlogi-gui/src/windows/ring_action_editor.rs @@ -140,6 +140,15 @@ impl PayloadKind { } } + /// Caption above this kind's payload input. + fn field_label_key(self) -> &'static str { + match self { + PayloadKind::Run => "Command", + PayloadKind::PasteText => "Text", + PayloadKind::Shortcut => "Shortcut", + } + } + /// The vendored icon shown on this kind's editor row. pub fn icon_path(self) -> &'static str { match self { @@ -153,7 +162,9 @@ impl PayloadKind { /// editor seeds its input with it so reopening a configured slot edits /// in place. pub fn payload_of(self, action: &Action) -> Option { - match (self, action) { + // Look through a user label: naming a Run action doesn't stop it + // being the Run action this dialog edits. + match (self, action.inner()) { (PayloadKind::Run, Action::Run(payload)) => Some(payload.clone()), (PayloadKind::PasteText, Action::PasteText(text)) => Some(text.clone()), (PayloadKind::Shortcut, Action::CustomShortcut(combo)) => Some(combo.rendered_label()), @@ -180,6 +191,10 @@ pub struct RingActionEditorView { target: EditTarget, kind: PayloadKind, input: Entity, + /// The user's label for this action ("Wispr Voice"), shown on the ring + /// instead of the raw payload. Empty means "use the payload's own + /// label". + name_input: Entity, /// Set when Save could not parse the input (Shortcut only); renders an /// inline error until the next attempt. invalid: bool, @@ -206,16 +221,25 @@ pub fn open(target: EditTarget, kind: PayloadKind, cx: &mut App) { let _ = handle.update(cx, |_, window, _| window.remove_window()); } - let seed: String = cx + let current = cx .try_global::() - .and_then(|state| target.current_action(state)) - .and_then(|action| kind.payload_of(&action)) + .and_then(|state| target.current_action(state)); + // Seed both fields from the slot, but only when it already holds this + // kind — a name belongs to the action it was given to. + let seed: String = current + .as_ref() + .and_then(|action| kind.payload_of(action)) + .unwrap_or_default(); + let name_seed: String = current + .as_ref() + .filter(|action| kind.payload_of(action).is_some()) + .and_then(|action| action.display_name().map(str::to_owned)) .unwrap_or_default(); windows::open_or_focus( |reg| &mut reg.ring_action_editor, tr!(kind.title_key()), - Size::new(px(420.), px(240.)), + Size::new(px(440.), px(320.)), move |window, cx| { let focus_handle = cx.focus_handle(); let input = cx.new(|cx| { @@ -223,7 +247,12 @@ pub fn open(target: EditTarget, kind: PayloadKind, cx: &mut App) { .placeholder(tr!(kind.placeholder_key())) .default_value(seed) }); - // Type straight away — the input is the whole dialog. + let name_input = cx.new(|cx| { + InputState::new(window, cx) + .placeholder(tr!("Optional — shown on the ring")) + .default_value(name_seed) + }); + // Type straight away — the payload is the point of the dialog. input.update(cx, |state, cx| state.focus(window, cx)); RingActionEditorView { focus_handle, @@ -231,6 +260,7 @@ pub fn open(target: EditTarget, kind: PayloadKind, cx: &mut App) { target, kind, input, + name_input, invalid: false, listening: None, } @@ -285,9 +315,13 @@ fn start_listening(editor: &Entity, target: EditTarget, cx } Poll::Done(chord) => { // The recorder emits canonical chord text, so this - // parse is the same one a typed chord takes. + // parse is the same one a typed chord takes. Any + // name already typed in the dialog rides along. + let name = editor.read_with(cx, |view, cx| { + view.name_input.read(cx).value().trim().to_owned() + }); if let Some(action) = PayloadKind::Shortcut.to_action(chord) { - target.commit(action, cx); + target.commit(action.with_name(&name), cx); close_editor_window(cx); } else { stop_listening(&editor, cx); @@ -428,6 +462,7 @@ impl Render for RingActionEditorView { )) }, ) + .child(field_label(tr!(self.kind.field_label_key()), pal)) .child(Input::new(&self.input)) .child( div() @@ -435,6 +470,8 @@ impl Render for RingActionEditorView { .text_color(pal.text_muted) .child(tr!(self.kind.hint_key())), ) + .child(field_label(tr!("Name"), pal)) + .child(Input::new(&self.name_input)) .when(self.invalid, |this| { this.child( div() @@ -445,22 +482,38 @@ impl Render for RingActionEditorView { )), ) }) - .child(button_row(&input, &editor, target, kind)), + .child(button_row( + &input, + &self.name_input, + &editor, + target, + kind, + )), ) } } +/// Small uppercase caption above an input. +fn field_label(text: gpui::SharedString, pal: crate::theme::Palette) -> impl IntoElement { + div() + .text_xs() + .font_weight(FontWeight::SEMIBOLD) + .text_color(pal.text_muted) + .child(text) +} + /// Cancel / Save. An emptied input closes without committing — clearing a /// slot is what the plain rows ("Do Nothing") are for — and input this kind /// can't parse (Shortcut) keeps the dialog open with an inline error /// instead of dropping the edit. fn button_row( input: &Entity, + name_input: &Entity, editor: &Entity, target: EditTarget, kind: PayloadKind, ) -> impl IntoElement { - let (input, editor) = (input.clone(), editor.clone()); + let (input, name_input, editor) = (input.clone(), name_input.clone(), editor.clone()); h_flex() .w_full() .justify_end() @@ -482,9 +535,10 @@ fn button_row( window.remove_window(); return; } + let name = name_input.read(cx).value().trim().to_owned(); match kind.to_action(payload) { Some(action) => { - target.commit(action, cx); + target.commit(action.with_name(&name), cx); window.remove_window(); } None => editor.update(cx, |view, vcx| { From a7a2a2fae24cff774421cf7ceb2b73055796cbb2 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 18:09:28 -0700 Subject: [PATCH 36/42] fix(inject): hold synthesized chords long enough to be observed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A chord sent as one SendInput batch is physically down for microseconds. Any hotkey listener that polls key state (GetAsyncKeyState) rather than handling key messages therefore never sees it — the press falls between two samples. Polling is common for chords like Ctrl+Space, where RegisterHotKey modifier-order rules are awkward, so "the shortcut fires but nothing happens" was the norm for those apps. CustomShortcut now presses, holds 120 ms (a real keypress is ~100 ms), then releases in reverse — on its own thread, so the hook callback never stalls. Injected keys also carry their hardware scan code (MapVirtualKeyW), which low-level hooks read as KBDLLHOOKSTRUCT::scanCode and some listeners reject when zero. An ignored test asserts a 15 ms poller observes the chord. Also declares Win32_UI_WindowsAndMessaging, which windows-sys requires for ShellExecuteW: the workspace build only linked because another crate enabled it, so `cargo test -p openlogi-inject` alone did not compile. --- crates/openlogi-inject/Cargo.toml | 8 +- crates/openlogi-inject/src/inject/windows.rs | 117 +++++++++++++++++-- 2 files changed, 113 insertions(+), 12 deletions(-) diff --git a/crates/openlogi-inject/Cargo.toml b/crates/openlogi-inject/Cargo.toml index 14f96e96..a82d1f80 100644 --- a/crates/openlogi-inject/Cargo.toml +++ b/crates/openlogi-inject/Cargo.toml @@ -37,10 +37,14 @@ objc2-app-kit = { workspace = true, features = [ objc2-core-graphics = { version = "0.3.2", features = ["CGEvent"] } objc2-foundation = { workspace = true } -# SendInput-based action synthesis (execute_windows); Win32_UI_Shell for -# ShellExecuteW, which backs the `Run` action. +# SendInput-based action synthesis (execute_windows). `Run` calls +# ShellExecuteW, which windows-sys gates on Win32_UI_WindowsAndMessaging +# (its SHOW_WINDOW_CMD parameter) as well as Win32_UI_Shell — both are +# required here even though a workspace build would unify them in from +# another crate. [target.'cfg(target_os = "windows")'.dependencies] windows-sys = { workspace = true, features = [ "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", ] } diff --git a/crates/openlogi-inject/src/inject/windows.rs b/crates/openlogi-inject/src/inject/windows.rs index bbb23dcf..bae0f1b8 100644 --- a/crates/openlogi-inject/src/inject/windows.rs +++ b/crates/openlogi-inject/src/inject/windows.rs @@ -5,16 +5,30 @@ use std::mem::size_of; use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ INPUT, INPUT_0, INPUT_KEYBOARD, INPUT_MOUSE, KEYBDINPUT, KEYEVENTF_KEYUP, KEYEVENTF_UNICODE, - MOUSEEVENTF_HWHEEL, MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEDOWN, - MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, MOUSEEVENTF_WHEEL, - MOUSEEVENTF_XDOWN, MOUSEEVENTF_XUP, MOUSEINPUT, SendInput, + MAPVK_VK_TO_VSC, MOUSEEVENTF_HWHEEL, MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, + MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, + MOUSEEVENTF_WHEEL, MOUSEEVENTF_XDOWN, MOUSEEVENTF_XUP, MOUSEINPUT, MapVirtualKeyW, SendInput, }; use windows_sys::Win32::UI::Shell::ShellExecuteW; +use windows_sys::Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL; use openlogi_core::binding::{Action, KeyCombo, split_run_target}; const WHEEL_DELTA: i32 = 120; +/// How long a [`Action::CustomShortcut`] holds its keys down. +/// +/// A whole chord delivered in one `SendInput` batch is physically down for +/// microseconds. That is invisible to any hotkey listener that **polls** +/// key state (`GetAsyncKeyState`) instead of handling key messages — the +/// press falls between two samples and the app never fires. Polling is +/// common precisely for chords like Ctrl+Space, where `RegisterHotKey`'s +/// modifier-order rules are awkward. A real keypress lasts ~100 ms, so the +/// synthetic one dwells too: long enough for any sane poll interval (a +/// 60 fps frame loop is 16 ms) to sample it, short enough to feel instant +/// and never auto-repeat. +const CHORD_HOLD: std::time::Duration = std::time::Duration::from_millis(120); + const VK_A: u16 = 0x41; const VK_C: u16 = 0x43; const VK_D: u16 = 0x44; @@ -137,11 +151,6 @@ pub(super) fn execute(action: &Action) { } } -// SW_SHOWNORMAL from WinUser.h — windows-sys puts it behind the -// Win32_UI_WindowsAndMessaging feature; not worth enabling for one integer -// (same treatment as the VK_* codes above). -const SW_SHOWNORMAL: i32 = 1; - /// Open a [`Action::Run`] target through the shell: URLs launch the default /// browser, documents open their association, executables run with the `||` /// argument string. `%VAR%` environment references expand in both halves. @@ -345,7 +354,33 @@ fn post_custom_shortcut(combo: &KeyCombo) { if combo.modifiers & KeyCombo::MOD_WIN != 0 { modifiers.push(VK_LWIN); } - post_key(vk, &modifiers); + post_held_chord(vk, modifiers); +} + +/// Press `modifiers` + `vk`, hold for [`CHORD_HOLD`], then release in +/// reverse order — the shape of a real keypress rather than an +/// instantaneous blip (see [`CHORD_HOLD`]). +/// +/// Runs on its own thread: the caller is a hook callback or an IPC handler +/// that must not stall for the hold. +fn post_held_chord(vk: u16, modifiers: Vec) { + std::thread::spawn(move || { + let mut press = Vec::with_capacity(modifiers.len() + 1); + for modifier in &modifiers { + press.push(key_input(*modifier, false)); + } + press.push(key_input(vk, false)); + send_inputs(&press); + + std::thread::sleep(CHORD_HOLD); + + let mut release = Vec::with_capacity(modifiers.len() + 1); + release.push(key_input(vk, true)); + for modifier in modifiers.iter().rev() { + release.push(key_input(*modifier, true)); + } + send_inputs(&release); + }); } fn send_inputs(inputs: &[INPUT]) { @@ -376,12 +411,19 @@ fn key_input(vk: u16, key_up: bool) -> INPUT { if key_up { flags |= KEYEVENTF_KEYUP; } + // Carry the hardware scan code alongside the virtual key. Windows + // forwards it to low-level hooks as `KBDLLHOOKSTRUCT::scanCode`, and + // listeners that read that field (rather than the virtual key) ignore + // events where it is zero. + // SAFETY: MapVirtualKeyW is a pure lookup over the active layout; it + // takes no pointers and returns 0 for codes with no mapping. + let scan = unsafe { MapVirtualKeyW(u32::from(vk), MAPVK_VK_TO_VSC) }; INPUT { r#type: INPUT_KEYBOARD, Anonymous: INPUT_0 { ki: KEYBDINPUT { wVk: vk, - wScan: 0, + wScan: u16::try_from(scan).unwrap_or(0), dwFlags: flags, time: 0, dwExtraInfo: 0, @@ -410,6 +452,61 @@ fn mouse_input(flags: u32, data: i32) -> INPUT { mod tests { use super::expand_env_with; + /// The [`CHORD_HOLD`] contract, tested against the mechanism it exists + /// for: a listener that samples physical key state on a timer (the + /// common shape for global chords) must observe the synthetic chord + /// down. Before the hold, the whole chord went out in one `SendInput` + /// batch and every such listener missed it. + /// + /// Ignored by default: it needs an interactive desktop session (CI has + /// none, and `SendInput` is a no-op in session 0) and it briefly + /// presses real keys. Run with: + /// `cargo test -p openlogi-inject -- --ignored held_chord` + #[test] + #[ignore = "injects real key events; needs an interactive desktop"] + fn held_chord_is_visible_to_a_polling_listener() { + use openlogi_core::binding::{Action, KeyCombo}; + use std::sync::Arc; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::time::{Duration, Instant}; + use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetAsyncKeyState; + + // kVK 0x69 → VK_F13: bound by essentially nothing, so the test + // can't trip a real hotkey on the machine running it. + const VK_CONTROL: i32 = 0x11; + const VK_F13: i32 = 0x7C; + + let seen = Arc::new(AtomicBool::new(false)); + let poller = { + let seen = Arc::clone(&seen); + std::thread::spawn(move || { + let deadline = Instant::now() + Duration::from_millis(600); + while Instant::now() < deadline { + // SAFETY: same pure state read as the production path. + let down = + |vk: i32| unsafe { GetAsyncKeyState(vk) }.cast_unsigned() & 0x8000 != 0; + if down(VK_CONTROL) && down(VK_F13) { + seen.store(true, Ordering::SeqCst); + return; + } + std::thread::sleep(Duration::from_millis(15)); + } + }) + }; + + super::execute(&Action::CustomShortcut(KeyCombo { + modifiers: KeyCombo::MOD_CTRL, + key_code: 0x69, + display: "Ctrl+F13".into(), + })); + + drop(poller.join()); + assert!( + seen.load(Ordering::SeqCst), + "a 15 ms poller never saw the chord down — CHORD_HOLD is too short or the press is instantaneous" + ); + } + #[test] fn expand_env_substitutes_known_and_keeps_unknown_names() { let lookup = |name: &str| match name { From 740e8745fe7cde312422e13581deb5b53394c54b Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Fri, 24 Jul 2026 18:09:28 -0700 Subject: [PATCH 37/42] test(gui): make the chord recorder deterministic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The recorder state machine was tested through the real keyboard, so a test run failed whenever a key happened to be held. The sample is now injected; the tests cover the whole gesture — bare modifier, growing chord, release, Escape-cancel — instead of one hardware-dependent case. --- crates/openlogi-gui/src/chord_recorder.rs | 80 +++++++++++++++++++---- 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/crates/openlogi-gui/src/chord_recorder.rs b/crates/openlogi-gui/src/chord_recorder.rs index e0549a80..55558837 100644 --- a/crates/openlogi-gui/src/chord_recorder.rs +++ b/crates/openlogi-gui/src/chord_recorder.rs @@ -36,7 +36,14 @@ pub struct ChordRecorder { impl ChordRecorder { /// Sample the keyboard once. Call on a ~30 ms timer while listening. pub fn poll(&mut self) -> Poll { - let (modifiers, key) = platform::sample(); + self.advance(platform::sample()) + } + + /// [`Self::poll`] with the keyboard reading supplied, so the state + /// machine is testable without depending on what is physically held + /// while the suite runs. + fn advance(&mut self, sample: (Vec<&'static str>, Option<&'static str>)) -> Poll { + let (modifiers, key) = sample; // Escape with no modifiers is the cancel gesture, never a binding — // matching the ring overlay, where Esc means "back out". if modifiers.is_empty() && key == Some("Escape") { @@ -181,24 +188,71 @@ pub const fn is_supported() -> bool { mod tests { use super::{ChordRecorder, Poll}; - /// The state machine's contract, exercised without touching the - /// keyboard: nothing held finalizes nothing, and a finished chord is - /// emitted exactly once. + /// Nothing held: no chord is ever invented, however long we wait. #[test] fn idle_polls_never_finalize_a_chord() { let mut recorder = ChordRecorder::default(); - // On a platform with no capture support `sample` reports nothing - // held, which must stay Idle rather than emitting an empty chord. - assert_eq!(recorder.poll(), Poll::Idle); - assert_eq!(recorder.poll(), Poll::Idle); + assert_eq!(recorder.advance((vec![], None)), Poll::Idle); + assert_eq!(recorder.advance((vec![], None)), Poll::Idle); } + /// The full gesture: modifiers first (not yet a chord), then the key, + /// then release — emitting the chord exactly once. #[test] fn a_held_chord_is_emitted_once_on_release() { - let mut recorder = ChordRecorder { - held: Some("Ctrl+Shift+P".into()), - }; - assert_eq!(recorder.poll(), Poll::Done("Ctrl+Shift+P".into())); - assert_eq!(recorder.poll(), Poll::Idle, "not re-emitted"); + let mut recorder = ChordRecorder::default(); + assert_eq!( + recorder.advance((vec!["Ctrl"], None)), + Poll::Idle, + "a bare modifier is not a chord" + ); + assert_eq!( + recorder.advance((vec!["Ctrl", "Shift"], Some("P"))), + Poll::Holding("Ctrl+Shift+P".into()) + ); + assert_eq!( + recorder.advance((vec!["Ctrl"], None)), + Poll::Idle, + "still coming up — the chord is not final until everything is up" + ); + assert_eq!( + recorder.advance((vec![], None)), + Poll::Done("Ctrl+Shift+P".into()) + ); + assert_eq!( + recorder.advance((vec![], None)), + Poll::Idle, + "not re-emitted" + ); + } + + /// A chord that grows keeps its most complete form. + #[test] + fn the_last_held_chord_wins() { + let mut recorder = ChordRecorder::default(); + let _ = recorder.advance((vec!["Ctrl"], Some("Space"))); + let _ = recorder.advance((vec!["Win", "Ctrl"], Some("Space"))); + assert_eq!( + recorder.advance((vec![], None)), + Poll::Done("Win+Ctrl+Space".into()) + ); + } + + /// Escape alone backs out and discards anything held. + #[test] + fn escape_cancels_without_emitting() { + let mut recorder = ChordRecorder::default(); + let _ = recorder.advance((vec!["Ctrl"], Some("P"))); + assert_eq!(recorder.advance((vec![], Some("Escape"))), Poll::Cancelled); + assert_eq!( + recorder.advance((vec![], None)), + Poll::Idle, + "the cancelled chord is gone, not pending" + ); + // Escape *with* a modifier is a real binding, not a cancel. + assert_eq!( + recorder.advance((vec!["Ctrl"], Some("Escape"))), + Poll::Holding("Ctrl+Escape".into()) + ); } } From 1626ff285f10496896ebebd7fb64235fe36e0867 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Sat, 25 Jul 2026 15:19:19 -0700 Subject: [PATCH 38/42] fix(core): stop an older build from wiping a newer config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two holes let a downgrade silently destroy user settings, as happened on a machine running both an installed 0.6.21 and a newer build: Schema. The ring bindings (ActionRing/Binding::Ring, Run, PasteText, Folder, Named) were added without bumping SCHEMA_VERSION, so a v3 reader accepted the file, failed to parse the unknown actions, and fell back to defaults — defeating the version gate that exists precisely to stop that. SCHEMA_VERSION is now 4. Persistence. Both binaries fell back to `Config::default()` on ANY load error and then saved it, replacing the file they could not read. Loading failures now yield `Config::defaults_for_unreadable()`, which refuses to save, so an unreadable config is left intact for a build that understands it. Tests pin both paths. --- crates/openlogi-agent/src/main.rs | 8 +- crates/openlogi-core/src/config.rs | 113 ++++++++++++++++++++++++++++- crates/openlogi-gui/src/main.rs | 21 +++++- 3 files changed, 133 insertions(+), 9 deletions(-) diff --git a/crates/openlogi-agent/src/main.rs b/crates/openlogi-agent/src/main.rs index 91d7666d..42256008 100644 --- a/crates/openlogi-agent/src/main.rs +++ b/crates/openlogi-agent/src/main.rs @@ -77,8 +77,12 @@ fn main() { self_restart::spawn(); let config = Config::load_or_default().unwrap_or_else(|e| { - warn!(error = %e, "could not load config.toml; using defaults"); - Config::default() + warn!( + error = %e, + "could not load config.toml; running on defaults WITHOUT persisting, \ + so the unreadable file is left intact" + ); + Config::defaults_for_unreadable() }); let runtime = match tokio::runtime::Builder::new_multi_thread() diff --git a/crates/openlogi-core/src/config.rs b/crates/openlogi-core/src/config.rs index d4763392..4f75b58f 100644 --- a/crates/openlogi-core/src/config.rs +++ b/crates/openlogi-core/src/config.rs @@ -34,6 +34,13 @@ use crate::paths::{self, PathsError}; /// changes; readers branch on the parsed value before consuming the rest of /// the file. /// +/// v4 adds bindings a v3 reader cannot represent: `ButtonId::ActionRing` with +/// its `Binding::Ring` slot map, and the `Run` / `PasteText` / `Folder` / +/// `Named` actions. A v3 build parsing them fails and falls back to defaults, +/// which the next save then writes over the user's real config — the exact +/// silent wipe the version gate exists to prevent, so writing any of them +/// must declare v4. +/// /// v3 changes the device map from model keys to physical-device keys. No v2 /// device entries are migrated because model-scoped settings cannot be assigned /// safely when two identical devices exist. @@ -43,7 +50,7 @@ use crate::paths::{self, PathsError}; /// `RawDeviceConfig` shim folds the legacy fields) and self-heals to v2 on the /// next save; [`Config::load_from_path`] rejects only versions *newer* than this /// so a forward file fails loudly instead of silently losing bindings. -pub const SCHEMA_VERSION: u32 = 3; +pub const SCHEMA_VERSION: u32 = 4; /// Top-level config document. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -65,6 +72,12 @@ pub struct Config { /// an entry. #[serde(default)] pub devices: BTreeMap, + /// Set when this doc is a stand-in for a config file that exists but + /// could not be read (see [`Self::defaults_for_unreadable`]). Saving is + /// then refused, because writing defaults over a file we failed to parse + /// destroys the user's real settings. + #[serde(skip)] + unreadable_source: bool, } impl Default for Config { @@ -74,6 +87,7 @@ impl Default for Config { app_settings: AppSettings::default(), selected_device: None, devices: BTreeMap::new(), + unreadable_source: false, } } } @@ -141,6 +155,31 @@ impl Config { Self::load_from_path(&paths::config_path()?) } + /// Defaults to run on after [`Self::load_or_default`] failed — a config + /// file exists but is unparseable, or was written by a newer build. + /// + /// Callers must use this rather than [`Self::default`], because the + /// resulting doc **refuses to save**. Persisting plain defaults over a + /// file we could not read is what turns "this build is too old for your + /// config" into "your bindings are gone": the failing build starts up + /// blank, and its first write — any setting toggle, any device + /// re-detection — replaces the user's real settings with that blank + /// state. + #[must_use] + pub fn defaults_for_unreadable() -> Self { + Self { + unreadable_source: true, + ..Self::default() + } + } + + /// Whether this doc stands in for an unreadable file and so will not be + /// persisted (see [`Self::defaults_for_unreadable`]). + #[must_use] + pub const fn is_read_only(&self) -> bool { + self.unreadable_source + } + /// Same as [`Self::load_or_default`] but reads from `path`. Used by tests /// to avoid touching the real user config. pub fn load_from_path(path: &Path) -> Result { @@ -185,6 +224,14 @@ impl Config { /// Same as [`Self::save_atomic`] but writes to `path`. Used by tests. pub fn save_to_path(&self, path: &Path) -> Result<(), ConfigError> { + // Never let a build that couldn't read the config write over it — + // that is how an unreadable file becomes a lost one (see + // [`Self::defaults_for_unreadable`]). Not an error: the caller's save + // is genuinely unnecessary, and failing it would only surface as a + // second, more confusing symptom. + if self.unreadable_source { + return Ok(()); + } if let Some(parent) = path.parent() { fs::create_dir_all(parent).map_err(|source| ConfigError::Write { path: path.to_path_buf(), @@ -696,6 +743,60 @@ mod tests { assert!(cfg.devices.is_empty()); } + /// The data-loss guard: a build that cannot read the config must never + /// overwrite it. This is the exact sequence that wiped a real user's + /// bindings — an older build parsed a config carrying actions it did not + /// know, fell back to defaults, and saved those defaults back. + #[test] + fn defaults_for_an_unreadable_config_never_overwrite_it() { + let dir = tempfile::tempdir().expect("tempdir"); + let path = dir.path().join("config.toml"); + let original = "schema_version = 4\nthis is not valid toml at all\n"; + fs::write(&path, original).expect("seed"); + + let err = Config::load_from_path(&path).expect_err("must not parse"); + assert!(matches!(err, ConfigError::Parse { .. })); + + // The fallback a binary uses on that error, then a save it would + // normally perform (a settings toggle, a device re-detection). + let fallback = Config::defaults_for_unreadable(); + assert!(fallback.is_read_only()); + fallback + .save_to_path(&path) + .expect("save is a no-op, not an error"); + + assert_eq!( + fs::read_to_string(&path).expect("read back"), + original, + "the unreadable config must be left exactly as it was" + ); + + // A normally-loaded config still saves. + let ok = Config::default(); + assert!(!ok.is_read_only()); + ok.save_to_path(&path).expect("save"); + assert_ne!(fs::read_to_string(&path).expect("read back"), original); + } + + /// A newer config must be refused rather than silently reset — the + /// version gate only works if writers bump [`SCHEMA_VERSION`] whenever + /// they emit bindings older readers cannot represent. + #[test] + fn a_newer_schema_is_rejected_and_left_untouched() { + let dir = tempfile::tempdir().expect("tempdir"); + let path = dir.path().join("config.toml"); + let original = format!("schema_version = {}\n", SCHEMA_VERSION + 1); + fs::write(&path, &original).expect("seed"); + + let err = Config::load_from_path(&path).expect_err("must be refused"); + assert!(matches!(err, ConfigError::UnsupportedSchemaVersion { .. })); + + Config::defaults_for_unreadable() + .save_to_path(&path) + .expect("no-op"); + assert_eq!(fs::read_to_string(&path).expect("read back"), original); + } + #[test] fn folder_conversion_keeps_the_old_action_and_edits_nest() { use crate::binding::{Action, RingSlot}; @@ -1006,7 +1107,10 @@ mod tests { // The key only contains [A-Za-z0-9_], so TOML emits it as a bare-word // table key (no surrounding quotes). The test asserts the observable // structure rather than locking in a specific quoting. - assert!(body.contains("schema_version = 3"), "got: {body}"); + assert!( + body.contains(&format!("schema_version = {SCHEMA_VERSION}")), + "got: {body}" + ); assert!(body.contains("[devices.2b042.bindings]"), "got: {body}"); // A `Single` binding serializes byte-identically to the pre-v2 bare // `Action`, so the leaf line is unchanged. @@ -1271,7 +1375,10 @@ Click = \"Paste\" // Saving self-heals to the current shape: stamped version + merged table, // legacy field names gone. let body = toml::to_string_pretty(&cfg).expect("serialize"); - assert!(body.contains("schema_version = 3"), "got: {body}"); + assert!( + body.contains(&format!("schema_version = {SCHEMA_VERSION}")), + "got: {body}" + ); assert!(body.contains("[devices.2b042.bindings]"), "got: {body}"); assert!(!body.contains("button_bindings"), "got: {body}"); assert!(!body.contains("gesture_bindings"), "got: {body}"); diff --git a/crates/openlogi-gui/src/main.rs b/crates/openlogi-gui/src/main.rs index 25898ea7..f5e730cf 100644 --- a/crates/openlogi-gui/src/main.rs +++ b/crates/openlogi-gui/src/main.rs @@ -143,8 +143,12 @@ fn main() -> Result<()> { let inventories: Vec = Vec::new(); let initial_config = Config::load_or_default().unwrap_or_else(|e| { - warn!(error = %e, "could not load config.toml; using defaults"); - Config::default() + warn!( + error = %e, + "could not load config.toml; running on defaults WITHOUT persisting, \ + so the unreadable file is left intact" + ); + Config::defaults_for_unreadable() }); // Resolve the UI locale before any menu or window is built so the first @@ -504,8 +508,17 @@ fn main() -> Result<()> { }); } Some(press) = ipc_ring.recv() => { - tracing::debug!(seq = press.seq, "action ring press received"); - cx.update(ring::on_pad_press); + tracing::debug!( + seq = press.seq, + device = press.device_key, + "action ring press received" + ); + // An empty key means the agent had no active device; + // fall back to the carousel selection rather than + // rendering nothing. + let device_key = (!press.device_key.is_empty()) + .then_some(press.device_key); + cx.update(|cx| ring::on_pad_press(device_key, cx)); } Some(cmd) = gui_cmd_rx.recv() => { cx.update(|cx| dispatch_gui_command(cmd, cx)); From ce0e5df421f29ac517cc9f394ecb21ce217bda6d Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Sat, 25 Jul 2026 15:19:19 -0700 Subject: [PATCH 39/42] fix(gui): render the ring of the device whose pad fired MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The overlay resolved slots via the carousel selection. With another device selected — trivially common, since the GUI starts before enumeration and lands on whichever device sorts first — it asked a keyboard for ring slots, got the canonical defaults, and drew a ring the user never configured: a phantom "old version" that appeared and vanished as selection changed. RingPress now carries the armed device''s config key (IPC v15) and the overlay resolves against it via ring_slots_for_device. The selection itself was also lost on every launch: the first inventory arrives after startup, so `with_runtime` has an empty list to match `selected_device` against, and the later refresh only preserved a live selection. It now falls back to the persisted key before "first device". That is not cosmetic — the agent aims control capture at the selected device, so landing on the keyboard left the mouse entirely uncaptured (no Action Ring, no diverted buttons). --- crates/openlogi-agent-core/src/ipc.rs | 15 ++- .../openlogi-agent-core/src/orchestrator.rs | 7 +- crates/openlogi-agent-core/src/ring.rs | 87 +++++++++++--- .../openlogi-agent-core/tests/wire_format.rs | 21 +++- crates/openlogi-gui/src/ring.rs | 23 +++- crates/openlogi-gui/src/state.rs | 108 +++++++++++++++++- 6 files changed, 228 insertions(+), 33 deletions(-) diff --git a/crates/openlogi-agent-core/src/ipc.rs b/crates/openlogi-agent-core/src/ipc.rs index b277b26d..f561b14c 100644 --- a/crates/openlogi-agent-core/src/ipc.rs +++ b/crates/openlogi-agent-core/src/ipc.rs @@ -34,7 +34,9 @@ use serde::{Deserialize, Serialize}; /// inside `execute_action` and config snapshots). /// v13: [`Action`] gains the appended `Folder` variant (ring sub-menus). /// v14: [`Action`] gains the appended `Named` variant (user action labels). -pub const PROTOCOL_VERSION: u32 = 14; +/// v15: [`RingPress`] gains the appended `device_key` field, so the overlay +/// resolves slots against the pad's own device. +pub const PROTOCOL_VERSION: u32 = 15; /// One Action Ring pad press, streamed to the GUI via /// [`Agent::next_ring_press`] so the on-screen ring opens (or confirms a @@ -42,12 +44,21 @@ pub const PROTOCOL_VERSION: u32 = 14; /// /// bincode encodes struct fields positionally — fields are append-only, like /// the [`Agent`] trait methods. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RingPress { /// Monotonic press counter within one agent run. Lets the GUI drop /// stale presses that queued while it had no poll outstanding (e.g. /// while reconnecting) instead of replaying a burst. pub seq: u64, + /// Config key of the device whose pad fired, so the overlay renders that + /// device's ring. + /// + /// Without it the GUI would resolve slots against its carousel selection, + /// which is a different device whenever the pad's mouse isn't the one on + /// screen — and a device with no ring binding falls back to the canonical + /// default ring, i.e. a ring the user never configured. Empty when the + /// agent has no active device key. + pub device_key: String, } /// Where the agent's device enumeration stands. The distinction matters diff --git a/crates/openlogi-agent-core/src/orchestrator.rs b/crates/openlogi-agent-core/src/orchestrator.rs index 9f09eaa7..25757314 100644 --- a/crates/openlogi-agent-core/src/orchestrator.rs +++ b/crates/openlogi-agent-core/src/orchestrator.rs @@ -189,11 +189,10 @@ impl Orchestrator { self.config.app_settings.thumbwheel_sensitivity, Ordering::Relaxed, ); - self.shared.ring.set_armed(ring_armed_for( - &self.config, + self.shared.ring.set_armed( + ring_armed_for(&self.config, key, self.current_app.as_deref()), key, - self.current_app.as_deref(), - )); + ); } /// Apply a fresh inventory snapshot. Always refreshes the snapshot the IPC diff --git a/crates/openlogi-agent-core/src/ring.rs b/crates/openlogi-agent-core/src/ring.rs index 60a04927..e9fb0bd4 100644 --- a/crates/openlogi-agent-core/src/ring.rs +++ b/crates/openlogi-agent-core/src/ring.rs @@ -32,6 +32,14 @@ struct RingChannelState { /// gesture watcher routes a pad press here only while set, and to the /// ordinary single-action dispatch otherwise. armed: bool, + /// Config key of the device whose pad is armed, published alongside + /// [`Self::armed`] and stamped onto every press. + /// + /// The GUI must resolve a press against *this* device, not whichever one + /// its carousel happens to show: the ring belongs to the mouse with the + /// pad, and asking a keyboard for ring slots yields the canonical + /// defaults — a second, phantom ring the user never configured. + device_key: Option, /// Monotonic press counter for [`RingPress::seq`]. seq: u64, pending: VecDeque, @@ -51,9 +59,12 @@ impl RingChannel { self.state.lock().unwrap_or_else(PoisonError::into_inner) } - /// Publish whether the pad's effective binding opens the ring. - pub fn set_armed(&self, armed: bool) { - self.lock().armed = armed; + /// Publish whether the pad's effective binding opens the ring, and which + /// device that binding belongs to. + pub fn set_armed(&self, armed: bool, device_key: Option<&str>) { + let mut st = self.lock(); + st.armed = armed; + st.device_key = device_key.map(str::to_owned); } /// Whether a pad press should be routed to the ring overlay. @@ -69,10 +80,11 @@ impl RingChannel { let mut st = self.lock(); st.seq += 1; let seq = st.seq; + let device_key = st.device_key.clone().unwrap_or_default(); if st.pending.len() == QUEUE_CAP { st.pending.pop_front(); } - st.pending.push_back(RingPress { seq }); + st.pending.push_back(RingPress { seq, device_key }); } // notify_one stores a permit when no poll is waiting, so a press that // lands between a poll's queue check and its await is never lost. @@ -102,30 +114,75 @@ impl RingChannel { mod tests { use super::*; + /// A press carrying `seq`, from the device the tests arm below. + fn press(seq: u64) -> RingPress { + RingPress { + seq, + device_key: "mouse".into(), + } + } + + /// An armed channel, as the orchestrator publishes it. + fn armed() -> RingChannel { + let chan = RingChannel::default(); + chan.set_armed(true, Some("mouse")); + chan + } + #[tokio::test] async fn queued_press_answers_immediately() { - let chan = RingChannel::default(); + let chan = armed(); chan.push_press(); - assert_eq!(chan.next_press().await, Some(RingPress { seq: 1 })); + assert_eq!(chan.next_press().await, Some(press(1))); } #[tokio::test] async fn presses_deliver_in_order_with_monotonic_seq() { - let chan = RingChannel::default(); + let chan = armed(); chan.push_press(); chan.push_press(); - assert_eq!(chan.next_press().await, Some(RingPress { seq: 1 })); - assert_eq!(chan.next_press().await, Some(RingPress { seq: 2 })); + assert_eq!(chan.next_press().await, Some(press(1))); + assert_eq!(chan.next_press().await, Some(press(2))); } #[tokio::test] async fn overflow_drops_the_oldest_press_not_the_newest() { - let chan = RingChannel::default(); + let chan = armed(); for _ in 0..QUEUE_CAP + 2 { chan.push_press(); } // Seqs 1 and 2 were evicted; the survivors are the most recent CAP. - assert_eq!(chan.next_press().await, Some(RingPress { seq: 3 })); + assert_eq!(chan.next_press().await, Some(press(3))); + } + + /// Every press names the device whose pad fired, so the overlay renders + /// that device's ring instead of falling back to defaults for whatever + /// the GUI's carousel happens to show. + #[tokio::test] + async fn a_press_carries_the_armed_device_key() { + let chan = RingChannel::default(); + chan.set_armed(true, Some("receiver:abc:slot:2")); + chan.push_press(); + assert_eq!( + chan.next_press().await.map(|p| p.device_key), + Some("receiver:abc:slot:2".to_string()) + ); + + // Re-arming for another device restamps subsequent presses. + chan.set_armed(true, Some("receiver:abc:slot:3")); + chan.push_press(); + assert_eq!( + chan.next_press().await.map(|p| p.device_key), + Some("receiver:abc:slot:3".to_string()) + ); + + // No active device: an empty key, which the GUI treats as "unknown". + chan.set_armed(true, None); + chan.push_press(); + assert_eq!( + chan.next_press().await.map(|p| p.device_key), + Some(String::new()) + ); } #[tokio::test(start_paused = true)] @@ -137,7 +194,7 @@ mod tests { #[tokio::test(start_paused = true)] async fn press_during_a_held_poll_wakes_it() { - let chan = RingChannel::default(); + let chan = armed(); let waiter = tokio::spawn({ let chan = chan.clone(); async move { chan.next_press().await } @@ -145,16 +202,16 @@ mod tests { // Let the poll reach its await before pushing. tokio::task::yield_now().await; chan.push_press(); - assert_eq!(waiter.await.expect("join"), Some(RingPress { seq: 1 })); + assert_eq!(waiter.await.expect("join"), Some(press(1))); } #[tokio::test] async fn armed_flag_round_trips() { let chan = RingChannel::default(); assert!(!chan.is_armed(), "unarmed until the orchestrator publishes"); - chan.set_armed(true); + chan.set_armed(true, Some("mouse")); assert!(chan.is_armed()); - chan.set_armed(false); + chan.set_armed(false, None); assert!(!chan.is_armed()); } } diff --git a/crates/openlogi-agent-core/tests/wire_format.rs b/crates/openlogi-agent-core/tests/wire_format.rs index f65eb6e8..5291b00f 100644 --- a/crates/openlogi-agent-core/tests/wire_format.rs +++ b/crates/openlogi-agent-core/tests/wire_format.rs @@ -62,7 +62,7 @@ fn assert_wire(value: &T, golden: &str) { /// that makes that visible in the same diff. #[test] fn protocol_version_is_pinned() { - assert_eq!(PROTOCOL_VERSION, 14); + assert_eq!(PROTOCOL_VERSION, 15); } /// tarpc encodes the request enum's variant index, so trait *method order* is @@ -127,9 +127,22 @@ fn request_variant_order() { #[test] fn ring_press() { - // Varint seq: single byte below 251, 0xfb + u16 LE above. - assert_wire(&RingPress { seq: 3 }, "03"); - assert_wire(&RingPress { seq: 300 }, "fb2c01"); + // Varint seq: single byte below 251, 0xfb + u16 LE above; then the + // appended device key as a length-prefixed string. + assert_wire( + &RingPress { + seq: 3, + device_key: String::new(), + }, + "0300", + ); + assert_wire( + &RingPress { + seq: 300, + device_key: "ab".into(), + }, + "fb2c01026162", + ); } #[test] diff --git a/crates/openlogi-gui/src/ring.rs b/crates/openlogi-gui/src/ring.rs index 75a01e3c..6ee321c3 100644 --- a/crates/openlogi-gui/src/ring.rs +++ b/crates/openlogi-gui/src/ring.rs @@ -114,6 +114,10 @@ pub struct RingOverlay { scale: f32, /// Generation counter; bumping it retires the previous open's watcher. epoch: u64, + /// Config key of the device whose pad opened this ring, carried on the + /// press. Slot lookups use it rather than the carousel selection — see + /// [`AppState::ring_slots_for_device`]. + device_key: Option, } impl gpui::Global for RingOverlay {} @@ -128,6 +132,7 @@ pub fn init(commands: mpsc::UnboundedSender, cx: &mut App) { center: point(0., 0.), scale: 1., epoch: 0, + device_key: None, }); // Create the hidden overlay window eagerly rather than on the first tap: // it removes window-construction latency from the first open, and — since @@ -144,14 +149,26 @@ pub fn init(commands: mpsc::UnboundedSender, cx: &mut App) { /// Handle one Action Ring pad press from the agent: open the ring when it is /// closed, confirm the aimed selection when it is open. -pub fn on_pad_press(cx: &mut App) { +pub fn on_pad_press(device_key: Option, cx: &mut App) { if cx.global::().open { confirm(cx); } else { + // The press names its own device; remember it for this open so the + // slots — and every re-read while the ring is up — come from the pad's + // mouse rather than whatever the carousel shows. + cx.global_mut::().device_key = device_key; open(cx); } } +/// The slots to render: always the pad's own device (carried on the press), +/// never the carousel selection. +fn ring_slots(cx: &App) -> Vec<(RingSlot, Action)> { + let key = cx.global::().device_key.clone(); + cx.global::() + .ring_slots_for_device(key.as_deref()) +} + /// Open the ring centred at the cursor. fn open(cx: &mut App) { let Some(cursor) = platform_win::cursor_pos() else { @@ -161,7 +178,7 @@ fn open(cx: &mut App) { return; }; - let slots = cx.global::().ring_slots_for_current(); + let slots = ring_slots(cx); let window = match cx.global::().window { Some(window) => { let refreshed = window.update(cx, |view, _, cx| { @@ -349,7 +366,7 @@ fn view_in_folder(cx: &mut App) -> bool { /// Swap an open folder back to the main ring. fn back_to_main(cx: &mut App) { - let slots = cx.global::().ring_slots_for_current(); + let slots = ring_slots(cx); if let Some(window) = cx.global::().window { let _ = window.update(cx, |view, _, cx| { view.slots.clone_from(&slots); diff --git a/crates/openlogi-gui/src/state.rs b/crates/openlogi-gui/src/state.rs index ff00aa60..28b3914a 100644 --- a/crates/openlogi-gui/src/state.rs +++ b/crates/openlogi-gui/src/state.rs @@ -388,9 +388,23 @@ impl AppState { } let previous_key = self.current_record().map(|r| r.config_key.clone()); + // Keep the live selection; if it is gone — or there is none yet — + // restore the persisted one before falling back to "first device". + // + // The GUI launches before enumeration finishes, so its first device + // list is empty and `with_runtime` has nothing to match + // `selected_device` against. Without this second chance the saved + // choice is silently lost the moment devices actually arrive, and the + // carousel lands on whichever device sorts first — for a mouse+keyboard + // pair, the keyboard. That is not cosmetic: the agent points its + // control-capture session at the selected device, so a mouse demoted + // this way stops having its buttons captured at all. + let saved_key = self.config.selected_device().map(str::to_owned); + let find = |key: &str| merged_list.iter().position(|r| r.config_key == key); let new_index = previous_key .as_deref() - .and_then(|k| merged_list.iter().position(|r| r.config_key == k)) + .and_then(find) + .or_else(|| saved_key.as_deref().and_then(find)) .unwrap_or(0); let connected_keys = merged_list .iter() @@ -1137,11 +1151,26 @@ impl AppState { /// ring-shaped, so this is display data, never dispatch policy. #[must_use] pub fn ring_slots_for_current(&self) -> Vec<(openlogi_core::binding::RingSlot, Action)> { + let key = self.current_record().map(|r| r.config_key.clone()); + self.ring_slots_for_device(key.as_deref()) + } + + /// The Action Ring sector actions stored for `device_key` — what the + /// on-screen ring renders for the pad that fired. + /// + /// The overlay must pass the *pressing* device's key rather than relying + /// on [`Self::ring_slots_for_current`]: the carousel often shows another + /// device (a keyboard, or a second mouse), and since defaults are filled + /// in below, resolving against it would render a ring the user never + /// configured instead of theirs. + #[must_use] + pub fn ring_slots_for_device( + &self, + device_key: Option<&str>, + ) -> Vec<(openlogi_core::binding::RingSlot, Action)> { use openlogi_core::binding::{RingSlot, default_binding_for}; - let stored = self - .current_record() - .map(|r| r.config_key.clone()) - .and_then(|key| self.config.bindings_for(&key).remove(&ButtonId::ActionRing)) + let stored = device_key + .and_then(|key| self.config.bindings_for(key).remove(&ButtonId::ActionRing)) .filter(Binding::is_ring); let mut binding = stored.unwrap_or_else(|| default_binding_for(ButtonId::ActionRing)); binding.fill_ring_defaults(); @@ -1382,6 +1411,75 @@ mod tests { ); } + /// The launch sequence that silently dropped a user's device choice: the + /// GUI starts before enumeration, so its first list is empty and there is + /// nothing to match `selected_device` against; when devices finally + /// arrive, the saved key must still win over "first in the list". + /// + /// This is load-bearing beyond the carousel — the agent aims its + /// control-capture session at the selected device, so landing on the + /// keyboard leaves the mouse's buttons (Action Ring included) uncaptured. + #[test] + fn a_late_arriving_inventory_restores_the_saved_device() { + use openlogi_core::device::{DeviceInventory, PairedDevice, ReceiverInfo}; + + let paired = |slot: u8, name: &str, kind: DeviceKind| PairedDevice { + slot, + codename: Some(name.to_string()), + wpid: Some(u16::from(slot)), + kind, + online: true, + battery: None, + model_info: None, + capabilities: None, + }; + let inventory = DeviceInventory { + receiver: ReceiverInfo { + name: "Logi Bolt Receiver".to_string(), + vendor_id: 0x046d, + product_id: 0xc548, + unique_id: Some("DEADBEEF".to_string()), + }, + // The saved choice is deliberately NOT the first listed. + paired: vec![ + paired(1, "Keyboard", DeviceKind::Keyboard), + paired(2, "Mouse", DeviceKind::Mouse), + ], + }; + let cache = AssetResolver::new(); + let (commands, _receiver) = tokio::sync::mpsc::unbounded_channel(); + + // Config keys are derived from route + slot, so read the real ones + // out of a throwaway state rather than restating the formula here. + let probe = AppState::with_runtime( + Config::default(), + std::slice::from_ref(&inventory), + &cache, + commands.clone(), + ); + let keys: Vec = probe + .device_list + .iter() + .map(|r| r.config_key.clone()) + .collect(); + assert_eq!(keys.len(), 2, "both devices enumerate"); + let wanted = keys[1].clone(); + + let mut config = Config::default(); + config.set_selected_device(Some(wanted.clone())); + // Launch with nothing enumerated yet — the real startup order. + let mut state = AppState::with_runtime(config, &[], &cache, commands); + assert!(state.current_record().is_none(), "no devices at launch"); + + state.refresh_inventories(std::slice::from_ref(&inventory), &cache, false); + + assert_eq!( + state.current_record().map(|r| r.config_key.as_str()), + Some(wanted.as_str()), + "the persisted selection must win over the first-listed device" + ); + } + #[test] fn gui_state_saves_and_clears_supported_wheel_resolution() { let mut config = Config::default(); From 873496c897e66e1d7980687335e976d96020a141 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Sat, 25 Jul 2026 15:29:29 -0700 Subject: [PATCH 40/42] feat(gui): allow shortcuts, commands and snippets on every binding surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CUSTOM section (Run Command / Paste Text / Keyboard Shortcut) existed only in the ring editor, so a gesture direction or a plain button could be bound to the built-in catalog and nothing else. EditTarget gains Gesture and Button variants, and one shared `payload_rows` appends the section to the gesture flyout and the button picker as well — a hold-and-swipe or the DPI toggle can now trigger another app''s global hotkey. --- crates/openlogi-gui/src/mouse_model/picker.rs | 43 +++++++++++++++---- .../src/mouse_model/ring_editor.rs | 18 ++------ .../src/windows/ring_action_editor.rs | 21 +++++++-- 3 files changed, 55 insertions(+), 27 deletions(-) diff --git a/crates/openlogi-gui/src/mouse_model/picker.rs b/crates/openlogi-gui/src/mouse_model/picker.rs index 9f454f72..8e69551d 100644 --- a/crates/openlogi-gui/src/mouse_model/picker.rs +++ b/crates/openlogi-gui/src/mouse_model/picker.rs @@ -71,14 +71,19 @@ pub fn action_picker( let pal = theme::palette(cx); let button = rust_i18n::t!(btn.label()); + let mut rows = action_rows("action-item", current.as_ref(), &on_pick, pal); + // Any button can carry a shortcut / command / snippet, not just ring + // slots — the DPI toggle bound to another app's hotkey, say. + rows.extend(payload_rows( + EditTarget::Button(btn), + current.as_ref().unwrap_or(&Action::None), + pal, + )); menu_card(pal) .min_w(px(POPOVER_W)) .child(title(tr!("Bind %{name}", name => button), pal)) .child(divider(pal)) - .child(scroll_list( - "picker-scroll", - action_rows("action-item", current.as_ref(), &on_pick, pal), - )) + .child(scroll_list("picker-scroll", rows)) .into_any_element() } @@ -108,6 +113,26 @@ pub fn gesture_overview( .into_any_element() } +/// The CUSTOM section every binding menu ends with: the payload actions that +/// carry free text and so open the editor dialog instead of committing +/// straight from the list. Appended to the catalog rows so any binding +/// surface — a ring slot, a gesture direction, a plain button — offers the +/// same three. +pub(crate) fn payload_rows(target: EditTarget, current: &Action, pal: Palette) -> Vec { + let mut rows = vec![section_header(&rust_i18n::t!("CUSTOM"), pal)]; + for (idx, kind) in [ + PayloadKind::Run, + PayloadKind::PasteText, + PayloadKind::Shortcut, + ] + .into_iter() + .enumerate() + { + rows.push(payload_editor_row(target, kind, idx, current, pal)); + } + rows +} + /// A "Run Command…" / "Paste Text…" action-list row. Unlike the catalog rows it /// commits nothing itself — it opens the payload-editor dialog seeded from /// the target's current action. Selection styling still mirrors the catalog @@ -293,14 +318,16 @@ fn flyout_card( view_pick.update(cx, |_, vcx| vcx.notify()); }); + let mut rows = action_rows("gesture-action", Some(¤t), &on_pick, pal); + // Hold + swipe should reach a keyboard shortcut or a command too, not + // only the built-in catalog. + rows.extend(payload_rows(EditTarget::Gesture(dir), ¤t, pal)); + menu_card(pal) .min_w(px(POPOVER_W)) .child(title(format!("{} {}", dir.glyph(), tr!(dir.label())), pal)) .child(divider(pal)) - .child(scroll_list( - "gesture-dir-scroll", - action_rows("gesture-action", Some(¤t), &on_pick, pal), - )) + .child(scroll_list("gesture-dir-scroll", rows)) .into_any_element() } diff --git a/crates/openlogi-gui/src/mouse_model/ring_editor.rs b/crates/openlogi-gui/src/mouse_model/ring_editor.rs index 5d98c2fe..fc27fa7c 100644 --- a/crates/openlogi-gui/src/mouse_model/ring_editor.rs +++ b/crates/openlogi-gui/src/mouse_model/ring_editor.rs @@ -21,13 +21,11 @@ use gpui::{ use gpui_component::{h_flex, v_flex}; use openlogi_core::binding::{Action, RingSlot, default_ring_binding}; -use crate::mouse_model::picker::{ - PickFn, action_icon_path, action_rows, payload_editor_row, section_header, -}; +use crate::mouse_model::picker::{PickFn, action_icon_path, action_rows, payload_rows}; use crate::mouse_model::view::{MouseModelView, localized_action_label}; use crate::state::AppState; use crate::theme::{ACCENT_BLUE, Palette, Typography as _}; -use crate::windows::ring_action_editor::{EditTarget, PayloadKind}; +use crate::windows::ring_action_editor::EditTarget; /// Radius of the circle the editor's slot buttons sit on. const EDIT_RADIUS: f32 = 110.; @@ -417,17 +415,7 @@ fn action_panel( &on_pick, pal, )); - rows.push(section_header(&rust_i18n::t!("CUSTOM"), pal)); - for (idx, kind) in [ - PayloadKind::Run, - PayloadKind::PasteText, - PayloadKind::Shortcut, - ] - .into_iter() - .enumerate() - { - rows.push(payload_editor_row(target, kind, idx, ¤t, pal)); - } + rows.extend(payload_rows(target, ¤t, pal)); if folder_open.is_none() { let view_convert = view.clone(); let is_folder = matches!(current, Action::Folder(_)); diff --git a/crates/openlogi-gui/src/windows/ring_action_editor.rs b/crates/openlogi-gui/src/windows/ring_action_editor.rs index 9ff8745f..65b059b5 100644 --- a/crates/openlogi-gui/src/windows/ring_action_editor.rs +++ b/crates/openlogi-gui/src/windows/ring_action_editor.rs @@ -19,7 +19,7 @@ use gpui_component::{ input::{Input, InputState}, v_flex, }; -use openlogi_core::binding::{Action, KeyCombo, RingSlot}; +use openlogi_core::binding::{Action, ButtonId, GestureDirection, KeyCombo, RingSlot}; use crate::app_menu::{CloseWindow, Minimize, Zoom}; use crate::chord_recorder::{ChordRecorder, Poll}; @@ -47,25 +47,32 @@ pub enum EditTarget { /// The position inside that folder. sub: RingSlot, }, + /// One direction of the gesture button (hold + swipe). + Gesture(GestureDirection), + /// A plain button's single action — the DPI toggle, Middle, Back, … + Button(ButtonId), } impl EditTarget { /// The action currently at this target, resolved against the selected /// device's ring. pub fn current_action(self, state: &AppState) -> Option { - let slots = state.ring_slots_for_current(); match self { - EditTarget::Slot(slot) => slots + EditTarget::Slot(slot) => state + .ring_slots_for_current() .into_iter() .find(|(candidate, _)| *candidate == slot) .map(|(_, action)| action), - EditTarget::FolderSlot { folder, sub } => slots + EditTarget::FolderSlot { folder, sub } => state + .ring_slots_for_current() .into_iter() .find(|(candidate, _)| *candidate == folder) .and_then(|(_, action)| match action { Action::Folder(items) => items.get(&sub).cloned(), _ => None, }), + EditTarget::Gesture(direction) => state.gesture_bindings.get(&direction).cloned(), + EditTarget::Button(button) => state.button_bindings.get(&button).cloned(), } } @@ -77,6 +84,8 @@ impl EditTarget { EditTarget::FolderSlot { folder, sub } => { state.commit_ring_folder_binding(folder, sub, action); } + EditTarget::Gesture(direction) => state.commit_gesture_binding(direction, action), + EditTarget::Button(button) => state.commit_binding(button, action), }); } @@ -92,6 +101,10 @@ impl EditTarget { sub.glyph(), tr!(sub.label()) ), + EditTarget::Gesture(direction) => { + format!("{} {}", direction.glyph(), tr!(direction.label())) + } + EditTarget::Button(button) => tr!(button.label()).to_string(), } } } From 73b86f79adb22b6f5fb882ee6f8f34227026c8a4 Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Tue, 28 Jul 2026 23:49:29 -0700 Subject: [PATCH 41/42] fix(hid): cache Bolt devices that report a zero unit id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pairing register''s unit id is the cache identity for a Bolt slot, and an all-zero id was treated as "unidentifiable — don''t cache, always probe when online". Real firmware reports zeros (an MX Master 4 over Bolt does), so such a device was re-probed on every enumeration tick: a full feature walk, tens of HID++ round-trips, every couple of seconds for as long as the agent ran — sustained radio traffic over the same link the device sends its own input on. Those devices now fall back to a receiver + slot + model key, mirroring the Unifying path. It is deliberately weaker than the unit-id key and scoped so no device can inherit another''s walk: a different model, slot or receiver is a different key, the battery is still re-read every tick, and REFRESH_TICKS still forces the periodic self-healing re-walk. --- crates/openlogi-hid/src/inventory/cache.rs | 17 ++++ crates/openlogi-hid/src/inventory/probe.rs | 106 +++++++++++++++++++-- 2 files changed, 115 insertions(+), 8 deletions(-) diff --git a/crates/openlogi-hid/src/inventory/cache.rs b/crates/openlogi-hid/src/inventory/cache.rs index 8899d502..be867445 100644 --- a/crates/openlogi-hid/src/inventory/cache.rs +++ b/crates/openlogi-hid/src/inventory/cache.rs @@ -21,6 +21,23 @@ pub(super) const REFRESH_TICKS: u64 = 15; pub(super) enum CacheKey { /// Bolt: the unit id from the pairing register (cheap, read every tick). Bolt { unit_id: [u8; 4] }, + /// Bolt fallback for a device whose pairing register reports an all-zero + /// unit id — real firmware does this (an MX Master 4 over Bolt, for one). + /// Without a key such a device is re-probed every tick forever: a full + /// feature walk (tens of HID++ round-trips) every couple of seconds, on + /// the radio link its own input shares, for as long as the agent runs. + /// + /// Keyed on receiver + slot + model rather than the device's own + /// identity, so it is weaker than [`Self::Bolt`]: a *different* model + /// paired into the same slot yields a different key, and the same model + /// re-paired there would have the same feature table anyway. Volatile + /// state (battery) is re-read per tick regardless, and `REFRESH_TICKS` + /// still forces a periodic self-healing re-walk. + BoltSlot { + receiver_uid: String, + slot: u8, + wpid: Option, + }, /// Unifying: keyed on the full receiver serial number + pairing slot. /// Using the complete serial (not just a prefix) avoids collisions between /// two receivers whose serials share a common prefix (e.g. "DA2699E1" and diff --git a/crates/openlogi-hid/src/inventory/probe.rs b/crates/openlogi-hid/src/inventory/probe.rs index 635cc912..f8f15be6 100644 --- a/crates/openlogi-hid/src/inventory/probe.rs +++ b/crates/openlogi-hid/src/inventory/probe.rs @@ -95,8 +95,14 @@ async fn probe_bolt_receiver( // serializing them costs little. let mut identities = Vec::new(); for slot in 1u8..=MAX_BOLT_SLOTS { - if let Some(identity) = - read_bolt_slot_identity(&bolt, &channel, by_slot.get(&slot), slot).await + if let Some(identity) = read_bolt_slot_identity( + &bolt, + &channel, + by_slot.get(&slot), + slot, + unique_id.as_deref(), + ) + .await { identities.push(identity); } @@ -266,6 +272,34 @@ async fn probe_unifying_receiver( } } +/// Cache identity for one Bolt slot. +/// +/// The pairing register gives the device's own unit id cheaply every tick and +/// is the preferred key. Some firmware reports it as all zeros, though (an MX +/// Master 4 over Bolt does), and treating that as "unidentifiable, re-probe +/// every tick" means a full feature walk — tens of HID++ round-trips — every +/// couple of seconds forever, over the same radio link the device sends its +/// input on. Those devices fall back to receiver + slot + model +/// ([`CacheKey::BoltSlot`]). +/// +/// `None` only when the receiver's own id is also unknown; that slot is +/// re-probed every tick, as before. +fn bolt_cache_key( + unit_id: [u8; 4], + receiver_uid: Option<&str>, + slot: u8, + wpid: Option, +) -> Option { + if unit_id != [0u8; 4] { + return Some(CacheKey::Bolt { unit_id }); + } + receiver_uid.map(|uid| CacheKey::BoltSlot { + receiver_uid: uid.to_string(), + slot, + wpid, + }) +} + /// Identity read from the receiver's registers for one occupied Bolt slot /// (phase 1). Both reads address the receiver at index `0xff`, and the channel /// correlates responses by register — not by the slot encoded in the request @@ -290,6 +324,7 @@ async fn read_bolt_slot_identity( channel: &Arc, event: Option<&BoltDeviceConnection>, slot: u8, + receiver_uid: Option<&str>, ) -> Option { let pairing = match bolt.get_device_pairing_information(slot).await { Ok(p) => p, @@ -314,12 +349,7 @@ async fn read_bolt_slot_identity( "paired slot" ); - // The pairing register gives the device's unit id cheaply every tick — its - // stable cache identity. An all-zero id is treated as unidentifiable (don't - // cache; always probe when online). - let id = (pairing.unit_id != [0u8; 4]).then_some(CacheKey::Bolt { - unit_id: pairing.unit_id, - }); + let id = bolt_cache_key(pairing.unit_id, receiver_uid, slot, wpid); Some(BoltSlotIdentity { slot, codename, @@ -655,3 +685,63 @@ async fn read_codename(channel: &HidppChannel, slot: u8) -> Option { .ok() .map(str::to_string) } + +#[cfg(test)] +mod tests { + use super::{CacheKey, bolt_cache_key}; + + /// A device that reports a real unit id keys on it — the strongest + /// identity, independent of which slot it sits in. + #[test] + fn a_real_unit_id_keys_on_the_device_itself() { + assert_eq!( + bolt_cache_key([1, 2, 3, 4], Some("F00DCAFE"), 2, Some(0xb042)), + Some(CacheKey::Bolt { + unit_id: [1, 2, 3, 4] + }) + ); + } + + /// The regression this fixes: an all-zero unit id used to yield no key at + /// all, so the device was re-walked (tens of HID++ round-trips) on every + /// enumeration tick for as long as the agent ran. + #[test] + fn a_zero_unit_id_still_gets_a_cache_key() { + let key = bolt_cache_key([0; 4], Some("F00DCAFE"), 2, Some(0xb042)); + assert_eq!( + key, + Some(CacheKey::BoltSlot { + receiver_uid: "F00DCAFE".into(), + slot: 2, + wpid: Some(0xb042), + }), + "a zero unit id must not mean 'never cache'" + ); + } + + /// The fallback stays scoped: a different model, slot, or receiver is a + /// different key, so no device can inherit another's cached feature walk. + #[test] + fn the_fallback_key_does_not_collide_across_slots_models_or_receivers() { + let base = bolt_cache_key([0; 4], Some("F00DCAFE"), 2, Some(0xb042)); + assert_ne!( + base, + bolt_cache_key([0; 4], Some("F00DCAFE"), 3, Some(0xb042)) + ); + assert_ne!( + base, + bolt_cache_key([0; 4], Some("F00DCAFE"), 2, Some(0xc548)) + ); + assert_ne!( + base, + bolt_cache_key([0; 4], Some("0BADBEEF"), 2, Some(0xb042)) + ); + } + + /// With no receiver id there is nothing stable to key on, so the old + /// re-probe-every-tick behaviour is kept rather than risking a collision. + #[test] + fn no_receiver_id_means_no_key() { + assert_eq!(bolt_cache_key([0; 4], None, 2, Some(0xb042)), None); + } +} From 2303272387fabcf38b3c966e94346f2746c04a7b Mon Sep 17 00:00:00 2001 From: ultradaoto Date: Thu, 30 Jul 2026 23:34:07 -0700 Subject: [PATCH 42/42] docs: handoff guide for setting this fork up on another Windows PC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers what the fork adds, the toolchain pin, build and install (including the two-installs-wipe-your-config trap), device-key discovery, the ring config with a working example to adapt, and the traps that cost real time — pad wedging, the click-telemetry CIDs, log volume, and the append-only wire and schema-version rules an agent editing this needs to know. --- HANDOFF-WINDOWS-ACTION-RING.md | 290 +++++++++++++++++++++++++++++++++ docs/action-ring-example.toml | 129 +++++++++++++++ 2 files changed, 419 insertions(+) create mode 100644 HANDOFF-WINDOWS-ACTION-RING.md create mode 100644 docs/action-ring-example.toml diff --git a/HANDOFF-WINDOWS-ACTION-RING.md b/HANDOFF-WINDOWS-ACTION-RING.md new file mode 100644 index 00000000..62616dfd --- /dev/null +++ b/HANDOFF-WINDOWS-ACTION-RING.md @@ -0,0 +1,290 @@ +# Handoff — OpenLogi with MX Master 4 Action Ring (Windows) + +**Read this first if you are an agent setting this up on a second Windows PC.** +Everything needed is here: what this fork is, how to build it, how to install it +so it stays installed, how to configure the Action Ring, and the specific traps +that cost the first machine several hours. Follow it top to bottom. + +--- + +## 1. What this is + +Upstream [OpenLogi](https://github.com/AprilNEA/OpenLogi) is a native, local-first +replacement for Logitech Options+ (Rust; no account, no telemetry, plain-TOML +config). It did **not** support the MX Master 4's **Action Ring** — the haptic +force-pad on the thumb rest, marked with eight dots. + +This fork adds that, Windows-first: + +| Capability | Detail | +|---|---| +| Pad capture | The pad speaks only HID++ and emits no native HID events. The agent arms it (`0x19c0` force threshold `0x15a3` + `0x1b04` analytics reporting) and decodes its press events. | +| On-screen ring | Tap the pad → a ring of eight circles appears at the cursor. Aim and tap again (or click) to fire. Centre ✕ / `Esc` / click-outside cancels. | +| Ring folders | A slot can hold a sub-ring; firing it swaps the ring in place, centre becomes ← to go back. One level deep. | +| Full editor | In-app page: the ring drawn large with labelled slots, a live action panel, folder drill-in. | +| Rich actions | `Run` (URL / file / program, `||` for args, `%VAR%` expansion), `PasteText` (typed as Unicode, no clipboard clobber), `CustomShortcut` (record by pressing the keys), `Named` (your own label for any action). | +| Side buttons | Back/Forward on the MX Master 4 emit **no** native HID either — this fork diverts and dispatches them, so browser back/forward finally work. | + +Everything is also available on the gesture button and plain buttons (e.g. the +DPI toggle), not just the ring. + +**Licensing:** upstream is dual MIT/Apache-2.0 — fine to use and modify. The +`design/` brand assets are **proprietary**; keep this mirror private and do not +redistribute those publicly. + +**Upstream status:** this work is proposed upstream as +[PR #436](https://github.com/AprilNEA/OpenLogi/pull/436). Until/unless it merges, +this fork is the only build with the ring. + +--- + +## 2. Prerequisites (target machine) + +- **Windows 10/11**, MX Master 4 paired via a **Logi Bolt receiver** (this is the + verified path; Bluetooth-direct is untested for the ring). +- **Rust** with the **GNU** toolchain. MSVC also works *if* Visual Studio Build + Tools are installed; the source machine had no VS, so it used GNU: + ```bash + rustup toolchain install stable-x86_64-pc-windows-gnu + ``` +- **Git**. +- **`fxc.exe`** (DirectX shader compiler) — **only for release builds**. GPUI + precompiles HLSL in release. If the Windows SDK is not installed, extract it + from the `Microsoft.Windows.SDK.CPP` NuGet package (it lives at + `c\bin\\x64\fxc.exe`) and set `GPUI_FXC_PATH` to it. Debug builds compile + shaders at runtime and do not need it. + +> **Toolchain trap.** The repo's `rust-toolchain.toml` says `channel = "stable"`, +> which resolves to the host default (often MSVC). On a machine without VS this +> fails with `linker link.exe not found` — and confusingly, plain `cargo build` +> may work while `cargo clippy` fails, because one is a direct binary and the +> other goes through the rustup shim. **Pin every cargo command explicitly:** +> ```bash +> rustup run stable-x86_64-pc-windows-gnu cargo ... +> ``` + +--- + +## 3. Get the code + +Private Forgejo (same LAN / Tailscale): + +| Route | URL | +|---|---| +| LAN | `http://10.0.0.140:3000/ultradaoto/openlogi.git` | +| Tailscale | `http://forgejo-lxc.tailb56dd.ts.net:3000/ultradaoto/openlogi.git` | + +```bash +git clone http://10.0.0.140:3000/ultradaoto/openlogi.git +cd openlogi +git checkout feat/haptic-sense-panel # <-- the ring lives here, NOT on master +``` + +Authenticate with a **Forgejo personal access token** (Settings → Applications → +Generate Token, scope `read:repository` is enough to clone). Use the token as the +password: + +```bash +git clone http://:@10.0.0.140:3000/ultradaoto/openlogi.git +``` + +`master` on this mirror tracks upstream; **`feat/haptic-sense-panel` is the branch +you want.** + +--- + +## 4. Build + +```bash +# from the repo root +$env:GPUI_FXC_PATH = 'C:\path\to\fxc.exe' # release builds only +rustup run stable-x86_64-pc-windows-gnu cargo build --release -p openlogi-agent -p openlogi-gui +``` + +Two binaries land in `target\release\`: + +- `openlogi-agent.exe` — background service: owns the input hook and **all** + device I/O. +- `openlogi-gui.exe` — the app window; a pure IPC client. It **spawns the agent + itself**, so you normally launch only the GUI. + +Optional sanity gate (matches CI): + +```bash +rustup run stable-x86_64-pc-windows-gnu cargo clippy --workspace --all-targets -- -D warnings +rustup run stable-x86_64-pc-windows-gnu cargo test --workspace +``` + +--- + +## 5. Install — and why it matters + +> ### ⚠️ The single worst trap: two OpenLogis on one machine +> If an official OpenLogi is already installed (MSI / winget), you now have two +> builds. Launching the **old** one from the Start menu starts an old agent that +> cannot parse the ring bindings, **falls back to defaults, and saves those +> defaults over `config.toml` — wiping every binding.** On the source machine +> this silently destroyed a fully configured ring. +> +> This fork now guards against it (config `schema_version` 4, and a build that +> cannot read the config refuses to save over it), but an *older* binary predates +> that guard. **Have exactly one OpenLogi on the machine.** + +**Do this:** if an official OpenLogi is installed, either uninstall it, or +overwrite its binaries so every launch path runs this build: + +```powershell +$dst = "$env:LOCALAPPDATA\Programs\OpenLogi" # default install location +Stop-Process -Name OpenLogi, openlogi-gui, openlogi-agent -Force -ErrorAction SilentlyContinue +Copy-Item .\target\release\openlogi-gui.exe "$dst\OpenLogi.exe" -Force +Copy-Item .\target\release\openlogi-agent.exe "$dst\openlogi-agent.exe" -Force +``` + +If OpenLogi was never installed, create that folder and copy both binaries in +(rename the GUI to `OpenLogi.exe`), then make a Start-menu shortcut to it. + +**Autostart** (one entry, GUI only — it starts the agent): + +```powershell +Set-ItemProperty -Path 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' ` + -Name 'OpenLogi' -Value "`"$env:LOCALAPPDATA\Programs\OpenLogi\OpenLogi.exe`" --background" +``` + +`--background` starts it resident without opening a window. Do **not** add a +second Run entry for the agent — two agents means two input hooks. + +**Also uninstall Logi Options+ if present.** It re-arms the same pad and fights +for the device. Its updater service resurrects the app, so disabling is not +enough — uninstall it: +`"C:\Program Files\LogiOptionsPlus\logioptionsplus_updater.exe" --uninstall --full --shadow` +(elevated). G HUB is a separate stack and can stay. + +--- + +## 6. Configure the Action Ring + +Config lives at **`%USERPROFILE%\.config\openlogi\config.toml`** (plain TOML; +edit it with the app closed, or use the in-app editor). + +### 6a. Find the device key — do not copy the source machine's + +Every device is keyed by its **physical** identity, e.g. +`receiver:97b76948a846c55a:slot:2`. That receiver UID is unique to the source +machine's Bolt receiver. **Yours will differ.** + +Run the app once (it writes an identity block for every device it sees), then: + +```powershell +Select-String -Path "$env:USERPROFILE\.config\openlogi\config.toml" -Pattern '^\[devices\.' | + ForEach-Object { $_.Line } +``` + +Use the key whose `identity.display_name` is `MX Master 4`. Substitute it +everywhere `receiver:97b76948a846c55a:slot:2` appears below. + +### 6b. The ring layout + +A ready-to-adapt example is in **[`docs/action-ring-example.toml`](docs/action-ring-example.toml)** — +copy the `ActionRing` sections into your config under your own device key. + +Slots are compass-named clockwise from the top: `North`, `NorthEast`, `East`, +`SouthEast`, `South`, `SouthWest`, `West`, `NorthWest`. + +Shape of each action: + +```toml +North = "PlayPause" # built-in action +East = { Run = "https://chatgpt.com/" } # URL, file, or program +South = { Run = 'C:\Tools\app.exe||--flag value' } # || separates arguments +West = { PasteText = "text typed at the cursor" } + +[devices."".bindings.ActionRing.NorthWest.CustomShortcut] +modifiers = 4 # bitmask: 1=Cmd 2=Shift 4=Ctrl 8=Alt 16=Win +key_code = 49 # macOS kVK code (49 = Space) — see below +display = "Ctrl+Space" + +# A folder = a nested ring (one level deep) +[devices."".bindings.ActionRing.NorthEast.Folder.North] +PasteText = "first item in the sub-ring" +``` + +**Don't hand-write `CustomShortcut`.** Use the app: ring editor → slot → +**Keyboard Shortcut…** → **Press Keys**, then physically press the chord and +release. It records and saves in one motion, including chords another app has +claimed globally. (`key_code` is a macOS virtual-key code because that is the +cross-platform storage format; the recorder handles the translation.) + +**Naming:** any action takes an optional label shown on the ring instead of the +raw payload (`{ Named = { name = "Dictation", action = { ... } } }`). The dialog's +**Name** field writes this. + +### 6c. Using it + +- **Tap the pad** → ring opens at the cursor. +- **Move toward a slot, tap again** (or click it) → fires. +- **Folder slot** → ring swaps to its contents; centre becomes ← . +- **Cancel** → centre ✕, `Esc`, or click outside. +- **Edit** → open the app → device → Buttons → click the ring hotspot or its + "8 actions" card → full editor page. + +--- + +## 7. Traps and troubleshooting + +| Symptom | Cause / fix | +|---|---| +| **Ring bindings reverted to defaults on their own** | An older OpenLogi build ran and overwrote the config. See §5 — remove the second install. Restore from a backup (`config.toml.bak-*`). | +| **Pad stops responding entirely** | The pad's analytics reporting can wedge in device RAM after repeated arm/kill cycles (dev churn). **Power-cycle the mouse** (switch off, on). This is device-side; restarting software will not clear it. | +| **Ring doesn't open, but the app runs** | Check the agent armed it. Launch with `OPENLOGI_LOG='warn,openlogi_hid=info'` and look for `control capture active ... action_ring=true`. | +| **Every mouse click opens the ring** | Only CID `0x01a0` may be armed. CIDs `0x0050`/`0x0051` are **left/right click telemetry** — arming those makes every click a pad press. (Fixed here; noted in case anyone edits the arming code.) | +| **Mouse pointer jerky / skipping after hours** | Was caused by a caching bug: devices whose pairing register reports an all-zero `unit_id` were re-probed *every tick* — a full 46-feature HID++ walk every ~2 s over the mouse's own radio link. Fixed on this branch. If it recurs, power-cycle the mouse and report it. | +| **Huge log file** | Don't run with bare `OPENLOGI_LOG=info` long-term — tarpc logs every IPC call (~200 MB in days). Use `OPENLOGI_LOG='warn,openlogi_hid=info'`. | +| **Device takes minutes to appear** | First feature walk on the Windows BLE stack takes 1–3 s; the probe budget is 3 s. Should be fine now; if a device never appears, that budget is the place to look. | +| **`cargo clippy` fails with `link.exe not found`** | Toolchain trap — see §2, pin to `stable-x86_64-pc-windows-gnu`. | +| **Release build panics "Failed to find fxc.exe"** | Set `GPUI_FXC_PATH` — see §2. | + +--- + +## 8. Architecture orientation (for the agent) + +Three tiers, one install. The **GUI is a pure IPC client**; the **agent** owns the +input hook and all device I/O. + +| Crate | Role in this feature | +|---|---| +| `openlogi-hid` | `gesture.rs` arms the pad and decodes its events; `inventory/` enumerates devices and caches probes. | +| `openlogi-core` | `binding.rs` — the `Action` catalog, `RingSlot`, `Binding::Ring`; `config.rs` — TOML schema (**v4**). | +| `openlogi-agent-core` | tarpc IPC contract (`ipc.rs`, **PROTOCOL_VERSION 14**) — append-only wire format. | +| `openlogi-gui` | `ring.rs` = the overlay window; `mouse_model/ring_editor.rs` = the editor page; `windows/ring_action_editor.rs` = the payload dialog; `chord_recorder.rs` = key recording. | +| `openlogi-inject` | Synthesizes actions (`SendInput`, `ShellExecuteW`). | + +**Two rules that will bite you:** + +1. **The IPC wire format is append-only.** bincode encodes variant *indices* and + tarpc encodes *method order*. Never reorder or insert — only append — and bump + `PROTOCOL_VERSION` plus the golden tests in + `crates/openlogi-agent-core/tests/wire_format.rs`. +2. **Bump `SCHEMA_VERSION` whenever you write config an older build can't + represent.** That gate is the only thing standing between a downgrade and a + wiped config. + +Also worth knowing: synthesized keyboard chords **must dwell** (~120 ms held). +Apps that detect a global hotkey by *polling* key state — common for chords like +`Ctrl+Space` — never see an instantaneous press. `openlogi-inject` holds chords +for this reason; don't "optimize" it away. + +Repo conventions live in `AGENTS.md` (+ `.claude/rules/`): conventional commits, +no AI attribution, i18n strings added to **all 20 locales** at the same position. + +--- + +## 9. Quick checklist + +1. [ ] Rust GNU toolchain installed; `fxc.exe` available; `GPUI_FXC_PATH` set. +2. [ ] Cloned from Forgejo, **on branch `feat/haptic-sense-panel`**. +3. [ ] `cargo build --release -p openlogi-agent -p openlogi-gui` (pinned toolchain). +4. [ ] Logi Options+ uninstalled; only **one** OpenLogi on the machine. +5. [ ] Binaries copied to `%LOCALAPPDATA%\Programs\OpenLogi\`; single Run key with `--background`. +6. [ ] App launched once; **device key** read out of `config.toml`. +7. [ ] Ring slots configured (in-app editor, or adapt `docs/action-ring-example.toml`). +8. [ ] Verified: log shows `action_ring=true`; tapping the pad opens the ring. diff --git a/docs/action-ring-example.toml b/docs/action-ring-example.toml new file mode 100644 index 00000000..a0d3cda1 --- /dev/null +++ b/docs/action-ring-example.toml @@ -0,0 +1,129 @@ +# Action Ring — working example configuration +# +# This is the live ring from the first machine, kept as a starting point. +# Copy the `ActionRing` sections into +# `%USERPROFILE%\.config\openlogi\config.toml` and adapt. +# +# ───────────────────────────────────────────────────────────────────────────── +# BEFORE YOU COPY — replace the device key +# +# receiver:97b76948a846c55a:slot:2 +# +# is the *first machine's* Bolt receiver UID + pairing slot. Yours is different. +# Launch OpenLogi once so it writes its device blocks, then read yours: +# +# Select-String -Path "$env:USERPROFILE\.config\openlogi\config.toml" ` +# -Pattern '^\[devices\.' | ForEach-Object { $_.Line } +# +# Use the key whose identity block says `display_name = "MX Master 4"`, and +# substitute it everywhere below. Edit with the app CLOSED, or it will +# overwrite your changes on its next save. +# ───────────────────────────────────────────────────────────────────────────── +# +# Slots are compass-named clockwise from the top: +# North · NorthEast · East · SouthEast · South · SouthWest · West · NorthWest +# +# Any slot may hold: +# "PlayPause" a built-in action (see the catalog in-app) +# { Run = "…" } URL, file, or program; `||` separates args, +# %VAR% expands +# { PasteText = "…" } typed at the cursor as Unicode +# { CustomShortcut = { … } } a key chord — RECORD THIS IN THE APP +# { Folder = { … } } a nested ring, one level deep +# { Named = { name = "…", any of the above with your own label, +# action = { … } } } shown on the ring instead of the payload + +schema_version = 4 + +# ── Top-level ring ─────────────────────────────────────────────────────────── +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing] +North = "PlayPause" +SouthEast = "LockScreen" +SouthWest = "CaptureRegion" # Win+Shift+S region snip + +# ── East: a folder of AI sites ─────────────────────────────────────────────── +# Firing this slot swaps the on-screen ring to these four; the centre button +# becomes ← to step back out. Empty positions simply don't render. +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing.East.Folder.North] +Run = "https://chatgpt.com/" + +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing.East.Folder.NorthEast] +Run = "https://www.perplexity.ai/" + +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing.East.Folder.East] +Run = "https://gemini.google.com/" + +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing.East.Folder.SouthEast] +Run = "https://copilot.microsoft.com/" + +# ── NorthEast: a folder of prompt snippets ─────────────────────────────────── +# PasteText types the snippet wherever the caret is — no clipboard involvement, +# so whatever you had copied survives. +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing.NorthEast.Folder.North] +PasteText = "Okay, then it's time to start making the four-track documents, all .md files. Include a preview for phase two, phase three, and phase four in each of the documents so I can hand them off and have them continue autonomously." + +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing.NorthEast.Folder.NorthEast] +PasteText = "We're working on a multiphase coding round. We've just completed phase one. Read the previews of the phases, then build phase two as a .md document I can hand back to the coding agent." + +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing.NorthEast.Folder.East] +PasteText = "Ok, please make the phase three document. Here's the report." + +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing.NorthEast.Folder.SouthEast] +PasteText = "Okay, great, phase three is complete. Now please write phase four." + +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing.NorthEast.Folder.South] +PasteText = "Please write a quick summary here in the chat, referencing any important components, backlogged items, and cross-messages between tracks, so I can send it to the master orchestrator." + +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing.NorthEast.Folder.SouthWest] +PasteText = "Let's do a master document I can paste from one agent to the next for an audit, ending with a release on the server. Make sure they all know to get, add, commit, and push." + +# ── South: launch a program ────────────────────────────────────────────────── +# Opens OpenLogi itself. Use `||` for arguments, e.g. +# Run = 'C:\Windows\notepad.exe||%USERPROFILE%\notes.txt' +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing.South] +Run = 'C:\Users\ultra\AppData\Local\Programs\OpenLogi\OpenLogi.exe' + +# ── West / NorthWest: key chords ───────────────────────────────────────────── +# PREFER RECORDING THESE IN THE APP: +# ring editor → slot → "Keyboard Shortcut…" → "Press Keys" → press → release. +# It writes this block for you and gets the codes right, including chords another +# app already owns globally (the recorder reads physical key state, so a hotkey +# owner swallowing the message doesn't matter). +# +# If you must hand-write: `modifiers` is a bitmask — +# 1 = Cmd/Meta 2 = Shift 4 = Ctrl 8 = Alt/Option 16 = Win +# and `key_code` is a macOS kVK virtual-key code (the cross-platform storage +# format; the injector maps it to a Windows VK). 49 = Space, 6 = Z, 0 = A. +# +# Note: chords are injected with a ~120 ms hold, so apps that detect their +# hotkey by polling key state (common) actually observe the press. + +# Ctrl+Space — dictation toggle on the first machine (Voice Node). +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing.West.CustomShortcut] +modifiers = 4 +key_code = 49 +display = "Ctrl+Space" + +# Win+Ctrl+Space +[devices."receiver:97b76948a846c55a:slot:2".bindings.ActionRing.NorthWest.CustomShortcut] +modifiers = 20 +key_code = 49 +display = "Win+Ctrl+Space" + +# ───────────────────────────────────────────────────────────────────────────── +# Other buttons take the same rich actions — not just the ring. +# The gesture button (hold + swipe) and plain buttons (DPI toggle, Middle, +# Back, Forward) all offer Run / Paste Text / Keyboard Shortcut in their +# pickers. Example: the gesture button's five directions. +# ───────────────────────────────────────────────────────────────────────────── +[devices."receiver:97b76948a846c55a:slot:2".bindings.GestureButton] +Up = "MissionControl" +Down = "ShowDesktop" +Left = "PrevTab" +Right = "NextTab" +Click = "AppExpose" + +# The pad is only a ring while its binding is a slot map. Replacing the whole +# table with a single action makes a tap fire that action directly instead: +# [devices."".bindings] +# ActionRing = "Copy"