diff options
Diffstat (limited to 'intern/ghost/intern')
-rw-r--r-- | intern/ghost/intern/GHOST_C-api.cpp | 31 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_ISystem.cpp | 12 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_System.cpp | 10 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_System.h | 4 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_SystemWayland.cpp | 1733 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_SystemWayland.h | 38 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_Window.cpp | 21 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_Window.h | 4 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_WindowWayland.cpp | 355 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_WindowWayland.h | 44 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_WindowX11.cpp | 2 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_Wintab.cpp | 19 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_Wintab.h | 2 |
13 files changed, 1705 insertions, 570 deletions
diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index 9374d087408..b1a15fdf4d7 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -376,6 +376,20 @@ GHOST_TSuccess GHOST_SetCursorGrab(GHOST_WindowHandle windowhandle, mode, wrap_axis, bounds ? &bounds_rect : nullptr, mouse_ungrab_xy ? mouse_xy : nullptr); } +void GHOST_GetCursorGrabState(GHOST_WindowHandle windowhandle, + GHOST_TGrabCursorMode *r_mode, + GHOST_TAxisFlag *r_axis_flag, + int r_bounds[4]) +{ + GHOST_IWindow *window = (GHOST_IWindow *)windowhandle; + GHOST_Rect bounds_rect; + window->getCursorGrabState(*r_mode, *r_axis_flag, bounds_rect); + r_bounds[0] = bounds_rect.m_l; + r_bounds[1] = bounds_rect.m_t; + r_bounds[2] = bounds_rect.m_r; + r_bounds[3] = bounds_rect.m_b; +} + GHOST_TSuccess GHOST_GetModifierKeyState(GHOST_SystemHandle systemhandle, GHOST_TModifierKeyMask mask, int *isDown) @@ -815,6 +829,23 @@ int GHOST_UseNativePixels(void) return system->useNativePixel(); } +int GHOST_SupportsCursorWarp(void) +{ + GHOST_ISystem *system = GHOST_ISystem::getSystem(); + return system->supportsCursorWarp(); +} + +int GHOST_SupportsWindowPosition(void) +{ + GHOST_ISystem *system = GHOST_ISystem::getSystem(); + return system->supportsWindowPosition(); +} + +void GHOST_SetBacktraceHandler(GHOST_TBacktraceFn backtrace_fn) +{ + GHOST_ISystem::setBacktraceFn(backtrace_fn); +} + void GHOST_UseWindowFocus(int use_focus) { GHOST_ISystem *system = GHOST_ISystem::getSystem(); diff --git a/intern/ghost/intern/GHOST_ISystem.cpp b/intern/ghost/intern/GHOST_ISystem.cpp index 87111306ec7..745d5faeed4 100644 --- a/intern/ghost/intern/GHOST_ISystem.cpp +++ b/intern/ghost/intern/GHOST_ISystem.cpp @@ -31,6 +31,8 @@ GHOST_ISystem *GHOST_ISystem::m_system = nullptr; +GHOST_TBacktraceFn GHOST_ISystem::m_backtrace_fn = nullptr; + GHOST_TSuccess GHOST_ISystem::createSystem() { GHOST_TSuccess success; @@ -89,3 +91,13 @@ GHOST_ISystem *GHOST_ISystem::getSystem() { return m_system; } + +GHOST_TBacktraceFn GHOST_ISystem::getBacktraceFn() +{ + return GHOST_ISystem::m_backtrace_fn; +} + +void GHOST_ISystem::setBacktraceFn(GHOST_TBacktraceFn backtrace_fn) +{ + GHOST_ISystem::m_backtrace_fn = backtrace_fn; +} diff --git a/intern/ghost/intern/GHOST_System.cpp b/intern/ghost/intern/GHOST_System.cpp index 1ddf884bbc5..c8308b3586b 100644 --- a/intern/ghost/intern/GHOST_System.cpp +++ b/intern/ghost/intern/GHOST_System.cpp @@ -390,6 +390,16 @@ void GHOST_System::useWindowFocus(const bool use_focus) m_windowFocus = use_focus; } +bool GHOST_System::supportsCursorWarp() +{ + return true; +} + +bool GHOST_System::supportsWindowPosition() +{ + return true; +} + void GHOST_System::initDebug(GHOST_Debug debug) { m_is_debug_enabled = debug.flags & GHOST_kDebugDefault; diff --git a/intern/ghost/intern/GHOST_System.h b/intern/ghost/intern/GHOST_System.h index 4a3cded1fbd..b60ce09f743 100644 --- a/intern/ghost/intern/GHOST_System.h +++ b/intern/ghost/intern/GHOST_System.h @@ -151,10 +151,14 @@ class GHOST_System : public GHOST_ISystem { bool useNativePixel(void); bool m_nativePixel; + bool supportsCursorWarp(void); + bool supportsWindowPosition(void); + /** * Focus window after opening, or put them in the background. */ void useWindowFocus(const bool use_focus); + bool m_windowFocus; /** diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 7e74287d6e3..d8fbe875f67 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -29,7 +29,10 @@ #include "GHOST_WaylandCursorSettings.h" #include <pointer-constraints-client-protocol.h> #include <relative-pointer-client-protocol.h> +#include <tablet-client-protocol.h> #include <wayland-cursor.h> +#include <xdg-output-client-protocol.h> + #include <xkbcommon/xkbcommon.h> #include <fcntl.h> @@ -37,6 +40,13 @@ #include <unistd.h> #include <cstring> +#include <mutex> + +static GHOST_WindowWayland *window_from_surface(struct wl_surface *surface); + +/* -------------------------------------------------------------------- */ +/** \name Private Types & Defines + * \{ */ /** * Selected input event code defines from `linux/input-event-codes.h` @@ -53,102 +63,155 @@ #define BTN_BACK 0x116 // #define BTN_TASK 0x117 /* UNUSED. */ +/** + * Tablet events, also from `linux/input-event-codes.h`. + */ +#define BTN_STYLUS 0x14b /* Use as right-mouse. */ +#define BTN_STYLUS2 0x14c /* Use as middle-mouse. */ +/* NOTE(@campbellbarton): Map to an additional button (not sure which hardware uses this). */ +#define BTN_STYLUS3 0x149 + struct buffer_t { - void *data; - size_t size; + void *data = nullptr; + size_t size = 0; }; struct cursor_t { - bool visible; - struct wl_surface *surface = nullptr; - struct wl_buffer *buffer; - struct wl_cursor_image image; + bool visible = false; + struct wl_surface *wl_surface = nullptr; + struct wl_buffer *wl_buffer = nullptr; + struct wl_cursor_image wl_image = {0}; + struct wl_cursor_theme *wl_theme = nullptr; struct buffer_t *file_buffer = nullptr; - struct wl_cursor_theme *theme = nullptr; - int size; + int size = 0; std::string theme_name; - // outputs on which the cursor is visible + /** Outputs on which the cursor is visible. */ std::unordered_set<const output_t *> outputs; int scale = 1; }; +/** + * A single tablet can have multiple tools (pen, eraser, brush... etc). + * WAYLAND exposes tools via #zwp_tablet_tool_v2. + * Since are no API's to access properties of the tool, store the values here. + */ +struct tablet_tool_input_t { + struct input_t *input = nullptr; + struct wl_surface *cursor_surface = nullptr; + + GHOST_TabletData data = GHOST_TABLET_DATA_NONE; +}; + struct data_offer_t { std::unordered_set<std::string> types; - uint32_t source_actions; - uint32_t dnd_action; - struct wl_data_offer *id; - std::atomic<bool> in_use; + uint32_t source_actions = 0; + uint32_t dnd_action = 0; + struct wl_data_offer *id = nullptr; + std::atomic<bool> in_use = false; struct { - int x, y; + /** Compatible with #input_t.xy coordinates. */ + wl_fixed_t xy[2] = {0, 0}; } dnd; }; struct data_source_t { - struct wl_data_source *data_source; - /** Last device that was active. */ - uint32_t source_serial; - char *buffer_out; + struct wl_data_source *data_source = nullptr; + char *buffer_out = nullptr; }; struct key_repeat_payload_t { - GHOST_SystemWayland *system; - GHOST_IWindow *window; - GHOST_TKey key; - GHOST_TEventKeyData key_data; + GHOST_SystemWayland *system = nullptr; + GHOST_IWindow *window = nullptr; + GHOST_TEventKeyData key_data = {GHOST_kKeyUnknown}; }; struct input_t { - GHOST_SystemWayland *system; + GHOST_SystemWayland *system = nullptr; std::string name; - struct wl_seat *seat; - struct wl_pointer *pointer = nullptr; - struct wl_keyboard *keyboard = nullptr; - - uint32_t pointer_serial; - int x, y; - GHOST_Buttons buttons; + struct wl_seat *wl_seat = nullptr; + struct wl_pointer *wl_pointer = nullptr; + struct wl_keyboard *wl_keyboard = nullptr; + struct zwp_tablet_seat_v2 *tablet_seat = nullptr; + + /** All currently active tablet tools (needed for changing the cursor). */ + std::unordered_set<zwp_tablet_tool_v2 *> tablet_tools; + + uint32_t pointer_serial = 0; + uint32_t tablet_serial = 0; + + /** Use to check if the last cursor input was tablet or pointer. */ + uint32_t cursor_serial = 0; + + /** + * High precision mouse coordinates (pointer or tablet). + * + * The following example converts these values to screen coordinates. + * \code{.cc} + * const wl_fixed_t scale = win->scale(); + * const int event_xy[2] = { + * wl_fixed_to_int(scale * input->xy[0]), + * wl_fixed_to_int(scale * input->xy[1]), + * }; + * \endocde + */ + wl_fixed_t xy[2] = {0, 0}; + GHOST_Buttons buttons = GHOST_Buttons(); struct cursor_t cursor; - struct zwp_relative_pointer_v1 *relative_pointer; - struct zwp_locked_pointer_v1 *locked_pointer; + struct zwp_relative_pointer_v1 *relative_pointer = nullptr; + struct zwp_locked_pointer_v1 *locked_pointer = nullptr; + struct zwp_confined_pointer_v1 *confined_pointer = nullptr; - struct xkb_context *xkb_context; - struct xkb_state *xkb_state; + struct xkb_context *xkb_context = nullptr; + struct xkb_state *xkb_state = nullptr; struct { - /* Key repetition in character per second. */ - int32_t rate; - /* Time (milliseconds) after which to start repeating keys. */ - int32_t delay; - /* Timer for key repeats. */ + /** Key repetition in character per second. */ + int32_t rate = 0; + /** Time (milliseconds) after which to start repeating keys. */ + int32_t delay = 0; + /** Timer for key repeats. */ GHOST_ITimerTask *timer = nullptr; } key_repeat; + struct wl_surface *focus_tablet = nullptr; struct wl_surface *focus_pointer = nullptr; struct wl_surface *focus_keyboard = nullptr; + struct wl_surface *focus_dnd = nullptr; struct wl_data_device *data_device = nullptr; - struct data_offer_t *data_offer_dnd; /* Drag & Drop. */ - struct data_offer_t *data_offer_copy_paste; /* Copy & Paste. */ + /** Drag & Drop. */ + struct data_offer_t *data_offer_dnd = nullptr; + std::mutex data_offer_dnd_mutex; + + /** Copy & Paste. */ + struct data_offer_t *data_offer_copy_paste = nullptr; + std::mutex data_offer_copy_paste_mutex; + + struct data_source_t *data_source = nullptr; + std::mutex data_source_mutex; - struct data_source_t *data_source; + /** Last device that was active. */ + uint32_t data_source_serial = 0; }; struct display_t { - GHOST_SystemWayland *system; + GHOST_SystemWayland *system = nullptr; - struct wl_display *display; + struct wl_display *display = nullptr; struct wl_compositor *compositor = nullptr; struct xdg_wm_base *xdg_shell = nullptr; struct zxdg_decoration_manager_v1 *xdg_decoration_manager = nullptr; + struct zxdg_output_manager_v1 *xdg_output_manager = nullptr; struct wl_shm *shm = nullptr; std::vector<output_t *> outputs; std::vector<input_t *> inputs; struct { std::string theme; - int size; + int size = 0; } cursor; struct wl_data_device_manager *data_device_manager = nullptr; + struct zwp_tablet_manager_v2 *tablet_manager = nullptr; struct zwp_relative_pointer_manager_v1 *relative_pointer_manager = nullptr; struct zwp_pointer_constraints_v1 *pointer_constraints = nullptr; @@ -156,56 +219,105 @@ struct display_t { std::vector<struct wl_egl_window *> os_egl_windows; }; +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Private Utility Functions + * \{ */ + static GHOST_WindowManager *window_manager = nullptr; +/** Check this lock before accessing `GHOST_SystemWayland::selection` from a thread. */ +static std::mutex system_selection_mutex; + +/** + * Callback for WAYLAND to run when there is an error. + * + * \note It's useful to set a break-point on this function as some errors are fatal + * (for all intents and purposes) but don't crash the process. + */ +static void ghost_wayland_log_handler(const char *msg, va_list arg) +{ + fprintf(stderr, "GHOST/Wayland: "); + vfprintf(stderr, msg, arg); /* Includes newline. */ + + GHOST_TBacktraceFn backtrace_fn = GHOST_ISystem::getBacktraceFn(); + if (backtrace_fn) { + backtrace_fn(stderr); /* Includes newline. */ + } +} + static void display_destroy(display_t *d) { if (d->data_device_manager) { wl_data_device_manager_destroy(d->data_device_manager); } + if (d->tablet_manager) { + zwp_tablet_manager_v2_destroy(d->tablet_manager); + } + for (output_t *output : d->outputs) { - wl_output_destroy(output->output); + wl_output_destroy(output->wl_output); delete output; } for (input_t *input : d->inputs) { - if (input->data_source) { - free(input->data_source->buffer_out); - if (input->data_source->data_source) { - wl_data_source_destroy(input->data_source->data_source); + + /* First handle members that require locking. + * While highly unlikely, it's possible they are being used while this function runs. */ + { + std::lock_guard lock{input->data_source_mutex}; + if (input->data_source) { + free(input->data_source->buffer_out); + if (input->data_source->data_source) { + wl_data_source_destroy(input->data_source->data_source); + } + delete input->data_source; } - delete input->data_source; } - if (input->data_offer_copy_paste) { - wl_data_offer_destroy(input->data_offer_copy_paste->id); - delete input->data_offer_copy_paste; + + { + std::lock_guard lock{input->data_offer_dnd_mutex}; + if (input->data_offer_dnd) { + wl_data_offer_destroy(input->data_offer_dnd->id); + delete input->data_offer_dnd; + } } + + { + std::lock_guard lock{input->data_offer_copy_paste_mutex}; + if (input->data_offer_copy_paste) { + wl_data_offer_destroy(input->data_offer_copy_paste->id); + delete input->data_offer_copy_paste; + } + } + if (input->data_device) { wl_data_device_release(input->data_device); } - if (input->pointer) { + if (input->wl_pointer) { if (input->cursor.file_buffer) { munmap(input->cursor.file_buffer->data, input->cursor.file_buffer->size); delete input->cursor.file_buffer; } - if (input->cursor.surface) { - wl_surface_destroy(input->cursor.surface); + if (input->cursor.wl_surface) { + wl_surface_destroy(input->cursor.wl_surface); } - if (input->cursor.theme) { - wl_cursor_theme_destroy(input->cursor.theme); + if (input->cursor.wl_theme) { + wl_cursor_theme_destroy(input->cursor.wl_theme); } - if (input->pointer) { - wl_pointer_destroy(input->pointer); + if (input->wl_pointer) { + wl_pointer_destroy(input->wl_pointer); } } - if (input->keyboard) { + if (input->wl_keyboard) { if (input->key_repeat.timer) { delete static_cast<key_repeat_payload_t *>(input->key_repeat.timer->getUserData()); input->system->removeTimer(input->key_repeat.timer); input->key_repeat.timer = nullptr; } - wl_keyboard_destroy(input->keyboard); + wl_keyboard_destroy(input->wl_keyboard); } if (input->xkb_state) { xkb_state_unref(input->xkb_state); @@ -213,7 +325,7 @@ static void display_destroy(display_t *d) if (input->xkb_context) { xkb_context_unref(input->xkb_context); } - wl_seat_destroy(input->seat); + wl_seat_destroy(input->wl_seat); delete input; } @@ -361,6 +473,27 @@ static GHOST_TKey xkb_map_gkey(const xkb_keysym_t &sym) return gkey; } +static GHOST_TTabletMode tablet_tool_map_type(enum zwp_tablet_tool_v2_type wl_tablet_tool_type) +{ + switch (wl_tablet_tool_type) { + case ZWP_TABLET_TOOL_V2_TYPE_ERASER: { + return GHOST_kTabletModeEraser; + } + case ZWP_TABLET_TOOL_V2_TYPE_PEN: + case ZWP_TABLET_TOOL_V2_TYPE_BRUSH: + case ZWP_TABLET_TOOL_V2_TYPE_PENCIL: + case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH: + case ZWP_TABLET_TOOL_V2_TYPE_FINGER: + case ZWP_TABLET_TOOL_V2_TYPE_MOUSE: + case ZWP_TABLET_TOOL_V2_TYPE_LENS: { + return GHOST_kTabletModeStylus; + } + } + + GHOST_PRINT("unknown tablet tool: " << wl_tablet_tool_type << std::endl); + return GHOST_kTabletModeStylus; +} + static const int default_cursor_size = 24; static const std::unordered_map<GHOST_TStandardCursor, std::string> cursors = { @@ -429,14 +562,16 @@ static const std::vector<std::string> mime_send = { "text/plain", }; +/** \} */ + /* -------------------------------------------------------------------- */ -/** \name Interface Callbacks +/** \name Listener (Relative Motion), #zwp_relative_pointer_v1_listener * * These callbacks are registered for Wayland interfaces and called when * an event is received from the compositor. * \{ */ -static void relative_pointer_relative_motion( +static void relative_pointer_handle_relative_motion( void *data, struct zwp_relative_pointer_v1 * /*zwp_relative_pointer_v1*/, uint32_t /*utime_hi*/, @@ -447,42 +582,56 @@ static void relative_pointer_relative_motion( wl_fixed_t /*dy_unaccel*/) { input_t *input = static_cast<input_t *>(data); - - input->x += wl_fixed_to_int(dx); - input->y += wl_fixed_to_int(dy); - - GHOST_IWindow *win = static_cast<GHOST_WindowWayland *>( - wl_surface_get_user_data(input->focus_pointer)); + GHOST_WindowWayland *win = window_from_surface(input->focus_pointer); + if (!win) { + return; + } + const wl_fixed_t scale = win->scale(); + input->xy[0] += dx / scale; + input->xy[1] += dy / scale; input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), GHOST_kEventCursorMove, win, - input->x, - input->y, + wl_fixed_to_int(scale * input->xy[0]), + wl_fixed_to_int(scale * input->xy[1]), GHOST_TABLET_DATA_NONE)); } static const zwp_relative_pointer_v1_listener relative_pointer_listener = { - relative_pointer_relative_motion, + relative_pointer_handle_relative_motion, }; +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Data Source), #wl_data_source_listener + * \{ */ + static void dnd_events(const input_t *const input, const GHOST_TEventType event) { + /* NOTE: `input->data_offer_dnd_mutex` must already be locked. */ const uint64_t time = input->system->getMilliSeconds(); - GHOST_IWindow *const window = static_cast<GHOST_WindowWayland *>( - wl_surface_get_user_data(input->focus_pointer)); + GHOST_WindowWayland *const win = static_cast<GHOST_WindowWayland *>( + wl_surface_get_user_data(input->focus_dnd)); + if (!win) { + return; + } + const wl_fixed_t scale = win->scale(); + const int event_xy[2] = { + wl_fixed_to_int(scale * input->data_offer_dnd->dnd.xy[0]), + wl_fixed_to_int(scale * input->data_offer_dnd->dnd.xy[1]), + }; + for (const std::string &type : mime_preference_order) { - input->system->pushEvent(new GHOST_EventDragnDrop(time, - event, - mime_dnd.at(type), - window, - input->data_offer_dnd->dnd.x, - input->data_offer_dnd->dnd.y, - nullptr)); + input->system->pushEvent(new GHOST_EventDragnDrop( + time, event, mime_dnd.at(type), win, event_xy[0], event_xy[1], nullptr)); } } -static std::string read_pipe(data_offer_t *data_offer, const std::string mime_receive) +static std::string read_pipe(data_offer_t *data_offer, + const std::string mime_receive, + std::mutex *mutex) { int pipefd[2]; if (pipe(pipefd) != 0) { @@ -491,6 +640,13 @@ static std::string read_pipe(data_offer_t *data_offer, const std::string mime_re wl_data_offer_receive(data_offer->id, mime_receive.c_str(), pipefd[1]); close(pipefd[1]); + data_offer->in_use.store(false); + + if (mutex) { + mutex->unlock(); + } + /* WARNING: `data_offer` may be freed from now on. */ + std::string data; ssize_t len; char buffer[4096]; @@ -498,7 +654,6 @@ static std::string read_pipe(data_offer_t *data_offer, const std::string mime_re data.insert(data.end(), buffer, buffer + len); } close(pipefd[0]); - data_offer->in_use.store(false); return data; } @@ -509,26 +664,29 @@ static std::string read_pipe(data_offer_t *data_offer, const std::string mime_re * Sent when a target accepts pointer_focus or motion events. If * a target does not accept any of the offered types, type is nullptr. */ -static void data_source_target(void * /*data*/, - struct wl_data_source * /*wl_data_source*/, - const char * /*mime_type*/) +static void data_source_handle_target(void * /*data*/, + struct wl_data_source * /*wl_data_source*/, + const char * /*mime_type*/) { /* pass */ } -static void data_source_send(void *data, - struct wl_data_source * /*wl_data_source*/, - const char * /*mime_type*/, - int32_t fd) +static void data_source_handle_send(void *data, + struct wl_data_source * /*wl_data_source*/, + const char * /*mime_type*/, + int32_t fd) { - const char *const buffer = static_cast<char *>(data); + input_t *input = static_cast<input_t *>(data); + std::lock_guard lock{input->data_source_mutex}; + + const char *const buffer = input->data_source->buffer_out; if (write(fd, buffer, strlen(buffer)) < 0) { GHOST_PRINT("error writing to clipboard: " << std::strerror(errno) << std::endl); } close(fd); } -static void data_source_cancelled(void * /*data*/, struct wl_data_source *wl_data_source) +static void data_source_handle_cancelled(void * /*data*/, struct wl_data_source *wl_data_source) { wl_data_source_destroy(wl_data_source); } @@ -540,8 +698,8 @@ static void data_source_cancelled(void * /*data*/, struct wl_data_source *wl_dat * indicate acceptance, #wl_data_source.cancelled may still be * emitted afterwards if the drop destination does not accept any mime type. */ -static void data_source_dnd_drop_performed(void * /*data*/, - struct wl_data_source * /*wl_data_source*/) +static void data_source_handle_dnd_drop_performed(void * /*data*/, + struct wl_data_source * /*wl_data_source*/) { /* pass */ } @@ -553,7 +711,8 @@ static void data_source_dnd_drop_performed(void * /*data*/, * source, so the client is now free to destroy this data source * and free all associated data. */ -static void data_source_dnd_finished(void * /*data*/, struct wl_data_source * /*wl_data_source*/) +static void data_source_handle_dnd_finished(void * /*data*/, + struct wl_data_source * /*wl_data_source*/) { /* pass */ } @@ -565,73 +724,87 @@ static void data_source_dnd_finished(void * /*data*/, struct wl_data_source * /* * after matching the source/destination side actions. Only one * action (or none) will be offered here. */ -static void data_source_action(void * /*data*/, - struct wl_data_source * /*wl_data_source*/, - uint32_t /*dnd_action*/) +static void data_source_handle_action(void * /*data*/, + struct wl_data_source * /*wl_data_source*/, + uint32_t /*dnd_action*/) { /* pass */ } static const struct wl_data_source_listener data_source_listener = { - data_source_target, - data_source_send, - data_source_cancelled, - data_source_dnd_drop_performed, - data_source_dnd_finished, - data_source_action, + data_source_handle_target, + data_source_handle_send, + data_source_handle_cancelled, + data_source_handle_dnd_drop_performed, + data_source_handle_dnd_finished, + data_source_handle_action, }; -static void data_offer_offer(void *data, - struct wl_data_offer * /*wl_data_offer*/, - const char *mime_type) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Data Offer), #wl_data_offer_listener + * \{ */ + +static void data_offer_handle_offer(void *data, + struct wl_data_offer * /*wl_data_offer*/, + const char *mime_type) { static_cast<data_offer_t *>(data)->types.insert(mime_type); } -static void data_offer_source_actions(void *data, - struct wl_data_offer * /*wl_data_offer*/, - uint32_t source_actions) +static void data_offer_handle_source_actions(void *data, + struct wl_data_offer * /*wl_data_offer*/, + uint32_t source_actions) { static_cast<data_offer_t *>(data)->source_actions = source_actions; } -static void data_offer_action(void *data, - struct wl_data_offer * /*wl_data_offer*/, - uint32_t dnd_action) +static void data_offer_handle_action(void *data, + struct wl_data_offer * /*wl_data_offer*/, + uint32_t dnd_action) { static_cast<data_offer_t *>(data)->dnd_action = dnd_action; } static const struct wl_data_offer_listener data_offer_listener = { - data_offer_offer, - data_offer_source_actions, - data_offer_action, + data_offer_handle_offer, + data_offer_handle_source_actions, + data_offer_handle_action, }; -static void data_device_data_offer(void * /*data*/, - struct wl_data_device * /*wl_data_device*/, - struct wl_data_offer *id) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Data Device), #wl_data_device_listener + * \{ */ + +static void data_device_handle_data_offer(void * /*data*/, + struct wl_data_device * /*wl_data_device*/, + struct wl_data_offer *id) { data_offer_t *data_offer = new data_offer_t; data_offer->id = id; wl_data_offer_add_listener(id, &data_offer_listener, data_offer); } -static void data_device_enter(void *data, - struct wl_data_device * /*wl_data_device*/, - uint32_t serial, - struct wl_surface * /*surface*/, - wl_fixed_t x, - wl_fixed_t y, - struct wl_data_offer *id) +static void data_device_handle_enter(void *data, + struct wl_data_device * /*wl_data_device*/, + uint32_t serial, + struct wl_surface *surface, + wl_fixed_t x, + wl_fixed_t y, + struct wl_data_offer *id) { input_t *input = static_cast<input_t *>(data); + std::lock_guard lock{input->data_offer_dnd_mutex}; + input->data_offer_dnd = static_cast<data_offer_t *>(wl_data_offer_get_user_data(id)); data_offer_t *data_offer = input->data_offer_dnd; data_offer->in_use.store(true); - data_offer->dnd.x = wl_fixed_to_int(x); - data_offer->dnd.y = wl_fixed_to_int(y); + data_offer->dnd.xy[0] = x; + data_offer->dnd.xy[1] = y; wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | @@ -642,14 +815,17 @@ static void data_device_enter(void *data, wl_data_offer_accept(id, serial, type.c_str()); } + input->focus_dnd = surface; dnd_events(input, GHOST_kEventDraggingEntered); } -static void data_device_leave(void *data, struct wl_data_device * /*wl_data_device*/) +static void data_device_handle_leave(void *data, struct wl_data_device * /*wl_data_device*/) { input_t *input = static_cast<input_t *>(data); + std::lock_guard lock{input->data_offer_dnd_mutex}; dnd_events(input, GHOST_kEventDraggingExited); + input->focus_dnd = nullptr; if (input->data_offer_dnd && !input->data_offer_dnd->in_use.load()) { wl_data_offer_destroy(input->data_offer_dnd->id); @@ -658,21 +834,26 @@ static void data_device_leave(void *data, struct wl_data_device * /*wl_data_devi } } -static void data_device_motion(void *data, - struct wl_data_device * /*wl_data_device*/, - uint32_t /*time*/, - wl_fixed_t x, - wl_fixed_t y) +static void data_device_handle_motion(void *data, + struct wl_data_device * /*wl_data_device*/, + uint32_t /*time*/, + wl_fixed_t x, + wl_fixed_t y) { input_t *input = static_cast<input_t *>(data); - input->data_offer_dnd->dnd.x = wl_fixed_to_int(x); - input->data_offer_dnd->dnd.y = wl_fixed_to_int(y); + std::lock_guard lock{input->data_offer_dnd_mutex}; + + input->data_offer_dnd->dnd.xy[0] = x; + input->data_offer_dnd->dnd.xy[1] = y; + dnd_events(input, GHOST_kEventDraggingUpdated); } -static void data_device_drop(void *data, struct wl_data_device * /*wl_data_device*/) +static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_data_device*/) { input_t *input = static_cast<input_t *>(data); + std::lock_guard lock{input->data_offer_dnd_mutex}; + data_offer_t *data_offer = input->data_offer_dnd; const std::string mime_receive = *std::find_first_of(mime_preference_order.begin(), @@ -680,13 +861,13 @@ static void data_device_drop(void *data, struct wl_data_device * /*wl_data_devic data_offer->types.begin(), data_offer->types.end()); - auto read_uris = [](input_t *const input, - data_offer_t *data_offer, - const std::string mime_receive) { - const int x = data_offer->dnd.x; - const int y = data_offer->dnd.y; + auto read_uris_fn = [](input_t *const input, + data_offer_t *data_offer, + wl_surface *surface, + const std::string mime_receive) { + const wl_fixed_t xy[2] = {data_offer->dnd.xy[0], data_offer->dnd.xy[1]}; - const std::string data = read_pipe(data_offer, mime_receive); + const std::string data = read_pipe(data_offer, mime_receive, nullptr); wl_data_offer_finish(data_offer->id); wl_data_offer_destroy(data_offer->id); @@ -700,6 +881,9 @@ static void data_device_drop(void *data, struct wl_data_device * /*wl_data_devic static constexpr const char *file_proto = "file://"; static constexpr const char *crlf = "\r\n"; + GHOST_WindowWayland *win = window_from_surface(surface); + GHOST_ASSERT(win != nullptr, "Unable to find window for drop event from surface"); + std::vector<std::string> uris; size_t pos = 0; @@ -723,14 +907,14 @@ static void data_device_drop(void *data, struct wl_data_device * /*wl_data_devic flist->strings[i] = static_cast<uint8_t *>(malloc((uris[i].size() + 1) * sizeof(uint8_t))); memcpy(flist->strings[i], uris[i].data(), uris[i].size() + 1); } - GHOST_IWindow *win = static_cast<GHOST_WindowWayland *>( - wl_surface_get_user_data(input->focus_pointer)); + + const wl_fixed_t scale = win->scale(); system->pushEvent(new GHOST_EventDragnDrop(system->getMilliSeconds(), GHOST_kEventDraggingDropDone, GHOST_kDragnDropTypeFilenames, win, - x, - y, + wl_fixed_to_int(scale * xy[0]), + wl_fixed_to_int(scale * xy[1]), flist)); } else if (mime_receive == mime_text_plain || mime_receive == mime_text_utf8) { @@ -740,15 +924,20 @@ static void data_device_drop(void *data, struct wl_data_device * /*wl_data_devic wl_display_roundtrip(system->display()); }; - std::thread read_thread(read_uris, input, data_offer, mime_receive); + /* Pass in `input->focus_dnd` instead of accessing it from `input` since the leave callback + * (#data_device_leave) will clear the value once this function starts. */ + std::thread read_thread(read_uris_fn, input, data_offer, input->focus_dnd, mime_receive); read_thread.detach(); } -static void data_device_selection(void *data, - struct wl_data_device * /*wl_data_device*/, - struct wl_data_offer *id) +static void data_device_handle_selection(void *data, + struct wl_data_device * /*wl_data_device*/, + struct wl_data_offer *id) { input_t *input = static_cast<input_t *>(data); + + std::lock_guard lock{input->data_offer_copy_paste_mutex}; + data_offer_t *data_offer = input->data_offer_copy_paste; /* Delete old data offer. */ @@ -766,59 +955,76 @@ static void data_device_selection(void *data, data_offer = static_cast<data_offer_t *>(wl_data_offer_get_user_data(id)); input->data_offer_copy_paste = data_offer; - std::string mime_receive; - for (const std::string type : {mime_text_utf8, mime_text_plain}) { - if (data_offer->types.count(type)) { - mime_receive = type; - break; + auto read_selection_fn = [](input_t *input) { + GHOST_SystemWayland *const system = input->system; + input->data_offer_copy_paste_mutex.lock(); + + data_offer_t *data_offer = input->data_offer_copy_paste; + std::string mime_receive; + for (const std::string type : {mime_text_utf8, mime_text_plain}) { + if (data_offer->types.count(type)) { + mime_receive = type; + break; + } } - } + const std::string data = read_pipe( + data_offer, mime_receive, &input->data_offer_copy_paste_mutex); - auto read_selection = [](GHOST_SystemWayland *const system, - data_offer_t *data_offer, - const std::string mime_receive) { - const std::string data = read_pipe(data_offer, mime_receive); - system->setSelection(data); + { + std::lock_guard lock{system_selection_mutex}; + system->setSelection(data); + } }; - std::thread read_thread(read_selection, input->system, data_offer, mime_receive); + std::thread read_thread(read_selection_fn, input); read_thread.detach(); } static const struct wl_data_device_listener data_device_listener = { - data_device_data_offer, - data_device_enter, - data_device_leave, - data_device_motion, - data_device_drop, - data_device_selection, + data_device_handle_data_offer, + data_device_handle_enter, + data_device_handle_leave, + data_device_handle_motion, + data_device_handle_drop, + data_device_handle_selection, }; -static void cursor_buffer_release(void *data, struct wl_buffer *wl_buffer) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Buffer), #wl_buffer_listener + * \{ */ + +static void cursor_buffer_handle_release(void *data, struct wl_buffer *wl_buffer) { cursor_t *cursor = static_cast<cursor_t *>(data); wl_buffer_destroy(wl_buffer); - if (wl_buffer == cursor->buffer) { + if (wl_buffer == cursor->wl_buffer) { /* the mapped buffer was from a custom cursor */ - cursor->buffer = nullptr; + cursor->wl_buffer = nullptr; } } const struct wl_buffer_listener cursor_buffer_listener = { - cursor_buffer_release, + cursor_buffer_handle_release, }; -static GHOST_IWindow *get_window(struct wl_surface *surface) -{ - if (!surface) { - return nullptr; - } +/** \} */ - for (GHOST_IWindow *win : window_manager->getWindows()) { - if (surface == static_cast<const GHOST_WindowWayland *>(win)->surface()) { - return win; +/* -------------------------------------------------------------------- */ +/** \name Listener (Surface), #wl_surface_listener + * \{ */ + +static GHOST_WindowWayland *window_from_surface(struct wl_surface *surface) +{ + if (surface) { + for (GHOST_IWindow *iwin : window_manager->getWindows()) { + GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin); + if (surface == win->surface()) { + return win; + } } } return nullptr; @@ -835,34 +1041,34 @@ static bool update_cursor_scale(cursor_t &cursor, wl_shm *shm) if (scale > 0 && cursor.scale != scale) { cursor.scale = scale; - wl_surface_set_buffer_scale(cursor.surface, scale); - wl_cursor_theme_destroy(cursor.theme); - cursor.theme = wl_cursor_theme_load(cursor.theme_name.c_str(), scale * cursor.size, shm); + wl_surface_set_buffer_scale(cursor.wl_surface, scale); + wl_cursor_theme_destroy(cursor.wl_theme); + cursor.wl_theme = wl_cursor_theme_load(cursor.theme_name.c_str(), scale * cursor.size, shm); return true; } return false; } -static void cursor_surface_enter(void *data, - struct wl_surface * /*wl_surface*/, - struct wl_output *output) +static void cursor_surface_handle_enter(void *data, + struct wl_surface * /*wl_surface*/, + struct wl_output *output) { input_t *input = static_cast<input_t *>(data); for (const output_t *reg_output : input->system->outputs()) { - if (reg_output->output == output) { + if (reg_output->wl_output == output) { input->cursor.outputs.insert(reg_output); } } update_cursor_scale(input->cursor, input->system->shm()); } -static void cursor_surface_leave(void *data, - struct wl_surface * /*wl_surface*/, - struct wl_output *output) +static void cursor_surface_handle_leave(void *data, + struct wl_surface * /*wl_surface*/, + struct wl_output *output) { input_t *input = static_cast<input_t *>(data); for (const output_t *reg_output : input->system->outputs()) { - if (reg_output->output == output) { + if (reg_output->wl_output == output) { input->cursor.outputs.erase(reg_output); } } @@ -870,19 +1076,24 @@ static void cursor_surface_leave(void *data, } struct wl_surface_listener cursor_surface_listener = { - cursor_surface_enter, - cursor_surface_leave, + cursor_surface_handle_enter, + cursor_surface_handle_leave, }; -static void pointer_enter(void *data, - struct wl_pointer * /*wl_pointer*/, - uint32_t serial, - struct wl_surface *surface, - wl_fixed_t surface_x, - wl_fixed_t surface_y) -{ - GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(get_window(surface)); +/** \} */ +/* -------------------------------------------------------------------- */ +/** \name Listener (Pointer), #wl_pointer_listener + * \{ */ + +static void pointer_handle_enter(void *data, + struct wl_pointer * /*wl_pointer*/, + uint32_t serial, + struct wl_surface *surface, + wl_fixed_t surface_x, + wl_fixed_t surface_y) +{ + GHOST_WindowWayland *win = window_from_surface(surface); if (!win) { return; } @@ -891,27 +1102,28 @@ static void pointer_enter(void *data, input_t *input = static_cast<input_t *>(data); input->pointer_serial = serial; - input->x = win->scale() * wl_fixed_to_int(surface_x); - input->y = win->scale() * wl_fixed_to_int(surface_y); + input->cursor_serial = serial; + input->xy[0] = surface_x; + input->xy[1] = surface_y; input->focus_pointer = surface; win->setCursorShape(win->getCursorShape()); + const wl_fixed_t scale = win->scale(); input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), GHOST_kEventCursorMove, static_cast<GHOST_WindowWayland *>(win), - input->x, - input->y, + wl_fixed_to_int(scale * input->xy[0]), + wl_fixed_to_int(scale * input->xy[1]), GHOST_TABLET_DATA_NONE)); } -static void pointer_leave(void *data, - struct wl_pointer * /*wl_pointer*/, - uint32_t /*serial*/, - struct wl_surface *surface) +static void pointer_handle_leave(void *data, + struct wl_pointer * /*wl_pointer*/, + uint32_t /*serial*/, + struct wl_surface *surface) { - GHOST_IWindow *win = get_window(surface); - + GHOST_IWindow *win = window_from_surface(surface); if (!win) { return; } @@ -920,42 +1132,39 @@ static void pointer_leave(void *data, static_cast<GHOST_WindowWayland *>(win)->deactivate(); } -static void pointer_motion(void *data, - struct wl_pointer * /*wl_pointer*/, - uint32_t /*time*/, - wl_fixed_t surface_x, - wl_fixed_t surface_y) +static void pointer_handle_motion(void *data, + struct wl_pointer * /*wl_pointer*/, + uint32_t /*time*/, + wl_fixed_t surface_x, + wl_fixed_t surface_y) { input_t *input = static_cast<input_t *>(data); - - GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(get_window(input->focus_pointer)); - + GHOST_WindowWayland *win = window_from_surface(input->focus_pointer); if (!win) { return; } - input->x = win->scale() * wl_fixed_to_int(surface_x); - input->y = win->scale() * wl_fixed_to_int(surface_y); + input->xy[0] = surface_x; + input->xy[1] = surface_y; + const wl_fixed_t scale = win->scale(); input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), GHOST_kEventCursorMove, win, - input->x, - input->y, + wl_fixed_to_int(scale * input->xy[0]), + wl_fixed_to_int(scale * input->xy[1]), GHOST_TABLET_DATA_NONE)); } -static void pointer_button(void *data, - struct wl_pointer * /*wl_pointer*/, - uint32_t serial, - uint32_t /*time*/, - uint32_t button, - uint32_t state) +static void pointer_handle_button(void *data, + struct wl_pointer * /*wl_pointer*/, + uint32_t serial, + uint32_t /*time*/, + uint32_t button, + uint32_t state) { input_t *input = static_cast<input_t *>(data); - - GHOST_IWindow *win = get_window(input->focus_pointer); - + GHOST_IWindow *win = window_from_surface(input->focus_pointer); if (!win) { return; } @@ -995,22 +1204,20 @@ static void pointer_button(void *data, break; } - input->data_source->source_serial = serial; + input->data_source_serial = serial; input->buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED); input->system->pushEvent(new GHOST_EventButton( input->system->getMilliSeconds(), etype, win, ebutton, GHOST_TABLET_DATA_NONE)); } -static void pointer_axis(void *data, - struct wl_pointer * /*wl_pointer*/, - uint32_t /*time*/, - uint32_t axis, - wl_fixed_t value) +static void pointer_handle_axis(void *data, + struct wl_pointer * /*wl_pointer*/, + uint32_t /*time*/, + uint32_t axis, + wl_fixed_t value) { input_t *input = static_cast<input_t *>(data); - - GHOST_IWindow *win = get_window(input->focus_pointer); - + GHOST_IWindow *win = window_from_surface(input->focus_pointer); if (!win) { return; } @@ -1024,14 +1231,358 @@ static void pointer_axis(void *data, } static const struct wl_pointer_listener pointer_listener = { - pointer_enter, - pointer_leave, - pointer_motion, - pointer_button, - pointer_axis, + pointer_handle_enter, + pointer_handle_leave, + pointer_handle_motion, + pointer_handle_button, + pointer_handle_axis, }; -static void keyboard_keymap( +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Tablet Tool), #zwp_tablet_tool_v2_listener + * \{ */ + +static void tablet_tool_handle_type(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t tool_type) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + + tool_input->data.Active = tablet_tool_map_type((enum zwp_tablet_tool_v2_type)tool_type); +} + +static void tablet_tool_handle_hardware_serial(void * /*data*/, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t /*hardware_serial_hi*/, + uint32_t /*hardware_serial_lo*/) +{ +} + +static void tablet_tool_handle_hardware_id_wacom( + void * /*data*/, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t /*hardware_id_hi*/, + uint32_t /*hardware_id_lo*/) +{ +} + +static void tablet_tool_handle_capability(void * /*data*/, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t /*capability*/) +{ +} + +static void tablet_tool_handle_done(void * /*data*/, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/) +{ +} +static void tablet_tool_handle_removed(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + + if (tool_input->cursor_surface) { + wl_surface_destroy(tool_input->cursor_surface); + } + input->tablet_tools.erase(zwp_tablet_tool_v2); + + delete tool_input; +} +static void tablet_tool_handle_proximity_in(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t serial, + struct zwp_tablet_v2 * /*tablet*/, + struct wl_surface *surface) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + + input->focus_tablet = surface; + input->tablet_serial = serial; + input->cursor_serial = serial; + + input->data_source_serial = serial; + + /* Update #GHOST_TabletData. */ + GHOST_TabletData &td = tool_input->data; + /* Reset, to avoid using stale tilt/pressure. */ + td.Xtilt = 0.0f; + td.Ytilt = 0.0f; + /* In case pressure isn't supported. */ + td.Pressure = 1.0f; + + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + win->activate(); + + win->setCursorShape(win->getCursorShape()); +} +static void tablet_tool_handle_proximity_out(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + input->focus_tablet = nullptr; + + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + win->setCursorShape(win->getCursorShape()); +} + +static void tablet_tool_handle_down(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t serial) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + + const GHOST_TEventType etype = GHOST_kEventButtonDown; + const GHOST_TButtonMask ebutton = GHOST_kButtonMaskLeft; + input->data_source_serial = serial; + input->buttons.set(ebutton, true); + input->system->pushEvent(new GHOST_EventButton( + input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data)); +} + +static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + + const GHOST_TEventType etype = GHOST_kEventButtonUp; + const GHOST_TButtonMask ebutton = GHOST_kButtonMaskLeft; + input->buttons.set(ebutton, false); + input->system->pushEvent(new GHOST_EventButton( + input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data)); +} + +static void tablet_tool_handle_motion(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + wl_fixed_t x, + wl_fixed_t y) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + + input->xy[0] = x; + input->xy[1] = y; + + const wl_fixed_t scale = win->scale(); + input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), + GHOST_kEventCursorMove, + win, + wl_fixed_to_int(scale * input->xy[0]), + wl_fixed_to_int(scale * input->xy[1]), + tool_input->data)); +} + +static void tablet_tool_handle_pressure(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t pressure) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + + GHOST_TabletData &td = tool_input->data; + td.Pressure = (float)pressure / 65535; +} +static void tablet_tool_handle_distance(void * /*data*/, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t /*distance*/) +{ +} +static void tablet_tool_handle_tilt(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + wl_fixed_t tilt_x, + wl_fixed_t tilt_y) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + + GHOST_TabletData &td = tool_input->data; + /* Map degrees to `-1.0..1.0`. */ + td.Xtilt = wl_fixed_to_double(tilt_x) / 90.0f; + td.Ytilt = wl_fixed_to_double(tilt_y) / 90.0f; + td.Xtilt = td.Xtilt < -1.0f ? -1.0f : (td.Xtilt > 1.0f ? 1.0f : td.Xtilt); + td.Ytilt = td.Ytilt < -1.0f ? -1.0f : (td.Ytilt > 1.0f ? 1.0f : td.Ytilt); +} + +static void tablet_tool_handle_rotation(void * /*data*/, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + wl_fixed_t /*degrees*/) +{ + /* Pass. */ +} + +static void tablet_tool_handle_slider(void * /*data*/, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + int32_t /*position*/) +{ + /* Pass. */ +} +static void tablet_tool_handle_wheel(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + wl_fixed_t /*degrees*/, + int32_t clicks) +{ + if (clicks == 0) { + return; + } + + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + + input->system->pushEvent(new GHOST_EventWheel(input->system->getMilliSeconds(), win, clicks)); +} +static void tablet_tool_handle_button(void *data, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t serial, + uint32_t button, + uint32_t state) +{ + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>(data); + input_t *input = tool_input->input; + GHOST_WindowWayland *win = window_from_surface(input->focus_tablet); + if (!win) { + return; + } + + GHOST_TEventType etype = GHOST_kEventUnknown; + switch (state) { + case WL_POINTER_BUTTON_STATE_RELEASED: + etype = GHOST_kEventButtonUp; + break; + case WL_POINTER_BUTTON_STATE_PRESSED: + etype = GHOST_kEventButtonDown; + break; + } + + GHOST_TButtonMask ebutton = GHOST_kButtonMaskLeft; + switch (button) { + case BTN_STYLUS: + ebutton = GHOST_kButtonMaskRight; + break; + case BTN_STYLUS2: + ebutton = GHOST_kButtonMaskMiddle; + break; + case BTN_STYLUS3: + ebutton = GHOST_kButtonMaskButton4; + break; + } + + input->data_source_serial = serial; + input->buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED); + input->system->pushEvent(new GHOST_EventButton( + input->system->getMilliSeconds(), etype, win, ebutton, tool_input->data)); +} +static void tablet_tool_handle_frame(void * /*data*/, + struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/, + uint32_t /*time*/) +{ +} + +static const struct zwp_tablet_tool_v2_listener tablet_tool_listner = { + tablet_tool_handle_type, + tablet_tool_handle_hardware_serial, + tablet_tool_handle_hardware_id_wacom, + tablet_tool_handle_capability, + tablet_tool_handle_done, + tablet_tool_handle_removed, + tablet_tool_handle_proximity_in, + tablet_tool_handle_proximity_out, + tablet_tool_handle_down, + tablet_tool_handle_up, + tablet_tool_handle_motion, + tablet_tool_handle_pressure, + tablet_tool_handle_distance, + tablet_tool_handle_tilt, + tablet_tool_handle_rotation, + tablet_tool_handle_slider, + tablet_tool_handle_wheel, + tablet_tool_handle_button, + tablet_tool_handle_frame, +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Table Seat), #zwp_tablet_seat_v2_listener + * \{ */ + +static void tablet_seat_handle_tablet_added(void * /*data*/, + struct zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/, + struct zwp_tablet_v2 * /*id*/) +{ + /* Pass. */ +} + +static void tablet_seat_handle_tool_added(void *data, + struct zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/, + struct zwp_tablet_tool_v2 *id) +{ + input_t *input = static_cast<input_t *>(data); + tablet_tool_input_t *tool_input = new tablet_tool_input_t(); + tool_input->input = input; + + /* Every tool has it's own cursor surface. */ + tool_input->cursor_surface = wl_compositor_create_surface(input->system->compositor()); + wl_surface_add_listener(tool_input->cursor_surface, &cursor_surface_listener, (void *)input); + + zwp_tablet_tool_v2_add_listener(id, &tablet_tool_listner, tool_input); + + input->tablet_tools.insert(id); +} + +static void tablet_seat_handle_pad_added(void * /*data*/, + struct zwp_tablet_seat_v2 * /*zwp_tablet_seat_v2*/, + struct zwp_tablet_pad_v2 * /*id*/) +{ + /* Pass. */ +} + +const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { + tablet_seat_handle_tablet_added, + tablet_seat_handle_tool_added, + tablet_seat_handle_pad_added, +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Keyboard), #wl_keyboard_listener + * \{ */ + +static void keyboard_handle_keymap( void *data, struct wl_keyboard * /*wl_keyboard*/, uint32_t format, int32_t fd, uint32_t size) { input_t *input = static_cast<input_t *>(data); @@ -1056,8 +1607,13 @@ static void keyboard_keymap( return; } - input->xkb_state = xkb_state_new(keymap); - + struct xkb_state *xkb_state_next = xkb_state_new(keymap); + if (xkb_state_next) { + if (input->xkb_state) { + xkb_state_unref(input->xkb_state); + } + input->xkb_state = xkb_state_next; + } xkb_keymap_unref(keymap); } @@ -1067,11 +1623,11 @@ static void keyboard_keymap( * Notification that this seat's keyboard focus is on a certain * surface. */ -static void keyboard_enter(void *data, - struct wl_keyboard * /*wl_keyboard*/, - uint32_t /*serial*/, - struct wl_surface *surface, - struct wl_array * /*keys*/) +static void keyboard_handle_enter(void *data, + struct wl_keyboard * /*wl_keyboard*/, + uint32_t /*serial*/, + struct wl_surface *surface, + struct wl_array * /*keys*/) { if (surface != nullptr) { static_cast<input_t *>(data)->focus_keyboard = surface; @@ -1084,10 +1640,10 @@ static void keyboard_enter(void *data, * Notification that this seat's keyboard focus is no longer on a * certain surface. */ -static void keyboard_leave(void *data, - struct wl_keyboard * /*wl_keyboard*/, - uint32_t /*serial*/, - struct wl_surface *surface) +static void keyboard_handle_leave(void *data, + struct wl_keyboard * /*wl_keyboard*/, + uint32_t /*serial*/, + struct wl_surface *surface) { if (surface != nullptr) { static_cast<input_t *>(data)->focus_keyboard = nullptr; @@ -1120,12 +1676,12 @@ static xkb_keysym_t xkb_state_key_get_one_sym_without_modifiers(struct xkb_state return sym; } -static void keyboard_key(void *data, - struct wl_keyboard * /*wl_keyboard*/, - uint32_t serial, - uint32_t /*time*/, - uint32_t key, - uint32_t state) +static void keyboard_handle_key(void *data, + struct wl_keyboard * /*wl_keyboard*/, + uint32_t serial, + uint32_t /*time*/, + uint32_t key, + uint32_t state) { input_t *input = static_cast<input_t *>(data); @@ -1144,7 +1700,6 @@ static void keyboard_key(void *data, if (sym == XKB_KEY_NoSymbol) { return; } - const GHOST_TKey gkey = xkb_map_gkey(sym); /* Delete previous timer. */ if (xkb_keymap_key_repeats(xkb_state_get_keymap(input->xkb_state), key + 8) && @@ -1154,7 +1709,9 @@ static void keyboard_key(void *data, input->key_repeat.timer = nullptr; } - GHOST_TEventKeyData key_data; + GHOST_TEventKeyData key_data = { + .key = xkb_map_gkey(sym), + }; if (etype == GHOST_kEventKeyDown) { xkb_state_key_get_utf8( @@ -1164,12 +1721,12 @@ static void keyboard_key(void *data, key_data.utf8_buf[0] = '\0'; } - input->data_source->source_serial = serial; + input->data_source_serial = serial; GHOST_IWindow *win = static_cast<GHOST_WindowWayland *>( wl_surface_get_user_data(input->focus_keyboard)); input->system->pushEvent(new GHOST_EventKey( - input->system->getMilliSeconds(), etype, win, gkey, '\0', key_data.utf8_buf, false)); + input->system->getMilliSeconds(), etype, win, key_data.key, '\0', key_data.utf8_buf, false)); /* Start timer for repeating key, if applicable. */ if (input->key_repeat.rate > 0 && @@ -1179,33 +1736,32 @@ static void keyboard_key(void *data, key_repeat_payload_t *payload = new key_repeat_payload_t({ .system = input->system, .window = win, - .key = gkey, .key_data = key_data, }); - auto cb = [](GHOST_ITimerTask *task, uint64_t /*time*/) { + auto key_repeat_fn = [](GHOST_ITimerTask *task, uint64_t /*time*/) { struct key_repeat_payload_t *payload = static_cast<key_repeat_payload_t *>( task->getUserData()); payload->system->pushEvent(new GHOST_EventKey(payload->system->getMilliSeconds(), GHOST_kEventKeyDown, payload->window, - payload->key, + payload->key_data.key, '\0', payload->key_data.utf8_buf, true)); }; input->key_repeat.timer = input->system->installTimer( - input->key_repeat.delay, 1000 / input->key_repeat.rate, cb, payload); + input->key_repeat.delay, 1000 / input->key_repeat.rate, key_repeat_fn, payload); } } -static void keyboard_modifiers(void *data, - struct wl_keyboard * /*wl_keyboard*/, - uint32_t /*serial*/, - uint32_t mods_depressed, - uint32_t mods_latched, - uint32_t mods_locked, - uint32_t group) +static void keyboard_handle_modifiers(void *data, + struct wl_keyboard * /*wl_keyboard*/, + uint32_t /*serial*/, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group) { xkb_state_update_mask(static_cast<input_t *>(data)->xkb_state, mods_depressed, @@ -1216,10 +1772,10 @@ static void keyboard_modifiers(void *data, group); } -static void keyboard_repeat_info(void *data, - struct wl_keyboard * /*wl_keyboard*/, - int32_t rate, - int32_t delay) +static void keyboard_repeat_handle_info(void *data, + struct wl_keyboard * /*wl_keyboard*/, + int32_t rate, + int32_t delay) { input_t *input = static_cast<input_t *>(data); @@ -1228,79 +1784,174 @@ static void keyboard_repeat_info(void *data, } static const struct wl_keyboard_listener keyboard_listener = { - keyboard_keymap, - keyboard_enter, - keyboard_leave, - keyboard_key, - keyboard_modifiers, - keyboard_repeat_info, + keyboard_handle_keymap, + keyboard_handle_enter, + keyboard_handle_leave, + keyboard_handle_key, + keyboard_handle_modifiers, + keyboard_repeat_handle_info, }; -static void seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Seat), #wl_seat_listener + * \{ */ + +static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) { input_t *input = static_cast<input_t *>(data); - input->pointer = nullptr; - input->keyboard = nullptr; + input->wl_pointer = nullptr; + input->wl_keyboard = nullptr; if (capabilities & WL_SEAT_CAPABILITY_POINTER) { - input->pointer = wl_seat_get_pointer(wl_seat); - input->cursor.surface = wl_compositor_create_surface(input->system->compositor()); + input->wl_pointer = wl_seat_get_pointer(wl_seat); + input->cursor.wl_surface = wl_compositor_create_surface(input->system->compositor()); input->cursor.visible = true; - input->cursor.buffer = nullptr; + input->cursor.wl_buffer = nullptr; input->cursor.file_buffer = new buffer_t; if (!get_cursor_settings(input->cursor.theme_name, input->cursor.size)) { input->cursor.theme_name = std::string(); input->cursor.size = default_cursor_size; } - wl_pointer_add_listener(input->pointer, &pointer_listener, data); - wl_surface_add_listener(input->cursor.surface, &cursor_surface_listener, data); + wl_pointer_add_listener(input->wl_pointer, &pointer_listener, data); + wl_surface_add_listener(input->cursor.wl_surface, &cursor_surface_listener, data); } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { - input->keyboard = wl_seat_get_keyboard(wl_seat); - wl_keyboard_add_listener(input->keyboard, &keyboard_listener, data); + input->wl_keyboard = wl_seat_get_keyboard(wl_seat); + wl_keyboard_add_listener(input->wl_keyboard, &keyboard_listener, data); } } -static void seat_name(void *data, struct wl_seat * /*wl_seat*/, const char *name) +static void seat_handle_name(void *data, struct wl_seat * /*wl_seat*/, const char *name) { static_cast<input_t *>(data)->name = std::string(name); } static const struct wl_seat_listener seat_listener = { - seat_capabilities, - seat_name, + seat_handle_capabilities, + seat_handle_name, }; -static void output_geometry(void *data, - struct wl_output * /*wl_output*/, - int32_t /*x*/, - int32_t /*y*/, - int32_t physical_width, - int32_t physical_height, - int32_t /*subpixel*/, - const char *make, - const char *model, - int32_t transform) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (XDG Output), #zxdg_output_v1_listener + * \{ */ + +static void xdg_output_handle_logical_position(void *data, + struct zxdg_output_v1 * /*xdg_output*/, + int32_t x, + int32_t y) +{ + output_t *output = static_cast<output_t *>(data); + output->position_logical[0] = x; + output->position_logical[1] = y; + output->has_position_logical = true; +} + +static void xdg_output_handle_logical_size(void *data, + struct zxdg_output_v1 * /*xdg_output*/, + int32_t width, + int32_t height) +{ + output_t *output = static_cast<output_t *>(data); + + if (output->size_logical[0] != 0 && output->size_logical[1] != 0) { + /* Original comment from SDL. */ + /* FIXME: GNOME has a bug where the logical size does not account for + * scale, resulting in bogus viewport sizes. + * + * Until this is fixed, validate that _some_ kind of scaling is being + * done (we can't match exactly because fractional scaling can't be + * detected otherwise), then override if necessary. + * -flibit + */ + if ((output->size_logical[0] == width) && (output->scale_fractional == wl_fixed_from_int(1))) { + GHOST_PRINT("xdg_output scale did not match, overriding with wl_output scale"); + return; + } + } + + output->size_logical[0] = width; + output->size_logical[1] = height; + output->has_size_logical = true; +} + +static void xdg_output_handle_done(void * /*data*/, struct zxdg_output_v1 * /*xdg_output*/) +{ + /* NOTE: `xdg-output.done` events are deprecated and only apply below version 3 of the protocol. + * `wl-output.done` event will be emitted in version 3 or higher. */ +} + +static void xdg_output_handle_name(void * /*data*/, + struct zxdg_output_v1 * /*xdg_output*/, + const char * /*name*/) +{ + /* Pass. */ +} + +static void xdg_output_handle_description(void * /*data*/, + struct zxdg_output_v1 * /*xdg_output*/, + const char * /*description*/) +{ + /* Pass. */ +} + +static const struct zxdg_output_v1_listener xdg_output_listener = { + xdg_output_handle_logical_position, + xdg_output_handle_logical_size, + xdg_output_handle_done, + xdg_output_handle_name, + xdg_output_handle_description, +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Output), #wl_output_listener + * \{ */ + +static void output_handle_geometry(void *data, + struct wl_output * /*wl_output*/, + int32_t /*x*/, + int32_t /*y*/, + int32_t physical_width, + int32_t physical_height, + int32_t /*subpixel*/, + const char *make, + const char *model, + int32_t transform) { output_t *output = static_cast<output_t *>(data); output->transform = transform; output->make = std::string(make); output->model = std::string(model); - output->width_mm = physical_width; - output->height_mm = physical_height; + output->size_mm[0] = physical_width; + output->size_mm[1] = physical_height; } -static void output_mode(void *data, - struct wl_output * /*wl_output*/, - uint32_t /*flags*/, - int32_t width, - int32_t height, - int32_t /*refresh*/) +static void output_handle_mode(void *data, + struct wl_output * /*wl_output*/, + uint32_t flags, + int32_t width, + int32_t height, + int32_t /*refresh*/) { output_t *output = static_cast<output_t *>(data); - output->width_pxl = width; - output->height_pxl = height; + + if (flags & WL_OUTPUT_MODE_CURRENT) { + output->size_native[0] = width; + output->size_native[1] = height; + + /* Don't rotate this yet, `wl-output` coordinates are transformed in + * handle_done and `xdg-output` coordinates are pre-transformed. */ + if (!output->has_size_logical) { + output->size_logical[0] = width; + output->size_logical[1] = height; + } + } } /** @@ -1311,36 +1962,71 @@ static void output_mode(void *data, * changes done after that. This allows changes to the output * properties to be seen as atomic, even if they happen via multiple events. */ -static void output_done(void * /*data*/, struct wl_output * /*wl_output*/) +static void output_handle_done(void *data, struct wl_output * /*wl_output*/) { + output_t *output = static_cast<output_t *>(data); + int32_t size_native[2]; + if (output->transform & WL_OUTPUT_TRANSFORM_90) { + size_native[0] = output->size_native[1]; + size_native[1] = output->size_native[1]; + } + else { + size_native[0] = output->size_native[0]; + size_native[1] = output->size_native[1]; + } + + /* If `xdg-output` is present, calculate the true scale of the desktop */ + if (output->has_size_logical) { + + /* NOTE: it's not necessary to divide these values by their greatest-common-denominator + * as even a 64k screen resolution doesn't approach overflowing an `int32_t`. */ + + GHOST_ASSERT(size_native[0] && output->size_logical[0], + "Screen size values were not set when they were expected to be."); + + output->scale_fractional = wl_fixed_from_int(size_native[0]) / output->size_logical[0]; + output->has_scale_fractional = true; + } } -static void output_scale(void *data, struct wl_output * /*wl_output*/, int32_t factor) +static void output_handle_scale(void *data, struct wl_output * /*wl_output*/, int32_t factor) { static_cast<output_t *>(data)->scale = factor; } static const struct wl_output_listener output_listener = { - output_geometry, - output_mode, - output_done, - output_scale, + output_handle_geometry, + output_handle_mode, + output_handle_done, + output_handle_scale, }; -static void shell_ping(void * /*data*/, struct xdg_wm_base *xdg_wm_base, uint32_t serial) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (XDG WM Base), #xdg_wm_base_listener + * \{ */ + +static void shell_handle_ping(void * /*data*/, struct xdg_wm_base *xdg_wm_base, uint32_t serial) { xdg_wm_base_pong(xdg_wm_base, serial); } static const struct xdg_wm_base_listener shell_listener = { - shell_ping, + shell_handle_ping, }; -static void global_add(void *data, - struct wl_registry *wl_registry, - uint32_t name, - const char *interface, - uint32_t /*version*/) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Registry), #wl_registry_listener + * \{ */ + +static void global_handle_add(void *data, + struct wl_registry *wl_registry, + uint32_t name, + const char *interface, + uint32_t /*version*/) { struct display_t *display = static_cast<struct display_t *>(data); if (!strcmp(interface, wl_compositor_interface.name)) { @@ -1356,30 +2042,37 @@ static void global_add(void *data, display->xdg_decoration_manager = static_cast<zxdg_decoration_manager_v1 *>( wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1)); } + else if (!strcmp(interface, zxdg_output_manager_v1_interface.name)) { + display->xdg_output_manager = static_cast<zxdg_output_manager_v1 *>( + wl_registry_bind(wl_registry, name, &zxdg_output_manager_v1_interface, 3)); + for (output_t *output : display->outputs) { + output->xdg_output = zxdg_output_manager_v1_get_xdg_output(display->xdg_output_manager, + output->wl_output); + zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); + } + } else if (!strcmp(interface, wl_output_interface.name)) { output_t *output = new output_t; - output->scale = 1; - output->output = static_cast<wl_output *>( + output->wl_output = static_cast<wl_output *>( wl_registry_bind(wl_registry, name, &wl_output_interface, 2)); display->outputs.push_back(output); - wl_output_add_listener(output->output, &output_listener, output); + wl_output_add_listener(output->wl_output, &output_listener, output); + + if (display->xdg_output_manager) { + output->xdg_output = zxdg_output_manager_v1_get_xdg_output(display->xdg_output_manager, + output->wl_output); + zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); + } } else if (!strcmp(interface, wl_seat_interface.name)) { input_t *input = new input_t; input->system = display->system; input->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - input->xkb_state = nullptr; - input->data_offer_dnd = nullptr; - input->data_offer_copy_paste = nullptr; input->data_source = new data_source_t; - input->data_source->data_source = nullptr; - input->data_source->buffer_out = nullptr; - input->relative_pointer = nullptr; - input->locked_pointer = nullptr; - input->seat = static_cast<wl_seat *>( + input->wl_seat = static_cast<wl_seat *>( wl_registry_bind(wl_registry, name, &wl_seat_interface, 4)); display->inputs.push_back(input); - wl_seat_add_listener(input->seat, &seat_listener, input); + wl_seat_add_listener(input->wl_seat, &seat_listener, input); } else if (!strcmp(interface, wl_shm_interface.name)) { display->shm = static_cast<wl_shm *>( @@ -1387,7 +2080,11 @@ static void global_add(void *data, } else if (!strcmp(interface, wl_data_device_manager_interface.name)) { display->data_device_manager = static_cast<wl_data_device_manager *>( - wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, 1)); + wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, 3)); + } + else if (!strcmp(interface, zwp_tablet_manager_v2_interface.name)) { + display->tablet_manager = static_cast<zwp_tablet_manager_v2 *>( + wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1)); } else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name)) { display->relative_pointer_manager = static_cast<zwp_relative_pointer_manager_v1 *>( @@ -1408,13 +2105,15 @@ static void global_add(void *data, * name is no longer available. If the client bound to the global * using the bind request, the client should now destroy that object. */ -static void global_remove(void * /*data*/, struct wl_registry * /*wl_registry*/, uint32_t /*name*/) +static void global_handle_remove(void * /*data*/, + struct wl_registry * /*wl_registry*/, + uint32_t /*name*/) { } static const struct wl_registry_listener registry_listener = { - global_add, - global_remove, + global_handle_add, + global_handle_remove, }; /** \} */ @@ -1427,6 +2126,8 @@ static const struct wl_registry_listener registry_listener = { GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), d(new display_t) { + wl_log_set_handler_client(ghost_wayland_log_handler); + d->system = this; /* Connect to the Wayland server. */ d->display = wl_display_connect(nullptr); @@ -1453,10 +2154,18 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), d(new display_t) if (d->data_device_manager) { for (input_t *input : d->inputs) { input->data_device = wl_data_device_manager_get_data_device(d->data_device_manager, - input->seat); + input->wl_seat); wl_data_device_add_listener(input->data_device, &data_device_listener, input); } } + + if (d->tablet_manager) { + for (input_t *input : d->inputs) { + input->tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(d->tablet_manager, + input->wl_seat); + zwp_tablet_seat_v2_add_listener(input->tablet_seat, &tablet_seat_listener, input); + } + } } GHOST_SystemWayland::~GHOST_SystemWayland() @@ -1485,42 +2194,47 @@ int GHOST_SystemWayland::setConsoleWindowState(GHOST_TConsoleWindowState /*actio GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) const { - if (!d->inputs.empty()) { - static const xkb_state_component mods_all = xkb_state_component( - XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_LOCKED | - XKB_STATE_MODS_EFFECTIVE); - - keys.set(GHOST_kModifierKeyLeftShift, - xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_SHIFT, mods_all) == - 1); - keys.set(GHOST_kModifierKeyRightShift, - xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_SHIFT, mods_all) == - 1); - keys.set(GHOST_kModifierKeyLeftAlt, - xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "LAlt", mods_all) == 1); - keys.set(GHOST_kModifierKeyRightAlt, - xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "RAlt", mods_all) == 1); - keys.set(GHOST_kModifierKeyLeftControl, - xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "LControl", mods_all) == 1); - keys.set(GHOST_kModifierKeyRightControl, - xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "RControl", mods_all) == 1); - keys.set(GHOST_kModifierKeyOS, - xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "Super", mods_all) == 1); - keys.set(GHOST_kModifierKeyNumMasks, - xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, "NumLock", mods_all) == 1); - - return GHOST_kSuccess; + if (d->inputs.empty()) { + return GHOST_kFailure; } - return GHOST_kFailure; + + static const xkb_state_component mods_all = xkb_state_component( + XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_LOCKED | + XKB_STATE_MODS_EFFECTIVE); + + bool val; + + /* NOTE: XKB doesn't seem to differentiate between left/right modifiers. */ + + val = xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_SHIFT, mods_all) == 1; + keys.set(GHOST_kModifierKeyLeftShift, val); + keys.set(GHOST_kModifierKeyRightShift, val); + + val = xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_ALT, mods_all) == 1; + keys.set(GHOST_kModifierKeyLeftAlt, val); + keys.set(GHOST_kModifierKeyRightAlt, val); + + val = xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_CTRL, mods_all) == 1; + keys.set(GHOST_kModifierKeyLeftControl, val); + keys.set(GHOST_kModifierKeyRightControl, val); + + val = xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_LOGO, mods_all) == 1; + keys.set(GHOST_kModifierKeyOS, val); + + val = xkb_state_mod_name_is_active(d->inputs[0]->xkb_state, XKB_MOD_NAME_NUM, mods_all) == 1; + keys.set(GHOST_kModifierKeyNumMasks, val); + + return GHOST_kSuccess; } GHOST_TSuccess GHOST_SystemWayland::getButtons(GHOST_Buttons &buttons) const { - if (!d->inputs.empty()) { - buttons = d->inputs[0]->buttons; - return GHOST_kSuccess; + if (d->inputs.empty()) { + return GHOST_kFailure; } - return GHOST_kFailure; + + buttons = d->inputs[0]->buttons; + return GHOST_kSuccess; } char *GHOST_SystemWayland::getClipboard(bool /*selection*/) const @@ -1536,25 +2250,29 @@ void GHOST_SystemWayland::putClipboard(const char *buffer, bool /*selection*/) c return; } - data_source_t *data_source = d->inputs[0]->data_source; + input_t *input = d->inputs[0]; + + std::lock_guard lock{input->data_source_mutex}; + + data_source_t *data_source = input->data_source; /* Copy buffer. */ + free(data_source->buffer_out); const size_t buffer_size = strlen(buffer) + 1; data_source->buffer_out = static_cast<char *>(malloc(buffer_size)); std::memcpy(data_source->buffer_out, buffer, buffer_size); data_source->data_source = wl_data_device_manager_create_data_source(d->data_device_manager); - wl_data_source_add_listener( - data_source->data_source, &data_source_listener, data_source->buffer_out); + wl_data_source_add_listener(data_source->data_source, &data_source_listener, input); for (const std::string &type : mime_send) { wl_data_source_offer(data_source->data_source, type.c_str()); } - if (!d->inputs.empty() && d->inputs[0]->data_device) { + if (input->data_device) { wl_data_device_set_selection( - d->inputs[0]->data_device, data_source->data_source, data_source->source_serial); + input->data_device, data_source->data_source, input->data_source_serial); } } @@ -1565,12 +2283,30 @@ uint8_t GHOST_SystemWayland::getNumDisplays() const GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) const { - if (d->inputs.empty() || (d->inputs[0]->focus_pointer == nullptr)) { + if (d->inputs.empty()) { return GHOST_kFailure; } - x = d->inputs[0]->x; - y = d->inputs[0]->y; + input_t *input = d->inputs[0]; + struct wl_surface *surface = nullptr; + if (input->pointer_serial == input->cursor_serial) { + surface = input->focus_pointer; + } + else if (input->tablet_serial == input->cursor_serial) { + surface = input->focus_tablet; + } + if (!surface) { + return GHOST_kFailure; + } + + GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(wl_surface_get_user_data(surface)); + if (!win) { + return GHOST_kFailure; + } + + const wl_fixed_t scale = win->scale(); + x = wl_fixed_to_int(scale * input->xy[0]); + y = wl_fixed_to_int(scale * input->xy[1]); return GHOST_kSuccess; } @@ -1583,8 +2319,8 @@ void GHOST_SystemWayland::getMainDisplayDimensions(uint32_t &width, uint32_t &he { if (getNumDisplays() > 0) { /* We assume first output as main. */ - width = uint32_t(d->outputs[0]->width_pxl) / d->outputs[0]->scale; - height = uint32_t(d->outputs[0]->height_pxl) / d->outputs[0]->scale; + width = uint32_t(d->outputs[0]->size_native[0]) / d->outputs[0]->scale; + height = uint32_t(d->outputs[0]->size_native[1]) / d->outputs[0]->scale; } } @@ -1705,12 +2441,12 @@ wl_compositor *GHOST_SystemWayland::compositor() return d->compositor; } -xdg_wm_base *GHOST_SystemWayland::shell() +xdg_wm_base *GHOST_SystemWayland::xdg_shell() { return d->xdg_shell; } -zxdg_decoration_manager_v1 *GHOST_SystemWayland::decoration_manager() +zxdg_decoration_manager_v1 *GHOST_SystemWayland::xdg_decoration_manager() { return d->xdg_decoration_manager; } @@ -1736,16 +2472,41 @@ static void set_cursor_buffer(input_t *input, wl_buffer *buffer) c->visible = (buffer != nullptr); - wl_surface_attach(c->surface, buffer, 0, 0); + const int32_t image_size_x = int32_t(c->wl_image.width); + const int32_t image_size_y = int32_t(c->wl_image.height); - wl_surface_damage(c->surface, 0, 0, int32_t(c->image.width), int32_t(c->image.height)); - wl_pointer_set_cursor(input->pointer, + const int32_t hotspot_x = int32_t(c->wl_image.hotspot_x) / c->scale; + const int32_t hotspot_y = int32_t(c->wl_image.hotspot_y) / c->scale; + + wl_surface_attach(c->wl_surface, buffer, 0, 0); + wl_surface_damage(c->wl_surface, 0, 0, image_size_x, image_size_y); + + wl_pointer_set_cursor(input->wl_pointer, input->pointer_serial, - c->visible ? c->surface : nullptr, - int32_t(c->image.hotspot_x) / c->scale, - int32_t(c->image.hotspot_y) / c->scale); + c->visible ? c->wl_surface : nullptr, + hotspot_x, + hotspot_y); + + wl_surface_commit(c->wl_surface); - wl_surface_commit(c->surface); + /* Set the cursor for all tablet tools as well. */ + for (struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : input->tablet_tools) { + tablet_tool_input_t *tool_input = static_cast<tablet_tool_input_t *>( + zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2)); + /* FIXME: for some reason cursor scale is applied twice (when the scale isn't 1x), + * this happens both in gnome-shell & KDE. Setting the surface scale here doesn't help. */ + // wl_surface_set_buffer_scale(tool_input->cursor_surface, 1); + wl_surface_attach(tool_input->cursor_surface, buffer, 0, 0); + wl_surface_damage(tool_input->cursor_surface, 0, 0, image_size_x, image_size_y); + + zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2, + input->tablet_serial, + c->visible ? tool_input->cursor_surface : nullptr, + hotspot_x, + hotspot_y); + + wl_surface_commit(tool_input->cursor_surface); + } } GHOST_TSuccess GHOST_SystemWayland::setCursorShape(GHOST_TStandardCursor shape) @@ -1759,12 +2520,13 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(GHOST_TStandardCursor shape) input_t *input = d->inputs[0]; cursor_t *c = &input->cursor; - if (!c->theme) { + if (!c->wl_theme) { /* The cursor surface hasn't entered an output yet. Initialize theme with scale 1. */ - c->theme = wl_cursor_theme_load(c->theme_name.c_str(), c->size, d->inputs[0]->system->shm()); + c->wl_theme = wl_cursor_theme_load( + c->theme_name.c_str(), c->size, d->inputs[0]->system->shm()); } - wl_cursor *cursor = wl_cursor_theme_get_cursor(c->theme, cursor_name.c_str()); + wl_cursor *cursor = wl_cursor_theme_get_cursor(c->wl_theme, cursor_name.c_str()); if (!cursor) { GHOST_PRINT("cursor '" << cursor_name << "' does not exist" << std::endl); @@ -1777,8 +2539,8 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(GHOST_TStandardCursor shape) return GHOST_kFailure; } - c->buffer = buffer; - c->image = *image; + c->wl_buffer = buffer; + c->wl_image = *image; set_cursor_buffer(input, buffer); @@ -1884,11 +2646,11 @@ GHOST_TSuccess GHOST_SystemWayland::setCustomCursorShape(uint8_t *bitmap, } } - cursor->buffer = buffer; - cursor->image.width = uint32_t(sizex); - cursor->image.height = uint32_t(sizey); - cursor->image.hotspot_x = uint32_t(hotX); - cursor->image.hotspot_y = uint32_t(hotY); + cursor->wl_buffer = buffer; + cursor->wl_image.width = uint32_t(sizex); + cursor->wl_image.height = uint32_t(sizey); + cursor->wl_image.hotspot_x = uint32_t(hotX); + cursor->wl_image.hotspot_y = uint32_t(hotY); set_cursor_buffer(d->inputs[0], buffer); @@ -1906,7 +2668,7 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorVisibility(bool visible) cursor_t *cursor = &input->cursor; if (visible) { if (!cursor->visible) { - set_cursor_buffer(input, cursor->buffer); + set_cursor_buffer(input, cursor->wl_buffer); } } else { @@ -1918,9 +2680,21 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorVisibility(bool visible) return GHOST_kSuccess; } +bool GHOST_SystemWayland::supportsCursorWarp() +{ + /* WAYLAND doesn't support setting the cursor position directly, + * this is an intentional choice, forcing us to use a software cursor in this case. */ + return false; +} + +bool GHOST_SystemWayland::supportsWindowPosition() +{ + /* WAYLAND doesn't support accessing the window position. */ + return false; +} + GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mode, const GHOST_TGrabCursorMode mode_current, - wl_surface *surface) { /* ignore, if the required protocols are not supported */ @@ -1939,42 +2713,123 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorGrab(const GHOST_TGrabCursorMode mo input_t *input = d->inputs[0]; - if (mode_current == GHOST_kGrabHide) { +#define MODE_NEEDS_LOCK(m) ((m) == GHOST_kGrabWrap || (m) == GHOST_kGrabHide) +#define MODE_NEEDS_HIDE(m) ((m) == GHOST_kGrabHide) +#define MODE_NEEDS_CONFINE(m) ((m) == GHOST_kGrabNormal) + + const bool was_lock = MODE_NEEDS_LOCK(mode_current); + const bool use_lock = MODE_NEEDS_LOCK(mode); + + /* Check for wrap as #supportsCursorWarp isn't supported. */ + const bool was_hide = MODE_NEEDS_HIDE(mode_current) || (mode_current == GHOST_kGrabWrap); + const bool use_hide = MODE_NEEDS_HIDE(mode) || (mode == GHOST_kGrabWrap); + + const bool was_confine = MODE_NEEDS_CONFINE(mode_current); + const bool use_confine = MODE_NEEDS_CONFINE(mode); + +#undef MODE_NEEDS_LOCK +#undef MODE_NEEDS_HIDE +#undef MODE_NEEDS_CONFINE + + if (!use_hide) { setCursorVisibility(true); } - if ((mode == GHOST_kGrabDisable) || - /* Switching from one grab mode to another, - * in this case disable the current locks as it makes logic confusing, - * postpone changing the cursor to avoid flickering. */ - (mode_current != GHOST_kGrabDisable)) { + /* Switching from one grab mode to another, + * in this case disable the current locks as it makes logic confusing, + * postpone changing the cursor to avoid flickering. */ + if (!use_lock) { if (input->relative_pointer) { zwp_relative_pointer_v1_destroy(input->relative_pointer); input->relative_pointer = nullptr; } if (input->locked_pointer) { + /* Request location to restore to. */ + if (mode_current == GHOST_kGrabWrap) { + /* The chance this fails is _very_ low. */ + GHOST_WindowWayland *win = window_from_surface(surface); + if (!win) { + GHOST_PRINT("could not find window from surface when un-grabbing!" << std::endl); + } + else { + GHOST_Rect bounds; + int32_t xy_new[2] = {input->xy[0], input->xy[1]}; + + /* Fallback to window bounds. */ + if (win->getCursorGrabBounds(bounds) == GHOST_kFailure) { + win->getClientBounds(bounds); + } + + const int scale = win->scale(); + + bounds.m_l = wl_fixed_from_int(bounds.m_l) / scale; + bounds.m_t = wl_fixed_from_int(bounds.m_t) / scale; + bounds.m_r = wl_fixed_from_int(bounds.m_r) / scale; + bounds.m_b = wl_fixed_from_int(bounds.m_b) / scale; + + bounds.wrapPoint(xy_new[0], xy_new[1], 0, win->getCursorGrabAxis()); + + /* Push an event so the new location is registered. */ + if ((xy_new[0] != input->xy[0]) || (xy_new[1] != input->xy[1])) { + input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), + GHOST_kEventCursorMove, + win, + wl_fixed_to_int(scale * xy_new[0]), + wl_fixed_to_int(scale * xy_new[1]), + GHOST_TABLET_DATA_NONE)); + } + input->xy[0] = xy_new[0]; + input->xy[1] = xy_new[1]; + + zwp_locked_pointer_v1_set_cursor_position_hint( + input->locked_pointer, xy_new[0], xy_new[1]); + wl_surface_commit(surface); + } + } + zwp_locked_pointer_v1_destroy(input->locked_pointer); input->locked_pointer = nullptr; } } + if (!use_confine) { + if (input->confined_pointer) { + zwp_confined_pointer_v1_destroy(input->confined_pointer); + input->confined_pointer = nullptr; + } + } + if (mode != GHOST_kGrabDisable) { - /* TODO(@campbellbarton): As WAYLAND does not support warping the pointer it may not be - * possible to support #GHOST_kGrabWrap by pragmatically settings it's coordinates. - * An alternative could be to draw the cursor in software (and hide the real cursor), - * or just accept a locked cursor on WAYLAND. */ - input->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer( - d->relative_pointer_manager, input->pointer); - zwp_relative_pointer_v1_add_listener( - input->relative_pointer, &relative_pointer_listener, input); - input->locked_pointer = zwp_pointer_constraints_v1_lock_pointer( - d->pointer_constraints, - surface, - input->pointer, - nullptr, - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); - - if (mode == GHOST_kGrabHide) { + if (use_lock) { + if (!was_lock) { + /* TODO(@campbellbarton): As WAYLAND does not support warping the pointer it may not be + * possible to support #GHOST_kGrabWrap by pragmatically settings it's coordinates. + * An alternative could be to draw the cursor in software (and hide the real cursor), + * or just accept a locked cursor on WAYLAND. */ + input->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer( + d->relative_pointer_manager, input->wl_pointer); + zwp_relative_pointer_v1_add_listener( + input->relative_pointer, &relative_pointer_listener, input); + input->locked_pointer = zwp_pointer_constraints_v1_lock_pointer( + d->pointer_constraints, + surface, + input->wl_pointer, + nullptr, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + } + } + else if (use_confine) { + if (!was_confine) { + input->confined_pointer = zwp_pointer_constraints_v1_confine_pointer( + d->pointer_constraints, + surface, + input->wl_pointer, + nullptr, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + } + } + + if (use_hide && !was_hide) { setCursorVisibility(false); } } diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h index eeb65eb4fc3..762ceb80e38 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.h +++ b/intern/ghost/intern/GHOST_SystemWayland.h @@ -22,11 +22,32 @@ class GHOST_WindowWayland; struct display_t; struct output_t { - struct wl_output *output; - int32_t width_pxl, height_pxl; /* Dimensions in pixel. */ - int32_t width_mm, height_mm; /* Dimensions in millimeter. */ - int transform; - int scale; + struct wl_output *wl_output = nullptr; + struct zxdg_output_v1 *xdg_output = nullptr; + /** Dimensions in pixels. */ + int32_t size_native[2] = {0, 0}; + /** Dimensions in millimeter. */ + int32_t size_mm[2] = {0, 0}; + + int32_t size_logical[2] = {0, 0}; + bool has_size_logical = false; + + int32_t position_logical[2] = {0, 0}; + bool has_position_logical = false; + + int transform = 0; + int scale = 1; + /** + * The integer `scale` value should be used in almost all cases, + * as this is what is used for most API calls. + * Only use fractional scaling to calculate the DPI. + * + * \note Internally an #wl_fixed_t is used to store the scale of the display, + * so use the same value here (avoid floating point arithmetic in general). + */ + wl_fixed_t scale_fractional = wl_fixed_from_int(1); + bool has_scale_fractional = false; + std::string make; std::string model; }; @@ -79,9 +100,9 @@ class GHOST_SystemWayland : public GHOST_System { wl_compositor *compositor(); - xdg_wm_base *shell(); + xdg_wm_base *xdg_shell(); - zxdg_decoration_manager_v1 *decoration_manager(); + zxdg_decoration_manager_v1 *xdg_decoration_manager(); const std::vector<output_t *> &outputs() const; @@ -103,6 +124,9 @@ class GHOST_SystemWayland : public GHOST_System { GHOST_TSuccess setCursorVisibility(bool visible); + bool supportsCursorWarp(); + bool supportsWindowPosition(); + GHOST_TSuccess setCursorGrab(const GHOST_TGrabCursorMode mode, const GHOST_TGrabCursorMode mode_current, wl_surface *surface); diff --git a/intern/ghost/intern/GHOST_Window.cpp b/intern/ghost/intern/GHOST_Window.cpp index 954f0bc244d..de7c5422d3f 100644 --- a/intern/ghost/intern/GHOST_Window.cpp +++ b/intern/ghost/intern/GHOST_Window.cpp @@ -155,10 +155,31 @@ GHOST_TSuccess GHOST_Window::setCursorGrab(GHOST_TGrabCursorMode mode, GHOST_TSuccess GHOST_Window::getCursorGrabBounds(GHOST_Rect &bounds) { + if (m_cursorGrab != GHOST_kGrabWrap) { + return GHOST_kFailure; + } bounds = m_cursorGrabBounds; return (bounds.m_l == -1 && bounds.m_r == -1) ? GHOST_kFailure : GHOST_kSuccess; } +void GHOST_Window::getCursorGrabState(GHOST_TGrabCursorMode &mode, + GHOST_TAxisFlag &wrap_axis, + GHOST_Rect &bounds) +{ + mode = m_cursorGrab; + if (m_cursorGrab == GHOST_kGrabWrap) { + bounds = m_cursorGrabBounds; + wrap_axis = m_cursorGrabAxis; + } + else { + bounds.m_l = -1; + bounds.m_r = -1; + bounds.m_t = -1; + bounds.m_b = -1; + wrap_axis = GHOST_kGrabAxisNone; + } +} + GHOST_TSuccess GHOST_Window::setCursorShape(GHOST_TStandardCursor cursorShape) { if (setWindowCursorShape(cursorShape)) { diff --git a/intern/ghost/intern/GHOST_Window.h b/intern/ghost/intern/GHOST_Window.h index 794e834d5c7..adbc29eb84e 100644 --- a/intern/ghost/intern/GHOST_Window.h +++ b/intern/ghost/intern/GHOST_Window.h @@ -152,6 +152,10 @@ class GHOST_Window : public GHOST_IWindow { */ GHOST_TSuccess getCursorGrabBounds(GHOST_Rect &bounds); + void getCursorGrabState(GHOST_TGrabCursorMode &mode, + GHOST_TAxisFlag &axis_flag, + GHOST_Rect &bounds); + /** * Sets the progress bar value displayed in the window/application icon * \param progress: The progress percentage (0.0 to 1.0). diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index f9f168f772d..21e3793d3b1 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -15,41 +15,117 @@ #include <wayland-egl.h> +#include <algorithm> /* For `std::find`. */ + static constexpr size_t base_dpi = 96; struct window_t { - GHOST_WindowWayland *w; - wl_surface *surface; - /* Outputs on which the window is currently shown on. */ - std::unordered_set<const output_t *> outputs; - uint16_t dpi = 0; - int scale = 1; - struct xdg_surface *xdg_surface; - struct xdg_toplevel *xdg_toplevel; + GHOST_WindowWayland *w = nullptr; + struct wl_surface *wl_surface = nullptr; + /** + * Outputs on which the window is currently shown on. + * + * This is an ordered set (whoever adds to this is responsible for keeping members unique). + * In practice this is rarely manipulated and is limited by the number of physical displays. + */ + std::vector<output_t *> outputs; + + /** The scale value written to #wl_surface_set_buffer_scale. */ + int scale = 0; + /** + * The DPI, either: + * - `scale * base_dpi` + * - `wl_fixed_to_int(scale_fractional * base_dpi)` + * When fractional scaling is available. + */ + uint32_t dpi = 0; + + struct xdg_surface *xdg_surface = nullptr; + struct xdg_toplevel *xdg_toplevel = nullptr; struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration = nullptr; - enum zxdg_toplevel_decoration_v1_mode decoration_mode; - wl_egl_window *egl_window; - int32_t pending_width, pending_height; - bool is_maximised; - bool is_fullscreen; - bool is_active; - bool is_dialog; - int32_t width, height; + enum zxdg_toplevel_decoration_v1_mode decoration_mode = (enum zxdg_toplevel_decoration_v1_mode)0; + wl_egl_window *egl_window = nullptr; + bool is_maximised = false; + bool is_fullscreen = false; + bool is_active = false; + bool is_dialog = false; + + int32_t size[2] = {0, 0}; + int32_t size_pending[2] = {0, 0}; }; /* -------------------------------------------------------------------- */ -/** \name Wayland Interface Callbacks - * - * These callbacks are registered for Wayland interfaces and called when - * an event is received from the compositor. +/** \name Internal Utilities + * \{ */ + +/** + * Return -1 if `output_a` has a scale smaller than `output_b`, 0 when there equal, otherwise 1. + */ +static int output_scale_cmp(const output_t *output_a, const output_t *output_b) +{ + if (output_a->scale < output_b->scale) { + return -1; + } + if (output_a->scale > output_b->scale) { + return 1; + } + if (output_a->has_scale_fractional || output_b->has_scale_fractional) { + const wl_fixed_t scale_fractional_a = output_a->has_scale_fractional ? + output_a->scale_fractional : + wl_fixed_from_int(output_a->scale); + const wl_fixed_t scale_fractional_b = output_b->has_scale_fractional ? + output_b->scale_fractional : + wl_fixed_from_int(output_b->scale); + if (scale_fractional_a < scale_fractional_b) { + return -1; + } + if (scale_fractional_a > scale_fractional_b) { + return 1; + } + } + return 0; +} + +static int outputs_max_scale_or_default(const std::vector<output_t *> &outputs, + const int32_t scale_default, + uint32_t *r_dpi) +{ + const output_t *output_max = nullptr; + for (const output_t *reg_output : outputs) { + if (!output_max || (output_scale_cmp(output_max, reg_output) == -1)) { + output_max = reg_output; + } + } + + if (output_max) { + if (r_dpi) { + *r_dpi = output_max->has_scale_fractional ? + /* Fractional DPI. */ + wl_fixed_to_int(output_max->scale_fractional * base_dpi) : + /* Simple non-fractional DPI. */ + (output_max->scale * base_dpi); + } + return output_max->scale; + } + + if (r_dpi) { + *r_dpi = scale_default * base_dpi; + } + return scale_default; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (XDG Top Level), #xdg_toplevel_listener * \{ */ -static void toplevel_configure( +static void xdg_toplevel_handle_configure( void *data, xdg_toplevel * /*xdg_toplevel*/, int32_t width, int32_t height, wl_array *states) { window_t *win = static_cast<window_t *>(data); - win->pending_width = width; - win->pending_height = height; + win->size_pending[0] = win->scale * width; + win->size_pending[1] = win->scale * height; win->is_maximised = false; win->is_fullscreen = false; @@ -77,17 +153,23 @@ static void toplevel_configure( } } -static void toplevel_close(void *data, xdg_toplevel * /*xdg_toplevel*/) +static void xdg_toplevel_handle_close(void *data, xdg_toplevel * /*xdg_toplevel*/) { static_cast<window_t *>(data)->w->close(); } static const xdg_toplevel_listener toplevel_listener = { - toplevel_configure, - toplevel_close, + xdg_toplevel_handle_configure, + xdg_toplevel_handle_close, }; -static void toplevel_decoration_configure( +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (XDG Decoration Listener), #zxdg_toplevel_decoration_v1_listener + * \{ */ + +static void xdg_toplevel_decoration_handle_configure( void *data, struct zxdg_toplevel_decoration_v1 * /*zxdg_toplevel_decoration_v1*/, uint32_t mode) @@ -96,10 +178,16 @@ static void toplevel_decoration_configure( } static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_v1_listener = { - toplevel_decoration_configure, + xdg_toplevel_decoration_handle_configure, }; -static void surface_configure(void *data, xdg_surface *xdg_surface, uint32_t serial) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (XDG Surface Handle Configure), #xdg_surface_listener + * \{ */ + +static void xdg_surface_handle_configure(void *data, xdg_surface *xdg_surface, uint32_t serial) { window_t *win = static_cast<window_t *>(data); @@ -107,12 +195,12 @@ static void surface_configure(void *data, xdg_surface *xdg_surface, uint32_t ser return; } - if (win->pending_width != 0 && win->pending_height != 0) { - win->width = win->scale * win->pending_width; - win->height = win->scale * win->pending_height; - wl_egl_window_resize(win->egl_window, win->width, win->height, 0, 0); - win->pending_width = 0; - win->pending_height = 0; + if (win->size_pending[0] != 0 && win->size_pending[1] != 0) { + win->size[0] = win->size_pending[0]; + win->size[1] = win->size_pending[1]; + wl_egl_window_resize(win->egl_window, win->size[0], win->size[1], 0, 0); + win->size_pending[0] = 0; + win->size_pending[1] = 0; win->w->notify_size(); } @@ -126,55 +214,49 @@ static void surface_configure(void *data, xdg_surface *xdg_surface, uint32_t ser xdg_surface_ack_configure(xdg_surface, serial); } -static const xdg_surface_listener surface_listener = { - surface_configure, +static const xdg_surface_listener xdg_surface_listener = { + xdg_surface_handle_configure, }; -static bool update_scale(GHOST_WindowWayland *window) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Listener (Surface), #wl_surface_listener + * \{ */ + +static void surface_handle_enter(void *data, + struct wl_surface * /*wl_surface*/, + struct wl_output *output) { - int scale = 0; - for (const output_t *output : window->outputs_active()) { - if (output->scale > scale) { - scale = output->scale; - } + GHOST_WindowWayland *w = static_cast<GHOST_WindowWayland *>(data); + output_t *reg_output = w->output_find_by_wl(output); + if (reg_output == nullptr) { + return; } - if (scale > 0 && window->scale() != scale) { - window->scale() = scale; - /* Using the real DPI will cause wrong scaling of the UI - * use a multiplier for the default DPI as workaround. */ - window->dpi() = scale * base_dpi; - wl_surface_set_buffer_scale(window->surface(), scale); - return true; + if (w->outputs_enter(reg_output)) { + w->outputs_changed_update_scale(); } - return false; } -static void surface_enter(void *data, struct wl_surface * /*wl_surface*/, struct wl_output *output) +static void surface_handle_leave(void *data, + struct wl_surface * /*wl_surface*/, + struct wl_output *output) { GHOST_WindowWayland *w = static_cast<GHOST_WindowWayland *>(data); - for (const output_t *reg_output : w->outputs()) { - if (reg_output->output == output) { - w->outputs_active().insert(reg_output); - } + output_t *reg_output = w->output_find_by_wl(output); + if (reg_output == nullptr) { + return; } - update_scale(w); -} -static void surface_leave(void *data, struct wl_surface * /*wl_surface*/, struct wl_output *output) -{ - GHOST_WindowWayland *w = static_cast<GHOST_WindowWayland *>(data); - for (const output_t *reg_output : w->outputs()) { - if (reg_output->output == output) { - w->outputs_active().erase(reg_output); - } + if (w->outputs_leave(reg_output)) { + w->outputs_changed_update_scale(); } - update_scale(w); } struct wl_surface_listener wl_surface_listener = { - surface_enter, - surface_leave, + surface_handle_enter, + surface_handle_leave, }; /** \} */ @@ -208,32 +290,51 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, { w->w = this; - w->width = int32_t(width); - w->height = int32_t(height); + w->size[0] = int32_t(width); + w->size[1] = int32_t(height); w->is_dialog = is_dialog; + /* NOTE(@campbellbarton): The scale set here to avoid flickering on startup. + * When all monitors use the same scale (which is quite common) there aren't any problems. + * + * When monitors have different scales there may still be a visible window resize on startup. + * Ideally it would be possible to know the scale this window will use however that's only + * known once #surface_enter callback runs (which isn't guaranteed to run at all). + * + * Using the maximum scale is best as it results in the window first being smaller, + * avoiding a large window flashing before it's made smaller. */ + w->scale = outputs_max_scale_or_default(this->m_system->outputs(), 1, &w->dpi); + /* Window surfaces. */ - w->surface = wl_compositor_create_surface(m_system->compositor()); - wl_surface_add_listener(w->surface, &wl_surface_listener, this); + w->wl_surface = wl_compositor_create_surface(m_system->compositor()); + wl_surface_set_buffer_scale(this->surface(), w->scale); + + wl_surface_add_listener(w->wl_surface, &wl_surface_listener, this); - w->egl_window = wl_egl_window_create(w->surface, int(width), int(height)); + w->egl_window = wl_egl_window_create(w->wl_surface, int(w->size[0]), int(w->size[1])); - w->xdg_surface = xdg_wm_base_get_xdg_surface(m_system->shell(), w->surface); + w->xdg_surface = xdg_wm_base_get_xdg_surface(m_system->xdg_shell(), w->wl_surface); w->xdg_toplevel = xdg_surface_get_toplevel(w->xdg_surface); - if (m_system->decoration_manager()) { + /* NOTE: The limit is in points (not pixels) so Hi-DPI will limit to larger number of pixels. + * This has the advantage that the size limit is the same when moving the window between monitors + * with different scales set. If it was important to limit in pixels it could be re-calculated + * when the `w->scale` changed. */ + xdg_toplevel_set_min_size(w->xdg_toplevel, 320, 240); + + if (m_system->xdg_decoration_manager()) { w->xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( - m_system->decoration_manager(), w->xdg_toplevel); + m_system->xdg_decoration_manager(), w->xdg_toplevel); zxdg_toplevel_decoration_v1_add_listener( w->xdg_toplevel_decoration, &toplevel_decoration_v1_listener, w); zxdg_toplevel_decoration_v1_set_mode(w->xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); } - wl_surface_set_user_data(w->surface, this); + wl_surface_set_user_data(w->wl_surface, this); - xdg_surface_add_listener(w->xdg_surface, &surface_listener, w); + xdg_surface_add_listener(w->xdg_surface, &xdg_surface_listener, w); xdg_toplevel_add_listener(w->xdg_toplevel, &toplevel_listener, w); if (parentWindow && is_dialog) { @@ -242,7 +343,7 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, } /* Call top-level callbacks. */ - wl_surface_commit(w->surface); + wl_surface_commit(w->wl_surface); wl_display_roundtrip(m_system->display()); #ifdef GHOST_OPENGL_ALPHA @@ -296,32 +397,92 @@ GHOST_TSuccess GHOST_WindowWayland::notify_size() wl_surface *GHOST_WindowWayland::surface() const { - return w->surface; + return w->wl_surface; } -const std::vector<output_t *> &GHOST_WindowWayland::outputs() const +const std::vector<output_t *> &GHOST_WindowWayland::outputs() { - return m_system->outputs(); + return w->outputs; } -std::unordered_set<const output_t *> &GHOST_WindowWayland::outputs_active() +output_t *GHOST_WindowWayland::output_find_by_wl(struct wl_output *output) { - return w->outputs; + for (output_t *reg_output : this->m_system->outputs()) { + if (reg_output->wl_output == output) { + return reg_output; + } + } + return nullptr; } -uint16_t &GHOST_WindowWayland::dpi() +bool GHOST_WindowWayland::outputs_changed_update_scale() +{ + uint32_t dpi_next; + const int scale_next = outputs_max_scale_or_default(this->outputs(), 0, &dpi_next); + if (scale_next == 0) { + return false; + } + + window_t *win = this->w; + const uint32_t dpi_curr = win->dpi; + const int scale_curr = win->scale; + bool changed = false; + + if (scale_next != scale_curr) { + /* Unlikely but possible there is a pending size change is set. */ + win->size_pending[0] = (win->size_pending[0] / scale_curr) * scale_next; + win->size_pending[1] = (win->size_pending[1] / scale_curr) * scale_next; + + win->scale = scale_next; + wl_surface_set_buffer_scale(this->surface(), scale_next); + changed = true; + } + + if (dpi_next != dpi_curr) { + /* Using the real DPI will cause wrong scaling of the UI + * use a multiplier for the default DPI as workaround. */ + win->dpi = dpi_next; + changed = true; + } + + return changed; +} + +bool GHOST_WindowWayland::outputs_enter(output_t *reg_output) +{ + std::vector<output_t *> &outputs = w->outputs; + auto it = std::find(outputs.begin(), outputs.end(), reg_output); + if (it != outputs.end()) { + return false; + } + outputs.push_back(reg_output); + return true; +} + +bool GHOST_WindowWayland::outputs_leave(output_t *reg_output) +{ + std::vector<output_t *> &outputs = w->outputs; + auto it = std::find(outputs.begin(), outputs.end(), reg_output); + if (it == outputs.end()) { + return false; + } + outputs.erase(it); + return true; +} + +uint16_t GHOST_WindowWayland::dpi() { return w->dpi; } -int &GHOST_WindowWayland::scale() +int GHOST_WindowWayland::scale() { return w->scale; } GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mode) { - return m_system->setCursorGrab(mode, m_cursorGrab, w->surface); + return m_system->setCursorGrab(mode, m_cursorGrab, w->wl_surface); } GHOST_TSuccess GHOST_WindowWayland::setWindowCursorShape(GHOST_TStandardCursor shape) @@ -356,22 +517,32 @@ void GHOST_WindowWayland::getWindowBounds(GHOST_Rect &bounds) const void GHOST_WindowWayland::getClientBounds(GHOST_Rect &bounds) const { - bounds.set(0, 0, w->width, w->height); + bounds.set(0, 0, w->size[0], w->size[1]); } GHOST_TSuccess GHOST_WindowWayland::setClientWidth(uint32_t width) { - return setClientSize(width, uint32_t(w->height)); + return setClientSize(width, uint32_t(w->size[1])); } GHOST_TSuccess GHOST_WindowWayland::setClientHeight(uint32_t height) { - return setClientSize(uint32_t(w->width), height); + return setClientSize(uint32_t(w->size[0]), height); } GHOST_TSuccess GHOST_WindowWayland::setClientSize(uint32_t width, uint32_t height) { wl_egl_window_resize(w->egl_window, int(width), int(height), 0, 0); + + /* Override any pending size that may be set. */ + w->size_pending[0] = 0; + w->size_pending[1] = 0; + + w->size[0] = width; + w->size[1] = height; + + notify_size(); + return GHOST_kSuccess; } @@ -403,7 +574,7 @@ GHOST_WindowWayland::~GHOST_WindowWayland() } xdg_toplevel_destroy(w->xdg_toplevel); xdg_surface_destroy(w->xdg_surface); - wl_surface_destroy(w->surface); + wl_surface_destroy(w->wl_surface); delete w; } @@ -494,7 +665,7 @@ void GHOST_WindowWayland::setOpaque() const /* Make the window opaque. */ region = wl_compositor_create_region(m_system->compositor()); - wl_region_add(region, 0, 0, w->width, w->height); + wl_region_add(region, 0, 0, w->size[0], w->size[1]); wl_surface_set_opaque_region(w->surface, region); wl_region_destroy(region); } diff --git a/intern/ghost/intern/GHOST_WindowWayland.h b/intern/ghost/intern/GHOST_WindowWayland.h index d5dd123014b..b6d9fa04079 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.h +++ b/intern/ghost/intern/GHOST_WindowWayland.h @@ -17,7 +17,6 @@ class GHOST_SystemWayland; struct output_t; struct window_t; -struct wl_surface; class GHOST_WindowWayland : public GHOST_Window { public: @@ -40,25 +39,8 @@ class GHOST_WindowWayland : public GHOST_Window { uint16_t getDPIHint() override; - GHOST_TSuccess close(); - - GHOST_TSuccess activate(); - - GHOST_TSuccess deactivate(); - - GHOST_TSuccess notify_size(); + /* Ghost API */ - wl_surface *surface() const; - - const std::vector<output_t *> &outputs() const; - - std::unordered_set<const output_t *> &outputs_active(); - - uint16_t &dpi(); - - int &scale(); - - protected: GHOST_TSuccess setWindowCursorGrab(GHOST_TGrabCursorMode mode) override; GHOST_TSuccess setWindowCursorShape(GHOST_TStandardCursor shape) override; @@ -109,6 +91,30 @@ class GHOST_WindowWayland : public GHOST_Window { void setOpaque() const; #endif + /* WAYLAND utility functions. */ + + GHOST_TSuccess close(); + + GHOST_TSuccess activate(); + + GHOST_TSuccess deactivate(); + + GHOST_TSuccess notify_size(); + + struct wl_surface *surface() const; + + output_t *output_find_by_wl(struct wl_output *output); + + const std::vector<output_t *> &outputs(); + + bool outputs_enter(output_t *reg_output); + bool outputs_leave(output_t *reg_output); + bool outputs_changed_update_scale(); + + uint16_t dpi(); + + int scale(); + private: GHOST_SystemWayland *m_system; struct window_t *w; diff --git a/intern/ghost/intern/GHOST_WindowX11.cpp b/intern/ghost/intern/GHOST_WindowX11.cpp index 3569c9574f1..ac7a476c76f 100644 --- a/intern/ghost/intern/GHOST_WindowX11.cpp +++ b/intern/ghost/intern/GHOST_WindowX11.cpp @@ -1711,7 +1711,7 @@ uint16_t GHOST_WindowX11::getDPIHint() XrmDestroyDatabase(xrdb); } - /* Fallback to calculating DPI using X reported DPI, set using xrandr --dpi */ + /* Fallback to calculating DPI using X reported DPI, set using `xrandr --dpi`. */ XWindowAttributes attr; if (!XGetWindowAttributes(m_display, m_window, &attr)) { /* Failed to get window attributes, return X11 default DPI */ diff --git a/intern/ghost/intern/GHOST_Wintab.cpp b/intern/ghost/intern/GHOST_Wintab.cpp index 974a07db9c3..b136acbe098 100644 --- a/intern/ghost/intern/GHOST_Wintab.cpp +++ b/intern/ghost/intern/GHOST_Wintab.cpp @@ -310,7 +310,7 @@ void GHOST_Wintab::getInput(std::vector<GHOST_WintabInfoWin32> &outWintabInfo) outWintabInfo.reserve(numPackets); for (int i = 0; i < numPackets; i++) { - PACKET pkt = m_pkts[i]; + const PACKET pkt = m_pkts[i]; GHOST_WintabInfoWin32 out; /* % 3 for multiple devices ("DualTrack"). */ @@ -367,11 +367,12 @@ void GHOST_Wintab::getInput(std::vector<GHOST_WintabInfoWin32> &outWintabInfo) /* Some Wintab libraries don't handle relative button input, so we track button presses * manually. */ DWORD buttonsChanged = m_buttons ^ pkt.pkButtons; - WORD buttonIndex = 0; + /* We only needed the prior button state to compare to current, so we can overwrite it now. */ + m_buttons = pkt.pkButtons; - while (buttonsChanged) { + /* Iterate over button flag indices until all flags are clear. */ + for (WORD buttonIndex = 0; buttonsChanged; buttonIndex++, buttonsChanged >>= 1) { if (buttonsChanged & 1) { - /* Find the index for the changed button from the button map. */ GHOST_TButtonMask button = mapWintabToGhostButton(pkt.pkCursor, buttonIndex); if (button != GHOST_kButtonMaskNone) { @@ -381,15 +382,11 @@ void GHOST_Wintab::getInput(std::vector<GHOST_WintabInfoWin32> &outWintabInfo) } out.button = button; - out.type = buttonsChanged & pkt.pkButtons ? GHOST_kEventButtonDown : - GHOST_kEventButtonUp; - } - m_buttons ^= 1 << buttonIndex; + DWORD buttonFlag = 1 << buttonIndex; + out.type = pkt.pkButtons & buttonFlag ? GHOST_kEventButtonDown : GHOST_kEventButtonUp; + } } - - buttonsChanged >>= 1; - buttonIndex++; } outWintabInfo.push_back(out); diff --git a/intern/ghost/intern/GHOST_Wintab.h b/intern/ghost/intern/GHOST_Wintab.h index 86a0143ecc0..80eacf1f3fa 100644 --- a/intern/ghost/intern/GHOST_Wintab.h +++ b/intern/ghost/intern/GHOST_Wintab.h @@ -187,7 +187,7 @@ class GHOST_Wintab { bool m_focused = false; /** Pressed button map. */ - uint8_t m_buttons = 0; + DWORD m_buttons = 0; /** Range of a coordinate space. */ struct Range { |