Prechádzať zdrojové kódy

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

Wojtek Porczyk 8 rokov pred
rodič
commit
0f9ca47d90

+ 92 - 0
qubes/ext/gui.py

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

+ 3 - 1
qubes/tools/__init__.py

@@ -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 - 0
qubes/tools/qubes_monitor_layout_notify.py

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

+ 14 - 60
qubes/vm/qubesvm.py

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

+ 2 - 0
rpm_spec/core-dom0.spec

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

+ 1 - 0
setup.py

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