Skip to content

Commit 4d858cd

Browse files
committed
Merge remote-tracking branch 'origin/pr/61'
* origin/pr/61: Add tests for the recents feature Improve hover, add menu settings Fix icons and improve communication Fix typing errors and errors introduced by black Blackify qubes menu settings Add enabling/disabling recent apps/searches to Menu Settings Black-ify menu files Add Recently Used Apps to Search Add sorting to favorites page Pull request description: * blackify menu files * implement sorting for favorites * implement recently used apps / disabling and enabling this feature in menu settings fixes QubesOS/qubes-issues#8041
2 parents 2b447ac + 22f5995 commit 4d858cd

27 files changed

Lines changed: 1497 additions & 636 deletions

.gitlab-ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ checks:pylint:
1010
stage: checks
1111
before_script:
1212
- sudo dnf install -y python3-gobject gtk3 xorg-x11-server-Xvfb
13-
python3-pip python3-mypy python3-pyxdg gtk-layer-shell
13+
python3-pip python3-mypy python3-pyxdg gtk-layer-shell cairo-devel
1414
- pip3 install --quiet -r ci/requirements.txt
1515
- git clone https://github.com/QubesOS/qubes-core-admin-client ~/core-admin-client
1616
script:
@@ -25,7 +25,7 @@ checks:tests:
2525
- "PATH=$PATH:$HOME/.local/bin"
2626
- sudo dnf install -y python3-gobject gtk3 python3-pytest python3-pytest-asyncio
2727
python3-coverage xorg-x11-server-Xvfb python3-inotify sequoia-sqv
28-
python3-pip python3-pyxdg gtk-layer-shell
28+
python3-pip python3-pyxdg gtk-layer-shell cairo-devel
2929
- pip3 install --quiet -r ci/requirements.txt
3030
- git clone https://github.com/QubesOS/qubes-core-admin-client ~/core-admin-client
3131
- git clone https://github.com/QubesOS/qubes-desktop-linux-manager ~/desktop-linux-manager

.pylintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ notes=FIXME,FIX,XXX,TODO
8080
[FORMAT]
8181

8282
# Maximum number of characters on a single line.
83-
max-line-length=80
83+
max-line-length=88
8484

8585
# Maximum number of lines in a module
8686
max-module-lines=3000

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ install-icons:
2828
cp icons/qappmenu-top-right.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qappmenu-top-right.svg
2929
cp icons/qappmenu-bottom-left.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qappmenu-bottom-left.svg
3030
cp icons/qappmenu-bottom-right.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qappmenu-bottom-right.svg
31+
cp icons/qappmenu-az.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qappmenu-az.svg
32+
cp icons/qappmenu-za.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qappmenu-za.svg
33+
cp icons/qappmenu-qube-az.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qappmenu-qube-az.svg
34+
cp icons/qappmenu-qube-za.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/qappmenu-qube-za.svg
3135
cp icons/settings-*.svg $(DESTDIR)/usr/share/icons/hicolor/scalable/apps/
3236

3337
install-autostart:

icons/qappmenu-az.svg

Lines changed: 1 addition & 0 deletions
Loading

icons/qappmenu-qube-az.svg

Lines changed: 33 additions & 0 deletions
Loading

icons/qappmenu-qube-za.svg

Lines changed: 33 additions & 0 deletions
Loading

icons/qappmenu-za.svg

Lines changed: 1 addition & 0 deletions
Loading

qubes_menu/app_widgets.py

Lines changed: 60 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,26 @@
2626
from typing import Optional, List
2727
from functools import reduce
2828

29-
from .custom_widgets import (LimitedWidthLabel, SelfAwareMenu, HoverEventBox,
30-
FavoritesMenu)
29+
from .custom_widgets import (
30+
LimitedWidthLabel,
31+
SelfAwareMenu,
32+
HoverEventBox,
33+
FavoritesMenu,
34+
)
3135
from .desktop_file_manager import ApplicationInfo
3236
from .vm_manager import VMManager, VMEntry
3337
from .utils import load_icon, text_search, highlight_words, remove_from_feature
3438
from . import constants
3539

3640
import gi
37-
gi.require_version('Gtk', '3.0')
41+
42+
gi.require_version("Gtk", "3.0")
3843
from gi.repository import Gtk, Gdk
3944

4045

41-
logger = logging.getLogger('qubes-appmenu')
46+
logger = logging.getLogger("qubes-appmenu")
4247

43-
DISP_TEXT = 'new Disposable Qube from '
48+
DISP_TEXT = "new Disposable Qube from "
4449

4550

