From c7301600ffd56e63f6c70e31b1a4d7b85435743b Mon Sep 17 00:00:00 2001 From: epage Date: Sat, 15 Aug 2009 03:27:10 +0000 Subject: [PATCH] Most of the way there on notification support; can turn off led, can set and delete alarms from the command line, just not tested setting alarms from the UI git-svn-id: file:///svnroot/gc-dialer/trunk@388 c39d3808-3fe2-4d86-a59f-b7f623ee9f21 --- src/alarm_handler.py | 136 ++++++++++++++++++++++++++++++++++++++ src/alarm_notify.py | 130 +++++++++++++++++++++++++++++++++++++ src/constants.py | 4 ++ src/dc_glade.py | 95 +++++++++++++++++++-------- src/dialcentral.glade | 172 ++++++++++++++++++++++++++++++++++++++++++++----- src/gc_views.py | 109 ++++++++++++++++++++++++++++--- src/gtk_toolbox.py | 38 +++++++++-- src/led_handler.py | 24 +++++++ src/null_views.py | 20 +++++- support/builddeb.py | 1 + 10 files changed, 672 insertions(+), 57 deletions(-) create mode 100644 src/alarm_handler.py create mode 100755 src/alarm_notify.py create mode 100755 src/led_handler.py diff --git a/src/alarm_handler.py b/src/alarm_handler.py new file mode 100644 index 0000000..f3aa648 --- /dev/null +++ b/src/alarm_handler.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python + +import os +import time +import datetime + +import dbus +import osso.alarmd as alarmd + + +class AlarmHandler(object): + + _INVALID_COOKIE = -1 + _TITLE = "Dialcentral Notifications" + _LAUNCHER = os.path.join(os.path.dirname(__file__), "alarm_notifier.py"), + _REPEAT_FOREVER = -1 + _DEFAULT_FLAGS = ( + alarmd.ALARM_EVENT_NO_DIALOG | + alarmd.ALARM_EVENT_NO_SNOOZE | + alarmd.ALARM_EVENT_CONNECTED + ) + + def __init__(self): + self._recurrence = 5 + + bus = dbus.SystemBus() + self._alarmdDBus = bus.get_object("com.nokia.alarmd", "/com/nokia/alarmd"); + self._alarmCookie = self._INVALID_COOKIE + + def load_settings(self, config, sectionName): + self._recurrence = config.getint(sectionName, "recurrence") + self._alarmCookie = config.getint(sectionName, "alarmCookie") + + def save_settings(self, config, sectionName): + config.set(sectionName, "recurrence", str(self._recurrence)) + config.set(sectionName, "alarmCookie", str(self._alarmCookie)) + + def apply_settings(self, enabled, recurrence): + if recurrence != self._recurrence and enabled != self.isEnabled: + if self.isEnabled: + self.delete_alarm() + elif enabled: + self._set_alarm(recurrence) + self._recurrence = int(recurrence) + + def delete_alarm(self): + if self._alarmCookie == self._INVALID_COOKIE: + return + deleteResult = self._alarmdDBus.del_event(dbus.Int32(self._alarmCookie)) + self._alarmCookie = self._INVALID_COOKIE + assert deleteResult != -1, "Deleting of alarm event failed" + + @property + def recurrence(self): + return self._recurrence + + @property + def isEnabled(self): + return self._alarmCookie != self._INVALID_COOKIE + + def _get_start_time(self, recurrence): + now = datetime.datetime.now() + startTimeMinute = now.minute + recurrence + now.replace(minute=startTimeMinute) + timestamp = int(time.mktime(now.timetuple())) + return timestamp + + def _set_alarm(self, recurrence): + alarmTime = self._get_start_time(recurrence) + + #Setup the alarm arguments so that they can be passed to the D-Bus add_event method + action = [] + action.extend(['flags', self._DEFAULT_FLAGS]) + action.extend(['title', self._TITLE]) + action.extend(['path', self._LAUNCHER]) + action.extend([ + 'arguments', + dbus.Array( + [alarmTime, int(27)], + signature=dbus.Signature('v') + ) + ]) #int(27) used in place of alarm_index + + event = [] + event.extend([dbus.ObjectPath('/AlarmdEventRecurring'), dbus.UInt32(4)]) + event.extend(['action', dbus.ObjectPath('/AlarmdActionExec')]) #use AlarmdActionExec instead of AlarmdActionDbus + event.append(dbus.UInt32(len(action) / 2)) + event.extend(action) + event.extend(['time', dbus.Int64(alarmTime)]) + event.extend(['recurr_interval', dbus.UInt32(recurrence)]) + event.extend(['recurr_count', dbus.Int32(self._REPEAT_FOREVER)]) + + self._alarmCookie = self._alarmdDBus.add_event(*event); + + +def main(): + import ConfigParser + import constants + try: + import optparse + except ImportError: + return + + parser = optparse.OptionParser() + parser.add_option("-x", "--display", action="store_true", dest="display", help="Display data") + parser.add_option("-e", "--enable", action="store_true", dest="enabled", help="Whether the alarm should be enabled or not", default=False) + parser.add_option("-d", "--disable", action="store_false", dest="enabled", help="Whether the alarm should be enabled or not", default=False) + parser.add_option("-r", "--recurrence", action="store", type="int", dest="recurrence", help="How often the alarm occurs", default=5) + (commandOptions, commandArgs) = parser.parse_args() + + alarmHandler = AlarmHandler() + config = ConfigParser.SafeConfigParser() + config.read(constants._user_settings_) + alarmHandler.load_settings(config, "alarm") + + if commandOptions.display: + print "Alarm (%s) is %s for every %d minutes" % ( + alarmHandler._alarmCookie, + "enabled" if alarmHandler.isEnabled else "disabled", + alarmHandler.recurrence, + ) + else: + isEnabled = commandOptions.enabled + recurrence = commandOptions.recurrence + alarmHandler.apply_settings(isEnabled, recurrence) + + alarmHandler.save_settings(config, "alarm") + configFile = open(constants._user_settings_, "wb") + try: + config.write(configFile) + finally: + configFile.close() + + +if __name__ == "__main__": + main() diff --git a/src/alarm_notify.py b/src/alarm_notify.py new file mode 100755 index 0000000..c2d80a2 --- /dev/null +++ b/src/alarm_notify.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python + +import os +import filecmp +import ConfigParser +import pprint + +import constants +import gv_backend + + +def get_missed(backend): + missedPage = backend._browser.download(backend._missedCallsURL) + missedJson = pprint.pformat(backend._grab_json(missedPage)) + return missedJson + + +def get_voicemail(backend): + voicemailPage = backend._browser.download(backend._voicemailURL) + voicemailJson = pprint.pformat(backend._grab_json(voicemailPage)) + return voicemailJson + + +def get_sms(backend): + smsPage = backend._browser.download(backend._smsURL) + smsJson = pprint.pformat(backend._grab_json(smsPage)) + return smsJson + + +def is_changed(backend, type, get_material): + currentMaterial = get_material(backend) + previousSnapshotPath = os.path.join(constants._data_path_, "snapshot_%s.old.json" % type) + currentSnapshotPath = os.path.join(constants._data_path_, "snapshot_%s.json" % type) + + try: + os.remove(previousSnapshotPath) + except OSError, e: + # check if failed purely because the old file didn't exist, which is fine + if e.errno != 2: + raise + try: + os.rename(currentSnapshotPath, previousSnapshotPath) + previousExists = True + except OSError, e: + # check if failed purely because the old file didn't exist, which is fine + if e.errno != 2: + raise + previousExists = False + + currentSnapshot = file(currentSnapshotPath, "w") + try: + currentSnapshot.write(currentMaterial) + finally: + currentSnapshot.close() + + if not previousExists: + return True + + seemEqual = filecmp.cmp(previousSnapshotPath, currentSnapshotPath) + return not seemEqual + + +def notify(): + gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt") + backend = gv_backend.GVDialer(gvCookiePath) + + loggedIn = False + + if not loggedIn: + loggedIn = backend.is_authed() + + config = ConfigParser.SafeConfigParser() + config.read(constants._user_settings_) + if not loggedIn: + import base64 + try: + blobs = ( + config.get(constants.__pretty_app_name__, "bin_blob_%i" % i) + for i in xrange(2) + ) + creds = ( + base64.b64decode(blob) + for blob in blobs + ) + username, password = tuple(creds) + loggedIn = backend.login(username, password) + except ConfigParser.NoOptionError, e: + pass + except ConfigParser.NoSectionError, e: + pass + + try: + notifyOnMissed = config.getboolean("2 - Account Info", "notifyOnMissed") + notifyOnVoicemail = config.getboolean("2 - Account Info", "notifyOnVoicemail") + notifyOnSms = config.getboolean("2 - Account Info", "notifyOnSms") + except ConfigParser.NoOptionError, e: + notifyOnMissed = False + notifyOnVoicemail = False + notifyOnSms = False + except ConfigParser.NoSectionError, e: + notifyOnMissed = False + notifyOnVoicemail = False + notifyOnSms = False + + assert loggedIn + notifySources = [] + if notifyOnMissed: + notifySources.append(("missed", get_missed)) + if notifyOnVoicemail: + notifySources.append(("voicemail", get_voicemail)) + if notifyOnSms: + notifySources.append(("sms", get_sms)) + + notifyUser = False + for type, get_material in ( + ("missed", get_missed), + ("voicemail", get_voicemail), + ("sms", get_sms), + ): + if is_changed(backend, type, get_material): + notifyUser = True + + if notifyUser: + import led_handler + led = led_handler.LedHandler() + led.on() + + +if __name__ == "__main__": + notify() diff --git a/src/constants.py b/src/constants.py index 8bdb923..c19dffb 100644 --- a/src/constants.py +++ b/src/constants.py @@ -1,4 +1,8 @@ +import os + __pretty_app_name__ = "DialCentral" __app_name__ = "dialcentral" __version__ = "1.0.4" __app_magic__ = 0xdeadbeef +_data_path_ = os.path.join(os.path.expanduser("~"), ".dialcentral") +_user_settings_ = "%s/settings.ini" % _data_path_ diff --git a/src/dc_glade.py b/src/dc_glade.py index 245d6c6..14b6db5 100755 --- a/src/dc_glade.py +++ b/src/dc_glade.py @@ -39,7 +39,6 @@ import ConfigParser import itertools import warnings -import gobject import gtk import gtk.glade @@ -71,9 +70,9 @@ def display_error_message(msg): class Dialcentral(object): _glade_files = [ - '/usr/lib/dialcentral/dialcentral.glade', os.path.join(os.path.dirname(__file__), "dialcentral.glade"), os.path.join(os.path.dirname(__file__), "../lib/dialcentral.glade"), + '/usr/lib/dialcentral/dialcentral.glade', ] KEYPAD_TAB = 0 @@ -87,9 +86,6 @@ class Dialcentral(object): GV_BACKEND = 2 BACKENDS = (NULL_BACKEND, GC_BACKEND, GV_BACKEND) - _data_path = os.path.join(os.path.expanduser("~"), ".dialcentral") - _user_settings = "%s/settings.ini" % _data_path - def __init__(self): self._initDone = False self._connection = None @@ -105,6 +101,8 @@ class Dialcentral(object): self._messagesViews = None self._recentViews = None self._contactsViews = None + self._alarmHandler = None + self._ledHandler = None self._originalCurrentLabels = [] for path in self._glade_files: @@ -212,6 +210,16 @@ class Dialcentral(object): else: pass # warnings.warn("No OSSO", UserWarning, 2) + try: + import alarm_handler + self._alarmHandler = alarm_handler.AlarmHandler() + except ImportError: + alarm_handler = None + if hildon is not None: + import led_handler + self._ledHandler = led_handler.LedHandler() + self._ledHandler.off() + # Setup maemo specifics try: import conic @@ -233,12 +241,12 @@ class Dialcentral(object): import gc_views try: - os.makedirs(self._data_path) + os.makedirs(constants._data_path_) except OSError, e: if e.errno != 17: raise - gcCookiePath = os.path.join(self._data_path, "gc_cookies.txt") - gvCookiePath = os.path.join(self._data_path, "gv_cookies.txt") + gcCookiePath = os.path.join(constants._data_path_, "gc_cookies.txt") + gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt") self._defaultBackendId = self._guess_preferred_backend(( (self.GC_BACKEND, gcCookiePath), (self.GV_BACKEND, gvCookiePath), @@ -257,12 +265,14 @@ class Dialcentral(object): }) self._accountViews.update({ self.GC_BACKEND: gc_views.AccountInfo( - self._widgetTree, self._phoneBackends[self.GC_BACKEND], self._errorDisplay + self._widgetTree, self._phoneBackends[self.GC_BACKEND], None, self._errorDisplay ), self.GV_BACKEND: gc_views.AccountInfo( - self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay + self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay ), }) + self._accountViews[self.GC_BACKEND].save_everything = lambda *args: None + self._accountViews[self.GV_BACKEND].save_everything = self._save_settings self._recentViews.update({ self.GC_BACKEND: gc_views.RecentCallsView( self._widgetTree, self._phoneBackends[self.GC_BACKEND], self._errorDisplay @@ -287,7 +297,7 @@ class Dialcentral(object): }) evoBackend = evo_backend.EvolutionAddressBook() - fsContactsPath = os.path.join(self._data_path, "contacts") + fsContactsPath = os.path.join(constants._data_path_, "contacts") fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath) for backendId in (self.GV_BACKEND, self.GC_BACKEND): self._dialpads[backendId].number_selected = self._select_action @@ -329,14 +339,14 @@ class Dialcentral(object): self._initDone = True config = ConfigParser.SafeConfigParser() - config.read(self._user_settings) + config.read(constants._user_settings_) with gtk_toolbox.gtk_lock(): self.load_settings(config) self._spawn_attempt_login(2) except Exception, e: with gtk_toolbox.gtk_lock(): - self._errorDisplay.push_exception(e) + self._errorDisplay.push_exception() def attempt_login(self, numOfAttempts = 10, force = False): """ @@ -365,7 +375,7 @@ class Dialcentral(object): self._change_loggedin_status(serviceId) except StandardError, e: with gtk_toolbox.gtk_lock(): - self._errorDisplay.push_exception(e) + self._errorDisplay.push_exception() def _spawn_attempt_login(self, *args): self._loginSink.send(args) @@ -493,10 +503,21 @@ class Dialcentral(object): for blob in blobs ) self._credentials = tuple(creds) + + if self._alarmHandler is not None: + self._alarmHandler.load_settings(config, "alarm") + except ConfigParser.NoOptionError, e: + warnings.warn( + "Settings file %s is missing section %s" % ( + constants._user_settings_, + e.section, + ), + stacklevel=2 + ) except ConfigParser.NoSectionError, e: warnings.warn( "Settings file %s is missing section %s" % ( - self._user_settings, + constants._user_settings_, e.section, ), stacklevel=2 @@ -512,10 +533,18 @@ class Dialcentral(object): sectionName = "%s - %s" % (backendId, view.name()) try: view.load_settings(config, sectionName) + except ConfigParser.NoOptionError, e: + warnings.warn( + "Settings file %s is missing section %s" % ( + constants._user_settings_, + e.section, + ), + stacklevel=2 + ) except ConfigParser.NoSectionError, e: warnings.warn( "Settings file %s is missing section %s" % ( - self._user_settings, + constants._user_settings_, e.section, ), stacklevel=2 @@ -530,6 +559,10 @@ class Dialcentral(object): for i, value in enumerate(self._credentials): blob = base64.b64encode(value) config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob) + config.add_section("alarm") + if self._alarmHandler is not None: + self._alarmHandler.save_settings(config, "alarm") + for backendId, view in itertools.chain( self._dialpads.iteritems(), self._accountViews.iteritems(), @@ -555,10 +588,13 @@ class Dialcentral(object): """ config = ConfigParser.SafeConfigParser() self.save_settings(config) - with open(self._user_settings, "wb") as configFile: + with open(constants._user_settings_, "wb") as configFile: config.write(configFile) def _refresh_active_tab(self): + if self._ledHandler is not None: + self._ledHandler.off() + pageIndex = self._notebook.get_current_page() if pageIndex == self.CONTACTS_TAB: self._contactsViews[self._selectedBackendId].update(force=True) @@ -676,7 +712,7 @@ class Dialcentral(object): loggedIn = self._phoneBackends[self._selectedBackendId].is_authed() except StandardError, e: loggedIn = False - self._errorDisplay.push_exception(e) + self._errorDisplay.push_exception() return if not loggedIn: @@ -690,9 +726,9 @@ class Dialcentral(object): self._phoneBackends[self._selectedBackendId].send_sms(number, message) dialed = True except StandardError, e: - self._errorDisplay.push_exception(e) + self._errorDisplay.push_exception() except ValueError, e: - self._errorDisplay.push_exception(e) + self._errorDisplay.push_exception() def _on_dial_clicked(self, number): assert number, "No number to call" @@ -700,7 +736,7 @@ class Dialcentral(object): loggedIn = self._phoneBackends[self._selectedBackendId].is_authed() except StandardError, e: loggedIn = False - self._errorDisplay.push_exception(e) + self._errorDisplay.push_exception() return if not loggedIn: @@ -715,9 +751,9 @@ class Dialcentral(object): self._phoneBackends[self._selectedBackendId].dial(number) dialed = True except StandardError, e: - self._errorDisplay.push_exception(e) + self._errorDisplay.push_exception() except ValueError, e: - self._errorDisplay.push_exception(e) + self._errorDisplay.push_exception() if dialed: self._dialpads[self._selectedBackendId].clear() @@ -753,11 +789,14 @@ def run_doctest(): def run_dialpad(): - gtk.gdk.threads_init() - if hildon is not None: - gtk.set_application_name(constants.__pretty_app_name__) - handle = Dialcentral() - gtk.main() + _lock_file = os.path.join(constants._data_path_, ".lock") + with gtk_toolbox.flock(_lock_file, 0): + gtk.gdk.threads_init() + + if hildon is not None: + gtk.set_application_name(constants.__pretty_app_name__) + handle = Dialcentral() + gtk.main() class DummyOptions(object): diff --git a/src/dialcentral.glade b/src/dialcentral.glade index 49c4a0a..71e6885 100644 --- a/src/dialcentral.glade +++ b/src/dialcentral.glade @@ -715,71 +715,213 @@ True 11 - 3 + 7 2 True + 0 No Number Available True 1 2 + GTK_FILL True - 1 - 5 - Google Voice Number: - right + 0 + 0 + 10 + Account Number: + + + True - 1 - 5 + 0 + 0 + 10 Callback Number: 1 2 + - - Clear Account Information + True - True - True - 1 2 + 1 + 2 + GTK_FILL + + + + + + True + + + True + True + + + + 0 + + + + + True + Minutes + + + False + 1 + + + + + 1 + 2 + 3 + 4 + GTK_FILL + + + + + + True + 0 + + 2 3 + + + + + True + 0 + + + 4 + 5 + + + + + True + vertical + + + Missed Calls + True + True + False + True + + + 0 + + + + + Voicemail + True + True + False + True + + + 1 + + + + + SMS + True + True + False + True + + + 2 + + + + + 1 + 2 + 4 + 5 + + + + + Notifications + True + True + False + 0 + True + + + 3 + 4 + GTK_FILL - + + Clear Account Information True + True + True + False + 1 2 - 1 - 2 + 6 + 7 GTK_FILL + + True + 0 + + + 5 + 6 + + + + + + + + + diff --git a/src/gc_views.py b/src/gc_views.py index 20a4359..f401775 100644 --- a/src/gc_views.py +++ b/src/gc_views.py @@ -491,7 +491,7 @@ class Dialpad(object): self._prettynumber = make_pretty(self._phonenumber) self._numberdisplay.set_label("%s" % (self._prettynumber)) except TypeError, e: - self._errorDisplay.push_exception(e) + self._errorDisplay.push_exception() def clear(self): self.set_number("") @@ -552,31 +552,85 @@ class Dialpad(object): class AccountInfo(object): - def __init__(self, widgetTree, backend, errorDisplay): + def __init__(self, widgetTree, backend, alarmHandler, errorDisplay): self._errorDisplay = errorDisplay self._backend = backend self._isPopulated = False + self._alarmHandler = alarmHandler + self._notifyOnMissed = False + self._notifyOnVoicemail = False + self._notifyOnSms = False self._callbackList = gtk.ListStore(gobject.TYPE_STRING) self._accountViewNumberDisplay = widgetTree.get_widget("gcnumber_display") self._callbackCombo = widgetTree.get_widget("callbackcombo") self._onCallbackentryChangedId = 0 + self._notifyCheckbox = widgetTree.get_widget("notifyCheckbox") + self._minutesEntry = widgetTree.get_widget("minutesEntry") + self._missedCheckbox = widgetTree.get_widget("missedCheckbox") + self._voicemailCheckbox = widgetTree.get_widget("voicemailCheckbox") + self._smsCheckbox = widgetTree.get_widget("smsCheckbox") + self._onNotifyToggled = 0 + self._onMinutesChanged = 0 + self._onMissedToggled = 0 + self._onVoicemailToggled = 0 + self._onSmsToggled = 0 + self._defaultCallback = "" def enable(self): assert self._backend.is_authed(), "Attempting to enable backend while not logged in" + self._accountViewNumberDisplay.set_use_markup(True) self.set_account_number("") + self._callbackList.clear() self._onCallbackentryChangedId = self._callbackCombo.get_child().connect("changed", self._on_callbackentry_changed) + + if self._alarmHandler is not None: + self._notifyCheckbox.set_active(self._alarmHandler.isEnabled) + self._minutesEntry.set_value(self._alarmHandler.recurrence) + self._missedCheckbox.set_active(self._notifyOnMissed) + self._voicemailCheckbox.set_active(self._notifyOnVoicemail) + self._smsCheckbox.set_active(self._notifyOnSms) + self._onNotifyToggled = self._notifyCheckbox.connect("toggled", self._on_notify_toggled) + self._onMinutesChanged = self._minutesEntry.connect("value-changed", self._on_minutes_changed) + self._onMissedToggled = self._missedCheckbox.connect("toggled", self._on_missed_toggled) + self._onVoicemailToggled = self._voicemailCheckbox.connect("toggled", self._on_voicemail_toggled) + self._onSmsToggled = self._smsCheckbox.connect("toggled", self._on_sms_toggled) + else: + self._notifyCheckbox.set_sensitive(False) + self._minutesEntry.set_sensitive(False) + self._missedCheckbox.set_sensitive(False) + self._voicemailCheckbox.set_sensitive(False) + self._smsCheckbox.set_sensitive(False) + self.update(force=True) def disable(self): self._callbackCombo.get_child().disconnect(self._onCallbackentryChangedId) + self._onCallbackentryChangedId = 0 - self.clear() + if self._alarmHandler is not None: + self._notifyCheckbox.disconnect(self._onNotifyToggled) + self._minutesEntry.disconnect(self._onMinutesChanged) + self._missedCheckbox.disconnect(self._onNotifyToggled) + self._voicemailCheckbox.disconnect(self._onNotifyToggled) + self._smsCheckbox.disconnect(self._onNotifyToggled) + self._onNotifyToggled = 0 + self._onMinutesChanged = 0 + self._onMissedToggled = 0 + self._onVoicemailToggled = 0 + self._onSmsToggled = 0 + else: + self._notifyCheckbox.set_sensitive(True) + self._minutesEntry.set_sensitive(True) + self._missedCheckbox.set_sensitive(True) + self._voicemailCheckbox.set_sensitive(True) + self._smsCheckbox.set_sensitive(True) + self.clear() self._callbackList.clear() def get_selected_callback_number(self): @@ -599,12 +653,18 @@ class AccountInfo(object): self.set_account_number("") self._isPopulated = False + def save_everything(self): + raise NotImplementedError + @staticmethod def name(): return "Account Info" def load_settings(self, config, section): self._defaultCallback = config.get(section, "callback") + self._notifyOnMissed = config.getboolean(section, "notifyOnMissed") + self._notifyOnVoicemail = config.getboolean(section, "notifyOnVoicemail") + self._notifyOnSms = config.getboolean(section, "notifyOnSms") def save_settings(self, config, section): """ @@ -612,6 +672,9 @@ class AccountInfo(object): """ callback = self.get_selected_callback_number() config.set(section, "callback", callback) + config.set(section, "notifyOnMissed", repr(self._notifyOnMissed)) + config.set(section, "notifyOnVoicemail", repr(self._notifyOnVoicemail)) + config.set(section, "notifyOnSms", repr(self._notifyOnSms)) def _populate_callback_combo(self): self._isPopulated = True @@ -619,7 +682,7 @@ class AccountInfo(object): try: callbackNumbers = self._backend.get_callback_numbers() except StandardError, e: - self._errorDisplay.push_exception(e) + self._errorDisplay.push_exception() self._isPopulated = False return @@ -656,13 +719,41 @@ class AccountInfo(object): UserWarning, 2 ) except StandardError, e: - self._errorDisplay.push_exception(e) + self._errorDisplay.push_exception() + + def _update_alarm_settings(self): + try: + isEnabled = self._notifyCheckbox.get_active() + recurrence = self._minutesEntry.get_value() + if isEnabled != self._alarmHandler.isEnabled and recurrence != self.recurrence: + self._alarmHandler.apply_settings(isEnabled, recurrence) + finally: + self.save_everything() + self._notifyCheckbox.set_active(self._alarmHandler.isEnabled) + self._minutesEntry.set_value(self._alarmHandler.recurrence) def _on_callbackentry_changed(self, *args): text = self.get_selected_callback_number() number = make_ugly(text) self._set_callback_number(number) + self.save_everything() + + def _on_notify_toggled(self, *args): + self._update_alarm_settings() + + def _on_minutes_changed(self, *args): + self._update_alarm_settings() + + def _on_missed_toggled(self, *args): + self.save_everything() + + def _on_voicemail_toggled(self, *args): + self.save_everything() + + def _on_sms_toggled(self, *args): + self.save_everything() + class RecentCallsView(object): @@ -772,7 +863,7 @@ class RecentCallsView(object): try: recentItems = self._backend.get_recent() except StandardError, e: - self._errorDisplay.push_exception_with_lock(e) + self._errorDisplay.push_exception_with_lock() self._isPopulated = False recentItems = [] @@ -919,7 +1010,7 @@ class MessagesView(object): try: messageItems = self._backend.get_messages() except StandardError, e: - self._errorDisplay.push_exception_with_lock(e) + self._errorDisplay.push_exception_with_lock() self._isPopulated = False messageItems = [] @@ -1102,7 +1193,7 @@ class ContactsView(object): except StandardError, e: contacts = [] self._isPopulated = False - self._errorDisplay.push_exception_with_lock(e) + self._errorDisplay.push_exception_with_lock() for contactId, contactName in contacts: contactType = (addressBook.contact_source_short_name(contactId), ) self._contactsmodel.append(contactType + (contactName, "", contactId) + ("", )) @@ -1132,7 +1223,7 @@ class ContactsView(object): contactDetails = self._addressBook.get_contact_details(contactId) except StandardError, e: contactDetails = [] - self._errorDisplay.push_exception(e) + self._errorDisplay.push_exception() contactPhoneNumbers = [phoneNumber for phoneNumber in contactDetails] if len(contactPhoneNumbers) == 0: diff --git a/src/gtk_toolbox.py b/src/gtk_toolbox.py index 833b097..7080949 100644 --- a/src/gtk_toolbox.py +++ b/src/gtk_toolbox.py @@ -2,7 +2,10 @@ from __future__ import with_statement +import os +import errno import sys +import time import traceback import functools import contextlib @@ -15,6 +18,33 @@ import gtk @contextlib.contextmanager +def flock(path, timeout=-1): + WAIT_FOREVER = -1 + DELAY = 0.1 + timeSpent = 0 + + acquired = False + + while timeSpent <= timeout or timeout == WAIT_FOREVER: + try: + fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR) + acquired = True + break + except OSError, e: + if e.errno != errno.EEXIST: + raise + time.sleep(DELAY) + timeSpent += DELAY + + assert acquired, "Failed to grab file-lock %s within timeout %d" % (path, timeout) + + try: + yield fd + finally: + os.unlink(path) + + +@contextlib.contextmanager def gtk_lock(): gtk.gdk.threads_enter() try: @@ -344,11 +374,11 @@ class ErrorDisplay(object): else: self.__show_message(message) - def push_exception_with_lock(self, exception = None): + def push_exception_with_lock(self, exception = None, stacklevel=3): with gtk_lock(): - self.push_exception(exception) + self.push_exception(exception, stacklevel=stacklevel) - def push_exception(self, exception = None): + def push_exception(self, exception = None, stacklevel=2): if exception is None: userMessage = str(sys.exc_value) warningMessage = str(traceback.format_exc()) @@ -356,7 +386,7 @@ class ErrorDisplay(object): userMessage = str(exception) warningMessage = str(exception) self.push_message(userMessage) - warnings.warn(warningMessage, stacklevel=2) + warnings.warn(warningMessage, stacklevel=stacklevel) def pop_message(self): if 0 < len(self.__messages): diff --git a/src/led_handler.py b/src/led_handler.py new file mode 100755 index 0000000..211036e --- /dev/null +++ b/src/led_handler.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +import dbus + + +class LedHandler(object): + + def __init__(self): + self._bus = dbus.SystemBus() + self._rawMceRequest = self._bus.get_object("com.nokia.mce", "/com/nokia/mce/request") + self._mceRequest = dbus.Interface(self._rawMceRequest, dbus_interface="com.nokia.mce.request") + + self._ledPattern = "PatternCommunicationChat" + + def on(self): + self._mceRequest.req_led_pattern_activate(self._ledPattern) + + def off(self): + self._mceRequest.req_led_pattern_deactivate(self._ledPattern) + + +if __name__ == "__main__": + leds = LedHandler() + leds.off() diff --git a/src/null_views.py b/src/null_views.py index 034eeb6..33413cb 100644 --- a/src/null_views.py +++ b/src/null_views.py @@ -57,15 +57,33 @@ class AccountInfo(object): self._callbackCombo = widgetTree.get_widget("callbackcombo") self._clearCookiesButton = widgetTree.get_widget("clearcookies") + self._notifyCheckbox = widgetTree.get_widget("notifyCheckbox") + self._minutesEntry = widgetTree.get_widget("minutesEntry") + self._missedCheckbox = widgetTree.get_widget("missedCheckbox") + self._voicemailCheckbox = widgetTree.get_widget("voicemailCheckbox") + self._smsCheckbox = widgetTree.get_widget("smsCheckbox") + def enable(self): self._callbackCombo.set_sensitive(False) self._clearCookiesButton.set_sensitive(False) + self._notifyCheckbox.set_sensitive(False) + self._minutesEntry.set_sensitive(False) + self._missedCheckbox.set_sensitive(False) + self._voicemailCheckbox.set_sensitive(False) + self._smsCheckbox.set_sensitive(False) + self._accountViewNumberDisplay.set_text("") def disable(self): - self._clearCookiesButton.set_sensitive(True) self._callbackCombo.set_sensitive(True) + self._clearCookiesButton.set_sensitive(True) + + self._notifyCheckbox.set_sensitive(True) + self._minutesEntry.set_sensitive(True) + self._missedCheckbox.set_sensitive(True) + self._voicemailCheckbox.set_sensitive(True) + self._smsCheckbox.set_sensitive(True) @staticmethod def update(): diff --git a/support/builddeb.py b/support/builddeb.py index 9fadb93..9a32ec0 100755 --- a/support/builddeb.py +++ b/support/builddeb.py @@ -22,6 +22,7 @@ __changelog__ = ''' * "Back" button and tabs now visually indicate when they've entered a "hold" state * Fixed the duplicate title on Maemo * Removing some device connection observer code due to high bug to low benefit ratio +* Notification support 1.0.3 * Holding down a tab for a second will now force a refresh -- 1.7.9.5