GHOST/Wayland: scalable vector cursor support via cursor-generators
Support showing vector cursors at the appropriate size based on each monitors DPI. This solves incorrectly sized cursors with Hi-DPI outputs as well as supporting outputs with different DPI's. Instead of passing pixel data, pass an object that can generate cursors, GHOST/Wayland can then ensure the correct size is used based on the display the cursor is shown on. While may sound overly complicated it actually simplifies displaying cursors on Wayland, especially for multiple monitors at different DPI's. Showing cursors at higher or lower resolutions than the monitor's DPI also behaves differently on GNOME & Plasma, resulting in situations where the cursor would show larger/smaller than expected. Details: - Take advantage of the recently added SVG cursors & BLF time cursor !140990 & !141367. - Setting bitmap cursors via GHOST_SetCustomCursorShape is now a no-op for Wayland since it's no longer used. This was supported in an earlier version of this PR and could be restored if needed. - While fractional scaling works it relies on the compositor downscaling the cursors. Ideally they would be rendered at the target size but this isn't a priority as the difference isn't noticeable. Ref !141597
This commit is contained in:
@@ -343,6 +343,18 @@ extern GHOST_TSuccess GHOST_SetCustomCursorShape(GHOST_WindowHandle windowhandle
|
||||
const int size[2],
|
||||
const int hot_spot[2],
|
||||
bool canInvertColor);
|
||||
/**
|
||||
* Set a cursor "generator", allowing the GHOST back-end to dynamically
|
||||
* generate cursors at different sizes as needed, depending on the monitor DPI.
|
||||
*
|
||||
* \param cursor_generator: An object which generates cursors.
|
||||
* Ownership is transferred to GHOST which is responsible for calling it's free method.
|
||||
*
|
||||
* The capability flag: #GHOST_kCapabilityCursorGenerator should be checked,
|
||||
* otherwise this call is a no-op.
|
||||
*/
|
||||
extern GHOST_TSuccess GHOST_SetCustomCursorGenerator(GHOST_WindowHandle windowhandle,
|
||||
GHOST_CursorGenerator *cursor_generator);
|
||||
|
||||
extern GHOST_TSuccess GHOST_GetCursorBitmap(GHOST_WindowHandle windowhandle,
|
||||
GHOST_CursorBitmapRef *bitmap);
|
||||
|
||||
@@ -332,6 +332,14 @@ class GHOST_IWindow {
|
||||
const int hot_spot[2],
|
||||
bool canInvertColor) = 0;
|
||||
|
||||
/**
|
||||
* Set the cursor generator.
|
||||
*
|
||||
* \param cursor_generator: An object which generates cursors.
|
||||
* Ownership is transferred to GHOST which is responsible for calling it's free method.
|
||||
*/
|
||||
virtual GHOST_TSuccess setCustomCursorGenerator(GHOST_CursorGenerator *cursor_generator) = 0;
|
||||
|
||||
virtual GHOST_TSuccess getCursorBitmap(GHOST_CursorBitmapRef *bitmap) = 0;
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,6 +69,45 @@ typedef struct {
|
||||
int hot_spot[2];
|
||||
} GHOST_CursorBitmapRef;
|
||||
|
||||
/**
|
||||
* Pass this as an argument to GHOST so each ghost back-end
|
||||
* can generate cursors on demand.
|
||||
*/
|
||||
typedef struct GHOST_CursorGenerator {
|
||||
/**
|
||||
* The main cursor generation callback.
|
||||
*
|
||||
* \note only supports RGBA cursors.
|
||||
*
|
||||
* \param cursor_generator: Pass in to allow accessing the user argument.
|
||||
* \param cursor_size: The cursor size to generate.
|
||||
* \param cursor_size_max: The maximum dimension (width or height).
|
||||
* \param r_bitmap_size: The bitmap width & height in pixels.
|
||||
* The generator must guarantee the resulting size (dimensions written to `r_bitmap_size`)
|
||||
* never exceeds `cursor_size_max`.
|
||||
* \param r_hot_spot: The cursor hot-spot.
|
||||
* \return the bitmap data or null if it could not be generated.
|
||||
* Must be at least: `sizeof(uint8_t[4]) * r_bitmap_size[0] * r_bitmap_size[1]` allocated bytes.
|
||||
*/
|
||||
uint8_t *(*generate_fn)(const struct GHOST_CursorGenerator *cursor_generator,
|
||||
int cursor_size,
|
||||
int cursor_size_max,
|
||||
uint8_t *(*alloc_fn)(size_t size),
|
||||
int r_bitmap_size[2],
|
||||
int r_hot_spot[2]);
|
||||
/**
|
||||
* Called once GHOST has finished with this object,
|
||||
* Typically this would free `user_data`.
|
||||
*/
|
||||
void (*free_fn)(struct GHOST_CursorGenerator *cursor_generator);
|
||||
/**
|
||||
* Implementation specific data used for rasterization
|
||||
* (could contain SVG data for example).
|
||||
*/
|
||||
GHOST_TUserDataPtr user_data;
|
||||
|
||||
} GHOST_CursorGenerator;
|
||||
|
||||
typedef enum {
|
||||
GHOST_gpuStereoVisual = (1 << 0),
|
||||
GHOST_gpuDebugContext = (1 << 1),
|
||||
@@ -133,6 +172,10 @@ typedef enum {
|
||||
* to be temporary as our intention is to implement on all platforms.
|
||||
*/
|
||||
GHOST_kCapabilityCursorRGBA = (1 << 10),
|
||||
/**
|
||||
* Setting cursors via #GHOST_SetCursorGenerator is supported.
|
||||
*/
|
||||
GHOST_kCapabilityCursorGenerator = (1 << 11),
|
||||
|
||||
} GHOST_TCapabilityFlag;
|
||||
|
||||
@@ -145,7 +188,8 @@ typedef enum {
|
||||
GHOST_kCapabilityClipboardPrimary | GHOST_kCapabilityGPUReadFrontBuffer | \
|
||||
GHOST_kCapabilityClipboardImage | GHOST_kCapabilityDesktopSample | GHOST_kCapabilityInputIME | \
|
||||
GHOST_kCapabilityTrackpadPhysicalDirection | GHOST_kCapabilityWindowDecorationStyles | \
|
||||
GHOST_kCapabilityKeyboardHyperKey | GHOST_kCapabilityCursorRGBA)
|
||||
GHOST_kCapabilityKeyboardHyperKey | GHOST_kCapabilityCursorRGBA | \
|
||||
GHOST_kCapabilityCursorGenerator)
|
||||
|
||||
/* Xtilt and Ytilt represent how much the pen is tilted away from
|
||||
* vertically upright in either the X or Y direction, with X and Y the
|
||||
|
||||
@@ -305,6 +305,13 @@ GHOST_TSuccess GHOST_SetCustomCursorShape(GHOST_WindowHandle windowhandle,
|
||||
return window->setCustomCursorShape(bitmap, mask, size, hot_spot, canInvertColor);
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SetCustomCursorGenerator(GHOST_WindowHandle windowhandle,
|
||||
GHOST_CursorGenerator *cursor_generator)
|
||||
{
|
||||
GHOST_IWindow *window = (GHOST_IWindow *)windowhandle;
|
||||
return window->setCustomCursorGenerator(cursor_generator);
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_GetCursorBitmap(GHOST_WindowHandle windowhandle,
|
||||
GHOST_CursorBitmapRef *bitmap)
|
||||
{
|
||||
|
||||
@@ -979,7 +979,9 @@ GHOST_TCapabilityFlag GHOST_SystemCocoa::getCapabilities() const
|
||||
* it's possible another modifier could be optionally used in it's place. */
|
||||
GHOST_kCapabilityKeyboardHyperKey |
|
||||
/* No support yet for RGBA mouse cursors. */
|
||||
GHOST_kCapabilityCursorRGBA));
|
||||
GHOST_kCapabilityCursorRGBA |
|
||||
/* No support yet for dynamic cursor generation. */
|
||||
GHOST_kCapabilityCursorGenerator));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
|
||||
@@ -74,7 +74,9 @@ class GHOST_SystemHeadless : public GHOST_System {
|
||||
/* Wrap. */
|
||||
GHOST_kCapabilityKeyboardHyperKey |
|
||||
/* Wrap. */
|
||||
GHOST_kCapabilityCursorRGBA)
|
||||
GHOST_kCapabilityCursorRGBA |
|
||||
/* Wrap. */
|
||||
GHOST_kCapabilityCursorGenerator)
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
@@ -799,7 +799,9 @@ GHOST_TCapabilityFlag GHOST_SystemSDL::getCapabilities() const
|
||||
/* No support for a Hyper modifier key. */
|
||||
GHOST_kCapabilityKeyboardHyperKey |
|
||||
/* No support yet for RGBA mouse cursors. */
|
||||
GHOST_kCapabilityCursorRGBA));
|
||||
GHOST_kCapabilityCursorRGBA |
|
||||
/* No support yet for dynamic cursor generation. */
|
||||
GHOST_kCapabilityCursorGenerator));
|
||||
}
|
||||
|
||||
char *GHOST_SystemSDL::getClipboard(bool /*selection*/) const
|
||||
|
||||
@@ -471,13 +471,16 @@ struct GWL_Cursor {
|
||||
void *custom_data = nullptr;
|
||||
/** The size of `custom_data` in bytes. */
|
||||
size_t custom_data_size = 0;
|
||||
|
||||
/** The current displayed size, use to check if the cursor need to be re-generated. */
|
||||
int custom_cursor_scale = 0;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* The size of the cursor in logical pixels.
|
||||
* This must be scaled by the maximum output scale when calculating the physical pixels.
|
||||
* See #update_cursor_scale.
|
||||
*/
|
||||
int theme_size = 0;
|
||||
int custom_scale = 1;
|
||||
};
|
||||
|
||||
/** \} */
|
||||
@@ -705,7 +708,7 @@ struct GWL_SeatStatePointer {
|
||||
/** Outputs on which the cursor is visible. */
|
||||
std::unordered_set<const GWL_Output *> outputs;
|
||||
|
||||
int theme_scale = 1;
|
||||
int buffer_scale = 1;
|
||||
|
||||
/**
|
||||
* The serial of the last used pointer or tablet.
|
||||
@@ -1905,6 +1908,11 @@ static void gwl_registry_entry_update_all(GWL_Display *display, const int interf
|
||||
/** \name Private Utility Functions
|
||||
* \{ */
|
||||
|
||||
static uint32_t round_up_uint(const uint32_t x, const uint32_t multiple)
|
||||
{
|
||||
return ((x + multiple - 1) / multiple) * multiple;
|
||||
}
|
||||
|
||||
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
|
||||
static const char *strchr_or_end(const char *str, const char ch)
|
||||
{
|
||||
@@ -2693,8 +2701,6 @@ static void cursor_buffer_set_surface_impl(const wl_cursor_image *wl_image,
|
||||
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!");
|
||||
(void)image_size_x;
|
||||
(void)image_size_y;
|
||||
|
||||
wl_surface_set_buffer_scale(wl_surface, scale);
|
||||
wl_surface_attach(wl_surface, buffer, 0, 0);
|
||||
@@ -2831,6 +2837,24 @@ static std::optional<wp_cursor_shape_device_v1_shape> gwl_seat_cursor_find_wl_sh
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently account for all cursors as the buffer is shared.
|
||||
*
|
||||
* Note that a per pointing device buffer would be better,
|
||||
* this is more of a low priority to-do.
|
||||
*/
|
||||
static int gwl_seat_cursor_buffer_scale_calc(const GWL_Seat *seat)
|
||||
{
|
||||
int scale = 1;
|
||||
if (seat->wl.pointer) {
|
||||
scale = std::max(scale, seat->pointer.buffer_scale);
|
||||
}
|
||||
if (seat->wp.tablet_seat) {
|
||||
scale = std::max(scale, seat->tablet.buffer_scale);
|
||||
}
|
||||
return scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the buffer defined by #gwl_seat_cursor_buffer_set without changing anything else,
|
||||
* so #gwl_seat_cursor_buffer_hide can be used to display it again.
|
||||
@@ -2847,8 +2871,9 @@ static void gwl_seat_cursor_buffer_show(GWL_Seat *seat)
|
||||
seat->cursor.shape.device, seat->pointer.serial, seat->cursor.shape.enum_id);
|
||||
}
|
||||
else {
|
||||
/* TODO: support scale for custom cursors. */
|
||||
const int scale = 1;
|
||||
/* TODO: use `seat->pointer.buffer_scale`, give each device it's own buffer. */
|
||||
const int scale = gwl_seat_cursor_buffer_scale_calc(seat);
|
||||
|
||||
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(
|
||||
@@ -2857,8 +2882,8 @@ static void gwl_seat_cursor_buffer_show(GWL_Seat *seat)
|
||||
}
|
||||
|
||||
if (!seat->wp.tablet_tools.empty()) {
|
||||
/* TODO: support scale for custom cursors. */
|
||||
const int scale = 1;
|
||||
/* TODO: use `seat->tablet.buffer_scale`, give each device it's own buffer. */
|
||||
const int scale = gwl_seat_cursor_buffer_scale_calc(seat);
|
||||
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) {
|
||||
@@ -2905,8 +2930,7 @@ static void gwl_seat_cursor_buffer_set(const GWL_Seat *seat,
|
||||
const GWL_Cursor *cursor = &seat->cursor;
|
||||
const bool visible = (cursor->visible && cursor->is_hardware);
|
||||
|
||||
/* TODO: support scale for custom cursors. */
|
||||
const int scale = 1;
|
||||
const int scale = gwl_seat_cursor_buffer_scale_calc(seat);
|
||||
|
||||
/* This is a requirement of WAYLAND, when this isn't the case,
|
||||
* it causes Blender's window to close intermittently. */
|
||||
@@ -3688,58 +3712,36 @@ static const wl_buffer_listener cursor_buffer_listener = {
|
||||
static CLG_LogRef LOG_WL_CURSOR_SURFACE = {"ghost.wl.handle.cursor_surface"};
|
||||
#define LOG (&LOG_WL_CURSOR_SURFACE)
|
||||
|
||||
static bool update_cursor_scale(GWL_Cursor &cursor,
|
||||
wl_shm *shm,
|
||||
GWL_SeatStatePointer *seat_state_pointer,
|
||||
wl_surface *wl_surface_cursor)
|
||||
static bool update_cursor_scale(GWL_Seat *seat,
|
||||
GWL_Cursor &cursor,
|
||||
GWL_SeatStatePointer *seat_state_pointer)
|
||||
{
|
||||
/* TODO: do cursor scaling correctly. */
|
||||
(void)cursor;
|
||||
(void)shm;
|
||||
(void)seat_state_pointer;
|
||||
(void)wl_surface_cursor;
|
||||
#if 0
|
||||
/* NOTE: currently there is no special handling for fractional scaling.
|
||||
* This could be supported however the default behavior of generating a cursor
|
||||
* rounded up to the nearest integer scaling works fairly well. */
|
||||
int scale = 0;
|
||||
for (const GWL_Output *output : seat_state_pointer->outputs) {
|
||||
int output_scale_floor = output->scale;
|
||||
|
||||
/* It's important to round down in the case of fractional scale,
|
||||
* otherwise the cursor can be scaled down to be unusably small.
|
||||
* This is especially a problem when:
|
||||
* - The cursor theme has one size (24px for the default cursor).
|
||||
* - The fractional scaling is set just above 1 (typically 125%).
|
||||
*
|
||||
* In this case the `output->scale` is rounded up to 2 and a larger cursor is requested.
|
||||
* It's assumed a large cursor is available but that's not always the case.
|
||||
* When only a smaller cursor is available it's still assumed to be large,
|
||||
* fractional scaling causes the cursor to be scaled down making it ~10px. see #105895. */
|
||||
if (output_scale_floor > 1 && output->has_scale_fractional) {
|
||||
output_scale_floor = std::max(1, output->scale_fractional / FRACTIONAL_DENOMINATOR);
|
||||
}
|
||||
|
||||
scale = std::max(output_scale_floor, scale);
|
||||
scale = std::max(scale, output->scale);
|
||||
}
|
||||
if (scale > 0 && seat_state_pointer->buffer_scale != scale) {
|
||||
seat_state_pointer->buffer_scale = scale;
|
||||
}
|
||||
|
||||
if (scale > 0 && seat_state_pointer->theme_scale != scale) {
|
||||
seat_state_pointer->theme_scale = scale;
|
||||
if (!cursor.is_custom) {
|
||||
if (wl_surface_cursor) {
|
||||
wl_surface_set_buffer_scale(wl_surface_cursor, scale);
|
||||
scale = gwl_seat_cursor_buffer_scale_calc(seat);
|
||||
|
||||
if (scale > 0 && cursor.custom_cursor_scale != scale) {
|
||||
cursor.custom_cursor_scale = scale;
|
||||
if (cursor.is_custom) {
|
||||
wl_surface *wl_surface_focus = seat_state_pointer->wl.surface_window;
|
||||
if (wl_surface_focus) {
|
||||
GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
|
||||
if (win) {
|
||||
win->cursor_shape_refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
wl_cursor_theme_destroy(cursor.wl.theme);
|
||||
cursor.wl.theme = wl_cursor_theme_load(
|
||||
(cursor.theme_name.empty() ? nullptr : cursor.theme_name.c_str()),
|
||||
scale * cursor.theme_size,
|
||||
shm);
|
||||
if (cursor.wl.theme_cursor) {
|
||||
cursor.wl.theme_cursor = wl_cursor_theme_get_cursor(cursor.wl.theme,
|
||||
cursor.wl.theme_cursor_name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3756,7 +3758,7 @@ 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);
|
||||
update_cursor_scale(seat, seat->cursor, seat_state_pointer);
|
||||
}
|
||||
|
||||
static void cursor_surface_handle_leave(void *data, wl_surface *wl_surface, wl_output *wl_output)
|
||||
@@ -3772,7 +3774,7 @@ 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);
|
||||
update_cursor_scale(seat, seat->cursor, seat_state_pointer);
|
||||
}
|
||||
|
||||
static void cursor_surface_handle_preferred_buffer_scale(void * /*data*/,
|
||||
@@ -8614,11 +8616,89 @@ GHOST_TSuccess GHOST_SystemWayland::cursor_shape_check(const GHOST_TStandardCurs
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWayland::cursor_shape_custom_set(const uint8_t *bitmap,
|
||||
const uint8_t *mask,
|
||||
const int size[2],
|
||||
const int hot_spot[2],
|
||||
const bool /*canInvertColor*/)
|
||||
static wl_buffer *ghost_wl_buffer_from_cursor_generator(const GHOST_CursorGenerator &cg,
|
||||
wl_shm *shm,
|
||||
void **buffer_data_p,
|
||||
size_t *buffer_data_size_p,
|
||||
const int cursor_size,
|
||||
const int cursor_size_max,
|
||||
const int scale,
|
||||
int r_bitmap_size[2],
|
||||
int r_hot_spot[2])
|
||||
{
|
||||
if (*buffer_data_p) {
|
||||
munmap(*buffer_data_p, *buffer_data_size_p);
|
||||
*buffer_data_p = nullptr;
|
||||
*buffer_data_size_p = 0; /* Not needed, but the value is no longer meaningful. */
|
||||
}
|
||||
|
||||
int bitmap_size_src[2];
|
||||
int hot_spot[2];
|
||||
|
||||
uint8_t *bitmap_src = cg.generate_fn(
|
||||
&cg,
|
||||
cursor_size,
|
||||
cursor_size_max,
|
||||
[](size_t size) -> uint8_t * { return new uint8_t[size]; },
|
||||
bitmap_size_src,
|
||||
hot_spot);
|
||||
|
||||
if (bitmap_src == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* There is no need to adjust the hot-spot when resizing. */
|
||||
int bitmap_size_dst[2] = {
|
||||
int(round_up_uint(bitmap_size_src[0], scale)),
|
||||
int(round_up_uint(bitmap_size_src[1], scale)),
|
||||
};
|
||||
|
||||
wl_buffer *buffer = ghost_wl_buffer_create_for_image(
|
||||
shm, bitmap_size_dst, WL_SHM_FORMAT_ARGB8888, buffer_data_p, buffer_data_size_p);
|
||||
|
||||
if (buffer != nullptr) {
|
||||
const bool is_trivial_copy = (bitmap_size_src[0] == bitmap_size_dst[0]) &&
|
||||
(bitmap_size_src[1] == bitmap_size_dst[1]);
|
||||
/* NOTE: the copy could be skipped in trivial cases.
|
||||
* Since it's such a small amount of data it hardly seems worth it. */
|
||||
if (is_trivial_copy) {
|
||||
/* RGBA color, direct copy. */
|
||||
const uint32_t *px_src = reinterpret_cast<const uint32_t *>(bitmap_src);
|
||||
uint32_t *px_dst = static_cast<uint32_t *>(*buffer_data_p);
|
||||
for (int y = 0; y < bitmap_size_src[1]; y++) {
|
||||
for (int x = 0; x < bitmap_size_src[0]; x++) {
|
||||
*px_dst++ = *px_src++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* RGBA color, copy into an expanded buffer. */
|
||||
const uint32_t *px_src = reinterpret_cast<const uint32_t *>(bitmap_src);
|
||||
uint32_t *px_dst = static_cast<uint32_t *>(*buffer_data_p);
|
||||
for (int y = 0; y < bitmap_size_dst[1]; y++) {
|
||||
for (int x = 0; x < bitmap_size_dst[0]; x++) {
|
||||
if (x >= bitmap_size_src[0] || y >= bitmap_size_src[1]) {
|
||||
*px_dst++ = 0x0;
|
||||
}
|
||||
else {
|
||||
*px_dst++ = *px_src++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r_bitmap_size[0] = bitmap_size_dst[0];
|
||||
r_bitmap_size[1] = bitmap_size_dst[1];
|
||||
|
||||
r_hot_spot[0] = hot_spot[0];
|
||||
r_hot_spot[1] = hot_spot[1];
|
||||
}
|
||||
|
||||
delete[] bitmap_src;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWayland::cursor_shape_custom_set(const GHOST_CursorGenerator &cg)
|
||||
{
|
||||
/* Caller needs to lock `server_mutex`. */
|
||||
GWL_Seat *seat = gwl_display_seat_active_get(display_);
|
||||
@@ -8643,115 +8723,44 @@ GHOST_TSuccess GHOST_SystemWayland::cursor_shape_custom_set(const uint8_t *bitma
|
||||
}
|
||||
}
|
||||
|
||||
GWL_Cursor *cursor = &seat->cursor;
|
||||
if (cursor->custom_data) {
|
||||
munmap(cursor->custom_data, cursor->custom_data_size);
|
||||
cursor->custom_data = nullptr;
|
||||
cursor->custom_data_size = 0; /* Not needed, but the value is no longer meaningful. */
|
||||
}
|
||||
/* Generate the buffer. */
|
||||
GWL_Cursor &cursor = seat->cursor;
|
||||
|
||||
wl_buffer *buffer = ghost_wl_buffer_create_for_image(display_->wl.shm,
|
||||
size,
|
||||
WL_SHM_FORMAT_ARGB8888,
|
||||
&cursor->custom_data,
|
||||
&cursor->custom_data_size);
|
||||
if (buffer == nullptr) {
|
||||
int bitmap_size[2] = {0, 0};
|
||||
int hot_spot[2] = {0, 0};
|
||||
|
||||
const int scale = gwl_seat_cursor_buffer_scale_calc(seat);
|
||||
const int cursor_size = seat->cursor.theme_size * scale;
|
||||
/* NOTE: Regarding the maximum cursor size.
|
||||
* - 256 seems to be a hardware limit.
|
||||
* - Twice the cursor size to allow "text" cursor to display wider than a typical cursor
|
||||
* without being *very* large - as that looks strange.
|
||||
*/
|
||||
const int cursor_size_max = std::min(256, cursor_size * 2);
|
||||
|
||||
wl_buffer *buffer = ghost_wl_buffer_from_cursor_generator(cg,
|
||||
display_->wl.shm,
|
||||
&cursor.custom_data,
|
||||
&cursor.custom_data_size,
|
||||
cursor_size,
|
||||
cursor_size_max,
|
||||
scale,
|
||||
bitmap_size,
|
||||
hot_spot);
|
||||
if (UNLIKELY(buffer == nullptr)) {
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
wl_buffer_add_listener(buffer, &cursor_buffer_listener, cursor);
|
||||
wl_buffer_add_listener(buffer, &cursor_buffer_listener, &cursor);
|
||||
|
||||
if (mask) {
|
||||
/* Monochrome & mask (expand to RGBA). */
|
||||
static constexpr uint32_t black = 0xFF000000;
|
||||
static constexpr uint32_t white = 0xFFFFFFFF;
|
||||
static constexpr uint32_t transparent = 0x00000000;
|
||||
cursor.visible = true;
|
||||
cursor.is_custom = true;
|
||||
|
||||
uint8_t datab = 0, maskb = 0;
|
||||
uint32_t *px_dst = static_cast<uint32_t *>(cursor->custom_data);
|
||||
|
||||
for (int y = 0; y < size[1]; y++) {
|
||||
for (int x = 0; x < size[0]; x++) {
|
||||
if ((x % 8) == 0) {
|
||||
datab = *bitmap++;
|
||||
maskb = *mask++;
|
||||
|
||||
/* Reverse bit order. */
|
||||
datab = uint8_t((datab * 0x0202020202ULL & 0x010884422010ULL) % 1023);
|
||||
maskb = uint8_t((maskb * 0x0202020202ULL & 0x010884422010ULL) % 1023);
|
||||
}
|
||||
|
||||
if (maskb & 0x80) {
|
||||
*px_dst++ = (datab & 0x80) ? white : black;
|
||||
}
|
||||
else {
|
||||
*px_dst++ = (datab & 0x80) ? white : transparent;
|
||||
}
|
||||
datab <<= 1;
|
||||
maskb <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* RGBA color (direct copy). */
|
||||
const uint32_t *px_src = reinterpret_cast<const uint32_t *>(bitmap);
|
||||
uint32_t *px_dst = static_cast<uint32_t *>(cursor->custom_data);
|
||||
for (int y = 0; y < size[1]; y++) {
|
||||
for (int x = 0; x < size[0]; x++) {
|
||||
*px_dst++ = *px_src++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursor->visible = true;
|
||||
cursor->is_custom = true;
|
||||
|
||||
/* Calculate the cursor size to use based on the theme setting. */
|
||||
{
|
||||
|
||||
/* WARNING: Weak logic, if we can't use vector cursors - ideally the custom cursor
|
||||
* function would receive multiple sizes which WAYLAND could then switch between
|
||||
* as it does with themes. The following logic is fairly weak but works perfectly
|
||||
* when all outputs have the same scale.
|
||||
*
|
||||
* There is nothing preventing multiple sized cursors from being passed in,
|
||||
* it's just a matter of refactoring and adding support to WAYLAND. */
|
||||
|
||||
/* Get the lowest scale so in the case of mixed-scale-outputs,
|
||||
* the cursor will be too big on some of the outputs instead of too small.
|
||||
*
|
||||
* Note that getting the min/max scale for all outputs be made into an function
|
||||
* however it's bad practice because it means the cursor size will be wrong
|
||||
* when there are multiple outputs with different scale.
|
||||
* So this is not something to encouraged. */
|
||||
int output_scale = -1;
|
||||
for (const GWL_Output *output : display_->outputs) {
|
||||
output_scale = (output_scale == -1) ? output->scale : std::min(output_scale, output->scale);
|
||||
}
|
||||
if (output_scale == -1) {
|
||||
output_scale = 1;
|
||||
}
|
||||
|
||||
const int custom_size = std::max(size[0], size[1]);
|
||||
const int target_size = seat->cursor.theme_size * output_scale;
|
||||
|
||||
cursor->custom_scale = std::max(1, (output_scale * custom_size) / target_size);
|
||||
/* It would make more sense to adjust the buffer size instead of the scale.
|
||||
* In practice with custom cursors of 16x16, 24x24 & 32x32 its only likely to cause
|
||||
* problems with odd-scaling (HI-DPI scale of 300% or 500% for example).
|
||||
* In these cases the custom cursor will be a little too large. */
|
||||
while ((cursor->custom_scale > 1) &&
|
||||
!((size[0] % cursor->custom_scale) == 0 && (size[1] % cursor->custom_scale) == 0))
|
||||
{
|
||||
cursor->custom_scale -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
cursor->wl.buffer = buffer;
|
||||
cursor->wl.image.width = uint32_t(size[0]);
|
||||
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.buffer = buffer;
|
||||
cursor.wl.image.width = uint32_t(bitmap_size[0]);
|
||||
cursor.wl.image.height = uint32_t(bitmap_size[1]);
|
||||
cursor.wl.image.hotspot_x = uint32_t(hot_spot[0]);
|
||||
cursor.wl.image.hotspot_y = uint32_t(hot_spot[1]);
|
||||
|
||||
gwl_seat_cursor_buffer_set_current(seat);
|
||||
|
||||
@@ -9372,21 +9381,11 @@ 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,
|
||||
seat->system->wl_shm_get(),
|
||||
&seat->pointer,
|
||||
seat->cursor.wl.surface_cursor);
|
||||
update_cursor_scale(seat, seat->cursor, &seat->pointer);
|
||||
}
|
||||
|
||||
if (seat->tablet.outputs.count(output)) {
|
||||
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));
|
||||
update_cursor_scale(seat->cursor,
|
||||
seat->system->wl_shm_get(),
|
||||
&seat->tablet,
|
||||
tablet_tool->wl.surface_cursor);
|
||||
}
|
||||
update_cursor_scale(seat, seat->cursor, &seat->tablet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,11 +233,7 @@ class GHOST_SystemWayland : public GHOST_System {
|
||||
|
||||
GHOST_TSuccess cursor_shape_check(GHOST_TStandardCursor cursorShape);
|
||||
|
||||
GHOST_TSuccess cursor_shape_custom_set(const uint8_t *bitmap,
|
||||
const uint8_t *mask,
|
||||
const int size[2],
|
||||
const int hot_spot[2],
|
||||
bool canInvertColor);
|
||||
GHOST_TSuccess cursor_shape_custom_set(const GHOST_CursorGenerator &cg);
|
||||
|
||||
GHOST_TSuccess cursor_bitmap_get(GHOST_CursorBitmapRef *bitmap);
|
||||
|
||||
|
||||
@@ -616,7 +616,9 @@ GHOST_TCapabilityFlag GHOST_SystemWin32::getCapabilities() const
|
||||
GHOST_kCapabilityClipboardPrimary |
|
||||
/* WIN32 doesn't define a Hyper modifier key,
|
||||
* it's possible another modifier could be optionally used in it's place. */
|
||||
GHOST_kCapabilityKeyboardHyperKey));
|
||||
GHOST_kCapabilityKeyboardHyperKey |
|
||||
/* No support yet for cursors generated on demand. */
|
||||
GHOST_kCapabilityCursorGenerator));
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWin32::init()
|
||||
|
||||
@@ -1829,7 +1829,9 @@ GHOST_TCapabilityFlag GHOST_SystemX11::getCapabilities() const
|
||||
/* No support for window decoration styles. */
|
||||
GHOST_kCapabilityWindowDecorationStyles |
|
||||
/* No support yet for RGBA mouse cursors. */
|
||||
GHOST_kCapabilityCursorRGBA));
|
||||
GHOST_kCapabilityCursorRGBA |
|
||||
/* No support yet for dynamic cursor generation. */
|
||||
GHOST_kCapabilityCursorGenerator));
|
||||
}
|
||||
|
||||
void GHOST_SystemX11::addDirtyWindow(GHOST_WindowX11 *bad_wind)
|
||||
|
||||
@@ -247,6 +247,15 @@ GHOST_TSuccess GHOST_Window::setCustomCursorShape(const uint8_t *bitmap,
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_Window::setCustomCursorGenerator(GHOST_CursorGenerator *cursor_generator)
|
||||
{
|
||||
if (setWindowCustomCursorGenerator(cursor_generator)) {
|
||||
m_cursorShape = GHOST_kStandardCursorCustom;
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_Window::getCursorBitmap(GHOST_CursorBitmapRef * /*bitmap*/)
|
||||
{
|
||||
/* Sub-classes may override. */
|
||||
|
||||
@@ -119,6 +119,9 @@ class GHOST_Window : public GHOST_IWindow {
|
||||
const int hot_spot[2],
|
||||
bool canInvertColor) override;
|
||||
|
||||
/** \copydoc #GHOST_IWindow::setCustomCursorGenerator */
|
||||
GHOST_TSuccess setCustomCursorGenerator(GHOST_CursorGenerator *cursor_generator) override;
|
||||
|
||||
GHOST_TSuccess getCursorBitmap(GHOST_CursorBitmapRef *bitmap) override;
|
||||
|
||||
/** \copydoc #GHOST_IWindow::getCursorVisibility */
|
||||
@@ -292,6 +295,12 @@ class GHOST_Window : public GHOST_IWindow {
|
||||
const int size[2],
|
||||
const int hot_size[2],
|
||||
bool canInvertColor) = 0;
|
||||
/** \copydoc #GHOST_IWindow::setWindowCustomCursorGenerator */
|
||||
virtual GHOST_TSuccess setWindowCustomCursorGenerator(GHOST_CursorGenerator *cursor_generator)
|
||||
{
|
||||
cursor_generator->free_fn(cursor_generator);
|
||||
return GHOST_kFailure;
|
||||
};
|
||||
|
||||
GHOST_TSuccess releaseNativeHandles();
|
||||
|
||||
|
||||
@@ -281,79 +281,28 @@ 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)
|
||||
static void gwl_window_cursor_custom_free(GHOST_CursorGenerator *cg)
|
||||
{
|
||||
if (ccs.bitmap) {
|
||||
free(ccs.bitmap);
|
||||
}
|
||||
if (ccs.mask) {
|
||||
free(ccs.mask);
|
||||
}
|
||||
cg->free_fn(cg);
|
||||
}
|
||||
|
||||
static void gwl_window_cursor_custom_clear(GWL_WindowCursorCustomShape &ccs)
|
||||
static void gwl_window_cursor_custom_clear(GHOST_CursorGenerator **cg)
|
||||
{
|
||||
gwl_window_cursor_custom_free(ccs);
|
||||
ccs = GWL_WindowCursorCustomShape{};
|
||||
if (*cg == nullptr) {
|
||||
return;
|
||||
}
|
||||
gwl_window_cursor_custom_free(*cg);
|
||||
*cg = nullptr;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (mask) {
|
||||
/* Monochrome bitmap (with mask). */
|
||||
/* 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);
|
||||
}
|
||||
ccs.mask = static_cast<uint8_t *>(malloc(bitmap_size));
|
||||
memcpy(ccs.mask, mask, bitmap_size);
|
||||
}
|
||||
else {
|
||||
/* RGBA bitmap (mask is alpha). */
|
||||
const size_t bitmap_size = sizeof(uint32_t) * size[0] * size[1];
|
||||
if (bitmap) {
|
||||
ccs.bitmap = static_cast<uint8_t *>(malloc(bitmap_size));
|
||||
memcpy(ccs.bitmap, bitmap, bitmap_size);
|
||||
}
|
||||
ccs.mask = nullptr;
|
||||
}
|
||||
|
||||
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(const GWL_WindowCursorCustomShape &ccs,
|
||||
static GHOST_TSuccess gwl_window_cursor_custom_load(const GHOST_CursorGenerator &cg,
|
||||
GHOST_SystemWayland *system)
|
||||
{
|
||||
return system->cursor_shape_custom_set(
|
||||
ccs.bitmap, ccs.mask, ccs.size, ccs.hot_spot, ccs.can_invert_color);
|
||||
return system->cursor_shape_custom_set(cg);
|
||||
}
|
||||
|
||||
static GHOST_TSuccess gwl_window_cursor_shape_refresh(GHOST_TStandardCursor shape,
|
||||
const GWL_WindowCursorCustomShape &ccs,
|
||||
const GHOST_CursorGenerator *cg,
|
||||
GHOST_SystemWayland *system)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
@@ -361,7 +310,7 @@ static GHOST_TSuccess gwl_window_cursor_shape_refresh(GHOST_TStandardCursor shap
|
||||
#endif
|
||||
|
||||
if (shape == GHOST_kStandardCursorCustom) {
|
||||
const GHOST_TSuccess ok = gwl_window_cursor_custom_load(ccs, system);
|
||||
const GHOST_TSuccess ok = gwl_window_cursor_custom_load(*cg, system);
|
||||
if (ok == GHOST_kSuccess) {
|
||||
return ok;
|
||||
}
|
||||
@@ -510,7 +459,7 @@ struct GWL_Window {
|
||||
std::mutex frame_pending_mutex;
|
||||
#endif
|
||||
|
||||
GWL_WindowCursorCustomShape cursor_custom_shape;
|
||||
GHOST_CursorGenerator *cursor_generator = nullptr;
|
||||
|
||||
std::string title;
|
||||
|
||||
@@ -989,7 +938,7 @@ static void gwl_window_pending_actions_handle(GWL_Window *win)
|
||||
}
|
||||
if (actions[PENDING_WINDOW_CURSOR_SHAPE_REFRESH]) {
|
||||
gwl_window_cursor_shape_refresh(
|
||||
win->ghost_window->getCursorShape(), win->cursor_custom_shape, win->ghost_system);
|
||||
win->ghost_window->getCursorShape(), win->cursor_generator, win->ghost_system);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2200,7 +2149,9 @@ 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);
|
||||
if (window_->cursor_generator) {
|
||||
gwl_window_cursor_custom_free(window_->cursor_generator);
|
||||
}
|
||||
|
||||
delete window_;
|
||||
}
|
||||
@@ -2254,7 +2205,7 @@ GHOST_TSuccess GHOST_WindowWayland::setWindowCursorShape(GHOST_TStandardCursor s
|
||||
|
||||
const bool is_active = this == static_cast<const GHOST_WindowWayland *>(
|
||||
system_->getWindowManager()->getActiveWindow());
|
||||
gwl_window_cursor_custom_clear(window_->cursor_custom_shape);
|
||||
gwl_window_cursor_custom_clear(&window_->cursor_generator);
|
||||
m_cursorShape = shape;
|
||||
|
||||
GHOST_TSuccess ok;
|
||||
@@ -2298,46 +2249,48 @@ bool GHOST_WindowWayland::getCursorGrabUseSoftwareDisplay()
|
||||
return system_->cursor_grab_use_software_display_get(m_cursorGrab);
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setWindowCustomCursorGenerator(
|
||||
GHOST_CursorGenerator *cursor_generator)
|
||||
{
|
||||
/* Before this, all logic is just setting up the cursor. */
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*system_->server_mutex};
|
||||
#endif
|
||||
m_cursorShape = GHOST_kStandardCursorCustom;
|
||||
if (window_->cursor_generator) {
|
||||
gwl_window_cursor_custom_free(window_->cursor_generator);
|
||||
}
|
||||
window_->cursor_generator = cursor_generator;
|
||||
|
||||
GHOST_TSuccess success = cursor_shape_refresh();
|
||||
|
||||
/* Let refresh handle applying the changes. */
|
||||
if (success == GHOST_kSuccess) {
|
||||
wl_display *display = system_->wl_display_get();
|
||||
/* For the cursor to display when the event queue isn't being handled. */
|
||||
wl_display_flush(display);
|
||||
#ifdef USE_CURSOR_IMMEDIATE_DISPATCH
|
||||
wl_display_dispatch_pending(display);
|
||||
#endif
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setWindowCustomCursorShape(const uint8_t *bitmap,
|
||||
const uint8_t *mask,
|
||||
const int size[2],
|
||||
const int hot_spot[2],
|
||||
const bool canInvertColor)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*system_->server_mutex};
|
||||
#endif
|
||||
/* This is no longer needed as all cursors are generated on demand. */
|
||||
GHOST_ASSERT(false, "All cursors must be generated!");
|
||||
(void)bitmap;
|
||||
(void)mask;
|
||||
(void)size;
|
||||
(void)hot_spot;
|
||||
(void)canInvertColor;
|
||||
|
||||
const bool is_active = this == static_cast<const GHOST_WindowWayland *>(
|
||||
system_->getWindowManager()->getActiveWindow());
|
||||
|
||||
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) {
|
||||
wl_display *display = system_->wl_display_get();
|
||||
/* For the cursor to display when the event queue isn't being handled. */
|
||||
wl_display_flush(display);
|
||||
#ifdef USE_CURSOR_IMMEDIATE_DISPATCH
|
||||
wl_display_dispatch_pending(display);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Set later when activating the window. */
|
||||
ok = GHOST_kSuccess;
|
||||
}
|
||||
return ok;
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitmap)
|
||||
@@ -2714,7 +2667,7 @@ GHOST_TSuccess GHOST_WindowWayland::cursor_shape_refresh()
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
#endif
|
||||
return gwl_window_cursor_shape_refresh(m_cursorShape, window_->cursor_custom_shape, system_);
|
||||
return gwl_window_cursor_shape_refresh(m_cursorShape, window_->cursor_generator, system_);
|
||||
}
|
||||
|
||||
void GHOST_WindowWayland::outputs_changed_update_scale_tag()
|
||||
|
||||
@@ -95,6 +95,8 @@ class GHOST_WindowWayland : public GHOST_Window {
|
||||
|
||||
GHOST_TSuccess setWindowCursorShape(GHOST_TStandardCursor shape) override;
|
||||
|
||||
GHOST_TSuccess setWindowCustomCursorGenerator(GHOST_CursorGenerator *cursor_generator) override;
|
||||
|
||||
GHOST_TSuccess setWindowCustomCursorShape(const uint8_t *bitmap,
|
||||
const uint8_t *mask,
|
||||
const int size[2],
|
||||
|
||||
@@ -203,6 +203,8 @@ enum eWM_CapabilitiesFlag {
|
||||
WM_CAPABILITY_KEYBOARD_HYPER_KEY = (1 << 9),
|
||||
/** Support for RGBA Cursors. */
|
||||
WM_CAPABILITY_CURSOR_RGBA = (1 << 10),
|
||||
/** Support on demand cursor generation. */
|
||||
WM_CAPABILITY_CURSOR_GENERATOR = (1 << 11),
|
||||
/** The initial value, indicates the value needs to be set by inspecting GHOST. */
|
||||
WM_CAPABILITY_INITIALIZED = (1u << 31),
|
||||
};
|
||||
|
||||
@@ -278,6 +278,47 @@ static void cursor_rgba_to_xbm_32(const uint8_t *rgba,
|
||||
}
|
||||
}
|
||||
|
||||
static bool window_set_custom_cursor_generator(wmWindow *win, const BCursor &cursor)
|
||||
{
|
||||
GHOST_CursorGenerator *cursor_generator = MEM_callocN<GHOST_CursorGenerator>(__func__);
|
||||
cursor_generator->generate_fn = [](const GHOST_CursorGenerator *cursor_generator,
|
||||
const int cursor_size,
|
||||
const int cursor_size_max,
|
||||
uint8_t *(*alloc_fn)(size_t size),
|
||||
int r_bitmap_size[2],
|
||||
int r_hot_spot[2]) -> uint8_t * {
|
||||
const BCursor &cursor = *(const BCursor *)(cursor_generator->user_data);
|
||||
/* Currently SVG uses the `cursor_size` as the maximum. */
|
||||
UNUSED_VARS(cursor_size_max);
|
||||
|
||||
int bitmap_size[2];
|
||||
uint8_t *bitmap_rgba = cursor_bitmap_from_svg(
|
||||
cursor.svg_source, cursor_size, alloc_fn, bitmap_size);
|
||||
|
||||
if (UNLIKELY(bitmap_rgba == nullptr)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
r_bitmap_size[0] = bitmap_size[0];
|
||||
r_bitmap_size[1] = bitmap_size[1];
|
||||
|
||||
r_hot_spot[0] = int(cursor.hotspot[0] * (bitmap_size[0] - 1));
|
||||
r_hot_spot[1] = int(cursor.hotspot[1] * (bitmap_size[1] - 1));
|
||||
|
||||
return bitmap_rgba;
|
||||
};
|
||||
|
||||
cursor_generator->user_data = (void *)&cursor;
|
||||
cursor_generator->free_fn = [](GHOST_CursorGenerator *cursor_generator) {
|
||||
MEM_freeN(cursor_generator);
|
||||
};
|
||||
|
||||
GHOST_TSuccess success = GHOST_SetCustomCursorGenerator(
|
||||
static_cast<GHOST_WindowHandle>(win->ghostwin), cursor_generator);
|
||||
|
||||
return (success == GHOST_kSuccess) ? true : false;
|
||||
}
|
||||
|
||||
static bool window_set_custom_cursor_pixmap(wmWindow *win, const BCursor &cursor)
|
||||
{
|
||||
/* Option to force use of 1bpp XBitMap cursors is needed for testing. */
|
||||
@@ -333,7 +374,9 @@ static bool window_set_custom_cursor_pixmap(wmWindow *win, const BCursor &cursor
|
||||
|
||||
static bool window_set_custom_cursor(wmWindow *win, const BCursor &cursor)
|
||||
{
|
||||
/* Keep this wrapper until other types are supported, see: !141597. */
|
||||
if (WM_capabilities_flag() & WM_CAPABILITY_CURSOR_GENERATOR) {
|
||||
return window_set_custom_cursor_generator(win, cursor);
|
||||
}
|
||||
return window_set_custom_cursor_pixmap(win, cursor);
|
||||
}
|
||||
|
||||
@@ -749,6 +792,60 @@ static uint8_t *cursor_bitmap_from_text(const std::string &text,
|
||||
return bitmap_rgba;
|
||||
}
|
||||
|
||||
static bool wm_cursor_text_generator(wmWindow *win, const std::string &text, int font_id)
|
||||
{
|
||||
struct WMCursorText {
|
||||
std::string text;
|
||||
int font_id;
|
||||
};
|
||||
|
||||
GHOST_CursorGenerator *cursor_generator = MEM_callocN<GHOST_CursorGenerator>(__func__);
|
||||
cursor_generator->generate_fn = [](const GHOST_CursorGenerator *cursor_generator,
|
||||
const int cursor_size,
|
||||
const int cursor_size_max,
|
||||
uint8_t *(*alloc_fn)(size_t size),
|
||||
int r_bitmap_size[2],
|
||||
int r_hot_spot[2]) -> uint8_t * {
|
||||
const WMCursorText &cursor_text = *(const WMCursorText *)(cursor_generator->user_data);
|
||||
|
||||
int bitmap_size[2];
|
||||
uint8_t *bitmap_rgba = cursor_bitmap_from_text(cursor_text.text,
|
||||
cursor_size,
|
||||
cursor_size_max,
|
||||
cursor_text.font_id,
|
||||
alloc_fn,
|
||||
bitmap_size);
|
||||
|
||||
if (UNLIKELY(bitmap_rgba == nullptr)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
r_bitmap_size[0] = bitmap_size[0];
|
||||
r_bitmap_size[1] = bitmap_size[1];
|
||||
|
||||
r_hot_spot[0] = bitmap_size[0] / 2;
|
||||
r_hot_spot[1] = bitmap_size[1] / 2;
|
||||
|
||||
return bitmap_rgba;
|
||||
};
|
||||
|
||||
WMCursorText *cursor_text = MEM_new<WMCursorText>(__func__);
|
||||
cursor_text->text = text;
|
||||
cursor_text->font_id = font_id;
|
||||
|
||||
cursor_generator->user_data = (void *)cursor_text;
|
||||
cursor_generator->free_fn = [](GHOST_CursorGenerator *cursor_generator) {
|
||||
const WMCursorText *cursor_text = (WMCursorText *)(cursor_generator->user_data);
|
||||
MEM_delete(cursor_text);
|
||||
MEM_freeN(cursor_generator);
|
||||
};
|
||||
|
||||
GHOST_TSuccess success = GHOST_SetCustomCursorGenerator(
|
||||
static_cast<GHOST_WindowHandle>(win->ghostwin), cursor_generator);
|
||||
|
||||
return (success == GHOST_kSuccess) ? true : false;
|
||||
}
|
||||
|
||||
static bool wm_cursor_text_pixmap(wmWindow *win, const std::string &text, int font_id)
|
||||
{
|
||||
const int cursor_size = wm_cursor_size(win);
|
||||
@@ -788,7 +885,9 @@ static bool wm_cursor_text_pixmap(wmWindow *win, const std::string &text, int fo
|
||||
|
||||
static bool wm_cursor_text(wmWindow *win, const std::string &text, int font_id)
|
||||
{
|
||||
/* Keep this wrapper until other types are supported, see: !141597. */
|
||||
if (WM_capabilities_flag() & WM_CAPABILITY_CURSOR_GENERATOR) {
|
||||
return wm_cursor_text_generator(win, text, font_id);
|
||||
}
|
||||
return wm_cursor_text_pixmap(win, text, font_id);
|
||||
}
|
||||
|
||||
|
||||
@@ -2235,6 +2235,9 @@ eWM_CapabilitiesFlag WM_capabilities_flag()
|
||||
if (ghost_flag & GHOST_kCapabilityCursorRGBA) {
|
||||
flag |= WM_CAPABILITY_CURSOR_RGBA;
|
||||
}
|
||||
if (ghost_flag & GHOST_kCapabilityCursorGenerator) {
|
||||
flag |= WM_CAPABILITY_CURSOR_GENERATOR;
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user