From 9b1daec63fe4e4c11feb44df3198c1162cb286c8 Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat, 5 Mar 2022 21:32:39 +0100 Subject: [PATCH 1/3] Add pipfile; Replace pyinotify with watchdog; Fix pytest warnings --- Pipfile | 16 +++ Pipfile.lock | 154 +++++++++++++++++++++++++++++ pytest.ini | 2 + qubes_menu/desktop_file_manager.py | 87 ++++++++-------- qubes_menu/tests/conftest.py | 1 + qubes_menu/tests/test_vmmanager.py | 1 - 6 files changed, 216 insertions(+), 45 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 pytest.ini diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..3a621ff --- /dev/null +++ b/Pipfile @@ -0,0 +1,16 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +watchdog = "*" +pyxdg = "*" +pygobject = "*" + +[dev-packages] +pytest = "*" +pytest-asyncio = "*" + +[requires] +python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..d89e40f --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,154 @@ +{ + "_meta": { + "hash": { + "sha256": "f54ba1a756241ec60921cb372dd285170623c0662a74cb65005041f76c5ffdab" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.10" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "pycairo": { + "hashes": [ + "sha256:0d7a6754d410d911a46f00396bee4be96500ccd3d178e7e98aef1140e3dd67ae", + "sha256:1ee72b035b21a475e1ed648e26541b04e5d7e753d75ca79de8c583b25785531b", + "sha256:261c69850d4b2ec03346c9745bad2a835bb8124e4c6961b8ceac503d744eb3b3", + "sha256:5525da2d8de912750dd157752aa96f1f0a42a437c5625e85b14c936b5c6305ae", + "sha256:6db823a18e7be1eb2a29c28961f2f01e84d3b449f06be7338d05ac8f90592cd5", + "sha256:736ffc618e851601e861a630293e5c910ef016b83b2d035a336f83a367bf56ab", + "sha256:9a32e4a3574a104aa876c35d5e71485dfd6986b18d045534c6ec510c44d5d6a7", + "sha256:b605151cdd23cedb31855b8666371b6e26b80f02753a52c8b8023a916b1df812", + "sha256:c8c2bb933974d91c5d19e54b846d964de177e7bf33433bf34ac34c85f9b30e94", + "sha256:e800486b51fffeb11ed867b4f2220d446e2a60a81a73b7c377123e0cbb72f49d", + "sha256:f123d3818e30b77b7209d70a6dcfd5b4e34885f9fa539d92dd7ff3e4e2037213" + ], + "markers": "python_version >= '3.6' and python_version < '4'", + "version": "==1.20.1" + }, + "pygobject": { + "hashes": [ + "sha256:b9803991ec0b0b4175e81fee0ad46090fa7af438fe169348a9b18ae53447afcd" + ], + "index": "pypi", + "version": "==3.42.0" + }, + "pyxdg": { + "hashes": [ + "sha256:2d6701ab7c74bbab8caa6a95e0a0a129b1643cf6c298bf7c569adec06d0709a0", + "sha256:80bd93aae5ed82435f20462ea0208fb198d8eec262e831ee06ce9ddb6b91c5a5" + ], + "index": "pypi", + "version": "==0.27" + }, + "watchdog": { + "hashes": [ + "sha256:25fb5240b195d17de949588628fdf93032ebf163524ef08933db0ea1f99bd685", + "sha256:3386b367e950a11b0568062b70cc026c6f645428a698d33d39e013aaeda4cc04", + "sha256:3becdb380d8916c873ad512f1701f8a92ce79ec6978ffde92919fd18d41da7fb", + "sha256:4ae38bf8ba6f39d5b83f78661273216e7db5b00f08be7592062cb1fc8b8ba542", + "sha256:8047da932432aa32c515ec1447ea79ce578d0559362ca3605f8e9568f844e3c6", + "sha256:8f1c00aa35f504197561060ca4c21d3cc079ba29cf6dd2fe61024c70160c990b", + "sha256:922a69fa533cb0c793b483becaaa0845f655151e7256ec73630a1b2e9ebcb660", + "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3", + "sha256:a0f1c7edf116a12f7245be06120b1852275f9506a7d90227648b250755a03923", + "sha256:a36e75df6c767cbf46f61a91c70b3ba71811dfa0aca4a324d9407a06a8b7a2e7", + "sha256:aba5c812f8ee8a3ff3be51887ca2d55fb8e268439ed44110d3846e4229eb0e8b", + "sha256:ad6f1796e37db2223d2a3f302f586f74c72c630b48a9872c1e7ae8e92e0ab669", + "sha256:ae67501c95606072aafa865b6ed47343ac6484472a2f95490ba151f6347acfc2", + "sha256:b2fcf9402fde2672545b139694284dc3b665fd1be660d73eca6805197ef776a3", + "sha256:b52b88021b9541a60531142b0a451baca08d28b74a723d0c99b13c8c8d48d604", + "sha256:b7d336912853d7b77f9b2c24eeed6a5065d0a0cc0d3b6a5a45ad6d1d05fb8cd8", + "sha256:bd9ba4f332cf57b2c1f698be0728c020399ef3040577cde2939f2e045b39c1e5", + "sha256:be9be735f827820a06340dff2ddea1fb7234561fa5e6300a62fe7f54d40546a0", + "sha256:cca7741c0fcc765568350cb139e92b7f9f3c9a08c4f32591d18ab0a6ac9e71b6", + "sha256:d0d19fb2441947b58fbf91336638c2b9f4cc98e05e1045404d7a4cb7cddc7a65", + "sha256:e02794ac791662a5eafc6ffeaf9bcc149035a0e48eb0a9d40a8feb4622605a3d", + "sha256:e0f30db709c939cabf64a6dc5babb276e6d823fd84464ab916f9b9ba5623ca15", + "sha256:e92c2d33858c8f560671b448205a268096e17870dcf60a9bb3ac7bfbafb7f5f9" + ], + "index": "pypi", + "version": "==2.1.6" + } + }, + "develop": { + "attrs": { + "hashes": [ + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.4.0" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "markers": "python_version >= '3.6'", + "version": "==21.3" + }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.0" + }, + "py": { + "hashes": [ + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.11.0" + }, + "pyparsing": { + "hashes": [ + "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", + "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484" + ], + "markers": "python_version >= '3.6'", + "version": "==3.0.7" + }, + "pytest": { + "hashes": [ + "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db", + "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171" + ], + "index": "pypi", + "version": "==7.0.1" + }, + "pytest-asyncio": { + "hashes": [ + "sha256:20db0bdd3d7581b2e11f5858a5d9541f2db9cd8c5853786f94ad273d466c8c6d", + "sha256:fc8e4190f33fee7797cc7f1829f46a82c213f088af5d1bb5d4e454fe87e6cdc2" + ], + "index": "pypi", + "version": "==0.18.2" + }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.1" + } + } +} diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..2f6c8d1 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +asyncio_mode=strict diff --git a/qubes_menu/desktop_file_manager.py b/qubes_menu/desktop_file_manager.py index 35c1b75..26a93df 100644 --- a/qubes_menu/desktop_file_manager.py +++ b/qubes_menu/desktop_file_manager.py @@ -20,11 +20,13 @@ """ Helper class that manages all events related to .desktop files. """ -import pyinotify import logging import asyncio import os import shlex + +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler import xdg.DesktopEntry import xdg.BaseDirectory import xdg.Menu @@ -125,6 +127,39 @@ def is_qubes_specific(self): return 'X-Qubes-VM' in self.categories + +class FileChangeHandler(FileSystemEventHandler): + def __init__(self, manager: 'DesktopFileManager'): + self.manager = manager + super().__init__() + + def try_load(self, filename): + try: + self.manager.load_file(filename) + except FileNotFoundError: + self.manager.remove_file(filename) + + def on_created(self, event): + """On file create, attempt to load it. This can lead to spurious + warnings due to 0-byte files being loaded, but in some cases + is necessary to correctly process files.""" + self.try_load(event.src_path) + + def on_deleted(self, event): + """ + On file delete, remove the tile and all its children menu entries + """ + self.manager.remove_file(event.src_path) + + def on_modified(self, event): + """On modify, simply attempt to load the file again.""" + self.try_load(event.src_path) + + def on_moved(self, event): + self.manager.remove_file(event.src_path) + self.try_load(event.dest_path) + + class DesktopFileManager: """ Class that loads, caches and observes changes in .desktop files. @@ -133,40 +168,10 @@ class DesktopFileManager: Path(xdg.BaseDirectory.xdg_data_home) / 'applications', Path('/usr/share/applications')] - # pylint: disable=invalid-name - class EventProcessor(pyinotify.ProcessEvent): - """pyinotify helper class""" - def __init__(self, parent): - self.parent = parent - super().__init__() - - def process_IN_CREATE(self, event): - """On file create, attempt to load it. This can lead to spurious - warnings due to 0-byte files being loaded, but in some cases - is necessary to correctly process files.""" - try: - self.parent.load_file(event.pathname) - except FileNotFoundError: - self.parent.remove_file(event.pathname) - - def process_IN_DELETE(self, event): - """ - On file delete, remove the tile and all its children menu entries - """ - self.parent.remove_file(event.pathname) - - def process_IN_MODIFY(self, event): - """On modify, simply attempt to laod the file again.""" - try: - self.parent.load_file(event.pathname) - except FileNotFoundError: - self.parent.remove_file(event.pathname) - def __init__(self, qapp): self.qapp = qapp self.watch_manager = None - self.notifier = None - self.watches = [] + self.observer = None self._callbacks: List[Callable] = [] # directories used by Qubes menu tools, not necessarily all possible @@ -273,18 +278,12 @@ def _initialize_watchers(self): """ Initialize all watcher entities. """ - self.watch_manager = pyinotify.WatchManager() + event_handler = FileChangeHandler(self) + observer = Observer() - # pylint: disable=no-member - mask = pyinotify.IN_CREATE | pyinotify.IN_DELETE | pyinotify.IN_MODIFY - - loop = asyncio.get_event_loop() - - self.notifier = pyinotify.AsyncioNotifier( - self.watch_manager, loop, - default_proc_fun=DesktopFileManager.EventProcessor(self)) + self.observer = observer for path in self.desktop_dirs: - self.watches.append( - self.watch_manager.add_watch( - str(path), mask, rec=True, auto_add=True)) + observer.schedule(event_handler, str(path), recursive=True) + + observer.start() diff --git a/qubes_menu/tests/conftest.py b/qubes_menu/tests/conftest.py index 6eaed85..2afc15b 100644 --- a/qubes_menu/tests/conftest.py +++ b/qubes_menu/tests/conftest.py @@ -22,6 +22,7 @@ import unittest.mock from qubesadmin.tests import TestVM, TestVMCollection +TestVM.__test__ = False # pytest, this is not a test suite class TestApp(object): def __init__(self): diff --git a/qubes_menu/tests/test_vmmanager.py b/qubes_menu/tests/test_vmmanager.py index 4b313a6..1a2fa9d 100644 --- a/qubes_menu/tests/test_vmmanager.py +++ b/qubes_menu/tests/test_vmmanager.py @@ -27,7 +27,6 @@ from ..application_page import VMTypeToggle -@pytest.mark.asyncio def test_vm_manager(test_qapp): dispatcher = qubesadmin.events.EventsDispatcher(test_qapp) vm_manager = VMManager(test_qapp, dispatcher) From 709a0b968489c1af0a635d1323d489f97b71a83c Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat, 5 Mar 2022 22:10:53 +0100 Subject: [PATCH 2/3] Add test env var --- Pipfile | 8 +- Pipfile.lock | 173 +++++++++++++++++++++++++++++++++-- README.md | 8 ++ qubes_menu/__main__.py | 4 + qubes_menu/appmenu.py | 16 ++-- qubes_menu/tests/conftest.py | 34 +------ qubes_menu/tests/mock_app.py | 31 +++++++ qubes_menu/utils.py | 32 +++++-- 8 files changed, 250 insertions(+), 56 deletions(-) create mode 100644 qubes_menu/__main__.py create mode 100644 qubes_menu/tests/mock_app.py diff --git a/Pipfile b/Pipfile index 3a621ff..b6ceaa2 100644 --- a/Pipfile +++ b/Pipfile @@ -7,10 +7,14 @@ name = "pypi" watchdog = "*" pyxdg = "*" pygobject = "*" +gbulb = "*" +qubesadmin = "*" # install this manually, or use the following +# qubesadmin = {git = "https://github.com/QubesOS/qubes-core-admin-client"} [dev-packages] pytest = "*" -pytest-asyncio = "*" +# pytest-asyncio = "*" +pylint = "*" [requires] -python_version = "3.10" +python_version = ">=3.8" diff --git a/Pipfile.lock b/Pipfile.lock index d89e40f..dc3968f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "f54ba1a756241ec60921cb372dd285170623c0662a74cb65005041f76c5ffdab" + "sha256": "0b2113b786cc4071634cca1d83683c6cd7e728bb1bf5a1a4af1865035c6bc707" }, "pipfile-spec": 6, "requires": { - "python_version": "3.10" + "python_version": ">=3.8" }, "sources": [ { @@ -16,6 +16,14 @@ ] }, "default": { + "gbulb": { + "hashes": [ + "sha256:05128081beaba915de8ab0245ace86b8cb27c701df016e2134dccf3ad7e170b6", + "sha256:da003c5b17d3a2ba15c7255bb174defaa0f6b77e8b23f229685eb2714ceaeeec" + ], + "index": "pypi", + "version": "==0.6.3" + }, "pycairo": { "hashes": [ "sha256:0d7a6754d410d911a46f00396bee4be96500ccd3d178e7e98aef1140e3dd67ae", @@ -48,6 +56,10 @@ "index": "pypi", "version": "==0.27" }, + "qubesadmin": { + "git": "https://github.com/locriacyber/qubes-core-admin-client", + "ref": "0f148d4071e6663d7c8166ce289fde53baff4626" + }, "watchdog": { "hashes": [ "sha256:25fb5240b195d17de949588628fdf93032ebf163524ef08933db0ea1f99bd685", @@ -79,6 +91,14 @@ } }, "develop": { + "astroid": { + "hashes": [ + "sha256:1efdf4e867d4d8ba4a9f6cf9ce07cd182c4c41de77f23814feb27ca93ca9d877", + "sha256:506daabe5edffb7e696ad82483ad0228245a9742ed7d2d8c9cdb31537decf9f6" + ], + "markers": "python_full_version >= '3.6.2'", + "version": "==2.9.3" + }, "attrs": { "hashes": [ "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", @@ -94,6 +114,64 @@ ], "version": "==1.1.1" }, + "isort": { + "hashes": [ + "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", + "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" + ], + "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", + "version": "==5.10.1" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7", + "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a", + "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c", + "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc", + "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f", + "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09", + "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442", + "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e", + "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029", + "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61", + "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb", + "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0", + "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35", + "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42", + "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1", + "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad", + "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443", + "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd", + "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9", + "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148", + "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38", + "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55", + "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36", + "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a", + "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b", + "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44", + "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6", + "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69", + "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4", + "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84", + "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de", + "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28", + "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c", + "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1", + "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8", + "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b", + "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb" + ], + "markers": "python_version >= '3.6'", + "version": "==1.7.1" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, "packaging": { "hashes": [ "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", @@ -102,6 +180,14 @@ "markers": "python_version >= '3.6'", "version": "==21.3" }, + "platformdirs": { + "hashes": [ + "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d", + "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227" + ], + "markers": "python_version >= '3.7'", + "version": "==2.5.1" + }, "pluggy": { "hashes": [ "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", @@ -118,6 +204,14 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==1.11.0" }, + "pylint": { + "hashes": [ + "sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9", + "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74" + ], + "index": "pypi", + "version": "==2.12.2" + }, "pyparsing": { "hashes": [ "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", @@ -134,13 +228,21 @@ "index": "pypi", "version": "==7.0.1" }, - "pytest-asyncio": { + "setuptools": { "hashes": [ - "sha256:20db0bdd3d7581b2e11f5858a5d9541f2db9cd8c5853786f94ad273d466c8c6d", - "sha256:fc8e4190f33fee7797cc7f1829f46a82c213f088af5d1bb5d4e454fe87e6cdc2" + "sha256:2347b2b432c891a863acadca2da9ac101eae6169b1d3dfee2ec605ecd50dbfe5", + "sha256:e4f30b9f84e5ab3decf945113119649fec09c1fc3507c6ebffec75646c56e62b" ], - "index": "pypi", - "version": "==0.18.2" + "markers": "python_version >= '3.7'", + "version": "==60.9.3" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.10.2" }, "tomli": { "hashes": [ @@ -149,6 +251,63 @@ ], "markers": "python_version >= '3.7'", "version": "==2.0.1" + }, + "wrapt": { + "hashes": [ + "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179", + "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096", + "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374", + "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df", + "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185", + "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785", + "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7", + "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909", + "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918", + "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33", + "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068", + "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829", + "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af", + "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79", + "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce", + "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc", + "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36", + "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade", + "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca", + "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32", + "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125", + "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e", + "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709", + "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f", + "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b", + "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb", + "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb", + "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489", + "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640", + "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb", + "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851", + "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d", + "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44", + "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13", + "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2", + "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb", + "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b", + "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9", + "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755", + "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c", + "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a", + "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf", + "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3", + "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229", + "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e", + "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de", + "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554", + "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10", + "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80", + "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056", + "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.13.3" } } } diff --git a/README.md b/README.md index 14ecf1f..c712777 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,14 @@ position or via executing `qubes-app-menu` with desired params in CLI. ![](readme_img/menu_howto.png) +## How to test +``` +pipenv shell +QUBES_MENU_TEST=1 python qubes_menu +``` + +You also need to install `qubesadmin` from https://github.com/QubesOS/qubes-core-admin-client. Run `python setup.py` in the dowloaded repo in pipenv shell to install. + ## Technical details ### New features diff --git a/qubes_menu/__main__.py b/qubes_menu/__main__.py new file mode 100644 index 0000000..f9b6c28 --- /dev/null +++ b/qubes_menu/__main__.py @@ -0,0 +1,4 @@ +from .appmenu import main + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/qubes_menu/appmenu.py b/qubes_menu/appmenu.py index 6a5b0b5..39e7006 100644 --- a/qubes_menu/appmenu.py +++ b/qubes_menu/appmenu.py @@ -264,23 +264,23 @@ def exit_app(self): task.cancel() +from .tests.mock_app import new_mock_qapp +import os + + def main(): """ Start the menu app """ + qapp = qubesadmin.Qubes() + if "QUBES_MENU_TEST" in os.environ: + qapp = new_mock_qapp(qapp) + qapp.domains[qapp.local_name] = qapp.domains['dom0'] dispatcher = qubesadmin.events.EventsDispatcher(qapp) app = AppMenu(qapp, dispatcher) app.run(sys.argv) - if f'--{constants.RESTART_PARAM_LONG}' in sys.argv or \ - f'-{constants.RESTART_PARAM_SHORT}' in sys.argv: - sys.argv = [x for x in sys.argv if x not in - (f'--{constants.RESTART_PARAM_LONG}', - f'-{constants.RESTART_PARAM_SHORT}')] - app = AppMenu(qapp, dispatcher) - app.run(sys.argv) - if __name__ == '__main__': sys.exit(main()) diff --git a/qubes_menu/tests/conftest.py b/qubes_menu/tests/conftest.py index 2afc15b..dac8c4e 100644 --- a/qubes_menu/tests/conftest.py +++ b/qubes_menu/tests/conftest.py @@ -21,10 +21,13 @@ import pytest import unittest.mock from qubesadmin.tests import TestVM, TestVMCollection +from .mock_app import new_mock_qapp + TestVM.__test__ = False # pytest, this is not a test suite -class TestApp(object): + +class TestApp: def __init__(self): self.domains = TestVMCollection( [ @@ -39,31 +42,4 @@ def _invalidate_cache(self, *_args, **_kwargs): @pytest.fixture def test_qapp(): - app = TestApp() - app.domains = TestVMCollection( - [ - ('dom0', TestVM('dom0', klass='AdminVM', label='black', - icon='adminvm-black', features={})), - ('test-vm', - TestVM('test-vm', klass='AppVM', label='blue', icon='appvm-blue', - netvm=TestVM('sys-firewall'), template=TestVM('template'), - features={})), - ('sys-firewall', - TestVM('sys-firewall', klass='DisposableVM', label='green', - icon='servicevm-green', netvm=TestVM('sys-net'), - template=TestVM('template'), features={})), - ('sys-net', - TestVM('sys-net', klass='StandaloneVM', label='red', - icon='servicevm-red', provides_network=True, - template=TestVM('template'), features={'servicevm': 1})), - ('template', - TestVM('template', klass='TemplateVM', label='red', - icon='templatevm-red', features={})), - ('template-dvm', - TestVM('template-dvm', klass='AppVM', label='red', - icon='templatevm-red', template_for_dispvms=True, - netvm=TestVM('sys-net'), template=TestVM('template'), - features={})), - ] - ) - return app + return new_mock_qapp(TestApp()) \ No newline at end of file diff --git a/qubes_menu/tests/mock_app.py b/qubes_menu/tests/mock_app.py new file mode 100644 index 0000000..b0c50cd --- /dev/null +++ b/qubes_menu/tests/mock_app.py @@ -0,0 +1,31 @@ +from qubesadmin.tests import TestVM, TestVMCollection + + +def new_mock_qapp(app): + app.domains = TestVMCollection( + [ + ('dom0', TestVM('dom0', klass='AdminVM', label='black', + icon='adminvm-black', features={})), + ('test-vm', + TestVM('test-vm', klass='AppVM', label='blue', icon='appvm-blue', + netvm=TestVM('sys-firewall'), template=TestVM('template'), + features={})), + ('sys-firewall', + TestVM('sys-firewall', klass='DisposableVM', label='green', + icon='servicevm-green', netvm=TestVM('sys-net'), + template=TestVM('template'), features={})), + ('sys-net', + TestVM('sys-net', klass='StandaloneVM', label='red', + icon='servicevm-red', provides_network=True, + template=TestVM('template'), features={'servicevm': 1})), + ('template', + TestVM('template', klass='TemplateVM', label='red', + icon='templatevm-red', features={})), + ('template-dvm', + TestVM('template-dvm', klass='AppVM', label='red', + icon='templatevm-red', template_for_dispvms=True, + netvm=TestVM('sys-net'), template=TestVM('template'), + features={})), + ] + ) + return app diff --git a/qubes_menu/utils.py b/qubes_menu/utils.py index 911411f..07a8a51 100644 --- a/qubes_menu/utils.py +++ b/qubes_menu/utils.py @@ -20,7 +20,7 @@ """ Miscellaneous Qubes Menu utility functions. """ -import gi +import os, gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GdkPixbuf, GLib @@ -33,19 +33,31 @@ def load_icon(icon_name, size: Gtk.IconSize = Gtk.IconSize.LARGE_TOOLBAR): """ _, width, height = Gtk.icon_size_lookup(size) try: + # icon name is a path return GdkPixbuf.Pixbuf.new_from_file_at_size(icon_name, width, height) - except (GLib.Error, TypeError): + except (TypeError, GLib.Error): + pass + + try: + # icon name is symbol + image: GdkPixbuf.Pixbuf = Gtk.IconTheme.get_default().load_icon( + icon_name, width, 0) + return image + except (TypeError, GLib.Error): + pass + + if "QUBES_MENU_TEST" in os.environ: try: # icon name is a path - image: GdkPixbuf.Pixbuf = Gtk.IconTheme.get_default().load_icon( - icon_name, width, 0) - return image + return GdkPixbuf.Pixbuf.new_from_file_at_size(os.path.join("./icons", icon_name + ".svg"), width, height) except (TypeError, GLib.Error): - # icon not found in any way - pixbuf: GdkPixbuf.Pixbuf = GdkPixbuf.Pixbuf.new( - GdkPixbuf.Colorspace.RGB, True, 8, width, height) - pixbuf.fill(0x000) - return pixbuf + pass + print(icon_name) + # icon not found in any way + pixbuf: GdkPixbuf.Pixbuf = GdkPixbuf.Pixbuf.new( + GdkPixbuf.Colorspace.RGB, True, 8, width, height) + pixbuf.fill(0x000) + return pixbuf def show_error(title, text): From 503690fc732d408c9f69c983ddf04e08839ec749 Mon Sep 17 00:00:00 2001 From: Locria Cyber <74560659+locriacyber@users.noreply.github.com> Date: Sat, 5 Mar 2022 22:17:41 +0100 Subject: [PATCH 3/3] Fix placeholder icon --- qubes_menu/utils.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/qubes_menu/utils.py b/qubes_menu/utils.py index 07a8a51..8d8d38d 100644 --- a/qubes_menu/utils.py +++ b/qubes_menu/utils.py @@ -38,6 +38,13 @@ def load_icon(icon_name, size: Gtk.IconSize = Gtk.IconSize.LARGE_TOOLBAR): except (TypeError, GLib.Error): pass + if "QUBES_MENU_TEST" in os.environ: + try: + # icon name is a path + return GdkPixbuf.Pixbuf.new_from_file_at_size(os.path.join("./icons", icon_name + ".svg"), width, height) + except (TypeError, GLib.Error): + pass + try: # icon name is symbol image: GdkPixbuf.Pixbuf = Gtk.IconTheme.get_default().load_icon( @@ -46,17 +53,11 @@ def load_icon(icon_name, size: Gtk.IconSize = Gtk.IconSize.LARGE_TOOLBAR): except (TypeError, GLib.Error): pass - if "QUBES_MENU_TEST" in os.environ: - try: - # icon name is a path - return GdkPixbuf.Pixbuf.new_from_file_at_size(os.path.join("./icons", icon_name + ".svg"), width, height) - except (TypeError, GLib.Error): - pass print(icon_name) # icon not found in any way pixbuf: GdkPixbuf.Pixbuf = GdkPixbuf.Pixbuf.new( GdkPixbuf.Colorspace.RGB, True, 8, width, height) - pixbuf.fill(0x000) + pixbuf.fill(0xff00ffff) # magenta return pixbuf