2023-11-08 00:28:29 +01:00
|
|
|
# pylint: disable=C0111,R0903
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
"""Shows a widget for each connected screen and allows the user to loop through different orientations.
|
|
|
|
|
2023-11-10 19:44:33 +01:00
|
|
|
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
|
|
|
|
|
2023-11-08 00:28:29 +01:00
|
|
|
Requires the following executable:
|
|
|
|
* swaymsg
|
|
|
|
"""
|
|
|
|
|
2023-11-08 21:52:08 +01:00
|
|
|
import core.module
|
|
|
|
import core.input
|
|
|
|
import util.cli
|
|
|
|
|
2023-11-10 19:44:33 +01:00
|
|
|
import iio
|
2023-11-08 00:28:29 +01:00
|
|
|
import json
|
2023-11-10 19:44:33 +01:00
|
|
|
from math import degrees, atan2, sqrt
|
2023-11-08 00:28:29 +01:00
|
|
|
from os import environ, path
|
|
|
|
|
|
|
|
possible_orientations = ["normal", "90", "180", "270"]
|
|
|
|
|
2023-11-10 19:44:33 +01:00
|
|
|
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)
|
|
|
|
|
2023-11-08 21:52:08 +01:00
|
|
|
class Module(core.module.Module):
|
2023-11-10 19:44:33 +01:00
|
|
|
@core.decorators.every(seconds=1)
|
2023-11-08 21:52:08 +01:00
|
|
|
def __init__(self, config, theme):
|
|
|
|
super().__init__(config, theme, [])
|
|
|
|
|
2023-11-10 19:44:33 +01:00
|
|
|
self.display = None
|
|
|
|
display_filter = self.parameter("display", None)
|
|
|
|
for display in json.loads(util.cli.execute("swaymsg -t get_outputs -r")):
|
2023-11-08 00:28:29 +01:00
|
|
|
name = display['name']
|
2023-11-10 19:44:33 +01:00
|
|
|
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
|
2023-11-08 00:28:29 +01:00
|
|
|
|
2023-11-10 19:44:33 +01:00
|
|
|
def update(self):
|
|
|
|
if self.display == None:
|
|
|
|
return
|
|
|
|
if self.display.locked:
|
|
|
|
return
|
2023-11-08 00:28:29 +01:00
|
|
|
|
2023-11-10 19:44:33 +01:00
|
|
|
self.display.auto_rotate()
|
2023-11-08 00:28:29 +01:00
|
|
|
|
2023-11-10 19:44:33 +01:00
|
|
|
def state(self, widget):
|
|
|
|
state = []
|
|
|
|
state.append("locked" if widget.get("locked", True) else "auto")
|
|
|
|
return state
|
2023-11-08 00:28:29 +01:00
|
|
|
|
|
|
|
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|