detached a1a57ba

fix(config): bound hint.size

commit
a1a57baddc8d5c37b10717b70eb78ec0d91fc8a2
author
brice · · 2026-10-19 15:32 UTC
parent
1c416bd
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +26 -4
M docs/config.md +1 -1
@@ -58,7 +58,7 @@ See [examples/config](../examples/config) for every key with its default.
5858 | `hint.enable` | `off` | Turn on the experimental [hint mode](#hint-mode) (restart required) |
5959 | `hint.targets` | `clickable` | What [hint mode](#hint-mode) labels: `clickable` (links, URLs and buttons) or `links` (links and URLs only) |
6060 | `hint.text-click` | `ctrl+shift` | Modifiers held for clicks on links found by OCR (terminals open links on a modified click); `none` sends a plain click |
61-| `hint.size` | `auto` | Label text size in logical pixels (positive number), or `auto` for screen pixel density |
61+| `hint.size` | `auto` | Label text size in logical pixels (6 to 72), or `auto` for screen pixel density |
6262 | `hint.background` | empty | Label background (`#rrggbb`); empty uses `border.active` |
6363 | `hint.foreground` | empty | Label text (`#rrggbb`); empty chooses a contrasting colour |
6464 | `workspace.<name>.monitor` | focused output | Home monitor of a named workspace |
@@ -58,7 +58,7 @@ See [examples/config](../examples/config) for every key with its default.
58 | `hint.enable` | `off` | Turn on the experimental [hint mode](#hint-mode) (restart required) |58 | `hint.enable` | `off` | Turn on the experimental [hint mode](#hint-mode) (restart required) |
59 | `hint.targets` | `clickable` | What [hint mode](#hint-mode) labels: `clickable` (links, URLs and buttons) or `links` (links and URLs only) |59 | `hint.targets` | `clickable` | What [hint mode](#hint-mode) labels: `clickable` (links, URLs and buttons) or `links` (links and URLs only) |
60 | `hint.text-click` | `ctrl+shift` | Modifiers held for clicks on links found by OCR (terminals open links on a modified click); `none` sends a plain click |60 | `hint.text-click` | `ctrl+shift` | Modifiers held for clicks on links found by OCR (terminals open links on a modified click); `none` sends a plain click |
61-| `hint.size` | `auto` | Label text size in logical pixels (positive number), or `auto` for screen pixel density |61+| `hint.size` | `auto` | Label text size in logical pixels (6 to 72), or `auto` for screen pixel density |
62 | `hint.background` | empty | Label background (`#rrggbb`); empty uses `border.active` |62 | `hint.background` | empty | Label background (`#rrggbb`); empty uses `border.active` |
63 | `hint.foreground` | empty | Label text (`#rrggbb`); empty chooses a contrasting colour |63 | `hint.foreground` | empty | Label text (`#rrggbb`); empty chooses a contrasting colour |
64 | `workspace.<name>.monitor` | focused output | Home monitor of a named workspace |64 | `workspace.<name>.monitor` | focused output | Home monitor of a named workspace |
M internal/adapters/config/config.go +2 -2
@@ -863,8 +863,8 @@ func set(c *ports.Config, key, v string) error {
863863 break
864864 }
865865 n, err := strconv.ParseFloat(v, 64)
866- if err != nil || n <= 0 || math.IsInf(n, 0) || math.IsNaN(n) {
867- return fmt.Errorf("must be auto or a finite positive number")
866+ if err != nil || math.IsNaN(n) || n < 6 || n > 72 {
867+ return fmt.Errorf("must be auto or a number from 6 to 72 (logical pixels)")
868868 }
869869 c.Hint.Size = n
870870 case "hint.background", "hint.foreground":
@@ -863,8 +863,8 @@ func set(c *ports.Config, key, v string) error {
863 break863 break
864 }864 }
865 n, err := strconv.ParseFloat(v, 64)865 n, err := strconv.ParseFloat(v, 64)
866- if err != nil || n <= 0 || math.IsInf(n, 0) || math.IsNaN(n) {866+ if err != nil || math.IsNaN(n) || n < 6 || n > 72 {
867- return fmt.Errorf("must be auto or a finite positive number")867+ return fmt.Errorf("must be auto or a number from 6 to 72 (logical pixels)")
868 }868 }
869 c.Hint.Size = n869 c.Hint.Size = n
870 case "hint.background", "hint.foreground":870 case "hint.background", "hint.foreground":
M internal/adapters/config/config_test.go +10 -1
@@ -986,7 +986,7 @@ func TestHintStyleConfig(t *testing.T) {
986986 if len(warnings) != 0 || c.Hint.Size != 0 || c.Hint.Background != "" || c.Hint.Foreground != "" {
987987 t.Fatalf("automatic: %+v %v", c.Hint, warnings)
988988 }
989- for _, line := range []string{"hint.size = 0", "hint.size = -1", "hint.size = NaN", "hint.size = Inf", "hint.size = huge", "hint.background = red", "hint.foreground = #fff"} {
989+ for _, line := range []string{"hint.size = 0", "hint.size = -1", "hint.size = NaN", "hint.size = Inf", "hint.size = huge", "hint.size = 5.9", "hint.size = 72.1", "hint.size = 1e9", "hint.background = red", "hint.foreground = #fff"} {
990990 _, warnings := parseString(t, line+"\n")
991991 if len(warnings) == 0 {
992992 t.Errorf("expected validate-config warning for %s", line)
@@ -994,6 +994,15 @@ func TestHintStyleConfig(t *testing.T) {
994994 }
995995 }
996996
997+func TestHintSizeBounds(t *testing.T) {
998+ for _, v := range []string{"6", "72", "13"} {
999+ c, w := parseString(t, "hint.size = "+v+"\n")
1000+ if len(w) != 0 || c.Hint.Size == 0 {
1001+ t.Errorf("hint.size = %s: %+v %v", v, c.Hint, w)
1002+ }
1003+ }
1004+}
1005+
9971006 func TestHintTextClick(t *testing.T) {
9981007 if got := Defaults().Hint.TextClick; got != ports.ModCtrl|ports.ModShift {
9991008 t.Fatalf("default %d", got)
@@ -986,7 +986,7 @@ func TestHintStyleConfig(t *testing.T) {
986 if len(warnings) != 0 || c.Hint.Size != 0 || c.Hint.Background != "" || c.Hint.Foreground != "" {986 if len(warnings) != 0 || c.Hint.Size != 0 || c.Hint.Background != "" || c.Hint.Foreground != "" {
987 t.Fatalf("automatic: %+v %v", c.Hint, warnings)987 t.Fatalf("automatic: %+v %v", c.Hint, warnings)
988 }988 }
989- for _, line := range []string{"hint.size = 0", "hint.size = -1", "hint.size = NaN", "hint.size = Inf", "hint.size = huge", "hint.background = red", "hint.foreground = #fff"} {989+ for _, line := range []string{"hint.size = 0", "hint.size = -1", "hint.size = NaN", "hint.size = Inf", "hint.size = huge", "hint.size = 5.9", "hint.size = 72.1", "hint.size = 1e9", "hint.background = red", "hint.foreground = #fff"} {
990 _, warnings := parseString(t, line+"\n")990 _, warnings := parseString(t, line+"\n")
991 if len(warnings) == 0 {991 if len(warnings) == 0 {
992 t.Errorf("expected validate-config warning for %s", line)992 t.Errorf("expected validate-config warning for %s", line)
@@ -994,6 +994,15 @@ func TestHintStyleConfig(t *testing.T) {
994 }994 }
995 }995 }
996 996
997+func TestHintSizeBounds(t *testing.T) {
998+ for _, v := range []string{"6", "72", "13"} {
999+ c, w := parseString(t, "hint.size = "+v+"\n")
1000+ if len(w) != 0 || c.Hint.Size == 0 {
1001+ t.Errorf("hint.size = %s: %+v %v", v, c.Hint, w)
1002+ }
1003+ }
1004+}
1005+
997 func TestHintTextClick(t *testing.T) {1006 func TestHintTextClick(t *testing.T) {
998 if got := Defaults().Hint.TextClick; got != ports.ModCtrl|ports.ModShift {1007 if got := Defaults().Hint.TextClick; got != ports.ModCtrl|ports.ModShift {
999 t.Fatalf("default %d", got)1008 t.Fatalf("default %d", got)
M internal/adapters/hintlabel/style.go +8 -0
@@ -15,6 +15,14 @@ type Style struct {
1515 // FontPixels resolves an explicit logical size or the output's pixel density.
1616 // Both millimetre dimensions must be plausible for the untransformed mode.
1717 func FontPixels(width, height, mmWidth, mmHeight int, scale, logical float64) float64 {
18+ return min(maxFontPx, fontPixels(width, height, mmWidth, mmHeight, scale, logical))
19+}
20+
21+// maxFontPx bounds the physical font size, whatever the configuration or the
22+// scale ask for.
23+const maxFontPx = 256
24+
25+func fontPixels(width, height, mmWidth, mmHeight int, scale, logical float64) float64 {
1826 if scale <= 0 || math.IsNaN(scale) || math.IsInf(scale, 0) {
1927 scale = 1
2028 }
@@ -15,6 +15,14 @@ type Style struct {
15 // FontPixels resolves an explicit logical size or the output's pixel density.15 // FontPixels resolves an explicit logical size or the output's pixel density.
16 // Both millimetre dimensions must be plausible for the untransformed mode.16 // Both millimetre dimensions must be plausible for the untransformed mode.
17 func FontPixels(width, height, mmWidth, mmHeight int, scale, logical float64) float64 {17 func FontPixels(width, height, mmWidth, mmHeight int, scale, logical float64) float64 {
18+ return min(maxFontPx, fontPixels(width, height, mmWidth, mmHeight, scale, logical))
19+}
20+
21+// maxFontPx bounds the physical font size, whatever the configuration or the
22+// scale ask for.
23+const maxFontPx = 256
24+
25+func fontPixels(width, height, mmWidth, mmHeight int, scale, logical float64) float64 {
18 if scale <= 0 || math.IsNaN(scale) || math.IsInf(scale, 0) {26 if scale <= 0 || math.IsNaN(scale) || math.IsInf(scale, 0) {
19 scale = 127 scale = 1
20 }28 }
M internal/adapters/hintlabel/style_test.go +5 -0
@@ -73,3 +73,8 @@ func TestStyledRasterSizeAndCache(t *testing.T) {
7373 require.NotEqual(t, b, c)
7474 require.Equal(t, byte(89), c[3], "outline has 35 percent physical-pixel alpha")
7575 }
76+
77+func TestFontPixelsClamped(t *testing.T) {
78+ require.InDelta(t, 256, FontPixels(1920, 1080, 0, 0, 3, 1e9), .00001)
79+ require.InDelta(t, 256, FontPixels(1920, 1080, 0, 0, 4, 72), .00001)
80+}
@@ -73,3 +73,8 @@ func TestStyledRasterSizeAndCache(t *testing.T) {
73 require.NotEqual(t, b, c)73 require.NotEqual(t, b, c)
74 require.Equal(t, byte(89), c[3], "outline has 35 percent physical-pixel alpha")74 require.Equal(t, byte(89), c[3], "outline has 35 percent physical-pixel alpha")
75 }75 }
76+
77+func TestFontPixelsClamped(t *testing.T) {
78+ require.InDelta(t, 256, FontPixels(1920, 1080, 0, 0, 3, 1e9), .00001)
79+ require.InDelta(t, 256, FontPixels(1920, 1080, 0, 0, 4, 72), .00001)
80+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close