GHOST: Implement the cursor shape protocol and remove X11 cursors from the wayland backend

This makes the the cursor drawing be handled by the Wayland compositor
unless the cursor shape is not in the standard cursor shape set.
For custom cursor shape, Blender still manually draws and handles these.

Removal of the X11 cursors was done because:
1. Greatly simplifies the code (and compositors not supporting the cursor
    shape protocol will fallback to Blenders built in cursors).

2. On a lot of compositors the X11 cursor theme is not set per default,
       this would lead to us falling back to a default theme. This would
       in almost all cases not match the actual cursor theme of the user.
       The fallback theme would also look quite ugly and the cursor size
       would be inconsistent with the rest of the system.

Pull Request: https://projects.blender.org/blender/blender/pulls/140366
This commit is contained in:
Sebastian Parborg
2025-07-01 15:16:44 +02:00
committed by Sebastian Parborg
parent b6b90d6835
commit 232b106e64
2 changed files with 257 additions and 327 deletions

View File

@@ -427,6 +427,10 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
"${WAYLAND_PROTOCOLS_DIR}/unstable/text-input/text-input-unstable-v3.xml"
)
endif()
# Desktop compositor controlled cursor rendering.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/staging/cursor-shape/cursor-shape-v1.xml"
)
unset(INC_DST)

View File

