Quellcode durchsuchen

Add 'gui-emulated' feature

Add an explicit method for forcing emulated VGA output. Previously it
was possible only by removing `gui` feature (setting it to false had a
different effect), or enabling debug mode.
Using lack of a feature as a third state was a bad idea.

QubesOS/qubes-issues#3585
Marek Marczykowski-Górecki vor 5 Jahren
Ursprung
Commit
4d61407f5d

+ 21 - 6
doc/manpages/qvm-features.rst

@@ -59,13 +59,28 @@ List of known features
 gui
 ^^^
 
-Qube provide any kind of GUI. Setting this feature to :py:obj:`False` disable
-GUI for given qubes - both gui-agent based and emulated VGA based one. Setting
-this feature to :py:obj:`True` enable gui-agent based GUI (i.e. with support of
-tools installed inside of qube). Not setting this feature at all, enable showing
-VGA emulated output.
+Qube has gui-agent installed. Setting this feature to :py:obj:`True` enables GUI
+based on a gui-agent installed inside the VM.
+See also `gui-emulated` feature.
 
-Default: show emulated VGA output only
+If neither `gui` nor `gui-emulated` is set, emulated VGA is used (if
+applicable for given VM virtualization mode).
+
+gui-emulated
+^^^^^^^^^^^^
+
+Qube provides GUI through emulated VGA. Setting this feature to
+:py:obj:`True` enables emulated VGA output. Note that when gui-agent connects to
+actual VM, emulated VGA output is closed (unless `debug` property is set to
+:py:obj:`True`). It's possible to open emulated VGA output for a running qube,
+regardless of this feature, using `qvm-start-gui --force-stubdomain QUBE_NAME`
+command.
+
+This feature is applicable only when qube's `virt_mode` is set to `hvm`.
+See also `gui` feature.
+
+If neither `gui` nor `gui-emulated` is set, emulated VGA is used (if
+applicable for given VM virtualization mode).
 
 qrexec
 ^^^^^^

+ 37 - 0
qubesadmin/tests/tools/qvm_start_gui.py

@@ -309,6 +309,43 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
         self.app.expected_calls[
             ('test-vm', 'admin.vm.feature.CheckWithTemplate', 'gui', None)] = \
             b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
+        self.app.expected_calls[
+            ('test-vm', 'admin.vm.feature.CheckWithTemplate', 'gui-emulated',
+            None)] = \
+            b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
+        proc_mock = unittest.mock.Mock()
+        with unittest.mock.patch('asyncio.create_subprocess_exec',
+                lambda *args: self.mock_coroutine(proc_mock, *args)):
+            with unittest.mock.patch.object(self.launcher,
+                    'common_guid_args', lambda vm: []):
+                loop.run_until_complete(self.launcher.start_gui_for_stubdomain(
+                    self.app.domains['test-vm']))
+                # common arguments dropped for simplicity
+                proc_mock.assert_called_once_with('-d', '3001', '-t', '3000')
+
+        self.assertAllCalled()
+
+    def test_031_start_gui_for_stubdomain_forced(self):
+        loop = asyncio.new_event_loop()
+        asyncio.set_event_loop(loop)
+        self.addCleanup(loop.close)
+
+        self.app.expected_calls[
+            ('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00test-vm class=AppVM state=Running\n'
+        self.app.expected_calls[
+            ('test-vm', 'admin.vm.property.Get', 'xid', None)] = \
+                b'0\x00default=False type=int 3000'
+        self.app.expected_calls[
+            ('test-vm', 'admin.vm.property.Get', 'stubdom_xid', None)] = \
+                b'0\x00default=False type=int 3001'
+        # self.app.expected_calls[
+        #     ('test-vm', 'admin.vm.feature.CheckWithTemplate', 'gui', None)] = \
+        #     b'0\x00'
+        self.app.expected_calls[
+            ('test-vm', 'admin.vm.feature.CheckWithTemplate', 'gui-emulated',
+            None)] = \
+            b'0\x001'
         proc_mock = unittest.mock.Mock()
         with unittest.mock.patch('asyncio.create_subprocess_exec',
                 lambda *args: self.mock_coroutine(proc_mock, *args)):

+ 9 - 5
qubesadmin/tools/qvm_start_gui.py

@@ -214,9 +214,13 @@ class GUILauncher(object):
         This function is a coroutine.
         '''
         want_stubdom = force
-        # if no 'gui' feature set at all, assume no gui agent installed
         if not want_stubdom and \
-                vm.features.check_with_template('gui', None) is None:
+                vm.features.check_with_template('gui-emulated', False):
+            want_stubdom = True
+        # if no 'gui' or 'gui-emulated' feature set at all, use emulated GUI
+        if not want_stubdom and \
+                vm.features.check_with_template('gui', None) is None and \
+                vm.features.check_with_template('gui-emulated', None) is None:
             want_stubdom = True
         if not want_stubdom and vm.debug:
             want_stubdom = True
@@ -240,13 +244,13 @@ class GUILauncher(object):
         :param force_stubdom: Force GUI daemon for stubdomain, even if the
         one for target AppVM is running.
         '''
-        if not vm.features.check_with_template('gui', True):
-            return
-
         if vm.virt_mode == 'hvm':
             yield from self.start_gui_for_stubdomain(vm,
                 force=force_stubdom)
 
+        if not vm.features.check_with_template('gui', True):
+            return
+
         if not os.path.exists(self.guid_pidfile(vm.xid)):
             yield from self.start_gui_for_vm(vm, monitor_layout=monitor_layout)