[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#954730: xterm: two press events sent for single press/release of mouse buttons 6 and 7



Package: xterm
Version: 353-1
Severity: normal

Dear Maintainer,

While developing a console based application using ncurses running under
an exterm, I noticed I was getting double events for the side-scrolling
buttons on my mouse (Elecom Huge: 12 buttons (including side-scrolling
on the wheel)). Upon investigation, I found that while X itself is
sending a press event followed immediately by a release event for
buttons 6 and 7, I found that my program was recieving two press events
for each press of either button 6 or 7
\x27[<0;14;24M      <- button 1 pressed
\x27[<0;14;24m      <- button 1 preleased
\x27[<66;14;24M     <- button 6 presssed ONCE
\x27[<66;14;24M
\x27[<67;14;24M     <- button 7 presssed ONCE
\x27[<67;14;24M
\x27[<65;14;24M     <- one click turning scroll one way
\x27[<64;14;24M     <- one click turning scroll the other way way

It does not matter if 1000 or 1003 mode is used, or if 1006 is on or off
(the above is 1003 + 1006).

I expect either a press followed by a release (M then m in sgr (1006)
mode, or just a single press as for buttons 4 and 5 (though I think I
would prefer press and release).

I have attached the test program used to produce the above.

-- System Information:
Debian Release: bullseye/sid
  APT prefers unstable
  APT policy: (500, 'unstable')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 5.4.0-4-amd64 (SMP w/8 CPU cores)
Kernel taint flags: TAINT_PROPRIETARY_MODULE, TAINT_OOT_MODULE, TAINT_UNSIGNED_MODULE
Locale: LANG=en_AU.UTF-8, LC_CTYPE=en_AU.UTF-8 (charmap=UTF-8) (ignored: LC_ALL set to en_US.utf8), LANGUAGE=en_AU:en (charmap=UTF-8) (ignored: LC_ALL set to en_US.utf8)
Shell: /bin/sh linked to /bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

Versions of packages xterm depends on:
ii  libc6           2.29-10
ii  libfontconfig1  2.13.1-2+b1
ii  libfreetype6    2.10.1-2
ii  libice6         2:1.0.9-2
ii  libtinfo6       6.1+20191019-1
ii  libutempter0    1.1.6-4
ii  libx11-6        2:1.6.8-1
ii  libxaw7         2:1.0.13-1+b2
ii  libxext6        2:1.3.3-1+b2
ii  libxft2         2.3.2-2
ii  libxinerama1    2:1.1.4-2
ii  libxmu6         2:1.1.2-2+b3
ii  libxpm4         1:3.5.12-1
ii  libxt6          1:1.1.5-1+b3
ii  xbitmaps        1.1.1-2

Versions of packages xterm recommends:
ii  x11-utils  7.7+4

Versions of packages xterm suggests:
pn  xfonts-cyrillic  <none>

-- no debconf information
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

#define MOUSE_MOVES_ON "\033[?1003h"
#define MOUSE_MOVES_OFF "\033[?1003l"
#define SGR_ON "\033[?1006h"
#define SGR_OFF "\033[?1006l"
#define UTF8_ON "\033[?1005h"
#define UTF8_OFF "\033[?1005h"

struct termios save_termios;

void tty_raw(int fd)
{
	struct termios termios;

	tcgetattr (fd, &save_termios);

	termios = save_termios;
	termios.c_lflag &= ~(ECHO | ICANON | ISIG);
	termios.c_cc[VMIN] = 1;
	termios.c_cc[VTIME] = 0;
	tcsetattr (fd, TCSAFLUSH, &termios);
}

void tty_reset(int fd)
{
	tcsetattr (fd, TCSAFLUSH, &save_termios);
}

typedef enum {
	st_idle,
	st_escape,
	st_csi,
	st_mouse1,
	st_mouse2,
	st_mouse3,
} state_t;

state_t state;
unsigned char mouse_chars[3];
int mouse_buttons;

void parse_mouse (void)
{
	int         x = mouse_chars[1] - '!';	// want 0-based coords
	int         y = mouse_chars[2] - '!';	// want 0-based coords
	int         c = mouse_chars[0] - ' ';
	int         b = c & 3;
	int         m = c & 0x20;				// motion + lowest button
	int         e = c & 0xc0;				// extended buttons
	int         shift = (c >> 2) & 7;		// shift state

	if (m) {
		b = -1;
	} else {
		// xterm doesn't send release events for buttons 4-7
		mouse_buttons &= ~(0x7c);
		if (!e && b == 3) {
			mouse_buttons = 0;		// we don't know which one :P
			b = -1;
		} else {
			if (e) {
				b += 4 * (e >> 6) - 1;
			}
			mouse_buttons |= 1 << b;
		}
	}
	printf ("%3d %3d %02x %03x %02x %2d\r", x, y, c, mouse_buttons, e, b + 1);
	fflush (stdout);
}

void process_char (int ch)
{
	if (ch == 0x1b) {
		// always reset if escape is seen, may be a desync
		state = st_escape;
	} else {
		switch (state) {
			case st_idle:
				break;
			case st_escape:
				if (ch == '[') {
					state = st_csi;
				}
				break;
			case st_csi:
				if (ch == 'M') {
					state = st_mouse1;
					memset (mouse_chars, 0, sizeof (mouse_chars));
				}
				break;
			case st_mouse1:
				mouse_chars[0] = ch;
				state = st_mouse2;
				break;
			case st_mouse2:
				mouse_chars[1] = ch;
				state = st_mouse3;
				break;
			case st_mouse3:
				mouse_chars[2] = ch;
				parse_mouse();
				state = st_idle;
				break;
		}
		//printf("state %d\n", state);
	}
}

void
print_bytes (const char *str, int len)
{
	char        c;

	if (!str)
		return;
	while (len-- > 0 && (c = *str++)) {
		switch (c) {
			case '\a':
				fputs ("\\a", stdout);
				break;
			case '\b':
				fputs ("\\b", stdout);
				break;
			case '\f':
				fputs ("\\f", stdout);
				break;
			case '\n':
				fputs ("\\n", stdout);
				break;
			case '\r':
				fputs ("\\r", stdout);
				break;
			case '\t':
				fputs ("\\t", stdout);
				break;
			case '\\':
				fputs ("\\\\", stdout);
				break;
			case '\'':
				fputs ("\\'", stdout);
				break;
			case '\"':
				fputs ("\\\"", stdout);
				break;
			default:
				if (c >= 127 || c < 32)
					printf ("\\x%02d", (unsigned char) c);
				else
					putc (c, stdout);
				break;
		}
	}
	puts("");
	fflush (stdout);
}

int main (void)
{
	char        buf[256];
	int         len;

	tty_raw (0);
	write(1, MOUSE_MOVES_ON, sizeof (MOUSE_MOVES_ON) - 1);
	write(1, SGR_ON, sizeof (SGR_ON) - 1);
	while (1) {
		len = read(0, buf, sizeof (buf));
		//for (int i = 0; i < len; i++) {
		//	process_char (buf[i]);
		//}
		print_bytes (buf, len);
		if (buf[0] == 'x') {
			break;
		}
	}
	write(1, SGR_OFF, sizeof (SGR_OFF) - 1);
	write(1, MOUSE_MOVES_OFF, sizeof (MOUSE_MOVES_OFF) - 1);
	tty_reset (0);
}

Reply to: