Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions qubesappmenus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,11 +501,36 @@ def appmenus_remove(self, vm, refresh_cache=True):
refresh_cache)
shutil.rmtree(appmenus_dir)

self._remove_menu_files(vm)

if refresh_cache:
if 'KDE_SESSION_UID' in os.environ:
subprocess.call(['kbuildsycoca' +
os.environ.get('KDE_SESSION_VERSION', '4')])

@staticmethod
def _remove_menu_files(vm):
"""Remove .menu files for a VM from ~/.config/menus/applications-merged/

xdg-desktop-menu uninstall does not always clean these up (errors
are suppressed, partial installs can leave entries behind).
"""
menus_dir = os.path.join(xdg.BaseDirectory.xdg_config_home,
'menus', 'applications-merged')
if not os.path.isdir(menus_dir):
return
vm_name = str(vm)
escaped = vm_name_escape(vm_name)
for prefix in ('qubes-vm-directory', 'qubes-dispvm-directory'):
with contextlib.suppress(FileNotFoundError):
os.unlink(os.path.join(menus_dir,
'user-' + prefix + escaped + '.menu'))
# also try old format (before VM name escaping was introduced)
with contextlib.suppress(FileNotFoundError):
os.unlink(os.path.join(menus_dir,
'user-' + prefix + '-' +
vm_name + '.menu'))

def appicons_create(self, vm, srcdirs=(), force=False):
"""Create/update applications icons"""
if not srcdirs:
Expand Down
80 changes: 80 additions & 0 deletions qubesappmenus/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import io
import os
import shutil
import tempfile
import sys

Expand Down Expand Up @@ -743,6 +744,85 @@ def test_132_get_available_does_not_require_unstable_flag(
appmenus_cls.return_value.get_available.assert_called_once_with(vm)
self.assertEqual(stdout.getvalue(), 'xterm.desktop - XTerm\n')

@unittest.mock.patch('qubesappmenus.Appmenus')
def test_133_unstable_flag_is_ignored(self, appmenus_cls):
vm = TestVM('test-inst-vm', klass='AppVM',
label=self.app.labels[1])
self.app.domains[vm.name] = vm
appmenus_cls.return_value.get_available.return_value = [
('xterm.desktop', 'XTerm')]

with unittest.mock.patch('sys.stdout', new_callable=io.StringIO) \
as stdout, \
unittest.mock.patch('sys.stderr', new_callable=io.StringIO) \
as stderr:
qubesappmenus.main(
['--force-root', '--get-available',
'--i-understand-format-is-unstable', vm.name],
app=self.app)

appmenus_cls.return_value.get_available.assert_called_once_with(vm)
self.assertEqual(stdout.getvalue(), 'xterm.desktop - XTerm\n')
self.assertIn('deprecated', stderr.getvalue())


@unittest.mock.patch('subprocess.check_call')
def test_140_remove_cleans_menu_files(self, mock_subprocess):
tpl = TestVM('test-inst-tpl',
klass='TemplateVM',
virt_mode='pvh',
updateable=True,
provides_network=False,
label=self.app.labels[1])
self.ext.appmenus_init(tpl)
appvm = TestVM('test-inst-app',
klass='AppVM',
template=tpl,
virt_mode='pvh',
updateable=False,
provides_network=False,
label=self.app.labels[1])
self.ext.appmenus_init(appvm)
self.ext.appmenus_create(appvm, refresh_cache=False)

config_dir = tempfile.mkdtemp()
try:
menus_dir = os.path.join(config_dir, 'menus', 'applications-merged')
os.makedirs(menus_dir)
escaped = qubesappmenus.vm_name_escape(appvm.name)
# current-format .menu file
menu_file = os.path.join(menus_dir,
'user-qubes-vm-directory' + escaped + '.menu')
# old-format .menu file (pre-escaping)
old_menu_file = os.path.join(menus_dir,
'user-qubes-vm-directory-' + appvm.name + '.menu')
for path in (menu_file, old_menu_file):
with open(path, 'w', encoding='utf-8') as f:
f.write('<Menu/>\n')

with unittest.mock.patch('xdg.BaseDirectory.xdg_config_home',
config_dir):
self.ext.appmenus_remove(appvm, refresh_cache=False)

self.assertPathNotExists(menu_file)
self.assertPathNotExists(old_menu_file)
finally:
shutil.rmtree(config_dir)

def test_141_remove_menu_files_missing_dir(self):
config_dir = tempfile.mkdtemp()
try:
menus_dir = os.path.join(config_dir, 'menus', 'applications-merged')
with unittest.mock.patch('xdg.BaseDirectory.xdg_config_home',
config_dir), \
unittest.mock.patch('os.unlink') as mock_unlink:
# Missing applications-merged directory should be ignored.
qubesappmenus.Appmenus._remove_menu_files('test-vm')
mock_unlink.assert_not_called()
self.assertFalse(os.path.exists(menus_dir))
finally:
shutil.rmtree(config_dir)


def list_tests():
return (TC_00_Appmenus,)
Expand Down