From 1d1dee7f6e980c80321ad14224e91704c51124cb Mon Sep 17 00:00:00 2001 From: Luna Date: Wed, 22 Jul 2026 04:35:37 -0700 Subject: [PATCH] feat(packaging): add desktop launcher entries for both GUI dashboards The tray applet shipped with a .desktop file but the GUI dashboards had none, so desktop users had no menu entry for them. Unlike the tray entry, these carry no autostart key: a dashboard window should open when the operator asks for it, not at login. Tests assert the Exec names match the pyproject console scripts and that neither entry can regain an autostart key. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JX86xeoBJVBb16qHkDf53K --- docs/PACKAGING.md | 7 ++++++ packaging/enodia-sentinel-gui-qt.desktop | 11 +++++++++ packaging/enodia-sentinel-gui.desktop | 11 +++++++++ tests/test_packaging.py | 29 ++++++++++++++++++++++++ 4 files changed, 58 insertions(+) create mode 100644 packaging/enodia-sentinel-gui-qt.desktop create mode 100644 packaging/enodia-sentinel-gui.desktop diff --git a/docs/PACKAGING.md b/docs/PACKAGING.md index d588f28..05942b6 100644 --- a/docs/PACKAGING.md +++ b/docs/PACKAGING.md @@ -215,6 +215,13 @@ Notes: `gui/model.py` stays import-clean so headless builders can run the test suite without a display server or `PySide6` installed. +`packaging/enodia-sentinel-gui.desktop` and +`packaging/enodia-sentinel-gui-qt.desktop` are application-menu launchers. +Unlike `enodia-sentinel-tray.desktop`, they carry no autostart key — a +dashboard window should open when the operator asks for it, not at login. +Desktop packagers may install them to `/usr/share/applications/`; source +installs can copy them to `~/.local/share/applications/`. + ## Terminal TUI and Completion The terminal dashboard is stdlib-only and available through diff --git a/packaging/enodia-sentinel-gui-qt.desktop b/packaging/enodia-sentinel-gui-qt.desktop new file mode 100644 index 0000000..ce41e5d --- /dev/null +++ b/packaging/enodia-sentinel-gui-qt.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Application +Name=Enodia Sentinel Dashboard (Qt) +GenericName=Security Dashboard +Comment=Read-only Qt desktop dashboard for the Enodia Sentinel security agent +Exec=enodia-sentinel-gui-qt +Icon=security-high +Terminal=false +Categories=System;Security;Monitor; +Keywords=security;ids;ips;edr;intrusion;monitor; +StartupNotify=true diff --git a/packaging/enodia-sentinel-gui.desktop b/packaging/enodia-sentinel-gui.desktop new file mode 100644 index 0000000..58b1c60 --- /dev/null +++ b/packaging/enodia-sentinel-gui.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Application +Name=Enodia Sentinel Dashboard +GenericName=Security Dashboard +Comment=Read-only desktop dashboard for the Enodia Sentinel security agent +Exec=enodia-sentinel-gui +Icon=security-high +Terminal=false +Categories=System;Security;Monitor; +Keywords=security;ids;ips;edr;intrusion;monitor; +StartupNotify=true diff --git a/tests/test_packaging.py b/tests/test_packaging.py index 0944a8a..7bea07b 100644 --- a/tests/test_packaging.py +++ b/tests/test_packaging.py @@ -166,5 +166,34 @@ class TestTrayDesktopEntry(unittest.TestCase): self.assertRegex(text, r"(?m)^Name=.*Sentinel") +class TestGuiDesktopEntries(unittest.TestCase): + ENTRIES = { + "packaging/enodia-sentinel-gui.desktop": "enodia-sentinel-gui", + "packaging/enodia-sentinel-gui-qt.desktop": "enodia-sentinel-gui-qt", + } + + def test_desktop_files_launch_gui_entry_points(self): + for path, exec_name in self.ENTRIES.items(): + with self.subTest(path=path): + text = _read(path) + self.assertIn("[Desktop Entry]", text) + self.assertIn("Type=Application", text) + self.assertRegex(text, rf"(?m)^Exec={exec_name}$") + self.assertRegex(text, r"(?m)^Name=.*Sentinel") + + def test_gui_entry_points_match_pyproject_scripts(self): + scripts = tomllib.loads(_read("pyproject.toml"))["project"]["scripts"] + for exec_name in self.ENTRIES.values(): + with self.subTest(script=exec_name): + self.assertIn(exec_name, scripts) + + def test_gui_entries_are_launchers_not_autostart(self): + # The tray applet is opt-in autostart; the dashboards are menu + # launchers and must never start themselves at login. + for path in self.ENTRIES: + with self.subTest(path=path): + self.assertNotIn("Autostart", _read(path)) + + if __name__ == "__main__": unittest.main()