Skip to content

Commit

Permalink
Add Icons to Qubes VM Settings Apps tab
Browse files Browse the repository at this point in the history
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
  • Loading branch information
alimirjamali committed Mar 8, 2025
1 parent f2d0a17 commit 64d89d3
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 35 deletions.
4 changes: 4 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ checks:pylint:
- pip3 install --quiet -r ci/requirements.txt
- git clone https://github.com/QubesOS/qubes-core-admin-client ~/core-admin-client
- (cd ~/core-admin-client;python3 setup.py egg_info)
- git clone https://github.com/QubesOS/qubes-linux-utils ~/linux-utils
- (cd ~/linux-utils/imgconverter;sudo python3 setup.py install)
script:
- PYTHONPATH=~/core-admin-client python3 -m pylint qubesmanager
stage: checks
Expand All @@ -27,6 +29,8 @@ checks:tests:
- pip3 install --quiet -r ci/requirements.txt
- git clone https://github.com/QubesOS/qubes-core-admin-client ~/core-admin-client
- (cd ~/core-admin-client;python3 setup.py egg_info)
- git clone https://github.com/QubesOS/qubes-linux-utils ~/linux-utils
- (cd ~/linux-utils/imgconverter;sudo python3 setup.py install)
script:
- make ui
- make res
Expand Down
2 changes: 2 additions & 0 deletions ci/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ pylint
sphinx
PyYAML
qasync
Pillow
numpy
114 changes: 87 additions & 27 deletions qubesmanager/appmenu_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
#

import subprocess
from PyQt6 import QtWidgets, QtCore # pylint: disable=import-error
from PyQt6 import QtWidgets, QtCore, QtGui # pylint: disable=import-error
from qubesadmin import exc
from qubesmanager.utils import tint_qimage
from os import path

# TODO description in tooltip
# TODO icon
# pylint: disable=too-few-public-methods


class AppListWidgetItem(QtWidgets.QListWidgetItem):
def __init__(self, name, ident, tooltip=None, parent=None):
super().__init__(name, parent)
Expand All @@ -39,38 +41,48 @@ def __init__(self, name, ident, tooltip=None, parent=None):

@classmethod
def from_line(cls, line):
ident, name, comment = line.split('|', maxsplit=3)
ident, name, comment = line.split("|", maxsplit=3)
return cls(name=name, ident=ident, tooltip=comment)

@classmethod
def from_ident(cls, ident):
name = 'Application missing in template! ({})'.format(ident)
comment = 'The listed application was available at some point to ' \
'this qube, but not any more. The most likely cause is ' \
'template change. Install the application in the template ' \
'if you want to restore it.'
name = "Application missing in template! ({})".format(ident)
comment = (
"The listed application was available at some point to "
"this qube, but not any more. The most likely cause is "
"template change. Install the application in the template "
"if you want to restore it."
)
return cls(name=name, ident=ident, tooltip=comment)


class AppmenuSelectManager:
def __init__(self, vm, apps_multiselect):
self.vm = vm
self.app_list = apps_multiselect # this is a multiselect wiget
self.app_list = apps_multiselect # this is a multiselect wiget
self.whitelisted = None
self.has_missing = False
self.fill_apps_list(template=None)

def fill_apps_list(self, template=None):
try:
self.whitelisted = [line for line in subprocess.check_output(
['qvm-appmenus', '--get-whitelist', self.vm.name]
).decode().strip().split('\n') if line]
self.whitelisted = [
line
for line in subprocess.check_output(
["qvm-appmenus", "--get-whitelist", self.vm.name]
)
.decode()
.strip()
.split("\n")
if line
]
except exc.QubesException:
self.whitelisted = []

currently_selected = [
self.app_list.selected_list.item(i).whatsThis()
for i in range(self.app_list.selected_list.count())]
for i in range(self.app_list.selected_list.count())
]

whitelist = set(self.whitelisted + currently_selected)

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

self.app_list.clear()

command = ['qvm-appmenus', '--get-available',
'--i-understand-format-is-unstable', '--file-field',
'Comment']
command = [
"qvm-appmenus",
"--get-available",
"--i-understand-format-is-unstable",
"--file-field",
"Comment",
"--file-field",
"Icon",
]
if template:
command.extend(['--template', template.name])
command.extend(["--template", template.name])
command.append(self.vm.name)

