feat(tray): remove Start on Login and About from the tray menu (#905)

* feat(tray): remove Start on Login and About from the tray menu

Both entries already live in Settings: the autostart switch is in the
general page and About is a dedicated sidebar page, so the tray items
are redundant. View Logs stays.

Removes the Start on Login checkbox item along with its
_update_autostart_checkbox/_on_autostart_toggled wiring and the now
unused _add_menu_checkbox helper, plus the About item and
_on_about_clicked. _show_settings_page("about") stays for the Update
Available item. autostart_manager is untouched since Settings uses it.

Fixes #654

* fix(settings): sync autostart switch with the real desktop entry

The tray's removed _update_autostart_checkbox also reconciled
general.autostart with the actual login desktop entry at startup. Move
that check into the settings load so the Start on Login switch still
reflects reality when the entry changes outside the app.

---------

Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
This commit is contained in:
devin-ai-integration[bot]
2026-10-07 17:12:01 -07:00
committed by GitHub
co-authored by Jatin K Malik
parent 26c4f9052b
commit 6a6cc93919
4 changed files with 19 additions and 74 deletions
+10 -1
View File
@@ -7320,7 +7320,16 @@ class SettingsDialog(Gtk.Dialog):
text_injection_settings = self.config_manager.get_settings().get("text_injection", {})
history_settings = self.config_manager.get_settings().get("history", {})
autostart_enabled = general_settings.get("autostart", False)
# The login desktop entry can be added or removed outside the app; the
# real file state wins over the stored preference (previously synced by
# the tray menu's Start on Login item).
from . import autostart_manager
autostart_enabled = autostart_manager.is_autostart_enabled()
if autostart_enabled != general_settings.get("autostart", False):
self.config_manager.set("general", "autostart", autostart_enabled)
self.config_manager.save_settings()
start_minimized = ui_settings.get("start_minimized", False)
show_missing_tray_warning = ui_settings.get("show_missing_tray_warning", True)
show_overlay = ui_settings.get("show_overlay", True)
-63
View File
@@ -179,7 +179,6 @@ class TrayIndicator:
# settings toggle, resume) must keep honoring the fallback rather
# than reapplying a disable_internal_hotkey setting D-Bus cannot serve.
self._external_activation_unavailable = False
self._syncing_autostart_menu = False
self._history_menu_item = None
# Refresh the history submenu whenever the history changes. The change
@@ -584,12 +583,6 @@ class TrayIndicator:
self._refresh_history_menu()
self._add_menu_separator()
self._autostart_menu_item = self._add_menu_checkbox(
"Start on Login", self._on_autostart_toggled
)
self._update_autostart_checkbox()
self._add_menu_separator()
if self.dictation_pad is not None:
self._add_menu_item("Dictation Pad", self._on_dictation_pad_clicked)
self._add_menu_item("Settings", self._on_settings_clicked)
@@ -606,7 +599,6 @@ class TrayIndicator:
)
self._update_menu_item.set_no_show_all(True)
self._update_menu_item.hide()
self._add_menu_item("About", self._on_about_clicked)
self._add_menu_item("Quit", self._on_quit_clicked)
# Set the indicator menu
@@ -827,56 +819,6 @@ class TrayIndicator:
separator = Gtk.SeparatorMenuItem()
self.menu.append(separator)
def _add_menu_checkbox(self, label: str, callback: Callable) -> Gtk.CheckMenuItem:
"""
Add a checkbox menu item to the indicator menu.
Args:
label: The label for the menu item
callback: The callback function to call when the item is toggled
Returns:
The checkbox menu item
"""
item = Gtk.CheckMenuItem.new_with_label(label)
item.connect("toggled", callback)
self.menu.append(item)
return item
def _update_autostart_checkbox(self):
"""Update the autostart checkbox state based on current config."""
from . import autostart_manager
autostart_enabled = autostart_manager.is_autostart_enabled()
config_enabled = self.config_manager.get_bool("general", "autostart", False)
if config_enabled != autostart_enabled:
self.config_manager.set("general", "autostart", autostart_enabled)
self.config_manager.save_settings()
self._syncing_autostart_menu = True
self._autostart_menu_item.set_active(autostart_enabled)
self._syncing_autostart_menu = False
def _on_autostart_toggled(self, widget):
"""Handle toggle of the Start on Login menu item."""
if self._syncing_autostart_menu:
return
enabled = widget.get_active()
logger.info(f"Autostart toggled: {enabled}")
from . import autostart_manager
if autostart_manager.set_autostart(enabled):
self.config_manager.set("general", "autostart", enabled)
self.config_manager.save_settings()
status = "enabled" if enabled else "disabled"
logger.info(f"Autostart {status}")
else:
self._syncing_autostart_menu = True
widget.set_active(not enabled)
self._syncing_autostart_menu = False
def _on_recognition_state_changed(self, state: RecognitionState):
"""
Handle changes in the speech recognition state.
@@ -1552,11 +1494,6 @@ class TrayIndicator:
logger.debug("No changes needed - shortcut and mode unchanged")
return True
def _on_about_clicked(self, widget):
"""Handle click on the About menu item."""
logger.debug("About clicked")
self._show_settings_page("about")
def _get_auto_pause_config(self):
"""Return (enabled, apps, poll_interval_seconds) for AutoPauseMonitor."""
enabled = self.config_manager.get_bool("auto_pause", "enabled", False)
+9 -9
View File
@@ -280,8 +280,8 @@ class TestTrayIndicator(unittest.TestCase):
mock_dialog_instance.present_with_time.assert_called_once()
mock_dialog_instance.navigate_to_page.assert_not_called()
def test_about_reuses_open_settings_dialog(self):
"""About focuses the existing Settings window and switches to the About page."""
def test_update_available_reuses_open_settings_dialog(self):
"""Update Available focuses the existing Settings window on the About page."""
import vocalinux.ui.tray_indicator as tray_module
mock_dialog_instance = MagicMock()
@@ -289,7 +289,7 @@ class TestTrayIndicator(unittest.TestCase):
with patch.object(tray_module, "SettingsDialog", mock_dialog_class):
self.tray_indicator._on_settings_clicked(None)
self.tray_indicator._on_about_clicked(None)
self.tray_indicator._on_update_available_clicked(None)
mock_dialog_class.assert_called_once()
mock_dialog_instance.navigate_to_page.assert_called_once_with("about")
@@ -312,13 +312,13 @@ class TestTrayIndicator(unittest.TestCase):
second_dialog.show.assert_called_once()
first_dialog.present_with_time.assert_not_called()
def test_about_dialog(self):
"""Test About opens Settings focused on the About page."""
def test_update_available_opens_about_page(self):
"""Test Update Available opens Settings focused on the About page."""
with patch("vocalinux.ui.tray_indicator.SettingsDialog") as mock_dialog_class:
mock_dialog_instance = MagicMock()
mock_dialog_class.return_value = mock_dialog_instance
self.tray_indicator._on_about_clicked(None)
self.tray_indicator._on_update_available_clicked(None)
mock_dialog_class.assert_called_once()
kwargs = mock_dialog_class.call_args.kwargs
@@ -661,13 +661,13 @@ class TestTrayIndicator(unittest.TestCase):
self.tray_indicator.run()
mock_quit.assert_called_once()
def test_about_opens_settings_without_raising(self):
"""Test About menu item opens settings even if dialog construction is mocked."""
def test_update_available_opens_settings_without_raising(self):
"""Test Update Available opens settings even if dialog construction is mocked."""
with patch("vocalinux.ui.tray_indicator.SettingsDialog") as mock_dialog_class:
mock_dialog_instance = MagicMock()
mock_dialog_class.return_value = mock_dialog_instance
self.tray_indicator._on_about_clicked(None)
self.tray_indicator._on_update_available_clicked(None)
mock_dialog_instance.connect.assert_called()
mock_dialog_instance.show.assert_called_once()
-1
View File
@@ -112,7 +112,6 @@ class TestTrayIndicatorInitialization(unittest.TestCase):
assert indicator.text_injector == mock_text_injector
assert indicator.config_manager == mock_config_inst
assert indicator.shortcut_manager == mock_keyboard_inst
assert indicator._syncing_autostart_menu is False
# Verify that idle_add was called to initialize the indicator
mock_glib.idle_add.assert_called()