3 # Copyright (C) 2009-2012 Neil Brown <neilb@suse.de>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 # blank and lock the screen when there is no activity.
22 # We detect activity by watching touch screen, power button,
24 # After a period with no activity, we drop brightness to 20%
25 # and Grab all devices.
26 # Any activity at this stage returns us to 100% and no grab
27 # Continued inactivity turns display off. After that, require
28 # a button (AUX or Power) to reactivate.
30 # While display is lit we prevent suspend. As soon as display
31 # is dark, suspend is allowed to progress.
33 # Also handle alerts. Alerts are signaled by files appearing in
34 # /run/alert. We respond by:
36 # - triggering a vibrator rumble
37 # - playing a suitable sound by linking a sound from /etc/alert/$name
38 # to /run/sound. A separate program plays the sound.
39 # - restoring the display to "dim" if it is "off".
50 from subprocess import Popen, PIPE
51 from vibra import Vibra
52 from evdev import EvDev
56 FB_BLANK_POWERDOWN = 4
58 def sysfs_write(file, val):
67 def gta02_set_vibrate(on, off, total):
71 vib = "/sys/class/leds/neo1973:vibrator"
72 sysfs_write(vib+"/trigger", "none")
73 sysfs_write(vib+"/trigger", "timer")
74 sysfs_write(vib+"/delay_on", "%d"%on)
75 sysfs_write(vib+"/delay_off", "%d"%off)
76 vib_timer = gobject.timeout_add(total, clear_vibrator)
81 gobject.source_remove(vib_timer)
83 vib = "/sys/class/leds/neo1973:vibrator"
84 sysfs_write(vib+"/trigger", "none")
87 def set_vibrate(on, off, total):
92 vib_han.play(vib_han.multi_vibe(on, total/(on+off), off))
93 gobject.timeout_add(total, clear_vibe)
101 def __init__(self, alertdir, actiondir):
102 # arrange to respond to alerts.
103 # If a name appears in 'alertdir', respond based
104 # on the content of the same name in 'actiondir'.
105 # Currently that must be a WAV file to be played
106 # If the file disappears, the action must stop instantly
107 # If the file is newer when the action completes, it is
109 self.alertdir = alertdir
110 self.actiondir = actiondir
111 self.watch = dnotify.dir(alertdir)
113 self.watch.watchall(self.runalert)
115 self.blocker = suspend.blocker(False)
117 def setpref(self, str):
122 gobject.idle_add(self.alert)
126 # Only look for new entries here.
127 for n in os.listdir(self.alertdir):
133 self.blocker.unblock()
136 def add_alert(self, name):
137 #print "adding", name
138 self.active[name] = (None, None, None)
139 w = self.watch.watch(name, lambda x : self.runcheck(x, name))
141 del self.active[name]
142 # already disappeared
144 a = self.action(name)
145 self.active[name] = (w, a, w.mtime)
147 def runcheck(self, w, name):
148 gobject.idle_add(lambda : self.check(w, name))
150 def check(self, w, name):
151 if name not in self.active:
152 #print "check", name, "not found"
155 (w2, a, mtime) = self.active[name]
159 del self.active[name]
161 #if a and not os.path.exists(a):
163 #if a == None and w.mtime > mtime + 1:
164 # # play back had stopped - start it again
165 # print "restart play"
166 a = self.action(name)
167 self.active[name] = (w, a, w.mtime)
170 def action(self, name):
171 n = '/run/sound/10-alert'
173 os.symlink(os.path.join(self.actiondir, self.pref, name), n)
176 set_vibrate(200,400,1800)
178 if display.state != 'on':
180 suspend.abort_cycle()
182 def stop(self, name):
189 for n in self.active:
190 w, a, w_time = self.active[n]
193 self.active[n] = (w, None, 0)
197 self.state = "unknown"
198 f = open("/sys/class/backlight/pwm-backlight/max_brightness")
199 self.max = int(f.read())
201 def bright(self, pcent):
202 b = int(pcent * self.max / 100)
203 f = open("/sys/class/backlight/pwm-backlight/brightness","w")
206 def power(self, ioc):
207 f = open("/dev/fb0", "r+")
208 fcntl.ioctl(f, FBIOBLANK, ioc)
211 if self.state != "on":
212 self.power(FB_BLANK_UNBLANK)
217 if self.state != "on":
218 self.power(FB_BLANK_UNBLANK)
223 if self.state != "off":
224 self.power(FB_BLANK_POWERDOWN)
228 global screen, power, aux, accel
235 global screen, power, aux, accel
241 # wall-clock might get reset when we wake from suspend, so
242 # use a monotonic clock to guard against early blanking.
245 CLOCK_MONOTONIC = 1 # not sure about this one, check the headers
247 class timespec(ctypes.Structure):
249 ('tv_sec', ctypes.c_long),
250 ('tv_nsec', ctypes.c_long)
253 librt = ctypes.CDLL('librt.so.1')
254 clock_gettime = librt.clock_gettime
255 clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]
259 clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(t))
260 return t.tv_sec + t.tv_nsec / 1e9
265 enable_suspend = True
270 global dimtime, last_set
272 last_set = get_ticks()
274 gobject.source_remove(timeout)
276 timeout = gobject.timeout_add(dimtime*1000, set_dim)
278 timeout = gobject.timeout_add(10*1000, set_off)
281 global susblock, enable_suspend
282 if state == "off" and enable_suspend:
288 global state, last_set
289 global aux, power, screen
291 if aux.down_count() + power.down_count() + screen.down_count() > 0:
297 if state != "off" and get_ticks() - last_set < dimtime - 1:
298 # if delay was too quick, try again
307 global state, dimtime
309 if get_ticks() - last_set < 9.5:
318 def read_num(filename, default = 0, sep = None):
322 f = [ "%d" % default ]
325 l = l.split(sep)[0].strip()
333 #Active Internet connections (w/o servers)
334 #Proto Recv-Q Send-Q Local Address Foreign Address State
335 #tcp 0 48 192.168.2.202:22 192.168.2.200:34244 ESTABLISHED
336 #tcp 0 0 192.168.2.202:22 192.168.2.200:41473 ESTABLISHED
338 # check for TCP connections to external computers.
339 netstat = Popen(['netstat','-nt'], stdout = PIPE, close_fds = True)
340 for l in netstat.stdout:
343 if f[0] != "tcp" or f[5] != 'ESTABLISHED':
346 if a[0] != "127.0.0.1":
350 def wake_all(down_cnt, moment, typ, code, value):
351 global state, dimtime, alert, suspended
357 if type == 1 and code != 330:
358 # EV_KEY but not BTN_TOUCH
363 if state == "dim" and dimtime < 120:
365 if state != "disable":
369 def wake_dim(down_cnt, moment, typ, code, value):
370 global state, suspended
373 if typ != 1: # EV_KEY
376 if state == "dim" or state == "full":
377 wake_all(down_cnt, moment, typ, code, value)
379 def wake_toggle(down_cnt, moment, typ, code, value):
380 global state, last_set, enable_suspend, suspended
383 # ignore down, just get up
384 if typ != 1 or value:
386 if ( state == "full" or state == "disable" ) and get_ticks() - last_set > 0.5:
387 enable_suspend = True
391 wake_all(down_cnt, moment, typ, code, value)
407 def check_attitude(down_cnt, moment, typ, code, value):
409 global shake_cnt, shake_time, shake_seen
410 if moment - shake_time > 0.4:
412 if typ == EV_ABS and abs(value) > 1500:
415 if typ == EV_SYN and shake_seen:
418 if typ == EV_ABS and code == ABS_Y and value > 100:
425 if typ == EV_KEY and code <= BTN_Z and code >= BTN_X and value == 1:
429 if typ == EV_ABS and code == ABS_Y and value > 500:
430 # upside down - need this for 0.6 seconds
431 #print "down", moment
432 if invert_timer == 0:
433 invert_timer = gobject.timeout_add(400, attitude_confirm)
434 elif typ == EV_ABS and code == ABS_Y:
437 gobject.source_remove(invert_timer)
440 def attitude_confirm():
441 global invert_timer, state
442 # seem to have been inverted for a while
453 global state, ico_file, ico
454 global aux, power, screen
455 down = aux.down_count() + power.down_count() + screen.down_count()
456 if state == "disable":
458 elif state == "full":
461 file = "lock-un-pressed.png"
465 file = "lock-un-nosusp.png"
470 file = "lock-nosusp.png"
472 ico.set_from_file("/usr/local/pixmaps/" + file)
478 global state, enable_suspend, last_ping, prev_ping
479 if state == "disable":
480 enable_suspend = False
482 elif not enable_suspend:
483 enable_suspend = True
487 prev_ping = last_ping
488 last_ping = time.time()
491 def setfile(name, val):
502 # make sure all buttons have been checked
503 # everything happens in events. When we go idle,
504 # we have processed everything and can release the suspend.
505 global suspender, suspended
507 gobject.idle_add(do_release)
518 global screen, power, aux, accel
519 global alert, suspender, susblock
520 aux = EvDev("/dev/input/aux", wake_all)
521 power = EvDev("/dev/input/power", wake_toggle)
522 screen = EvDev("/dev/input/touchscreen", wake_dim)
525 os.mkdir("/run/alert")
528 alert = SetAlerts("/run/alert", "/etc/alert")
529 #setfile('/sys/bus/spi/devices/spi3.1/threshold', '500')
530 #setfile('/sys/bus/spi/devices/spi3.1/sample_rate','0')
531 #setfile('/sys/bus/spi/devices/spi3.1/taps', '7000 5')
532 #accel = EvDev("/dev/input/accel", check_attitude)
536 susblock = suspend.blocker()
537 ico = gtk.StatusIcon()
539 ico.connect("activate", ping)
541 suspender.immediate(True)
546 suspender = suspend.monitor(suspending, resumed)