Hello,

here are the mouse-related fixes I was discussing earlier.
See commit messages for more details.

Is there a real version control system for ncurses or are you working
will tarballs and patch series only?

These fixes will probably take many years to become widespread on many
installations. Unfortunately, I did not find workarounds that will fully
solve the issues. You should not reset the mouse mask unless you have to to
any avoid additional bogus BUTTON3_PRESSED events when scrolling
horizontally.
Also, you could ignore BUTTON2_PRESSED and react only to BUTTON2_RELEASED.
You will inevitably loose the separation between PRESSED and RELEASED events
even on terminal emulators that do deliver them separately.

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