Skip to content

Commit 64d89d3

Browse files
committed
Add Icons to Qubes VM Settings Apps tab
The current patch adds regular tinted icons. Option for fade in/out to regular images (not tinted) icons on mouse hover is on TODO list. fixes: QubesOS/qubes-issues#9829
1 parent f2d0a17 commit 64d89d3

File tree

4 files changed

+101
-35
lines changed

4 files changed

+101
-35
lines changed

.gitlab-ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ checks:pylint:
44
- pip3 install --quiet -r ci/requirements.txt
55
- git clone https://github.com/QubesOS/qubes-core-admin-client ~/core-admin-client
66
- (cd ~/core-admin-client;python3 setup.py egg_info)
7+
- git clone https://github.com/QubesOS/qubes-linux-utils ~/linux-utils
8+
- (cd ~/linux-utils/imgconverter;sudo python3 setup.py install)
79
script:
810
- PYTHONPATH=~/core-admin-client python3 -m pylint qubesmanager
911
stage: checks
@@ -27,6 +29,8 @@ checks:tests:
2729
- pip3 install --quiet -r ci/requirements.txt
2830
- git clone https://github.com/QubesOS/qubes-core-admin-client ~/core-admin-client
2931
- (cd ~/core-admin-client;python3 setup.py egg_info)
32+
- git clone https://github.com/QubesOS/qubes-linux-utils ~/linux-utils
33+
- (cd ~/linux-utils/imgconverter;sudo python3 setup.py install)
3034
script:
3135
- make ui
3236
- make res

ci/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ pylint
99
sphinx
1010
PyYAML
1111
qasync
12+
Pillow
13+
numpy

qubesmanager/appmenu_select.py

Lines changed: 87 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
#
1919

2020
import subprocess
21-
from PyQt6 import QtWidgets, QtCore # pylint: disable=import-error
21+
from PyQt6 import QtWidgets, QtCore, QtGui # pylint: disable=import-error
2222
from qubesadmin import exc
23+
from qubesmanager.utils import tint_qimage
24+
from os import path
2325

24-
# TODO description in tooltip
25-
# TODO icon
2626
# pylint: disable=too-few-public-methods
27+
28+
2729
class AppListWidgetItem(QtWidgets.QListWidgetItem):
2830
def __init__(self, name, ident, tooltip=None, parent=None):
2931
super().__init__(name, parent)
@@ -39,38 +41,48 @@ def __init__(self, name, ident, tooltip=None, parent=None):
3941

4042
@classmethod
4143
def from_line(cls, line):
42-
ident, name, comment = line.split('|', maxsplit=3)
44+
ident, name, comment = line.split("|", maxsplit=3)
4345
return cls(name=name, ident=ident, tooltip=comment)
4446

4547
@classmethod
4648
def from_ident(cls, ident):
47-
name = 'Application missing in template! ({})'.format(ident)
48-
comment = 'The listed application was available at some point to ' \
49-
'this qube, but not any more. The most likely cause is ' \
50-
'template change. Install the application in the template ' \
51-
'if you want to restore it.'
49+
name = "Application missing in template! ({})".format(ident)
50+
comment = (
51+
"The listed application was available at some point to "
52+
"this qube, but not any more. The most likely cause is "
53+
"template change. Install the application in the template "
54+
"if you want to restore it."
55+
)
5256
return cls(name=name, ident=ident, tooltip=comment)
5357

5458

5559
class AppmenuSelectManager:
5660
def __init__(self, vm, apps_multiselect):
5761
self.vm = vm
58-
self.app_list = apps_multiselect # this is a multiselect wiget
62+
self.app_list = apps_multiselect # this is a multiselect wiget
5963
self.whitelisted = None
6064
self.has_missing = False
6165
self.fill_apps_list(template=None)
6266

6367
def fill_apps_list(self, template=None):
6468
try:
65-
self.whitelisted = [line for line in subprocess.check_output(
66-
['qvm-appmenus', '--get-whitelist', self.vm.name]
67-
).decode().strip().split('\n') if line]
69+
self.whitelisted = [
70+
line
71+
for line in subprocess.check_output(
72+
["qvm-appmenus", "--get-whitelist", self.vm.name]
73+
)
74+
.decode()
75+
.strip()
76+
.split("\n")
77+
if line
78+
]
6879
except exc.QubesException:
6980
self.whitelisted = []
7081

7182
currently_selected = [
7283
self.app_list.selected_list.item(i).whatsThis()
73-
for i in range(self.app_list.selected_list.count())]
84+
for i in range(self.app_list.selected_list.count())
85+
]
7486

7587
whitelist = set(self.whitelisted + currently_selected)
7688

@@ -81,18 +93,61 @@ def fill_apps_list(self, template=None):
8193

8294
self.app_list.clear()
8395