if not hasattr(self.vm, "template"):
# TemplateVMs and StandaloneVMs
main_template = self.vm.name
elif not hasattr(self.vm.template, "template"):
# AppVMs
main_template = self.vm.template.name
else:
# DispVMs
main_template = self.vm.template.template.name

Check warning on line 117 in qubesmanager/appmenu_select.py

View check run for this annotation

Codecov / codecov/patch

qubesmanager/appmenu_select.py#L117

Added line #L117 was not covered by tests

template_icons_path = path.join(
path.expanduser("~"),
".local",
"share",
"qubes-appmenus",
f"{main_template}",
"apps.tempicons",
)

try:
available_appmenus = [
AppListWidgetItem.from_line(line)
for line in subprocess.check_output(
command).decode().splitlines()]
available_appmenus = []
for line in subprocess.check_output(command).decode().splitlines():
ident, name, comment, icon_path = line.split("|", maxsplit=4)
app_item = AppListWidgetItem.from_line(
"|".join([ident, name, comment])
)
icon_path = icon_path.replace(
"%VMDIR%/apps.icons", template_icons_path
)
if path.exists(icon_path):
icon = QtGui.QIcon(icon_path)
qpixmap = icon.pixmap(QtCore.QSize(512, 512))
qimage = QtGui.QImage(qpixmap)
qimage = tint_qimage(qimage, self.vm.label.color)
qpixmap = QtGui.QPixmap(qimage)
icon = QtGui.QIcon(qpixmap)

Check warning on line 144 in qubesmanager/appmenu_select.py

View check run for this annotation

Codecov / codecov/patch

qubesmanager/appmenu_select.py#L139-L144

Added lines #L139 - L144 were not covered by tests
else:
# for `qubes-start.desktop` & missing icons
icon = QtGui.QIcon.fromTheme(self.vm.icon)
app_item.setIcon(icon)
available_appmenus.append(app_item)

except exc.QubesException:
available_appmenus = []

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

def save_appmenu_select_changes(self):
new_whitelisted = [self.app_list.selected_list.item(i).whatsThis()
for i in range(self.app_list.selected_list.count())]
new_whitelisted = [
self.app_list.selected_list.item(i).whatsThis()
for i in range(self.app_list.selected_list.count())
]

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

try:
self.vm.features['menu-items'] = " ".join(new_whitelisted)
self.vm.features["menu-items"] = " ".join(new_whitelisted)
except exc.QubesException as ex:
raise RuntimeError(QtCore.QCoreApplication.translate(
"exception", 'Failed to set menu items')) from ex
raise RuntimeError(

Check warning on line 182 in qubesmanager/appmenu_select.py

View check run for this annotation

Codecov / codecov/patch

qubesmanager/appmenu_select.py#L182

Added line #L182 was not covered by tests
QtCore.QCoreApplication.translate(
"exception", "Failed to set menu items"
)
) from ex

return True
16 changes: 8 additions & 8 deletions qubesmanager/tests/test_vm_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,16 @@ def mock_subprocess_complex(command):
vm_name = command[-1]
if command[1] == '--get-available':
if vm_name == 'test-vm-set':
return (b'test.desktop|Test App|\n'
b'test2.desktop|Test2 App| test2\n'
b'test3.desktop|Test3 App|\n'
b'myvm.desktop|My VM app|\n')
return (b'test.desktop|Test App||\n'
b'test2.desktop|Test2 App| test2|\n'
b'test3.desktop|Test3 App||\n'
b'myvm.desktop|My VM app||\n')
elif vm_name == 'fedora-36':
return b'tpl.desktop|Template App|\n'
return b'tpl.desktop|Template App||\n'
else:
return (b'test.desktop|Test App|\n'
b'test2.desktop|Test2 App| test2\n'
b'test3.desktop|Test3 App|\n')
return (b'test.desktop|Test App||\n'
b'test2.desktop|Test2 App| test2|\n'
b'test3.desktop|Test3 App||\n')
elif command[1] == '--get-whitelist':
if vm_name == 'test-vm-set':
return b'test.desktop\nmissing.desktop'
Expand Down

0 comments on commit 64d89d3

Please sign in to comment.