From: NeilBrown Date: Sat, 21 Apr 2012 22:33:45 +0000 (+1000) Subject: Add utils/lock.py X-Git-Url: http://git.neil.brown.name/?a=commitdiff_plain;h=f53e9f16af6c5af1748903edfdeb3647b7c39cb2;p=plato.git Add utils/lock.py Also add associated icons in new 'icons' directory. 'lock' handles locking the display when idle and allowing suspend when completely idle. This has lots of dependencies on things that aren't packaged yet. - susman (suspend manager) - sound (plays sound files when they appear in a directory) Signed-off-by: NeilBrown --- diff --git a/icons/lock-no.png b/icons/lock-no.png new file mode 100644 index 0000000..fe367d3 Binary files /dev/null and b/icons/lock-no.png differ diff --git a/icons/lock-nosusp.png b/icons/lock-nosusp.png new file mode 100644 index 0000000..3174bb5 Binary files /dev/null and b/icons/lock-nosusp.png differ diff --git a/icons/lock-un-nosusp.png b/icons/lock-un-nosusp.png new file mode 100644 index 0000000..965b4e7 Binary files /dev/null and b/icons/lock-un-nosusp.png differ diff --git a/icons/lock-un-pressed.png b/icons/lock-un-pressed.png new file mode 100644 index 0000000..0387031 Binary files /dev/null and b/icons/lock-un-pressed.png differ diff --git a/icons/lock-un.png b/icons/lock-un.png new file mode 100644 index 0000000..990d127 Binary files /dev/null and b/icons/lock-un.png differ diff --git a/icons/lock.png b/icons/lock.png new file mode 100644 index 0000000..bb7b064 Binary files /dev/null and b/icons/lock.png differ diff --git a/icons/lock.xcf b/icons/lock.xcf new file mode 100644 index 0000000..92462fc Binary files /dev/null and b/icons/lock.xcf differ diff --git a/icons/tapinput.png b/icons/tapinput.png new file mode 100644 index 0000000..a6f815b Binary files /dev/null and b/icons/tapinput.png differ diff --git a/utils/lock.py b/utils/lock.py new file mode 100755 index 0000000..ac456ba --- /dev/null +++ b/utils/lock.py @@ -0,0 +1,557 @@ +#!/usr/bin/env python + +# Copyright (C) 2009-2012 Neil Brown +# +# 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() diff --git a/utils/tapinput.png b/utils/tapinput.png deleted file mode 100644 index a6f815b..0000000 Binary files a/utils/tapinput.png and /dev/null differ