mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-01 19:18:28 -04:00
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -27,8 +28,19 @@ var (
|
||||
ssNoConfirm bool
|
||||
ssReset bool
|
||||
ssStdout bool
|
||||
ssJSON bool
|
||||
)
|
||||
|
||||
type screenshotMetadata struct {
|
||||
Status string `json:"status"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
Scale float64 `json:"scale,omitempty"`
|
||||
Mime string `json:"mime,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
var screenshotCmd = &cobra.Command{
|
||||
Use: "screenshot",
|
||||
Short: "Capture screenshots",
|
||||
@@ -59,7 +71,8 @@ Examples:
|
||||
dms screenshot --no-file # Clipboard only
|
||||
dms screenshot --no-confirm # Region capture on mouse release
|
||||
dms screenshot --cursor=on # Include cursor
|
||||
dms screenshot -f jpg -q 85 # JPEG with quality 85`,
|
||||
dms screenshot -f jpg -q 85 # JPEG with quality 85
|
||||
dms screenshot --json # Print capture metadata as JSON`,
|
||||
}
|
||||
|
||||
var ssRegionCmd = &cobra.Command{
|
||||
@@ -128,6 +141,7 @@ func init() {
|
||||
screenshotCmd.PersistentFlags().BoolVar(&ssNoConfirm, "no-confirm", false, "Region mode: capture on mouse release without Enter/Space confirmation")
|
||||
screenshotCmd.PersistentFlags().BoolVar(&ssReset, "reset", false, "Reset saved last-region preselection before capturing")
|
||||
screenshotCmd.PersistentFlags().BoolVar(&ssStdout, "stdout", false, "Output image to stdout (for piping to swappy, etc.)")
|
||||
screenshotCmd.PersistentFlags().BoolVar(&ssJSON, "json", false, "Print capture metadata as JSON")
|
||||
|
||||
screenshotCmd.AddCommand(ssRegionCmd)
|
||||
screenshotCmd.AddCommand(ssFullCmd)
|
||||
@@ -203,7 +217,36 @@ func setPopoutScreenshotMode(begin bool) {
|
||||
_ = exec.Command("qs", cmdArgs...).Run()
|
||||
}
|
||||
|
||||
func writeScreenshotJSON(meta screenshotMetadata) {
|
||||
_ = json.NewEncoder(os.Stdout).Encode(meta)
|
||||
}
|
||||
|
||||
func exitScreenshotError(context string, err error) {
|
||||
if ssJSON {
|
||||
writeScreenshotJSON(screenshotMetadata{Status: "error", Error: err.Error()})
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Error%s: %v\n", context, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func formatMime(format screenshot.Format) string {
|
||||
switch format {
|
||||
case screenshot.FormatJPEG:
|
||||
return "image/jpeg"
|
||||
case screenshot.FormatPPM:
|
||||
return "image/x-portable-pixmap"
|
||||
default:
|
||||
return "image/png"
|
||||
}
|
||||
}
|
||||
|
||||
func runScreenshot(config screenshot.Config) {
|
||||
if ssJSON && config.Stdout {
|
||||
fmt.Fprintln(os.Stderr, "Error: --json cannot be combined with --stdout")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Region select needs the keyboard; drop popout grabs for its duration.
|
||||
result, err := func() (*screenshot.CaptureResult, error) {
|
||||
interactive := config.Mode == screenshot.ModeRegion || config.Mode == screenshot.ModeLastRegion
|
||||
@@ -215,11 +258,13 @@ func runScreenshot(config screenshot.Config) {
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
exitScreenshotError("", err)
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
if ssJSON {
|
||||
writeScreenshotJSON(screenshotMetadata{Status: "aborted", Error: "User cancelled selection"})
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@@ -231,8 +276,7 @@ func runScreenshot(config screenshot.Config) {
|
||||
|
||||
if config.Stdout {
|
||||
if err := writeImageToStdout(result.Buffer, config.Format, config.Quality, result.Format); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing to stdout: %v\n", err)
|
||||
os.Exit(1)
|
||||
exitScreenshotError(" writing to stdout", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -252,22 +296,37 @@ func runScreenshot(config screenshot.Config) {
|
||||
|
||||
filePath = filepath.Join(outputDir, filename)
|
||||
if err := screenshot.WriteToFileWithFormat(result.Buffer, filePath, config.Format, config.Quality, result.Format); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing file: %v\n", err)
|
||||
os.Exit(1)
|
||||
exitScreenshotError(" writing file", err)
|
||||
}
|
||||
if !ssJSON {
|
||||
fmt.Println(filePath)
|
||||
}
|
||||
fmt.Println(filePath)
|
||||
}
|
||||
|
||||
if config.Clipboard {
|
||||
if err := copyImageToClipboard(result.Buffer, config.Format, config.Quality, result.Format); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error copying to clipboard: %v\n", err)
|
||||
os.Exit(1)
|
||||
exitScreenshotError(" copying to clipboard", err)
|
||||
}
|
||||
if !config.SaveFile {
|
||||
if !ssJSON && !config.SaveFile {
|
||||
fmt.Println("Copied to clipboard")
|
||||
}
|
||||
}
|
||||
|
||||
if ssJSON {
|
||||
scale := result.Scale
|
||||
if scale <= 0 {
|
||||
scale = 1.0
|
||||
}
|
||||
writeScreenshotJSON(screenshotMetadata{
|
||||
Status: "success",
|
||||
Path: filePath,
|
||||
Width: result.Buffer.Width,
|
||||
Height: result.Buffer.Height,
|
||||
Scale: scale,
|
||||
Mime: formatMime(config.Format),
|
||||
})
|
||||
}
|
||||
|
||||
if config.Notify {
|
||||
thumbData, thumbW, thumbH := bufferToRGBThumbnail(result.Buffer, 256, result.Format)
|
||||
screenshot.SendNotification(screenshot.NotifyResult{
|
||||
|
||||
@@ -178,9 +178,13 @@ func (r *RegionSelector) Run() (*CaptureResult, bool, error) {
|
||||
|
||||
yInverted := false
|
||||
var format uint32
|
||||
scale := 1.0
|
||||
if r.selection.surface != nil {
|
||||
yInverted = r.selection.surface.yInverted
|
||||
format = r.selection.surface.screenFormat
|
||||
if s := r.selection.surface.output.fractionalScale; s > 0 {
|
||||
scale = s
|
||||
}
|
||||
}
|
||||
|
||||
return &CaptureResult{
|
||||
@@ -188,6 +192,7 @@ func (r *RegionSelector) Run() (*CaptureResult, bool, error) {
|
||||
Region: r.result,
|
||||
YInverted: yInverted,
|
||||
Format: format,
|
||||
Scale: scale,
|
||||
}, false, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,21 @@ type CaptureResult struct {
|
||||
Region Region
|
||||
YInverted bool
|
||||
Format uint32
|
||||
Scale float64
|
||||
}
|
||||
|
||||
func (o *WaylandOutput) effectiveScale() float64 {
|
||||
scale := o.fractionalScale
|
||||
if scale <= 0 && DetectCompositor() == CompositorHyprland {
|
||||
scale = GetHyprlandMonitorScale(o.name)
|
||||
}
|
||||
if scale <= 0 {
|
||||
scale = float64(o.scale)
|
||||
}
|
||||
if scale <= 0 {
|
||||
return 1.0
|
||||
}
|
||||
return scale
|
||||
}
|
||||
|
||||
type Screenshoter struct {
|
||||
@@ -255,6 +270,7 @@ func (s *Screenshoter) captureMangoWindow(output *WaylandOutput, region Region,
|
||||
Region: region,
|
||||
YInverted: false,
|
||||
Format: result.Format,
|
||||
Scale: scale,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -430,6 +446,7 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
|
||||
Buffer: composite,
|
||||
Region: Region{X: int32(minX), Y: int32(minY), Width: int32(totalW), Height: int32(totalH)},
|
||||
Format: format,
|
||||
Scale: maxScale,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -502,6 +519,7 @@ func (s *Screenshoter) captureWholeOutput(output *WaylandOutput) (*CaptureResult
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Scale = output.effectiveScale()
|
||||
|
||||
if result.YInverted {
|
||||
result.Buffer.FlipVertical()
|
||||
@@ -604,6 +622,7 @@ func (s *Screenshoter) captureAndCrop(output *WaylandOutput, region Region) (*Ca
|
||||
Region: region,
|
||||
YInverted: false,
|
||||
Format: result.Format,
|
||||
Scale: scale,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -612,16 +631,7 @@ func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Regio
|
||||
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
|
||||
}
|
||||
scale := output.effectiveScale()
|
||||
|
||||
localX := int32(float64(region.X-output.x) * scale)
|
||||
localY := int32(float64(region.Y-output.y) * scale)
|
||||
@@ -660,7 +670,12 @@ func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Regio
|
||||
return nil, fmt.Errorf("capture region: %w", err)
|
||||
}
|
||||
|
||||
return s.processFrame(frame, region)
|
||||
result, err := s.processFrame(frame, region)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Scale = scale
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Screenshoter) captureRegionOnTransformedOutput(output *WaylandOutput, region Region) (*CaptureResult, error) {
|
||||
@@ -669,16 +684,7 @@ func (s *Screenshoter) captureRegionOnTransformedOutput(output *WaylandOutput, r
|
||||
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
|
||||
}
|
||||
scale := output.effectiveScale()
|
||||
|
||||
localX := int(float64(region.X-output.x) * scale)
|
||||
localY := int(float64(region.Y-output.y) * scale)
|
||||
@@ -730,6 +736,7 @@ func (s *Screenshoter) captureRegionOnTransformedOutput(output *WaylandOutput, r
|
||||
Region: region,
|
||||
YInverted: false,
|
||||
Format: result.Format,
|
||||
Scale: scale,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user