1 Cursors
dxtr edited this page 2026-10-14 03:53:18 +02:00

Cursors

A client sets the pointer's cursor itself, after each wl_pointer :enter, with the enter's serial: until it does, the cursor over its surfaces is undefined. There are two ways to give it one.

By name: cursor-shape-v1

Where the compositor offers wp_cursor_shape_manager_v1, the client names the cursor and the compositor draws it, in the user's theme and at the surface's scale:

(asdf:load-system :cl-wl/cursor-shape)

(let ((manager (wl:bind display 'cl-wl/cursor-shape:wp-cursor-shape-manager-v1 :errorp nil)))
  (when manager
    (let ((device (cl-wl/cursor-shape:wp-cursor-shape-manager-v1-get-pointer manager pointer)))
      ;; In the pointer's :enter handler, with its serial:
      (cl-wl/cursor-shape:wp-cursor-shape-device-v1-set-shape device serial :text))))

The names are CSS's, as keywords: :default, :text, :pointer, :wait, :progress, :crosshair, :grab, :grabbing, :move, :not-allowed, :ew-resize, :ns-resize, :nwse-resize, :nesw-resize and the others the type wp-cursor-shape-device-v1-shape lists. Make one device per pointer and keep it. See Protocol Bindings.

From a cursor theme: cl-wl/cursor

Elsewhere, the client attaches the cursor's image to a surface of its own and gives that to wl_pointer.set_cursor. cl-wl/cursor loads the images from the user's cursor theme through libwayland-cursor, into shared memory of a wl_shm:

(asdf:load-system :cl-wl/cursor)

(cl-wl/cursor:with-cursor-theme (theme shm)            ; the user's theme and size
  (let* ((cursor (or (cl-wl/cursor:find-cursor theme "text")
                     (cl-wl/cursor:find-cursor theme "xterm")))
         (image (svref (cl-wl/cursor:cursor-images cursor) 0))
         (surface (wl-compositor-create-surface compositor)))
    (wl-surface-attach surface (cl-wl/cursor:cursor-image-buffer image) 0 0)
    (wl-surface-commit surface)
    (wl-pointer-set-cursor pointer serial surface
                           (cl-wl/cursor:cursor-image-hotspot-x image)
                           (cl-wl/cursor:cursor-image-hotspot-y image))
    …))

Themes

(cl-wl/cursor:load-cursor-theme shm &key name size)
  • name is the theme, by default the user's, from $XCURSOR_THEME as other toolkits read it (default-cursor-theme-name), or the theme named "default" when that is not set.
  • size is the cursor's size in pixels, by default the user's, from $XCURSOR_SIZE (default-cursor-size), or 24.
  • For a surface of buffer scale 2, load the theme at twice the size, and set the cursor surface's buffer scale to 2.

Themes are searched for where Xcursor searches, or in $XCURSOR_PATH. A theme that is not installed falls back to the theme named "default", then to a few cursors built into libwayland-cursor ("default", "text", "pointer", "wait" and some more), so some cursors are always found.

with-cursor-theme loads a theme around its body and destroys it on exit. cursor-theme-name and cursor-theme-size describe a theme, and cursor-theme-live-p says whether it has been destroyed.

Cursors and images

(find-cursor theme name) returns the theme's cursor of that name, or nil. Themes name their cursors as CSS does, such as "text" and "ew-resize", and older themes as X did, such as "xterm", "left_ptr" and "sb_h_double_arrow": look for both. Finding a cursor again allocates nothing.

A cursor has a cursor-name and cursor-images, a vector of cursor-images: one for a still cursor, the frames of an animation for another. Each image has a cursor-image-width and cursor-image-height, its hotspot, the point that is the pointer's position, at cursor-image-hotspot-x and cursor-image-hotspot-y pixels from its top left corner, and a cursor-image-delay in milliseconds. cursor-image-buffer returns its wl_buffer, in the ARGB8888 format, to attach to the cursor surface.

Animated cursors

(cursor-frame cursor milliseconds) returns the index in cursor-images of the frame to show that many milliseconds into the animation, and the milliseconds until the next frame, or 0 for a cursor that is not animated. It allocates nothing. Attach the frame's buffer, commit, and ask again when the time has passed, from a frame callback or a timer of your own.

Lifetimes and threads

  • The theme owns the images' buffers. Never destroy them: they live until the theme is destroyed, and asking for one again returns the same proxy. They are borrowed proxies: see borrow-proxy in the API Reference.
  • Destroy the theme with destroy-cursor-theme before disconnecting its display. Destroying it after signals wl-error and leaves its memory allocated.
  • After the theme is destroyed, find-cursor, cursor-frame and cursor-image-buffer signal wl-error, and the image buffers are forgotten.
  • A theme follows the thread rules of its display.

The raw libwayland-cursor layer is the package cl-wl/cursor-ffi, in the system of the same name.