4651
class AppEntry(Gtk.ListBoxRow):
@@ -53,6 +58,7 @@ class AppEntry(Gtk.ListBoxRow):
5358
- supports running an application on click; after click signals to the
5459
complete menu it might need hiding
5560
"""
61+
5662
def __init__(self, app_info: ApplicationInfo, **properties):
5763
"""
5864
:param app_info: ApplicationInfo obj with data about related app file
@@ -61,23 +67,21 @@ def __init__(self, app_info: ApplicationInfo, **properties):
6167
super().__init__(**properties)
6268
self.app_info = app_info
6369
self.app_info.entries.append(self)
64-
self.vm_name = app_info.vm.name if app_info.vm else 'dom0'
70+
self.vm_name = app_info.vm.name if app_info.vm else "dom0"
6571

6672
self.menu = SelfAwareMenu()
6773

6874
self.event_box = HoverEventBox(focus_widget=self)
6975
self.add(self.event_box)
7076
self.event_box.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
71-
self.event_box.connect('button-press-event', self.show_menu)
77+
self.event_box.connect("button-press-event", self.show_menu)
7278

73-
self.drag_source_set(
74-
Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
79+
self.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
7580
self.drag_source_add_uri_targets()
7681
self.connect("drag-data-get", self._on_drag_data_get)
7782

7883
def _on_drag_data_get(self, _widget, _drag_context, data, _info, _time):
79-
data.set_uris(['file://' +
80-
urllib.parse.quote(str(self.app_info.file_path))])
84+
data.set_uris(["file://" + urllib.parse.quote(str(self.app_info.file_path))])
8185

8286
def show_menu(self, _widget, event):
8387
"""
@@ -100,13 +104,17 @@ def run_app(self, vm):
100104
# pylint: disable=consider-using-with
101105
command = self.app_info.get_command_for_vm(vm)
102106
subprocess.Popen(command, stdin=subprocess.DEVNULL)
107+
self.get_toplevel().get_application().emit(
108+
"app-started", self.app_info.file_path.name
109+
)
103110
self.get_toplevel().get_application().hide_menu()
104111

105112

106113
class BaseAppEntry(AppEntry):
107114
"""
108115
A 'normal' Application row, used by main applications menu and system tools.
109116
"""
117+
110118
def __init__(self, app_info: ApplicationInfo, **properties):
111119
"""
112120
:param app_info: ApplicationInfo obj with data about related app file
@@ -115,7 +123,7 @@ def __init__(self, app_info: ApplicationInfo, **properties):
115123
super().__init__(app_info, **properties)
116124
self.box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
117125
self.event_box.add(self.box)
118-
self.get_style_context().add_class('app_entry')
126+
self.get_style_context().add_class("app_entry")
119127
self.menu = FavoritesMenu(lambda: self.app_info)
120128

121129
self.icon = Gtk.Image()
@@ -137,25 +145,29 @@ def show_menu(self, widget, event):
137145
def update_contents(self):
138146
"""Update icon and app name."""
139147
self.icon.set_from_pixbuf(
140-
load_icon(self.app_info.app_icon, Gtk.IconSize.LARGE_TOOLBAR))
148+
load_icon(self.app_info.app_icon, Gtk.IconSize.LARGE_TOOLBAR)
149+
)
141150
self.label.set_label(self.app_info.app_name)
142151
self.show_all()
143152

144153

145154
class VMIcon(Gtk.Image):
146155
"""Helper class for displaying and auto-updating"""
156+
147157
def __init__(self, vm_entry: Optional[VMEntry]):
148158
super().__init__()
149159
self.vm_entry = vm_entry
150160
if self.vm_entry:
151161
self.vm_entry.entries.append(self)
152162
self.update_contents(update_label=True)
153163

154-
def update_contents(self,
155-
update_power_state=False,
156-
update_label=False,
157-
update_has_network=False,
158-
update_type=False):
164+
def update_contents(
165+
self,
166+
update_power_state=False,
167+
update_label=False,
168+
update_has_network=False,
169+
update_type=False,
170+
):
159171
# pylint: disable=unused-argument
160172
"""
161173
Update own contents (or related widgets, if applicable) based on state
@@ -168,19 +180,18 @@ def update_contents(self,
168180
:return:
169181
"""
170182
if update_label and self.vm_entry:
171-
vm_icon = load_icon(self.vm_entry.vm_icon_name,
172-
Gtk.IconSize.LARGE_TOOLBAR)
183+
vm_icon = load_icon(self.vm_entry.vm_icon_name, Gtk.IconSize.LARGE_TOOLBAR)
173184
self.set_from_pixbuf(vm_icon)
174185
self.show_all()
175186

176187

