Skip to content

Instantly share code, notes, and snippets.

@jbhannah
Created May 23, 2011 05:24
Show Gist options
  • Save jbhannah/986262 to your computer and use it in GitHub Desktop.
Save jbhannah/986262 to your computer and use it in GitHub Desktop.
ThinkPad X60 Tablet Ubuntu setup scripts
[Desktop Entry]
Type=Application
Exec=rotate.py monitor
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name[en_US]=Monitor Screen Rotation
Name=Monitor Screen Rotation
Comment[en_US]=Monitor the rotation of the display and rotate the screen to match.
Comment=Monitor the rotation of the display and rotate the screen to match.
#!/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 <dmendler at wurzelteiler . de>
import os, sys, re, signal, time, errno, subprocess
# 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'
def hasHDAPS():
return os.path.exists(hdapsPosFile) and os.path.exists(hdapsCalibrateFile)
# 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.
if hasHDAPS():
tabletMode = "monitor"
else:
tabletMode = "right"
# If true, this causes the monitor daemon to check the swivel state of the tablet
# in addition to its orientation.
monitorObeysSwivelState = True
# 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': 104, 'dn': 109, 'lt': 105, 'rt': 106},
'right': {'up': 105, 'dn': 106, 'lt': 109, 'rt': 104},
'inverted': {'up': 109, 'dn': 104, 'lt': 106, 'rt': 105},
'left': {'up': 106, 'dn': 105, 'lt': 104, 'rt': 109}
}
# 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'
tabletModeFile = "/sys/devices/platform/thinkpad_acpi/hotkey_tablet_mode"
# 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'
xinput = '/usr/bin/xinput'
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")
## 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)
def getSwivelState():
return open(tabletModeFile).read().strip()
## HDAPS monitoring loop
def monitorHDAPS():
useHDAPS = hasHDAPS()
signal.signal(signal.SIGTERM, quitHDAPS)
if useHDAPS:
centerX, centerY = readHDAPSPos(hdapsCalibrateFile)
x = centerX
y = centerY
rot = getCurrentRotation()
while True:
time.sleep(0.1)
newrot = rot
## check swivel state
if monitorObeysSwivelState:
swivel = getSwivelState()
if swivel == '0':
newrot = laptopMode
elif swivel == '1':
newrot = tabletMode
## Check orientation if HDAPS is available and orientation is not overridden by swivel state
if useHDAPS and (newrot == 'monitor' or not monitorObeysSwivelState):
newrot = tabletMode
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
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 and newrot != 'monitor':
setRotation(newrot)
rot = newrot
## Start daemon by double forking
def startHDAPSDaemon():
stopHDAPSDaemon()
## double fork
if os.fork() > 0: os._exit(0)
os.chdir('/')
os.setsid()
os.umask(0)
pid = os.fork()
if pid > 0:
return
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': 'NONE', 'left': 'CW', 'right': 'CCW', '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():
cmd = runCmd("%s --list" % xinput)[1]
dev = []
for s in cmd:
m = re.search(r'Serial Wacom Table.*id=\d*', s)
if m != None:
dev.append(re.sub(r'[\WA-Za-z]', '', m.group(0)))
return dev
main()
#!/bin/sh
cd /tmp
mkdir x60tablet
cd x60tablet
sudo sed -i'.old' -e'$a\
# Have the volume keys also change the system volume and display notifications\
echo 0x00fdffff > /sys/devices/platform/thinkpad_acpi/hotkey_mask\
\
# Define the up/down keys on the tablet panel\
setkeycodes 6f 109 # down arrow -> pgdn\
setkeycodes 71 104 # up arrow -> pgup\
setkeycodes 6e 105 # left arrow -> left\
setkeycodes 6d 106 # right arrow -> right' -e'/^exit/d' /etc/rc.local
# Create .tmp file, chown and chmod, THEN remove .tmp to keep sudo from breaking
echo "%admin ALL=NOPASSWD: /usr/bin/setkeycodes" | sudo tee setkeycodes.tmp
sudo chown root.root setkeycodes.tmp
sudo chmod 0440 setkeycodes.tmp
sudo mv setkeycodes.tmp /etc/sudoers.d/setkeycodes
# Do the things we just added to /etc/rc.local
echo 0x00fdffff | sudo tee /sys/devices/platform/thinkpad_acpi/hotkey_mask
sudo setkeycodes 6f 109
sudo setkeycodes 71 104
sudo setkeycodes 6e 105
sudo setkeycodes 6d 106
# rotate, switchPenButton, and ACPI event scripts by Luke Campagnola
# originally from http://luke.no-ip.org/x60tablet/
wget https://gist.github.com/raw/986262/rotate.py
sudo chown root.root rotate.py
sudo chmod 755 rotate.py
sudo mv rotate.py /usr/local/bin
# Set up hdapsd for orientation monitoring
sudo apt-get -y install module-assistant
sudo m-a prepare
sudo m-a update
sudo m-a a-i tp-smapi
sudo depmod -a
sudo modprobe -r hdaps
sudo modprobe -a tp_smapi hdaps
sudo sed -i'.old' -e'$a\
tp_smapi\
hdaps' /etc/modules
sudo apt-get -y install hdapsd
# Clean up packages
sudo apt-get --purge -y autoremove module-assistant
# Monitor screen rotation on login
wget https://gist.github.com/raw/986262/rotate.desktop
mkdir -p ~/.config/autostart
mv rotate.desktop ~/.config/autostart/
rotate.py monitor
# Enable ACPI screen swivel events
wget https://gist.github.com/raw/986262/x60t-swivel-down
wget https://gist.github.com/raw/986262/x60t-swivel-up
sudo chown root.root x60t-swivel-*
sudo chmod 644 x60t-swivel-*
sudo mv x60t-swivel-* /etc/acpi/events
sudo service acpid restart
cd ..
rmdir x60tablet
cd
# called when tablet screen swivels down (into tablet mode)
event=ibm/hotkey HKEY 00000080 00005009
action=/usr/local/bin/rotate.py tablet
# called when tablet screen swivels up (into laptop mode)
event=ibm/hotkey HKEY 00000080 0000500a
action=/usr/local/bin/rotate.py laptop
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment