[PATCH] cursor: add wl_cursor_manager and xcursor-configuration-unstable-v1

Simon Ser contact at emersion.fr
Mon Oct 22 10:58:40 UTC 2018


The xcursor-configuration protocol allows compositors to send
xcursor theme configuration to clients.

Support for this protocol has been added to libwayland-cursor via
a new wl_cursor_manager API.

Signed-off-by: Simon Ser <contact at emersion.fr>
---

This patch implements the new libwayland-cursor API. A simple client
proof-of-concept [1] and a wlroots compositor implementation [2] are
available.

Known issues:
1. This design requires to call wl_display_dispatch in libwayland-cursor,
   is this acceptable?
2. This design doesn't support live theme updates. Toolkits can always
   roll their own implementation of the protocol if they want to support
   it. Is this an issue?
3. To avoid circular dependencies to wayland-protocols, the protocol file
   has been copied over. Is there a better solution?

I'm also not sure about code style, especially when there are very long
function names.

Comments and ideas welcome!

[1]: https://github.com/emersion/hello-wayland/tree/xcursor-configuration
[2]: https://github.com/swaywm/wlroots/pull/1324

 Makefile.am                                   |   7 +-
 cursor/wayland-cursor.c                       | 187 +++++++++++++++++-
 cursor/wayland-cursor.h                       |  21 ++
 .../xcursor-configuration-unstable-v1.xml     | 115 +++++++++++
 4 files changed, 328 insertions(+), 2 deletions(-)
 create mode 100644 protocol/xcursor-configuration-unstable-v1.xml

diff --git a/Makefile.am b/Makefile.am
index 697c517..1624c75 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -117,7 +117,9 @@ BUILT_SOURCES =					\
 	$(nodist_libwayland_server_la_SOURCES)	\
 	$(nodist_libwayland_client_la_SOURCES)	\
 	$(nodist_headers_test_SOURCES)		\
-	$(nodist_display_test_SOURCES)
+	$(nodist_display_test_SOURCES)		\
+	protocol/xcursor-configuration-unstable-v1-client-protocol.h \
+	protocol/xcursor-configuration-unstable-v1-protocol.c
 
 CLEANFILES = $(BUILT_SOURCES) doc/doxygen/doxygen_sqlite3.db
 DISTCLEANFILES = src/wayland-version.h
@@ -136,6 +138,9 @@ libwayland_cursor_la_SOURCES =			\
 	cursor/cursor-data.h			\
 	cursor/xcursor.c			\
 	cursor/xcursor.h
+nodist_libwayland_cursor_la_SOURCES =		\
+	protocol/xcursor-configuration-unstable-v1-client-protocol.h \
+	protocol/xcursor-configuration-unstable-v1-protocol.c
 libwayland_cursor_la_LIBADD = libwayland-client.la
 
 pkgconfig_DATA += cursor/wayland-cursor.pc
diff --git a/cursor/wayland-cursor.c b/cursor/wayland-cursor.c
index d40c5c8..bdd8464 100644
--- a/cursor/wayland-cursor.c
+++ b/cursor/wayland-cursor.c
@@ -27,6 +27,7 @@
 #include "xcursor.h"
 #include "wayland-cursor.h"
 #include "wayland-client.h"
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
@@ -37,6 +38,7 @@
 #include <errno.h>
 
 #include "os-compatibility.h"
+#include "xcursor-configuration-unstable-v1-client-protocol.h"
 
 #define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
 
