Fix #124693: Wrong cursor after saving with multiple windows on Wayland

The last set cursor of any window was being used under Wayland.
Support for per-window cursor shapes to resolve the problem.
This commit is contained in:
Campbell Barton
2024-08-21 17:26:20 +10:00
parent d6ea01bd38
commit bf529e248a
3 changed files with 183 additions and 18 deletions

View File

@@ -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();
}
}

View File

@@ -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<uint8_t *>(malloc(bitmap_size));
memcpy(ccs.bitmap, bitmap, bitmap_size);
}
if (mask) {
ccs.mask = static_cast<uint8_t *>(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<const GHOST_WindowWayland *>(
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<const GHOST_WindowWayland *>(
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

View File

@@ -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);