From db214d1145e46d527a46d1fc2519548d2c4d23f1 Mon Sep 17 00:00:00 2001 From: maxim nikonov Date: Thu, 5 Feb 2026 15:21:34 +0500 Subject: wip: fork --- src/drivers/WinAPI/Fl_WinAPI_Pen_Driver.cxx | 518 ---------------------------- 1 file changed, 518 deletions(-) delete mode 100644 src/drivers/WinAPI/Fl_WinAPI_Pen_Driver.cxx (limited to 'src/drivers/WinAPI/Fl_WinAPI_Pen_Driver.cxx') diff --git a/src/drivers/WinAPI/Fl_WinAPI_Pen_Driver.cxx b/src/drivers/WinAPI/Fl_WinAPI_Pen_Driver.cxx deleted file mode 100644 index 06cc1e477..000000000 --- a/src/drivers/WinAPI/Fl_WinAPI_Pen_Driver.cxx +++ /dev/null @@ -1,518 +0,0 @@ -// -// Definition of Windows Pen/Tablet event driver. -// -// Copyright 2025-2026 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -// Note: We require Windows 8 or later features for Pen/Tablet support. -// Defining WINVER and _WIN32_WINNT to 0x0602 *may* be required on some -// Windows build platforms. Must be done before all #include's. - -#if !defined(WINVER) || (WINVER < 0x0602) -# ifdef WINVER -# undef WINVER -# endif -# define WINVER 0x0602 -#endif -#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0602) -# ifdef _WIN32_WINNT -# undef _WIN32_WINNT -# endif -# define _WIN32_WINNT 0x0602 -#endif - -#include "src/drivers/Base/Fl_Base_Pen_Events.H" - -#include -#include -#include -#include "../../Fl_Screen_Driver.H" -#include -#include -#include -#include -// Some versions of MinGW now require us to explicitly include winerror to get S_OK defined -#include - -extern Fl_Window *fl_xmousewin; - -static constexpr uint8_t _FL_PEN = 0; // internal use -static constexpr uint8_t _FL_ERASER = 1; // internal use -static uint8_t device_type_ = _FL_PEN; - -static int _e_x_down = 0; -static int _e_y_down = 0; - -// Click counting state -static DWORD last_click_time_ = 0; -static int last_click_x_ = 0; -static int last_click_y_ = 0; -static Fl::Pen::State last_click_trigger_ = Fl::Pen::State::NONE; - -// The trait list keeps track of traits for every pen ID that appears while -// handling events. -// AppKit does not tell us what traits are available per pen or tablet, so -// we use the first 5 motion events to discover event values that are not -// the default value, and enter that knowledge into the traits database. -typedef std::map TraitList; -static TraitList trait_list_; -static int trait_countdown_ { 5 }; -static int current_pen_id_ { -1 }; -static Fl::Pen::Trait current_pen_trait_ { Fl::Pen::Trait::DRIVER_AVAILABLE }; -static Fl::Pen::Trait driver_traits_ { - Fl::Pen::Trait::DRIVER_AVAILABLE | Fl::Pen::Trait::PEN_ID | - Fl::Pen::Trait::ERASER | Fl::Pen::Trait::PRESSURE | - Fl::Pen::Trait::TILT_X | - Fl::Pen::Trait::TILT_Y | Fl::Pen::Trait::TWIST - // Notably missing: PROXIMITY, BARREL_PRESSURE -}; - -// Temporary storage of event data for the driver; -static Fl::Pen::EventData ev; - - -namespace Fl { - -// namespace Private { - -// // Global mouse position at mouse down event -// extern int e_x_down; -// extern int e_y_down; - -// }; // namespace Private - -namespace Pen { - -class Windows_Driver : public Driver { -public: - Windows_Driver() = default; - //virtual void subscribe(Fl_Widget* widget) override; - //virtual void unsubscribe(Fl_Widget* widget) override; - //virtual void release() override; - virtual Trait traits() override { return driver_traits_; } - virtual Trait pen_traits(int pen_id) override { - auto it = trait_list_.find(pen_id); - if (pen_id == 0) - return current_pen_trait_; - if (it == trait_list_.end()) { - return Trait::DRIVER_AVAILABLE; - } else { - return it->second; - } - } -}; - -Windows_Driver windows_driver; -Driver& driver { windows_driver }; - -} // namespace Pen - -} // namespace Fl - - -using namespace Fl::Pen; - -/* - Copy the event state. - */ -static void copy_state() { - Fl::Pen::State tr = (Fl::Pen::State)((uint32_t)Fl::Pen::e.state ^ (uint32_t)ev.state); - Fl::Pen::e = ev; - Fl::Pen::e.trigger = tr; - Fl::e_x = (int)ev.x; - Fl::e_y = (int)ev.y; - Fl::e_x_root = (int)ev.rx; - Fl::e_y_root = (int)ev.ry; -} - -/* - Check if coordinates are within the widget box. - Coordinates are in top_window space. We iterate up the hierarchy to ensure - that we handle subwindows correctly. - */ -static bool event_inside(Fl_Widget *w, double x, double y) { - if (w->as_window()) { - return ((x >= 0) && (y >= 0) && (x < w->w()) && (y < w->h())); - } else { - return ((x >= w->x()) && (y >= w->y()) && (x < w->x() + w->w()) && (y < w->y() + w->h())); - } -} - -/* - Find the widget under the pen event. - Search the subscriber list for widgets that are inside the same window, - are visible, and are within the give coordinates. Subwindow aware. - */ -static Fl_Widget *find_below_pen(Fl_Window *win, double x, double y) { - for (auto &sub: subscriber_list_) { - Fl_Widget *candidate = sub.second->widget(); - if (candidate && ((candidate == win) || (!candidate->as_window() && candidate->window() == win))) { - if (candidate->visible() && event_inside(candidate, x, y)) { - return candidate; - } - } - } - return nullptr; -} - -/* - Send the current event and event data to a widget. - Note: we will get the wrong coordinates if the widget is not a child of - the current event window (LEAVE events between windows). - */ -static int pen_send(Fl_Widget *w, int event, State trigger, bool &copied) { - // Copy most event data only once - if (!copied) { - copy_state(); - copied = true; - } - // Copy the top_window coordinates again as they may change when w changes - Fl::e_x = e.x = ev.x; - Fl::e_y = e.y = ev.y; - // Send the event. - e.trigger = trigger; - return w->handle(event); -} - -/* - Send an event to all subscribers. - */ -static int pen_send_all(int event, State trigger) { - bool copied = false; - // use local value because handler may still change ev values - for (auto &it: subscriber_list_) { - auto w = it.second->widget(); - if (w) - pen_send(w, event, trigger, copied); - } - return 1; -} - -/* - Convert the NSEvent button number to Fl::Pen::State, - */ -static State button_to_trigger(POINTER_BUTTON_CHANGE_TYPE button, bool down) { - switch (button) { - case POINTER_CHANGE_FIRSTBUTTON_DOWN: - case POINTER_CHANGE_FIRSTBUTTON_UP: - if ( (ev.state & (State::ERASER_DOWN | State::ERASER_HOVERS)) != State::NONE ) { - return down ? State::ERASER_DOWN : State::ERASER_HOVERS; - } else { - return down ? State::TIP_DOWN : State::TIP_HOVERS; - } - case POINTER_CHANGE_SECONDBUTTON_DOWN: - case POINTER_CHANGE_SECONDBUTTON_UP: - return State::BUTTON0; - case POINTER_CHANGE_THIRDBUTTON_DOWN: - case POINTER_CHANGE_THIRDBUTTON_UP: - return State::BUTTON1; - case POINTER_CHANGE_FOURTHBUTTON_DOWN: - case POINTER_CHANGE_FOURTHBUTTON_UP: - return State::BUTTON2; - case POINTER_CHANGE_FIFTHBUTTON_DOWN: - case POINTER_CHANGE_FIFTHBUTTON_UP: - return State::BUTTON3; - default: return State::NONE; - } -} - -/* - Handle events coming from the Win32 API. - WM_TABLET (Windows 2000 and up) - WM_POINTER (Windows 8 and up) - https://learn.microsoft.com/en-us/windows/win32/inputmsg/messages-and-notifications-portal - #if(WINVER >= 0x0602) ... #endif - \return -1 if we did not handle the event and want the main event handler to call DefWindowProc() - \return any other value that will then be return from WndProc() directly. - */ -LRESULT fl_win32_tablet_handler(MSG& msg) { - auto message = msg.message; - if (message < WM_NCPOINTERUPDATE || message > WM_POINTERROUTEDRELEASED) { - return -1; - } - - Fl_Window *eventWindow = fl_find(msg.hwnd); // can be nullptr - bool is_proximity = false; - bool is_down = false; - bool is_up = false; - bool is_motion = false; - - switch (msg.message) { - case WM_NCPOINTERDOWN: // pen pushed over window decoration, don't care - case WM_NCPOINTERUP: // pen released over window decoration, don't care - case WM_NCPOINTERUPDATE: // pen moved over decoration, don't care - case WM_POINTERACTIVATE: // shall the pointer activate an inactive window? - return -1; // let the system handle this forwarding this to DefWindowProc - - case WM_POINTERENTER: // pointer moved into window area from top or sides - is_proximity = true; - break; - case WM_POINTERLEAVE: // left window area to top or sides - is_proximity = true; - break; - - - case WM_POINTERDOWN: - is_down = true; - break; - case WM_POINTERUP: - is_up = true; - break; - case WM_POINTERUPDATE: - is_motion = true; - break; - - case WM_POINTERCAPTURECHANGED: - case WM_TOUCHHITTESTING: - case WM_POINTERWHEEL: - case WM_POINTERHWHEEL: - case DM_POINTERHITTEST: - case WM_POINTERROUTEDTO: - case WM_POINTERROUTEDAWAY: - case WM_POINTERROUTEDRELEASED: - default: - // printf("Windows message: msg=0x%04X wParam=0x%08X lParam=0x%08X\n", - // msg.message, (unsigned)msg.wParam, (unsigned)msg.lParam); - return -1; - } - // printf(" msg=0x%04X wParam=0x%08X lParam=0x%08X\n", - // msg.message, (unsigned)msg.wParam, (unsigned)msg.lParam); - - POINTER_PEN_INFO info; - BOOL has_position = GetPointerPenInfo( - GET_POINTERID_WPARAM(msg.wParam), - &info - ); - // if (has_position && info.pointerInfo.ButtonChangeType!=0) { - // printf(" pointerFlags: %08x [", (unsigned)info.pointerInfo.pointerFlags); - // if (info.pointerInfo.pointerFlags & POINTER_FLAG_FIRSTBUTTON) printf(" 1ST"); - // if (info.pointerInfo.pointerFlags & POINTER_FLAG_SECONDBUTTON) printf(" 2ND"); - // if (info.pointerInfo.pointerFlags & POINTER_FLAG_THIRDBUTTON) printf(" 3RD"); - // if (info.pointerInfo.pointerFlags & POINTER_FLAG_FOURTHBUTTON) printf(" 4TH"); - // if (info.pointerInfo.pointerFlags & POINTER_FLAG_FIFTHBUTTON) printf(" 5TH"); - // printf(" ]\n penFlags: %08x [", (unsigned)info.penFlags); - // if (info.penFlags & PEN_FLAG_BARREL) printf(" BARREL"); - // if (info.penFlags & PEN_FLAG_INVERTED) printf(" INVERTED"); - // if (info.penFlags & PEN_FLAG_ERASER) printf(" ERASER"); - // printf(" ]\n penMask: %08x ButtonChangeType: %d\n", - // (unsigned)info.penMask, info.pointerInfo.ButtonChangeType); - // } - - // Event has extended pen data set: - if (has_position) { - // Get the position data. - double s = Fl::screen_driver()->scale(0); - double ex = info.pointerInfo.ptPixelLocation.x/s; - double ey = info.pointerInfo.ptPixelLocation.y/s; - // Go from global coordinates to event window coordinates - Fl_Widget *p = eventWindow; - while (p) { - if (p->as_window()) { - ex -= p->x(); - ey -= p->y(); - } - p = p->parent(); - }; - ev.x = ex; - ev.y = ey; - ev.rx = info.pointerInfo.ptPixelLocation.x/s; - ev.ry = info.pointerInfo.ptPixelLocation.y/s; - if (!is_proximity) { - // Get the extended data. - if (info.penMask & PEN_MASK_PRESSURE) - ev.pressure = info.pressure / 1024.0; - if (info.penMask & PEN_MASK_TILT_X) - ev.tilt_x = -info.tiltX / 90.0; - if (info.penMask & PEN_MASK_TILT_Y) - ev.tilt_y = -info.tiltY / 90.0; - if (info.penMask & PEN_MASK_ROTATION) - ev.twist = info.rotation > 180 ? (info.rotation - 360) : info.rotation; - if (info.pointerInfo.pointerFlags & POINTER_FLAG_INCONTACT) - ev.proximity = 0.0; - else - ev.proximity = 1.0; - } - if (info.penFlags & PEN_FLAG_INVERTED) { - device_type_ = _FL_ERASER; - if (info.pointerInfo.pointerFlags & POINTER_FLAG_INCONTACT) - ev.state = State::ERASER_DOWN; - else - ev.state = State::ERASER_HOVERS; - } else { - device_type_ = _FL_PEN; - if (info.pointerInfo.pointerFlags & POINTER_FLAG_INCONTACT) - ev.state = State::TIP_DOWN; - else - ev.state = State::TIP_HOVERS; - } - // Add pen barrel button states - // Note: POINTER_FLAG_FIRSTBUTTON is the pen tip - // PEN_FLAG_BARREL and POINTER_FLAG_SECONDBUTTON both indicate the primary barrel button - if ((info.penFlags & PEN_FLAG_BARREL) || (info.pointerInfo.pointerFlags & POINTER_FLAG_SECONDBUTTON)) - ev.state |= State::BUTTON0; - // Note: the following code does not work very well with the Wayland driver - // More research is needed to find out how to get these button states reliably. - if (info.pointerInfo.pointerFlags & POINTER_FLAG_THIRDBUTTON) ev.state |= State::BUTTON1; - if (info.pointerInfo.pointerFlags & POINTER_FLAG_FOURTHBUTTON) ev.state |= State::BUTTON2; - if (info.pointerInfo.pointerFlags & POINTER_FLAG_FIFTHBUTTON) ev.state |= State::BUTTON3; - } - // printf(" %08x\n", (unsigned)ev.state); - if (is_proximity) { - ev.pen_id = GET_POINTERID_WPARAM(msg.wParam); - } - if ((msg.message == WM_POINTERENTER) || (msg.message == WM_POINTERLEAVE)) { - if (msg.message == WM_POINTERENTER) { - // Check if this is the first time we see this pen, or if the pen changed - if (current_pen_id_ != ev.pen_id) { - current_pen_id_ = ev.pen_id; - auto it = trait_list_.find(current_pen_id_); - if (it == trait_list_.end()) { // not found, create a new entry - trait_list_[current_pen_id_] = Trait::DRIVER_AVAILABLE; - trait_countdown_ = 5; - pen_send_all(Fl::Pen::DETECTED, State::NONE); - // printf("IN RANGE, NEW PEN\n"); - } else { - pen_send_all(Fl::Pen::CHANGED, State::NONE); - // printf("IN RANGE, CHANGED PEN\n"); - } - trait_list_[0] = trait_list_[current_pen_id_]; // set current pen traits - } else { - pen_send_all(Fl::Pen::IN_RANGE, State::NONE); - // printf("IN RANGE\n"); - } - } else { - pen_send_all(Fl::Pen::OUT_OF_RANGE, State::NONE); - // printf("OUT OF RANGE\n"); - } - } - - Fl_Widget *receiver = nullptr; - bool pushed = false; - bool event_data_copied = false; - - if (has_position) { - if (trait_countdown_) { - trait_countdown_--; - if (ev.tilt_x != 0.0) current_pen_trait_ |= Trait::TILT_X; - if (ev.tilt_y != 0.0) current_pen_trait_ |= Trait::TILT_Y; - if (ev.pressure != 1.0) current_pen_trait_ |= Trait::PRESSURE; - if (ev.barrel_pressure != 0.0) current_pen_trait_ |= Trait::BARREL_PRESSURE; - if (ev.pen_id != 0) current_pen_trait_ |= Trait::PEN_ID; - if (ev.twist != 0.0) current_pen_trait_ |= Trait::TWIST; - //if (ev.proximity != 0) current_pen_trait_ |= Trait::PROXIMITY; - trait_list_[current_pen_id_] = current_pen_trait_; - } - fl_xmousewin = eventWindow; - if (pushed_ && pushed_->widget() && (Fl::pushed() == pushed_->widget())) { - receiver = pushed_->widget(); - if (Fl::grab() && (Fl::grab() != receiver->top_window())) - return -1; - if (Fl::modal() && (Fl::modal() != receiver->top_window())) - return -1; - pushed = true; - } else { - if (Fl::grab() && (Fl::grab() != eventWindow)) - return -1; - if (Fl::modal() && (Fl::modal() != eventWindow)) - return -1; - auto bpen = below_pen_ ? below_pen_->widget() : nullptr; - auto bmouse = Fl::belowmouse(); - auto bpen_old = bmouse && (bmouse == bpen) ? bpen : nullptr; - auto bpen_now = find_below_pen(eventWindow, ev.x, ev.y); - - if (bpen_now != bpen_old) { - if (bpen_old) { - pen_send(bpen_old, Fl::Pen::LEAVE, State::NONE, event_data_copied); - } - below_pen_ = nullptr; - if (bpen_now) { - State state = (device_type_ == _FL_ERASER) ? State::ERASER_HOVERS : State::TIP_HOVERS; - if (pen_send(bpen_now, Fl::Pen::ENTER, state, event_data_copied)) { - below_pen_ = subscriber_list_[bpen_now]; - Fl::belowmouse(bpen_now); - } - } - } - - receiver = below_pen_ ? below_pen_->widget() : nullptr; - if (!receiver) - return -1; - } - } else { - // Proximity events were handled earlier. - } - - if (!receiver) - return -1; - - if (is_down) { - if (!pushed) { - pushed_ = subscriber_list_[receiver]; - Fl::pushed(receiver); - } - State trigger = button_to_trigger(info.pointerInfo.ButtonChangeType, true); - if (msg.message == WM_POINTERDOWN) { - Fl::e_is_click = 1; - _e_x_down = (int)ev.x; - _e_y_down = (int)ev.y; - - // Implement click counting using Windows system metrics - DWORD current_time = GetMessageTime(); - DWORD double_click_time = GetDoubleClickTime(); - int double_click_dx = GetSystemMetrics(SM_CXDOUBLECLK) / 2; - int double_click_dy = GetSystemMetrics(SM_CYDOUBLECLK) / 2; - - // Check if this is a multi-click: same trigger, within time and distance thresholds - if (trigger == last_click_trigger_ && - (current_time - last_click_time_) < double_click_time && - abs((int)ev.rx - last_click_x_) < double_click_dx && - abs((int)ev.ry - last_click_y_) < double_click_dy) { - Fl::e_clicks++; - } else { - Fl::e_clicks = 0; - } - - last_click_time_ = current_time; - last_click_x_ = (int)ev.rx; - last_click_y_ = (int)ev.ry; - last_click_trigger_ = trigger; - - pen_send(receiver, Fl::Pen::TOUCH, trigger, event_data_copied); - } else { - pen_send(receiver, Fl::Pen::BUTTON_PUSH, trigger, event_data_copied); - } - } else if (is_up) { - if ( (ev.state & State::ANY_DOWN) == State::NONE ) { - Fl::pushed(nullptr); - pushed_ = nullptr; - } - State trigger = button_to_trigger(info.pointerInfo.ButtonChangeType, true); - if (info.pointerInfo.ButtonChangeType == 0) - pen_send(receiver, Fl::Pen::LIFT, trigger, event_data_copied); - else - pen_send(receiver, Fl::Pen::BUTTON_RELEASE, trigger, event_data_copied); - } else if (is_motion) { - if ( Fl::e_is_click && - ( (fabs((int)ev.x - _e_x_down) > 5) || - (fabs((int)ev.y - _e_y_down) > 5) ) ) - Fl::e_is_click = 0; - if (pushed) { - pen_send(receiver, Fl::Pen::DRAW, State::NONE, event_data_copied); - } else { - pen_send(receiver, Fl::Pen::HOVER, State::NONE, event_data_copied); - } - } - // Always return 0 because at this point, we capture pen events and don't - // want mouse events anymore! - return 0; -} -- cgit v1.2.3