From f3913fb6bf2cb351c7762d3feb4b06f4df5d6f13 Mon Sep 17 00:00:00 2001 From: Jatin K Malik Date: Mon, 2 Nov 2026 12:31:02 -0800 Subject: [PATCH] fix(ui): show the selected microphone's full name in the device tooltip (#1029) (#1037) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Input Device dropdown has a fixed width and cuts labels off at about 18 characters. Raw ALSA names such as "sof-hda-dsp: - (hw:0,6)" therefore all look like "sof-hda-dsp: - (h…", and the tooltip always showed the same generic text, so there was no way to tell which entry was selected. The tooltip now shows the full label of the selected device. It updates when the list is filled and every time the selection changes, and goes back to the generic hint for System Default. --- src/vocalinux/ui/settings_dialog.py | 16 +++++++++- tests/test_pulse_sources.py | 46 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/vocalinux/ui/settings_dialog.py b/src/vocalinux/ui/settings_dialog.py index 40fab6e..a97f32b 100644 --- a/src/vocalinux/ui/settings_dialog.py +++ b/src/vocalinux/ui/settings_dialog.py @@ -2140,6 +2140,18 @@ def _combo_chrome(combo: Gtk.Widget, tooltip: Optional[str] = None) -> None: _prevent_scroll_on_hover(combo) +_AUDIO_DEVICE_TOOLTIP = "Select the microphone to use for voice recognition" + + +def _sync_audio_device_tooltip(combo: Gtk.ComboBoxText) -> None: + """Show the selected microphone's full name, which the fixed-width combo truncates.""" + label = combo.get_active_text() + if not label or combo.get_active_id() in (None, "-1"): + combo.set_tooltip_text(_AUDIO_DEVICE_TOOLTIP) + else: + combo.set_tooltip_text(label) + + def _new_spin( lower: float, upper: float, step: float, digits: int, tooltip: Optional[str] = None ) -> Gtk.SpinButton: @@ -3137,7 +3149,7 @@ class SettingsDialog(AboutSectionMixin): self.audio_device_combo = Gtk.ComboBoxText() _combo_chrome( self.audio_device_combo, - tooltip="Select the microphone to use for voice recognition", + tooltip=_AUDIO_DEVICE_TOOLTIP, ) refresh_btn = Gtk.Button.new_from_icon_name("view-refresh-symbolic", Gtk.IconSize.BUTTON) @@ -9925,6 +9937,7 @@ For now, the engine has been reverted to VOSK.""" if self.speech_engine is not None: self.speech_engine.set_audio_device(None, None) + _sync_audio_device_tooltip(self.audio_device_combo) logger.info(f"Found {len(devices)} audio input devices") def _on_refresh_audio_devices(self, widget): @@ -9934,6 +9947,7 @@ For now, the engine has been reverted to VOSK.""" def _on_audio_device_changed(self, widget): """Handle changes in the selected audio device.""" + _sync_audio_device_tooltip(self.audio_device_combo) if self._initializing: return diff --git a/tests/test_pulse_sources.py b/tests/test_pulse_sources.py index c8c0a5c..055e37b 100644 --- a/tests/test_pulse_sources.py +++ b/tests/test_pulse_sources.py @@ -633,6 +633,52 @@ class TestSettingsPersistsServerName: dialog.audio_device_combo.set_active_id.assert_called_with(str(PULSE_INDEX_BASE - 1)) +class TestAudioDeviceTooltip: + """The fixed-width dropdown truncates long names; the tooltip shows them whole (#1029).""" + + _SOF = "sof-hda-dsp: - (hw:0,6)" + + def _combo(self, device_id: Optional[str], label: Optional[str]) -> MagicMock: + combo = MagicMock() + combo.get_active_id.return_value = device_id + combo.get_active_text.return_value = label + return combo + + def test_choosing_a_device_shows_its_full_name_in_the_tooltip(self) -> None: + settings_dialog = _load_settings_dialog() + dialog = MagicMock(_initializing=False, _pulse_source_names={}) + dialog.audio_device_combo = self._combo("1", self._SOF) + settings_dialog.SettingsDialog._on_audio_device_changed(dialog, None) + dialog.audio_device_combo.set_tooltip_text.assert_called_with(self._SOF) + + def test_system_default_keeps_the_generic_tooltip(self) -> None: + settings_dialog = _load_settings_dialog() + dialog = MagicMock(_initializing=False, _pulse_source_names={}) + dialog.audio_device_combo = self._combo("-1", "System Default") + settings_dialog.SettingsDialog._on_audio_device_changed(dialog, None) + dialog.audio_device_combo.set_tooltip_text.assert_called_with( + "Select the microphone to use for voice recognition" + ) + + def test_populating_shows_the_saved_device_in_the_tooltip(self) -> None: + """The saved device is selected before the change handler is wired up.""" + settings_dialog = _load_settings_dialog() + dialog = MagicMock() + dialog.config_manager.get_optional_int.return_value = 1 + dialog.config_manager.get.return_value = self._SOF + dialog.audio_device_combo = self._combo("1", self._SOF) + devices = [(0, "sof-hda-dsp: - (hw:0,0)", False), (1, self._SOF, False)] + with ( + patch.object(settings_dialog, "get_pulse_sources", return_value=[]), + patch( + "vocalinux.speech_recognition.recognition_manager.get_audio_input_devices", + return_value=devices, + ), + ): + settings_dialog.SettingsDialog._populate_audio_devices(dialog) + dialog.audio_device_combo.set_tooltip_text.assert_called_with(self._SOF) + + def _load_settings_dialog() -> ModuleType: """Import settings_dialog with real GTK base classes so its methods are callable.