summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region.go
diff options
context:
space:
mode:
authorAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
committerAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
commit70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch)
treeab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region.go
parent5d94c0a7d44a2255b81815a52a7056a94a39842d (diff)
downloadRaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz
RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region.go')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region.go859
1 files changed, 859 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region.go
new file mode 100644
index 0000000..f296434
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region.go
@@ -0,0 +1,859 @@
+package screenshot
+
+import (
+ "fmt"
+ "sync"
+
+ "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
+ "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_viewporter"
+ wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client"
+ "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
+)
+
+type SelectionState struct {
+ hasSelection bool // There's a selection to display (pre-loaded or user-drawn)
+ dragging bool // User is actively drawing a new selection
+ surface *OutputSurface // Surface where selection was made
+ // Surface-local logical coordinates (from pointer events)
+ anchorX float64
+ anchorY float64
+ currentX float64
+ currentY float64
+}
+
+type RenderSlot struct {
+ shm *ShmBuffer
+ pool *client.ShmPool
+ wlBuf *client.Buffer
+ busy bool
+}
+
+type OutputSurface struct {
+ output *WaylandOutput
+ wlSurface *client.Surface
+ layerSurf *wlr_layer_shell.ZwlrLayerSurfaceV1
+ viewport *wp_viewporter.WpViewport
+ screenBuf *ShmBuffer
+ screenBufNoCursor *ShmBuffer
+ screenFormat uint32
+ logicalW int
+ logicalH int
+ configured bool
+ yInverted bool
+
+ // Triple-buffered render slots
+ slots [3]*RenderSlot
+ slotsReady bool
+}
+
+type PreCapture struct {
+ screenBuf *ShmBuffer
+ screenBufNoCursor *ShmBuffer
+ format uint32
+ yInverted bool
+}
+
+type RegionSelector struct {
+ screenshoter *Screenshoter
+
+ display *client.Display
+ registry *client.Registry
+ ctx *client.Context
+
+ compositor *client.Compositor
+ shm *client.Shm
+ seat *client.Seat
+ pointer *client.Pointer
+ keyboard *client.Keyboard
+ layerShell *wlr_layer_shell.ZwlrLayerShellV1
+ screencopy *wlr_screencopy.ZwlrScreencopyManagerV1
+ viewporter *wp_viewporter.WpViewporter
+
+ shortcutsInhibitMgr *keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitManagerV1
+ shortcutsInhibitor *keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitorV1
+
+ outputs map[uint32]*WaylandOutput
+ outputsMu sync.Mutex
+ preCapture map[*WaylandOutput]*PreCapture
+
+ surfaces []*OutputSurface
+ activeSurface *OutputSurface
+
+ // Cursor surface for crosshair
+ cursorSurface *client.Surface
+ cursorBuffer *ShmBuffer
+ cursorWlBuf *client.Buffer
+ cursorPool *client.ShmPool
+
+ selection SelectionState
+ pointerX float64
+ pointerY float64
+ preSelect Region
+ showCapturedCursor bool
+ shiftHeld bool
+
+ running bool
+ cancelled bool
+ result Region
+
+ capturedBuffer *ShmBuffer
+ capturedRegion Region
+}
+
+func NewRegionSelector(s *Screenshoter) *RegionSelector {
+ return &RegionSelector{
+ screenshoter: s,
+ outputs: make(map[uint32]*WaylandOutput),
+ preCapture: make(map[*WaylandOutput]*PreCapture),
+ showCapturedCursor: s.config.Cursor == CursorOn,
+ }
+}
+
+func (r *RegionSelector) Run() (*CaptureResult, bool, error) {
+ if r.screenshoter != nil && r.screenshoter.config.Reset {
+ r.preSelect = Region{}
+ } else {
+ r.preSelect = GetLastRegion()
+ }
+
+ if err := r.connect(); err != nil {
+ return nil, false, fmt.Errorf("wayland connect: %w", err)
+ }
+ defer r.cleanup()
+
+ if err := r.setupRegistry(); err != nil {
+ return nil, false, fmt.Errorf("registry setup: %w", err)
+ }
+
+ if err := r.roundtrip(); err != nil {
+ return nil, false, fmt.Errorf("roundtrip after registry: %w", err)
+ }
+
+ switch {
+ case r.screencopy == nil:
+ return nil, false, fmt.Errorf("compositor does not support wlr-screencopy-unstable-v1")
+ case r.layerShell == nil:
+ return nil, false, fmt.Errorf("compositor does not support wlr-layer-shell-unstable-v1")
+ case r.seat == nil:
+ return nil, false, fmt.Errorf("no seat available")
+ case r.compositor == nil:
+ return nil, false, fmt.Errorf("compositor not available")
+ case r.shm == nil:
+ return nil, false, fmt.Errorf("wl_shm not available")
+ case len(r.outputs) == 0:
+ return nil, false, fmt.Errorf("no outputs available")
+ }
+
+ if err := r.roundtrip(); err != nil {
+ return nil, false, fmt.Errorf("roundtrip after protocol check: %w", err)
+ }
+
+ if err := r.preCaptureAllOutputs(); err != nil {
+ return nil, false, fmt.Errorf("pre-capture: %w", err)
+ }
+
+ if err := r.createSurfaces(); err != nil {
+ return nil, false, fmt.Errorf("create surfaces: %w", err)
+ }
+
+ _ = r.createCursor()
+
+ if err := r.roundtrip(); err != nil {
+ return nil, false, fmt.Errorf("roundtrip after surfaces: %w", err)
+ }
+
+ r.running = true
+ for r.running {
+ if err := r.ctx.Dispatch(); err != nil {
+ return nil, false, fmt.Errorf("dispatch: %w", err)
+ }
+ }
+
+ if r.cancelled || r.capturedBuffer == nil {
+ return nil, r.cancelled, nil
+ }
+
+ yInverted := false
+ var format uint32
+ if r.selection.surface != nil {
+ yInverted = r.selection.surface.yInverted
+ format = r.selection.surface.screenFormat
+ }
+
+ return &CaptureResult{
+ Buffer: r.capturedBuffer,
+ Region: r.result,
+ YInverted: yInverted,
+ Format: format,
+ }, false, nil
+}
+
+func (r *RegionSelector) connect() error {
+ display, err := client.Connect("")
+ if err != nil {
+ return err
+ }
+ r.display = display
+ r.ctx = display.Context()
+ return nil
+}
+
+func (r *RegionSelector) roundtrip() error {
+ return wlhelpers.Roundtrip(r.display, r.ctx)
+}
+
+func (r *RegionSelector) setupRegistry() error {
+ registry, err := r.display.GetRegistry()
+ if err != nil {
+ return err
+ }
+ r.registry = registry
+
+ registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) {
+ r.handleGlobal(e)
+ })
+
+ registry.SetGlobalRemoveHandler(func(e client.RegistryGlobalRemoveEvent) {
+ r.outputsMu.Lock()
+ delete(r.outputs, e.Name)
+ r.outputsMu.Unlock()
+ })
+
+ return nil
+}
+
+func (r *RegionSelector) handleGlobal(e client.RegistryGlobalEvent) {
+ switch e.Interface {
+ case client.CompositorInterfaceName:
+ comp := client.NewCompositor(r.ctx)
+ if err := r.registry.Bind(e.Name, e.Interface, e.Version, comp); err == nil {
+ r.compositor = comp
+ }
+
+ case client.ShmInterfaceName:
+ shm := client.NewShm(r.ctx)
+ if err := r.registry.Bind(e.Name, e.Interface, e.Version, shm); err == nil {
+ r.shm = shm
+ }
+
+ case client.SeatInterfaceName:
+ seat := client.NewSeat(r.ctx)
+ if err := r.registry.Bind(e.Name, e.Interface, e.Version, seat); err == nil {
+ r.seat = seat
+ r.setupInput()
+ }
+
+ case client.OutputInterfaceName:
+ output := client.NewOutput(r.ctx)
+ version := e.Version
+ if version > 4 {
+ version = 4
+ }
+ if err := r.registry.Bind(e.Name, e.Interface, version, output); err == nil {
+ r.outputsMu.Lock()
+ r.outputs[e.Name] = &WaylandOutput{
+ wlOutput: output,
+ globalName: e.Name,
+ scale: 1,
+ fractionalScale: 1.0,
+ }
+ r.outputsMu.Unlock()
+ r.setupOutputHandlers(e.Name, output)
+ }
+
+ case wlr_layer_shell.ZwlrLayerShellV1InterfaceName:
+ ls := wlr_layer_shell.NewZwlrLayerShellV1(r.ctx)
+ version := e.Version
+ if version > 4 {
+ version = 4
+ }
+ if err := r.registry.Bind(e.Name, e.Interface, version, ls); err == nil {
+ r.layerShell = ls
+ }
+
+ case wlr_screencopy.ZwlrScreencopyManagerV1InterfaceName:
+ sc := wlr_screencopy.NewZwlrScreencopyManagerV1(r.ctx)
+ version := e.Version
+ if version > 3 {
+ version = 3
+ }
+ if err := r.registry.Bind(e.Name, e.Interface, version, sc); err == nil {
+ r.screencopy = sc
+ }
+
+ case wp_viewporter.WpViewporterInterfaceName:
+ vp := wp_viewporter.NewWpViewporter(r.ctx)
+ if err := r.registry.Bind(e.Name, e.Interface, e.Version, vp); err == nil {
+ r.viewporter = vp
+ }
+
+ case keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitManagerV1InterfaceName:
+ mgr := keyboard_shortcuts_inhibit.NewZwpKeyboardShortcutsInhibitManagerV1(r.ctx)
+ if err := r.registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil {
+ r.shortcutsInhibitMgr = mgr
+ }
+ }
+}
+
+func (r *RegionSelector) setupOutputHandlers(name uint32, output *client.Output) {
+ output.SetGeometryHandler(func(e client.OutputGeometryEvent) {
+ r.outputsMu.Lock()
+ if o, ok := r.outputs[name]; ok {
+ o.x = e.X
+ o.y = e.Y
+ o.transform = int32(e.Transform)
+ }
+ r.outputsMu.Unlock()
+ })
+
+ output.SetModeHandler(func(e client.OutputModeEvent) {
+ if e.Flags&uint32(client.OutputModeCurrent) == 0 {
+ return
+ }
+ r.outputsMu.Lock()
+ if o, ok := r.outputs[name]; ok {
+ o.width = e.Width
+ o.height = e.Height
+ }
+ r.outputsMu.Unlock()
+ })
+
+ output.SetScaleHandler(func(e client.OutputScaleEvent) {
+ r.outputsMu.Lock()
+ if o, ok := r.outputs[name]; ok {
+ o.scale = e.Factor
+ o.fractionalScale = float64(e.Factor)
+ }
+ r.outputsMu.Unlock()
+ })
+
+ output.SetNameHandler(func(e client.OutputNameEvent) {
+ r.outputsMu.Lock()
+ if o, ok := r.outputs[name]; ok {
+ o.name = e.Name
+ }
+ r.outputsMu.Unlock()
+ })
+}
+
+func (r *RegionSelector) preCaptureAllOutputs() error {
+ r.outputsMu.Lock()
+ outputs := make([]*WaylandOutput, 0, len(r.outputs))
+ for _, o := range r.outputs {
+ outputs = append(outputs, o)
+ }
+ r.outputsMu.Unlock()
+
+ pending := len(outputs) * 2
+ done := make(chan struct{}, pending)
+
+ for _, output := range outputs {
+ pc := &PreCapture{}
+ r.preCapture[output] = pc
+
+ r.preCaptureOutput(output, pc, true, func() { done <- struct{}{} })
+ r.preCaptureOutput(output, pc, false, func() { done <- struct{}{} })
+ }
+
+ for i := 0; i < pending; i++ {
+ if err := r.ctx.Dispatch(); err != nil {
+ return err
+ }
+ select {
+ case <-done:
+ default:
+ i--
+ }
+ }
+ return nil
+}
+
+func (r *RegionSelector) preCaptureOutput(output *WaylandOutput, pc *PreCapture, withCursor bool, onReady func()) {
+ cursor := int32(0)
+ if withCursor {
+ cursor = 1
+ }
+
+ frame, err := r.screencopy.CaptureOutput(cursor, output.wlOutput)
+ if err != nil {
+ log.Error("screencopy capture failed", "err", err)
+ onReady()
+ return
+ }
+
+ var capturedBuf *ShmBuffer
+
+ var capturedFormat PixelFormat
+ frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) {
+ capturedFormat = PixelFormat(e.Format)
+ bpp := capturedFormat.BytesPerPixel()
+ if int(e.Stride) < int(e.Width)*bpp {
+ log.Error("invalid stride from compositor", "stride", e.Stride, "width", e.Width, "bpp", bpp)
+ return
+ }
+ buf, err := CreateShmBuffer(int(e.Width), int(e.Height), int(e.Stride))
+ if err != nil {
+ log.Error("create screen buffer failed", "err", err)
+ return
+ }
+
+ capturedBuf = buf
+ buf.Format = capturedFormat
+
+ pool, err := r.shm.CreatePool(buf.Fd(), int32(buf.Size()))
+ if err != nil {
+ log.Error("create shm pool failed", "err", err)
+ return
+ }
+
+ wlBuf, err := pool.CreateBuffer(0, int32(buf.Width), int32(buf.Height), int32(buf.Stride), e.Format)
+ if err != nil {
+ log.Error("create wl_buffer failed", "err", err)
+ pool.Destroy()
+ return
+ }
+
+ if err := frame.Copy(wlBuf); err != nil {
+ log.Error("frame copy failed", "err", err)
+ }
+ pool.Destroy()
+ })
+
+ frame.SetFlagsHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1FlagsEvent) {
+ if withCursor {
+ pc.yInverted = (e.Flags & 1) != 0
+ }
+ })
+
+ frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) {
+ frame.Destroy()
+
+ if capturedBuf == nil {
+ onReady()
+ return
+ }
+
+ if capturedFormat.Is24Bit() {
+ converted, newFormat, err := capturedBuf.ConvertTo32Bit(capturedFormat)
+ if err != nil {
+ log.Error("convert 24-bit to 32-bit failed", "err", err)
+ } else if converted != capturedBuf {
+ capturedBuf.Close()
+ capturedBuf = converted
+ capturedFormat = newFormat
+ }
+ }
+
+ pc.format = uint32(capturedFormat)
+
+ if pc.yInverted {
+ capturedBuf.FlipVertical()
+ pc.yInverted = false
+ }
+
+ if output.transform != TransformNormal {
+ invTransform := InverseTransform(output.transform)
+ transformed, err := capturedBuf.ApplyTransform(invTransform)
+ if err != nil {
+ log.Error("apply transform failed", "err", err)
+ } else if transformed != capturedBuf {
+ capturedBuf.Close()
+ capturedBuf = transformed
+ }
+ }
+
+ if withCursor {
+ pc.screenBuf = capturedBuf
+ } else {
+ pc.screenBufNoCursor = capturedBuf
+ }
+
+ onReady()
+ })
+
+ frame.SetFailedHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1FailedEvent) {
+ log.Error("screencopy failed")
+ frame.Destroy()
+ onReady()
+ })
+}
+
+func (r *RegionSelector) createSurfaces() error {
+ r.outputsMu.Lock()
+ outputs := make([]*WaylandOutput, 0, len(r.outputs))
+ for _, o := range r.outputs {
+ outputs = append(outputs, o)
+ }
+ r.outputsMu.Unlock()
+
+ for _, output := range outputs {
+ os, err := r.createOutputSurface(output)
+ if err != nil {
+ return fmt.Errorf("output %s: %w", output.name, err)
+ }
+ r.surfaces = append(r.surfaces, os)
+ }
+
+ return nil
+}
+
+func (r *RegionSelector) createCursor() error {
+ const size = 24
+ const hotspot = size / 2
+
+ surface, err := r.compositor.CreateSurface()
+ if err != nil {
+ return fmt.Errorf("create cursor surface: %w", err)
+ }
+ r.cursorSurface = surface
+
+ buf, err := CreateShmBuffer(size, size, size*4)
+ if err != nil {
+ return fmt.Errorf("create cursor buffer: %w", err)
+ }
+ r.cursorBuffer = buf
+
+ // Draw crosshair
+ data := buf.Data()
+ for y := 0; y < size; y++ {
+ for x := 0; x < size; x++ {
+ off := (y*size + x) * 4
+ // Vertical line
+ if x >= hotspot-1 && x <= hotspot && y >= 2 && y < size-2 {
+ data[off+0] = 255 // B
+ data[off+1] = 255 // G
+ data[off+2] = 255 // R
+ data[off+3] = 255 // A
+ continue
+ }
+ // Horizontal line
+ if y >= hotspot-1 && y <= hotspot && x >= 2 && x < size-2 {
+ data[off+0] = 255
+ data[off+1] = 255
+ data[off+2] = 255
+ data[off+3] = 255
+ continue
+ }
+ // Transparent
+ data[off+0] = 0
+ data[off+1] = 0
+ data[off+2] = 0
+ data[off+3] = 0
+ }
+ }
+
+ pool, err := r.shm.CreatePool(buf.Fd(), int32(buf.Size()))
+ if err != nil {
+ return fmt.Errorf("create cursor pool: %w", err)
+ }
+ r.cursorPool = pool
+
+ wlBuf, err := pool.CreateBuffer(0, size, size, size*4, uint32(FormatARGB8888))
+ if err != nil {
+ return fmt.Errorf("create cursor wl_buffer: %w", err)
+ }
+ r.cursorWlBuf = wlBuf
+
+ if err := surface.Attach(wlBuf, 0, 0); err != nil {
+ return fmt.Errorf("attach cursor: %w", err)
+ }
+ if err := surface.Damage(0, 0, size, size); err != nil {
+ return fmt.Errorf("damage cursor: %w", err)
+ }
+ if err := surface.Commit(); err != nil {
+ return fmt.Errorf("commit cursor: %w", err)
+ }
+
+ return nil
+}
+
+func (r *RegionSelector) createOutputSurface(output *WaylandOutput) (*OutputSurface, error) {
+ surface, err := r.compositor.CreateSurface()
+ if err != nil {
+ return nil, fmt.Errorf("create surface: %w", err)
+ }
+
+ layerSurf, err := r.layerShell.GetLayerSurface(
+ surface,
+ output.wlOutput,
+ uint32(wlr_layer_shell.ZwlrLayerShellV1LayerOverlay),
+ "dms-screenshot",
+ )
+ if err != nil {
+ return nil, fmt.Errorf("get layer surface: %w", err)
+ }
+
+ os := &OutputSurface{
+ output: output,
+ wlSurface: surface,
+ layerSurf: layerSurf,
+ }
+
+ if r.viewporter != nil {
+ vp, err := r.viewporter.GetViewport(surface)
+ if err == nil {
+ os.viewport = vp
+ }
+ }
+
+ if err := layerSurf.SetAnchor(
+ uint32(wlr_layer_shell.ZwlrLayerSurfaceV1AnchorTop) |
+ uint32(wlr_layer_shell.ZwlrLayerSurfaceV1AnchorBottom) |
+ uint32(wlr_layer_shell.ZwlrLayerSurfaceV1AnchorLeft) |
+ uint32(wlr_layer_shell.ZwlrLayerSurfaceV1AnchorRight),
+ ); err != nil {
+ return nil, fmt.Errorf("set anchor: %w", err)
+ }
+ if err := layerSurf.SetExclusiveZone(-1); err != nil {
+ return nil, fmt.Errorf("set exclusive zone: %w", err)
+ }
+ if err := layerSurf.SetKeyboardInteractivity(uint32(wlr_layer_shell.ZwlrLayerSurfaceV1KeyboardInteractivityExclusive)); err != nil {
+ return nil, fmt.Errorf("set keyboard interactivity: %w", err)
+ }
+
+ layerSurf.SetConfigureHandler(func(e wlr_layer_shell.ZwlrLayerSurfaceV1ConfigureEvent) {
+ if err := layerSurf.AckConfigure(e.Serial); err != nil {
+ log.Error("ack configure failed", "err", err)
+ return
+ }
+ os.logicalW = int(e.Width)
+ os.logicalH = int(e.Height)
+ os.configured = true
+ r.captureForSurface(os)
+ r.ensureShortcutsInhibitor(os)
+ })
+
+ layerSurf.SetClosedHandler(func(e wlr_layer_shell.ZwlrLayerSurfaceV1ClosedEvent) {
+ r.running = false
+ r.cancelled = true
+ })
+
+ if err := surface.Commit(); err != nil {
+ return nil, fmt.Errorf("surface commit: %w", err)
+ }
+
+ return os, nil
+}
+
+func (r *RegionSelector) ensureShortcutsInhibitor(os *OutputSurface) {
+ if r.shortcutsInhibitMgr == nil || r.seat == nil || r.shortcutsInhibitor != nil {
+ return
+ }
+ inhibitor, err := r.shortcutsInhibitMgr.InhibitShortcuts(os.wlSurface, r.seat)
+ if err == nil {
+ r.shortcutsInhibitor = inhibitor
+ }
+}
+
+func (r *RegionSelector) captureForSurface(os *OutputSurface) {
+ pc := r.preCapture[os.output]
+ if pc == nil {
+ return
+ }
+
+ os.screenBuf = pc.screenBuf
+ os.screenBufNoCursor = pc.screenBufNoCursor
+ os.screenFormat = pc.format
+ os.yInverted = pc.yInverted
+
+ if os.logicalW > 0 && os.screenBuf != nil {
+ os.output.fractionalScale = float64(os.screenBuf.Width) / float64(os.logicalW)
+ }
+
+ r.initRenderBuffer(os)
+ r.applyPreSelection(os)
+ r.redrawSurface(os)
+}
+
+func (r *RegionSelector) initRenderBuffer(os *OutputSurface) {
+ if os.screenBuf == nil {
+ return
+ }
+
+ for i := 0; i < 3; i++ {
+ slot := &RenderSlot{}
+
+ buf, err := CreateShmBuffer(os.screenBuf.Width, os.screenBuf.Height, os.screenBuf.Stride)
+ if err != nil {
+ log.Error("create render slot buffer failed", "err", err)
+ return
+ }
+ slot.shm = buf
+
+ pool, err := r.shm.CreatePool(buf.Fd(), int32(buf.Size()))
+ if err != nil {
+ log.Error("create render slot pool failed", "err", err)
+ buf.Close()
+ return
+ }
+ slot.pool = pool
+
+ wlBuf, err := pool.CreateBuffer(0, int32(buf.Width), int32(buf.Height), int32(buf.Stride), os.screenFormat)
+ if err != nil {
+ log.Error("create render slot wl_buffer failed", "err", err)
+ pool.Destroy()
+ buf.Close()
+ return
+ }
+ slot.wlBuf = wlBuf
+
+ slotRef := slot
+ wlBuf.SetReleaseHandler(func(e client.BufferReleaseEvent) {
+ slotRef.busy = false
+ })
+
+ os.slots[i] = slot
+ }
+ os.slotsReady = true
+}
+
+func (os *OutputSurface) acquireFreeSlot() *RenderSlot {
+ for _, slot := range os.slots {
+ if slot != nil && !slot.busy {
+ return slot
+ }
+ }
+ return nil
+}
+
+func (r *RegionSelector) applyPreSelection(os *OutputSurface) {
+ if r.preSelect.IsEmpty() || os.screenBuf == nil || r.selection.hasSelection {
+ return
+ }
+
+ if r.preSelect.Output != "" && r.preSelect.Output != os.output.name {
+ return
+ }
+
+ scaleX := float64(os.logicalW) / float64(os.screenBuf.Width)
+ scaleY := float64(os.logicalH) / float64(os.screenBuf.Height)
+
+ x1 := float64(r.preSelect.X-os.output.x) * scaleX
+ y1 := float64(r.preSelect.Y-os.output.y) * scaleY
+ x2 := float64(r.preSelect.X-os.output.x+r.preSelect.Width) * scaleX
+ y2 := float64(r.preSelect.Y-os.output.y+r.preSelect.Height) * scaleY
+
+ r.selection.hasSelection = true
+ r.selection.dragging = false
+ r.selection.surface = os
+ r.selection.anchorX = x1
+ r.selection.anchorY = y1
+ r.selection.currentX = x2
+ r.selection.currentY = y2
+ r.activeSurface = os
+}
+
+func (r *RegionSelector) getSourceBuffer(os *OutputSurface) *ShmBuffer {
+ if !r.showCapturedCursor && os.screenBufNoCursor != nil {
+ return os.screenBufNoCursor
+ }
+ return os.screenBuf
+}
+
+func (r *RegionSelector) redrawSurface(os *OutputSurface) {
+ srcBuf := r.getSourceBuffer(os)
+ if srcBuf == nil || !os.slotsReady {
+ return
+ }
+
+ slot := os.acquireFreeSlot()
+ if slot == nil {
+ return
+ }
+
+ slot.shm.CopyFrom(srcBuf)
+
+ // Draw overlay (dimming + selection) into this slot
+ r.drawOverlay(os, slot.shm)
+
+ if os.viewport != nil {
+ _ = os.wlSurface.SetBufferScale(1)
+ _ = os.viewport.SetSource(0, 0, float64(slot.shm.Width), float64(slot.shm.Height))
+ _ = os.viewport.SetDestination(int32(os.logicalW), int32(os.logicalH))
+ } else {
+ bufferScale := os.output.scale
+ if bufferScale <= 0 {
+ bufferScale = 1
+ }
+ _ = os.wlSurface.SetBufferScale(bufferScale)
+ }
+
+ _ = os.wlSurface.Attach(slot.wlBuf, 0, 0)
+ _ = os.wlSurface.Damage(0, 0, int32(os.logicalW), int32(os.logicalH))
+ _ = os.wlSurface.Commit()
+
+ // Mark this slot as busy until compositor releases it
+ slot.busy = true
+}
+
+func (r *RegionSelector) cleanup() {
+ if r.cursorWlBuf != nil {
+ r.cursorWlBuf.Destroy()
+ }
+ if r.cursorPool != nil {
+ r.cursorPool.Destroy()
+ }
+ if r.cursorSurface != nil {
+ r.cursorSurface.Destroy()
+ }
+ if r.cursorBuffer != nil {
+ r.cursorBuffer.Close()
+ }
+
+ for _, os := range r.surfaces {
+ for _, slot := range os.slots {
+ if slot == nil {
+ continue
+ }
+ if slot.wlBuf != nil {
+ slot.wlBuf.Destroy()
+ }
+ if slot.pool != nil {
+ slot.pool.Destroy()
+ }
+ if slot.shm != nil {
+ slot.shm.Close()
+ }
+ }
+ if os.viewport != nil {
+ os.viewport.Destroy()
+ }
+ if os.layerSurf != nil {
+ os.layerSurf.Destroy()
+ }
+ if os.wlSurface != nil {
+ os.wlSurface.Destroy()
+ }
+ if os.screenBuf != nil {
+ os.screenBuf.Close()
+ }
+ if os.screenBufNoCursor != nil {
+ os.screenBufNoCursor.Close()
+ }
+ }
+
+ if r.shortcutsInhibitor != nil {
+ _ = r.shortcutsInhibitor.Destroy()
+ }
+ if r.shortcutsInhibitMgr != nil {
+ _ = r.shortcutsInhibitMgr.Destroy()
+ }
+ if r.viewporter != nil {
+ r.viewporter.Destroy()
+ }
+ if r.screencopy != nil {
+ r.screencopy.Destroy()
+ }
+ if r.pointer != nil {
+ r.pointer.Release()
+ }
+ if r.keyboard != nil {
+ r.keyboard.Release()
+ }
+ if r.display != nil {
+ r.ctx.Close()
+ }
+}