@@ -59,6 +59,7 @@
#include <xkbcommon/xkbcommon.h>
/* Generated by `wayland-scanner`. */
#include <cursor-shape-v1-client-protocol.h>
#include <fractional-scale-v1-client-protocol.h>
#include <pointer-constraints-unstable-v1-client-protocol.h>
#include <pointer-gestures-unstable-v1-client-protocol.h>
@@ -124,11 +125,6 @@ static void gwl_seat_capability_pointer_disable(GWL_Seat *seat);
static void gwl_seat_capability_keyboard_disable(GWL_Seat *seat);
static void gwl_seat_capability_touch_disable(GWL_Seat *seat);
static void gwl_seat_cursor_anim_begin(GWL_Seat *seat);
static void gwl_seat_cursor_anim_begin_if_needed(GWL_Seat *seat);
static void gwl_seat_cursor_anim_end(GWL_Seat *seat);
static void gwl_seat_cursor_anim_reset(GWL_Seat *seat);
static bool gwl_registry_entry_remove_by_name(GWL_Display *display,
uint32_t name,
int *r_interface_slot);
@@ -437,28 +433,28 @@ static void gwl_simple_buffer_set_from_string(GWL_SimpleBuffer *buffer, const ch
*/
#define EVDEV_OFFSET 8
/**
* Data owned by the thread that updates the cursor.
* Exposed so the #GWL_Seat can request the thread to exit & free itself.
/** Wayland cursor shape protocol types.
* Not used if the compositor doesn't support the cursor shape protocol.
*/
struct GWL_Cursor_AnimHandle {
std::atomic<bool> exit_pending = false;
struct GWL_CursorShape {
/* The enum_id is currently only used to keep track of the last cursor shape used.
* This is so we can restore it after hiding the cursor. */
int enum_id = 0;
wp_cursor_shape_device_v1 *device = nullptr;
};
struct GWL_Cursor {
/** Wayland core types. */
struct {
/* Everything below in this wl struct is used for custom cursor shapes. */
wl_surface *surface_cursor = nullptr;
wl_buffer *buffer = nullptr;
wl_cursor_image image = {0};
wl_cursor_theme *theme = nullptr;
/** Only set when the cursor is from the theme (it may be animated). */
const wl_cursor *theme_cursor = nullptr;
/** Needed so changing the theme scale can reload 'theme_cursor' at a new scale. */
const char *theme_cursor_name = nullptr;
} wl;
GWL_CursorShape shape;
bool visible = false;
/**
* When false, hide the hardware cursor, while the cursor is still considered to be `visible`,
@@ -472,15 +468,6 @@ struct GWL_Cursor {
void *custom_data = nullptr;
/** The size of `custom_data` in bytes. */
size_t custom_data_size = 0;
/** Use for animated cursors. */
GWL_Cursor_AnimHandle *anim_handle = nullptr;
/**
* The name of the theme (set by an environment variable).
* When disabled, leave as an empty string and pass in nullptr to use the default theme.
*/
std::string theme_name;
/**
* The size of the cursor (when looking up a cursor theme).
* This must be scaled by the maximum output scale when passing to wl_cursor_theme_load.
@@ -547,6 +534,8 @@ struct GWL_TabletTool {
wl_surface *surface_cursor = nullptr;
} wl;
GWL_CursorShape shape;
GWL_Seat *seat = nullptr;
/** Used to delay clearing tablet focused wl_surface until the frame is handled. */
bool proximity = false;
@@ -1446,6 +1435,7 @@ struct GWL_Display {
#ifdef WITH_INPUT_IME
zwp_text_input_manager_v3 *text_input_manager = nullptr;
#endif
wp_cursor_shape_manager_v1 *cursor_shape_manager = nullptr;
} wp;
/** Wayland XDG types. */
@@ -1557,11 +1547,6 @@ static void gwl_display_destroy(GWL_Display *display)
}
#endif
/* Stop all animated cursors (freeing their #GWL_Cursor_AnimHandle). */
for (GWL_Seat *seat : display->seats) {
gwl_seat_cursor_anim_end(seat);
}
/* For typical WAYLAND use this will always be set.
* However when WAYLAND isn't running, this will early-exit and be null. */
if (display->wl.registry) {
@@ -2266,73 +2251,6 @@ static GHOST_TTabletMode tablet_tool_map_type(enum zwp_tablet_tool_v2_type wp_ta
static const int default_cursor_size = 24;
struct GWL_Cursor_ShapeInfo {
const char *names[GHOST_kStandardCursorNumCursors] = {nullptr};
};
static const GWL_Cursor_ShapeInfo ghost_wl_cursors = []() -> GWL_Cursor_ShapeInfo {
GWL_Cursor_ShapeInfo info{};
#define CASE_CURSOR(shape_id, shape_name_in_theme) \
case shape_id: { \
info.names[int(shape_id)] = shape_name_in_theme; \
} \
((void)0)
/* Use a switch to ensure missing values show a compiler warning. */
switch (GHOST_kStandardCursorDefault) {
CASE_CURSOR(GHOST_kStandardCursorDefault, "left_ptr");
CASE_CURSOR(GHOST_kStandardCursorRightArrow, "right_ptr");
CASE_CURSOR(GHOST_kStandardCursorLeftArrow, "left_ptr");
CASE_CURSOR(GHOST_kStandardCursorInfo, "left_ptr_help");
CASE_CURSOR(GHOST_kStandardCursorDestroy, "pirate");
CASE_CURSOR(GHOST_kStandardCursorHelp, "question_arrow");
CASE_CURSOR(GHOST_kStandardCursorWait, "watch");
CASE_CURSOR(GHOST_kStandardCursorText, "xterm");
CASE_CURSOR(GHOST_kStandardCursorCrosshair, "crosshair");
CASE_CURSOR(GHOST_kStandardCursorCrosshairA, "");
CASE_CURSOR(GHOST_kStandardCursorCrosshairB, "");
CASE_CURSOR(GHOST_kStandardCursorCrosshairC, "");
CASE_CURSOR(GHOST_kStandardCursorPencil, "pencil");
CASE_CURSOR(GHOST_kStandardCursorUpArrow, "sb_up_arrow");
CASE_CURSOR(GHOST_kStandardCursorDownArrow, "sb_down_arrow");
CASE_CURSOR(GHOST_kStandardCursorVerticalSplit, "split_v");
CASE_CURSOR(GHOST_kStandardCursorHorizontalSplit, "split_h");
CASE_CURSOR(GHOST_kStandardCursorEraser, "");
CASE_CURSOR(GHOST_kStandardCursorKnife, "");
CASE_CURSOR(GHOST_kStandardCursorEyedropper, "color-picker");
CASE_CURSOR(GHOST_kStandardCursorZoomIn, "zoom-in");
CASE_CURSOR(GHOST_kStandardCursorZoomOut, "zoom-out");
CASE_CURSOR(GHOST_kStandardCursorMove, "move");
CASE_CURSOR(GHOST_kStandardCursorHandOpen, "hand1");
CASE_CURSOR(GHOST_kStandardCursorHandClosed, "grabbing");
CASE_CURSOR(GHOST_kStandardCursorHandPoint, "hand2");
CASE_CURSOR(GHOST_kStandardCursorNSEWScroll, "all-scroll");
CASE_CURSOR(GHOST_kStandardCursorNSScroll, "size_ver");
CASE_CURSOR(GHOST_kStandardCursorEWScroll, "size_hor");
CASE_CURSOR(GHOST_kStandardCursorStop, "not-allowed");
CASE_CURSOR(GHOST_kStandardCursorUpDown, "sb_v_double_arrow");
CASE_CURSOR(GHOST_kStandardCursorLeftRight, "sb_h_double_arrow");
CASE_CURSOR(GHOST_kStandardCursorTopSide, "top_side");
CASE_CURSOR(GHOST_kStandardCursorBottomSide, "bottom_side");
CASE_CURSOR(GHOST_kStandardCursorLeftSide, "left_side");
CASE_CURSOR(GHOST_kStandardCursorRightSide, "right_side");
CASE_CURSOR(GHOST_kStandardCursorTopLeftCorner, "top_left_corner");
CASE_CURSOR(GHOST_kStandardCursorTopRightCorner, "top_right_corner");
CASE_CURSOR(GHOST_kStandardCursorBottomRightCorner, "bottom_right_corner");
CASE_CURSOR(GHOST_kStandardCursorBottomLeftCorner, "bottom_left_corner");
CASE_CURSOR(GHOST_kStandardCursorCopy, "copy");
CASE_CURSOR(GHOST_kStandardCursorLeftHandle, "");
CASE_CURSOR(GHOST_kStandardCursorRightHandle, "");
CASE_CURSOR(GHOST_kStandardCursorBothHandles, "");
CASE_CURSOR(GHOST_kStandardCursorBlade, "");
CASE_CURSOR(GHOST_kStandardCursorCustom, "");
}
#undef CASE_CURSOR
return info;
}();
static constexpr const char *ghost_wl_mime_text_plain = "text/plain";
static constexpr const char *ghost_wl_mime_text_utf8 = "text/plain;charset=utf-8";
static constexpr const char *ghost_wl_mime_text_uri_list = "text/uri-list";
@@ -2410,16 +2328,6 @@ static void pthread_set_min_priority(pthread_t handle)
}
}
static void thread_set_min_priority(std::thread &thread)
{
constexpr bool is_pthread = std::is_same<std::thread::native_handle_type, pthread_t>();
if (!is_pthread) {
return;
}
/* The cast is "safe" as non-matching types will have returned already.
* This cast might be avoided with clever template use. */
pthread_set_min_priority(reinterpret_cast<pthread_t>(thread.native_handle()));
}
#endif /* USE_EVENT_BACKGROUND_THREAD */
static int memfd_create_sealed(const char *name)
@@ -2767,66 +2675,115 @@ static char *read_file_as_buffer(const int fd, const bool nil_terminate, size_t
static void cursor_buffer_set_surface_impl(const wl_cursor_image *wl_image,
wl_buffer *buffer,
wl_surface *wl_surface,
const int scale)
wl_surface *wl_surface)
{
const int32_t image_size_x = int32_t(wl_image->width);
const int32_t image_size_y = int32_t(wl_image->height);
GHOST_ASSERT((image_size_x % scale) == 0 && (image_size_y % scale) == 0,
"The size must be a multiple of the scale!");
wl_surface_set_buffer_scale(wl_surface, scale);
wl_surface_attach(wl_surface, buffer, 0, 0);
wl_surface_damage(wl_surface, 0, 0, image_size_x, image_size_y);
wl_surface_commit(wl_surface);
}
/**
* Needed to ensure the cursor size is always a multiple of scale.
*/
static int cursor_buffer_compatible_scale_from_image(const wl_cursor_image *wl_image, int scale)
static int get_cursor_shape_enum_from_ghost_shape(const GHOST_TStandardCursor shape)
{
const int32_t image_size_x = int32_t(wl_image->width);
const int32_t image_size_y = int32_t(wl_image->height);
while (scale > 1) {
if ((image_size_x % scale) == 0 && (image_size_y % scale) == 0) {
break;
}
scale -= 1;
switch (shape) {
case GHOST_kStandardCursorDefault:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;
case GHOST_kStandardCursorRightArrow:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE;
case GHOST_kStandardCursorLeftArrow:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE;
case GHOST_kStandardCursorInfo:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CONTEXT_MENU;
case GHOST_kStandardCursorDestroy:
return 0; /* Not available. */
case GHOST_kStandardCursorHelp:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP;
case GHOST_kStandardCursorWait:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT;
case GHOST_kStandardCursorText:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT;
case GHOST_kStandardCursorCrosshair:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR;
case GHOST_kStandardCursorCrosshairA:
return 0; /* Not available. */
case GHOST_kStandardCursorCrosshairB:
return 0; /* Not available. */
case GHOST_kStandardCursorCrosshairC:
return 0; /* Not available. */
case GHOST_kStandardCursorPencil:
return 0; /* Not available. */
case GHOST_kStandardCursorUpArrow:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE;
case GHOST_kStandardCursorDownArrow:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE;
case GHOST_kStandardCursorVerticalSplit:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE;
case GHOST_kStandardCursorHorizontalSplit:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ROW_RESIZE;
case GHOST_kStandardCursorEraser:
return 0; /* Not available. */
case GHOST_kStandardCursorKnife:
return 0; /* Not available. */
case GHOST_kStandardCursorEyedropper:
return 0; /* Not available. */
case GHOST_kStandardCursorZoomIn:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN;
case GHOST_kStandardCursorZoomOut:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT;
case GHOST_kStandardCursorMove:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE;
case GHOST_kStandardCursorHandOpen:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB;
case GHOST_kStandardCursorHandClosed:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING;
case GHOST_kStandardCursorHandPoint:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER;
case GHOST_kStandardCursorNSEWScroll:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL;
case GHOST_kStandardCursorNSScroll:
return 0; /* Not available. */
case GHOST_kStandardCursorEWScroll:
return 0; /* Not available. */
case GHOST_kStandardCursorStop:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED;
case GHOST_kStandardCursorUpDown:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE;
case GHOST_kStandardCursorLeftRight:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE;
case GHOST_kStandardCursorTopSide:
return 0; /* Not available. */
case GHOST_kStandardCursorBottomSide:
return 0; /* Not available. */
case GHOST_kStandardCursorLeftSide:
return 0; /* Not available. */
case GHOST_kStandardCursorRightSide:
return 0; /* Not available. */
case GHOST_kStandardCursorTopLeftCorner:
return 0; /* Not available. */
case GHOST_kStandardCursorTopRightCorner:
return 0; /* Not available. */
case GHOST_kStandardCursorBottomRightCorner:
return 0; /* Not available. */
case GHOST_kStandardCursorBottomLeftCorner:
return 0; /* Not available. */
case GHOST_kStandardCursorCopy:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY;
case GHOST_kStandardCursorLeftHandle:
return 0; /* Not available. */
case GHOST_kStandardCursorRightHandle:
return 0; /* Not available. */
case GHOST_kStandardCursorBothHandles:
return 0; /* Not available. */
case GHOST_kStandardCursorBlade:
return 0; /* Not available. */
case GHOST_kStandardCursorCustom:
return 0; /* Not available. */
}
return scale;
}
static const wl_cursor *gwl_seat_cursor_find_from_shape(GWL_Seat *seat,
const GHOST_TStandardCursor shape,
const char **r_cursor_name)
{
/* Caller must lock `server_mutex`. */
GWL_Cursor *cursor = &seat->cursor;
wl_cursor *wl_cursor = nullptr;
const char *cursor_name = ghost_wl_cursors.names[shape];
if (cursor_name[0] != '\0') {
if (!cursor->wl.theme) {
/* The cursor wl_surface hasn't entered an output yet. Initialize theme with scale 1. */
cursor->wl.theme = wl_cursor_theme_load(
(cursor->theme_name.empty() ? nullptr : cursor->theme_name.c_str()),
cursor->theme_size,
seat->system->wl_shm_get());
}
if (cursor->wl.theme) {
wl_cursor = wl_cursor_theme_get_cursor(cursor->wl.theme, cursor_name);
if (!wl_cursor) {
GHOST_PRINT("cursor '" << cursor_name << "' does not exist" << std::endl);
}
}
}
if (r_cursor_name && wl_cursor) {
*r_cursor_name = cursor_name;
}
return wl_cursor;
GHOST_ASSERT(0, "Unhandled GHOST cursor shape!");
/* Shape not found. */
return 0;
}
/**
@@ -2840,32 +2797,40 @@ static void gwl_seat_cursor_buffer_show(GWL_Seat *seat)
const GWL_Cursor *cursor = &seat->cursor;
if (seat->wl.pointer) {
const int scale = cursor->is_custom ? cursor->custom_scale : seat->pointer.theme_scale;
const int32_t hotspot_x = int32_t(cursor->wl.image.hotspot_x) / scale;
const int32_t hotspot_y = int32_t(cursor->wl.image.hotspot_y) / scale;
wl_pointer_set_cursor(
seat->wl.pointer, seat->pointer.serial, cursor->wl.surface_cursor, hotspot_x, hotspot_y);
}
if (!seat->wp.tablet_tools.empty()) {
const int scale = cursor->is_custom ? cursor->custom_scale : seat->tablet.theme_scale;
const int32_t hotspot_x = int32_t(cursor->wl.image.hotspot_x) / scale;
const int32_t hotspot_y = int32_t(cursor->wl.image.hotspot_y) / scale;
for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2,
seat->tablet.serial,
tablet_tool->wl.surface_cursor,
hotspot_x,
hotspot_y);
#ifdef USE_KDE_TABLET_HIDDEN_CURSOR_HACK
wl_surface_commit(tablet_tool->wl.surface_cursor);
#endif
if (seat->cursor.shape.device) {
wp_cursor_shape_device_v1_set_shape(
seat->cursor.shape.device, seat->pointer.serial, seat->cursor.shape.enum_id);
}
else {
const int32_t hotspot_x = int32_t(cursor->wl.image.hotspot_x);
const int32_t hotspot_y = int32_t(cursor->wl.image.hotspot_y);
wl_pointer_set_cursor(
seat->wl.pointer, seat->pointer.serial, cursor->wl.surface_cursor, hotspot_x, hotspot_y);
}
}
gwl_seat_cursor_anim_reset(seat);
if (!seat->wp.tablet_tools.empty()) {
const int32_t hotspot_x = int32_t(cursor->wl.image.hotspot_x);
const int32_t hotspot_y = int32_t(cursor->wl.image.hotspot_y);
for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
if (tablet_tool->shape.device) {
wp_cursor_shape_device_v1_set_shape(
tablet_tool->shape.device, seat->tablet.serial, tablet_tool->shape.enum_id);
}
else {
zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2,
seat->tablet.serial,
tablet_tool->wl.surface_cursor,
hotspot_x,
hotspot_y);
#ifdef USE_KDE_TABLET_HIDDEN_CURSOR_HACK
wl_surface_commit(tablet_tool->wl.surface_cursor);
#endif
}
}
}
}
/**
@@ -2876,8 +2841,6 @@ static void gwl_seat_cursor_buffer_show(GWL_Seat *seat)
*/
static void gwl_seat_cursor_buffer_hide(GWL_Seat *seat)
{
gwl_seat_cursor_anim_end(seat);
wl_pointer_set_cursor(seat->wl.pointer, seat->pointer.serial, nullptr, 0, 0);
for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2, seat->tablet.serial, nullptr, 0, 0);
@@ -2894,11 +2857,9 @@ static void gwl_seat_cursor_buffer_set(const GWL_Seat *seat,
/* This is a requirement of WAYLAND, when this isn't the case,
* it causes Blender's window to close intermittently. */
if (seat->wl.pointer) {
const int scale = cursor_buffer_compatible_scale_from_image(
wl_image, cursor->is_custom ? cursor->custom_scale : seat->pointer.theme_scale);
const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale;
const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale;
cursor_buffer_set_surface_impl(wl_image, buffer, cursor->wl.surface_cursor, scale);
const int32_t hotspot_x = int32_t(wl_image->hotspot_x);
const int32_t hotspot_y = int32_t(wl_image->hotspot_y);
cursor_buffer_set_surface_impl(wl_image, buffer, cursor->wl.surface_cursor);
wl_pointer_set_cursor(seat->wl.pointer,
seat->pointer.serial,
visible ? cursor->wl.surface_cursor : nullptr,
@@ -2908,14 +2869,12 @@ static void gwl_seat_cursor_buffer_set(const GWL_Seat *seat,
/* Set the cursor for all tablet tools as well. */
if (!seat->wp.tablet_tools.empty()) {
const int scale = cursor_buffer_compatible_scale_from_image(
wl_image, cursor->is_custom ? cursor->custom_scale : seat->tablet.theme_scale);
const int32_t hotspot_x = int32_t(wl_image->hotspot_x) / scale;
const int32_t hotspot_y = int32_t(wl_image->hotspot_y) / scale;
const int32_t hotspot_x = int32_t(wl_image->hotspot_x);
const int32_t hotspot_y = int32_t(wl_image->hotspot_y);
for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
cursor_buffer_set_surface_impl(wl_image, buffer, tablet_tool->wl.surface_cursor, scale);
cursor_buffer_set_surface_impl(wl_image, buffer, tablet_tool->wl.surface_cursor);
zwp_tablet_tool_v2_set_cursor(zwp_tablet_tool_v2,
seat->tablet.serial,
visible ? tablet_tool->wl.surface_cursor : nullptr,
@@ -2928,9 +2887,7 @@ static void gwl_seat_cursor_buffer_set(const GWL_Seat *seat,
static void gwl_seat_cursor_buffer_set_current(GWL_Seat *seat)
{
const GWL_Cursor *cursor = &seat->cursor;
gwl_seat_cursor_anim_end(seat);
gwl_seat_cursor_buffer_set(seat, &cursor->wl.image, cursor->wl.buffer);
gwl_seat_cursor_anim_begin_if_needed(seat);
}
enum eCursorSetMode {
@@ -2978,106 +2935,6 @@ static void gwl_seat_cursor_visible_set(GWL_Seat *seat,
/** \} */
/* -------------------------------------------------------------------- */
/** \name Private Cursor Animation API
* \{ */
#ifdef USE_EVENT_BACKGROUND_THREAD
static bool gwl_seat_cursor_anim_check(GWL_Seat *seat)
{
const wl_cursor *wl_cursor = seat->cursor.wl.theme_cursor;
if (!wl_cursor) {
return false;
}
/* NOTE: return true to stress test animated cursor,
* to ensure (otherwise rare) issues are triggered more frequently. */
// return true;
return wl_cursor->image_count > 1;
}
static void gwl_seat_cursor_anim_begin(GWL_Seat *seat)
{
/* Caller must lock `server_mutex`. */
GHOST_ASSERT(seat->cursor.anim_handle == nullptr, "Must be cleared");
/* Callback for updating the cursor animation. */
auto cursor_anim_frame_step_fn =
[](GWL_Seat *seat, GWL_Cursor_AnimHandle *anim_handle, int delay) {
/* It's possible the `wl_cursor` is reloaded while the cursor is animating.
* Don't access outside the lock, that's why the `delay` is passed in. */
std::mutex *server_mutex = seat->system->server_mutex;
int frame = 0;
while (!anim_handle->exit_pending.load()) {
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
if (!anim_handle->exit_pending.load()) {
std::lock_guard lock_server_guard{*server_mutex};
if (!anim_handle->exit_pending.load()) {
const wl_cursor *wl_cursor = seat->cursor.wl.theme_cursor;
frame = (frame + 1) % wl_cursor->image_count;
wl_cursor_image *image = wl_cursor->images[frame];
wl_buffer *buffer = wl_cursor_image_get_buffer(image);
gwl_seat_cursor_buffer_set(seat, image, buffer);
delay = wl_cursor->images[frame]->delay;
/* Without this the cursor won't update when other processes are occupied. */
wl_display_flush(seat->system->wl_display_get());
}
}
}
delete anim_handle;
};
/* Allocate so this can be set before the thread begins. */
GWL_Cursor_AnimHandle *anim_handle = new GWL_Cursor_AnimHandle;
seat->cursor.anim_handle = anim_handle;
const int delay = seat->cursor.wl.theme_cursor->images[0]->delay;
std::thread cursor_anim_thread(cursor_anim_frame_step_fn, seat, anim_handle, delay);
/* Application logic should take priority. */
thread_set_min_priority(cursor_anim_thread);
cursor_anim_thread.detach();
}
static void gwl_seat_cursor_anim_begin_if_needed(GWL_Seat *seat)
{
if (gwl_seat_cursor_anim_check(seat)) {
gwl_seat_cursor_anim_begin(seat);
}
}
static void gwl_seat_cursor_anim_end(GWL_Seat *seat)
{
GWL_Cursor *cursor = &seat->cursor;
if (cursor->anim_handle) {
GWL_Cursor_AnimHandle *anim_handle = cursor->anim_handle;
cursor->anim_handle = nullptr;
anim_handle->exit_pending.store(true);
}
}
static void gwl_seat_cursor_anim_reset(GWL_Seat *seat)
{
gwl_seat_cursor_anim_end(seat);
gwl_seat_cursor_anim_begin_if_needed(seat);
}
#else
/* Unfortunately cursor animation requires a background thread. */
[[maybe_unused]] static bool gwl_seat_cursor_anim_check(GWL_Seat * /*seat*/)
{
return false;
}
[[maybe_unused]] static void gwl_seat_cursor_anim_begin(GWL_Seat * /*seat*/) {}
[[maybe_unused]] static void gwl_seat_cursor_anim_begin_if_needed(GWL_Seat * /*seat*/) {}
[[maybe_unused]] static void gwl_seat_cursor_anim_end(GWL_Seat * /*seat*/) {}
[[maybe_unused]] static void gwl_seat_cursor_anim_reset(GWL_Seat * /*seat*/) {}
#endif /* !USE_EVENT_BACKGROUND_THREAD */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Private Keyboard Depressed Key Tracking
*
@@ -3782,6 +3639,12 @@ static bool update_cursor_scale(GWL_Cursor &cursor,
GWL_SeatStatePointer *seat_state_pointer,
wl_surface *wl_surface_cursor)
{
/* TODO: do cursor scaling correctly. */
(void)cursor;
(void)shm;
(void)seat_state_pointer;
(void)wl_surface_cursor;
#if 0
int scale = 0;
for (const GWL_Output *output : seat_state_pointer->outputs) {
int output_scale_floor = output->scale;
@@ -3822,6 +3685,7 @@ static bool update_cursor_scale(GWL_Cursor &cursor,
return true;
}
#endif
return false;
}
@@ -3838,7 +3702,6 @@ static void cursor_surface_handle_enter(void *data, wl_surface *wl_surface, wl_o
seat, wl_surface);
const GWL_Output *reg_output = ghost_wl_output_user_data(wl_output);
seat_state_pointer->outputs.insert(reg_output);
update_cursor_scale(seat->cursor, seat->system->wl_shm_get(), seat_state_pointer, wl_surface);
}
static void cursor_surface_handle_leave(void *data, wl_surface *wl_surface, wl_output *wl_output)
@@ -3854,7 +3717,6 @@ static void cursor_surface_handle_leave(void *data, wl_surface *wl_surface, wl_o
seat, wl_surface);
const GWL_Output *reg_output = ghost_wl_output_user_data(wl_output);
seat_state_pointer->outputs.erase(reg_output);
update_cursor_scale(seat->cursor, seat->system->wl_shm_get(), seat_state_pointer, wl_surface);
}
static void cursor_surface_handle_preferred_buffer_scale(void * /*data*/,
@@ -4699,6 +4561,9 @@ static void tablet_tool_handle_removed(void *data, zwp_tablet_tool_v2 *zwp_table
if (tablet_tool->wl.surface_cursor) {
wl_surface_destroy(tablet_tool->wl.surface_cursor);
}
if (tablet_tool->shape.device) {
wp_cursor_shape_device_v1_destroy(tablet_tool->shape.device);
}
seat->wp.tablet_tools.erase(zwp_tablet_tool_v2);
delete tablet_tool;
@@ -6123,15 +5988,17 @@ static void gwl_seat_capability_pointer_enable(GWL_Seat *seat)
seat->cursor.wl.surface_cursor = wl_compositor_create_surface(seat->system->wl_compositor_get());
seat->cursor.visible = true;
seat->cursor.wl.buffer = nullptr;
wl_pointer_add_listener(seat->wl.pointer, &pointer_listener, seat);
wl_surface_add_listener(seat->cursor.wl.surface_cursor, &cursor_surface_listener, seat);
ghost_wl_surface_tag_cursor_pointer(seat->cursor.wl.surface_cursor);
gwl_seat_capability_pointer_multitouch_enable(seat);
{
/* Use environment variables, falling back to defaults.
* These environment variables are used by enough WAYLAND applications
* that it makes sense to check them (see `Xcursor` man page). */
const char *env;
env = getenv("XCURSOR_THEME");
seat->cursor.theme_name = std::string(env ? env : "");
env = getenv("XCURSOR_SIZE");
seat->cursor.theme_size = default_cursor_size;
@@ -6145,12 +6012,6 @@ static void gwl_seat_capability_pointer_enable(GWL_Seat *seat)
}
}
}
wl_pointer_add_listener(seat->wl.pointer, &pointer_listener, seat);
wl_surface_add_listener(seat->cursor.wl.surface_cursor, &cursor_surface_listener, seat);
ghost_wl_surface_tag_cursor_pointer(seat->cursor.wl.surface_cursor);
gwl_seat_capability_pointer_multitouch_enable(seat);
}
static void gwl_seat_capability_pointer_disable(GWL_Seat *seat)
@@ -6159,16 +6020,17 @@ static void gwl_seat_capability_pointer_disable(GWL_Seat *seat)
return;
}
if (seat->cursor.shape.device) {
wp_cursor_shape_device_v1_destroy(seat->cursor.shape.device);
seat->cursor.shape.device = nullptr;
}
gwl_seat_capability_pointer_multitouch_disable(seat);
if (seat->cursor.wl.surface_cursor) {
wl_surface_destroy(seat->cursor.wl.surface_cursor);
seat->cursor.wl.surface_cursor = nullptr;
}
if (seat->cursor.wl.theme) {
wl_cursor_theme_destroy(seat->cursor.wl.theme);
seat->cursor.wl.theme = nullptr;
}
wl_pointer_destroy(seat->wl.pointer);
seat->wl.pointer = nullptr;
@@ -7118,6 +6980,26 @@ static void gwl_registry_wp_text_input_manager_remove(GWL_Display *display,
#endif /* WITH_INPUT_IME */
/* #GWL_Display.wp_cursor_shape_manager */
static void gwl_registry_wp_cursor_shape_manager_add(GWL_Display *display,
const GWL_RegisteryAdd_Params &params)
{
const uint version = GWL_IFACE_VERSION_CLAMP(params.version, 1u, 1u);
display->wp.cursor_shape_manager = static_cast<wp_cursor_shape_manager_v1 *>(wl_registry_bind(
display->wl.registry, params.name, &wp_cursor_shape_manager_v1_interface, version));
gwl_registry_entry_add(display, params, nullptr);
}
static void gwl_registry_wp_cursor_shape_manager_remove(GWL_Display *display,
void * /*user_data*/,
const bool /*on_exit*/)
{
wp_cursor_shape_manager_v1 **value_p = &display->wp.cursor_shape_manager;
wp_cursor_shape_manager_v1_destroy(*value_p);
*value_p = nullptr;
}
/**
* Map interfaces to initialization functions.
*
@@ -7193,6 +7075,12 @@ static const GWL_RegistryHandler gwl_registry_handlers[] = {
/*remove_fn*/ gwl_registry_wp_text_input_manager_remove,
},
#endif
{
/*interface_p*/ &wp_cursor_shape_manager_v1_interface.name,
/*add_fn*/ gwl_registry_wp_cursor_shape_manager_add,
/*update_fn*/ nullptr,
/*remove_fn*/ gwl_registry_wp_cursor_shape_manager_remove,
},
/* Higher level interfaces. */
{
/*interface_p*/ &zwp_pointer_constraints_v1_interface.name,
@@ -8610,28 +8498,48 @@ GHOST_TSuccess GHOST_SystemWayland::cursor_shape_set(const GHOST_TStandardCursor
return GHOST_kFailure;
}
const char *cursor_name = nullptr;
const wl_cursor *wl_cursor = gwl_seat_cursor_find_from_shape(seat, shape, &cursor_name);
if (wl_cursor == nullptr) {
if (!display_->wp.cursor_shape_manager) {
return GHOST_kFailure;
}
GWL_Cursor *cursor = &seat->cursor;
wl_cursor_image *image = wl_cursor->images[0];
wl_buffer *buffer = wl_cursor_image_get_buffer(image);
if (!buffer) {
const int wl_shape = get_cursor_shape_enum_from_ghost_shape(shape);
if (!wl_shape) {
return GHOST_kFailure;
}
cursor->visible = true;
cursor->is_custom = false;
cursor->wl.buffer = buffer;
cursor->wl.image = *image;
cursor->wl.theme_cursor = wl_cursor;
cursor->wl.theme_cursor_name = cursor_name;
if (seat->wl.pointer) {
/* Set cursor for the pointer device. */
if (!seat->cursor.shape.device) {
seat->cursor.shape.device = wp_cursor_shape_manager_v1_get_pointer(
display_->wp.cursor_shape_manager, seat->wl.pointer);
if (!seat->cursor.shape.device) {
return GHOST_kFailure;
}
}
wp_cursor_shape_device_v1_set_shape(seat->cursor.shape.device, seat->pointer.serial, wl_shape);
/* Set this to make sure we remember which shape we set when unhiding cursors. */
seat->cursor.shape.enum_id = wl_shape;
gwl_seat_cursor_buffer_set_current(seat);
GWL_Cursor *cursor = &seat->cursor;
cursor->visible = true;
cursor->is_custom = false;
}
/* Set cursor for all tablet tool devices. */
for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
if (!tablet_tool->shape.device) {
tablet_tool->shape.device = wp_cursor_shape_manager_v1_get_tablet_tool_v2(
display_->wp.cursor_shape_manager, zwp_tablet_tool_v2);
if (!tablet_tool->shape.device) {
return GHOST_kFailure;
}
}
wp_cursor_shape_device_v1_set_shape(tablet_tool->shape.device, seat->tablet.serial, wl_shape);
/* Set this to make sure we remember which shape we set when unhiding cursors. */
tablet_tool->shape.enum_id = wl_shape;
}
return GHOST_kSuccess;
}
@@ -8643,8 +8551,12 @@ GHOST_TSuccess GHOST_SystemWayland::cursor_shape_check(const GHOST_TStandardCurs
return GHOST_kFailure;
}
const wl_cursor *wl_cursor = gwl_seat_cursor_find_from_shape(seat, cursorShape, nullptr);
if (wl_cursor == nullptr) {
if (!display_->wp.cursor_shape_manager) {
return GHOST_kFailure;
}
const int wl_shape = get_cursor_shape_enum_from_ghost_shape(cursorShape);
if (!wl_shape) {
return GHOST_kFailure;
}
return GHOST_kSuccess;
@@ -8661,6 +8573,23 @@ GHOST_TSuccess GHOST_SystemWayland::cursor_shape_custom_set(const uint8_t *bitma
if (UNLIKELY(!seat)) {
return GHOST_kFailure;
}
/* If we were using a wayland cursor shape, be sure to free it up before we try to use any
* custom shapes. */
if (seat->cursor.shape.device) {
wp_cursor_shape_device_v1_destroy(seat->cursor.shape.device);
seat->cursor.shape.device = nullptr;
}
/* If we were using a wayland cursor shape, be sure to free it for the tablet tools
* before we try to use any custom shapes. */
for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) {
GWL_TabletTool *tablet_tool = static_cast<GWL_TabletTool *>(
zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2));
if (tablet_tool->shape.device) {
wp_cursor_shape_device_v1_destroy(tablet_tool->shape.device);
tablet_tool->shape.device = nullptr;
}
}
GWL_Cursor *cursor = &seat->cursor;
if (cursor->custom_data) {
@@ -8771,8 +8700,6 @@ GHOST_TSuccess GHOST_SystemWayland::cursor_shape_custom_set(const uint8_t *bitma
cursor->wl.image.height = uint32_t(size[1]);
cursor->wl.image.hotspot_x = uint32_t(hot_spot[0]);
cursor->wl.image.hotspot_y = uint32_t(hot_spot[1]);
cursor->wl.theme_cursor = nullptr;
cursor->wl.theme_cursor_name = nullptr;
gwl_seat_cursor_buffer_set_current(seat);
@@ -9389,7 +9316,6 @@ void GHOST_SystemWayland::output_scale_update(GWL_Output *output)
}
}
}
for (GWL_Seat *seat : display_->seats) {
if (seat->pointer.outputs.count(output)) {
update_cursor_scale(seat->cursor,