Created
May 23, 2011 05:24
-
-
Save jbhannah/986262 to your computer and use it in GitHub Desktop.
ThinkPad X60 Tablet Ubuntu setup scripts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# called when tablet screen swivels down (into tablet mode) | |
event=ibm/hotkey HKEY 00000080 00005009 | |
action=/usr/local/bin/rotate.py tablet |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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