Hello,

as promised here are my patches for the mouse-related bugs I was observing.

Refer to the commit messages for details.

I haven't found solid workarounds for these bugs on existing ncurses
versions. (I guess it will take years for any fixes to become widespread
on installations in the wild.)
You shouldn't reset the mousemask() unless you have to to reduce the number
of bogus BUTTON3_PRESSED events delivered. The middle click issue can be sort
of solved by ignoring BUTTON2_PRESSED and reacting only to BUTTON2_RELEASED.

Please tell me if you apply these patches.

Best regards,
Robin

-- 
@ii._._.*.._+__.+_+.+...+.+.++..+*+.+._.+...*_*.*.__+__._._.++..+_*.++__+__
.+_..*...+.+_+__.+._.+...*_+_+__._ ...*_ +.+._.+.._+*+_+__._._ .+_..+.+***_
. *_+_+__.+.*.++..+_+.*.__+_ _.+...*_*_+__.++*.+...++..+* +.+.._+__._+_.+..
.++..+*_.*...+*+.+.*_ +*+i2^rj.u#__%uu#_.%uu#_+%uu#_*!+!0a"t1010^t^c^c'0a^#
1010"=d'0a-100000"=d'0auuqq*100+q[_^euu]uq-rq:^/100@oo,+,+,+oqq^t0uq@o*+*!!
From 7a335729a68f24192be14c73f1b00f3bc69702df Mon Sep 17 00:00:00 2001
From: Robin Haberkorn <[email protected]>
Date: Wed, 10 Sep 2025 00:25:17 +0300
Subject: [PATCH 1/2] fixed horizontal mouse scroll events

These can be delivered by Xterm and GNOME Terminal
in SGR mouse mode.
They were erroneously reported as BUTTON3_PRESSED.
---
 ncurses/base/lib_mouse.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/ncurses/base/lib_mouse.c b/ncurses/base/lib_mouse.c
index 65cb005f..06748890 100644
--- a/ncurses/base/lib_mouse.c
+++ b/ncurses/base/lib_mouse.c
@@ -984,7 +984,12 @@ handle_wheel(SCREEN *sp, MEVENT * eventp, int button, int wheel)
 	}
 	break;
     case 2:
-	PRESS_POSITION(3);
+	if (wheel) {
+	    /* Ignore this event as it is not a true press of the button */
+	    eventp->bstate = REPORT_MOUSE_POSITION;
+	} else {
+	    PRESS_POSITION(3);
+	}
 	break;
     default:
 	/*
-- 
2.50.1

From 315c4b332bee8535077a9dfca8e7d5d5ba547ec4 Mon Sep 17 00:00:00 2001
From: Robin Haberkorn <[email protected]>
Date: Wed, 10 Sep 2025 01:36:36 +0300
Subject: [PATCH 2/2] fixed processing of multiple simultaneous mouse events,
 ie. middle clicks in certain terminal emulators

* At least on Xterm and simpleterm (st) pressing the middle mouse button
  sends a PRESSED and RELEASED event immediately when releasing the button.
  These will have to be read with two getmouse() calls.
  This is a rare case where the _mouse_events queue is actually made use of.
* Unfortunately, the queue was rather a stack. We need separate
  read and write pointers for a proper circular queue.
  The order of events returned by getmouse() was therefore reversed.
* Also, if you mask for CLICKED events, but disabled click detection
  via mouseinterval(0), _nc_mouse_parse() would kick in and synthesize CLICKED
  events when the queue actually has more than 1 entry as happens when pressing
  the middle button.
  This had to be guarded against explicitly.
---
 ncurses/base/lib_mouse.c | 53 +++++++++++++++++++++-------------------
 ncurses/curses.priv.h    |  3 ++-
 2 files changed, 30 insertions(+), 26 deletions(-)

diff --git a/ncurses/base/lib_mouse.c b/ncurses/base/lib_mouse.c
index 06748890..8e9cce74 100644
--- a/ncurses/base/lib_mouse.c
+++ b/ncurses/base/lib_mouse.c
@@ -786,7 +786,7 @@ _nc_mouse_init(SCREEN *sp)
 
 	    TR(MY_TRACE, ("set _mouse_initialized"));
 
-	    sp->_mouse_eventp = FirstEV(sp);
+	    sp->_mouse_readp = sp->_mouse_writep = FirstEV(sp);
 	    for (i = 0; i < EV_MAX; i++)
 		Invalidate(sp->_mouse_events + i);
 
@@ -806,7 +806,7 @@ _nc_mouse_init(SCREEN *sp)
 static bool
 _nc_mouse_event(SCREEN *sp)
 {
-    MEVENT *eventp = sp->_mouse_eventp;
+    MEVENT *eventp = sp->_mouse_writep;
     bool result = FALSE;
 
     (void) eventp;
@@ -874,7 +874,7 @@ _nc_mouse_event(SCREEN *sp)
 		eventp->z = 0;
 
 		/* bump the next-free pointer into the circular list */
