Avoid atd to hang due to multiple alarm-script calls
[shr-notifier.git] / notifier
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 # Copyright (C) 2008,2009
5 #       Pietro Montorfano <monto@telefoninux.org>
6 #       Marco Trevisan (TreviƱo) <mail@3v1n0.net>
7 #
8 # This program is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU General Public License
10 # as published by the Free Software Foundation; either version 3
11 # of the License, or (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17
18 import elementary
19 import dbus, e_dbus
20 import time
21 import evas
22 import ecore
23 #import signal
24 import fcntl
25 import sys
26 import os
27 import re
28
29 TITLE                    = "SHR Notifier"
30
31 LOCK_FILE                = "/var/lock/shr-notifier"
32
33 DIALER_ICON              = "/usr/share/icons/shr/86x86/apps/openmoko-dialer.png"
34 MESSAGES_ICON            = "/usr/share/icons/shr/86x86/apps/openmoko-messages.png"
35 NOTIFY_SOUND             = "/usr/share/sounds/purple/alert.wav"
36
37 MISSED_CALLS_PROGRAM     = "/usr/bin/phonelog"
38 #MISSED_CALLS_PROGRAM     = "/usr/bin/elmphonelog"
39 MISSED_SMS_PROGRAM       = "/usr/bin/phoneui-messages"
40
41 ATD_PROGRAM              = "/usr/sbin/atd"
42 ATSPOOL_DIR              = "/var/spool/at"
43 ATD_TRIGGER              = "/var/spool/at/trigger"
44
45 LCD_BACKLIGHT_SYSFILE    = "/sys/class/backlight/gta02-bl/bl_power"
46 RESUME_REASON_SYSFILE    = "/sys/class/i2c-adapter/i2c-0/0-0073/resume_reason"
47 RTC_TIME_SYSFILE         = "/sys/class/rtc/rtc0/since_epoch"
48
49 SMS_1                    = " unread message"
50 SMS_2                    = " unread messages"
51 CALL_1                   = " missed call"
52 CALL_2                   = " missed calls"
53
54 USAGE_BUSNAME            = 'org.freesmartphone.ousaged'
55 USAGE_OBJECTPATH         = '/org/freesmartphone/Usage'
56 USAGE_INTERFACE          = 'org.freesmartphone.Usage'
57
58 GSM_BUSNAME              = 'org.freesmartphone.ogsmd'
59 GSM_OBJECTPATH           = '/org/freesmartphone/GSM/Device'
60 GSM_SIM_INTERFACE        = 'org.freesmartphone.GSM.SIM'
61 GSM_CALL_INTERFACE       = 'org.freesmartphone.GSM.Call'
62
63 PREFERENCES_BUSNAME      = 'org.freesmartphone.opreferencesd'
64 PREFERENCES_OBJECTPATH   = '/org/freesmartphone/Preferences/phone'
65 PREFERENCES_INTERFACE    = 'org.freesmartphone.Preferences.Service'
66
67 DEVICE_BUSNAME           = 'org.freesmartphone.odeviced'
68 AUDIO_OBJECTPATH         = '/org/freesmartphone/Device/Audio'
69 AUDIO_INTERFACE          = 'org.freesmartphone.Device.Audio'
70 VIBRATOR_OBJECTPATH      = '/org/freesmartphone/Device/Vibrator/0'
71 VIBRATOR_INTERFACE       = 'org.freesmartphone.Device.Vibrator'
72 RTC_OBJECTPATH           = '/org/freesmartphone/Device/RTC/0'
73 RTC_INTERFACE            = 'org.freesmartphone.Device.RealtimeClock'
74
75 SECONDS_FOR_RETRY        = 5
76 SECONDS_FOR_NOTIFY       = 120
77 MINUTES_FOR_WAKEUP       = 15
78
79 system_bus               = None
80 gsm_bus                  = None
81 gsm_message_iface        = None
82 gsm_sim_iface            = None
83 gsm_call_iface           = None
84 usage_bus                = None
85 usage_iface              = None
86 pref_bus                 = None
87 pref_iface               = None
88 audio_bus                = None
89 audio_iface              = None
90 vibrator_bus             = None
91 vibrator_iface           = None
92 rtc_bus                  = None
93 rtc_iface                = None
94 notify_timer             = None
95 call_active              = ""
96 missed_calls             = {}
97 missed_sms               = 0
98
99 ATD_NOTIFY_SCRIPT = r'''#!/bin/sh
100 # SHR-notifier check #
101 # File automatically genrated by shr-notifier, don't edit!
102
103 mv "$0" "x$0.$$"
104
105 resume_reason="$(cat %(RESUME_REASON)s)"
106 nextwakeup=$(($(cat %(TIME_FILE)s) + %(INTERVAL)d*60))
107
108 mv "x$0.$$" "$(dirname "$0")/${nextwakeup}.$(basename "$0" | sed "s/^[0-9]\+[^.]\.//")"
109 echo -e "\n" > %(TRIGGER)s
110
111 if [ "$resume_reason" = "4000000000" ]; then
112         #echo 1 > %(LCD_BACKLIGHT)s
113
114         volume="$(expr "$(dbus-send --print-reply --type=method_call --system --dest=%(OPREFERENCESD)s %(PHONE_PATH)s org.freesmartphone.Preferences.Service.GetValue string:"message-volume")" : ".* \(.*\)$")"
115
116         vibration="$(expr "$(dbus-send --print-reply --type=method_call --system --dest=%(OPREFERENCESD)s %(PHONE_PATH)s org.freesmartphone.Preferences.Service.GetValue string:"ring-vibration")" : ".* \(.*\)$")"
117         
118         if [ -z "$volume" ]; then
119                 volume=0
120         fi
121
122         if [ $volume -gt 0 ]; then
123                 dbus-send --type=method_call --print-reply --system --dest=%(ODEVICED)s %(AUDIO_PATH)s org.freesmartphone.Device.Audio.PlaySound string:"%(SOUND)s" int32:0 int32:0
124         fi
125
126         if [ "$vibration" = "true" ]; then
127                 dbus-send --type=method_call --print-reply --system --dest=%(ODEVICED)s %(VIBRATOR_PATH)s org.freesmartphone.Device.Vibrator.VibratePattern int32:2 int32:150 int32:50 int32:100
128         fi
129
130         dbus-send --type=method_call --print-reply --system --dest=%(OUSAGED)s %(USAGE_PATH)s org.freesmartphone.Usage.Suspend || apm -s
131
132         #echo 0 > %(LCD_BACKLIGHT)s
133 fi
134 '''
135
136 ### ATD Notifier (Notify when the phone is suspended)
137 def atd_trigger_update():
138     if not os.path.exists(ATD_TRIGGER):
139         return;
140
141     f = file(ATD_TRIGGER, 'a')
142     f.write('\n')
143     f.close()
144
145 def atd_notify_init():
146     atd_notify_stop()
147
148     if not os.path.exists(ATD_TRIGGER) or not os.path.exists(ATD_PROGRAM):
149         return;
150
151     notifytime = rtc_iface.GetCurrentTime() + (MINUTES_FOR_WAKEUP * 60)
152
153     atfile = os.path.join(ATSPOOL_DIR, '%d.shr-notifier.%d' % (notifytime, os.getpid()))
154     f = file(atfile, 'w')
155     f.write(ATD_NOTIFY_SCRIPT % dict(TIME_FILE=RTC_TIME_SYSFILE, INTERVAL=MINUTES_FOR_WAKEUP,
156                                      TRIGGER=ATD_TRIGGER, SOUND=NOTIFY_SOUND,
157                                      LCD_BACKLIGHT=LCD_BACKLIGHT_SYSFILE,
158                                      RESUME_REASON=RESUME_REASON_SYSFILE,
159                                      OPREFERENCESD=PREFERENCES_BUSNAME, PHONE_PATH=PREFERENCES_OBJECTPATH,
160                                      ODEVICED=DEVICE_BUSNAME, AUDIO_PATH=AUDIO_OBJECTPATH,
161                                      VIBRATOR_PATH=VIBRATOR_OBJECTPATH, OUSAGED=USAGE_BUSNAME,
162                                      USAGE_PATH=USAGE_OBJECTPATH))
163     f.close()
164     os.chmod(atfile, 0755)
165
166     atd_trigger_update()
167
168 def atd_notify_stop():
169     if not os.path.exists(ATSPOOL_DIR):
170         return;
171
172     atd_notifier_found = False
173     for atspool in os.listdir(ATSPOOL_DIR):
174         if re.match('^[0-9]+[^.]\.shr-notifier\.[0-9]+$', atspool):
175             os.unlink(os.path.join(ATSPOOL_DIR, atspool))
176             atd_notifier_found = True
177
178     if atd_notifier_found:
179         atd_trigger_update()
180
181 ### Notify Timer (Vibration and Sound if enabled)
182 def notify():
183     if pref_iface.GetValue("message-volume") > 0:
184         audio_iface.PlaySound(NOTIFY_SOUND, 0, 0)
185
186     if pref_iface.GetValue("ring-vibration"):
187         vibrator_iface.VibratePattern(2, 150, 50, 100)
188
189     return True
190
191 def notify_timer_start():
192     global notify_timer
193
194     if notify_timer != None:
195         notify_timer.delete()
196
197     notify_timer = ecore.timer_add(float(SECONDS_FOR_NOTIFY), notify)
198
199 def notify_timer_stop():
200     global notify_timer
201
202     if notify_timer != None:
203         notify_timer.delete()
204         notify_timer = None
205
206 ### UI functions
207 def update_ui():
208     lost_call_count = 0
209     for (key, value) in missed_calls.iteritems():
210         lost_call_count += len(value)
211         
212     if (lost_call_count == 1):
213         bt_calls.label_set(str(lost_call_count) + CALL_1)
214         bt_calls.show()
215     elif (lost_call_count > 1):
216         bt_calls.label_set(str(lost_call_count) + CALL_2)
217         bt_calls.show()
218     else:
219         bt_calls.hide()
220
221     if (missed_sms == 1):
222         bt_sms.label_set(str(missed_sms) + SMS_1)
223         bt_sms.show()
224     elif (missed_sms > 1):
225         bt_sms.label_set(str(missed_sms) + SMS_2)
226         bt_sms.show()
227     else:
228         bt_sms.hide()
229
230     if ((missed_sms == 0) and ((lost_call_count == 0) or (missed_calls == {}))):
231         win.hide()
232         notify_timer_stop()
233         atd_notify_stop()
234     else:
235         notify_timer_start()
236         atd_notify_init()
237         win.show()
238
239 ## here's the main function, using args so that it doesn't crash even if new parameters will be added
240 def call_signal_handler(sender = "", *args, **kwargs):
241     global call_active
242     call_status = str(args[0])
243     if (args[1].has_key("peer")):
244         caller = str(args[1]["peer"])
245     else:
246         caller = call_active
247     if (call_status == "INCOMING"):
248         call_active = caller
249     elif ((call_status == "ACTIVE") and (call_active != "")):
250         call_active = ""
251     elif ((call_status == "RELEASE") and (call_active != "")):
252         if (missed_calls.has_key(call_active)):
253             missed_calls[call_active].append(time.time())
254         else:
255             missed_calls[call_active] = [time.time()]
256         update_ui()
257         
258 def sms_signal_handler(*args, **kwargs):
259     global missed_sms
260     missed_sms += 1
261     update_ui()
262
263 def sigterm_handler(signum, frame):
264     print "Exiting..."
265     atd_notify_stop()
266     sys.exit(0)
267
268 ## elementary standard signal
269 def destroy(obj = None, event = None, data = ""):
270     global missed_calls
271     global missed_sms
272     global notify_timer
273     missed_calls.clear()
274     missed_sms = 0
275     notify_timer_stop()
276     atd_notify_stop()
277     win.hide()
278     
279 def show_missed_calls(*args, **kwargs):
280     global missed_calls
281     missed_calls = {}
282     update_ui()
283     os.system(MISSED_CALLS_PROGRAM + " &")
284
285 def show_missed_sms(*args, **kwargs):
286     global missed_sms
287     missed_sms = 0
288     update_ui()
289     os.system(MISSED_SMS_PROGRAM + " &")
290
291 if __name__ == "__main__":
292     try:
293         lock = file(LOCK_FILE, 'w')
294         fcntl.lockf(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
295     except IOError:
296         print TITLE + " is already running. Kill it!"
297         sys.exit(1)
298
299     #signal.signal(signal.SIGTERM, sigterm_handler)
300     atd_notify_stop()
301
302     initialized = False
303     while (not initialized):
304         try:
305             elementary.init()
306             
307 ### setting up the dbus and all the interfaces that we need
308             dbus_loop = e_dbus.DBusEcoreMainLoop()
309         
310             system_bus = dbus.SystemBus(mainloop = dbus_loop)
311             gsm_bus = system_bus.get_object(GSM_BUSNAME, GSM_OBJECTPATH)
312             gsm_call_iface = dbus.Interface(gsm_bus, GSM_CALL_INTERFACE)
313             gsm_sim_iface = dbus.Interface(gsm_bus, GSM_SIM_INTERFACE)
314
315             pref_bus = system_bus.get_object(PREFERENCES_BUSNAME, PREFERENCES_OBJECTPATH)
316             pref_iface = dbus.Interface(pref_bus, PREFERENCES_INTERFACE)
317
318             audio_bus = system_bus.get_object(DEVICE_BUSNAME, AUDIO_OBJECTPATH)
319             audio_iface = dbus.Interface(audio_bus, AUDIO_INTERFACE)
320
321             vibrator_bus = system_bus.get_object(DEVICE_BUSNAME, VIBRATOR_OBJECTPATH)
322             vibrator_iface = dbus.Interface(vibrator_bus, VIBRATOR_INTERFACE)
323
324             rtc_bus = system_bus.get_object(DEVICE_BUSNAME, RTC_OBJECTPATH)
325             rtc_iface = dbus.Interface(rtc_bus, RTC_INTERFACE)
326
327 ### Actually we only need to listen for incoming calls
328             gsm_call_iface.connect_to_signal("CallStatus", call_signal_handler)
329             gsm_sim_iface.connect_to_signal("IncomingStoredMessage", sms_signal_handler)
330             initialized = True
331             print "Let's go on"
332         except:
333             time.sleep(SECONDS_FOR_RETRY)
334             initialized = False
335             print "Retrying..."
336
337 ### Now that dbus it's ok lets build the UI
338     win = elementary.Window(TITLE, elementary.ELM_WIN_BASIC)
339     win.title_set(TITLE)
340     win.callback_destroy_add(destroy)
341
342     bg = elementary.Background(win)
343     win.resize_object_add(bg)
344     bg.size_hint_weight_set(0.0, 0.0)
345     bg.size_hint_align_set(-1.0, -1.0)
346     bg.show()
347     
348     box_main = elementary.Box(win)
349     box_main.size_hint_weight_set(0.0, 0.0)
350     box_main.size_hint_align_set(-1.0, -1.0)
351     win.resize_object_add(box_main)
352     box_main.show()
353         
354     bt_calls = elementary.Button(win)
355     bt_calls.label_set(CALL_1)
356     bt_calls.size_hint_weight_set(0.0, 0.0)
357     bt_calls.size_hint_align_set(-1.0, -1.0)
358     bt_calls_icon = elementary.Icon(bt_calls)
359     bt_calls_icon.file_set(DIALER_ICON)
360     bt_calls_icon.size_hint_aspect_set(evas.EVAS_ASPECT_CONTROL_VERTICAL, 1, 1)
361     bt_calls.icon_set(bt_calls_icon)
362     box_main.pack_end(bt_calls)
363     bt_calls.callback_clicked_add(show_missed_calls)
364     bt_calls.show()    
365
366     bt_sms = elementary.Button(win)
367     bt_sms.label_set(SMS_1)
368     bt_sms.size_hint_weight_set(0.0, 0.0)
369     bt_sms.size_hint_align_set(-1.0, -1.0)
370     bt_sms_icon = elementary.Icon(bt_sms)
371     bt_sms_icon.file_set(MESSAGES_ICON)
372     bt_sms_icon.size_hint_aspect_set(evas.EVAS_ASPECT_CONTROL_VERTICAL, 1, 1)
373     bt_sms.icon_set(bt_sms_icon)
374     box_main.pack_end(bt_sms)
375     bt_sms.callback_clicked_add(show_missed_sms)
376     bt_sms.show()    
377
378 ### ok, let's go!!    
379     elementary.run()
380     elementary.shutdown()