]> git.neil.brown.name Git - freerunner.git/blob - lock/lock.py
Improve the check for "are any keys down"
[freerunner.git] / lock / lock.py
1 #!/usr/bin/env python
2
3 # This software is copyright Neil Brown 2009.
4 # It is licensed to you under the terms of the
5 # GNU General Public License version 2.
6 #
7 #
8 # blank and lock the screen when there is no activity.
9 #
10 # We detect activity by watching touch screen, power button,
11 # aux button
12 # After a period with no activity, we drop brightness to 20%
13 # and Grab all devices.
14 # Any activity at this stage returns us to 100% and no grab
15 # Continued inactivity turns display off.  After that, require
16 # a button (AUX or Power) to reactivate.
17 #
18 # If the device seems really idle, we suspend with "apm -s"
19 # Other applications can disable this by getting a LOCK_SH
20 # lock on /var/run/suspend_disabled
21 #
22
23 import gobject
24 import gtk
25 import fcntl
26 import os
27 import sys
28 import struct
29 import time
30 from subprocess import Popen, PIPE
31
32 class EvDev:
33     def __init__(self, path, on_event):
34         self.f = os.open(path, os.O_RDWR|os.O_NONBLOCK);
35         self.ev = gobject.io_add_watch(self.f, gobject.IO_IN, self.read)
36         self.on_event = on_event
37         self.grabbed = False
38         self.downlist = []
39     def read(self, x, y):
40         try:
41             str = os.read(self.f, 16)
42         except:
43             return True
44
45         if len(str) != 16:
46             return True
47         (sec,usec,typ,code,value) = struct.unpack_from("IIHHI", str)
48         if typ == 0x01:
49             # KEY event
50             if value == 0:
51                 # 'up' - remove from downlist
52                 if code in self.downlist:
53                     self.downlist.remove(code)
54             else:
55                 # 'down' - add to downlist
56                 if code not in self.downlist:
57                     self.downlist.append(code)
58         self.on_event(self.down_count(), typ, code, value)
59         return True
60     def down_count(self):
61         if len(self.downlist) == 0:
62             return 0
63         # double check, we might have missed an 'up' event somehow.
64         try:
65             rv = fcntl.ioctl(self.f, EVIOCGKEY(768/8), (768/8) * " " )
66             l = len(rv)/4
67             ra = struct.unpack('%dI' % l, rv)
68             isup = []
69             for k in self.downlist:
70                 by = int(k/8)
71                 bt = k % 8
72                 if by < l and ((ra[by] >> bt) & 1) == 0:
73                     isup.append[k]
74             for k in isup:
75                 self.downlist.remove(k)
76         except:
77             pass
78         return len(self.downlist)
79
80     def grab(self):
81         if self.grabbed:
82             return
83         #print "grab"
84         fcntl.ioctl(self.f, EVIOCGRAB, 1)
85         self.grabbed = True
86     def ungrab(self):
87         if not self.grabbed:
88             return
89         #print "release"
90         fcntl.ioctl(self.f, EVIOCGRAB, 0)
91         self.grabbed = False
92
93 FBIOBLANK = 0x4611
94 FB_BLANK_UNBLANK = 0
95 FB_BLANK_POWERDOWN = 4
96
97 EVIOCGRAB = 0x40044590
98 def EVIOCGKEY(len):
99     return 0x80004518 + len * 65536
100
101 class Screen:
102     def __init__(self):
103         self.state = "unknown"
104         f = open("/sys/class/backlight/gta02-bl/max_brightness")
105         self.max = int(f.read())
106         f.close()
107     def bright(self, pcent):
108         b = int(pcent * self.max / 100)
109         f = open("/sys/class/backlight/gta02-bl/brightness","w")
110         f.write("%d\n" % b)
111         f.close()
112     def power(self, ioc):
113         f = open("/dev/fb0", "r+")
114         fcntl.ioctl(f, FBIOBLANK, ioc)
115         f.close()
116     def on(self):
117         if self.state != "on":
118             self.power(FB_BLANK_UNBLANK)
119             self.state = "on"
120         self.bright(100)
121     def dim(self):
122         if self.state != "on":
123             self.power(FB_BLANK_UNBLANK)
124             self.state = "on"
125         self.bright(20)
126     def off(self):
127         self.bright(0)
128         if self.state != "off":
129             self.power(FB_BLANK_POWERDOWN)
130             self.state = "off"
131
132 def grab_all():
133     global screen, power, aux
134     screen.grab()
135     power.grab()
136     aux.grab()
137
138 def release_all():
139     global screen, power, aux
140     screen.ungrab()
141     power.ungrab()
142     aux.ungrab()
143
144 timeout = None
145 state = "full"
146 dimtime = 15
147 enable_suspend = True
148 def set_timeout():
149     global timeout
150     global state, enable_suspend
151     global dimtime
152     #print "set timeout for", state
153     if timeout != None:
154         gobject.source_remove(timeout)
155     if state == "full":
156         timeout = gobject.timeout_add(dimtime*1000, set_dim)
157     elif state == "dim":
158         timeout = gobject.timeout_add(10*1000, set_off)
159     elif state == "insta-lock":
160         timeout = gobject.timeout_add(1000, set_off)
161     elif state == "off" and enable_suspend:
162         timeout = gobject.timeout_add(30*1000, set_suspend)
163
164     set_ico_file()
165
166 def set_dim():
167     global state
168     global aux, power, screen
169     if aux.down_count() + power.down_count() + screen.down_count() > 0:
170         # button still down
171         set_timeout()
172         return
173     display.dim()
174     grab_all()
175     state = "dim"
176     set_timeout()
177
178 def set_off():
179     global state, dimtime
180     display.off()
181     state = "off"
182     set_timeout()
183     dimtime = 15
184
185 def read_num(filename, default = 0, sep = None):
186     try:
187         f = open(filename)
188     except:
189         f = [ "%d" % default ]
190     num = default
191     for l in f:
192         l = l.split(sep)[0].strip()
193         try:
194             num = float(l)
195             break
196         except:
197             num = default
198     return num
199
200 #Active Internet connections (w/o servers)
201 #Proto Recv-Q Send-Q Local Address           Foreign Address         State
202 #tcp        0     48 192.168.2.202:22        192.168.2.200:34244     ESTABLISHED
203 #tcp        0      0 192.168.2.202:22        192.168.2.200:41473     ESTABLISHED
204 def external_tcp():
205     # check for TCP connections to external computers.
206     netstat = Popen(['netstat','-nt'], stdout = PIPE, close_fds = True)
207     for l in netstat.stdout:
208         l = l.strip()
209         f = l.split()
210         if f[0] != "tcp" or f[5] != 'ESTABLISHED':
211             continue
212         a = f[4].split(':')
213         if a[0] != "127.0.0.1":
214             return True
215     return False
216
217 def set_suspend():
218     global state, display
219     # Check for lock file
220     try:
221         f = open("/var/run/suspend_disabled", "r+")
222     except:
223         f = None
224     if f:
225         try:
226             fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
227         except:
228             set_timeout()
229             return
230
231     # Check for loadavg > 0.5
232     load = read_num("/proc/loadavg")
233     if load > 0.5:
234         set_timeout()
235         return
236
237     # Check for USB power, at least 500mA
238     current = read_num("/sys/class/i2c-adapter/i2c-0/0-0073/pcf50633-mbc/usb_curlim")
239     if current >= 500:
240         set_timeout()
241         return
242
243     #if external_tcp():
244     #    set_timeout()
245     #    return
246
247     os.system("apm -s")
248     if f:
249         f.close()
250     state = "full"
251     set_timeout()
252     display.on()
253
254 def wake_all(down_cnt, *rest):
255     global state, dimtime
256     #print "wake all"
257     display.on()
258     if down_cnt == 0:
259         release_all()
260     if state == "dim" and dimtime < 120:
261         dimtime += 15
262     if state != "disable":
263         state = "full"
264     set_timeout()
265
266 def wake_dim(down_cnt, *rest):
267     global state
268     #print "wake_dim"
269     if state == "dim" or state == "full":
270         wake_all(down_cnt)
271
272
273 ico_file = None
274 def set_ico_file():
275     global state, ico_file, ico
276     if state == "disable":
277         file = "lock-no.png"
278     elif state == "full":
279         if enable_suspend:
280             file = "lock-un.png"
281         else:
282             file = "lock-un-nosusp.png"
283     else:
284         if enable_suspend:
285             file = "lock.png"
286         else:
287             file = "lock-nosusp.png"
288     if file != ico_file:
289         ico.set_from_file("/usr/local/pixmaps/" + file)
290         ico_file = file
291
292 last_ping = 0
293 prev_ping = 0
294 def ping(icon):
295     global state, enable_suspend, last_ping, prev_ping
296     if time.time() - prev_ping < 0.8:
297         grab_all()
298         state = "insta-lock"
299     elif state == "disable":
300         enable_suspend = False
301         state = "full"
302     elif not enable_suspend:
303         enable_suspend = True
304         state = "full"
305     else:
306         state = "disable"
307     prev_ping = last_ping
308     last_ping = time.time()
309     set_timeout()
310
311 def main():
312     global display, ico
313     global screen, power, aux
314     aux = EvDev("/dev/input/event4", wake_all)
315     power = EvDev("/dev/input/event0", wake_all)
316     screen = EvDev("/dev/input/event1", wake_dim)
317     state = "full"
318     display = Screen()
319     display.on()
320     ico = gtk.StatusIcon()
321     set_timeout()
322     ico.connect("activate", ping)
323
324     gtk.main()
325
326 main()