By default visual, sound and vibration events are enabled, default timeout interval...
[callnotify] / src / usr / lib / hildon-desktop / CallNotify.py
1 import gtk, gst
2 import gobject
3 import hildon, hildondesktop
4 import sqlite3
5 import time
6 import dbus
7 import osso
8 import atexit, os, datetime
9 from dbus.mainloop.glib import DBusGMainLoop
10
11 WriteLog = False
12
13 class Playbin2:
14         def __init__(self):
15                 self.idle = True # not playing at the moment
16                 self.configDir = "/home/user/.config/CallNotify/"
17                 self.Debug = WriteLog
18                 # create a playbin2 pipe
19                 self.player = gst.element_factory_make("playbin2", "player")
20                 # connect a signal handler to it's bus
21                 bus = self.player.get_bus()
22                 bus.add_signal_watch()
23                 bus.connect("message", self.on_message)
24
25         def on_message(self, bus, message):
26                 t = message.type
27                 if t == gst.MESSAGE_EOS:
28                         self.player.set_state(gst.STATE_NULL)
29                         self.idle = True
30                         self.dbg('Playbin2: EOS: STATE_NULL')
31                 elif t == gst.MESSAGE_ERROR:
32                         #err, debug = message.parse_error()
33                         #print >> sys.stderr, "Error: {0} {1}".format(err, debug)
34                         self.player.set_state(gst.STATE_NULL)
35                         self.idle = True
36                         self.dbg('Playbin2: ERROR: STATE_NULL')
37                 return self.idle
38
39         def play(self, file, volume):
40                 # abort previous play if still busy
41                 if not self.idle:
42                         #print >> sys.stderr, 'audio truncated'
43                         self.player.set_state(gst.STATE_NULL)
44                 self.player.set_property("uri", "file://" + file)
45                 if volume > 0.0:
46                         self.player.set_property("volume", min(volume, 1.0))
47                 self.dbg('Volume:' + str(self.player.get_property("volume")))
48                 self.player.set_state(gst.STATE_PLAYING)
49                 self.idle = False # now playing
50
51         def dbg(self, txt):
52                         if self.Debug:
53                                 f = open(self.configDir+'log.txt', 'a')
54                                 f.write(str(datetime.datetime.now()) + ': '+ txt)
55                                 f.write('\n')
56
57                                 f.close()
58
59 class CallNotify(hildondesktop.StatusMenuItem):
60         def __init__(self):
61                 hildondesktop.StatusMenuItem.__init__(self)
62                 # Set members
63                 self.configDir = "/home/user/.config/CallNotify/"
64                 self.configFile = "conf.txt"
65                 self.configSoundFile = "sound.txt"
66                 self.Debug = WriteLog
67                 self.dbg("debugging started")
68                 self.msgType = ""
69                 self.toShow = True
70                 self.stop = False
71                 self.path = "/home/user/.config/hildon-desktop/notifications.db"
72                 self.mainLoop = None
73                 self.soundFile = "/usr/share/CallNotify/missed.wav"
74                 self.soundCall = self.soundFile
75                 self.soundSMS = self.soundFile
76                 self.soundBoth = self.soundFile
77                 self.volume = 0.5
78                 self.visual = True
79                 self.sound = True
80                 self.vibration = True
81                 self.interval = float(5.0)
82                 self.readConfigurationFile()
83
84                 self.dbg('constructor')
85
86                 # Load images
87                 self.loadImages()
88
89                 # Register to handle screen off/on events
90                 osso_c = osso.Context("osso_test_device_on", "0.0.1", False)
91                 device = osso.DeviceState(osso_c)
92
93                 # add d-bus listener for removing notification after viewing missed call
94                 # Doing timeout_add with return False instead of explicitly raising a thread
95                 gobject.timeout_add(500, self.startDbusListeners)
96
97                 self.dbg('constructor end')
98
99         def checkForConfigFile(self):
100                 self.dbg('checkForConfigFile started')
101                 os.umask(0)
102                 if not os.path.exists(self.configDir):
103                                 os.mkdir(self.configDir)
104                 if not os.path.exists(self.configDir+self.configFile):
105                                 a = open(self.configDir+self.configFile,'w+')
106                                 a.write('y;y;y;5.0\n')
107                                 a.close()
108                 if not os.path.exists(self.configDir+self.configSoundFile):
109                                 a = open(self.configDir+self.configSoundFile,'w+')
110                                 a.write('\n')
111                                 a.close()
112                 # set proper permissions
113                 os.system("chmod 755 " + self.configDir)
114                 os.system("chmod 644 " + self.configDir+self.configFile)
115                 os.system("chmod 644 " + self.configDir+self.configSoundFile)
116
117
118         def readConfigurationFile(self):
119                 try:
120                         self.dbg('readConfigurationFile started')
121                         self.checkForConfigFile()
122                         f = open(self.configDir+self.configFile, 'r')
123                         raw_set = f.readline().rsplit(';')
124                         self.visual = raw_set[0] in ('y')
125                         self.sound = raw_set[1] in ('y')
126                         self.vibration = raw_set[2] in ('y')
127                         self.interval = float(raw_set[3].replace(',','.'))
128                         self.dbg('visual='+str(self.visual)+' sound='+str(self.sound)+' vibration='+str(self.vibration)+' interval='+str(self.interval))
129                         f.close()
130
131                         # read sound config file
132                         f = open(self.configDir+self.configSoundFile, 'r')
133                         line = f.readline()
134                         # check if specific missed call, SMS or common sound was defined in config file
135                         if line.strip():
136                                 self.soundCall = line.strip()
137                                 self.dbg('soundCall changed to: '+self.soundCall)
138                         line = f.readline()
139                         if line.strip():
140                                 self.soundSMS = line.rstrip()
141                                 self.dbg('soundSMS changed to: '+self.soundSMS)
142                         line = f.readline()
143                         if line.strip():
144                                 self.soundBoth = line.strip()
145                                 self.dbg('soundBoth changed to: '+self.soundBoth)
146                         line = f.readline()
147                         if line.strip():
148                                 self.volume = float(line.strip())
149                                 self.dbg('volume changed to: '+self.volume)
150                         f.close()
151                 except:
152                         os.remove(self.configDir+self.configFile)
153                         os.remove(self.configDir+self.configSoundFile)
154                         self.checkForConfigFile()
155
156         def playSound(self):
157                 self.dbg('playSound started')
158                 profiled = dbus.Interface(dbus.SessionBus().get_object("com.nokia.profiled", "/com/nokia/profiled"), "com.nokia.profiled")
159                 mce = dbus.Interface(dbus.SystemBus().get_object("com.nokia.mce", "/com/nokia/mce/request"), "com.nokia.mce.request")
160                 if self.sound and profiled.get_value("", "ringing.alert.type") != "silent":
161                         # Create the player_name sink
162                         if self.msgType == "Call" and profiled.get_value("", "ringing.alert.volume") != "0":
163                                 self.dbg('play soundCall:' + self.soundCall)
164                                 Playbin2().play(self.soundCall, self.volume)
165                         elif self.msgType == "SMS" and profiled.get_value("", "sms.alert.volume") != "0":
166                                 self.dbg('play soundSMS:' + self.soundSMS)
167                                 Playbin2().play(self.soundSMS, self.volume)
168                         elif self.msgType == "Both":
169                                 self.dbg('play soundBoth:' + self.soundBoth)
170                                 Playbin2().play(self.soundBoth, self.volume)
171                         else:
172                                 Playbin2().play(self.soundFile, self.volume)
173
174                 if self.vibration and profiled.get_value("", "vibrating.alert.enabled") == "On":
175                         self.dbg('vibrate:')
176                         mce.req_vibrator_pattern_activate("PatternIncomingCall")
177                         time.sleep(0.5);
178                         mce.req_vibrator_pattern_deactivate("PatternIncomingCall")
179                 return True
180
181         def loadImages(self):
182                 self.dbg('loadImages started')
183                 icon_theme = gtk.icon_theme_get_default()
184                 self.callPicture = gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/call.png",18,18)
185                 self.smsPicture = gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/sms.png",18,18)
186
187                 # Load 5 numbers and the "+5"
188                 self.imgList = []
189                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/1.png",18,18))
190                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/2.png",18,18))
191                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/3.png",18,18))
192                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/4.png",18,18))
193                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/5.png",18,18))
194                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/more.png",18,18))
195
196         def startDbusListeners(self):
197                 self.dbg('startDbusListeners started')
198                 DBusGMainLoop(set_as_default=True)
199                 bus = dbus.SessionBus()
200
201                 bus.add_signal_receiver(self.notificationClosed, "NotificationClosed", "org.freedesktop.Notifications", None, "/org/freedesktop/Notifications")
202                 bus.add_signal_receiver(self.pendingMessagesRemoved, "PendingMessagesRemoved", None, None, None)
203                 bus.add_signal_receiver(self.newEvent, "NewEvent", None, None, None)
204
205                 self.mainLoop = gobject.MainLoop()
206                 self.mainLoop.run()
207                 return False
208
209         def newEvent(self, a, b, c, d, e, f):
210                 self.dbg('newEvent started')
211                 # On NewEvent the notifications.db is not immediately filled, thus check the event after one minute waiting
212                 self.tmr_main = gobject.timeout_add(20000, self.handleMissed)
213                 return True
214
215         def notificationClosed(self, a):
216                 self.dbg('notificationClosed started')
217                 self.stop_notification(a)
218
219         def pendingMessagesRemoved(self, a):
220                 self.dbg('pendingMessagesRemoved started')
221                 self.stop_notification(self)
222
223         def handleMissed(self):
224                 missedCall = self.getMissedCallsCount(False)
225                 missedSMS = self.getMissedCallsCount(True)
226                 self.dbg('Missed CALL: ' + str(missedCall))
227                 self.dbg('Missed SMS: ' + str(missedSMS))
228
229                 if missedCall and missedSMS:
230                         self.msgType = "Both"
231                         self.dbg('***********handleMissed BOTH started***********: ' + str(missedCall) + str(missedSMS))
232                 elif missedCall and not missedSMS:
233                         self.msgType = "Call"
234                         self.dbg('***********handleMissed CALL started***********: ' + str(missedCall))
235                 elif missedSMS and not missedCall:
236                         self.msgType = "SMS"
237                         self.dbg('***********handleMissed SMS started***********: ' + str(missedSMS))
238
239                 if missedCall or missedSMS:
240                         self.show()
241
242                 # Execute the function only once on NewEvent
243                 return False
244
245         def stop_notification(self, a):
246                 self.dbg('stop_notification started')
247                 try:
248                         self.set_status_area_icon(None)
249                         # Reset the notification (get recent missed call count)
250                         self.stop = False
251                         self.msgType = ""
252                         gobject.source_remove(self.tmr_ptr)
253                         gobject.source_remove(self.tmr_ptr2)
254                         gobject.source_remove(self.tmr_main)
255                 except:
256                         pass
257
258         def getMissedCallsCount(self, isSms):
259                 conn = sqlite3.connect(self.path)
260                 cur = conn.cursor()
261                 if isSms:
262                         cur.execute("select count(id) from notifications where icon_name='general_sms'")
263                 else:
264                         cur.execute("select count(id) from notifications where icon_name='general_missed'")
265                 missed = cur.fetchone()[0]
266
267                 #if isSms:
268                         #self.dbg('get missed SMSs: ' + str(missed))
269                 #else:
270                         #self.dbg('get missed Calls: ' + str(missed))
271
272                 return missed
273
274         def show(self):
275                 self.dbg('show started')
276                 # blink the icon every 1 second
277                 if not(self.stop):
278                         self.readConfigurationFile()
279                         self.stop = True
280                         if self.visual:
281                                 self.tmr_ptr = gobject.timeout_add(1000, self.blinkIcon)
282                         self.tmr_ptr2 = gobject.timeout_add(int(self.interval*1000*60), self.playSound)
283
284         def blinkIcon(self):
285                 # self.dbg('blinkIcon started')
286                 if self.toShow:
287                         self.toShow = False
288                         img = self.callPicture
289                         if self.msgType == "SMS":
290                                 img = self.smsPicture
291                         self.set_status_area_icon(img)
292                         return True
293                 else:
294                         img = self.smsPicture
295                         isSMS = False
296                         if self.msgType == "SMS":
297                                 isSMS = True
298                         index = self.getMissedCallsCount(isSMS) - 1
299                         if index >= 5:
300                                 index = 5
301                         if index < 0:
302                                 index = 0
303                         if self.msgType != "Both":
304                                 img = self.imgList[index]
305                         self.toShow = True
306                         self.set_status_area_icon(img)
307                         return True
308
309         def dbg(self, txt):
310                         if self.Debug:
311                                 f = open(self.configDir+'log.txt', 'a')
312                                 f.write(str(datetime.datetime.now()) + ': '+ txt)
313                                 f.write('\n')
314
315                                 f.close()
316
317 hd_plugin_type = CallNotify
318
319
320 # Uncomment from "if __name__..." to "gtk.main()" if running from CLI as:
321 # "run-standalone.sh python CallNotify.py"
322
323 #if __name__=="__main__":
324 #               gobject.type_register(hd_plugin_type)
325 #               obj = gobject.new(hd_plugin_type, plugin_id="plugid_id")
326 #               obj.show_all()
327 #               gtk.main()
328