@@ -137,6 +139,8 @@ struct wl_cursor_theme {
 	struct shm_pool *pool;
 	char *name;
 	int size;
+
+	char *default_cursor;
 };
 
 struct cursor_image {
@@ -403,6 +407,7 @@ wl_cursor_theme_load(const char *name, int size, struct wl_shm *shm)
 	theme->size = size;
 	theme->cursor_count = 0;
 	theme->cursors = NULL;
+	theme->default_cursor = NULL;
 
 	theme->pool = shm_pool_create(shm, size * size * 4);
 	if (!theme->pool)
@@ -436,6 +441,7 @@ wl_cursor_theme_destroy(struct wl_cursor_theme *theme)
 
 	shm_pool_destroy(theme->pool);
 
+	free(theme->default_cursor);
 	free(theme->name);
 	free(theme->cursors);
 	free(theme);
@@ -444,7 +450,7 @@ wl_cursor_theme_destroy(struct wl_cursor_theme *theme)
 /** Get the cursor for a given name from a cursor theme
  *
  * \param theme The cursor theme
- * \param name Name of the desired cursor
+ * \param name Name of the desired cursor or %NULL for the default one
  * \return The theme's cursor of the given name or %NULL if there is no
  * such cursor
  */
@@ -454,6 +460,11 @@ wl_cursor_theme_get_cursor(struct wl_cursor_theme *theme,
 {
 	unsigned int i;
 
+	if (!name)
+		name = theme->default_cursor;
+	if (!name)
+		name = "left_ptr";
+
 	for (i = 0; i < theme->cursor_count; i++) {
 		if (strcmp(name, theme->cursors[i]->name) == 0)
 			return theme->cursors[i];
@@ -527,3 +538,177 @@ wl_cursor_frame(struct wl_cursor *_cursor, uint32_t time)
 {
 	return wl_cursor_frame_and_duration(_cursor, time, NULL);
 }
+
+struct wl_cursor_manager {
+	struct wl_display *display;
+	struct wl_registry *registry;
+	struct wl_shm *shm;
+	struct zwp_xcursor_configuration_manager_v1 *configuration_manager;
+};
+
+static void
+manager_handle_global(void *data, struct wl_registry *registry, uint32_t name,
+		      const char *interface, uint32_t version)
+{
+	struct wl_cursor_manager *manager = data;
+
+	if (strcmp(interface, wl_shm_interface.name) == 0) {
+		manager->shm = wl_registry_bind(registry, name,
+					        &wl_shm_interface, 1);
+	} else if (strcmp(interface,
+		   zwp_xcursor_configuration_manager_v1_interface.name) == 0) {
+		manager->configuration_manager = wl_registry_bind(registry,
+			name, &zwp_xcursor_configuration_manager_v1_interface,
+			1);
+	}
+}
+
+static void
+manager_handle_global_remove(void *data, struct wl_registry *registry,
+			     uint32_t name)
+{
+	/* No-op */
+}
+
+static const struct wl_registry_listener manager_registry_listener = {
+	.global = manager_handle_global,
+	.global_remove = manager_handle_global_remove,
+};
+
+WL_EXPORT struct wl_cursor_manager *
+wl_cursor_manager_create(struct wl_display *display)
+{
+	struct wl_cursor_manager *manager;
+
+	manager = calloc(1, sizeof(*manager));
+	if (!manager)
+		return NULL;
+
+	manager->display = display;
+
+	manager->registry = wl_display_get_registry(display);
+	if (!manager->registry) {
+		free(manager);
+		return NULL;
+	}
+
+	wl_registry_add_listener(manager->registry, &manager_registry_listener,
+				 manager);
+	wl_display_dispatch(display);
+	wl_display_roundtrip(display);
+
+	if (!manager->shm) {
+		wl_registry_destroy(manager->registry);
+		free(manager);
+		return NULL;
+	}
+
+	return manager;
+}
+
+WL_EXPORT void
+wl_cursor_manager_destroy(struct wl_cursor_manager *manager)
+{
+	if (manager->configuration_manager) {
+		zwp_xcursor_configuration_manager_v1_destroy(
+			manager->configuration_manager);
+	}
+	wl_shm_destroy(manager->shm);
+	wl_registry_destroy(manager->registry);
+	free(manager);
+}
+
+struct configuration_attributes {
+	char *theme_name;
+	uint32_t theme_size;
+	char *default_cursor;
+	bool done;
+};
+
+static void
+manager_configuration_handle_done(void *data,
+				  struct zwp_xcursor_configuration_v1 *config)
+{
+	struct configuration_attributes *attrs = data;
+
+	attrs->done = true;
+}
+
+static void
+manager_configuration_handle_theme(void *data,
+				  struct zwp_xcursor_configuration_v1 *config,
+				  const char *name, uint32_t size)
+{
+	struct configuration_attributes *attrs = data;
+
+	attrs->theme_name = strdup(name);
+	attrs->theme_size = size;
+}
+
+static void
+manager_configuration_handle_default_cursor(void *data,
+					    struct zwp_xcursor_configuration_v1 *config,
+					    const char *name)
+{
+	struct configuration_attributes *attrs = data;
+
+	attrs->default_cursor = strdup(name);
+}
+
+static const struct zwp_xcursor_configuration_v1_listener manager_configuration_listener = {
+	.done = manager_configuration_handle_done,
+	.theme = manager_configuration_handle_theme,
+	.default_cursor = manager_configuration_handle_default_cursor,
+};
+
+WL_EXPORT struct wl_cursor_theme *
+wl_cursor_manager_get_theme(struct wl_cursor_manager *manager,
+			    struct wl_seat *seat,
+			    enum wl_cursor_device_type device,
+			    int32_t scale)
+{
+	struct configuration_attributes attrs;
+	struct zwp_xcursor_configuration_v1 *config;
+	struct wl_cursor_theme *theme;
+	enum zwp_xcursor_configuration_manager_v1_device_type config_device;
+
+	attrs.theme_name = NULL;
+	attrs.theme_size = 32;
+	attrs.default_cursor = NULL;
+	attrs.done = false;
+
+	if (manager->configuration_manager) {
+		switch (device) {
+		case WL_CURSOR_DEVICE_TYPE_POINTER:
+			config_device = ZWP_XCURSOR_CONFIGURATION_MANAGER_V1_DEVICE_TYPE_POINTER;
+			break;
+		case WL_CURSOR_DEVICE_TYPE_TABLET_TOOL:
+			config_device = ZWP_XCURSOR_CONFIGURATION_MANAGER_V1_DEVICE_TYPE_TABLET_TOOL;
+			break;
+		}
+
+		config = zwp_xcursor_configuration_manager_v1_get_device_xcursor_configuration(
+			manager->configuration_manager, seat, config_device);
+		zwp_xcursor_configuration_v1_add_listener(config,
+			&manager_configuration_listener, &attrs);
+
+		while (!attrs.done && wl_display_dispatch(manager->display) != -1) {
+			/* Wait for the done event */
+		}
+
+		zwp_xcursor_configuration_v1_destroy(config);
+	}
+
+	theme = wl_cursor_theme_load(attrs.theme_name, scale * attrs.theme_size,
+				     manager->shm);
+	if (!theme)
+		goto out;
+
+	if (attrs.default_cursor)
+		theme->default_cursor = strdup(attrs.default_cursor);
+
+out:
+	free(attrs.theme_name);
+	free(attrs.default_cursor);
+	return theme;
+}
diff --git a/cursor/wayland-cursor.h b/cursor/wayland-cursor.h
index 40d3fc5..dfe2e19 100644
--- a/cursor/wayland-cursor.h
+++ b/cursor/wayland-cursor.h
@@ -70,6 +70,27 @@ int
 wl_cursor_frame_and_duration(struct wl_cursor *cursor, uint32_t time,
 			     uint32_t *duration);
 
+struct wl_cursor_manager;
+struct wl_display;
+struct wl_seat;
+
+enum wl_cursor_device_type {
+	WL_CURSOR_DEVICE_TYPE_POINTER,
+	WL_CURSOR_DEVICE_TYPE_TABLET_TOOL,
+};
+
+struct wl_cursor_manager *
+wl_cursor_manager_create(struct wl_display *display);
+
+void
+wl_cursor_manager_destroy(struct wl_cursor_manager *manager);
+
+struct wl_cursor_theme *
+wl_cursor_manager_get_theme(struct wl_cursor_manager *manager,
+			    struct wl_seat *seat,
+			    enum wl_cursor_device_type device,
+			    int32_t scale);
+
 #ifdef  __cplusplus
 }
 #endif
diff --git a/protocol/xcursor-configuration-unstable-v1.xml b/protocol/xcursor-configuration-unstable-v1.xml
new file mode 100644
index 0000000..b479c6f
--- /dev/null
+++ b/protocol/xcursor-configuration-unstable-v1.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wp_xcursor_configuration_unstable_v1">
+  <copyright>
+    Copyright © 2018 Simon Ser
+
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice (including the next
+    paragraph) shall be included in all copies or substantial portions of the
+    Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
+  </copyright>
+
+  <description summary="protocol to configure XCursor themes">
+    This protocol allows compositors to advertize XCursor configuration to
+    clients.
+
+    Once compositor configuration is received, clients are responsible for
+    loading the XCursor theme, creating wl_buffers with cursor images and
+    setting the cursor. Clients are free to ignore configuration set by this
+    protocol and use different settings: this protocol merely exposes hints.
+
+    Warning! The protocol described in this file is experimental and
+    backward incompatible changes may be made. Backward compatible changes
+    may be added together with the corresponding interface version bump.
+    Backward incompatible changes are done by bumping the version number in
+    the protocol and interface names and resetting the interface version.
+    Once the protocol is to be declared stable, the 'z' prefix and the
+    version number in the protocol and interface names are removed and the
+    interface version number is reset.
+  </description>
+
+  <interface name="zwp_xcursor_configuration_manager_v1" version="1">
+    <description summary="XCursor configuration manager">
+      A global factory interface for wp_xcursor_configuration objects.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the XCursor configuration manager object">
+        Destroy the XCursor configuration manager. This doesn't destroy objects
+        created with the manager.
+      </description>
+    </request>
+
+    <enum name="device_type">
+      <description summary="an input device type">
+        Describes input devices that have a cursor and are attached to a seat.
+      </description>
+      <entry name="pointer" value="1" summary="Pointer"/>
+      <entry name="tablet_tool" value="2" summary="Tablet tool"/>
+    </enum>
+
+    <request name="get_device_xcursor_configuration">
+      <description summary="create a wp_xcursor_configuration object for a device">
+        This creates a new wp_xcursor_configuration object for the input device
+        attached to the given seat.
+      </description>
+      <arg name="id" type="new_id" interface="zwp_xcursor_configuration_v1"/>
+      <arg name="seat" type="object" interface="wl_seat"/>
+      <arg name="device" type="uint" enum="device_type"/>
+    </request>
+  </interface>
+
+  <interface name="zwp_xcursor_configuration_v1" version="1">
+    <description summary="XCursor configuration for a device">
+      A Xcursor configuration object describes XCursor settings for a specific
+      device.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy this object">
+        Using this request a client can tell the server that it is not going to
+        use this object anymore.
+      </description>
+    </request>
+
+    <event name="done">
+      <description summary="all information about the configuration has been sent">
+        This event is sent after all other properties of a
+        wp_xcursor_configuration have been sent.
+
+        This allows changes to the wp_xcursor_configuration properties to be
+        seen as atomic, even if they happen via multiple events.
+      </description>
+    </event>
+
+    <event name="theme">
+      <description summary="theme configuration">
+        The theme event describes XCursor theme configuration for this device.
+      </description>
+      <arg name="name" type="string" summary="theme name"/>
+      <arg name="size" type="uint" summary="theme size"/>
+    </event>
+
+    <event name="default_cursor">
+      <description summary="default cursor name">
+        The default_cursor event describes the default XCursor cursor name to be
+        used for this device.
+      </description>
+      <arg name="name" type="string" summary="default cursor name"/>
+    </event>
+  </interface>
+</protocol>
-- 
2.19.1




More information about the wayland-devel mailing list