#!/usr/bin/python3

#####CONFIGURATION#####
#sensorname="accel_3d"

screenname = None
touchname = None

#####PROGRAM CODE#####
#Do not change unless you know what you are doing!

xrandr="/usr/bin/xrandr"
xinput="/usr/bin/xinput"

resourcepaths=[
	"./",
	"/usr/share/autorotate/",
	"/usr/local/share/autorotate/"
	]

orientations=[
        "normal",
        "inverted",
        "left",
        "right"
        ]

rotlock=False

import pyudev
import re
import subprocess
import os.path
import signal
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GdkPixbuf, GObject

hasAppIndicator = True
try:
    gi.require_version('AppIndicator', '0.1')
    from gi.repository import AppIndicator3 as AppIndicator
except:
    hasAppIndicator = False

def run_shell_cmd(cmd):
    """runs a shell commands and returns the string generated by the command."""
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
    out = p.stdout.read().strip()
    return out  #This is the stdout from the shell command

def twos_comp(val, scale):
    if scale:
        # Chuwi
        val = val * scale / 9.8
    else:
        # Microsoft Surface (unsupported)
        if val & (1 << (16-1)) != 0:
            val = val - (1 << 16)
    return val

def getOrientation(accelX, accelY, accelZ):
    absAccelX = abs(accelX)
    absAccelY = abs(accelY)
    absAccelZ = abs(accelZ)

    orientation = "nochange"
    if absAccelZ > 0.5:
        orientation = "nochange"
    elif absAccelX > 3 * absAccelY:
        orientation = "normal" if accelX > 0 else "inverted"
    elif absAccelY > 3 * absAccelX:
        orientation = "right" if accelY > 0 else "left"

    return orientation

def rotate(orientation):
    if orientation in orientations and screenname is not None:
        subprocess.call([xrandr, "--output", screenname, "--rotate", orientation])
        if touchname is not None:
            if orientation == "normal":
                subprocess.call([xinput, "set-prop", "pointer:" + touchname, "Coordinate Transformation Matrix",
                    "0", "0", "0", "0", "0", "0", "0", "0", "0"])
            elif orientation == "inverted":
                subprocess.call([xinput, "set-prop", "pointer:" + touchname, "Coordinate Transformation Matrix",
                    "-1", "0", "1", "0", "-1", "1", "0", "0", "1"])
            elif orientation == "right":
                subprocess.call([xinput, "set-prop", "pointer:" + touchname, "Coordinate Transformation Matrix",
                    "0", "1", "0", "-1", "0", "1", "0", "0", "1"])
            elif orientation == "left":
                subprocess.call([xinput, "set-prop", "pointer:" + touchname, "Coordinate Transformation Matrix",
                    "0", "-1", "1", "1", "0", "0", "0", "0", "1"])

def toggleRotLock(event):
    global rotlock
    global icon
    global lockrot
    global unlockrot

    rotlock = not rotlock
    if hasAppIndicator:
        global toggle
        icon.set_icon(lockrot if rotlock else unlockrot)
        toggle.set_label("Unlock Rotation" if rotlock else "Lock Rotation")
    else:
        icon.set_from_pixbuf(lockrot if rotlock else unlockrot)

def checkRotation():
    device = pyudev.Device.from_path(context, path)
    scale = float(device.attributes.get("in_accel_scale"))
    accelX = twos_comp(device.attributes.asint("in_accel_x_raw"), scale)
    accelY = twos_comp(device.attributes.asint("in_accel_y_raw"), scale)
    accelZ = twos_comp(device.attributes.asint("in_accel_z_raw"), scale)
    orientation = getOrientation(accelX, accelY, accelZ)

    if not rotlock and orientation != "nochange":
        global prevorientation
        if orientation != prevorientation:
            prevorientation = orientation
            rotate(orientation)
    GObject.timeout_add(1000, checkRotation)

context = pyudev.Context()

try:
    for device in context.list_devices(subsystem="iio").match_attribute("name", sensorname): break
except:
    for device in context.list_devices(subsystem="iio"): break
assert device

"""Find which output to rotate, and get its current rotation"""
# Cache the xrandr info as we'll use it several times
XRANDR_INFO = run_shell_cmd('xrandr --verbose').decode('utf-8')

# Find the display we are interested to move (= the laptop panel)
POSSIBLE_OUTPUT = [ "DSI1", "DSI-1", "eDP1", "LVDS", "LVDS1" ]
for out in POSSIBLE_OUTPUT:
    match = re.search("^" + out +  r" connected .* \(.*\) (?P<rotation>.*) \(",
                     XRANDR_INFO,
                     re.MULTILINE
                     )
    if match:
        screenname = out
        break

# Cache the xinput info
XINPUT_INFO = run_shell_cmd('xinput --list --name-only').decode('utf-8')

# Find the display we are interested to move (= the laptop panel)
POSSIBLE_OUTPUT = [ "CHPN0001:00" ]
for out in POSSIBLE_OUTPUT:
    match = re.search("^" + out + "$",
                     XINPUT_INFO,
                     re.MULTILINE
                     )
    if match:
        touchname = out
        break

path = device.device_path

prevorientation = ""

for resourcepath in resourcepaths[::-1]:
    if os.path.isfile(resourcepath + "rotate_lock.png"):
        if hasAppIndicator:
            lockrot = resourcepath + "rotate_lock.png"
        else:
            lockrot = GdkPixbuf.Pixbuf.new_from_file(resourcepath + "rotate_lock.png")

    if os.path.isfile(resourcepath + "rotate.png"):
        if hasAppIndicator:
            unlockrot = resourcepath + "rotate.png"
        else:
            unlockrot = GdkPixbuf.Pixbuf.new_from_file(resourcepath + "rotate.png")


if __name__ == "__main__":
    GObject.threads_init()

    global icon
    if hasAppIndicator:
        icon = AppIndicator.Indicator.new("autorotate", "unlockrot", AppIndicator.IndicatorCategory.APPLICATION_STATUS)
        icon.set_status(AppIndicator.IndicatorStatus.ACTIVE)
        menu = Gtk.Menu()
        global toggle
        toggle = Gtk.MenuItem("Lock Rotation")

        menu.append(toggle)
        icon.set_menu(menu)
        toggle.show()

        toggle.connect("activate", toggleRotLock)
    else:
        icon = Gtk.StatusIcon.new_from_pixbuf(unlockrot)
        icon.connect("activate", toggleRotLock)

    # Ctrl-c now kills the scipt
    # https://bugzilla.gnome.org/show_bug.cgi?id=622084#c12
    signal.signal(signal.SIGINT, signal.SIG_DFL)

    newRef=os.fork()
    if newRef==0:
        GObject.idle_add(checkRotation)
        Gtk.main()
    else:
        checkRotation()
