4552a8b9f98c04068c8beafacea2f2bf90058162
[callnotify] / src / usr / lib / hildon-desktop / CallNotify.py
1 import gtk
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
12 class CallNotify(hildondesktop.StatusMenuItem):
13     def __init__(self):
14                 hildondesktop.StatusMenuItem.__init__(self)
15                 # Set members
16                 self.configDir = "/home/user/.config/CallNotify/"
17                 self.configFile = "conf.txt"
18                 self.Debug = False
19                 self.dbg("debugging started")
20                 self.readConfigurationFile()
21                 self.msgType = ""               
22                 self.toShow = True
23                 self.stop = False
24                 self.path = "/home/user/.rtcom-eventlogger/el-v1.db"
25                 self.missed = self.getMissedCallsCount(False)
26                 self.missedSMS = self.getMissedCallsCount(True)
27                 self.missedLastCall = self.missed
28                 self.missedLastSMS = self.missedSMS
29                 self.mainLoop = None    
30                 self.soundFile = "/usr/share/CallNotify/missed.wav"
31                 self.dbg('constructor')
32                 
33                 # Load images
34                 self.loadImages()
35                         
36                 # Register to handle screen off/on events
37                 osso_c = osso.Context("osso_test_device_on", "0.0.1", False)
38                 device = osso.DeviceState(osso_c)
39                 #device.set_display_event_cb(self.state_cb)
40                 
41                 # Check missed calls notification
42                 self.tmr_main = gobject.timeout_add(5000, self.handleMissedCall) 
43                 self.tmrset = True      
44                 # add d-bus listener for removing notification after viewing missed call
45                 # Doing timeout_add with return False instead of explicitly raising a thread
46                 gobject.timeout_add(500, self.startDbusListeners)
47                 #atexit.register(self.cleanup)
48                 
49                 # add GUI buttons
50                 # self.addGUI()
51                 self.dbg('constructor end')
52         
53     def addGUI(self):
54           # add GUI buttons                                              
55                 label = gtk.Label("Call Notify")                               
56                 button = gtk.Button()                                          
57                 button.add(label)                                              
58                 button.connect("clicked", self.openSettingsDialog)             
59                 self.add(button)                                               
60                 self.show_all()    
61                 self.dbg('addGUI end')
62
63     def checkForConfigFile(self):
64                 self.dbg('checkForConfigFile started')
65                 os.umask(0)
66                 if not os.path.exists(self.configDir):
67                         os.mkdir(self.configDir)
68                 if not os.path.exists(self.configDir+self.configFile):
69                         a = open(self.configDir+self.configFile,'w+')
70                         a.write('y;y;y;5.0\n')
71                         a.close()
72                 # set proper permissions
73                 os.system("chmod 766 " + self.configDir)
74                 os.system("chmod 766" + self.configDir+self.configFile)
75
76                 
77     def readConfigurationFile(self):
78                 try:
79                         self.dbg('readConfigurationFile started')
80                         self.checkForConfigFile()
81                         f = open(self.configDir+self.configFile, 'r')
82                         raw_set = f.readline().rsplit(';')
83                         self.visual = raw_set[0] in ('y')
84                         self.sound = raw_set[1] in ('y')
85                         self.vibration = raw_set[2] in ('y')
86                         self.interval = float(raw_set[3].replace(',','.'))
87                         f.close()
88                 except:
89                         os.remove(self.configDir+self.configFile)
90                         self.checkForConfigFile()
91         
92     def saveConfigurationFile(self):
93                 self.dbg('saveConfigurationFile started')
94                 f = open(self.configDir+self.configFile, "w")
95                 conf = ''
96                 if self.visual:
97                         conf += 'y;'
98                 else:
99                         conf += 'n;'
100
101                 if self.sound:
102                         conf += 'y;'
103                 else:
104                         conf +='n;'
105         
106                 if self.vibration:
107                         conf += 'y;'
108                 else:
109                         conf += 'n;'
110
111                 conf += str(self.interval)
112         
113                 f.write(conf)
114                 f.close()
115
116     def openSettingsDialog(self, widget, data=None):
117                 self.dbg('openSettingsDialog started')
118                 self.dialog = gtk.Dialog(title="Call Notify Settings")               
119                 self.dialog.set_size_request(800,300)
120                 #self.dialog.connect("response", self.dialogClosed)  
121                 self.readConfigurationFile()            
122                 # Visual
123                                 
124                 b2 = gtk.CheckButton(label="Visual Notification On")           
125                 b2.connect("clicked", self.notificationActivate)               
126                 b2.set_active(self.visual)
127                 self.dialog.vbox.add(b2)                    
128                                 
129                                 # Sound
130                                 
131                 b3 = gtk.CheckButton(label="Sound Notification On")            
132                 b3.connect("clicked", self.soundActivate)                      
133                 b3.set_active(self.sound) 
134                 self.dialog.vbox.add(b3)  
135                                 
136                                 # Vibration
137                                 
138                 b4 = gtk.CheckButton(label="Vibrate Notification On")            
139                 b4.connect("clicked", self.vibrateActivate)                      
140                 b4.set_active(self.vibration)
141                 self.dialog.vbox.add(b4)  
142                                 
143                                 # Slider
144                                 
145                 Adj = gtk.Adjustment(self.interval, lower=0, upper=60, step_incr=5, page_incr=5)
146                 Adj.connect("value_changed", self.intervalChanged)
147                 Adj.set_value(self.interval)
148                                 
149                 Slider = gtk.HScale(adjustment=Adj)
150                 self.dialog.vbox.add(Slider)
151                                 
152                                 # Manual reset
153                                 
154                 b5 = gtk.Button(label="Manually reset notification")           
155                 b5.connect("clicked", self.resetNotification)                  
156                 self.dialog.vbox.add(b5)
157                 
158                                 # Save Button
159                 
160                 bSave = gtk.Button(label="Save")
161                 bSave.connect("clicked", self.saveSettings)
162                 self.dialog.action_area.add(bSave)
163                                 
164                                 # Cancel Button
165                                 
166                 bCancel = gtk.Button(label="Cancel")
167                 bCancel.connect("clicked", self.cancelDialog)
168                 self.dialog.vbox.add(bCancel)
169                 
170                 self.dialog.show_all()
171
172     def intervalChanged(self, adj):
173                 self.dbg('intervalChanged started')
174                 self.interval = adj.value
175
176     def saveSettings(self, widget, data=None):
177                 self.dbg('saveSettings started')
178                 self.saveConfigurationFile()
179         
180     def dialogClosed(self, dialog, response_id):
181                 self.dbg('dialogClosed started')
182         
183     def cancelDialog(self, widget, data=None):
184                 self.dbg('cancelDialog started')
185                 self.dialog.destroy()
186         
187     def resetNotification(self, widget, data=None):
188                 self.dbg('resetNotification started')
189                 self.stop_notification(self)
190
191     def soundActivate(self, widget, data=None):
192                 self.dbg('soundActivate started')
193                 self.sound = widget.get_active() #not(self.sound)
194         
195     def notificationActivate(self,widget, data=None):
196                 self.dbg('notificationActivate started')
197                 self.visual = widget.get_active() #not(self.visual)
198
199     def vibrateActivate(self, widget, data=None):
200                 self.dbg('vibrateActivate started')
201                 self.vibration = widget.get_active() #not(self.vibrate)
202
203         
204     def playSound(self):
205                 self.dbg('playSound started')
206                 if self.sound:
207                         hildon.hildon_play_system_sound(self.soundFile)
208                         #pygame.time.delay(1000)
209                 if self.vibration:
210                         bb = 'run-standalone.sh dbus-send --print-reply --system --dest=com.nokia.mce /com/nokia/mce/request com.nokia.mce.request.req_vibrator_pattern_activate string:' + "\'PatternIncomingCall\'"
211                         bb = str(bb)
212                         b = os.popen(bb)
213                         b.close()
214                         bb = 'run-standalone.sh dbus-send --print-reply --system --dest=com.nokia.mce /com/nokia/mce/request com.nokia.mce.request.req_vibrator_pattern_deactivate string:' + "\'PatternIncomingCall\'" 
215                         b = os.popen(bb)
216                         b.close()
217                 return True
218                 
219         
220     def cleanup(self):
221                 self.dbg('cleanup started')
222                 gobject.source_remove(self.tmr_main)                  
223                 gobject.source_remove(self.tmr_ptr)     
224                 gobject.source_remove(self.tmr_ptr2)
225                 
226                 self.mainLoop.quit()
227
228     def loadImages(self):
229                 self.dbg('loadImages started')
230                 # Load phone image
231                 #self.pixbuf = gtk.gdk.pixbuf_new_from_file_at_size("/home/user/phone.png",18,18)
232                 icon_theme = gtk.icon_theme_get_default()
233                 self.callPicture = gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/call.png",18,18)
234                 #icon_theme.load_icon("general_call", 18, gtk.ICON_LOOKUP_NO_SVG)
235                 self.smsPicture = gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/sms.png",18,18)
236                 
237                 # Load 5 numbers and the "+5" 
238                 self.imgList = []
239                 #self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/home/user/1.png",18,18))
240                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/1.png",18,18))
241                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/2.png",18,18))
242                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/3.png",18,18))
243                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/4.png",18,18))
244                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/5.png",18,18))
245                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/more.png",18,18))
246                 
247         # Screen off event-handler
248     def state_cb(self, state, a):
249                 self.dbg('state_cb started')
250                 if state == osso.device_state.OSSO_DISPLAY_OFF:
251                         try:
252                                 #gobject.source_remove(self.tmr_main)
253                                 self.tmrset = False
254                                 #gobject.source_remove(self.tmr_ptr)
255                                 
256                                 #gobject.source_remove(self.tmr_ptr2)
257                         except:
258                                 pass
259                 elif state == osso.device_state.OSSO_DISPLAY_ON:
260                         if not tmrset:
261                                 pass
262                                 #self.tmr_main = gobject.timeout_add(5000, self.handleMissedCall)
263                         #self.handleMissedCall()
264                 return False
265                 
266         # Method to define the way to add dbus signal receiver
267
268     def smsRead2(self, a):
269                 self.dbg('smsrec started')
270                 self.stop_notification(self)
271
272     def startDbusListeners(self):
273                 self.dbg('startDbusListeners started')
274                 DBusGMainLoop(set_as_default=True)                             
275                 bus = dbus.SessionBus()                                        
276                 #bus.add_signal_receiver(self.stop_notification, "NotificationClosed", "org.freedesktop.Notifications", "org.freedesktop.Notifications", "/org/freedesktop/Notifications") 
277                 #bus.add_signal_receiver(self.handleMissedCall, "Notify", None, None, None)
278                 #bus.add_signal_receiver(self.handleMissedCall, "MembersChanged", None, None, None)
279                 bus.add_signal_receiver(self.smsReceived, "MessageReceived", None, None, None)
280                 bus.add_signal_receiver(self.smsRead, "NotificationClosed", "org.freedesktop.Notifications", None, "/org/freedesktop/Notifications")
281                 bus.add_signal_receiver(self.smsRead2, "PendingMessagesRemoved", None, None, None)
282
283                 self.mainLoop = gobject.MainLoop()
284                 self.mainLoop.run()                                       
285                 return False
286     
287     def smsReceived(self, a):
288                 self.dbg('snsReceived started')
289                 if a[0].has_key('message-type'):
290                         if self.missedLastSMS == self.getMissedCallsCount(True):
291                                 if self.msgType == "Call":
292                                         self.msgType = "Both"
293                                 else:
294                                         self.msgType = "SMS"
295                                 self.show()
296                         self.missedLastSMS = self.getMissedCallsCount(True)
297         
298     def smsRead(self, a):
299                 self.dbg('smsRead started')
300                 self.stop_notification(a)
301         
302     def handleMissedCall(self): 
303                 self.dbg('handleMissedCall started')
304                 #self.dbg('self.missedLastCall: ' + self.missedLastCall)
305                 #self.dbg('self.getMissedCallsCount(False): ' + self.getMissedCallsCount(False))
306                 if self.missedLastCall != self.getMissedCallsCount(False):
307                         if self.msgType == "SMS":
308                                 self.msgType = "Both"
309                         else:
310                                 self.msgType = "Call"
311                         self.show()
312                         self.missedLastCall = self.getMissedCallsCount(False)
313                 return True
314         
315     def stop_notification(self, a):
316                 self.dbg('stop_notification started')
317                 try:
318                         self.set_status_area_icon(None)
319                         # Reset the notification (get recent missed call count)
320                         self.missed = self.getMissedCallsCount(False)
321                         self.missedSMS = self.getMissedCallsCount(True)
322                         self.missedLastCall = self.missed
323                         self.missedLastSMS = self.missedSMS
324                         self.stop = False
325                         self.msgType = ""
326                         gobject.source_remove(self.tmr_ptr)
327                         gobject.source_remove(self.tmr_ptr2)
328                 except:
329                         pass
330
331
332     def theLoop(self):
333                 self.dbg('theLoop started')
334                 missedCalls = self.getMissedCallsCount(False)
335                 if self.missedLastCall != missedCalls:
336                         self.show()
337                         self.missedLastCall  = missedCalls
338                 return True
339
340     def getMissedCallsCount(self, isSms):
341                 self.dbg('getMissedCallsCount started. agrs: ' + str(isSms))
342                 conn = sqlite3.connect(self.path)
343                 cur = conn.cursor()
344                 if isSms:
345                         #Nokia changed the event number from 7 to 11 and also combined the incomming and outgoing sms's
346                         cur.execute("select count(id) from Events where event_type_id = 7 and outgoing = 0")
347                 else:
348                         #Nokia changed the event from 3 to 2
349                         cur.execute("select count(id) from Events where event_type_id = 3 and outgoing = 0")
350                 return cur.fetchone()[0]
351
352     def show(self):
353                 self.dbg('show started')
354                 # blink the icon every 1 second
355                 if not(self.stop):
356                         self.readConfigurationFile()
357                         self.stop = True
358                         if self.visual:
359                                 self.tmr_ptr = gobject.timeout_add(1000, self.blinkIcon)
360                         self.tmr_ptr2 = gobject.timeout_add(int(self.interval*1000*60), self.playSound)
361                         
362     def blinkIcon(self):
363                 self.dbg('blinkIcon started')
364                 if self.toShow:
365                         self.toShow = False
366                         img = self.callPicture
367                         if self.msgType == "SMS":
368                                 img = self.smsPicture
369                         self.set_status_area_icon(img)
370                         return True
371                 else:
372                         img = self.smsPicture
373                         isSMS = False
374                         counter = self.missed
375                         if self.msgType == "SMS":
376                                 counter = self.missedSMS
377                                 isSMS = True
378                         index = self.getMissedCallsCount(isSMS) - counter - 1
379                         if index >= 5:
380                                 index = 5
381                                 if index < 0:
382                                         index = 0
383                         if self.msgType != "Both":
384                                 img = self.imgList[index]
385                         self.toShow = True
386                         self.set_status_area_icon(img)
387                         return True
388                         
389     def dbg(self, txt):
390                         if self.Debug:
391                                 f = open(self.configDir+'log.txt', 'a')
392                                 f.write(str(datetime.datetime.now()) + ': '+ txt)
393                                 f.write('\n')
394
395                                 f.close()
396                 
397 hd_plugin_type = CallNotify
398
399
400 # Uncomment from "if __name__..." to "gtk.main()" if running from CLI as:
401 # "run-standalone.sh python CallNotify.py"
402
403 #if __name__=="__main__":
404 #               gobject.type_register(hd_plugin_type)
405 #               obj = gobject.new(hd_plugin_type, plugin_id="plugid_id")
406 #               obj.show_all()
407 #               gtk.main()
408