qubes/ext/guid: Move gui-related code to extension

This commit is contained in:
Wojtek Porczyk 2016-03-05 10:57:58 +01:00
parent 75dd882b83
commit 0f9ca47d90
6 changed files with 219 additions and 61 deletions

92
qubes/ext/gui.py Normal file
View File

@ -0,0 +1,92 @@
#!/usr/bin/env python
# vim: fileencoding=utf-8
#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2010-2016 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013-2016 Marek Marczykowski-Górecki
# <marmarek@invisiblethingslab.com>
# Copyright (C) 2014-2016 Wojtek Porczyk <woju@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 os
import subprocess
import qubes.config
import qubes.ext
class GUI(qubes.ext.Extension):
@qubes.ext.handler('domain-start', 'domain-cmd-pre-start')
def start_guid(self, vm, start_guid, preparing_dvm=False,
extra_guid_args=None, **kwargs):
'''Launch gui daemon.
GUI daemon securely displays windows from domain.
''' # pylint: disable=no-self-use,unused-argument
if not start_guid or preparing_dvm \
or not os.path.exists('/var/run/shm.id'):
return
if not vm.features.check_with_template('gui', not vm.hvm):
vm.log.debug('Not starting gui daemon, disabled by features')
return
if not os.getenv('DISPLAY'):
vm.log.error('Not starting gui daemon, no DISPLAY set')
return
vm.log.info('Starting gui daemon')
guid_cmd = [qubes.config.system_path['qubes_guid_path'],
'-d', str(vm.xid), '-N', vm.name,
'-c', vm.label.color,
'-i', vm.label.icon_path,
'-l', str(vm.label.index)]
if extra_guid_args is not None:
guid_cmd += extra_guid_args
if vm.debug:
guid_cmd += ['-v', '-v']
# elif not verbose:
else:
guid_cmd += ['-q']
retcode = subprocess.call(guid_cmd)
if retcode != 0:
raise qubes.exc.QubesVMError(vm,
'Cannot start qubes-guid for domain {!r}'.format(vm.name))
vm.notify_monitor_layout()
vm.wait_for_session()
@qubes.ext.handler('monitor-layout-change')
def on_monitor_layout_change(self, vm, monitor_layout):
# pylint: disable=no-self-use
if vm.features.check_with_template('no-monitor-layout', False) \
or not vm.is_running():
return
pipe = vm.run('QUBESRPC qubes.SetMonitorLayout dom0',
passio_popen=True, wait=True)
pipe.stdin.write(''.join(monitor_layout))
pipe.stdin.close()
pipe.wait()

View File

