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
This commit is contained in:
Marek Marczykowski-Górecki 2017-05-17 15:47:13 +02:00
parent ea6f47bf33
commit 22cf6df02f
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
43 changed files with 2 additions and 2482 deletions

View File

@ -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

View File

@ -1,8 +0,0 @@
[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Name=Explorer
Comment=Browse files
Categories=Utility;Core;
Exec=explorer

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -1,5 +0,0 @@
[Desktop Entry]
Encoding=UTF-8
Type=Directory
Name=DisposableVM
Icon=dispvm-red

View File

@ -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;

View File

@ -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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

View File

@ -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.

View File

@ -1 +0,0 @@
dom0-update-avail icon from gnome-packagekit project distributed under GPLv2

View File

@ -1 +0,0 @@
Color padlock images downloaded from www.openclipart.org

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

View File

@ -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)

View File

@ -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;

View File

@ -1,5 +0,0 @@
[Desktop Entry]
Encoding=UTF-8
Type=Directory
Name=ServiceVM: %VMNAME%
Icon=%XDGICON%

View File

@ -1,5 +0,0 @@
[Desktop Entry]
Encoding=UTF-8
Type=Directory
Name=Template: %VMNAME%
Icon=%XDGICON%

View File

@ -1,5 +0,0 @@
[Desktop Entry]
Encoding=UTF-8
Type=Directory
Name=Domain: %VMNAME%
Icon=%XDGICON%

View File

@ -1 +0,0 @@
/usr/bin/qvm-sync-appmenus

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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,
)

View File

@ -76,7 +76,6 @@ ln -sf . %{name}-%{version}
%setup -T -D
%build
python setup.py build
(cd dom0-updates; make)
(cd qrexec; make)
(cd file-copy-vm; make)
@ -84,17 +83,10 @@ python setup.py build
%install
### Appmenus
# force /usr/bin before /bin to have /usr/bin/python instead of /bin/python
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/
## Appmenus
install -d $RPM_BUILD_ROOT/etc/qubes-rpc/policy
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
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
@ -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-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
(cd doc; make DESTDIR=$RPM_BUILD_ROOT install)
@ -172,13 +158,6 @@ fi
%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
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
# 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
fi
@ -208,27 +181,7 @@ rm -f /lib/udev/rules.d/69-xorg-vmmouse.rules
chmod -x /etc/grub.d/10_linux
%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/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
/etc/cron.daily/qubes-dom0-updates.cron
/etc/yum.real.repos.d/qubes-cached.repo

View File

@ -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',
],
}
)