--- /dev/null
+#!/usr/bin/env python
+
+# Copyright (C) 2009-2012 Neil Brown <neilb@suse.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#
+# blank and lock the screen when there is no activity.
+#
+# We detect activity by watching touch screen, power button,
+# aux button
+# After a period with no activity, we drop brightness to 20%
+# and Grab all devices.
+# Any activity at this stage returns us to 100% and no grab
+# Continued inactivity turns display off. After that, require
+# a button (AUX or Power) to reactivate.
+#
+# While display is lit we prevent suspend. As soon as display
+# is dark, suspend is allowed to progress.
+#
+# Also handle alerts. Alerts are signaled by files appearing in
+# /var/run/alert. We respond by:
+# - disabling suspend
+# - triggering a vibrator rumble
+# - playing a suitable sound by linking a sound from /etc/alert/$name
+# to /var/run/sound. A separate program plays the sound.
+# - restoring the display to "dim" if it is "off".
+#
+#
+# contains lots of old/dead code
+# - vibrator control for GTA02
+# - watches GTA04 accelerometer and locks display when upside-down
+# - look for external network connections. The idea was to disable
+# suspend if there were any, but it is too easy for them to remain
+# after network is disconnected.
+# Needs lots of cleaning up.
+
+import gobject
+import gtk
+import fcntl
+import os
+import time
+import suspend
+import dnotify
+
+from subprocess import Popen, PIPE
+from vibra import Vibra
+from evdev import EvDev
+
+FBIOBLANK = 0x4611
+FB_BLANK_UNBLANK = 0
+FB_BLANK_POWERDOWN = 4
+
+def sysfs_write(file, val):
+ try:
+ f = open(file, "w")
+ f.write(val)
+ f.close()
+ except:
+ pass
+
+vib_timer = None
+def gta02_set_vibrate(on, off, total):
+ global vib_timer
+ if vib_timer:
+ return
+ vib = "/sys/class/leds/neo1973:vibrator"
+ sysfs_write(vib+"/trigger", "none")
+ sysfs_write(vib+"/trigger", "timer")
+ sysfs_write(vib+"/delay_on", "%d"%on)
+ sysfs_write(vib+"/delay_off", "%d"%off)
+ vib_timer = gobject.timeout_add(total, clear_vibrator)
+def clear_vibrator():
+ return
+ global vib_timer
+ if vib_timer:
+ gobject.source_remove(vib_timer)
+ vib_timer = None
+ vib = "/sys/class/leds/neo1973:vibrator"
+ sysfs_write(vib+"/trigger", "none")
+
+vib_han = None
+def set_vibrate(on, off, total):
+ global vib_han
+ if vib_han:
+ return
+ vib_han = Vibra()
+ vib_han.play(vib_han.multi_vibe(on, total/(on+off), off))
+ gobject.timeout_add(total, clear_vibe)
+def clear_vibe():
+ global vib_han
+ if vib_han:
+ vib_han.close()
+ vib_han = None
+
+class SetAlerts:
+ def __init__(self, alertdir, actiondir):
+ # arrange to respond to alerts.
+ # If a name appears in 'alertdir', respond based
+ # on the content of the same name in 'actiondir'.
+ # Currently that must be a WAV file to be played
+ # If the file disappears, the action must stop instantly
+ # If the file is newer when the action completes, it is
+ # restarted
+ self.alertdir = alertdir
+ self.actiondir = actiondir
+ self.watch = dnotify.dir(alertdir)
+ self.active = {}
+ self.watch.watchall(self.runalert)
+ self.pref = "normal"
+ self.blocker = suspend.blocker(False)
+
+ def setpref(self, str):
+ self.pref = str
+
+ def runalert(self):
+ self.blocker.block()
+ gobject.idle_add(self.alert)
+ return True
+
+ def alert(self):
+ # Only look for new entries here.
+ for n in os.listdir(self.alertdir):
+ if n in self.active:
+ continue
+ if n[0] == '.':
+ continue
+ self.add_alert(n)
+ self.blocker.unblock()
+ return False
+
+ def add_alert(self, name):
+ #print "adding", name
+ self.active[name] = (None, None, None)
+ w = self.watch.watch(name, lambda x : self.runcheck(x, name))
+ if w.ino == 0:
+ del self.active[name]
+ # already disappeared
+ return
+ a = self.action(name)
+ self.active[name] = (w, a, w.mtime)
+
+ def runcheck(self, w, name):
+ gobject.idle_add(lambda : self.check(w, name))
+ return True
+ def check(self, w, name):
+ if name not in self.active:
+ #print "check", name, "not found"
+ return False
+ #print "check", name
+ (w2, a, mtime) = self.active[name]
+ if w.ino == 0:
+ if a:
+ self.stop(a)
+ del self.active[name]
+ return False
+ #if a and not os.path.exists(a):
+ # a = None
+ #if a == None and w.mtime > mtime + 1:
+ # # play back had stopped - start it again
+ # print "restart play"
+ a = self.action(name)
+ self.active[name] = (w, a, w.mtime)
+ return False
+
+ def action(self, name):
+ n = '/var/run/sound/10-alert'
+ try:
+ os.symlink(os.path.join(self.actiondir, self.pref, name), n)
+ except:
+ pass
+ set_vibrate(200,400,1800)
+
+ if display.state != 'on':
+ set_dim()
+ suspend.abort_cycle()
+ return n
+ def stop(self, name):
+ try:
+ os.unlink(name)
+ except OSError:
+ pass
+
+ def stopall(self):
+ for n in self.active:
+ w, a, w_time = self.active[n]
+ if a:
+ self.stop(a)
+ self.active[n] = (w, None, 0)
+
+class Screen:
+ def __init__(self):
+ self.state = "unknown"
+ f = open("/sys/class/backlight/pwm-backlight/max_brightness")
+ self.max = int(f.read())
+ f.close()
+ def bright(self, pcent):
+ b = int(pcent * self.max / 100)
+ f = open("/sys/class/backlight/pwm-backlight/brightness","w")
+ f.write("%d\n" % b)
+ f.close()
+ def power(self, ioc):
+ f = open("/dev/fb0", "r+")
+ fcntl.ioctl(f, FBIOBLANK, ioc)
+ f.close()
+ def on(self):
+ if self.state != "on":
+ self.power(FB_BLANK_UNBLANK)
+ self.state = "on"
+ self.bright(100)
+
+ def dim(self):
+ if self.state != "on":
+ self.power(FB_BLANK_UNBLANK)
+ self.state = "on"
+ self.bright(20)
+ def off(self):
+ self.bright(0)
+ if self.state != "off":
+ self.power(FB_BLANK_POWERDOWN)
+ self.state = "off"
+
+def grab_all():
+ global screen, power, aux, accel
+ screen.grab()
+ power.grab()
+ aux.grab()
+ #accel.grab()
+
+def release_all():
+ global screen, power, aux, accel
+ screen.ungrab()
+ power.ungrab()
+ aux.ungrab()
+ #accel.ungrab()
+
+# wall-clock might get reset when we wake from suspend, so
+# use a monotonic clock to guard against early blanking.
+import ctypes
+
+CLOCK_MONOTONIC = 1 # not sure about this one, check the headers
+
+class timespec(ctypes.Structure):
+ _fields_ = [
+ ('tv_sec', ctypes.c_long),
+ ('tv_nsec', ctypes.c_long)
+ ]
+
+librt = ctypes.CDLL('librt.so.1')
+clock_gettime = librt.clock_gettime
+clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]
+
+def get_ticks():
+ t = timespec()
+ clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(t))
+ return t.tv_sec + t.tv_nsec / 1e9
+
+
+
+timeout = None
+state = "full"
+dimtime = 15
+enable_suspend = True
+last_set = 0
+def set_timeout():
+ global timeout
+ global state
+ global dimtime, last_set
+
+ last_set = get_ticks()
+ if timeout != None:
+ gobject.source_remove(timeout)
+ if state == "full":
+ timeout = gobject.timeout_add(dimtime*1000, set_dim)
+ elif state == "dim":
+ timeout = gobject.timeout_add(10*1000, set_off)
+
+
+ set_ico_file()
+ global susblock, enable_suspend
+ if state == "off" and enable_suspend:
+ susblock.unblock()
+ else:
+ susblock.block()
+
+def set_dim():
+ global state, last_set
+ global aux, power, screen
+ global one_down
+ if aux.down_count() + power.down_count() + screen.down_count() > 0:
+ # button still down
+ one_down = True
+ set_timeout()
+ return
+ one_down = False
+ if state != "off" and get_ticks() - last_set < dimtime - 1:
+ # if delay was too quick, try again
+ set_timeout()
+ return
+ display.dim()
+ grab_all()
+ state = "dim"
+ set_timeout()
+
+def set_off():
+ global state, dimtime
+ global last_set
+ if get_ticks() - last_set < 9.5:
+ set_timeout()
+ return
+ grab_all()
+ display.off()
+ state = "off"
+ set_timeout()
+ dimtime = 15
+
+def read_num(filename, default = 0, sep = None):
+ try:
+ f = open(filename)
+ except:
+ f = [ "%d" % default ]
+ num = default
+ for l in f:
+ l = l.split(sep)[0].strip()
+ try:
+ num = float(l)
+ break
+ except:
+ num = default
+ return num
+
+#Active Internet connections (w/o servers)
+#Proto Recv-Q Send-Q Local Address Foreign Address State
+#tcp 0 48 192.168.2.202:22 192.168.2.200:34244 ESTABLISHED
+#tcp 0 0 192.168.2.202:22 192.168.2.200:41473 ESTABLISHED
+def external_tcp():
+ # check for TCP connections to external computers.
+ netstat = Popen(['netstat','-nt'], stdout = PIPE, close_fds = True)
+ for l in netstat.stdout:
+ l = l.strip()
+ f = l.split()
+ if f[0] != "tcp" or f[5] != 'ESTABLISHED':
+ continue
+ a = f[4].split(':')
+ if a[0] != "127.0.0.1":
+ return True
+ return False
+
+
+def wake_all(down_cnt, moment, typ, code, value):
+ global state, dimtime, alert, suspended
+ if typ == 0:
+ # ignore sync events
+ return
+ if suspended:
+ return
+ alert.stopall()
+ display.on()
+ if down_cnt == 0:
+ release_all()
+ if state == "dim" and dimtime < 120:
+ dimtime += 15
+ if state != "disable":
+ state = "full"
+ set_timeout()
+
+def wake_dim(down_cnt, moment, typ, code, value):
+ global state, suspended
+ if suspended:
+ return
+ if typ == 0:
+ return
+ #print "wake_dim"
+ if state == "dim" or state == "full":
+ wake_all(down_cnt, moment, typ, code, value)
+
+def wake_toggle(down_cnt, moment, typ, code, value):
+ global state, last_set, enable_suspend, suspended
+ if suspended:
+ return
+ # ignore down, just get up
+ if typ != 1 or value:
+ return
+ if ( state == "full" or state == "disable" ) and get_ticks() - last_set > 0.5:
+ enable_suspend = True
+ last_set = 0
+ set_off()
+ else:
+ wake_all(down_cnt, moment, typ, code, value)
+
+EV_SYN = 0
+EV_KEY = 1
+EV_ABS = 3
+ABS_X = 0
+ABS_Y = 1
+ABS_Z = 2
+BTN_X = 307
+BTN_Y = 308
+BTN_Z = 309
+
+shake_cnt = 0
+shake_time = 0
+shake_seen = 0
+invert_timer = 0
+def check_attitude(down_cnt, moment, typ, code, value):
+ global state
+ global shake_cnt, shake_time, shake_seen
+ if moment - shake_time > 0.4:
+ shake_cnt = 0
+ if typ == EV_ABS and abs(value) > 1500:
+ shake_time = moment
+ shake_seen = 1
+ if typ == EV_SYN and shake_seen:
+ shake_cnt += 1
+ shake_seen = 0
+ if typ == EV_ABS and code == ABS_Y and value > 100:
+ shake_cnt = 0
+ if shake_cnt >= 3:
+ shake_cnt = 0
+ #no wake_all(0)
+ #no return
+
+ if typ == EV_KEY and code <= BTN_Z and code >= BTN_X and value == 1:
+ wake_dim(0)
+
+ global invert_timer
+ if typ == EV_ABS and code == ABS_Y and value > 500:
+ # upside down - need this for 0.6 seconds
+ #print "down", moment
+ if invert_timer == 0:
+ invert_timer = gobject.timeout_add(400, attitude_confirm)
+ elif typ == EV_ABS and code == ABS_Y:
+ #print "up", moment
+ if invert_timer:
+ gobject.source_remove(invert_timer)
+ invert_timer = 0
+
+def attitude_confirm():
+ global invert_timer, state
+ # seem to have been inverted for a while
+ invert_timer = 0
+ if state != "off":
+ display.off()
+ grab_all()
+ state = "off"
+ set_timeout()
+ return False
+
+ico_file = None
+def set_ico_file():
+ global state, ico_file, ico
+ global aux, power, screen
+ down = aux.down_count() + power.down_count() + screen.down_count()
+ if state == "disable":
+ file = "lock-no.png"
+ elif state == "full":
+ if enable_suspend:
+ if down:
+ file = "lock-un-pressed.png"
+ else:
+ file = "lock-un.png"
+ else:
+ file = "lock-un-nosusp.png"
+ else:
+ if enable_suspend:
+ file = "lock.png"
+ else:
+ file = "lock-nosusp.png"
+ if file != ico_file:
+ ico.set_from_file("/usr/local/pixmaps/" + file)
+ ico_file = file
+
+last_ping = 0
+prev_ping = 0
+def ping(icon):
+ global state, enable_suspend, last_ping, prev_ping
+ if state == "disable":
+ enable_suspend = False
+ state = "full"
+ elif not enable_suspend:
+ enable_suspend = True
+ state = "full"
+ else:
+ state = "disable"
+ prev_ping = last_ping
+ last_ping = time.time()
+ set_timeout()
+
+def setfile(name, val):
+ f = open(name, 'w')
+ f.write(val)
+ f.close()
+
+def do_release():
+ #print "Releasing"
+ global suspender
+ suspender.release()
+
+def suspending():
+ # make sure all buttons have been checked
+ # everything happens in events. When we go idle,
+ # we have processed everything and can release the suspend.
+ global suspender, suspended
+ #print "suspending"
+ gobject.idle_add(do_release)
+ #suspended = True
+ return False
+
+def resumed():
+ global suspended
+ #print "resumed"
+ suspended = False
+
+def main():
+ global display, ico
+ global screen, power, aux, accel
+ global alert, suspender, susblock
+ aux = EvDev("/dev/input/aux", wake_all)
+ power = EvDev("/dev/input/power", wake_toggle)
+ screen = EvDev("/dev/input/touchscreen", wake_dim)
+
+ try:
+ os.mkdir("/var/run/alert")
+ except:
+ pass
+ alert = SetAlerts("/var/run/alert", "/etc/alert")
+ #setfile('/sys/bus/spi/devices/spi3.1/threshold', '500')
+ #setfile('/sys/bus/spi/devices/spi3.1/sample_rate','0')
+ #setfile('/sys/bus/spi/devices/spi3.1/taps', '7000 5')
+ #accel = EvDev("/dev/input/accel", check_attitude)
+ state = "full"
+ display = Screen()
+ display.on()
+ susblock = suspend.blocker()
+ ico = gtk.StatusIcon()
+ set_timeout()
+ ico.connect("activate", ping)
+
+ suspender.immediate(True)
+ gtk.main()
+
+suspended = False
+one_down = False
+suspender = suspend.monitor(suspending, resumed)
+main()