177188
class AppEntryWithVM(AppEntry):
178189
"""Application Gtk.ListBoxRow with VM description underneath; to be
179190
used in Search and Favorites."""
180-
def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager,
181-
**properties):
191+
192+
def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager, **properties):
182193
super().__init__(app_info, **properties)
183-
self.get_style_context().add_class('favorite_entry')
194+
self.get_style_context().add_class("favorite_entry")
184195
self.grid = Gtk.Grid()
185196
self.event_box.add(self.grid)
186197

@@ -192,8 +203,8 @@ def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager,
192203
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
193204
box.pack_start(self.vm_icon, False, False, 5)
194205
box.pack_start(self.vm_label, False, False, 5)
195-
self.vm_label.get_style_context().add_class('favorite_vm_name')
196-
self.app_label.get_style_context().add_class('favorite_app_name')
206+
self.vm_label.get_style_context().add_class("favorite_vm_name")
207+
self.app_label.get_style_context().add_class("favorite_app_name")
197208
self.app_label.set_halign(Gtk.Align.START)
198209

199210
self.grid.attach(self.app_icon, 0, 0, 1, 2)
@@ -210,8 +221,7 @@ def update_contents(self):
210221
self.app_icon.set_from_pixbuf(app_icon)
211222

212223
if self.app_info.disposable:
213-
self.vm_label.set_text(
214-
DISP_TEXT + str(self.app_info.vm))
224+
self.vm_label.set_text(DISP_TEXT + str(self.app_info.vm))
215225
elif self.app_info.vm:
216226
self.vm_label.set_text(str(self.app_info.vm))
217227
else:
@@ -228,11 +238,11 @@ class FavoritesAppEntry(AppEntryWithVM):
228238
constants.py, as a space-separated list containing a subset of menu-items
229239
feature.
230240
"""
231-
def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager,
232-
**properties):
241+
242+
def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager, **properties):
233243
super().__init__(app_info, vm_manager, **properties)
234-
self.remove_item = Gtk.MenuItem(label='Remove from favorites')
235-
self.remove_item.connect('activate', self._remove_from_favorites)
244+
self.remove_item = Gtk.MenuItem(label="Remove from favorites")
245+
self.remove_item.connect("activate", self._remove_from_favorites)
236246
self.menu.add(self.remove_item)
237247
self.menu.show_all()
238248

@@ -241,16 +251,17 @@ def _remove_from_favorites(self, *_args, **_kwargs):
241251
feature"""
242252
if not self.app_info.entry_name:
243253
return # there is nothing to remove
244-
vm = self.app_info.vm or self.app_info.qapp.domains[
245-
self.app_info.qapp.local_name]
246-
remove_from_feature(vm, constants.FAVORITES_FEATURE,
247-
self.app_info.entry_name)
254+
vm = (
255+
self.app_info.vm
256+
or self.app_info.qapp.domains[self.app_info.qapp.local_name]
257+
)
258+
remove_from_feature(vm, constants.FAVORITES_FEATURE, self.app_info.entry_name)
248259

249260

250261
class SearchAppEntry(AppEntryWithVM):
251262
"""Entry for apps listed on the Search tab."""
252-
def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager,
253-
**properties):
263+
264+
def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager, **properties):
254265

255266
super().__init__(app_info, vm_manager, **properties)
256267
self.menu = FavoritesMenu(lambda: self.app_info)
@@ -269,17 +280,21 @@ def __init__(self, app_info: ApplicationInfo, vm_manager: VMManager,
269280

270281
if self.app_info.vm:
271282
self.search_words.extend(
272-
self.app_info.vm.name.lower().replace('_', '-').split('-'))
283+
self.app_info.vm.name.lower().replace("_", "-").split("-")
284+
)
273285
else:
274-
self.search_words.append('dom0')
286+
self.search_words.append("dom0")
275287

276288
if self.app_info.disposable:
277289
self.search_words.extend(DISP_TEXT.lower().split())
278290

279291
if self.app_info.app_name:
280292
self.search_words.extend(
281-
self.app_info.app_name.lower().replace(
282-
'_', ' ').replace('-', ' ').split())
293+
self.app_info.app_name.lower()
294+
.replace("_", " ")
295+
.replace("-", " ")
296+
.split()
297+
)
283298

284299
if self.app_info.keywords:
285300
self.search_words.extend(k.lower() for k in self.app_info.keywords)
@@ -293,9 +308,10 @@ def find_text(self, search_words: List[str]):
293308
return self.last_search_result
294309

295310
if search_words:
296-
result = reduce(lambda x, y: x*y,
297-
[text_search(word, self.search_words)
298-
for word in search_words])
311+
result = reduce(
312+
lambda x, y: x * y,
313+
[text_search(word, self.search_words) for word in search_words],
314+
)
299315
else:
300316
result = 0
301317

0 commit comments

Comments
 (0)