diff options
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot')
11 files changed, 0 insertions, 3900 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/compositor.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/compositor.go deleted file mode 100644 index 9bd5f0e..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/compositor.go +++ /dev/null @@ -1,694 +0,0 @@ -package screenshot - -import ( - "encoding/json" - "fmt" - "os" - "os/exec" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/dwl_ipc" - "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management" - wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" -) - -type Compositor int - -const ( - CompositorUnknown Compositor = iota - CompositorHyprland - CompositorSway - CompositorNiri - CompositorDWL - CompositorScroll - CompositorMiracle -) - -var detectedCompositor Compositor = -1 - -func DetectCompositor() Compositor { - if detectedCompositor >= 0 { - return detectedCompositor - } - - hyprlandSig := os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") - niriSocket := os.Getenv("NIRI_SOCKET") - swaySocket := os.Getenv("SWAYSOCK") - scrollSocket := os.Getenv("SCROLLSOCK") - miracleSocket := os.Getenv("MIRACLESOCK") - - switch { - case niriSocket != "": - if _, err := os.Stat(niriSocket); err == nil { - detectedCompositor = CompositorNiri - return detectedCompositor - } - case scrollSocket != "": - if _, err := os.Stat(scrollSocket); err == nil { - detectedCompositor = CompositorScroll - return detectedCompositor - } - case miracleSocket != "": - if _, err := os.Stat(miracleSocket); err == nil { - detectedCompositor = CompositorMiracle - return detectedCompositor - } - case swaySocket != "": - if _, err := os.Stat(swaySocket); err == nil { - detectedCompositor = CompositorSway - return detectedCompositor - } - case hyprlandSig != "": - detectedCompositor = CompositorHyprland - return detectedCompositor - } - - if detectDWLProtocol() { - detectedCompositor = CompositorDWL - return detectedCompositor - } - - detectedCompositor = CompositorUnknown - return detectedCompositor -} - -func detectDWLProtocol() bool { - display, err := client.Connect("") - if err != nil { - return false - } - ctx := display.Context() - defer ctx.Close() - - registry, err := display.GetRegistry() - if err != nil { - return false - } - - found := false - registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) { - if e.Interface == dwl_ipc.ZdwlIpcManagerV2InterfaceName { - found = true - } - }) - - if err := wlhelpers.Roundtrip(display, ctx); err != nil { - return false - } - - return found -} - -func SetCompositorDWL() { - detectedCompositor = CompositorDWL -} - -type WindowGeometry struct { - X int32 - Y int32 - Width int32 - Height int32 - Output string - Scale float64 - OutputX int32 - OutputY int32 - OutputTransform int32 -} - -func GetActiveWindow() (*WindowGeometry, error) { - switch DetectCompositor() { - case CompositorHyprland: - return getHyprlandActiveWindow() - case CompositorDWL: - return getDWLActiveWindow() - default: - return nil, fmt.Errorf("window capture requires Hyprland or DWL") - } -} - -type hyprlandWindow struct { - At [2]int32 `json:"at"` - Size [2]int32 `json:"size"` -} - -func getHyprlandActiveWindow() (*WindowGeometry, error) { - output, err := exec.Command("hyprctl", "-j", "activewindow").Output() - if err != nil { - return nil, fmt.Errorf("hyprctl activewindow: %w", err) - } - - var win hyprlandWindow - if err := json.Unmarshal(output, &win); err != nil { - return nil, fmt.Errorf("parse activewindow: %w", err) - } - - if win.Size[0] <= 0 || win.Size[1] <= 0 { - return nil, fmt.Errorf("no active window") - } - - return &WindowGeometry{ - X: win.At[0], - Y: win.At[1], - Width: win.Size[0], - Height: win.Size[1], - }, nil -} - -type hyprlandMonitor struct { - Name string `json:"name"` - X int32 `json:"x"` - Y int32 `json:"y"` - Width int32 `json:"width"` - Height int32 `json:"height"` - Scale float64 `json:"scale"` - Focused bool `json:"focused"` -} - -func GetHyprlandMonitorScale(name string) float64 { - output, err := exec.Command("hyprctl", "-j", "monitors").Output() - if err != nil { - return 0 - } - - var monitors []hyprlandMonitor - if err := json.Unmarshal(output, &monitors); err != nil { - return 0 - } - - for _, m := range monitors { - if m.Name == name { - return m.Scale - } - } - return 0 -} - -func getHyprlandFocusedMonitor() string { - output, err := exec.Command("hyprctl", "-j", "monitors").Output() - if err != nil { - return "" - } - - var monitors []hyprlandMonitor - if err := json.Unmarshal(output, &monitors); err != nil { - return "" - } - - for _, m := range monitors { - if m.Focused { - return m.Name - } - } - return "" -} - -func GetHyprlandMonitorGeometry(name string) (x, y, w, h int32, ok bool) { - output, err := exec.Command("hyprctl", "-j", "monitors").Output() - if err != nil { - return 0, 0, 0, 0, false - } - - var monitors []hyprlandMonitor - if err := json.Unmarshal(output, &monitors); err != nil { - return 0, 0, 0, 0, false - } - - for _, m := range monitors { - if m.Name == name { - logicalW := int32(float64(m.Width) / m.Scale) - logicalH := int32(float64(m.Height) / m.Scale) - return m.X, m.Y, logicalW, logicalH, true - } - } - return 0, 0, 0, 0, false -} - -type swayWorkspace struct { - Output string `json:"output"` - Focused bool `json:"focused"` -} - -func getSwayFocusedMonitor() string { - output, err := exec.Command("swaymsg", "-t", "get_workspaces").Output() - if err != nil { - return "" - } - - var workspaces []swayWorkspace - if err := json.Unmarshal(output, &workspaces); err != nil { - return "" - } - - for _, ws := range workspaces { - if ws.Focused { - return ws.Output - } - } - return "" -} - -func getScrollFocusedMonitor() string { - output, err := exec.Command("scrollmsg", "-t", "get_workspaces").Output() - if err != nil { - return "" - } - - var workspaces []swayWorkspace - if err := json.Unmarshal(output, &workspaces); err != nil { - return "" - } - - for _, ws := range workspaces { - if ws.Focused { - return ws.Output - } - } - return "" -} - -func getMiracleFocusedMonitor() string { - output, err := exec.Command("miraclemsg", "-t", "get_workspaces").Output() - if err != nil { - return "" - } - - var workspaces []swayWorkspace - if err := json.Unmarshal(output, &workspaces); err != nil { - return "" - } - - for _, ws := range workspaces { - if ws.Focused { - return ws.Output - } - } - return "" -} - -type niriWorkspace struct { - Output string `json:"output"` - IsFocused bool `json:"is_focused"` -} - -func getNiriFocusedMonitor() string { - output, err := exec.Command("niri", "msg", "-j", "workspaces").Output() - if err != nil { - return "" - } - - var workspaces []niriWorkspace - if err := json.Unmarshal(output, &workspaces); err != nil { - return "" - } - - for _, ws := range workspaces { - if ws.IsFocused { - return ws.Output - } - } - return "" -} - -var dwlActiveOutput string - -func SetDWLActiveOutput(name string) { - dwlActiveOutput = name -} - -func getDWLFocusedMonitor() string { - if dwlActiveOutput != "" { - return dwlActiveOutput - } - return queryDWLActiveOutput() -} - -func queryDWLActiveOutput() string { - display, err := client.Connect("") - if err != nil { - return "" - } - ctx := display.Context() - defer ctx.Close() - - registry, err := display.GetRegistry() - if err != nil { - return "" - } - - var dwlManager *dwl_ipc.ZdwlIpcManagerV2 - outputs := make(map[uint32]*client.Output) - - registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) { - switch e.Interface { - case dwl_ipc.ZdwlIpcManagerV2InterfaceName: - mgr := dwl_ipc.NewZdwlIpcManagerV2(ctx) - if err := registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil { - dwlManager = mgr - } - case client.OutputInterfaceName: - out := client.NewOutput(ctx) - version := e.Version - if version > 4 { - version = 4 - } - if err := registry.Bind(e.Name, e.Interface, version, out); err == nil { - outputs[e.Name] = out - } - } - }) - - if err := wlhelpers.Roundtrip(display, ctx); err != nil { - return "" - } - - if dwlManager == nil || len(outputs) == 0 { - return "" - } - - outputNames := make(map[uint32]string) - for name, out := range outputs { - n := name - out.SetNameHandler(func(e client.OutputNameEvent) { - outputNames[n] = e.Name - }) - } - - if err := wlhelpers.Roundtrip(display, ctx); err != nil { - return "" - } - - type outputState struct { - name string - active bool - gotFrame bool - } - states := make(map[uint32]*outputState) - - for name, out := range outputs { - dwlOut, err := dwlManager.GetOutput(out) - if err != nil { - continue - } - state := &outputState{name: outputNames[name]} - states[name] = state - - dwlOut.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) { - state.active = e.Active != 0 - }) - dwlOut.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) { - state.gotFrame = true - }) - } - - allFramesReceived := func() bool { - for _, s := range states { - if !s.gotFrame { - return false - } - } - return true - } - - for !allFramesReceived() { - if err := ctx.Dispatch(); err != nil { - return "" - } - } - - for _, state := range states { - if state.active { - return state.name - } - } - - return "" -} - -func GetFocusedMonitor() string { - switch DetectCompositor() { - case CompositorHyprland: - return getHyprlandFocusedMonitor() - case CompositorSway: - return getSwayFocusedMonitor() - case CompositorScroll: - return getScrollFocusedMonitor() - case CompositorMiracle: - return getMiracleFocusedMonitor() - case CompositorNiri: - return getNiriFocusedMonitor() - case CompositorDWL: - return getDWLFocusedMonitor() - } - return "" -} - -type outputInfo struct { - x, y int32 - scale float64 - transform int32 -} - -func getAllOutputInfos() map[string]*outputInfo { - display, err := client.Connect("") - if err != nil { - return nil - } - ctx := display.Context() - defer ctx.Close() - - registry, err := display.GetRegistry() - if err != nil { - return nil - } - - var outputManager *wlr_output_management.ZwlrOutputManagerV1 - - registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) { - if e.Interface == wlr_output_management.ZwlrOutputManagerV1InterfaceName { - mgr := wlr_output_management.NewZwlrOutputManagerV1(ctx) - version := e.Version - if version > 4 { - version = 4 - } - if err := registry.Bind(e.Name, e.Interface, version, mgr); err == nil { - outputManager = mgr - } - } - }) - - if err := wlhelpers.Roundtrip(display, ctx); err != nil { - return nil - } - - if outputManager == nil { - return nil - } - - type headState struct { - name string - x, y int32 - scale float64 - transform int32 - } - heads := make(map[*wlr_output_management.ZwlrOutputHeadV1]*headState) - done := false - - outputManager.SetHeadHandler(func(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) { - state := &headState{} - heads[e.Head] = state - e.Head.SetNameHandler(func(ne wlr_output_management.ZwlrOutputHeadV1NameEvent) { - state.name = ne.Name - }) - e.Head.SetPositionHandler(func(pe wlr_output_management.ZwlrOutputHeadV1PositionEvent) { - state.x = pe.X - state.y = pe.Y - }) - e.Head.SetScaleHandler(func(se wlr_output_management.ZwlrOutputHeadV1ScaleEvent) { - state.scale = se.Scale - }) - e.Head.SetTransformHandler(func(te wlr_output_management.ZwlrOutputHeadV1TransformEvent) { - state.transform = te.Transform - }) - }) - outputManager.SetDoneHandler(func(e wlr_output_management.ZwlrOutputManagerV1DoneEvent) { - done = true - }) - - for !done { - if err := ctx.Dispatch(); err != nil { - return nil - } - } - - result := make(map[string]*outputInfo, len(heads)) - for _, state := range heads { - if state.name == "" { - continue - } - result[state.name] = &outputInfo{ - x: state.x, - y: state.y, - scale: state.scale, - transform: state.transform, - } - } - return result -} - -func getOutputInfo(outputName string) (*outputInfo, bool) { - infos := getAllOutputInfos() - if infos == nil { - return nil, false - } - info, ok := infos[outputName] - return info, ok -} - -func getDWLActiveWindow() (*WindowGeometry, error) { - display, err := client.Connect("") - if err != nil { - return nil, fmt.Errorf("connect: %w", err) - } - ctx := display.Context() - defer ctx.Close() - - registry, err := display.GetRegistry() - if err != nil { - return nil, fmt.Errorf("get registry: %w", err) - } - - var dwlManager *dwl_ipc.ZdwlIpcManagerV2 - outputs := make(map[uint32]*client.Output) - - registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) { - switch e.Interface { - case dwl_ipc.ZdwlIpcManagerV2InterfaceName: - mgr := dwl_ipc.NewZdwlIpcManagerV2(ctx) - if err := registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil { - dwlManager = mgr - } - case client.OutputInterfaceName: - out := client.NewOutput(ctx) - version := e.Version - if version > 4 { - version = 4 - } - if err := registry.Bind(e.Name, e.Interface, version, out); err == nil { - outputs[e.Name] = out - } - } - }) - - if err := wlhelpers.Roundtrip(display, ctx); err != nil { - return nil, fmt.Errorf("roundtrip: %w", err) - } - - if dwlManager == nil { - return nil, fmt.Errorf("dwl_ipc_manager not available") - } - - if len(outputs) == 0 { - return nil, fmt.Errorf("no outputs found") - } - - outputNames := make(map[uint32]string) - for name, out := range outputs { - n := name - out.SetNameHandler(func(e client.OutputNameEvent) { - outputNames[n] = e.Name - }) - } - - if err := wlhelpers.Roundtrip(display, ctx); err != nil { - return nil, fmt.Errorf("roundtrip: %w", err) - } - - type dwlOutputState struct { - output *dwl_ipc.ZdwlIpcOutputV2 - name string - active bool - x, y int32 - w, h int32 - scalefactor uint32 - gotFrame bool - } - - dwlOutputs := make(map[uint32]*dwlOutputState) - for name, out := range outputs { - dwlOut, err := dwlManager.GetOutput(out) - if err != nil { - continue - } - state := &dwlOutputState{output: dwlOut, name: outputNames[name]} - dwlOutputs[name] = state - - dwlOut.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) { - state.active = e.Active != 0 - }) - dwlOut.SetXHandler(func(e dwl_ipc.ZdwlIpcOutputV2XEvent) { - state.x = e.X - }) - dwlOut.SetYHandler(func(e dwl_ipc.ZdwlIpcOutputV2YEvent) { - state.y = e.Y - }) - dwlOut.SetWidthHandler(func(e dwl_ipc.ZdwlIpcOutputV2WidthEvent) { - state.w = e.Width - }) - dwlOut.SetHeightHandler(func(e dwl_ipc.ZdwlIpcOutputV2HeightEvent) { - state.h = e.Height - }) - dwlOut.SetScalefactorHandler(func(e dwl_ipc.ZdwlIpcOutputV2ScalefactorEvent) { - state.scalefactor = e.Scalefactor - }) - dwlOut.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) { - state.gotFrame = true - }) - } - - allFramesReceived := func() bool { - for _, s := range dwlOutputs { - if !s.gotFrame { - return false - } - } - return true - } - - for !allFramesReceived() { - if err := ctx.Dispatch(); err != nil { - return nil, fmt.Errorf("dispatch: %w", err) - } - } - - for _, state := range dwlOutputs { - if !state.active { - continue - } - if state.w <= 0 || state.h <= 0 { - return nil, fmt.Errorf("no active window") - } - scale := float64(state.scalefactor) / 100.0 - if scale <= 0 { - scale = 1.0 - } - - geom := &WindowGeometry{ - X: state.x, - Y: state.y, - Width: state.w, - Height: state.h, - Output: state.name, - Scale: scale, - } - - if info, ok := getOutputInfo(state.name); ok { - geom.OutputX = info.x - geom.OutputY = info.y - geom.OutputTransform = info.transform - } - - return geom, nil - } - - return nil, fmt.Errorf("no active output found") -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/encode.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/encode.go deleted file mode 100644 index 8b40dca..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/encode.go +++ /dev/null @@ -1,172 +0,0 @@ -package screenshot - -import ( - "bufio" - "fmt" - "image" - "image/jpeg" - "image/png" - "io" - "os" - "path/filepath" - "strings" - "time" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/internal/utils" -) - -func BufferToImage(buf *ShmBuffer) *image.RGBA { - return BufferToImageWithFormat(buf, uint32(FormatARGB8888)) -} - -func BufferToImageWithFormat(buf *ShmBuffer, format uint32) *image.RGBA { - img := image.NewRGBA(image.Rect(0, 0, buf.Width, buf.Height)) - data := buf.Data() - - var swapRB bool - switch format { - case uint32(FormatABGR8888), uint32(FormatXBGR8888): - swapRB = false - default: - swapRB = true - } - - for y := 0; y < buf.Height; y++ { - srcOff := y * buf.Stride - dstOff := y * img.Stride - for x := 0; x < buf.Width; x++ { - si := srcOff + x*4 - di := dstOff + x*4 - if si+3 >= len(data) || di+3 >= len(img.Pix) { - continue - } - if swapRB { - img.Pix[di+0] = data[si+2] - img.Pix[di+1] = data[si+1] - img.Pix[di+2] = data[si+0] - } else { - img.Pix[di+0] = data[si+0] - img.Pix[di+1] = data[si+1] - img.Pix[di+2] = data[si+2] - } - img.Pix[di+3] = 255 - } - } - return img -} - -func EncodePNG(w io.Writer, img image.Image) error { - enc := png.Encoder{CompressionLevel: png.BestSpeed} - return enc.Encode(w, img) -} - -func EncodeJPEG(w io.Writer, img image.Image, quality int) error { - return jpeg.Encode(w, img, &jpeg.Options{Quality: quality}) -} - -func EncodePPM(w io.Writer, img *image.RGBA) error { - bw := bufio.NewWriter(w) - bounds := img.Bounds() - if _, err := fmt.Fprintf(bw, "P6\n%d %d\n255\n", bounds.Dx(), bounds.Dy()); err != nil { - return err - } - for y := bounds.Min.Y; y < bounds.Max.Y; y++ { - for x := bounds.Min.X; x < bounds.Max.X; x++ { - off := (y-bounds.Min.Y)*img.Stride + (x-bounds.Min.X)*4 - if err := bw.WriteByte(img.Pix[off+0]); err != nil { - return err - } - if err := bw.WriteByte(img.Pix[off+1]); err != nil { - return err - } - if err := bw.WriteByte(img.Pix[off+2]); err != nil { - return err - } - } - } - return bw.Flush() -} - -func GenerateFilename(format Format) string { - t := time.Now() - ext := "png" - switch format { - case FormatJPEG: - ext = "jpg" - case FormatPPM: - ext = "ppm" - } - return fmt.Sprintf("screenshot_%s.%s", t.Format("2006-01-02_15-04-05"), ext) -} - -func GetOutputDir() string { - if dir := os.Getenv("DMS_SCREENSHOT_DIR"); dir != "" { - return dir - } - - if xdgPics := getXDGPicturesDir(); xdgPics != "" { - screenshotDir := filepath.Join(xdgPics, "Screenshots") - if err := os.MkdirAll(screenshotDir, 0o755); err == nil { - return screenshotDir - } - return xdgPics - } - - if home := os.Getenv("HOME"); home != "" { - return home - } - return "." -} - -func getXDGPicturesDir() string { - userConfigDir, err := os.UserConfigDir() - if err != nil { - log.Error("failed to get user config dir", "err", err) - return "" - } - userDirsFile := filepath.Join(userConfigDir, "user-dirs.dirs") - data, err := os.ReadFile(userDirsFile) - if err != nil { - return "" - } - - for _, line := range strings.Split(string(data), "\n") { - if len(line) == 0 || line[0] == '#' { - continue - } - const prefix = "XDG_PICTURES_DIR=" - if !strings.HasPrefix(line, prefix) { - continue - } - path := strings.Trim(line[len(prefix):], "\"") - expanded, err := utils.ExpandPath(path) - if err != nil { - return "" - } - return expanded - } - return "" -} - -func WriteToFile(buf *ShmBuffer, path string, format Format, quality int) error { - return WriteToFileWithFormat(buf, path, format, quality, uint32(FormatARGB8888)) -} - -func WriteToFileWithFormat(buf *ShmBuffer, path string, format Format, quality int, pixelFormat uint32) error { - f, err := os.Create(path) - if err != nil { - return err - } - defer f.Close() - - img := BufferToImageWithFormat(buf, pixelFormat) - switch format { - case FormatJPEG: - return EncodeJPEG(f, img, quality) - case FormatPPM: - return EncodePPM(f, img) - default: - return EncodePNG(f, img) - } -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/notify.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/notify.go deleted file mode 100644 index 9633b42..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/notify.go +++ /dev/null @@ -1,180 +0,0 @@ -package screenshot - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "strconv" - "syscall" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/godbus/dbus/v5" -) - -const ( - notifyDest = "org.freedesktop.Notifications" - notifyPath = "/org/freedesktop/Notifications" - notifyInterface = "org.freedesktop.Notifications" -) - -type NotifyResult struct { - FilePath string - Clipboard bool - ImageData []byte - Width int - Height int -} - -func SendNotification(result NotifyResult) { - conn, err := dbus.SessionBus() - if err != nil { - log.Debug("dbus session failed", "err", err) - return - } - - var actions []string - if result.FilePath != "" { - actions = []string{"default", "Open"} - } - - hints := map[string]dbus.Variant{} - if len(result.ImageData) > 0 && result.Width > 0 && result.Height > 0 { - rowstride := result.Width * 3 - hints["image_data"] = dbus.MakeVariant(struct { - Width int32 - Height int32 - Rowstride int32 - HasAlpha bool - BitsPerSample int32 - Channels int32 - Data []byte - }{ - Width: int32(result.Width), - Height: int32(result.Height), - Rowstride: int32(rowstride), - HasAlpha: false, - BitsPerSample: 8, - Channels: 3, - Data: result.ImageData, - }) - } else if result.FilePath != "" { - hints["image_path"] = dbus.MakeVariant(result.FilePath) - } - - summary := "Screenshot captured" - body := "" - if result.Clipboard && result.FilePath != "" { - body = fmt.Sprintf("Copied to clipboard\n%s", filepath.Base(result.FilePath)) - } else if result.Clipboard { - body = "Copied to clipboard" - } else if result.FilePath != "" { - body = filepath.Base(result.FilePath) - } - - obj := conn.Object(notifyDest, notifyPath) - call := obj.Call( - notifyInterface+".Notify", - 0, - "DMS", - uint32(0), - "", - summary, - body, - actions, - hints, - int32(5000), - ) - - if call.Err != nil { - log.Debug("notify call failed", "err", call.Err) - return - } - - var notificationID uint32 - if err := call.Store(¬ificationID); err != nil { - log.Debug("failed to get notification id", "err", err) - return - } - - if len(actions) == 0 || result.FilePath == "" { - return - } - - spawnActionListener(notificationID, result.FilePath) -} - -func spawnActionListener(notificationID uint32, filePath string) { - exe, err := os.Executable() - if err != nil { - log.Debug("failed to get executable", "err", err) - return - } - - cmd := exec.Command(exe, "notify-action", fmt.Sprintf("%d", notificationID), filePath) - cmd.SysProcAttr = &syscall.SysProcAttr{ - Setsid: true, - } - cmd.Start() -} - -func RunNotifyActionListener(args []string) { - if len(args) < 2 { - return - } - - notificationID, err := strconv.ParseUint(args[0], 10, 32) - if err != nil { - return - } - - filePath := args[1] - - conn, err := dbus.SessionBus() - if err != nil { - return - } - - if err := conn.AddMatchSignal( - dbus.WithMatchObjectPath(notifyPath), - dbus.WithMatchInterface(notifyInterface), - ); err != nil { - return - } - - signals := make(chan *dbus.Signal, 10) - conn.Signal(signals) - - for sig := range signals { - switch sig.Name { - case notifyInterface + ".ActionInvoked": - if len(sig.Body) < 2 { - continue - } - id, ok := sig.Body[0].(uint32) - if !ok || id != uint32(notificationID) { - continue - } - openFile(filePath) - return - - case notifyInterface + ".NotificationClosed": - if len(sig.Body) < 1 { - continue - } - id, ok := sig.Body[0].(uint32) - if !ok || id != uint32(notificationID) { - continue - } - return - } - } -} - -func openFile(filePath string) { - cmd := exec.Command("xdg-open", filePath) - cmd.SysProcAttr = &syscall.SysProcAttr{ - Setsid: true, - } - cmd.Start() -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region.go deleted file mode 100644 index f296434..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region.go +++ /dev/null @@ -1,859 +0,0 @@ -package screenshot - -import ( - "fmt" - "sync" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/keyboard_shortcuts_inhibit" - "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_layer_shell" - "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_screencopy" - "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wp_viewporter" - wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" -) - -type SelectionState struct { - hasSelection bool // There's a selection to display (pre-loaded or user-drawn) - dragging bool // User is actively drawing a new selection - surface *OutputSurface // Surface where selection was made - // Surface-local logical coordinates (from pointer events) - anchorX float64 - anchorY float64 - currentX float64 - currentY float64 -} - -type RenderSlot struct { - shm *ShmBuffer - pool *client.ShmPool - wlBuf *client.Buffer - busy bool -} - -type OutputSurface struct { - output *WaylandOutput - wlSurface *client.Surface - layerSurf *wlr_layer_shell.ZwlrLayerSurfaceV1 - viewport *wp_viewporter.WpViewport - screenBuf *ShmBuffer - screenBufNoCursor *ShmBuffer - screenFormat uint32 - logicalW int - logicalH int - configured bool - yInverted bool - - // Triple-buffered render slots - slots [3]*RenderSlot - slotsReady bool -} - -type PreCapture struct { - screenBuf *ShmBuffer - screenBufNoCursor *ShmBuffer - format uint32 - yInverted bool -} - -type RegionSelector struct { - screenshoter *Screenshoter - - display *client.Display - registry *client.Registry - ctx *client.Context - - compositor *client.Compositor - shm *client.Shm - seat *client.Seat - pointer *client.Pointer - keyboard *client.Keyboard - layerShell *wlr_layer_shell.ZwlrLayerShellV1 - screencopy *wlr_screencopy.ZwlrScreencopyManagerV1 - viewporter *wp_viewporter.WpViewporter - - shortcutsInhibitMgr *keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitManagerV1 - shortcutsInhibitor *keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitorV1 - - outputs map[uint32]*WaylandOutput - outputsMu sync.Mutex - preCapture map[*WaylandOutput]*PreCapture - - surfaces []*OutputSurface - activeSurface *OutputSurface - - // Cursor surface for crosshair - cursorSurface *client.Surface - cursorBuffer *ShmBuffer - cursorWlBuf *client.Buffer - cursorPool *client.ShmPool - - selection SelectionState - pointerX float64 - pointerY float64 - preSelect Region - showCapturedCursor bool - shiftHeld bool - - running bool - cancelled bool - result Region - - capturedBuffer *ShmBuffer - capturedRegion Region -} - -func NewRegionSelector(s *Screenshoter) *RegionSelector { - return &RegionSelector{ - screenshoter: s, - outputs: make(map[uint32]*WaylandOutput), - preCapture: make(map[*WaylandOutput]*PreCapture), - showCapturedCursor: s.config.Cursor == CursorOn, - } -} - -func (r *RegionSelector) Run() (*CaptureResult, bool, error) { - if r.screenshoter != nil && r.screenshoter.config.Reset { - r.preSelect = Region{} - } else { - r.preSelect = GetLastRegion() - } - - if err := r.connect(); err != nil { - return nil, false, fmt.Errorf("wayland connect: %w", err) - } - defer r.cleanup() - - if err := r.setupRegistry(); err != nil { - return nil, false, fmt.Errorf("registry setup: %w", err) - } - - if err := r.roundtrip(); err != nil { - return nil, false, fmt.Errorf("roundtrip after registry: %w", err) - } - - switch { - case r.screencopy == nil: - return nil, false, fmt.Errorf("compositor does not support wlr-screencopy-unstable-v1") - case r.layerShell == nil: - return nil, false, fmt.Errorf("compositor does not support wlr-layer-shell-unstable-v1") - case r.seat == nil: - return nil, false, fmt.Errorf("no seat available") - case r.compositor == nil: - return nil, false, fmt.Errorf("compositor not available") - case r.shm == nil: - return nil, false, fmt.Errorf("wl_shm not available") - case len(r.outputs) == 0: - return nil, false, fmt.Errorf("no outputs available") - } - - if err := r.roundtrip(); err != nil { - return nil, false, fmt.Errorf("roundtrip after protocol check: %w", err) - } - - if err := r.preCaptureAllOutputs(); err != nil { - return nil, false, fmt.Errorf("pre-capture: %w", err) - } - - if err := r.createSurfaces(); err != nil { - return nil, false, fmt.Errorf("create surfaces: %w", err) - } - - _ = r.createCursor() - - if err := r.roundtrip(); err != nil { - return nil, false, fmt.Errorf("roundtrip after surfaces: %w", err) - } - - r.running = true - for r.running { - if err := r.ctx.Dispatch(); err != nil { - return nil, false, fmt.Errorf("dispatch: %w", err) - } - } - - if r.cancelled || r.capturedBuffer == nil { - return nil, r.cancelled, nil - } - - yInverted := false - var format uint32 - if r.selection.surface != nil { - yInverted = r.selection.surface.yInverted - format = r.selection.surface.screenFormat - } - - return &CaptureResult{ - Buffer: r.capturedBuffer, - Region: r.result, - YInverted: yInverted, - Format: format, - }, false, nil -} - -func (r *RegionSelector) connect() error { - display, err := client.Connect("") - if err != nil { - return err - } - r.display = display - r.ctx = display.Context() - return nil -} - -func (r *RegionSelector) roundtrip() error { - return wlhelpers.Roundtrip(r.display, r.ctx) -} - -func (r *RegionSelector) setupRegistry() error { - registry, err := r.display.GetRegistry() - if err != nil { - return err - } - r.registry = registry - - registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) { - r.handleGlobal(e) - }) - - registry.SetGlobalRemoveHandler(func(e client.RegistryGlobalRemoveEvent) { - r.outputsMu.Lock() - delete(r.outputs, e.Name) - r.outputsMu.Unlock() - }) - - return nil -} - -func (r *RegionSelector) handleGlobal(e client.RegistryGlobalEvent) { - switch e.Interface { - case client.CompositorInterfaceName: - comp := client.NewCompositor(r.ctx) - if err := r.registry.Bind(e.Name, e.Interface, e.Version, comp); err == nil { - r.compositor = comp - } - - case client.ShmInterfaceName: - shm := client.NewShm(r.ctx) - if err := r.registry.Bind(e.Name, e.Interface, e.Version, shm); err == nil { - r.shm = shm - } - - case client.SeatInterfaceName: - seat := client.NewSeat(r.ctx) - if err := r.registry.Bind(e.Name, e.Interface, e.Version, seat); err == nil { - r.seat = seat - r.setupInput() - } - - case client.OutputInterfaceName: - output := client.NewOutput(r.ctx) - version := e.Version - if version > 4 { - version = 4 - } - if err := r.registry.Bind(e.Name, e.Interface, version, output); err == nil { - r.outputsMu.Lock() - r.outputs[e.Name] = &WaylandOutput{ - wlOutput: output, - globalName: e.Name, - scale: 1, - fractionalScale: 1.0, - } - r.outputsMu.Unlock() - r.setupOutputHandlers(e.Name, output) - } - - case wlr_layer_shell.ZwlrLayerShellV1InterfaceName: - ls := wlr_layer_shell.NewZwlrLayerShellV1(r.ctx) - version := e.Version - if version > 4 { - version = 4 - } - if err := r.registry.Bind(e.Name, e.Interface, version, ls); err == nil { - r.layerShell = ls - } - - case wlr_screencopy.ZwlrScreencopyManagerV1InterfaceName: - sc := wlr_screencopy.NewZwlrScreencopyManagerV1(r.ctx) - version := e.Version - if version > 3 { - version = 3 - } - if err := r.registry.Bind(e.Name, e.Interface, version, sc); err == nil { - r.screencopy = sc - } - - case wp_viewporter.WpViewporterInterfaceName: - vp := wp_viewporter.NewWpViewporter(r.ctx) - if err := r.registry.Bind(e.Name, e.Interface, e.Version, vp); err == nil { - r.viewporter = vp - } - - case keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitManagerV1InterfaceName: - mgr := keyboard_shortcuts_inhibit.NewZwpKeyboardShortcutsInhibitManagerV1(r.ctx) - if err := r.registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil { - r.shortcutsInhibitMgr = mgr - } - } -} - -func (r *RegionSelector) setupOutputHandlers(name uint32, output *client.Output) { - output.SetGeometryHandler(func(e client.OutputGeometryEvent) { - r.outputsMu.Lock() - if o, ok := r.outputs[name]; ok { - o.x = e.X - o.y = e.Y - o.transform = int32(e.Transform) - } - r.outputsMu.Unlock() - }) - - output.SetModeHandler(func(e client.OutputModeEvent) { - if e.Flags&uint32(client.OutputModeCurrent) == 0 { - return - } - r.outputsMu.Lock() - if o, ok := r.outputs[name]; ok { - o.width = e.Width - o.height = e.Height - } - r.outputsMu.Unlock() - }) - - output.SetScaleHandler(func(e client.OutputScaleEvent) { - r.outputsMu.Lock() - if o, ok := r.outputs[name]; ok { - o.scale = e.Factor - o.fractionalScale = float64(e.Factor) - } - r.outputsMu.Unlock() - }) - - output.SetNameHandler(func(e client.OutputNameEvent) { - r.outputsMu.Lock() - if o, ok := r.outputs[name]; ok { - o.name = e.Name - } - r.outputsMu.Unlock() - }) -} - -func (r *RegionSelector) preCaptureAllOutputs() error { - r.outputsMu.Lock() - outputs := make([]*WaylandOutput, 0, len(r.outputs)) - for _, o := range r.outputs { - outputs = append(outputs, o) - } - r.outputsMu.Unlock() - - pending := len(outputs) * 2 - done := make(chan struct{}, pending) - - for _, output := range outputs { - pc := &PreCapture{} - r.preCapture[output] = pc - - r.preCaptureOutput(output, pc, true, func() { done <- struct{}{} }) - r.preCaptureOutput(output, pc, false, func() { done <- struct{}{} }) - } - - for i := 0; i < pending; i++ { - if err := r.ctx.Dispatch(); err != nil { - return err - } - select { - case <-done: - default: - i-- - } - } - return nil -} - -func (r *RegionSelector) preCaptureOutput(output *WaylandOutput, pc *PreCapture, withCursor bool, onReady func()) { - cursor := int32(0) - if withCursor { - cursor = 1 - } - - frame, err := r.screencopy.CaptureOutput(cursor, output.wlOutput) - if err != nil { - log.Error("screencopy capture failed", "err", err) - onReady() - return - } - - var capturedBuf *ShmBuffer - - var capturedFormat PixelFormat - frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) { - capturedFormat = PixelFormat(e.Format) - bpp := capturedFormat.BytesPerPixel() - if int(e.Stride) < int(e.Width)*bpp { - log.Error("invalid stride from compositor", "stride", e.Stride, "width", e.Width, "bpp", bpp) - return - } - buf, err := CreateShmBuffer(int(e.Width), int(e.Height), int(e.Stride)) - if err != nil { - log.Error("create screen buffer failed", "err", err) - return - } - - capturedBuf = buf - buf.Format = capturedFormat - - pool, err := r.shm.CreatePool(buf.Fd(), int32(buf.Size())) - if err != nil { - log.Error("create shm pool failed", "err", err) - return - } - - wlBuf, err := pool.CreateBuffer(0, int32(buf.Width), int32(buf.Height), int32(buf.Stride), e.Format) - if err != nil { - log.Error("create wl_buffer failed", "err", err) - pool.Destroy() - return - } - - if err := frame.Copy(wlBuf); err != nil { - log.Error("frame copy failed", "err", err) - } - pool.Destroy() - }) - - frame.SetFlagsHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1FlagsEvent) { - if withCursor { - pc.yInverted = (e.Flags & 1) != 0 - } - }) - - frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) { - frame.Destroy() - - if capturedBuf == nil { - onReady() - return - } - - if capturedFormat.Is24Bit() { - converted, newFormat, err := capturedBuf.ConvertTo32Bit(capturedFormat) - if err != nil { - log.Error("convert 24-bit to 32-bit failed", "err", err) - } else if converted != capturedBuf { - capturedBuf.Close() - capturedBuf = converted - capturedFormat = newFormat - } - } - - pc.format = uint32(capturedFormat) - - if pc.yInverted { - capturedBuf.FlipVertical() - pc.yInverted = false - } - - if output.transform != TransformNormal { - invTransform := InverseTransform(output.transform) - transformed, err := capturedBuf.ApplyTransform(invTransform) - if err != nil { - log.Error("apply transform failed", "err", err) - } else if transformed != capturedBuf { - capturedBuf.Close() - capturedBuf = transformed - } - } - - if withCursor { - pc.screenBuf = capturedBuf - } else { - pc.screenBufNoCursor = capturedBuf - } - - onReady() - }) - - frame.SetFailedHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1FailedEvent) { - log.Error("screencopy failed") - frame.Destroy() - onReady() - }) -} - -func (r *RegionSelector) createSurfaces() error { - r.outputsMu.Lock() - outputs := make([]*WaylandOutput, 0, len(r.outputs)) - for _, o := range r.outputs { - outputs = append(outputs, o) - } - r.outputsMu.Unlock() - - for _, output := range outputs { - os, err := r.createOutputSurface(output) - if err != nil { - return fmt.Errorf("output %s: %w", output.name, err) - } - r.surfaces = append(r.surfaces, os) - } - - return nil -} - -func (r *RegionSelector) createCursor() error { - const size = 24 - const hotspot = size / 2 - - surface, err := r.compositor.CreateSurface() - if err != nil { - return fmt.Errorf("create cursor surface: %w", err) - } - r.cursorSurface = surface - - buf, err := CreateShmBuffer(size, size, size*4) - if err != nil { - return fmt.Errorf("create cursor buffer: %w", err) - } - r.cursorBuffer = buf - - // Draw crosshair - data := buf.Data() - for y := 0; y < size; y++ { - for x := 0; x < size; x++ { - off := (y*size + x) * 4 - // Vertical line - if x >= hotspot-1 && x <= hotspot && y >= 2 && y < size-2 { - data[off+0] = 255 // B - data[off+1] = 255 // G - data[off+2] = 255 // R - data[off+3] = 255 // A - continue - } - // Horizontal line - if y >= hotspot-1 && y <= hotspot && x >= 2 && x < size-2 { - data[off+0] = 255 - data[off+1] = 255 - data[off+2] = 255 - data[off+3] = 255 - continue - } - // Transparent - data[off+0] = 0 - data[off+1] = 0 - data[off+2] = 0 - data[off+3] = 0 - } - } - - pool, err := r.shm.CreatePool(buf.Fd(), int32(buf.Size())) - if err != nil { - return fmt.Errorf("create cursor pool: %w", err) - } - r.cursorPool = pool - - wlBuf, err := pool.CreateBuffer(0, size, size, size*4, uint32(FormatARGB8888)) - if err != nil { - return fmt.Errorf("create cursor wl_buffer: %w", err) - } - r.cursorWlBuf = wlBuf - - if err := surface.Attach(wlBuf, 0, 0); err != nil { - return fmt.Errorf("attach cursor: %w", err) - } - if err := surface.Damage(0, 0, size, size); err != nil { - return fmt.Errorf("damage cursor: %w", err) - } - if err := surface.Commit(); err != nil { - return fmt.Errorf("commit cursor: %w", err) - } - - return nil -} - -func (r *RegionSelector) createOutputSurface(output *WaylandOutput) (*OutputSurface, error) { - surface, err := r.compositor.CreateSurface() - if err != nil { - return nil, fmt.Errorf("create surface: %w", err) - } - - layerSurf, err := r.layerShell.GetLayerSurface( - surface, - output.wlOutput, - uint32(wlr_layer_shell.ZwlrLayerShellV1LayerOverlay), - "dms-screenshot", - ) - if err != nil { - return nil, fmt.Errorf("get layer surface: %w", err) - } - - os := &OutputSurface{ - output: output, - wlSurface: surface, - layerSurf: layerSurf, - } - - if r.viewporter != nil { - vp, err := r.viewporter.GetViewport(surface) - if err == nil { - os.viewport = vp - } - } - - if err := layerSurf.SetAnchor( - uint32(wlr_layer_shell.ZwlrLayerSurfaceV1AnchorTop) | - uint32(wlr_layer_shell.ZwlrLayerSurfaceV1AnchorBottom) | - uint32(wlr_layer_shell.ZwlrLayerSurfaceV1AnchorLeft) | - uint32(wlr_layer_shell.ZwlrLayerSurfaceV1AnchorRight), - ); err != nil { - return nil, fmt.Errorf("set anchor: %w", err) - } - if err := layerSurf.SetExclusiveZone(-1); err != nil { - return nil, fmt.Errorf("set exclusive zone: %w", err) - } - if err := layerSurf.SetKeyboardInteractivity(uint32(wlr_layer_shell.ZwlrLayerSurfaceV1KeyboardInteractivityExclusive)); err != nil { - return nil, fmt.Errorf("set keyboard interactivity: %w", err) - } - - layerSurf.SetConfigureHandler(func(e wlr_layer_shell.ZwlrLayerSurfaceV1ConfigureEvent) { - if err := layerSurf.AckConfigure(e.Serial); err != nil { - log.Error("ack configure failed", "err", err) - return - } - os.logicalW = int(e.Width) - os.logicalH = int(e.Height) - os.configured = true - r.captureForSurface(os) - r.ensureShortcutsInhibitor(os) - }) - - layerSurf.SetClosedHandler(func(e wlr_layer_shell.ZwlrLayerSurfaceV1ClosedEvent) { - r.running = false - r.cancelled = true - }) - - if err := surface.Commit(); err != nil { - return nil, fmt.Errorf("surface commit: %w", err) - } - - return os, nil -} - -func (r *RegionSelector) ensureShortcutsInhibitor(os *OutputSurface) { - if r.shortcutsInhibitMgr == nil || r.seat == nil || r.shortcutsInhibitor != nil { - return - } - inhibitor, err := r.shortcutsInhibitMgr.InhibitShortcuts(os.wlSurface, r.seat) - if err == nil { - r.shortcutsInhibitor = inhibitor - } -} - -func (r *RegionSelector) captureForSurface(os *OutputSurface) { - pc := r.preCapture[os.output] - if pc == nil { - return - } - - os.screenBuf = pc.screenBuf - os.screenBufNoCursor = pc.screenBufNoCursor - os.screenFormat = pc.format - os.yInverted = pc.yInverted - - if os.logicalW > 0 && os.screenBuf != nil { - os.output.fractionalScale = float64(os.screenBuf.Width) / float64(os.logicalW) - } - - r.initRenderBuffer(os) - r.applyPreSelection(os) - r.redrawSurface(os) -} - -func (r *RegionSelector) initRenderBuffer(os *OutputSurface) { - if os.screenBuf == nil { - return - } - - for i := 0; i < 3; i++ { - slot := &RenderSlot{} - - buf, err := CreateShmBuffer(os.screenBuf.Width, os.screenBuf.Height, os.screenBuf.Stride) - if err != nil { - log.Error("create render slot buffer failed", "err", err) - return - } - slot.shm = buf - - pool, err := r.shm.CreatePool(buf.Fd(), int32(buf.Size())) - if err != nil { - log.Error("create render slot pool failed", "err", err) - buf.Close() - return - } - slot.pool = pool - - wlBuf, err := pool.CreateBuffer(0, int32(buf.Width), int32(buf.Height), int32(buf.Stride), os.screenFormat) - if err != nil { - log.Error("create render slot wl_buffer failed", "err", err) - pool.Destroy() - buf.Close() - return - } - slot.wlBuf = wlBuf - - slotRef := slot - wlBuf.SetReleaseHandler(func(e client.BufferReleaseEvent) { - slotRef.busy = false - }) - - os.slots[i] = slot - } - os.slotsReady = true -} - -func (os *OutputSurface) acquireFreeSlot() *RenderSlot { - for _, slot := range os.slots { - if slot != nil && !slot.busy { - return slot - } - } - return nil -} - -func (r *RegionSelector) applyPreSelection(os *OutputSurface) { - if r.preSelect.IsEmpty() || os.screenBuf == nil || r.selection.hasSelection { - return - } - - if r.preSelect.Output != "" && r.preSelect.Output != os.output.name { - return - } - - scaleX := float64(os.logicalW) / float64(os.screenBuf.Width) - scaleY := float64(os.logicalH) / float64(os.screenBuf.Height) - - x1 := float64(r.preSelect.X-os.output.x) * scaleX - y1 := float64(r.preSelect.Y-os.output.y) * scaleY - x2 := float64(r.preSelect.X-os.output.x+r.preSelect.Width) * scaleX - y2 := float64(r.preSelect.Y-os.output.y+r.preSelect.Height) * scaleY - - r.selection.hasSelection = true - r.selection.dragging = false - r.selection.surface = os - r.selection.anchorX = x1 - r.selection.anchorY = y1 - r.selection.currentX = x2 - r.selection.currentY = y2 - r.activeSurface = os -} - -func (r *RegionSelector) getSourceBuffer(os *OutputSurface) *ShmBuffer { - if !r.showCapturedCursor && os.screenBufNoCursor != nil { - return os.screenBufNoCursor - } - return os.screenBuf -} - -func (r *RegionSelector) redrawSurface(os *OutputSurface) { - srcBuf := r.getSourceBuffer(os) - if srcBuf == nil || !os.slotsReady { - return - } - - slot := os.acquireFreeSlot() - if slot == nil { - return - } - - slot.shm.CopyFrom(srcBuf) - - // Draw overlay (dimming + selection) into this slot - r.drawOverlay(os, slot.shm) - - if os.viewport != nil { - _ = os.wlSurface.SetBufferScale(1) - _ = os.viewport.SetSource(0, 0, float64(slot.shm.Width), float64(slot.shm.Height)) - _ = os.viewport.SetDestination(int32(os.logicalW), int32(os.logicalH)) - } else { - bufferScale := os.output.scale - if bufferScale <= 0 { - bufferScale = 1 - } - _ = os.wlSurface.SetBufferScale(bufferScale) - } - - _ = os.wlSurface.Attach(slot.wlBuf, 0, 0) - _ = os.wlSurface.Damage(0, 0, int32(os.logicalW), int32(os.logicalH)) - _ = os.wlSurface.Commit() - - // Mark this slot as busy until compositor releases it - slot.busy = true -} - -func (r *RegionSelector) cleanup() { - if r.cursorWlBuf != nil { - r.cursorWlBuf.Destroy() - } - if r.cursorPool != nil { - r.cursorPool.Destroy() - } - if r.cursorSurface != nil { - r.cursorSurface.Destroy() - } - if r.cursorBuffer != nil { - r.cursorBuffer.Close() - } - - for _, os := range r.surfaces { - for _, slot := range os.slots { - if slot == nil { - continue - } - if slot.wlBuf != nil { - slot.wlBuf.Destroy() - } - if slot.pool != nil { - slot.pool.Destroy() - } - if slot.shm != nil { - slot.shm.Close() - } - } - if os.viewport != nil { - os.viewport.Destroy() - } - if os.layerSurf != nil { - os.layerSurf.Destroy() - } - if os.wlSurface != nil { - os.wlSurface.Destroy() - } - if os.screenBuf != nil { - os.screenBuf.Close() - } - if os.screenBufNoCursor != nil { - os.screenBufNoCursor.Close() - } - } - - if r.shortcutsInhibitor != nil { - _ = r.shortcutsInhibitor.Destroy() - } - if r.shortcutsInhibitMgr != nil { - _ = r.shortcutsInhibitMgr.Destroy() - } - if r.viewporter != nil { - r.viewporter.Destroy() - } - if r.screencopy != nil { - r.screencopy.Destroy() - } - if r.pointer != nil { - r.pointer.Release() - } - if r.keyboard != nil { - r.keyboard.Release() - } - if r.display != nil { - r.ctx.Close() - } -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region_input.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region_input.go deleted file mode 100644 index e243443..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region_input.go +++ /dev/null @@ -1,274 +0,0 @@ -package screenshot - -import ( - "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" -) - -func (r *RegionSelector) setupInput() { - if r.seat == nil { - return - } - - r.seat.SetCapabilitiesHandler(func(e client.SeatCapabilitiesEvent) { - if e.Capabilities&uint32(client.SeatCapabilityPointer) != 0 && r.pointer == nil { - if pointer, err := r.seat.GetPointer(); err == nil { - r.pointer = pointer - r.setupPointerHandlers() - } - } - if e.Capabilities&uint32(client.SeatCapabilityKeyboard) != 0 && r.keyboard == nil { - if keyboard, err := r.seat.GetKeyboard(); err == nil { - r.keyboard = keyboard - r.setupKeyboardHandlers() - } - } - }) -} - -func (r *RegionSelector) setupPointerHandlers() { - r.pointer.SetEnterHandler(func(e client.PointerEnterEvent) { - if r.cursorSurface != nil { - _ = r.pointer.SetCursor(e.Serial, r.cursorSurface, 12, 12) - } - - r.activeSurface = nil - for _, os := range r.surfaces { - if os.wlSurface.ID() == e.Surface.ID() { - r.activeSurface = os - break - } - } - - r.pointerX = e.SurfaceX - r.pointerY = e.SurfaceY - }) - - r.pointer.SetMotionHandler(func(e client.PointerMotionEvent) { - if r.activeSurface == nil { - return - } - - r.pointerX = e.SurfaceX - r.pointerY = e.SurfaceY - - if !r.selection.dragging { - return - } - - curX, curY := e.SurfaceX, e.SurfaceY - if r.shiftHeld { - dx := curX - r.selection.anchorX - dy := curY - r.selection.anchorY - adx, ady := dx, dy - if adx < 0 { - adx = -adx - } - if ady < 0 { - ady = -ady - } - size := adx - if ady > adx { - size = ady - } - if dx < 0 { - curX = r.selection.anchorX - size - } else { - curX = r.selection.anchorX + size - } - if dy < 0 { - curY = r.selection.anchorY - size - } else { - curY = r.selection.anchorY + size - } - } - - r.selection.currentX = curX - r.selection.currentY = curY - for _, os := range r.surfaces { - r.redrawSurface(os) - } - }) - - r.pointer.SetButtonHandler(func(e client.PointerButtonEvent) { - if r.activeSurface == nil { - return - } - - switch e.Button { - case 0x110: // BTN_LEFT - switch e.State { - case 1: // pressed - r.preSelect = Region{} - r.selection.hasSelection = true - r.selection.dragging = true - r.selection.surface = r.activeSurface - r.selection.anchorX = r.pointerX - r.selection.anchorY = r.pointerY - r.selection.currentX = r.pointerX - r.selection.currentY = r.pointerY - for _, os := range r.surfaces { - r.redrawSurface(os) - } - case 0: // released - r.selection.dragging = false - for _, os := range r.surfaces { - r.redrawSurface(os) - } - if r.screenshoter != nil && r.screenshoter.config.NoConfirm && r.selection.hasSelection { - r.finishSelection() - } - } - default: - r.cancelled = true - r.running = false - } - }) -} - -func (r *RegionSelector) setupKeyboardHandlers() { - r.keyboard.SetModifiersHandler(func(e client.KeyboardModifiersEvent) { - r.shiftHeld = e.ModsDepressed&1 != 0 - }) - - r.keyboard.SetKeyHandler(func(e client.KeyboardKeyEvent) { - if e.State != 1 { - return - } - - switch e.Key { - case 1: - r.cancelled = true - r.running = false - case 25: - r.showCapturedCursor = !r.showCapturedCursor - for _, os := range r.surfaces { - r.redrawSurface(os) - } - case 28, 57, 96: - if r.selection.hasSelection { - r.finishSelection() - } - } - }) -} - -func (r *RegionSelector) finishSelection() { - if r.selection.surface == nil { - r.running = false - return - } - - os := r.selection.surface - srcBuf := r.getSourceBuffer(os) - if srcBuf == nil { - r.running = false - return - } - - x1, y1 := r.selection.anchorX, r.selection.anchorY - x2, y2 := r.selection.currentX, r.selection.currentY - - if x1 > x2 { - x1, x2 = x2, x1 - } - if y1 > y2 { - y1, y2 = y2, y1 - } - - scaleX, scaleY := 1.0, 1.0 - if os.logicalW > 0 { - scaleX = float64(srcBuf.Width) / float64(os.logicalW) - scaleY = float64(srcBuf.Height) / float64(os.logicalH) - } - - bx1 := int(x1 * scaleX) - by1 := int(y1 * scaleY) - bx2 := int(x2 * scaleX) - by2 := int(y2 * scaleY) - - // Clamp to buffer bounds - if bx1 < 0 { - bx1 = 0 - } - if by1 < 0 { - by1 = 0 - } - if bx2 > srcBuf.Width { - bx2 = srcBuf.Width - } - if by2 > srcBuf.Height { - by2 = srcBuf.Height - } - - w, h := bx2-bx1+1, by2-by1+1 - if r.shiftHeld && w != h { - if w < h { - h = w - } else { - w = h - } - } - if w < 1 { - w = 1 - } - if h < 1 { - h = 1 - } - - // Create cropped buffer and copy pixels directly - cropped, err := CreateShmBuffer(w, h, w*4) - if err != nil { - r.running = false - return - } - - srcData := srcBuf.Data() - dstData := cropped.Data() - for y := 0; y < h; y++ { - srcY := by1 + y - if os.yInverted { - srcY = srcBuf.Height - 1 - (by1 + y) - } - if srcY < 0 || srcY >= srcBuf.Height { - continue - } - dstY := y - if os.yInverted { - dstY = h - 1 - y - } - for x := 0; x < w; x++ { - srcX := bx1 + x - if srcX < 0 || srcX >= srcBuf.Width { - continue - } - si := srcY*srcBuf.Stride + srcX*4 - di := dstY*cropped.Stride + x*4 - if si+3 < len(srcData) && di+3 < len(dstData) { - dstData[di+0] = srcData[si+0] - dstData[di+1] = srcData[si+1] - dstData[di+2] = srcData[si+2] - dstData[di+3] = srcData[si+3] - } - } - } - - r.capturedBuffer = cropped - r.capturedRegion = Region{ - X: int32(bx1), - Y: int32(by1), - Width: int32(w), - Height: int32(h), - Output: os.output.name, - } - - // Also store for "last region" feature with global coords - r.result = Region{ - X: int32(bx1) + os.output.x, - Y: int32(by1) + os.output.y, - Width: int32(w), - Height: int32(h), - Output: os.output.name, - } - - r.running = false -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region_render.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region_render.go deleted file mode 100644 index 4071f8a..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/region_render.go +++ /dev/null @@ -1,326 +0,0 @@ -package screenshot - -import "fmt" - -var fontGlyphs = map[rune][12]uint8{ - '0': {0x3C, 0x66, 0x66, 0x6E, 0x76, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00}, - '1': {0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, 0x00}, - '2': {0x3C, 0x66, 0x66, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x66, 0x7E, 0x00, 0x00}, - '3': {0x3C, 0x66, 0x06, 0x06, 0x1C, 0x06, 0x06, 0x06, 0x66, 0x3C, 0x00, 0x00}, - '4': {0x0C, 0x1C, 0x3C, 0x6C, 0xCC, 0xCC, 0xFE, 0x0C, 0x0C, 0x1E, 0x00, 0x00}, - '5': {0x7E, 0x60, 0x60, 0x60, 0x7C, 0x06, 0x06, 0x06, 0x66, 0x3C, 0x00, 0x00}, - '6': {0x1C, 0x30, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00}, - '7': {0x7E, 0x66, 0x06, 0x06, 0x0C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00}, - '8': {0x3C, 0x66, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00}, - '9': {0x3C, 0x66, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x06, 0x0C, 0x38, 0x00, 0x00}, - 'x': {0x00, 0x00, 0x00, 0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00, 0x00}, - 'E': {0x7E, 0x60, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00, 0x00}, - 'P': {0x7C, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00}, - 'S': {0x3C, 0x66, 0x60, 0x60, 0x3C, 0x06, 0x06, 0x06, 0x66, 0x3C, 0x00, 0x00}, - 'a': {0x00, 0x00, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x66, 0x66, 0x3E, 0x00, 0x00}, - 'c': {0x00, 0x00, 0x00, 0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00, 0x00}, - 'd': {0x00, 0x00, 0x06, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x66, 0x3E, 0x00, 0x00}, - 'e': {0x00, 0x00, 0x00, 0x3C, 0x66, 0x66, 0x7E, 0x60, 0x60, 0x3C, 0x00, 0x00}, - 'h': {0x00, 0x60, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00}, - 'i': {0x00, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00}, - 'n': {0x00, 0x00, 0x00, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00}, - 'o': {0x00, 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00}, - 'p': {0x00, 0x00, 0x00, 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x00, 0x00}, - 'r': {0x00, 0x00, 0x00, 0x6E, 0x76, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00}, - 's': {0x00, 0x00, 0x00, 0x3E, 0x60, 0x60, 0x3C, 0x06, 0x06, 0x7C, 0x00, 0x00}, - 't': {0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0E, 0x00, 0x00}, - 'u': {0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3E, 0x00, 0x00}, - 'w': {0x00, 0x00, 0x00, 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00, 0x00}, - 'l': {0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00}, - ' ': {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - ':': {0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00}, - '/': {0x00, 0x02, 0x06, 0x0C, 0x18, 0x18, 0x30, 0x60, 0x40, 0x00, 0x00, 0x00}, - '[': {0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 0x00}, - ']': {0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00, 0x00}, -} - -type OverlayStyle struct { - BackgroundR, BackgroundG, BackgroundB, BackgroundA uint8 - TextR, TextG, TextB uint8 - AccentR, AccentG, AccentB uint8 -} - -var DefaultOverlayStyle = OverlayStyle{ - BackgroundR: 30, BackgroundG: 30, BackgroundB: 30, BackgroundA: 220, - TextR: 255, TextG: 255, TextB: 255, - AccentR: 100, AccentG: 180, AccentB: 255, -} - -func (r *RegionSelector) drawOverlay(os *OutputSurface, renderBuf *ShmBuffer) { - data := renderBuf.Data() - stride := renderBuf.Stride - w, h := renderBuf.Width, renderBuf.Height - format := os.screenFormat - - // Dim the entire buffer - for y := 0; y < h; y++ { - off := y * stride - for x := 0; x < w; x++ { - i := off + x*4 - if i+3 >= len(data) { - continue - } - data[i+0] = uint8(int(data[i+0]) * 3 / 5) - data[i+1] = uint8(int(data[i+1]) * 3 / 5) - data[i+2] = uint8(int(data[i+2]) * 3 / 5) - } - } - - r.drawHUD(data, stride, w, h, format) - - if !r.selection.hasSelection || r.selection.surface != os { - return - } - - scaleX := float64(w) / float64(os.logicalW) - scaleY := float64(h) / float64(os.logicalH) - - bx1 := int(r.selection.anchorX * scaleX) - by1 := int(r.selection.anchorY * scaleY) - bx2 := int(r.selection.currentX * scaleX) - by2 := int(r.selection.currentY * scaleY) - - if bx1 > bx2 { - bx1, bx2 = bx2, bx1 - } - if by1 > by2 { - by1, by2 = by2, by1 - } - - bx1 = clamp(bx1, 0, w-1) - by1 = clamp(by1, 0, h-1) - bx2 = clamp(bx2, 0, w-1) - by2 = clamp(by2, 0, h-1) - - srcBuf := r.getSourceBuffer(os) - srcData := srcBuf.Data() - for y := by1; y <= by2; y++ { - rowOff := y * stride - for x := bx1; x <= bx2; x++ { - si := y*srcBuf.Stride + x*4 - di := rowOff + x*4 - if si+3 >= len(srcData) || di+3 >= len(data) { - continue - } - data[di+0] = srcData[si+0] - data[di+1] = srcData[si+1] - data[di+2] = srcData[si+2] - data[di+3] = srcData[si+3] - } - } - - selW, selH := bx2-bx1+1, by2-by1+1 - if r.shiftHeld && selW != selH { - if selW < selH { - selH = selW - } else { - selW = selH - } - } - r.drawBorder(data, stride, w, h, bx1, by1, selW, selH, format) - r.drawDimensions(data, stride, w, h, bx1, by1, selW, selH, format) -} - -func (r *RegionSelector) drawHUD(data []byte, stride, bufW, bufH int, format uint32) { - if r.selection.dragging { - return - } - - style := LoadOverlayStyle() - const charW, charH, padding, itemSpacing = 8, 12, 12, 24 - - cursorLabel := "hide" - if !r.showCapturedCursor { - cursorLabel = "show" - } - captureKey := "Space/Enter" - if r.screenshoter != nil && r.screenshoter.config.NoConfirm { - captureKey = "Drag+Release" - } - - items := []struct{ key, desc string }{ - {captureKey, "capture"}, - {"P", cursorLabel + " cursor"}, - {"Esc", "cancel"}, - } - - totalW := 0 - for i, item := range items { - totalW += len(item.key)*(charW+1) + 4 + len(item.desc)*(charW+1) - if i < len(items)-1 { - totalW += itemSpacing - } - } - - hudW := totalW + padding*2 - hudH := charH + padding*2 - hudX := (bufW - hudW) / 2 - hudY := bufH - hudH - 20 - - r.fillRect(data, stride, bufW, bufH, hudX, hudY, hudW, hudH, - style.BackgroundR, style.BackgroundG, style.BackgroundB, style.BackgroundA, format) - - tx, ty := hudX+padding, hudY+padding - for i, item := range items { - r.drawText(data, stride, bufW, bufH, tx, ty, item.key, - style.AccentR, style.AccentG, style.AccentB, format) - tx += len(item.key) * (charW + 1) - - r.drawText(data, stride, bufW, bufH, tx, ty, " "+item.desc, - style.TextR, style.TextG, style.TextB, format) - tx += (1 + len(item.desc)) * (charW + 1) - - if i < len(items)-1 { - tx += itemSpacing - } - } -} - -func (r *RegionSelector) drawBorder(data []byte, stride, bufW, bufH, x, y, w, h int, format uint32) { - const thickness = 2 - for i := 0; i < thickness; i++ { - r.drawHLine(data, stride, bufW, bufH, x-i, y-i, w+2*i, format) - r.drawHLine(data, stride, bufW, bufH, x-i, y+h+i-1, w+2*i, format) - r.drawVLine(data, stride, bufW, bufH, x-i, y-i, h+2*i, format) - r.drawVLine(data, stride, bufW, bufH, x+w+i-1, y-i, h+2*i, format) - } -} - -func (r *RegionSelector) drawHLine(data []byte, stride, bufW, bufH, x, y, length int, _ uint32) { - if y < 0 || y >= bufH { - return - } - rowOff := y * stride - for i := 0; i < length; i++ { - px := x + i - if px < 0 || px >= bufW { - continue - } - off := rowOff + px*4 - if off+3 >= len(data) { - continue - } - data[off], data[off+1], data[off+2], data[off+3] = 255, 255, 255, 255 - } -} - -func (r *RegionSelector) drawVLine(data []byte, stride, bufW, bufH, x, y, length int, _ uint32) { - if x < 0 || x >= bufW { - return - } - for i := 0; i < length; i++ { - py := y + i - if py < 0 || py >= bufH { - continue - } - off := py*stride + x*4 - if off+3 >= len(data) { - continue - } - data[off], data[off+1], data[off+2], data[off+3] = 255, 255, 255, 255 - } -} - -func (r *RegionSelector) drawDimensions(data []byte, stride, bufW, bufH, x, y, w, h int, format uint32) { - text := fmt.Sprintf("%dx%d", w, h) - - const charW, charH = 8, 12 - textW := len(text) * (charW + 1) - textH := charH - - tx := x + (w-textW)/2 - ty := y + h + 8 - - if ty+textH > bufH { - ty = y - textH - 8 - } - tx = clamp(tx, 0, bufW-textW) - - r.fillRect(data, stride, bufW, bufH, tx-4, ty-2, textW+8, textH+4, 0, 0, 0, 200, format) - r.drawText(data, stride, bufW, bufH, tx, ty, text, 255, 255, 255, format) -} - -func (r *RegionSelector) fillRect(data []byte, stride, bufW, bufH, x, y, w, h int, cr, cg, cb, ca uint8, format uint32) { - alpha := float64(ca) / 255.0 - invAlpha := 1.0 - alpha - - c0, c2 := cb, cr - if format == uint32(FormatABGR8888) || format == uint32(FormatXBGR8888) { - c0, c2 = cr, cb - } - - for py := y; py < y+h && py < bufH; py++ { - if py < 0 { - continue - } - for px := x; px < x+w && px < bufW; px++ { - if px < 0 { - continue - } - off := py*stride + px*4 - if off+3 >= len(data) { - continue - } - data[off+0] = uint8(float64(data[off+0])*invAlpha + float64(c0)*alpha) - data[off+1] = uint8(float64(data[off+1])*invAlpha + float64(cg)*alpha) - data[off+2] = uint8(float64(data[off+2])*invAlpha + float64(c2)*alpha) - data[off+3] = 255 - } - } -} - -func (r *RegionSelector) drawText(data []byte, stride, bufW, bufH, x, y int, text string, cr, cg, cb uint8, format uint32) { - for i, ch := range text { - r.drawChar(data, stride, bufW, bufH, x+i*9, y, ch, cr, cg, cb, format) - } -} - -func (r *RegionSelector) drawChar(data []byte, stride, bufW, bufH, x, y int, ch rune, cr, cg, cb uint8, format uint32) { - glyph, ok := fontGlyphs[ch] - if !ok { - return - } - - c0, c2 := cb, cr - if format == uint32(FormatABGR8888) || format == uint32(FormatXBGR8888) { - c0, c2 = cr, cb - } - - for row := 0; row < 12; row++ { - py := y + row - if py < 0 || py >= bufH { - continue - } - bits := glyph[row] - for col := 0; col < 8; col++ { - if (bits & (1 << (7 - col))) == 0 { - continue - } - px := x + col - if px < 0 || px >= bufW { - continue - } - off := py*stride + px*4 - if off+3 >= len(data) { - continue - } - data[off], data[off+1], data[off+2], data[off+3] = c0, cg, c2, 255 - } - } -} - -func clamp(v, lo, hi int) int { - switch { - case v < lo: - return lo - case v > hi: - return hi - default: - return v - } -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/screenshot.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/screenshot.go deleted file mode 100644 index 42d3404..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/screenshot.go +++ /dev/null @@ -1,1089 +0,0 @@ -package screenshot - -import ( - "fmt" - "math" - "sync" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_screencopy" - wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" -) - -type WaylandOutput struct { - wlOutput *client.Output - globalName uint32 - name string - x, y int32 - width int32 - height int32 - scale int32 - fractionalScale float64 - transform int32 -} - -type CaptureResult struct { - Buffer *ShmBuffer - Region Region - YInverted bool - Format uint32 -} - -type Screenshoter struct { - config Config - - display *client.Display - registry *client.Registry - ctx *client.Context - - compositor *client.Compositor - shm *client.Shm - screencopy *wlr_screencopy.ZwlrScreencopyManagerV1 - - outputs map[uint32]*WaylandOutput - outputsMu sync.Mutex -} - -func New(config Config) *Screenshoter { - return &Screenshoter{ - config: config, - outputs: make(map[uint32]*WaylandOutput), - } -} - -func (s *Screenshoter) Run() (*CaptureResult, error) { - if err := s.connect(); err != nil { - return nil, fmt.Errorf("wayland connect: %w", err) - } - defer s.cleanup() - - if err := s.setupRegistry(); err != nil { - return nil, fmt.Errorf("registry setup: %w", err) - } - - if err := s.roundtrip(); err != nil { - return nil, fmt.Errorf("roundtrip: %w", err) - } - - if s.screencopy == nil { - return nil, fmt.Errorf("compositor does not support wlr-screencopy-unstable-v1") - } - - if err := s.roundtrip(); err != nil { - return nil, fmt.Errorf("roundtrip: %w", err) - } - - switch s.config.Mode { - case ModeLastRegion: - return s.captureLastRegion() - case ModeRegion: - return s.captureRegion() - case ModeWindow: - return s.captureWindow() - case ModeOutput: - return s.captureOutput(s.config.OutputName) - case ModeFullScreen: - return s.captureFullScreen() - case ModeAllScreens: - return s.captureAllScreens() - default: - return s.captureRegion() - } -} - -func (s *Screenshoter) captureLastRegion() (*CaptureResult, error) { - lastRegion := GetLastRegion() - if lastRegion.IsEmpty() { - return s.captureRegion() - } - - output := s.findOutputForRegion(lastRegion) - if output == nil { - return s.captureRegion() - } - - return s.captureRegionOnOutput(output, lastRegion) -} - -func (s *Screenshoter) captureRegion() (*CaptureResult, error) { - if s.config.Reset { - if err := SaveLastRegion(Region{}); err != nil { - log.Debug("failed to reset last region", "err", err) - } - } - - selector := NewRegionSelector(s) - result, cancelled, err := selector.Run() - if err != nil { - return nil, fmt.Errorf("region selection: %w", err) - } - if cancelled || result == nil { - return nil, nil - } - - if err := SaveLastRegion(result.Region); err != nil { - log.Debug("failed to save last region", "err", err) - } - - return result, nil -} - -func (s *Screenshoter) captureWindow() (*CaptureResult, error) { - geom, err := GetActiveWindow() - if err != nil { - return nil, err - } - - region := Region{ - X: geom.X, - Y: geom.Y, - Width: geom.Width, - Height: geom.Height, - } - - var output *WaylandOutput - if geom.Output != "" { - output = s.findOutputByName(geom.Output) - } - if output == nil { - output = s.findOutputForRegion(region) - } - if output == nil { - return nil, fmt.Errorf("could not find output for window") - } - - switch DetectCompositor() { - case CompositorHyprland: - return s.captureAndCrop(output, region) - case CompositorDWL: - return s.captureDWLWindow(output, region, geom) - default: - return s.captureRegionOnOutput(output, region) - } -} - -func (s *Screenshoter) captureDWLWindow(output *WaylandOutput, region Region, geom *WindowGeometry) (*CaptureResult, error) { - result, err := s.captureWholeOutput(output) - if err != nil { - return nil, err - } - - scale := geom.Scale - if scale <= 0 || scale == 1.0 { - if output.fractionalScale > 1.0 { - scale = output.fractionalScale - } - } - if scale <= 0 { - scale = 1.0 - } - - localX := int(float64(region.X-geom.OutputX) * scale) - localY := int(float64(region.Y-geom.OutputY) * scale) - w := int(float64(region.Width) * scale) - h := int(float64(region.Height) * scale) - - if localX < 0 { - w += localX - localX = 0 - } - if localY < 0 { - h += localY - localY = 0 - } - if localX+w > result.Buffer.Width { - w = result.Buffer.Width - localX - } - if localY+h > result.Buffer.Height { - h = result.Buffer.Height - localY - } - - if w <= 0 || h <= 0 { - result.Buffer.Close() - return nil, fmt.Errorf("window not visible on output") - } - - cropped, err := CreateShmBuffer(w, h, w*4) - if err != nil { - result.Buffer.Close() - return nil, fmt.Errorf("create crop buffer: %w", err) - } - - srcData := result.Buffer.Data() - dstData := cropped.Data() - - for y := 0; y < h; y++ { - srcY := localY + y - if result.YInverted { - srcY = result.Buffer.Height - 1 - (localY + y) - } - if srcY < 0 || srcY >= result.Buffer.Height { - continue - } - - dstY := y - if result.YInverted { - dstY = h - 1 - y - } - - for x := 0; x < w; x++ { - srcX := localX + x - if srcX < 0 || srcX >= result.Buffer.Width { - continue - } - - si := srcY*result.Buffer.Stride + srcX*4 - di := dstY*cropped.Stride + x*4 - - if si+3 >= len(srcData) || di+3 >= len(dstData) { - continue - } - - dstData[di+0] = srcData[si+0] - dstData[di+1] = srcData[si+1] - dstData[di+2] = srcData[si+2] - dstData[di+3] = srcData[si+3] - } - } - - result.Buffer.Close() - cropped.Format = PixelFormat(result.Format) - - return &CaptureResult{ - Buffer: cropped, - Region: region, - YInverted: false, - Format: result.Format, - }, nil -} - -func (s *Screenshoter) captureFullScreen() (*CaptureResult, error) { - output := s.findFocusedOutput() - if output == nil { - s.outputsMu.Lock() - for _, o := range s.outputs { - output = o - break - } - s.outputsMu.Unlock() - } - - if output == nil { - return nil, fmt.Errorf("no output available") - } - - return s.captureWholeOutput(output) -} - -func (s *Screenshoter) captureOutput(name string) (*CaptureResult, error) { - s.outputsMu.Lock() - var output *WaylandOutput - for _, o := range s.outputs { - if o.name == name { - output = o - break - } - } - s.outputsMu.Unlock() - - if output == nil { - return nil, fmt.Errorf("output %q not found", name) - } - - return s.captureWholeOutput(output) -} - -func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) { - s.outputsMu.Lock() - outputs := make([]*WaylandOutput, 0, len(s.outputs)) - for _, o := range s.outputs { - outputs = append(outputs, o) - } - s.outputsMu.Unlock() - - if len(outputs) == 0 { - return nil, fmt.Errorf("no outputs available") - } - if len(outputs) == 1 { - return s.captureWholeOutput(outputs[0]) - } - - wlrInfos := getAllOutputInfos() - - type pendingOutput struct { - result *CaptureResult - logX float64 - logY float64 - scale float64 - } - var pending []pendingOutput - maxScale := 1.0 - - for _, output := range outputs { - result, err := s.captureWholeOutput(output) - if err != nil { - log.Warn("failed to capture output", "name", output.name, "err", err) - continue - } - - logX, logY := float64(output.x), float64(output.y) - scale := float64(output.scale) - - switch DetectCompositor() { - case CompositorHyprland: - if hx, hy, _, _, ok := GetHyprlandMonitorGeometry(output.name); ok { - logX, logY = float64(hx), float64(hy) - } - if hs := GetHyprlandMonitorScale(output.name); hs > 0 { - scale = hs - } - default: - if wlrInfos != nil { - if info, ok := wlrInfos[output.name]; ok { - logX, logY = float64(info.x), float64(info.y) - if info.scale > 0 { - scale = info.scale - } - } - } - } - - if scale <= 0 { - scale = 1.0 - } - - pending = append(pending, pendingOutput{result: result, logX: logX, logY: logY, scale: scale}) - if scale > maxScale { - maxScale = scale - } - } - - if len(pending) == 0 { - return nil, fmt.Errorf("failed to capture any outputs") - } - if len(pending) == 1 { - return pending[0].result, nil - } - - type layoutEntry struct { - result *CaptureResult - canvasX int - canvasY int - canvasW int - canvasH int - } - entries := make([]layoutEntry, len(pending)) - var minX, minY, maxX, maxY int - - for i, p := range pending { - cx := int(math.Round(p.logX * maxScale)) - cy := int(math.Round(p.logY * maxScale)) - cw := int(math.Round(float64(p.result.Buffer.Width) * maxScale / p.scale)) - ch := int(math.Round(float64(p.result.Buffer.Height) * maxScale / p.scale)) - - entries[i] = layoutEntry{result: p.result, canvasX: cx, canvasY: cy, canvasW: cw, canvasH: ch} - - right := cx + cw - bottom := cy + ch - if i == 0 { - minX, minY, maxX, maxY = cx, cy, right, bottom - continue - } - if cx < minX { - minX = cx - } - if cy < minY { - minY = cy - } - if right > maxX { - maxX = right - } - if bottom > maxY { - maxY = bottom - } - } - - totalW := maxX - minX - totalH := maxY - minY - composite, err := CreateShmBuffer(totalW, totalH, totalW*4) - if err != nil { - for _, e := range entries { - e.result.Buffer.Close() - } - return nil, fmt.Errorf("create composite buffer: %w", err) - } - composite.Clear() - - var format uint32 - for _, e := range entries { - if format == 0 { - format = e.result.Format - } - s.blitBufferScaled(composite, e.result.Buffer, - e.canvasX-minX, e.canvasY-minY, e.canvasW, e.canvasH, - e.result.YInverted) - e.result.Buffer.Close() - } - - return &CaptureResult{ - Buffer: composite, - Region: Region{X: int32(minX), Y: int32(minY), Width: int32(totalW), Height: int32(totalH)}, - Format: format, - }, nil -} - -func (s *Screenshoter) blitBufferScaled(dst, src *ShmBuffer, dstX, dstY, dstW, dstH int, yInverted bool) { - if dstW <= 0 || dstH <= 0 { - return - } - - srcData := src.Data() - dstData := dst.Data() - - for dy := 0; dy < dstH; dy++ { - canvasY := dstY + dy - if canvasY < 0 || canvasY >= dst.Height { - continue - } - - srcY := dy * src.Height / dstH - if yInverted { - srcY = src.Height - 1 - srcY - } - if srcY < 0 || srcY >= src.Height { - continue - } - - srcRowOff := srcY * src.Stride - dstRowOff := canvasY * dst.Stride - - for dx := 0; dx < dstW; dx++ { - canvasX := dstX + dx - if canvasX < 0 || canvasX >= dst.Width { - continue - } - - srcX := dx * src.Width / dstW - if srcX >= src.Width { - continue - } - - si := srcRowOff + srcX*4 - di := dstRowOff + canvasX*4 - - if si+3 >= len(srcData) || di+3 >= len(dstData) { - continue - } - - dstData[di+0] = srcData[si+0] - dstData[di+1] = srcData[si+1] - dstData[di+2] = srcData[si+2] - dstData[di+3] = srcData[si+3] - } - } -} - -func (s *Screenshoter) captureWholeOutput(output *WaylandOutput) (*CaptureResult, error) { - cursor := int32(s.config.Cursor) - - frame, err := s.screencopy.CaptureOutput(cursor, output.wlOutput) - if err != nil { - return nil, fmt.Errorf("capture output: %w", err) - } - - result, err := s.processFrame(frame, Region{ - X: output.x, - Y: output.y, - Width: output.width, - Height: output.height, - Output: output.name, - }) - if err != nil { - return nil, err - } - - if result.YInverted { - result.Buffer.FlipVertical() - result.YInverted = false - } - - if output.transform == TransformNormal { - return result, nil - } - - invTransform := InverseTransform(output.transform) - transformed, err := result.Buffer.ApplyTransform(invTransform) - if err != nil { - result.Buffer.Close() - return nil, fmt.Errorf("apply transform: %w", err) - } - - if transformed != result.Buffer { - result.Buffer.Close() - result.Buffer = transformed - } - - result.Region.Width = int32(transformed.Width) - result.Region.Height = int32(transformed.Height) - - return result, nil -} - -func (s *Screenshoter) captureAndCrop(output *WaylandOutput, region Region) (*CaptureResult, error) { - result, err := s.captureWholeOutput(output) - if err != nil { - return nil, err - } - - outX, outY := output.x, output.y - scale := float64(output.scale) - if hx, hy, _, _, ok := GetHyprlandMonitorGeometry(output.name); ok { - outX, outY = hx, hy - } - if s := GetHyprlandMonitorScale(output.name); s > 0 { - scale = s - } - if scale <= 0 { - scale = 1.0 - } - - localX := int(float64(region.X-outX) * scale) - localY := int(float64(region.Y-outY) * scale) - w := int(float64(region.Width) * scale) - h := int(float64(region.Height) * scale) - - cropped, err := CreateShmBuffer(w, h, w*4) - if err != nil { - result.Buffer.Close() - return nil, fmt.Errorf("create crop buffer: %w", err) - } - - srcData := result.Buffer.Data() - dstData := cropped.Data() - - for y := 0; y < h; y++ { - srcY := localY + y - if result.YInverted { - srcY = result.Buffer.Height - 1 - (localY + y) - } - if srcY < 0 || srcY >= result.Buffer.Height { - continue - } - - dstY := y - if result.YInverted { - dstY = h - 1 - y - } - - for x := 0; x < w; x++ { - srcX := localX + x - if srcX < 0 || srcX >= result.Buffer.Width { - continue - } - - si := srcY*result.Buffer.Stride + srcX*4 - di := dstY*cropped.Stride + x*4 - - if si+3 >= len(srcData) || di+3 >= len(dstData) { - continue - } - - dstData[di+0] = srcData[si+0] - dstData[di+1] = srcData[si+1] - dstData[di+2] = srcData[si+2] - dstData[di+3] = srcData[si+3] - } - } - - result.Buffer.Close() - cropped.Format = PixelFormat(result.Format) - - return &CaptureResult{ - Buffer: cropped, - Region: region, - YInverted: false, - Format: result.Format, - }, nil -} - -func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Region) (*CaptureResult, error) { - if output.transform != TransformNormal { - return s.captureRegionOnTransformedOutput(output, region) - } - - scale := output.fractionalScale - if scale <= 0 && DetectCompositor() == CompositorHyprland { - scale = GetHyprlandMonitorScale(output.name) - } - if scale <= 0 { - scale = float64(output.scale) - } - if scale <= 0 { - scale = 1.0 - } - - localX := int32(float64(region.X-output.x) * scale) - localY := int32(float64(region.Y-output.y) * scale) - w := int32(float64(region.Width) * scale) - h := int32(float64(region.Height) * scale) - - if DetectCompositor() == CompositorDWL { - scaledOutW := int32(float64(output.width) * scale) - scaledOutH := int32(float64(output.height) * scale) - if localX >= scaledOutW { - localX = localX % scaledOutW - } - if localY >= scaledOutH { - localY = localY % scaledOutH - } - if localX+w > scaledOutW { - w = scaledOutW - localX - } - if localY+h > scaledOutH { - h = scaledOutH - localY - } - if localX < 0 { - w += localX - localX = 0 - } - if localY < 0 { - h += localY - localY = 0 - } - } - - cursor := int32(s.config.Cursor) - - frame, err := s.screencopy.CaptureOutputRegion(cursor, output.wlOutput, localX, localY, w, h) - if err != nil { - return nil, fmt.Errorf("capture region: %w", err) - } - - return s.processFrame(frame, region) -} - -func (s *Screenshoter) captureRegionOnTransformedOutput(output *WaylandOutput, region Region) (*CaptureResult, error) { - result, err := s.captureWholeOutput(output) - if err != nil { - return nil, err - } - - scale := output.fractionalScale - if scale <= 0 && DetectCompositor() == CompositorHyprland { - scale = GetHyprlandMonitorScale(output.name) - } - if scale <= 0 { - scale = float64(output.scale) - } - if scale <= 0 { - scale = 1.0 - } - - localX := int(float64(region.X-output.x) * scale) - localY := int(float64(region.Y-output.y) * scale) - w := int(float64(region.Width) * scale) - h := int(float64(region.Height) * scale) - - if localX < 0 { - w += localX - localX = 0 - } - if localY < 0 { - h += localY - localY = 0 - } - if localX+w > result.Buffer.Width { - w = result.Buffer.Width - localX - } - if localY+h > result.Buffer.Height { - h = result.Buffer.Height - localY - } - - if w <= 0 || h <= 0 { - result.Buffer.Close() - return nil, fmt.Errorf("region not visible on output") - } - - cropped, err := CreateShmBuffer(w, h, w*4) - if err != nil { - result.Buffer.Close() - return nil, fmt.Errorf("create crop buffer: %w", err) - } - - srcData := result.Buffer.Data() - dstData := cropped.Data() - - for y := 0; y < h; y++ { - srcOff := (localY+y)*result.Buffer.Stride + localX*4 - dstOff := y * cropped.Stride - if srcOff+w*4 <= len(srcData) && dstOff+w*4 <= len(dstData) { - copy(dstData[dstOff:dstOff+w*4], srcData[srcOff:srcOff+w*4]) - } - } - - result.Buffer.Close() - cropped.Format = PixelFormat(result.Format) - - return &CaptureResult{ - Buffer: cropped, - Region: region, - YInverted: false, - Format: result.Format, - }, nil -} - -func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1, region Region) (*CaptureResult, error) { - var buf *ShmBuffer - var pool *client.ShmPool - var wlBuf *client.Buffer - var format PixelFormat - var yInverted bool - ready := false - failed := false - - frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) { - format = PixelFormat(e.Format) - bpp := format.BytesPerPixel() - if int(e.Stride) < int(e.Width)*bpp { - log.Error("invalid stride from compositor", "stride", e.Stride, "width", e.Width, "bpp", bpp) - return - } - var err error - buf, err = CreateShmBuffer(int(e.Width), int(e.Height), int(e.Stride)) - if err != nil { - log.Error("failed to create buffer", "err", err) - return - } - buf.Format = format - }) - - frame.SetFlagsHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1FlagsEvent) { - yInverted = (e.Flags & 1) != 0 - }) - - frame.SetBufferDoneHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferDoneEvent) { - if buf == nil { - return - } - - var err error - pool, err = s.shm.CreatePool(buf.Fd(), int32(buf.Size())) - if err != nil { - log.Error("failed to create pool", "err", err) - return - } - - wlBuf, err = pool.CreateBuffer(0, int32(buf.Width), int32(buf.Height), int32(buf.Stride), uint32(format)) - if err != nil { - pool.Destroy() - pool = nil - log.Error("failed to create wl_buffer", "err", err) - return - } - - if err := frame.Copy(wlBuf); err != nil { - log.Error("failed to copy frame", "err", err) - } - }) - - frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) { - ready = true - }) - - frame.SetFailedHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1FailedEvent) { - failed = true - }) - - for !ready && !failed { - if err := s.ctx.Dispatch(); err != nil { - frame.Destroy() - return nil, fmt.Errorf("dispatch: %w", err) - } - } - - frame.Destroy() - if wlBuf != nil { - wlBuf.Destroy() - } - if pool != nil { - pool.Destroy() - } - - if failed { - if buf != nil { - buf.Close() - } - return nil, fmt.Errorf("frame capture failed") - } - - if format.Is24Bit() { - converted, newFormat, err := buf.ConvertTo32Bit(format) - if err != nil { - buf.Close() - return nil, fmt.Errorf("convert 24-bit to 32-bit: %w", err) - } - if converted != buf { - buf.Close() - buf = converted - } - format = newFormat - } - - return &CaptureResult{ - Buffer: buf, - Region: region, - YInverted: yInverted, - Format: uint32(format), - }, nil -} - -func (s *Screenshoter) findOutputByName(name string) *WaylandOutput { - s.outputsMu.Lock() - defer s.outputsMu.Unlock() - for _, o := range s.outputs { - if o.name == name { - return o - } - } - return nil -} - -func (s *Screenshoter) findOutputForRegion(region Region) *WaylandOutput { - s.outputsMu.Lock() - defer s.outputsMu.Unlock() - - cx := region.X + region.Width/2 - cy := region.Y + region.Height/2 - - for _, o := range s.outputs { - x, y, w, h := o.x, o.y, o.width, o.height - if DetectCompositor() == CompositorHyprland { - if hx, hy, hw, hh, ok := GetHyprlandMonitorGeometry(o.name); ok { - x, y, w, h = hx, hy, hw, hh - } - } - if cx >= x && cx < x+w && cy >= y && cy < y+h { - return o - } - } - - for _, o := range s.outputs { - x, y, w, h := o.x, o.y, o.width, o.height - if DetectCompositor() == CompositorHyprland { - if hx, hy, hw, hh, ok := GetHyprlandMonitorGeometry(o.name); ok { - x, y, w, h = hx, hy, hw, hh - } - } - if region.X >= x && region.X < x+w && - region.Y >= y && region.Y < y+h { - return o - } - } - - return nil -} - -func (s *Screenshoter) findFocusedOutput() *WaylandOutput { - if mon := GetFocusedMonitor(); mon != "" { - s.outputsMu.Lock() - defer s.outputsMu.Unlock() - for _, o := range s.outputs { - if o.name == mon { - return o - } - } - } - s.outputsMu.Lock() - defer s.outputsMu.Unlock() - for _, o := range s.outputs { - return o - } - return nil -} - -func (s *Screenshoter) connect() error { - display, err := client.Connect("") - if err != nil { - return err - } - s.display = display - s.ctx = display.Context() - return nil -} - -func (s *Screenshoter) roundtrip() error { - return wlhelpers.Roundtrip(s.display, s.ctx) -} - -func (s *Screenshoter) setupRegistry() error { - registry, err := s.display.GetRegistry() - if err != nil { - return err - } - s.registry = registry - - registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) { - s.handleGlobal(e) - }) - - registry.SetGlobalRemoveHandler(func(e client.RegistryGlobalRemoveEvent) { - s.outputsMu.Lock() - delete(s.outputs, e.Name) - s.outputsMu.Unlock() - }) - - return nil -} - -func (s *Screenshoter) handleGlobal(e client.RegistryGlobalEvent) { - switch e.Interface { - case client.CompositorInterfaceName: - comp := client.NewCompositor(s.ctx) - if err := s.registry.Bind(e.Name, e.Interface, e.Version, comp); err == nil { - s.compositor = comp - } - - case client.ShmInterfaceName: - shm := client.NewShm(s.ctx) - if err := s.registry.Bind(e.Name, e.Interface, e.Version, shm); err == nil { - s.shm = shm - } - - case client.OutputInterfaceName: - output := client.NewOutput(s.ctx) - version := e.Version - if version > 4 { - version = 4 - } - if err := s.registry.Bind(e.Name, e.Interface, version, output); err == nil { - s.outputsMu.Lock() - s.outputs[e.Name] = &WaylandOutput{ - wlOutput: output, - globalName: e.Name, - scale: 1, - fractionalScale: 1.0, - } - s.outputsMu.Unlock() - s.setupOutputHandlers(e.Name, output) - } - - case wlr_screencopy.ZwlrScreencopyManagerV1InterfaceName: - sc := wlr_screencopy.NewZwlrScreencopyManagerV1(s.ctx) - version := e.Version - if version > 3 { - version = 3 - } - if err := s.registry.Bind(e.Name, e.Interface, version, sc); err == nil { - s.screencopy = sc - } - } -} - -func (s *Screenshoter) setupOutputHandlers(name uint32, output *client.Output) { - output.SetGeometryHandler(func(e client.OutputGeometryEvent) { - s.outputsMu.Lock() - if o, ok := s.outputs[name]; ok { - o.x, o.y = e.X, e.Y - o.transform = int32(e.Transform) - } - s.outputsMu.Unlock() - }) - - output.SetModeHandler(func(e client.OutputModeEvent) { - if e.Flags&uint32(client.OutputModeCurrent) == 0 { - return - } - s.outputsMu.Lock() - if o, ok := s.outputs[name]; ok { - o.width, o.height = e.Width, e.Height - } - s.outputsMu.Unlock() - }) - - output.SetScaleHandler(func(e client.OutputScaleEvent) { - s.outputsMu.Lock() - if o, ok := s.outputs[name]; ok { - o.scale = e.Factor - o.fractionalScale = float64(e.Factor) - } - s.outputsMu.Unlock() - }) - - output.SetNameHandler(func(e client.OutputNameEvent) { - s.outputsMu.Lock() - if o, ok := s.outputs[name]; ok { - o.name = e.Name - } - s.outputsMu.Unlock() - }) -} - -func (s *Screenshoter) cleanup() { - if s.screencopy != nil { - s.screencopy.Destroy() - } - if s.display != nil { - s.ctx.Close() - } -} - -func (s *Screenshoter) GetOutputs() []*WaylandOutput { - s.outputsMu.Lock() - defer s.outputsMu.Unlock() - out := make([]*WaylandOutput, 0, len(s.outputs)) - for _, o := range s.outputs { - out = append(out, o) - } - return out -} - -func ListOutputs() ([]Output, error) { - sc := New(DefaultConfig()) - if err := sc.connect(); err != nil { - return nil, err - } - defer sc.cleanup() - - if err := sc.setupRegistry(); err != nil { - return nil, err - } - if err := sc.roundtrip(); err != nil { - return nil, err - } - if err := sc.roundtrip(); err != nil { - return nil, err - } - - sc.outputsMu.Lock() - defer sc.outputsMu.Unlock() - - compositor := DetectCompositor() - result := make([]Output, 0, len(sc.outputs)) - for _, o := range sc.outputs { - out := Output{ - Name: o.name, - X: o.x, - Y: o.y, - Width: o.width, - Height: o.height, - Scale: o.scale, - FractionalScale: o.fractionalScale, - Transform: o.transform, - } - - switch compositor { - case CompositorHyprland: - if hx, hy, hw, hh, ok := GetHyprlandMonitorGeometry(o.name); ok { - out.X, out.Y = hx, hy - out.Width, out.Height = hw, hh - } - if s := GetHyprlandMonitorScale(o.name); s > 0 { - out.FractionalScale = s - } - } - - result = append(result, out) - } - return result, nil -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/shm.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/shm.go deleted file mode 100644 index a178cf0..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/shm.go +++ /dev/null @@ -1,35 +0,0 @@ -package screenshot - -import "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/shm" - -type PixelFormat = shm.PixelFormat - -const ( - FormatARGB8888 = shm.FormatARGB8888 - FormatXRGB8888 = shm.FormatXRGB8888 - FormatABGR8888 = shm.FormatABGR8888 - FormatXBGR8888 = shm.FormatXBGR8888 - FormatRGB888 = shm.FormatRGB888 - FormatBGR888 = shm.FormatBGR888 -) - -const ( - TransformNormal = shm.TransformNormal - Transform90 = shm.Transform90 - Transform180 = shm.Transform180 - Transform270 = shm.Transform270 - TransformFlipped = shm.TransformFlipped - TransformFlipped90 = shm.TransformFlipped90 - TransformFlipped180 = shm.TransformFlipped180 - TransformFlipped270 = shm.TransformFlipped270 -) - -type ShmBuffer = shm.Buffer - -func CreateShmBuffer(width, height, stride int) (*ShmBuffer, error) { - return shm.CreateBuffer(width, height, stride) -} - -func InverseTransform(transform int32) int32 { - return shm.InverseTransform(transform) -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/state.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/state.go deleted file mode 100644 index 0ba6c63..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/state.go +++ /dev/null @@ -1,65 +0,0 @@ -package screenshot - -import ( - "encoding/json" - "os" - "path" - "path/filepath" -) - -type PersistentState struct { - LastRegion Region `json:"last_region"` -} - -func getStateFilePath() string { - cacheDir, err := os.UserCacheDir() - if err != nil { - cacheDir = path.Join(os.Getenv("HOME"), ".cache") - } - return filepath.Join(cacheDir, "dms", "screenshot-state.json") -} - -func LoadState() (*PersistentState, error) { - path := getStateFilePath() - data, err := os.ReadFile(path) - if err != nil { - if os.IsNotExist(err) { - return &PersistentState{}, nil - } - return nil, err - } - - var state PersistentState - if err := json.Unmarshal(data, &state); err != nil { - return &PersistentState{}, nil - } - return &state, nil -} - -func SaveState(state *PersistentState) error { - path := getStateFilePath() - dir := filepath.Dir(path) - if err := os.MkdirAll(dir, 0o755); err != nil { - return err - } - - data, err := json.MarshalIndent(state, "", " ") - if err != nil { - return err - } - return os.WriteFile(path, data, 0o644) -} - -func GetLastRegion() Region { - state, err := LoadState() - if err != nil { - return Region{} - } - return state.LastRegion -} - -func SaveLastRegion(r Region) error { - state, _ := LoadState() - state.LastRegion = r - return SaveState(state) -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/theme.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/theme.go deleted file mode 100644 index a2bd5f6..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/theme.go +++ /dev/null @@ -1,125 +0,0 @@ -package screenshot - -import ( - "encoding/json" - "os" - "path/filepath" - "strings" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/internal/utils" -) - -type ThemeColors struct { - Background string `json:"surface"` - OnSurface string `json:"on_surface"` - Primary string `json:"primary"` -} - -type ColorScheme struct { - Dark ThemeColors `json:"dark"` - Light ThemeColors `json:"light"` -} - -type ColorsFile struct { - Colors ColorScheme `json:"colors"` -} - -var cachedStyle *OverlayStyle - -func LoadOverlayStyle() OverlayStyle { - if cachedStyle != nil { - return *cachedStyle - } - - style := DefaultOverlayStyle - colors := loadColorsFile() - if colors == nil { - cachedStyle = &style - return style - } - - theme := &colors.Dark - if isLightMode() { - theme = &colors.Light - } - - if bg, ok := parseHexColor(theme.Background); ok { - style.BackgroundR, style.BackgroundG, style.BackgroundB = bg[0], bg[1], bg[2] - } - if text, ok := parseHexColor(theme.OnSurface); ok { - style.TextR, style.TextG, style.TextB = text[0], text[1], text[2] - } - if accent, ok := parseHexColor(theme.Primary); ok { - style.AccentR, style.AccentG, style.AccentB = accent[0], accent[1], accent[2] - } - - cachedStyle = &style - return style -} - -func loadColorsFile() *ColorScheme { - path := getColorsFilePath() - data, err := os.ReadFile(path) - if err != nil { - return nil - } - - var file ColorsFile - if err := json.Unmarshal(data, &file); err != nil { - return nil - } - - return &file.Colors -} - -func getColorsFilePath() string { - cacheDir, err := os.UserCacheDir() - if err != nil { - log.Error("Failed to get user cache dir", "err", err) - return "" - } - return filepath.Join(cacheDir, "DankMaterialShell", "dms-colors.json") -} - -func isLightMode() bool { - scheme, err := utils.GsettingsGet("org.gnome.desktop.interface", "color-scheme") - if err != nil { - return false - } - - switch scheme { - case "'prefer-light'", "'default'": - return true - } - return false -} - -func parseHexColor(hex string) ([3]uint8, bool) { - hex = strings.TrimPrefix(hex, "#") - if len(hex) != 6 { - return [3]uint8{}, false - } - - var r, g, b uint8 - for i, ptr := range []*uint8{&r, &g, &b} { - val := 0 - for j := 0; j < 2; j++ { - c := hex[i*2+j] - val *= 16 - switch { - case c >= '0' && c <= '9': - val += int(c - '0') - case c >= 'a' && c <= 'f': - val += int(c - 'a' + 10) - case c >= 'A' && c <= 'F': - val += int(c - 'A' + 10) - default: - return [3]uint8{}, false - } - } - *ptr = uint8(val) - } - - return [3]uint8{r, g, b}, true -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/types.go deleted file mode 100644 index 00cbe1c..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/screenshot/types.go +++ /dev/null @@ -1,81 +0,0 @@ -package screenshot - -type Mode int - -const ( - ModeRegion Mode = iota - ModeWindow - ModeFullScreen - ModeAllScreens - ModeOutput - ModeLastRegion -) - -type Format int - -const ( - FormatPNG Format = iota - FormatJPEG - FormatPPM -) - -type CursorMode int - -const ( - CursorOff CursorMode = iota - CursorOn -) - -type Region struct { - X int32 `json:"x"` - Y int32 `json:"y"` - Width int32 `json:"width"` - Height int32 `json:"height"` - Output string `json:"output,omitempty"` -} - -func (r Region) IsEmpty() bool { - return r.Width <= 0 || r.Height <= 0 -} - -type Output struct { - Name string - X, Y int32 - Width int32 - Height int32 - Scale int32 - FractionalScale float64 - Transform int32 -} - -type Config struct { - Mode Mode - OutputName string - Cursor CursorMode - NoConfirm bool - Reset bool - Format Format - Quality int - OutputDir string - Filename string - Clipboard bool - SaveFile bool - Notify bool - Stdout bool -} - -func DefaultConfig() Config { - return Config{ - Mode: ModeRegion, - Cursor: CursorOff, - NoConfirm: false, - Reset: false, - Format: FormatPNG, - Quality: 90, - OutputDir: "", - Filename: "", - Clipboard: true, - SaveFile: true, - Notify: true, - } -} |