diff --git a/core/README.md b/core/README.md index a3b5d3b6..572d24b4 100644 --- a/core/README.md +++ b/core/README.md @@ -16,6 +16,9 @@ Distribution-aware installer with TUI for deploying DMS and compositor configura **Wayland Protocols** - `wlr-gamma-control-unstable-v1` - Night mode and gamma control +- `wlr-screencopy-unstable-v1` - Screen capture for color picker +- `wlr-layer-shell-unstable-v1` - Overlay surfaces for color picker +- `wp-viewporter` - Fractional scaling support - `dwl-ipc-unstable-v2` - dwl/MangoWC workspace integration - `ext-workspace-v1` - Workspace protocol support - `wlr-output-management-unstable-v1` - Display configuration @@ -44,9 +47,24 @@ Distribution-aware installer with TUI for deploying DMS and compositor configura - `dms ipc ` - Send IPC commands (toggle launcher, notifications, etc.) - `dms plugins [install|browse|search]` - Plugin management - `dms brightness [list|set]` - Control display/monitor brightness +- `dms color pick` - Native color picker (see below) - `dms update` - Update DMS and dependencies (disabled in distro packages) - `dms greeter install` - Install greetd greeter (disabled in distro packages) +### Color Picker + +Native Wayland color picker with magnifier, no external dependencies. Supports HiDPI and fractional scaling. + +```bash +dms color pick # Pick color, output hex +dms color pick --rgb # Output as RGB (255 128 64) +dms color pick --hsv # Output as HSV (24 75% 100%) +dms color pick --json # Output all formats as JSON +dms color pick -a # Auto-copy to clipboard +``` + +The on-screen preview displays the selected format. JSON output includes hex, RGB, HSL, HSV, and CMYK values. + ## Building Requires Go 1.24+ diff --git a/core/cmd/dms/commands_colorpicker.go b/core/cmd/dms/commands_colorpicker.go new file mode 100644 index 00000000..ae38b2dd --- /dev/null +++ b/core/cmd/dms/commands_colorpicker.go @@ -0,0 +1,133 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/colorpicker" + "github.com/spf13/cobra" +) + +var ( + colorOutputFmt string + colorAutocopy bool + colorNotify bool + colorLowercase bool +) + +var colorCmd = &cobra.Command{ + Use: "color", + Short: "Color utilities", + Long: "Color utilities including picking colors from the screen", +} + +var colorPickCmd = &cobra.Command{ + Use: "pick", + Short: "Pick a color from the screen", + Long: `Pick a color from anywhere on your screen using an interactive color picker. + +Click on any pixel to capture its color, or press Escape to cancel. + +Output format flags (mutually exclusive, default: --hex): + --hex - Hexadecimal (#RRGGBB) + --rgb - RGB values (R G B) + --hsl - HSL values (H S% L%) + --hsv - HSV values (H S% V%) + --cmyk - CMYK values (C% M% Y% K%) + --json - JSON with all formats + +Examples: + dms color pick # Pick color, output as hex + dms color pick --rgb # Output as RGB + dms color pick --json # Output all formats as JSON + dms color pick --hex -l # Output hex in lowercase + dms color pick -a # Auto-copy result to clipboard`, + Run: runColorPick, +} + +func init() { + colorPickCmd.Flags().Bool("hex", false, "Output as hexadecimal (#RRGGBB)") + colorPickCmd.Flags().Bool("rgb", false, "Output as RGB (R G B)") + colorPickCmd.Flags().Bool("hsl", false, "Output as HSL (H S% L%)") + colorPickCmd.Flags().Bool("hsv", false, "Output as HSV (H S% V%)") + colorPickCmd.Flags().Bool("cmyk", false, "Output as CMYK (C% M% Y% K%)") + colorPickCmd.Flags().Bool("json", false, "Output all formats as JSON") + colorPickCmd.Flags().StringVarP(&colorOutputFmt, "output-format", "o", "", "Custom output format template") + colorPickCmd.Flags().BoolVarP(&colorAutocopy, "autocopy", "a", false, "Copy result to clipboard") + colorPickCmd.Flags().BoolVarP(&colorLowercase, "lowercase", "l", false, "Output hex in lowercase") + + colorPickCmd.MarkFlagsMutuallyExclusive("hex", "rgb", "hsl", "hsv", "cmyk", "json") + + colorCmd.AddCommand(colorPickCmd) +} + +func runColorPick(cmd *cobra.Command, args []string) { + format := colorpicker.FormatHex // default + jsonOutput, _ := cmd.Flags().GetBool("json") + + if rgb, _ := cmd.Flags().GetBool("rgb"); rgb { + format = colorpicker.FormatRGB + } else if hsl, _ := cmd.Flags().GetBool("hsl"); hsl { + format = colorpicker.FormatHSL + } else if hsv, _ := cmd.Flags().GetBool("hsv"); hsv { + format = colorpicker.FormatHSV + } else if cmyk, _ := cmd.Flags().GetBool("cmyk"); cmyk { + format = colorpicker.FormatCMYK + } + + config := colorpicker.Config{ + Format: format, + CustomFormat: colorOutputFmt, + Lowercase: colorLowercase, + Autocopy: colorAutocopy, + Notify: colorNotify, + } + + picker := colorpicker.New(config) + color, err := picker.Run() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + if color == nil { + os.Exit(0) + } + + var output string + if jsonOutput { + jsonStr, err := color.ToJSON() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + output = jsonStr + } else { + output = color.Format(config.Format, config.Lowercase, config.CustomFormat) + } + + if colorAutocopy { + copyToClipboard(output) + } + + if jsonOutput { + fmt.Println(output) + } else if color.IsDark() { + fmt.Printf("\033[48;2;%d;%d;%dm\033[97m %s \033[0m\n", color.R, color.G, color.B, output) + } else { + fmt.Printf("\033[48;2;%d;%d;%dm\033[30m %s \033[0m\n", color.R, color.G, color.B, output) + } +} + +func copyToClipboard(text string) { + var cmd *exec.Cmd + if _, err := exec.LookPath("wl-copy"); err == nil { + cmd = exec.Command("wl-copy", text) + } else { + fmt.Fprintln(os.Stderr, "wl-copy not found, cannot copy to clipboard") + return + } + + _ = cmd.Run() +} diff --git a/core/cmd/dms/commands_common.go b/core/cmd/dms/commands_common.go index eccc3043..d1dc23e0 100644 --- a/core/cmd/dms/commands_common.go +++ b/core/cmd/dms/commands_common.go @@ -471,5 +471,6 @@ func getCommonCommands() []*cobra.Command { keybindsCmd, greeterCmd, setupCmd, + colorCmd, } } diff --git a/core/internal/colorpicker/color.go b/core/internal/colorpicker/color.go new file mode 100644 index 00000000..461730a9 --- /dev/null +++ b/core/internal/colorpicker/color.go @@ -0,0 +1,306 @@ +package colorpicker + +import ( + "encoding/json" + "fmt" + "math" + "strings" +) + +type Color struct { + R, G, B, A uint8 +} + +type OutputFormat int + +const ( + FormatHex OutputFormat = iota + FormatRGB + FormatHSL + FormatHSV + FormatCMYK +) + +func ParseFormat(s string) OutputFormat { + switch strings.ToLower(s) { + case "rgb": + return FormatRGB + case "hsl": + return FormatHSL + case "hsv": + return FormatHSV + case "cmyk": + return FormatCMYK + default: + return FormatHex + } +} + +func (c Color) ToHex(lowercase bool) string { + if lowercase { + return fmt.Sprintf("#%02x%02x%02x", c.R, c.G, c.B) + } + return fmt.Sprintf("#%02X%02X%02X", c.R, c.G, c.B) +} + +func (c Color) ToRGB() string { + return fmt.Sprintf("%d %d %d", c.R, c.G, c.B) +} + +func (c Color) ToHSL() string { + h, s, l := rgbToHSL(c.R, c.G, c.B) + return fmt.Sprintf("%d %d%% %d%%", h, s, l) +} + +func (c Color) ToHSV() string { + h, s, v := rgbToHSV(c.R, c.G, c.B) + return fmt.Sprintf("%d %d%% %d%%", h, s, v) +} + +func (c Color) ToCMYK() string { + cy, m, y, k := rgbToCMYK(c.R, c.G, c.B) + return fmt.Sprintf("%d%% %d%% %d%% %d%%", cy, m, y, k) +} + +func (c Color) Format(format OutputFormat, lowercase bool, customFmt string) string { + if customFmt != "" { + return c.formatCustom(format, customFmt) + } + + switch format { + case FormatRGB: + return c.ToRGB() + case FormatHSL: + return c.ToHSL() + case FormatHSV: + return c.ToHSV() + case FormatCMYK: + return c.ToCMYK() + default: + return c.ToHex(lowercase) + } +} + +func (c Color) formatCustom(format OutputFormat, customFmt string) string { + switch format { + case FormatRGB: + return replaceArgs(customFmt, c.R, c.G, c.B) + case FormatHSL: + h, s, l := rgbToHSL(c.R, c.G, c.B) + return replaceArgs(customFmt, h, s, l) + case FormatHSV: + h, s, v := rgbToHSV(c.R, c.G, c.B) + return replaceArgs(customFmt, h, s, v) + case FormatCMYK: + cy, m, y, k := rgbToCMYK(c.R, c.G, c.B) + return replaceArgs4(customFmt, cy, m, y, k) + default: + if strings.Contains(customFmt, "{0}") { + r := fmt.Sprintf("%02X", c.R) + g := fmt.Sprintf("%02X", c.G) + b := fmt.Sprintf("%02X", c.B) + return replaceArgsStr(customFmt, r, g, b) + } + return c.ToHex(false) + } +} + +func replaceArgs[T any](format string, a, b, c T) string { + result := format + result = strings.ReplaceAll(result, "{0}", fmt.Sprintf("%v", a)) + result = strings.ReplaceAll(result, "{1}", fmt.Sprintf("%v", b)) + result = strings.ReplaceAll(result, "{2}", fmt.Sprintf("%v", c)) + return result +} + +func replaceArgs4[T any](format string, a, b, c, d T) string { + result := format + result = strings.ReplaceAll(result, "{0}", fmt.Sprintf("%v", a)) + result = strings.ReplaceAll(result, "{1}", fmt.Sprintf("%v", b)) + result = strings.ReplaceAll(result, "{2}", fmt.Sprintf("%v", c)) + result = strings.ReplaceAll(result, "{3}", fmt.Sprintf("%v", d)) + return result +} + +func replaceArgsStr(format, a, b, c string) string { + result := format + result = strings.ReplaceAll(result, "{0}", a) + result = strings.ReplaceAll(result, "{1}", b) + result = strings.ReplaceAll(result, "{2}", c) + return result +} + +func rgbToHSL(r, g, b uint8) (int, int, int) { + rf := float64(r) / 255.0 + gf := float64(g) / 255.0 + bf := float64(b) / 255.0 + + maxVal := math.Max(rf, math.Max(gf, bf)) + minVal := math.Min(rf, math.Min(gf, bf)) + l := (maxVal + minVal) / 2 + + if maxVal == minVal { + return 0, 0, int(math.Round(l * 100)) + } + + d := maxVal - minVal + var s float64 + if l > 0.5 { + s = d / (2 - maxVal - minVal) + } else { + s = d / (maxVal + minVal) + } + + var h float64 + switch maxVal { + case rf: + h = (gf - bf) / d + if gf < bf { + h += 6 + } + case gf: + h = (bf-rf)/d + 2 + case bf: + h = (rf-gf)/d + 4 + } + h /= 6 + + return int(math.Round(h * 360)), int(math.Round(s * 100)), int(math.Round(l * 100)) +} + +func rgbToHSV(r, g, b uint8) (int, int, int) { + rf := float64(r) / 255.0 + gf := float64(g) / 255.0 + bf := float64(b) / 255.0 + + maxVal := math.Max(rf, math.Max(gf, bf)) + minVal := math.Min(rf, math.Min(gf, bf)) + v := maxVal + d := maxVal - minVal + + var s float64 + if maxVal != 0 { + s = d / maxVal + } + + if maxVal == minVal { + return 0, int(math.Round(s * 100)), int(math.Round(v * 100)) + } + + var h float64 + switch maxVal { + case rf: + h = (gf - bf) / d + if gf < bf { + h += 6 + } + case gf: + h = (bf-rf)/d + 2 + case bf: + h = (rf-gf)/d + 4 + } + h /= 6 + + return int(math.Round(h * 360)), int(math.Round(s * 100)), int(math.Round(v * 100)) +} + +func rgbToCMYK(r, g, b uint8) (int, int, int, int) { + if r == 0 && g == 0 && b == 0 { + return 0, 0, 0, 100 + } + + rf := float64(r) / 255.0 + gf := float64(g) / 255.0 + bf := float64(b) / 255.0 + + k := 1 - math.Max(rf, math.Max(gf, bf)) + c := (1 - rf - k) / (1 - k) + m := (1 - gf - k) / (1 - k) + y := (1 - bf - k) / (1 - k) + + return int(math.Round(c * 100)), int(math.Round(m * 100)), int(math.Round(y * 100)), int(math.Round(k * 100)) +} + +func (c Color) Luminance() float64 { + r := float64(c.R) / 255.0 + g := float64(c.G) / 255.0 + b := float64(c.B) / 255.0 + + if r <= 0.03928 { + r = r / 12.92 + } else { + r = math.Pow((r+0.055)/1.055, 2.4) + } + + if g <= 0.03928 { + g = g / 12.92 + } else { + g = math.Pow((g+0.055)/1.055, 2.4) + } + + if b <= 0.03928 { + b = b / 12.92 + } else { + b = math.Pow((b+0.055)/1.055, 2.4) + } + + return 0.2126*r + 0.7152*g + 0.0722*b +} + +func (c Color) IsDark() bool { + return c.Luminance() < 0.179 +} + +type ColorJSON struct { + Hex string `json:"hex"` + RGB struct { + R int `json:"r"` + G int `json:"g"` + B int `json:"b"` + } `json:"rgb"` + HSL struct { + H int `json:"h"` + S int `json:"s"` + L int `json:"l"` + } `json:"hsl"` + HSV struct { + H int `json:"h"` + S int `json:"s"` + V int `json:"v"` + } `json:"hsv"` + CMYK struct { + C int `json:"c"` + M int `json:"m"` + Y int `json:"y"` + K int `json:"k"` + } `json:"cmyk"` +} + +func (c Color) ToJSON() (string, error) { + h, s, l := rgbToHSL(c.R, c.G, c.B) + hv, sv, v := rgbToHSV(c.R, c.G, c.B) + cy, m, y, k := rgbToCMYK(c.R, c.G, c.B) + + data := ColorJSON{ + Hex: c.ToHex(false), + } + data.RGB.R = int(c.R) + data.RGB.G = int(c.G) + data.RGB.B = int(c.B) + data.HSL.H = h + data.HSL.S = s + data.HSL.L = l + data.HSV.H = hv + data.HSV.S = sv + data.HSV.V = v + data.CMYK.C = cy + data.CMYK.M = m + data.CMYK.Y = y + data.CMYK.K = k + + bytes, err := json.MarshalIndent(data, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/core/internal/colorpicker/picker.go b/core/internal/colorpicker/picker.go new file mode 100644 index 00000000..b871f304 --- /dev/null +++ b/core/internal/colorpicker/picker.go @@ -0,0 +1,653 @@ +package colorpicker + +import ( + "fmt" + "math" + "sync" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/log" + "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" + "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" +) + +type Config struct { + Format OutputFormat + CustomFormat string + Lowercase bool + Autocopy bool + Notify bool +} + +type Output struct { + wlOutput *client.Output + name string + globalName uint32 + x, y int32 + width int32 + height int32 + scale int32 + fractionalScale float64 +} + +type LayerSurface struct { + output *Output + state *SurfaceState + wlSurface *client.Surface + layerSurf *wlr_layer_shell.ZwlrLayerSurfaceV1 + viewport *wp_viewporter.WpViewport + wlPool *client.ShmPool + wlBuffer *client.Buffer + configured bool +} + +type Picker struct { + config Config + + 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 + + outputs map[uint32]*Output + outputsMu sync.Mutex + + surfaces []*LayerSurface + activeSurface *LayerSurface + + running bool + pickedColor *Color + err error +} + +func New(config Config) *Picker { + return &Picker{ + config: config, + outputs: make(map[uint32]*Output), + } +} + +func (p *Picker) Run() (*Color, error) { + if err := p.connect(); err != nil { + return nil, fmt.Errorf("wayland connect: %w", err) + } + defer p.cleanup() + + if err := p.setupRegistry(); err != nil { + return nil, fmt.Errorf("registry setup: %w", err) + } + + if err := p.roundtrip(); err != nil { + return nil, fmt.Errorf("roundtrip: %w", err) + } + + if p.screencopy == nil { + return nil, fmt.Errorf("compositor does not support wlr-screencopy-unstable-v1") + } + + if p.layerShell == nil { + return nil, fmt.Errorf("compositor does not support wlr-layer-shell-unstable-v1") + } + + if p.seat == nil { + return nil, fmt.Errorf("no seat available") + } + + if err := p.roundtrip(); err != nil { + return nil, fmt.Errorf("roundtrip: %w", err) + } + + if err := p.createSurfaces(); err != nil { + return nil, fmt.Errorf("create surfaces: %w", err) + } + + if err := p.roundtrip(); err != nil { + return nil, fmt.Errorf("roundtrip: %w", err) + } + + p.running = true + for p.running { + if err := p.ctx.Dispatch(); err != nil { + p.err = err + break + } + + p.checkDone() + } + + if p.err != nil { + return nil, p.err + } + + return p.pickedColor, nil +} + +func (p *Picker) checkDone() { + for _, ls := range p.surfaces { + picked, cancelled := ls.state.IsDone() + switch { + case cancelled: + p.running = false + return + case picked: + color, ok := ls.state.PickColor() + if ok { + p.pickedColor = &color + } + p.running = false + return + } + } +} + +func (p *Picker) connect() error { + display, err := client.Connect("") + if err != nil { + return err + } + p.display = display + p.ctx = display.Context() + return nil +} + +func (p *Picker) roundtrip() error { + callback, err := p.display.Sync() + if err != nil { + return err + } + + done := make(chan struct{}) + callback.SetDoneHandler(func(e client.CallbackDoneEvent) { + close(done) + }) + + for { + select { + case <-done: + return nil + default: + if err := p.ctx.Dispatch(); err != nil { + return err + } + } + } +} + +func (p *Picker) setupRegistry() error { + registry, err := p.display.GetRegistry() + if err != nil { + return err + } + p.registry = registry + + registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) { + p.handleGlobal(e) + }) + + registry.SetGlobalRemoveHandler(func(e client.RegistryGlobalRemoveEvent) { + p.outputsMu.Lock() + delete(p.outputs, e.Name) + p.outputsMu.Unlock() + }) + + return nil +} + +func (p *Picker) handleGlobal(e client.RegistryGlobalEvent) { + switch e.Interface { + case client.CompositorInterfaceName: + compositor := client.NewCompositor(p.ctx) + if err := p.registry.Bind(e.Name, e.Interface, e.Version, compositor); err == nil { + p.compositor = compositor + } + + case client.ShmInterfaceName: + shm := client.NewShm(p.ctx) + if err := p.registry.Bind(e.Name, e.Interface, e.Version, shm); err == nil { + p.shm = shm + } + + case client.SeatInterfaceName: + seat := client.NewSeat(p.ctx) + if err := p.registry.Bind(e.Name, e.Interface, e.Version, seat); err == nil { + p.seat = seat + p.setupInput() + } + + case client.OutputInterfaceName: + output := client.NewOutput(p.ctx) + version := e.Version + if version > 4 { + version = 4 + } + if err := p.registry.Bind(e.Name, e.Interface, version, output); err == nil { + p.outputsMu.Lock() + p.outputs[e.Name] = &Output{ + wlOutput: output, + globalName: e.Name, + scale: 1, + fractionalScale: 1.0, + } + p.outputsMu.Unlock() + p.setupOutputHandlers(e.Name, output) + } + + case wlr_layer_shell.ZwlrLayerShellV1InterfaceName: + layerShell := wlr_layer_shell.NewZwlrLayerShellV1(p.ctx) + version := e.Version + if version > 4 { + version = 4 + } + if err := p.registry.Bind(e.Name, e.Interface, version, layerShell); err == nil { + p.layerShell = layerShell + } + + case wlr_screencopy.ZwlrScreencopyManagerV1InterfaceName: + screencopy := wlr_screencopy.NewZwlrScreencopyManagerV1(p.ctx) + version := e.Version + if version > 3 { + version = 3 + } + if err := p.registry.Bind(e.Name, e.Interface, version, screencopy); err == nil { + p.screencopy = screencopy + } + + case wp_viewporter.WpViewporterInterfaceName: + viewporter := wp_viewporter.NewWpViewporter(p.ctx) + if err := p.registry.Bind(e.Name, e.Interface, e.Version, viewporter); err == nil { + p.viewporter = viewporter + } + } +} + +func (p *Picker) setupOutputHandlers(name uint32, output *client.Output) { + output.SetGeometryHandler(func(e client.OutputGeometryEvent) { + p.outputsMu.Lock() + if o, ok := p.outputs[name]; ok { + o.x = e.X + o.y = e.Y + } + p.outputsMu.Unlock() + }) + + output.SetModeHandler(func(e client.OutputModeEvent) { + if e.Flags&uint32(client.OutputModeCurrent) == 0 { + return + } + p.outputsMu.Lock() + if o, ok := p.outputs[name]; ok { + o.width = e.Width + o.height = e.Height + } + p.outputsMu.Unlock() + }) + + output.SetScaleHandler(func(e client.OutputScaleEvent) { + p.outputsMu.Lock() + if o, ok := p.outputs[name]; ok { + o.scale = e.Factor + o.fractionalScale = float64(e.Factor) + } + p.outputsMu.Unlock() + }) + + output.SetNameHandler(func(e client.OutputNameEvent) { + p.outputsMu.Lock() + if o, ok := p.outputs[name]; ok { + o.name = e.Name + } + p.outputsMu.Unlock() + }) +} + +func (p *Picker) createSurfaces() error { + p.outputsMu.Lock() + outputs := make([]*Output, 0, len(p.outputs)) + for _, o := range p.outputs { + outputs = append(outputs, o) + } + p.outputsMu.Unlock() + + for _, output := range outputs { + ls, err := p.createLayerSurface(output) + if err != nil { + return fmt.Errorf("output %s: %w", output.name, err) + } + p.surfaces = append(p.surfaces, ls) + } + + return nil +} + +func (p *Picker) createLayerSurface(output *Output) (*LayerSurface, error) { + surface, err := p.compositor.CreateSurface() + if err != nil { + return nil, fmt.Errorf("create surface: %w", err) + } + + layerSurf, err := p.layerShell.GetLayerSurface( + surface, + output.wlOutput, + uint32(wlr_layer_shell.ZwlrLayerShellV1LayerOverlay), + "dms-colorpicker", + ) + if err != nil { + return nil, fmt.Errorf("get layer surface: %w", err) + } + + ls := &LayerSurface{ + output: output, + state: NewSurfaceState(p.config.Format, p.config.Lowercase), + wlSurface: surface, + layerSurf: layerSurf, + } + + if p.viewporter != nil { + vp, err := p.viewporter.GetViewport(surface) + if err == nil { + ls.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 { + log.Warn("failed to set layer anchor", "err", err) + } + if err := layerSurf.SetExclusiveZone(-1); err != nil { + log.Warn("failed to set exclusive zone", "err", err) + } + if err := layerSurf.SetKeyboardInteractivity(uint32(wlr_layer_shell.ZwlrLayerSurfaceV1KeyboardInteractivityExclusive)); err != nil { + log.Warn("failed to set keyboard interactivity", "err", err) + } + + layerSurf.SetConfigureHandler(func(e wlr_layer_shell.ZwlrLayerSurfaceV1ConfigureEvent) { + if err := layerSurf.AckConfigure(e.Serial); err != nil { + log.Warn("failed to ack configure", "err", err) + } + if err := ls.state.OnLayerConfigure(int(e.Width), int(e.Height)); err != nil { + log.Warn("failed to handle layer configure", "err", err) + } + ls.configured = true + + scale := p.computeSurfaceScale(ls) + ls.state.SetScale(scale) + + if !ls.state.IsReady() { + p.captureForSurface(ls) + } else { + p.redrawSurface(ls) + } + }) + + layerSurf.SetClosedHandler(func(e wlr_layer_shell.ZwlrLayerSurfaceV1ClosedEvent) { + p.running = false + }) + + if err := surface.Commit(); err != nil { + log.Warn("failed to commit surface", "err", err) + } + return ls, nil +} + +func (p *Picker) computeSurfaceScale(ls *LayerSurface) int32 { + out := ls.output + if out == nil || out.fractionalScale <= 0 { + return 1 + } + + scale := int32(math.Ceil(out.fractionalScale)) + if scale <= 0 { + scale = 1 + } + return scale +} + +func (p *Picker) captureForSurface(ls *LayerSurface) { + frame, err := p.screencopy.CaptureOutput(0, ls.output.wlOutput) + if err != nil { + return + } + + frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) { + if err := ls.state.OnScreencopyBuffer(PixelFormat(e.Format), int(e.Width), int(e.Height), int(e.Stride)); err != nil { + log.Error("failed to create screencopy buffer", "err", err) + } + }) + + frame.SetBufferDoneHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferDoneEvent) { + screenBuf := ls.state.ScreenBuffer() + if screenBuf == nil { + return + } + + pool, err := p.shm.CreatePool(screenBuf.Fd(), int32(screenBuf.Size())) + if err != nil { + return + } + + wlBuffer, err := pool.CreateBuffer(0, int32(screenBuf.Width), int32(screenBuf.Height), int32(screenBuf.Stride), uint32(ls.state.screenFormat)) + if err != nil { + pool.Destroy() + return + } + + if err := frame.Copy(wlBuffer); err != nil { + log.Error("failed to copy frame", "err", err) + } + pool.Destroy() + }) + + frame.SetFlagsHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1FlagsEvent) { + ls.state.OnScreencopyFlags(e.Flags) + }) + + frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) { + ls.state.OnScreencopyReady() + scale := p.computeSurfaceScale(ls) + ls.state.SetScale(scale) + frame.Destroy() + p.redrawSurface(ls) + }) + + frame.SetFailedHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1FailedEvent) { + frame.Destroy() + }) +} + +func (p *Picker) redrawSurface(ls *LayerSurface) { + renderBuf := ls.state.Redraw() + if renderBuf == nil { + return + } + + if ls.wlPool != nil { + ls.wlPool.Destroy() + ls.wlPool = nil + } + if ls.wlBuffer != nil { + ls.wlBuffer.Destroy() + ls.wlBuffer = nil + } + + pool, err := p.shm.CreatePool(renderBuf.Fd(), int32(renderBuf.Size())) + if err != nil { + return + } + ls.wlPool = pool + + wlBuffer, err := pool.CreateBuffer(0, int32(renderBuf.Width), int32(renderBuf.Height), int32(renderBuf.Stride), uint32(FormatARGB8888)) + if err != nil { + return + } + ls.wlBuffer = wlBuffer + + logicalW, logicalH := ls.state.LogicalSize() + if logicalW == 0 || logicalH == 0 { + logicalW = int(ls.output.width) + logicalH = int(ls.output.height) + } + + scale := ls.state.Scale() + if scale <= 0 { + scale = 1 + } + + if ls.viewport != nil { + srcW := float64(renderBuf.Width) / float64(scale) + srcH := float64(renderBuf.Height) / float64(scale) + if err := ls.viewport.SetSource(0, 0, srcW, srcH); err != nil { + log.Warn("failed to set viewport source", "err", err) + } + if err := ls.viewport.SetDestination(int32(logicalW), int32(logicalH)); err != nil { + log.Warn("failed to set viewport destination", "err", err) + } + if err := ls.wlSurface.SetBufferScale(scale); err != nil { + log.Warn("failed to set buffer scale", "err", err) + } + } else { + if err := ls.wlSurface.SetBufferScale(scale); err != nil { + log.Warn("failed to set buffer scale", "err", err) + } + } + + if err := ls.wlSurface.Attach(wlBuffer, 0, 0); err != nil { + log.Warn("failed to attach buffer", "err", err) + } + if err := ls.wlSurface.Damage(0, 0, int32(logicalW), int32(logicalH)); err != nil { + log.Warn("failed to damage surface", "err", err) + } + if err := ls.wlSurface.Commit(); err != nil { + log.Warn("failed to commit surface", "err", err) + } + + ls.state.SwapBuffers() +} + +func (p *Picker) setupInput() { + if p.seat == nil { + return + } + + p.seat.SetCapabilitiesHandler(func(e client.SeatCapabilitiesEvent) { + if e.Capabilities&uint32(client.SeatCapabilityPointer) != 0 && p.pointer == nil { + pointer, err := p.seat.GetPointer() + if err == nil { + p.pointer = pointer + p.setupPointerHandlers() + } + } + if e.Capabilities&uint32(client.SeatCapabilityKeyboard) != 0 && p.keyboard == nil { + keyboard, err := p.seat.GetKeyboard() + if err == nil { + p.keyboard = keyboard + p.setupKeyboardHandlers() + } + } + }) +} + +func (p *Picker) setupPointerHandlers() { + p.pointer.SetEnterHandler(func(e client.PointerEnterEvent) { + if err := p.pointer.SetCursor(e.Serial, nil, 0, 0); err != nil { + log.Debug("failed to hide cursor", "err", err) + } + + p.activeSurface = nil + for _, ls := range p.surfaces { + if ls.wlSurface.ID() == e.Surface.ID() { + p.activeSurface = ls + break + } + } + if p.activeSurface == nil { + return + } + p.activeSurface.state.OnPointerMotion(e.SurfaceX, e.SurfaceY) + p.redrawSurface(p.activeSurface) + }) + + p.pointer.SetLeaveHandler(func(e client.PointerLeaveEvent) { + if p.activeSurface != nil && p.activeSurface.wlSurface.ID() == e.Surface.ID() { + p.activeSurface = nil + } + }) + + p.pointer.SetMotionHandler(func(e client.PointerMotionEvent) { + if p.activeSurface == nil { + return + } + p.activeSurface.state.OnPointerMotion(e.SurfaceX, e.SurfaceY) + p.redrawSurface(p.activeSurface) + }) + + p.pointer.SetButtonHandler(func(e client.PointerButtonEvent) { + if p.activeSurface == nil { + return + } + p.activeSurface.state.OnPointerButton(e.Button, e.State) + }) +} + +func (p *Picker) setupKeyboardHandlers() { + p.keyboard.SetKeyHandler(func(e client.KeyboardKeyEvent) { + for _, ls := range p.surfaces { + ls.state.OnKey(e.Key, e.State) + } + }) +} + +func (p *Picker) cleanup() { + for _, ls := range p.surfaces { + if ls.wlBuffer != nil { + ls.wlBuffer.Destroy() + } + if ls.wlPool != nil { + ls.wlPool.Destroy() + } + if ls.viewport != nil { + ls.viewport.Destroy() + } + if ls.layerSurf != nil { + ls.layerSurf.Destroy() + } + if ls.wlSurface != nil { + ls.wlSurface.Destroy() + } + if ls.state != nil { + ls.state.Destroy() + } + } + + if p.viewporter != nil { + p.viewporter.Destroy() + } + + if p.screencopy != nil { + p.screencopy.Destroy() + } + + if p.pointer != nil { + p.pointer.Release() + } + + if p.keyboard != nil { + p.keyboard.Release() + } + + if p.display != nil { + p.ctx.Close() + } +} diff --git a/core/internal/colorpicker/shm.go b/core/internal/colorpicker/shm.go new file mode 100644 index 00000000..2c94b8dc --- /dev/null +++ b/core/internal/colorpicker/shm.go @@ -0,0 +1,93 @@ +package colorpicker + +import ( + "fmt" + + "golang.org/x/sys/unix" +) + +type ShmBuffer struct { + fd int + data []byte + size int + Width int + Height int + Stride int +} + +func CreateShmBuffer(width, height, stride int) (*ShmBuffer, error) { + size := stride * height + + fd, err := unix.MemfdCreate("dms-colorpicker", 0) + if err != nil { + return nil, fmt.Errorf("failed to create memfd: %w", err) + } + + if err := unix.Ftruncate(fd, int64(size)); err != nil { + unix.Close(fd) + return nil, fmt.Errorf("ftruncate failed: %w", err) + } + + data, err := unix.Mmap(fd, 0, size, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED) + if err != nil { + unix.Close(fd) + return nil, fmt.Errorf("mmap failed: %w", err) + } + + return &ShmBuffer{ + fd: fd, + data: data, + size: size, + Width: width, + Height: height, + Stride: stride, + }, nil +} + +func (s *ShmBuffer) Fd() int { + return s.fd +} + +func (s *ShmBuffer) Size() int { + return s.size +} + +func (s *ShmBuffer) Data() []byte { + return s.data +} + +func (s *ShmBuffer) GetPixel(x, y int) Color { + if x < 0 || x >= s.Width || y < 0 || y >= s.Height { + return Color{} + } + + offset := y*s.Stride + x*4 + + if offset+3 >= len(s.data) { + return Color{} + } + + return Color{ + B: s.data[offset], + G: s.data[offset+1], + R: s.data[offset+2], + A: s.data[offset+3], + } +} + +func (s *ShmBuffer) Close() error { + var firstErr error + if s.data != nil { + if err := unix.Munmap(s.data); err != nil && firstErr == nil { + firstErr = fmt.Errorf("munmap failed: %w", err) + } + s.data = nil + } + if s.fd >= 0 { + if err := unix.Close(s.fd); err != nil && firstErr == nil { + firstErr = fmt.Errorf("close fd failed: %w", err) + } + s.fd = -1 + } + return firstErr +} diff --git a/core/internal/colorpicker/state.go b/core/internal/colorpicker/state.go new file mode 100644 index 00000000..fce7755d --- /dev/null +++ b/core/internal/colorpicker/state.go @@ -0,0 +1,1092 @@ +package colorpicker + +import ( + "math" + "strings" + "sync" +) + +type PixelFormat uint32 + +const ( + FormatARGB8888 PixelFormat = 0 + FormatXRGB8888 PixelFormat = 1 + FormatABGR8888 PixelFormat = 0x34324241 + FormatXBGR8888 PixelFormat = 0x34324258 +) + +type SurfaceState struct { + mu sync.Mutex + + screenBuf *ShmBuffer + screenFormat PixelFormat + yInverted bool + + logicalW int + logicalH int + + renderBufs [2]*ShmBuffer + front int + + scale int32 + scaleX float64 + scaleY float64 + + pointerX int + pointerY int + + displayFormat OutputFormat + lowercase bool + + readyForDisplay bool + colorPicked bool + cancelled bool +} + +func NewSurfaceState(format OutputFormat, lowercase bool) *SurfaceState { + return &SurfaceState{ + scale: 1, + displayFormat: format, + lowercase: lowercase, + } +} + +func (s *SurfaceState) SetScale(scale int32) { + s.mu.Lock() + defer s.mu.Unlock() + + if scale <= 0 { + scale = 1 + } + s.scale = scale +} + +func (s *SurfaceState) Scale() int32 { + s.mu.Lock() + defer s.mu.Unlock() + return s.scale +} + +func (s *SurfaceState) LogicalSize() (int, int) { + s.mu.Lock() + defer s.mu.Unlock() + return s.logicalW, s.logicalH +} + +func (s *SurfaceState) OnScreencopyBuffer(format PixelFormat, width, height, stride int) error { + s.mu.Lock() + defer s.mu.Unlock() + + if s.screenBuf != nil { + s.screenBuf.Close() + s.screenBuf = nil + } + + buf, err := CreateShmBuffer(width, height, stride) + if err != nil { + return err + } + + s.screenBuf = buf + s.screenFormat = format + return nil +} + +func (s *SurfaceState) ScreenBuffer() *ShmBuffer { + s.mu.Lock() + defer s.mu.Unlock() + return s.screenBuf +} + +func (s *SurfaceState) OnScreencopyFlags(flags uint32) { + s.mu.Lock() + s.yInverted = (flags & 1) != 0 + s.mu.Unlock() +} + +func (s *SurfaceState) OnScreencopyReady() { + s.mu.Lock() + defer s.mu.Unlock() + + if s.screenBuf == nil || s.logicalW == 0 || s.logicalH == 0 { + return + } + + s.recomputeScale() + s.ensureRenderBuffers() + s.readyForDisplay = true +} + +func (s *SurfaceState) OnLayerConfigure(width, height int) error { + s.mu.Lock() + defer s.mu.Unlock() + + if width <= 0 || height <= 0 { + return nil + } + + if s.logicalW == width && s.logicalH == height { + return nil + } + + s.logicalW = width + s.logicalH = height + + s.recomputeScale() + s.ensureRenderBuffers() + + return nil +} + +func (s *SurfaceState) recomputeScale() { + if s.screenBuf == nil || s.logicalW == 0 || s.logicalH == 0 { + s.scaleX = 1 + s.scaleY = 1 + return + } + s.scaleX = float64(s.screenBuf.Width) / float64(s.logicalW) + s.scaleY = float64(s.screenBuf.Height) / float64(s.logicalH) +} + +func (s *SurfaceState) ensureRenderBuffers() { + if s.screenBuf == nil { + return + } + + width := s.screenBuf.Width + height := s.screenBuf.Height + stride := s.screenBuf.Stride + + for i := range s.renderBufs { + buf := s.renderBufs[i] + if buf != nil { + if buf.Width == width && buf.Height == height && buf.Stride == stride { + continue + } + buf.Close() + s.renderBufs[i] = nil + } + + newBuf, err := CreateShmBuffer(width, height, stride) + if err != nil { + continue + } + s.renderBufs[i] = newBuf + } +} + +func (s *SurfaceState) OnPointerMotion(x, y float64) { + s.mu.Lock() + s.pointerX = int(x) + s.pointerY = int(y) + s.mu.Unlock() +} + +func (s *SurfaceState) OnPointerButton(button, state uint32) { + if state != 1 { + return + } + + s.mu.Lock() + defer s.mu.Unlock() + + switch button { + case 0x110: // BTN_LEFT + if s.readyForDisplay && s.screenBuf != nil { + s.colorPicked = true + } + } +} + +func (s *SurfaceState) OnKey(key, state uint32) { + if state != 1 { + return + } + + s.mu.Lock() + defer s.mu.Unlock() + + switch key { + case 1: // KEY_ESC + s.cancelled = true + case 28: // KEY_ENTER + if s.readyForDisplay && s.screenBuf != nil { + s.colorPicked = true + } + } +} + +func (s *SurfaceState) IsDone() (picked, cancelled bool) { + s.mu.Lock() + defer s.mu.Unlock() + return s.colorPicked, s.cancelled +} + +func (s *SurfaceState) IsReady() bool { + s.mu.Lock() + defer s.mu.Unlock() + return s.readyForDisplay +} + +func (s *SurfaceState) FrontRenderBuffer() *ShmBuffer { + s.mu.Lock() + defer s.mu.Unlock() + return s.renderBufs[s.front] +} + +func (s *SurfaceState) SwapBuffers() { + s.mu.Lock() + s.front ^= 1 + s.mu.Unlock() +} + +func (s *SurfaceState) Redraw() *ShmBuffer { + s.mu.Lock() + defer s.mu.Unlock() + + if !s.readyForDisplay || s.screenBuf == nil { + return nil + } + + dst := s.renderBufs[s.front] + if dst == nil { + return nil + } + + copy(dst.data, s.screenBuf.data) + + px := int(math.Round(float64(s.pointerX) * s.scaleX)) + py := int(math.Round(float64(s.pointerY) * s.scaleY)) + + px = clamp(px, 0, dst.Width-1) + py = clamp(py, 0, dst.Height-1) + + picked := s.screenBuf.GetPixel(px, py) + + drawMagnifier( + dst.data, dst.Stride, dst.Width, dst.Height, + s.screenBuf.data, s.screenBuf.Stride, s.screenBuf.Width, s.screenBuf.Height, + px, py, picked, + ) + + drawColorPreview(dst.data, dst.Stride, dst.Width, dst.Height, px, py, picked, s.displayFormat, s.lowercase) + + return dst +} + +func (s *SurfaceState) PickColor() (Color, bool) { + s.mu.Lock() + defer s.mu.Unlock() + + if s.screenBuf == nil { + return Color{}, false + } + + sx := int(math.Round(float64(s.pointerX) * s.scaleX)) + sy := int(math.Round(float64(s.pointerY) * s.scaleY)) + + sx = clamp(sx, 0, s.screenBuf.Width-1) + sy = clamp(sy, 0, s.screenBuf.Height-1) + + if s.yInverted { + sy = s.screenBuf.Height - 1 - sy + } + + return s.screenBuf.GetPixel(sx, sy), true +} + +func (s *SurfaceState) Destroy() { + s.mu.Lock() + defer s.mu.Unlock() + + if s.screenBuf != nil { + s.screenBuf.Close() + s.screenBuf = nil + } + + for i := range s.renderBufs { + if s.renderBufs[i] != nil { + s.renderBufs[i].Close() + s.renderBufs[i] = nil + } + } +} + +func clamp(v, lo, hi int) int { + switch { + case v < lo: + return lo + case v > hi: + return hi + default: + return v + } +} + +func clampF(v, lo, hi float64) float64 { + switch { + case v < lo: + return lo + case v > hi: + return hi + default: + return v + } +} + +func abs(v int) int { + if v < 0 { + return -v + } + return v +} + +func blendColors(bg, fg Color, alpha float64) Color { + alpha = clampF(alpha, 0, 1) + invAlpha := 1.0 - alpha + return Color{ + R: uint8(clampF(float64(bg.R)*invAlpha+float64(fg.R)*alpha, 0, 255)), + G: uint8(clampF(float64(bg.G)*invAlpha+float64(fg.G)*alpha, 0, 255)), + B: uint8(clampF(float64(bg.B)*invAlpha+float64(fg.B)*alpha, 0, 255)), + A: 255, + } +} + +func drawMagnifier( + dst []byte, dstStride, dstW, dstH int, + src []byte, srcStride, srcW, srcH int, + cx, cy int, + borderColor Color, +) { + if dstW <= 0 || dstH <= 0 || srcW <= 0 || srcH <= 0 { + return + } + + const ( + outerRadius = 80 + borderThickness = 4 + aaWidth = 1.5 + zoom = 8.0 + crossThickness = 2 + crossInnerRadius = 8 + ) + + innerRadius := float64(outerRadius - borderThickness) + outerRadiusF := float64(outerRadius) + + for dy := -outerRadius - 2; dy <= outerRadius+2; dy++ { + y := cy + dy + if y < 0 || y >= dstH { + continue + } + dstRowOff := y * dstStride + + for dx := -outerRadius - 2; dx <= outerRadius+2; dx++ { + x := cx + dx + if x < 0 || x >= dstW { + continue + } + + dist := math.Sqrt(float64(dx*dx + dy*dy)) + if dist > outerRadiusF+aaWidth { + continue + } + + dstOff := dstRowOff + x*4 + if dstOff+4 > len(dst) { + continue + } + + bgColor := Color{ + B: dst[dstOff+0], + G: dst[dstOff+1], + R: dst[dstOff+2], + A: dst[dstOff+3], + } + + var finalColor Color + + switch { + case dist > outerRadiusF: + alpha := clampF(1.0-(dist-outerRadiusF)/aaWidth, 0, 1) + finalColor = blendColors(bgColor, borderColor, alpha) + + case dist > innerRadius: + if dist > outerRadiusF-aaWidth { + alpha := clampF((outerRadiusF-dist)/aaWidth, 0, 1) + finalColor = blendColors(borderColor, borderColor, alpha) + } else if dist < innerRadius+aaWidth { + alpha := clampF((dist-innerRadius)/aaWidth, 0, 1) + fx := float64(dx) / zoom + fy := float64(dy) / zoom + sx := cx + int(math.Round(fx)) + sy := cy + int(math.Round(fy)) + sx = clamp(sx, 0, srcW-1) + sy = clamp(sy, 0, srcH-1) + srcOff := sy*srcStride + sx*4 + if srcOff+4 <= len(src) { + magColor := Color{B: src[srcOff+0], G: src[srcOff+1], R: src[srcOff+2], A: 255} + finalColor = blendColors(magColor, borderColor, alpha) + } else { + finalColor = borderColor + } + } else { + finalColor = borderColor + } + + default: + fx := float64(dx) / zoom + fy := float64(dy) / zoom + sx := cx + int(math.Round(fx)) + sy := cy + int(math.Round(fy)) + sx = clamp(sx, 0, srcW-1) + sy = clamp(sy, 0, srcH-1) + srcOff := sy*srcStride + sx*4 + if srcOff+4 <= len(src) { + finalColor = Color{B: src[srcOff+0], G: src[srcOff+1], R: src[srcOff+2], A: 255} + } else { + continue + } + } + + dst[dstOff+0] = finalColor.B + dst[dstOff+1] = finalColor.G + dst[dstOff+2] = finalColor.R + dst[dstOff+3] = 255 + } + } + + drawMagnifierCrosshair(dst, dstStride, dstW, dstH, cx, cy, int(innerRadius), crossThickness, crossInnerRadius) +} + +func drawMagnifierCrosshair( + data []byte, stride, width, height, cx, cy, radius, thickness, innerRadius int, +) { + if width <= 0 || height <= 0 { + return + } + + cx = clamp(cx, 0, width-1) + cy = clamp(cy, 0, height-1) + + innerR2 := innerRadius * innerRadius + + for dy := -radius; dy <= radius; dy++ { + y := cy + dy + if y < 0 || y >= height { + continue + } + rowOff := y * stride + + for dx := -radius; dx <= radius; dx++ { + x := cx + dx + if x < 0 || x >= width { + continue + } + + dist2 := dx*dx + dy*dy + if dist2 > innerR2 { + continue + } + + absX := abs(dx) + absY := abs(dy) + if absX > thickness && absY > thickness { + continue + } + + off := rowOff + x*4 + if off+4 > len(data) { + continue + } + + isOutline := absX == thickness || absY == thickness + if isOutline { + data[off+0] = 0 + data[off+1] = 0 + data[off+2] = 0 + data[off+3] = 255 + } else { + data[off+0] = 255 + data[off+1] = 255 + data[off+2] = 255 + data[off+3] = 255 + } + } + } +} + +const ( + fontW = 8 + fontH = 12 +) + +var fontGlyphs = map[rune][fontH]uint8{ + '0': { + 0b00111100, + 0b01100110, + 0b01100110, + 0b01101110, + 0b01110110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b00111100, + 0b00000000, + 0b00000000, + }, + '1': { + 0b00011000, + 0b00111000, + 0b01111000, + 0b00011000, + 0b00011000, + 0b00011000, + 0b00011000, + 0b00011000, + 0b00011000, + 0b01111110, + 0b00000000, + 0b00000000, + }, + '2': { + 0b00111100, + 0b01100110, + 0b01100110, + 0b00000110, + 0b00001100, + 0b00011000, + 0b00110000, + 0b01100000, + 0b01100110, + 0b01111110, + 0b00000000, + 0b00000000, + }, + '3': { + 0b00111100, + 0b01100110, + 0b00000110, + 0b00000110, + 0b00011100, + 0b00000110, + 0b00000110, + 0b00000110, + 0b01100110, + 0b00111100, + 0b00000000, + 0b00000000, + }, + '4': { + 0b00001100, + 0b00011100, + 0b00111100, + 0b01101100, + 0b11001100, + 0b11001100, + 0b11111110, + 0b00001100, + 0b00001100, + 0b00011110, + 0b00000000, + 0b00000000, + }, + '5': { + 0b01111110, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01111100, + 0b00000110, + 0b00000110, + 0b00000110, + 0b01100110, + 0b00111100, + 0b00000000, + 0b00000000, + }, + '6': { + 0b00011100, + 0b00110000, + 0b01100000, + 0b01100000, + 0b01111100, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b00111100, + 0b00000000, + 0b00000000, + }, + '7': { + 0b01111110, + 0b01100110, + 0b00000110, + 0b00000110, + 0b00001100, + 0b00011000, + 0b00011000, + 0b00011000, + 0b00011000, + 0b00011000, + 0b00000000, + 0b00000000, + }, + '8': { + 0b00111100, + 0b01100110, + 0b01100110, + 0b01100110, + 0b00111100, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b00111100, + 0b00000000, + 0b00000000, + }, + '9': { + 0b00111100, + 0b01100110, + 0b01100110, + 0b01100110, + 0b00111110, + 0b00000110, + 0b00000110, + 0b00000110, + 0b00001100, + 0b00111000, + 0b00000000, + 0b00000000, + }, + 'A': { + 0b00011000, + 0b00111100, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01111110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b00000000, + 0b00000000, + }, + 'B': { + 0b01111100, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01111100, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01111100, + 0b00000000, + 0b00000000, + }, + 'C': { + 0b00111100, + 0b01100110, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01100110, + 0b00111100, + 0b00000000, + 0b00000000, + }, + 'D': { + 0b01111000, + 0b01101100, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01101100, + 0b01111000, + 0b00000000, + 0b00000000, + }, + 'E': { + 0b01111110, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01111100, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01111110, + 0b00000000, + 0b00000000, + }, + 'F': { + 0b01111110, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01111100, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01100000, + 0b00000000, + 0b00000000, + }, + '#': { + 0b00000000, + 0b00100100, + 0b00100100, + 0b01111110, + 0b00100100, + 0b00100100, + 0b01111110, + 0b00100100, + 0b00100100, + 0b00000000, + 0b00000000, + 0b00000000, + }, + 'G': { + 0b00111100, + 0b01100110, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01101110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b00111100, + 0b00000000, + 0b00000000, + }, + 'H': { + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01111110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b00000000, + 0b00000000, + }, + 'K': { + 0b01100110, + 0b01100110, + 0b01101100, + 0b01111000, + 0b01110000, + 0b01111000, + 0b01101100, + 0b01100110, + 0b01100110, + 0b01100110, + 0b00000000, + 0b00000000, + }, + 'L': { + 0b01100000, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01100000, + 0b01111110, + 0b00000000, + 0b00000000, + }, + 'M': { + 0b01100011, + 0b01110111, + 0b01111111, + 0b01101011, + 0b01100011, + 0b01100011, + 0b01100011, + 0b01100011, + 0b01100011, + 0b01100011, + 0b00000000, + 0b00000000, + }, + 'R': { + 0b01111100, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01111100, + 0b01111000, + 0b01101100, + 0b01100110, + 0b01100110, + 0b01100110, + 0b00000000, + 0b00000000, + }, + 'S': { + 0b00111100, + 0b01100110, + 0b01100000, + 0b01100000, + 0b00111100, + 0b00000110, + 0b00000110, + 0b00000110, + 0b01100110, + 0b00111100, + 0b00000000, + 0b00000000, + }, + 'V': { + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b00111100, + 0b00011000, + 0b00011000, + 0b00000000, + 0b00000000, + }, + 'Y': { + 0b01100110, + 0b01100110, + 0b01100110, + 0b01100110, + 0b00111100, + 0b00011000, + 0b00011000, + 0b00011000, + 0b00011000, + 0b00011000, + 0b00000000, + 0b00000000, + }, + '(': { + 0b00001100, + 0b00011000, + 0b00110000, + 0b00110000, + 0b00110000, + 0b00110000, + 0b00110000, + 0b00110000, + 0b00011000, + 0b00001100, + 0b00000000, + 0b00000000, + }, + ')': { + 0b00110000, + 0b00011000, + 0b00001100, + 0b00001100, + 0b00001100, + 0b00001100, + 0b00001100, + 0b00001100, + 0b00011000, + 0b00110000, + 0b00000000, + 0b00000000, + }, + ',': { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00011000, + 0b00011000, + 0b00110000, + 0b00000000, + 0b00000000, + }, + '%': { + 0b01100010, + 0b01100110, + 0b00001100, + 0b00001100, + 0b00011000, + 0b00011000, + 0b00110000, + 0b00110000, + 0b01100110, + 0b01000110, + 0b00000000, + 0b00000000, + }, + ' ': { + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + }, +} + +func drawColorPreview(data []byte, stride, width, height int, cx, cy int, c Color, format OutputFormat, lowercase bool) { + text := formatColorForPreview(c, format, lowercase) + if len(text) == 0 { + return + } + + const ( + paddingX = 8 + paddingY = 4 + space = 2 + offset = 88 + ) + + textW := len(text)*(fontW+space) - space + textH := fontH + + boxW := textW + paddingX*2 + boxH := textH + paddingY*2 + + x := cx + offset + y := cy - boxH/2 + + if x+boxW >= width { + x = cx - boxW - offset + } + if x < 0 { + x = 0 + } + if y < 0 { + y = 0 + } + if y+boxH >= height { + y = height - boxH + } + + drawFilledRect(data, stride, width, height, x, y, boxW, boxH, c) + + // Use contrasting text color based on luminance + lum := 0.299*float64(c.R) + 0.587*float64(c.G) + 0.114*float64(c.B) + var fg Color + if lum > 128 { + fg = Color{R: 0, G: 0, B: 0, A: 255} + } else { + fg = Color{R: 255, G: 255, B: 255, A: 255} + } + drawText(data, stride, width, height, x+paddingX, y+paddingY, text, fg) +} + +func formatColorForPreview(c Color, format OutputFormat, lowercase bool) string { + switch format { + case FormatRGB: + return strings.ToUpper(c.ToRGB()) + case FormatHSL: + return strings.ToUpper(c.ToHSL()) + case FormatHSV: + return strings.ToUpper(c.ToHSV()) + case FormatCMYK: + return strings.ToUpper(c.ToCMYK()) + default: + if lowercase { + return c.ToHex(true) + } + return c.ToHex(false) + } +} + +func drawFilledRect(data []byte, stride, width, height, x, y, w, h int, col Color) { + if w <= 0 || h <= 0 { + return + } + xEnd := clamp(x+w, 0, width) + yEnd := clamp(y+h, 0, height) + x = clamp(x, 0, width) + y = clamp(y, 0, height) + + for yy := y; yy < yEnd; yy++ { + rowOff := yy * stride + for xx := x; xx < xEnd; xx++ { + off := rowOff + xx*4 + if off+4 > len(data) { + continue + } + data[off+0] = col.B + data[off+1] = col.G + data[off+2] = col.R + data[off+3] = 255 + } + } +} + +func drawText(data []byte, stride, width, height, x, y int, text string, col Color) { + for i, r := range text { + drawGlyph(data, stride, width, height, x+i*(fontW+2), y, r, col) + } +} + +func drawGlyph(data []byte, stride, width, height, x, y int, r rune, col Color) { + g, ok := fontGlyphs[r] + if !ok { + return + } + + for row := 0; row < fontH; row++ { + yy := y + row + if yy < 0 || yy >= height { + continue + } + rowPattern := g[row] + dstRowOff := yy * stride + + for colIdx := 0; colIdx < fontW; colIdx++ { + if (rowPattern & (1 << (fontW - 1 - colIdx))) == 0 { + continue + } + + xx := x + colIdx + if xx < 0 || xx >= width { + continue + } + + off := dstRowOff + xx*4 + if off+4 > len(data) { + continue + } + + data[off+0] = col.B + data[off+1] = col.G + data[off+2] = col.R + data[off+3] = 255 + } + } +} diff --git a/core/internal/proto/wlr_layer_shell/layer_shell.go b/core/internal/proto/wlr_layer_shell/layer_shell.go new file mode 100644 index 00000000..b5fcd182 --- /dev/null +++ b/core/internal/proto/wlr_layer_shell/layer_shell.go @@ -0,0 +1,792 @@ +// Generated by go-wayland-scanner +// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner +// XML file : internal/proto/xml/wlr-layer-shell-unstable-v1.xml +// +// wlr_layer_shell_unstable_v1 Protocol Copyright: +// +// Copyright © 2017 Drew DeVault +// +// Permission to use, copy, modify, distribute, and sell this +// software and its documentation for any purpose is hereby granted +// without fee, provided that the above copyright notice appear in +// all copies and that both that copyright notice and this permission +// notice appear in supporting documentation, and that the name of +// the copyright holders not be used in advertising or publicity +// pertaining to distribution of the software without specific, +// written prior permission. The copyright holders make no +// representations about the suitability of this software for any +// purpose. It is provided "as is" without express or implied +// warranty. +// +// THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS +// SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY +// SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +// THIS SOFTWARE. + +package wlr_layer_shell + +import ( + "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" + xdg_shell "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/stable/xdg-shell" +) + +// ZwlrLayerShellV1InterfaceName 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 ZwlrLayerShellV1InterfaceName = "zwlr_layer_shell_v1" + +// ZwlrLayerShellV1 : create surfaces that are layers of the desktop +// +// Clients can use this interface to assign the surface_layer role to +// wl_surfaces. Such surfaces are assigned to a "layer" of the output and +// rendered with a defined z-depth respective to each other. They may also be +// anchored to the edges and corners of a screen and specify input handling +// semantics. This interface should be suitable for the implementation of +// many desktop shell components, and a broad number of other applications +// that interact with the desktop. +type ZwlrLayerShellV1 struct { + client.BaseProxy +} + +// NewZwlrLayerShellV1 : create surfaces that are layers of the desktop +// +// Clients can use this interface to assign the surface_layer role to +// wl_surfaces. Such surfaces are assigned to a "layer" of the output and +// rendered with a defined z-depth respective to each other. They may also be +// anchored to the edges and corners of a screen and specify input handling +// semantics. This interface should be suitable for the implementation of +// many desktop shell components, and a broad number of other applications +// that interact with the desktop. +func NewZwlrLayerShellV1(ctx *client.Context) *ZwlrLayerShellV1 { + zwlrLayerShellV1 := &ZwlrLayerShellV1{} + ctx.Register(zwlrLayerShellV1) + return zwlrLayerShellV1 +} + +// GetLayerSurface : create a layer_surface from a surface +// +// Create a layer surface for an existing surface. This assigns the role of +// layer_surface, or raises a protocol error if another role is already +// assigned. +// +// Creating a layer surface from a wl_surface which has a buffer attached +// or committed is a client error, and any attempts by a client to attach +// or manipulate a buffer prior to the first layer_surface.configure call +// must also be treated as errors. +// +// After creating a layer_surface object and setting it up, the client +// must perform an initial commit without any buffer attached. +// The compositor will reply with a layer_surface.configure event. +// The client must acknowledge it and is then allowed to attach a buffer +// to map the surface. +// +// You may pass NULL for output to allow the compositor to decide which +// output to use. Generally this will be the one that the user most +// recently interacted with. +// +// Clients can specify a namespace that defines the purpose of the layer +// surface. +// +// layer: layer to add this surface to +// namespace: namespace for the layer surface +func (i *ZwlrLayerShellV1) GetLayerSurface(surface *client.Surface, output *client.Output, layer uint32, namespace string) (*ZwlrLayerSurfaceV1, error) { + id := NewZwlrLayerSurfaceV1(i.Context()) + const opcode = 0 + namespaceLen := client.PaddedLen(len(namespace) + 1) + _reqBufLen := 8 + 4 + 4 + 4 + 4 + (4 + namespaceLen) + _reqBuf := make([]byte, _reqBufLen) + 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], id.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], surface.ID()) + l += 4 + if output == nil { + client.PutUint32(_reqBuf[l:l+4], 0) + l += 4 + } else { + client.PutUint32(_reqBuf[l:l+4], output.ID()) + l += 4 + } + client.PutUint32(_reqBuf[l:l+4], uint32(layer)) + l += 4 + client.PutString(_reqBuf[l:l+(4+namespaceLen)], namespace) + l += (4 + namespaceLen) + err := i.Context().WriteMsg(_reqBuf, nil) + return id, err +} + +// Destroy : destroy the layer_shell object +// +// This request indicates that the client will not use the layer_shell +// object any more. Objects that have been created through this instance +// are not affected. +func (i *ZwlrLayerShellV1) Destroy() error { + defer i.Context().Unregister(i) + const opcode = 1 + 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 +} + +type ZwlrLayerShellV1Error uint32 + +// ZwlrLayerShellV1Error : +const ( + // ZwlrLayerShellV1ErrorRole : wl_surface has another role + ZwlrLayerShellV1ErrorRole ZwlrLayerShellV1Error = 0 + // ZwlrLayerShellV1ErrorInvalidLayer : layer value is invalid + ZwlrLayerShellV1ErrorInvalidLayer ZwlrLayerShellV1Error = 1 + // ZwlrLayerShellV1ErrorAlreadyConstructed : wl_surface has a buffer attached or committed + ZwlrLayerShellV1ErrorAlreadyConstructed ZwlrLayerShellV1Error = 2 +) + +func (e ZwlrLayerShellV1Error) Name() string { + switch e { + case ZwlrLayerShellV1ErrorRole: + return "role" + case ZwlrLayerShellV1ErrorInvalidLayer: + return "invalid_layer" + case ZwlrLayerShellV1ErrorAlreadyConstructed: + return "already_constructed" + default: + return "" + } +} + +func (e ZwlrLayerShellV1Error) Value() string { + switch e { + case ZwlrLayerShellV1ErrorRole: + return "0" + case ZwlrLayerShellV1ErrorInvalidLayer: + return "1" + case ZwlrLayerShellV1ErrorAlreadyConstructed: + return "2" + default: + return "" + } +} + +func (e ZwlrLayerShellV1Error) String() string { + return e.Name() + "=" + e.Value() +} + +type ZwlrLayerShellV1Layer uint32 + +// ZwlrLayerShellV1Layer : available layers for surfaces +// +// These values indicate which layers a surface can be rendered in. They +// are ordered by z depth, bottom-most first. Traditional shell surfaces +// will typically be rendered between the bottom and top layers. +// Fullscreen shell surfaces are typically rendered at the top layer. +// Multiple surfaces can share a single layer, and ordering within a +// single layer is undefined. +const ( + ZwlrLayerShellV1LayerBackground ZwlrLayerShellV1Layer = 0 + ZwlrLayerShellV1LayerBottom ZwlrLayerShellV1Layer = 1 + ZwlrLayerShellV1LayerTop ZwlrLayerShellV1Layer = 2 + ZwlrLayerShellV1LayerOverlay ZwlrLayerShellV1Layer = 3 +) + +func (e ZwlrLayerShellV1Layer) Name() string { + switch e { + case ZwlrLayerShellV1LayerBackground: + return "background" + case ZwlrLayerShellV1LayerBottom: + return "bottom" + case ZwlrLayerShellV1LayerTop: + return "top" + case ZwlrLayerShellV1LayerOverlay: + return "overlay" + default: + return "" + } +} + +func (e ZwlrLayerShellV1Layer) Value() string { + switch e { + case ZwlrLayerShellV1LayerBackground: + return "0" + case ZwlrLayerShellV1LayerBottom: + return "1" + case ZwlrLayerShellV1LayerTop: + return "2" + case ZwlrLayerShellV1LayerOverlay: + return "3" + default: + return "" + } +} + +func (e ZwlrLayerShellV1Layer) String() string { + return e.Name() + "=" + e.Value() +} + +// ZwlrLayerSurfaceV1InterfaceName 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 ZwlrLayerSurfaceV1InterfaceName = "zwlr_layer_surface_v1" + +// ZwlrLayerSurfaceV1 : layer metadata interface +// +// An interface that may be implemented by a wl_surface, for surfaces that +// are designed to be rendered as a layer of a stacked desktop-like +// environment. +// +// Layer surface state (layer, size, anchor, exclusive zone, +// margin, interactivity) is double-buffered, and will be applied at the +// time wl_surface.commit of the corresponding wl_surface is called. +// +// Attaching a null buffer to a layer surface unmaps it. +// +// Unmapping a layer_surface means that the surface cannot be shown by the +// compositor until it is explicitly mapped again. The layer_surface +// returns to the state it had right after layer_shell.get_layer_surface. +// The client can re-map the surface by performing a commit without any +// buffer attached, waiting for a configure event and handling it as usual. +type ZwlrLayerSurfaceV1 struct { + client.BaseProxy + configureHandler ZwlrLayerSurfaceV1ConfigureHandlerFunc + closedHandler ZwlrLayerSurfaceV1ClosedHandlerFunc +} + +// NewZwlrLayerSurfaceV1 : layer metadata interface +// +// An interface that may be implemented by a wl_surface, for surfaces that +// are designed to be rendered as a layer of a stacked desktop-like +// environment. +// +// Layer surface state (layer, size, anchor, exclusive zone, +// margin, interactivity) is double-buffered, and will be applied at the +// time wl_surface.commit of the corresponding wl_surface is called. +// +// Attaching a null buffer to a layer surface unmaps it. +// +// Unmapping a layer_surface means that the surface cannot be shown by the +// compositor until it is explicitly mapped again. The layer_surface +// returns to the state it had right after layer_shell.get_layer_surface. +// The client can re-map the surface by performing a commit without any +// buffer attached, waiting for a configure event and handling it as usual. +func NewZwlrLayerSurfaceV1(ctx *client.Context) *ZwlrLayerSurfaceV1 { + zwlrLayerSurfaceV1 := &ZwlrLayerSurfaceV1{} + ctx.Register(zwlrLayerSurfaceV1) + return zwlrLayerSurfaceV1 +} + +// SetSize : sets the size of the surface +// +// Sets the size of the surface in surface-local coordinates. The +// compositor will display the surface centered with respect to its +// anchors. +// +// If you pass 0 for either value, the compositor will assign it and +// inform you of the assignment in the configure event. You must set your +// anchor to opposite edges in the dimensions you omit; not doing so is a +// protocol error. Both values are 0 by default. +// +// Size is double-buffered, see wl_surface.commit. +func (i *ZwlrLayerSurfaceV1) SetSize(width, height uint32) error { + const opcode = 0 + 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(width)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(height)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// SetAnchor : configures the anchor point of the surface +// +// Requests that the compositor anchor the surface to the specified edges +// and corners. If two orthogonal edges are specified (e.g. 'top' and +// 'left'), then the anchor point will be the intersection of the edges +// (e.g. the top left corner of the output); otherwise the anchor point +// will be centered on that edge, or in the center if none is specified. +// +// Anchor is double-buffered, see wl_surface.commit. +func (i *ZwlrLayerSurfaceV1) SetAnchor(anchor uint32) error { + const opcode = 1 + const _reqBufLen = 8 + 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(anchor)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// SetExclusiveZone : configures the exclusive geometry of this surface +// +// Requests that the compositor avoids occluding an area with other +// surfaces. The compositor's use of this information is +// implementation-dependent - do not assume that this region will not +// actually be occluded. +// +// A positive value is only meaningful if the surface is anchored to one +// edge or an edge and both perpendicular edges. If the surface is not +// anchored, anchored to only two perpendicular edges (a corner), anchored +// to only two parallel edges or anchored to all edges, a positive value +// will be treated the same as zero. +// +// A positive zone is the distance from the edge in surface-local +// coordinates to consider exclusive. +// +// Surfaces that do not wish to have an exclusive zone may instead specify +// how they should interact with surfaces that do. If set to zero, the +// surface indicates that it would like to be moved to avoid occluding +// surfaces with a positive exclusive zone. If set to -1, the surface +// indicates that it would not like to be moved to accommodate for other +// surfaces, and the compositor should extend it all the way to the edges +// it is anchored to. +// +// For example, a panel might set its exclusive zone to 10, so that +// maximized shell surfaces are not shown on top of it. A notification +// might set its exclusive zone to 0, so that it is moved to avoid +// occluding the panel, but shell surfaces are shown underneath it. A +// wallpaper or lock screen might set their exclusive zone to -1, so that +// they stretch below or over the panel. +// +// The default value is 0. +// +// Exclusive zone is double-buffered, see wl_surface.commit. +func (i *ZwlrLayerSurfaceV1) SetExclusiveZone(zone int32) error { + const opcode = 2 + const _reqBufLen = 8 + 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(zone)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// SetMargin : sets a margin from the anchor point +// +// Requests that the surface be placed some distance away from the anchor +// point on the output, in surface-local coordinates. Setting this value +// for edges you are not anchored to has no effect. +// +// The exclusive zone includes the margin. +// +// Margin is double-buffered, see wl_surface.commit. +func (i *ZwlrLayerSurfaceV1) SetMargin(top, right, bottom, left int32) error { + const opcode = 3 + const _reqBufLen = 8 + 4 + 4 + 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(top)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(right)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(bottom)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(left)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// SetKeyboardInteractivity : requests keyboard events +// +// Set how keyboard events are delivered to this surface. By default, +// layer shell surfaces do not receive keyboard events; this request can +// be used to change this. +// +// This setting is inherited by child surfaces set by the get_popup +// request. +// +// Layer surfaces receive pointer, touch, and tablet events normally. If +// you do not want to receive them, set the input region on your surface +// to an empty region. +// +// Keyboard interactivity is double-buffered, see wl_surface.commit. +func (i *ZwlrLayerSurfaceV1) SetKeyboardInteractivity(keyboardInteractivity uint32) error { + const opcode = 4 + const _reqBufLen = 8 + 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(keyboardInteractivity)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// GetPopup : assign this layer_surface as an xdg_popup parent +// +// This assigns an xdg_popup's parent to this layer_surface. This popup +// should have been created via xdg_surface::get_popup with the parent set +// to NULL, and this request must be invoked before committing the popup's +// initial state. +// +// See the documentation of xdg_popup for more details about what an +// xdg_popup is and how it is used. +func (i *ZwlrLayerSurfaceV1) GetPopup(popup *xdg_shell.Popup) error { + const opcode = 5 + const _reqBufLen = 8 + 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], popup.ID()) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// AckConfigure : ack a configure event +// +// When a configure event is received, if a client commits the +// surface in response to the configure event, then the client +// must make an ack_configure request sometime before the commit +// request, passing along the serial of the configure event. +// +// If the client receives multiple configure events before it +// can respond to one, it only has to ack the last configure event. +// +// A client is not required to commit immediately after sending +// an ack_configure request - it may even ack_configure several times +// before its next surface commit. +// +// A client may send multiple ack_configure requests before committing, but +// only the last request sent before a commit indicates which configure +// event the client really is responding to. +// +// serial: the serial from the configure event +func (i *ZwlrLayerSurfaceV1) AckConfigure(serial uint32) error { + const opcode = 6 + const _reqBufLen = 8 + 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 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// Destroy : destroy the layer_surface +// +// This request destroys the layer surface. +func (i *ZwlrLayerSurfaceV1) Destroy() error { + defer i.Context().Unregister(i) + const opcode = 7 + 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 +} + +// SetLayer : change the layer of the surface +// +// Change the layer that the surface is rendered on. +// +// Layer is double-buffered, see wl_surface.commit. +// +// layer: layer to move this surface to +func (i *ZwlrLayerSurfaceV1) SetLayer(layer uint32) error { + const opcode = 8 + const _reqBufLen = 8 + 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(layer)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// SetExclusiveEdge : set the edge the exclusive zone will be applied to +// +// Requests an edge for the exclusive zone to apply. The exclusive +// edge will be automatically deduced from anchor points when possible, +// but when the surface is anchored to a corner, it will be necessary +// to set it explicitly to disambiguate, as it is not possible to deduce +// which one of the two corner edges should be used. +// +// The edge must be one the surface is anchored to, otherwise the +// invalid_exclusive_edge protocol error will be raised. +func (i *ZwlrLayerSurfaceV1) SetExclusiveEdge(edge uint32) error { + const opcode = 9 + const _reqBufLen = 8 + 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(edge)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +type ZwlrLayerSurfaceV1KeyboardInteractivity uint32 + +// ZwlrLayerSurfaceV1KeyboardInteractivity : types of keyboard interaction possible for a layer shell surface +// +// Types of keyboard interaction possible for layer shell surfaces. The +// rationale for this is twofold: (1) some applications are not interested +// in keyboard events and not allowing them to be focused can improve the +// desktop experience; (2) some applications will want to take exclusive +// keyboard focus. +const ( + ZwlrLayerSurfaceV1KeyboardInteractivityNone ZwlrLayerSurfaceV1KeyboardInteractivity = 0 + ZwlrLayerSurfaceV1KeyboardInteractivityExclusive ZwlrLayerSurfaceV1KeyboardInteractivity = 1 + ZwlrLayerSurfaceV1KeyboardInteractivityOnDemand ZwlrLayerSurfaceV1KeyboardInteractivity = 2 +) + +func (e ZwlrLayerSurfaceV1KeyboardInteractivity) Name() string { + switch e { + case ZwlrLayerSurfaceV1KeyboardInteractivityNone: + return "none" + case ZwlrLayerSurfaceV1KeyboardInteractivityExclusive: + return "exclusive" + case ZwlrLayerSurfaceV1KeyboardInteractivityOnDemand: + return "on_demand" + default: + return "" + } +} + +func (e ZwlrLayerSurfaceV1KeyboardInteractivity) Value() string { + switch e { + case ZwlrLayerSurfaceV1KeyboardInteractivityNone: + return "0" + case ZwlrLayerSurfaceV1KeyboardInteractivityExclusive: + return "1" + case ZwlrLayerSurfaceV1KeyboardInteractivityOnDemand: + return "2" + default: + return "" + } +} + +func (e ZwlrLayerSurfaceV1KeyboardInteractivity) String() string { + return e.Name() + "=" + e.Value() +} + +type ZwlrLayerSurfaceV1Error uint32 + +// ZwlrLayerSurfaceV1Error : +const ( + // ZwlrLayerSurfaceV1ErrorInvalidSurfaceState : provided surface state is invalid + ZwlrLayerSurfaceV1ErrorInvalidSurfaceState ZwlrLayerSurfaceV1Error = 0 + // ZwlrLayerSurfaceV1ErrorInvalidSize : size is invalid + ZwlrLayerSurfaceV1ErrorInvalidSize ZwlrLayerSurfaceV1Error = 1 + // ZwlrLayerSurfaceV1ErrorInvalidAnchor : anchor bitfield is invalid + ZwlrLayerSurfaceV1ErrorInvalidAnchor ZwlrLayerSurfaceV1Error = 2 + // ZwlrLayerSurfaceV1ErrorInvalidKeyboardInteractivity : keyboard interactivity is invalid + ZwlrLayerSurfaceV1ErrorInvalidKeyboardInteractivity ZwlrLayerSurfaceV1Error = 3 + // ZwlrLayerSurfaceV1ErrorInvalidExclusiveEdge : exclusive edge is invalid given the surface anchors + ZwlrLayerSurfaceV1ErrorInvalidExclusiveEdge ZwlrLayerSurfaceV1Error = 4 +) + +func (e ZwlrLayerSurfaceV1Error) Name() string { + switch e { + case ZwlrLayerSurfaceV1ErrorInvalidSurfaceState: + return "invalid_surface_state" + case ZwlrLayerSurfaceV1ErrorInvalidSize: + return "invalid_size" + case ZwlrLayerSurfaceV1ErrorInvalidAnchor: + return "invalid_anchor" + case ZwlrLayerSurfaceV1ErrorInvalidKeyboardInteractivity: + return "invalid_keyboard_interactivity" + case ZwlrLayerSurfaceV1ErrorInvalidExclusiveEdge: + return "invalid_exclusive_edge" + default: + return "" + } +} + +func (e ZwlrLayerSurfaceV1Error) Value() string { + switch e { + case ZwlrLayerSurfaceV1ErrorInvalidSurfaceState: + return "0" + case ZwlrLayerSurfaceV1ErrorInvalidSize: + return "1" + case ZwlrLayerSurfaceV1ErrorInvalidAnchor: + return "2" + case ZwlrLayerSurfaceV1ErrorInvalidKeyboardInteractivity: + return "3" + case ZwlrLayerSurfaceV1ErrorInvalidExclusiveEdge: + return "4" + default: + return "" + } +} + +func (e ZwlrLayerSurfaceV1Error) String() string { + return e.Name() + "=" + e.Value() +} + +type ZwlrLayerSurfaceV1Anchor uint32 + +// ZwlrLayerSurfaceV1Anchor : +const ( + // ZwlrLayerSurfaceV1AnchorTop : the top edge of the anchor rectangle + ZwlrLayerSurfaceV1AnchorTop ZwlrLayerSurfaceV1Anchor = 1 + // ZwlrLayerSurfaceV1AnchorBottom : the bottom edge of the anchor rectangle + ZwlrLayerSurfaceV1AnchorBottom ZwlrLayerSurfaceV1Anchor = 2 + // ZwlrLayerSurfaceV1AnchorLeft : the left edge of the anchor rectangle + ZwlrLayerSurfaceV1AnchorLeft ZwlrLayerSurfaceV1Anchor = 4 + // ZwlrLayerSurfaceV1AnchorRight : the right edge of the anchor rectangle + ZwlrLayerSurfaceV1AnchorRight ZwlrLayerSurfaceV1Anchor = 8 +) + +func (e ZwlrLayerSurfaceV1Anchor) Name() string { + switch e { + case ZwlrLayerSurfaceV1AnchorTop: + return "top" + case ZwlrLayerSurfaceV1AnchorBottom: + return "bottom" + case ZwlrLayerSurfaceV1AnchorLeft: + return "left" + case ZwlrLayerSurfaceV1AnchorRight: + return "right" + default: + return "" + } +} + +func (e ZwlrLayerSurfaceV1Anchor) Value() string { + switch e { + case ZwlrLayerSurfaceV1AnchorTop: + return "1" + case ZwlrLayerSurfaceV1AnchorBottom: + return "2" + case ZwlrLayerSurfaceV1AnchorLeft: + return "4" + case ZwlrLayerSurfaceV1AnchorRight: + return "8" + default: + return "" + } +} + +func (e ZwlrLayerSurfaceV1Anchor) String() string { + return e.Name() + "=" + e.Value() +} + +// ZwlrLayerSurfaceV1ConfigureEvent : suggest a surface change +// +// The configure event asks the client to resize its surface. +// +// Clients should arrange their surface for the new states, and then send +// an ack_configure request with the serial sent in this configure event at +// some point before committing the new surface. +// +// The client is free to dismiss all but the last configure event it +// received. +// +// The width and height arguments specify the size of the window in +// surface-local coordinates. +// +// The size is a hint, in the sense that the client is free to ignore it if +// it doesn't resize, pick a smaller size (to satisfy aspect ratio or +// resize in steps of NxM pixels). If the client picks a smaller size and +// is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the +// surface will be centered on this axis. +// +// If the width or height arguments are zero, it means the client should +// decide its own window dimension. +type ZwlrLayerSurfaceV1ConfigureEvent struct { + Serial uint32 + Width uint32 + Height uint32 +} +type ZwlrLayerSurfaceV1ConfigureHandlerFunc func(ZwlrLayerSurfaceV1ConfigureEvent) + +// SetConfigureHandler : sets handler for ZwlrLayerSurfaceV1ConfigureEvent +func (i *ZwlrLayerSurfaceV1) SetConfigureHandler(f ZwlrLayerSurfaceV1ConfigureHandlerFunc) { + i.configureHandler = f +} + +// ZwlrLayerSurfaceV1ClosedEvent : surface should be closed +// +// The closed event is sent by the compositor when the surface will no +// longer be shown. The output may have been destroyed or the user may +// have asked for it to be removed. Further changes to the surface will be +// ignored. The client should destroy the resource after receiving this +// event, and create a new surface if they so choose. +type ZwlrLayerSurfaceV1ClosedEvent struct{} +type ZwlrLayerSurfaceV1ClosedHandlerFunc func(ZwlrLayerSurfaceV1ClosedEvent) + +// SetClosedHandler : sets handler for ZwlrLayerSurfaceV1ClosedEvent +func (i *ZwlrLayerSurfaceV1) SetClosedHandler(f ZwlrLayerSurfaceV1ClosedHandlerFunc) { + i.closedHandler = f +} + +func (i *ZwlrLayerSurfaceV1) Dispatch(opcode uint32, fd int, data []byte) { + switch opcode { + case 0: + if i.configureHandler == nil { + return + } + var e ZwlrLayerSurfaceV1ConfigureEvent + l := 0 + e.Serial = client.Uint32(data[l : l+4]) + l += 4 + e.Width = client.Uint32(data[l : l+4]) + l += 4 + e.Height = client.Uint32(data[l : l+4]) + l += 4 + + i.configureHandler(e) + case 1: + if i.closedHandler == nil { + return + } + var e ZwlrLayerSurfaceV1ClosedEvent + + i.closedHandler(e) + } +} diff --git a/core/internal/proto/wlr_screencopy/screencopy.go b/core/internal/proto/wlr_screencopy/screencopy.go new file mode 100644 index 00000000..ba322450 --- /dev/null +++ b/core/internal/proto/wlr_screencopy/screencopy.go @@ -0,0 +1,532 @@ +// Generated by go-wayland-scanner +// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner +// XML file : internal/proto/xml/wlr-screencopy-unstable-v1.xml +// +// wlr_screencopy_unstable_v1 Protocol Copyright: +// +// Copyright © 2018 Simon Ser +// Copyright © 2019 Andri Yngvason +// +// 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 wlr_screencopy + +import "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" + +// ZwlrScreencopyManagerV1InterfaceName 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 ZwlrScreencopyManagerV1InterfaceName = "zwlr_screencopy_manager_v1" + +// ZwlrScreencopyManagerV1 : manager to inform clients and begin capturing +// +// This object is a manager which offers requests to start capturing from a +// source. +type ZwlrScreencopyManagerV1 struct { + client.BaseProxy +} + +// NewZwlrScreencopyManagerV1 : manager to inform clients and begin capturing +// +// This object is a manager which offers requests to start capturing from a +// source. +func NewZwlrScreencopyManagerV1(ctx *client.Context) *ZwlrScreencopyManagerV1 { + zwlrScreencopyManagerV1 := &ZwlrScreencopyManagerV1{} + ctx.Register(zwlrScreencopyManagerV1) + return zwlrScreencopyManagerV1 +} + +// CaptureOutput : capture an output +// +// Capture the next frame of an entire output. +// +// overlayCursor: composite cursor onto the frame +func (i *ZwlrScreencopyManagerV1) CaptureOutput(overlayCursor int32, output *client.Output) (*ZwlrScreencopyFrameV1, error) { + frame := NewZwlrScreencopyFrameV1(i.Context()) + const opcode = 0 + const _reqBufLen = 8 + 4 + 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], frame.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(overlayCursor)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], output.ID()) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return frame, err +} + +// CaptureOutputRegion : capture an output's region +// +// Capture the next frame of an output's region. +// +// The region is given in output logical coordinates, see +// xdg_output.logical_size. The region will be clipped to the output's +// extents. +// +// overlayCursor: composite cursor onto the frame +func (i *ZwlrScreencopyManagerV1) CaptureOutputRegion(overlayCursor int32, output *client.Output, x, y, width, height int32) (*ZwlrScreencopyFrameV1, error) { + frame := NewZwlrScreencopyFrameV1(i.Context()) + const opcode = 1 + const _reqBufLen = 8 + 4 + 4 + 4 + 4 + 4 + 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], frame.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(overlayCursor)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], output.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(x)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(y)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(width)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(height)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return frame, err +} + +// Destroy : destroy the manager +// +// All objects created by the manager will still remain valid, until their +// appropriate destroy request has been called. +func (i *ZwlrScreencopyManagerV1) Destroy() error { + defer i.Context().Unregister(i) + const opcode = 2 + 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 +} + +// ZwlrScreencopyFrameV1InterfaceName 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 ZwlrScreencopyFrameV1InterfaceName = "zwlr_screencopy_frame_v1" + +// ZwlrScreencopyFrameV1 : a frame ready for copy +// +// This object represents a single frame. +// +// When created, a series of buffer events will be sent, each representing a +// supported buffer type. The "buffer_done" event is sent afterwards to +// indicate that all supported buffer types have been enumerated. The client +// will then be able to send a "copy" request. If the capture is successful, +// the compositor will send a "flags" event followed by a "ready" event. +// +// For objects version 2 or lower, wl_shm buffers are always supported, ie. +// the "buffer" event is guaranteed to be sent. +// +// If the capture failed, the "failed" event is sent. This can happen anytime +// before the "ready" event. +// +// Once either a "ready" or a "failed" event is received, the client should +// destroy the frame. +type ZwlrScreencopyFrameV1 struct { + client.BaseProxy + bufferHandler ZwlrScreencopyFrameV1BufferHandlerFunc + flagsHandler ZwlrScreencopyFrameV1FlagsHandlerFunc + readyHandler ZwlrScreencopyFrameV1ReadyHandlerFunc + failedHandler ZwlrScreencopyFrameV1FailedHandlerFunc + damageHandler ZwlrScreencopyFrameV1DamageHandlerFunc + linuxDmabufHandler ZwlrScreencopyFrameV1LinuxDmabufHandlerFunc + bufferDoneHandler ZwlrScreencopyFrameV1BufferDoneHandlerFunc +} + +// NewZwlrScreencopyFrameV1 : a frame ready for copy +// +// This object represents a single frame. +// +// When created, a series of buffer events will be sent, each representing a +// supported buffer type. The "buffer_done" event is sent afterwards to +// indicate that all supported buffer types have been enumerated. The client +// will then be able to send a "copy" request. If the capture is successful, +// the compositor will send a "flags" event followed by a "ready" event. +// +// For objects version 2 or lower, wl_shm buffers are always supported, ie. +// the "buffer" event is guaranteed to be sent. +// +// If the capture failed, the "failed" event is sent. This can happen anytime +// before the "ready" event. +// +// Once either a "ready" or a "failed" event is received, the client should +// destroy the frame. +func NewZwlrScreencopyFrameV1(ctx *client.Context) *ZwlrScreencopyFrameV1 { + zwlrScreencopyFrameV1 := &ZwlrScreencopyFrameV1{} + ctx.Register(zwlrScreencopyFrameV1) + return zwlrScreencopyFrameV1 +} + +// Copy : copy the frame +// +// Copy the frame to the supplied buffer. The buffer must have the +// correct size, see zwlr_screencopy_frame_v1.buffer and +// zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a +// supported format. +// +// If the frame is successfully copied, "flags" and "ready" events are +// sent. Otherwise, a "failed" event is sent. +func (i *ZwlrScreencopyFrameV1) Copy(buffer *client.Buffer) error { + const opcode = 0 + const _reqBufLen = 8 + 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], buffer.ID()) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// Destroy : delete this object, used or not +// +// Destroys the frame. This request can be sent at any time by the client. +func (i *ZwlrScreencopyFrameV1) Destroy() error { + defer i.Context().Unregister(i) + const opcode = 1 + 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 +} + +// CopyWithDamage : copy the frame when it's damaged +// +// Same as copy, except it waits until there is damage to copy. +func (i *ZwlrScreencopyFrameV1) CopyWithDamage(buffer *client.Buffer) error { + const opcode = 2 + const _reqBufLen = 8 + 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], buffer.ID()) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +type ZwlrScreencopyFrameV1Error uint32 + +// ZwlrScreencopyFrameV1Error : +const ( + // ZwlrScreencopyFrameV1ErrorAlreadyUsed : the object has already been used to copy a wl_buffer + ZwlrScreencopyFrameV1ErrorAlreadyUsed ZwlrScreencopyFrameV1Error = 0 + // ZwlrScreencopyFrameV1ErrorInvalidBuffer : buffer attributes are invalid + ZwlrScreencopyFrameV1ErrorInvalidBuffer ZwlrScreencopyFrameV1Error = 1 +) + +func (e ZwlrScreencopyFrameV1Error) Name() string { + switch e { + case ZwlrScreencopyFrameV1ErrorAlreadyUsed: + return "already_used" + case ZwlrScreencopyFrameV1ErrorInvalidBuffer: + return "invalid_buffer" + default: + return "" + } +} + +func (e ZwlrScreencopyFrameV1Error) Value() string { + switch e { + case ZwlrScreencopyFrameV1ErrorAlreadyUsed: + return "0" + case ZwlrScreencopyFrameV1ErrorInvalidBuffer: + return "1" + default: + return "" + } +} + +func (e ZwlrScreencopyFrameV1Error) String() string { + return e.Name() + "=" + e.Value() +} + +type ZwlrScreencopyFrameV1Flags uint32 + +// ZwlrScreencopyFrameV1Flags : +const ( + // ZwlrScreencopyFrameV1FlagsYInvert : contents are y-inverted + ZwlrScreencopyFrameV1FlagsYInvert ZwlrScreencopyFrameV1Flags = 1 +) + +func (e ZwlrScreencopyFrameV1Flags) Name() string { + switch e { + case ZwlrScreencopyFrameV1FlagsYInvert: + return "y_invert" + default: + return "" + } +} + +func (e ZwlrScreencopyFrameV1Flags) Value() string { + switch e { + case ZwlrScreencopyFrameV1FlagsYInvert: + return "1" + default: + return "" + } +} + +func (e ZwlrScreencopyFrameV1Flags) String() string { + return e.Name() + "=" + e.Value() +} + +// ZwlrScreencopyFrameV1BufferEvent : wl_shm buffer information +// +// Provides information about wl_shm buffer parameters that need to be +// used for this frame. This event is sent once after the frame is created +// if wl_shm buffers are supported. +type ZwlrScreencopyFrameV1BufferEvent struct { + Format uint32 + Width uint32 + Height uint32 + Stride uint32 +} +type ZwlrScreencopyFrameV1BufferHandlerFunc func(ZwlrScreencopyFrameV1BufferEvent) + +// SetBufferHandler : sets handler for ZwlrScreencopyFrameV1BufferEvent +func (i *ZwlrScreencopyFrameV1) SetBufferHandler(f ZwlrScreencopyFrameV1BufferHandlerFunc) { + i.bufferHandler = f +} + +// ZwlrScreencopyFrameV1FlagsEvent : frame flags +// +// Provides flags about the frame. This event is sent once before the +// "ready" event. +type ZwlrScreencopyFrameV1FlagsEvent struct { + Flags uint32 +} +type ZwlrScreencopyFrameV1FlagsHandlerFunc func(ZwlrScreencopyFrameV1FlagsEvent) + +// SetFlagsHandler : sets handler for ZwlrScreencopyFrameV1FlagsEvent +func (i *ZwlrScreencopyFrameV1) SetFlagsHandler(f ZwlrScreencopyFrameV1FlagsHandlerFunc) { + i.flagsHandler = f +} + +// ZwlrScreencopyFrameV1ReadyEvent : indicates frame is available for reading +// +// Called as soon as the frame is copied, indicating it is available +// for reading. This event includes the time at which the presentation took place. +// +// The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, +// each component being an unsigned 32-bit value. Whole seconds are in +// tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, +// and the additional fractional part in tv_nsec as nanoseconds. Hence, +// for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part +// may have an arbitrary offset at start. +// +// After receiving this event, the client should destroy the object. +type ZwlrScreencopyFrameV1ReadyEvent struct { + TvSecHi uint32 + TvSecLo uint32 + TvNsec uint32 +} +type ZwlrScreencopyFrameV1ReadyHandlerFunc func(ZwlrScreencopyFrameV1ReadyEvent) + +// SetReadyHandler : sets handler for ZwlrScreencopyFrameV1ReadyEvent +func (i *ZwlrScreencopyFrameV1) SetReadyHandler(f ZwlrScreencopyFrameV1ReadyHandlerFunc) { + i.readyHandler = f +} + +// ZwlrScreencopyFrameV1FailedEvent : frame copy failed +// +// This event indicates that the attempted frame copy has failed. +// +// After receiving this event, the client should destroy the object. +type ZwlrScreencopyFrameV1FailedEvent struct{} +type ZwlrScreencopyFrameV1FailedHandlerFunc func(ZwlrScreencopyFrameV1FailedEvent) + +// SetFailedHandler : sets handler for ZwlrScreencopyFrameV1FailedEvent +func (i *ZwlrScreencopyFrameV1) SetFailedHandler(f ZwlrScreencopyFrameV1FailedHandlerFunc) { + i.failedHandler = f +} + +// ZwlrScreencopyFrameV1DamageEvent : carries the coordinates of the damaged region +// +// This event is sent right before the ready event when copy_with_damage is +// requested. It may be generated multiple times for each copy_with_damage +// request. +// +// The arguments describe a box around an area that has changed since the +// last copy request that was derived from the current screencopy manager +// instance. +// +// The union of all regions received between the call to copy_with_damage +// and a ready event is the total damage since the prior ready event. +type ZwlrScreencopyFrameV1DamageEvent struct { + X uint32 + Y uint32 + Width uint32 + Height uint32 +} +type ZwlrScreencopyFrameV1DamageHandlerFunc func(ZwlrScreencopyFrameV1DamageEvent) + +// SetDamageHandler : sets handler for ZwlrScreencopyFrameV1DamageEvent +func (i *ZwlrScreencopyFrameV1) SetDamageHandler(f ZwlrScreencopyFrameV1DamageHandlerFunc) { + i.damageHandler = f +} + +// ZwlrScreencopyFrameV1LinuxDmabufEvent : linux-dmabuf buffer information +// +// Provides information about linux-dmabuf buffer parameters that need to +// be used for this frame. This event is sent once after the frame is +// created if linux-dmabuf buffers are supported. +type ZwlrScreencopyFrameV1LinuxDmabufEvent struct { + Format uint32 + Width uint32 + Height uint32 +} +type ZwlrScreencopyFrameV1LinuxDmabufHandlerFunc func(ZwlrScreencopyFrameV1LinuxDmabufEvent) + +// SetLinuxDmabufHandler : sets handler for ZwlrScreencopyFrameV1LinuxDmabufEvent +func (i *ZwlrScreencopyFrameV1) SetLinuxDmabufHandler(f ZwlrScreencopyFrameV1LinuxDmabufHandlerFunc) { + i.linuxDmabufHandler = f +} + +// ZwlrScreencopyFrameV1BufferDoneEvent : all buffer types reported +// +// This event is sent once after all buffer events have been sent. +// +// The client should proceed to create a buffer of one of the supported +// types, and send a "copy" request. +type ZwlrScreencopyFrameV1BufferDoneEvent struct{} +type ZwlrScreencopyFrameV1BufferDoneHandlerFunc func(ZwlrScreencopyFrameV1BufferDoneEvent) + +// SetBufferDoneHandler : sets handler for ZwlrScreencopyFrameV1BufferDoneEvent +func (i *ZwlrScreencopyFrameV1) SetBufferDoneHandler(f ZwlrScreencopyFrameV1BufferDoneHandlerFunc) { + i.bufferDoneHandler = f +} + +func (i *ZwlrScreencopyFrameV1) Dispatch(opcode uint32, fd int, data []byte) { + switch opcode { + case 0: + if i.bufferHandler == nil { + return + } + var e ZwlrScreencopyFrameV1BufferEvent + l := 0 + e.Format = client.Uint32(data[l : l+4]) + l += 4 + e.Width = client.Uint32(data[l : l+4]) + l += 4 + e.Height = client.Uint32(data[l : l+4]) + l += 4 + e.Stride = client.Uint32(data[l : l+4]) + l += 4 + + i.bufferHandler(e) + case 1: + if i.flagsHandler == nil { + return + } + var e ZwlrScreencopyFrameV1FlagsEvent + l := 0 + e.Flags = client.Uint32(data[l : l+4]) + l += 4 + + i.flagsHandler(e) + case 2: + if i.readyHandler == nil { + return + } + var e ZwlrScreencopyFrameV1ReadyEvent + l := 0 + e.TvSecHi = client.Uint32(data[l : l+4]) + l += 4 + e.TvSecLo = client.Uint32(data[l : l+4]) + l += 4 + e.TvNsec = client.Uint32(data[l : l+4]) + l += 4 + + i.readyHandler(e) + case 3: + if i.failedHandler == nil { + return + } + var e ZwlrScreencopyFrameV1FailedEvent + + i.failedHandler(e) + case 4: + if i.damageHandler == nil { + return + } + var e ZwlrScreencopyFrameV1DamageEvent + l := 0 + e.X = client.Uint32(data[l : l+4]) + l += 4 + e.Y = client.Uint32(data[l : l+4]) + l += 4 + e.Width = client.Uint32(data[l : l+4]) + l += 4 + e.Height = client.Uint32(data[l : l+4]) + l += 4 + + i.damageHandler(e) + case 5: + if i.linuxDmabufHandler == nil { + return + } + var e ZwlrScreencopyFrameV1LinuxDmabufEvent + l := 0 + e.Format = client.Uint32(data[l : l+4]) + l += 4 + e.Width = client.Uint32(data[l : l+4]) + l += 4 + e.Height = client.Uint32(data[l : l+4]) + l += 4 + + i.linuxDmabufHandler(e) + case 6: + if i.bufferDoneHandler == nil { + return + } + var e ZwlrScreencopyFrameV1BufferDoneEvent + + i.bufferDoneHandler(e) + } +} diff --git a/core/internal/proto/wp_viewporter/viewporter.go b/core/internal/proto/wp_viewporter/viewporter.go new file mode 100644 index 00000000..d5b343c3 --- /dev/null +++ b/core/internal/proto/wp_viewporter/viewporter.go @@ -0,0 +1,399 @@ +// Generated by go-wayland-scanner +// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner +// XML file : /tmp/viewporter.xml +// +// viewporter Protocol Copyright: +// +// Copyright © 2013-2016 Collabora, Ltd. +// +// 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_viewporter + +import "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" + +// WpViewporterInterfaceName 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 WpViewporterInterfaceName = "wp_viewporter" + +// WpViewporter : surface cropping and scaling +// +// The global interface exposing surface cropping and scaling +// capabilities is used to instantiate an interface extension for a +// wl_surface object. This extended interface will then allow +// cropping and scaling the surface contents, effectively +// disconnecting the direct relationship between the buffer and the +// surface size. +type WpViewporter struct { + client.BaseProxy +} + +// NewWpViewporter : surface cropping and scaling +// +// The global interface exposing surface cropping and scaling +// capabilities is used to instantiate an interface extension for a +// wl_surface object. This extended interface will then allow +// cropping and scaling the surface contents, effectively +// disconnecting the direct relationship between the buffer and the +// surface size. +func NewWpViewporter(ctx *client.Context) *WpViewporter { + wpViewporter := &WpViewporter{} + ctx.Register(wpViewporter) + return wpViewporter +} + +// Destroy : unbind from the cropping and scaling interface +// +// Informs the server that the client will not be using this +// protocol object anymore. This does not affect any other objects, +// wp_viewport objects included. +func (i *WpViewporter) Destroy() error { + defer i.Context().Unregister(i) + 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 +} + +// GetViewport : extend surface interface for crop and scale +// +// Instantiate an interface extension for the given wl_surface to +// crop and scale its content. If the given wl_surface already has +// a wp_viewport object associated, the viewport_exists +// protocol error is raised. +// +// surface: the surface +func (i *WpViewporter) GetViewport(surface *client.Surface) (*WpViewport, error) { + id := NewWpViewport(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], id.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], surface.ID()) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return id, err +} + +type WpViewporterError uint32 + +// WpViewporterError : +const ( + // WpViewporterErrorViewportExists : the surface already has a viewport object associated + WpViewporterErrorViewportExists WpViewporterError = 0 +) + +func (e WpViewporterError) Name() string { + switch e { + case WpViewporterErrorViewportExists: + return "viewport_exists" + default: + return "" + } +} + +func (e WpViewporterError) Value() string { + switch e { + case WpViewporterErrorViewportExists: + return "0" + default: + return "" + } +} + +func (e WpViewporterError) String() string { + return e.Name() + "=" + e.Value() +} + +// WpViewportInterfaceName 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 WpViewportInterfaceName = "wp_viewport" + +// WpViewport : crop and scale interface to a wl_surface +// +// An additional interface to a wl_surface object, which allows the +// client to specify the cropping and scaling of the surface +// contents. +// +// This interface works with two concepts: the source rectangle (src_x, +// src_y, src_width, src_height), and the destination size (dst_width, +// dst_height). The contents of the source rectangle are scaled to the +// destination size, and content outside the source rectangle is ignored. +// This state is double-buffered, see wl_surface.commit. +// +// The two parts of crop and scale state are independent: the source +// rectangle, and the destination size. Initially both are unset, that +// is, no scaling is applied. The whole of the current wl_buffer is +// used as the source, and the surface size is as defined in +// wl_surface.attach. +// +// If the destination size is set, it causes the surface size to become +// dst_width, dst_height. The source (rectangle) is scaled to exactly +// this size. This overrides whatever the attached wl_buffer size is, +// unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface +// has no content and therefore no size. Otherwise, the size is always +// at least 1x1 in surface local coordinates. +// +// If the source rectangle is set, it defines what area of the wl_buffer is +// taken as the source. If the source rectangle is set and the destination +// size is not set, then src_width and src_height must be integers, and the +// surface size becomes the source rectangle size. This results in cropping +// without scaling. If src_width or src_height are not integers and +// destination size is not set, the bad_size protocol error is raised when +// the surface state is applied. +// +// The coordinate transformations from buffer pixel coordinates up to +// the surface-local coordinates happen in the following order: +// 1. buffer_transform (wl_surface.set_buffer_transform) +// 2. buffer_scale (wl_surface.set_buffer_scale) +// 3. crop and scale (wp_viewport.set*) +// This means, that the source rectangle coordinates of crop and scale +// are given in the coordinates after the buffer transform and scale, +// i.e. in the coordinates that would be the surface-local coordinates +// if the crop and scale was not applied. +// +// If src_x or src_y are negative, the bad_value protocol error is raised. +// Otherwise, if the source rectangle is partially or completely outside of +// the non-NULL wl_buffer, then the out_of_buffer protocol error is raised +// when the surface state is applied. A NULL wl_buffer does not raise the +// out_of_buffer error. +// +// If the wl_surface associated with the wp_viewport is destroyed, +// all wp_viewport requests except 'destroy' raise the protocol error +// no_surface. +// +// If the wp_viewport object is destroyed, the crop and scale +// state is removed from the wl_surface. The change will be applied +// on the next wl_surface.commit. +type WpViewport struct { + client.BaseProxy +} + +// NewWpViewport : crop and scale interface to a wl_surface +// +// An additional interface to a wl_surface object, which allows the +// client to specify the cropping and scaling of the surface +// contents. +// +// This interface works with two concepts: the source rectangle (src_x, +// src_y, src_width, src_height), and the destination size (dst_width, +// dst_height). The contents of the source rectangle are scaled to the +// destination size, and content outside the source rectangle is ignored. +// This state is double-buffered, see wl_surface.commit. +// +// The two parts of crop and scale state are independent: the source +// rectangle, and the destination size. Initially both are unset, that +// is, no scaling is applied. The whole of the current wl_buffer is +// used as the source, and the surface size is as defined in +// wl_surface.attach. +// +// If the destination size is set, it causes the surface size to become +// dst_width, dst_height. The source (rectangle) is scaled to exactly +// this size. This overrides whatever the attached wl_buffer size is, +// unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface +// has no content and therefore no size. Otherwise, the size is always +// at least 1x1 in surface local coordinates. +// +// If the source rectangle is set, it defines what area of the wl_buffer is +// taken as the source. If the source rectangle is set and the destination +// size is not set, then src_width and src_height must be integers, and the +// surface size becomes the source rectangle size. This results in cropping +// without scaling. If src_width or src_height are not integers and +// destination size is not set, the bad_size protocol error is raised when +// the surface state is applied. +// +// The coordinate transformations from buffer pixel coordinates up to +// the surface-local coordinates happen in the following order: +// 1. buffer_transform (wl_surface.set_buffer_transform) +// 2. buffer_scale (wl_surface.set_buffer_scale) +// 3. crop and scale (wp_viewport.set*) +// This means, that the source rectangle coordinates of crop and scale +// are given in the coordinates after the buffer transform and scale, +// i.e. in the coordinates that would be the surface-local coordinates +// if the crop and scale was not applied. +// +// If src_x or src_y are negative, the bad_value protocol error is raised. +// Otherwise, if the source rectangle is partially or completely outside of +// the non-NULL wl_buffer, then the out_of_buffer protocol error is raised +// when the surface state is applied. A NULL wl_buffer does not raise the +// out_of_buffer error. +// +// If the wl_surface associated with the wp_viewport is destroyed, +// all wp_viewport requests except 'destroy' raise the protocol error +// no_surface. +// +// If the wp_viewport object is destroyed, the crop and scale +// state is removed from the wl_surface. The change will be applied +// on the next wl_surface.commit. +func NewWpViewport(ctx *client.Context) *WpViewport { + wpViewport := &WpViewport{} + ctx.Register(wpViewport) + return wpViewport +} + +// Destroy : remove scaling and cropping from the surface +// +// The associated wl_surface's crop and scale state is removed. +// The change is applied on the next wl_surface.commit. +func (i *WpViewport) Destroy() error { + defer i.Context().Unregister(i) + 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 +} + +// SetSource : set the source rectangle for cropping +// +// Set the source rectangle of the associated wl_surface. See +// wp_viewport for the description, and relation to the wl_buffer +// size. +// +// If all of x, y, width and height are -1.0, the source rectangle is +// unset instead. Any other set of values where width or height are zero +// or negative, or x or y are negative, raise the bad_value protocol +// error. +// +// The crop and scale state is double-buffered, see wl_surface.commit. +// +// x: source rectangle x +// y: source rectangle y +// width: source rectangle width +// height: source rectangle height +func (i *WpViewport) SetSource(x, y, width, height float64) error { + const opcode = 1 + const _reqBufLen = 8 + 4 + 4 + 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.PutFixed(_reqBuf[l:l+4], x) + l += 4 + client.PutFixed(_reqBuf[l:l+4], y) + l += 4 + client.PutFixed(_reqBuf[l:l+4], width) + l += 4 + client.PutFixed(_reqBuf[l:l+4], height) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// SetDestination : set the surface size for scaling +// +// Set the destination size of the associated wl_surface. See +// wp_viewport for the description, and relation to the wl_buffer +// size. +// +// If width is -1 and height is -1, the destination size is unset +// instead. Any other pair of values for width and height that +// contains zero or negative values raises the bad_value protocol +// error. +// +// The crop and scale state is double-buffered, see wl_surface.commit. +// +// width: surface width +// height: surface height +func (i *WpViewport) SetDestination(width, height int32) error { + const opcode = 2 + 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(width)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(height)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +type WpViewportError uint32 + +// WpViewportError : +const ( + // WpViewportErrorBadValue : negative or zero values in width or height + WpViewportErrorBadValue WpViewportError = 0 + // WpViewportErrorBadSize : destination size is not integer + WpViewportErrorBadSize WpViewportError = 1 + // WpViewportErrorOutOfBuffer : source rectangle extends outside of the content area + WpViewportErrorOutOfBuffer WpViewportError = 2 + // WpViewportErrorNoSurface : the wl_surface was destroyed + WpViewportErrorNoSurface WpViewportError = 3 +) + +func (e WpViewportError) Name() string { + switch e { + case WpViewportErrorBadValue: + return "bad_value" + case WpViewportErrorBadSize: + return "bad_size" + case WpViewportErrorOutOfBuffer: + return "out_of_buffer" + case WpViewportErrorNoSurface: + return "no_surface" + default: + return "" + } +} + +func (e WpViewportError) Value() string { + switch e { + case WpViewportErrorBadValue: + return "0" + case WpViewportErrorBadSize: + return "1" + case WpViewportErrorOutOfBuffer: + return "2" + case WpViewportErrorNoSurface: + return "3" + default: + return "" + } +} + +func (e WpViewportError) String() string { + return e.Name() + "=" + e.Value() +} diff --git a/core/internal/proto/xml/wlr-layer-shell-unstable-v1.xml b/core/internal/proto/xml/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 00000000..e9f27e4f --- /dev/null +++ b/core/internal/proto/xml/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,407 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + + + + + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + + + + + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + + + + + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + + + + + + + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Keyboard interactivity is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + + + + + + Requests an edge for the exclusive zone to apply. The exclusive + edge will be automatically deduced from anchor points when possible, + but when the surface is anchored to a corner, it will be necessary + to set it explicitly to disambiguate, as it is not possible to deduce + which one of the two corner edges should be used. + + The edge must be one the surface is anchored to, otherwise the + invalid_exclusive_edge protocol error will be raised. + + + + + diff --git a/core/internal/proto/xml/wlr-screencopy-unstable-v1.xml b/core/internal/proto/xml/wlr-screencopy-unstable-v1.xml new file mode 100644 index 00000000..8b206612 --- /dev/null +++ b/core/internal/proto/xml/wlr-screencopy-unstable-v1.xml @@ -0,0 +1,234 @@ + + + + Copyright © 2018 Simon Ser + Copyright © 2019 Andri Yngvason + + 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 protocol allows clients to ask the compositor to copy part of the + screen content to a client buffer. + + 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. + + Note! This protocol is deprecated and not intended for production use. + The ext-image-copy-capture-v1 protocol should be used instead. + + + + + This object is a manager which offers requests to start capturing from a + source. + + + + + Capture the next frame of an entire output. + + + + + + + + + Capture the next frame of an output's region. + + The region is given in output logical coordinates, see + xdg_output.logical_size. The region will be clipped to the output's + extents. + + + + + + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object represents a single frame. + + When created, a series of buffer events will be sent, each representing a + supported buffer type. The "buffer_done" event is sent afterwards to + indicate that all supported buffer types have been enumerated. The client + will then be able to send a "copy" request. If the capture is successful, + the compositor will send a "flags" event followed by a "ready" event. + + For objects version 2 or lower, wl_shm buffers are always supported, ie. + the "buffer" event is guaranteed to be sent. + + If the capture failed, the "failed" event is sent. This can happen anytime + before the "ready" event. + + Once either a "ready" or a "failed" event is received, the client should + destroy the frame. + + + + + Provides information about wl_shm buffer parameters that need to be + used for this frame. This event is sent once after the frame is created + if wl_shm buffers are supported. + + + + + + + + + + Copy the frame to the supplied buffer. The buffer must have the + correct size, see zwlr_screencopy_frame_v1.buffer and + zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a + supported format. + + If the frame is successfully copied, "flags" and "ready" events are + sent. Otherwise, a "failed" event is sent. + + + + + + + + + + + + + + + + Provides flags about the frame. This event is sent once before the + "ready" event. + + + + + + + Called as soon as the frame is copied, indicating it is available + for reading. This event includes the time at which the presentation took place. + + The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, + each component being an unsigned 32-bit value. Whole seconds are in + tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, + and the additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part + may have an arbitrary offset at start. + + After receiving this event, the client should destroy the object. + + + + + + + + + This event indicates that the attempted frame copy has failed. + + After receiving this event, the client should destroy the object. + + + + + + Destroys the frame. This request can be sent at any time by the client. + + + + + + + Same as copy, except it waits until there is damage to copy. + + + + + + + This event is sent right before the ready event when copy_with_damage is + requested. It may be generated multiple times for each copy_with_damage + request. + + The arguments describe a box around an area that has changed since the + last copy request that was derived from the current screencopy manager + instance. + + The union of all regions received between the call to copy_with_damage + and a ready event is the total damage since the prior ready event. + + + + + + + + + + + Provides information about linux-dmabuf buffer parameters that need to + be used for this frame. This event is sent once after the frame is + created if linux-dmabuf buffers are supported. + + + + + + + + + This event is sent once after all buffer events have been sent. + + The client should proceed to create a buffer of one of the supported + types, and send a "copy" request. + + + + diff --git a/core/pkg/go-wayland/wayland/stable/xdg-shell/xdg_shell.go b/core/pkg/go-wayland/wayland/stable/xdg-shell/xdg_shell.go new file mode 100644 index 00000000..19a203b2 --- /dev/null +++ b/core/pkg/go-wayland/wayland/stable/xdg-shell/xdg_shell.go @@ -0,0 +1,13 @@ +package xdg_shell + +import "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" + +type Popup struct { + client.BaseProxy +} + +func NewPopup(ctx *client.Context) *Popup { + p := &Popup{} + ctx.Register(p) + return p +}