diff --git a/dikte/cli.py b/dikte/cli.py index c1ef786..37bc87d 100644 --- a/dikte/cli.py +++ b/dikte/cli.py @@ -873,7 +873,7 @@ def cmd_doctor(opts): # Recording, the device list, and KDE's shortcut registry. wanted += ["pw-record", "pactl", "kwriteconfig6"] wanted += ["ffmpeg", - assistant.executable(assistant.provider(conf)) or "claude", + assistant.executable(assistant.provider(conf)), cleanup.executable(cleanup.provider(conf))] programs = {name: shutil.which(name) or "" for name in wanted if name} target = conf.transcribe_target() diff --git a/dikte/hotkey.py b/dikte/hotkey.py index 91e4c6b..cc793e8 100644 --- a/dikte/hotkey.py +++ b/dikte/hotkey.py @@ -57,7 +57,7 @@ SHORTCUTS = { "Dikte: pause/resume the recording", "pause_shortcut", ""), "cancel": Shortcut("cancel", CANCEL_DESKTOP_ID, "Dikte: discard the recording", "cancel_shortcut", ""), - "ask": Shortcut("ask", ASK_DESKTOP_ID, "Dikte: ask Claude Code", + "ask": Shortcut("ask", ASK_DESKTOP_ID, "Dikte: ask the agent", "assistant_shortcut", ""), "meeting": Shortcut("meeting", MEETING_DESKTOP_ID, "Dikte: start/end a meeting recording", diff --git a/dikte/settings_ui.py b/dikte/settings_ui.py index 791c7dd..8825b06 100644 --- a/dikte/settings_ui.py +++ b/dikte/settings_ui.py @@ -223,7 +223,9 @@ class WheelGuard(QObject): """ def eventFilter(self, box, event): - if event.type() == QEvent.Type.Wheel and not box.hasFocus(): + win = box.window() + focused = box.hasFocus() or (win is not None and win.focusWidget() is box) + if event.type() == QEvent.Type.Wheel and not focused: # Refused rather than swallowed. An unaccepted wheel event carries # on up the parents to the scroll area, so the page still moves. event.ignore() diff --git a/tests/test_cli.py b/tests/test_cli.py index 4ab82e1..f7619d8 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -578,6 +578,21 @@ class Doctor(DikteTest): self.run_doctor(as_json=False, cleanup_provider="codex", cleanup_codex_model="gpt-5.4")) + def test_agent_on_hosted_provider_does_not_ask_for_a_cli_program(self): + for provider in ("openrouter", "opencode"): + with self.subTest(provider=provider): + reply = self.run_doctor(assistant_provider=provider) + self.assertEqual(reply["agent"]["provider"], provider) + for cli_name in ("claude", "codex", "agy"): + self.assertNotIn(cli_name, reply["programs"]) + + def test_agent_on_a_cli_asks_for_the_program(self): + for provider, binary in (("claude", "claude"), ("codex", "codex"), ("agy", "agy")): + with self.subTest(provider=provider): + reply = self.run_doctor(assistant_provider=provider) + self.assertEqual(reply["agent"]["provider"], provider) + self.assertIn(binary, reply["programs"]) + class Devices(DikteTest): def test_a_machine_with_nothing_names_its_own_missing_program(self): diff --git a/tests/test_ui.py b/tests/test_ui.py index 0a06fb1..e97e2f9 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -16,7 +16,7 @@ from unittest import mock from PyQt6.QtCore import QPoint, QPointF, QRect, Qt from PyQt6.QtGui import QWheelEvent -from PyQt6.QtWidgets import QApplication, QMessageBox +from PyQt6.QtWidgets import QApplication, QComboBox, QMessageBox, QSpinBox, QWidget from dikte import audio from dikte import cleanup @@ -230,6 +230,51 @@ class Settings(DikteTest): QApplication.sendEvent(box, self.wheel()) self.assertNotEqual(box.currentIndex(), before) + def test_the_wheel_uses_remembered_focus_in_an_inactive_window(self): + # Keep the window hidden so no desktop activation policy can give it + # keyboard focus. Its remembered focus still selects the wheel target. + for widget_type in (QComboBox, QSpinBox): + with self.subTest(widget=widget_type.__name__): + window = QWidget() + self.addCleanup(window.deleteLater) + box = widget_type(window) + other = QComboBox(window) + if isinstance(box, QComboBox): + box.addItems(["first", "second", "third"]) + box.setCurrentIndex(1) + value = box.currentIndex + else: + box.setValue(5) + value = box.value + box.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + guard = settings_ui.WheelGuard(window) + box.installEventFilter(guard) + box.setFocus() + self.assertFalse(window.isActiveWindow()) + self.assertFalse(box.hasFocus()) + self.assertIs(window.focusWidget(), box) + before = value() + QApplication.sendEvent(box, self.wheel()) + self.assertNotEqual(value(), before) + other.setFocus() + self.assertIs(window.focusWidget(), other) + before = value() + rolled = self.wheel() + QApplication.sendEvent(box, rolled) + self.assertEqual(value(), before) + self.assertFalse(rolled.isAccepted()) + + def test_the_wheel_is_refused_when_another_widget_has_focus(self): + window = self.window(cfg.Config()) + box = window.ui_language + other = window.corner + other.setFocus() + before = box.currentIndex() + rolled = self.wheel() + QApplication.sendEvent(box, rolled) + self.assertEqual(box.currentIndex(), before) + self.assertFalse(rolled.isAccepted()) + def test_a_wrapped_label_keeps_the_room_its_lines_need(self): # The program path shares a row with a button, and a row is measured # before its width is known: the label has to claim the second line back