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: