<filename>powerline/segments/randr.py<gh_stars>10-100
from powerline.lib.threaded import ThreadedSegment
from powerline.segments import with_docstring
from powerline.theme import requires_segment_info
from powerline.bindings.wm import get_randr_outputs
from os import path
from subprocess import check_call, check_output, run
from glob import glob
from threading import Lock

from Xlib import X, display
from Xlib.ext import randr

lock = Lock()

xlib_rots = {'normal': randr.Rotate_0, 'inverted': randr.Rotate_180,
        'left': randr.Rotate_90, 'right': randr.Rotate_270}
MODES = ['locked', 'auto']
@requires_segment_info
class ScreenRotationSegment(ThreadedSegment):

    interval = 2

    current_state = 0

    # output to manage
    output = None
    touch_output = None

    # Input devices to manage
    devices = None

    # Basedir for accelerometer
    basedir = None

    # Scale of the accelerometer
    scale = 1.0

    # Input devices to be mapped to the specified output
    mapped_inputs = []
    # Touchpads to be enabled/disabled
    touchpads = []

    STATES = []
    g = 8
    # Gravity triggers (rotation -> value)
    triggers = {'normal': -g, 'inverted': g, 'left': g, 'right': -g}
    checks = { }
    touchpad_state = {'normal': 'enable', 'inverted': 'disable',
            'left': 'disable', 'right': 'disable'}

    accel_x = None
    accel_y = None

    # Disable this segment if it runs in a bar on the wrong output
    mode = 1

    last_oneshot = 0
    bar_needs_resize = None

    rotation_hook = None
    hide_controls = { }

    d = None
    window = None

    def set_state(self, output, states=['normal', 'inverted', 'left', 'right'],
            gravity_triggers=None, mapped_inputs=[], touchpads=[], touchpad_states=None,
            rotation_hook=None, hide_controls=True, sensor_is_unsigned=False,
            sensor_max_value=None, **kwargs):
        self.output = output
        self.touch_output = output

        self.sensor_is_unsigned = sensor_is_unsigned
        self.sensor_max_value = sensor_max_value

        self.d = display.Display()
        s = self.d.screen()
        self.window = s.root.create_window(0, 0, 1, 1, 1, s.root_depth)

        self.rotation_hook = rotation_hook
        self.hide_controls = { 'default': hide_controls, output: hide_controls }

        for basedir in glob('/sys/bus/iio/devices/iio:device*'):
            with open(path.join(basedir, 'name')) as f:
                if 'accel' in f.read():
                    self.basedir = basedir
                    break
        else:
            # No accels found, throw an error
            pass

        self.devices = check_output(['xinput', '--list', '--name-only']).splitlines()
        with open(path.join(self.basedir, 'in_accel_scale')) as f:
            self.scale = float(f.read())

        if gravity_triggers:
            self.triggers = gravity_triggers
        if touchpad_states:
            self.touchpad_states = touchpad_states

        self.mapped_inputs = mapped_inputs
        self.touchpads = touchpads

        self.current_state = 0
        self.STATES = states


        self.checks = {
            'normal':   lambda x, y: y < self.triggers['normal'],
            'inverted': lambda x, y: y > self.triggers['inverted'],
            'left':     lambda x, y: x > self.triggers['left'],
            'right':    lambda x, y: x < self.triggers['right']
        }

        self.rotate(self.current_state)
        self.update_touchpad(self.current_state)
        super(ScreenRotationSegment, self).set_state(**kwargs)

    def rotate(self, state):
        outs = get_randr_outputs(self.d, self.window)
        if outs == None:
            return False
        outs = [o for o in outs if o['crtc']]
        op = [o for o in outs if o['name'] == self.output]
        if not len(op):
            # The output to be rotated doesn't exist :(
            return False
        op = op[0]

        # Get all outputs that are mirrored to the output we shall rotate
        # (We must also rotate these outputs)
        current_mode = op['current_mode']

        mirrored_outs = [o for o in outs if o['x'] == op['x'] and o['y'] == op['y'] and o['current_mode'] == op['current_mode']]

        if (self.STATES[self.current_state] in ['left', 'right']) != (self.STATES[state] in ['left', 'right']):

            # If the user has some non-mirrored setup, only normal and inverted
            # layouts are implemented for now.
            # Anything else requires work . . .

            if len(outs) != len(mirrored_outs):
                return False

            # Disable all screens to be rotated
            for o in mirrored_outs:
                randr.set_crtc_config(self.d, o['crtc_id'], 0, 0, 0, 0, 1, [])

            mx_x = 0
            mx_y = 0
            mx_mm_x = 0.0
            mx_mm_y = 0.0

            ratio = 1

            for o in outs:
                if o['width']:
                    mx_x = max(mx_x, o['x'] + o['width'])
                if o['height']:
                    mx_y = max(mx_y, o['y'] + o['height'])
                if o['width'] and o['height']:
                    ratio = max(ratio, max(o['width'] / o['height'], o['height'] / o['width'] ))
                if o in mirrored_outs and o['crtc'].rotation in [xlib_rots['left'],
                        xlib_rots['right']]:
                    if o['mm_width'] and o['width']:
                        mx_mm_x = max(mx_mm_x, o['width'] * 1.0 / o['mm_width'])
                    if o['mm_height'] and o['height']:
                        mx_mm_y = max(mx_mm_y, o['height'] * 1.0 / o['mm_height'])
                else:
                    if o['mm_width'] and o['height']:
                        mx_mm_y = max(mx_mm_y, o['height'] * 1.0 / o['mm_width'])
                    if o['mm_height'] and o['width']:
                        mx_mm_x = max(mx_mm_x, o['width'] * 1.0 / o['mm_height'])

            if mx_x and mx_y and mx_mm_x and mx_mm_y:
                self.window.xrandr_set_screen_size(mx_y, mx_x, int(mx_x / mx_mm_x * ratio),
                        int(mx_y / mx_mm_y * ratio))

        # Actually rotate these outputs, don't change anything besides the rotation
        for o in mirrored_outs:
            randr.set_crtc_config(self.d, o['crtc_id'], 0, o['x'], o['y'],
                    current_mode, xlib_rots[self.STATES[state]], [o['id']])

        if (self.STATES[self.current_state] in ['left', 'right']) != (self.STATES[state] in ['left', 'right']):
            self.bar_needs_resize = self.output
            if self.rotation_hook:
                run(self.rotation_hook, shell=True)

        needs_map = [i.decode('utf-8') for i in self.devices if len([j for j in self.mapped_inputs
            if j in i.decode('utf-8')])]

        ids = [check_output(['xinput', '--list', '--id-only', i]).splitlines()[0].decode()
                for i in needs_map]
        for i in ids:
            check_call(['xinput', '--map-to-output', i, self.touch_output])

        return True

    def update_touchpad(self, state):
        needs_map = [i.decode('utf-8') for i in self.devices if len([j for j in self.touchpads
            if j in i.decode('utf-8')])]

        for dev in needs_map:
            check_call(['xinput', self.touchpad_state[self.STATES[state]], dev])

    def read_accel(self, f):
        val = f.read()
        if not self.sensor_is_unsigned:
            return float(val) * self.scale
        else:
            if(int(val) <= self.sensor_max_value // 2):
                return float(val) * self.scale
            return float(int(val) - self.sensor_max_value) * self.scale


    def update(self, *args, **kwargs):
        if self.mode == 0:
            return -1

        self.accel_x = open(path.join(self.basedir, 'in_accel_x_raw'))
        self.accel_y = open(path.join(self.basedir, 'in_accel_y_raw'))
        x = self.read_accel(self.accel_x)
        y = self.read_accel(self.accel_y)
        self.accel_x.close()
        self.accel_y.close()

        self.devices = check_output(['xinput', '--list', '--name-only']).splitlines()

        for i in range(len(self.STATES)):
            if i == self.current_state:
                continue
            if self.checks[self.STATES[i]](x, y):
                global lock
                with lock:
                    if i == self.current_state:
                        continue
                    if self.rotate(i):
                        self.current_state = i
                        self.update_touchpad(self.current_state)

        return self.current_state

    def render(self, data, segment_info, show_on_all_outputs=True, name='rotation',
            format='{icon}', icons={'left':'l', 'right':'r', 'normal':'n', 'inverted':'i',
                'locked':'l', 'auto':'a'}, additional_controls=[], **kwargs):

        channel_name = 'randr.srot'
        channel_value = None
        if 'payloads' in segment_info and channel_name in segment_info['payloads']:
            channel_value = segment_info['payloads'][channel_name]

        current_output = segment_info['output'] if 'output' in segment_info else None

        if self.bar_needs_resize:
            scrn = self.bar_needs_resize
            self.bar_needs_resize = None
            segment_info['restart'](scrn)


        # A user wants to map devices to a different screen
        if channel_value and not isinstance(channel_value, str) and len(channel_value) == 2 and channel_value[0].startswith('capture_input:') and current_output and channel_value[1] > self.last_oneshot:
            new_output = channel_value[0].split(':')[1]
            if current_output == new_output:
                self.last_oneshot = channel_value[1]
                self.touch_output = new_output
                self.rotate(self.current_state)
        # A user wants to rotate a different screen
        if channel_value and not isinstance(channel_value, str) and len(channel_value) == 2 and channel_value[0].startswith('capture:') and current_output and channel_value[1] > self.last_oneshot:
            new_output = channel_value[0].split(':')[1]
            if current_output == new_output:
                self.last_oneshot = channel_value[1]
                self.output = new_output
                self.touch_output = new_output
                self.rotate(self.current_state)
        # A user wants to toggle auto rotation
        if channel_value and not isinstance(channel_value, str) and len(channel_value) == 2 and channel_value[0] == 'toggle_rot' and current_output and self.output == current_output and channel_value[1] > self.last_oneshot:
            self.last_oneshot = channel_value[1]
            self.mode = 1 - self.mode
            self.rotate(self.current_state)
        # A user wants to toggle visibility of controls
        if channel_value and not isinstance(channel_value, str) and len(channel_value) == 2 and channel_value[0].startswith('toggle_controls') and current_output and channel_value[1] > self.last_oneshot:
            new_output = channel_value[0].split(':')[1]
            if current_output == new_output:
                self.last_oneshot = channel_value[1]
                if new_output in self.hide_controls:
                    self.hide_controls[new_output] = not self.hide_controls[new_output]
                else:
                    self.hide_controls[new_output] = not self.hide_controls['default']

        c_vals = {
                    'mode': MODES[self.mode],
                    'rotation': self.STATES[self.current_state],
                    'output': current_output,
                    'managed_output': self.output,
                    'touch_output': self.touch_output
                }

        if (current_output in self.hide_controls and not self.hide_controls[current_output]) or (not current_output in self.hide_controls and not self.hide_controls['default']):
            add_segments = [{
                    'contents': i[0].format(rotation=self.STATES[self.current_state],
                        mode=MODES[self.mode],
                        managed_output=self.output, touch_output=self.touch_output),
                    'payload_name': channel_name,
                    'highlight_groups': i[1] + ['srot'],
                    'click_values': c_vals,
                    'draw_inner_divider': True
                } for i in additional_controls]
        else:
            add_segments = []

        if current_output and current_output != self.output:
            if not show_on_all_outputs:
                return add_segments if len(add_segments) else None

        if name == 'rotation':
            return [{
                'contents': format.format(rotation=self.STATES[self.current_state],
                    mode=MODES[self.mode], icon=icons[self.STATES[self.current_state]],
                    managed_output=self.output, touch_output=self.touch_output),
                'payload_name': channel_name,
                'highlight_groups': ['srot:' + self.STATES[data], 'srot:rotation', 'srot'],
                'click_values': c_vals,
                'draw_inner_divider': True
            }] + add_segments

        if name == 'mode':
            return [{
                'contents': format.format(rotation=self.STATES[self.current_state],
                    mode=MODES[self.mode], icon=icons[MODES[self.mode]],
                    managed_output=self.output, touch_output=self.touch_output),
                'payload_name': channel_name,
                'highlight_groups': ['srot:' + MODES[self.mode], 'srot:mode', 'srot'],
                'click_values': c_vals,
                'draw_inner_divider': True
            }] + add_segments

        return add_segments if len(add_segments) else None


srot = with_docstring(ScreenRotationSegment(),
''' Manage screen rotation and optionally display some information. Optionally disables
    Touchpads in rotated states. (Note that rotating to the ``left`` and ``right``
    states does not currently work if there is another output connected whose
    displayed content is not mirrored to the screen to be rotated.)

    Requires ``xinput`` and ``python-xlib`` and an accelerometer.

    :param string output:
        The initial output to be rotated and to which touchscreen and stylus inputs are mapped.
        (Note that this can be changed at runtime via interaction with the segment.)
    :param bool show_on_all_outputs:
        If set to false, this segment is only visible on the specified output.
    :param string name:
        Possible values are ``rotation`` and ``mode``. This value is used to determine
        which highlight groups to use and how to populate the ``icon`` field in the
        format string in the returned segment.
        If set to any other value, this segment will produce no output.
    :param string format:
        Format string. Possible fields are ``rotation`` (the current rotation state of the screen),
        ``mode`` (either ``auto`` or ``locked``, depending on whether auto-rotation on the
        screen is enabled or not), and ``icon`` (an icon depicting either the rotation status
        or the auto-rotation status, depending on the segment's name).
    :param dict icons:
        Dictionary mapping rotation states (``normal``, ``inverted``, ``left``, ``right``)
        and auto-rotation states (``locked``, ``auto``) to strings to use to display them.
        Depending on the given name parameter, not all of these fields must be populated.
    :param string list states:
        Allowed rotation states. Possible entries are ``normal``, ``inverted``, ``left``, and
        ``right``. Per default, all of them are enabled.
    :param dict gravity_triggers:
        Sensor values that trigger rotation as a dictionary mapping rotation states
        (``normal``, ``inverted``, ``left``, ``right``) to numbers.
        Defaults to ``{'normal': -8, 'inverted': 8, 'left': 8, 'right': -8}``, meaning that
        a (scaled) reading of the ``in_accel_x_raw`` reading greater than 8 triggers a
        rotation to state ``left`` and a reading less than -8 triggers a rotation to state
        ``right``. Readings of ``in_accel_y_raw`` greater and less than 8 and -8 respectively
        will yield a rotation to the ``inverted`` and ``normal`` states respectively.
    :param string_list mapped_inputs:
        List of substrings of device names that should be mapped to the specified output.
        The entries in the specified list should be only substrings of devices listed as
        ``Virtual core pointer``, not of devices listed as ``Virtual core keyboard``.
    :param string_list touchpads:
        List of substrings of device names of touchpads to be managed.
        The entries in the specified list should be only substrings of devices listed as
        ``Virtual core pointer``, not of devices listed as ``Virtual core keyboard``.
    :param dict touchpad_states:
        Dictionary mapping a rotation state (``normal``, ``inverted``, ``left``, ``right``)
        to either ``enabled`` or ``disabled``, depending on whether the touchpads shall be
        enabled or disabled if the output is currently in the corresponding state.
    :param string rotation_hook:
        A string to be run by a shell after a rotation that changes the screen ratio
        (e.g. from ``normal`` to ``left``).
        It will be executed after the rotation takes place, but before the inputs are
        mapped to the output and before the bar resizes itself.
    :param (string,string_list)_list additional_controls:
        A list of (contents, highlight_groups) pairs. For each entry, an additional
        segment with the given contents and highlight groups is omitted. These segments
        obtain the same click values and may also be used to control the segment behavior.
        Also, all segments additionally use the ``srot`` highlight group and the contents
        may be a format string with all fields (except ``icon``) available.
    :param bool hide_controls:
        Hide the extra control segments. They may be shown via segment interaction.

    Highlight groups used: ``srot:normal`` or ``srot:inverted`` or ``srot:right`` or ``srot:left`` or ``srot:rotation`` or ``srot`` (if the name parameter is ``rotation``) or
    ``srot:auto`` or ``srot:locked`` or ``srot:mode`` or ``srot`` (if the name parameter
    is ``mode``) or None (if the name is set to something else).

    Click values supplied: ``mode`` (string), ``rotation`` (string), ``output`` (string,
    the output this segment is rendered to), ``managed_output`` (string, the screen
    currently managed), ``touch_output`` (string, the screen where touch inputs are mapped to).

    Interaction: This segment supports interaction via bar commands in the following way.
    (Note that parameters given to the bar may be combined with click values.)

    +------------------------------------------+---------------------------------------------+
    | Bar command                              | Description                                 |
    +==========================================+=============================================+
    | #bar;pass_oneshot:capture_input:<output> | Map all specified input devices to <output> |
    |                                          | (experimental)                              |
    +------------------------------------------+---------------------------------------------+
    | #bar;pass_oneshot:capture:<output>       | Rotate the screen <output> instead          |
    |                                          | (experimental)                              |
    +------------------------------------------+---------------------------------------------+
    | #bar;pass_oneshot:toggle_rot             | Toggle auto rotation if used on the screen  |
    |                                          | that is currently managed; otherwise        |
    |                                          | ignored.                                    |
    +------------------------------------------+---------------------------------------------+
    | #bar;pass_oneshot:toggle_controls:<outpt>| Toggles the visibility of additional        |
    |                                          | control segments on output <output>         |
    +------------------------------------------+---------------------------------------------+
''')

@requires_segment_info
class OutputSegment(ThreadedSegment):
    interval = 2

    d = None
    window = None
    outputs = {}

    segment_state = 0 # 0: minimal, 1: list outputs

    MIRROR_STATES = ['extend', 'mirror']
    mirror_state = 0 # 0: extend, 1: mirror

    last_oneshot = 0

    bar_needs_resize = None

    auto_update = False

    lock = Lock()

    redraw_hook = None

    def set_state(self, auto_update=False, redraw_hook=None, **kwargs):
        self.d = display.Display()
        s = self.d.screen()
        self.window = s.root.create_window(0, 0, 1, 1, 1, s.root_depth)

        outs = get_randr_outputs(self.d, self.window)
        if outs != None:
            self.outputs = [o for o in outs if o['connection']]

        prim = [o for o  in self.outputs if o['primary'] != None]
        if len(prim) > 0:
            prim = prim[0]
            onaji = [o for o in self.outputs if o['x'] == prim['x'] and o['y'] == prim['y']]
            if len(onaji) > 1:
                self.mirror_state = 1

        self.auto_update = auto_update

        self.redraw_hook = redraw_hook

        super(OutputSegment, self).set_state(**kwargs)

    def update(self, *args, **kwargs):
        od_out = self.outputs
        outs = get_randr_outputs(self.d, self.window)
        if outs == None:
            return None
        nw_out = [o for o in outs if o['connection']]

        old = [o['name'] for o in self.outputs]
        new = [o['name'] for o in nw_out]

        mg = [o for o in old if o in new]
        mg = [o for o in new if o in mg]

        change = not (len(mg) == len(old) and len(mg) == len(new))

        if change:
            if self.auto_update:
                for o in old:
                    if not o in mg:
                        self.disable_output([out for out in od_out if out['name'] == o][0])
                for o in new:
                    if not o in mg:
                        self.enable_output([out for out in nw_out if out['name'] == o][0])
            else:
                with self.lock:
                    self.outputs = nw_out
        return None

    def update_mirror_state(self):
        if self.mirror_state == 0:
            self.configure_extend()
        elif self.mirror_state == 1:
            self.configure_mirror()

    def resize_randr_screen(self):
        outs = [o for o in self.outputs if o['crtc']]

        for o in outs:
            randr.set_crtc_config(self.d, o['crtc_id'], 0, 0, 0, 0, 1, [])

        mx_x = 0
        mx_y = 0
        mx_mm_x = 0.0
        mx_mm_y = 0.0

        ratio =  1
        for o in outs:
            if o['width']:
                mx_x = max(mx_x, o['x'] + o['width'])
            if o['height']:
                mx_y = max(mx_y, o['y'] + o['height'])
            if o['width'] and o['height']:
                ratio = max(ratio, max(o['width'] / o['height'], o['height'] / o['width'] ))
            if not o['crtc'].rotation in [xlib_rots['left'], xlib_rots['right']]:
                if o['mm_width'] and o['width']:
                    mx_mm_x = max(mx_mm_x, o['width'] * 1.0 / o['mm_width'])
                if o['mm_height'] and o['height']:
                    mx_mm_y = max(mx_mm_y, o['height'] * 1.0 / o['mm_height'])
            else:
                if o['mm_width'] and o['height']:
                    mx_mm_y = max(mx_mm_y, o['height'] * 1.0 / o['mm_width'])
                if o['mm_height'] and o['width']:
                    mx_mm_x = max(mx_mm_x, o['width'] * 1.0 / o['mm_height'])

        if mx_x and mx_y and mx_mm_x and mx_mm_y:
            self.window.xrandr_set_screen_size(mx_x, mx_y, int(mx_x / mx_mm_x * ratio),
                    int(mx_y / mx_mm_y * ratio))

        for o in outs:
            randr.set_crtc_config(self.d, o['crtc_id'], 0, o['x'], o['y'],
                    o['current_mode'], o['crtc'].rotation, [o['id']])



    def configure_mirror(self, output=None):
        with self.lock:
            outs = get_randr_outputs(self.d, self.window)
            if outs == None:
                return False
            self.outputs = [o for o in outs if o['connection']]

        used_crtc = [o['crtc_id'] for o in self.outputs if o['crtc_id']]
        if output:
            free_crtc = [c for c in output['crtcs'] if c not in used_crtc]

            if len(free_crtc) < 1:
                # No crtc available, so we cannot enable this output
                return False

        # We need to find a mode that every connected output supports
        enabled_outputs = [o for o in self.outputs if o['crtc']]

        # print([o['name'] for o in enabled_outputs])

        if len(enabled_outputs) == 0:
            return False

        def resolutions(modes):
            return {(m['width'], m['height']) for m in modes}

        ress = {}
        mode_map = {}
        if output:
            # print(output['modes'])
            ress = resolutions(output['modes'])
            # print(ress)
            mode_map.update({output['name']: output['modes']})
        else:
            ress = resolutions(enabled_outputs[0]['modes'])

        # print(ress)

        for e in enabled_outputs:
            mode_map.update({e['name']: e['modes']})
            ress = [m for m in ress if m in resolutions(e['modes'])]
        if len(ress) > 0:
            # Outputs could agree on a resolution, so pick the largest one
            ress = sorted(ress, reverse=True)
            # print(ress)
            if output:
                mode_map.update({output['name']: \
                    [o for o in output['modes'] if (o['width'], o['height']) == ress[0]]})
            for e in enabled_outputs:
                mode_map.update({e['name']: \
                    [o for o in e['modes'] if (o['width'], o['height']) == ress[0]]})

        if output:
            randr.set_crtc_config(self.d, free_crtc[0],
                0, 0, 0, mode_map[output['name']][0]['id'], randr.Rotate_0, [output['id']])

        for o in enabled_outputs:
            randr.set_crtc_config(self.d, o['crtc_id'],
                0, 0, 0, mode_map[o['name']][0]['id'], o['crtc'].rotation, [o['id']])

        with self.lock:
            outs = get_randr_outputs(self.d, self.window)
            if outs == None:
                return False
            self.outputs = [o for o in outs if o['connection']]
        enabled_outputs = [o for o in self.outputs if o['crtc']]
        # Everything worked (hopefully), so redraw the bar
        if not self.bar_needs_resize:
            self.bar_needs_resize = []
        self.bar_needs_resize += [o['name'] for o in enabled_outputs]
        self.resize_randr_screen()
        if self.redraw_hook:
            run(self.redraw_hook, shell=True)
        return True

    def configure_extend(self, output=None):
        with self.lock:
            outs = get_randr_outputs(self.d, self.window)
            if outs == None:
                return False
            self.outputs = [o for o in outs if o['connection']]
        used_crtc = [o['crtc_id'] for o in self.outputs if o['crtc_id']]
        if output:
            free_crtc = [c for c in output['crtcs'] if c not in used_crtc]

            if len(free_crtc) < 1:
                # No crtc available, so we cannot enable this output
                return False

        enabled_outputs = [o for o in self.outputs if o['crtc']]

        if len(enabled_outputs) == 0:
            return False

        wd = 0
        for o in enabled_outputs:
            randr.set_crtc_config(self.d, o['crtc_id'],
                0, wd, 0, o['mode_ids'][0], o['crtc'].rotation, [o['id']])
            wd += [m['width'] for m in o['modes'] if m['id'] == o['mode_ids'][0]][0]

        if output:
            randr.set_crtc_config(self.d, free_crtc[0],
                    0, wd, 0, output['mode_ids'][0], randr.Rotate_0, [output['id']])

        with self.lock:
            outs = get_randr_outputs(self.d, self.window)
            if outs == None:
                return False
            self.outputs = [o for o in outs if o['connection']]
        enabled_outputs = [o for o in self.outputs if o['crtc']]
        # Everything worked (hopefully), so redraw the bar
        if not self.bar_needs_resize:
            self.bar_needs_resize = []
        self.bar_needs_resize += [o['name'] for o in enabled_outputs]
        self.resize_randr_screen()
        if self.redraw_hook:
            run(self.redraw_hook, shell=True)
        return True

    def enable_output(self, output):
        if self.mirror_state == 0:
            return self.configure_extend(output)
        elif self.mirror_state == 1:
            return self.configure_mirror(output)

        return False

    def disable_output(self, output):
        enabled_outputs = [o for o in self.outputs if o['crtc']]
        if len(enabled_outputs) <= 1 and output in enabled_outputs:
            # Yeah, I know most users are stupid, but at least don't let them disable all outputs
            return False

        # disable the output
        if output['crtc']:
            randr.set_crtc_config(self.d, output['crtc_id'], 0, 0, 0, 0, randr.Rotate_0, [])

        if self.mirror_state == 0:
            res = self.configure_extend(None)
            if res:
                if not self.bar_needs_resize:
                    self.bar_needs_resize = []
                self.bar_needs_resize += [output['name']]
            return res
        elif self.mirror_state == 1:
            res = self.configure_mirror(None)
            if res:
                if not self.bar_needs_resize:
                    self.bar_needs_resize = []
                self.bar_needs_resize += [output['name']]
            return res

        return False

    def render(self, data, segment_info, mirror_format='{mirror_icon}',
            mirror_icons={'mirror': 'M', 'extend': 'E'}, output_format='{output} {status_icon}',
            short_format='{mirror_icon} {output_count}', status_icons={'on': 'on', 'off': 'off'},
            hide_if_single_output=True, auto_shrink=True, **kwargs):
        channel_name = 'randr.output'

        channel_value = None
        if 'payloads' in segment_info and channel_name in segment_info['payloads']:
            channel_value = segment_info['payloads'][channel_name]

        if channel_value and not isinstance(channel_value, str) and len(channel_value) == 2 and channel_value[0].startswith('mode:') and channel_value[1] > self.last_oneshot:
            command = channel_value[0].split(':')[1]
            self.last_oneshot = channel_value[1]
            if command == 'toggle':
                self.mirror_state = (self.mirror_state + 1) % len(self.MIRROR_STATES)
            else:
                for i in self.MIRROR_STATES:
                    if i == command:
                        self.mirror_state = i
            self.update_mirror_state()

        if channel_value and not isinstance(channel_value, str) and len(channel_value) == 2 and channel_value[0].startswith('output:') and channel_value[1] > self.last_oneshot:
            self.last_oneshot = channel_value[1]
            output = channel_value[0].split(':')[1]
            command = channel_value[0].split(':')[2]

            output = [o for o in self.outputs if o['name'] == output]
            if len(output) == 1:
                output = output[0]
                if command == 'on':
                    self.enable_output(output)
                if command == 'off':
                    self.disable_output(output)
                if command == 'toggle':
                    if output['crtc']:
                        self.disable_output(output)
                    else:
                        self.enable_output(output)

        if channel_value and not isinstance(channel_value, str) and len(channel_value) == 2 and channel_value[0] == 'ch_toggle' and channel_value[1] > self.last_oneshot:
            self.last_oneshot = channel_value[1]
            self.segment_state = 1 - self.segment_state

        if self.bar_needs_resize:
            scrn = self.bar_needs_resize
            self.bar_needs_resize = None
            segment_info['restart'](scrn)


        if hide_if_single_output and len(self.outputs) < 2:
            return None

        if auto_shrink and self.segment_state == 0:
            return [{
                'contents': short_format.format(mirror_state=self.MIRROR_STATES[self.mirror_state],
                    mirror_icon=mirror_icons[self.MIRROR_STATES[self.mirror_state]],
                    output_count=len(self.outputs)),
                'highlight_groups': ['output:short',
                    'output:' + self.MIRROR_STATES[self.mirror_state], 'output'],
                'draw_inner_divider': True,
                'payload_name': channel_name,
                'click_values': {'mirror_state': self.MIRROR_STATES[self.mirror_state]}
            }]

        result = []

        result += [{
            'contents': mirror_format.format(mirror_state=self.MIRROR_STATES[self.mirror_state],
                mirror_icon=mirror_icons[self.MIRROR_STATES[self.mirror_state]]),
            'highlight_groups': ['output:' + self.MIRROR_STATES[self.mirror_state],
                'output:mirror_state', 'output'],
            'draw_inner_divider': True,
            'payload_name': channel_name,
            'click_values': {'mirror_state': self.MIRROR_STATES[self.mirror_state]}
        }]

        result += [{
            'contents': output_format.format(output=o['name'],
                status_icon=status_icons[o['status']]),
            'highlight_groups': ['output:' + o['status'], 'output:status', 'output'],
            'draw_inner_divider': True,
            'payload_name': channel_name,
            'click_values': {'output_name': o['name'], 'output_status': o['status']}
        } for o in sorted(self.outputs, key=lambda o: -1.0/o['x'] if o['x'] else 0, reverse=True)]

        return result

    # [ ][ ] / [=] / ... > eDPI (--o) > HDMI1 (o--) >
    # extend/mirror/ ... > only show connected outputs, use different colors for enabled/ disabled
    # Click on [ ][ ] / [=] cycles through modes
    # Click on outputs toggles them on / off acc to mode;
    # In extend mode: sort outputs according to their relative position

output = with_docstring(OutputSegment(),
'''Manage connected outputs, optionally detect newly (dis-)connected outputs automatically.

    Requires ``python-xlib``.

    :param string mirror_format:
        Format used to display the mirror mode (extend/mirror) part of the segment.
        Valid fields are ``mirror_state`` and ``mirror_icon``.
    :param dict mirror_icons:
        Icons used in the ``mirror_icon`` field of ``mirror_format``.
        Needs the entries ``extend`` and ``mirror``.
    :param string output_format:
        Format used to display outputs and information about their status.
        Valid fields are ``output`` and ``status_icon``.
    :param dict status_icons:
        Icons used in the ``status_icon`` field of ``output_format``.
        Needs the entries ``off`` and ``on``.
    :param bool hide_if_single_output:
        Hide the segment if only a single output is connected. (Enabling this will still show
        the segment if there is more than one output connected, of whom only one is not turned
        ``off``.)
    :param bool auto_update:
        If set to true, this segment will automatically enable newly connected outputs
        or disable newly disconnected outputs according to the current mode.
        Also restarts bars appropriately.


    Highlight groups used: ``output:mirror`` or ``output:extend`` or ``output:mirror_state`` or ``output`` (for the mirror mode part) and ``output:off`` or ``output:on`` or ``output:status`` or ``output`` (for the outputs).

    Click values supplied: ``mirror_state`` (string) for the mirror mode part and
    ``output_name`` (string), ``output_status`` (string) in the remaining part.

    Interaction: This segment supports interaction via bar commands in the following way.
    (Note that parameters given to the bar may be combined with click values.)

    +---------------------------------------------------+------------------------------------+
    | Bar command                                       | Description                        |
    +===================================================+====================================+
    | #bar;pass_oneshot:mode:<mode/toggle>              | Set the mirror mode to <mode> or   |
    |                                                   | <toggle> it.                       |
    +---------------------------------------------------+------------------------------------+
    | #bar;pass_oneshot:output:<output>:<on/off/toggle> | Turn output <output> <on/off> or   |
    |                                                   | <toggle> its status. Restarts bars.|
    +---------------------------------------------------+------------------------------------+

''')

<filename>powerline/segments/randr.py<gh_stars>10-100
from powerline.lib.threaded import ThreadedSegment
from powerline.segments import with_docstring
from powerline.theme import requires_segment_info
from powerline.bindings.wm import get_randr_outputs
from os import path
from subprocess import check_call, check_output, run
from glob import glob
from threading import Lock

from Xlib import X, display
from Xlib.ext import randr

lock = Lock()

xlib_rots = {'normal': randr.Rotate_0, 'inverted': randr.Rotate_180,
        'left': randr.Rotate_90, 'right': randr.Rotate_270}
MODES = ['locked', 'auto']
@requires_segment_info
class ScreenRotationSegment(ThreadedSegment):

    interval = 2

    current_state = 0

    # output to manage
    output = None
    touch_output = None

    # Input devices to manage
    devices = None

    # Basedir for accelerometer
    basedir = None

    # Scale of the accelerometer
    scale = 1.0

    # Input devices to be mapped to the specified output
    mapped_inputs = []
    # Touchpads to be enabled/disabled
    touchpads = []

    STATES = []
    g = 8
    # Gravity triggers (rotation -> value)
    triggers = {'normal': -g, 'inverted': g, 'left': g, 'right': -g}
    checks = { }
    touchpad_state = {'normal': 'enable', 'inverted': 'disable',
            'left': 'disable', 'right': 'disable'}

    accel_x = None
    accel_y = None

    # Disable this segment if it runs in a bar on the wrong output
    mode = 1

    last_oneshot = 0
    bar_needs_resize = None

    rotation_hook = None
    hide_controls = { }

    d = None
    window = None

    def set_state(self, output, states=['normal', 'inverted', 'left', 'right'],
            gravity_triggers=None, mapped_inputs=[], touchpads=[], touchpad_states=None,
            rotation_hook=None, hide_controls=True, sensor_is_unsigned=False,
            sensor_max_value=None, **kwargs):
        self.output = output
        self.touch_output = output

        self.sensor_is_unsigned = sensor_is_unsigned
        self.sensor_max_value = sensor_max_value

        self.d = display.Display()
        s = self.d.screen()
        self.window = s.root.create_window(0, 0, 1, 1, 1, s.root_depth)

        self.rotation_hook = rotation_hook
        self.hide_controls = { 'default': hide_controls, output: hide_controls }

        for basedir in glob('/sys/bus/iio/devices/iio:device*'):
            with open(path.join(basedir, 'name')) as f:
                if 'accel' in f.read():
                    self.basedir = basedir
                    break
        else:
            # No accels found, throw an error
            pass

        self.devices = check_output(['xinput', '--list', '--name-only']).splitlines()
        with open(path.join(self.basedir, 'in_accel_scale')) as f:
            self.scale = float(f.read())

        if gravity_triggers:
            self.triggers = gravity_triggers
        if touchpad_states:
            self.touchpad_states = touchpad_states

        self.mapped_inputs = mapped_inputs
        self.touchpads = touchpads

        self.current_state = 0
        self.STATES = states


        self.checks = {
            'normal':   lambda x, y: y < self.triggers['normal'],
            'inverted': lambda x, y: y > self.triggers['inverted'],
            'left':     lambda x, y: x > self.triggers['left'],
            'right':    lambda x, y: x < self.triggers['right']
        }

        self.rotate(self.current_state)
        self.update_touchpad(self.current_state)
        super(ScreenRotationSegment, self).set_state(**kwargs)

    def rotate(self, state):
        outs = get_randr_outputs(self.d, self.window)
        if outs == None:
            return False
        outs = [o for o in outs if o['crtc']]
        op = [o for o in outs if o['name'] == self.output]
        if not len(op):
            # The output to be rotated doesn't exist :(
            return False
        op = op[0]

        # Get all outputs that are mirrored to the output we shall rotate
        # (We must also rotate these outputs)
        current_mode = op['current_mode']

        mirrored_outs = [o for o in outs if o['x'] == op['x'] and o['y'] == op['y'] and o['current_mode'] == op['current_mode']]

        if (self.STATES[self.current_state] in ['left', 'right']) != (self.STATES[state] in ['left', 'right']):

            # If the user has some non-mirrored setup, only normal and inverted
            # layouts are implemented for now.
            # Anything else requires work . . .

            if len(outs) != len(mirrored_outs):
                return False

            # Disable all screens to be rotated
            for o in mirrored_outs:
                randr.set_crtc_config(self.d, o['crtc_id'], 0, 0, 0, 0, 1, [])

            mx_x = 0
            mx_y = 0
            mx_mm_x = 0.0
            mx_mm_y = 0.0

            ratio = 1

            for o in outs:
                if o['width']:
                    mx_x = max(mx_x, o['x'] + o['width'])
                if o['height']:
                    mx_y = max(mx_y, o['y'] + o['height'])
                if o['width'] and o['height']:
                    ratio = max(ratio, max(o['width'] / o['height'], o['height'] / o['width'] ))
                if o in mirrored_outs and o['crtc'].rotation in [xlib_rots['left'],
                        xlib_rots['right']]:
                    if o['mm_width'] and o['width']:
                        mx_mm_x = max(mx_mm_x, o['width'] * 1.0 / o['mm_width'])
                    if o['mm_height'] and o['height']:
                        mx_mm_y = max(mx_mm_y, o['height'] * 1.0 / o['mm_height'])
                else:
                    if o['mm_width'] and o['height']:
                        mx_mm_y = max(mx_mm_y, o['height'] * 1.0 / o['mm_width'])
                    if o['mm_height'] and o['width']:
                        mx_mm_x = max(mx_mm_x, o['width'] * 1.0 / o['mm_height'])

            if mx_x and mx_y and mx_mm_x and mx_mm_y:
                self.window.xrandr_set_screen_size(mx_y, mx_x, int(mx_x / mx_mm_x * ratio),
                        int(mx_y / mx_mm_y * ratio))

        # Actually rotate these outputs, don't change anything besides the rotation
        for o in mirrored_outs:
            randr.set_crtc_config(self.d, o['crtc_id'], 0, o['x'], o['y'],
                    current_mode, xlib_rots[self.STATES[state]], [o['id']])

        if (self.STATES[self.current_state] in ['left', 'right']) != (self.STATES[state] in ['left', 'right']):
            self.bar_needs_resize = self.output
            if self.rotation_hook:
                run(self.rotation_hook, shell=True)

        needs_map = [i.decode('utf-8') for i in self.devices if len([j for j in self.mapped_inputs
            if j in i.decode('utf-8')])]

        ids = [check_output(['xinput', '--list', '--id-only', i]).splitlines()[0].decode()
                for i in needs_map]
        for i in ids:
            check_call(['xinput', '--map-to-output', i, self.touch_output])

        return True

    def update_touchpad(self, state):
        needs_map = [i.decode('utf-8') for i in self.devices if len([j for j in self.touchpads
            if j in i.decode('utf-8')])]

        for dev in needs_map:
            check_call(['xinput', self.touchpad_state[self.STATES[state]], dev])

    def read_accel(self, f):
        val = f.read()
        if not self.sensor_is_unsigned:
            return float(val) * self.scale
        else:
            if(int(val) <= self.sensor_max_value // 2):
                return float(val) * self.scale
            return float(int(val) - self.sensor_max_value) * self.scale


    def update(self, *args, **kwargs):
        if self.mode == 0:
            return -1

        self.accel_x = open(path.join(self.basedir, 'in_accel_x_raw'))
        self.accel_y = open(path.join(self.basedir, 'in_accel_y_raw'))
        x = self.read_accel(self.accel_x)
        y = self.read_accel(self.accel_y)
        self.accel_x.close()
        self.accel_y.close()

        self.devices = check_output(['xinput', '--list', '--name-only']).splitlines()

        for i in range(len(self.STATES)):
            if i == self.current_state:
                continue
            if self.checks[self.STATES[i]](x, y):
                global lock
                with lock:
                    if i == self.current_state:
                        continue
                    if self.rotate(i):
                        self.current_state = i
                        self.update_touchpad(self.current_state)

        return self.current_state

    def render(self, data, segment_info, show_on_all_outputs=True, name='rotation',
            format='{icon}', icons={'left':'l', 'right':'r', 'normal':'n', 'inverted':'i',
                'locked':'l', 'auto':'a'}, additional_controls=[], **kwargs):

        channel_name = 'randr.srot'
        channel_value = None
        if 'payloads' in segment_info and channel_name in segment_info['payloads']:
            channel_value = segment_info['payloads'][channel_name]

        current_output = segment_info['output'] if 'output' in segment_info else None

        if self.bar_needs_resize:
            scrn = self.bar_needs_resize
            self.bar_needs_resize = None
            segment_info['restart'](scrn)


        # A user wants to map devices to a different screen
        if channel_value and not isinstance(channel_value, str) and len(channel_value) == 2 and channel_value[0].startswith('capture_input:') and current_output and channel_value[1] > self.last_oneshot:
            new_output = channel_value[0].split(':')[1]
            if current_output == new_output:
                self.last_oneshot = channel_value[1]
                self.touch_output = new_output
                self.rotate(self.current_state)
        # A user wants to rotate a different screen
        if channel_value and not isinstance(channel_value, str) and len(channel_value) == 2 and channel_value[0].startswith('capture:') and current_output and channel_value[1] > self.last_oneshot:
            new_output = channel_value[0].split(':')[1]
            if current_output == new_output:
                self.last_oneshot = channel_value[1]
                self.output = new_output
                self.touch_output = new_output
                self.rotate(self.current_state)
        # A user wants to toggle auto rotation
        if channel_value and not isinstance(channel_value, str) and len(channel_value) == 2 and channel_value[0] == 'toggle_rot' and current_output and self.output == current_output and channel_value[1] > self.last_oneshot:
            self.last_oneshot = channel_value[1]
            self.mode = 1 - self.mode
            self.rotate(self.current_state)
        # A user wants to toggle visibility of controls
        if channel_value and not isinstance(channel_value, str) and len(channel_value) == 2 and channel_value[0].startswith('toggle_controls') and current_output and channel_value[1] > self.last_oneshot:
            new_output = channel_value[0].split(':')[1]
            if current_output == new_output:
                self.last_oneshot = channel_value[1]
                if new_output in self.hide_controls:
                    self.hide_controls[new_output] = not self.hide_controls[new_output]
                else:
                    self.hide_controls[new_output] = not self.hide_controls['default']

        c_vals = {
                    'mode': MODES[self.mode],
                    'rotation': self.STATES[self.current_state],
                    'output': current_output,
                    'managed_output': self.output,
                    'touch_output': self.touch_output
                }

        if (current_output in self.hide_controls and not self.hide_controls[current_output]) or (not current_output in self.hide_controls and not self.hide_controls['default']):
            add_segments = [{
                    'contents': i[0].format(rotation=self.STATES[self.current_state],
                        mode=MODES[self.mode],
                        managed_output=self.output, touch_output=self.touch_output),
                    'payload_name': channel_name,
                    'highlight_groups': i[1] + ['srot'],
                    'click_values': c_vals,
                    'draw_inner_divider': True
                } for i in additional_controls]
        else:
            add_segments = []

        if current_output and current_output != self.output:
            if not show_on_all_outputs:
                return add_segments if len(add_segments) else None

        if name == 'rotation':
            return [{
                'contents': format.format(rotation=self.STATES[self.current_state],
                    mode=MODES[self.mode], icon=icons[self.STATES[self.current_state]],
                    managed_output=self.output, touch_output=self.touch_output),
                'payload_name': channel_name,
                'highlight_groups': ['srot:' + self.STATES[data], 'srot:rotation', 'srot'],
                'click_values': c_vals,
                'draw_inner_divider': True
            }] + add_segments

        if name == 'mode':
            return [{
                'contents': format.format(rotation=self.STATES[self.current_state],
                    mode=MODES[self.mode], icon=icons[MODES[self.mode]],
                    managed_output=self.output, touch_output=self.touch_output),
                'payload_name': channel_name,
                'highlight_groups': ['srot:' + MODES[self.mode], 'srot:mode', 'srot'],
                'click_values': c_vals,
                'draw_inner_divider': True
            }] + add_segments

        return add_segments if len(add_segments) else None


srot = with_docstring(ScreenRotationSegment(),
''' Manage screen rotation and optionally display some information. Optionally disables
    Touchpads in rotated states. (Note that rotating to the ``left`` and ``right``
    states does not currently work if there is another output connected whose
    displayed content is not mirrored to the screen to be rotated.)

    Requires ``xinput`` and ``python-xlib`` and an accelerometer.

    :param string output:
        The initial output to be rotated and to which touchscreen and stylus inputs are mapped.
        (Note that this can be changed at runtime via interaction with the segment.)
    :param bool show_on_all_outputs:
        If set to false, this segment is only visible on the specified output.
    :param string name:
        Possible values are ``rotation`` and ``mode``. This value is used to determine
        which highlight groups to use and how to populate the ``icon`` field in the
        format string in the returned segment.
        If set to any other value, this segment will produce no output.
    :param string format:
        Format string. Possible fields are ``rotation`` (the current rotation state of the screen),
        ``mode`` (either ``auto`` or ``locked``, depending on whether auto-rotation on the
        screen is enabled or not), and ``icon`` (an icon depicting either the rotation status
        or the auto-rotation status, depending on the segment's name).
    :param dict icons:
        Dictionary mapping rotation states (``normal``, ``inverted``, ``left``, ``right``)
        and auto-rotation states (``locked``, ``auto``) to strings to use to display them.
        Depending on the given name parameter, not all of these fields must be populated.
    :param string list states:
        Allowed rotation states. Possible entries are ``normal``, ``inverted``, ``left``, and
        ``right``. Per default, all of them are enabled.
    :param dict gravity_triggers:
        Sensor values that trigger rotation as a dictionary mapping rotation states
        (``normal``, ``inverted``, ``left``, ``right``) to numbers.
        Defaults to ``{'normal': -8, 'inverted': 8, 'left': 8, 'right': -8}``, meaning that
        a (scaled) reading of the ``in_accel_x_raw`` reading greater than 8 triggers a
        rotation to state ``left`` and a reading less than -8 triggers a rotation to state
        ``right``. Readings of ``in_accel_y_raw`` greater and less than 8 and -8 respectively
        will yield a rotation to the ``inverted`` and ``normal`` states respectively.
    :param string_list mapped_inputs:
        List of substrings of device names that should be mapped to the specified output.
        The entries in the specified list should be only substrings of devices listed as
        ``Virtual core pointer``, not of devices listed as ``Virtual core keyboard``.
    :param string_list touchpads:
        List of substrings of device names of touchpads to be managed.
        The entries in the specified list should be only substrings of devices listed as
        ``Virtual core pointer``, not of devices listed as ``Virtual core keyboard``.
    :param dict touchpad_states:
        Dictionary mapping a rotation state (``normal``, ``inverted``, ``left``, ``right``)
        to either ``enabled`` or ``disabled``, depending on whether the touchpads shall be
        enabled or disabled if the output is currently in the corresponding state.
    :param string rotation_hook:
        A string to be run by a shell after a rotation that changes the screen ratio
        (e.g. from ``normal`` to ``left``).
        It will be executed after the rotation takes place, but before the inputs are
        mapped to the output and before the bar resizes itself.
    :param (string,string_list)_list additional_controls:
        A list of (contents, highlight_groups) pairs. For each entry, an additional
        segment with the given contents and highlight groups is omitted. These segments
        obtain the same click values and may also be used to control the segment behavior.
        Also, all segments additionally use the ``srot`` highlight group and the contents
        may be a format string with all fields (except ``icon``) available.
    :param bool hide_controls:
        Hide the extra control segments. They may be shown via segment interaction.

    Highlight groups used: ``srot:normal`` or ``srot:inverted`` or ``srot:right`` or ``srot:left`` or ``srot:rotation`` or ``srot`` (if the name parameter is ``rotation``) or
    ``srot:auto`` or ``srot:locked`` or ``srot:mode`` or ``srot`` (if the name parameter
    is ``mode``) or None (if the name is set to something else).

    Click values supplied: ``mode`` (string), ``rotation`` (string), ``output`` (string,
    the output this segment is rendered to), ``managed_output`` (string, the screen
    currently managed), ``touch_output`` (string, the screen where touch inputs are mapped to).

    Interaction: This segment supports interaction via bar commands in the following way.
    (Note that parameters given to the bar may be combined with click values.)

    +------------------------------------------+---------------------------------------------+
    | Bar command                              | Description                                 |
    +==========================================+=============================================+
    | #bar;pass_oneshot:capture_input:<output> | Map all specified input devices to <output> |
    |                                          | (experimental)                              |
    +------------------------------------------+---------------------------------------------+
    | #bar;pass_oneshot:capture:<output>       | Rotate the screen <output> instead          |
    |                                          | (experimental)                              |
    +------------------------------------------+---------------------------------------------+
    | #bar;pass_oneshot:toggle_rot             | Toggle auto rotation if used on the screen  |
    |                                          | that is currently managed; otherwise        |
    |                                          | ignored.                                    |
    +------------------------------------------+---------------------------------------------+
    | #bar;pass_oneshot:toggle_controls:<outpt>| Toggles the visibility of additional        |
    |                                          | control segments on output <output>         |
    +------------------------------------------+---------------------------------------------+
''')

@requires_segment_info
class OutputSegment(ThreadedSegment):
    interval = 2

    d = None
    window = None
    outputs = {}

    segment_state = 0 # 0: minimal, 1: list outputs

    MIRROR_STATES = ['extend', 'mirror']
    mirror_state = 0 # 0: extend, 1: mirror

    last_oneshot = 0

    bar_needs_resize = None

    auto_update = False

    lock = Lock()

    redraw_hook = None

    def set_state(self, auto_update=False, redraw_hook=None, **kwargs):
        self.d = display.Display()
        s = self.d.screen()
        self.window = s.root.create_window(0, 0, 1, 1, 1, s.root_depth)

        outs = get_randr_outputs(self.d, self.window)
        if outs != None:
            self.outputs = [o for o in outs if o['connection']]

        prim = [o for o  in self.outputs if o['primary'] != None]
        if len(prim) > 0:
            prim = prim[0]
            onaji = [o for o in self.outputs if o['x'] == prim['x'] and o['y'] == prim['y']]
            if len(onaji) > 1:
                self.mirror_state = 1

        self.auto_update = auto_update

        self.redraw_hook = redraw_hook

        super(OutputSegment, self).set_state(**kwargs)

    def update(self, *args, **kwargs):
        od_out = self.outputs
        outs = get_randr_outputs(self.d, self.window)
        if outs == None:
            return None
        nw_out = [o for o in outs if o['connection']]

        old = [o['name'] for o in self.outputs]
        new = [o['name'] for o in nw_out]

        mg = [o for o in old if o in new]
        mg = [o for o in new if o in mg]

        change = not (len(mg) == len(old) and len(mg) == len(new))

        if change:
            if self.auto_update:
                for o in old:
                    if not o in mg:
                        self.disable_output([out for out in od_out if out['name'] == o][0])
                for o in new:
                    if not o in mg:
                        self.enable_output([out for out in nw_out if out['name'] == o][0])
            else:
                with self.lock:
                    self.outputs = nw_out
        return None

    def update_mirror_state(self):
        if self.mirror_state == 0:
            self.configure_extend()
        elif self.mirror_state == 1:
            self.configure_mirror()

    def resize_randr_screen(self):
        outs = [o for o in self.outputs if o['crtc']]

        for o in outs:
            randr.set_crtc_config(self.d, o['crtc_id'], 0, 0, 0, 0, 1, [])

        mx_x = 0
        mx_y = 0
        mx_mm_x = 0.0
        mx_mm_y = 0.0

        ratio =  1
        for o in outs:
            if o['width']:
                mx_x = max(mx_x, o['x'] + o['width'])
            if o['height']:
                mx_y = max(mx_y, o['y'] + o['height'])
            if o['width'] and o['height']:
                ratio = max(ratio, max(o['width'] / o['height'], o['height'] / o['width'] ))
            if not o['crtc'].rotation in [xlib_rots['left'], xlib_rots['right']]:
                if o['mm_width'] and o['width']:
                    mx_mm_x = max(mx_mm_x, o['width'] * 1.0 / o['mm_width'])
                if o['mm_height'] and o['height']:
                    mx_mm_y = max(mx_mm_y, o['height'] * 1.0 / o['mm_height'])
            else:
                if o['mm_width'] and o['height']:
                    mx_mm_y = max(mx_mm_y, o['height'] * 1.0 / o['mm_width'])
                if o['mm_height'] and o['width']:
                    mx_mm_x = max(mx_mm_x, o['width'] * 1.0 / o['mm_height'])

        if mx_x and mx_y and mx_mm_x and mx_mm_y:
            self.window.xrandr_set_screen_size(mx_x, mx_y, int(mx_x / mx_mm_x * ratio),
                    int(mx_y / mx_mm_y * ratio))

        for o in outs:
            randr.set_crtc_config(self.d, o['crtc_id'], 0, o['x'], o['y'],
                    o['current_mode'], o['crtc'].rotation, [o['id']])



    def configure_mirror(self, output=None):
        with self.lock:
            outs = get_randr_outputs(self.d, self.window)
            if outs == None:
                return False
            self.outputs = [o for o in outs if o['connection']]

        used_crtc = [o['crtc_id'] for o in self.outputs if o['crtc_id']]
        if output:
            free_crtc = [c for c in output['crtcs'] if c not in used_crtc]

            if len(free_crtc) < 1:
                # No crtc available, so we cannot enable this output
                return False

        # We need to find a mode that every connected output supports
        enabled_outputs = [o for o in self.outputs if o['crtc']]

        # print([o['name'] for o in enabled_outputs])

        if len(enabled_outputs) == 0:
            return False

        def resolutions(modes):
            return {(m['width'], m['height']) for m in modes}

        ress = {}
        mode_map = {}
        if output:
            # print(output['modes'])
            ress = resolutions(output['modes'])
            # print(ress)
            mode_map.update({output['name']: output['modes']})
        else:
            ress = resolutions(enabled_outputs[0]['modes'])

        # print(ress)

        for e in enabled_outputs:
            mode_map.update({e['name']: e['modes']})
            ress = [m for m in ress if m in resolutions(e['modes'])]
        if len(ress) > 0:
            # Outputs could agree on a resolution, so pick the largest one
            ress = sorted(ress, reverse=True)
            # print(ress)
            if output:
                mode_map.update({output['name']: \
                    [o for o in output['modes'] if (o['width'], o['height']) == ress[0]]})
            for e in enabled_outputs:
                mode_map.update({e['name']: \
                    [o for o in e['modes'] if (o['width'], o['height']) == ress[0]]})

        if output:
            randr.set_crtc_config(self.d, free_crtc[0],
                0, 0, 0, mode_map[output['name']][0]['id'], randr.Rotate_0, [output['id']])

        for o in enabled_outputs:
            randr.set_crtc_config(self.d, o['crtc_id'],
                0, 0, 0, mode_map[o['name']][0]['id'], o['crtc'].rotation, [o['id']])

        with self.lock:
            outs = get_randr_outputs(self.d, self.window)
            if outs == None:
                return False
            self.outputs = [o for o in outs if o['connection']]
        enabled_outputs = [o for o in self.outputs if o['crtc']]
        # Everything worked (hopefully), so redraw the bar
        if not self.bar_needs_resize:
            self.bar_needs_resize = []
        self.bar_needs_resize += [o['name'] for o in enabled_outputs]
        self.resize_randr_screen()
        if self.redraw_hook:
            run(self.redraw_hook, shell=True)
        return True

    def configure_extend(self, output=None):
        with self.lock:
            outs = get_randr_outputs(self.d, self.window)
            if outs == None:
                return False
            self.outputs = [o for o in outs if o['connection']]
        used_crtc = [o['crtc_id'] for o in self.outputs if o['crtc_id']]
        if output:
            free_crtc = [c for c in output['crtcs'] if c not in used_crtc]

            if len(free_crtc) < 1:
                # No crtc available, so we cannot enable this output
                return False

        enabled_outputs = [o for o in self.outputs if o['crtc']]

        if len(enabled_outputs) == 0:
            return False

        wd = 0
        for o in enabled_outputs:
            randr.set_crtc_config(self.d, o['crtc_id'],
                0, wd, 0, o['mode_ids'][0], o['crtc'].rotation, [o['id']])
            wd += [m['width'] for m in o['modes'] if m['id'] == o['mode_ids'][0]][0]

        if output:
            randr.set_crtc_config(self.d, free_crtc[0],
                    0, wd, 0, output['mode_ids'][0], randr.Rotate_0, [output['id']])

        with self.lock:
            outs = get_randr_outputs(self.d, self.window)
            if outs == None:
                return False
            self.outputs = [o for o in outs if o['connection']]
        enabled_outputs = [o for o in self.outputs if o['crtc']]
        # Everything worked (hopefully), so redraw the bar
        if not self.bar_needs_resize:
            self.bar_needs_resize = []
        self.bar_needs_resize += [o['name'] for o in enabled_outputs]
        self.resize_randr_screen()
        if self.redraw_hook:
            run(self.redraw_hook, shell=True)
        return True

    def enable_output(self, output):
        if self.mirror_state == 0:
            return self.configure_extend(output)
        elif self.mirror_state == 1:
            return self.configure_mirror(output)

        return False

    def disable_output(self, output):
        enabled_outputs = [o for o in self.outputs if o['crtc']]
        if len(enabled_outputs) <= 1 and output in enabled_outputs:
            # Yeah, I know most users are stupid, but at least don't let them disable all outputs
            return False

        # disable the output
        if output['crtc']:
            randr.set_crtc_config(self.d, output['crtc_id'], 0, 0, 0, 0, randr.Rotate_0, [])

        if self.mirror_state == 0:
            res = self.configure_extend(None)
            if res:
                if not self.bar_needs_resize:
                    self.bar_needs_resize = []
                self.bar_needs_resize += [output['name']]
            return res
        elif self.mirror_state == 1:
            res = self.configure_mirror(None)
            if res:
                if not self.bar_needs_resize:
                    self.bar_needs_resize = []
                self.bar_needs_resize += [output['name']]
            return res

        return False

    def render(self, data, segment_info, mirror_format='{mirror_icon}',
            mirror_icons={'mirror': 'M', 'extend': 'E'}, output_format='{output} {status_icon}',
            short_format='{mirror_icon} {output_count}', status_icons={'on': 'on', 'off': 'off'},
            hide_if_single_output=True, auto_shrink=True, **kwargs):
        channel_name = 'randr.output'

        channel_value = None
        if 'payloads' in segment_info and channel_name in segment_info['payloads']:
            channel_value = segment_info['payloads'][channel_name]

        if channel_value and not isinstance(channel_value, str) and len(channel_value) == 2 and channel_value[0].startswith('mode:') and channel_value[1] > self.last_oneshot:
            command = channel_value[0].split(':')[1]
            self.last_oneshot = channel_value[1]
            if command == 'toggle':
                self.mirror_state = (self.mirror_state + 1) % len(self.MIRROR_STATES)
            else:
                for i in self.MIRROR_STATES:
                    if i == command:
                        self.mirror_state = i
            self.update_mirror_state()

        if channel_value and not isinstance(channel_value, str) and len(channel_value) == 2 and channel_value[0].startswith('output:') and channel_value[1] > self.last_oneshot:
            self.last_oneshot = channel_value[1]
            output = channel_value[0].split(':')[1]
            command = channel_value[0].split(':')[2]

            output = [o for o in self.outputs if o['name'] == output]
            if len(output) == 1:
                output = output[0]
                if command == 'on':
                    self.enable_output(output)
                if command == 'off':
                    self.disable_output(output)
                if command == 'toggle':
                    if output['crtc']:
                        self.disable_output(output)
                    else:
                        self.enable_output(output)

        if channel_value and not isinstance(channel_value, str) and len(channel_value) == 2 and channel_value[0] == 'ch_toggle' and channel_value[1] > self.last_oneshot:
            self.last_oneshot = channel_value[1]
            self.segment_state = 1 - self.segment_state

        if self.bar_needs_resize:
            scrn = self.bar_needs_resize
            self.bar_needs_resize = None
            segment_info['restart'](scrn)


        if hide_if_single_output and len(self.outputs) < 2:
            return None

        if auto_shrink and self.segment_state == 0:
            return [{
                'contents': short_format.format(mirror_state=self.MIRROR_STATES[self.mirror_state],
                    mirror_icon=mirror_icons[self.MIRROR_STATES[self.mirror_state]],
                    output_count=len(self.outputs)),
                'highlight_groups': ['output:short',
                    'output:' + self.MIRROR_STATES[self.mirror_state], 'output'],
                'draw_inner_divider': True,
                'payload_name': channel_name,
                'click_values': {'mirror_state': self.MIRROR_STATES[self.mirror_state]}
            }]

        result = []

        result += [{
            'contents': mirror_format.format(mirror_state=self.MIRROR_STATES[self.mirror_state],
                mirror_icon=mirror_icons[self.MIRROR_STATES[self.mirror_state]]),
            'highlight_groups': ['output:' + self.MIRROR_STATES[self.mirror_state],
                'output:mirror_state', 'output'],
            'draw_inner_divider': True,
            'payload_name': channel_name,
            'click_values': {'mirror_state': self.MIRROR_STATES[self.mirror_state]}
        }]

        result += [{
            'contents': output_format.format(output=o['name'],
                status_icon=status_icons[o['status']]),
            'highlight_groups': ['output:' + o['status'], 'output:status', 'output'],
            'draw_inner_divider': True,
            'payload_name': channel_name,
            'click_values': {'output_name': o['name'], 'output_status': o['status']}
        } for o in sorted(self.outputs, key=lambda o: -1.0/o['x'] if o['x'] else 0, reverse=True)]

        return result

    # [ ][ ] / [=] / ... > eDPI (--o) > HDMI1 (o--) >
    # extend/mirror/ ... > only show connected outputs, use different colors for enabled/ disabled
    # Click on [ ][ ] / [=] cycles through modes
    # Click on outputs toggles them on / off acc to mode;
    # In extend mode: sort outputs according to their relative position

output = with_docstring(OutputSegment(),
'''Manage connected outputs, optionally detect newly (dis-)connected outputs automatically.

    Requires ``python-xlib``.

    :param string mirror_format:
        Format used to display the mirror mode (extend/mirror) part of the segment.
        Valid fields are ``mirror_state`` and ``mirror_icon``.
    :param dict mirror_icons:
        Icons used in the ``mirror_icon`` field of ``mirror_format``.
        Needs the entries ``extend`` and ``mirror``.
    :param string output_format:
        Format used to display outputs and information about their status.
        Valid fields are ``output`` and ``status_icon``.
    :param dict status_icons:
        Icons used in the ``status_icon`` field of ``output_format``.
        Needs the entries ``off`` and ``on``.
    :param bool hide_if_single_output:
        Hide the segment if only a single output is connected. (Enabling this will still show
        the segment if there is more than one output connected, of whom only one is not turned
        ``off``.)
    :param bool auto_update:
        If set to true, this segment will automatically enable newly connected outputs
        or disable newly disconnected outputs according to the current mode.
        Also restarts bars appropriately.


    Highlight groups used: ``output:mirror`` or ``output:extend`` or ``output:mirror_state`` or ``output`` (for the mirror mode part) and ``output:off`` or ``output:on`` or ``output:status`` or ``output`` (for the outputs).

    Click values supplied: ``mirror_state`` (string) for the mirror mode part and
    ``output_name`` (string), ``output_status`` (string) in the remaining part.

    Interaction: This segment supports interaction via bar commands in the following way.
    (Note that parameters given to the bar may be combined with click values.)

    +---------------------------------------------------+------------------------------------+
    | Bar command                                       | Description                        |
    +===================================================+====================================+
    | #bar;pass_oneshot:mode:<mode/toggle>              | Set the mirror mode to <mode> or   |
    |                                                   | <toggle> it.                       |
    +---------------------------------------------------+------------------------------------+
    | #bar;pass_oneshot:output:<output>:<on/off/toggle> | Turn output <output> <on/off> or   |
    |                                                   | <toggle> its status. Restarts bars.|
    +---------------------------------------------------+------------------------------------+

''')

# output to manage # Input devices to manage # Basedir for accelerometer # Scale of the accelerometer # Input devices to be mapped to the specified output # Touchpads to be enabled/disabled # Gravity triggers (rotation -> value) # Disable this segment if it runs in a bar on the wrong output # No accels found, throw an error # The output to be rotated doesn't exist :( # Get all outputs that are mirrored to the output we shall rotate # (We must also rotate these outputs) # If the user has some non-mirrored setup, only normal and inverted # layouts are implemented for now. # Anything else requires work . . . # Disable all screens to be rotated # Actually rotate these outputs, don't change anything besides the rotation # A user wants to map devices to a different screen # A user wants to rotate a different screen # A user wants to toggle auto rotation # A user wants to toggle visibility of controls Manage screen rotation and optionally display some information. Optionally disables     Touchpads in rotated states. (Note that rotating to the ``left`` and ``right``     states does not currently work if there is another output connected whose     displayed content is not mirrored to the screen to be rotated.)      Requires ``xinput`` and ``python-xlib`` and an accelerometer.      :param string output:         The initial output to be rotated and to which touchscreen and stylus inputs are mapped.         (Note that this can be changed at runtime via interaction with the segment.)     :param bool show_on_all_outputs:         If set to false, this segment is only visible on the specified output.     :param string name:         Possible values are ``rotation`` and ``mode``. This value is used to determine         which highlight groups to use and how to populate the ``icon`` field in the         format string in the returned segment.         If set to any other value, this segment will produce no output.     :param string format:         Format string. Possible fields are ``rotation`` (the current rotation state of the screen),         ``mode`` (either ``auto`` or ``locked``, depending on whether auto-rotation on the         screen is enabled or not), and ``icon`` (an icon depicting either the rotation status         or the auto-rotation status, depending on the segment's name).     :param dict icons:         Dictionary mapping rotation states (``normal``, ``inverted``, ``left``, ``right``)         and auto-rotation states (``locked``, ``auto``) to strings to use to display them.         Depending on the given name parameter, not all of these fields must be populated.     :param string list states:         Allowed rotation states. Possible entries are ``normal``, ``inverted``, ``left``, and         ``right``. Per default, all of them are enabled.     :param dict gravity_triggers:         Sensor values that trigger rotation as a dictionary mapping rotation states         (``normal``, ``inverted``, ``left``, ``right``) to numbers.         Defaults to ``{'normal': -8, 'inverted': 8, 'left': 8, 'right': -8}``, meaning that         a (scaled) reading of the ``in_accel_x_raw`` reading greater than 8 triggers a         rotation to state ``left`` and a reading less than -8 triggers a rotation to state         ``right``. Readings of ``in_accel_y_raw`` greater and less than 8 and -8 respectively         will yield a rotation to the ``inverted`` and ``normal`` states respectively.     :param string_list mapped_inputs:         List of substrings of device names that should be mapped to the specified output.         The entries in the specified list should be only substrings of devices listed as         ``Virtual core pointer``, not of devices listed as ``Virtual core keyboard``.     :param string_list touchpads:         List of substrings of device names of touchpads to be managed.         The entries in the specified list should be only substrings of devices listed as         ``Virtual core pointer``, not of devices listed as ``Virtual core keyboard``.     :param dict touchpad_states:         Dictionary mapping a rotation state (``normal``, ``inverted``, ``left``, ``right``)         to either ``enabled`` or ``disabled``, depending on whether the touchpads shall be         enabled or disabled if the output is currently in the corresponding state.     :param string rotation_hook:         A string to be run by a shell after a rotation that changes the screen ratio         (e.g. from ``normal`` to ``left``).         It will be executed after the rotation takes place, but before the inputs are         mapped to the output and before the bar resizes itself.     :param (string,string_list)_list additional_controls:         A list of (contents, highlight_groups) pairs. For each entry, an additional         segment with the given contents and highlight groups is omitted. These segments         obtain the same click values and may also be used to control the segment behavior.         Also, all segments additionally use the ``srot`` highlight group and the contents         may be a format string with all fields (except ``icon``) available.     :param bool hide_controls:         Hide the extra control segments. They may be shown via segment interaction.      Highlight groups used: ``srot:normal`` or ``srot:inverted`` or ``srot:right`` or ``srot:left`` or ``srot:rotation`` or ``srot`` (if the name parameter is ``rotation``) or     ``srot:auto`` or ``srot:locked`` or ``srot:mode`` or ``srot`` (if the name parameter     is ``mode``) or None (if the name is set to something else).      Click values supplied: ``mode`` (string), ``rotation`` (string), ``output`` (string,     the output this segment is rendered to), ``managed_output`` (string, the screen     currently managed), ``touch_output`` (string, the screen where touch inputs are mapped to).      Interaction: This segment supports interaction via bar commands in the following way.     (Note that parameters given to the bar may be combined with click values.)      +------------------------------------------+---------------------------------------------+     | Bar command                              | Description                                 |     +==========================================+=============================================+     | #bar;pass_oneshot:capture_input:<output> | Map all specified input devices to <output> |     |                                          | (experimental)                              |     +------------------------------------------+---------------------------------------------+     | #bar;pass_oneshot:capture:<output>       | Rotate the screen <output> instead          |     |                                          | (experimental)                              |     +------------------------------------------+---------------------------------------------+     | #bar;pass_oneshot:toggle_rot             | Toggle auto rotation if used on the screen  |     |                                          | that is currently managed; otherwise        |     |                                          | ignored.                                    |     +------------------------------------------+---------------------------------------------+     | #bar;pass_oneshot:toggle_controls:<outpt>| Toggles the visibility of additional        |     |                                          | control segments on output <output>         |     +------------------------------------------+---------------------------------------------+ # 0: minimal, 1: list outputs # 0: extend, 1: mirror # No crtc available, so we cannot enable this output # We need to find a mode that every connected output supports # print([o['name'] for o in enabled_outputs]) # print(output['modes']) # print(ress) # print(ress) # Outputs could agree on a resolution, so pick the largest one # print(ress) # Everything worked (hopefully), so redraw the bar # No crtc available, so we cannot enable this output # Everything worked (hopefully), so redraw the bar # Yeah, I know most users are stupid, but at least don't let them disable all outputs # disable the output # [ ][ ] / [=] / ... > eDPI (--o) > HDMI1 (o--) > # extend/mirror/ ... > only show connected outputs, use different colors for enabled/ disabled # Click on [ ][ ] / [=] cycles through modes # Click on outputs toggles them on / off acc to mode; # In extend mode: sort outputs according to their relative position Manage connected outputs, optionally detect newly (dis-)connected outputs automatically.      Requires ``python-xlib``.      :param string mirror_format:         Format used to display the mirror mode (extend/mirror) part of the segment.         Valid fields are ``mirror_state`` and ``mirror_icon``.     :param dict mirror_icons:         Icons used in the ``mirror_icon`` field of ``mirror_format``.         Needs the entries ``extend`` and ``mirror``.     :param string output_format:         Format used to display outputs and information about their status.         Valid fields are ``output`` and ``status_icon``.     :param dict status_icons:         Icons used in the ``status_icon`` field of ``output_format``.         Needs the entries ``off`` and ``on``.     :param bool hide_if_single_output:         Hide the segment if only a single output is connected. (Enabling this will still show         the segment if there is more than one output connected, of whom only one is not turned         ``off``.)     :param bool auto_update:         If set to true, this segment will automatically enable newly connected outputs         or disable newly disconnected outputs according to the current mode.         Also restarts bars appropriately.       Highlight groups used: ``output:mirror`` or ``output:extend`` or ``output:mirror_state`` or ``output`` (for the mirror mode part) and ``output:off`` or ``output:on`` or ``output:status`` or ``output`` (for the outputs).      Click values supplied: ``mirror_state`` (string) for the mirror mode part and     ``output_name`` (string), ``output_status`` (string) in the remaining part.      Interaction: This segment supports interaction via bar commands in the following way.     (Note that parameters given to the bar may be combined with click values.)      +---------------------------------------------------+------------------------------------+     | Bar command                                       | Description                        |     +===================================================+====================================+     | #bar;pass_oneshot:mode:<mode/toggle>              | Set the mirror mode to <mode> or   |     |                                                   | <toggle> it.                       |     +---------------------------------------------------+------------------------------------+     | #bar;pass_oneshot:output:<output>:<on/off/toggle> | Turn output <output> <on/off> or   |     |                                                   | <toggle> its status. Restarts bars.|     +---------------------------------------------------+------------------------------------+