84-
command = ['qvm-appmenus', '--get-available',
85-
'--i-understand-format-is-unstable', '--file-field',
86-
'Comment']
96+
command = [
97+
"qvm-appmenus",
98+
"--get-available",
99+
"--i-understand-format-is-unstable",
100+
"--file-field",
101+
"Comment",
102+
"--file-field",
103+
"Icon",
104+
]
87105
if template:
88-
command.extend(['--template', template.name])
106+
command.extend(["--template", template.name])
89107
command.append(self.vm.name)
90108

109+
if not hasattr(self.vm, "template"):
110+
# TemplateVMs and StandaloneVMs
111+
main_template = self.vm.name
112+
elif not hasattr(self.vm.template, "template"):
113+
# AppVMs
114+
main_template = self.vm.template.name
115+
else:
116+
# DispVMs
117+
main_template = self.vm.template.template.name
118+
119+
template_icons_path = path.join(
120+
path.expanduser("~"),
121+
".local",
122+
"share",
123+
"qubes-appmenus",
124+
f"{main_template}",
125+
"apps.tempicons",
126+
)
127+
91128
try:
92-
available_appmenus = [
93-
AppListWidgetItem.from_line(line)
94-
for line in subprocess.check_output(
95-
command).decode().splitlines()]
129+
available_appmenus = []
130+
for line in subprocess.check_output(command).decode().splitlines():
131+
ident, name, comment, icon_path = line.split("|", maxsplit=4)
132+
app_item = AppListWidgetItem.from_line(
133+
"|".join([ident, name, comment])
134+
)
135+
icon_path = icon_path.replace(
136+
"%VMDIR%/apps.icons", template_icons_path
137+
)
138+
if path.exists(icon_path):
139+
icon = QtGui.QIcon(icon_path)
140+
qpixmap = icon.pixmap(QtCore.QSize(512, 512))
141+
qimage = QtGui.QImage(qpixmap)
142+
qimage = tint_qimage(qimage, self.vm.label.color)
143+
qpixmap = QtGui.QPixmap(qimage)
144+
icon = QtGui.QIcon(qpixmap)
145+
else:
146+
# for `qubes-start.desktop` & missing icons
147+
icon = QtGui.QIcon.fromTheme(self.vm.icon)
148+
app_item.setIcon(icon)
149+
available_appmenus.append(app_item)
150+
96151
except exc.QubesException:
97152
available_appmenus = []
98153

@@ -113,16 +168,21 @@ def fill_apps_list(self, template=None):
113168
self.app_list.selected_list.sortItems()
114169

115170
def save_appmenu_select_changes(self):
116-
new_whitelisted = [self.app_list.selected_list.item(i).whatsThis()
117-
for i in range(self.app_list.selected_list.count())]
171+
new_whitelisted = [
172+
self.app_list.selected_list.item(i).whatsThis()
173+
for i in range(self.app_list.selected_list.count())
174+
]
118175

119176
if set(new_whitelisted) == set(self.whitelisted):
120177
return False
121178

122179
try:
123-
self.vm.features['menu-items'] = " ".join(new_whitelisted)
180+
self.vm.features["menu-items"] = " ".join(new_whitelisted)
124181
except exc.QubesException as ex:
125-
raise RuntimeError(QtCore.QCoreApplication.translate(
126-
"exception", 'Failed to set menu items')) from ex
182+
raise RuntimeError(
183+
QtCore.QCoreApplication.translate(
184+
"exception", "Failed to set menu items"
185+
)
186+
) from ex
127187

128188
return True

qubesmanager/tests/test_vm_settings.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,16 @@ def mock_subprocess_complex(command):
4848
vm_name = command[-1]
4949
if command[1] == '--get-available':
5050
if vm_name == 'test-vm-set':
51-
return (b'test.desktop|Test App|\n'
52-
b'test2.desktop|Test2 App| test2\n'
53-
b'test3.desktop|Test3 App|\n'
54-
b'myvm.desktop|My VM app|\n')
51+
return (b'test.desktop|Test App||\n'
52+
b'test2.desktop|Test2 App| test2|\n'
53+
b'test3.desktop|Test3 App||\n'
54+
b'myvm.desktop|My VM app||\n')
5555
elif vm_name == 'fedora-36':
56-
return b'tpl.desktop|Template App|\n'
56+
return b'tpl.desktop|Template App||\n'
5757
else:
58-
return (b'test.desktop|Test App|\n'
59-
b'test2.desktop|Test2 App| test2\n'
60-
b'test3.desktop|Test3 App|\n')
58+
return (b'test.desktop|Test App||\n'
59+
b'test2.desktop|Test2 App| test2|\n'
60+
b'test3.desktop|Test3 App||\n')
6161
elif command[1] == '--get-whitelist':
6262
if vm_name == 'test-vm-set':
6363
return b'test.desktop\nmissing.desktop'

0 commit comments

Comments
 (0)