|
|
@ -1,27 +1,69 @@ |
|
|
|
#!/usr/bin/env python3 |
|
|
|
#!/usr/bin/env python3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import os |
|
|
|
import subprocess |
|
|
|
import subprocess |
|
|
|
|
|
|
|
|
|
|
|
from PyQt5 import QtCore, QtDBus, QtWidgets |
|
|
|
from PyQt5 import QtCore, QtGui, QtDBus, QtWidgets |
|
|
|
from PyQt5.QtCore import pyqtSignal as Signal, pyqtSlot as Slot, pyqtProperty as Property, Q_CLASSINFO |
|
|
|
from PyQt5.QtCore import pyqtSignal as Signal, pyqtSlot as Slot, pyqtProperty as Property, Q_CLASSINFO |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if "WAYLAND_DISPLAY" in os.environ: |
|
|
|
|
|
|
|
IS_WAYLAND = True |
|
|
|
|
|
|
|
KSCREEN_OUTPUT = "unknown DSI-1-unknown" |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
IS_WAYLAND = False |
|
|
|
|
|
|
|
KSCREEN_OUTPUT = "DSI-1" |
|
|
|
|
|
|
|
|
|
|
|
KSCREEN_OUTPUT = "DSI-1" |
|
|
|
|
|
|
|
XINPUT_TOUCH = "silead_ts" |
|
|
|
XINPUT_TOUCH = "silead_ts" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IIO_BUSNAME = "net.hadess.SensorProxy" |
|
|
|
|
|
|
|
IIO_OBJPATH = "/net/hadess/SensorProxy" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IIO_TO_KSCREEN = { |
|
|
|
IIO_TO_KSCREEN = { |
|
|
|
"normal": "none", |
|
|
|
"normal": "none", |
|
|
|
"right-up": "right", |
|
|
|
"right-up": "left" if IS_WAYLAND else "right", |
|
|
|
"left-up": "left", |
|
|
|
"left-up": "right" if IS_WAYLAND else "left", |
|
|
|
"bottom-up": "inverted" |
|
|
|
"bottom-up": "inverted" |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
KEYBOARD_ORIENTATION = IIO_TO_KSCREEN["right-up"] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IIO_BUSNAME = "net.hadess.SensorProxy" |
|
|
|
|
|
|
|
IIO_OBJPATH = "/net/hadess/SensorProxy" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
KWIN_BUSNAME = "org.kde.KWin" |
|
|
|
|
|
|
|
KWIN_OBJPATH = "/org/kde/KWin" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# DBus |
|
|
|
# DBus |
|
|
|
|
|
|
|
class DIKWinTabletModeManager(QtDBus.QDBusAbstractInterface): |
|
|
|
|
|
|
|
tabletModeAvailableChanged = Signal(bool) |
|
|
|
|
|
|
|
@Property(bool, notify=tabletModeAvailableChanged) |
|
|
|
|
|
|
|
def tabletModeAvailable(self): |
|
|
|
|
|
|
|
return self.property("tabletModeAvailable") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tabletModeChanged = Signal(bool) |
|
|
|
|
|
|
|
@Property(bool, notify=tabletModeChanged) |
|
|
|
|
|
|
|
def tabletMode(self): |
|
|
|
|
|
|
|
return self.property("tabletMode") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Slot(QtDBus.QDBusMessage) |
|
|
|
|
|
|
|
def _on_properties_changed(self, msg): |
|
|
|
|
|
|
|
intf, updated, invald = msg.arguments() |
|
|
|
|
|
|
|
updated = dict(updated) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if "tabletModeAvailable" in updated: |
|
|
|
|
|
|
|
self.tabletModeAvailableChanged.emit(updated["tabletModeAvailable"]) |
|
|
|
|
|
|
|
if "tabletMode" in updated: |
|
|
|
|
|
|
|
self.tabletModeChanged.emit(updated["tabletMode"]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, service, path, connection, parent=None): |
|
|
|
|
|
|
|
super().__init__(service, path, "org.kde.KWin.TabletModeManager", connection, parent) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not connection.connect(service, path, "org.freedesktop.DBus.Properties", |
|
|
|
|
|
|
|
"PropertiesChanged", ["org.kde.KWin.TabletModeManager"], "sa{sv}as", |
|
|
|
|
|
|
|
self._on_properties_changed): |
|
|
|
|
|
|
|
raise RuntimeError("Could not connect to PropertiesChanged") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DISensorProxy(QtDBus.QDBusAbstractInterface): |
|
|
|
class DISensorProxy(QtDBus.QDBusAbstractInterface): |
|
|
|
HasAccelerometerChanged = Signal(bool) |
|
|
|
HasAccelerometerChanged = Signal(bool) |
|
|
|
@Property(bool, notify=HasAccelerometerChanged) |
|
|
|
@Property(bool, notify=HasAccelerometerChanged) |
|
|
@ -33,7 +75,6 @@ class DISensorProxy(QtDBus.QDBusAbstractInterface): |
|
|
|
def AccelerometerOrientation(self) -> str: |
|
|
|
def AccelerometerOrientation(self) -> str: |
|
|
|
return self.property("AccelerometerOrientation") |
|
|
|
return self.property("AccelerometerOrientation") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ClaimAccelerometer(self): |
|
|
|
def ClaimAccelerometer(self): |
|
|
|
self.call("ClaimAccelerometer") |
|
|
|
self.call("ClaimAccelerometer") |
|
|
|
|
|
|
|
|
|
|
@ -82,6 +123,18 @@ class DARotated(QtDBus.QDBusAbstractAdaptor): |
|
|
|
def AutoTurn(self, val): |
|
|
|
def AutoTurn(self, val): |
|
|
|
self.app.set_turn_enabled(val) |
|
|
|
self.app.set_turn_enabled(val) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Property(bool) |
|
|
|
|
|
|
|
def TabletModeManager(self): |
|
|
|
|
|
|
|
return self.app.tmm_enabled |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@TabletModeManager.write |
|
|
|
|
|
|
|
def TabletModeManager(self, val): |
|
|
|
|
|
|
|
self.app.set_tmm_enabled(val) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Property(bool) |
|
|
|
|
|
|
|
def TabletModeManagerAvailable(self): |
|
|
|
|
|
|
|
return self.app.tmm is not None |
|
|
|
|
|
|
|
|
|
|
|
@Slot(str, result=bool) |
|
|
|
@Slot(str, result=bool) |
|
|
|
def Turn(self, orientation): |
|
|
|
def Turn(self, orientation): |
|
|
|
if orientation in ("none", "right", "left", "inverted"): |
|
|
|
if orientation in ("none", "right", "left", "inverted"): |
|
|
@ -106,6 +159,8 @@ class Main(QtCore.QObject): |
|
|
|
# TODO: Figure out initial orientation |
|
|
|
# TODO: Figure out initial orientation |
|
|
|
self.orientation = None |
|
|
|
self.orientation = None |
|
|
|
self.turn_enabled = True |
|
|
|
self.turn_enabled = True |
|
|
|
|
|
|
|
self.tmm_enabled = IS_WAYLAND |
|
|
|
|
|
|
|
self.tablet_mode = None |
|
|
|
|
|
|
|
|
|
|
|
# Turning |
|
|
|
# Turning |
|
|
|
orientationChanged = Signal(str) |
|
|
|
orientationChanged = Signal(str) |
|
|
@ -115,8 +170,14 @@ class Main(QtCore.QObject): |
|
|
|
Turn the Display into a different orientation |
|
|
|
Turn the Display into a different orientation |
|
|
|
@param orientation The KScreen orientation (none|right|left|inverted) |
|
|
|
@param orientation The KScreen orientation (none|right|left|inverted) |
|
|
|
""" |
|
|
|
""" |
|
|
|
|
|
|
|
try: |
|
|
|
subprocess.check_call(["kscreen-doctor", "output.%s.rotation.%s" % (KSCREEN_OUTPUT, orientation)]) |
|
|
|
subprocess.check_call(["kscreen-doctor", "output.%s.rotation.%s" % (KSCREEN_OUTPUT, orientation)]) |
|
|
|
|
|
|
|
if not IS_WAYLAND: |
|
|
|
subprocess.check_call(["xinput", "--map-to-output", XINPUT_TOUCH, KSCREEN_OUTPUT]) |
|
|
|
subprocess.check_call(["xinput", "--map-to-output", XINPUT_TOUCH, KSCREEN_OUTPUT]) |
|
|
|
|
|
|
|
except: |
|
|
|
|
|
|
|
import traceback |
|
|
|
|
|
|
|
traceback.print_exc() |
|
|
|
|
|
|
|
else: |
|
|
|
self.orientation = orientation |
|
|
|
self.orientation = orientation |
|
|
|
self.orientationChanged.emit(orientation) |
|
|
|
self.orientationChanged.emit(orientation) |
|
|
|
|
|
|
|
|
|
|
@ -130,13 +191,28 @@ class Main(QtCore.QObject): |
|
|
|
self.turn_enabled = v |
|
|
|
self.turn_enabled = v |
|
|
|
self.turn_enabled_changed.emit(v) |
|
|
|
self.turn_enabled_changed.emit(v) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_tmm_enabled(self, v): |
|
|
|
|
|
|
|
self.tmm_enabled = v |
|
|
|
|
|
|
|
self.tmm_enabled_changed.emit(v) |
|
|
|
|
|
|
|
|
|
|
|
turn_enabled_changed = Signal(bool) |
|
|
|
turn_enabled_changed = Signal(bool) |
|
|
|
|
|
|
|
tmm_enabled_changed = Signal(bool) |
|
|
|
|
|
|
|
|
|
|
|
def on_systray_clicked(self, reason): |
|
|
|
def on_systray_clicked(self, reason): |
|
|
|
print("Activated", reason) |
|
|
|
print("Activated", reason) |
|
|
|
# TODO: this should change the Icon |
|
|
|
if reason == QtWidgets.QSystemTrayIcon.Trigger: |
|
|
|
#if reason == QtWidgets.QSystemTrayIcon.Trigger: |
|
|
|
if not self.tmm_enabled: |
|
|
|
# self.set_turn_enabled(not self.turn_enabled) |
|
|
|
self.set_turn_enabled(not self.turn_enabled) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def on_tabletmode(self, v): |
|
|
|
|
|
|
|
if self.tmm_enabled: |
|
|
|
|
|
|
|
self.set_turn_enabled(v) |
|
|
|
|
|
|
|
if v: |
|
|
|
|
|
|
|
print("Entered Tablet Mode") |
|
|
|
|
|
|
|
#self.turn_screen(IIO_TO_KSCREEN[self.iio.AccelerometerOrientation]) |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
print("Left Tablet Mode") |
|
|
|
|
|
|
|
self.turn_screen(KEYBOARD_ORIENTATION) |
|
|
|
|
|
|
|
|
|
|
|
# Main |
|
|
|
# Main |
|
|
|
def main(self): |
|
|
|
def main(self): |
|
|
@ -152,6 +228,15 @@ class Main(QtCore.QObject): |
|
|
|
self.adaptor = DARotated(self) |
|
|
|
self.adaptor = DARotated(self) |
|
|
|
self.session_bus.registerObject("/Rotated", self) |
|
|
|
self.session_bus.registerObject("/Rotated", self) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
|
|
|
self.tmm = DIKWinTabletModeManager(KWIN_BUSNAME, KWIN_OBJPATH, self.session_bus) |
|
|
|
|
|
|
|
except: |
|
|
|
|
|
|
|
print("Could not connect to KWin TabletModeManager") |
|
|
|
|
|
|
|
import traceback |
|
|
|
|
|
|
|
traceback.print_exc() |
|
|
|
|
|
|
|
self.tmm_enabled = False |
|
|
|
|
|
|
|
self.tmm = None |
|
|
|
|
|
|
|
|
|
|
|
# Connect to System Bus |
|
|
|
# Connect to System Bus |
|
|
|
self.system_bus = QtDBus.QDBusConnection.systemBus() |
|
|
|
self.system_bus = QtDBus.QDBusConnection.systemBus() |
|
|
|
|
|
|
|
|
|
|
@ -159,23 +244,39 @@ class Main(QtCore.QObject): |
|
|
|
raise RuntimeError("Not connected to System Bus") |
|
|
|
raise RuntimeError("Not connected to System Bus") |
|
|
|
|
|
|
|
|
|
|
|
# Look for iio-sensor-proxy |
|
|
|
# Look for iio-sensor-proxy |
|
|
|
iio = DISensorProxy(IIO_BUSNAME, IIO_OBJPATH, self.system_bus) |
|
|
|
self.iio = iio = DISensorProxy(IIO_BUSNAME, IIO_OBJPATH, self.system_bus) |
|
|
|
|
|
|
|
|
|
|
|
if not iio.HasAccelerometer: |
|
|
|
if not iio.HasAccelerometer: |
|
|
|
raise RuntimeError("No accelerometer reported") |
|
|
|
raise RuntimeError("No accelerometer reported") |
|
|
|
|
|
|
|
|
|
|
|
# Set up System Tray Icon |
|
|
|
# Set up System Tray Icon |
|
|
|
|
|
|
|
self.icon_lock = QtGui.QIcon.fromTheme("emblem-locked") |
|
|
|
|
|
|
|
self.icon_turn = QtGui.QIcon.fromTheme("emblem-unlocked") |
|
|
|
# TODO: Directly work with StatusNotifierItem API? |
|
|
|
# TODO: Directly work with StatusNotifierItem API? |
|
|
|
self.systray = QtWidgets.QSystemTrayIcon(self) |
|
|
|
self.systray = QtWidgets.QSystemTrayIcon(self.icon_turn, self) |
|
|
|
#self.systray.setToolTip("Screen Orientation") |
|
|
|
#self.systray.setToolTip("Screen Orientation") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.turn_enabled_changed.connect(lambda v: self.systray.setIcon(self.icon_turn if v else self.icon_lock)) |
|
|
|
|
|
|
|
|
|
|
|
self.menu = QtWidgets.QMenu() |
|
|
|
self.menu = QtWidgets.QMenu() |
|
|
|
|
|
|
|
# Tablet Mode detection enable |
|
|
|
|
|
|
|
action = self.menu.addAction("Detect Tablet Mode") |
|
|
|
|
|
|
|
action.setEnabled(self.tmm is not None) |
|
|
|
|
|
|
|
action.setCheckable(True) |
|
|
|
|
|
|
|
action.setChecked(self.tmm_enabled) |
|
|
|
|
|
|
|
action.triggered.connect(lambda c: self.set_tmm_enabled(c)) |
|
|
|
|
|
|
|
self.tmm_enabled_changed.connect(lambda c, action=action: action.setChecked(c)) |
|
|
|
|
|
|
|
# Auto turn enable |
|
|
|
action = self.menu.addAction("Auto-Turn") |
|
|
|
action = self.menu.addAction("Auto-Turn") |
|
|
|
action.setCheckable(True) |
|
|
|
action.setCheckable(True) |
|
|
|
action.setChecked(self.turn_enabled) |
|
|
|
action.setChecked(self.turn_enabled) |
|
|
|
self.turn_enabled_changed.connect(lambda v, action=action: action.setChecked(v)) |
|
|
|
action.setEnabled(not self.tmm_enabled) |
|
|
|
action.triggered.connect(lambda c: self.set_turn_enabled(c)) |
|
|
|
action.triggered.connect(lambda c: self.set_turn_enabled(c)) |
|
|
|
|
|
|
|
self.tmm_enabled_changed.connect(lambda v, action=action: action.setEnabled(not v)) |
|
|
|
|
|
|
|
self.turn_enabled_changed.connect(lambda v, action=action: action.setChecked(v)) |
|
|
|
|
|
|
|
|
|
|
|
self.menu.addSeparator() |
|
|
|
self.menu.addSeparator() |
|
|
|
|
|
|
|
# Manual turning |
|
|
|
for label, direction in (("Normal", "none"), |
|
|
|
for label, direction in (("Normal", "none"), |
|
|
|
("Anti-CW", "right"), |
|
|
|
("Anti-CW", "right"), |
|
|
|
("Clockwise", "left"), |
|
|
|
("Clockwise", "left"), |
|
|
@ -184,7 +285,9 @@ class Main(QtCore.QObject): |
|
|
|
action.triggered.connect(lambda *a, d=direction: self.turn_screen(d)) |
|
|
|
action.triggered.connect(lambda *a, d=direction: self.turn_screen(d)) |
|
|
|
action.setEnabled(not self.turn_enabled) |
|
|
|
action.setEnabled(not self.turn_enabled) |
|
|
|
self.turn_enabled_changed.connect(lambda v, action=action: action.setEnabled(not v)) |
|
|
|
self.turn_enabled_changed.connect(lambda v, action=action: action.setEnabled(not v)) |
|
|
|
|
|
|
|
|
|
|
|
self.menu.addSeparator() |
|
|
|
self.menu.addSeparator() |
|
|
|
|
|
|
|
# Quit |
|
|
|
action = self.menu.addAction("Quit") |
|
|
|
action = self.menu.addAction("Quit") |
|
|
|
action.triggered.connect(self.qapp.quit) |
|
|
|
action.triggered.connect(self.qapp.quit) |
|
|
|
self.systray.setContextMenu(self.menu) |
|
|
|
self.systray.setContextMenu(self.menu) |
|
|
@ -192,6 +295,12 @@ class Main(QtCore.QObject): |
|
|
|
self.systray.activated.connect(self.on_systray_clicked) |
|
|
|
self.systray.activated.connect(self.on_systray_clicked) |
|
|
|
self.systray.show() |
|
|
|
self.systray.show() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if self.tmm is not None: |
|
|
|
|
|
|
|
#self.tmm.tabletModeAvailableChanged.connect(self.updateTabletModeAvailable) |
|
|
|
|
|
|
|
#self.updateTabletModeAvailable(self.tmm.tabletModeAvailable) |
|
|
|
|
|
|
|
self.tmm.tabletModeChanged.connect(self.on_tabletmode) |
|
|
|
|
|
|
|
self.on_tabletmode(self.tmm.tabletMode) |
|
|
|
|
|
|
|
|
|
|
|
# Run |
|
|
|
# Run |
|
|
|
iio.ClaimAccelerometer() |
|
|
|
iio.ClaimAccelerometer() |
|
|
|
|
|
|
|
|
|
|
|