Commit 9c840d9b authored by Saul Ibarra's avatar Saul Ibarra

Added initial presence support

parent 8272c5a2
...@@ -42,4 +42,12 @@ Traceback (most recent call last): ...@@ -42,4 +42,12 @@ Traceback (most recent call last):
File "/home/dan/work/voip/blink-qt/sipsimple/bonjour.py", line 1125, in DNSServiceRegister File "/home/dan/work/voip/blink-qt/sipsimple/bonjour.py", line 1125, in DNSServiceRegister
TypeError: an integer is required TypeError: an integer is required
Presence
--------
- Is picking the most recent timestamp a good winning method?
- Calculate user idleness
- Add a GUI element for the offline note
- Delete own icon if we don't get anything back from XCAP?
- Unify settings for inbound and outbound presence
...@@ -41,6 +41,7 @@ from blink.configuration.datatypes import InvalidToken ...@@ -41,6 +41,7 @@ from blink.configuration.datatypes import InvalidToken
from blink.configuration.settings import SIPSimpleSettingsExtension from blink.configuration.settings import SIPSimpleSettingsExtension
from blink.logging import LogManager from blink.logging import LogManager
from blink.mainwindow import MainWindow from blink.mainwindow import MainWindow
from blink.presence import PresenceManager
from blink.resources import ApplicationData from blink.resources import ApplicationData
from blink.sessions import SessionManager from blink.sessions import SessionManager
from blink.update import UpdateManager from blink.update import UpdateManager
...@@ -96,6 +97,7 @@ class Blink(QApplication): ...@@ -96,6 +97,7 @@ class Blink(QApplication):
self.main_window = MainWindow() self.main_window = MainWindow()
self.ip_address_monitor = IPAddressMonitor() self.ip_address_monitor = IPAddressMonitor()
self.log_manager = LogManager() self.log_manager = LogManager()
self.presence_manager = PresenceManager()
self.update_manager = UpdateManager() self.update_manager = UpdateManager()
self.main_window.check_for_updates_action.triggered.connect(self.update_manager.check_for_updates) self.main_window.check_for_updates_action.triggered.connect(self.update_manager.check_for_updates)
...@@ -212,6 +214,7 @@ class Blink(QApplication): ...@@ -212,6 +214,7 @@ class Blink(QApplication):
def _NH_SIPApplicationWillStart(self, notification): def _NH_SIPApplicationWillStart(self, notification):
self.log_manager.start() self.log_manager.start()
self.presence_manager.start()
@run_in_gui_thread @run_in_gui_thread
def _NH_SIPApplicationDidStart(self, notification): def _NH_SIPApplicationDidStart(self, notification):
...@@ -229,6 +232,9 @@ class Blink(QApplication): ...@@ -229,6 +232,9 @@ class Blink(QApplication):
def _NH_SIPApplicationWillEnd(self, notification): def _NH_SIPApplicationWillEnd(self, notification):
self.ip_address_monitor.stop() self.ip_address_monitor.stop()
def _NH_SIPApplicationDidEnd(self, notification):
self.presence_manager.stop()
def _initialize_sipsimple(self): def _initialize_sipsimple(self):
if not os.path.exists(ApplicationData.get('config')): if not os.path.exists(ApplicationData.get('config')):
self.first_run = True self.first_run = True
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
__all__ = ['AccountExtension', 'BonjourAccountExtension'] __all__ = ['AccountExtension', 'BonjourAccountExtension']
from sipsimple.account import BonjourMSRPSettings, MessageSummarySettings, MSRPSettings, RTPSettings, SIPSettings, TLSSettings, XCAPSettings from sipsimple.account import BonjourMSRPSettings, MessageSummarySettings, MSRPSettings, PresenceSettings, RTPSettings, SIPSettings, TLSSettings, XCAPSettings
from sipsimple.configuration import Setting, SettingsGroup, SettingsObjectExtension from sipsimple.configuration import Setting, SettingsGroup, SettingsObjectExtension
from sipsimple.configuration.datatypes import AudioCodecList, Hostname, MSRPConnectionModel, MSRPTransport, NonNegativeInteger, SIPTransportList, SRTPEncryption from sipsimple.configuration.datatypes import AudioCodecList, Hostname, MSRPConnectionModel, MSRPTransport, NonNegativeInteger, SIPTransportList, SRTPEncryption
from sipsimple.util import user_info from sipsimple.util import user_info
...@@ -29,6 +29,10 @@ class MSRPSettingsExtension(MSRPSettings): ...@@ -29,6 +29,10 @@ class MSRPSettingsExtension(MSRPSettings):
connection_model = Setting(type=MSRPConnectionModel, default='relay') connection_model = Setting(type=MSRPConnectionModel, default='relay')
class PresenceSettingsExtension(PresenceSettings):
enabled = Setting(type=bool, default=True)
class PSTNSettings(SettingsGroup): class PSTNSettings(SettingsGroup):
idd_prefix = Setting(type=unicode, default=None, nillable=True) idd_prefix = Setting(type=unicode, default=None, nillable=True)
prefix = Setting(type=unicode, default=None, nillable=True) prefix = Setting(type=unicode, default=None, nillable=True)
...@@ -71,6 +75,7 @@ class AccountExtension(SettingsObjectExtension): ...@@ -71,6 +75,7 @@ class AccountExtension(SettingsObjectExtension):
message_summary = MessageSummarySettingsExtension message_summary = MessageSummarySettingsExtension
msrp = MSRPSettingsExtension msrp = MSRPSettingsExtension
pstn = PSTNSettings pstn = PSTNSettings
presence = PresenceSettingsExtension
rtp = RTPSettingsExtension rtp = RTPSettingsExtension
server = ServerSettings server = ServerSettings
sip = SIPSettingsExtension sip = SIPSettingsExtension
......
...@@ -94,6 +94,7 @@ class SIPSimpleSettingsExtension(SettingsObjectExtension): ...@@ -94,6 +94,7 @@ class SIPSimpleSettingsExtension(SettingsObjectExtension):
class BlinkPresenceSettings(SettingsGroup): class BlinkPresenceSettings(SettingsGroup):
current_state = Setting(type=PresenceState, default=PresenceState('Available')) current_state = Setting(type=PresenceState, default=PresenceState('Available'))
state_history = Setting(type=PresenceStateList, default=PresenceStateList()) state_history = Setting(type=PresenceStateList, default=PresenceStateList())
offline_note = Setting(type=unicode, nillable=True)
icon = Setting(type=IconDescriptor, nillable=True) icon = Setting(type=IconDescriptor, nillable=True)
......
...@@ -790,7 +790,8 @@ class Contact(object): ...@@ -790,7 +790,8 @@ class Contact(object):
def __init__(self, contact, group): def __init__(self, contact, group):
self.settings = contact self.settings = contact
self.group = group self.group = group
self.status = 'unknown' self.state = 'unknown'
self.note = None
notification_center = NotificationCenter() notification_center = NotificationCenter()
notification_center.add_observer(ObserverWeakrefProxy(self), sender=contact) notification_center.add_observer(ObserverWeakrefProxy(self), sender=contact)
...@@ -818,7 +819,7 @@ class Contact(object): ...@@ -818,7 +819,7 @@ class Contact(object):
return '%s(%r, %r)' % (self.__class__.__name__, self.settings, self.group) return '%s(%r, %r)' % (self.__class__.__name__, self.settings, self.group)
def __getstate__(self): def __getstate__(self):
return (self.settings.id, dict(group=self.group, status=self.status)) return (self.settings.id, dict(group=self.group, state=self.state))
def __setstate__(self, state): def __setstate__(self, state):
contact_id, state = state contact_id, state = state
...@@ -851,7 +852,7 @@ class Contact(object): ...@@ -851,7 +852,7 @@ class Contact(object):
@property @property
def info(self): def info(self):
return self.uri return self.note or self.uri
@property @property
def uri(self): def uri(self):
...@@ -909,6 +910,19 @@ class Contact(object): ...@@ -909,6 +910,19 @@ class Contact(object):
self.__dict__.pop('pixmap', None) self.__dict__.pop('pixmap', None)
notification.center.post_notification('BlinkContactDidChange', sender=self) notification.center.post_notification('BlinkContactDidChange', sender=self)
def _NH_AddressbookContactGotPresenceUpdate(self, notification):
if notification.data.state in ('available', 'away', 'busy', 'offline'):
self.__dict__['state'] = notification.data.state
else:
self.__dict__['state'] = 'unknown'
self.note = notification.data.note
if notification.data.icon_data:
icon = IconManager().store_data(self.settings.id, notification.data.icon_data)
if icon:
self.settings.icon = notification.data.icon_descriptor
self.settings.save()
notification.center.post_notification('BlinkContactDidChange', sender=self)
ui_class, base_class = uic.loadUiType(Resources.get('google_contacts_dialog.ui')) ui_class, base_class = uic.loadUiType(Resources.get('google_contacts_dialog.ui'))
...@@ -1293,9 +1307,9 @@ class ContactDelegate(QStyledItemDelegate): ...@@ -1293,9 +1307,9 @@ class ContactDelegate(QStyledItemDelegate):
widget.render(pixmap) widget.render(pixmap)
painter.drawPixmap(option.rect, pixmap) painter.drawPixmap(option.rect, pixmap)
if contact.status not in ('offline', 'unknown'): if contact.state not in ('offline', 'unknown'):
status_colors = dict(available='#00ff00', away='#ffff00', busy='#ff0000') status_colors = dict(available='#00ff00', away='#ffff00', busy='#ff0000')
color = QColor(status_colors[contact.status]) color = QColor(status_colors[contact.state])
painter.setRenderHint(QPainter.Antialiasing, True) painter.setRenderHint(QPainter.Antialiasing, True)
painter.setBrush(color) painter.setBrush(color)
painter.setPen(color.darker(200)) painter.setPen(color.darker(200))
...@@ -2708,11 +2722,16 @@ class ContactEditorDialog(base_class, ui_class): ...@@ -2708,11 +2722,16 @@ class ContactEditorDialog(base_class, ui_class):
self.display_name_editor.setText(contact.name) self.display_name_editor.setText(contact.name)
if contact.settings.icon is not None and contact.settings.icon.is_local: if contact.settings.icon is not None and contact.settings.icon.is_local:
self.icon_selector.filename = contact.settings.icon.url[len('file://'):] self.icon_selector.filename = contact.settings.icon.url[len('file://'):]
elif contact.settings.icon:
icon = IconManager().get(contact.settings.id)
if icon:
self.icon_selector.setPixmap(icon.pixmap(32))
else: else:
self.icon_selector.filename = None self.icon_selector.filename = None
self.preferred_media.setCurrentIndex(self.preferred_media.findText(contact.settings.preferred_media.title())) self.preferred_media.setCurrentIndex(self.preferred_media.findText(contact.settings.preferred_media.title()))
self.accept_button.setText(u'Ok') self.accept_button.setText(u'Ok')
self.accept_button.setEnabled(True) self.accept_button.setEnabled(True)
self.subscribe_presence.setChecked(contact.settings.presence.subscribe)
self.show() self.show()
def reset_icon(self): def reset_icon(self):
...@@ -2736,6 +2755,12 @@ class ContactEditorDialog(base_class, ui_class): ...@@ -2736,6 +2755,12 @@ class ContactEditorDialog(base_class, ui_class):
uri.uri = self.sip_address_editor.text() uri.uri = self.sip_address_editor.text()
contact.name = self.display_name_editor.text() contact.name = self.display_name_editor.text()
contact.preferred_media = self.preferred_media.currentText().lower() contact.preferred_media = self.preferred_media.currentText().lower()
if self.subscribe_presence.isChecked():
contact.presence.policy = 'allow'
contact.presence.subscribe = True
else:
contact.presence.policy = 'default'
contact.presence.subscribe = False
if self.icon_selector.filename is not None: if self.icon_selector.filename is not None:
icon_file = ApplicationData.get(self.icon_selector.filename) icon_file = ApplicationData.get(self.icon_selector.filename)
icon_descriptor = IconDescriptor('file://' + icon_file) icon_descriptor = IconDescriptor('file://' + icon_file)
......
...@@ -29,6 +29,7 @@ from blink.preferences import PreferencesWindow ...@@ -29,6 +29,7 @@ from blink.preferences import PreferencesWindow
from blink.sessions import ConferenceDialog, SessionManager, SessionModel from blink.sessions import ConferenceDialog, SessionManager, SessionModel
from blink.configuration.datatypes import IconDescriptor, InvalidToken, PresenceState from blink.configuration.datatypes import IconDescriptor, InvalidToken, PresenceState
from blink.configuration.settings import BlinkSettings from blink.configuration.settings import BlinkSettings
from blink.presence import PendingWatcherDialog
from blink.resources import IconManager, Resources from blink.resources import IconManager, Resources
from blink.util import run_in_gui_thread from blink.util import run_in_gui_thread
from blink.widgets.buttons import AccountState, SwitchViewButton from blink.widgets.buttons import AccountState, SwitchViewButton
...@@ -47,8 +48,11 @@ class MainWindow(base_class, ui_class): ...@@ -47,8 +48,11 @@ class MainWindow(base_class, ui_class):
notification_center.add_observer(self, name='SIPApplicationWillStart') notification_center.add_observer(self, name='SIPApplicationWillStart')
notification_center.add_observer(self, name='SIPApplicationDidStart') notification_center.add_observer(self, name='SIPApplicationDidStart')
notification_center.add_observer(self, name='SIPAccountGotMessageSummary') notification_center.add_observer(self, name='SIPAccountGotMessageSummary')
notification_center.add_observer(self, name='SIPAccountGotPendingWatcher')
notification_center.add_observer(self, sender=AccountManager()) notification_center.add_observer(self, sender=AccountManager())
self.pending_watcher_dialogs = []
self.mwi_icons = [QIcon(Resources.get('icons/mwi-%d.png' % i)) for i in xrange(0, 11)] self.mwi_icons = [QIcon(Resources.get('icons/mwi-%d.png' % i)) for i in xrange(0, 11)]
self.mwi_icons.append(QIcon(Resources.get('icons/mwi-many.png'))) self.mwi_icons.append(QIcon(Resources.get('icons/mwi-many.png')))
...@@ -58,7 +62,7 @@ class MainWindow(base_class, ui_class): ...@@ -58,7 +62,7 @@ class MainWindow(base_class, ui_class):
self.setWindowTitle('Blink') self.setWindowTitle('Blink')
self.setWindowIconText('Blink') self.setWindowIconText('Blink')
self.default_icon_path = Resources.get('icons/avatar.jpg') self.default_icon_path = Resources.get('icons/default-avatar.png')
self.default_icon = QIcon(self.default_icon_path) self.default_icon = QIcon(self.default_icon_path)
self.last_icon_directory = os.path.expanduser('~') self.last_icon_directory = os.path.expanduser('~')
self.set_user_icon(IconManager().get('myicon')) self.set_user_icon(IconManager().get('myicon'))
...@@ -212,6 +216,8 @@ class MainWindow(base_class, ui_class): ...@@ -212,6 +216,8 @@ class MainWindow(base_class, ui_class):
self.google_contacts_dialog.close() self.google_contacts_dialog.close()
self.preferences_window.close() self.preferences_window.close()
self.server_tools_window.close() self.server_tools_window.close()
for dialog in self.pending_watcher_dialogs[:]:
dialog.close()
def set_user_icon(self, icon): def set_user_icon(self, icon):
self.account_state.setIcon(icon or self.default_icon) self.account_state.setIcon(icon or self.default_icon)
...@@ -590,6 +596,9 @@ class MainWindow(base_class, ui_class): ...@@ -590,6 +596,9 @@ class MainWindow(base_class, ui_class):
action = self.received_calls_menu.addAction(unicode(entry)) action = self.received_calls_menu.addAction(unicode(entry))
action.entry = entry action.entry = entry
def _SH_PendingWatcherDialogFinished(self, dialog, code):
self.pending_watcher_dialogs.remove(dialog)
@run_in_gui_thread @run_in_gui_thread
def handle_notification(self, notification): def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null) handler = getattr(self, '_NH_%s' % notification.name, Null)
...@@ -646,6 +655,7 @@ class MainWindow(base_class, ui_class): ...@@ -646,6 +655,7 @@ class MainWindow(base_class, ui_class):
def _NH_CFGSettingsObjectDidChange(self, notification): def _NH_CFGSettingsObjectDidChange(self, notification):
settings = SIPSimpleSettings() settings = SIPSimpleSettings()
blink_settings = BlinkSettings()
if notification.sender is settings: if notification.sender is settings:
if 'audio.silent' in notification.data.modified: if 'audio.silent' in notification.data.modified:
self.silent_action.setChecked(settings.audio.silent) self.silent_action.setChecked(settings.audio.silent)
...@@ -673,6 +683,15 @@ class MainWindow(base_class, ui_class): ...@@ -673,6 +683,15 @@ class MainWindow(base_class, ui_class):
self.google_contacts_action.setText(u'Disable Google Contacts') self.google_contacts_action.setText(u'Disable Google Contacts')
if authorization_token is InvalidToken: if authorization_token is InvalidToken:
self.google_contacts_dialog.open_for_incorrect_password() self.google_contacts_dialog.open_for_incorrect_password()
elif notification.sender is blink_settings:
if 'presence.current_state' in notification.data.modified:
state = getattr(AccountState, blink_settings.presence.current_state.state, AccountState.Available)
self.account_state.setState(state, blink_settings.presence.current_state.note)
if 'presence.icon' in notification.data.modified:
self.set_user_icon(IconManager().get('myicon'))
if 'presence.offline_note' in notification.data.modified:
# TODO: set offline note -Saul
pass
elif isinstance(notification.sender, (Account, BonjourAccount)): elif isinstance(notification.sender, (Account, BonjourAccount)):
account_manager = AccountManager() account_manager = AccountManager()
account = notification.sender account = notification.sender
...@@ -730,6 +749,13 @@ class MainWindow(base_class, ui_class): ...@@ -730,6 +749,13 @@ class MainWindow(base_class, ui_class):
new_messages = 0 new_messages = 0
action.setIcon(self.mwi_icons[new_messages]) action.setIcon(self.mwi_icons[new_messages])
def _NH_SIPAccountGotPendingWatcher(self, notification):
dialog = PendingWatcherDialog(notification.sender, notification.data.uri, notification.data.display_name)
dialog.finished.connect(partial(self._SH_PendingWatcherDialogFinished, dialog))
self.pending_watcher_dialogs.append(dialog)
dialog.show()
del ui_class, base_class del ui_class, base_class
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment