Compare commits

...

11 Commits

Author SHA1 Message Date
Matthias Clasen
2346bb4087 wip: Dynamic cursor api 2024-03-20 18:38:21 -04:00
Matthias Clasen
0f9ed94a85 Add a test app
This app has a custom animated cursor that is made from a
paintable. The cursor also uses a 96x96 image for a 32s32
cursor, so it can remain sharp on scaled displays.
2024-03-20 12:34:25 -04:00
Matthias Clasen
11ddd5ed40 wayland: Support animated paintable cursors
If the paintable content isn't static, don't cache the cursor
image, and connect to ::invalidate-contents to update the cursor.
2024-03-20 12:34:25 -04:00
Matthias Clasen
160b8a0f7e wayland: Use a viewport for pointer surfaces
This should give us more flexibility for buffer size vs surface
size. Unfortunately, mutter doesn't play along, so for now the
use of a viewporter for pointer surfaces is gated behind the
POINTER_USE_VIEWPORT environment variable.
2024-03-20 12:34:25 -04:00
Matthias Clasen
ce8cf6866b wayland: Keep pointer_output_scale as double
We can round up to the next integer when we need to.
2024-03-20 12:34:25 -04:00
Matthias Clasen
e5fb0dd3d2 macos: Handle paintable cursors 2024-03-20 12:34:25 -04:00
Matthias Clasen
c2cae1d0fb win32: Handle paintable cursors 2024-03-20 12:34:25 -04:00
Matthias Clasen
d1c7d5eac7 x11: Handle paintable cursors
We don't do any scale handling here, we use the paintable at its
intrinsic size.
2024-03-20 12:34:25 -04:00
Matthias Clasen
24e3ad54a7 wayland: Handle paintable cursors
For now, we just create a texture of the scaled size, and return
it. This works, but does not give us crip cursors on hi-dpi. For
that, we need use a viewporter.
2024-03-20 12:34:25 -04:00
Matthias Clasen
7c357e7da5 cursor: Add private convenience api
Add function that either returns the texture, or converts
the paintable to a texture at its intrinsic size, and
returns that.

This allows for quick fallback implementations that minimally
handle the paintable cursor case.
2024-03-20 12:34:25 -04:00
Matthias Clasen
667e2fc73a Add GdkCursor:paintable
Allow creating GdkCursor objects that use a GdkPaintable for
their pixel data.
2024-03-20 12:34:25 -04:00
14 changed files with 697 additions and 37 deletions

View File

