wlrotation: refactored the entire module and integrated external services

This commit is contained in:
Ludwig Behm 2023-11-10 19:44:33 +01:00
parent bbc26c263c
commit cbd0c58b4a
Signed by: l.behm
GPG key ID: D344835D63B89384

View file

@ -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