feat(screenshot): add flag to control HUD scale and visibility (#3398)

* feat(screenshot): add --hud flag to control HUD scale and visibility

* fix(screenshot): clamp HUD scale to 1-4 and clarify flag description
This commit is contained in:
Huỳnh Thiện Lộc
2026-09-11 10:05:11 -04:00
committed by GitHub
parent 09ddd6da82
commit 13192370f5
5 changed files with 88 additions and 4 deletions
+3
View File
@@ -35,6 +35,7 @@ var (
ssJSON bool
ssGeometry bool
ssAllowMulti bool
ssHUD string
)
type screenshotMetadata struct {
@@ -181,6 +182,7 @@ func init() {
screenshotCmd.PersistentFlags().BoolVar(&ssJSON, "json", false, "Print capture metadata as JSON")
screenshotCmd.PersistentFlags().BoolVarP(&ssGeometry, "geometry", "g", false, "Print selected region geometry (X,Y WxH) to stdout without capturing an image")
screenshotCmd.PersistentFlags().BoolVar(&ssAllowMulti, "allow-multiple", false, "Open a selector even when another one is already open")
screenshotCmd.PersistentFlags().StringVar(&ssHUD, "hud", "auto", "HUD overlay scale in region selector (auto, off/0, or integer scale 1-4)")
ssScrollCmd.Flags().IntVar(&ssScrollInterval, "interval", 45, "Capture interval in milliseconds (30-1000)")
@@ -212,6 +214,7 @@ func getScreenshotConfig(mode screenshot.Mode) screenshot.Config {
config.Stdout = ssStdout
config.Geometry = ssGeometry
config.AllowMultiple = ssAllowMulti
config.HUD = ssHUD
if ssGeometry {
config.Clipboard = false
+4 -3
View File
@@ -973,11 +973,12 @@ func (r *RegionSelector) renderSurface(os *OutputSurface) {
case !slot.cacheValid(srcBuf, r.selection.dragging, r.showCapturedCursor, r.phase, handles, shift):
slot.shm.CopyFrom(srcBuf)
r.dimBackground(slot.shm)
scale := 1
effectiveScale := 1.0
if os.output != nil {
scale = scaleFactor(os.output.effectiveScale())
effectiveScale = os.output.effectiveScale()
}
r.drawHUD(slot.shm.Data(), slot.shm.Stride, slot.shm.Width, slot.shm.Height, os.screenFormat, scale)
hudScale := r.hudScale(effectiveScale)
r.drawHUD(slot.shm.Data(), slot.shm.Stride, slot.shm.Width, slot.shm.Height, os.screenFormat, hudScale)
slot.backgroundInitialized = true
slot.backgroundSource = srcBuf
slot.backgroundDragging = r.selection.dragging
+32 -1
View File
@@ -4,6 +4,8 @@ import (
"fmt"
"math"
"slices"
"strconv"
"strings"
)
const (
@@ -630,6 +632,32 @@ func (r *RegionSelector) drawScrollBar(data []byte, stride, bufW, bufH int, form
style.TextR, style.TextG, style.TextB, format)
}
const (
minHUDScale = 1
maxHUDScale = 4
)
func (r *RegionSelector) hudScale(effectiveScale float64) int {
hudOpt := "auto"
if r.screenshoter != nil && r.screenshoter.config.HUD != "" {
hudOpt = strings.ToLower(strings.TrimSpace(r.screenshoter.config.HUD))
}
switch hudOpt {
case "off", "none", "false", "0":
return 0
case "", "auto":
return min(scaleFactor(effectiveScale), maxHUDScale)
default:
if val, err := strconv.ParseFloat(hudOpt, 64); err == nil {
if val <= 0 {
return 0
}
return min(max(scaleFactor(val), minHUDScale), maxHUDScale)
}
return min(scaleFactor(effectiveScale), maxHUDScale)
}
}
type hudItem struct {
key, desc string
}
@@ -657,6 +685,9 @@ func (r *RegionSelector) hudItems() []hudItem {
}
func (r *RegionSelector) hudDimensions(bufW, bufH, scale int) (hudX, hudY, hudW, hudH int) {
if scale <= 0 {
return 0, 0, 0, 0
}
scale = scaleFactor(float64(scale))
charH := fontCharH * scale
padding, itemSpacing := 12*scale, 24*scale
@@ -680,7 +711,7 @@ func (r *RegionSelector) hudDimensions(bufW, bufH, scale int) (hudX, hudY, hudW,
}
func (r *RegionSelector) drawHUD(data []byte, stride, bufW, bufH int, format uint32, scale int) {
if r.selection.dragging {
if r.selection.dragging || scale <= 0 {
return
}
@@ -238,6 +238,12 @@ func TestHUDDimensionsContainText(t *testing.T) {
},
}
// When scale <= 0, hudDimensions should return zero
hx, hy, hw, hh := r.hudDimensions(bufW, bufH, 0)
if hx != 0 || hy != 0 || hw != 0 || hh != 0 {
t.Fatalf("expected 0 dimensions for scale 0, got %d, %d, %d, %d", hx, hy, hw, hh)
}
for _, scale := range []int{1, 2} {
hudX, hudY, hudW, hudH := r.hudDimensions(bufW, bufH, scale)
data := make([]byte, bufH*stride)
@@ -260,6 +266,47 @@ func TestHUDDimensionsContainText(t *testing.T) {
}
}
func TestHUDScale(t *testing.T) {
tests := []struct {
hudOpt string
effectiveScale float64
expectedScale int
}{
{"auto", 1.0, 1},
{"auto", 1.8, 2},
{"auto", 2.4, 2},
{"", 1.0, 1},
{"", 2.0, 2},
{"off", 1.0, 0},
{"OFF", 2.0, 0},
{"none", 1.5, 0},
{"false", 1.0, 0},
{"0", 2.0, 0},
{"0.0", 1.0, 0},
{"-1", 1.0, 0},
{"1", 1.0, 1},
{"1.5", 1.0, 2},
{"2", 1.0, 2},
{"3", 1.0, 3},
{"4", 1.0, 4},
{"1000", 1.0, 4},
{"0.2", 1.0, 1},
{"invalid", 1.7, 2},
}
for _, tc := range tests {
r := &RegionSelector{
screenshoter: &Screenshoter{
config: Config{HUD: tc.hudOpt},
},
}
got := r.hudScale(tc.effectiveScale)
if got != tc.expectedScale {
t.Errorf("hudScale(%q, %v) = %d, expected %d", tc.hudOpt, tc.effectiveScale, got, tc.expectedScale)
}
}
}
func TestOverlayDeltaHandlesClampedMonitorEdge(t *testing.T) {
prev := &overlay{
interior: dirtyRect{100, 100, 1920, 1080},
+2
View File
@@ -73,6 +73,7 @@ type Config struct {
Geometry bool
AllowMultiple bool
IntervalMs int
HUD string
// SelectorHook runs as the interactive selector starts (true) and ends (false).
SelectorHook func(begin bool)
}
@@ -90,5 +91,6 @@ func DefaultConfig() Config {
Clipboard: true,
SaveFile: true,
Notify: true,
HUD: "auto",
}
}