Skip to content

v1.6.16: system-aligned mouse thresholds + UIA selection pre-gate - #6

Merged
rokogan merged 1 commit into
masterfrom
claude/v1.6.16-uia-pregate
May 28, 2026
Merged

v1.6.16: system-aligned mouse thresholds + UIA selection pre-gate#6
rokogan merged 1 commit into
masterfrom
claude/v1.6.16-uia-pregate

Conversation

@rokogan

@rokogan rokogan commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Two-layer fix for false-positive toolbar / paste mode triggers reported during slow window drag, scroll, and double-click-to-open in Explorer.

  • Layer A — system thresholds. LongPressMoveCancelDistSq and MultiClickRadiusSq now resolve from GetSystemMetrics(SM_CXDRAG) / SM_CXDOUBLECLK at process start. Same threshold DragDetect uses internally; typically 4 px. The old hardcoded 8 px let a slow drag onset sit inside the cancel radius for the full long-press timeout.
  • Layer B+C — UIA selection pre-gate (TextCapture.ProbeSelectionViaUIA). Runs before the clipboard pipeline:
    • HasTextTextPattern.GetSelection returns non-empty → use it directly, skip the clipboard dance entirely.
    • Suppress — TextPattern present but selection is degenerate, or focused element is DataItem/ListItem/TreeItem with SelectionItemPattern (Explorer file row, desktop icon, list row). No toolbar.
    • Unknown — no TextPattern, no matching item type, or UIA threw → falls through to the existing WM_COPY → CopyViaUIA → Ctrl+Insert pipeline.

Why this gate doesn't repeat past mistakes

Three UIA gates were tried and reverted across v1.6.5–v1.6.12 because they suppressed on "no TextPattern found" (false negatives in browsers / Electron, which focus parent panes). This one only suppresses on definitive answers (empty selection or item type). Anything ambiguous falls through to the unchanged clipboard fallback, so the historical problem apps still work.

What this kills

  • Double-click a file in Explorer → focused = DataItem + SelectionItemPattern → Layer C suppresses (was: WM_COPY copied filename → toolbar appeared).
  • Double-click a desktop icon → same shape.
  • Long-press during slow drag of a custom title bar or mid-pane scrollbar → Layer A's tighter system-metric threshold cancels the timer.
  • Long-press on a focused-but-empty text field → Layer B suppresses.

Fallback for misses

Settings → Excluded apps still works app-by-app (the gate isn't excluded-app aware; the existing ForegroundApp.IsExcluded check still runs upstream).

Test plan

  • Build clean (Debug + Release)
  • 275/275 unit tests pass (2 new range checks)
  • Single-file SnapActions.exe publishes locally (~170 MB self-contained)
  • Drag a Chrome / Edge window by the custom title bar → no paste mode
  • Drag-scroll a long page → no paste mode
  • Double-click a file in Explorer → no toolbar
  • Double-click a desktop icon → no toolbar
  • Double-click a word in Notepad → toolbar still appears
  • Drag-select text in browser → toolbar still appears
  • Hold mouse on text → paste mode still appears
  • Watch %APPDATA%\SnapActions\logs\YYYY-MM-DD.log for UIA pre-gate ... entries to verify which path fires per app

Files

  • SnapActions/Core/MouseHook.cs — Layer A
  • SnapActions/Core/TextCapture.cs — Layer B+C (ProbeSelectionViaUIA + pre-gate call)
  • SnapActions/Core/SelectionTracker.cs — comment-only, updated history note
  • SnapActions/SnapActions.csproj — version 1.6.15 → 1.6.16
  • SnapActions.Tests/MouseHookGeometryTests.cs — threshold sanity tests

User report: long-press paste mode firing during slow window drag and scroll,
and the selection toolbar firing on double-click-to-open in Explorer / desktop
icons / other non-text contexts. The "no text was actually selected" cases were
slipping through because WM_COPY succeeds in many non-selection contexts (copies
the focused element's content / filename).

Layer A — system thresholds (MouseHook):
LongPressMoveCancelDistSq and MultiClickRadiusSq now resolve from
GetSystemMetrics(SM_CXDRAG) and (SM_CXDOUBLECLK) at process start. Same numbers
DragDetect uses internally; typically 16 (4 px). The old hardcoded 64 (8 px)
let a slow drag onset sit inside the cancel radius for the full long-press
timeout. Falls back to 4 px if a stripped runner returns 0.

Layer B+C — UIA selection pre-gate (TextCapture.ProbeSelectionViaUIA):
Runs before the clipboard pipeline. Three outcomes:
- HasText  — TextPattern.GetSelection returns non-empty text → use it directly,
             skip the WM_COPY → snapshot → clear → restore dance entirely.
- Suppress — Either TextPattern is present but the selection is degenerate, OR
             focused element is DataItem/ListItem/TreeItem with
             SelectionItemPattern (Explorer file row, desktop icon, list row).
             No toolbar.
- Unknown  — No TextPattern in the walk, no matching item type, or UIA threw.
             Falls through to the existing WM_COPY → CopyViaUIA → Ctrl+Insert
             pipeline. Preserves compatibility with Java Swing / legacy Edge /
             shallow-UIA-tree apps that broke the previous gates.

Why this gate doesn't repeat the v1.6.5–1.6.12 over-suppression mistakes: it
only acts on definitive answers. The old gates treated "no TextPattern found"
as a stop signal (false negatives in browsers / Electron because focus lands
on parent panes). This one falls through in that case — the historical
problem apps still hit the clipboard path.

Tests: 2 new range checks for the system-metric-derived thresholds (can't pin
exact numbers since they depend on the runner's OS settings, but the bug we
fixed is excluded — values > 8 px slip past). 275 tests pass.
@rokogan
rokogan merged commit 20b32fe into master May 28, 2026
1 check passed
rokogan added a commit that referenced this pull request Jun 29, 2026
From an adversarial review of the v2.0.0 diff:
- TextCapture: only restore the clipboard if its sequence number still
  equals the one right after our copy — if a third party wrote in the
  meantime, they own it now; don't clobber them. [review #1]
- ResultPopup: broaden the online-lookup consent wording so it's accurate
  for custom "fetch" recipe actions (which send to their own host), not
  just the three built-in services. [review #6]
- SelectionTracker: claim the Ctrl+C debounce slot before the 100ms await
  so a rapid second Ctrl+C is dropped instead of double-firing. [review #2]
- MouseHook/KeyboardHook: null _hookThread in Uninstall so a later
  Install() isn't no-op'd by the non-null guard. [review #7]
- SettingsManager: prune stale IDs from AppHiddenActions too, dropping
  now-empty per-app entries. [review #13]

Release build clean; 289 tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant