067cfb7cd6
When properly set, applications will have a chance to automatically detect HiDPI and act accordingly. This is the case for Fedora 23 template and GNOME apps (maybe even all built on top of GTK). But for privacy reasons, don't provide real values, only some approximate one. Give enough information to distinguish DPI above 150, 200 and 300. This is some compromise between privacy and HiDPI support. QubesOS/qubes-issues#1951 This commit is migrated from gui-daemon repository (dec462795d14a336bf27cc46948bbd592c307401).
257 lines
8.4 KiB
Python
257 lines
8.4 KiB
Python
#!/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 re
|
|
import subprocess
|
|
|
|
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<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 options
|
|
( # 304mm x 228mm
|
|
(?P<width_mm>\d+)mm[ ]x[ ]
|
|
(?P<height_mm>\d+)mm
|
|
)?
|
|
.* # 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']:
|
|
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):
|
|
@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 \
|
|
or not os.path.exists('/var/run/shm.id'):
|
|
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
|
|
|
|
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]
|
|
|
|
try:
|
|
subprocess.check_call(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')
|
|
vm.wait_for_session()
|
|
|
|
|
|
@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',
|
|
input=('SEAMLESS'
|
|
if vm.features.get('gui-seamless', False)
|
|
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']
|
|
|
|
try:
|
|
subprocess.check_call(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, monitor_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 monitor_layout is None:
|
|
monitor_layout = get_monitor_layout()
|
|
if not monitor_layout:
|
|
return
|
|
|
|
pipe = vm.run('QUBESRPC qubes.SetMonitorLayout dom0',
|
|
passio_popen=True, wait=True)
|
|
|
|
pipe.stdin.write(''.join(monitor_layout))
|
|
pipe.stdin.close()
|
|
pipe.wait()
|
|
|
|
|
|
@staticmethod
|
|
def is_guid_running(vm):
|
|
'''Check whether gui daemon for this domain is available.
|
|
|
|
:returns: :py:obj:`True` if guid is running, \
|
|
:py:obj:`False` otherwise.
|
|
:rtype: bool
|
|
'''
|
|
xid = vm.xid
|
|
if xid < 0:
|
|
return False
|
|
if not os.path.exists('/var/run/qubes/guid-running.{}'.format(xid)):
|
|
return False
|
|
return True
|
|
|
|
|
|
@qubes.ext.handler('domain-is-fully-usable')
|
|
def on_domain_is_fully_usable(self, vm, event):
|
|
# pylint: disable=unused-argument
|
|
if not self.is_guid_running(vm):
|
|
yield False
|