#include "pen_channel.h" #include #include #include #include #include #include namespace { std::unique_ptr> g_pen_sink; std::unique_ptr> g_pen_channel; // Diagnostic counters so the Dart side can see WHAT the observer receives: // - g_ptr_msgs: WM_POINTER* messages seen (is WM_POINTER reaching us at all?) // - g_pen_msgs: of those, PT_PEN with a successful GetPointerPenInfo // - g_mouse_msgs: legacy mouse/touch input messages (Flutter on the old path?) int g_ptr_msgs = 0; int g_pen_msgs = 0; int g_mouse_msgs = 0; int g_last_msg = 0; // OR-accumulated raw flag fields, so a momentary button press is CAPTURED and // held (a live readout would miss it). These are the ground truth for "which // field/bit does the side button / eraser set?". int g_ptr_flags_or = 0; // POINTER_INFO.pointerFlags (POINTER_FLAG_SECONDBUTTON = barrel on many pens) int g_pen_flags_or = 0; // POINTER_PEN_INFO.penFlags (PEN_FLAG_BARREL/INVERTED/ERASER) int g_pen_mask_or = 0; // POINTER_PEN_INFO.penMask int g_btn_change_last = 0; // last non-zero POINTER_INFO.ButtonChangeType int g_tilt_abs_max = 0; // max |tiltX|,|tiltY| seen float g_pressure_max_seen = 0.f; } // namespace void RegisterPenChannel(flutter::FlutterEngine* engine) { g_pen_channel = std::make_unique>( engine->messenger(), "badnote/pen", &flutter::StandardMethodCodec::GetInstance()); auto handler = std::make_unique< flutter::StreamHandlerFunctions>( [](const flutter::EncodableValue* arguments, std::unique_ptr>&& events) -> std::unique_ptr< flutter::StreamHandlerError> { g_pen_sink = std::move(events); return nullptr; }, [](const flutter::EncodableValue* arguments) -> std::unique_ptr< flutter::StreamHandlerError> { g_pen_sink = nullptr; return nullptr; }); g_pen_channel->SetStreamHandler(std::move(handler)); } void ObservePenMessage(UINT message, WPARAM wparam, LPARAM lparam) { if (!g_pen_sink) { return; } const bool is_pointer = message == WM_POINTERENTER || message == WM_POINTERDOWN || message == WM_POINTERUPDATE || message == WM_POINTERUP; // Legacy input path: if Flutter is feeding us mouse/touch instead of pointer // messages, these tell us so (so we know WM_POINTER never arrives here). const bool is_legacy_input = message == WM_LBUTTONDOWN || message == WM_LBUTTONUP || message == WM_MOUSEMOVE || message == WM_TOUCH; if (!is_pointer && !is_legacy_input) { return; } g_last_msg = static_cast(message); if (is_legacy_input) { ++g_mouse_msgs; } int flags = 0; double tilt_x = 0.0; double tilt_y = 0.0; double pressure = 0.0; int pressure_valid = 0; int raw_ptr_flags = 0; int raw_pen_flags = 0; int raw_pen_mask = 0; int btn_change = 0; int history_count = 0; if (is_pointer) { ++g_ptr_msgs; UINT32 pointerId = GET_POINTERID_WPARAM(wparam); POINTER_INPUT_TYPE type = PT_POINTER; if (GetPointerType(pointerId, &type) && type == PT_PEN) { POINTER_PEN_INFO ppi{}; if (GetPointerPenInfo(pointerId, &ppi)) { ++g_pen_msgs; raw_pen_flags = static_cast(ppi.penFlags); raw_pen_mask = static_cast(ppi.penMask); raw_ptr_flags = static_cast(ppi.pointerInfo.pointerFlags); btn_change = static_cast(ppi.pointerInfo.ButtonChangeType); tilt_x = static_cast(ppi.tiltX); tilt_y = static_cast(ppi.tiltY); // Pressure: Win32 reports 0..1024 when PEN_MASK_PRESSURE is set. // Normalize to [0,1] for Dart. Also scan history for a non-zero sample // (some drivers zero the tip sample while history has the real value). if (ppi.penMask & PEN_MASK_PRESSURE) { pressure_valid = 1; pressure = static_cast(ppi.pressure) / 1024.0; if (pressure < 0.0) pressure = 0.0; if (pressure > 1.0) pressure = 1.0; } const bool barrel = (ppi.penFlags & PEN_FLAG_BARREL) || (ppi.pointerInfo.pointerFlags & POINTER_FLAG_SECONDBUTTON); const bool inverted = (ppi.penFlags & PEN_FLAG_INVERTED) != 0; const bool eraser = (ppi.penFlags & PEN_FLAG_ERASER) != 0; if (barrel) flags |= 1; if (inverted) flags |= 2; if (eraser) flags |= 4; g_ptr_flags_or |= raw_ptr_flags; g_pen_flags_or |= raw_pen_flags; g_pen_mask_or |= raw_pen_mask; if (btn_change != 0) g_btn_change_last = btn_change; const int ax = ppi.tiltX < 0 ? -ppi.tiltX : ppi.tiltX; const int ay = ppi.tiltY < 0 ? -ppi.tiltY : ppi.tiltY; if (ax > g_tilt_abs_max) g_tilt_abs_max = ax; if (ay > g_tilt_abs_max) g_tilt_abs_max = ay; POINTER_PEN_INFO history[32]; UINT32 hist_n = 32; if (GetPointerPenInfoHistory(pointerId, &hist_n, history)) { history_count = static_cast(hist_n); // Prefer the max pressure in the history window (smoother + avoids // a zero tip sample when mask says pressure is present). for (UINT32 i = 0; i < hist_n; ++i) { if (!(history[i].penMask & PEN_MASK_PRESSURE)) continue; pressure_valid = 1; double p = static_cast(history[i].pressure) / 1024.0; if (p > pressure) pressure = p; } if (pressure > 1.0) pressure = 1.0; } if (pressure > g_pressure_max_seen) { g_pressure_max_seen = static_cast(pressure); } } } if (message == WM_POINTERUP) { flags = 0; pressure = 0.0; pressure_valid = 0; } } flutter::EncodableMap payload{ {flutter::EncodableValue("flags"), flutter::EncodableValue(flags)}, {flutter::EncodableValue("tiltX"), flutter::EncodableValue(tilt_x)}, {flutter::EncodableValue("tiltY"), flutter::EncodableValue(tilt_y)}, {flutter::EncodableValue("pressure"), flutter::EncodableValue(pressure)}, {flutter::EncodableValue("pressureValid"), flutter::EncodableValue(pressure_valid)}, {flutter::EncodableValue("diagPtr"), flutter::EncodableValue(g_ptr_msgs)}, {flutter::EncodableValue("diagPen"), flutter::EncodableValue(g_pen_msgs)}, {flutter::EncodableValue("diagMouse"), flutter::EncodableValue(g_mouse_msgs)}, {flutter::EncodableValue("diagMsg"), flutter::EncodableValue(g_last_msg)}, {flutter::EncodableValue("rawPtrFlags"), flutter::EncodableValue(raw_ptr_flags)}, {flutter::EncodableValue("rawPenFlags"), flutter::EncodableValue(raw_pen_flags)}, {flutter::EncodableValue("rawPenMask"), flutter::EncodableValue(raw_pen_mask)}, {flutter::EncodableValue("btnChange"), flutter::EncodableValue(btn_change)}, {flutter::EncodableValue("orPtrFlags"), flutter::EncodableValue(g_ptr_flags_or)}, {flutter::EncodableValue("orPenFlags"), flutter::EncodableValue(g_pen_flags_or)}, {flutter::EncodableValue("orPenMask"), flutter::EncodableValue(g_pen_mask_or)}, {flutter::EncodableValue("btnChangeLast"), flutter::EncodableValue(g_btn_change_last)}, {flutter::EncodableValue("tiltAbsMax"), flutter::EncodableValue(g_tilt_abs_max)}, {flutter::EncodableValue("historyCount"), flutter::EncodableValue(history_count)}, {flutter::EncodableValue("pressureMaxSeen"), flutter::EncodableValue(static_cast(g_pressure_max_seen))}, }; g_pen_sink->Success(flutter::EncodableValue(payload)); }