mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
screenshot: add window capture for Hyprland
This commit is contained in:
@@ -35,6 +35,7 @@ Modes:
|
|||||||
full - Capture the focused output
|
full - Capture the focused output
|
||||||
all - Capture all outputs combined
|
all - Capture all outputs combined
|
||||||
output - Capture a specific output by name
|
output - Capture a specific output by name
|
||||||
|
window - Capture the focused window (Hyprland only)
|
||||||
last - Capture the last selected region
|
last - Capture the last selected region
|
||||||
|
|
||||||
Output format (--format):
|
Output format (--format):
|
||||||
@@ -47,6 +48,7 @@ Examples:
|
|||||||
dms screenshot full # Full screen of focused output
|
dms screenshot full # Full screen of focused output
|
||||||
dms screenshot all # All screens combined
|
dms screenshot all # All screens combined
|
||||||
dms screenshot output -o DP-1 # Specific output
|
dms screenshot output -o DP-1 # Specific output
|
||||||
|
dms screenshot window # Focused window (Hyprland)
|
||||||
dms screenshot last # Last region (pre-selected)
|
dms screenshot last # Last region (pre-selected)
|
||||||
dms screenshot --no-clipboard # Save file only
|
dms screenshot --no-clipboard # Save file only
|
||||||
dms screenshot --no-file # Clipboard only
|
dms screenshot --no-file # Clipboard only
|
||||||
@@ -86,6 +88,14 @@ If no previous region exists, falls back to interactive selection.`,
|
|||||||
Run: runScreenshotLast,
|
Run: runScreenshotLast,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ssWindowCmd = &cobra.Command{
|
||||||
|
Use: "window",
|
||||||
|
Short: "Capture the focused window",
|
||||||
|
Long: `Capture the currently focused window.
|
||||||
|
Currently only supported on Hyprland.`,
|
||||||
|
Run: runScreenshotWindow,
|
||||||
|
}
|
||||||
|
|
||||||
var ssListCmd = &cobra.Command{
|
var ssListCmd = &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List available outputs",
|
Short: "List available outputs",
|
||||||
@@ -117,6 +127,7 @@ func init() {
|
|||||||
screenshotCmd.AddCommand(ssAllCmd)
|
screenshotCmd.AddCommand(ssAllCmd)
|
||||||
screenshotCmd.AddCommand(ssOutputCmd)
|
screenshotCmd.AddCommand(ssOutputCmd)
|
||||||
screenshotCmd.AddCommand(ssLastCmd)
|
screenshotCmd.AddCommand(ssLastCmd)
|
||||||
|
screenshotCmd.AddCommand(ssWindowCmd)
|
||||||
screenshotCmd.AddCommand(ssListCmd)
|
screenshotCmd.AddCommand(ssListCmd)
|
||||||
|
|
||||||
screenshotCmd.Run = runScreenshotRegion
|
screenshotCmd.Run = runScreenshotRegion
|
||||||
@@ -347,6 +358,11 @@ func runScreenshotLast(cmd *cobra.Command, args []string) {
|
|||||||
runScreenshot(config)
|
runScreenshot(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runScreenshotWindow(cmd *cobra.Command, args []string) {
|
||||||
|
config := getScreenshotConfig(screenshot.ModeWindow)
|
||||||
|
runScreenshot(config)
|
||||||
|
}
|
||||||
|
|
||||||
func runScreenshotList(cmd *cobra.Command, args []string) {
|
func runScreenshotList(cmd *cobra.Command, args []string) {
|
||||||
outputs, err := screenshot.ListOutputs()
|
outputs, err := screenshot.ListOutputs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
69
core/internal/screenshot/compositor.go
Normal file
69
core/internal/screenshot/compositor.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package screenshot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Compositor int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CompositorUnknown Compositor = iota
|
||||||
|
CompositorHyprland
|
||||||
|
)
|
||||||
|
|
||||||
|
func DetectCompositor() Compositor {
|
||||||
|
if os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") != "" {
|
||||||
|
return CompositorHyprland
|
||||||
|
}
|
||||||
|
return CompositorUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
type WindowGeometry struct {
|
||||||
|
X int32
|
||||||
|
Y int32
|
||||||
|
Width int32
|
||||||
|
Height int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetActiveWindow() (*WindowGeometry, error) {
|
||||||
|
compositor := DetectCompositor()
|
||||||
|
|
||||||
|
switch compositor {
|
||||||
|
case CompositorHyprland:
|
||||||
|
return getHyprlandActiveWindow()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("window capture requires Hyprland (other compositors not yet supported)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type hyprlandWindow struct {
|
||||||
|
At [2]int32 `json:"at"`
|
||||||
|
Size [2]int32 `json:"size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHyprlandActiveWindow() (*WindowGeometry, error) {
|
||||||
|
cmd := exec.Command("hyprctl", "-j", "activewindow")
|
||||||
|
output, err := cmd.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
|
||||||
|
}
|
||||||
@@ -77,6 +77,8 @@ func (s *Screenshoter) Run() (*CaptureResult, error) {
|
|||||||
return s.captureLastRegion()
|
return s.captureLastRegion()
|
||||||
case ModeRegion:
|
case ModeRegion:
|
||||||
return s.captureRegion()
|
return s.captureRegion()
|
||||||
|
case ModeWindow:
|
||||||
|
return s.captureWindow()
|
||||||
case ModeOutput:
|
case ModeOutput:
|
||||||
return s.captureOutput(s.config.OutputName)
|
return s.captureOutput(s.config.OutputName)
|
||||||
case ModeFullScreen:
|
case ModeFullScreen:
|
||||||
@@ -119,6 +121,27 @@ func (s *Screenshoter) captureRegion() (*CaptureResult, error) {
|
|||||||
return result, nil
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
output := s.findOutputForRegion(region)
|
||||||
|
if output == nil {
|
||||||
|
return nil, fmt.Errorf("could not find output for window")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.captureRegionOnOutput(output, region)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Screenshoter) captureFullScreen() (*CaptureResult, error) {
|
func (s *Screenshoter) captureFullScreen() (*CaptureResult, error) {
|
||||||
output := s.findFocusedOutput()
|
output := s.findFocusedOutput()
|
||||||
if output == nil {
|
if output == nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user