<commit_before># -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2017 <NAME>
#                               <<EMAIL>>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
#
# 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/AUDIO daemon launcher tool"""

import os
import signal
import subprocess
import asyncio
import re
import functools
import sys
import xcffib
import xcffib.xproto  # pylint: disable=unused-import

import daemon.pidfile
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

GUI_DAEMON_PATH = '/usr/bin/qubes-guid'
PACAT_DAEMON_PATH = '/usr/bin/pacat-simple-vchan'
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"""
        (?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
        )?                             # everything after (dis)connect is optional
        """)


def get_monitor_layout():
    """Get list of monitors and their size/position"""
    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'] and int(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


def set_keyboard_layout(vm):
    """Set layout configuration into features for Gui admin extension"""
    try:
        # Examples of 'xprop -root _XKB_RULES_NAMES' output values:
        # "evdev", "pc105", "fr", "oss", ""
        # "evdev", "pc105", "pl,us", ",", "grp:win_switch,compose:caps"

        # We use the first layout provided
        xkb_re = r'_XKB_RULES_NAMES\(STRING\) = ' \
                 r'\"(.*)\", \"(.*)\", \"(.*)\", \"(.*)\", \"(.*)\"\n'
        xkb_rules_names = subprocess.check_output(
            ['xprop', '-root', '_XKB_RULES_NAMES']).decode()
        xkb_parsed = re.match(xkb_re, xkb_rules_names)
        xkb_layout = [x.split(',')[0] for x in xkb_parsed.groups()[2:4]]
        # We keep all options
        xkb_layout.append(xkb_parsed.group(5))
        keyboard_layout = '+'.join(xkb_layout)
        vm.features['keyboard-layout'] = keyboard_layout
    except subprocess.CalledProcessError as e:
        vm.log.warning('Failed to set layout for %s: %s', vm, str(e))


class DAEMONLauncher:
    """Launch GUI/AUDIO daemon for VMs"""

    def __init__(self, app: qubesadmin.app.QubesBase):
        """ Initialize DAEMONLauncher.

        :param app: :py:class:`qubesadmin.Qubes` instance
        """
        self.app = app
        self.started_processes = {}

    @asyncio.coroutine
    def send_monitor_layout(self, vm, layout=None, startup=False):
        """Send monitor layout to a given VM

        This function is a coroutine.

        :param vm: VM to which send monitor layout
        :param layout: monitor layout to send; if None, fetch it from<commit_msg>qvm-start-daemon: check if layout is parsed<commit_after># -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2017 <NAME>
#                               <<EMAIL>>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
#
# 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/AUDIO daemon launcher tool"""

import os
import signal
import subprocess
import asyncio
import re
import functools
import sys
import xcffib
import xcffib.xproto  # pylint: disable=unused-import

import daemon.pidfile
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

GUI_DAEMON_PATH = '/usr/bin/qubes-guid'
PACAT_DAEMON_PATH = '/usr/bin/pacat-simple-vchan'
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"""
        (?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
        )?                             # everything after (dis)connect is optional
        """)


def get_monitor_layout():
    """Get list of monitors and their size/position"""
    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'] and int(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


def set_keyboard_layout(vm):
    """Set layout configuration into features for Gui admin extension"""
    try:
        # Examples of 'xprop -root _XKB_RULES_NAMES' output values:
        # "evdev", "pc105", "fr", "oss", ""
        # "evdev", "pc105", "pl,us", ",", "grp:win_switch,compose:caps"

        # We use the first layout provided
        xkb_re = r'_XKB_RULES_NAMES\(STRING\) = ' \
                 r'\"(.*)\", \"(.*)\", \"(.*)\", \"(.*)\", \"(.*)\"\n'
        xkb_rules_names = subprocess.check_output(
            ['xprop', '-root', '_XKB_RULES_NAMES']).decode()
        xkb_parsed = re.match(xkb_re, xkb_rules_names)
        if xkb_parsed:
            xkb_layout = [x.split(',')[0] for x in xkb_parsed.groups()[2:4]]
            # We keep all options
            xkb_layout.append(xkb_parsed.group(5))
            keyboard_layout = '+'.join(xkb_layout)
            vm.features['keyboard-layout'] = keyboard_layout
        else:
            vm.log.warning('Failed to parse layout for %s', vm)
    except subprocess.CalledProcessError as e:
        vm.log.warning('Failed to set layout for %s: %s', vm, str(e))


class DAEMONLauncher:
    """Launch GUI/AUDIO daemon for VMs"""

    def __init__(self, app: qubesadmin.app.QubesBase):
        """ Initialize DAEMONLauncher.

        :param app: :py:class:`qubesadmin.Qubes` instance
        """
        self.app = app
        self.started_processes = {}

    @asyncio.coroutine
    def send_monitor_layout(self, vm, layout=None, startup=False):
        """Send monitor layout to a given VM

        This function is a coroutine.

        :param vm: VM to which send monitor layout
        :param layout: monitor layout to send; if None, fetch it from
{'fraction_non_alphanumeric': 0.1202134067636176, 'fraction_numerical': 0.016005507271319164, 'mean_word_length': 1.7540284360189573, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3814576', 'n_tokens_mistral': 3609, 'n_tokens_neox': 3309, 'n_words': 936}