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