qubes/ext/guid: Move gui-related code to extension
This commit is contained in:
parent
75dd882b83
commit
0f9ca47d90
92
qubes/ext/gui.py
Normal file
92
qubes/ext/gui.py
Normal 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()
|
@ -152,6 +152,7 @@ class QubesArgumentParser(argparse.ArgumentParser):
|
||||
want_app_no_instance=False,
|
||||
want_force_root=False,
|
||||
want_vm=False,
|
||||
want_vm_optional=False,
|
||||
want_vm_all=False,
|
||||
**kwargs):
|
||||
|
||||
@ -161,6 +162,7 @@ class QubesArgumentParser(argparse.ArgumentParser):
|
||||
self._want_app_no_instance = want_app_no_instance
|
||||
self._want_force_root = want_force_root
|
||||
self._want_vm = want_vm
|
||||
self._want_vm_optional = want_vm_optional
|
||||
self._want_vm_all = want_vm_all
|
||||
|
||||
if self._want_app:
|
||||
@ -193,7 +195,7 @@ class QubesArgumentParser(argparse.ArgumentParser):
|
||||
nargs = '?'
|
||||
else:
|
||||
vmchoice = self
|
||||
nargs = None
|
||||
nargs = '?' if self._want_vm_optional else None
|
||||
|
||||
vmchoice.add_argument('vm', metavar='VMNAME',
|
||||
action='store', nargs=nargs,
|
||||
|
107
qubes/tools/qubes_monitor_layout_notify.py
Normal file
107
qubes/tools/qubes_monitor_layout_notify.py
Normal 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
|
@ -624,6 +624,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
try:
|
||||
if preparing_dvm:
|
||||
self.features['dvm'] = True
|
||||
self.fire_event('domain-spawn',
|
||||
preparing_dvm=preparing_dvm, start_guid=start_guid)
|
||||
|
||||
self.log.info('Setting Qubes DB info for the VM')
|
||||
self.start_qubesdb()
|
||||
self.create_qdb_entries()
|
||||
|
||||
if preparing_dvm:
|
||||
self.qdb.write('/dvm', '1')
|
||||
|
||||
self.log.info('Updating firewall rules')
|
||||
|
||||
for vm in self.app.domains:
|
||||
@ -691,10 +697,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
if not preparing_dvm:
|
||||
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',
|
||||
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(
|
||||
self, 'Domain {!r}: qrexec not connected'.format(self.name))
|
||||
|
||||
if gui and os.getenv("DISPLAY") is not None \
|
||||
and not self.is_guid_running():
|
||||
self.start_guid()
|
||||
self.fire_event_pre('domain-cmd-pre-run', start_guid=gui)
|
||||
|
||||
args = [qubes.config.system_path['qrexec_client_path'],
|
||||
'-d', str(self.name),
|
||||
@ -951,46 +951,17 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
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):
|
||||
'''Start qrexec daemon.
|
||||
|
||||
: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')
|
||||
qrexec_args = [str(self.xid), self.name, self.default_user]
|
||||
if not self.debug:
|
||||
@ -1036,23 +1007,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
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
|
||||
def create_on_disk(self, source_template=None):
|
||||
'''Create files needed for VM.
|
||||
|
@ -231,6 +231,7 @@ fi
|
||||
%{python_sitelib}/qubes/tools/__init__.py*
|
||||
%{python_sitelib}/qubes/tools/qmemmand.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/qvm_create.py*
|
||||
%{python_sitelib}/qubes/tools/qvm_kill.py*
|
||||
@ -243,6 +244,7 @@ fi
|
||||
|
||||
%dir %{python_sitelib}/qubes/ext
|
||||
%{python_sitelib}/qubes/ext/__init__.py*
|
||||
%{python_sitelib}/qubes/ext/gui.py*
|
||||
%{python_sitelib}/qubes/ext/qubesmanager.py*
|
||||
|
||||
%dir %{python_sitelib}/qubes/tests
|
||||
|
Loading…
Reference in New Issue
Block a user