From 4d9d3422d28f60866e16d80e91c3334f6f70b8d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=E1=BB=B3nh=20Thi=E1=BB=87n=20L=E1=BB=99c?= Date: Mon, 24 Aug 2026 00:49:02 +0700 Subject: [PATCH] feat(screenshot): use native cursor shapes (#3155) * feat(screenshot): use native cursor shapes * fix(screenshot): reuse cursor shape device * refactor(proto): generate cursor shape binding --- .../proto/wp_cursor_shape/cursor_shape.go | 444 ++++++++++++++++++ core/internal/proto/xml/cursor-shape-v1.xml | 138 ++++++ core/internal/screenshot/region.go | 44 +- core/internal/screenshot/region_input.go | 12 +- 4 files changed, 636 insertions(+), 2 deletions(-) create mode 100644 core/internal/proto/wp_cursor_shape/cursor_shape.go create mode 100644 core/internal/proto/xml/cursor-shape-v1.xml diff --git a/core/internal/proto/wp_cursor_shape/cursor_shape.go b/core/internal/proto/wp_cursor_shape/cursor_shape.go new file mode 100644 index 000000000..3a88c04db --- /dev/null +++ b/core/internal/proto/wp_cursor_shape/cursor_shape.go @@ -0,0 +1,444 @@ +// Generated by go-wayland-scanner +// https://github.com/AvengeMedia/dankgo/cmd/go-wayland-scanner +// XML file : internal/proto/xml/cursor-shape-v1.xml +// +// cursor_shape_v1 Protocol Copyright: +// +// Copyright 2018 The Chromium Authors +// Copyright 2023 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. + +package wp_cursor_shape + +import "github.com/AvengeMedia/dankgo/wayland/client" + +// WpCursorShapeManagerV1InterfaceName is the name of the interface as it appears in the [client.Registry]. +// It can be used to match the [client.RegistryGlobalEvent.Interface] in the +// [Registry.SetGlobalHandler] and can be used in [Registry.Bind] if this applies. +const WpCursorShapeManagerV1InterfaceName = "wp_cursor_shape_manager_v1" + +// WpCursorShapeManagerV1 : cursor shape manager +// +// This global allows clients to set cursor images by name instead of +// creating and attaching buffers. +// +// Warning! The protocol described in this file is currently in the testing +// phase. Backward compatible changes may be added together with the +// corresponding interface version bump. Backward incompatible changes can +// only be done by creating a new major version of the extension. +type WpCursorShapeManagerV1 struct { + client.BaseProxy +} + +// NewWpCursorShapeManagerV1 : cursor shape manager +// +// This global allows clients to set cursor images by name instead of +// creating and attaching buffers. +// +// Warning! The protocol described in this file is currently in the testing +// phase. Backward compatible changes may be added together with the +// corresponding interface version bump. Backward incompatible changes can +// only be done by creating a new major version of the extension. +func NewWpCursorShapeManagerV1(ctx *client.Context) *WpCursorShapeManagerV1 { + wpCursorShapeManagerV1 := &WpCursorShapeManagerV1{} + ctx.Register(wpCursorShapeManagerV1) + return wpCursorShapeManagerV1 +} + +// Destroy : destroy the manager +// +// Destroy the cursor shape manager. +func (i *WpCursorShapeManagerV1) Destroy() error { + defer i.MarkZombie() + const opcode = 0 + const _reqBufLen = 8 + var _reqBuf [_reqBufLen]byte + l := 0 + client.PutUint32(_reqBuf[l:4], i.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// GetPointer : manage the cursor shape of a pointer device +// +// Obtain a wp_cursor_shape_device_v1 for a wl_pointer object. +func (i *WpCursorShapeManagerV1) GetPointer(pointer *client.Pointer) (*WpCursorShapeDeviceV1, error) { + cursorShapeDevice := NewWpCursorShapeDeviceV1(i.Context()) + const opcode = 1 + const _reqBufLen = 8 + 4 + 4 + var _reqBuf [_reqBufLen]byte + l := 0 + client.PutUint32(_reqBuf[l:4], i.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], cursorShapeDevice.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], pointer.ID()) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return cursorShapeDevice, err +} + +// WpCursorShapeDeviceV1InterfaceName is the name of the interface as it appears in the [client.Registry]. +// It can be used to match the [client.RegistryGlobalEvent.Interface] in the +// [Registry.SetGlobalHandler] and can be used in [Registry.Bind] if this applies. +const WpCursorShapeDeviceV1InterfaceName = "wp_cursor_shape_device_v1" + +// WpCursorShapeDeviceV1 : cursor shape for a device +// +// This interface advertises the list of supported cursor shapes for a +// device, and allows clients to set the cursor shape. +type WpCursorShapeDeviceV1 struct { + client.BaseProxy +} + +// NewWpCursorShapeDeviceV1 : cursor shape for a device +// +// This interface advertises the list of supported cursor shapes for a +// device, and allows clients to set the cursor shape. +func NewWpCursorShapeDeviceV1(ctx *client.Context) *WpCursorShapeDeviceV1 { + wpCursorShapeDeviceV1 := &WpCursorShapeDeviceV1{} + ctx.Register(wpCursorShapeDeviceV1) + return wpCursorShapeDeviceV1 +} + +// Destroy : destroy the cursor shape device +// +// Destroy the cursor shape device. +// +// The device cursor shape remains unchanged. +func (i *WpCursorShapeDeviceV1) Destroy() error { + defer i.MarkZombie() + const opcode = 0 + const _reqBufLen = 8 + var _reqBuf [_reqBufLen]byte + l := 0 + client.PutUint32(_reqBuf[l:4], i.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// SetShape : set device cursor to the shape +// +// Sets the device cursor to the specified shape. The compositor will +// change the cursor image based on the specified shape. +// +// The cursor actually changes only if the input device focus is one of +// the requesting client's surfaces. If any, the previous cursor image +// (surface or shape) is replaced. +// +// The "shape" argument must be a valid enum entry, otherwise the +// invalid_shape protocol error is raised. +// +// This is similar to the wl_pointer.set_cursor and +// zwp_tablet_tool_v2.set_cursor requests, but this request accepts a +// shape instead of contents in the form of a surface. Clients can mix +// set_cursor and set_shape requests. +// +// The serial parameter must match the latest wl_pointer.enter or +// zwp_tablet_tool_v2.proximity_in serial number sent to the client. +// Otherwise the request will be ignored. +// +// serial: serial number of the enter event +func (i *WpCursorShapeDeviceV1) SetShape(serial, shape uint32) error { + const opcode = 1 + const _reqBufLen = 8 + 4 + 4 + var _reqBuf [_reqBufLen]byte + l := 0 + client.PutUint32(_reqBuf[l:4], i.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(serial)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(shape)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +type WpCursorShapeDeviceV1Shape uint32 + +// WpCursorShapeDeviceV1Shape : cursor shapes +// +// The names are taken from the CSS W3C specification: +// https://w3c.github.io/csswg-drafts/css-ui/#cursor +const ( + // WpCursorShapeDeviceV1ShapeDefault : default cursor + WpCursorShapeDeviceV1ShapeDefault WpCursorShapeDeviceV1Shape = 1 + // WpCursorShapeDeviceV1ShapeContextMenu : a context menu is available for the object under the cursor + WpCursorShapeDeviceV1ShapeContextMenu WpCursorShapeDeviceV1Shape = 2 + // WpCursorShapeDeviceV1ShapeHelp : help is available for the object under the cursor + WpCursorShapeDeviceV1ShapeHelp WpCursorShapeDeviceV1Shape = 3 + // WpCursorShapeDeviceV1ShapePointer : pointer that indicates a link or another interactive element + WpCursorShapeDeviceV1ShapePointer WpCursorShapeDeviceV1Shape = 4 + // WpCursorShapeDeviceV1ShapeProgress : progress indicator + WpCursorShapeDeviceV1ShapeProgress WpCursorShapeDeviceV1Shape = 5 + // WpCursorShapeDeviceV1ShapeWait : program is busy, user should wait + WpCursorShapeDeviceV1ShapeWait WpCursorShapeDeviceV1Shape = 6 + // WpCursorShapeDeviceV1ShapeCell : a cell or set of cells may be selected + WpCursorShapeDeviceV1ShapeCell WpCursorShapeDeviceV1Shape = 7 + // WpCursorShapeDeviceV1ShapeCrosshair : simple crosshair + WpCursorShapeDeviceV1ShapeCrosshair WpCursorShapeDeviceV1Shape = 8 + // WpCursorShapeDeviceV1ShapeText : text may be selected + WpCursorShapeDeviceV1ShapeText WpCursorShapeDeviceV1Shape = 9 + // WpCursorShapeDeviceV1ShapeVerticalText : vertical text may be selected + WpCursorShapeDeviceV1ShapeVerticalText WpCursorShapeDeviceV1Shape = 10 + // WpCursorShapeDeviceV1ShapeAlias : drag-and-drop: alias of/shortcut to something is to be created + WpCursorShapeDeviceV1ShapeAlias WpCursorShapeDeviceV1Shape = 11 + // WpCursorShapeDeviceV1ShapeCopy : drag-and-drop: something is to be copied + WpCursorShapeDeviceV1ShapeCopy WpCursorShapeDeviceV1Shape = 12 + // WpCursorShapeDeviceV1ShapeMove : drag-and-drop: something is to be moved + WpCursorShapeDeviceV1ShapeMove WpCursorShapeDeviceV1Shape = 13 + // WpCursorShapeDeviceV1ShapeNoDrop : the dragged item cannot be dropped at the current cursor location + WpCursorShapeDeviceV1ShapeNoDrop WpCursorShapeDeviceV1Shape = 14 + // WpCursorShapeDeviceV1ShapeNotAllowed : the requested action will not be carried out + WpCursorShapeDeviceV1ShapeNotAllowed WpCursorShapeDeviceV1Shape = 15 + // WpCursorShapeDeviceV1ShapeGrab : drag-and-drop: something can be grabbed + WpCursorShapeDeviceV1ShapeGrab WpCursorShapeDeviceV1Shape = 16 + // WpCursorShapeDeviceV1ShapeGrabbing : drag-and-drop: something is being grabbed + WpCursorShapeDeviceV1ShapeGrabbing WpCursorShapeDeviceV1Shape = 17 + // WpCursorShapeDeviceV1ShapeEResize : resizing: the east border is to be moved + WpCursorShapeDeviceV1ShapeEResize WpCursorShapeDeviceV1Shape = 18 + // WpCursorShapeDeviceV1ShapeNResize : resizing: the north border is to be moved + WpCursorShapeDeviceV1ShapeNResize WpCursorShapeDeviceV1Shape = 19 + // WpCursorShapeDeviceV1ShapeNeResize : resizing: the north-east corner is to be moved + WpCursorShapeDeviceV1ShapeNeResize WpCursorShapeDeviceV1Shape = 20 + // WpCursorShapeDeviceV1ShapeNwResize : resizing: the north-west corner is to be moved + WpCursorShapeDeviceV1ShapeNwResize WpCursorShapeDeviceV1Shape = 21 + // WpCursorShapeDeviceV1ShapeSResize : resizing: the south border is to be moved + WpCursorShapeDeviceV1ShapeSResize WpCursorShapeDeviceV1Shape = 22 + // WpCursorShapeDeviceV1ShapeSeResize : resizing: the south-east corner is to be moved + WpCursorShapeDeviceV1ShapeSeResize WpCursorShapeDeviceV1Shape = 23 + // WpCursorShapeDeviceV1ShapeSwResize : resizing: the south-west corner is to be moved + WpCursorShapeDeviceV1ShapeSwResize WpCursorShapeDeviceV1Shape = 24 + // WpCursorShapeDeviceV1ShapeWResize : resizing: the west border is to be moved + WpCursorShapeDeviceV1ShapeWResize WpCursorShapeDeviceV1Shape = 25 + // WpCursorShapeDeviceV1ShapeEwResize : resizing: the east and west borders are to be moved + WpCursorShapeDeviceV1ShapeEwResize WpCursorShapeDeviceV1Shape = 26 + // WpCursorShapeDeviceV1ShapeNsResize : resizing: the north and south borders are to be moved + WpCursorShapeDeviceV1ShapeNsResize WpCursorShapeDeviceV1Shape = 27 + // WpCursorShapeDeviceV1ShapeNeswResize : resizing: the north-east and south-west corners are to be moved + WpCursorShapeDeviceV1ShapeNeswResize WpCursorShapeDeviceV1Shape = 28 + // WpCursorShapeDeviceV1ShapeNwseResize : resizing: the north-west and south-east corners are to be moved + WpCursorShapeDeviceV1ShapeNwseResize WpCursorShapeDeviceV1Shape = 29 + // WpCursorShapeDeviceV1ShapeColResize : resizing: the item/column can be resized horizontally + WpCursorShapeDeviceV1ShapeColResize WpCursorShapeDeviceV1Shape = 30 + // WpCursorShapeDeviceV1ShapeRowResize : resizing: the item/row can be resized vertically + WpCursorShapeDeviceV1ShapeRowResize WpCursorShapeDeviceV1Shape = 31 + // WpCursorShapeDeviceV1ShapeAllScroll : something can be scrolled in any direction + WpCursorShapeDeviceV1ShapeAllScroll WpCursorShapeDeviceV1Shape = 32 + // WpCursorShapeDeviceV1ShapeZoomIn : something can be zoomed in + WpCursorShapeDeviceV1ShapeZoomIn WpCursorShapeDeviceV1Shape = 33 + // WpCursorShapeDeviceV1ShapeZoomOut : something can be zoomed out + WpCursorShapeDeviceV1ShapeZoomOut WpCursorShapeDeviceV1Shape = 34 +) + +func (e WpCursorShapeDeviceV1Shape) Name() string { + switch e { + case WpCursorShapeDeviceV1ShapeDefault: + return "default" + case WpCursorShapeDeviceV1ShapeContextMenu: + return "context_menu" + case WpCursorShapeDeviceV1ShapeHelp: + return "help" + case WpCursorShapeDeviceV1ShapePointer: + return "pointer" + case WpCursorShapeDeviceV1ShapeProgress: + return "progress" + case WpCursorShapeDeviceV1ShapeWait: + return "wait" + case WpCursorShapeDeviceV1ShapeCell: + return "cell" + case WpCursorShapeDeviceV1ShapeCrosshair: + return "crosshair" + case WpCursorShapeDeviceV1ShapeText: + return "text" + case WpCursorShapeDeviceV1ShapeVerticalText: + return "vertical_text" + case WpCursorShapeDeviceV1ShapeAlias: + return "alias" + case WpCursorShapeDeviceV1ShapeCopy: + return "copy" + case WpCursorShapeDeviceV1ShapeMove: + return "move" + case WpCursorShapeDeviceV1ShapeNoDrop: + return "no_drop" + case WpCursorShapeDeviceV1ShapeNotAllowed: + return "not_allowed" + case WpCursorShapeDeviceV1ShapeGrab: + return "grab" + case WpCursorShapeDeviceV1ShapeGrabbing: + return "grabbing" + case WpCursorShapeDeviceV1ShapeEResize: + return "e_resize" + case WpCursorShapeDeviceV1ShapeNResize: + return "n_resize" + case WpCursorShapeDeviceV1ShapeNeResize: + return "ne_resize" + case WpCursorShapeDeviceV1ShapeNwResize: + return "nw_resize" + case WpCursorShapeDeviceV1ShapeSResize: + return "s_resize" + case WpCursorShapeDeviceV1ShapeSeResize: + return "se_resize" + case WpCursorShapeDeviceV1ShapeSwResize: + return "sw_resize" + case WpCursorShapeDeviceV1ShapeWResize: + return "w_resize" + case WpCursorShapeDeviceV1ShapeEwResize: + return "ew_resize" + case WpCursorShapeDeviceV1ShapeNsResize: + return "ns_resize" + case WpCursorShapeDeviceV1ShapeNeswResize: + return "nesw_resize" + case WpCursorShapeDeviceV1ShapeNwseResize: + return "nwse_resize" + case WpCursorShapeDeviceV1ShapeColResize: + return "col_resize" + case WpCursorShapeDeviceV1ShapeRowResize: + return "row_resize" + case WpCursorShapeDeviceV1ShapeAllScroll: + return "all_scroll" + case WpCursorShapeDeviceV1ShapeZoomIn: + return "zoom_in" + case WpCursorShapeDeviceV1ShapeZoomOut: + return "zoom_out" + default: + return "" + } +} + +func (e WpCursorShapeDeviceV1Shape) Value() string { + switch e { + case WpCursorShapeDeviceV1ShapeDefault: + return "1" + case WpCursorShapeDeviceV1ShapeContextMenu: + return "2" + case WpCursorShapeDeviceV1ShapeHelp: + return "3" + case WpCursorShapeDeviceV1ShapePointer: + return "4" + case WpCursorShapeDeviceV1ShapeProgress: + return "5" + case WpCursorShapeDeviceV1ShapeWait: + return "6" + case WpCursorShapeDeviceV1ShapeCell: + return "7" + case WpCursorShapeDeviceV1ShapeCrosshair: + return "8" + case WpCursorShapeDeviceV1ShapeText: + return "9" + case WpCursorShapeDeviceV1ShapeVerticalText: + return "10" + case WpCursorShapeDeviceV1ShapeAlias: + return "11" + case WpCursorShapeDeviceV1ShapeCopy: + return "12" + case WpCursorShapeDeviceV1ShapeMove: + return "13" + case WpCursorShapeDeviceV1ShapeNoDrop: + return "14" + case WpCursorShapeDeviceV1ShapeNotAllowed: + return "15" + case WpCursorShapeDeviceV1ShapeGrab: + return "16" + case WpCursorShapeDeviceV1ShapeGrabbing: + return "17" + case WpCursorShapeDeviceV1ShapeEResize: + return "18" + case WpCursorShapeDeviceV1ShapeNResize: + return "19" + case WpCursorShapeDeviceV1ShapeNeResize: + return "20" + case WpCursorShapeDeviceV1ShapeNwResize: + return "21" + case WpCursorShapeDeviceV1ShapeSResize: + return "22" + case WpCursorShapeDeviceV1ShapeSeResize: + return "23" + case WpCursorShapeDeviceV1ShapeSwResize: + return "24" + case WpCursorShapeDeviceV1ShapeWResize: + return "25" + case WpCursorShapeDeviceV1ShapeEwResize: + return "26" + case WpCursorShapeDeviceV1ShapeNsResize: + return "27" + case WpCursorShapeDeviceV1ShapeNeswResize: + return "28" + case WpCursorShapeDeviceV1ShapeNwseResize: + return "29" + case WpCursorShapeDeviceV1ShapeColResize: + return "30" + case WpCursorShapeDeviceV1ShapeRowResize: + return "31" + case WpCursorShapeDeviceV1ShapeAllScroll: + return "32" + case WpCursorShapeDeviceV1ShapeZoomIn: + return "33" + case WpCursorShapeDeviceV1ShapeZoomOut: + return "34" + default: + return "" + } +} + +func (e WpCursorShapeDeviceV1Shape) String() string { + return e.Name() + "=" + e.Value() +} + +type WpCursorShapeDeviceV1Error uint32 + +// WpCursorShapeDeviceV1Error : +const ( + // WpCursorShapeDeviceV1ErrorInvalidShape : the specified shape value is invalid + WpCursorShapeDeviceV1ErrorInvalidShape WpCursorShapeDeviceV1Error = 1 +) + +func (e WpCursorShapeDeviceV1Error) Name() string { + switch e { + case WpCursorShapeDeviceV1ErrorInvalidShape: + return "invalid_shape" + default: + return "" + } +} + +func (e WpCursorShapeDeviceV1Error) Value() string { + switch e { + case WpCursorShapeDeviceV1ErrorInvalidShape: + return "1" + default: + return "" + } +} + +func (e WpCursorShapeDeviceV1Error) String() string { + return e.Name() + "=" + e.Value() +} diff --git a/core/internal/proto/xml/cursor-shape-v1.xml b/core/internal/proto/xml/cursor-shape-v1.xml new file mode 100644 index 000000000..156361414 --- /dev/null +++ b/core/internal/proto/xml/cursor-shape-v1.xml @@ -0,0 +1,138 @@ + + + + Copyright 2018 The Chromium Authors + Copyright 2023 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. + + + + + This global allows clients to set cursor images by name instead of + creating and attaching buffers. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + Destroy the cursor shape manager. + + + + + + Obtain a wp_cursor_shape_device_v1 for a wl_pointer object. + + + + + + + + + + This interface advertises the list of supported cursor shapes for a + device, and allows clients to set the cursor shape. + + + + + The names are taken from the CSS W3C specification: + https://w3c.github.io/csswg-drafts/css-ui/#cursor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Destroy the cursor shape device. + + The device cursor shape remains unchanged. + + + + + + Sets the device cursor to the specified shape. The compositor will + change the cursor image based on the specified shape. + + The cursor actually changes only if the input device focus is one of + the requesting client's surfaces. If any, the previous cursor image + (surface or shape) is replaced. + + The "shape" argument must be a valid enum entry, otherwise the + invalid_shape protocol error is raised. + + This is similar to the wl_pointer.set_cursor and + zwp_tablet_tool_v2.set_cursor requests, but this request accepts a + shape instead of contents in the form of a surface. Clients can mix + set_cursor and set_shape requests. + + The serial parameter must match the latest wl_pointer.enter or + zwp_tablet_tool_v2.proximity_in serial number sent to the client. + Otherwise the request will be ignored. + + + + + + diff --git a/core/internal/screenshot/region.go b/core/internal/screenshot/region.go index b84b4d20a..d70b5a0d7 100644 --- a/core/internal/screenshot/region.go +++ b/core/internal/screenshot/region.go @@ -8,6 +8,7 @@ import ( "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/keyboard_shortcuts_inhibit" "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_layer_shell" "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_screencopy" + "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wp_cursor_shape" "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wp_viewporter" wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client" "github.com/AvengeMedia/dankgo/wayland/client" @@ -103,9 +104,12 @@ type RegionSelector struct { surfaces []*OutputSurface activeSurface *OutputSurface - // Cursor surface for crosshair + // Cursor surface fallback when the compositor lacks cursor shapes. cursorSurface *client.Surface cursorBuffer *ShmBuffer + cursorSerial uint32 + cursorShape *wp_cursor_shape.WpCursorShapeManagerV1 + cursorDevice *wp_cursor_shape.WpCursorShapeDeviceV1 cursorWlBuf *client.Buffer cursorPool *client.ShmPool @@ -115,6 +119,8 @@ type RegionSelector struct { preSelect Region showCapturedCursor bool shiftHeld bool + ctrlHeld bool + movingSelection bool phase selectorPhase scroll *scrollSession @@ -324,6 +330,12 @@ func (r *RegionSelector) handleGlobal(e client.RegistryGlobalEvent) { if err := r.registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil { r.shortcutsInhibitMgr = mgr } + + case wp_cursor_shape.WpCursorShapeManagerV1InterfaceName: + mgr := wp_cursor_shape.NewWpCursorShapeManagerV1(r.ctx) + if err := r.registry.Bind(e.Name, e.Interface, 1, mgr); err == nil { + r.cursorShape = mgr + } } } @@ -605,6 +617,30 @@ func (r *RegionSelector) createCursor() error { return nil } +func (r *RegionSelector) refreshCursor() { + r.setNativeCursor(r.cursorSerial) +} + +func (r *RegionSelector) setNativeCursor(serial uint32) { + if r.cursorShape == nil || r.pointer == nil || serial == 0 { + return + } + shape := uint32(wp_cursor_shape.WpCursorShapeDeviceV1ShapeCrosshair) + if r.movingSelection && r.selection.dragging { + shape = uint32(wp_cursor_shape.WpCursorShapeDeviceV1ShapeGrabbing) + } else if r.ctrlHeld { + shape = uint32(wp_cursor_shape.WpCursorShapeDeviceV1ShapeGrab) + } + if r.cursorDevice == nil { + device, err := r.cursorShape.GetPointer(r.pointer) + if err != nil { + return + } + r.cursorDevice = device + } + _ = r.cursorDevice.SetShape(serial, shape) +} + func (r *RegionSelector) createOutputSurface(output *WaylandOutput) (*OutputSurface, error) { surface, err := r.compositor.CreateSurface() if err != nil { @@ -901,6 +937,12 @@ func (r *RegionSelector) cleanup() { if r.cursorBuffer != nil { r.cursorBuffer.Close() } + if r.cursorDevice != nil { + _ = r.cursorDevice.Destroy() + } + if r.cursorShape != nil { + _ = r.cursorShape.Destroy() + } r.cleanupScroll() diff --git a/core/internal/screenshot/region_input.go b/core/internal/screenshot/region_input.go index 0dbdc59b8..34f65b031 100644 --- a/core/internal/screenshot/region_input.go +++ b/core/internal/screenshot/region_input.go @@ -29,7 +29,8 @@ func (r *RegionSelector) setupInput() { func (r *RegionSelector) setupPointerHandlers() { r.pointer.SetEnterHandler(func(e client.PointerEnterEvent) { - if r.cursorSurface != nil { + r.cursorSerial = e.Serial + if r.cursorShape == nil && r.cursorSurface != nil { _ = r.pointer.SetCursor(e.Serial, r.cursorSurface, 12, 12) } @@ -46,6 +47,7 @@ func (r *RegionSelector) setupPointerHandlers() { if r.selection.dragging { r.updateSelectionCurrent(r.activeSurface, r.pointerX, r.pointerY) } + r.refreshCursor() }) r.pointer.SetMotionHandler(func(e client.PointerMotionEvent) { @@ -94,11 +96,13 @@ func (r *RegionSelector) setupPointerHandlers() { r.selection.anchorY = r.pointerY + float64(r.activeSurface.output.y) r.selection.currentX = r.selection.anchorX r.selection.currentY = r.selection.anchorY + r.refreshCursor() for _, os := range r.surfaces { r.redrawSurface(os) } case 0: // released r.selection.dragging = false + r.refreshCursor() for _, os := range r.surfaces { r.redrawSurface(os) } @@ -156,9 +160,15 @@ func (r *RegionSelector) updateSelectionCurrent(os *OutputSurface, surfaceX, sur func (r *RegionSelector) setupKeyboardHandlers() { r.keyboard.SetModifiersHandler(func(e client.KeyboardModifiersEvent) { r.shiftHeld = e.ModsDepressed&1 != 0 + r.ctrlHeld = e.ModsDepressed&4 != 0 + r.refreshCursor() }) r.keyboard.SetKeyHandler(func(e client.KeyboardKeyEvent) { + if e.Key == 29 || e.Key == 97 { // Ctrl left/right + r.ctrlHeld = e.State != 0 + r.refreshCursor() + } if e.State != 1 { return }