diff --git a/qubes/ext/gui.py b/qubes/ext/gui.py index 1cb1a01e..f33bd394 100644 --- a/qubes/ext/gui.py +++ b/qubes/ext/gui.py @@ -22,209 +22,13 @@ # import os -import re -import subprocess - -import asyncio import qubes.config import qubes.ext -# "LVDS connected 1024x768+0+0 (normal left inverted right) 304mm x 228mm" -REGEX_OUTPUT = re.compile(r''' - (?x) # ignore whitespace - ^ # start of string - (?P[A-Za-z0-9\-]*)[ ] # LVDS VGA etc - (?P(dis)?connected) # dis/connected - ([ ] - (?P(primary)?)[ ]? - (( # a group - (?P\d+)x # either 1024x768+0+0 - (?P\d+)[+] - (?P\d+)[+] - (?P\d+) - )|[\D]) # or not a digit - ([ ]\(.*\))?[ ]? # ignore options - ( # 304mm x 228mm - (?P\d+)mm[ ]x[ ] - (?P\d+)mm - )? - .* # ignore rest of line - )? # everything after (dis)connect is optional - ''') - - -def get_monitor_layout(): - outputs = [] - - for line in subprocess.Popen( - ['xrandr', '-q'], stdout=subprocess.PIPE).stdout: - line = line.decode() - if not line.startswith("Screen") and not line.startswith(" "): - output_params = REGEX_OUTPUT.match(line).groupdict() - if output_params['width']: - phys_size = "" - if output_params['width_mm']: - # don't provide real values for privacy reasons - see - # #1951 for details - dpi = (int(output_params['width']) * 254 / - int(output_params['width_mm']) / 10) - if dpi > 300: - dpi = 300 - elif dpi > 200: - dpi = 200 - elif dpi > 150: - dpi = 150 - else: - # if lower, don't provide this info to the VM at all - dpi = 0 - if dpi: - # now calculate dimensions based on approximate DPI - phys_size = " {} {}".format( - int(output_params['width']) * 254 / dpi / 10, - 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, - )) - return outputs - class GUI(qubes.ext.Extension): - @staticmethod - def kde_guid_args(vm): - '''Return KDE-specific arguments for guid, if applicable''' - - guid_cmd = [] - # Avoid using environment variables for checking the current session, - # because this script may be called with cleared env (like with sudo). - if subprocess.check_output( - ['xprop', '-root', '-notype', 'KDE_SESSION_VERSION']) == \ - 'KDE_SESSION_VERSION = 5\n': - # native decoration plugins is used, so adjust window properties - # accordingly - guid_cmd += ['-T'] # prefix window titles with VM name - # get owner of X11 session - session_owner = None - for line in subprocess.check_output(['xhost']).splitlines(): - if line == b'SI:localuser:root': - pass - elif line.startswith(b'SI:localuser:'): - session_owner = line.split(":")[2] - if session_owner is not None: - data_dir = os.path.expanduser( - '~{}/.local/share'.format(session_owner)) - else: - # fallback to current user - 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'))] - return guid_cmd - - - @qubes.ext.handler('domain-start', 'domain-cmd-pre-run') - def start_guid(self, vm, event, preparing_dvm=False, start_guid=True, - 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: - return - - if self.is_guid_running(vm): - 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 - - display = os.getenv('DISPLAY') - if not display.startswith(':'): - vm.log.error('Expected local $DISPLAY, got \'{}\''.format(display)) - return - - display_num = display[1:].partition('.')[0] - shmid_path = '/var/run/qubes/shm.id.{}'.format(display_num) - if not os.path.exists(shmid_path): - vm.log.error( - 'Not starting gui daemon, no {} file'.format(shmid_path)) - 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'] - - if vm.hvm: - guid_cmd += ['-Q', '-n'] - - stubdom_guid_pidfile = '/var/run/qubes/guid-running.{}'.format( - self.get_stubdom_xid(vm)) - if not vm.debug and os.path.exists(stubdom_guid_pidfile): - # Terminate stubdom guid once "real" gui agent connects - stubdom_guid_pid = \ - open(stubdom_guid_pidfile, 'r').read().strip() - guid_cmd += ['-K', stubdom_guid_pid] - - guid_cmd += self.kde_guid_args(vm) - - @asyncio.coroutine - def coro(): - try: - yield from vm.start_daemon(guid_cmd) - except subprocess.CalledProcessError: - raise qubes.exc.QubesVMError(vm, - 'Cannot start qubes-guid for domain {!r}'.format(vm.name)) - - vm.fire_event('monitor-layout-change') - - asyncio.ensure_future(coro()) - - - @staticmethod - def get_stubdom_xid(vm): - if vm.xid < 0: - return -1 - - if vm.app.vmm.xs is None: - return -1 - - stubdom_xid_str = vm.app.vmm.xs.read('', - '/local/domain/{}/image/device-model-domid'.format(vm.xid)) - if stubdom_xid_str is None or not stubdom_xid_str.isdigit(): - return -1 - - return int(stubdom_xid_str) - - @staticmethod def send_gui_mode(vm): vm.run_service('qubes.SetGuiMode', @@ -233,65 +37,12 @@ class GUI(qubes.ext.Extension): else 'FULLSCREEN')) - @qubes.ext.handler('domain-spawn') - def on_domain_spawn(self, vm, event, start_guid=True, **kwargs): - # pylint: disable=unused-argument - if not start_guid: - return - - if not vm.hvm: - return - - if not os.getenv('DISPLAY'): - vm.log.error('Not starting gui daemon, no DISPLAY set') - return - - guid_cmd = [qubes.config.system_path['qubes_guid_path'], - '-d', str(self.get_stubdom_xid(vm)), - '-t', str(vm.xid), - '-N', vm.name, - '-c', vm.label.color, - '-i', vm.label.icon_path, - '-l', str(vm.label.index), - ] - - if vm.debug: - guid_cmd += ['-v', '-v'] - else: - guid_cmd += ['-q'] - - guid_cmd += self.kde_guid_args(vm) - - try: - vm.start_daemon(guid_cmd) - except subprocess.CalledProcessError: - raise qubes.exc.QubesVMError(vm, 'Cannot start gui daemon') - - - @qubes.ext.handler('monitor-layout-change') - def on_monitor_layout_change(self, vm, event, layout=None): - # pylint: disable=no-self-use,unused-argument - if vm.features.check_with_template('no-monitor-layout', False) \ - or not vm.is_running(): - return - - if layout is None: - layout = get_monitor_layout() - if not layout: - return - - pipe = vm.run('QUBESRPC qubes.SetMonitorLayout dom0', - passio_popen=True, wait=True) - - pipe.stdin.write(''.join(layout).encode()) - pipe.stdin.close() - pipe.wait() - - @staticmethod def is_guid_running(vm): '''Check whether gui daemon for this domain is available. + Notice: this will be irrelevant here, after real splitting GUI/Admin. + :returns: :py:obj:`True` if guid is running, \ :py:obj:`False` otherwise. :rtype: bool