diff --git a/FL/Enumerations.H b/FL/Enumerations.H index 1beb450e5..97b4c7029 100644 --- a/FL/Enumerations.H +++ b/FL/Enumerations.H @@ -548,9 +548,11 @@ 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_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 -#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 +// 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_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! diff --git a/FL/Fl.H b/FL/Fl.H index 57be52e5d..fceafc4c8 100644 --- a/FL/Fl.H +++ b/FL/Fl.H @@ -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. - The legal event state bits are: + \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. - - FL_SHIFT - - FL_CAPS_LOCK - - FL_CTRL - - FL_ALT - - FL_NUM_LOCK - - FL_META - - FL_SCROLL_LOCK - - FL_BUTTON1 - - FL_BUTTON2 - - FL_BUTTON3 + - 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 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. + \note This inconsistency \b may be fixed (on X11 and Wayland) in a later release. + + The legal event state bits are: + + | 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;} /** @} */ /** diff --git a/src/Fl_cocoa.mm b/src/Fl_cocoa.mm index ddb96c6b2..369ce8444 100644 --- a/src/Fl_cocoa.mm +++ b/src/Fl_cocoa.mm @@ -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(); @@ -1060,7 +1060,7 @@ static void cocoaMouseHandler(NSEvent *theEvent) float s = Fl::screen_driver()->scale(0); pos.x /= s; pos.y /= s; pos.y = window->h() - pos.y; - NSInteger btn = [theEvent buttonNumber] + 1; + NSInteger btn = [theEvent buttonNumber] + 1; NSUInteger mods = [theEvent modifierFlags]; int sendEvent = 0; @@ -1070,13 +1070,17 @@ 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 ) { case NSEventTypeLeftMouseDown: diff --git a/src/Fl_win32.cxx b/src/Fl_win32.cxx index 36e77a99b..dec95241e 100644 --- a/src/Fl_win32.cxx +++ b/src/Fl_win32.cxx @@ -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 diff --git a/src/Fl_x.cxx b/src/Fl_x.cxx index dcae946ab..681014486 100644 --- a/src/Fl_x.cxx +++ b/src/Fl_x.cxx @@ -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)); - event = FL_PUSH; - checkdouble(); + } 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); diff --git a/src/drivers/Wayland/Fl_Wayland_Screen_Driver.cxx b/src/drivers/Wayland/Fl_Wayland_Screen_Driver.cxx index 01fd46536..4a7ace768 100644 --- a/src/drivers/Wayland/Fl_Wayland_Screen_Driver.cxx +++ b/src/drivers/Wayland/Fl_Wayland_Screen_Driver.cxx @@ -300,13 +300,21 @@ static void pointer_button(void *data, int b = 0; // Fl::e_state &= ~FL_BUTTONS; // DO NOT reset the mouse button state! if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - 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; } + 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; } + 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;