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_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,
|
||||||
|
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.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.
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user