Source code for powerline.segments.randr

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']
[docs]@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> | +------------------------------------------+---------------------------------------------+ ''')
[docs]@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.| +---------------------------------------------------+------------------------------------+ ''')