Import patched yrannadx version from http://talk.maemo.org/showpost.php?p=1164866...
[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                 if self.sound:
152                         # Create the player_name sink
153                         if self.msgType == "Call":
154                                 self.dbg('play soundCall:' + self.soundCall)
155                                 Playbin2().play(self.soundCall, self.volume)
156                         elif self.msgType == "SMS":
157                                 self.dbg('play soundSMS:' + self.soundSMS)
158                                 Playbin2().play(self.soundSMS, self.volume)
159                         elif self.msgType == "Both":
160                                 self.dbg('play soundBoth:' + self.soundBoth)
161                                 Playbin2().play(self.soundBoth, self.volume)
162                         else:
163                                 Playbin2().play(self.soundFile, self.volume)
164
165                 if self.vibration:
166                         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\'"
167                         bb = str(bb)
168                         b = os.popen(bb)
169                         b.close()
170                         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\'" 
171                         b = os.popen(bb)
172                         b.close()
173                 return True
174                 
175         def loadImages(self):
176                 self.dbg('loadImages started')
177                 icon_theme = gtk.icon_theme_get_default()
178                 self.callPicture = gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/call.png",18,18)
179                 self.smsPicture = gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/sms.png",18,18)
180                 
181                 # Load 5 numbers and the "+5" 
182                 self.imgList = []
183                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/1.png",18,18))
184                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/2.png",18,18))
185                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/3.png",18,18))
186                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/4.png",18,18))
187                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/5.png",18,18))
188                 self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/more.png",18,18))
189                 
190         def startDbusListeners(self):
191                 self.dbg('startDbusListeners started')
192                 DBusGMainLoop(set_as_default=True)
193                 bus = dbus.SessionBus()
194
195                 bus.add_signal_receiver(self.notificationClosed, "NotificationClosed", "org.freedesktop.Notifications", None, "/org/freedesktop/Notifications")
196                 bus.add_signal_receiver(self.pendingMessagesRemoved, "PendingMessagesRemoved", None, None, None)
197                 bus.add_signal_receiver(self.newEvent, "NewEvent", None, None, None)
198
199                 self.mainLoop = gobject.MainLoop()
200                 self.mainLoop.run()
201                 return False
202
203         def newEvent(self, a, b, c, d, e, f):
204                 self.dbg('newEvent started')
205                 # On NewEvent the notifications.db is not immediately filled, thus check the event after one minute waiting
206                 self.tmr_main = gobject.timeout_add(60000, self.handleMissed) 
207                 return True
208
209         def notificationClosed(self, a):
210                 self.dbg('notificationClosed started')
211                 self.stop_notification(a)
212         
213         def pendingMessagesRemoved(self, a):
214                 self.dbg('pendingMessagesRemoved started')
215                 self.stop_notification(self)
216
217         def handleMissed(self):
218                 missedCall = self.getMissedCallsCount(False)
219                 missedSMS = self.getMissedCallsCount(True)
220                 self.dbg('Missed CALL: ' + str(missedCall))
221                 self.dbg('Missed SMS: ' + str(missedSMS))
222
223                 if missedCall and missedSMS:
224                         self.msgType = "Both"
225                         self.dbg('***********handleMissed BOTH started***********: ' + str(missedCall) + str(missedSMS))
226                 elif missedCall and not missedSMS:
227                         self.msgType = "Call"
228                         self.dbg('***********handleMissed CALL started***********: ' + str(missedCall))
229                 elif missedSMS and not missedCall:
230                         self.msgType = "SMS"
231                         self.dbg('***********handleMissed SMS started***********: ' + str(missedSMS))
232                         
233                 if missedCall or missedSMS:
234                         self.show()
235                         
236                 # Execute the function only once on NewEvent
237                 return False
238                 
239         def stop_notification(self, a):
240                 self.dbg('stop_notification started')
241                 try:
242                         self.set_status_area_icon(None)
243                         # Reset the notification (get recent missed call count)
244                         self.stop = False
245                         self.msgType = ""
246                         gobject.source_remove(self.tmr_ptr)
247                         gobject.source_remove(self.tmr_ptr2)
248                         gobject.source_remove(self.tmr_main)
249                 except:
250                         pass
251
252         def getMissedCallsCount(self, isSms):
253                 conn = sqlite3.connect(self.path)
254                 cur = conn.cursor()
255                 if isSms:
256                         cur.execute("select count(id) from notifications where icon_name='general_sms'")
257                 else:
258                         cur.execute("select count(id) from notifications where icon_name='general_missed'")
259                 missed = cur.fetchone()[0]
260
261                 #if isSms:
262                         #self.dbg('get missed SMSs: ' + str(missed))
263                 #else:
264                         #self.dbg('get missed Calls: ' + str(missed))
265                 
266                 return missed
267
268         def show(self):
269                 self.dbg('show started')
270                 # blink the icon every 1 second
271                 if not(self.stop):
272                         self.readConfigurationFile()
273                         self.stop = True
274                         if self.visual:
275                                 self.tmr_ptr = gobject.timeout_add(1000, self.blinkIcon)
276                         self.tmr_ptr2 = gobject.timeout_add(int(self.interval*1000*60), self.playSound)
277                         
278         def blinkIcon(self):
279                 # self.dbg('blinkIcon started')
280                 if self.toShow:
281                         self.toShow = False
282                         img = self.callPicture
283                         if self.msgType == "SMS":
284                                 img = self.smsPicture
285                         self.set_status_area_icon(img)
286                         return True
287                 else:
288                         img = self.smsPicture
289                         isSMS = False
290                         if self.msgType == "SMS":
291                                 isSMS = True
292                         index = self.getMissedCallsCount(isSMS) - 1
293                         if index >= 5:
294                                 index = 5
295                         if index < 0:
296                                 index = 0
297                         if self.msgType != "Both":
298                                 img = self.imgList[index]
299                         self.toShow = True
300                         self.set_status_area_icon(img)
301                         return True
302                         
303         def dbg(self, txt):
304                         if self.Debug:
305                                 f = open(self.configDir+'log.txt', 'a')
306                                 f.write(str(datetime.datetime.now()) + ': '+ txt)
307                                 f.write('\n')
308
309                                 f.close()
310                 
311 hd_plugin_type = CallNotify
312
313
314 # Uncomment from "if __name__..." to "gtk.main()" if running from CLI as:
315 # "run-standalone.sh python CallNotify.py"
316
317 #if __name__=="__main__":
318 #               gobject.type_register(hd_plugin_type)
319 #               obj = gobject.new(hd_plugin_type, plugin_id="plugid_id")
320 #               obj.show_all()
321 #               gtk.main()
322