-		sp->_mouse_eventp = NEXT(eventp);
+		sp->_mouse_writep = NEXT(eventp);
 		result = TRUE;
 		break;
 	    }
@@ -899,7 +899,7 @@ _nc_mouse_event(SCREEN *sp)
 	    }
 
 	    /* bump the next-free pointer into the circular list */
-	    sp->_mouse_eventp = eventp = NEXT(eventp);
+	    sp->_mouse_writep = eventp = NEXT(eventp);
 	    result = TRUE;
 	}
 	break;
@@ -921,7 +921,7 @@ _nc_mouse_event(SCREEN *sp)
 	    }
 
 	    /* bump the next-free pointer into the circular list */
-	    sp->_mouse_eventp = eventp = NEXT(eventp);
+	    sp->_mouse_writep = eventp = NEXT(eventp);
 	    result = TRUE;
 	}
 	break;
@@ -1360,7 +1360,7 @@ _nc_mouse_inline(SCREEN *sp)
 /* mouse report received in the keyboard stream -- parse its info */
 {
     bool result = FALSE;
-    MEVENT *eventp = sp->_mouse_eventp;
+    MEVENT *eventp = sp->_mouse_writep;
 
     TR(MY_TRACE, ("_nc_mouse_inline() called"));
 
@@ -1385,7 +1385,7 @@ _nc_mouse_inline(SCREEN *sp)
 	    (long) IndexEV(sp, eventp)));
 
 	/* bump the next-free pointer into the circular list */
