Feature/trackpoint virtual touch mode - #53
Merged
Conversation
- add TrackpointMode enum (PHYSICAL_MOUSE, VIRTUAL_TOUCH) in MacroPadLayout.kt - extend PadAction.TrackpointMove to include trackpoint mode parameter - add enableTouch flag to PadProfile data model and update state synchronization in MacroPadState.kt - implement dropdown selector and explainer descriptions in PadButtonEditDialog.kt - define strings for tracking modes and descriptions in English and German strings.xml - implement absolute cursor coordinate tracking and virtual touch event injection in MacroPadHitTestEngine.kt - manage TouchInjector lifecycle dynamically in MacroPadViewModel.kt and BackgroundMacroPadOverlay.kt - update device availability checks for virtual touch in ButtonListItem.kt, MacroPadEditor.kt, and PadCanvas.kt - add unit test suite in MacroPadHitTestEngineTest.kt covering coordinate calculations, swipes, and boundaries - update macropad FEATURE.md functional requirements and schemas
…irection reversal jumps - Clamp physical touch coordinates to display bounds [0.0, 1.0] in TouchInjector to prevent coordinate wrapping and negative coordinate jumps. - Implement direction-change detection in MacroPadHitTestEngine using drag delta signs. - Snap unclamped cursor coordinates immediately to clamped internally-tracked positions on direction changes to eliminate lag and dead zones when moving back from edges. - Reset direction sign trackers on press, release, and resets. - Update MacroPadHitTestEngineTest unit tests to verify clamped coordinate injection and direction-change snapping. - Synchronize FEATURE.md documentation to reflect the updated edge clamping and direction snapping requirements.
…t relative mouse catch-up - Update coordinate clamping in TouchInjector.kt to the safe overrun range of [-0.5f, 1.5f]. - Resolve issue where simulated relative cursors became caught and could not reach screen boundaries. - Update MacroPadHitTestEngineTest assertions to verify clamped [-0.5f, 1.5f] coordinates.
…ing mode - Add ' (WIP)' suffix to 'macropad_trackpoint_mode_touch' string resource. - Add ' (WIP)' suffix to 'macropad_trackpoint_mode_touch_desc' string resource. - Update both English and German locale configurations.
- update VIEWPORT_ZOOM_MAX to 10f in MirrorViewportController - update mirror zoom range in README.md and mirror FEATURE.md - update manual verification checklist to assert 10.0x zoom limit
Comment on lines
61
to
63
| @@ -63,17 +63,21 @@ object TouchInjector { | |||
| * @param normalizedY 0.0 (top) … 1.0 (bottom) of the logical display | |||
Comment on lines
+63
to
+66
| - `enableKeyboard = true` if any button has a `KeyboardKey` action. | ||
| - `enableGamepad = true` if any button has a `GamepadButton` action. | ||
| - `enableMouse = true` if any button has a `MouseButton`, `ScrollWheel`, or `TrackpointMove` action. | ||
| - `enableMouse = true` if any button has a `MouseButton`, `ScrollWheel`, or `TrackpointMove` (with `PHYSICAL_MOUSE` tracking mode) action. | ||
| - `enableTouch = true` if any button has a `TrackpointMove` (with `VIRTUAL_TOUCH` tracking mode) action. |
| - In use mode, dragging a finger on a trackpoint button translates relative motion into **REL_X / REL_Y mouse events** via `MouseInjector.moveMouse()`. Sensitivity is fixed at 3× the raw pixel delta (`MP_TRACKPOINT_SENSITIVITY = 3f`). | ||
| - In use mode, dragging a finger on a trackpoint button translates relative motion depending on the configured **Tracking Mode**: | ||
| - **Virtual Physical Mouse**: relative motion is translated into **REL_X / REL_Y mouse events** via `MouseInjector.moveMouse()`. Sensitivity is fixed at 3× the raw pixel delta (`MP_TRACKPOINT_SENSITIVITY = 3f`). | ||
| - **Virtual Touch**: relative motion is translated into absolute touches on the primary landscape display (`1920x1080`) via `TouchInjector.injectTouch()`. A virtual cursor position is tracked internally (initially at center `(0.5f, 0.5f)`) and persists across finger lifts, allowing subsequent swipes from the same position. The internally tracked position (`virtualCursorX` / `virtualCursorY`) is clamped to screen boundaries `[0.0f, 1.0f]` at all times. Touch events injected to the system are clamped to screen boundaries `[0.0f, 1.0f]` to prevent coordinate wrapping or jumps. Relative movements are tracked unclamped during dragging, but snap immediately to the internally tracked position as soon as the user changes drag direction (sign change in delta) to eliminate lag or dead zones when moving back from edges. There is no snap-back logic on release or overshoot limits. |
Comment on lines
44
to
49
| val ms = hasMacro || allButtons.any { | ||
| it.action is PadAction.MouseButton || | ||
| it.action is PadAction.ScrollWheel || | ||
| it.action is PadAction.TrackpointMove || | ||
| (it.action is PadAction.TrackpointMove && (it.action as PadAction.TrackpointMove).mode == TrackpointMode.PHYSICAL_MOUSE) || | ||
| it.action is PadAction.FullScreenMouse || | ||
| it.action is PadAction.MirrorTouchProjection |
Comment on lines
+143
to
+147
| @@ -143,20 +144,23 @@ | |||
| KeyInjector.stop() | |||
| GamepadInjector.stop() | |||
| MouseInjector.stop() | |||
| TouchInjector.stop() | |||
Comment on lines
150
to
154
| AppLog.d(TAG, "pill menu open → stopping gamepad/mouse injectors") | ||
| GamepadInjector.stop() | ||
| MouseInjector.stop() | ||
| TouchInjector.stop() | ||
| } |
…ch injector safety - remove MirrorTouchProjection from enableMouse profile flag derivation - remove TouchInjector.stop() from BackgroundMacroPadOverlay modal gates to prevent lifecycle races - update TouchInjector KDocs and macropad FEATURE.md to match safe overrun clamping [-0.5, 1.5] - add withSyncedDeviceFlags synchronization rules unit test in MacroPadStateTest
Comment on lines
312
to
+323
| fun releaseAll(buttons: List<PadButton>) { | ||
| val activeIds = _pressedIds.value | ||
| _pressedIds.value = emptySet() | ||
| pointerMap.clear() | ||
| lastTpPos = null | ||
| scrollStartY.clear() | ||
| virtualCursorX = virtualCursorX.coerceIn(0f, 1f) | ||
| virtualCursorY = virtualCursorY.coerceIn(0f, 1f) | ||
| unclampedCursorX = virtualCursorX | ||
| unclampedCursorY = virtualCursorY | ||
| lastDxSign = 0f | ||
| lastDySign = 0f |
Comment on lines
+183
to
+184
| val dxNormalized = (deltaX * MP_TRACKPOINT_SENSITIVITY) / 1920f | ||
| val dyNormalized = (deltaY * MP_TRACKPOINT_SENSITIVITY) / 1080f |
| - In use mode, dragging a finger on a trackpoint button translates relative motion into **REL_X / REL_Y mouse events** via `MouseInjector.moveMouse()`. Sensitivity is fixed at 3× the raw pixel delta (`MP_TRACKPOINT_SENSITIVITY = 3f`). | ||
| - In use mode, dragging a finger on a trackpoint button translates relative motion depending on the configured **Tracking Mode**: | ||
| - **Virtual Physical Mouse**: relative motion is translated into **REL_X / REL_Y mouse events** via `MouseInjector.moveMouse()`. Sensitivity is fixed at 3× the raw pixel delta (`MP_TRACKPOINT_SENSITIVITY = 3f`). | ||
| - **Virtual Touch**: relative motion is translated into absolute touches on the primary landscape display (`1920x1080`) via `TouchInjector.injectTouch()`. A virtual cursor position is tracked internally (initially at center `(0.5f, 0.5f)`) and persists across finger lifts, allowing subsequent swipes from the same position. The internally tracked position (`virtualCursorX` / `virtualCursorY`) is clamped to screen boundaries `[0.0f, 1.0f]` at all times. Touch events injected to the system are clamped to the safe overrun range of `[-0.5f, 1.5f]` to prevent coordinate wrapping or jumps while allowing the system cursor to catch up. Relative movements are tracked unclamped during dragging, but snap immediately to the internally tracked position as soon as the user changes drag direction (sign change in delta) to eliminate lag or dead zones when moving back from edges. There is no snap-back logic on release or overshoot limits. |
…gic numbers - update releaseAll() in MacroPadHitTestEngine to send UP event for active virtual-touch trackpoints on cancellation - extract logical display dimensions 1920f and 1080f to private constants in MacroPadHitTestEngine - resolve documentation inconsistency regarding overshoot limits in macropad FEATURE.md - add releaseAll unit test for virtual touch trackpoint cancellation
Comment on lines
44
to
49
| val ms = hasMacro || allButtons.any { | ||
| it.action is PadAction.MouseButton || | ||
| it.action is PadAction.ScrollWheel || | ||
| it.action is PadAction.TrackpointMove || | ||
| it.action is PadAction.FullScreenMouse || | ||
| it.action is PadAction.MirrorTouchProjection | ||
| (it.action is PadAction.TrackpointMove && (it.action as PadAction.TrackpointMove).mode == TrackpointMode.PHYSICAL_MOUSE) || | ||
| it.action is PadAction.FullScreenMouse | ||
| } |
Comment on lines
128
to
134
| fun stopInjectors() { | ||
| AppLog.i(TAG, "stopInjectors called") | ||
| KeyInjector.stop() | ||
| GamepadInjector.stop() | ||
| MouseInjector.stop() | ||
| TouchInjector.stop() | ||
| MacroPadState.resetPeek() |
Comment on lines
139
to
144
| AppLog.i(TAG, "onCleared → all injectors stopped") | ||
| KeyInjector.stop() | ||
| GamepadInjector.stop() | ||
| MouseInjector.stop() | ||
| TouchInjector.stop() | ||
| MacroPadState.resetPeek() |
Comment on lines
171
to
176
| AppLog.d(TAG, "BackgroundMacroPadOverlay disposed → all injectors stopped") | ||
| KeyInjector.stop() | ||
| GamepadInjector.stop() | ||
| MouseInjector.stop() | ||
| TouchInjector.stop() | ||
| MacroPadState.resetPeek() |
…client tokens - introduce synchronized activeClients registry in TouchInjector to manage concurrent client start/stop requests - update all TouchInjector start and stop calls in overlays, presentations, ViewModels, and executors to pass unique client tokens - remove redundant injectorStartedByUs flag from RecordingMirrorPresentation - update KDoc for withSyncedDeviceFlags in MacroPadState to reflect enableTouch derivation - sync technical implementation documentation in mirror and touchpad FEATURE.md files
Comment on lines
+69
to
+76
| /* Flush and stop on a daemon thread to avoid blocking the calling thread. | ||
| stop() can be invoked from Compose DisposableEffect.onDispose (main thread). */ | ||
| Thread { | ||
| if (!ShellInputInjector.flushPendingTouches(TOUCH_STOP_FLUSH_TIMEOUT_MS)) { | ||
| AppLog.w(TAG, "stop() timed out while flushing touch release commands") | ||
| } | ||
| ShellInputInjector.stop() | ||
| }.also { it.isDaemon = true }.start() |
Comment on lines
+56
to
+60
| fun stop(token: String) { | ||
| if (!activeClients.contains(token)) { | ||
| AppLog.w(TAG, "stop() called for non-active client '$token'. Ignoring.") | ||
| return | ||
| } |
Comment on lines
+329
to
+334
| Both the Virtual Touchpad and Mirror Touch Projection use `TouchInjector` from the `input/` package. The same native binary (`touchinjector_arm64`) and device node (`/dev/input/event6`) are used by both features. To coordinate the native process lifetime across multiple concurrent callers (Mirror Touch Projection, relative trackpoints in MacroPad, macro executors), `TouchInjector` implements a thread-safe, client-aware reference-counted lifecycle. The native binary is started when the first client registers itself, and is terminated only after the last active client has unregistered. | ||
|
|
||
| **Lifecycle:** | ||
|
|
||
| - `LaunchedEffect(isTouchProjectionActive)` starts the injector when projection is enabled and stops it when disabled. | ||
| - `DisposableEffect(Unit)` stops the injector unconditionally when `MirrorScreen` leaves composition (mode switch). | ||
| - `LaunchedEffect(isTouchProjectionActive)` starts the injector with the `"MirrorPresentation"` token when projection is enabled, and stops it when disabled. | ||
| - `DisposableEffect(Unit)` stops the injector with the `"MirrorPresentation"` token when `MirrorScreen` leaves composition (mode switch). |
Comment on lines
+61
to
+68
| - Each profile has four independent boolean flags: `enableKeyboard`, `enableGamepad`, `enableMouse`, `enableTouch` (all default **`false`** — new profiles start with all injectors off). | ||
| - These flags are **not user-configurable** directly. They are automatically recomputed whenever the button list changes (add / edit / delete) by inspecting the action types of all buttons. The exact derivation rules are: | ||
| - If the profile contains any button with a `Macro` action, all four flags are force-enabled (`true`). | ||
| - Otherwise, each flag is set to `true` if any button matches the following actions: | ||
| - `enableKeyboard = true` if any button has a `KeyboardKey` or `FullScreenKeyboard` action. | ||
| - `enableGamepad = true` if any button has a `GamepadButton` action. | ||
| - `enableMouse = true` if any button has a `MouseButton`, `ScrollWheel`, `TrackpointMove` (with `PHYSICAL_MOUSE` tracking mode), or `FullScreenMouse` action. (Note: `MirrorTouchProjection` is explicitly excluded from this derivation because the screen mirror presentation manages its own touch injector lifecycle). | ||
| - `enableTouch = true` if any button has a `TrackpointMove` (with `VIRTUAL_TOUCH` tracking mode) action. |
…r lifecycle stop - synchronize on TouchInjector inside stop(token)'s flush thread to prevent stopping if new clients register during flush - downgrade non-active client stop warning log to debug level to prevent noisy logs in normal safety-net flows - update remaining no-arg TouchInjector.stop() documentation references in mirror and macropad FEATURE.md files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.