@ -152,6 +152,7 @@ class QubesArgumentParser(argparse.ArgumentParser):
want_app_no_instance=False, want_app_no_instance=False,
want_force_root=False, want_force_root=False,
want_vm=False, want_vm=False,
want_vm_optional=False,
want_vm_all=False, want_vm_all=False,
**kwargs): **kwargs):
@ -161,6 +162,7 @@ class QubesArgumentParser(argparse.ArgumentParser):
self._want_app_no_instance = want_app_no_instance self._want_app_no_instance = want_app_no_instance
self._want_force_root = want_force_root self._want_force_root = want_force_root
self._want_vm = want_vm self._want_vm = want_vm
self._want_vm_optional = want_vm_optional
self._want_vm_all = want_vm_all self._want_vm_all = want_vm_all
if self._want_app: if self._want_app:
@ -193,7 +195,7 @@ class QubesArgumentParser(argparse.ArgumentParser):
nargs = '?' nargs = '?'
else: else:
vmchoice = self vmchoice = self
nargs = None nargs = '?' if self._want_vm_optional else None
vmchoice.add_argument('vm', metavar='VMNAME', vmchoice.add_argument('vm', metavar='VMNAME',
action='store', nargs=nargs, action='store', nargs=nargs,

View File

@ -0,0 +1,107 @@
#!/usr/bin/python2 -O
# vim: fileencoding=utf-8
#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2015-2016 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2015-2016 Wojtek Porczyk <woju@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.
#
'''qvm-create - Create new Qubes OS store'''
# TODO allow to set properties and create domains
import re
import subprocess
import threading
import qubes.tools
# "LVDS connected 1024x768+0+0 (normal left inverted right) 304mm x 228mm"
REGEX_OUTPUT = re.compile(r'''
(?x) # ignore whitespace
^ # start of string
(?P<output>[A-Za-z0-9\-]*)[ ] # LVDS VGA etc
(?P<connect>(dis)?connected)[ ]# dis/connected
(?P<primary>(primary)?)[ ]?
(( # a group
(?P<width>\d+)x # either 1024x768+0+0
(?P<height>\d+)[+]
(?P<x>\d+)[+]
(?P<y>\d+)
)|[\D]) # or not a digit
.* # ignore rest of line
''')
def get_monitor_layout():
outputs = []
for line in subprocess.Popen(
['xrandr', '-q'], stdout=subprocess.PIPE).stdout:
if not line.startswith("Screen") and not line.startswith(" "):
output_params = REGEX_OUTPUT.match(line).groupdict()
if output_params['width']:
outputs.append("%s %s %s %s\n" % (
output_params['width'],
output_params['height'],
output_params['x'],
output_params['y']))
return outputs
parser = qubes.tools.QubesArgumentParser(
description='Send monitor layout to one qube or to all of them',
want_app=True,
want_vm=True,
want_vm_optional=True)
def main(args=None):
'''Main routine of :program:`qubes-create`.
:param list args: Optional arguments to override those delivered from \
command line.
'''
args = parser.parse_args(args)
monitor_layout = get_monitor_layout()
# notify only if we've got a non-empty monitor_layout or else we
# break proper qube resolution set by gui-agent
if not monitor_layout:
args.app.log.error('cannot get monitor layout')
return 1
if args.vm:
args.vm.fire_event('monitor-layout-change', monitor_layout)
else:
subprocess.check_call(['killall', '-HUP', 'qubes-guid'])
threads = []
for vm in args.app.domains:
thread = threading.Thread(name=vm.name, target=vm.fire_event,
args=('monitor-layout-change',),
kwargs={'monitor_layout': monitor_layout})
threads.append(thread)
thread.run()
for thread in threads:
thread.join()
return 0

View File

@ -624,6 +624,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self.log.info('Starting {}'.format(self.name)) self.log.info('Starting {}'.format(self.name))
self.fire_event_pre('domain-pre-start', preparing_dvm=preparing_dvm,
start_guid=start_guid, mem_required=mem_required)
self.verify_files() self.verify_files()
if self.netvm is not None: if self.netvm is not None:
@ -660,13 +663,16 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self.libvirt_domain.createWithFlags(libvirt.VIR_DOMAIN_START_PAUSED) self.libvirt_domain.createWithFlags(libvirt.VIR_DOMAIN_START_PAUSED)
try: try:
if preparing_dvm: self.fire_event('domain-spawn',
self.features['dvm'] = True preparing_dvm=preparing_dvm, start_guid=start_guid)
self.log.info('Setting Qubes DB info for the VM') self.log.info('Setting Qubes DB info for the VM')
self.start_qubesdb() self.start_qubesdb()
self.create_qdb_entries() self.create_qdb_entries()
if preparing_dvm:
self.qdb.write('/dvm', '1')
self.log.info('Updating firewall rules') self.log.info('Updating firewall rules')
for vm in self.app.domains: for vm in self.app.domains:
@ -691,10 +697,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
if not preparing_dvm: if not preparing_dvm:
self.start_qrexec_daemon() self.start_qrexec_daemon()
if start_guid and not preparing_dvm \
and os.path.exists('/var/run/shm.id'):
self.start_guid()
self.fire_event('domain-start', self.fire_event('domain-start',
preparing_dvm=preparing_dvm, start_guid=start_guid) preparing_dvm=preparing_dvm, start_guid=start_guid)
@ -850,9 +852,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
raise qubes.exc.QubesVMError( raise qubes.exc.QubesVMError(
self, 'Domain {!r}: qrexec not connected'.format(self.name)) self, 'Domain {!r}: qrexec not connected'.format(self.name))
if gui and os.getenv("DISPLAY") is not None \ self.fire_event_pre('domain-cmd-pre-run', start_guid=gui)
and not self.is_guid_running():
self.start_guid()
args = [qubes.config.system_path['qrexec_client_path'], args = [qubes.config.system_path['qrexec_client_path'],
'-d', str(self.name), '-d', str(self.name),
@ -951,46 +951,17 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
return qmemman_client return qmemman_client
def start_guid(self, extra_guid_args=None):
'''Launch gui daemon.
GUI daemon securely displays windows from domain.
:param list extra_guid_args: Extra argv to pass to :program:`guid`.
'''
self.log.info('Starting gui daemon')
guid_cmd = [qubes.config.system_path['qubes_guid_path'],
'-d', str(self.xid), "-N", self.name,
'-c', self.label.color,
'-i', self.label.icon_path,
'-l', str(self.label.index)]
if extra_guid_args is not None:
guid_cmd += extra_guid_args
if self.debug:
guid_cmd += ['-v', '-v']
# elif not verbose:
else:
guid_cmd += ['-q']
retcode = subprocess.call(guid_cmd)
if retcode != 0:
raise qubes.exc.QubesVMError(self,
'Cannot start qubes-guid for domain {!r}'.format(self.name))
self.notify_monitor_layout()
self.wait_for_session()
def start_qrexec_daemon(self): def start_qrexec_daemon(self):
'''Start qrexec daemon. '''Start qrexec daemon.
:raises OSError: when starting fails. :raises OSError: when starting fails.
''' '''
if not self.features.check_with_template('qrexec', not self.hvm):
self.log.debug(
'Not starting the qrexec daemon, disabled by features')
return
self.log.debug('Starting the qrexec daemon') self.log.debug('Starting the qrexec daemon')
qrexec_args = [str(self.xid), self.name, self.default_user] qrexec_args = [str(self.xid), self.name, self.default_user]
if not self.debug: if not self.debug:
@ -1036,23 +1007,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
p.communicate(input=self.default_user) p.communicate(input=self.default_user)
# TODO event, extension
def notify_monitor_layout(self):
try:
import qubes.monitorlayoutnotify
monitor_layout = qubes.monitorlayoutnotify.get_monitor_layout()
# notify qube only if we've got a non-empty monitor_layout or else we
# break proper qube resolution set by gui-agent
if not monitor_layout:
return
self.log.info('Sending monitor layout')
qubes.monitorlayoutnotify.notify_vm(self, monitor_layout)
except ImportError:
self.log.warning('Monitor layout notify module not installed')
# TODO move to storage # TODO move to storage
def create_on_disk(self, source_template=None): def create_on_disk(self, source_template=None):
'''Create files needed for VM. '''Create files needed for VM.

View File

@ -231,6 +231,7 @@ fi
%{python_sitelib}/qubes/tools/__init__.py* %{python_sitelib}/qubes/tools/__init__.py*
%{python_sitelib}/qubes/tools/qmemmand.py* %{python_sitelib}/qubes/tools/qmemmand.py*
%{python_sitelib}/qubes/tools/qubes_create.py* %{python_sitelib}/qubes/tools/qubes_create.py*
%{python_sitelib}/qubes/tools/qubes_monitor_layout_notify.py*
%{python_sitelib}/qubes/tools/qubes_prefs.py* %{python_sitelib}/qubes/tools/qubes_prefs.py*
%{python_sitelib}/qubes/tools/qvm_create.py* %{python_sitelib}/qubes/tools/qvm_create.py*
%{python_sitelib}/qubes/tools/qvm_kill.py* %{python_sitelib}/qubes/tools/qvm_kill.py*
@ -243,6 +244,7 @@ fi
%dir %{python_sitelib}/qubes/ext %dir %{python_sitelib}/qubes/ext
%{python_sitelib}/qubes/ext/__init__.py* %{python_sitelib}/qubes/ext/__init__.py*
%{python_sitelib}/qubes/ext/gui.py*
%{python_sitelib}/qubes/ext/qubesmanager.py* %{python_sitelib}/qubes/ext/qubesmanager.py*
%dir %{python_sitelib}/qubes/tests %dir %{python_sitelib}/qubes/tests

View File

@ -36,6 +36,7 @@ if __name__ == '__main__':
], ],
'qubes.ext': [ 'qubes.ext': [
'qubes.ext.qubesmanager = qubes.ext.qubesmanager:QubesManager', 'qubes.ext.qubesmanager = qubes.ext.qubesmanager:QubesManager',
'qubes.ext.gui = qubes.ext.gui:GUI',
], ],
} }
) )