A volume and brightness OSD
A pill near the bottom of the screen that shows the volume or the screen brightness when either changes, and fades away a moment after you stop. It never shows when you log in or save a file. The guide's level 4 explains each line.
The file
// osd.strand. Media keys stay in the compositor: strand set audio.sink.volume +5%
enum Kind { volume, brightness }
state kind = volume
state shown = false
on change audio.sink.volume, audio.sink.muted { kind = volume; shown = true }
on change brightness.level { kind = brightness; shown = true }
on change audio.sink.volume, audio.sink.muted, brightness.level after 1.2s { shown = false }
let level = kind == volume ? (audio.sink.muted ? 0 : audio.sink.volume) : brightness.level
osd Level { // focused monitor, overlay, click-through
anchor: bottom; margin: 0, 0, 96; open: shown
enter { opacity: 0; scale: 0.9 }
row { width: 260; pad: $space.3; gap: $space.3; align: center
radius: full; bg: $surface.alpha(0.8); blur: 24; shadow: $elevation.lg
icon kind == volume ? audio.sink.icon : "display-brightness-symbolic" { size: 20 }
meter level { grow: 1; height: 6; color: $accent; track: $fg.alpha(0.15) }
text pct(level) { width: 4ch; align: end; font: $font.mono }
}
}
Bind the keys
Strand does not grab keys: your compositor keeps them and runs
strand set. These are the commands for the volume and brightness
keys:
strand set audio.sink.volume +5%
strand set audio.sink.volume -5%
strand set brightness.level +5%
strand set brightness.level -5%
Bind them to the keys in your compositor, so they work while the screen is locked and repeat while held (how: Start it and bind keys). On a service field, a signed number or percentage is a step from the value it has now, and it never goes below 0.
What it looks like
The volume steps up three times, a few frames apart, as a held key does. The OSD springs in at the first change, follows the level, and fades out 1.2 seconds after the last one.
How it works
osd Level { … }is on the focused monitor, above everything, and clicks pass through it to what is underneath. It shows whileopen:is true.on change a, b { … }runs when a watched value changes, from anywhere: your keys, a slider, another program. It never runs for the first value, nor for a value a reload writes, nor whenaudio.sinkbecomes another device because the default output changed: the new device's volume is only the new baseline. That is why the OSD stays hidden when the shell starts, when you save a file and when the default output changes.on change … after 1.2swaits for quiet: it runs once, 1.2 seconds after the last change, so holding a key keeps the OSD up.let levelfollows whichever value changed last, and 0 while the output is muted.meter levelfills from 0 to 1, andtext pct(level)shows it as a percentage in a fixed-width font,4chwide, so the digits do not jump.
Variations
- Several values in one place. Past two choices, a
matchreads better than nested?and:; the guide shows the same OSD withmatch. - Rolling digits.
roll: trueon thetextrolls the digits when the number changes. - On every monitor.
screens: allon theosdputs a copy on each monitor, withscreenin scope in each.