-
Notifications
You must be signed in to change notification settings - Fork 739
Description
When a user double-clicks the mouse, v2_devellop raises duplicate click and double-click events. The drivers send 6 events instead of the expected 3-4:
Current (Incorrect) Behavior:
Button1Pressed
Button1Released
Button1Clicked ← First duplicate
Button1Pressed
Button1Released
Button1DoubleClicked ← Second duplicate
Expected Behavior:
Button1Pressed
Button1Released
Button1Released
Button1DoubleClicked
Root Cause
The mouse event processing pipeline has two layers that both attempt to synthesize click events from press/release events:
- Platform Drivers (WindowsDriver, CursesDriver, NetDriver) - Some drivers (especially WindowsDriver) synthesize their own
ClickedandDoubleClickedevents - MouseInterpreter - Always synthesizes
ClickedandDoubleClickedevents fromPressed/Releasedpairs
This causes duplication because both layers are doing the same work.
Architecture
Current Flow
┌──────────────────┐
│ Platform Driver │
│ (Windows/Unix) │
└────────┬─────────┘
│ Sends: Pressed, Released, [Clicked], [DoubleClicked]
↓
┌──────────────────┐
│ MouseInterpreter │ ← Process(MouseEventArgs e)
│ │ - yield return e; (passes through original)
│ │ - Tracks button state
│ │ - Synthesizes Clicked/DoubleClicked from Pressed/Released
└────────┬─────────┘
│ Result: DUPLICATE events
↓
┌──────────────────┐
│ Application │
│ (receives 6 │
│ events) │
└──────────────────┘
Code Location
The duplication logic is split between:
1. MouseInterpreter (Terminal.Gui/Drivers/MouseInterpreter.cs)
public IEnumerable<MouseEventArgs> Process (MouseEventArgs e)
{
yield return e; // ← Passes through driver events (including any Clicked/DoubleClicked)
// For each mouse button
for (var i = 0; i < 4; i++)
{
_buttonStates [i].UpdateState (e, out int? numClicks);
if (numClicks.HasValue)
{
yield return RaiseClick (i, numClicks.Value, e); // ← ALSO synthesizes Clicked/DoubleClicked
}
}
}2. MouseButtonStateEx (Terminal.Gui/Drivers/MouseButtonStateEx.cs)
public void UpdateState (MouseEventArgs e, out int? numClicks)
{
// ...
if (Pressed)
{
// Click released
numClicks = ++_consecutiveClicks; // ← Synthesizes click on release
}
// ...
}3. Platform Drivers
The drivers may also synthesize these events:
- WindowsDriver: Receives
DOUBLE_CLICKflag from Windows API and translates toButton1DoubleClicked - CursesDriver: Only sends Pressed/Released (no native double-click detection)
- NetDriver: Only sends Pressed/Released via ANSI sequences (no native double-click detection)
Platform Behavior
Windows Console API
Native events from OS:
MOUSE_BUTTON_PRESSED→Button1PressedMOUSE_BUTTON_RELEASED→Button1ReleasedDOUBLE_CLICKflag →Button1DoubleClickedMOUSE_BUTTON_RELEASED→Button1Released
Unix/Linux (ncurses)
Native events from ncurses:
BUTTON1_PRESSED→Button1PressedBUTTON1_RELEASED→Button1ReleasedBUTTON1_PRESSED→Button1Pressed(second press)BUTTON1_RELEASED→Button1Released
Note: ncurses does NOT detect double-clicks natively.
.NET Console (ANSI/VT)
Native events from ANSI escape sequences:
CSI < 0;x;y M→Button1PressedCSI < 0;x;y m→Button1ReleasedCSI < 0;x;y M→Button1Pressed(second press)CSI < 0;x;y m→Button1Released
Note: ANSI sequences do NOT include click/double-click information.
Impact
This affects any code that handles mouse clicks:
view.MouseEvent += (s, e) =>
{
if (e.Flags.HasFlag(MouseFlags.Button1Clicked))
{
// This fires TWICE on a double-click!
DoSomething();
}
if (e.Flags.HasFlag(MouseFlags.Button1DoubleClicked))
{
// This fires TWICE on a double-click!
DoSomethingElse();
}
};Or when using mouse bindings:
MouseBindings.Add(MouseFlags.Button1Clicked, Command.Activate);
// Command.Activate gets invoked twice on double-clickProposed Solution
Option 1: Drivers Send Only Pressed/Released (Recommended)
Have all drivers send ONLY Pressed and Released events, and let MouseInterpreter handle click synthesis uniformly:
- Strip out any
Clicked/DoubleClicked/TripleClickedevent generation from all drivers - Drivers only send:
Pressed,Released,Moved,Wheel*events MouseInterpretersynthesizes allClicked/DoubleClicked/TripleClickedevents
Pros:
- Consistent behavior across all platforms
- Single source of truth for click detection
- Easier to maintain and test
Cons:
- Loses native platform double-click detection on Windows (but we can use platform timing)
Option 2: Skip Processing of Pre-Synthesized Events
Modify MouseInterpreter.Process() to skip processing if the driver already sent click events:
public IEnumerable<MouseEventArgs> Process (MouseEventArgs e)
{
yield return e;
// Don't re-process if driver already sent a click/double-click event
if (e.IsSingleDoubleOrTripleClicked)
{
yield break;
}
// For each mouse button
for (var i = 0; i < 4; i++)
{
_buttonStates [i].UpdateState (e, out int? numClicks);
// ...
}
}Pros:
- Preserves native platform double-click detection on Windows
- Less invasive change
Cons:
- Inconsistent behavior across platforms
- More complex logic
- Drivers and MouseInterpreter stay coupled
Recommendation
Option 1 is recommended because:
- It provides consistent behavior across all platforms
- It simplifies the codebase (single source of truth)
- It's easier to test and maintain
- The MouseInterpreter already has the infrastructure for accurate click detection (timing, position tracking)
Future Consideration: Windows Driver ANSI Mouse Support
The Windows driver currently uses native Win32 API (ReadConsoleInput()) for mouse input regardless of the IsLegacyConsole setting, while Unix/Linux drivers use ANSI escape sequences. This architectural difference complicates cross-platform testing and maintenance.
Potential Benefits of ANSI Mouse Input on Windows:
- Unified codebase: Same mouse handling logic across all platforms
- Simplified testing: Mock ANSI sequences instead of Win32
INPUT_RECORDstructs - Consistency: Already using ANSI for keyboard input when
IsLegacyConsole = false - Standards-based: VT100/xterm mouse protocol is well-documented
Trade-offs:
- ANSI parsing overhead (string/regex vs. binary structs) - likely negligible for typical mouse event rates (<100/sec)
- Loss of native Windows double-click timing - MouseInterpreter already handles this uniformly
- Requires Windows 10+ with VT support enabled (already a requirement for non-legacy mode)
This change would complement Option 1 by ensuring all drivers provide identical mouse event streams (Pressed/Released only), with MouseInterpreter as the sole authority for click synthesis. Worth investigating after resolving the current duplication issue.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status