-	sp->_mouse_eventp = NEXT(eventp);
+	sp->_mouse_writep = NEXT(eventp);
 
 	if (!result) {
 	    /* If this event is from a wheel-mouse, treat it like position
@@ -1506,7 +1506,7 @@ static bool
 _nc_mouse_parse(SCREEN *sp, int runcount)
 /* parse a run of atomic mouse events into a gesture */
 {
-    MEVENT *eventp = sp->_mouse_eventp;
+    MEVENT *eventp = sp->_mouse_writep;
     MEVENT *next, *ep;
     MEVENT *first_valid = NULL;
     MEVENT *first_invalid = NULL;
@@ -1517,6 +1517,10 @@ _nc_mouse_parse(SCREEN *sp, int runcount)
 
     TR(MY_TRACE, ("_nc_mouse_parse(%d) called", runcount));
 
+    if (!sp->_maxclick)
+	return sp->_mouse_readp && ValidEvent(sp->_mouse_readp) &&
+	        ((sp->_mouse_readp->bstate & sp->_mouse_mask) != 0);
+
     /*
      * When we enter this routine, the event list next-free pointer
      * points just past a run of mouse events that we know were separated
@@ -1552,6 +1556,7 @@ _nc_mouse_parse(SCREEN *sp, int runcount)
 	Invalidate(ep);
 	ep = NEXT(ep);
     }
+    assert(ep == sp->_mouse_readp);
 
 #ifdef TRACE
     if (USE_TRACEF(TRACE_IEVENT)) {
@@ -1728,7 +1733,7 @@ _nc_mouse_parse(SCREEN *sp, int runcount)
     if (first_invalid == NULL) {
 	first_invalid = eventp;
     }
-    sp->_mouse_eventp = first_invalid;
+    sp->_mouse_writep = first_invalid;
 
 #ifdef TRACE
     if (first_valid != NULL) {
@@ -1835,36 +1840,34 @@ NCURSES_EXPORT(int)
 NCURSES_SP_NAME(getmouse) (NCURSES_SP_DCLx MEVENT * aevent)
 {
     int result = ERR;
-    MEVENT *eventp;
+    MEVENT *readp;
 
     T((T_CALLED("getmouse(%p,%p)"), (void *) SP_PARM, (void *) aevent));
 
     if ((aevent != NULL) &&
 	(SP_PARM != NULL) &&
 	(SP_PARM->_mouse_type != M_NONE) &&
-	(eventp = SP_PARM->_mouse_eventp) != NULL) {
-	/* compute the current-event pointer */
-	MEVENT *prev = PREV(eventp);
-
+	(readp = SP_PARM->_mouse_readp) != NULL) {
 	/*
 	 * Discard events not matching mask (there could be still some if
 	 * _nc_mouse_parse was not called, e.g., when _nc_mouse_inline returns
 	 * false).
 	 */
-	while (ValidEvent(prev) && (!(prev->bstate & SP_PARM->_mouse_mask2))) {
-	    Invalidate(prev);
-	    prev = PREV(prev);
+	while (readp != SP_PARM->_mouse_writep &&
+	       (!ValidEvent(readp) || !(readp->bstate & SP_PARM->_mouse_mask2))) {
+	    Invalidate(readp);
+	    readp = NEXT(readp);
 	}
-	if (ValidEvent(prev)) {
+	if (readp != SP_PARM->_mouse_writep && ValidEvent(readp)) {
 	    /* copy the event we find there */
-	    *aevent = *prev;
+	    *aevent = *readp;
 
 	    TR(TRACE_IEVENT, ("getmouse: returning event %s from slot %ld",
-			      _nc_tracemouse(SP_PARM, prev),
-			      (long) IndexEV(SP_PARM, prev)));
+			      _nc_tracemouse(SP_PARM, readp),
+			      (long) IndexEV(SP_PARM, readp)));
 
-	    Invalidate(prev);	/* so the queue slot becomes free */
-	    SP_PARM->_mouse_eventp = prev;
+	    Invalidate(readp);	/* so the queue slot becomes free */
+	    SP_PARM->_mouse_readp = NEXT(readp);
 	    result = OK;
 	} else {
 	    /* Reset the provided event */
@@ -1897,13 +1900,13 @@ NCURSES_SP_NAME(ungetmouse) (NCURSES_SP_DCLx MEVENT * aevent)
 
     if (aevent != NULL &&
 	SP_PARM != NULL &&
-	(eventp = SP_PARM->_mouse_eventp) != NULL) {
+	(eventp = SP_PARM->_mouse_writep) != NULL) {
 
 	/* stick the given event in the next-free slot */
 	*eventp = *aevent;
 
 	/* bump the next-free pointer into the circular list */
-	SP_PARM->_mouse_eventp = NEXT(eventp);
+	SP_PARM->_mouse_writep = NEXT(eventp);
 
 	/* push back the notification event on the keyboard queue */
 	result = NCURSES_SP_NAME(ungetch) (NCURSES_SP_ARGx KEY_MOUSE);
diff --git a/ncurses/curses.priv.h b/ncurses/curses.priv.h
index 4fed9b97..9536cc51 100644
--- a/ncurses/curses.priv.h
+++ b/ncurses/curses.priv.h
@@ -1152,7 +1152,8 @@ typedef struct screen {
 	MouseFormat	_mouse_format;	/* type of xterm mouse protocol */
 	NCURSES_CONST char *_mouse_xtermcap; /* string to enable/disable mouse */
 	MEVENT		_mouse_events[EV_MAX];	/* hold the last mouse event seen */
-	MEVENT		*_mouse_eventp;	/* next free slot in event queue */
+	MEVENT		*_mouse_readp;	/* read pointer into event queue */
+	MEVENT		*_mouse_writep;	/* write pointer into event queue */
 
 	/*
 	 * These are data that support the proper handling of the panel stack on an
-- 
2.50.1

Attachment: signature.asc
Description: PGP signature

Reply via email to