@@ -30,11 +30,14 @@
#include "gdkcursor.h"
#include "gdkcursorprivate.h"
#include "gdktexture.h"
#include "gdkdisplay.h"
#include <glib/gi18n-lib.h>
#include <math.h>
#include <errno.h>
#include <graphene.h>
/**
* GdkCursor:
*
@@ -81,8 +84,11 @@ enum {
PROP_HOTSPOT_Y,
PROP_NAME,
PROP_TEXTURE,
PROP_PAINTABLE,
};
static guint invalidate_signal;
G_DEFINE_TYPE (GdkCursor, gdk_cursor, G_TYPE_OBJECT)
static void
@@ -110,6 +116,9 @@ gdk_cursor_get_property (GObject *object,
case PROP_TEXTURE:
g_value_set_object (value, cursor->texture);
break;
case PROP_PAINTABLE:
g_value_set_object (value, cursor->paintable);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -141,6 +150,9 @@ gdk_cursor_set_property (GObject *object,
case PROP_TEXTURE:
cursor->texture = g_value_dup_object (value);
break;
case PROP_PAINTABLE:
cursor->paintable = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -155,6 +167,10 @@ gdk_cursor_finalize (GObject *object)
g_free (cursor->name);
g_clear_object (&cursor->texture);
g_clear_object (&cursor->fallback);
g_clear_object (&cursor->paintable);
if (cursor->destroy)
cursor->destroy (cursor->data);
G_OBJECT_CLASS (gdk_cursor_parent_class)->finalize (object);
}
@@ -231,6 +247,37 @@ gdk_cursor_class_init (GdkCursorClass *cursor_class)
GDK_TYPE_TEXTURE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* GdkCursor:paintable:
*
* The paintable displayed by this cursor.
*
* The intrinsic size of the paintable determines
* the size of the cursor.
*
* If the GDK backend has the capability, non-static paintables
* may create animated cursors.
*
* The paintable will be %NULL if the cursor was created
* from a name or texture.
*
* Since: 4.16
*/
g_object_class_install_property (object_class,
PROP_PAINTABLE,
g_param_spec_object ("paintable", NULL, NULL,
GDK_TYPE_PAINTABLE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
invalidate_signal = g_signal_new ("invalidate",
GDK_TYPE_CURSOR,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
}
static void
@@ -253,6 +300,8 @@ gdk_cursor_hash (gconstpointer pointer)
hash ^= g_str_hash (cursor->name);
else if (cursor->texture)
hash ^= g_direct_hash (cursor->texture);
else if (cursor->paintable)
hash ^= g_direct_hash (cursor->paintable);
hash ^= (cursor->hotspot_x << 8) | cursor->hotspot_y;
@@ -277,6 +326,9 @@ gdk_cursor_equal (gconstpointer a,
if (ca->texture != cb->texture)
return FALSE;
if (ca->paintable != cb->paintable)
return FALSE;
if (ca->hotspot_x != cb->hotspot_x ||
ca->hotspot_y != cb->hotspot_y)
return FALSE;
@@ -355,6 +407,45 @@ gdk_cursor_new_from_texture (GdkTexture *texture,
NULL);
}
/**
* gdk_cursor_new_from_paintable:
* @paintable: the paintable providing the pixel data
* @hotspot_x: the horizontal offset of the “hotspot” of the cursor
* @hotspot_y: the vertical offset of the “hotspot” of the cursor
* @fallback: (nullable): the `GdkCursor` to fall back to when
* this one cannot be supported
*
* Creates a new cursor from a `GdkPaintable`.
*
* The intrinsic size of the paintable determines
* the size of the cursor.
*
* If the GDK backend has the capability, non-static paintables
* may create animated cursors.
*
* Returns: a new `GdkCursor`
*
* Since: 4.16
*/
GdkCursor *
gdk_cursor_new_from_paintable (GdkPaintable *paintable,
int hotspot_x,
int hotspot_y,
GdkCursor *fallback)
{
g_return_val_if_fail (GDK_IS_PAINTABLE (paintable), NULL);
g_return_val_if_fail (0 <= hotspot_x && hotspot_x < gdk_paintable_get_intrinsic_width (paintable), NULL);
g_return_val_if_fail (0 <= hotspot_y && hotspot_y < gdk_paintable_get_intrinsic_height (paintable), NULL);
g_return_val_if_fail (fallback == NULL || GDK_IS_CURSOR (fallback), NULL);
return g_object_new (GDK_TYPE_CURSOR,
"paintable", paintable,
"hotspot-x", hotspot_x,
"hotspot-y", hotspot_y,
"fallback", fallback,
NULL);
}
/**
* gdk_cursor_get_fallback: (attributes org.gtk.Method.get_property=fallback)
* @cursor: a `GdkCursor`
@@ -416,6 +507,27 @@ gdk_cursor_get_texture (GdkCursor *cursor)
return cursor->texture;
}
/**
* gdk_cursor_get_paintable:
* @cursor: a `GdkCursor`
*
* Returns the paintable for the cursor.
*
* If the cursor is a named or texture cursor, %NULL will be returned.
*
* Returns: (transfer none) (nullable): the paintable for @cursor
* or %NULL if it is a named or texture cursor
*
* Since: 4.16
*/
GdkPaintable *
gdk_cursor_get_paintable (GdkCursor *cursor)
{
g_return_val_if_fail (GDK_IS_CURSOR (cursor), NULL);
return cursor->paintable;
}
/**
* gdk_cursor_get_hotspot_x: (attributes org.gtk.Method.get_property=hotspot-x)
* @cursor: a `GdkCursor`
@@ -459,3 +571,220 @@ gdk_cursor_get_hotspot_y (GdkCursor *cursor)
return cursor->hotspot_y;
}
/* Hack. We don't include gsk/gsk.h here to avoid a build order problem
* with the generated header gskenumtypes.h, so we need to hack around
* a bit to access the gsk and gtk api we need.
*/
typedef struct _GskRenderer GskRenderer;
typedef struct _GskRenderNode GskRenderNode;
typedef struct _GdkSnapshot GtkSnapshot;
extern GskRenderer * gsk_cairo_renderer_new (void);
extern gboolean gsk_renderer_realize_for_display (GskRenderer *renderer,
GdkDisplay *display,
GError **error);
extern void gsk_renderer_unrealize (GskRenderer *renderer);
extern GdkTexture * gsk_renderer_render_texture (GskRenderer *renderer,
GskRenderNode *root,
const graphene_rect_t *viewport);
extern void gsk_render_node_get_bounds (GskRenderNode *node,
graphene_rect_t *bounds);
extern void gsk_render_node_unref (GskRenderNode *node);
extern GtkSnapshot * gtk_snapshot_new (void);
extern GskRenderNode * gtk_snapshot_free_to_node (GtkSnapshot *snapshot);
static GdkTexture *
paintable_to_texture (GdkPaintable *paintable,
double scale)
{
GtkSnapshot *snapshot;
GskRenderNode *node;
GskRenderer *renderer;
int width, height;
GdkTexture *texture;
graphene_rect_t bounds;
width = gdk_paintable_get_intrinsic_width (paintable);
height = gdk_paintable_get_intrinsic_height (paintable);
snapshot = gtk_snapshot_new ();
gdk_paintable_snapshot (paintable, snapshot, width * scale, height * scale);
node = gtk_snapshot_free_to_node (snapshot);
/* FIXME: use a proper renderer, and cache it */
renderer = gsk_cairo_renderer_new ();
gsk_renderer_realize_for_display (renderer, gdk_display_get_default (), NULL);
gsk_render_node_get_bounds (node, &bounds);
texture = gsk_renderer_render_texture (renderer, node, &bounds);
gsk_renderer_unrealize (renderer);
g_object_unref (renderer);
gsk_render_node_unref (node);
return texture;
}
GdkTexture *
gdk_cursor_create_texture (GdkCursor *cursor,
double scale)
{
if (cursor->texture)
return g_object_ref (cursor->texture);
else if (cursor->paintable)
return paintable_to_texture (cursor->paintable, scale);
else
return NULL;
}
/**
* GdkCursorGetTestureCallback:
* @cursor: the `GdkCursor`
* @cursor_size: the nominal cursor size, in application pixels
* @scale: the device scale
* @width: (out): return location for the actual cursor width,
* in application pixels
* @height: (out): return location for the actual cursor height,
* in application pixels
* @hotspot_x: (out): return location for the hotspot X position,
* in application pixels
* @hotspot_y: (out): return location for the hotspot Y position,
* in application pixels
* @data: User data for the callback
*
* The type of callback used by a dynamic `GdkCursor` to generate
* a texture for the cursor image at the given @cursor_size
* and @scale.
*
* The actual cursor size in application pixels may be different
* from @cursor_size x @cursor_size, and will be returned in
* @width, @height.
*
* The returned texture should have a size that corresponds to
* the actual cursor size, in device pixels.
*
* This function may fail and return `NULL`, in which case
* the backend should use the fallback cursor.
*
* Returns: (nullable) (transfer full): the cursor image, or
* `NULL` if none could be produced.
*/
/**
* gdk_cursor_new_dynamic:
* @callback: the `GdkCursorGetTextureCallback`
* @data: data to pass to @callback
* @destroy: destroy notify for @data
* @fallback: (nullable): the `GdkCursor` to fall back to when
* this one cannot be supported
*
* Creates a new dynamic cursor object.
*
* Dynamic cursors produce textures for the cursor image
* on demand, when the @callback is called.
*
* Returns: (nullable): a new `GdkCursor`
*
* Since: 4.16
*/
GdkCursor *
gdk_cursor_new_dynamic (GdkCursorGetTextureCallback callback,
gpointer data,
GDestroyNotify destroy,
GdkCursor *fallback)
{
GdkCursor *cursor;
g_return_val_if_fail (callback != NULL, NULL);
g_return_val_if_fail (fallback == NULL || GDK_IS_CURSOR (fallback), NULL);
cursor = g_object_new (GDK_TYPE_CURSOR,
"fallback", fallback,
NULL);
cursor->callback = callback;
cursor->data = data;
cursor->destroy = destroy;
return cursor;
}
/**
* gdk_cursor_invalidate:
* @cursor: a `GdkCursor`
*
* Emits the invalidate signal to indicate that the cursor
* has changed and backends should recreate their cursor
* image.
*
* This function does nothing unless the cursor has been
* created with [constructor@Gdk.Cursor.new_dynamic].
*
* Since: 4.16
*/
void
gdk_cursor_invalidate (GdkCursor *cursor)
{
if (cursor->callback == NULL)
return;
g_signal_emit (cursor, invalidate_signal, 0);
}
/*< private >
* gdk_cursor_get_dynamic_texture:
* @cursor: a dynamic `GdkCursor`
* @cursor_size: the nominal size of the cursor image
* to produce, in application pixels
* @scale: the device scale to use
* @width: return location for the cursor width,
* in application pixels
* @height : return location for the cursor height,
* in application pixels
* @hotspot_x: return location for the hotspot X position,
* in application pixels
* @hotspot_y: return location for the hotspot X position,
* in application pixels
*
* Call the callback of a dynamic cursor to generate
* a texture for the cursor image at the given @cursor_size
* and @scale.
*
* The actual cursor size in application pixels may be different
* from @cursor_size x @cursor_size, and will be returned in
* @width, @height.
*
* The returned texture should have a size that corresponds to
* the actual cursor size, in device pixels.
*
* This function may fail and return `NULL`, in which case
* the backend should use the fallback cursor.
*
* Returns: (nullable) (transfer full): the cursor image, or
* `NULL` if none could be produced.
*/
GdkTexture *
gdk_cursor_get_dynamic_texture (GdkCursor *cursor,
int cursor_size,
double scale,
int *width,
int *height,
int *hotspot_x,
int *hotspot_y)
{
if (cursor->callback == NULL)
return NULL;
return cursor->callback (cursor,
cursor_size, scale,
width, height,
hotspot_x, hotspot_y,
cursor->data);
}

View File

@@ -29,6 +29,7 @@
#endif
#include <gdk/gdktypes.h>
#include <gdk/gdkpaintable.h>
G_BEGIN_DECLS
@@ -47,6 +48,13 @@ GdkCursor* gdk_cursor_new_from_texture (GdkTexture *texture,
int hotspot_x,
int hotspot_y,
GdkCursor *fallback);
GDK_AVAILABLE_IN_4_14
GdkCursor * gdk_cursor_new_from_paintable (GdkPaintable *paintable,
int hotspot_x,
int hotspot_y,
GdkCursor *fallback);
GDK_AVAILABLE_IN_ALL
GdkCursor* gdk_cursor_new_from_name (const char *name,
GdkCursor *fallback);
@@ -57,11 +65,32 @@ GDK_AVAILABLE_IN_ALL
const char *gdk_cursor_get_name (GdkCursor *cursor);
GDK_AVAILABLE_IN_ALL
GdkTexture *gdk_cursor_get_texture (GdkCursor *cursor);
GDK_AVAILABLE_IN_4_14
GdkPaintable *
gdk_cursor_get_paintable (GdkCursor *cursor);
GDK_AVAILABLE_IN_ALL
int gdk_cursor_get_hotspot_x (GdkCursor *cursor);
GDK_AVAILABLE_IN_ALL
int gdk_cursor_get_hotspot_y (GdkCursor *cursor);
typedef GdkTexture * (* GdkCursorGetTextureCallback) (GdkCursor *cursor,
int cursor_size,
double scale,
int *width,
int *height,
int *hotspot_x,
int *hotspot_y,
gpointer data);
GDK_AVAILABLE_IN_4_14
GdkCursor * gdk_cursor_new_dynamic (GdkCursorGetTextureCallback callback,
gpointer data,
GDestroyNotify destroy,
GdkCursor *fallback);
GDK_AVAILABLE_IN_4_14
void gdk_cursor_invalidate (GdkCursor *cursor);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkCursor, g_object_unref)
G_END_DECLS

View File

@@ -42,8 +42,13 @@ struct _GdkCursor
GdkCursor *fallback;
char *name;
GdkTexture *texture;
GdkPaintable *paintable;
int hotspot_x;
int hotspot_y;
GdkCursorGetTextureCallback callback;
gpointer data;
GDestroyNotify destroy;
};
struct _GdkCursorClass
@@ -55,5 +60,16 @@ guint gdk_cursor_hash (gconstpointer
gboolean gdk_cursor_equal (gconstpointer a,
gconstpointer b);
GdkTexture * gdk_cursor_create_texture (GdkCursor *cursor,
double scale);
GdkTexture * gdk_cursor_get_dynamic_texture (GdkCursor *cursor,
int cursor_size,
double scale,
int *width,
int *height,
int *hotspot_x,
int *hotspot_y);
G_END_DECLS

View File

@@ -217,9 +217,11 @@ _gdk_macos_cursor_get_ns_cursor (GdkCursor *cursor)
if (name == NULL)
{
nscursor = create_cursor_from_texture (gdk_cursor_get_texture (cursor),
GdkTexture *texture = gdk_cursor_create_texture (cursor, 1);
nscursor = create_cursor_from_texture (texture,
gdk_cursor_get_hotspot_x (cursor),
gdk_cursor_get_hotspot_y (cursor));
g_object_unref (texture);
return nscursor;
}
}

View File

@@ -35,6 +35,10 @@
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "cursor/wayland-cursor.h"
#include "gtk/gtksnapshot.h"
#include "gsk/gskrenderer.h"
#include "gsk/gskrendernodeprivate.h"
static void
gdk_wayland_cursor_remove_from_cache (gpointer data, GObject *cursor)
{
@@ -154,15 +158,20 @@ static const struct wl_buffer_listener buffer_listener = {
struct wl_buffer *
_gdk_wayland_cursor_get_buffer (GdkWaylandDisplay *display,
GdkCursor *cursor,
guint desired_scale,
double desired_scale,
gboolean use_viewporter,
guint image_index,
int *hotspot_x,
int *hotspot_y,
int *width,
int *height,
int *scale)
double *scale)
{
GdkTexture *texture;
int desired_scale_factor;
gboolean can_cache = TRUE;
desired_scale_factor = (int) ceil (desired_scale);
if (gdk_cursor_get_name (cursor))
{
@@ -173,7 +182,7 @@ _gdk_wayland_cursor_get_buffer (GdkWaylandDisplay *display,
c = gdk_wayland_cursor_load_for_name (display,
_gdk_wayland_display_get_cursor_theme (display),
desired_scale,
desired_scale_factor,
gdk_cursor_get_name (cursor));
if (c && c->image_count > 0)
{
@@ -190,7 +199,7 @@ _gdk_wayland_cursor_get_buffer (GdkWaylandDisplay *display,
image = c->images[image_index];
cursor_scale = desired_scale;
cursor_scale = desired_scale_factor;
if ((image->width % cursor_scale != 0) ||
(image->height % cursor_scale != 0))
{
@@ -210,36 +219,44 @@ _gdk_wayland_cursor_get_buffer (GdkWaylandDisplay *display,
return wl_cursor_image_get_buffer (image);
}
}
else
else if (gdk_cursor_get_texture (cursor))
{
cairo_surface_t *surface;
cairo_surface_t *surface = NULL;
struct wl_buffer *buffer;
texture = g_object_ref (gdk_cursor_get_texture (cursor));
from_texture:
surface = g_hash_table_lookup (display->cursor_surface_cache, cursor);
*width = gdk_texture_get_width (texture);
*height = gdk_texture_get_height (texture);
*scale = 1;
from_texture2:
*hotspot_x = gdk_cursor_get_hotspot_x (cursor);
*hotspot_y = gdk_cursor_get_hotspot_y (cursor);
if (can_cache)
surface = g_hash_table_lookup (display->cursor_surface_cache, cursor);
if (surface == NULL)
{
surface = gdk_wayland_display_create_shm_surface (display,
gdk_texture_get_width (texture),
gdk_texture_get_height (texture),
&GDK_FRACTIONAL_SCALE_INIT_INT (1));
gdk_texture_download (texture,
cairo_image_surface_get_data (surface),
cairo_image_surface_get_stride (surface));
cairo_surface_mark_dirty (surface);
g_object_weak_ref (G_OBJECT (cursor), gdk_wayland_cursor_remove_from_cache, display);
g_hash_table_insert (display->cursor_surface_cache, cursor, surface);
}
if (can_cache)
{
g_object_weak_ref (G_OBJECT (cursor), gdk_wayland_cursor_remove_from_cache, display);
*hotspot_x = gdk_cursor_get_hotspot_x (cursor);
*hotspot_y = gdk_cursor_get_hotspot_y (cursor);
*width = gdk_texture_get_width (texture);
*height = gdk_texture_get_height (texture);
*scale = 1;
g_hash_table_insert (display->cursor_surface_cache, cursor, surface);
}
}
cairo_surface_reference (surface);
buffer = _gdk_wayland_shm_surface_get_wl_buffer (surface);
@@ -249,11 +266,33 @@ from_texture:
return buffer;
}
else
{
GdkPaintable *paintable;
paintable = gdk_cursor_get_paintable (cursor);
*width = gdk_paintable_get_intrinsic_width (paintable);
*height = gdk_paintable_get_intrinsic_height (paintable);
if (!use_viewporter)
*scale = desired_scale_factor;
else
*scale = desired_scale;
texture = gdk_cursor_create_texture (cursor, *scale);
can_cache = (gdk_paintable_get_flags (paintable) & GDK_PAINTABLE_STATIC_CONTENTS) != 0;
can_cache = (gdk_paintable_get_flags (paintable) & GDK_PAINTABLE_STATIC_CONTENTS) != 0;
goto from_texture2;
}
if (gdk_cursor_get_fallback (cursor))
return _gdk_wayland_cursor_get_buffer (display,
gdk_cursor_get_fallback (cursor),
gdk_cursor_get_fallback (cursor),
desired_scale,
use_viewporter,
image_index,
hotspot_x, hotspot_y,
width, height,

View File

@@ -62,14 +62,16 @@ struct _GdkWaylandPointerData {
uint32_t grab_time;
struct wl_surface *pointer_surface;
struct wp_viewport *pointer_surface_viewport;
guint cursor_is_default: 1;
GdkCursor *cursor;
guint cursor_timeout_id;
guint cursor_image_index;
guint cursor_image_delay;
gulong cursor_invalidated_handler;
guint touchpad_event_sequence;
guint current_output_scale;
double current_output_scale;
GSList *pointer_surface_outputs;
/* Accumulated event data for a pointer frame */

View File

@@ -58,11 +58,12 @@ gdk_wayland_device_set_surface_cursor (GdkDevice *device,
{
if (!pointer->cursor_is_default)
{
gdk_wayland_seat_stop_cursor_animation (seat, pointer);
g_clear_object (&pointer->cursor);
pointer->cursor = gdk_cursor_new_from_name ("default", NULL);
pointer->cursor_is_default = TRUE;
gdk_wayland_seat_stop_cursor_animation (seat, pointer);
gdk_wayland_device_update_surface_cursor (device);
}
else
@@ -72,10 +73,11 @@ gdk_wayland_device_set_surface_cursor (GdkDevice *device,
}
else
{
gdk_wayland_seat_stop_cursor_animation (seat, pointer);
g_set_object (&pointer->cursor, cursor);
pointer->cursor_is_default = FALSE;
gdk_wayland_seat_stop_cursor_animation (seat, pointer);
gdk_wayland_device_update_surface_cursor (device);
}
}
@@ -260,7 +262,8 @@ gdk_wayland_device_update_surface_cursor (GdkDevice *device)
GdkWaylandPointerData *pointer =
gdk_wayland_device_get_pointer (wayland_device);
struct wl_buffer *buffer;
int x, y, w, h, scale;
int x, y, w, h;
double scale;
guint next_image_index, next_image_delay;
gboolean retval = G_SOURCE_REMOVE;
GdkWaylandTabletData *tablet;
@@ -272,6 +275,7 @@ gdk_wayland_device_update_surface_cursor (GdkDevice *device)
buffer = _gdk_wayland_cursor_get_buffer (GDK_WAYLAND_DISPLAY (seat->display),
pointer->cursor,
pointer->current_output_scale,
pointer->pointer_surface_viewport != NULL,
pointer->cursor_image_index,
&x, &y, &w, &h, &scale);
}
@@ -310,7 +314,16 @@ gdk_wayland_device_update_surface_cursor (GdkDevice *device)
if (buffer)
{
wl_surface_attach (pointer->pointer_surface, buffer, 0, 0);
if (wl_surface_get_version (pointer->pointer_surface) >= WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION)
if (pointer->pointer_surface_viewport)
{
wp_viewport_set_source (pointer->pointer_surface_viewport,
wl_fixed_from_int (0),
wl_fixed_from_int (0),
wl_fixed_from_double (w * scale),
wl_fixed_from_double (h * scale));
wp_viewport_set_destination (pointer->pointer_surface_viewport, w, h);
}
else if (wl_surface_get_version (pointer->pointer_surface) >= WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION)
wl_surface_set_buffer_scale (pointer->pointer_surface, scale);
wl_surface_damage (pointer->pointer_surface, 0, 0, w, h);
wl_surface_commit (pointer->pointer_surface);
@@ -321,6 +334,23 @@ gdk_wayland_device_update_surface_cursor (GdkDevice *device)
wl_surface_commit (pointer->pointer_surface);
}
GdkPaintable *paintable = gdk_cursor_get_paintable (pointer->cursor);
if (paintable)
{
if ((gdk_paintable_get_flags (paintable) & GDK_PAINTABLE_STATIC_CONTENTS) == 0)
{
if (pointer->cursor_invalidated_handler == 0)
{
pointer->cursor_invalidated_handler =
g_signal_connect_swapped (paintable, "invalidate-contents",
G_CALLBACK (gdk_wayland_device_update_surface_cursor), device);
}
}
return G_SOURCE_REMOVE;
}
next_image_index =
_gdk_wayland_cursor_get_next_image_index (GDK_WAYLAND_DISPLAY (seat->display),
pointer->cursor,

View File

@@ -115,13 +115,14 @@ void gdk_wayland_display_system_bell (GdkDisplay *display,
struct wl_buffer *_gdk_wayland_cursor_get_buffer (GdkWaylandDisplay *display,
GdkCursor *cursor,
guint desired_scale,
double desired_scale,
gboolean use_viewporter,
guint image_index,
int *hotspot_x,
int *hotspot_y,
int *w,
int *h,
int *scale);
double *scale);
guint _gdk_wayland_cursor_get_next_image_index (GdkWaylandDisplay *display,
GdkCursor *cursor,
guint scale,

View File

@@ -117,6 +117,15 @@ gdk_wayland_seat_stop_cursor_animation (GdkWaylandSeat *seat,
}
pointer->cursor_image_index = 0;
if (pointer->cursor_invalidated_handler != 0)
{
GdkPaintable *paintable;
paintable = gdk_cursor_get_paintable (pointer->cursor);
g_signal_handler_disconnect (paintable, pointer->cursor_invalidated_handler);
pointer->cursor_invalidated_handler = 0;
}
}
GdkWaylandTabletData *
@@ -3766,8 +3775,7 @@ pointer_surface_update_scale (GdkDevice *device)
GdkWaylandDevice *wayland_device = GDK_WAYLAND_DEVICE (device);
GdkWaylandPointerData *pointer =
gdk_wayland_device_get_pointer (wayland_device);
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (seat->display);
guint32 scale;
double scale;
GSList *l;
if (wl_surface_get_version (pointer->pointer_surface) < WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION)
@@ -3782,8 +3790,8 @@ pointer_surface_update_scale (GdkDevice *device)
scale = 1;
for (l = pointer->pointer_surface_outputs; l != NULL; l = l->next)
{
guint32 output_scale = gdk_wayland_display_get_output_scale (display_wayland, l->data);
scale = MAX (scale, output_scale);
GdkMonitor *monitor = gdk_wayland_display_get_monitor_for_output (seat->display, l->data);
scale = MAX (scale, gdk_monitor_get_scale (monitor));
}
if (pointer->current_output_scale == scale)
@@ -3878,6 +3886,7 @@ gdk_wayland_pointer_data_finalize (GdkWaylandPointerData *pointer)
g_clear_object (&pointer->cursor);
wl_surface_destroy (pointer->pointer_surface);
g_slist_free (pointer->pointer_surface_outputs);
g_clear_pointer (&pointer->pointer_surface_viewport, wp_viewport_destroy);
}
static void
@@ -4241,6 +4250,9 @@ init_pointer_data (GdkWaylandPointerData *pointer_data,
wl_surface_add_listener (pointer_data->pointer_surface,
&pointer_surface_listener,
logical_device);
if (display_wayland->viewporter && g_getenv ("POINTER_USE_VIEWPORT"))
pointer_data->pointer_surface_viewport = wp_viewporter_get_viewport (display_wayland->viewporter, pointer_data->pointer_surface);
}
void

View File

@@ -1498,10 +1498,14 @@ gdk_win32_display_get_win32hcursor (GdkWin32Display *display,
if (cursor_name)
win32hcursor = gdk_win32hcursor_create_for_name (display, cursor_name);
else
win32hcursor = gdk_win32hcursor_create_for_texture (display,
gdk_cursor_get_texture (cursor),
gdk_cursor_get_hotspot_x (cursor),
gdk_cursor_get_hotspot_y (cursor));
{
GdkTexture *texture = gdk_cursor_create_texture (cursor, 1);
win32hcursor = gdk_win32hcursor_create_for_texture (display,
texture,
gdk_cursor_get_hotspot_x (cursor),
gdk_cursor_get_hotspot_y (cursor));
g_object_unref (texture);
}
if (win32hcursor != NULL)
{

View File

@@ -43,6 +43,10 @@
#endif
#include <string.h>
#include "gtk/gtksnapshot.h"
#include "gsk/gskrenderer.h"
#include "gsk/gskrendernodeprivate.h"
static void
gdk_x11_cursor_remove_from_cache (gpointer data, GObject *cursor)
{
@@ -371,10 +375,15 @@ gdk_x11_display_get_xcursor (GdkDisplay *display,
if (gdk_cursor_get_name (cursor))
xcursor = gdk_x11_cursor_create_for_name (display, gdk_cursor_get_name (cursor));
else
xcursor = gdk_x11_cursor_create_for_texture (display,
gdk_cursor_get_texture (cursor),
gdk_cursor_get_hotspot_x (cursor),
gdk_cursor_get_hotspot_y (cursor));
{
GdkTexture *texture = gdk_cursor_create_texture (cursor, 1);
xcursor = gdk_x11_cursor_create_for_texture (display,
texture,
gdk_cursor_get_hotspot_x (cursor),
gdk_cursor_get_hotspot_y (cursor));
g_object_unref (texture);
}
if (xcursor != None)
{

BIN
tests/all-scroll.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -1,5 +1,6 @@
gtk_tests = [
# testname, optional extra sources
['testpaintablecursor'],
['devicepixels'],
['testsections'],
['testfilelauncher'],

186
tests/testpaintablecursor.c Normal file
View File

@@ -0,0 +1,186 @@
/* testpaintablecursor.c
* Copyright (C) 2024 Red Hat, Inc.
* Author: Matthias Clasen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE (MyPaintable, my_paintable, MY, PAINTABLE, GObject)
struct _MyPaintable
{
GObject parent_instance;
GdkTexture *texture;
int width;
int height;
double angle;
guint timeout_id;
};
struct _MyPaintableClass
{
GObjectClass parent_class;
};
static void
my_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
MyPaintable *self = MY_PAINTABLE (paintable);
gtk_snapshot_push_opacity (GTK_SNAPSHOT (snapshot), 0.5 * sin (self->angle) + 0.5);
gtk_snapshot_append_texture (GTK_SNAPSHOT (snapshot), self->texture,
&GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (GTK_SNAPSHOT (snapshot));
}
static int
my_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
MyPaintable *self = MY_PAINTABLE (paintable);
return self->width;
}
static int
my_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
MyPaintable *self = MY_PAINTABLE (paintable);
return self->height;
}
static void
my_paintable_init_interface (GdkPaintableInterface *iface)
{
iface->snapshot = my_paintable_snapshot;
iface->get_intrinsic_width = my_paintable_get_intrinsic_width;
iface->get_intrinsic_height = my_paintable_get_intrinsic_height;
}
G_DEFINE_TYPE_WITH_CODE (MyPaintable, my_paintable, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
my_paintable_init_interface))
static void
my_paintable_init (MyPaintable *self)
{
}
static void
my_paintable_dispose (GObject *object)
{
MyPaintable *self = MY_PAINTABLE (object);
g_clear_object (&self->texture);
g_source_remove (self->timeout_id);
self->timeout_id = 0;
G_OBJECT_CLASS (my_paintable_parent_class)->dispose (object);
}
static void
my_paintable_class_init (MyPaintableClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->dispose = my_paintable_dispose;
}
static gboolean
rotate_cb (gpointer data)
{
MyPaintable *self = data;
self->angle += 20;
g_signal_emit_by_name (self, "invalidate-contents", 0);
return G_SOURCE_CONTINUE;
}
static GdkPaintable *
my_paintable_new (GdkTexture *texture, int width, int height)
{
MyPaintable *self;
self = g_object_new (my_paintable_get_type (), NULL);
self->texture = g_object_ref (texture);
self->width = width;
self->height = height;
self->angle = 0;
self->timeout_id = g_timeout_add (100, rotate_cb, self);
return GDK_PAINTABLE (self);
}
int
main (int argc, char *argv[])
{
GtkWidget *window, *button;
gboolean done = FALSE;
GdkTexture *texture;
GdkCursor *cursor;
GdkPaintable *paintable;
gtk_init ();
window = gtk_window_new ();
gtk_window_set_title (GTK_WINDOW (window), "hello world");
gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
button = gtk_button_new ();
gtk_button_set_label (GTK_BUTTON (button), "hello world");
gtk_widget_set_margin_top (button, 10);
gtk_widget_set_margin_bottom (button, 10);
gtk_widget_set_margin_start (button, 10);
gtk_widget_set_margin_end (button, 10);
/* A 96x96 image */
texture = gdk_texture_new_from_filename ("tests/all-scroll.png", NULL);
#if 0
cursor = gdk_cursor_new_from_name ("all-scroll", NULL);
#elif 0
cursor = gdk_cursor_new_from_texture (texture, gdk_texture_ge t_width (texture) / 2, gdk_texture_get_height (texture) / 2, NULL);
#else
/* We create a cursor of size 32x32 */
paintable = my_paintable_new (texture, 32, 32);
cursor = gdk_cursor_new_from_paintable (paintable, 16, 16, NULL);
g_object_unref (paintable);
#endif
g_object_unref (texture);
gtk_widget_set_cursor (button, cursor);
g_object_unref (cursor);
gtk_window_set_child (GTK_WINDOW (window), button);
gtk_window_present (GTK_WINDOW (window));
while (!done)
g_main_context_iteration (NULL, TRUE);
return 0;
}