# pylint: disable=C0111,R0903 # -*- coding: utf-8 -*- """Shows a widget for each connected screen and allows the user to loop through different orientations. Parameters: * wlrotation.display : Name of the output display that will be rotated + wlrotation.auto : Boolean value if the display should be rotatet automatic by default Requires the following executable: * swaymsg """ import core.module import core.input import util.cli import iio import json from math import degrees, atan2, sqrt from os import environ, path possible_orientations = ["normal", "90", "180", "270"] class iioValue: def __init__(self, channel): self.channel = channel self.scale = self.read('scale') self.offset = self.read('offset') def read(self, attr): return float(self.channel.attrs[attr].value) def value(self): return (self.read('raw') + self.offset) * self.scale class iioAccelDevice: def __init__(self): self.ctx = iio.Context() # store ctx pointer d = self.ctx.find_device('accel_3d') self.x = iioValue(d.find_channel('accel_x')) self.y = iioValue(d.find_channel('accel_y')) self.z = iioValue(d.find_channel('accel_z')) def orientation(self): """ returns tuple of `[success, value]` where `success` indicates, if an accurate value could be meassured and `value` the sway output api compatible value or `normal` if success is `False` """ x_deg, y_deg, z_deg = self._deg() if abs(z_deg) < 70: # checks if device is angled too shallow if x_deg >= 70: return True, "270" if x_deg <= -70: return True, "90" if abs(x_deg) <= 20: if y_deg < 0: return True, "normal" if y_deg > 0: return True, "180" return False, "normal" def _deg(self): gravity = 9.81 x, y, z = self.x.value() / gravity, self.y.value() / gravity, self.z.value() / gravity return degrees(atan2(x, sqrt(pow(y, 2) + pow(z, 2)))), degrees(atan2(y, sqrt(pow(z, 2) + pow(x, 2)))), degrees(atan2(z, sqrt(pow(x, 2) + pow(y, 2)))) class Display(): def __init__(self, name, widget, display_data, auto=False): self.name = name self.widget = widget self.accelDevice = iioAccelDevice() self._lock_auto_rotation(not auto) self.widget.set("orientation", display_data['transform']) core.input.register(widget, button=core.input.LEFT_MOUSE, cmd=self.rotate_90deg) core.input.register(widget, button=core.input.RIGHT_MOUSE, cmd=self.toggle) def rotate_90deg(self, event): # compute new orientation based on current orientation current = self.widget.get("orientation") self._set_rotation(possible_orientations[(possible_orientations.index(current) + 1) % len(possible_orientations)]) # disable auto rotation self._lock_auto_rotation(True) def toggle(self, event): self._lock_auto_rotation(not self.locked) def auto_rotate(self): # automagically rotate the display based on sensor values # this is only called if rotation lock is disabled success, value = self.accelDevice.orientation() if success: self._set_rotation(value) def _set_rotation(self, new_orientation): self.widget.set("orientation", new_orientation) util.cli.execute("swaymsg 'output {} transform {}'".format(self.name, new_orientation)) def _lock_auto_rotation(self, locked): self.locked = locked self.widget.set("locked", self.locked) class Module(core.module.Module): @core.decorators.every(seconds=1) def __init__(self, config, theme): super().__init__(config, theme, []) self.display = None display_filter = self.parameter("display", None) for display in json.loads(util.cli.execute("swaymsg -t get_outputs -r")): name = display['name'] if display_filter == None or display_filter == name: self.display = Display(name, self.add_widget(name=name), display, auto=util.format.asbool(self.parameter("auto", False))) break # I assume that it makes only sense to rotate a single screen def update(self): if self.display == None: return if self.display.locked: return self.display.auto_rotate() def state(self, widget): state = [] state.append("locked" if widget.get("locked", True) else "auto") return state # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4