mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-07 05:58:28 -04:00
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -27,8 +28,19 @@ var (
|
|||||||
ssNoConfirm bool
|
ssNoConfirm bool
|
||||||
ssReset bool
|
ssReset bool
|
||||||
ssStdout 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{
|
var screenshotCmd = &cobra.Command{
|
||||||
Use: "screenshot",
|
Use: "screenshot",
|
||||||
Short: "Capture screenshots",
|
Short: "Capture screenshots",
|
||||||
@@ -59,7 +71,8 @@ Examples:
|
|||||||
dms screenshot --no-file # Clipboard only
|
dms screenshot --no-file # Clipboard only
|
||||||
dms screenshot --no-confirm # Region capture on mouse release
|
dms screenshot --no-confirm # Region capture on mouse release
|
||||||
dms screenshot --cursor=on # Include cursor
|
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{
|
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(&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(&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(&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(ssRegionCmd)
|
||||||
screenshotCmd.AddCommand(ssFullCmd)
|
screenshotCmd.AddCommand(ssFullCmd)
|
||||||
@@ -203,7 +217,36 @@ func setPopoutScreenshotMode(begin bool) {
|
|||||||
_ = exec.Command("qs", cmdArgs...).Run()
|
_ = 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) {
|
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.
|
// Region select needs the keyboard; drop popout grabs for its duration.
|
||||||
result, err := func() (*screenshot.CaptureResult, error) {
|
result, err := func() (*screenshot.CaptureResult, error) {
|
||||||
interactive := config.Mode == screenshot.ModeRegion || config.Mode == screenshot.ModeLastRegion
|
interactive := config.Mode == screenshot.ModeRegion || config.Mode == screenshot.ModeLastRegion
|
||||||
@@ -215,11 +258,13 @@ func runScreenshot(config screenshot.Config) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
exitScreenshotError("", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if result == nil {
|
if result == nil {
|
||||||
|
if ssJSON {
|
||||||
|
writeScreenshotJSON(screenshotMetadata{Status: "aborted", Error: "User cancelled selection"})
|
||||||
|
}
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,8 +276,7 @@ func runScreenshot(config screenshot.Config) {
|
|||||||
|
|
||||||
if config.Stdout {
|
if config.Stdout {
|
||||||
if err := writeImageToStdout(result.Buffer, config.Format, config.Quality, result.Format); err != nil {
|
if err := writeImageToStdout(result.Buffer, config.Format, config.Quality, result.Format); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error writing to stdout: %v\n", err)
|
exitScreenshotError(" writing to stdout", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -252,22 +296,37 @@ func runScreenshot(config screenshot.Config) {
|
|||||||
|
|
||||||
filePath = filepath.Join(outputDir, filename)
|
filePath = filepath.Join(outputDir, filename)
|
||||||
if err := screenshot.WriteToFileWithFormat(result.Buffer, filePath, config.Format, config.Quality, result.Format); err != nil {
|
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)
|
exitScreenshotError(" writing file", err)
|
||||||
os.Exit(1)
|
}
|
||||||
|
if !ssJSON {
|
||||||
|
fmt.Println(filePath)
|
||||||
}
|
}
|
||||||
fmt.Println(filePath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Clipboard {
|
if config.Clipboard {
|
||||||
if err := copyImageToClipboard(result.Buffer, config.Format, config.Quality, result.Format); err != nil {
|
if err := copyImageToClipboard(result.Buffer, config.Format, config.Quality, result.Format); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error copying to clipboard: %v\n", err)
|
exitScreenshotError(" copying to clipboard", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
if !config.SaveFile {
|
if !ssJSON && !config.SaveFile {
|
||||||
fmt.Println("Copied to clipboard")
|
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 {
|
if config.Notify {
|
||||||
thumbData, thumbW, thumbH := bufferToRGBThumbnail(result.Buffer, 256, result.Format)
|
thumbData, thumbW, thumbH := bufferToRGBThumbnail(result.Buffer, 256, result.Format)
|
||||||
screenshot.SendNotification(screenshot.NotifyResult{
|
screenshot.SendNotification(screenshot.NotifyResult{
|
||||||
|
|||||||
@@ -178,9 +178,13 @@ func (r *RegionSelector) Run() (*CaptureResult, bool, error) {
|
|||||||
|
|
||||||
yInverted := false
|
yInverted := false
|
||||||
var format uint32
|
var format uint32
|
||||||
|
scale := 1.0
|
||||||
if r.selection.surface != nil {
|
if r.selection.surface != nil {
|
||||||
yInverted = r.selection.surface.yInverted
|
yInverted = r.selection.surface.yInverted
|
||||||
format = r.selection.surface.screenFormat
|
format = r.selection.surface.screenFormat
|
||||||
|
if s := r.selection.surface.output.fractionalScale; s > 0 {
|
||||||
|
scale = s
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CaptureResult{
|
return &CaptureResult{
|
||||||
@@ -188,6 +192,7 @@ func (r *RegionSelector) Run() (*CaptureResult, bool, error) {
|
|||||||
Region: r.result,
|
Region: r.result,
|
||||||
YInverted: yInverted,
|
YInverted: yInverted,
|
||||||
Format: format,
|
Format: format,
|
||||||
|
Scale: scale,
|
||||||
}, false, nil
|
}, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,21 @@ type CaptureResult struct {
|
|||||||
Region Region
|
Region Region
|
||||||
YInverted bool
|
YInverted bool
|
||||||
Format uint32
|
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 {
|
type Screenshoter struct {
|
||||||
@@ -255,6 +270,7 @@ func (s *Screenshoter) captureMangoWindow(output *WaylandOutput, region Region,
|
|||||||
Region: region,
|
Region: region,
|
||||||
YInverted: false,
|
YInverted: false,
|
||||||
Format: result.Format,
|
Format: result.Format,
|
||||||
|
Scale: scale,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,6 +446,7 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
|
|||||||
Buffer: composite,
|
Buffer: composite,
|
||||||
Region: Region{X: int32(minX), Y: int32(minY), Width: int32(totalW), Height: int32(totalH)},
|
Region: Region{X: int32(minX), Y: int32(minY), Width: int32(totalW), Height: int32(totalH)},
|
||||||
Format: format,
|
Format: format,
|
||||||
|
Scale: maxScale,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,6 +519,7 @@ func (s *Screenshoter) captureWholeOutput(output *WaylandOutput) (*CaptureResult
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
result.Scale = output.effectiveScale()
|
||||||
|
|
||||||
if result.YInverted {
|
if result.YInverted {
|
||||||
result.Buffer.FlipVertical()
|
result.Buffer.FlipVertical()
|
||||||
@@ -604,6 +622,7 @@ func (s *Screenshoter) captureAndCrop(output *WaylandOutput, region Region) (*Ca
|
|||||||
Region: region,
|
Region: region,
|
||||||
YInverted: false,
|
YInverted: false,
|
||||||
Format: result.Format,
|
Format: result.Format,
|
||||||
|
Scale: scale,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,16 +631,7 @@ func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Regio
|
|||||||
return s.captureRegionOnTransformedOutput(output, region)
|
return s.captureRegionOnTransformedOutput(output, region)
|
||||||
}
|
}
|
||||||
|
|
||||||
scale := output.fractionalScale
|
scale := output.effectiveScale()
|
||||||
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)
|
localX := int32(float64(region.X-output.x) * scale)
|
||||||
localY := int32(float64(region.Y-output.y) * 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 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) {
|
func (s *Screenshoter) captureRegionOnTransformedOutput(output *WaylandOutput, region Region) (*CaptureResult, error) {
|
||||||
@@ -669,16 +684,7 @@ func (s *Screenshoter) captureRegionOnTransformedOutput(output *WaylandOutput, r
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
scale := output.fractionalScale
|
scale := output.effectiveScale()
|
||||||
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)
|
localX := int(float64(region.X-output.x) * scale)
|
||||||
localY := int(float64(region.Y-output.y) * scale)
|
localY := int(float64(region.Y-output.y) * scale)
|
||||||
@@ -730,6 +736,7 @@ func (s *Screenshoter) captureRegionOnTransformedOutput(output *WaylandOutput, r
|
|||||||
Region: region,
|
Region: region,
|
||||||
YInverted: false,
|
YInverted: false,
|
||||||
Format: result.Format,
|
Format: result.Format,
|
||||||
|
Scale: scale,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user