b121c31555b563aee436af11575336f9053d346a
[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.0
78                 self.readConfigurationFile()
79
80                 self.dbg('constructor')
81
82                 # Load images
83                 self.loadImages()
84                 
85                 # Register to handle screen off/on events
86                 osso_c = osso.Context("osso_test_device_on", "0.0.1", False)
87                 device = osso.DeviceState(osso_c)
88                 
89                 # add d-bus listener for removing notification after viewing missed call
90                 # Doing timeout_add with return False instead of explicitly raising a thread
91                 gobject.timeout_add(500, self.startDbusListeners)
92                 
93                 if self.Debug:
94                         hildon.hildon_play_system_sound(self.soundFile)
95                 self.dbg('constructor end')
96         
97         def checkForConfigFile(self):
98                 self.dbg('checkForConfigFile started')
99                 os.umask(0)
100                 if not os.path.exists(self.configDir):
101                                 os.mkdir(self.configDir)
102                 if not os.path.exists(self.configDir+self.configFile):
103                                 a = open(self.configDir+self.configFile,'w+')
104                                 a.write('y;y;y;5.0\n')
105                                 a.close()
106                 if not os.path.exists(self.configDir+self.configSoundFile):
107                                 a = open(self.configDir+self.configSoundFile,'w+')
108                                 a.write('\n')
109                                 a.close()
110                 # set proper permissions
111                 os.system("chmod 766 " + self.configDir)
112                 os.system("chmod 766" + self.configDir+self.configFile)
113                 os.system("chmod 766" + self.configDir+self.configSoundFile)
114
115
116         def readConfigurationFile(self):
117                 try:
118                         self.dbg('readConfigurationFile started')
119                         self.checkForConfigFile()
120                         f = open(self.configDir+self.configFile, 'r')
121                         raw_set = f.readline().rsplit(';')
122                         self.visual = raw_set[0] in ('y')
123                         self.sound = raw_set[1] in ('y')
124                         self.vibration = raw_set[2] in ('y')
125                         self.interval = float(raw_set[3].replace(',','.'))
126                         f.close()
127                         
128                         # read sound config file
129                         f = open(self.configDir+self.configSoundFile, 'r')
130                         line = f.readline()
131                         # check if specific missed call, SMS or common sound was defined in config file
132                         if line:
133                                 self.soundCall = line.rstrip()
134                         line = f.readline()
135                         if line:
136                                 self.soundSMS = line.rstrip()
137                         line = f.readline()
138                         if line:
139                                 self.soundBoth = line.rstrip()
140                         line = f.readline()
141                         if line:
142                                 self.volume = float(line.rstrip())
143                         f.close()
144                 except:
145                         os.remove(self.configDir+self.configFile)
146                         os.remove(self.configDir+self.configSoundFile)
147                         self.checkForConfigFile()
148         
149         def playSound(self):
150                 self.dbg('playSound started')
151                 profiled = dbus.Interface(dbus.SessionBus().get_object("com.nokia.profiled", "/com/nokia/profiled"), "com.nokia.profiled")
152                 mce = dbus.Interface(dbus.SystemBus().get_object("com.nokia.mce", "/com/nokia/mce/request"), "com.nokia.mce.request")
153                 if self.sound and profiled.get_value("", "ringing.alert.type") != "silent":
154                         # Create the player_name sink
155                         if self.msgType == "Call" and profiled.get_value("", "ringing.alert.volume") != "0":
156                                 self.dbg('play soundCall:' + self.soundCall)
157                                 Playbin2().play(self.soundCall, self.volume)
158                         elif self.msgType == "SMS" and profiled.get_value("", "sms.alert.volume") != "0":
159                                 self.dbg('play soundSMS:' + self.soundSMS)
160                                 Playbin2().play(self.soundSMS, self.volume)
161                         elif self.msgType == "Both":
162                                 self.dbg('play soundBoth:' + self.soundBoth)
163                                 Playbin2().play(self.soundBoth, self.volume)
164                         else:
165                                 Playbin2().play(self.soundFile, self.volume)
166
167                 if self.vibration and profiled.get_value("", "vibrating.alert.enabled") == "On":
168                         mce.req_vibrator_pattern_activate("PatternIncomingCall")
169                         mce.req_vibrator_pattern_deactivate("PatternIncomingCall")
170                 return True
171                 
172         def loadImages(self):
173                 self.dbg('loadImages started')
174                 icon_theme = gtk.icon_theme_get_default()
175                 self.callPicture = gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/call.png",18,18)
176                 self.smsPicture = gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/sms.png",18,18)
177                 
178                 # Load 5 numbers and the "+5" 
179                 self.imgList = []
180                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/1.png",18,18))
181                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/2.png",18,18))
182                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/3.png",18,18))
183                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/4.png",18,18))
184                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/5.png",18,18))
185                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/more.png",18,18))
186                 
187         def startDbusListeners(self):
188                 self.dbg('startDbusListeners started')
189                 DBusGMainLoop(set_as_default=True)
190                 bus = dbus.SessionBus()
191
192                 bus.add_signal_receiver(self.notificationClosed, "NotificationClosed", "org.freedesktop.Notifications", None, "/org/freedesktop/Notifications")
193                 bus.add_signal_receiver(self.pendingMessagesRemoved, "PendingMessagesRemoved", None, None, None)
194                 bus.add_signal_receiver(self.newEvent, "NewEvent", None, None, None)
195
196                 self.mainLoop = gobject.MainLoop()
197                 self.mainLoop.run()
198                 return False
199
200         def newEvent(self, a, b, c, d, e, f):
201                 self.dbg('newEvent started')
202                 # On NewEvent the notifications.db is not immediately filled, thus check the event after one minute waiting
203                 self.tmr_main = gobject.timeout_add(60000, self.handleMissed) 
204                 return True
205
206         def notificationClosed(self, a):
207                 self.dbg('notificationClosed started')
208                 self.stop_notification(a)
209         
210         def pendingMessagesRemoved(self, a):
211                 self.dbg('pendingMessagesRemoved started')
212                 self.stop_notification(self)
213
214         def handleMissed(self):
215                 missedCall = self.getMissedCallsCount(False)
216                 missedSMS = self.getMissedCallsCount(True)
217                 self.dbg('Missed CALL: ' + str(missedCall))
218                 self.dbg('Missed SMS: ' + str(missedSMS))
219
220                 if missedCall and missedSMS:
221                         self.msgType = "Both"
222                         self.dbg('***********handleMissed BOTH started***********: ' + str(missedCall) + str(missedSMS))
223                 elif missedCall and not missedSMS:
224                         self.msgType = "Call"
225                         self.dbg('***********handleMissed CALL started***********: ' + str(missedCall))
226                 elif missedSMS and not missedCall:
227                         self.msgType = "SMS"
228                         self.dbg('***********handleMissed SMS started***********: ' + str(missedSMS))
229                         
230                 if missedCall or missedSMS:
231                         self.show()
232                         
233                 # Execute the function only once on NewEvent
234                 return False
235                 
236         def stop_notification(self, a):
237                 self.dbg('stop_notification started')
238                 try:
239                         self.set_status_area_icon(None)
240                         # Reset the notification (get recent missed call count)
241                         self.stop = False
242                         self.msgType = ""
243                         gobject.source_remove(self.tmr_ptr)
244                         gobject.source_remove(self.tmr_ptr2)
245                         gobject.source_remove(self.tmr_main)
246                 except:
247                         pass
248
249         def getMissedCallsCount(self, isSms):
250                 conn = sqlite3.connect(self.path)
251                 cur = conn.cursor()
252                 if isSms:
253                         cur.execute("select count(id) from notifications where icon_name='general_sms'")
254                 else:
255                         cur.execute("select count(id) from notifications where icon_name='general_missed'")
256                 missed = cur.fetchone()[0]
257
258                 #if isSms:
259                         #self.dbg('get missed SMSs: ' + str(missed))
260                 #else:
261                         #self.dbg('get missed Calls: ' + str(missed))
262                 
263                 return missed
264
265         def show(self):
266                 self.dbg('show started')
267                 # blink the icon every 1 second
268                 if not(self.stop):
269                         self.readConfigurationFile()
270                         self.stop = True
271                         if self.visual:
272                                 self.tmr_ptr = gobject.timeout_add(1000, self.blinkIcon)
273                         self.tmr_ptr2 = gobject.timeout_add(int(self.interval*1000*60), self.playSound)
274                         
275         def blinkIcon(self):
276                 # self.dbg('blinkIcon started')
277                 if self.toShow:
278                         self.toShow = False
279                         img = self.callPicture
280                         if self.msgType == "SMS":
281                                 img = self.smsPicture
282                         self.set_status_area_icon(img)
283                         return True
284                 else:
285                         img = self.smsPicture
286                         isSMS = False
287                         if self.msgType == "SMS":
288                                 isSMS = True
289                         index = self.getMissedCallsCount(isSMS) - 1
290                         if index >= 5:
291                                 index = 5
292                         if index < 0:
293                                 index = 0
294                         if self.msgType != "Both":
295                                 img = self.imgList[index]
296                         self.toShow = True
297                         self.set_status_area_icon(img)
298                         return True
299                         
300         def dbg(self, txt):
301                         if self.Debug:
302                                 f = open(self.configDir+'log.txt', 'a')
303                                 f.write(str(datetime.datetime.now()) + ': '+ txt)
304                                 f.write('\n')
305
306                                 f.close()
307                 
308 hd_plugin_type = CallNotify
309
310
311 # Uncomment from "if __name__..." to "gtk.main()" if running from CLI as:
312 # "run-standalone.sh python CallNotify.py"
313
314 #if __name__=="__main__":
315 #               gobject.type_register(hd_plugin_type)
316 #               obj = gobject.new(hd_plugin_type, plugin_id="plugid_id")
317 #               obj.show_all()
318 #               gtk.main()
319