diff --git a/intern/ghost/intern/GHOST_SystemWayland.cc b/intern/ghost/intern/GHOST_SystemWayland.cc index 77ef28ae265..79f62437b11 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cc +++ b/intern/ghost/intern/GHOST_SystemWayland.cc @@ -3761,8 +3761,7 @@ static void pointer_handle_enter(void *data, seat->pointer.wl.surface_window = wl_surface; seat->system->seat_active_set(seat); - - seat->system->cursor_shape_set(win->getCursorShape()); + win->cursor_shape_refresh(); const int event_xy[2] = {WL_FIXED_TO_INT_FOR_WINDOW_V2(win, seat->pointer.xy)}; seat->system->pushEvent_maybe_pending(new GHOST_EventCursor( @@ -4512,6 +4511,9 @@ static void tablet_tool_handle_proximity_in(void *data, seat->system->seat_active_set(seat); + GHOST_WindowWayland *win = ghost_wl_surface_user_data(seat->tablet.wl.surface_window); + win->cursor_shape_refresh(); + /* Update #GHOST_TabletData. */ GHOST_TabletData &td = tablet_tool->data; /* Reset, to avoid using stale tilt/pressure. */ @@ -4519,10 +4521,6 @@ static void tablet_tool_handle_proximity_in(void *data, td.Ytilt = 0.0f; /* In case pressure isn't supported. */ td.Pressure = 1.0f; - - const GHOST_WindowWayland *win = ghost_wl_surface_user_data(seat->tablet.wl.surface_window); - - seat->system->cursor_shape_set(win->getCursorShape()); } static void tablet_tool_handle_proximity_out(void *data, zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/) @@ -4762,7 +4760,7 @@ static void tablet_tool_handle_frame(void *data, } if (tablet_tool->proximity == false) { - seat->system->cursor_shape_set(win->getCursorShape()); + win->cursor_shape_refresh(); } } diff --git a/intern/ghost/intern/GHOST_WindowWayland.cc b/intern/ghost/intern/GHOST_WindowWayland.cc index d7ab5e9e673..fd0a8ab3424 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cc +++ b/intern/ghost/intern/GHOST_WindowWayland.cc @@ -259,6 +259,98 @@ int gwl_window_scale_int_from(const GWL_WindowScaleParams &scale_params, int val /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Internal #GWL_WindowCursorCustomShape + * \{ */ + +struct GWL_WindowCursorCustomShape { + uint8_t *bitmap = nullptr; + uint8_t *mask = nullptr; + int32_t hot_spot[2] = {0, 0}; + int32_t size[2] = {0, 0}; + bool can_invert_color = false; +}; + +static void gwl_window_cursor_custom_free(GWL_WindowCursorCustomShape &ccs) +{ + if (ccs.bitmap) { + free(ccs.bitmap); + } + if (ccs.mask) { + free(ccs.mask); + } +} + +static void gwl_window_cursor_custom_clear(GWL_WindowCursorCustomShape &ccs) +{ + gwl_window_cursor_custom_free(ccs); + ccs = GWL_WindowCursorCustomShape{}; +} + +static void gwl_window_cursor_custom_store(GWL_WindowCursorCustomShape &ccs, + const uint8_t *bitmap, + const uint8_t *mask, + const int32_t size[2], + const int32_t hot_spot[2], + bool can_invert_color) +{ + gwl_window_cursor_custom_clear(ccs); + /* The width is divided by 8, rounding up. */ + const size_t bitmap_size = sizeof(uint8_t) * ((size[0] + 7) / 8) * size[1]; + + if (bitmap) { + ccs.bitmap = static_cast(malloc(bitmap_size)); + memcpy(ccs.bitmap, bitmap, bitmap_size); + } + if (mask) { + ccs.mask = static_cast(malloc(bitmap_size)); + memcpy(ccs.mask, mask, bitmap_size); + } + + ccs.size[0] = size[0]; + ccs.size[1] = size[1]; + + ccs.hot_spot[0] = hot_spot[0]; + ccs.hot_spot[1] = hot_spot[1]; + + ccs.can_invert_color = can_invert_color; +} + +static GHOST_TSuccess gwl_window_cursor_custom_load(GWL_WindowCursorCustomShape &ccs, + GHOST_SystemWayland *system) +{ + return system->cursor_shape_custom_set(ccs.bitmap, + ccs.mask, + ccs.size[0], + ccs.size[1], + ccs.hot_spot[0], + ccs.hot_spot[1], + ccs.can_invert_color); +} + +static GHOST_TSuccess gwl_window_cursor_shape_refresh(GHOST_TStandardCursor shape, + GWL_WindowCursorCustomShape &ccs, + GHOST_SystemWayland *system) +{ +#ifdef USE_EVENT_BACKGROUND_THREAD + GHOST_ASSERT(system->main_thread_id == std::this_thread::get_id(), "Only from main thread!"); +#endif + + if (shape == GHOST_kStandardCursorCustom) { + const GHOST_TSuccess ok = gwl_window_cursor_custom_load(ccs, system); + if (ok == GHOST_kSuccess) { + return ok; + } + shape = GHOST_kStandardCursorDefault; + system->cursor_shape_set(shape); + return GHOST_kFailure; + } + + return system->cursor_shape_set(shape); +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Internal #GWL_Window * \{ */ @@ -294,8 +386,13 @@ enum eGWL_PendingWindowActions { */ PENDING_WINDOW_SURFACE_COMMIT, + /** + * The window has gained focus and the cursor shape needs to be refreshed. + */ + PENDING_WINDOW_CURSOR_SHAPE_REFRESH, + }; -# define PENDING_NUM (PENDING_WINDOW_SURFACE_COMMIT + 1) +# define PENDING_NUM (PENDING_WINDOW_CURSOR_SHAPE_REFRESH + 1) #endif /* USE_EVENT_BACKGROUND_THREAD */ @@ -389,6 +486,8 @@ struct GWL_Window { std::mutex frame_pending_mutex; #endif + GWL_WindowCursorCustomShape cursor_custom_shape; + std::string title; bool is_dialog = false; @@ -864,6 +963,10 @@ static void gwl_window_pending_actions_handle(GWL_Window *win) if (actions[PENDING_WINDOW_SURFACE_COMMIT]) { wl_surface_commit(win->wl.surface); } + if (actions[PENDING_WINDOW_CURSOR_SHAPE_REFRESH]) { + gwl_window_cursor_shape_refresh( + win->ghost_window->getCursorShape(), win->cursor_custom_shape, win->ghost_system); + } } #endif /* USE_EVENT_BACKGROUND_THREAD */ @@ -2071,6 +2174,8 @@ GHOST_WindowWayland::~GHOST_WindowWayland() * This is not fool-proof though, hence the call to #window_surface_unref, see: #99078. */ wl_display_flush(system_->wl_display_get()); + gwl_window_cursor_custom_free(window_->cursor_custom_shape); + delete window_; } @@ -2120,12 +2225,33 @@ GHOST_TSuccess GHOST_WindowWayland::setWindowCursorShape(GHOST_TStandardCursor s #ifdef USE_EVENT_BACKGROUND_THREAD std::lock_guard lock_server_guard{*system_->server_mutex}; #endif - const GHOST_TSuccess ok = system_->cursor_shape_set(shape); - m_cursorShape = (ok == GHOST_kSuccess) ? shape : GHOST_kStandardCursorDefault; - if (ok == GHOST_kSuccess) { - /* For the cursor to display when the event queue isn't being handled. */ - wl_display_flush(system_->wl_display_get()); + const bool is_active = this == static_cast( + system_->getWindowManager()->getActiveWindow()); + gwl_window_cursor_custom_clear(window_->cursor_custom_shape); + m_cursorShape = shape; + + GHOST_TSuccess ok; + if (is_active) { + ok = system_->cursor_shape_set(m_cursorShape); + GHOST_TSuccess ok_test = ok; + if (ok == GHOST_kFailure) { + /* Failed, try again with the default cursor. */ + m_cursorShape = GHOST_kStandardCursorDefault; + ok_test = system_->cursor_shape_set(m_cursorShape); + } + + if (ok_test == GHOST_kFailure) { + /* For the cursor to display when the event queue isn't being handled. */ + wl_display_flush(system_->wl_display_get()); + } + } + else { + /* Set later when activating the window. */ + ok = system_->cursor_shape_check(shape); + if (ok == GHOST_kFailure) { + m_cursorShape = GHOST_kStandardCursorDefault; + } } return ok; } @@ -2144,12 +2270,33 @@ GHOST_TSuccess GHOST_WindowWayland::setWindowCustomCursorShape( #ifdef USE_EVENT_BACKGROUND_THREAD std::lock_guard lock_server_guard{*system_->server_mutex}; #endif - const GHOST_TSuccess ok = system_->cursor_shape_custom_set( - bitmap, mask, sizex, sizey, hotX, hotY, canInvertColor); - if (ok == GHOST_kSuccess) { - /* For the cursor to display when the event queue isn't being handled. */ - wl_display_flush(system_->wl_display_get()); + const bool is_active = this == static_cast( + system_->getWindowManager()->getActiveWindow()); + const int32_t size[2] = {sizex, sizey}; + const int32_t hot_spot[2] = {hotX, hotY}; + + gwl_window_cursor_custom_store( + window_->cursor_custom_shape, bitmap, mask, size, hot_spot, canInvertColor); + m_cursorShape = GHOST_kStandardCursorCustom; + + GHOST_TSuccess ok; + if (is_active) { + ok = gwl_window_cursor_custom_load(window_->cursor_custom_shape, system_); + GHOST_TSuccess ok_test = ok; + if (ok == GHOST_kFailure) { + /* Failed, try again with the default cursor. */ + m_cursorShape = GHOST_kStandardCursorDefault; + ok_test = system_->cursor_shape_set(m_cursorShape); + } + if (ok_test == GHOST_kSuccess) { + /* For the cursor to display when the event queue isn't being handled. */ + wl_display_flush(system_->wl_display_get()); + } + } + else { + /* Set later when activating the window. */ + ok = GHOST_kSuccess; } return ok; } @@ -2552,6 +2699,17 @@ GHOST_TSuccess GHOST_WindowWayland::notify_decor_redraw() * Functionality only used for the WAYLAND implementation. * \{ */ +GHOST_TSuccess GHOST_WindowWayland::cursor_shape_refresh() +{ +#ifdef USE_EVENT_BACKGROUND_THREAD + if (system_->main_thread_id != std::this_thread::get_id()) { + gwl_window_pending_actions_tag(window_, PENDING_WINDOW_CURSOR_SHAPE_REFRESH); + return GHOST_kSuccess; + } +#endif + return gwl_window_cursor_shape_refresh(m_cursorShape, window_->cursor_custom_shape, system_); +} + void GHOST_WindowWayland::outputs_changed_update_scale_tag() { #ifdef USE_EVENT_BACKGROUND_THREAD diff --git a/intern/ghost/intern/GHOST_WindowWayland.hh b/intern/ghost/intern/GHOST_WindowWayland.hh index cba5e1c6626..1567cb68b86 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.hh +++ b/intern/ghost/intern/GHOST_WindowWayland.hh @@ -180,6 +180,15 @@ class GHOST_WindowWayland : public GHOST_Window { /* WAYLAND utility functions. */ + /** + * Refresh the cursor using the cursor assigned to this window. + * + * \note This is needed because in GHOST the cursor is per window, + * where as in WAYLAND the cursor is set per-seat (and per input device). + * When an input device enters a window, this function must run. + */ + GHOST_TSuccess cursor_shape_refresh(); + bool outputs_enter(GWL_Output *output); bool outputs_leave(GWL_Output *output);