feat(diag): full input logging + read barrel from pointerFlags; focal-jump reject
All checks were successful
CI / Windows build (push) Successful in 12m44s

Buttons (likely fix + ground truth): device diag showed ptr=12577 pen=10056 —
WM_POINTER reaches the observer and GetPointerPenInfo succeeds, so the buttons
were just read from the wrong field. Native now resolves the barrel from BOTH
penFlags(PEN_FLAG_BARREL) AND pointerInfo.pointerFlags(POINTER_FLAG_SECONDBUTTON)
— many pens use the latter. It also emits the full raw set (pointerFlags,
penFlags, penMask, ButtonChangeType, tilt) plus OR-accumulated flags so a single
session reveals exactly which field each button sets.

Comprehensive logging (per user request "用好用的log库 / 我手动开启日志再记录"):
new DiagnosticLogger emits through dart:developer log(name 'badnote.input') —
capturable via `flutter run` / DevTools / `flutter logs` — AND mirrors to a file
(path shown in the overlay) for the packaged GUI build that has no console.
Manually enabled by the toolbar diagnostic toggle; off by default. PEN lines log
on raw-field change; ZOOM lines log every scale frame + rebaselines.

Zoom: scale-only glitch rejection didn't stop the jumping, so add focal/position
glitch rejection — drop a 2-finger frame whose focal jumps >250px (a touch
misread). The full per-frame trace (raw scale, pointerCount, applied change,
focal jump, drops) is now logged so the residual cause is unambiguous.

InputDiagnostics singleton accumulates the stats; the overlay shows summary +
last trace lines + log path + reset. Removed the ad-hoc inline zoom min/max.

Dart: analyze clean, 66/66 tests, linux build green. Native compiles on CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-23 01:10:17 +08:00
parent ae9e070b46
commit f9ec04fe86
6 changed files with 379 additions and 55 deletions

View File

@@ -23,6 +23,8 @@ import 'dart:async';
import 'package:flutter/services.dart';
import 'diagnostic_logger.dart';
/// Latest hardware pen state delivered by the native observer.
class PenHardwareState {
const PenHardwareState({
@@ -94,17 +96,27 @@ class PenInputService {
bool get isActive => _active;
bool _active = false;
// Native-side diagnostics (see windows/runner/pen_channel.cpp): how many
// WM_POINTER / PT_PEN / legacy-mouse messages the observer has seen. Lets the
// on-device overlay tell us WHICH layer is failing for buttons/tilt.
// Native-side diagnostics (see windows/runner/pen_channel.cpp).
int _diagPtr = 0;
int _diagPen = 0;
int _diagMouse = 0;
int _diagMsg = 0;
int _orPtrFlags = 0;
int _orPenFlags = 0;
int _orPenMask = 0;
int _btnChangeLast = 0;
int _tiltAbsMax = 0;
/// One-line native readout for the diagnostic overlay.
String _hex(int v) => '0x${v.toRadixString(16)}';
/// Multi-line native readout for the diagnostic overlay. The OR-accumulated
/// flag fields are the ground truth for which field carries the side/eraser
/// button: e.g. orPtrFlags with bit 0x20 (POINTER_FLAG_SECONDBUTTON) set means
/// the barrel button IS detectable.
String get debugSummary => _active
? 'native ptr=$_diagPtr pen=$_diagPen mouse=$_diagMouse msg=0x${_diagMsg.toRadixString(16)}'
? 'native ptr=$_diagPtr pen=$_diagPen mouse=$_diagMouse msg=${_hex(_diagMsg)}'
'\n orPtrFlags=${_hex(_orPtrFlags)} orPenFlags=${_hex(_orPenFlags)}'
' mask=${_hex(_orPenMask)} btnChg=$_btnChangeLast tiltMax=$_tiltAbsMax'
: 'native: channel silent (no events)';
/// Begins listening to the native channel. Idempotent; safe on any platform
@@ -140,9 +152,35 @@ class PenInputService {
_diagPen = (event['diagPen'] as num?)?.toInt() ?? _diagPen;
_diagMouse = (event['diagMouse'] as num?)?.toInt() ?? _diagMouse;
_diagMsg = (event['diagMsg'] as num?)?.toInt() ?? _diagMsg;
_orPtrFlags = (event['orPtrFlags'] as num?)?.toInt() ?? _orPtrFlags;
_orPenFlags = (event['orPenFlags'] as num?)?.toInt() ?? _orPenFlags;
_orPenMask = (event['orPenMask'] as num?)?.toInt() ?? _orPenMask;
_btnChangeLast = (event['btnChangeLast'] as num?)?.toInt() ?? _btnChangeLast;
_tiltAbsMax = (event['tiltAbsMax'] as num?)?.toInt() ?? _tiltAbsMax;
_active = true;
// Log a PEN line whenever the raw per-event button/flag fields change, so
// the file captures exactly which field a button press sets (without
// flooding on every high-rate WM_POINTERUPDATE).
final rawPtr = (event['rawPtrFlags'] as num?)?.toInt() ?? 0;
final rawPen = (event['rawPenFlags'] as num?)?.toInt() ?? 0;
final rawMask = (event['rawPenMask'] as num?)?.toInt() ?? 0;
final btnChange = (event['btnChange'] as num?)?.toInt() ?? 0;
final key = '$rawPtr,$rawPen,$rawMask,$btnChange,${_current.tiltX},${_current.tiltY}';
if (key != _lastPenLogKey) {
_lastPenLogKey = key;
DiagnosticLogger.instance.log(
'PEN ptrFlags=0x${rawPtr.toRadixString(16)} '
'penFlags=0x${rawPen.toRadixString(16)} '
'mask=0x${rawMask.toRadixString(16)} btnChg=$btnChange '
'tilt=${_current.tiltX.toStringAsFixed(0)},${_current.tiltY.toStringAsFixed(0)} '
'msg=0x${_diagMsg.toRadixString(16)} resolved=0x${flags.toRadixString(16)}',
);
}
}
String _lastPenLogKey = '';
/// Stops listening and resets state.
void stop() {
_sub?.cancel();