Merge remote-tracking branch 'origin/pr/108'
* origin/pr/108: app: switch get_local_name method to property tests: qvm_start_gui: make PEP8 happier tests: fix with respect to gui properties app: get_local_name set/from self attribute app: fix missing docstring for get_local_name qvm-start-gui: simplify log info for start_gui function guivm: use getattr instead of try/except and direct property access qvm-start-gui: handle GuiVM Make PEP8 happier
This commit is contained in:
commit
33f04bb49a
@ -164,16 +164,17 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
||||
#: cache for available storage pool drivers and options to create them
|
||||
self._pool_drivers = None
|
||||
self.log = logging.getLogger('app')
|
||||
self._local_name = None
|
||||
|
||||
def list_vmclass(self):
|
||||
"""Call Qubesd in order to obtain the vm classes list"""
|
||||
vmclass = self.qubesd_call('dom0', 'admin.vmclass.List')\
|
||||
vmclass = self.qubesd_call('dom0', 'admin.vmclass.List') \
|
||||
.decode().splitlines()
|
||||
return sorted(vmclass)
|
||||
|
||||
def list_deviceclass(self):
|
||||
"""Call Qubesd in order to obtain the device classes list"""
|
||||
deviceclasses = self.qubesd_call('dom0', 'admin.deviceclass.List')\
|
||||
deviceclasses = self.qubesd_call('dom0', 'admin.deviceclass.List') \
|
||||
.decode().splitlines()
|
||||
return sorted(deviceclasses)
|
||||
|
||||
@ -226,6 +227,14 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
||||
""" Remove a storage pool """
|
||||
self.qubesd_call('dom0', 'admin.pool.Remove', name, None)
|
||||
|
||||
@property
|
||||
def local_name(self):
|
||||
""" Get localhost name """
|
||||
if not self._local_name:
|
||||
self._local_name = os.uname()[1]
|
||||
|
||||
return self._local_name
|
||||
|
||||
def get_label(self, label):
|
||||
"""Get label as identified by index or name
|
||||
|
||||
@ -715,8 +724,8 @@ class QubesRemote(QubesBase):
|
||||
kwargs.setdefault('stderr', subprocess.PIPE)
|
||||
proc = subprocess.Popen(
|
||||
[qubesadmin.config.QREXEC_CLIENT_VM] +
|
||||
qrexec_opts +
|
||||
[dest or '', service] +
|
||||
(shlex.split(localcmd) if localcmd else []),
|
||||
qrexec_opts +
|
||||
[dest or '', service] +
|
||||
(shlex.split(localcmd) if localcmd else []),
|
||||
**kwargs)
|
||||
return proc
|
||||
|
@ -1 +0,0 @@
|
||||
/home/user/qubes-builder/qubes-src/core-admin-client/qubesadmin
|
@ -42,7 +42,7 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
b'0\x00test-vm class=AppVM state=Running\n'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'label', None)] = \
|
||||
b'0\x00default=False type=label red'
|
||||
b'0\x00default=False type=label red'
|
||||
|
||||
proc_mock.side_effect = [
|
||||
b'KWIN_RUNNING = 0x1\n',
|
||||
@ -53,8 +53,9 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
|
||||
args = self.launcher.kde_guid_args(self.app.domains['test-vm'])
|
||||
self.assertEqual(args, ['-T', '-p',
|
||||
'_KDE_NET_WM_COLOR_SCHEME=s:' +
|
||||
os.path.expanduser('~/.local/share/qubes-kde/red.colors')])
|
||||
'_KDE_NET_WM_COLOR_SCHEME=s:' +
|
||||
os.path.expanduser(
|
||||
'~/.local/share/qubes-kde/red.colors')])
|
||||
|
||||
self.assertAllCalled()
|
||||
|
||||
@ -77,10 +78,10 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
b'0\x00test-vm class=AppVM state=Running\n'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'label', None)] = \
|
||||
b'0\x00default=False type=label red'
|
||||
b'0\x00default=False type=label red'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'debug', None)] = \
|
||||
b'0\x00default=False type=bool False'
|
||||
b'0\x00default=False type=bool False'
|
||||
self.app.expected_calls[
|
||||
('dom0', 'admin.label.Get', 'red', None)] = \
|
||||
b'0\x000xff0000'
|
||||
@ -89,7 +90,7 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
b'0\x001'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.feature.CheckWithTemplate',
|
||||
'rpc-clipboard', None)] = \
|
||||
'rpc-clipboard', None)] = \
|
||||
b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
|
||||
|
||||
with unittest.mock.patch.object(self.launcher, 'kde_guid_args') as \
|
||||
@ -111,10 +112,10 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
b'0\x00test-vm class=AppVM state=Running\n'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'label', None)] = \
|
||||
b'0\x00default=False type=label red'
|
||||
b'0\x00default=False type=label red'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'debug', None)] = \
|
||||
b'0\x00default=False type=bool True'
|
||||
b'0\x00default=False type=bool True'
|
||||
self.app.expected_calls[
|
||||
('dom0', 'admin.label.Get', 'red', None)] = \
|
||||
b'0\x000xff0000'
|
||||
@ -123,7 +124,7 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
b'0\x001'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.feature.CheckWithTemplate',
|
||||
'rpc-clipboard', None)] = \
|
||||
'rpc-clipboard', None)] = \
|
||||
b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
|
||||
|
||||
with unittest.mock.patch.object(self.launcher, 'kde_guid_args') as \
|
||||
@ -145,10 +146,10 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
b'0\x00test-vm class=AppVM state=Running\n'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'label', None)] = \
|
||||
b'0\x00default=False type=label red'
|
||||
b'0\x00default=False type=label red'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'debug', None)] = \
|
||||
b'0\x00default=False type=bool False'
|
||||
b'0\x00default=False type=bool False'
|
||||
self.app.expected_calls[
|
||||
('dom0', 'admin.label.Get', 'red', None)] = \
|
||||
b'0\x000xff0000'
|
||||
@ -157,7 +158,7 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
b'0\x001'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.feature.CheckWithTemplate',
|
||||
'rpc-clipboard', None)] = \
|
||||
'rpc-clipboard', None)] = \
|
||||
b'0\x001'
|
||||
|
||||
with unittest.mock.patch.object(self.launcher, 'kde_guid_args') as \
|
||||
@ -187,16 +188,16 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
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'
|
||||
b'0\x00default=False type=int 3000'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'virt_mode', None)] = \
|
||||
b'0\x00default=False type=str pv'
|
||||
b'0\x00default=False type=str pv'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.feature.CheckWithTemplate',
|
||||
'no-monitor-layout', None)] = \
|
||||
'no-monitor-layout', None)] = \
|
||||
b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
|
||||
with unittest.mock.patch.object(self.launcher,
|
||||
'common_guid_args', lambda vm: []):
|
||||
'common_guid_args', lambda vm: []):
|
||||
loop.run_until_complete(self.launcher.start_gui_for_vm(
|
||||
self.app.domains['test-vm']))
|
||||
# common arguments dropped for simplicity
|
||||
@ -218,22 +219,22 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
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'
|
||||
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'
|
||||
b'0\x00default=False type=int 3001'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'virt_mode', None)] = \
|
||||
b'0\x00default=False type=str hvm'
|
||||
b'0\x00default=False type=str hvm'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'debug', None)] = \
|
||||
b'0\x00default=False type=bool False'
|
||||
b'0\x00default=False type=bool False'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.feature.CheckWithTemplate',
|
||||
'no-monitor-layout', None)] = \
|
||||
'no-monitor-layout', None)] = \
|
||||
b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
|
||||
with unittest.mock.patch.object(self.launcher,
|
||||
'common_guid_args', lambda vm: []):
|
||||
'common_guid_args', lambda vm: []):
|
||||
loop.run_until_complete(self.launcher.start_gui_for_vm(
|
||||
self.app.domains['test-vm']))
|
||||
# common arguments dropped for simplicity
|
||||
@ -254,19 +255,19 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
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'
|
||||
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'
|
||||
b'0\x00default=False type=int 3001'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'virt_mode', None)] = \
|
||||
b'0\x00default=False type=str hvm'
|
||||
b'0\x00default=False type=str hvm'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'debug', None)] = \
|
||||
b'0\x00default=False type=bool False'
|
||||
b'0\x00default=False type=bool False'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.feature.CheckWithTemplate',
|
||||
'no-monitor-layout', None)] = \
|
||||
'no-monitor-layout', None)] = \
|
||||
b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
|
||||
pidfile = tempfile.NamedTemporaryFile()
|
||||
pidfile.write(b'1234\n')
|
||||
@ -275,9 +276,11 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
|
||||
patch_proc = unittest.mock.patch('asyncio.create_subprocess_exec')
|
||||
patch_args = unittest.mock.patch.object(self.launcher,
|
||||
'common_guid_args', lambda vm: [])
|
||||
'common_guid_args',
|
||||
lambda vm: [])
|
||||
patch_pidfile = unittest.mock.patch.object(self.launcher,
|
||||
'guid_pidfile', lambda vm: pidfile.name)
|
||||
'guid_pidfile',
|
||||
lambda vm: pidfile.name)
|
||||
try:
|
||||
mock_proc = patch_proc.start()
|
||||
patch_args.start()
|
||||
@ -302,22 +305,23 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
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'
|
||||
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'
|
||||
b'0\x00default=False type=int 3001'
|
||||
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)] = \
|
||||
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)):
|
||||
lambda *args: self.mock_coroutine(proc_mock,
|
||||
*args)):
|
||||
with unittest.mock.patch.object(self.launcher,
|
||||
'common_guid_args', lambda vm: []):
|
||||
'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
|
||||
@ -335,22 +339,23 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
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'
|
||||
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'
|
||||
b'0\x00default=False type=int 3001'
|
||||
# self.app.expected_calls[
|
||||
# ('test-vm', 'admin.vm.feature.CheckWithTemplate', 'gui', None)] = \
|
||||
# b'0\x00'
|
||||
# ('test-vm', 'admin.vm.feature.CheckWithTemplate', 'gui', None)] = \
|
||||
# b'0\x00'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.feature.CheckWithTemplate', 'gui-emulated',
|
||||
None)] = \
|
||||
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)):
|
||||
lambda *args: self.mock_coroutine(proc_mock,
|
||||
*args)):
|
||||
with unittest.mock.patch.object(self.launcher,
|
||||
'common_guid_args', lambda vm: []):
|
||||
'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
|
||||
@ -369,7 +374,8 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
|
||||
self.app.expected_calls[
|
||||
('dom0', 'admin.vm.List', None, None)] = \
|
||||
b'0\x00test-vm class=AppVM state=Running\n'
|
||||
b'0\x00test-vm class=AppVM state=Running\n' \
|
||||
b'gui-vm class=AppVM state=Running'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.List', None, None)] = \
|
||||
b'0\x00test-vm class=AppVM state=Running\n'
|
||||
@ -378,18 +384,22 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
|
||||
b'0\x00True'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.feature.CheckWithTemplate',
|
||||
'no-monitor-layout', None)] = \
|
||||
'no-monitor-layout', None)] = \
|
||||
b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'virt_mode', None)] = \
|
||||
b'0\x00default=False type=str hvm'
|
||||
b'0\x00default=False type=str hvm'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'xid', None)] = \
|
||||
b'0\x00default=False type=int 3000'
|
||||
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'
|
||||
b'0\x00default=False type=int 3001'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'guivm', None)] = \
|
||||
b'0\x00default=False type=vm gui-vm'
|
||||
|
||||
self.app._local_name = 'gui-vm'
|
||||
vm = self.app.domains['test-vm']
|
||||
mock_start_vm = unittest.mock.Mock()
|
||||
mock_start_stubdomain = unittest.mock.Mock()
|
||||
@ -440,7 +450,7 @@ VGA1 disconnected (normal left inverted right x axis y axis)
|
||||
VIRTUAL1 disconnected (normal left inverted right x axis y axis)
|
||||
'''.splitlines()
|
||||
self.assertEqual(qubesadmin.tools.qvm_start_gui.get_monitor_layout(),
|
||||
['1920 1200 0 0\n'])
|
||||
['1920 1200 0 0\n'])
|
||||
|
||||
@unittest.mock.patch('subprocess.Popen')
|
||||
def test_051_get_monitor_layout_multiple(self, proc_mock):
|
||||
@ -449,7 +459,7 @@ LVDS1 connected 1600x900+0+0 (normal left inverted right x axis y axis)
|
||||
VGA1 connected 1280x1024+1600+0 (normal left inverted right x axis y axis)
|
||||
'''.splitlines()
|
||||
self.assertEqual(qubesadmin.tools.qvm_start_gui.get_monitor_layout(),
|
||||
['1600 900 0 0\n', '1280 1024 1600 0\n'])
|
||||
['1600 900 0 0\n', '1280 1024 1600 0\n'])
|
||||
|
||||
@unittest.mock.patch('subprocess.Popen')
|
||||
def test_052_get_monitor_layout_hidpi1(self, proc_mock):
|
||||
@ -459,8 +469,9 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 372mm x
|
||||
'''.splitlines()
|
||||
dpi = 150
|
||||
self.assertEqual(qubesadmin.tools.qvm_start_gui.get_monitor_layout(),
|
||||
['2560 1920 0 0 {} {}\n'.format(
|
||||
int(2560/dpi*254/10), int(1920/dpi*254/10))])
|
||||
['2560 1920 0 0 {} {}\n'.format(
|
||||
int(2560 / dpi * 254 / 10),
|
||||
int(1920 / dpi * 254 / 10))])
|
||||
|
||||
@unittest.mock.patch('subprocess.Popen')
|
||||
def test_052_get_monitor_layout_hidpi2(self, proc_mock):
|
||||
@ -470,8 +481,9 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 310mm x
|
||||
'''.splitlines()
|
||||
dpi = 200
|
||||
self.assertEqual(qubesadmin.tools.qvm_start_gui.get_monitor_layout(),
|
||||
['2560 1920 0 0 {} {}\n'.format(
|
||||
int(2560/dpi*254/10), int(1920/dpi*254/10))])
|
||||
['2560 1920 0 0 {} {}\n'.format(
|
||||
int(2560 / dpi * 254 / 10),
|
||||
int(1920 / dpi * 254 / 10))])
|
||||
|
||||
@unittest.mock.patch('subprocess.Popen')
|
||||
def test_052_get_monitor_layout_hidpi3(self, proc_mock):
|
||||
@ -481,8 +493,9 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 206mm x
|
||||
'''.splitlines()
|
||||
dpi = 300
|
||||
self.assertEqual(qubesadmin.tools.qvm_start_gui.get_monitor_layout(),
|
||||
['2560 1920 0 0 {} {}\n'.format(
|
||||
int(2560/dpi*254/10), int(1920/dpi*254/10))])
|
||||
['2560 1920 0 0 {} {}\n'.format(
|
||||
int(2560 / dpi * 254 / 10),
|
||||
int(1920 / dpi * 254 / 10))])
|
||||
|
||||
def test_060_send_monitor_layout(self):
|
||||
loop = asyncio.new_event_loop()
|
||||
@ -497,7 +510,7 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 206mm x
|
||||
b'0\x00test-vm class=AppVM state=Running\n'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.feature.CheckWithTemplate',
|
||||
'no-monitor-layout', None)] = \
|
||||
'no-monitor-layout', None)] = \
|
||||
b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
|
||||
|
||||
vm = self.app.domains['test-vm']
|
||||
@ -524,7 +537,7 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 206mm x
|
||||
b'0\x00test-vm class=AppVM state=Running\n'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.feature.CheckWithTemplate',
|
||||
'no-monitor-layout', None)] = \
|
||||
'no-monitor-layout', None)] = \
|
||||
b'0\x00True'
|
||||
|
||||
vm = self.app.domains['test-vm']
|
||||
@ -553,7 +566,7 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 206mm x
|
||||
b'0\x00test-vm class=AppVM state=Halted\n'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.feature.CheckWithTemplate',
|
||||
'no-monitor-layout', None)] = \
|
||||
'no-monitor-layout', None)] = \
|
||||
b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
|
||||
|
||||
vm = self.app.domains['test-vm']
|
||||
@ -588,7 +601,7 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 206mm x
|
||||
b'0\x00default=False type=int 124'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.feature.CheckWithTemplate',
|
||||
'no-monitor-layout', None)] = \
|
||||
'no-monitor-layout', None)] = \
|
||||
b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
|
||||
|
||||
vm = self.app.domains['test-vm']
|
||||
@ -609,11 +622,11 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 206mm x
|
||||
loop.run_until_complete(self.launcher.send_monitor_layout(
|
||||
vm, layout=monitor_layout, startup=False))
|
||||
self.assertEqual(mock_guid_pidfile.mock_calls,
|
||||
[unittest.mock.call(123),
|
||||
unittest.mock.call(124)])
|
||||
[unittest.mock.call(123),
|
||||
unittest.mock.call(124)])
|
||||
self.assertEqual(mock_kill.mock_calls,
|
||||
[unittest.mock.call(1234, signal.SIGHUP),
|
||||
unittest.mock.call(1234, signal.SIGHUP)])
|
||||
[unittest.mock.call(1234, signal.SIGHUP),
|
||||
unittest.mock.call(1234, signal.SIGHUP)])
|
||||
self.assertAllCalled()
|
||||
|
||||
def test_070_send_monitor_layout_all(self):
|
||||
@ -625,8 +638,9 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 206mm x
|
||||
('dom0', 'admin.vm.List', None, None)] = \
|
||||
b'0\x00test-vm class=AppVM state=Running\n' \
|
||||
b'test-vm2 class=AppVM state=Running\n' \
|
||||
b'test-vm3 class=AppVM state=Runnig\n' \
|
||||
b'test-vm4 class=AppVM state=Halted\n'
|
||||
b'test-vm3 class=AppVM state=Running\n' \
|
||||
b'test-vm4 class=AppVM state=Halted\n' \
|
||||
b'gui-vm class=AppVM state=Running'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.List', None, None)] = \
|
||||
b'0\x00test-vm class=AppVM state=Running\n'
|
||||
@ -641,16 +655,33 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 206mm x
|
||||
b'0\x00test-vm4 class=AppVM state=Halted\n'
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.feature.CheckWithTemplate',
|
||||
'gui', None)] = \
|
||||
'gui', None)] = \
|
||||
b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
|
||||
self.app.expected_calls[
|
||||
('test-vm2', 'admin.vm.feature.CheckWithTemplate',
|
||||
'gui', None)] = \
|
||||
'gui', None)] = \
|
||||
b'0\x00True'
|
||||
self.app.expected_calls[
|
||||
('test-vm3', 'admin.vm.feature.CheckWithTemplate',
|
||||
'gui', None)] = \
|
||||
'gui', None)] = \
|
||||
b'0\x00'
|
||||
self.app.expected_calls[
|
||||
('gui-vm', 'admin.vm.property.Get', 'guivm', None)] = \
|
||||
b'0\x00default=True type=vm '
|
||||
self.app.expected_calls[
|
||||
('test-vm', 'admin.vm.property.Get', 'guivm', None)] = \
|
||||
b'0\x00default=False type=vm gui-vm'
|
||||
self.app.expected_calls[
|
||||
('test-vm2', 'admin.vm.property.Get', 'guivm', None)] = \
|
||||
b'0\x00default=False type=vm gui-vm'
|
||||
self.app.expected_calls[
|
||||
('test-vm3', 'admin.vm.property.Get', 'guivm', None)] = \
|
||||
b'0\x00default=False type=vm gui-vm'
|
||||
self.app.expected_calls[
|
||||
('test-vm4', 'admin.vm.property.Get', 'guivm', None)] = \
|
||||
b'0\x00default=False type=vm gui-vm'
|
||||
|
||||
self.app._local_name = 'gui-vm'
|
||||
|
||||
vm = self.app.domains['test-vm']
|
||||
vm2 = self.app.domains['test-vm2']
|
||||
@ -674,7 +705,7 @@ HDMI1 connected 2560x1920+0+0 (normal left inverted right x axis y axis) 206mm x
|
||||
# test-vm3 not called b/c feature 'gui' set to false
|
||||
# test-vm4 not called b/c not running
|
||||
self.assertCountEqual(mock_send_monitor_layout.mock_calls,
|
||||
[unittest.mock.call(vm, monitor_layout),
|
||||
unittest.mock.call(vm2, monitor_layout)])
|
||||
[unittest.mock.call(vm, monitor_layout),
|
||||
unittest.mock.call(vm2, monitor_layout)])
|
||||
mock_get_monior_layout.assert_called_once_with()
|
||||
self.assertAllCalled()
|
||||
|
@ -18,7 +18,7 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
''' GUI daemon launcher tool'''
|
||||
""" GUI daemon launcher tool"""
|
||||
|
||||
import os
|
||||
import signal
|
||||
@ -35,10 +35,12 @@ import qubesadmin
|
||||
import qubesadmin.exc
|
||||
import qubesadmin.tools
|
||||
import qubesadmin.vm
|
||||
|
||||
have_events = False
|
||||
try:
|
||||
# pylint: disable=wrong-import-position
|
||||
import qubesadmin.events
|
||||
|
||||
have_events = True
|
||||
except ImportError:
|
||||
pass
|
||||
@ -47,7 +49,7 @@ GUI_DAEMON_PATH = '/usr/bin/qubes-guid'
|
||||
QUBES_ICON_DIR = '/usr/share/icons/hicolor/128x128/devices'
|
||||
|
||||
# "LVDS connected 1024x768+0+0 (normal left inverted right) 304mm x 228mm"
|
||||
REGEX_OUTPUT = re.compile(r'''
|
||||
REGEX_OUTPUT = re.compile(r"""
|
||||
(?x) # ignore whitespace
|
||||
^ # start of string
|
||||
(?P<output>[A-Za-z0-9\-]*)[ ] # LVDS VGA etc
|
||||
@ -67,11 +69,11 @@ REGEX_OUTPUT = re.compile(r'''
|
||||
)?
|
||||
.* # ignore rest of line
|
||||
)? # everything after (dis)connect is optional
|
||||
''')
|
||||
""")
|
||||
|
||||
|
||||
def get_monitor_layout():
|
||||
'''Get list of monitors and their size/position'''
|
||||
"""Get list of monitors and their size/position"""
|
||||
outputs = []
|
||||
|
||||
for line in subprocess.Popen(
|
||||
@ -85,7 +87,7 @@ def get_monitor_layout():
|
||||
# don't provide real values for privacy reasons - see
|
||||
# #1951 for details
|
||||
dpi = (int(output_params['width']) * 254 //
|
||||
int(output_params['width_mm']) // 10)
|
||||
int(output_params['width_mm']) // 10)
|
||||
if dpi > 300:
|
||||
dpi = 300
|
||||
elif dpi > 200:
|
||||
@ -102,28 +104,29 @@ def get_monitor_layout():
|
||||
int(output_params['height']) * 254 // dpi // 10,
|
||||
)
|
||||
outputs.append("%s %s %s %s%s\n" % (
|
||||
output_params['width'],
|
||||
output_params['height'],
|
||||
output_params['x'],
|
||||
output_params['y'],
|
||||
phys_size,
|
||||
output_params['width'],
|
||||
output_params['height'],
|
||||
output_params['x'],
|
||||
output_params['y'],
|
||||
phys_size,
|
||||
))
|
||||
return outputs
|
||||
|
||||
|
||||
class GUILauncher(object):
|
||||
'''Launch GUI daemon for VMs'''
|
||||
"""Launch GUI daemon for VMs"""
|
||||
|
||||
def __init__(self, app: qubesadmin.app.QubesBase):
|
||||
''' Initialize GUILauncher.
|
||||
""" Initialize GUILauncher.
|
||||
|
||||
:param app: :py:class:`qubesadmin.Qubes` instance
|
||||
'''
|
||||
"""
|
||||
self.app = app
|
||||
self.started_processes = {}
|
||||
|
||||
@staticmethod
|
||||
def kde_guid_args(vm):
|
||||
'''Return KDE-specific arguments for gui-daemon, if applicable'''
|
||||
"""Return KDE-specific arguments for gui-daemon, if applicable"""
|
||||
|
||||
guid_cmd = []
|
||||
# Avoid using environment variables for checking the current session,
|
||||
@ -149,19 +152,20 @@ class GUILauncher(object):
|
||||
data_dir = os.path.expanduser('~/.local/share')
|
||||
|
||||
guid_cmd += ['-p',
|
||||
'_KDE_NET_WM_COLOR_SCHEME=s:{}'.format(
|
||||
os.path.join(data_dir,
|
||||
'qubes-kde', vm.label.name + '.colors'))]
|
||||
'_KDE_NET_WM_COLOR_SCHEME=s:{}'.format(
|
||||
os.path.join(data_dir,
|
||||
'qubes-kde',
|
||||
vm.label.name + '.colors'))]
|
||||
return guid_cmd
|
||||
|
||||
def common_guid_args(self, vm):
|
||||
'''Common qubes-guid arguments for PV(H), HVM and Stubdomain'''
|
||||
"""Common qubes-guid arguments for PV(H), HVM and Stubdomain"""
|
||||
|
||||
guid_cmd = [GUI_DAEMON_PATH,
|
||||
'-N', vm.name,
|
||||
'-c', vm.label.color,
|
||||
'-i', os.path.join(QUBES_ICON_DIR, vm.label.icon) + '.png',
|
||||
'-l', str(vm.label.index)]
|
||||
'-N', vm.name,
|
||||
'-c', vm.label.color,
|
||||
'-i', os.path.join(QUBES_ICON_DIR, vm.label.icon) + '.png',
|
||||
'-l', str(vm.label.index)]
|
||||
|
||||
if vm.debug:
|
||||
guid_cmd += ['-v', '-v']
|
||||
@ -177,19 +181,19 @@ class GUILauncher(object):
|
||||
|
||||
@staticmethod
|
||||
def guid_pidfile(xid):
|
||||
'''Helper function to construct a pidfile path'''
|
||||
"""Helper function to construct a pidfile path"""
|
||||
return '/var/run/qubes/guid-running.{}'.format(xid)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start_gui_for_vm(self, vm, monitor_layout=None):
|
||||
'''Start GUI daemon (qubes-guid) connected directly to a VM
|
||||
"""Start GUI daemon (qubes-guid) connected directly to a VM
|
||||
|
||||
This function is a coroutine.
|
||||
|
||||
:param vm: VM for which start GUI daemon
|
||||
:param monitor_layout: monitor layout to send; if None, fetch it from
|
||||
local X server.
|
||||
'''
|
||||
"""
|
||||
guid_cmd = self.common_guid_args(vm)
|
||||
guid_cmd.extend(['-d', str(vm.xid)])
|
||||
|
||||
@ -208,14 +212,14 @@ class GUILauncher(object):
|
||||
yield from asyncio.create_subprocess_exec(*guid_cmd)
|
||||
|
||||
yield from self.send_monitor_layout(vm, layout=monitor_layout,
|
||||
startup=True)
|
||||
startup=True)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start_gui_for_stubdomain(self, vm, force=False):
|
||||
'''Start GUI daemon (qubes-guid) connected to a stubdomain
|
||||
"""Start GUI daemon (qubes-guid) connected to a stubdomain
|
||||
|
||||
This function is a coroutine.
|
||||
'''
|
||||
"""
|
||||
want_stubdom = force
|
||||
if not want_stubdom and \
|
||||
vm.features.check_with_template('gui-emulated', False):
|
||||
@ -239,17 +243,22 @@ class GUILauncher(object):
|
||||
|
||||
@asyncio.coroutine
|
||||
def start_gui(self, vm, force_stubdom=False, monitor_layout=None):
|
||||
'''Start GUI daemon regardless of start event.
|
||||
"""Start GUI daemon regardless of start event.
|
||||
|
||||
This function is a coroutine.
|
||||
|
||||
:param vm: VM for which GUI daemon should be started
|
||||
:param force_stubdom: Force GUI daemon for stubdomain, even if the
|
||||
one for target AppVM is running.
|
||||
'''
|
||||
:param monitor_layout: monitor layout configuration
|
||||
"""
|
||||
guivm = getattr(vm, 'guivm', None)
|
||||
if guivm != vm.app.local_name:
|
||||
vm.log.info('GUI connected to {}. Skipping.'.format(guivm))
|
||||
return
|
||||
|
||||
if vm.virt_mode == 'hvm':
|
||||
yield from self.start_gui_for_stubdomain(vm,
|
||||
force=force_stubdom)
|
||||
yield from self.start_gui_for_stubdomain(vm, force=force_stubdom)
|
||||
|
||||
if not vm.features.check_with_template('gui', True):
|
||||
return
|
||||
@ -259,7 +268,7 @@ class GUILauncher(object):
|
||||
|
||||
@asyncio.coroutine
|
||||
def send_monitor_layout(self, vm, layout=None, startup=False):
|
||||
'''Send monitor layout to a given VM
|
||||
"""Send monitor layout to a given VM
|
||||
|
||||
This function is a coroutine.
|
||||
|
||||
@ -268,7 +277,7 @@ class GUILauncher(object):
|
||||
local X server.
|
||||
:param startup:
|
||||
:return: None
|
||||
'''
|
||||
"""
|
||||
# pylint: disable=no-self-use
|
||||
if vm.features.check_with_template('no-monitor-layout', False) \
|
||||
or not vm.is_running():
|
||||
@ -293,29 +302,35 @@ class GUILauncher(object):
|
||||
pass
|
||||
|
||||
try:
|
||||
yield from asyncio.get_event_loop().run_in_executor(None,
|
||||
functools.partial(vm.run_service_for_stdio,
|
||||
'qubes.SetMonitorLayout',
|
||||
input=''.join(layout).encode(),
|
||||
autostart=False))
|
||||
yield from asyncio.get_event_loop(). \
|
||||
run_in_executor(None,
|
||||
functools.partial(
|
||||
vm.run_service_for_stdio,
|
||||
'qubes.SetMonitorLayout',
|
||||
input=''.join(layout).encode(),
|
||||
autostart=False))
|
||||
except subprocess.CalledProcessError as e:
|
||||
vm.log.warning('Failed to send monitor layout: %s', e.stderr)
|
||||
|
||||
def send_monitor_layout_all(self):
|
||||
'''Send monitor layout to all (running) VMs'''
|
||||
"""Send monitor layout to all (running) VMs"""
|
||||
monitor_layout = get_monitor_layout()
|
||||
for vm in self.app.domains:
|
||||
if getattr(vm, 'guivm', None) != vm.app.local_name:
|
||||
continue
|
||||
if vm.klass == 'AdminVM':
|
||||
continue
|
||||
if vm.is_running():
|
||||
if not vm.features.check_with_template('gui', True):
|
||||
continue
|
||||
asyncio.ensure_future(self.send_monitor_layout(vm,
|
||||
monitor_layout))
|
||||
monitor_layout))
|
||||
|
||||
def on_domain_spawn(self, vm, _event, **kwargs):
|
||||
'''Handler of 'domain-spawn' event, starts GUI daemon for stubdomain'''
|
||||
"""Handler of 'domain-spawn' event, starts GUI daemon for stubdomain"""
|
||||
try:
|
||||
if getattr(vm, 'guivm', None) != vm.app.local_name:
|
||||
return
|
||||
if not vm.features.check_with_template('gui', True):
|
||||
return
|
||||
if vm.virt_mode == 'hvm' and \
|
||||
@ -325,8 +340,10 @@ class GUILauncher(object):
|
||||
vm.log.warning('Failed to start GUI for %s: %s', vm.name, str(e))
|
||||
|
||||
def on_domain_start(self, vm, _event, **kwargs):
|
||||
'''Handler of 'domain-start' event, starts GUI daemon for actual VM'''
|
||||
"""Handler of 'domain-start' event, starts GUI daemon for actual VM"""
|
||||
try:
|
||||
if getattr(vm, 'guivm', None) != vm.app.local_name:
|
||||
return
|
||||
if not vm.features.check_with_template('gui', True):
|
||||
return
|
||||
if kwargs.get('start_guid', 'True') == 'True':
|
||||
@ -335,20 +352,22 @@ class GUILauncher(object):
|
||||
vm.log.warning('Failed to start GUI for %s: %s', vm.name, str(e))
|
||||
|
||||
def on_connection_established(self, _subject, _event, **_kwargs):
|
||||
'''Handler of 'connection-established' event, used to launch GUI
|
||||
daemon for domains started before this tool. '''
|
||||
"""Handler of 'connection-established' event, used to launch GUI
|
||||
daemon for domains started before this tool. """
|
||||
|
||||
monitor_layout = get_monitor_layout()
|
||||
self.app.domains.clear_cache()
|
||||
for vm in self.app.domains:
|
||||
if vm.klass == 'AdminVM':
|
||||
continue
|
||||
if getattr(vm, 'guivm', None) != vm.app.local_name:
|
||||
continue
|
||||
if not vm.features.check_with_template('gui', True):
|
||||
continue
|
||||
power_state = vm.get_power_state()
|
||||
if power_state == 'Running':
|
||||
asyncio.ensure_future(self.start_gui(vm,
|
||||
monitor_layout=monitor_layout))
|
||||
asyncio.ensure_future(
|
||||
self.start_gui(vm, monitor_layout=monitor_layout))
|
||||
elif power_state == 'Transient':
|
||||
# it is still starting, we'll get 'domain-start' event when
|
||||
# fully started
|
||||
@ -356,43 +375,47 @@ class GUILauncher(object):
|
||||
asyncio.ensure_future(self.start_gui_for_stubdomain(vm))
|
||||
|
||||
def register_events(self, events):
|
||||
'''Register domain startup events in app.events dispatcher'''
|
||||
"""Register domain startup events in app.events dispatcher"""
|
||||
events.add_handler('domain-spawn', self.on_domain_spawn)
|
||||
events.add_handler('domain-start', self.on_domain_start)
|
||||
events.add_handler('connection-established',
|
||||
self.on_connection_established)
|
||||
self.on_connection_established)
|
||||
|
||||
|
||||
def x_reader(conn, callback):
|
||||
'''Try reading something from X connection to check if it's still alive.
|
||||
"""Try reading something from X connection to check if it's still alive.
|
||||
In case it isn't, call *callback*.
|
||||
'''
|
||||
"""
|
||||
try:
|
||||
conn.poll_for_event()
|
||||
except xcffib.ConnectionException:
|
||||
callback()
|
||||
|
||||
|
||||
if 'XDG_RUNTIME_DIR' in os.environ:
|
||||
pidfile_path = os.path.join(os.environ['XDG_RUNTIME_DIR'],
|
||||
'qvm-start-gui.pid')
|
||||
'qvm-start-gui.pid')
|
||||
else:
|
||||
pidfile_path = os.path.join(os.environ.get('HOME', '/'),
|
||||
'.qvm-start-gui.pid')
|
||||
'.qvm-start-gui.pid')
|
||||
|
||||
parser = qubesadmin.tools.QubesArgumentParser(
|
||||
description='start GUI for qube(s)', vmname_nargs='*')
|
||||
parser.add_argument('--watch', action='store_true',
|
||||
help='Keep watching for further domains startups, must be used with --all')
|
||||
help='Keep watching for further domains'
|
||||
' startups, must be used with --all')
|
||||
parser.add_argument('--force-stubdomain', action='store_true',
|
||||
help='Start GUI to stubdomain-emulated VGA, even if gui-agent is running '
|
||||
'in the VM')
|
||||
help='Start GUI to stubdomain-emulated VGA,'
|
||||
' even if gui-agent is running in the VM')
|
||||
parser.add_argument('--pidfile', action='store', default=pidfile_path,
|
||||
help='Pidfile path to create in --watch mode')
|
||||
help='Pidfile path to create in --watch mode')
|
||||
parser.add_argument('--notify-monitor-layout', action='store_true',
|
||||
help='Notify running instance in --watch mode about changed monitor layout')
|
||||
help='Notify running instance in --watch mode'
|
||||
' about changed monitor layout')
|
||||
|
||||
|
||||
def main(args=None):
|
||||
''' Main function of qvm-start-gui tool'''
|
||||
""" Main function of qvm-start-gui tool"""
|
||||
args = parser.parse_args(args)
|
||||
if args.watch and not args.all_domains:
|
||||
parser.error('--watch option must be used with --all')
|
||||
@ -413,10 +436,10 @@ def main(args=None):
|
||||
|
||||
for signame in ('SIGINT', 'SIGTERM'):
|
||||
loop.add_signal_handler(getattr(signal, signame),
|
||||
events_listener.cancel) # pylint: disable=no-member
|
||||
events_listener.cancel) # pylint: disable=no-member
|
||||
|
||||
loop.add_signal_handler(signal.SIGHUP,
|
||||
launcher.send_monitor_layout_all)
|
||||
launcher.send_monitor_layout_all)
|
||||
|
||||
conn = xcffib.connect()
|
||||
x_fd = conn.get_file_descriptor()
|
||||
@ -437,7 +460,7 @@ def main(args=None):
|
||||
os.kill(pid, signal.SIGHUP)
|
||||
except (FileNotFoundError, ValueError) as e:
|
||||
parser.error('Cannot open pidfile {}: {}'.format(pidfile_path,
|
||||
str(e)))
|
||||
str(e)))
|
||||
else:
|
||||
loop = asyncio.get_event_loop()
|
||||
tasks = []
|
||||
|
Loading…
Reference in New Issue
Block a user