From a96bedea20d84d06075775ed9f5af9b162700d1b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Pali=20Roh=C3=A1r?= Date: Fri, 22 Jun 2012 14:11:04 +0200 Subject: [PATCH 1/1] Imported Upstream version 0.0.4 --- debian/changelog | 9 +- debian/copyright | 2 +- debian/rules | 4 + src/usr/lib/hildon-desktop/CallNotify.py | 304 +++++++++++++++++++++++++----- src/usr/share/CallNotify/call.png | Bin 0 -> 434 bytes src/usr/share/CallNotify/missed.wav | Bin 0 -> 28270 bytes 6 files changed, 265 insertions(+), 54 deletions(-) create mode 100644 src/usr/share/CallNotify/call.png create mode 100644 src/usr/share/CallNotify/missed.wav diff --git a/debian/changelog b/debian/changelog index 1e77328..3464c6b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,7 @@ -callnotify (0.0.3-1) stable; urgency=low +callnotify (0.0.4-1) stable; urgency=low - * Sending SMS to yourself will not rise notification - * Better cleanup after update/remove. + * A very simple and ugly GUI has been added to the status menu. + * User can define sound and vibration notification in a set interval + * Config file at /home/user/.config/CallNotify/conf.txt - -- Omer Agmon Sun, 28 Mar 2010 20:51:53 +0000 + -- Omer Agmon Tue, 30 Mar 2010 21:43:31 +0000 diff --git a/debian/copyright b/debian/copyright index d8a123c..d11d616 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,5 +1,5 @@ This package was py2debianized(0.5.3) by Omer Agmon on -Sun, 28 Mar 2010 20:51:53 +0000. +Tue, 30 Mar 2010 21:43:31 +0000. It was downloaded from diff --git a/debian/rules b/debian/rules index 6c38520..d466b90 100755 --- a/debian/rules +++ b/debian/rules @@ -63,6 +63,10 @@ install: build mkdir -p "$(CURDIR)/debian/callnotify/usr/share/CallNotify/" cp -a "src/usr/share/CallNotify/5.png" "$(CURDIR)/debian/callnotify/usr/share/CallNotify/5.png" mkdir -p "$(CURDIR)/debian/callnotify/usr/share/CallNotify/" + cp -a "src/usr/share/CallNotify/call.png" "$(CURDIR)/debian/callnotify/usr/share/CallNotify/call.png" + mkdir -p "$(CURDIR)/debian/callnotify/usr/share/CallNotify/" + cp -a "src/usr/share/CallNotify/missed.wav" "$(CURDIR)/debian/callnotify/usr/share/CallNotify/missed.wav" + mkdir -p "$(CURDIR)/debian/callnotify/usr/share/CallNotify/" cp -a "src/usr/share/CallNotify/more.png" "$(CURDIR)/debian/callnotify/usr/share/CallNotify/more.png" mkdir -p "$(CURDIR)/debian/callnotify/usr/share/CallNotify/" cp -a "src/usr/share/CallNotify/sms.png" "$(CURDIR)/debian/callnotify/usr/share/CallNotify/sms.png" diff --git a/src/usr/lib/hildon-desktop/CallNotify.py b/src/usr/lib/hildon-desktop/CallNotify.py index d0c9989..798b1df 100644 --- a/src/usr/lib/hildon-desktop/CallNotify.py +++ b/src/usr/lib/hildon-desktop/CallNotify.py @@ -1,54 +1,228 @@ import gtk import gobject -import hildondesktop +import hildon, hildondesktop import sqlite3 import time import dbus import osso -import atexit +import atexit, os, datetime from dbus.mainloop.glib import DBusGMainLoop class CallNotify(hildondesktop.StatusMenuItem): def __init__(self): hildondesktop.StatusMenuItem.__init__(self) - - self.path = "/home/user/.rtcom-eventlogger/el.db" - - # Prevent multiple timers to refresh the status icon - self.stop = False - - # Load images - self.loadImages() + # Set members + self.Debug = False + self.configDir = "/home/user/.config/CallNotify/" + self.configFile = "conf.txt" + self.readConfigurationFile() self.msgType = "" self.toShow = True + self.stop = False + self.path = "/home/user/.rtcom-eventlogger/el.db" self.missed = self.getMissedCallsCount(False) self.missedSMS = self.getMissedCallsCount(True) self.missedLastCall = self.missed self.missedLastSMS = self.missedSMS - self.mainLoop = None + self.mainLoop = None + self.soundFile = "/usr/share/CallNotify/missed.wav" + self.dbg('constructor') + + # Load images + self.loadImages() + # Register to handle screen off/on events osso_c = osso.Context("osso_test_device_on", "0.0.1", False) device = osso.DeviceState(osso_c) - device.set_display_event_cb(self.state_cb) - - self.tmr_main = gobject.timeout_add(5000, self.handleMissedCall) + #device.set_display_event_cb(self.state_cb) + # Check missed calls notification + self.tmr_main = gobject.timeout_add(5000, self.handleMissedCall) + self.tmrset = True # add d-bus listener for removing notification after viewing missed call # Doing timeout_add with return False instead of explicitly raising a thread gobject.timeout_add(500, self.startDbusListeners) - atexit.register(self.cleanup) + #atexit.register(self.cleanup) + + # add GUI buttons + self.addGUI() + dbg('constructor end') + + def addGUI(self): + # add GUI buttons + label = gtk.Label("Call Notify") + button = gtk.Button() + button.add(label) + button.connect("clicked", self.openSettingsDialog) + self.add(button) + self.show_all() + self.dbg('addGUI end') + + def checkForConfigFile(self): + self.dbg('checkForConfigFile started') + if not os.path.exists(self.configDir): + os.mkdir(self.configDir) + if not os.path.exists(self.configDir+self.configFile): + a = open(self.configDir+self.configFile,'w') + a.write('y;y;y;5') + a.close() - def cleanup(): - gobject.source_remove(self.tmr_main) - gobject.source_remove(self.tmr_ptr) + + def readConfigurationFile(self): + self.dbg('readConfigurationFile started') + self.checkForConfigFile() + f = open(self.configDir+self.configFile, 'r') + raw_set = f.readline().rsplit(';') + self.visual = raw_set[0] in ('y') + self.sound = raw_set[1] in ('y') + self.vibration = raw_set[2] in ('y') + self.interval = float(raw_set[3]) + f.close() + + def saveConfigurationFile(self): + self.dbg('saveConfigurationFile started') + f = open(self.configDir+self.configFile, "w") + conf = '' + if self.visual: + conf += 'y;' + else: + conf += 'n;' + + if self.sound: + conf += 'y;' + else: + conf +='n;' + + if self.vibration: + conf += 'y;' + else: + conf += 'n;' + + conf += str(self.interval) + + f.write(conf) + f.close() + + def openSettingsDialog(self, widget, data=None): + self.dbg('openSettingsDialog started') + self.dialog = gtk.Dialog(title="Call Notify Settings") + self.dialog.set_size_request(800,300) + #self.dialog.connect("response", self.dialogClosed) + self.readConfigurationFile() + # Visual + + b2 = gtk.CheckButton(label="Visual Notification On") + b2.connect("clicked", self.notificationActivate) + b2.set_active(self.visual) + self.dialog.vbox.add(b2) + + # Sound + + b3 = gtk.CheckButton(label="Sound Notification On") + b3.connect("clicked", self.soundActivate) + b3.set_active(self.sound) + self.dialog.vbox.add(b3) + + # Vibration + + b4 = gtk.CheckButton(label="Vibrate Notification On") + b4.connect("clicked", self.vibrateActivate) + b4.set_active(self.vibration) + self.dialog.vbox.add(b4) + + # Slider + + Adj = gtk.Adjustment(self.interval, lower=0, upper=60, step_incr=5, page_incr=5) + Adj.connect("value_changed", self.intervalChanged) + Adj.set_value(self.interval) + + Slider = gtk.HScale(adjustment=Adj) + self.dialog.vbox.add(Slider) + + # Manual reset + + b5 = gtk.Button(label="Manually reset notification") + b5.connect("clicked", self.resetNotification) + self.dialog.vbox.add(b5) + + # Save Button + + bSave = gtk.Button(label="Save") + bSave.connect("clicked", self.saveSettings) + self.dialog.action_area.add(bSave) + + # Cancel Button + + bCancel = gtk.Button(label="Cancel") + bCancel.connect("clicked", self.cancelDialog) + self.dialog.vbox.add(bCancel) + + self.dialog.show_all() + + def intervalChanged(self, adj): + self.dbg('intervalChanged started') + self.interval = adj.value + + def saveSettings(self, widget, data=None): + self.dbg('saveSettings started') + self.saveConfigurationFile() + + def dialogClosed(self, dialog, response_id): + self.dbg('dialogClosed started') + + def cancelDialog(self, widget, data=None): + self.dbg('cancelDialog started') + self.dialog.destroy() + + def resetNotification(self, widget, data=None): + self.dbg('resetNotification started') + self.stop_notification(self) + + def soundActivate(self, widget, data=None): + self.dbg('soundActivate started') + self.sound = widget.get_active() #not(self.sound) + + def notificationActivate(self,widget, data=None): + self.dbg('notificationActivate started') + self.visual = widget.get_active() #not(self.visual) + + def vibrateActivate(self, widget, data=None): + self.dbg('vibrateActivate started') + self.vibration = widget.get_active() #not(self.vibrate) + + + def playSound(self): + self.dbg('playSound started') + if self.sound: + hildon.hildon_play_system_sound(self.soundFile) + #pygame.time.delay(1000) + if self.vibration: + 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\'" + bb = str(bb) + b = os.popen(bb) + b.close() + 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\'" + b = os.popen(bb) + b.close() + return True + + + def cleanup(self): + self.dbg('cleanup started') + gobject.source_remove(self.tmr_main) + gobject.source_remove(self.tmr_ptr) + gobject.source_remove(self.tmr_ptr2) + self.mainLoop.quit() def loadImages(self): + self.dbg('loadImages started') # Load phone image #self.pixbuf = gtk.gdk.pixbuf_new_from_file_at_size("/home/user/phone.png",18,18) icon_theme = gtk.icon_theme_get_default() - self.callPicture = icon_theme.load_icon("general_call", 18, gtk.ICON_LOOKUP_NO_SVG) + self.callPicture = gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/call.png",18,18) + #icon_theme.load_icon("general_call", 18, gtk.ICON_LOOKUP_NO_SVG) self.smsPicture = gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/sms.png",18,18) # Load 5 numbers and the "+5" @@ -62,21 +236,32 @@ class CallNotify(hildondesktop.StatusMenuItem): self.imgList.append(gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/CallNotify/more.png",18,18)) # Screen off event-handler - def state_cb(self, state): - if state == osso.device_state.OSSO_DISPLAY_OFF: - gobject.source_remove(self.tmr_main) - gobject.source_remove(self.tmr_ptr) - elif state == osso.device_state.OSSO_DISPLAY_ON: - self.tmr_main = gobject.timeout_add(5000, self.handleMissedCall) - self.show() - return False + def state_cb(self, state, a): + self.dbg('state_cb started') + if state == osso.device_state.OSSO_DISPLAY_OFF: + try: + #gobject.source_remove(self.tmr_main) + self.tmrset = False + #gobject.source_remove(self.tmr_ptr) + + #gobject.source_remove(self.tmr_ptr2) + except: + pass + elif state == osso.device_state.OSSO_DISPLAY_ON: + if not tmrset: + pass + #self.tmr_main = gobject.timeout_add(5000, self.handleMissedCall) + #self.handleMissedCall() + return False # Method to define the way to add dbus signal receiver def smsrec(self): - self.stop_notification(self) + self.dbg('smsrec started') + self.stop_notification(self) def startDbusListeners(self): + self.dbg('startDbusListeners started') DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() #bus.add_signal_receiver(self.stop_notification, "NotificationClosed", "org.freedesktop.Notifications", "org.freedesktop.Notifications", "/org/freedesktop/Notifications") @@ -91,19 +276,22 @@ class CallNotify(hildondesktop.StatusMenuItem): return False def smsReceived(self, a): - if a[0].has_key('message-type'): - if self.missedLastSMS == self.getMissedCallsCount(True): - if self.msgType == "Call": - self.msgType = "Both" - else: - self.msgType = "SMS" - self.show() - self.missedLastSMS = self.getMissedCallsCount(True) + self.dbg('snsReceived started') + if a[0].has_key('message-type'): + if self.missedLastSMS == self.getMissedCallsCount(True): + if self.msgType == "Call": + self.msgType = "Both" + else: + self.msgType = "SMS" + self.show() + self.missedLastSMS = self.getMissedCallsCount(True) def smsRead(self, a): - self.stop_notification(a) + self.dbg('smsRead started') + self.stop_notification(a) def handleMissedCall(self): + self.dbg('handleMissedCall started') if self.missedLastCall != self.getMissedCallsCount(False): if self.msgType == "SMS": self.msgType = "Both" @@ -114,18 +302,24 @@ class CallNotify(hildondesktop.StatusMenuItem): return True def stop_notification(self, a): - self.set_status_area_icon(None) - gobject.source_remove(self.tmr_ptr) - self.set_status_area_icon(None) - # Reset the notification (get recent missed call count) - self.missed = self.getMissedCallsCount(False) - self.missedSMS = self.getMissedCallsCount(True) - self.missedLastCall = self.missed - self.missedLastSMS = self.missedSMS - self.stop = False - self.msgType = "" + self.dbg('stop_notification started') + try: + self.set_status_area_icon(None) + # Reset the notification (get recent missed call count) + self.missed = self.getMissedCallsCount(False) + self.missedSMS = self.getMissedCallsCount(True) + self.missedLastCall = self.missed + self.missedLastSMS = self.missedSMS + self.stop = False + self.msgType = "" + gobject.source_remove(self.tmr_ptr) + gobject.source_remove(self.tmr_ptr2) + except: + pass + def theLoop(self): + self.dbg('theLoop started') missedCalls = self.getMissedCallsCount(False) if self.missedLastCall != missedCalls: self.show() @@ -133,6 +327,7 @@ class CallNotify(hildondesktop.StatusMenuItem): return True def getMissedCallsCount(self, isSms): + self.dbg('getMissedCallsCount started. agrs: ' + str(isSms)) eType = 3 if isSms: eType=7 @@ -142,12 +337,15 @@ class CallNotify(hildondesktop.StatusMenuItem): return cur.fetchone()[0] def show(self): + self.dbg('show started') # blink the icon every 1 second - if not(self.stop): - self.tmr_ptr = gobject.timeout_add(1000, self.blinkIcon) + if not(self.stop) and self.visual: self.stop = True + self.tmr_ptr = gobject.timeout_add(1000, self.blinkIcon) + self.tmr_ptr2 = gobject.timeout_add(int(self.interval*1000*60), self.playSound) def blinkIcon(self): + self.dbg('blinkIcon started') if self.toShow: self.toShow = False img = self.callPicture @@ -172,6 +370,14 @@ class CallNotify(hildondesktop.StatusMenuItem): self.toShow = True self.set_status_area_icon(img) return True + + def dbg(self, txt): + if self.Debug: + f = open(self.configDir+'log.txt', 'a') + f.write(str(datetime.datetime.now()) + ': '+ txt) + f.write('\n') + + f.close() hd_plugin_type = CallNotify diff --git a/src/usr/share/CallNotify/call.png b/src/usr/share/CallNotify/call.png new file mode 100644 index 0000000000000000000000000000000000000000..af83c5ec5f8d3437e1dda968455b0809bd760cd4 GIT binary patch literal 434 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|mn3BBRT^Rni_n+Ah2>8 zUk71ECym(^Ai=T%$8;bK*2|!5k>>^E?DBMR4AD5B{NucXM3VTkpYosU|NVdUKkvWf zM(sy`?*IJ%x?aNK<9(6fiHH8b{vUWSq{OxA|NMoTGdVX2M0Vc&&!5n_{{P{B@r;|( z7Vq}_|6hHDkh+5ZoBvz?7b|!PbUpcJ?;6C^K+6!1#V$n^Wy%0pF3SQ{?|9Rar&hyFbOua|1*9Zw1s0;%JF)=J8GGK{&R$El6v92 z$MdMX1ml?{%hXLc7`~QFG~ke2paArRYKdz^NlIc#s#S7PDv)9@GB7mIH89pSvIsG> zurf8XGBVdTFt9Q(a5~P&jG`eoKP5A*61N89Svx@?DhaY7I6tkVJh3R1p}f3YFEcN@ cI61K(RWH9NefB#WDWD<-Pgg&ebxsLQ06$HgegFUf literal 0 HcmV?d00001 diff --git a/src/usr/share/CallNotify/missed.wav b/src/usr/share/CallNotify/missed.wav new file mode 100644 index 0000000000000000000000000000000000000000..73ff62b5fe86f57982f05f825b1d4bc1dbac1bd1 GIT binary patch literal 28270 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