#!/usr/bin/python # -*- coding: utf-8 -*- # Screen rotation script for X60 tablet (http://luke.no-ip.org/x60tablet) # HDAPS monitoring added by by Daniel Mendler import os, sys, re, signal, time, errno, subprocess # All allowed rotations rotations = ['normal', 'right', 'inverted', 'left'] # Rotations to pick from when no specific rotation is given on the command line # Also controls the order in which rotations are chosen. preferredRotations = rotations # Rotation to use when switched to tablet mode. If this is set to 'monitor', the # script will check the hdaps orientation and automatically rotate the screen. tabletMode = "monitor" # Use this instead of tabletMode if HDAPS is not installed noHDAPSTabletMode = "left" # Rotation to use when switched to normal laptop mode laptopMode = "normal" # Keycodes to use for each rotation # 104 = pgup, 109 = pgdn, 105 = left, 106 = right, 103 = up, 108 = down keyCodes = { 'normal': {'up': 103, 'dn': 108, 'lt': 105, 'rt': 106}, 'right': {'up': 105, 'dn': 106, 'lt': 108, 'rt': 103}, 'inverted': {'up': 108, 'dn': 103, 'lt': 106, 'rt': 105}, 'left': {'up': 106, 'dn': 105, 'lt': 103, 'rt': 108} } # Keyboard scan codes for arrow keys (you probably don't need to change these) scanCodes = {'up': 0x71, 'dn': 0x6f, 'lt': 0x6e, 'rt': 0x6d} # Pid file for hdaps monitor daemon hdapsPidFile = '/tmp/hdaps-rotate.pid' # HDAPS system files hdapsPosFile = '/sys/devices/platform/hdaps/position' # HDAPS calibrate seems mostly useless, use manually stored calibration instead. hdapsCalibrateFile = '/sys/devices/platform/hdaps/calibrate' #hdapsCalibrateFile = '/etc/hdaps.calibration' # name of display to be rotated as reported by xrandr (if None, it will be determined automatically) displayName = None ## If a local xsetwacom is installed, it should probably take precedent (?) if os.path.isfile('/usr/local/bin/xsetwacom'): xsetwacom = '/usr/local/bin/xsetwacom' elif os.path.isfile('/usr/bin/xsetwacom'): xsetwacom = '/usr/bin/xsetwacom' else: ## If it's not one of those two, just hope it's in the path somewhere. xsetwacom = 'xsetwacom' xrandr = '/usr/bin/xrandr' def main(): global displayName setEnv() if displayName is None: displayName = guessDisplayName() if len(sys.argv) < 2: # No rotation specified, just go to the next one in the preferred list cr = getCurrentRotation() if cr in preferredRotations: nextIndex = (preferredRotations.index(cr) + 1) % len(preferredRotations) else: nextIndex = 0 next = preferredRotations[nextIndex] else: next = sys.argv[1] if not next in rotations: if next == "tablet": next = tabletMode if tabletMode == 'monitor' and not hasHDAPS(): sys.stderr.write("warning: HDAPS does not appear to be installed, skipping monitor mode\n") next = noHDAPSTabletMode elif next == "laptop": next = laptopMode elif next == 'monitor': pass else: sys.stderr.write("Rotation \"%s\" not allowed (pick from %s, tablet, laptop, or monitor)\n" % (next, ', '.join(rotations))) sys.stderr.write(""" monitor -- means the script should run in the background and rotate the screen based on the tablet's orientation (requires HDAPS). tablet -- uses watever orientation is specified in the tabletMode variable in the script laptop -- uses whatever orientation is specified in the laptopMode variable in the script (tabletMode and laptopMode may be edited to suit your preferences) """) sys.exit(-1) if next == 'monitor': if not hasHDAPS(): sys.stderr.write("ERROR: HDAPS does not appear to be installed, can not start orientation monitor.\n") sys.exit(-1) startHDAPSDaemon() else: stopHDAPSDaemon() print "Setting rotation to %s" % next setRotation(next) cr = getCurrentRotation() if cr != next: sys.stderr.write("Failed to change rotation! (is xrandr broken?)\n") def hasHDAPS(): return os.path.exists(hdapsPosFile) and os.path.exists(hdapsCalibrateFile) ## Read and parse HDAPS position def readHDAPSPos(file): try: f = open(file) except: raise Exception("Could not read HDAPS file %s! This is required for automatic orientation-based rotation." % file) l = f.read() f.close() return [int(x) for x in l[1:-2].split(',')] ## Signal handler def quitHDAPS(a, b): os.unlink(hdapsPidFile) os._exit(0) ## HDAPS monitoring loop def monitorHDAPS(): signal.signal(signal.SIGTERM, quitHDAPS) centerX, centerY = readHDAPSPos(hdapsCalibrateFile) x = centerX y = centerY rot = getCurrentRotation() while True: time.sleep(0.1) nx, ny = readHDAPSPos(hdapsPosFile) hRate = 0.1 ## Hysteresis x = x * (1. - hRate) + nx * hRate y = y * (1. - hRate) + ny * hRate dx = x - centerX dy = y - centerY newrot = rot if abs(dx) - abs(dy) > 30: if dx > 30: newrot = 'right' elif dx < -30: newrot = 'left' elif abs(dy) - abs(dx) > 30: if dy > 30: newrot = 'inverted' elif dy < -30: newrot = 'normal' print "%d, %d %s" % (dx, dy, newrot) if rot != newrot: setRotation(newrot) rot = newrot ## Start daemon by double forking def startHDAPSDaemon(): stopHDAPSDaemon() try: if os.fork() > 0: os._exit(0) except OSError, error: sys.stderr.write("fork #1 failed: %d (%s)\n" % (error.errno, error.strerror)) os._exit(1) os.chdir('/') os.setsid() os.umask(0) try: pid = os.fork() if pid > 0: return except OSError, error: sys.stderr.write("fork #2 failed: %d (%s)\n" % (error.errno, error.strerror)) os._exit(1) setUID() f = open(hdapsPidFile, 'w') f.write('%d' % os.getpid()) f.close() #os.chmod(hdapsPidFile, 0777) sys.stderr.write('HDAPS monitor started\n') monitorHDAPS() ## Check for pid file and stop daemon def stopHDAPSDaemon(): try: if os.path.exists(hdapsPidFile): f = open(hdapsPidFile) pid = f.read() f.close() os.kill(int(pid), signal.SIGTERM) sys.stderr.write('HDAPS monitor terminated\n') except OSError, error: if error.errno == errno.ESRCH: sys.stderr.write("Removing stale pid file\n") os.unlink(hdapsPidFile) else: sys.stderr.write('Failed to kill already running daemon!\n') print error sys.exit(1) def runCmd(cmd): c = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) rval = c.wait() stdout = c.stdout.read() stderr = c.stderr.read() if rval != 0: sys.stderr.write("WARNING--Command failed (returned %d):\n %s\n" % (rval, cmd)) sys.stderr.write(stdout) sys.stderr.write(stderr) return (rval, stdout.split('\n'), stderr.split('\n')) def getCurrentRotation(): global displayName try: rrv = randrVersion() if rrv < '1.2': l = [s for s in runCmd(xrandr)[1] if re.match('Current rotation', s)] r = re.sub('Current rotation - ', '', l[0]) return r.strip() elif rrv >= '1.2': l = runCmd(xrandr)[1] #"%s | grep 'LVDS connected' | gawk '{print $4}' | sed -e 's/(//'" % xrandr) l = [x for x in l if re.search(displayName + ' connected', x)][0] l = l.split(' ')[3] l = re.sub(r'\(', '', l) cr = l.strip() print "Current rotation: %s" % cr return cr except: sys.stderr.write("Can not determine current rotation, bailing out :(\n") raise def guessDisplayName(): try: l = runCmd(xrandr)[1] #"%s | grep 'LVDS connected' | gawk '{print $4}' | sed -e 's/(//'" % xrandr) l = [x for x in l if re.search(r'(LVDS.*|default) connected', x)][0] n = l.split(' ')[0] print "Guessed display name:", n return n except: sys.stderr.write("Can not determine current rotation, bailing out :(\n") raise ## Calls xrandr and xsetwacom, sets new keymap. def setRotation(o): global displayName, xrandr if o == None: return if runCmd("%s --output %s --rotate %s" % (xrandr, displayName, o))[0] != 0: raise Exception("xrandr rotate command failed, bailing out.") wacomRots = {'normal': '0', 'left': '2', 'right': '1', 'inverted': '3'} #wacomRots = {'normal': 'NONE', 'left': 'CCW', 'right': 'CW', 'inverted': 'HALF'} tabletDevs = listDevices() if len(tabletDevs) < 1: sys.stderr.write('Did not find any tablet devices, only rotating screen.\n') for d in tabletDevs: if runCmd("%s set %s Rotate %s" % (xsetwacom, d, wacomRots[o]))[0] != 0: raise Exception("xsetwacom rotate command failed, bailing out") setKeymap(o) ## set process UID to the same as the user logged in on :0 def setUID(): username = getUsername() if username == None: return uid = int(passwdRecord(username)[2]) if os.getuid() != uid: try: os.setuid(uid) except: sys.stderr.write('Could not set process UID :(\n') ## Return the /etc/passwd record for user def passwdRecord(user): fd = open('/etc/passwd', 'r') lines = fd.readlines() fd.close() match = filter(lambda s: re.match('%s:' % user, s), lines) return match[0].split(':') ## Get username logged in on :0 def getUsername(): who = runCmd('/usr/bin/who')[1] ## Search for any line that looks like it mentions display :0 l = filter(lambda s: re.search(r'\S+.+\:0(\.0)?\D*', s), who) if len(l) > 1: ## try to pick out the user logged in on the tty (there should be only one on :0) l2 = filter(lambda s: re.search(r'\btty\d+\b', s), l) if len(l2) < 1: sys.stderr.write("WARNING: Guessing X session user is [%s]\n" % l[0]) else: l = l2 if len(l) < 1: sys.stderr.write("Can not determine current X session username\n") return None ustr = (l[0].strip().split(' '))[0] return ustr ## Set up the X environmental variables needed for xrandr and xsetwacom def setEnv(): if os.environ.has_key('DISPLAY'): return # DISPLAY is already set, don't mess with it. username = getUsername() if username == None: return print "Rotating screen for user %s" % username home = passwdRecord(username)[5] xauth = '%s/.Xauthority' % home os.environ['DISPLAY'] = ':0.0' os.environ['XAUTHORITY'] = xauth def setKeymap(o): for sc in scanCodes.keys(): os.system('sudo setkeycodes %x %d' % (scanCodes[sc], keyCodes[o][sc])) def randrVersion(): xrv = runCmd('%s -v' % xrandr)[1][0] xrv = re.sub(r'.*version ', '', xrv).strip() if len(xrv) < 1: raise Exception('Could not determine xrandr version!') return xrv def listDevices(): dev = runCmd("%s list dev" % xsetwacom)[1] dev = [re.sub(r'\s.*', '', s).strip() for s in dev] dev.remove('') return dev main()