From cbd0c58b4a67df4caefd03b93b00fe9416baec18 Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Fri, 10 Nov 2023 19:44:33 +0100 Subject: [PATCH] wlrotation: refactored the entire module and integrated external services --- .../modules/contrib/wlrotation.py | 135 +++++++++++++----- 1 file changed, 99 insertions(+), 36 deletions(-) diff --git a/bumblebee_status/modules/contrib/wlrotation.py b/bumblebee_status/modules/contrib/wlrotation.py index ae9dbe8..242c50b 100644 --- a/bumblebee_status/modules/contrib/wlrotation.py +++ b/bumblebee_status/modules/contrib/wlrotation.py @@ -3,6 +3,10 @@ """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 """ @@ -11,53 +15,112 @@ 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.service = "sway-auto-rotate@%s" % path.basename(environ['SWAYSOCK']).replace('-', '\\\\x2d') -# self._widgets = [] - self.update_widgets() - - def update_widgets(self): - for display in json.loads(util.cli.execute("/usr/bin/swaymsg -t get_outputs -r")): + 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'] - widget = self.widget(name) - if not widget: - widget = self.add_widget(name=name, full_text="󰑵 "+name) - widget.set("auto", util.cli.execute("systemctl --user show %s -p ActiveState" % self.service).split("\n")[0].split("=")[1]) - core.input.register(widget, button=core.input.LEFT_MOUSE, cmd=self._toggle) - core.input.register(widget, button=core.input.RIGHT_MOUSE, cmd=self._toggle_auto) - widget.set("orientation", display['transform']) + 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): - curr = widget.get("auto", "inactive") - if curr == "inactive": - return ["warning"] - return [curr] - - def _toggle_auto(self, event): - widget = self.widget(widget_id=event["instance"]) - if widget.get("auto") == "inactive": - util.cli.execute("systemctl --user start %s" % self.service) - else: - util.cli.execute("systemctl --user stop %s" % self.service) - widget.set("auto", util.cli.execute("systemctl --user show %s -p ActiveState" % self.service).split("\n")[0].split("=")[1]) - - def _toggle(self, event): - widget = self.widget(widget_id=event["instance"]) - - # compute new orientation based on current orientation - idx = (possible_orientations.index(widget.get("orientation"))+ 1) % len(possible_orientations) - new_orientation = possible_orientations[idx] - - widget.set("orientation", new_orientation) - - util.cli.execute("swaymsg 'output {} transform {}'".format(widget.name, new_orientation)) + state = [] + state.append("locked" if widget.get("locked", True) else "auto") + return state # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4