Show OSD and sync touchpad steps when scrolling bar widgets (audio, monitor) (#6421)

* Show OSD and sync touchpad steps when scrolling bar widgets

Scrolling the volume or brightness icon in the bar changed the value
directly without ever calling omarchy-osd, so only the keyboard media
keys showed the popup. On top of that, a touchpad's stream of many
small wheel events per finger-drag wasn't matched to a mouse's single
±120 notch per click, so touchpad scrolling felt uncoordinated and
uneven next to the keyboard/mouse behavior.

- shell/plugins/panels/audio/Panel.qml: accumulate raw wheel delta and
  only apply a step once it crosses a full mouse-notch's worth, so
  touchpad and mouse move the volume in identical 5% increments, and
  ping the OSD each time a step actually lands.
- shell/plugins/panels/monitor/Panel.qml: same accumulator for
  brightness, with a 5% floor so scrolling down can never blank the
  screen.
- shell/plugins/osd/Osd.qml: animate the progress bar's width instead
  of snapping, so successive steps glide smoothly.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Fix bar wheel OSD behavior

* Normalize scaled wheel events

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
This commit is contained in:
Saman Shirdel
2026-07-29 18:36:18 -04:00
committed by GitHub
co-authored by Copilot Autofix powered by AI David Heinemeier Hansson
parent 09b955dc75
commit 2093d1c9c7
4 changed files with 58 additions and 7 deletions
+11
View File
@@ -17,6 +17,17 @@ QtObject {
return clamp(value, 0, 1) return clamp(value, 0, 1)
} }
function wheelSteps(accumulator, delta) {
// Some mouse/compositor combinations scale a single notch well beyond
// Qt's conventional 120 units. Keep one event to one step while still
// accumulating the smaller deltas emitted by touchpads.
delta = Math.max(-120, Math.min(120, delta))
if (accumulator * delta < 0) accumulator = 0
var total = accumulator + delta
var steps = total < 0 ? Math.ceil(total / 120) : Math.floor(total / 120)
return { steps: steps, remainder: total - steps * 120 }
}
// Compose a base color with an opacity. Accepts a color object or a hex // Compose a base color with an opacity. Accepts a color object or a hex
// string; null/undefined yields transparent black at the requested alpha. // string; null/undefined yields transparent black at the requested alpha.
function alpha(c, opacity) { function alpha(c, opacity) {
+7
View File
@@ -56,6 +56,8 @@ Item {
function show(iconName, rawMessage, rawValue, rawMax, rawProgressText, rawDuration) { function show(iconName, rawMessage, rawValue, rawMax, rawProgressText, rawDuration) {
var next = OsdModel.stateForShow(iconName, rawMessage, rawValue, rawMax, rawProgressText, rawDuration) var next = OsdModel.stateForShow(iconName, rawMessage, rawValue, rawMax, rawProgressText, rawDuration)
// Update before opening so a fresh OSD starts at its new value; only
// subsequent updates while it remains open animate the progress bar.
iconKey = next.iconKey iconKey = next.iconKey
maxValue = next.maxValue maxValue = next.maxValue
hasProgress = next.hasProgress hasProgress = next.hasProgress
@@ -176,6 +178,11 @@ Item {
height: parent.height height: parent.height
width: parent.width * (root.hasProgress ? root.value / root.maxValue : 0) width: parent.width * (root.hasProgress ? root.value / root.maxValue : 0)
color: Color.accent color: Color.accent
Behavior on width {
enabled: root.opened
NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
}
} }
} }
Text { Text {
+23 -6
View File
@@ -121,6 +121,9 @@ Panel {
// selected while a tuning still exists. // selected while a tuning still exists.
property string volumeSinkName: "" property string volumeSinkName: ""
// Carry sub-notch touchpad deltas between wheel events.
property real wheelAccumulator: 0
readonly property var volumeSink: { readonly property var volumeSink: {
if (volumeSinkName === "" || !sink) return sink if (volumeSinkName === "" || !sink) return sink
if (volumeSinkName === String(sink.name)) return sink if (volumeSinkName === String(sink.name)) return sink
@@ -395,13 +398,13 @@ Panel {
if (selectedIndex < floor) selectedIndex = floor if (selectedIndex < floor) selectedIndex = floor
} }
function outputIcon() { function outputIcon(volume) {
// Match the old Waybar pulseaudio glyph set. The Material Design speaker // Match the old Waybar pulseaudio glyph set. The Material Design speaker
// icons render visually smaller in JetBrainsMono Nerd Font. // icons render visually smaller in JetBrainsMono Nerd Font.
if (!sink || !sink.audio) return "" if (!sink || !sink.audio) return ""
if (isHeadphones(sink)) return "󰋋" if (isHeadphones(sink)) return "󰋋"
if (outputMuted) return "" if (outputMuted) return ""
var v = outputVolume var v = volume === undefined ? outputVolume : volume
if (v >= 0.67) return "" if (v >= 0.67) return ""
if (v >= 0.34) return "" if (v >= 0.34) return ""
if (v > 0) return "" if (v > 0) return ""
@@ -421,8 +424,18 @@ Panel {
} }
function setOutputVolume(v) { function setOutputVolume(v) {
if (!sink || !sink.audio) return if (!volumeSink || !volumeSink.audio) return outputVolume
volumeSink.audio.volume = Math.max(0, Math.min(1, v)) var volume = Math.max(0, Math.min(1, v))
volumeSink.audio.volume = volume
return volume
}
function showVolumeOsd(volume) {
if (!bar || !bar.shell) return
bar.shell.summon("omarchy.osd", JSON.stringify({
icon: outputIcon(volume),
value: Math.round(volume * 100)
}))
} }
function setInputVolume(v) { function setInputVolume(v) {
@@ -625,8 +638,12 @@ Panel {
} }
onWheelMoved: function(delta) { onWheelMoved: function(delta) {
var step = 0.05 if (!root.hasOutput) return
root.setOutputVolume(root.outputVolume + (delta > 0 ? step : -step)) var wheel = Util.wheelSteps(root.wheelAccumulator, delta)
root.wheelAccumulator = wheel.remainder
if (wheel.steps === 0) return
var volume = root.setOutputVolume(root.outputVolume + wheel.steps * 0.05)
root.showVolumeOsd(volume)
} }
} }
+17 -1
View File
@@ -27,6 +27,9 @@ Panel {
property var displays: [] property var displays: []
property int enabledDisplayCount: 0 property int enabledDisplayCount: 0
// Carry sub-notch touchpad deltas between wheel events.
property real wheelAccumulator: 0
// Cursor model shared by keyboard and mouse. Sections: // Cursor model shared by keyboard and mouse. Sections:
// "brightness" - single slider row, selectedIndex = -1 sentinel // "brightness" - single slider row, selectedIndex = -1 sentinel
// (mirrors Audio's slider rows). Only present if a // (mirrors Audio's slider rows). Only present if a
@@ -250,6 +253,14 @@ Panel {
brightnessDebounce.restart() brightnessDebounce.restart()
} }
function showBrightnessOsd(percent) {
if (!bar || !bar.shell) return
bar.shell.summon("omarchy.osd", JSON.stringify({
icon: "brightness",
value: percent
}))
}
function normalizeScale(scale) { function normalizeScale(scale) {
return Model.normalizeScale(scale) return Model.normalizeScale(scale)
} }
@@ -461,7 +472,12 @@ Panel {
text: Quickshell.screens.length > 1 ? "󰍺" : "󰍹" text: Quickshell.screens.length > 1 ? "󰍺" : "󰍹"
onPressed: function(b) { root.toggle() } onPressed: function(b) { root.toggle() }
onWheelMoved: function(delta) { onWheelMoved: function(delta) {
if (root.brightnessAvailable) root.setBrightness(root.brightnessPercent + (delta > 0 ? 5 : -5)) if (!root.brightnessAvailable) return
var wheel = Util.wheelSteps(root.wheelAccumulator, delta)
root.wheelAccumulator = wheel.remainder
if (wheel.steps === 0) return
root.setBrightness(root.brightnessPercent + wheel.steps * 5)
root.showBrightnessOsd(root.brightnessPercent)
} }
} }