Support mouse buttons 4 + 5 (aka "side buttons") (#1076, #1068)

This work is based on PR 1068 (patch by @CendioHalim) and
extended to store button status (4,5) in Fl::event_state() like
it's done for other mouse buttons (1-3).

Changes:
- new symbol: FL_BUTTON4 = side button 1 = "back"
- new symbol: FL_BUTTON5 = side button 2 = "forward"
- modified  : FL_BUTTONS now includes bits for two side buttons

Note: the status of these new buttons is not maintained by X11,
  therefore we need to maintain them in internal variables for
  this platform.
This commit is contained in:
Albrecht Schlosser 2024-09-26 17:59:52 +02:00 committed by Albrecht Schlosser
parent 3fbd4f944f
commit 4f4a9be15b
6 changed files with 202 additions and 83 deletions

View File

@ -551,6 +551,8 @@ enum Fl_Callback_Reason {
#define FL_LEFT_MOUSE 1 ///< The left mouse button
#define FL_MIDDLE_MOUSE 2 ///< The middle mouse button
#define FL_RIGHT_MOUSE 3 ///< The right mouse button
#define FL_BACK_MOUSE 4 ///< The back mouse button (side button 1)
#define FL_FORWARD_MOUSE 5 ///< The forward mouse button (side button 2)
/**@}*/ // group: Mouse Buttons
@ -575,11 +577,17 @@ enum Fl_Callback_Reason {
// correct for XFree86
#define FL_SCROLL_LOCK 0x00800000 ///< The scroll lock is on
// correct for XFree86
// Mouse buttons
#define FL_BUTTON1 0x01000000 ///< Mouse button 1 is pushed (L)
#define FL_BUTTON2 0x02000000 ///< Mouse button 2 is pushed (M)
#define FL_BUTTON3 0x04000000 ///< Mouse button 3 is pushed (R)
#define FL_BUTTONS 0x07000000 ///< Any mouse button (1-3) is pushed
#define FL_BUTTON(n) (0x00800000<<(n)) ///< Mouse button n (n > 0) is pushed
#define FL_BUTTON4 0x08000000 ///< Mouse button 4 is pushed (BACK)
#define FL_BUTTON5 0x10000000 ///< Mouse button 5 is pushed (FORWARD)
#define FL_BUTTONS 0x1f000000 ///< Bitmask: any mouse button (1-5) is pushed
#define FL_BUTTON(n) (0x00800000<<(n)) ///< Mouse button n (n = 1..5) is pushed,
///< *undefined* if n outside 1..5
#define FL_KEY_MASK 0x0000ffff ///< All keys are 16 bit for now
// FIXME: Unicode needs 21 bits!

77
FL/Fl.H
View File

@ -705,40 +705,51 @@ public:
This returns garbage if the most recent event was not a FL_PUSH or FL_RELEASE event.
\retval FL_LEFT_MOUSE
\retval FL_MIDDLE_MOUSE
\retval FL_RIGHT_MOUSE.
\see Fl::event_buttons()
\retval FL_RIGHT_MOUSE
\retval FL_BACK_MOUSE
\retval FL_FORWARD_MOUSE.
\see Fl::event_buttons(), Fl::event_state()
*/
static int event_button() {return e_keysym-FL_Button;}
static int event_button() { return e_keysym - FL_Button; }
/**
Returns the keyboard and mouse button states of the last event.
This is a bitfield of what shift states were on and what mouse buttons
were held down during the most recent event.
\note FLTK platforms differ in what Fl::event_state() returns when it is called
while a modifier key or mouse button is being pressed or released.
- Under X11 and Wayland, Fl::event_state() indicates the state of the modifier keys and
mouse buttons just \b prior to the event. Thus, during the \c FL_KEYDOWN event generated
when pressing the shift key, for example, the \c FL_SHIFT bit of event_state() is 0 and
becomes 1 only at the next event which can be any other event, including e.g. \c FL_MOVE.
- Under other platforms the reported state of modifier keys or mouse buttons includes that
of the key or button being pressed or released.
- Fl::event_state() returns the same value under all platforms when it's called while a
non-modifier key (e.g. a letter or function key) is being pressed or released.
- X servers do not agree on shift states, and \c FL_NUM_LOCK, \c FL_META, and \c FL_SCROLL_LOCK
may not work.
- The values were selected to match the XFree86 server on Linux.
\note This inconsistency \b may be fixed (on X11 and Wayland) in a later release.
The legal event state bits are:
- FL_SHIFT
- FL_CAPS_LOCK
- FL_CTRL
- FL_ALT
- FL_NUM_LOCK
- FL_META
- FL_SCROLL_LOCK
- FL_BUTTON1
- FL_BUTTON2
- FL_BUTTON3
\note FLTK platforms differ in what Fl::event_state() returns when it is called while a modifier key
is being pressed or released.
Under X11 and Wayland, Fl::event_state() indicates the state of the modifier keys just \b prior to the event.
Thus, during the FL_KEYDOWN event generated when pressing the shift key, for example, the FL_SHIFT bit of event_state()
is 0 and becomes 1 only at the next event (which can be another FL_KEYDOWN, FL_DRAG or FL_KEYUP).
Under other platforms, the reported state of modifier keys includes that of the key being pressed or released.
Notice that Fl::event_state() returns the same value under all platforms when it's called while a non-modifier key
(e.g., a letter, a function key) is being pressed or released.
X servers do not agree on shift states, and FL_NUM_LOCK, FL_META, and
FL_SCROLL_LOCK may not work. The values were selected to match the
XFree86 server on Linux.
| Device | State Bit | Key or Button | Since |
|----------|----------------|-------------------------|-------|
| Keyboard | FL_SHIFT | Shift | |
| Keyboard | FL_CAPS_LOCK | Caps Lock | |
| Keyboard | FL_CTRL | Ctrl | |
| Keyboard | FL_ALT | Alt | |
| Keyboard | FL_NUM_LOCK | Num Lock | |
| Keyboard | FL_META | Meta, e.g. "Windows" | |
| Keyboard | FL_SCROLL_LOCK | Scroll Lock | |
| Mouse | FL_BUTTON1 | left button | |
| Mouse | FL_BUTTON2 | middle button | |
| Mouse | FL_BUTTON3 | right button | |
| Mouse | FL_BUTTON4 | side button 1 (back) | 1.4.0 |
| Mouse | FL_BUTTON5 | side button 2 (forward) | 1.4.0 |
*/
static int event_state() {return e_state;}
@ -1267,7 +1278,7 @@ public:
time of the event. During an FL_RELEASE event, the state
of the released button will be 0. To find out, which button
caused an FL_RELEASE event, you can use Fl::event_button() instead.
\return a bit mask value like { [FL_BUTTON1] | [FL_BUTTON2] | [FL_BUTTON3] }
\return a bit mask value like { [FL_BUTTON1] | [FL_BUTTON2] | ... | [FL_BUTTON5] }
*/
static int event_buttons() {return e_state & FL_BUTTONS;}
/**
@ -1276,15 +1287,25 @@ public:
*/
static int event_button1() {return e_state & FL_BUTTON1;}
/**
Returns non-zero if button 2 is currently held down.
Returns non-zero if mouse button 2 is currently held down.
For more details, see Fl::event_buttons().
*/
static int event_button2() {return e_state & FL_BUTTON2;}
/**
Returns non-zero if button 3 is currently held down.
Returns non-zero if mouse button 3 is currently held down.
For more details, see Fl::event_buttons().
*/
static int event_button3() {return e_state & FL_BUTTON3;}
/**
Returns non-zero if mouse button 4 is currently held down.
For more details, see Fl::event_buttons().
*/
static int event_button4() {return e_state & FL_BUTTON4;}
/**
Returns non-zero if mouse button 5 is currently held down.
For more details, see Fl::event_buttons().
*/
static int event_button5() {return e_state & FL_BUTTON5;}
/** @} */
/**

View File

@ -1046,7 +1046,7 @@ static void cocoaMagnifyHandler(NSEvent *theEvent)
*/
static void cocoaMouseHandler(NSEvent *theEvent)
{
static int keysym[] = { 0, FL_Button+1, FL_Button+3, FL_Button+2 };
static int keysym[] = { 0, FL_Button+1, FL_Button+3, FL_Button+2, FL_Button+4, FL_Button+5 };
static int px, py;
fl_lock_function();
@ -1070,12 +1070,16 @@ static void cocoaMouseHandler(NSEvent *theEvent)
if (btn == 1) Fl::e_state |= FL_BUTTON1;
else if (btn == 3) Fl::e_state |= FL_BUTTON2;
else if (btn == 2) Fl::e_state |= FL_BUTTON3;
else if (btn == 4) Fl::e_state |= FL_BUTTON4;
else if (btn == 5) Fl::e_state |= FL_BUTTON5;
}
else if (etype == NSEventTypeLeftMouseUp || etype == NSEventTypeRightMouseUp ||
etype == NSEventTypeOtherMouseUp) {
if (btn == 1) Fl::e_state &= ~FL_BUTTON1;
else if (btn == 3) Fl::e_state &= ~FL_BUTTON2;
else if (btn == 2) Fl::e_state &= ~FL_BUTTON3;
else if (btn == 4) Fl::e_state &= ~FL_BUTTON4;
else if (btn == 5) Fl::e_state &= ~FL_BUTTON5;
}
switch ( etype ) {

View File

@ -1044,9 +1044,12 @@ static int mouse_event(Fl_Window *window, int what, int button,
if (wParam & MK_SHIFT) state |= FL_SHIFT;
if (wParam & MK_CONTROL) state |= FL_CTRL;
#endif
if (wParam & MK_LBUTTON) state |= FL_BUTTON1;
if (wParam & MK_MBUTTON) state |= FL_BUTTON2;
if (wParam & MK_RBUTTON) state |= FL_BUTTON3;
if (wParam & MK_LBUTTON) state |= FL_BUTTON1; // left
if (wParam & MK_MBUTTON) state |= FL_BUTTON2; // right
if (wParam & MK_RBUTTON) state |= FL_BUTTON3; // middle
if (wParam & MK_XBUTTON1) state |= FL_BUTTON4; // side button 1 (back)
if (wParam & MK_XBUTTON2) state |= FL_BUTTON5; // side button 2 (forward)
Fl::e_state = state;
switch (what) {
@ -1348,6 +1351,21 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar
case WM_RBUTTONUP:
mouse_event(window, 2, 3, wParam, lParam);
return 0;
case WM_XBUTTONDOWN: {
int xbutton = GET_XBUTTON_WPARAM(wParam) == XBUTTON1 ? 4 : 5;
mouse_event(window, 0, xbutton, wParam, lParam);
return 0;
}
case WM_XBUTTONDBLCLK: {
int xbutton = GET_XBUTTON_WPARAM(wParam) == XBUTTON1 ? 4 : 5;
mouse_event(window, 1, xbutton, wParam, lParam);
return 0;
}
case WM_XBUTTONUP: {
int xbutton = GET_XBUTTON_WPARAM(wParam) == XBUTTON1 ? 4 : 5;
mouse_event(window, 2, xbutton, wParam, lParam);
return 0;
}
case WM_MOUSEMOVE:
#ifdef USE_TRACK_MOUSE

View File

@ -987,12 +987,25 @@ static ulong ptime;
// Button1Mask, Button2Mask, Button3Mask, Button4Mask, Button5Mask // 1<<8 .. 1<<12
// };
//
// Note: some more (undefined?) state bits *can* be set if the user uses a keyboard
// Note: some more (undefined?) state bits *may* be set if the user uses a keyboard
// other than the primary one (the top-most in keyboard settings). Therefore we must
// take care not to use these undefined bits. These undefined bits will be set in
// Fl::event_state() though: for backwards compatibility and transparency.
// See definition of FL_BUTTONS in FL/Enumerations.H: only three "sticky" mouse
// buttons as of July 2024.
// take care not to use these undefined bits (found by accident).
// These undefined bits are ignored and not set in Fl::event_state(), otherwise we
// might overwrite other valid bits (since FLTK 1.4.0, Sep 2024 or later).
// See definition of FL_BUTTONS in FL/Enumerations.H:
// there are only five "sticky" mouse buttons as of Sep 27, 2024.
static unsigned int xbutton_state = 0; // extended button state (back, forward)
// Define the state bits we're interested in for Fl::event_state().
// Note that we ignore Button4Mask and Button5Mask (vertical scroll wheel).
// X11 doesn't define masks for Button6 and Button7 (horizontal scroll wheel)
// and any higher button numbers.
static const unsigned int event_state_mask =
ShiftMask | LockMask | ControlMask |
Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask |
Button1Mask | Button2Mask | Button3Mask;
static void set_event_xy(Fl_Window *win) {
# if FLTK_CONSOLIDATE_MOTION
@ -1006,7 +1019,7 @@ static void set_event_xy(Fl_Window *win) {
Fl::e_x = fl_xevent->xbutton.x/s;
Fl::e_y_root = fl_xevent->xbutton.y_root/s;
Fl::e_y = fl_xevent->xbutton.y/s;
Fl::e_state = fl_xevent->xbutton.state << 16;
Fl::e_state = ((fl_xevent->xbutton.state & event_state_mask) << 16) | xbutton_state;
fl_event_time = fl_xevent->xbutton.time;
# ifdef __sgi
// get the meta key off PC keyboards:
@ -2015,26 +2028,56 @@ int fl_handle(const XEvent& thisevent)
Fl::e_is_click = 0; }
break;
case ButtonPress:
Fl::e_keysym = FL_Button + xevent.xbutton.button;
// Mouse button "press" event:
// ---------------------------
// X11 uses special conventions for mouse "button" numbers:
// 1-3: standard mouse buttons left, middle, right in this order
// 4-5: scroll wheel up, down - not reflected in Fl::event_state()
// 6-7: scroll wheel left, right - not reflected in Fl::event_state()
// 8-9: side buttons back, forward - mapped to 4-5, see below
// Since X11 pseudo button numbers 4-7 are useless for Fl::event_state() we map
// real button numbers 8 and 9 to 4 and 5, respectively in FLTK's button numbers
// and in the event state (Fl::event_state()).
// Variable `xbutton_state` is used internally to store the status of the extra
// mouse buttons 4 (back) and 5 (forward) since X11 doesn't keep their status.
case ButtonPress: {
int mb = xevent.xbutton.button; // mouse button
if (mb < 1 || mb > 9) return 0; // unknown or unsupported button, ignore
// FIXME(?): here we set some event related variables although we *might*
// ignore an event sent by X because we don't know or want it. This may lead to
// inconsistencies in Fl::event_key(), Fl::event_state() and more (see set_event_xy).
// For now we ignore this fact though, it's likely that it never happens.
// Albrecht, Sep 27, 2024
Fl::e_keysym = 0; // init: not used (zero) for scroll wheel events
set_event_xy(window);
Fl::e_dx = Fl::e_dy = 0;
if (xevent.xbutton.button == Button4 && !Fl::event_shift()) {
Fl::e_dy = -1; // Up
if (mb == Button4 && !Fl::event_shift()) {
Fl::e_dy = -1; // up
event = FL_MOUSEWHEEL;
} else if (xevent.xbutton.button == Button5 && !Fl::event_shift()) {
Fl::e_dy = +1; // Down
} else if (mb == Button5 && !Fl::event_shift()) {
Fl::e_dy = +1; // down
event = FL_MOUSEWHEEL;
} else if (xevent.xbutton.button == 6 || (xevent.xbutton.button == Button4 && Fl::event_shift())) {
Fl::e_dx = -1; // Left
} else if (mb == 6 || (mb == Button4 && Fl::event_shift())) {
Fl::e_dx = -1; // left
event = FL_MOUSEWHEEL;
} else if (xevent.xbutton.button == 7 || (xevent.xbutton.button == Button5 && Fl::event_shift())) {
Fl::e_dx = +1; // Right
} else if (mb == 7 || (mb == Button5 && Fl::event_shift())) {
Fl::e_dx = +1; // right
event = FL_MOUSEWHEEL;
} else {
Fl::e_state |= (FL_BUTTON1 << (xevent.xbutton.button-1));
} else if (mb < 4 || mb > 7) { // real mouse *buttons*, not scroll wheel
if (mb > 7) // 8 = back, 9 = forward
mb -= 4; // map to 4 and 5, resp.
Fl::e_keysym = FL_Button + mb;
Fl::e_state |= (FL_BUTTON1 << (mb-1)); // set button state
if (mb == 4) xbutton_state |= FL_BUTTON4; // save extra button state internally
if (mb == 5) xbutton_state |= FL_BUTTON5; // save extra button state internally
event = FL_PUSH;
checkdouble();
} else { // unknown button or shift combination
return 0;
}
#if FLTK_CONSOLIDATE_MOTION
@ -2042,6 +2085,38 @@ int fl_handle(const XEvent& thisevent)
#endif // FLTK_CONSOLIDATE_MOTION
in_a_window = true;
break;
} // ButtonPress
// Mouse button release event: for details see ButtonPress above
case ButtonRelease: {
int mb = xevent.xbutton.button; // mouse button
switch (mb) { // figure out which real button this is
case 1: // left
case 2: // middle
case 3: // right
break; // continue
case 8: // side button 1 (back)
case 9: // side button 2 (forward)
mb -= 4; // map to 4 and 5, respectively
break; // continue
default: // unknown button or scroll wheel:
return 0; // don't send FL_RELEASE event
}
Fl::e_keysym = FL_Button + mb; // == FL_BUTTON1 .. FL_BUTTON5
set_event_xy(window);
Fl::e_state &= ~(FL_BUTTON1 << (mb-1));
if (mb == 4) xbutton_state &= ~FL_BUTTON4; // clear internal button state
if (mb == 5) xbutton_state &= ~FL_BUTTON5; // clear internal button state
event = FL_RELEASE;
#if FLTK_CONSOLIDATE_MOTION
fl_xmousewin = window;
#endif // FLTK_CONSOLIDATE_MOTION
in_a_window = true;
break;
} // ButtonRelease
case PropertyNotify:
if (xevent.xproperty.atom == fl_NET_WM_STATE) {
@ -2085,21 +2160,6 @@ int fl_handle(const XEvent& thisevent)
break;
# endif
case ButtonRelease:
Fl::e_keysym = FL_Button + xevent.xbutton.button;
set_event_xy(window);
Fl::e_state &= ~(FL_BUTTON1 << (xevent.xbutton.button-1));
if (xevent.xbutton.button > Button3) { // "buttons" 4-7 = mousewheel events: don't send FL_RELEASE
return 0;
}
event = FL_RELEASE;
#if FLTK_CONSOLIDATE_MOTION
fl_xmousewin = window;
#endif // FLTK_CONSOLIDATE_MOTION
in_a_window = true;
break;
case EnterNotify:
if (xevent.xcrossing.detail == NotifyInferior) break;
// XInstallColormap(fl_display, Fl_X::flx(window)->colormap);

View File

@ -303,10 +303,18 @@ static void pointer_button(void *data,
if (button == BTN_LEFT) { Fl::e_state |= FL_BUTTON1; b = 1; }
else if (button == BTN_RIGHT) { Fl::e_state |= FL_BUTTON3; b = 3; }
else if (button == BTN_MIDDLE) { Fl::e_state |= FL_BUTTON2; b = 2; }
else if (button == BTN_BACK) { Fl::e_state |= FL_BUTTON4; b = 4; } // ?
else if (button == BTN_SIDE) { Fl::e_state |= FL_BUTTON4; b = 4; } // OK: Debian 12
else if (button == BTN_FORWARD) { Fl::e_state |= FL_BUTTON5; b = 5; } // ?
else if (button == BTN_EXTRA) { Fl::e_state |= FL_BUTTON5; b = 5; } // OK: Debian 12
} else { // must be WL_POINTER_BUTTON_STATE_RELEASED
if (button == BTN_LEFT) { Fl::e_state &= ~FL_BUTTON1; b = 1; }
else if (button == BTN_RIGHT) { Fl::e_state &= ~FL_BUTTON3; b = 3; }
else if (button == BTN_MIDDLE) { Fl::e_state &= ~FL_BUTTON2; b = 2; }
else if (button == BTN_BACK) { Fl::e_state &= ~FL_BUTTON4; b = 4; } // ?
else if (button == BTN_SIDE) { Fl::e_state &= ~FL_BUTTON4; b = 4; } // OK: Debian 12
else if (button == BTN_FORWARD) { Fl::e_state &= ~FL_BUTTON5; b = 5; } // ?
else if (button == BTN_EXTRA) { Fl::e_state &= ~FL_BUTTON5; b = 5; } // OK: Debian 12
}
Fl::e_keysym = FL_Button + b;
Fl::e_dx = Fl::e_dy = 0;