Add vertical graphic EQ slider widget for headset equalizer

Replace horizontal slider rows with a traditional graphic EQ layout
using vertical sliders side-by-side, with dB value display and
frequency labels per band.
This commit is contained in:
Ken Sanislo 2026-03-04 13:36:19 -07:00
parent b439c2d53f
commit 9e36ede037
3 changed files with 68 additions and 0 deletions

View File

@ -42,6 +42,7 @@ class Kind(IntEnum):
MAP_CHOICE = 0x0A
MULTIPLE_TOGGLE = 0x10
PACKED_RANGE = 0x20
GRAPHIC_EQ = 0x21
MULTIPLE_RANGE = 0x40
HETERO = 0x80
MAP_RANGE = 0x102

View File

@ -1703,6 +1703,8 @@ class HeadsetOnboardEQ(settings.RangeFieldSetting):
keys_universe = []
class validator_class(settings_validator.PackedRangeValidator):
kind = settings.Kind.GRAPHIC_EQ
@classmethod
def build(cls, setting_class, device):
info = hidpp20.get_onboard_eq_info(device)

View File

@ -545,6 +545,69 @@ class PackedRangeControl(MultipleRangeControl):
self._button.set_tooltip_text(b)
class GraphicEQControl(MultipleControl):
def setup(self, setting):
self._items = []
validator = setting._validator
row = Gtk.ListBoxRow()
hbox = Gtk.HBox(homogeneous=True, spacing=8)
for item in range(validator.count):
vbox = Gtk.VBox(homogeneous=False, spacing=2)
scale = Gtk.Scale.new_with_range(Gtk.Orientation.VERTICAL, validator.min_value, validator.max_value, 1)
scale.set_inverted(True)
scale.set_round_digits(0)
scale.set_digits(0)
scale.set_draw_value(True)
scale.connect("format-value", lambda s, v: f"{int(v)} dB")
scale.set_has_origin(True)
scale.set_size_request(-1, 150)
scale.add_mark(0, Gtk.PositionType.LEFT, "0")
scale.connect(GtkSignal.VALUE_CHANGED.value, self._changed, validator.keys[item])
lbl = Gtk.Label(label=str(validator.keys[item]))
lbl.set_line_wrap(True)
lbl.set_justify(Gtk.Justification.CENTER)
vbox.pack_start(scale, True, True, 0)
vbox.pack_end(lbl, False, False, 0)
vbox._setting_item = validator.keys[item]
vbox.control = scale
hbox.pack_start(vbox, True, True, 0)
self._items.append(vbox)
row.add(hbox)
self.add(row)
def _changed(self, control, item):
if control.get_sensitive():
if hasattr(control, "_timer"):
control._timer.cancel()
control._timer = Timer(0.5, lambda: GLib.idle_add(self._write, control, item))
control._timer.start()
def _write(self, control, item):
control._timer.cancel()
delattr(control, "_timer")
new_state = int(control.get_value())
if self.sbox.setting._value[int(item)] != new_state:
self.sbox.setting._value[int(item)] = new_state
_write_async(self.sbox.setting, self.sbox.setting._value[int(item)], self.sbox, key=int(item))
def set_value(self, value):
if value is None:
return
b = ""
n = len(self._items)
for vbox in self._items:
item = vbox._setting_item
v = value.get(int(item), None)
if v is not None:
vbox.control.set_value(v)
else:
v = self.sbox.setting._value[int(item)]
b += f"{str(item)}: ({str(v)}) "
lbl_text = ngettext("%d value", "%d values", n) % n
self._button.set_label(lbl_text)
self._button.set_tooltip_text(b)
# control with an ID key that determines what else to show
class HeteroKeyControl(Gtk.HBox, Control):
def __init__(self, sbox, delegate=None):
@ -715,6 +778,8 @@ def _create_sbox(s, _device):
control = MultipleRangeControl(sbox, change)
elif s.kind == settings.Kind.PACKED_RANGE:
control = PackedRangeControl(sbox, change)
elif s.kind == settings.Kind.GRAPHIC_EQ:
control = GraphicEQControl(sbox, change)
elif s.kind == settings.Kind.HETERO:
control = HeteroKeyControl(sbox, change)
else: