Move appmenus/icons related to desktop-linux-common
This is the right place for desktop related files - later it will be installed in GUI VM (but core-admin-linux will not). QubesOS/qubes-issues#2735
@ -1,8 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Version=1.0
|
|
||||||
Type=Application
|
|
||||||
Terminal=false
|
|
||||||
Name=Command Prompt
|
|
||||||
Comment=Use the command line
|
|
||||||
Categories=GNOME;GTK;Utility;TerminalEmulator;System;
|
|
||||||
Exec=cmd /c start cmd
|
|
@ -1,8 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Version=1.0
|
|
||||||
Type=Application
|
|
||||||
Terminal=false
|
|
||||||
Name=Explorer
|
|
||||||
Comment=Browse files
|
|
||||||
Categories=Utility;Core;
|
|
||||||
Exec=explorer
|
|
@ -1,8 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Version=1.0
|
|
||||||
Type=Application
|
|
||||||
Terminal=false
|
|
||||||
Name=Internet Explorer
|
|
||||||
Comment=Browse the Web
|
|
||||||
Categories=Network;WebBrowser;
|
|
||||||
Exec=C:\\Program Files\\Internet Explorer\\iexplore.exe
|
|
@ -1,10 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Version=1.0
|
|
||||||
Type=Application
|
|
||||||
Exec=sh -c 'echo firefox | /usr/lib/qubes/qfile-daemon-dvm qubes.VMShell dom0 DEFAULT red'
|
|
||||||
Icon=dispvm-red
|
|
||||||
Terminal=false
|
|
||||||
Name=DispVM: Firefox web browser
|
|
||||||
GenericName=DispVM: Web browser
|
|
||||||
StartupNotify=false
|
|
||||||
Categories=Network;X-Qubes-VM;
|
|
@ -1,10 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Version=1.0
|
|
||||||
Type=Application
|
|
||||||
Exec=sh -c 'echo xterm | /usr/lib/qubes/qfile-daemon-dvm qubes.VMShell dom0 DEFAULT red'
|
|
||||||
Icon=dispvm-red
|
|
||||||
Terminal=false
|
|
||||||
Name=DispVM: xterm
|
|
||||||
GenericName=DispVM: Terminal
|
|
||||||
StartupNotify=false
|
|
||||||
Categories=Network;X-Qubes-VM;
|
|
@ -1,5 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Encoding=UTF-8
|
|
||||||
Type=Directory
|
|
||||||
Name=DisposableVM
|
|
||||||
Icon=dispvm-red
|
|
@ -1,10 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Version=1.0
|
|
||||||
Type=Application
|
|
||||||
Exec=qvm-start --quiet --tray %VMNAME%
|
|
||||||
Icon=%XDGICON%
|
|
||||||
Terminal=false
|
|
||||||
Name=%VMNAME%: Start
|
|
||||||
GenericName=%VMNAME%: Start
|
|
||||||
StartupNotify=false
|
|
||||||
Categories=System;X-Qubes-VM;
|
|
@ -1,56 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU General Public License
|
|
||||||
# as published by the Free Software Foundation; either version 2
|
|
||||||
# of the License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
import sys
|
|
||||||
from qubes.qubes import QubesVmCollection
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) != 4:
|
|
||||||
print >> sys.stderr, \
|
|
||||||
'Usage: qvm-appmenu-replace VM_NAME OLD_NAME.desktop NEW_NAME.desktop'
|
|
||||||
sys.exit(1)
|
|
||||||
vm_name = sys.argv[1]
|
|
||||||
old_name = sys.argv[2]
|
|
||||||
new_name = sys.argv[3]
|
|
||||||
|
|
||||||
qvm_collection = QubesVmCollection()
|
|
||||||
qvm_collection.lock_db_for_reading()
|
|
||||||
qvm_collection.load()
|
|
||||||
qvm_collection.unlock_db()
|
|
||||||
|
|
||||||
vm = qvm_collection.get_vm_by_name(vm_name)
|
|
||||||
|
|
||||||
if vm is None:
|
|
||||||
print >> sys.stderr, "ERROR: A VM with the name '{0}' " \
|
|
||||||
"does not exist in the system.".format(
|
|
||||||
vm_name)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if vm.template is not None:
|
|
||||||
print >> sys.stderr, "ERROR: To replace appmenu for template based VM, " \
|
|
||||||
"do it on template instead"
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
vm.appmenus_replace_entry(old_name, new_name)
|
|
||||||
if hasattr(vm, 'appvms'):
|
|
||||||
for child_vm in vm.appvms.values():
|
|
||||||
child_vm.appmenus_replace_entry(old_name, new_name)
|
|
||||||
|
|
||||||
main()
|
|
BIN
icons/black.png
Before Width: | Height: | Size: 169 KiB |
BIN
icons/blue.png
Before Width: | Height: | Size: 181 KiB |
@ -1,10 +0,0 @@
|
|||||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
||||||
This copyright and license notice covers the images in this directory.
|
|
||||||
************************************************************************
|
|
||||||
|
|
||||||
TITLE: Crystal Project Icons
|
|
||||||
AUTHOR: Everaldo Coelho
|
|
||||||
SITE: http://www.everaldo.com
|
|
||||||
CONTACT: everaldo@everaldo.com
|
|
||||||
|
|
||||||
Copyright (c) 2006-2007 Everaldo Coelho.
|
|
@ -1 +0,0 @@
|
|||||||
dom0-update-avail icon from gnome-packagekit project distributed under GPLv2
|
|
@ -1 +0,0 @@
|
|||||||
Color padlock images downloaded from www.openclipart.org
|
|
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 2.5 KiB |
BIN
icons/gray.png
Before Width: | Height: | Size: 192 KiB |
BIN
icons/green.png
Before Width: | Height: | Size: 187 KiB |
BIN
icons/netvm.png
Before Width: | Height: | Size: 15 KiB |
BIN
icons/orange.png
Before Width: | Height: | Size: 188 KiB |
BIN
icons/purple.png
Before Width: | Height: | Size: 188 KiB |
BIN
icons/qubes.png
Before Width: | Height: | Size: 20 KiB |
BIN
icons/red.png
Before Width: | Height: | Size: 177 KiB |
Before Width: | Height: | Size: 20 KiB |
BIN
icons/yellow.png
Before Width: | Height: | Size: 185 KiB |
@ -1,491 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
|
||||||
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU General Public License
|
|
||||||
# as published by the Free Software Foundation; either version 2
|
|
||||||
# of the License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
|
||||||
# USA.
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import shutil
|
|
||||||
import dbus
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
import qubes.ext
|
|
||||||
import qubes.vm.dispvm
|
|
||||||
|
|
||||||
import qubesimgconverter
|
|
||||||
|
|
||||||
|
|
||||||
class AppmenusSubdirs:
|
|
||||||
templates_subdir = 'apps.templates'
|
|
||||||
template_icons_subdir = 'apps.tempicons'
|
|
||||||
subdir = 'apps'
|
|
||||||
icons_subdir = 'apps.icons'
|
|
||||||
template_templates_subdir = 'apps-template.templates'
|
|
||||||
whitelist = 'whitelisted-appmenus.list'
|
|
||||||
|
|
||||||
|
|
||||||
class AppmenusPaths:
|
|
||||||
appmenu_start_hvm_template = \
|
|
||||||
'/usr/share/qubes-appmenus/qubes-start.desktop'
|
|
||||||
|
|
||||||
|
|
||||||
class AppmenusExtension(qubes.ext.Extension):
|
|
||||||
def __init__(self, *args):
|
|
||||||
super(AppmenusExtension, self).__init__(*args)
|
|
||||||
import qubes.vm.qubesvm
|
|
||||||
import qubes.vm.templatevm
|
|
||||||
|
|
||||||
def templates_dir(self, vm):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:type vm: qubes.vm.qubesvm.QubesVM
|
|
||||||
"""
|
|
||||||
if vm.updateable:
|
|
||||||
return os.path.join(vm.dir_path,
|
|
||||||
AppmenusSubdirs.templates_subdir)
|
|
||||||
elif hasattr(vm, 'template'):
|
|
||||||
return self.templates_dir(vm.template)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def template_icons_dir(self, vm):
|
|
||||||
'''Directory for not yet colore icons'''
|
|
||||||
if vm.updateable:
|
|
||||||
return os.path.join(vm.dir_path,
|
|
||||||
AppmenusSubdirs.template_icons_subdir)
|
|
||||||
elif hasattr(vm, 'template'):
|
|
||||||
return self.template_icons_dir(vm.template)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def appmenus_dir(self, vm):
|
|
||||||
'''Desktop files generated for particular VM'''
|
|
||||||
return os.path.join(vm.dir_path, AppmenusSubdirs.subdir)
|
|
||||||
|
|
||||||
def icons_dir(self, vm):
|
|
||||||
'''Icon files generated (colored) for particular VM'''
|
|
||||||
return os.path.join(vm.dir_path, AppmenusSubdirs.icons_subdir)
|
|
||||||
|
|
||||||
def whitelist_path(self, vm):
|
|
||||||
'''File listing files wanted in menu'''
|
|
||||||
return os.path.join(vm.dir_path, AppmenusSubdirs.whitelist)
|
|
||||||
|
|
||||||
def directory_template_name(self, vm):
|
|
||||||
'''File name of desktop directory entry template'''
|
|
||||||
if isinstance(vm, qubes.vm.templatevm.TemplateVM):
|
|
||||||
return 'qubes-templatevm.directory.template'
|
|
||||||
elif vm.provides_network:
|
|
||||||
return 'qubes-servicevm.directory.template'
|
|
||||||
else:
|
|
||||||
return 'qubes-vm.directory.template'
|
|
||||||
|
|
||||||
def write_desktop_file(self, vm, source, destination_path):
|
|
||||||
"""Format .desktop/.directory file
|
|
||||||
|
|
||||||
:param vm: QubesVM object for which write desktop file
|
|
||||||
:param source: desktop file template (path or template itself)
|
|
||||||
:param destination_path: where to write the desktop file
|
|
||||||
:return: True if target file was changed, otherwise False
|
|
||||||
"""
|
|
||||||
if source.startswith('/'):
|
|
||||||
source = open(source).read()
|
|
||||||
data = source.\
|
|
||||||
replace("%VMNAME%", vm.name).\
|
|
||||||
replace("%VMDIR%", vm.dir_path).\
|
|
||||||
replace("%XDGICON%", vm.label.icon)
|
|
||||||
if os.path.exists(destination_path):
|
|
||||||
current_dest = open(destination_path).read()
|
|
||||||
if current_dest == data:
|
|
||||||
return False
|
|
||||||
with open(destination_path, "w") as f:
|
|
||||||
f.write(data)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def appmenus_create(self, vm, refresh_cache=True):
|
|
||||||
"""Create/update .desktop files
|
|
||||||
|
|
||||||
:param vm: QubesVM object for which create entries
|
|
||||||
:param refresh_cache: refresh desktop environment cache; if false,
|
|
||||||
must be refreshed manually later
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
|
|
||||||
if vm.internal:
|
|
||||||
return
|
|
||||||
if isinstance(vm, qubes.vm.dispvm.DispVM):
|
|
||||||
return
|
|
||||||
|
|
||||||
vm.log.info("Creating appmenus")
|
|
||||||
appmenus_dir = self.appmenus_dir(vm)
|
|
||||||
if not os.path.exists(appmenus_dir):
|
|
||||||
os.makedirs(appmenus_dir)
|
|
||||||
|
|
||||||
anything_changed = False
|
|
||||||
directory_file = os.path.join(appmenus_dir, vm.name + '-vm.directory')
|
|
||||||
if self.write_desktop_file(vm,
|
|
||||||
pkg_resources.resource_string(__name__,
|
|
||||||
self.directory_template_name(vm)), directory_file):
|
|
||||||
anything_changed = True
|
|
||||||
|
|
||||||
templates_dir = self.templates_dir(vm)
|
|
||||||
if os.path.exists(templates_dir):
|
|
||||||
appmenus = os.listdir(templates_dir)
|
|
||||||
else:
|
|
||||||
appmenus = []
|
|
||||||
changed_appmenus = []
|
|
||||||
if os.path.exists(self.whitelist_path(vm)):
|
|
||||||
whitelist = [x.rstrip() for x in open(self.whitelist_path(vm))]
|
|
||||||
appmenus = [x for x in appmenus if x in whitelist]
|
|
||||||
|
|
||||||
for appmenu in appmenus:
|
|
||||||
if self.write_desktop_file(vm,
|
|
||||||
os.path.join(templates_dir, appmenu),
|
|
||||||
os.path.join(appmenus_dir,
|
|
||||||
'-'.join((vm.name, appmenu)))):
|
|
||||||
changed_appmenus.append(appmenu)
|
|
||||||
if self.write_desktop_file(vm,
|
|
||||||
pkg_resources.resource_string(
|
|
||||||
__name__, 'qubes-appmenu-select.desktop.template'
|
|
||||||
),
|
|
||||||
os.path.join(appmenus_dir,
|
|
||||||
'-'.join((vm.name, 'qubes-appmenu-select.desktop')))):
|
|
||||||
changed_appmenus.append('qubes-appmenu-select.desktop')
|
|
||||||
|
|
||||||
if changed_appmenus:
|
|
||||||
anything_changed = True
|
|
||||||
|
|
||||||
target_appmenus = map(
|
|
||||||
lambda x: '-'.join((vm.name, x)),
|
|
||||||
appmenus + ['qubes-appmenu-select.desktop']
|
|
||||||
)
|
|
||||||
|
|
||||||
# remove old entries
|
|
||||||
installed_appmenus = os.listdir(appmenus_dir)
|
|
||||||
installed_appmenus.remove(os.path.basename(directory_file))
|
|
||||||
appmenus_to_remove = set(installed_appmenus).difference(set(
|
|
||||||
target_appmenus))
|
|
||||||
if len(appmenus_to_remove):
|
|
||||||
appmenus_to_remove_fnames = map(
|
|
||||||
lambda x: os.path.join(appmenus_dir, x), appmenus_to_remove)
|
|
||||||
try:
|
|
||||||
desktop_menu_cmd = ['xdg-desktop-menu', 'uninstall']
|
|
||||||
if not refresh_cache:
|
|
||||||
desktop_menu_cmd.append('--noupdate')
|
|
||||||
desktop_menu_cmd.append(directory_file)
|
|
||||||
desktop_menu_cmd.extend(appmenus_to_remove_fnames)
|
|
||||||
desktop_menu_env = os.environ.copy()
|
|
||||||
desktop_menu_env['LC_COLLATE'] = 'C'
|
|
||||||
subprocess.check_call(desktop_menu_cmd, env=desktop_menu_env)
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
vm.log.warning("Problem removing old appmenus")
|
|
||||||
|
|
||||||
for appmenu in appmenus_to_remove_fnames:
|
|
||||||
os.unlink(appmenu)
|
|
||||||
|
|
||||||
# add new entries
|
|
||||||
if anything_changed:
|
|
||||||
try:
|
|
||||||
desktop_menu_cmd = ['xdg-desktop-menu', 'install']
|
|
||||||
if not refresh_cache:
|
|
||||||
desktop_menu_cmd.append('--noupdate')
|
|
||||||
desktop_menu_cmd.append(directory_file)
|
|
||||||
desktop_menu_cmd.extend(map(
|
|
||||||
lambda x: os.path.join(
|
|
||||||
appmenus_dir, '-'.join((vm.name, x))),
|
|
||||||
changed_appmenus))
|
|
||||||
desktop_menu_env = os.environ.copy()
|
|
||||||
desktop_menu_env['LC_COLLATE'] = 'C'
|
|
||||||
subprocess.check_call(desktop_menu_cmd, env=desktop_menu_env)
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
vm.log.warning("Problem creating appmenus")
|
|
||||||
|
|
||||||
if refresh_cache:
|
|
||||||
if 'KDE_SESSION_UID' in os.environ:
|
|
||||||
subprocess.call(['kbuildsycoca' +
|
|
||||||
os.environ.get('KDE_SESSION_VERSION', '4')])
|
|
||||||
|
|
||||||
def appmenus_remove(self, vm, refresh_cache=True):
|
|
||||||
'''Remove desktop files for particular VM'''
|
|
||||||
appmenus_dir = self.appmenus_dir(vm)
|
|
||||||
if os.path.exists(appmenus_dir):
|
|
||||||
vm.log.info("Removing appmenus")
|
|
||||||
installed_appmenus = os.listdir(appmenus_dir)
|
|
||||||
directory_file = os.path.join(self.appmenus_dir(vm),
|
|
||||||
vm.name + '-vm.directory')
|
|
||||||
installed_appmenus.remove(os.path.basename(directory_file))
|
|
||||||
if installed_appmenus:
|
|
||||||
appmenus_to_remove_fnames = map(
|
|
||||||
lambda x: os.path.join(appmenus_dir, x), installed_appmenus)
|
|
||||||
try:
|
|
||||||
desktop_menu_cmd = ['xdg-desktop-menu', 'uninstall']
|
|
||||||
if not refresh_cache:
|
|
||||||
desktop_menu_cmd.append('--noupdate')
|
|
||||||
desktop_menu_cmd.append(directory_file)
|
|
||||||
desktop_menu_cmd.extend(appmenus_to_remove_fnames)
|
|
||||||
desktop_menu_env = os.environ.copy()
|
|
||||||
desktop_menu_env['LC_COLLATE'] = 'C'
|
|
||||||
subprocess.check_call(desktop_menu_cmd,
|
|
||||||
env=desktop_menu_env)
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
vm.log.warning("Problem removing appmenus")
|
|
||||||
shutil.rmtree(appmenus_dir)
|
|
||||||
|
|
||||||
if refresh_cache:
|
|
||||||
if 'KDE_SESSION_UID' in os.environ:
|
|
||||||
subprocess.call(['kbuildsycoca' +
|
|
||||||
os.environ.get('KDE_SESSION_VERSION', '4')])
|
|
||||||
|
|
||||||
def appicons_create(self, vm, srcdir=None, force=False):
|
|
||||||
"""Create/update applications icons"""
|
|
||||||
if srcdir is None:
|
|
||||||
srcdir = self.template_icons_dir(vm)
|
|
||||||
if srcdir is None:
|
|
||||||
return
|
|
||||||
if not os.path.exists(srcdir):
|
|
||||||
return
|
|
||||||
|
|
||||||
if vm.internal:
|
|
||||||
return
|
|
||||||
if isinstance(vm, qubes.vm.dispvm.DispVM):
|
|
||||||
return
|
|
||||||
|
|
||||||
whitelist = self.whitelist_path(vm)
|
|
||||||
if os.path.exists(whitelist):
|
|
||||||
whitelist = [line.strip() for line in open(whitelist)]
|
|
||||||
else:
|
|
||||||
whitelist = None
|
|
||||||
|
|
||||||
dstdir = self.icons_dir(vm)
|
|
||||||
if not os.path.exists(dstdir):
|
|
||||||
os.mkdir(dstdir)
|
|
||||||
elif not os.path.isdir(dstdir):
|
|
||||||
os.unlink(dstdir)
|
|
||||||
os.mkdir(dstdir)
|
|
||||||
|
|
||||||
if whitelist:
|
|
||||||
expected_icons = \
|
|
||||||
map(lambda x: os.path.splitext(x)[0] + '.png', whitelist)
|
|
||||||
else:
|
|
||||||
expected_icons = os.listdir(srcdir)
|
|
||||||
|
|
||||||
for icon in os.listdir(srcdir):
|
|
||||||
if icon not in expected_icons:
|
|
||||||
continue
|
|
||||||
|
|
||||||
src_icon = os.path.join(srcdir, icon)
|
|
||||||
dst_icon = os.path.join(dstdir, icon)
|
|
||||||
if not os.path.exists(dst_icon) or force or \
|
|
||||||
os.path.getmtime(src_icon) > os.path.getmtime(dst_icon):
|
|
||||||
qubesimgconverter.tint(src_icon, dst_icon, vm.label.color)
|
|
||||||
|
|
||||||
for icon in os.listdir(dstdir):
|
|
||||||
if icon not in expected_icons:
|
|
||||||
os.unlink(os.path.join(dstdir, icon))
|
|
||||||
|
|
||||||
def appicons_remove(self, vm):
|
|
||||||
'''Remove icons'''
|
|
||||||
if not os.path.exists(self.icons_dir(vm)):
|
|
||||||
return
|
|
||||||
shutil.rmtree(self.icons_dir(vm))
|
|
||||||
|
|
||||||
@qubes.ext.handler('property-pre-set:name', vm=qubes.vm.qubesvm.QubesVM)
|
|
||||||
def pre_rename(self, vm, event, prop, *args):
|
|
||||||
if not vm.dir_path or not os.path.exists(vm.dir_path):
|
|
||||||
return
|
|
||||||
self.appmenus_remove(vm)
|
|
||||||
|
|
||||||
@qubes.ext.handler('property-set:name', vm=qubes.vm.qubesvm.QubesVM)
|
|
||||||
def post_rename(self, vm, event, prop, *args):
|
|
||||||
if not vm.dir_path or not os.path.exists(vm.dir_path):
|
|
||||||
return
|
|
||||||
self.appmenus_create(vm)
|
|
||||||
|
|
||||||
@qubes.ext.handler('domain-create-on-disk')
|
|
||||||
def create_on_disk(self, vm, event):
|
|
||||||
if not vm.dir_path or not os.path.exists(vm.dir_path):
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
source_template = vm.template
|
|
||||||
except AttributeError:
|
|
||||||
source_template = None
|
|
||||||
if vm.updateable and source_template is None:
|
|
||||||
os.mkdir(self.templates_dir(vm))
|
|
||||||
os.mkdir(self.template_icons_dir(vm))
|
|
||||||
if vm.hvm and source_template is None:
|
|
||||||
vm.log.info("Creating appmenus directory: {0}".format(
|
|
||||||
self.templates_dir(vm)))
|
|
||||||
shutil.copy(AppmenusPaths.appmenu_start_hvm_template,
|
|
||||||
self.templates_dir(vm))
|
|
||||||
|
|
||||||
source_whitelist_filename = 'vm-' + AppmenusSubdirs.whitelist
|
|
||||||
if source_template and os.path.exists(
|
|
||||||
os.path.join(source_template.dir_path, source_whitelist_filename)):
|
|
||||||
vm.log.info("Creating default whitelisted apps list: {0}".
|
|
||||||
format(vm.dir_path + '/' + AppmenusSubdirs.whitelist))
|
|
||||||
shutil.copy(
|
|
||||||
os.path.join(source_template.dir_path, source_whitelist_filename),
|
|
||||||
os.path.join(vm.dir_path, AppmenusSubdirs.whitelist))
|
|
||||||
|
|
||||||
if vm.updateable:
|
|
||||||
vm.log.info("Creating/copying appmenus templates")
|
|
||||||
if source_template and os.path.isdir(self.templates_dir(
|
|
||||||
source_template)):
|
|
||||||
shutil.copytree(self.templates_dir(source_template),
|
|
||||||
self.templates_dir(vm))
|
|
||||||
if source_template and os.path.isdir(self.template_icons_dir(
|
|
||||||
source_template)):
|
|
||||||
shutil.copytree(self.template_icons_dir(source_template),
|
|
||||||
self.template_icons_dir(vm))
|
|
||||||
|
|
||||||
# Create appmenus
|
|
||||||
self.appicons_create(vm)
|
|
||||||
self.appmenus_create(vm)
|
|
||||||
|
|
||||||
@qubes.ext.handler('domain-clone-files')
|
|
||||||
def clone_disk_files(self, vm, event, src_vm):
|
|
||||||
if not vm.dir_path or not os.path.exists(vm.dir_path):
|
|
||||||
return
|
|
||||||
if src_vm.updateable and self.templates_dir(vm) is not None and \
|
|
||||||
self.templates_dir(vm) is not None:
|
|
||||||
vm.log.info("Copying the template's appmenus templates "
|
|
||||||
"dir:\n{0} ==>\n{1}".
|
|
||||||
format(self.templates_dir(src_vm),
|
|
||||||
self.templates_dir(vm)))
|
|
||||||
shutil.copytree(self.templates_dir(src_vm),
|
|
||||||
self.templates_dir(vm))
|
|
||||||
|
|
||||||
if src_vm.updateable and self.template_icons_dir(vm) is not None \
|
|
||||||
and self.template_icons_dir(vm) is not None and \
|
|
||||||
os.path.isdir(self.template_icons_dir(src_vm)):
|
|
||||||
vm.log.info("Copying the template's appmenus "
|
|
||||||
"template icons dir:\n{0} ==>\n{1}".
|
|
||||||
format(self.template_icons_dir(src_vm),
|
|
||||||
self.template_icons_dir(vm)))
|
|
||||||
shutil.copytree(self.template_icons_dir(src_vm),
|
|
||||||
self.template_icons_dir(vm))
|
|
||||||
|
|
||||||
for whitelist in (
|
|
||||||
AppmenusSubdirs.whitelist,
|
|
||||||
'vm-' + AppmenusSubdirs.whitelist,
|
|
||||||
'netvm-' + AppmenusSubdirs.whitelist):
|
|
||||||
if os.path.exists(os.path.join(src_vm.dir_path, whitelist)):
|
|
||||||
vm.log.info("Copying whitelisted apps list: {0}".
|
|
||||||
format(whitelist))
|
|
||||||
shutil.copy(os.path.join(src_vm.dir_path, whitelist),
|
|
||||||
os.path.join(vm.dir_path, whitelist))
|
|
||||||
|
|
||||||
# Create appmenus
|
|
||||||
self.appicons_create(vm)
|
|
||||||
self.appmenus_create(vm)
|
|
||||||
|
|
||||||
|
|
||||||
@qubes.ext.handler('domain-remove-from-disk')
|
|
||||||
def remove_from_disk(self, vm, event):
|
|
||||||
self.appmenus_remove(vm)
|
|
||||||
|
|
||||||
|
|
||||||
@qubes.ext.handler('property-set:label')
|
|
||||||
def label_setter(self, vm, event, *args):
|
|
||||||
if not vm.dir_path or not os.path.exists(vm.dir_path):
|
|
||||||
return
|
|
||||||
self.appicons_create(vm, force=True)
|
|
||||||
|
|
||||||
# Apparently desktop environments heavily caches the icons,
|
|
||||||
# see #751 for details
|
|
||||||
if "plasma" in os.environ.get("DESKTOP_SESSION", ""):
|
|
||||||
try:
|
|
||||||
os.unlink(os.path.expandvars(
|
|
||||||
"$HOME/.kde/cache-$HOSTNAME/icon-cache.kcache"))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
notify_object = dbus.SessionBus().get_object(
|
|
||||||
"org.freedesktop.Notifications",
|
|
||||||
"/org/freedesktop/Notifications")
|
|
||||||
notify_object.Notify(
|
|
||||||
"Qubes", 0, vm.label.icon, "Qubes",
|
|
||||||
"You will need to log off and log in again for the VM icons "
|
|
||||||
"to update in the KDE launcher menu",
|
|
||||||
[], [], 10000,
|
|
||||||
dbus_interface="org.freedesktop.Notifications")
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
elif "xfce" in os.environ.get("DESKTOP_SESSION", ""):
|
|
||||||
self.appmenus_remove(vm)
|
|
||||||
self.appmenus_create(vm)
|
|
||||||
|
|
||||||
@qubes.ext.handler('property-set:internal')
|
|
||||||
def on_property_set_internal(self, vm, event, prop, newvalue, *args):
|
|
||||||
if not vm.dir_path or not os.path.exists(vm.dir_path):
|
|
||||||
return
|
|
||||||
if len(args):
|
|
||||||
oldvalue = args[0]
|
|
||||||
else:
|
|
||||||
oldvalue = vm.__class__.internal._default
|
|
||||||
if newvalue and not oldvalue:
|
|
||||||
self.appmenus_remove(vm)
|
|
||||||
elif not newvalue and oldvalue:
|
|
||||||
self.appmenus_create(vm)
|
|
||||||
|
|
||||||
@qubes.ext.handler('backup-get-files')
|
|
||||||
def files_for_backup(self, vm, event):
|
|
||||||
if not vm.dir_path or not os.path.exists(vm.dir_path):
|
|
||||||
return
|
|
||||||
if vm.internal:
|
|
||||||
return
|
|
||||||
if vm.updateable:
|
|
||||||
yield self.templates_dir(vm)
|
|
||||||
yield self.template_icons_dir(vm)
|
|
||||||
if os.path.exists(self.whitelist_path(vm)):
|
|
||||||
yield self.whitelist_path(vm)
|
|
||||||
for whitelist in (
|
|
||||||
'vm-' + AppmenusSubdirs.whitelist,
|
|
||||||
'netvm-' + AppmenusSubdirs.whitelist):
|
|
||||||
if os.path.exists(os.path.join(vm.dir_path, whitelist)):
|
|
||||||
yield os.path.join(vm.dir_path, whitelist)
|
|
||||||
|
|
||||||
def appmenus_update(self, vm):
|
|
||||||
'''Update (regenerate) desktop files and icons for this VM and (in
|
|
||||||
case of template) child VMs'''
|
|
||||||
self.appicons_create(vm)
|
|
||||||
self.appmenus_create(vm)
|
|
||||||
if hasattr(vm, 'appvms'):
|
|
||||||
for child_vm in vm.appvms:
|
|
||||||
try:
|
|
||||||
self.appicons_create(child_vm)
|
|
||||||
self.appmenus_create(child_vm, refresh_cache=False)
|
|
||||||
except Exception as e:
|
|
||||||
child_vm.log.error("Failed to recreate appmenus for "
|
|
||||||
"'{0}': {1}".format(child_vm.name,
|
|
||||||
str(e)))
|
|
||||||
subprocess.call(['xdg-desktop-menu', 'forceupdate'])
|
|
||||||
if 'KDE_SESSION_UID' in os.environ:
|
|
||||||
subprocess.call([
|
|
||||||
'kbuildsycoca' + os.environ.get('KDE_SESSION_VERSION',
|
|
||||||
'4')])
|
|
||||||
|
|
||||||
@qubes.ext.handler('template-postinstall')
|
|
||||||
def on_template_postinstall(self, vm, event):
|
|
||||||
import qubesappmenus.receive
|
|
||||||
new_appmenus = qubesappmenus.receive.retrieve_appmenus_templates(
|
|
||||||
vm, use_stdin=False)
|
|
||||||
qubesappmenus.receive.process_appmenus_templates(self, vm, new_appmenus)
|
|
@ -1,10 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Version=1.0
|
|
||||||
Type=Application
|
|
||||||
Exec=qubes-vm-settings %VMNAME% applications
|
|
||||||
Icon=qubes-appmenu-select
|
|
||||||
Terminal=false
|
|
||||||
Name=%VMNAME%: Add more shortcuts...
|
|
||||||
GenericName=%VMNAME%: Add more shortcuts...
|
|
||||||
StartupNotify=false
|
|
||||||
Categories=System;X-Qubes-VM;
|
|
@ -1,5 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Encoding=UTF-8
|
|
||||||
Type=Directory
|
|
||||||
Name=ServiceVM: %VMNAME%
|
|
||||||
Icon=%XDGICON%
|
|
@ -1,5 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Encoding=UTF-8
|
|
||||||
Type=Directory
|
|
||||||
Name=Template: %VMNAME%
|
|
||||||
Icon=%XDGICON%
|
|
@ -1,5 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Encoding=UTF-8
|
|
||||||
Type=Directory
|
|
||||||
Name=Domain: %VMNAME%
|
|
||||||
Icon=%XDGICON%
|
|
@ -1 +0,0 @@
|
|||||||
/usr/bin/qvm-sync-appmenus
|
|
@ -1,357 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2011 Marek Marczykowski <marmarek@mimuw.edu.pl>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU General Public License
|
|
||||||
# as published by the Free Software Foundation; either version 2
|
|
||||||
# of the License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
import optparse
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import shutil
|
|
||||||
import pipes
|
|
||||||
|
|
||||||
from optparse import OptionParser
|
|
||||||
import qubes.exc
|
|
||||||
import qubes.tools
|
|
||||||
import qubesappmenus
|
|
||||||
|
|
||||||
import qubesimgconverter
|
|
||||||
|
|
||||||
parser = qubes.tools.QubesArgumentParser(
|
|
||||||
vmname_nargs='?',
|
|
||||||
want_force_root=True,
|
|
||||||
description='retrieve appmenus')
|
|
||||||
|
|
||||||
parser.add_argument('--force-rpc',
|
|
||||||
action='store_true', default=False,
|
|
||||||
help="Force to start a new RPC call, even if called from existing one")
|
|
||||||
|
|
||||||
parser.add_argument('--regenerate-only',
|
|
||||||
action='store_true', default=False,
|
|
||||||
help='Only regenerate appmenus entries, do not synchronize with system '
|
|
||||||
'in template')
|
|
||||||
|
|
||||||
# TODO offline mode
|
|
||||||
|
|
||||||
# fields required to be present (and verified) in retrieved desktop file
|
|
||||||
required_fields = ["Name", "Exec"]
|
|
||||||
|
|
||||||
# limits
|
|
||||||
appmenus_line_size = 1024
|
|
||||||
appmenus_line_count = 100000
|
|
||||||
|
|
||||||
# regexps for sanitization of retrieved values
|
|
||||||
std_re = re.compile(r"^[/a-zA-Z0-9.,:&()_ -]*$")
|
|
||||||
fields_regexp = {
|
|
||||||
"Name": std_re,
|
|
||||||
"GenericName": std_re,
|
|
||||||
"Comment": std_re,
|
|
||||||
"Categories": re.compile(r"^[a-zA-Z0-9/.;:'() -]*$"),
|
|
||||||
"Exec": re.compile(r"^[a-zA-Z0-9()_%&>/{}\"'\\:.= -]*$"),
|
|
||||||
"Icon": re.compile(r"^[a-zA-Z0-9/_.-]*$"),
|
|
||||||
}
|
|
||||||
|
|
||||||
CATEGORIES_WHITELIST = {
|
|
||||||
# Main Categories
|
|
||||||
# http://standards.freedesktop.org/menu-spec/1.1/apa.html 20140507
|
|
||||||
'AudioVideo', 'Audio', 'Video', 'Development', 'Education', 'Game',
|
|
||||||
'Graphics', 'Network', 'Office', 'Science', 'Settings', 'System',
|
|
||||||
'Utility',
|
|
||||||
|
|
||||||
# Additional Categories
|
|
||||||
# http://standards.freedesktop.org/menu-spec/1.1/apas02.html
|
|
||||||
'Building', 'Debugger', 'IDE', 'GUIDesigner', 'Profiling',
|
|
||||||
'RevisionControl', 'Translation', 'Calendar', 'ContactManagement',
|
|
||||||
'Database', 'Dictionary', 'Chart', 'Email', 'Finance', 'FlowChart', 'PDA',
|
|
||||||
'ProjectManagement', 'Presentation', 'Spreadsheet', 'WordProcessor',
|
|
||||||
'2DGraphics', 'VectorGraphics', 'RasterGraphics', '3DGraphics', 'Scanning',
|
|
||||||
'OCR', 'Photography', 'Publishing', 'Viewer', 'TextTools',
|
|
||||||
'DesktopSettings', 'HardwareSettings', 'Printing', 'PackageManager',
|
|
||||||
'Dialup', 'InstantMessaging', 'Chat', 'IRCClient', 'Feed', 'FileTransfer',
|
|
||||||
'HamRadio', 'News', 'P2P', 'RemoteAccess', 'Telephony', 'TelephonyTools',
|
|
||||||
'VideoConference', 'WebBrowser', 'WebDevelopment', 'Midi', 'Mixer',
|
|
||||||
'Sequencer', 'Tuner', 'TV', 'AudioVideoEditing', 'Player', 'Recorder',
|
|
||||||
'DiscBurning', 'ActionGame', 'AdventureGame', 'ArcadeGame', 'BoardGame',
|
|
||||||
'BlocksGame', 'CardGame', 'KidsGame', 'LogicGame', 'RolePlaying',
|
|
||||||
'Shooter', 'Simulation', 'SportsGame', 'StrategyGame', 'Art',
|
|
||||||
'Construction', 'Music', 'Languages', 'ArtificialIntelligence',
|
|
||||||
'Astronomy', 'Biology', 'Chemistry', 'ComputerScience',
|
|
||||||
'DataVisualization', 'Economy', 'Electricity', 'Geography', 'Geology',
|
|
||||||
'Geoscience', 'History', 'Humanities', 'ImageProcessing', 'Literature',
|
|
||||||
'Maps', 'Math', 'NumericalAnalysis', 'MedicalSoftware', 'Physics',
|
|
||||||
'Robotics', 'Spirituality', 'Sports', 'ParallelComputing', 'Amusement',
|
|
||||||
'Archiving', 'Compression', 'Electronics', 'Emulator', 'Engineering',
|
|
||||||
'FileTools', 'FileManager', 'TerminalEmulator', 'Filesystem', 'Monitor',
|
|
||||||
'Security', 'Accessibility', 'Calculator', 'Clock', 'TextEditor',
|
|
||||||
'Documentation', 'Adult', 'Core', 'KDE', 'GNOME', 'XFCE', 'GTK', 'Qt',
|
|
||||||
'Motif', 'Java', 'ConsoleOnly',
|
|
||||||
|
|
||||||
# Reserved Categories (not whitelisted)
|
|
||||||
# http://standards.freedesktop.org/menu-spec/1.1/apas03.html
|
|
||||||
# 'Screensaver', 'TrayIcon', 'Applet', 'Shell',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def sanitise_categories(untrusted_value):
|
|
||||||
untrusted_categories = (c.strip() for c in untrusted_value.split(';') if c)
|
|
||||||
categories = (c for c in untrusted_categories if c in CATEGORIES_WHITELIST)
|
|
||||||
|
|
||||||
return ';'.join(categories) + ';'
|
|
||||||
|
|
||||||
|
|
||||||
def fallback_hvm_appmenulist():
|
|
||||||
p = subprocess.Popen(["grep", "-rH", "=", "/usr/share/qubes-appmenus/hvm"],
|
|
||||||
stdout=subprocess.PIPE)
|
|
||||||
(stdout, stderr) = p.communicate()
|
|
||||||
return stdout.splitlines()
|
|
||||||
|
|
||||||
|
|
||||||
def get_appmenus(vm):
|
|
||||||
appmenus_line_limit_left = appmenus_line_count
|
|
||||||
untrusted_appmenulist = []
|
|
||||||
if vm is None:
|
|
||||||
while appmenus_line_limit_left > 0:
|
|
||||||
untrusted_line = sys.stdin.readline(appmenus_line_size)
|
|
||||||
if untrusted_line == "":
|
|
||||||
break
|
|
||||||
untrusted_appmenulist.append(untrusted_line.strip())
|
|
||||||
appmenus_line_limit_left -= 1
|
|
||||||
if appmenus_line_limit_left == 0:
|
|
||||||
raise qubes.exc.QubesException("Line count limit exceeded")
|
|
||||||
else:
|
|
||||||
p = vm.run('QUBESRPC qubes.GetAppmenus dom0', passio_popen=True,
|
|
||||||
gui=False)
|
|
||||||
while appmenus_line_limit_left > 0:
|
|
||||||
untrusted_line = p.stdout.readline(appmenus_line_size)
|
|
||||||
if untrusted_line == "":
|
|
||||||
break
|
|
||||||
untrusted_appmenulist.append(untrusted_line.strip())
|
|
||||||
appmenus_line_limit_left -= 1
|
|
||||||
p.wait()
|
|
||||||
if p.returncode != 0:
|
|
||||||
if vm.hvm:
|
|
||||||
untrusted_appmenulist = fallback_hvm_appmenulist()
|
|
||||||
else:
|
|
||||||
raise qubes.exc.QubesException("Error getting application list")
|
|
||||||
if appmenus_line_limit_left == 0:
|
|
||||||
raise qubes.exc.QubesException("Line count limit exceeded")
|
|
||||||
|
|
||||||
appmenus = {}
|
|
||||||
line_rx = re.compile(
|
|
||||||
r"([a-zA-Z0-9.()_-]+.desktop):([a-zA-Z0-9-]+(?:\[[a-zA-Z@_]+\])?)=(.*)")
|
|
||||||
ignore_rx = re.compile(r".*([a-zA-Z0-9._-]+.desktop):(#.*|\s+)$")
|
|
||||||
for untrusted_line in untrusted_appmenulist:
|
|
||||||
# Ignore blank lines and comments
|
|
||||||
if len(untrusted_line) == 0 or ignore_rx.match(untrusted_line):
|
|
||||||
continue
|
|
||||||
# use search instead of match to skip file path
|
|
||||||
untrusted_m = line_rx.search(untrusted_line)
|
|
||||||
if untrusted_m:
|
|
||||||
filename = untrusted_m.group(1)
|
|
||||||
assert '/' not in filename
|
|
||||||
assert '\0' not in filename
|
|
||||||
|
|
||||||
untrusted_key = untrusted_m.group(2)
|
|
||||||
assert '\0' not in untrusted_key
|
|
||||||
assert '\x1b' not in untrusted_key
|
|
||||||
assert '=' not in untrusted_key
|
|
||||||
|
|
||||||
untrusted_value = untrusted_m.group(3)
|
|
||||||
# TODO add key-dependent asserts
|
|
||||||
|
|
||||||
# Look only at predefined keys
|
|
||||||
if untrusted_key in fields_regexp:
|
|
||||||
if fields_regexp[untrusted_key].match(untrusted_value):
|
|
||||||
# now values are sanitized
|
|
||||||
key = untrusted_key
|
|
||||||
if key == 'Categories':
|
|
||||||
value = sanitise_categories(untrusted_value)
|
|
||||||
else:
|
|
||||||
value = untrusted_value
|
|
||||||
|
|
||||||
if filename not in appmenus:
|
|
||||||
appmenus[filename] = {}
|
|
||||||
|
|
||||||
appmenus[filename][key] = value
|
|
||||||
else:
|
|
||||||
print >> sys.stderr, \
|
|
||||||
"Warning: ignoring key %r of %s" % \
|
|
||||||
(untrusted_key, filename)
|
|
||||||
# else: ignore this key
|
|
||||||
|
|
||||||
return appmenus
|
|
||||||
|
|
||||||
|
|
||||||
def create_template(path, values):
|
|
||||||
# check if all required fields are present
|
|
||||||
for key in required_fields:
|
|
||||||
if key not in values:
|
|
||||||
print >> sys.stderr, "Warning: not creating/updating '%s' " \
|
|
||||||
"because of missing '%s' key" % (
|
|
||||||
path, key)
|
|
||||||
return
|
|
||||||
|
|
||||||
desktop_entry = ""
|
|
||||||
desktop_entry += "[Desktop Entry]\n"
|
|
||||||
desktop_entry += "Version=1.0\n"
|
|
||||||
desktop_entry += "Type=Application\n"
|
|
||||||
desktop_entry += "Terminal=false\n"
|
|
||||||
desktop_entry += "X-Qubes-VmName=%VMNAME%\n"
|
|
||||||
|
|
||||||
if 'Icon' in values:
|
|
||||||
icon_file = os.path.splitext(os.path.split(path)[1])[0] + '.png'
|
|
||||||
desktop_entry += "Icon={0}\n".format(os.path.join(
|
|
||||||
'%VMDIR%', qubesappmenus.AppmenusSubdirs.icons_subdir, icon_file))
|
|
||||||
else:
|
|
||||||
desktop_entry += "Icon=%XDGICON%\n"
|
|
||||||
|
|
||||||
for key in ["Name", "GenericName"]:
|
|
||||||
if key in values:
|
|
||||||
desktop_entry += "{0}=%VMNAME%: {1}\n".format(key, values[key])
|
|
||||||
|
|
||||||
# force category X-Qubes-VM
|
|
||||||
values["Categories"] = values.get("Categories", "") + "X-Qubes-VM;"
|
|
||||||
|
|
||||||
for key in ["Comment", "Categories"]:
|
|
||||||
if key in values:
|
|
||||||
desktop_entry += "{0}={1}\n".format(key, values[key])
|
|
||||||
|
|
||||||
desktop_entry += "Exec=qvm-run -q --tray -a %VMNAME% -- {0}\n".format(
|
|
||||||
pipes.quote(values['Exec']))
|
|
||||||
if not os.path.exists(path) or desktop_entry != open(path, "r").read():
|
|
||||||
desktop_file = open(path, "w")
|
|
||||||
desktop_file.write(desktop_entry)
|
|
||||||
desktop_file.close()
|
|
||||||
|
|
||||||
|
|
||||||
def process_appmenus_templates(appmenusext, vm, appmenus):
|
|
||||||
old_umask = os.umask(002)
|
|
||||||
|
|
||||||
if not os.path.exists(appmenusext.templates_dir(vm)):
|
|
||||||
os.mkdir(appmenusext.templates_dir(vm))
|
|
||||||
|
|
||||||
if not os.path.exists(appmenusext.template_icons_dir(vm)):
|
|
||||||
os.mkdir(appmenusext.template_icons_dir(vm))
|
|
||||||
|
|
||||||
if vm.hvm:
|
|
||||||
if not os.path.exists(os.path.join(
|
|
||||||
appmenusext.templates_dir(vm),
|
|
||||||
os.path.basename(
|
|
||||||
qubesappmenus.AppmenusPaths.appmenu_start_hvm_template))):
|
|
||||||
shutil.copy(qubesappmenus.AppmenusPaths.appmenu_start_hvm_template,
|
|
||||||
appmenusext.templates_dir(vm))
|
|
||||||
|
|
||||||
|
|
||||||
for appmenu_file in appmenus.keys():
|
|
||||||
if os.path.exists(
|
|
||||||
os.path.join(appmenusext.templates_dir(vm),
|
|
||||||
appmenu_file)):
|
|
||||||
vm.log.info("Updating {0}".format(appmenu_file))
|
|
||||||
else:
|
|
||||||
vm.log.info("Creating {0}".format(appmenu_file))
|
|
||||||
|
|
||||||
# TODO: icons support in offline mode
|
|
||||||
# TODO if options.offline_mode:
|
|
||||||
# TODO new_appmenus[appmenu_file].pop('Icon', None)
|
|
||||||
if 'Icon' in appmenus[appmenu_file]:
|
|
||||||
# the following line is used for time comparison
|
|
||||||
icondest = os.path.join(appmenusext.template_icons_dir(vm),
|
|
||||||
os.path.splitext(appmenu_file)[0] + '.png')
|
|
||||||
|
|
||||||
try:
|
|
||||||
icon = qubesimgconverter.Image. \
|
|
||||||
get_xdg_icon_from_vm(vm, appmenus[appmenu_file]['Icon'])
|
|
||||||
if os.path.exists(icondest):
|
|
||||||
old_icon = qubesimgconverter.Image.load_from_file(icondest)
|
|
||||||
else:
|
|
||||||
old_icon = None
|
|
||||||
if old_icon is None or icon != old_icon:
|
|
||||||
icon.save(icondest)
|
|
||||||
except Exception as e:
|
|
||||||
vm.log.warning('Failed to get icon for {0}: {1!s}'.\
|
|
||||||
format(appmenu_file, e))
|
|
||||||
|
|
||||||
if os.path.exists(icondest):
|
|
||||||
vm.log.warning('Found old icon, using it instead')
|
|
||||||
else:
|
|
||||||
del appmenus[appmenu_file]['Icon']
|
|
||||||
|
|
||||||
create_template(os.path.join(appmenusext.templates_dir(vm),
|
|
||||||
appmenu_file), appmenus[appmenu_file])
|
|
||||||
|
|
||||||
# Delete appmenus of removed applications
|
|
||||||
for appmenu_file in os.listdir(appmenusext.templates_dir(vm)):
|
|
||||||
if not appmenu_file.endswith('.desktop'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if appmenu_file not in appmenus:
|
|
||||||
vm.log.info("Removing {0}".format(appmenu_file))
|
|
||||||
os.unlink(os.path.join(appmenusext.templates_dir(vm),
|
|
||||||
appmenu_file))
|
|
||||||
|
|
||||||
os.umask(old_umask)
|
|
||||||
|
|
||||||
|
|
||||||
def retrieve_appmenus_templates(vm, use_stdin=True):
|
|
||||||
'''Retrieve appmenus from the VM. If not running in offline mode,
|
|
||||||
additionally retrieve application icons and store them into
|
|
||||||
:py:metch:`template_icons_dir`.
|
|
||||||
|
|
||||||
Returns: dict of desktop entries, each being dict itself.
|
|
||||||
'''
|
|
||||||
if hasattr(vm, 'template'):
|
|
||||||
raise qubes.exc.QubesException(
|
|
||||||
"To sync appmenus for template based VM, do it on template instead")
|
|
||||||
|
|
||||||
if not vm.is_running():
|
|
||||||
raise qubes.exc.QubesVMNotRunningError(vm,
|
|
||||||
"Appmenus can be retrieved only from running VM")
|
|
||||||
|
|
||||||
new_appmenus = get_appmenus(vm if not use_stdin else None)
|
|
||||||
|
|
||||||
if len(new_appmenus) == 0:
|
|
||||||
raise qubes.exc.QubesException("No appmenus received, terminating")
|
|
||||||
return new_appmenus
|
|
||||||
|
|
||||||
|
|
||||||
def main(args=None):
|
|
||||||
env_vmname = os.environ.get("QREXEC_REMOTE_DOMAIN")
|
|
||||||
|
|
||||||
args = parser.parse_args(args)
|
|
||||||
|
|
||||||
if env_vmname:
|
|
||||||
vm = args.app.domains[env_vmname]
|
|
||||||
else:
|
|
||||||
vm = args.domains[0]
|
|
||||||
|
|
||||||
if vm is None:
|
|
||||||
parser.error("You must specify at least the VM name!")
|
|
||||||
|
|
||||||
if env_vmname is None or args.force_rpc:
|
|
||||||
use_stdin = False
|
|
||||||
else:
|
|
||||||
use_stdin = True
|
|
||||||
appmenusext = qubesappmenus.AppmenusExtension()
|
|
||||||
if not args.regenerate_only:
|
|
||||||
new_appmenus = retrieve_appmenus_templates(vm, use_stdin=use_stdin)
|
|
||||||
process_appmenus_templates(appmenusext, vm, new_appmenus)
|
|
||||||
appmenusext.appmenus_update(vm)
|
|
@ -1,383 +0,0 @@
|
|||||||
#!/usr/bin/python2
|
|
||||||
# coding=utf-8
|
|
||||||
#
|
|
||||||
# The Qubes OS Project, http://www.qubes-os.org
|
|
||||||
#
|
|
||||||
# Copyright (C) 2016 Marek Marczykowski-Górecki
|
|
||||||
# <marmarek@invisiblethingslab.com>
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU General Public License
|
|
||||||
# as published by the Free Software Foundation; either version 2
|
|
||||||
# of the License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
|
||||||
# USA.
|
|
||||||
|
|
||||||
import colorsys
|
|
||||||
import os
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
import pkg_resources
|
|
||||||
import xdg
|
|
||||||
import xdg.BaseDirectory
|
|
||||||
import xdg.DesktopEntry
|
|
||||||
import qubes
|
|
||||||
import qubes.tests
|
|
||||||
import qubes.tests.extra
|
|
||||||
import qubes.vm.appvm
|
|
||||||
import qubes.vm.templatevm
|
|
||||||
import qubesappmenus
|
|
||||||
import qubesappmenus.receive
|
|
||||||
import qubesimgconverter
|
|
||||||
|
|
||||||
|
|
||||||
class TestApp(object):
|
|
||||||
labels = {1: qubes.Label(1, '0xcc0000', 'red')}
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.domains = {}
|
|
||||||
|
|
||||||
|
|
||||||
class TestVM(object):
|
|
||||||
# pylint: disable=too-few-public-methods
|
|
||||||
app = TestApp()
|
|
||||||
|
|
||||||
def __init__(self, name, **kwargs):
|
|
||||||
self.running = False
|
|
||||||
self.installed_by_rpm = False
|
|
||||||
self.is_template = False
|
|
||||||
self.name = name
|
|
||||||
for k, v in kwargs.items():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
def is_running(self):
|
|
||||||
return self.running
|
|
||||||
|
|
||||||
|
|
||||||
class TC_00_Appmenus(qubes.tests.QubesTestCase):
|
|
||||||
"""Unittests for appmenus, theoretically runnable from git checkout"""
|
|
||||||
def setUp(self):
|
|
||||||
super(TC_00_Appmenus, self).setUp()
|
|
||||||
vmname = qubes.tests.VMPREFIX + 'standalone'
|
|
||||||
self.standalone = TestVM(
|
|
||||||
name=vmname,
|
|
||||||
dir_path=os.path.join(qubes.config.qubes_base_dir, 'appvms',
|
|
||||||
vmname),
|
|
||||||
updateable=True,
|
|
||||||
)
|
|
||||||
vmname = qubes.tests.VMPREFIX + 'template'
|
|
||||||
self.template = TestVM(
|
|
||||||
name=vmname,
|
|
||||||
dir_path=os.path.join(
|
|
||||||
qubes.config.qubes_base_dir,
|
|
||||||
'vm-templates', vmname),
|
|
||||||
is_template=True,
|
|
||||||
updateable=True,
|
|
||||||
)
|
|
||||||
vmname = qubes.tests.VMPREFIX + 'vm'
|
|
||||||
self.appvm = TestVM(
|
|
||||||
name=vmname,
|
|
||||||
dir_path=os.path.join(
|
|
||||||
qubes.config.qubes_base_dir,
|
|
||||||
'appvms', vmname),
|
|
||||||
template=self.template,
|
|
||||||
updateable=False,
|
|
||||||
)
|
|
||||||
self.app = TestApp()
|
|
||||||
self.ext = qubesappmenus.AppmenusExtension()
|
|
||||||
|
|
||||||
def test_000_templates_dir(self):
|
|
||||||
self.assertEquals(
|
|
||||||
self.ext.templates_dir(self.standalone),
|
|
||||||
os.path.join(qubes.config.qubes_base_dir, 'appvms',
|
|
||||||
self.standalone.name, 'apps.templates')
|
|
||||||
)
|
|
||||||
self.assertEquals(
|
|
||||||
self.ext.templates_dir(self.template),
|
|
||||||
os.path.join(qubes.config.qubes_base_dir, 'vm-templates',
|
|
||||||
self.template.name, 'apps.templates')
|
|
||||||
)
|
|
||||||
self.assertEquals(
|
|
||||||
self.ext.templates_dir(self.appvm),
|
|
||||||
os.path.join(qubes.config.qubes_base_dir, 'vm-templates',
|
|
||||||
self.template.name, 'apps.templates')
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_001_template_icons_dir(self):
|
|
||||||
self.assertEquals(
|
|
||||||
self.ext.template_icons_dir(self.standalone),
|
|
||||||
os.path.join(qubes.config.qubes_base_dir, 'appvms',
|
|
||||||
self.standalone.name, 'apps.tempicons')
|
|
||||||
)
|
|
||||||
self.assertEquals(
|
|
||||||
self.ext.template_icons_dir(self.template),
|
|
||||||
os.path.join(qubes.config.qubes_base_dir, 'vm-templates',
|
|
||||||
self.template.name, 'apps.tempicons')
|
|
||||||
)
|
|
||||||
self.assertEquals(
|
|
||||||
self.ext.template_icons_dir(self.appvm),
|
|
||||||
os.path.join(qubes.config.qubes_base_dir, 'vm-templates',
|
|
||||||
self.template.name, 'apps.tempicons')
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_002_appmenus_dir(self):
|
|
||||||
self.assertEquals(
|
|
||||||
self.ext.appmenus_dir(self.standalone),
|
|
||||||
os.path.join(qubes.config.qubes_base_dir, 'appvms',
|
|
||||||
self.standalone.name, 'apps')
|
|
||||||
)
|
|
||||||
self.assertEquals(
|
|
||||||
self.ext.appmenus_dir(self.template),
|
|
||||||
os.path.join(qubes.config.qubes_base_dir, 'vm-templates',
|
|
||||||
self.template.name, 'apps')
|
|
||||||
)
|
|
||||||
self.assertEquals(
|
|
||||||
self.ext.appmenus_dir(self.appvm),
|
|
||||||
os.path.join(qubes.config.qubes_base_dir, 'appvms',
|
|
||||||
self.appvm.name, 'apps')
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_003_icons_dir(self):
|
|
||||||
self.assertEquals(
|
|
||||||
self.ext.icons_dir(self.standalone),
|
|
||||||
os.path.join(qubes.config.qubes_base_dir, 'appvms',
|
|
||||||
self.standalone.name, 'apps.icons')
|
|
||||||
)
|
|
||||||
self.assertEquals(
|
|
||||||
self.ext.icons_dir(self.template),
|
|
||||||
os.path.join(qubes.config.qubes_base_dir, 'vm-templates',
|
|
||||||
self.template.name, 'apps.icons')
|
|
||||||
)
|
|
||||||
self.assertEquals(
|
|
||||||
self.ext.icons_dir(self.appvm),
|
|
||||||
os.path.join(qubes.config.qubes_base_dir, 'appvms',
|
|
||||||
self.appvm.name, 'apps.icons')
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_100_get_appmenus(self):
|
|
||||||
def _run(cmd, **kwargs):
|
|
||||||
class PopenMockup(object):
|
|
||||||
pass
|
|
||||||
self.assertEquals(cmd, 'QUBESRPC qubes.GetAppmenus dom0')
|
|
||||||
self.assertEquals(kwargs.get('passio_popen', False), True)
|
|
||||||
self.assertEquals(kwargs.get('gui', True), False)
|
|
||||||
p = PopenMockup()
|
|
||||||
p.stdout = pkg_resources.resource_stream(__name__,
|
|
||||||
'test-data/appmenus.input')
|
|
||||||
p.wait = lambda: None
|
|
||||||
p.returncode = 0
|
|
||||||
return p
|
|
||||||
vm = TestVM('test-vm', run=_run)
|
|
||||||
appmenus = qubesappmenus.receive.get_appmenus(vm)
|
|
||||||
expected_appmenus = {
|
|
||||||
'org.gnome.Nautilus.desktop': {
|
|
||||||
'Name': 'Files',
|
|
||||||
'Comment': 'Access and organize files',
|
|
||||||
'Categories': 'GNOME;GTK;Utility;Core;FileManager;',
|
|
||||||
'Exec': 'qubes-desktop-run '
|
|
||||||
'/usr/share/applications/org.gnome.Nautilus.desktop',
|
|
||||||
'Icon': 'system-file-manager',
|
|
||||||
},
|
|
||||||
'org.gnome.Weather.Application.desktop': {
|
|
||||||
'Name': 'Weather',
|
|
||||||
'Comment': 'Show weather conditions and forecast',
|
|
||||||
'Categories': 'GNOME;GTK;Utility;Core;',
|
|
||||||
'Exec': 'qubes-desktop-run '
|
|
||||||
'/usr/share/applications/org.gnome.Weather.Application.desktop',
|
|
||||||
'Icon': 'org.gnome.Weather.Application',
|
|
||||||
},
|
|
||||||
'org.gnome.Cheese.desktop': {
|
|
||||||
'Name': 'Cheese',
|
|
||||||
'GenericName': 'Webcam Booth',
|
|
||||||
'Comment': 'Take photos and videos with your webcam, with fun graphical effects',
|
|
||||||
'Categories': 'GNOME;AudioVideo;Video;Recorder;',
|
|
||||||
'Exec': 'qubes-desktop-run '
|
|
||||||
'/usr/share/applications/org.gnome.Cheese.desktop',
|
|
||||||
'Icon': 'cheese',
|
|
||||||
},
|
|
||||||
'evince.desktop': {
|
|
||||||
'Name': 'Document Viewer',
|
|
||||||
'Comment': 'View multi-page documents',
|
|
||||||
'Categories': 'GNOME;GTK;Office;Viewer;Graphics;2DGraphics;VectorGraphics;',
|
|
||||||
'Exec': 'qubes-desktop-run '
|
|
||||||
'/usr/share/applications/evince.desktop',
|
|
||||||
'Icon': 'evince',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
self.assertEquals(expected_appmenus, appmenus)
|
|
||||||
|
|
||||||
|
|
||||||
class TC_10_AppmenusIntegration(qubes.tests.extra.ExtraTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TC_10_AppmenusIntegration, self).setUp()
|
|
||||||
self.vm = self.create_vms(['vm'])[0]
|
|
||||||
self.appmenus = qubesappmenus.AppmenusExtension()
|
|
||||||
|
|
||||||
def assertPathExists(self, path):
|
|
||||||
if not os.path.exists(path):
|
|
||||||
self.fail("Path {} does not exist".format(path))
|
|
||||||
|
|
||||||
def assertPathNotExists(self, path):
|
|
||||||
if os.path.exists(path):
|
|
||||||
self.fail("Path {} exists while it should not".format(path))
|
|
||||||
|
|
||||||
def get_whitelist(self, whitelist_path):
|
|
||||||
self.assertPathExists(whitelist_path)
|
|
||||||
with open(whitelist_path) as f:
|
|
||||||
whitelisted = [x.rstrip() for x in f.readlines()]
|
|
||||||
return whitelisted
|
|
||||||
|
|
||||||
def test_000_created(self, vm=None):
|
|
||||||
if vm is None:
|
|
||||||
vm = self.vm
|
|
||||||
whitelist_path = os.path.join(vm.dir_path,
|
|
||||||
qubesappmenus.AppmenusSubdirs.whitelist)
|
|
||||||
whitelisted = self.get_whitelist(whitelist_path)
|
|
||||||
self.assertPathExists(self.appmenus.appmenus_dir(vm))
|
|
||||||
appmenus = os.listdir(self.appmenus.appmenus_dir(vm))
|
|
||||||
self.assertTrue(all(x.startswith(vm.name + '-') for x in appmenus))
|
|
||||||
appmenus = [x[len(vm.name) + 1:] for x in appmenus]
|
|
||||||
self.assertIn('vm.directory', appmenus)
|
|
||||||
appmenus.remove('vm.directory')
|
|
||||||
self.assertIn('qubes-appmenu-select.desktop', appmenus)
|
|
||||||
appmenus.remove('qubes-appmenu-select.desktop')
|
|
||||||
self.assertEquals(set(whitelisted), set(appmenus))
|
|
||||||
self.assertPathExists(self.appmenus.icons_dir(vm))
|
|
||||||
appicons = os.listdir(self.appmenus.icons_dir(vm))
|
|
||||||
whitelisted_icons = set()
|
|
||||||
for appmenu in whitelisted:
|
|
||||||
desktop = xdg.DesktopEntry.DesktopEntry(
|
|
||||||
os.path.join(self.appmenus.appmenus_dir(vm),
|
|
||||||
'-'.join((vm.name, appmenu))))
|
|
||||||
if desktop.getIcon():
|
|
||||||
whitelisted_icons.add(os.path.basename(desktop.getIcon()))
|
|
||||||
self.assertEquals(set(whitelisted_icons), set(appicons))
|
|
||||||
|
|
||||||
def test_001_created_registered(self):
|
|
||||||
"""Check whether appmenus was registered in desktop environment"""
|
|
||||||
whitelist_path = os.path.join(self.vm.dir_path,
|
|
||||||
qubesappmenus.AppmenusSubdirs.whitelist)
|
|
||||||
if not os.path.exists(whitelist_path):
|
|
||||||
self.skipTest("Appmenus whitelist does not exists")
|
|
||||||
whitelisted = self.get_whitelist(whitelist_path)
|
|
||||||
for appmenu in whitelisted:
|
|
||||||
if appmenu.endswith('.directory'):
|
|
||||||
subdir = 'desktop-directories'
|
|
||||||
else:
|
|
||||||
subdir = 'applications'
|
|
||||||
self.assertPathExists(os.path.join(
|
|
||||||
xdg.BaseDirectory.xdg_data_home, subdir,
|
|
||||||
'-'.join([self.vm.name, appmenu])))
|
|
||||||
# TODO: some KDE specific dir?
|
|
||||||
|
|
||||||
def test_002_unregistered_after_remove(self):
|
|
||||||
"""Check whether appmenus was unregistered after VM removal"""
|
|
||||||
whitelist_path = os.path.join(self.vm.dir_path,
|
|
||||||
qubesappmenus.AppmenusSubdirs.whitelist)
|
|
||||||
if not os.path.exists(whitelist_path):
|
|
||||||
self.skipTest("Appmenus whitelist does not exists")
|
|
||||||
whitelisted = self.get_whitelist(whitelist_path)
|
|
||||||
self.vm.remove_from_disk()
|
|
||||||
for appmenu in whitelisted:
|
|
||||||
if appmenu.endswith('.directory'):
|
|
||||||
subdir = 'desktop-directories'
|
|
||||||
else:
|
|
||||||
subdir = 'applications'
|
|
||||||
self.assertPathNotExists(os.path.join(
|
|
||||||
xdg.BaseDirectory.xdg_data_home, subdir,
|
|
||||||
'-'.join([self.vm.name, appmenu])))
|
|
||||||
|
|
||||||
def test_003_created_template_empty(self):
|
|
||||||
tpl = self.app.add_new_vm(qubes.vm.templatevm.TemplateVM,
|
|
||||||
name=self.make_vm_name('tpl'), label='red')
|
|
||||||
tpl.create_on_disk()
|
|
||||||
self.assertPathExists(self.appmenus.templates_dir(tpl))
|
|
||||||
self.assertPathExists(self.appmenus.template_icons_dir(tpl))
|
|
||||||
|
|
||||||
def test_004_created_template_from_other(self):
|
|
||||||
tpl = self.app.add_new_vm(qubes.vm.templatevm.TemplateVM,
|
|
||||||
name=self.make_vm_name('tpl'), label='red')
|
|
||||||
tpl.clone_disk_files(self.app.default_template)
|
|
||||||
self.assertPathExists(self.appmenus.templates_dir(tpl))
|
|
||||||
self.assertPathExists(self.appmenus.template_icons_dir(tpl))
|
|
||||||
self.assertPathExists(os.path.join(tpl.dir_path,
|
|
||||||
qubesappmenus.AppmenusSubdirs.whitelist))
|
|
||||||
|
|
||||||
for appmenu in os.listdir(self.appmenus.templates_dir(
|
|
||||||
self.app.default_template)):
|
|
||||||
self.assertPathExists(os.path.join(
|
|
||||||
self.appmenus.templates_dir(tpl), appmenu))
|
|
||||||
|
|
||||||
for appicon in os.listdir(self.appmenus.template_icons_dir(
|
|
||||||
self.app.default_template)):
|
|
||||||
self.assertPathExists(os.path.join(
|
|
||||||
self.appmenus.template_icons_dir(tpl), appicon))
|
|
||||||
|
|
||||||
def get_image_color(self, path, expected_color):
|
|
||||||
"""Return mean color of the image as (r, g, b) in float"""
|
|
||||||
image = qubesimgconverter.Image.load_from_file(path)
|
|
||||||
_, l, _ = colorsys.rgb_to_hls(
|
|
||||||
*qubesimgconverter.hex_to_float(expected_color))
|
|
||||||
|
|
||||||
def get_hls(pixels, l):
|
|
||||||
for i in xrange(0, len(pixels), 4):
|
|
||||||
r, g, b, a = tuple(ord(c) / 255. for c in pixels[i:i + 4])
|
|
||||||
if a == 0.0:
|
|
||||||
continue
|
|
||||||
h, _, s = colorsys.rgb_to_hls(r, g, b)
|
|
||||||
yield h, l, s
|
|
||||||
|
|
||||||
mean_hls = reduce(
|
|
||||||
lambda x, y: (x[0] + y[0], x[1] + y[1], x[2] + y[2]),
|
|
||||||
get_hls(image.data, l),
|
|
||||||
(0, 0, 0)
|
|
||||||
)
|
|
||||||
mean_hls = map(lambda x: x / (mean_hls[1] / l), mean_hls)
|
|
||||||
image_color = colorsys.hls_to_rgb(*mean_hls)
|
|
||||||
return image_color
|
|
||||||
|
|
||||||
def assertIconColor(self, path, expected_color):
|
|
||||||
image_color_float = self.get_image_color(path, expected_color)
|
|
||||||
expected_color_float = qubesimgconverter.hex_to_float(expected_color)
|
|
||||||
if not all(map(lambda a, b: abs(a - b) <= 0.15,
|
|
||||||
image_color_float, expected_color_float)):
|
|
||||||
self.fail(
|
|
||||||
"Icon {} is not colored as {}".format(path, expected_color))
|
|
||||||
|
|
||||||
def test_010_icon_color(self, vm=None):
|
|
||||||
if vm is None:
|
|
||||||
vm = self.vm
|
|
||||||
icons_dir = self.appmenus.icons_dir(vm)
|
|
||||||
appicons = os.listdir(icons_dir)
|
|
||||||
for icon in appicons:
|
|
||||||
self.assertIconColor(os.path.join(icons_dir, icon),
|
|
||||||
vm.label.color)
|
|
||||||
|
|
||||||
def test_011_icon_color_label_change(self):
|
|
||||||
"""Regression test for #1606"""
|
|
||||||
self.vm.label = 'green'
|
|
||||||
self.test_010_icon_color()
|
|
||||||
|
|
||||||
def test_020_clone(self):
|
|
||||||
vm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
|
|
||||||
name=self.make_vm_name('vm2'), label='green')
|
|
||||||
|
|
||||||
vm2.clone_properties(self.vm)
|
|
||||||
vm2.clone_disk_files(self.vm)
|
|
||||||
self.test_000_created(vm=vm2)
|
|
||||||
self.test_010_icon_color(vm=vm2)
|
|
||||||
|
|
||||||
|
|
||||||
def list_tests():
|
|
||||||
return (
|
|
||||||
TC_00_Appmenus,
|
|
||||||
TC_10_AppmenusIntegration,
|
|
||||||
)
|
|
@ -76,7 +76,6 @@ ln -sf . %{name}-%{version}
|
|||||||
%setup -T -D
|
%setup -T -D
|
||||||
|
|
||||||
%build
|
%build
|
||||||
python setup.py build
|
|
||||||
(cd dom0-updates; make)
|
(cd dom0-updates; make)
|
||||||
(cd qrexec; make)
|
(cd qrexec; make)
|
||||||
(cd file-copy-vm; make)
|
(cd file-copy-vm; make)
|
||||||
@ -84,17 +83,10 @@ python setup.py build
|
|||||||
|
|
||||||
%install
|
%install
|
||||||
|
|
||||||
### Appmenus
|
## Appmenus
|
||||||
# force /usr/bin before /bin to have /usr/bin/python instead of /bin/python
|
install -d $RPM_BUILD_ROOT/etc/qubes-rpc/policy
|
||||||
PATH="/usr/bin:$PATH" python setup.py install -O1 --skip-build --root $RPM_BUILD_ROOT
|
|
||||||
|
|
||||||
mkdir -p $RPM_BUILD_ROOT/etc/qubes-rpc/policy
|
|
||||||
cp qubesappmenus/qubes.SyncAppMenus $RPM_BUILD_ROOT/etc/qubes-rpc/
|
|
||||||
cp qubesappmenus/qubes.SyncAppMenus.policy $RPM_BUILD_ROOT/etc/qubes-rpc/policy/qubes.SyncAppMenus
|
cp qubesappmenus/qubes.SyncAppMenus.policy $RPM_BUILD_ROOT/etc/qubes-rpc/policy/qubes.SyncAppMenus
|
||||||
|
|
||||||
mkdir -p $RPM_BUILD_ROOT/usr/share/qubes-appmenus/
|
|
||||||
cp -r appmenus-files/* $RPM_BUILD_ROOT/usr/share/qubes-appmenus/
|
|
||||||
|
|
||||||
### Dom0 updates
|
### Dom0 updates
|
||||||
install -D dom0-updates/qubes-dom0-updates.cron $RPM_BUILD_ROOT/etc/cron.daily/qubes-dom0-updates.cron
|
install -D dom0-updates/qubes-dom0-updates.cron $RPM_BUILD_ROOT/etc/cron.daily/qubes-dom0-updates.cron
|
||||||
install -D dom0-updates/qubes-dom0-update $RPM_BUILD_ROOT/usr/bin/qubes-dom0-update
|
install -D dom0-updates/qubes-dom0-update $RPM_BUILD_ROOT/usr/bin/qubes-dom0-update
|
||||||
@ -156,12 +148,6 @@ install -m 755 file-copy-vm/qfile-dom0-agent $RPM_BUILD_ROOT/usr/lib/qubes/
|
|||||||
install -m 755 file-copy-vm/qvm-copy-to-vm $RPM_BUILD_ROOT/usr/bin/
|
install -m 755 file-copy-vm/qvm-copy-to-vm $RPM_BUILD_ROOT/usr/bin/
|
||||||
install -m 755 file-copy-vm/qvm-move-to-vm $RPM_BUILD_ROOT/usr/bin/
|
install -m 755 file-copy-vm/qvm-move-to-vm $RPM_BUILD_ROOT/usr/bin/
|
||||||
|
|
||||||
### Icons
|
|
||||||
mkdir -p $RPM_BUILD_ROOT/usr/share/qubes/icons
|
|
||||||
for icon in icons/*.png; do
|
|
||||||
convert -resize 48 $icon $RPM_BUILD_ROOT/usr/share/qubes/$icon
|
|
||||||
done
|
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
(cd doc; make DESTDIR=$RPM_BUILD_ROOT install)
|
(cd doc; make DESTDIR=$RPM_BUILD_ROOT install)
|
||||||
|
|
||||||
@ -172,13 +158,6 @@ fi
|
|||||||
|
|
||||||
%post
|
%post
|
||||||
|
|
||||||
for i in /usr/share/qubes/icons/*.png ; do
|
|
||||||
xdg-icon-resource install --noupdate --novendor --size 48 $i
|
|
||||||
done
|
|
||||||
xdg-icon-resource forceupdate
|
|
||||||
|
|
||||||
xdg-desktop-menu install /usr/share/qubes-appmenus/qubes-dispvm.directory /usr/share/qubes-appmenus/qubes-dispvm-*.desktop
|
|
||||||
|
|
||||||
/usr/lib/qubes/patch-dnf-yum-config
|
/usr/lib/qubes/patch-dnf-yum-config
|
||||||
|
|
||||||
systemctl enable qubes-suspend.service >/dev/null 2>&1
|
systemctl enable qubes-suspend.service >/dev/null 2>&1
|
||||||
@ -187,12 +166,6 @@ systemctl enable qubes-suspend.service >/dev/null 2>&1
|
|||||||
if [ "$1" = 0 ] ; then
|
if [ "$1" = 0 ] ; then
|
||||||
# no more packages left
|
# no more packages left
|
||||||
|
|
||||||
for i in /usr/share/qubes/icons/*.png ; do
|
|
||||||
xdg-icon-resource uninstall --novendor --size 48 $i
|
|
||||||
done
|
|
||||||
|
|
||||||
xdg-desktop-menu uninstall /usr/share/qubes-appmenus/qubes-dispvm.directory /usr/share/qubes-appmenus/qubes-dispvm-*.desktop
|
|
||||||
|
|
||||||
systemctl disable qubes-suspend.service > /dev/null 2>&1
|
systemctl disable qubes-suspend.service > /dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -208,27 +181,7 @@ rm -f /lib/udev/rules.d/69-xorg-vmmouse.rules
|
|||||||
chmod -x /etc/grub.d/10_linux
|
chmod -x /etc/grub.d/10_linux
|
||||||
|
|
||||||
%files
|
%files
|
||||||
%attr(2775,root,qubes) %dir /etc/qubes-rpc
|
|
||||||
%attr(2775,root,qubes) %dir /etc/qubes-rpc/policy
|
|
||||||
%dir %{python_sitelib}/qubeslinux-*.egg-info
|
|
||||||
%{python_sitelib}/qubeslinux-*.egg-info/*
|
|
||||||
/usr/lib/python2.7/site-packages/qubesappmenus/__init__.py*
|
|
||||||
/usr/lib/python2.7/site-packages/qubesappmenus/receive.py*
|
|
||||||
/usr/lib/python2.7/site-packages/qubesappmenus/qubes-appmenu-select.desktop.template
|
|
||||||
/usr/lib/python2.7/site-packages/qubesappmenus/qubes-servicevm.directory.template
|
|
||||||
/usr/lib/python2.7/site-packages/qubesappmenus/qubes-templatevm.directory.template
|
|
||||||
/usr/lib/python2.7/site-packages/qubesappmenus/qubes-vm.directory.template
|
|
||||||
/usr/lib/python2.7/site-packages/qubesappmenus/tests.py*
|
|
||||||
/usr/lib/python2.7/site-packages/qubesappmenus/test-data
|
|
||||||
/etc/qubes-rpc/policy/qubes.SyncAppMenus
|
/etc/qubes-rpc/policy/qubes.SyncAppMenus
|
||||||
/etc/qubes-rpc/qubes.SyncAppMenus
|
|
||||||
/usr/share/qubes-appmenus/qubes-dispvm-firefox.desktop
|
|
||||||
/usr/share/qubes-appmenus/qubes-dispvm-xterm.desktop
|
|
||||||
/usr/share/qubes-appmenus/qubes-dispvm.directory
|
|
||||||
/usr/share/qubes-appmenus/qubes-start.desktop
|
|
||||||
/usr/share/qubes-appmenus/hvm
|
|
||||||
/usr/share/qubes/icons/*.png
|
|
||||||
/usr/bin/qvm-sync-appmenus
|
|
||||||
# Dom0 updates
|
# Dom0 updates
|
||||||
/etc/cron.daily/qubes-dom0-updates.cron
|
/etc/cron.daily/qubes-dom0-updates.cron
|
||||||
/etc/yum.real.repos.d/qubes-cached.repo
|
/etc/yum.real.repos.d/qubes-cached.repo
|
||||||
|
32
setup.py
@ -1,32 +0,0 @@
|
|||||||
# vim: fileencoding=utf-8
|
|
||||||
|
|
||||||
import setuptools
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
setuptools.setup(
|
|
||||||
name='qubeslinux',
|
|
||||||
version=open('version').read().strip(),
|
|
||||||
author='Invisible Things Lab',
|
|
||||||
author_email='woju@invisiblethingslab.com',
|
|
||||||
description='Qubes core-linux package',
|
|
||||||
license='GPL2+',
|
|
||||||
url='https://www.qubes-os.org/',
|
|
||||||
|
|
||||||
packages=('qubesappmenus',),
|
|
||||||
|
|
||||||
package_data = {
|
|
||||||
'qubesappmenus': ['test-data/*', '*.template'],
|
|
||||||
},
|
|
||||||
|
|
||||||
entry_points={
|
|
||||||
'console_scripts': [
|
|
||||||
'qvm-sync-appmenus = qubesappmenus.receive:main'
|
|
||||||
],
|
|
||||||
'qubes.ext': [
|
|
||||||
'qubesappmenus = qubesappmenus:AppmenusExtension'
|
|
||||||
],
|
|
||||||
'qubes.tests.extra': [
|
|
||||||
'qubesappmenus = qubesappmenus.tests:list_tests',
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|