From 20316bccb697f144d823a735f0fe96aabb8cdbb7 Mon Sep 17 00:00:00 2001 From: Walter Bender Date: Mon, 23 Aug 2010 12:03:55 +0000 Subject: Merge branch 'master' of git.sugarlabs.org:sugar/mainline --- diff --git a/bin/.gitignore b/bin/.gitignore deleted file mode 100644 index 9e78b64..0000000 --- a/bin/.gitignore +++ /dev/null @@ -1 +0,0 @@ -sugar-shell-service diff --git a/bin/sugar-session b/bin/sugar-session index cc8358c..b1f0086 100755 --- a/bin/sugar-session +++ b/bin/sugar-session @@ -79,7 +79,6 @@ def start_ui_service(): from jarabe.view.service import UIService ui_service = UIService() - ui_service.start() def start_session_manager(): from jarabe.model.session import get_session_manager diff --git a/bin/sugar.in b/bin/sugar.in index 898bd59..2df0ab8 100644 --- a/bin/sugar.in +++ b/bin/sugar.in @@ -36,6 +36,11 @@ while [ $# -ne 0 ] ; do shift done +# Set default profile dir +if test -z "$SUGAR_PROFILE"; then + export SUGAR_PROFILE=default +fi + if test -z "$SUGAR_SCALING"; then export SUGAR_SCALING=72 fi @@ -54,6 +59,9 @@ fi export LANG="${LANG:-en_US.utf8}" export LANGUAGE="${LANGUAGE:-${LANG}}" +# Set Sugar's telepathy accounts directory +export MC_ACCOUNT_DIR=$HOME/.sugar/$SUGAR_PROFILE/accounts + # Source language settings and debug definitions if [ -f ~/.i18n ]; then . ~/.i18n diff --git a/configure.ac b/configure.ac index 15c8745..238aacd 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,11 @@ -AC_INIT([Sugar],[0.89.2],[],[sugar]) +AC_INIT([Sugar],[0.89.3],[],[sugar]) AC_PREREQ([2.59]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([configure.ac]) -SUCROSE_VERSION="0.89.2" +SUCROSE_VERSION="0.89.3" AC_SUBST(SUCROSE_VERSION) AM_INIT_AUTOMAKE([1.9 foreign dist-bzip2 no-dist-gzip]) diff --git a/extensions/deviceicon/network.py b/extensions/deviceicon/network.py index 562d63b..6171f39 100644 --- a/extensions/deviceicon/network.py +++ b/extensions/deviceicon/network.py @@ -540,7 +540,7 @@ class OlpcMeshDeviceView(ToolButton): self._icon.show() self.set_palette_invoker(FrameWidgetInvoker(self)) - self._palette = WirelessPalette(_("Mesh Network"), can_create=False) + self._palette = WirelessPalette(_("Mesh Network")) self._palette.connect('deactivate-connection', self.__deactivate_connection) self.set_palette(self._palette) diff --git a/po/POTFILES.in b/po/POTFILES.in index e9a7f1c..45a0549 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -28,6 +28,7 @@ extensions/cpsection/updater/view.py extensions/deviceicon/battery.py extensions/deviceicon/network.py extensions/deviceicon/speaker.py +extensions/deviceicon/touchpad.py extensions/deviceicon/volume.py extensions/globalkey/screenshot.py data/sugar.schemas.in diff --git a/src/jarabe/desktop/Makefile.am b/src/jarabe/desktop/Makefile.am index 5b47455..25fb0b4 100644 --- a/src/jarabe/desktop/Makefile.am +++ b/src/jarabe/desktop/Makefile.am @@ -11,7 +11,7 @@ sugar_PYTHON = \ homewindow.py \ keydialog.py \ meshbox.py \ - myicon.py \ + networkviews.py \ schoolserver.py \ snowflakelayout.py \ spreadlayout.py \ diff --git a/src/jarabe/desktop/favoritesview.py b/src/jarabe/desktop/favoritesview.py index fad25dd..bb85024 100644 --- a/src/jarabe/desktop/favoritesview.py +++ b/src/jarabe/desktop/favoritesview.py @@ -37,16 +37,16 @@ from sugar.datastore import datastore from jarabe.view.palettes import JournalPalette from jarabe.view.palettes import CurrentActivityPalette, ActivityPalette +from jarabe.view.buddyicon import BuddyIcon from jarabe.view.buddymenu import BuddyMenu from jarabe.view import launcher -from jarabe.model.buddy import BuddyModel +from jarabe.model.buddy import BuddyModel, get_owner_instance from jarabe.model import shell from jarabe.model import bundleregistry from jarabe.journal import misc from jarabe.desktop import schoolserver from jarabe.desktop.schoolserver import RegisterError -from jarabe.desktop.myicon import MyIcon from jarabe.desktop import favoriteslayout _logger = logging.getLogger('FavoritesView') @@ -82,7 +82,7 @@ class FavoritesView(hippo.Canvas): self._box.props.background_color = style.COLOR_WHITE.get_int() self.set_root(self._box) - self._my_icon = _MyIcon(style.XLARGE_ICON_SIZE) + self._my_icon = OwnerIcon(style.XLARGE_ICON_SIZE) self._my_icon.connect('register-activate', self.__register_activate_cb) self._box.append(self._my_icon) @@ -593,17 +593,16 @@ class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem): self._home_activity = home_activity self._update() -class _MyIcon(MyIcon): - __gtype_name__ = 'SugarFavoritesMyIcon' +class OwnerIcon(BuddyIcon): + __gtype_name__ = 'SugarFavoritesOwnerIcon' __gsignals__ = { 'register-activate' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])) } - def __init__(self, scale): - MyIcon.__init__(self, scale) + def __init__(self, size): + BuddyIcon.__init__(self, buddy=get_owner_instance(), size=size) - self._power_manager = None self._palette_enabled = False self._register_menu = None @@ -613,8 +612,7 @@ class _MyIcon(MyIcon): return presence_service = presenceservice.get_instance() - owner = BuddyModel(buddy=presence_service.get_owner()) - palette = BuddyMenu(owner) + palette = BuddyMenu(self.buddy) client = gconf.client_get_default() backup_url = client.get_string('/desktop/sugar/backup_url') diff --git a/src/jarabe/desktop/friendview.py b/src/jarabe/desktop/friendview.py index 4c5f1c8..c3faef8 100644 --- a/src/jarabe/desktop/friendview.py +++ b/src/jarabe/desktop/friendview.py @@ -1,4 +1,5 @@ # Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2010 Collabora Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -37,14 +38,12 @@ class FriendView(hippo.CanvasBox): self._activity_icon = CanvasIcon(size=style.LARGE_ICON_SIZE) self._activity_icon_visible = False - if self._buddy.is_present(): - self._buddy_appeared_cb(buddy) + self._update_activity() - self._buddy.connect('current-activity-changed', - self._buddy_activity_changed_cb) - self._buddy.connect('appeared', self._buddy_appeared_cb) - self._buddy.connect('disappeared', self._buddy_disappeared_cb) - self._buddy.connect('color-changed', self._buddy_color_changed_cb) + self._buddy.connect('notify::current-activity', + self.__buddy_notify_current_activity_cb) + self._buddy.connect('notify::present', self.__buddy_notify_present_cb) + self._buddy.connect('notify::color', self.__buddy_notify_color_cb) def _get_new_icon_name(self, ps_activity): registry = bundleregistry.get_registry() @@ -58,30 +57,31 @@ class FriendView(hippo.CanvasBox): self.remove(self._activity_icon) self._activity_icon_visible = False - def _buddy_activity_changed_cb(self, buddy, ps_activity=None): - if not ps_activity: + def __buddy_notify_current_activity_cb(self, buddy, pspec): + self._update_activity() + + def _update_activity(self): + if not self._buddy.props.present or \ + not self._buddy.props.current_activity: self._remove_activity_icon() return # FIXME: use some sort of "unknown activity" icon rather # than hiding the icon? - name = self._get_new_icon_name(ps_activity) + name = self._get_new_icon_name(self._buddy.current_activity) if name: self._activity_icon.props.file_name = name - self._activity_icon.props.xo_color = buddy.get_color() + self._activity_icon.props.xo_color = self._buddy.props.color if not self._activity_icon_visible: self.append(self._activity_icon, hippo.PACK_EXPAND) self._activity_icon_visible = True else: self._remove_activity_icon() - def _buddy_appeared_cb(self, buddy): - home_activity = self._buddy.get_current_activity() - self._buddy_activity_changed_cb(buddy, home_activity) - - def _buddy_disappeared_cb(self, buddy): - self._buddy_activity_changed_cb(buddy, None) + def __buddy_notify_present_cb(self, buddy, pspec): + self._update_activity() - def _buddy_color_changed_cb(self, buddy, color): + def __buddy_notify_color_cb(self, buddy, pspec): # TODO: shouldn't this change self._buddy_icon instead? - self._activity_icon.props.xo_color = buddy.get_color() + self._activity_icon.props.xo_color = buddy.props.color + diff --git a/src/jarabe/desktop/groupbox.py b/src/jarabe/desktop/groupbox.py index 76c2981..89043fe 100644 --- a/src/jarabe/desktop/groupbox.py +++ b/src/jarabe/desktop/groupbox.py @@ -23,10 +23,9 @@ import gconf from sugar.graphics import style from sugar.graphics.icon import CanvasIcon from sugar.graphics.xocolor import XoColor -from sugar.presence import presenceservice from jarabe.view.buddymenu import BuddyMenu -from jarabe.model.buddy import BuddyModel +from jarabe.model.buddy import get_owner_instance from jarabe.model import friends from jarabe.desktop.friendview import FriendView from jarabe.desktop.spreadlayout import SpreadLayout @@ -54,9 +53,7 @@ class GroupBox(hippo.Canvas): xo_color=color) self._owner_icon.props.size = style.LARGE_ICON_SIZE - presence_service = presenceservice.get_instance() - owner = BuddyModel(buddy=presence_service.get_owner()) - self._owner_icon.set_palette(BuddyMenu(owner)) + self._owner_icon.set_palette(BuddyMenu(get_owner_instance())) self._layout.add(self._owner_icon) friends_model = friends.get_model() diff --git a/src/jarabe/desktop/homewindow.py b/src/jarabe/desktop/homewindow.py index d830ed0..fec4289 100644 --- a/src/jarabe/desktop/homewindow.py +++ b/src/jarabe/desktop/homewindow.py @@ -45,8 +45,10 @@ class HomeWindow(gtk.Window): self._active = False self._fully_obscured = True - self.set_default_size(gtk.gdk.screen_width(), - gtk.gdk.screen_height()) + screen = self.get_screen() + screen.connect('size-changed', self.__screen_size_change_cb) + self.set_default_size(screen.get_width(), + screen.get_height()) self.realize() self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DESKTOP) @@ -80,6 +82,9 @@ class HomeWindow(gtk.Window): elif level == ShellModel.ZOOM_MESH: self._mesh_box.suspend() + def __screen_size_change_cb(self, screen): + self.resize(screen.get_width(), screen.get_height()) + def _activate_view(self, level): if level == ShellModel.ZOOM_HOME: self._home_box.resume() diff --git a/src/jarabe/desktop/meshbox.py b/src/jarabe/desktop/meshbox.py index ae63ad2..cf72053 100644 --- a/src/jarabe/desktop/meshbox.py +++ b/src/jarabe/desktop/meshbox.py @@ -1,6 +1,7 @@ # Copyright (C) 2006-2007 Red Hat, Inc. # Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer # Copyright (C) 2009-2010 One Laptop per Child +# Copyright (C) 2010 Collabora Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,7 +19,6 @@ from gettext import gettext as _ import logging -import hashlib import dbus import hippo @@ -28,30 +28,20 @@ import gtk import gconf from sugar.graphics.icon import CanvasIcon, Icon -from sugar.graphics.xocolor import XoColor -from sugar.graphics import xocolor from sugar.graphics import style -from sugar.graphics.icon import get_icon_state from sugar.graphics import palette from sugar.graphics import iconentry from sugar.graphics.menuitem import MenuItem -from sugar.activity.activityhandle import ActivityHandle -from sugar.activity import activityfactory -from sugar.util import unique_id -from sugar import profile from jarabe.model import neighborhood +from jarabe.model.buddy import get_owner_instance from jarabe.view.buddyicon import BuddyIcon -from jarabe.view.pulsingicon import CanvasPulsingIcon -from jarabe.view import launcher from jarabe.desktop.snowflakelayout import SnowflakeLayout from jarabe.desktop.spreadlayout import SpreadLayout -from jarabe.desktop import keydialog -from jarabe.model import bundleregistry +from jarabe.desktop.networkviews import WirelessNetworkView +from jarabe.desktop.networkviews import OlpcMeshView +from jarabe.desktop.networkviews import SugarAdhocView from jarabe.model import network -from jarabe.model.network import Settings -from jarabe.model.network import IP4Config -from jarabe.model.network import WirelessSecurity from jarabe.model.network import AccessPoint from jarabe.model.olpcmesh import OlpcMeshManager from jarabe.model.adhoc import get_adhoc_manager_instance @@ -69,673 +59,14 @@ _NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' _AP_ICON_NAME = 'network-wireless' _OLPC_MESH_ICON_NAME = 'network-mesh' - -class WirelessNetworkView(CanvasPulsingIcon): - def __init__(self, initial_ap): - CanvasPulsingIcon.__init__(self, size=style.STANDARD_ICON_SIZE, - cache=True) - self._bus = dbus.SystemBus() - self._access_points = {initial_ap.model.object_path: initial_ap} - self._active_ap = None - self._device = initial_ap.device - self._palette_icon = None - self._disconnect_item = None - self._connect_item = None - self._greyed_out = False - self._name = initial_ap.name - self._mode = initial_ap.mode - self._strength = initial_ap.strength - self._flags = initial_ap.flags - self._wpa_flags = initial_ap.wpa_flags - self._rsn_flags = initial_ap.rsn_flags - self._device_caps = 0 - self._device_state = None - self._connection = None - self._color = None - - if self._mode == network.NM_802_11_MODE_ADHOC and \ - network.is_sugar_adhoc_network(self._name): - self._color = profile.get_color() - else: - sha_hash = hashlib.sha1() - data = self._name + hex(self._flags) - sha_hash.update(data) - digest = hash(sha_hash.digest()) - index = digest % len(xocolor.colors) - - self._color = xocolor.XoColor('%s,%s' % - (xocolor.colors[index][0], - xocolor.colors[index][1])) - - self.connect('button-release-event', self.__button_release_event_cb) - - pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), - style.COLOR_TRANSPARENT.get_svg())) - self.props.pulse_color = pulse_color - - self._palette = self._create_palette() - self.set_palette(self._palette) - self._palette_icon.props.xo_color = self._color - - if self._mode != network.NM_802_11_MODE_ADHOC: - if network.find_connection_by_ssid(self._name) is not None: - self.props.badge_name = "emblem-favorite" - self._palette_icon.props.badge_name = "emblem-favorite" - elif self._flags == network.NM_802_11_AP_FLAGS_PRIVACY: - self.props.badge_name = "emblem-locked" - self._palette_icon.props.badge_name = "emblem-locked" - else: - self.props.badge_name = None - self._palette_icon.props.badge_name = None - else: - self.props.badge_name = None - self._palette_icon.props.badge_name = None - - interface_props = dbus.Interface(self._device, - 'org.freedesktop.DBus.Properties') - interface_props.Get(_NM_DEVICE_IFACE, 'State', - reply_handler=self.__get_device_state_reply_cb, - error_handler=self.__get_device_state_error_cb) - interface_props.Get(_NM_WIRELESS_IFACE, 'WirelessCapabilities', - reply_handler=self.__get_device_caps_reply_cb, - error_handler=self.__get_device_caps_error_cb) - interface_props.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint', - reply_handler=self.__get_active_ap_reply_cb, - error_handler=self.__get_active_ap_error_cb) - - self._bus.add_signal_receiver(self.__device_state_changed_cb, - signal_name='StateChanged', - path=self._device.object_path, - dbus_interface=_NM_DEVICE_IFACE) - self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, - signal_name='PropertiesChanged', - path=self._device.object_path, - dbus_interface=_NM_WIRELESS_IFACE) - - def _create_palette(self): - icon_name = get_icon_state(_AP_ICON_NAME, self._strength) - self._palette_icon = Icon(icon_name=icon_name, - icon_size=style.STANDARD_ICON_SIZE, - badge_name=self.props.badge_name) - - p = palette.Palette(primary_text=glib.markup_escape_text(self._name), - icon=self._palette_icon) - - self._connect_item = MenuItem(_('Connect'), 'dialog-ok') - self._connect_item.connect('activate', self.__connect_activate_cb) - p.menu.append(self._connect_item) - - self._disconnect_item = MenuItem(_('Disconnect'), 'media-eject') - self._disconnect_item.connect('activate', - self._disconnect_activate_cb) - p.menu.append(self._disconnect_item) - - return p - - def __device_state_changed_cb(self, new_state, old_state, reason): - self._device_state = new_state - self._update_state() - - def __update_active_ap(self, ap_path): - if ap_path in self._access_points: - # save reference to active AP, so that we always display the - # strength of that one - self._active_ap = self._access_points[ap_path] - self.update_strength() - self._update_state() - elif self._active_ap is not None: - # revert to showing state of strongest AP again - self._active_ap = None - self.update_strength() - self._update_state() - - def __wireless_properties_changed_cb(self, properties): - if 'ActiveAccessPoint' in properties: - self.__update_active_ap(properties['ActiveAccessPoint']) - - def __get_active_ap_reply_cb(self, ap_path): - self.__update_active_ap(ap_path) - - def __get_active_ap_error_cb(self, err): - logging.error('Error getting the active access point: %s', err) - - def __get_device_caps_reply_cb(self, caps): - self._device_caps = caps - - def __get_device_caps_error_cb(self, err): - logging.error('Error getting the wireless device properties: %s', err) - - def __get_device_state_reply_cb(self, state): - self._device_state = state - self._update() - - def __get_device_state_error_cb(self, err): - logging.error('Error getting the device state: %s', err) - - def _update(self): - self._update_state() - self._update_color() - - def _update_state(self): - if self._active_ap is not None: - state = self._device_state - else: - state = network.DEVICE_STATE_UNKNOWN - - if self._mode == network.NM_802_11_MODE_ADHOC and \ - network.is_sugar_adhoc_network(self._name): - channel = max([1] + [ap.channel for ap in - self._access_points.values()]) - if state == network.DEVICE_STATE_ACTIVATED: - icon_name = 'network-adhoc-%s-connected' % channel - else: - icon_name = 'network-adhoc-%s' % channel - self.props.icon_name = icon_name - icon = self._palette.props.icon - icon.props.icon_name = icon_name - else: - if state == network.DEVICE_STATE_ACTIVATED: - connection = network.find_connection_by_ssid(self._name) - if connection is not None: - if self._mode == network.NM_802_11_MODE_INFRA: - connection.set_connected() - icon_name = '%s-connected' % _AP_ICON_NAME - else: - icon_name = _AP_ICON_NAME - - icon_name = get_icon_state(icon_name, self._strength) - if icon_name: - self.props.icon_name = icon_name - icon = self._palette.props.icon - icon.props.icon_name = icon_name - - if state == network.DEVICE_STATE_PREPARE or \ - state == network.DEVICE_STATE_CONFIG or \ - state == network.DEVICE_STATE_NEED_AUTH or \ - state == network.DEVICE_STATE_IP_CONFIG: - if self._disconnect_item: - self._disconnect_item.show() - self._connect_item.hide() - self._palette.props.secondary_text = _('Connecting...') - self.props.pulsing = True - elif state == network.DEVICE_STATE_ACTIVATED: - if self._disconnect_item: - self._disconnect_item.show() - self._connect_item.hide() - self._palette.props.secondary_text = _('Connected') - self.props.pulsing = False - else: - if self._disconnect_item: - self._disconnect_item.hide() - self._connect_item.show() - self._palette.props.secondary_text = None - self.props.pulsing = False - - def _update_color(self): - if self._greyed_out: - self.props.pulsing = False - self.props.base_color = XoColor('#D5D5D5,#D5D5D5') - else: - self.props.base_color = self._color - - def _disconnect_activate_cb(self, item): - pass - - def _add_ciphers_from_flags(self, flags, pairwise): - ciphers = [] - if pairwise: - if flags & network.NM_802_11_AP_SEC_PAIR_TKIP: - ciphers.append("tkip") - if flags & network.NM_802_11_AP_SEC_PAIR_CCMP: - ciphers.append("ccmp") - else: - if flags & network.NM_802_11_AP_SEC_GROUP_WEP40: - ciphers.append("wep40") - if flags & network.NM_802_11_AP_SEC_GROUP_WEP104: - ciphers.append("wep104") - if flags & network.NM_802_11_AP_SEC_GROUP_TKIP: - ciphers.append("tkip") - if flags & network.NM_802_11_AP_SEC_GROUP_CCMP: - ciphers.append("ccmp") - return ciphers - - def _get_security(self): - if not (self._flags & network.NM_802_11_AP_FLAGS_PRIVACY) and \ - (self._wpa_flags == network.NM_802_11_AP_SEC_NONE) and \ - (self._rsn_flags == network.NM_802_11_AP_SEC_NONE): - # No security - return None - - if (self._flags & network.NM_802_11_AP_FLAGS_PRIVACY) and \ - (self._wpa_flags == network.NM_802_11_AP_SEC_NONE) and \ - (self._rsn_flags == network.NM_802_11_AP_SEC_NONE): - # Static WEP, Dynamic WEP, or LEAP - wireless_security = WirelessSecurity() - wireless_security.key_mgmt = 'none' - return wireless_security - - if (self._mode != network.NM_802_11_MODE_INFRA): - # Stuff after this point requires infrastructure - logging.error('The infrastructure mode is not supoorted' - ' by your wireless device.') - return None - - if (self._rsn_flags & network.NM_802_11_AP_SEC_KEY_MGMT_PSK) and \ - (self._device_caps & network.NM_802_11_DEVICE_CAP_RSN): - # WPA2 PSK first - pairwise = self._add_ciphers_from_flags(self._rsn_flags, True) - group = self._add_ciphers_from_flags(self._rsn_flags, False) - wireless_security = WirelessSecurity() - wireless_security.key_mgmt = 'wpa-psk' - wireless_security.proto = 'rsn' - wireless_security.pairwise = pairwise - wireless_security.group = group - return wireless_security - - if (self._wpa_flags & network.NM_802_11_AP_SEC_KEY_MGMT_PSK) and \ - (self._device_caps & network.NM_802_11_DEVICE_CAP_WPA): - # WPA PSK - pairwise = self._add_ciphers_from_flags(self._wpa_flags, True) - group = self._add_ciphers_from_flags(self._wpa_flags, False) - wireless_security = WirelessSecurity() - wireless_security.key_mgmt = 'wpa-psk' - wireless_security.proto = 'wpa' - wireless_security.pairwise = pairwise - wireless_security.group = group - return wireless_security - - def __connect_activate_cb(self, icon): - self._connect() - - def __button_release_event_cb(self, icon, event): - self._connect() - - def _connect(self): - connection = network.find_connection_by_ssid(self._name) - if connection is None: - settings = Settings() - settings.connection.id = 'Auto ' + self._name - uuid = settings.connection.uuid = unique_id() - settings.connection.type = '802-11-wireless' - settings.wireless.ssid = self._name - - if self._mode == network.NM_802_11_MODE_INFRA: - settings.wireless.mode = 'infrastructure' - elif self._mode == network.NM_802_11_MODE_ADHOC: - settings.wireless.mode = 'adhoc' - settings.wireless.band = 'bg' - settings.ip4_config = IP4Config() - settings.ip4_config.method = 'link-local' - - wireless_security = self._get_security() - settings.wireless_security = wireless_security - - if wireless_security is not None: - settings.wireless.security = '802-11-wireless-security' - - connection = network.add_connection(uuid, settings) - - obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) - netmgr = dbus.Interface(obj, _NM_IFACE) - - netmgr.ActivateConnection(network.SETTINGS_SERVICE, connection.path, - self._device.object_path, - "/", - reply_handler=self.__activate_reply_cb, - error_handler=self.__activate_error_cb) - - def __activate_reply_cb(self, connection): - logging.debug('Connection activated: %s', connection) - - def __activate_error_cb(self, err): - logging.error('Failed to activate connection: %s', err) - - def set_filter(self, query): - self._greyed_out = self._name.lower().find(query) == -1 - self._update_state() - self._update_color() - - def create_keydialog(self, settings, response): - keydialog.create(self._name, self._flags, self._wpa_flags, - self._rsn_flags, self._device_caps, settings, response) - - def update_strength(self): - if self._active_ap is not None: - # display strength of AP that we are connected to - new_strength = self._active_ap.strength - else: - # display the strength of the strongest AP that makes up this - # network, also considering that there may be no APs - new_strength = max([0] + [ap.strength for ap in - self._access_points.values()]) - - if new_strength != self._strength: - self._strength = new_strength - self._update_state() - - def add_ap(self, ap): - self._access_points[ap.model.object_path] = ap - self.update_strength() - - def remove_ap(self, ap): - path = ap.model.object_path - if path not in self._access_points: - return - del self._access_points[path] - if self._active_ap == ap: - self._active_ap = None - self.update_strength() - - def num_aps(self): - return len(self._access_points) - - def find_ap(self, ap_path): - if ap_path not in self._access_points: - return None - return self._access_points[ap_path] - - def is_olpc_mesh(self): - return self._mode == network.NM_802_11_MODE_ADHOC \ - and self.name == "olpc-mesh" - - def remove_all_aps(self): - for ap in self._access_points.values(): - ap.disconnect() - self._access_points = {} - self._active_ap = None - self.update_strength() - - def disconnect(self): - self._bus.remove_signal_receiver(self.__device_state_changed_cb, - signal_name='StateChanged', - path=self._device.object_path, - dbus_interface=_NM_DEVICE_IFACE) - self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb, - signal_name='PropertiesChanged', - path=self._device.object_path, - dbus_interface=_NM_WIRELESS_IFACE) - -class SugarAdhocView(CanvasPulsingIcon): - """To mimic the mesh behavior on devices where mesh hardware is - not available we support the creation of an Ad-hoc network on - three channels 1, 6, 11. This is the class for an icon - representing a channel in the neighborhood view. - - """ - - _ICON_NAME = 'network-adhoc-' - _NAME = 'Ad-hoc Network ' - - def __init__(self, channel): - CanvasPulsingIcon.__init__(self, - icon_name=self._ICON_NAME + str(channel), - size=style.STANDARD_ICON_SIZE, cache=True) - self._bus = dbus.SystemBus() - self._channel = channel - self._disconnect_item = None - self._connect_item = None - self._palette_icon = None - self._greyed_out = False - - get_adhoc_manager_instance().connect('members-changed', - self.__members_changed_cb) - get_adhoc_manager_instance().connect('state-changed', - self.__state_changed_cb) - - self.connect('button-release-event', self.__button_release_event_cb) - - pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), - style.COLOR_TRANSPARENT.get_svg())) - self.props.pulse_color = pulse_color - self._state_color = XoColor('%s,%s' % \ - (profile.get_color().get_stroke_color(), - style.COLOR_TRANSPARENT.get_svg())) - self.props.base_color = self._state_color - self._palette = self._create_palette() - self.set_palette(self._palette) - self._palette_icon.props.xo_color = self._state_color - - def _create_palette(self): - self._palette_icon = Icon( \ - icon_name=self._ICON_NAME + str(self._channel), - icon_size=style.STANDARD_ICON_SIZE) - - palette_ = palette.Palette(_("Ad-hoc Network %d") % self._channel, - icon=self._palette_icon) - - self._connect_item = MenuItem(_('Connect'), 'dialog-ok') - self._connect_item.connect('activate', self.__connect_activate_cb) - palette_.menu.append(self._connect_item) - - self._disconnect_item = MenuItem(_('Disconnect'), 'media-eject') - self._disconnect_item.connect('activate', - self.__disconnect_activate_cb) - palette_.menu.append(self._disconnect_item) - - return palette_ - - def __button_release_event_cb(self, icon, event): - get_adhoc_manager_instance().activate_channel(self._channel) - - def __connect_activate_cb(self, icon): - get_adhoc_manager_instance().activate_channel(self._channel) - - def __disconnect_activate_cb(self, icon): - get_adhoc_manager_instance().deactivate_active_channel() - - def __state_changed_cb(self, adhoc_manager, channel, device_state): - if self._channel == channel: - state = device_state - else: - state = network.DEVICE_STATE_UNKNOWN - - if state == network.DEVICE_STATE_ACTIVATED: - icon_name = '%s-connected' % (self._ICON_NAME + str(self._channel)) - else: - icon_name = self._ICON_NAME + str(self._channel) - - self.props.base_color = self._state_color - self._palette_icon.props.xo_color = self._state_color - - if icon_name is not None: - self.props.icon_name = icon_name - icon = self._palette.props.icon - icon.props.icon_name = icon_name - - if state in [network.DEVICE_STATE_PREPARE, - network.DEVICE_STATE_CONFIG, - network.DEVICE_STATE_NEED_AUTH, - network.DEVICE_STATE_IP_CONFIG]: - if self._disconnect_item: - self._disconnect_item.show() - self._connect_item.hide() - self._palette.props.secondary_text = _('Connecting...') - self.props.pulsing = True - elif state == network.DEVICE_STATE_ACTIVATED: - if self._disconnect_item: - self._disconnect_item.show() - self._connect_item.hide() - self._palette.props.secondary_text = _('Connected') - self.props.pulsing = False - else: - if self._disconnect_item: - self._disconnect_item.hide() - self._connect_item.show() - self._palette.props.secondary_text = None - self.props.pulsing = False - - def _update_color(self): - if self._greyed_out: - self.props.base_color = XoColor('#D5D5D5,#D5D5D5') - else: - self.props.base_color = self._state_color - - def __members_changed_cb(self, adhoc_manager, channel, has_members): - if channel == self._channel: - if has_members == True: - self._state_color = profile.get_color() - self.props.base_color = self._state_color - self._palette_icon.props.xo_color = self._state_color - else: - color = '%s,%s' % (profile.get_color().get_stroke_color(), - style.COLOR_TRANSPARENT.get_svg()) - self._state_color = XoColor(color) - self.props.base_color = self._state_color - self._palette_icon.props.xo_color = self._state_color - - def set_filter(self, query): - name = self._NAME + str(self._channel) - self._greyed_out = name.lower().find(query) == -1 - self._update_color() - - -class OlpcMeshView(CanvasPulsingIcon): - def __init__(self, mesh_mgr, channel): - CanvasPulsingIcon.__init__(self, icon_name=_OLPC_MESH_ICON_NAME, - size=style.STANDARD_ICON_SIZE, cache=True) - self._bus = dbus.SystemBus() - self._channel = channel - self._mesh_mgr = mesh_mgr - self._disconnect_item = None - self._connect_item = None - self._greyed_out = False - self._name = '' - self._device_state = None - self._connection = None - self._active = False - device = mesh_mgr.mesh_device - - self.connect('button-release-event', self.__button_release_event_cb) - - interface_props = dbus.Interface(device, - 'org.freedesktop.DBus.Properties') - interface_props.Get(_NM_DEVICE_IFACE, 'State', - reply_handler=self.__get_device_state_reply_cb, - error_handler=self.__get_device_state_error_cb) - interface_props.Get(_NM_OLPC_MESH_IFACE, 'ActiveChannel', - reply_handler=self.__get_active_channel_reply_cb, - error_handler=self.__get_active_channel_error_cb) - - self._bus.add_signal_receiver(self.__device_state_changed_cb, - signal_name='StateChanged', - path=device.object_path, - dbus_interface=_NM_DEVICE_IFACE) - self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, - signal_name='PropertiesChanged', - path=device.object_path, - dbus_interface=_NM_OLPC_MESH_IFACE) - - pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), - style.COLOR_TRANSPARENT.get_svg())) - self.props.pulse_color = pulse_color - self.props.base_color = profile.get_color() - self._palette = self._create_palette() - self.set_palette(self._palette) - - def _create_palette(self): - _palette = palette.Palette(_("Mesh Network %d") % self._channel) - - self._connect_item = MenuItem(_('Connect'), 'dialog-ok') - self._connect_item.connect('activate', self.__connect_activate_cb) - _palette.menu.append(self._connect_item) - - return _palette - - def __get_device_state_reply_cb(self, state): - self._device_state = state - self._update() - - def __get_device_state_error_cb(self, err): - logging.error('Error getting the device state: %s', err) - - def __device_state_changed_cb(self, new_state, old_state, reason): - self._device_state = new_state - self._update() - - def __get_active_channel_reply_cb(self, channel): - self._active = (channel == self._channel) - self._update() - - def __get_active_channel_error_cb(self, err): - logging.error('Error getting the active channel: %s', err) - - def __wireless_properties_changed_cb(self, properties): - if 'ActiveChannel' in properties: - channel = properties['ActiveChannel'] - self._active = (channel == self._channel) - self._update() - - def _update(self): - if self._active: - state = self._device_state - else: - state = network.DEVICE_STATE_UNKNOWN - - if state in [network.DEVICE_STATE_PREPARE, - network.DEVICE_STATE_CONFIG, - network.DEVICE_STATE_NEED_AUTH, - network.DEVICE_STATE_IP_CONFIG]: - if self._disconnect_item: - self._disconnect_item.show() - self._connect_item.hide() - self._palette.props.secondary_text = _('Connecting...') - self.props.pulsing = True - elif state == network.DEVICE_STATE_ACTIVATED: - if self._disconnect_item: - self._disconnect_item.show() - self._connect_item.hide() - self._palette.props.secondary_text = _('Connected') - self.props.pulsing = False - else: - if self._disconnect_item: - self._disconnect_item.hide() - self._connect_item.show() - self._palette.props.secondary_text = None - self.props.pulsing = False - - def _update_color(self): - if self._greyed_out: - self.props.base_color = XoColor('#D5D5D5,#D5D5D5') - else: - self.props.base_color = profile.get_color() - - def __connect_activate_cb(self, icon): - self._connect() - - def __button_release_event_cb(self, icon, event): - self._connect() - - def _connect(self): - self._mesh_mgr.user_activate_channel(self._channel) - - def __activate_reply_cb(self, connection): - logging.debug('Connection activated: %s', connection) - - def __activate_error_cb(self, err): - logging.error('Failed to activate connection: %s', err) - - def set_filter(self, query): - self._greyed_out = (query != '') - self._update_color() - - def disconnect(self): - device_object_path = self._mesh_mgr.mesh_device.object_path - - self._bus.remove_signal_receiver(self.__device_state_changed_cb, - signal_name='StateChanged', - path=device_object_path, - dbus_interface=_NM_DEVICE_IFACE) - self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb, - signal_name='PropertiesChanged', - path=device_object_path, - dbus_interface=_NM_OLPC_MESH_IFACE) - - class ActivityView(hippo.CanvasBox): def __init__(self, model): hippo.CanvasBox.__init__(self) self._model = model + self._model.connect('current-buddy-added', self.__buddy_added_cb) + self._model.connect('current-buddy-removed', self.__buddy_removed_cb) + self._icons = {} self._palette = None @@ -745,33 +76,30 @@ class ActivityView(hippo.CanvasBox): self._icon = self._create_icon() self._layout.add(self._icon, center=True) - self._update_palette() + self._palette = self._create_palette() + self._icon.set_palette(self._palette) - activity = self._model.activity - activity.connect('notify::name', self._name_changed_cb) - activity.connect('notify::color', self._color_changed_cb) - activity.connect('notify::private', self._private_changed_cb) - activity.connect('joined', self._joined_changed_cb) - #FIXME: 'joined' signal not working, see #5032 + for buddy in self._model.props.current_buddies: + self._add_buddy(buddy) def _create_icon(self): - icon = CanvasIcon(file_name=self._model.get_icon_name(), + icon = CanvasIcon(file_name=self._model.bundle.get_icon(), xo_color=self._model.get_color(), cache=True, size=style.STANDARD_ICON_SIZE) icon.connect('activated', self._clicked_cb) return icon def _create_palette(self): - p_text = glib.markup_escape_text(self._model.activity.props.name) - p_icon = Icon(file=self._model.get_icon_name(), + p_text = glib.markup_escape_text(self._model.bundle.get_name()) + p_icon = Icon(file=self._model.bundle.get_icon(), xo_color=self._model.get_color()) p_icon.props.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR p = palette.Palette(None, primary_text=p_text, icon=p_icon) - private = self._model.activity.props.private - joined = self._model.activity.props.joined + private = self._model.props.private + joined = get_owner_instance() in self._model.props.buddies if joined: item = MenuItem(_('Resume'), 'activity-start') @@ -786,32 +114,30 @@ class ActivityView(hippo.CanvasBox): return p - def _update_palette(self): - self._palette = self._create_palette() - self._icon.set_palette(self._palette) - def has_buddy_icon(self, key): return self._icons.has_key(key) - def add_buddy_icon(self, key, icon): - self._icons[key] = icon + def __buddy_added_cb(self, activity, buddy): + self._add_buddy(buddy) + + def _add_buddy(self, buddy): + icon = BuddyIcon(buddy, style.STANDARD_ICON_SIZE) + self._icons[buddy.props.key] = icon self._layout.add(icon) - def remove_buddy_icon(self, key): - icon = self._icons[key] - del self._icons[key] + def __buddy_removed_cb(self, activity, buddy): + icon = self._icons[buddy.props.key] + del self._icons[buddy.props.key] icon.destroy() def _clicked_cb(self, item): - bundle_id = self._model.get_bundle_id() - bundle = bundleregistry.get_registry().get_bundle(bundle_id) - - misc.launch(bundle, activity_id=self._model.get_id(), - color=self._model.get_color()) + bundle = self._model.get_bundle() + misc.launch(bundle, activity_id=self._model.activity_id, + color=self._model.get_color()) def set_filter(self, query): - text_to_check = self._model.activity.props.name.lower() + \ - self._model.activity.props.type.lower() + text_to_check = self._model.bundle.get_name().lower() + \ + self._model.bundle.get_bundle_id().lower() if text_to_check.find(query) == -1: self._icon.props.stroke_color = '#D5D5D5' self._icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg() @@ -822,21 +148,6 @@ class ActivityView(hippo.CanvasBox): if hasattr(icon, 'set_filter'): icon.set_filter(query) - def _name_changed_cb(self, activity, pspec): - self._update_palette() - - def _color_changed_cb(self, activity, pspec): - self._layout.remove(self._icon) - self._icon = self._create_icon() - self._layout.add(self._icon, center=True) - self._icon.set_palette(self._palette) - - def _private_changed_cb(self, activity, pspec): - self._update_palette() - - def _joined_changed_cb(self, widget, event): - logging.debug('ActivityView._joined_changed_cb') - _AUTOSEARCH_TIMEOUT = 1000 @@ -1122,11 +433,10 @@ class MeshBox(gtk.VBox): self._layout_box.set_layout(self._layout) for buddy_model in self._model.get_buddies(): - self._add_alone_buddy(buddy_model) + self._add_buddy(buddy_model) self._model.connect('buddy-added', self._buddy_added_cb) self._model.connect('buddy-removed', self._buddy_removed_cb) - self._model.connect('buddy-moved', self._buddy_moved_cb) for activity_model in self._model.get_activities(): self._add_activity(activity_model) @@ -1150,24 +460,23 @@ class MeshBox(gtk.VBox): gtk.VBox.do_size_allocate(self, allocation) def _buddy_added_cb(self, model, buddy_model): - self._add_alone_buddy(buddy_model) + self._add_buddy(buddy_model) def _buddy_removed_cb(self, model, buddy_model): self._remove_buddy(buddy_model) - def _buddy_moved_cb(self, model, buddy_model, activity_model): - # Owner doesn't move from the center - if buddy_model.is_owner(): - return - self._move_buddy(buddy_model, activity_model) - def _activity_added_cb(self, model, activity_model): self._add_activity(activity_model) def _activity_removed_cb(self, model, activity_model): self._remove_activity(activity_model) - def _add_alone_buddy(self, buddy_model): + def _add_buddy(self, buddy_model): + logging.debug('MeshBox._add_buddy %r', buddy_model.props.key) + buddy_model.connect('notify::current-activity', + self.__buddy_notify_current_activity_cb) + if buddy_model.props.current_activity is not None: + return icon = BuddyIcon(buddy_model) if buddy_model.is_owner(): self._owner_icon = icon @@ -1176,36 +485,23 @@ class MeshBox(gtk.VBox): if hasattr(icon, 'set_filter'): icon.set_filter(self._query) - self._buddies[buddy_model.get_buddy().object_path()] = icon + self._buddies[buddy_model.props.key] = icon - def _remove_alone_buddy(self, buddy_model): - icon = self._buddies[buddy_model.get_buddy().object_path()] + def _remove_buddy(self, buddy_model): + logging.debug('MeshBox._remove_buddy') + icon = self._buddies[buddy_model.props.key] self._layout.remove(icon) - del self._buddies[buddy_model.get_buddy().object_path()] + del self._buddies[buddy_model.props.key] icon.destroy() - def _remove_buddy(self, buddy_model): - object_path = buddy_model.get_buddy().object_path() - if self._buddies.has_key(object_path): - self._remove_alone_buddy(buddy_model) - else: - for activity in self._activities.values(): - if activity.has_buddy_icon(object_path): - activity.remove_buddy_icon(object_path) - - def _move_buddy(self, buddy_model, activity_model): - self._remove_buddy(buddy_model) - - if activity_model == None: - self._add_alone_buddy(buddy_model) - elif activity_model.get_id() in self._activities: - activity = self._activities[activity_model.get_id()] - - icon = BuddyIcon(buddy_model, style.STANDARD_ICON_SIZE) - activity.add_buddy_icon(buddy_model.get_buddy().object_path(), icon) - - if hasattr(icon, 'set_filter'): - icon.set_filter(self._query) + def __buddy_notify_current_activity_cb(self, buddy_model, pspec): + logging.debug('MeshBox.__buddy_notify_current_activity_cb %s', + buddy_model.props.current_activity) + if buddy_model.props.current_activity is None: + if not buddy_model.props.key in self._buddies: + self._add_buddy(buddy_model) + elif buddy_model.props.key in self._buddies: + self._remove_buddy(buddy_model) def _add_activity(self, activity_model): icon = ActivityView(activity_model) @@ -1214,12 +510,12 @@ class MeshBox(gtk.VBox): if hasattr(icon, 'set_filter'): icon.set_filter(self._query) - self._activities[activity_model.get_id()] = icon + self._activities[activity_model.activity_id] = icon def _remove_activity(self, activity_model): - icon = self._activities[activity_model.get_id()] + icon = self._activities[activity_model.activity_id] self._layout.remove(icon) - del self._activities[activity_model.get_id()] + del self._activities[activity_model.activity_id] icon.destroy() # add AP to its corresponding network icon on the desktop, @@ -1315,7 +611,6 @@ class MeshBox(gtk.VBox): def remove_adhoc_networks(self): for icon in self._adhoc_networks: - icon.disconnect() self._layout.remove(icon) self._adhoc_networks = [] diff --git a/src/jarabe/desktop/myicon.py b/src/jarabe/desktop/myicon.py deleted file mode 100644 index 4a4ad95..0000000 --- a/src/jarabe/desktop/myicon.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (C) 2006-2007 Red Hat, Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -import gconf - -from sugar.graphics.icon import CanvasIcon -from sugar.graphics.xocolor import XoColor - -class MyIcon(CanvasIcon): - def __init__(self, size): - client = gconf.client_get_default() - color = XoColor(client.get_string("/desktop/sugar/user/color")) - CanvasIcon.__init__(self, size=size, - icon_name='computer-xo', - xo_color=color) diff --git a/src/jarabe/desktop/networkviews.py b/src/jarabe/desktop/networkviews.py new file mode 100644 index 0000000..121c817 --- /dev/null +++ b/src/jarabe/desktop/networkviews.py @@ -0,0 +1,716 @@ +# Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer +# Copyright (C) 2009-2010 One Laptop per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +from gettext import gettext as _ +import logging +import hashlib + +import dbus +import glib + +from sugar.graphics.icon import Icon +from sugar.graphics.xocolor import XoColor +from sugar.graphics import xocolor +from sugar.graphics import style +from sugar.graphics.icon import get_icon_state +from sugar.graphics import palette +from sugar.graphics.menuitem import MenuItem +from sugar.util import unique_id +from sugar import profile + +from jarabe.view.pulsingicon import CanvasPulsingIcon +from jarabe.desktop import keydialog +from jarabe.model import network +from jarabe.model.network import Settings +from jarabe.model.network import IP4Config +from jarabe.model.network import WirelessSecurity +from jarabe.model.adhoc import get_adhoc_manager_instance + +_NM_SERVICE = 'org.freedesktop.NetworkManager' +_NM_IFACE = 'org.freedesktop.NetworkManager' +_NM_PATH = '/org/freedesktop/NetworkManager' +_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' +_NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless' +_NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh' +_NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' +_NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' + +_AP_ICON_NAME = 'network-wireless' +_OLPC_MESH_ICON_NAME = 'network-mesh' + + +class WirelessNetworkView(CanvasPulsingIcon): + def __init__(self, initial_ap): + CanvasPulsingIcon.__init__(self, size=style.STANDARD_ICON_SIZE, + cache=True) + self._bus = dbus.SystemBus() + self._access_points = {initial_ap.model.object_path: initial_ap} + self._active_ap = None + self._device = initial_ap.device + self._palette_icon = None + self._disconnect_item = None + self._connect_item = None + self._greyed_out = False + self._name = initial_ap.name + self._mode = initial_ap.mode + self._strength = initial_ap.strength + self._flags = initial_ap.flags + self._wpa_flags = initial_ap.wpa_flags + self._rsn_flags = initial_ap.rsn_flags + self._device_caps = 0 + self._device_state = None + self._connection = None + self._color = None + + if self._mode == network.NM_802_11_MODE_ADHOC and \ + network.is_sugar_adhoc_network(self._name): + self._color = profile.get_color() + else: + sha_hash = hashlib.sha1() + data = self._name + hex(self._flags) + sha_hash.update(data) + digest = hash(sha_hash.digest()) + index = digest % len(xocolor.colors) + + self._color = xocolor.XoColor('%s,%s' % + (xocolor.colors[index][0], + xocolor.colors[index][1])) + + self.connect('button-release-event', self.__button_release_event_cb) + + pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), + style.COLOR_TRANSPARENT.get_svg())) + self.props.pulse_color = pulse_color + + self._palette = self._create_palette() + self.set_palette(self._palette) + self._palette_icon.props.xo_color = self._color + + if self._mode != network.NM_802_11_MODE_ADHOC: + if network.find_connection_by_ssid(self._name) is not None: + self.props.badge_name = "emblem-favorite" + self._palette_icon.props.badge_name = "emblem-favorite" + elif self._flags == network.NM_802_11_AP_FLAGS_PRIVACY: + self.props.badge_name = "emblem-locked" + self._palette_icon.props.badge_name = "emblem-locked" + else: + self.props.badge_name = None + self._palette_icon.props.badge_name = None + else: + self.props.badge_name = None + self._palette_icon.props.badge_name = None + + interface_props = dbus.Interface(self._device, + 'org.freedesktop.DBus.Properties') + interface_props.Get(_NM_DEVICE_IFACE, 'State', + reply_handler=self.__get_device_state_reply_cb, + error_handler=self.__get_device_state_error_cb) + interface_props.Get(_NM_WIRELESS_IFACE, 'WirelessCapabilities', + reply_handler=self.__get_device_caps_reply_cb, + error_handler=self.__get_device_caps_error_cb) + interface_props.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint', + reply_handler=self.__get_active_ap_reply_cb, + error_handler=self.__get_active_ap_error_cb) + + self._bus.add_signal_receiver(self.__device_state_changed_cb, + signal_name='StateChanged', + path=self._device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=self._device.object_path, + dbus_interface=_NM_WIRELESS_IFACE) + + def _create_palette(self): + icon_name = get_icon_state(_AP_ICON_NAME, self._strength) + self._palette_icon = Icon(icon_name=icon_name, + icon_size=style.STANDARD_ICON_SIZE, + badge_name=self.props.badge_name) + + p = palette.Palette(primary_text=glib.markup_escape_text(self._name), + icon=self._palette_icon) + + self._connect_item = MenuItem(_('Connect'), 'dialog-ok') + self._connect_item.connect('activate', self.__connect_activate_cb) + p.menu.append(self._connect_item) + + self._disconnect_item = MenuItem(_('Disconnect'), 'media-eject') + self._disconnect_item.connect('activate', + self._disconnect_activate_cb) + p.menu.append(self._disconnect_item) + + return p + + def __device_state_changed_cb(self, new_state, old_state, reason): + self._device_state = new_state + self._update_state() + + def __update_active_ap(self, ap_path): + if ap_path in self._access_points: + # save reference to active AP, so that we always display the + # strength of that one + self._active_ap = self._access_points[ap_path] + self.update_strength() + self._update_state() + elif self._active_ap is not None: + # revert to showing state of strongest AP again + self._active_ap = None + self.update_strength() + self._update_state() + + def __wireless_properties_changed_cb(self, properties): + if 'ActiveAccessPoint' in properties: + self.__update_active_ap(properties['ActiveAccessPoint']) + + def __get_active_ap_reply_cb(self, ap_path): + self.__update_active_ap(ap_path) + + def __get_active_ap_error_cb(self, err): + logging.error('Error getting the active access point: %s', err) + + def __get_device_caps_reply_cb(self, caps): + self._device_caps = caps + + def __get_device_caps_error_cb(self, err): + logging.error('Error getting the wireless device properties: %s', err) + + def __get_device_state_reply_cb(self, state): + self._device_state = state + self._update() + + def __get_device_state_error_cb(self, err): + logging.error('Error getting the device state: %s', err) + + def _update(self): + self._update_state() + self._update_color() + + def _update_state(self): + if self._active_ap is not None: + state = self._device_state + else: + state = network.DEVICE_STATE_UNKNOWN + + if self._mode == network.NM_802_11_MODE_ADHOC and \ + network.is_sugar_adhoc_network(self._name): + channel = max([1] + [ap.channel for ap in + self._access_points.values()]) + if state == network.DEVICE_STATE_ACTIVATED: + icon_name = 'network-adhoc-%s-connected' % channel + else: + icon_name = 'network-adhoc-%s' % channel + self.props.icon_name = icon_name + icon = self._palette.props.icon + icon.props.icon_name = icon_name + else: + if state == network.DEVICE_STATE_ACTIVATED: + connection = network.find_connection_by_ssid(self._name) + if connection is not None: + if self._mode == network.NM_802_11_MODE_INFRA: + connection.set_connected() + icon_name = '%s-connected' % _AP_ICON_NAME + else: + icon_name = _AP_ICON_NAME + + icon_name = get_icon_state(icon_name, self._strength) + if icon_name: + self.props.icon_name = icon_name + icon = self._palette.props.icon + icon.props.icon_name = icon_name + + if state == network.DEVICE_STATE_PREPARE or \ + state == network.DEVICE_STATE_CONFIG or \ + state == network.DEVICE_STATE_NEED_AUTH or \ + state == network.DEVICE_STATE_IP_CONFIG: + if self._disconnect_item: + self._disconnect_item.show() + self._connect_item.hide() + self._palette.props.secondary_text = _('Connecting...') + self.props.pulsing = True + elif state == network.DEVICE_STATE_ACTIVATED: + if self._disconnect_item: + self._disconnect_item.show() + self._connect_item.hide() + self._palette.props.secondary_text = _('Connected') + self.props.pulsing = False + else: + if self._disconnect_item: + self._disconnect_item.hide() + self._connect_item.show() + self._palette.props.secondary_text = None + self.props.pulsing = False + + def _update_color(self): + if self._greyed_out: + self.props.pulsing = False + self.props.base_color = XoColor('#D5D5D5,#D5D5D5') + else: + self.props.base_color = self._color + + def _disconnect_activate_cb(self, item): + pass + + def _add_ciphers_from_flags(self, flags, pairwise): + ciphers = [] + if pairwise: + if flags & network.NM_802_11_AP_SEC_PAIR_TKIP: + ciphers.append("tkip") + if flags & network.NM_802_11_AP_SEC_PAIR_CCMP: + ciphers.append("ccmp") + else: + if flags & network.NM_802_11_AP_SEC_GROUP_WEP40: + ciphers.append("wep40") + if flags & network.NM_802_11_AP_SEC_GROUP_WEP104: + ciphers.append("wep104") + if flags & network.NM_802_11_AP_SEC_GROUP_TKIP: + ciphers.append("tkip") + if flags & network.NM_802_11_AP_SEC_GROUP_CCMP: + ciphers.append("ccmp") + return ciphers + + def _get_security(self): + if not (self._flags & network.NM_802_11_AP_FLAGS_PRIVACY) and \ + (self._wpa_flags == network.NM_802_11_AP_SEC_NONE) and \ + (self._rsn_flags == network.NM_802_11_AP_SEC_NONE): + # No security + return None + + if (self._flags & network.NM_802_11_AP_FLAGS_PRIVACY) and \ + (self._wpa_flags == network.NM_802_11_AP_SEC_NONE) and \ + (self._rsn_flags == network.NM_802_11_AP_SEC_NONE): + # Static WEP, Dynamic WEP, or LEAP + wireless_security = WirelessSecurity() + wireless_security.key_mgmt = 'none' + return wireless_security + + if (self._mode != network.NM_802_11_MODE_INFRA): + # Stuff after this point requires infrastructure + logging.error('The infrastructure mode is not supoorted' + ' by your wireless device.') + return None + + if (self._rsn_flags & network.NM_802_11_AP_SEC_KEY_MGMT_PSK) and \ + (self._device_caps & network.NM_802_11_DEVICE_CAP_RSN): + # WPA2 PSK first + pairwise = self._add_ciphers_from_flags(self._rsn_flags, True) + group = self._add_ciphers_from_flags(self._rsn_flags, False) + wireless_security = WirelessSecurity() + wireless_security.key_mgmt = 'wpa-psk' + wireless_security.proto = 'rsn' + wireless_security.pairwise = pairwise + wireless_security.group = group + return wireless_security + + if (self._wpa_flags & network.NM_802_11_AP_SEC_KEY_MGMT_PSK) and \ + (self._device_caps & network.NM_802_11_DEVICE_CAP_WPA): + # WPA PSK + pairwise = self._add_ciphers_from_flags(self._wpa_flags, True) + group = self._add_ciphers_from_flags(self._wpa_flags, False) + wireless_security = WirelessSecurity() + wireless_security.key_mgmt = 'wpa-psk' + wireless_security.proto = 'wpa' + wireless_security.pairwise = pairwise + wireless_security.group = group + return wireless_security + + def __connect_activate_cb(self, icon): + self._connect() + + def __button_release_event_cb(self, icon, event): + self._connect() + + def _connect(self): + connection = network.find_connection_by_ssid(self._name) + if connection is None: + settings = Settings() + settings.connection.id = 'Auto ' + self._name + uuid = settings.connection.uuid = unique_id() + settings.connection.type = '802-11-wireless' + settings.wireless.ssid = self._name + + if self._mode == network.NM_802_11_MODE_INFRA: + settings.wireless.mode = 'infrastructure' + elif self._mode == network.NM_802_11_MODE_ADHOC: + settings.wireless.mode = 'adhoc' + settings.wireless.band = 'bg' + settings.ip4_config = IP4Config() + settings.ip4_config.method = 'link-local' + + wireless_security = self._get_security() + settings.wireless_security = wireless_security + + if wireless_security is not None: + settings.wireless.security = '802-11-wireless-security' + + connection = network.add_connection(uuid, settings) + + obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) + netmgr = dbus.Interface(obj, _NM_IFACE) + + netmgr.ActivateConnection(network.SETTINGS_SERVICE, connection.path, + self._device.object_path, + "/", + reply_handler=self.__activate_reply_cb, + error_handler=self.__activate_error_cb) + + def __activate_reply_cb(self, connection): + logging.debug('Connection activated: %s', connection) + + def __activate_error_cb(self, err): + logging.error('Failed to activate connection: %s', err) + + def set_filter(self, query): + self._greyed_out = self._name.lower().find(query) == -1 + self._update_state() + self._update_color() + + def create_keydialog(self, settings, response): + keydialog.create(self._name, self._flags, self._wpa_flags, + self._rsn_flags, self._device_caps, settings, response) + + def update_strength(self): + if self._active_ap is not None: + # display strength of AP that we are connected to + new_strength = self._active_ap.strength + else: + # display the strength of the strongest AP that makes up this + # network, also considering that there may be no APs + new_strength = max([0] + [ap.strength for ap in + self._access_points.values()]) + + if new_strength != self._strength: + self._strength = new_strength + self._update_state() + + def add_ap(self, ap): + self._access_points[ap.model.object_path] = ap + self.update_strength() + + def remove_ap(self, ap): + path = ap.model.object_path + if path not in self._access_points: + return + del self._access_points[path] + if self._active_ap == ap: + self._active_ap = None + self.update_strength() + + def num_aps(self): + return len(self._access_points) + + def find_ap(self, ap_path): + if ap_path not in self._access_points: + return None + return self._access_points[ap_path] + + def is_olpc_mesh(self): + return self._mode == network.NM_802_11_MODE_ADHOC \ + and self.name == "olpc-mesh" + + def remove_all_aps(self): + for ap in self._access_points.values(): + ap.disconnect() + self._access_points = {} + self._active_ap = None + self.update_strength() + + def disconnect(self): + self._bus.remove_signal_receiver(self.__device_state_changed_cb, + signal_name='StateChanged', + path=self._device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=self._device.object_path, + dbus_interface=_NM_WIRELESS_IFACE) + +class SugarAdhocView(CanvasPulsingIcon): + """To mimic the mesh behavior on devices where mesh hardware is + not available we support the creation of an Ad-hoc network on + three channels 1, 6, 11. This is the class for an icon + representing a channel in the neighborhood view. + + """ + + _ICON_NAME = 'network-adhoc-' + _NAME = 'Ad-hoc Network ' + + def __init__(self, channel): + CanvasPulsingIcon.__init__(self, + icon_name=self._ICON_NAME + str(channel), + size=style.STANDARD_ICON_SIZE, cache=True) + self._bus = dbus.SystemBus() + self._channel = channel + self._disconnect_item = None + self._connect_item = None + self._palette_icon = None + self._greyed_out = False + + get_adhoc_manager_instance().connect('members-changed', + self.__members_changed_cb) + get_adhoc_manager_instance().connect('state-changed', + self.__state_changed_cb) + + self.connect('button-release-event', self.__button_release_event_cb) + + pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), + style.COLOR_TRANSPARENT.get_svg())) + self.props.pulse_color = pulse_color + self._state_color = XoColor('%s,%s' % \ + (profile.get_color().get_stroke_color(), + style.COLOR_TRANSPARENT.get_svg())) + self.props.base_color = self._state_color + self._palette = self._create_palette() + self.set_palette(self._palette) + self._palette_icon.props.xo_color = self._state_color + + def _create_palette(self): + self._palette_icon = Icon( \ + icon_name=self._ICON_NAME + str(self._channel), + icon_size=style.STANDARD_ICON_SIZE) + + palette_ = palette.Palette(_("Ad-hoc Network %d") % self._channel, + icon=self._palette_icon) + + self._connect_item = MenuItem(_('Connect'), 'dialog-ok') + self._connect_item.connect('activate', self.__connect_activate_cb) + palette_.menu.append(self._connect_item) + + self._disconnect_item = MenuItem(_('Disconnect'), 'media-eject') + self._disconnect_item.connect('activate', + self.__disconnect_activate_cb) + palette_.menu.append(self._disconnect_item) + + return palette_ + + def __button_release_event_cb(self, icon, event): + get_adhoc_manager_instance().activate_channel(self._channel) + + def __connect_activate_cb(self, icon): + get_adhoc_manager_instance().activate_channel(self._channel) + + def __disconnect_activate_cb(self, icon): + get_adhoc_manager_instance().deactivate_active_channel() + + def __state_changed_cb(self, adhoc_manager, channel, device_state): + if self._channel == channel: + state = device_state + else: + state = network.DEVICE_STATE_UNKNOWN + + if state == network.DEVICE_STATE_ACTIVATED: + icon_name = '%s-connected' % (self._ICON_NAME + str(self._channel)) + else: + icon_name = self._ICON_NAME + str(self._channel) + + self.props.base_color = self._state_color + self._palette_icon.props.xo_color = self._state_color + + if icon_name is not None: + self.props.icon_name = icon_name + icon = self._palette.props.icon + icon.props.icon_name = icon_name + + if state in [network.DEVICE_STATE_PREPARE, + network.DEVICE_STATE_CONFIG, + network.DEVICE_STATE_NEED_AUTH, + network.DEVICE_STATE_IP_CONFIG]: + if self._disconnect_item: + self._disconnect_item.show() + self._connect_item.hide() + self._palette.props.secondary_text = _('Connecting...') + self.props.pulsing = True + elif state == network.DEVICE_STATE_ACTIVATED: + if self._disconnect_item: + self._disconnect_item.show() + self._connect_item.hide() + self._palette.props.secondary_text = _('Connected') + self.props.pulsing = False + else: + if self._disconnect_item: + self._disconnect_item.hide() + self._connect_item.show() + self._palette.props.secondary_text = None + self.props.pulsing = False + + def _update_color(self): + if self._greyed_out: + self.props.base_color = XoColor('#D5D5D5,#D5D5D5') + else: + self.props.base_color = self._state_color + + def __members_changed_cb(self, adhoc_manager, channel, has_members): + if channel == self._channel: + if has_members == True: + self._state_color = profile.get_color() + self.props.base_color = self._state_color + self._palette_icon.props.xo_color = self._state_color + else: + color = '%s,%s' % (profile.get_color().get_stroke_color(), + style.COLOR_TRANSPARENT.get_svg()) + self._state_color = XoColor(color) + self.props.base_color = self._state_color + self._palette_icon.props.xo_color = self._state_color + + def set_filter(self, query): + name = self._NAME + str(self._channel) + self._greyed_out = name.lower().find(query) == -1 + self._update_color() + + +class OlpcMeshView(CanvasPulsingIcon): + def __init__(self, mesh_mgr, channel): + CanvasPulsingIcon.__init__(self, icon_name=_OLPC_MESH_ICON_NAME, + size=style.STANDARD_ICON_SIZE, cache=True) + self._bus = dbus.SystemBus() + self._channel = channel + self._mesh_mgr = mesh_mgr + self._disconnect_item = None + self._connect_item = None + self._greyed_out = False + self._name = '' + self._device_state = None + self._connection = None + self._active = False + device = mesh_mgr.mesh_device + + self.connect('button-release-event', self.__button_release_event_cb) + + interface_props = dbus.Interface(device, + 'org.freedesktop.DBus.Properties') + interface_props.Get(_NM_DEVICE_IFACE, 'State', + reply_handler=self.__get_device_state_reply_cb, + error_handler=self.__get_device_state_error_cb) + interface_props.Get(_NM_OLPC_MESH_IFACE, 'ActiveChannel', + reply_handler=self.__get_active_channel_reply_cb, + error_handler=self.__get_active_channel_error_cb) + + self._bus.add_signal_receiver(self.__device_state_changed_cb, + signal_name='StateChanged', + path=device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=device.object_path, + dbus_interface=_NM_OLPC_MESH_IFACE) + + pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), + style.COLOR_TRANSPARENT.get_svg())) + self.props.pulse_color = pulse_color + self.props.base_color = profile.get_color() + self._palette = self._create_palette() + self.set_palette(self._palette) + + def _create_palette(self): + _palette = palette.Palette(_("Mesh Network %d") % self._channel) + + self._connect_item = MenuItem(_('Connect'), 'dialog-ok') + self._connect_item.connect('activate', self.__connect_activate_cb) + _palette.menu.append(self._connect_item) + + return _palette + + def __get_device_state_reply_cb(self, state): + self._device_state = state + self._update() + + def __get_device_state_error_cb(self, err): + logging.error('Error getting the device state: %s', err) + + def __device_state_changed_cb(self, new_state, old_state, reason): + self._device_state = new_state + self._update() + + def __get_active_channel_reply_cb(self, channel): + self._active = (channel == self._channel) + self._update() + + def __get_active_channel_error_cb(self, err): + logging.error('Error getting the active channel: %s', err) + + def __wireless_properties_changed_cb(self, properties): + if 'ActiveChannel' in properties: + channel = properties['ActiveChannel'] + self._active = (channel == self._channel) + self._update() + + def _update(self): + if self._active: + state = self._device_state + else: + state = network.DEVICE_STATE_UNKNOWN + + if state in [network.DEVICE_STATE_PREPARE, + network.DEVICE_STATE_CONFIG, + network.DEVICE_STATE_NEED_AUTH, + network.DEVICE_STATE_IP_CONFIG]: + if self._disconnect_item: + self._disconnect_item.show() + self._connect_item.hide() + self._palette.props.secondary_text = _('Connecting...') + self.props.pulsing = True + elif state == network.DEVICE_STATE_ACTIVATED: + if self._disconnect_item: + self._disconnect_item.show() + self._connect_item.hide() + self._palette.props.secondary_text = _('Connected') + self.props.pulsing = False + else: + if self._disconnect_item: + self._disconnect_item.hide() + self._connect_item.show() + self._palette.props.secondary_text = None + self.props.pulsing = False + + def _update_color(self): + if self._greyed_out: + self.props.base_color = XoColor('#D5D5D5,#D5D5D5') + else: + self.props.base_color = profile.get_color() + + def __connect_activate_cb(self, icon): + self._connect() + + def __button_release_event_cb(self, icon, event): + self._connect() + + def _connect(self): + self._mesh_mgr.user_activate_channel(self._channel) + + def __activate_reply_cb(self, connection): + logging.debug('Connection activated: %s', connection) + + def __activate_error_cb(self, err): + logging.error('Failed to activate connection: %s', err) + + def set_filter(self, query): + self._greyed_out = (query != '') + self._update_color() + + def disconnect(self): + device_object_path = self._mesh_mgr.mesh_device.object_path + + self._bus.remove_signal_receiver(self.__device_state_changed_cb, + signal_name='StateChanged', + path=device_object_path, + dbus_interface=_NM_DEVICE_IFACE) + self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=device_object_path, + dbus_interface=_NM_OLPC_MESH_IFACE) + diff --git a/src/jarabe/desktop/transitionbox.py b/src/jarabe/desktop/transitionbox.py index af17cfb..cf9e0d6 100644 --- a/src/jarabe/desktop/transitionbox.py +++ b/src/jarabe/desktop/transitionbox.py @@ -20,7 +20,8 @@ import gobject from sugar.graphics import style from sugar.graphics import animator -from jarabe.desktop.myicon import MyIcon +from jarabe.model.buddy import get_owner_instance +from jarabe.view.buddyicon import BuddyIcon class _Animation(animator.Animation): def __init__(self, icon, start_size, end_size): @@ -77,7 +78,8 @@ class TransitionBox(hippo.Canvas): self._layout = _Layout() self._box.set_layout(self._layout) - self._my_icon = MyIcon(style.XLARGE_ICON_SIZE) + self._my_icon = BuddyIcon(buddy=get_owner_instance(), + size=style.XLARGE_ICON_SIZE) self._box.append(self._my_icon) self._animator = animator.Animator(0.3) diff --git a/src/jarabe/frame/activitiestray.py b/src/jarabe/frame/activitiestray.py index f594342..9a1a9da 100644 --- a/src/jarabe/frame/activitiestray.py +++ b/src/jarabe/frame/activitiestray.py @@ -1,5 +1,6 @@ # Copyright (C) 2006-2007 Red Hat, Inc. # Copyright (C) 2008 One Laptop Per Child +# Copyright (C) 2010 Collabora Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -33,23 +34,18 @@ from sugar.graphics.toolbutton import ToolButton from sugar.graphics.icon import Icon, get_icon_file_name from sugar.graphics.palette import Palette, WidgetInvoker from sugar.graphics.menuitem import MenuItem -from sugar.activity.activityhandle import ActivityHandle -from sugar.activity import activityfactory from sugar.datastore import datastore from sugar import mime from sugar import env from jarabe.model import shell -from jarabe.model import neighborhood -from jarabe.model import owner +from jarabe.model import invites from jarabe.model import bundleregistry from jarabe.model import filetransfer from jarabe.view.palettes import JournalPalette, CurrentActivityPalette from jarabe.view.pulsingicon import PulsingIcon -from jarabe.view import launcher from jarabe.frame.frameinvoker import FrameWidgetInvoker from jarabe.frame.notification import NotificationIcon -from jarabe.journal import misc import jarabe.frame @@ -102,131 +98,79 @@ class ActivityButton(RadioToolButton): self._icon.props.pulsing = False -class BaseInviteButton(ToolButton): + +class InviteButton(ToolButton): + """Invite to shared activity""" def __init__(self, invite): ToolButton.__init__(self) - self._invite = invite - self._icon = Icon() - self.set_icon_widget(self._icon) - self._icon.show() + self._invite = invite self.connect('clicked', self.__clicked_cb) self.connect('destroy', self.__destroy_cb) - self._notif_icon = NotificationIcon() - self._notif_icon.connect('button-release-event', - self.__button_release_event_cb) - def __button_release_event_cb(self, icon, event): - self.emit('clicked') + bundle_registry = bundleregistry.get_registry() + bundle = bundle_registry.get_bundle(invite.get_bundle_id()) - def __clicked_cb(self, button): - if self._notif_icon is not None: - frame = jarabe.frame.get_view() - frame.remove_notification(self._notif_icon) - self._notif_icon = None - self._launch() - - def _launch(self): - """Launch the target of the invite""" - raise NotImplementedError - - def __destroy_cb(self, button): - frame = jarabe.frame.get_view() - frame.remove_notification(self._notif_icon) - -class ActivityInviteButton(BaseInviteButton): - """Invite to shared activity""" - def __init__(self, invite): - BaseInviteButton.__init__(self, invite) - mesh = neighborhood.get_model() - activity_model = mesh.get_activity(invite.get_activity_id()) - self._activity_model = activity_model - self._bundle_id = activity_model.get_bundle_id() - - self._icon.props.xo_color = activity_model.get_color() - if activity_model.get_icon_name(): - self._icon.props.file = activity_model.get_icon_name() + self._icon = Icon() + self._icon.props.xo_color = invite.get_color() + if bundle is not None: + self._icon.props.file = bundle.get_icon() else: self._icon.props.icon_name = 'image-missing' + self.set_icon_widget(self._icon) + self._icon.show() - palette = ActivityInvitePalette(invite) + palette = InvitePalette(invite) palette.props.invoker = FrameWidgetInvoker(self) palette.set_group_id('frame') self.set_palette(palette) - self._notif_icon.props.xo_color = activity_model.get_color() - if activity_model.get_icon_name(): - icon_name = activity_model.get_icon_name() - self._notif_icon.props.icon_filename = icon_name + self._notif_icon = NotificationIcon() + self._notif_icon.connect('button-release-event', + self.__button_release_event_cb) + + self._notif_icon.props.xo_color = invite.get_color() + if bundle is not None: + self._notif_icon.props.icon_filename = bundle.get_icon() else: self._notif_icon.props.icon_name = 'image-missing' - palette = ActivityInvitePalette(invite) + palette = InvitePalette(invite) palette.props.invoker = WidgetInvoker(self._notif_icon) palette.set_group_id('frame') self._notif_icon.palette = palette frame = jarabe.frame.get_view() - frame.add_notification(self._notif_icon, - gtk.CORNER_TOP_LEFT) - - def _launch(self): - """Join the activity in the invite.""" - registry = bundleregistry.get_registry() - bundle = registry.get_bundle(self._bundle_id) - - misc.launch(bundle, color=self._activity_model.get_color()) - -class PrivateInviteButton(BaseInviteButton): - """Invite to a private one to one channel""" - def __init__(self, invite): - BaseInviteButton.__init__(self, invite) - self._private_channel = invite.get_private_channel() - self._bundle_id = invite.get_bundle_id() - - client = gconf.client_get_default() - color = XoColor(client.get_string('/desktop/sugar/user/color')) - - self._icon.props.xo_color = color - registry = bundleregistry.get_registry() - self._bundle = registry.get_bundle(self._bundle_id) - - if self._bundle: - self._icon.props.file = self._bundle.get_icon() - else: - self._icon.props.icon_name = 'image-missing' - - palette = PrivateInvitePalette(invite) - palette.props.invoker = FrameWidgetInvoker(self) - palette.set_group_id('frame') - self.set_palette(palette) + frame.add_notification(self._notif_icon, gtk.CORNER_TOP_LEFT) - self._notif_icon.props.xo_color = color - - if self._bundle: - self._notif_icon.props.icon_filename = self._bundle.get_icon() - else: - self._notif_icon.props.icon_name = 'image-missing' + def __button_release_event_cb(self, icon, event): + self.emit('clicked') - palette = PrivateInvitePalette(invite) - palette.props.invoker = WidgetInvoker(self._notif_icon) - palette.set_group_id('frame') - self._notif_icon.palette = palette + def __clicked_cb(self, button): + if self._notif_icon is not None: + frame = jarabe.frame.get_view() + frame.remove_notification(self._notif_icon) + self._notif_icon = None + self._launch() + def __destroy_cb(self, button): frame = jarabe.frame.get_view() - frame.add_notification(self._notif_icon, - gtk.CORNER_TOP_LEFT) + frame.remove_notification(self._notif_icon) def _launch(self): - """Start the activity with private channel.""" - misc.launch(self._bundle, uri=self._private_channel) + """Join the activity in the invite.""" + self._invite.join() + -class BaseInvitePalette(Palette): +class InvitePalette(Palette): """Palette for frame or notification icon for invites.""" - def __init__(self): + + def __init__(self, invite): Palette.__init__(self, '') + self._invite = invite + menu_item = MenuItem(_('Join'), icon_name='dialog-ok') menu_item.connect('activate', self.__join_activate_cb) self.menu.append(menu_item) @@ -237,71 +181,22 @@ class BaseInvitePalette(Palette): self.menu.append(menu_item) menu_item.show() - def __join_activate_cb(self, menu_item): - self._join() - - def __decline_activate_cb(self, menu_item): - self._decline() - - def _join(self): - raise NotImplementedError - - def _decline(self): - raise NotImplementedError - - -class ActivityInvitePalette(BaseInvitePalette): - """Palette for shared activity invites.""" - - def __init__(self, invite): - BaseInvitePalette.__init__(self) - - mesh = neighborhood.get_model() - activity_model = mesh.get_activity(invite.get_activity_id()) - self._activity_model = activity_model - self._bundle_id = activity_model.get_bundle_id() + bundle_id = invite.get_bundle_id() registry = bundleregistry.get_registry() - self._bundle = registry.get_bundle(self._bundle_id) + self._bundle = registry.get_bundle(bundle_id) if self._bundle: self.set_primary_text(self._bundle.get_name()) else: - self.set_primary_text(self._bundle_id) + self.set_primary_text(bundle_id) - def _join(self): - misc.launch(self._bundle, activity_id=self._activity_model.get_id()) + def __join_activate_cb(self, menu_item): + self._invite.join() - def _decline(self): - invites = owner.get_model().get_invites() + def __decline_activate_cb(self, menu_item): + invites_model = invites.get_instance() activity_id = self._activity_model.get_id() - invites.remove_activity(activity_id) - - -class PrivateInvitePalette(BaseInvitePalette): - """Palette for private channel invites.""" - - def __init__(self, invite): - BaseInvitePalette.__init__(self) - - self._private_channel = invite.get_private_channel() - self._bundle_id = invite.get_bundle_id() - - registry = bundleregistry.get_registry() - self._bundle = registry.get_bundle(self._bundle_id) - if self._bundle: - self.set_primary_text(self._bundle.get_name()) - else: - self.set_primary_text(self._bundle_id) - - def _join(self): - misc.launch(self._bundle, uri=self._private_channel) - - invites = owner.get_model().get_invites() - invites.remove_private_channel(self._private_channel) - - def _decline(self): - invites = owner.get_model().get_invites() - invites.remove_private_channel(self._private_channel) + invites_model.remove_activity(activity_id) class ActivitiesTray(HTray): @@ -320,7 +215,7 @@ class ActivitiesTray(HTray): self._home_model.connect('tabbing-activity-changed', self.__tabbing_activity_changed_cb) - self._invites = owner.get_model().get_invites() + self._invites = invites.get_instance() for invite in self._invites: self._add_invite(invite) self._invites.connect('invite-added', self.__invite_added_cb) @@ -384,32 +279,22 @@ class ActivitiesTray(HTray): window.activate(gtk.get_current_event_time()) def __invite_clicked_cb(self, icon, invite): - if hasattr(invite, 'get_activity_id'): - self._invites.remove_invite(invite) - else: - self._invites.remove_private_invite(invite) + self._invites.remove_invite(invite) - def __invite_added_cb(self, invites, invite): + def __invite_added_cb(self, invites_model, invite): self._add_invite(invite) - def __invite_removed_cb(self, invites, invite): + def __invite_removed_cb(self, invites_model, invite): self._remove_invite(invite) def _add_invite(self, invite): - """Add an invite (SugarInvite or PrivateInvite)""" - item = None - if hasattr(invite, 'get_activity_id'): - mesh = neighborhood.get_model() - activity_model = mesh.get_activity(invite.get_activity_id()) - if activity_model is not None: - item = ActivityInviteButton(invite) - else: - item = PrivateInviteButton(invite) - if item is not None: - item.connect('clicked', self.__invite_clicked_cb, invite) - self.add_item(item) - item.show() - self._invite_to_item[invite] = item + """Add an invite""" + item = InviteButton(invite) + item.connect('clicked', self.__invite_clicked_cb, invite) + self.add_item(item) + item.show() + + self._invite_to_item[invite] = item def _remove_invite(self, invite): self.remove_item(self._invite_to_item[invite]) diff --git a/src/jarabe/frame/frame.py b/src/jarabe/frame/frame.py index 55f866f..7dde55b 100644 --- a/src/jarabe/frame/frame.py +++ b/src/jarabe/frame/frame.py @@ -322,7 +322,7 @@ class Frame(object): del self._notif_by_icon[icon] def __notification_received_cb(self, **kwargs): - logging.debug('__notification_received_cb %r', kwargs) + logging.debug('__notification_received_cb') icon = NotificationIcon() hints = kwargs['hints'] diff --git a/src/jarabe/frame/friendstray.py b/src/jarabe/frame/friendstray.py index b5437e5..141505b 100644 --- a/src/jarabe/frame/friendstray.py +++ b/src/jarabe/frame/friendstray.py @@ -14,13 +14,15 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -from sugar.presence import presenceservice +import logging + from sugar.graphics.tray import VTray, TrayIcon from jarabe.view.buddymenu import BuddyMenu from jarabe.frame.frameinvoker import FrameWidgetInvoker from jarabe.model import shell -from jarabe.model.buddy import BuddyModel +from jarabe.model.buddy import get_owner_instance +from jarabe.model import neighborhood class FriendIcon(TrayIcon): def __init__(self, buddy): @@ -36,35 +38,20 @@ class FriendsTray(VTray): def __init__(self): VTray.__init__(self) - self._activity_ps = None - self._joined_hid = -1 - self._left_hid = -1 + self._shared_activity = None self._buddies = {} - self._pservice = presenceservice.get_instance() - self._pservice.connect('activity-appeared', - self.__activity_appeared_cb) - - self._owner = self._pservice.get_owner() - - # Add initial activities the PS knows about - self._pservice.get_activities_async( \ - reply_handler=self._get_activities_cb) - shell.get_model().connect('active-activity-changed', - self._active_activity_changed_cb) + self.__active_activity_changed_cb) - def _get_activities_cb(self, activities_list): - for act in activities_list: - self.__activity_appeared_cb(self._pservice, act) + neighborhood.get_model().connect('activity-added', + self.__neighborhood_activity_added_cb) def add_buddy(self, buddy): if self._buddies.has_key(buddy.props.key): return - model = BuddyModel(buddy=buddy) - - icon = FriendIcon(model) + icon = FriendIcon(buddy) self.add_item(icon) icon.show() @@ -83,39 +70,23 @@ class FriendsTray(VTray): item.destroy() self._buddies = {} - def __activity_appeared_cb(self, pservice, activity_ps): - activity = shell.get_model().get_active_activity() - if activity and activity_ps.props.id == activity.get_activity_id(): - self._set_activity_ps(activity_ps, True) - - def _set_activity_ps(self, activity_ps, shared_activity): - if self._activity_ps == activity_ps: - return + def __neighborhood_activity_added_cb(self, neighborhood_model, + shared_activity): + logging.debug('FriendsTray.__neighborhood_activity_added_cb') + self.clear() - if self._joined_hid > 0: - self._activity_ps.disconnect(self._joined_hid) - self._joined_hid = -1 - if self._left_hid > 0: - self._activity_ps.disconnect(self._left_hid) - self._left_hid = -1 + # always display ourselves + self.add_buddy(get_owner_instance()) - self._activity_ps = activity_ps + self._set_current_activity(shared_activity.activity_id) + def __active_activity_changed_cb(self, home_model, home_activity): + logging.debug('FriendsTray.__active_activity_changed_cb') self.clear() # always display ourselves - self.add_buddy(self._owner) - - if shared_activity is True: - for buddy in activity_ps.get_joined_buddies(): - self.add_buddy(buddy) + self.add_buddy(get_owner_instance()) - self._joined_hid = activity_ps.connect( - 'buddy-joined', self.__buddy_joined_cb) - self._left_hid = activity_ps.connect( - 'buddy-left', self.__buddy_left_cb) - - def _active_activity_changed_cb(self, home_model, home_activity): if home_activity is None: return @@ -123,19 +94,25 @@ class FriendsTray(VTray): if activity_id is None: return - # check if activity is shared - activity = None - for act in self._pservice.get_activities(): - if activity_id == act.props.id: - activity = act - break - if activity: - self._set_activity_ps(activity, True) - else: - self._set_activity_ps(home_activity, False) - - def __buddy_joined_cb(self, activity, buddy): + self._set_current_activity(activity_id) + + def _set_current_activity(self, activity_id): + logging.debug('FriendsTray._set_current_activity') + neighborhood_model = neighborhood.get_model() + self._shared_activity = neighborhood_model.get_activity(activity_id) + if self._shared_activity is None: + return + + for buddy in self._shared_activity.get_buddies(): + self.add_buddy(buddy) + + self._shared_activity.connect('buddy-added', self.__buddy_added_cb) + self._shared_activity.connect('buddy-removed', self.__buddy_removed_cb) + + def __buddy_added_cb(self, activity, buddy): + logging.debug('FriendsTray.__buddy_added_cb') self.add_buddy(buddy) - def __buddy_left_cb(self, activity, buddy): + def __buddy_removed_cb(self, activity, buddy): + logging.debug('FriendsTray.__buddy_removed_cb') self.remove_buddy(buddy) diff --git a/src/jarabe/journal/misc.py b/src/jarabe/journal/misc.py index f5e2c89..657b60e 100644 --- a/src/jarabe/journal/misc.py +++ b/src/jarabe/journal/misc.py @@ -215,7 +215,8 @@ def resume(metadata, bundle_id=None): launch(bundle, activity_id=activity_id, object_id=object_id, color=get_icon_color(metadata)) -def launch(bundle, activity_id=None, object_id=None, uri=None, color=None): +def launch(bundle, activity_id=None, object_id=None, uri=None, color=None, + invited=False): if activity_id is None: activity_id = activityfactory.create_activity_id() @@ -235,7 +236,7 @@ def launch(bundle, activity_id=None, object_id=None, uri=None, color=None): launcher.add_launcher(activity_id, bundle.get_icon(), color) activity_handle = ActivityHandle(activity_id=activity_id, - object_id=object_id, uri=uri) + object_id=object_id, uri=uri, invited=invited) activityfactory.create(bundle, activity_handle) def is_activity_bundle(metadata): diff --git a/src/jarabe/model/Makefile.am b/src/jarabe/model/Makefile.am index 4650c3b..92e8712 100644 --- a/src/jarabe/model/Makefile.am +++ b/src/jarabe/model/Makefile.am @@ -8,12 +8,12 @@ sugar_PYTHON = \ friends.py \ invites.py \ olpcmesh.py \ - owner.py \ - mimeregistry.py \ + mimeregistry.py \ neighborhood.py \ network.py \ notifications.py \ shell.py \ screen.py \ session.py \ - sound.py + sound.py \ + telepathyclient.py diff --git a/src/jarabe/model/buddy.py b/src/jarabe/model/buddy.py index 5978bae..6cf7be9 100644 --- a/src/jarabe/model/buddy.py +++ b/src/jarabe/model/buddy.py @@ -1,4 +1,5 @@ # Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2010 Collabora Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -14,169 +15,236 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -from sugar.presence import presenceservice -from sugar.graphics.xocolor import XoColor +import logging + import gobject +import gconf +import dbus +from telepathy.client import Connection +from telepathy.interfaces import CONNECTION + +from sugar.graphics.xocolor import XoColor +from sugar.profile import get_profile + +from jarabe.util.telepathy import connection_watcher _NOT_PRESENT_COLOR = "#d5d5d5,#FFFFFF" -class BuddyModel(gobject.GObject): - __gsignals__ = { - 'appeared': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), - 'disappeared': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), - 'nick-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'color-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'icon-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([])), - 'tags-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'current-activity-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) - } - - def __init__(self, key=None, buddy=None, nick=None): - if (key and buddy) or (not key and not buddy): - raise RuntimeError("Must specify only _one_ of key or buddy.") - - gobject.GObject.__init__(self) +CONNECTION_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo' + +class BaseBuddyModel(gobject.GObject): + __gtype_name__ = 'SugarBaseBuddyModel' + def __init__(self, **kwargs): + self._key = None + self._nick = None self._color = None self._tags = None - self._ba_handler = None - self._pc_handler = None - self._dis_handler = None - self._bic_handler = None - self._cac_handler = None - - self._pservice = presenceservice.get_instance() - - self._buddy = None - - if not buddy: - self._key = key - # connect to the PS's buddy-appeared signal and - # wait for the buddy to appear - self._ba_handler = self._pservice.connect('buddy-appeared', - self._buddy_appeared_cb) - # Set color to 'inactive'/'disconnected' - self._set_color_from_string(_NOT_PRESENT_COLOR) - self._nick = nick - - self._pservice.get_buddies_async(reply_handler=self._get_buddies_cb) - else: - self._update_buddy(buddy) - - def _get_buddies_cb(self, buddy_list): - buddy = None - for iter_buddy in buddy_list: - if iter_buddy.props.key == self._key: - buddy = iter_buddy - break - - if buddy: - if self._ba_handler: - # Once we have the buddy, we no longer need to - # monitor buddy-appeared events - self._pservice.disconnect(self._ba_handler) - self._ba_handler = None - - self._update_buddy(buddy) - - def _set_color_from_string(self, color_string): - self._color = XoColor(color_string) + self._present = False + self._current_activity = None - def get_key(self): - return self._key + gobject.GObject.__init__(self, **kwargs) + + def is_present(self): + return self._present + + def set_present(self, present): + self._present = present + + present = gobject.property(type=bool, default=False, getter=is_present, + setter=set_present) def get_nick(self): return self._nick + def set_nick(self, nick): + self._nick = nick + + nick = gobject.property(type=object, getter=get_nick, setter=set_nick) + + def get_key(self): + return self._key + + def set_key(self, key): + self._key = key + + key = gobject.property(type=object, getter=get_key, setter=set_key) + def get_color(self): return self._color + def set_color(self, color): + self._color = color + + color = gobject.property(type=object, getter=get_color, setter=set_color) + def get_tags(self): return self._tags + tags = gobject.property(type=object, getter=get_tags) + + def get_current_activity(self): + return self._current_activity + + def set_current_activity(self, current_activity): + if self._current_activity != current_activity: + self._current_activity = current_activity + self.notify('current-activity') + + current_activity = gobject.property(type=object, + getter=get_current_activity, + setter=set_current_activity) + + def is_owner(self): + raise NotImplementedError + def get_buddy(self): - return self._buddy + raise NotImplementedError + + +class OwnerBuddyModel(BaseBuddyModel): + __gtype_name__ = 'SugarOwnerBuddyModel' + def __init__(self): + BaseBuddyModel.__init__(self) + self.props.present = True + + client = gconf.client_get_default() + self.props.nick = client.get_string('/desktop/sugar/user/nick') + color = client.get_string('/desktop/sugar/user/color') + self.props.color = XoColor(color) + + self.props.key = get_profile().pubkey + + self.connect('notify::nick', self.__property_changed_cb) + self.connect('notify::color', self.__property_changed_cb) + self.connect('notify::current-activity', + self.__current_activity_changed_cb) + + bus = dbus.SessionBus() + bus.add_signal_receiver( + self.__name_owner_changed_cb, + signal_name='NameOwnerChanged', + dbus_interface='org.freedesktop.DBus') + + bus_object = bus.get_object(dbus.BUS_DAEMON_NAME, dbus.BUS_DAEMON_PATH) + for service in bus_object.ListNames( + dbus_interface=dbus.BUS_DAEMON_IFACE): + if service.startswith('org.freedesktop.Telepathy.Connection.'): + path = '/%s' % service.replace('.', '/') + Connection(service, path, bus, + ready_handler=self.__connection_ready_cb) + + def __connection_ready_cb(self, connection): + self._sync_properties_on_connection(connection) + + def __name_owner_changed_cb(self, name, old, new): + if name.startswith(CONNECTION) and not old and new: + path = '/' + name.replace('.', '/') + Connection(name, path, ready_handler=self.__connection_ready_cb) + + def __property_changed_cb(self, pspec): + self._sync_properties() + + def __current_activity_changed_cb(self, pspec): + conn_watcher = connection_watcher.get_instance() + for connection in conn_watcher.get_connections(): + if self.props.current_activity is not None: + activity_id = self.props.current_activity.activity_id + room_handle = self.props.current_activity.room_handle + else: + activity_id = '' + room_handle = 0 + + connection[CONNECTION_INTERFACE_BUDDY_INFO].SetCurrentActivity( + activity_id, + room_handle, + reply_handler=self.__set_current_activity_cb, + error_handler=self.__error_handler_cb) + + def __set_current_activity_cb(self): + logging.debug('__set_current_activity_cb') + + def _sync_properties(self): + conn_watcher = connection_watcher.get_instance() + for connection in conn_watcher.get_connections(): + self._sync_properties_on_connection(connection) + + def _sync_properties_on_connection(self, connection): + if CONNECTION_INTERFACE_BUDDY_INFO in connection: + properties = {} + if self.props.key is not None: + properties['key'] = dbus.ByteArray(self.props.key) + if self.props.color is not None: + properties['color'] = self.props.color.to_string() + + logging.debug('calling SetProperties with %r', properties) + connection[CONNECTION_INTERFACE_BUDDY_INFO].SetProperties( + properties, + reply_handler=self.__set_properties_cb, + error_handler=self.__error_handler_cb) + + def __set_properties_cb(self): + logging.debug('__set_properties_cb') + + def __error_handler_cb(self, error): + raise RuntimeError(error) + + def __connection_added_cb(self, conn_watcher, connection): + self._sync_properties_on_connection(connection) def is_owner(self): - if not self._buddy: - return False - return self._buddy.props.owner + return True - def is_present(self): - if self._buddy: - return True + def get_buddy(self): + raise NotImplementedError + + +_owner_instance = None +def get_owner_instance(): + global _owner_instance + if _owner_instance is None: + _owner_instance = OwnerBuddyModel() + return _owner_instance + + +class BuddyModel(BaseBuddyModel): + __gtype_name__ = 'SugarBuddyModel' + def __init__(self, **kwargs): + + self._account = None + self._contact_id = None + + BaseBuddyModel.__init__(self, **kwargs) + + def is_owner(self): return False - def get_current_activity(self): - if self._buddy: - return self._buddy.props.current_activity - return None - - def _update_buddy(self, buddy): - if not buddy: - raise ValueError("Buddy cannot be None.") - - self._buddy = buddy - self._key = self._buddy.props.key - self._nick = self._buddy.props.nick - self._tags = self._buddy.props.tags - self._set_color_from_string(self._buddy.props.color) - - self._pc_handler = self._buddy.connect('property-changed', - self._buddy_property_changed_cb) - self._bic_handler = self._buddy.connect('icon-changed', - self._buddy_icon_changed_cb) - - def _buddy_appeared_cb(self, pservice, buddy): - if self._buddy or buddy.props.key != self._key: - return - - if self._ba_handler: - # Once we have the buddy, we no longer need to - # monitor buddy-appeared events - self._pservice.disconnect(self._ba_handler) - self._ba_handler = None - - self._update_buddy(buddy) - self.emit('appeared') - - def _buddy_property_changed_cb(self, buddy, keys): - if not self._buddy: - return - if 'color' in keys: - self._set_color_from_string(self._buddy.props.color) - self.emit('color-changed', self.get_color()) - if 'current-activity' in keys: - self.emit('current-activity-changed', buddy.props.current_activity) - if 'nick' in keys: - self._nick = self._buddy.props.nick - self.emit('nick-changed', self.get_nick()) - if 'tags' in keys: - self._tags = self._buddy.props.tags - self.emit('tags-changed', self.get_tags()) - - def _buddy_disappeared_cb(self, buddy): - if buddy != self._buddy: - return - self._buddy.disconnect(self._pc_handler) - self._buddy.disconnect(self._dis_handler) - self._buddy.disconnect(self._bic_handler) - self._buddy.disconnect(self._cac_handler) - self._set_color_from_string(_NOT_PRESENT_COLOR) - self.emit('disappeared') - self._buddy = None - - def _buddy_icon_changed_cb(self, buddy): - self.emit('icon-changed') + def get_buddy(self): + raise NotImplementedError + + def get_account(self): + return self._account + + def set_account(self, account): + self._account = account + + account = gobject.property(type=object, getter=get_account, + setter=set_account) + + def get_contact_id(self): + return self._contact_id + + def set_contact_id(self, contact_id): + self._contact_id = contact_id + + contact_id = gobject.property(type=object, getter=get_contact_id, + setter=set_contact_id) + + +class FriendBuddyModel(BuddyModel): + __gtype_name__ = 'SugarFriendBuddyModel' + def __init__(self, nick, key): + BuddyModel.__init__(self, nick=nick, key=key) + + def get_buddy(self): + raise NotImplementedError diff --git a/src/jarabe/model/filetransfer.py b/src/jarabe/model/filetransfer.py index 46b246d..e0809bb 100644 --- a/src/jarabe/model/filetransfer.py +++ b/src/jarabe/model/filetransfer.py @@ -319,21 +319,18 @@ def _monitor_connection(connection): connection[CONNECTION_INTERFACE_REQUESTS].connect_to_signal('NewChannels', lambda channels: _new_channels_cb(connection, channels)) -def _connection_addded_cb(conn_watcher, connection): +def _connection_added_cb(conn_watcher, connection): _monitor_connection(connection) def _connection_removed_cb(conn_watcher, connection): logging.debug('connection removed %r', connection) -_conn_watcher = None - def init(): - global _conn_watcher - _conn_watcher = connection_watcher.ConnectionWatcher() - _conn_watcher.connect('connection-added', _connection_addded_cb) - _conn_watcher.connect('connection-removed', _connection_removed_cb) + conn_watcher = connection_watcher.get_instance() + conn_watcher.connect('connection-added', _connection_added_cb) + conn_watcher.connect('connection-removed', _connection_removed_cb) - for connection in _conn_watcher.get_connections(): + for connection in conn_watcher.get_connections(): _monitor_connection(connection) def start_transfer(buddy, file_name, title, description, mime_type): @@ -342,7 +339,8 @@ def start_transfer(buddy, file_name, title, description, mime_type): new_file_transfer.send(None, file_transfer=outgoing_file_transfer) def file_transfer_available(): - for connection in _conn_watcher.get_connections(): + conn_watcher = connection_watcher.get_instance() + for connection in conn_watcher.get_connections(): properties_iface = connection[dbus.PROPERTIES_IFACE] properties = properties_iface.GetAll(CONNECTION_INTERFACE_REQUESTS) diff --git a/src/jarabe/model/invites.py b/src/jarabe/model/invites.py index c918308..a386d30 100644 --- a/src/jarabe/model/invites.py +++ b/src/jarabe/model/invites.py @@ -1,4 +1,5 @@ # Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2010 Collabora Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -14,52 +15,95 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -import gobject -from sugar.presence import presenceservice - - -class BaseInvite: - """Invitation to shared activity or private 1-1 Telepathy channel""" - def __init__(self, bundle_id): - """init for BaseInvite. - - bundle_id: string, e.g. 'org.laptop.Chat' - """ - self._bundle_id = bundle_id - - def get_bundle_id(self): - return self._bundle_id +import logging +from functools import partial - -class ActivityInvite(BaseInvite): +import gobject +import dbus +from telepathy.interfaces import CHANNEL, \ + CHANNEL_DISPATCHER, \ + CHANNEL_DISPATCH_OPERATION, \ + CHANNEL_TYPE_CONTACT_LIST, \ + CHANNEL_TYPE_DBUS_TUBE, \ + CHANNEL_TYPE_STREAMED_MEDIA, \ + CHANNEL_TYPE_STREAM_TUBE, \ + CHANNEL_TYPE_TEXT, \ + CLIENT +from telepathy.constants import HANDLE_TYPE_ROOM + +from sugar.graphics.xocolor import XoColor + +from jarabe.model import telepathyclient +from jarabe.model import bundleregistry +from jarabe.model import neighborhood +from jarabe.journal import misc + +CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \ + 'org.laptop.Telepathy.ActivityProperties' + + +class ActivityInvite(object): """Invitation to a shared activity.""" - def __init__(self, bundle_id, activity_id): - BaseInvite.__init__(self, bundle_id) - self._activity_id = activity_id - - def get_activity_id(self): - return self._activity_id - - -class PrivateInvite(BaseInvite): - """Invitation to a private 1-1 Telepathy channel. - - This includes text chat or streaming media. - """ - def __init__(self, bundle_id, private_channel): - """init for PrivateInvite. + def __init__(self, dispatch_operation_path, handle, handler, + activity_properties): + self.dispatch_operation_path = dispatch_operation_path + self._handle = handle + self._handler = handler - bundle_id: string, e.g. 'org.laptop.Chat' - private_channel: string containing simplejson dump of Telepathy - bus, connection and channel - """ - BaseInvite.__init__(self, bundle_id) - self._private_channel = private_channel - - def get_private_channel(self): - """Telepathy channel info from private invitation""" - return self._private_channel + if activity_properties is not None: + self._activity_properties = activity_properties + else: + self._activity_properties = {} + def get_bundle_id(self): + if CLIENT in self._handler: + return self._handler[len(CLIENT + '.'):] + else: + return None + + def get_color(self): + color = self._activity_properties.get('color', None) + return XoColor(color) + + def join(self): + logging.error('ActivityInvite.join handler %r', self._handler) + + registry = bundleregistry.get_registry() + bundle_id = self.get_bundle_id() + bundle = registry.get_bundle(bundle_id) + if bundle is None: + self._call_handle_with() + else: + bus = dbus.SessionBus() + bus.add_signal_receiver(self.__name_owner_changed_cb, + 'NameOwnerChanged', + 'org.freedesktop.DBus', + arg0=self._handler) + + model = neighborhood.get_model() + activity_id = model.get_activity_by_room(self._handle).activity_id + misc.launch(bundle, color=self.get_color(), invited=True, + activity_id=activity_id) + + def __name_owner_changed_cb(self, name, old_owner, new_owner): + logging.debug('ActivityInvite.__name_owner_changed_cb %r %r %r', name, + new_owner, old_owner) + if name == self._handler and new_owner and not old_owner: + self._call_handle_with() + + def _call_handle_with(self): + bus = dbus.Bus() + obj = bus.get_object(CHANNEL_DISPATCHER, self.dispatch_operation_path) + dispatch_operation = dbus.Interface(obj, CHANNEL_DISPATCH_OPERATION) + dispatch_operation.HandleWith(self._handler, + reply_handler=self.__handle_with_reply_cb, + error_handler=self.__handle_with_reply_cb) + + def __handle_with_reply_cb(self, error=None): + if error is not None: + raise error + else: + logging.debug('__handle_with_reply_cb') class Invites(gobject.GObject): __gsignals__ = { @@ -72,52 +116,124 @@ class Invites(gobject.GObject): def __init__(self): gobject.GObject.__init__(self) - self._dict = {} - - ps = presenceservice.get_instance() - owner = ps.get_owner() - owner.connect('joined-activity', self._owner_joined_cb) - - def add_invite(self, bundle_id, activity_id): - if activity_id in self._dict: - # there is no point to add more than one time - # an invite for the same activity - return - - invite = ActivityInvite(bundle_id, activity_id) - self._dict[activity_id] = invite - self.emit('invite-added', invite) - - def add_private_invite(self, private_channel, bundle_id): - if private_channel in self._dict: - # there is no point to add more than one invite for the - # same incoming connection + self._dispatch_operations = {} + + client_handler = telepathyclient.get_instance() + client_handler.got_dispatch_operation.connect( + self.__got_dispatch_operation_cb) + + def __got_dispatch_operation_cb(self, **kwargs): + logging.debug('__got_dispatch_operation_cb') + dispatch_operation_path = kwargs['dispatch_operation_path'] + channel_path, channel_properties = kwargs['channels'][0] + properties = kwargs['properties'] + channel_type = channel_properties[CHANNEL + '.ChannelType'] + handle_type = channel_properties[CHANNEL + '.TargetHandleType'] + handle = channel_properties[CHANNEL + '.TargetHandle'] + + if handle_type == HANDLE_TYPE_ROOM and \ + channel_type == CHANNEL_TYPE_TEXT: + logging.debug('May be an activity, checking its properties') + connection_path = properties[CHANNEL_DISPATCH_OPERATION + + '.Connection'] + connection_name = connection_path.replace('/', '.')[1:] + + bus = dbus.Bus() + connection = bus.get_object(connection_name, connection_path) + connection.GetProperties( + channel_properties[CHANNEL + '.TargetHandle'], + dbus_interface=CONNECTION_INTERFACE_ACTIVITY_PROPERTIES, + reply_handler=partial(self.__get_properties_cb, + handle, + dispatch_operation_path), + error_handler=partial(self.__error_handler_cb, + handle, + channel_properties, + dispatch_operation_path)) + else: + self._dispatch_non_sugar_invitation(channel_path, + channel_properties, + dispatch_operation_path) + + def __get_properties_cb(self, handle, dispatch_operation_path, properties): + logging.debug('__get_properties_cb %r', properties) + handler = '%s.%s' % (CLIENT, properties['type']) + self._add_invite(dispatch_operation_path, handle, handler, properties) + + def __error_handler_cb(self, handle, channel_properties, + dispatch_operation_path, error): + logging.debug('__error_handler_cb %r', error) + exception_name = 'org.freedesktop.Telepathy.Error.NotAvailable' + if error.get_dbus_name() == exception_name: + self._dispatch_non_sugar_invitation(handle, + channel_properties, + dispatch_operation_path) + else: + raise error + + def _dispatch_non_sugar_invitation(self, handle, channel_properties, + dispatch_operation_path): + handler = None + channel_type = channel_properties[CHANNEL + '.ChannelType'] + if channel_type == CHANNEL_TYPE_CONTACT_LIST: + self._handle_with(dispatch_operation_path, CLIENT + '.Sugar') + elif channel_type == CHANNEL_TYPE_TEXT: + handler = CLIENT + '.org.laptop.Chat' + elif channel_type == CHANNEL_TYPE_STREAMED_MEDIA: + handler = CLIENT + '.org.laptop.VideoChat' + elif channel_type == CHANNEL_TYPE_DBUS_TUBE: + handler = channel_properties[CHANNEL_TYPE_DBUS_TUBE + + '.ServiceName'] + elif channel_type == CHANNEL_TYPE_STREAM_TUBE: + handler = channel_properties[CHANNEL_TYPE_STREAM_TUBE + '.Service'] + else: + self._handle_with(dispatch_operation_path, '') + + if handler is not None: + logging.debug('Adding an invite from a non-Sugar client') + self._add_invite(dispatch_operation_path, handle, handler) + + def _handle_with(self, dispatch_operation_path, handler): + logging.debug('_handle_with %r %r', dispatch_operation_path, handler) + bus = dbus.Bus() + obj = bus.get_object(CHANNEL_DISPATCHER, dispatch_operation_path) + dispatch_operation = dbus.Interface(obj, CHANNEL_DISPATCH_OPERATION) + dispatch_operation.HandleWith(handler, + reply_handler=self.__handle_with_reply_cb, + error_handler=self.__handle_with_reply_cb) + + def __handle_with_reply_cb(self, error=None): + if error is not None: + logging.error('__handle_with_reply_cb %r', error) + else: + logging.debug('__handle_with_reply_cb') + + def _add_invite(self, dispatch_operation_path, handle, handler, + activity_properties=None): + logging.debug('_add_invite %r %r %r', dispatch_operation_path, handle, + handler) + if dispatch_operation_path in self._dispatch_operations: + # there is no point to have more than one invite for the same + # dispatch operation return - invite = PrivateInvite(bundle_id, private_channel) - self._dict[private_channel] = invite + invite = ActivityInvite(dispatch_operation_path, handle, handler, + activity_properties) + self._dispatch_operations[dispatch_operation_path] = invite self.emit('invite-added', invite) def remove_invite(self, invite): - del self._dict[invite.get_activity_id()] - self.emit('invite-removed', invite) - - def remove_private_invite(self, invite): - del self._dict[invite.get_private_channel()] + del self._dispatch_operations[invite.dispatch_operation_path] self.emit('invite-removed', invite) - def remove_activity(self, activity_id): - invite = self._dict.get(activity_id) - if invite is not None: - self.remove_invite(invite) + def __iter__(self): + return self._dispatch_operations.values().__iter__() - def remove_private_channel(self, private_channel): - invite = self._dict.get(private_channel) - if invite is not None: - self.remove_private_invite(invite) - def _owner_joined_cb(self, owner, activity): - self.remove_activity(activity.props.id) +_instance = None - def __iter__(self): - return self._dict.values().__iter__() +def get_instance(): + global _instance + if not _instance: + _instance = Invites() + return _instance diff --git a/src/jarabe/model/neighborhood.py b/src/jarabe/model/neighborhood.py index 53e5581..90531a6 100644 --- a/src/jarabe/model/neighborhood.py +++ b/src/jarabe/model/neighborhood.py @@ -1,4 +1,4 @@ -# Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2010 Collabora Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -14,255 +14,845 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +import logging +from functools import partial + import gobject import gconf -import logging +import dbus +from dbus import PROPERTIES_IFACE +from telepathy.interfaces import ACCOUNT, \ + ACCOUNT_MANAGER, \ + CHANNEL, \ + CHANNEL_INTERFACE_GROUP, \ + CHANNEL_TYPE_CONTACT_LIST, \ + CONNECTION, \ + CONNECTION_INTERFACE_ALIASING, \ + CONNECTION_INTERFACE_CONTACTS, \ + CONNECTION_INTERFACE_REQUESTS, \ + CONNECTION_INTERFACE_SIMPLE_PRESENCE +from telepathy.constants import HANDLE_TYPE_LIST, \ + CONNECTION_PRESENCE_TYPE_OFFLINE, \ + CONNECTION_STATUS_CONNECTED, \ + CONNECTION_STATUS_DISCONNECTED +from telepathy.client import Connection, Channel from sugar.graphics.xocolor import XoColor -from sugar.presence import presenceservice -from sugar import activity +from sugar.profile import get_profile -from jarabe.model.buddy import BuddyModel +from jarabe.model.buddy import BuddyModel, get_owner_instance from jarabe.model import bundleregistry -from jarabe.util.telepathy import connection_watcher -from dbus import PROPERTIES_IFACE -from telepathy.interfaces import CONNECTION_INTERFACE_REQUESTS -from telepathy.interfaces import CHANNEL_INTERFACE -from telepathy.client import Channel +ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager' +ACCOUNT_MANAGER_PATH = '/org/freedesktop/Telepathy/AccountManager' +CHANNEL_DISPATCHER_SERVICE = 'org.freedesktop.Telepathy.ChannelDispatcher' +CHANNEL_DISPATCHER_PATH = '/org/freedesktop/Telepathy/ChannelDispatcher' +SUGAR_CLIENT_SERVICE = 'org.freedesktop.Telepathy.Client.Sugar' +SUGAR_CLIENT_PATH = '/org/freedesktop/Telepathy/Client/Sugar' -CONN_INTERFACE_GADGET = 'org.laptop.Telepathy.Gadget' -CHAN_INTERFACE_VIEW = 'org.laptop.Telepathy.Channel.Interface.View' -CHAN_INTERFACE_BUDBY_VIEW = 'org.laptop.Telepathy.Channel.Type.BuddyView' -CHAN_INTERFACE_ACTIVITY_VIEW = 'org.laptop.Telepathy.Channel.Type.ActivityView' +CONNECTION_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo' +CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \ + 'org.laptop.Telepathy.ActivityProperties' -NB_RANDOM_BUDDIES = 20 -NB_RANDOM_ACTIVITIES = 40 +class ActivityModel(gobject.GObject): + __gsignals__ = { + 'current-buddy-added': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object])), + 'current-buddy-removed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object])), + 'buddy-added': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object])), + 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object])), + } + def __init__(self, activity_id, room_handle): + gobject.GObject.__init__(self) -class ActivityModel: - def __init__(self, act, bundle): - self.activity = act - self.bundle = bundle + self.activity_id = activity_id + self.room_handle = room_handle + self._bundle = None + self._color = None + self._private = True + self._name = None + self._current_buddies = [] + self._buddies = [] - def get_id(self): - return self.activity.props.id + def get_color(self): + return self._color - def get_icon_name(self): - return self.bundle.get_icon() + def set_color(self, color): + self._color = color - def get_color(self): - return XoColor(self.activity.props.color) + color = gobject.property(type=object, getter=get_color, setter=set_color) + + def get_bundle(self): + return self._bundle + + def set_bundle(self, bundle): + self._bundle = bundle + + bundle = gobject.property(type=object, getter=get_bundle, setter=set_bundle) + + def get_name(self): + return self._name + + def set_name(self, name): + self._name = name + + name = gobject.property(type=object, getter=get_name, setter=set_name) + + def is_private(self): + return self._private + + def set_private(self, private): + self._private = private + + private = gobject.property(type=object, getter=is_private, + setter=set_private) + + def get_buddies(self): + return self._buddies + + def add_buddy(self, buddy): + self._buddies.append(buddy) + self.notify('buddies') + self.emit('buddy-added', buddy) + + def remove_buddy(self, buddy): + self._buddies.remove(buddy) + self.notify('buddies') + self.emit('buddy-removed', buddy) + + buddies = gobject.property(type=object, getter=get_buddies) + + def get_current_buddies(self): + return self._current_buddies + + def add_current_buddy(self, buddy): + self._current_buddies.append(buddy) + self.notify('current-buddies') + self.emit('current-buddy-added', buddy) + + def remove_current_buddy(self, buddy): + self._current_buddies.remove(buddy) + self.notify('current-buddies') + self.emit('current-buddy-removed', buddy) + + current_buddies = gobject.property(type=object, getter=get_current_buddies) + +class _Account(gobject.GObject): + __gsignals__ = { + 'activity-added': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object, object])), + 'activity-updated': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object, object])), + 'activity-removed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object])), + 'buddy-added': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object, object, object])), + 'buddy-updated': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object, object])), + 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object])), + 'buddy-joined-activity': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object, object])), + 'buddy-left-activity': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object, object])), + 'current-activity-updated': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object, object])), + 'connected': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([])), + 'disconnected': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([])), + } + + def __init__(self, account_path): + gobject.GObject.__init__(self) + + self.object_path = account_path + + self._connection = None + self._buddy_handles = {} + self._activity_handles = {} + self._self_handle = None + + self._buddies_per_activity = {} + self._activities_per_buddy = {} + + self._start_listening() + + def _start_listening(self): + bus = dbus.Bus() + obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, self.object_path) + obj.Get(ACCOUNT, 'Connection', + reply_handler=self.__got_connection_cb, + error_handler=partial(self.__error_handler_cb, + 'Account.GetConnection')) + obj.connect_to_signal( + 'AccountPropertyChanged', self.__account_property_changed_cb) + + def __error_handler_cb(self, function_name, error): + raise RuntimeError('Error when calling %s: %s' % (function_name, error)) + + def __got_connection_cb(self, connection_path): + logging.debug('_Account.__got_connection_cb %r', connection_path) + + if connection_path == '/': + # Account has no connection, wait until it has one. + return + + self._prepare_connection(connection_path) + + def __account_property_changed_cb(self, properties): + logging.debug('_Account.__account_property_changed_cb %r %r %r', + self.object_path, properties.get('Connection', None), + self._connection) + if 'Connection' not in properties: + return + if properties['Connection'] == '/': + self._connection = None + elif self._connection is None: + self._prepare_connection(properties['Connection']) + + def _prepare_connection(self, connection_path): + connection_name = connection_path.replace('/', '.')[1:] + + self._connection = Connection(connection_name, connection_path, + ready_handler=self.__connection_ready_cb) + + def __connection_ready_cb(self, connection): + logging.debug('_Account.__connection_ready_cb %r', + connection.object_path) + connection.connect_to_signal('StatusChanged', + self.__status_changed_cb) + + connection[PROPERTIES_IFACE].Get(CONNECTION, + 'Status', + reply_handler=self.__get_status_cb, + error_handler=partial(self.__error_handler_cb, + 'Connection.GetStatus')) + + def __get_status_cb(self, status): + logging.debug('_Account.__get_status_cb %r %r', + self._connection.object_path, status) + self._update_status(status) + + def __status_changed_cb(self, status, reason): + logging.debug('_Account.__status_changed_cb %r %r', status, reason) + self._update_status(status) + + def _update_status(self, status): + if status == CONNECTION_STATUS_CONNECTED: + self._connection[PROPERTIES_IFACE].Get(CONNECTION, + 'SelfHandle', + reply_handler=self.__get_self_handle_cb, + error_handler=partial(self.__error_handler_cb, + 'Connection.GetSelfHandle')) + self.emit('connected') + else: + for contact_handle, contact_id in self._buddy_handles.items(): + self.emit('buddy-removed', contact_id) + + for room_handle, activity_id in self._activity_handles.items(): + self.emit('activity-removed', activity_id) + + self._buddy_handles = {} + self._activity_handles = {} + self._buddies_per_activity = {} + self._activities_per_buddy = {} + + self.emit('disconnected') + + if status == CONNECTION_STATUS_DISCONNECTED: + self._connection = None + + def __get_self_handle_cb(self, self_handle): + self._self_handle = self_handle + + connection = self._connection[CONNECTION_INTERFACE_ALIASING] + connection.connect_to_signal('AliasesChanged', + self.__aliases_changed_cb) - def get_bundle_id(self): - return self.bundle.get_bundle_id() + connection = self._connection[CONNECTION_INTERFACE_SIMPLE_PRESENCE] + connection.connect_to_signal('PresencesChanged', + self.__presences_changed_cb) + + if CONNECTION_INTERFACE_BUDDY_INFO in self._connection: + connection = self._connection[CONNECTION_INTERFACE_BUDDY_INFO] + connection.connect_to_signal('PropertiesChanged', + self.__buddy_info_updated_cb, + byte_arrays=True) + + connection.connect_to_signal('ActivitiesChanged', + self.__buddy_activities_changed_cb) + + connection.connect_to_signal('CurrentActivityChanged', + self.__current_activity_changed_cb) + else: + logging.warning('Connection %s does not support OLPC buddy ' + 'properties', self._connection.object_path) + + if CONNECTION_INTERFACE_ACTIVITY_PROPERTIES in self._connection: + connection = self._connection[ + CONNECTION_INTERFACE_ACTIVITY_PROPERTIES] + connection.connect_to_signal( + 'ActivityPropertiesChanged', + self.__activity_properties_changed_cb) + else: + logging.warning('Connection %s does not support OLPC activity ' + 'properties', self._connection.object_path) + + properties = { + CHANNEL + '.ChannelType': CHANNEL_TYPE_CONTACT_LIST, + CHANNEL + '.TargetHandleType': HANDLE_TYPE_LIST, + CHANNEL + '.TargetID': 'subscribe', + } + properties = dbus.Dictionary(properties, signature='sv') + connection = self._connection[CONNECTION_INTERFACE_REQUESTS] + is_ours, channel_path, properties = \ + connection.EnsureChannel(properties) + + channel = Channel(self._connection.service_name, channel_path) + channel[CHANNEL_INTERFACE_GROUP].connect_to_signal( + 'MembersChanged', self.__members_changed_cb) + + channel[PROPERTIES_IFACE].Get(CHANNEL_INTERFACE_GROUP, + 'Members', + reply_handler=self.__get_members_ready_cb, + error_handler=partial(self.__error_handler_cb, + 'Connection.GetMembers')) + + def __aliases_changed_cb(self, aliases): + logging.debug('_Account.__aliases_changed_cb') + for handle, alias in aliases: + if handle in self._buddy_handles: + logging.debug('Got handle %r with nick %r, going to update', + handle, alias) + + def __presences_changed_cb(self, presences): + logging.debug('_Account.__presences_changed_cb %r', presences) + for handle, presence in presences.iteritems(): + if handle in self._buddy_handles: + presence_type, status_, message_ = presence + if presence_type == CONNECTION_PRESENCE_TYPE_OFFLINE: + del self._buddy_handles[handle] + self.emit('buddy-removed', handle) + + def __buddy_info_updated_cb(self, handle, properties): + logging.debug('_Account.__buddy_info_updated_cb %r %r', handle, + properties) + + def __current_activity_changed_cb(self, contact_handle, activity_id, + room_handle): + logging.debug('_Account.__current_activity_changed_cb %r %r %r', + contact_handle, activity_id, room_handle) + if contact_handle in self._buddy_handles: + contact_id = self._buddy_handles[contact_handle] + if not activity_id and room_handle: + activity_id = self._activity_handles.get(room_handle, '') + self.emit('current-activity-updated', contact_id, activity_id) + + def __get_current_activity_cb(self, contact_handle, activity_id, + room_handle): + logging.debug('_Account.__get_current_activity_cb %r %r %r', + contact_handle, activity_id, room_handle) + contact_id = self._buddy_handles[contact_handle] + self.emit('current-activity-updated', contact_id, activity_id) + + def __buddy_activities_changed_cb(self, buddy_handle, activities): + logging.debug('_Account.__buddy_activities_changed_cb %r %r', + buddy_handle, activities) + self._update_buddy_activities(buddy_handle, activities) + + def _update_buddy_activities(self, buddy_handle, activities): + logging.debug('_Account._update_buddy_activities') + if not buddy_handle in self._buddy_handles: + self._buddy_handles[buddy_handle] = None + + if not buddy_handle in self._activities_per_buddy: + self._activities_per_buddy[buddy_handle] = set() + + for activity_id, room_handle in activities: + if room_handle not in self._activity_handles: + self._activity_handles[room_handle] = activity_id + self.emit('activity-added', room_handle, activity_id) + + connection = self._connection[ + CONNECTION_INTERFACE_ACTIVITY_PROPERTIES] + connection.GetProperties(room_handle, + reply_handler=partial(self.__get_properties_cb, + room_handle), + error_handler=partial(self.__error_handler_cb, + 'ActivityProperties.GetProperties')) + + # Sometimes we'll get CurrentActivityChanged before we get to + # know about the activity so we miss the event. In that case, + # request again the current activity for this buddy. + connection = self._connection[CONNECTION_INTERFACE_BUDDY_INFO] + connection.GetCurrentActivity( + buddy_handle, + reply_handler=partial(self.__get_current_activity_cb, + buddy_handle), + error_handler=partial(self.__error_handler_cb, + 'BuddyInfo.GetCurrentActivity')) + + if not activity_id in self._buddies_per_activity: + self._buddies_per_activity[activity_id] = set() + self._buddies_per_activity[activity_id].add(buddy_handle) + if activity_id not in self._activities_per_buddy[buddy_handle]: + self._activities_per_buddy[buddy_handle].add(activity_id) + if self._buddy_handles[buddy_handle] is not None: + self.emit('buddy-joined-activity', + self._buddy_handles[buddy_handle], + activity_id) + + current_activity_ids = \ + [activity_id for activity_id, room_handle in activities] + for activity_id in self._activities_per_buddy[buddy_handle].copy(): + if not activity_id in current_activity_ids: + self._remove_buddy_from_activity(buddy_handle, activity_id) + + def __get_properties_cb(self, room_handle, properties): + logging.debug('_Account.__get_properties_cb %r %r', room_handle, + properties) + if properties: + self._update_activity(room_handle, properties) + + def _remove_buddy_from_activity(self, buddy_handle, activity_id): + if buddy_handle in self._buddies_per_activity[activity_id]: + self._buddies_per_activity[activity_id].remove(buddy_handle) + + if activity_id in self._activities_per_buddy[buddy_handle]: + self._activities_per_buddy[buddy_handle].remove(activity_id) + + if self._buddy_handles[buddy_handle] is not None: + self.emit('buddy-left-activity', + self._buddy_handles[buddy_handle], + activity_id) + + if not self._buddies_per_activity[activity_id]: + del self._buddies_per_activity[activity_id] + + for room_handle in self._activity_handles.copy(): + if self._activity_handles[room_handle] == activity_id: + del self._activity_handles[room_handle] + break + + self.emit('activity-removed', activity_id) + + def __activity_properties_changed_cb(self, room_handle, properties): + logging.debug('_Account.__activity_properties_changed_cb %r %r', + room_handle, properties) + self._update_activity(room_handle, properties) + + def _update_activity(self, room_handle, properties): + if room_handle in self._activity_handles: + self.emit('activity-updated', self._activity_handles[room_handle], + properties) + else: + logging.debug('_Account.__activity_properties_changed_cb unknown ' + 'activity') + # We don't get ActivitiesChanged for the owner of the connection, + # so we query for its activities in order to find out. + if CONNECTION_INTERFACE_BUDDY_INFO in self._connection: + handle = self._self_handle + connection = self._connection[CONNECTION_INTERFACE_BUDDY_INFO] + connection.GetActivities( + handle, + reply_handler=partial(self.__got_activities_cb, handle), + error_handler=partial(self.__error_handler_cb, + 'BuddyInfo.Getactivities')) + + def __members_changed_cb(self, message, added, removed, local_pending, + remote_pending, actor, reason): + self._add_buddy_handles(added) + + def __get_members_ready_cb(self, handles): + logging.debug('_Account.__get_members_ready_cb %r', handles) + if not handles: + return + + self._add_buddy_handles(handles) + + def _add_buddy_handles(self, handles): + logging.debug('_Account._add_buddy_handles %r', handles) + interfaces = [CONNECTION, CONNECTION_INTERFACE_ALIASING] + self._connection[CONNECTION_INTERFACE_CONTACTS].GetContactAttributes( + handles, interfaces, False, + reply_handler=self.__get_contact_attributes_cb, + error_handler=partial(self.__error_handler_cb, + 'Contacts.GetContactAttributes')) + + def __got_buddy_info_cb(self, handle, nick, properties): + logging.debug('_Account.__got_buddy_info_cb %r', properties) + self.emit('buddy-added', self._buddy_handles[handle], nick, + properties.get('key', None)) + self.emit('buddy-updated', self._buddy_handles[handle], properties) + + def __get_contact_attributes_cb(self, attributes): + logging.debug('_Account.__get_contact_attributes_cb %r', + attributes.keys()) + + for handle in attributes.keys(): + nick = attributes[handle][CONNECTION_INTERFACE_ALIASING + '/alias'] + + if handle in self._buddy_handles and \ + not self._buddy_handles[handle] is None: + logging.debug('Got handle %r with nick %r, going to update', + handle, nick) + self.emit('buddy-updated', self._buddy_handles[handle], + attributes[handle]) + else: + logging.debug('Got handle %r with nick %r, going to add', + handle, nick) + + contact_id = attributes[handle][CONNECTION + '/contact-id'] + self._buddy_handles[handle] = contact_id + + if CONNECTION_INTERFACE_BUDDY_INFO in self._connection: + connection = \ + self._connection[CONNECTION_INTERFACE_BUDDY_INFO] + + connection.GetProperties( + handle, + reply_handler=partial(self.__got_buddy_info_cb, handle, + nick), + error_handler=partial(self.__error_handler_cb, + 'BuddyInfo.GetProperties'), + byte_arrays=True) + + connection.GetActivities( + handle, + reply_handler=partial(self.__got_activities_cb, handle), + error_handler=partial(self.__error_handler_cb, + 'BuddyInfo.GetActivities')) + + connection.GetCurrentActivity( + handle, + reply_handler=partial(self.__get_current_activity_cb, + handle), + error_handler=partial(self.__error_handler_cb, + 'BuddyInfo.GetCurrentActivity')) + else: + self.emit('buddy-added', contact_id, nick, None) + + def __got_activities_cb(self, buddy_handle, activities): + logging.debug('_Account.__got_activities_cb %r %r', buddy_handle, + activities) + self._update_buddy_activities(buddy_handle, activities) + + def enable(self): + logging.debug('_Account.enable %s', self.object_path) + self._set_enabled(True) + + def disable(self): + logging.debug('_Account.disable %s', self.object_path) + self._set_enabled(False) + self._connection = None + + def _set_enabled(self, value): + bus = dbus.Bus() + obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, self.object_path) + obj.Set(ACCOUNT, 'Enabled', value, + reply_handler=self.__set_enabled_cb, + error_handler=partial(self.__error_handler_cb, + 'Account.SetEnabled'), + dbus_interface='org.freedesktop.DBus.Properties') + + def __set_enabled_cb(self): + logging.debug('_Account.__set_enabled_cb success') class Neighborhood(gobject.GObject): __gsignals__ = { 'activity-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), + gobject.TYPE_NONE, ([object])), 'activity-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), + gobject.TYPE_NONE, ([object])), 'buddy-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), - 'buddy-moved': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT, - gobject.TYPE_PYOBJECT])), + gobject.TYPE_NONE, ([object])), 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])) + gobject.TYPE_NONE, ([object])) } def __init__(self): gobject.GObject.__init__(self) + self._buddies = {None: get_owner_instance()} self._activities = {} - self._buddies = {} - - self._pservice = presenceservice.get_instance() - self._pservice.connect("activity-appeared", - self._activity_appeared_cb) - self._pservice.connect('activity-disappeared', - self._activity_disappeared_cb) - self._pservice.connect("buddy-appeared", - self._buddy_appeared_cb) - self._pservice.connect("buddy-disappeared", - self._buddy_disappeared_cb) - - # Add any buddies the PS knows about already - self._pservice.get_buddies_async(reply_handler=self._get_buddies_cb) - - self._pservice.get_activities_async( - reply_handler=self._get_activities_cb) - - self._conn_watcher = connection_watcher.ConnectionWatcher() - self._conn_watcher.connect('connection-added', self.__conn_addded_cb) - - for conn in self._conn_watcher.get_connections(): - self.__conn_addded_cb(self._conn_watcher, conn) - - gconf_client = gconf.client_get_default() - gconf_client.add_dir('/desktop/sugar/collaboration', - gconf.CLIENT_PRELOAD_NONE) - gconf_client.notify_add('/desktop/sugar/collaboration/publish_gadget', - self.__publish_gadget_changed_cb) - - def __conn_addded_cb(self, watcher, conn): - if CONN_INTERFACE_GADGET not in conn: + self._link_local_account = None + self._server_account = None + + bus = dbus.Bus() + obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH) + account_manager = dbus.Interface(obj, ACCOUNT_MANAGER) + account_manager.Get(ACCOUNT_MANAGER, 'ValidAccounts', + dbus_interface=PROPERTIES_IFACE, + reply_handler=self.__got_accounts_cb, + error_handler=self.__error_handler_cb) + + def __got_accounts_cb(self, account_paths): + self._link_local_account = \ + self._ensure_link_local_account(account_paths) + self._connect_to_account(self._link_local_account) + + self._server_account = self._ensure_server_account(account_paths) + self._connect_to_account(self._server_account) + + def __error_handler_cb(self, error): + raise RuntimeError(error) + + def _connect_to_account(self, account): + account.connect('buddy-added', self.__buddy_added_cb) + account.connect('buddy-updated', self.__buddy_updated_cb) + account.connect('buddy-removed', self.__buddy_removed_cb) + account.connect('buddy-joined-activity', + self.__buddy_joined_activity_cb) + account.connect('buddy-left-activity', self.__buddy_left_activity_cb) + account.connect('activity-added', self.__activity_added_cb) + account.connect('activity-updated', self.__activity_updated_cb) + account.connect('activity-removed', self.__activity_removed_cb) + account.connect('current-activity-updated', + self.__current_activity_updated_cb) + account.connect('connected', self.__account_connected_cb) + account.connect('disconnected', self.__account_disconnected_cb) + + def __account_connected_cb(self, account): + logging.debug('__account_connected_cb %s', account.object_path) + if account == self._server_account: + self._link_local_account.disable() + + def __account_disconnected_cb(self, account): + logging.debug('__account_disconnected_cb %s', account.object_path) + if account == self._server_account: + self._link_local_account.enable() + + def _ensure_link_local_account(self, account_paths): + for account_path in account_paths: + if 'salut' in account_path: + logging.debug('Already have a Salut account') + account = _Account(account_path) + account.enable() + return account + + logging.debug('Still dont have a Salut account, creating one') + + client = gconf.client_get_default() + nick = client.get_string('/desktop/sugar/user/nick') + server = client.get_string('/desktop/sugar/collaboration/jabber_server') + + params = { + 'nickname': nick, + 'first-name': '', + 'last-name': '', + 'jid': '%s@%s' % (self._sanitize_nick(nick), server), + 'published-name': nick, + } + + properties = { + 'org.freedesktop.Telepathy.Account.Enabled': True, + 'org.freedesktop.Telepathy.Account.Nickname': nick, + 'org.freedesktop.Telepathy.Account.ConnectAutomatically': True, + } + + bus = dbus.Bus() + obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH) + account_manager = dbus.Interface(obj, ACCOUNT_MANAGER) + account_path = account_manager.CreateAccount('salut', 'local-xmpp', + 'salut', params, + properties) + return _Account(account_path) + + def _ensure_server_account(self, account_paths): + for account_path in account_paths: + if 'gabble' in account_path: + logging.debug('Already have a Gabble account') + account = _Account(account_path) + account.enable() + return account + + logging.debug('Still dont have a Gabble account, creating one') + + client = gconf.client_get_default() + nick = client.get_string('/desktop/sugar/user/nick') + server = client.get_string('/desktop/sugar/collaboration/jabber_server') + key_hash = get_profile().privkey_hash + + params = { + 'account': '%s@%s' % (self._sanitize_nick(nick), server), + 'password': key_hash, + 'server': server, + 'resource': 'sugar', + 'require-encryption': True, + 'ignore-ssl-errors': True, + 'register': True, + 'old-ssl': True, + 'port': dbus.UInt32(5223), + } + + properties = { + 'org.freedesktop.Telepathy.Account.Enabled': True, + 'org.freedesktop.Telepathy.Account.Nickname': nick, + 'org.freedesktop.Telepathy.Account.ConnectAutomatically': True, + } + + bus = dbus.Bus() + obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH) + account_manager = dbus.Interface(obj, ACCOUNT_MANAGER) + account_path = account_manager.CreateAccount('gabble', 'jabber', + 'jabber', params, + properties) + return _Account(account_path) + + def _sanitize_nick(self, nick): + return nick.replace(' ', '_') + + def __buddy_added_cb(self, account, contact_id, nick, key): + logging.debug('__buddy_added_cb %r', contact_id) + + if contact_id in self._buddies: + logging.debug('__buddy_added_cb buddy already tracked') return - conn[CONN_INTERFACE_GADGET].connect_to_signal('GadgetDiscovered', - lambda: self._gadget_discovered(conn)) - - gadget_discovered = conn[PROPERTIES_IFACE].Get(CONN_INTERFACE_GADGET, - 'GadgetAvailable') - if gadget_discovered: - self._gadget_discovered(conn) - - def _gadget_discovered(self, conn): - gconf_client = gconf.client_get_default() - key = '/desktop/sugar/collaboration/publish_gadget' - publish = gconf_client.get_bool(key) - logging.debug('Gadget discovered on connection %s.' - ' Publish our status: %r', conn.service_name.split('.')[-1], - publish) - conn[CONN_INTERFACE_GADGET].Publish(publish) - - self._request_random_buddies(conn, NB_RANDOM_BUDDIES) - self._request_random_activities(conn, NB_RANDOM_ACTIVITIES) - - def _request_random_buddies(self, conn, nb): - logging.debug('Request %d random buddies', nb) - - path, props_ = conn[CONNECTION_INTERFACE_REQUESTS].CreateChannel( - { 'org.freedesktop.Telepathy.Channel.ChannelType': - 'org.laptop.Telepathy.Channel.Type.BuddyView', - 'org.laptop.Telepathy.Channel.Interface.View.MaxSize': nb - }) - - view = Channel(conn.service_name, path) - view[CHANNEL_INTERFACE].connect_to_signal('Closed', - lambda: self.__respawnable_view_closed_cb( - lambda: self._request_random_buddies(conn, nb))) - - def _request_random_activities(self, conn, nb): - logging.debug('Request %d random activities', nb) - - path, props_ = conn[CONNECTION_INTERFACE_REQUESTS].CreateChannel( - { 'org.freedesktop.Telepathy.Channel.ChannelType': - 'org.laptop.Telepathy.Channel.Type.ActivityView', - 'org.laptop.Telepathy.Channel.Interface.View.MaxSize': nb - }) - - view = Channel(conn.service_name, path) - view[CHANNEL_INTERFACE].connect_to_signal('Closed', - lambda: self.__respawnable_view_closed_cb( - lambda: self._request_random_activities(conn, nb))) - - def __publish_gadget_changed_cb(self, client_, cnxn_id_, entry, - user_data=None): - if entry.value.type == gconf.VALUE_BOOL: - publish = entry.value.get_bool() - - for conn in self._conn_watcher.get_connections(): - if CONN_INTERFACE_GADGET not in conn: - continue - - gadget_discovered = conn[PROPERTIES_IFACE].Get( - CONN_INTERFACE_GADGET, 'GadgetAvailable') - if gadget_discovered: - logging.debug("publish_gadget gconf key changed." - " Publish our status on %s: %r" % - (conn.service_name.split('.')[-1], publish)) - conn[CONN_INTERFACE_GADGET].Publish(publish) - - def __respawnable_view_closed_cb(self, request_fct): - # Views are closed if the Gadget component is restarted. As we always - # want to have the random views opened, we re-request them if they are - # closed. - logging.debug('View closed. Re-request it') - request_fct() - - def _get_buddies_cb(self, buddy_list): - for buddy in buddy_list: - self._buddy_appeared_cb(self._pservice, buddy) - - def _get_activities_cb(self, activity_list): - for act in activity_list: - self._check_activity(act) - - def get_activities(self): - return self._activities.values() + buddy = BuddyModel( + nick=nick, + account=account.object_path, + contact_id=contact_id, + key=key) + self._buddies[contact_id] = buddy - def get_buddies(self): - return self._buddies.values() + self.emit('buddy-added', buddy) - def _buddy_activity_changed_cb(self, model, cur_activity): - if not self._buddies.has_key(model.get_buddy().object_path()): + def __buddy_updated_cb(self, account, contact_id, properties): + logging.debug('__buddy_updated_cb %r %r', contact_id, properties) + if contact_id not in self._buddies: + logging.debug('__buddy_updated_cb Unknown buddy with contact_id %r', + contact_id) return - if cur_activity and self._activities.has_key(cur_activity.props.id): - activity_model = self._activities[cur_activity.props.id] - self.emit('buddy-moved', model, activity_model) - else: - self.emit('buddy-moved', model, None) - def _buddy_appeared_cb(self, pservice, buddy): - if self._buddies.has_key(buddy.object_path()): - return + buddy = self._buddies[contact_id] + if 'color' in properties: + buddy.props.color = XoColor(properties['color']) - model = BuddyModel(buddy=buddy) - model.connect('current-activity-changed', - self._buddy_activity_changed_cb) - self._buddies[buddy.object_path()] = model - self.emit('buddy-added', model) + def __buddy_removed_cb(self, account, contact_id): + logging.debug('Neighborhood.__buddy_removed_cb %r', contact_id) + if contact_id not in self._buddies: + logging.debug('Neighborhood.__buddy_removed_cb Unknown buddy with ' + 'contact_id %r', contact_id) + return - cur_activity = buddy.props.current_activity - if cur_activity: - self._buddy_activity_changed_cb(model, cur_activity) + buddy = self._buddies[contact_id] + del self._buddies[contact_id] + self.emit('buddy-removed', buddy) - def _buddy_disappeared_cb(self, pservice, buddy): - if not self._buddies.has_key(buddy.object_path()): + def __activity_added_cb(self, account, room_handle, activity_id): + logging.debug('__activity_added_cb %r %r', room_handle, activity_id) + if activity_id in self._activities: + logging.debug('__activity_added_cb activity already tracked') return - self.emit('buddy-removed', self._buddies[buddy.object_path()]) - del self._buddies[buddy.object_path()] - def _activity_appeared_cb(self, pservice, act): - self._check_activity(act) + activity = ActivityModel(activity_id, room_handle) + self._activities[activity_id] = activity + + def __activity_updated_cb(self, account, activity_id, properties): + logging.debug('__activity_updated_cb %r %r', activity_id, properties) + if activity_id not in self._activities: + logging.debug('__activity_updated_cb Unknown activity with ' + 'activity_id %r', activity_id) + return - def _check_activity(self, presence_activity): registry = bundleregistry.get_registry() - bundle = registry.get_bundle(presence_activity.props.type) + bundle = registry.get_bundle(properties['type']) if not bundle: + logging.warning('Ignoring shared activity we don''t have') return - if self.has_activity(presence_activity.props.id): + + activity = self._activities[activity_id] + + is_new = activity.props.bundle is None + + activity.props.color = XoColor(properties['color']) + activity.props.bundle = bundle + activity.props.name = properties['name'] + activity.props.private = properties['private'] + + if is_new: + self.emit('activity-added', activity) + + def __activity_removed_cb(self, account, activity_id): + logging.debug('__activity_removed_cb %r', activity_id) + if activity_id not in self._activities: + logging.debug('Unknown activity with id %s. Already removed?', + activity_id) + return + activity = self._activities[activity_id] + del self._activities[activity_id] + self.emit('activity-removed', activity) + + def __current_activity_updated_cb(self, account, contact_id, activity_id): + logging.debug('__current_activity_updated_cb %r %r', contact_id, + activity_id) + if contact_id not in self._buddies: + logging.debug('__current_activity_updated_cb Unknown buddy with ' + 'contact_id %r', contact_id) + return + if activity_id and activity_id not in self._activities: + logging.debug('__current_activity_updated_cb Unknown activity with ' + 'id %s', activity_id) + activity_id = '' + + buddy = self._buddies[contact_id] + if buddy.props.current_activity is not None: + if buddy.props.current_activity.activity_id == activity_id: + return + buddy.props.current_activity.remove_current_buddy(buddy) + + if activity_id: + activity = self._activities[activity_id] + buddy.props.current_activity = activity + activity.add_current_buddy(buddy) + else: + buddy.props.current_activity = None + + def __buddy_joined_activity_cb(self, account, contact_id, activity_id): + if contact_id not in self._buddies: + logging.debug('__buddy_joined_activity_cb Unknown buddy with ' + 'contact_id %r', contact_id) + return + + if activity_id not in self._activities: + logging.debug('__buddy_joined_activity_cb Unknown activity with ' + 'activity_id %r', activity_id) + return + + self._activities[activity_id].add_buddy(self._buddies[contact_id]) + + def __buddy_left_activity_cb(self, account, contact_id, activity_id): + if contact_id not in self._buddies: + logging.debug('__buddy_left_activity_cb Unknown buddy with ' + 'contact_id %r', contact_id) + return + + if activity_id not in self._activities: + logging.debug('__buddy_left_activity_cb Unknown activity with ' + 'activity_id %r', activity_id) return - self.add_activity(bundle, presence_activity) - def has_activity(self, activity_id): - return self._activities.has_key(activity_id) + self._activities[activity_id].remove_buddy(self._buddies[contact_id]) + + def get_buddies(self): + return self._buddies.values() def get_activity(self, activity_id): - if self.has_activity(activity_id): - return self._activities[activity_id] - else: - return None - - def add_activity(self, bundle, act): - model = ActivityModel(act, bundle) - self._activities[model.get_id()] = model - self.emit('activity-added', model) - - for buddy in self._pservice.get_buddies(): - cur_activity = buddy.props.current_activity - object_path = buddy.object_path() - if cur_activity == activity and object_path in self._buddies: - buddy_model = self._buddies[object_path] - self.emit('buddy-moved', buddy_model, model) - - def _activity_disappeared_cb(self, pservice, act): - if self._activities.has_key(act.props.id): - activity_model = self._activities[act.props.id] - self.emit('activity-removed', activity_model) - del self._activities[act.props.id] + return self._activities.get(activity_id, None) + + def get_activity_by_room(self, room_handle): + for activity in self._activities.values(): + if activity.room_handle == room_handle: + return activity + return None + + def get_activities(self): + return self._activities.values() _model = None diff --git a/src/jarabe/model/notifications.py b/src/jarabe/model/notifications.py index 10a0237..f2e2d65 100644 --- a/src/jarabe/model/notifications.py +++ b/src/jarabe/model/notifications.py @@ -43,7 +43,7 @@ class NotificationService(dbus.service.Object): hints, expire_timeout): logging.debug('Received notification: %r', [app_name, replaces_id, - app_icon, summary, body, actions, hints, expire_timeout]) + '', summary, body, actions, '', expire_timeout]) if replaces_id > 0: notification_id = replaces_id diff --git a/src/jarabe/model/owner.py b/src/jarabe/model/owner.py deleted file mode 100644 index 17996e6..0000000 --- a/src/jarabe/model/owner.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright (C) 2006-2007 Red Hat, Inc. -# Copyright (C) 2008 One Laptop Per Child -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -import gobject -import os -import gconf -import simplejson - -from telepathy.interfaces import CHANNEL_TYPE_TEXT - -from sugar import env -from sugar.presence import presenceservice -from sugar import util -from jarabe.model.invites import Invites - -class Owner(gobject.GObject): - """Class representing the owner of this machine/instance. This class - runs in the shell and serves up the buddy icon and other stuff. It's the - server portion of the Owner, paired with the client portion in Buddy.py. - """ - __gtype_name__ = "ShellOwner" - - __gsignals__ = { - 'nick-changed' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_STRING])), - 'color-changed' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'icon-changed' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) - } - - def __init__(self): - gobject.GObject.__init__(self) - - client = gconf.client_get_default() - self._nick = client.get_string("/desktop/sugar/user/nick") - - self._icon = None - self._icon_hash = "" - icon = os.path.join(env.get_profile_path(), "buddy-icon.jpg") - if not os.path.exists(icon): - raise RuntimeError("missing buddy icon") - - fd = open(icon, "r") - self._icon = fd.read() - fd.close() - if not self._icon: - raise RuntimeError("invalid buddy icon") - - # Get the icon's hash - import hashlib - digest = hashlib.md5(self._icon).digest() - self._icon_hash = util.printable_hash(digest) - - self._pservice = presenceservice.get_instance() - self._pservice.connect('activity-invitation', - self._activity_invitation_cb) - self._pservice.connect('private-invitation', - self._private_invitation_cb) - self._pservice.connect('activity-disappeared', - self._activity_disappeared_cb) - - self._invites = Invites() - - def get_invites(self): - return self._invites - - def get_nick(self): - return self._nick - - def _activity_invitation_cb(self, pservice, activity, buddy, message): - self._invites.add_invite(activity.props.type, - activity.props.id) - - def _private_invitation_cb(self, pservice, bus_name, connection, - channel, channel_type): - """Handle a private-invitation from Presence Service. - - This is a connection by a non-Sugar XMPP client, so - launch Chat or VideoChat with the Telepathy connection and - channel. - """ - if channel_type == CHANNEL_TYPE_TEXT: - bundle_id = 'org.laptop.Chat' - else: - bundle_id = 'org.laptop.VideoChat' - tp_channel = simplejson.dumps([bus_name, connection, channel]) - self._invites.add_private_invite(tp_channel, bundle_id) - - def _activity_disappeared_cb(self, pservice, activity): - self._invites.remove_activity(activity.props.id) - -_model = None - -def get_model(): - global _model - if _model is None: - _model = Owner() - return _model diff --git a/src/jarabe/model/shell.py b/src/jarabe/model/shell.py index 553e889..db0e050 100644 --- a/src/jarabe/model/shell.py +++ b/src/jarabe/model/shell.py @@ -27,9 +27,9 @@ import dbus from sugar import wm from sugar import dispatch from sugar.graphics.xocolor import XoColor -from sugar.presence import presenceservice from jarabe.model.bundleregistry import get_registry +from jarabe.model import neighborhood _SERVICE_NAME = "org.laptop.Activity" _SERVICE_PATH = "/org/laptop/Activity" @@ -145,18 +145,16 @@ class Activity(gobject.GObject): have an entry (implying that this is not a Sugar-shared application) uses the local user's profile colour for the icon. """ - pservice = presenceservice.get_instance() - # HACK to suppress warning in logs when activity isn't found # (if it's locally launched and not shared yet) activity = None - for act in pservice.get_activities(): - if self._activity_id == act.props.id: + for act in neighborhood.get_model().get_activities(): + if self._activity_id == act.activity_id: activity = act break if activity != None: - return XoColor(activity.props.color) + return activity.props.color else: client = gconf.client_get_default() return XoColor(client.get_string("/desktop/sugar/user/color")) @@ -254,10 +252,17 @@ class Activity(gobject.GObject): def _name_owner_changed_cb(self, name, old, new): if name == self._get_service_name(): - self._retrieve_service() - self.set_active(True) - self._name_owner_changed_handler.remove() - self._name_owner_changed_handler = None + if old and not new: + logging.debug('Activity._name_owner_changed_cb: ' \ + 'activity %s went away', name) + self._name_owner_changed_handler.remove() + self._name_owner_changed_handler = None + self._service = None + elif not old and new: + logging.debug('Activity._name_owner_changed_cb: ' \ + 'activity %s started up', name) + self._retrieve_service() + self.set_active(True) def set_active(self, state): """Propagate the current state to the activity object""" @@ -350,7 +355,6 @@ class ShellModel(gobject.GObject): self._activities = [] self._active_activity = None self._tabbing_activity = None - self._pservice = presenceservice.get_instance() self._launchers = {} self._screen.toggle_showing_desktop(True) diff --git a/src/jarabe/model/telepathyclient.py b/src/jarabe/model/telepathyclient.py new file mode 100644 index 0000000..f4eccc3 --- /dev/null +++ b/src/jarabe/model/telepathyclient.py @@ -0,0 +1,100 @@ +# Copyright (C) 2010 Collabora Ltd. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging + +import dbus +from dbus import PROPERTIES_IFACE +from telepathy.interfaces import CLIENT, \ + CLIENT_APPROVER, \ + CLIENT_HANDLER, \ + CLIENT_INTERFACE_REQUESTS +from telepathy.server import DBusProperties + +from sugar import dispatch + +SUGAR_CLIENT_SERVICE = 'org.freedesktop.Telepathy.Client.Sugar' +SUGAR_CLIENT_PATH = '/org/freedesktop/Telepathy/Client/Sugar' + +class TelepathyClient(dbus.service.Object, DBusProperties): + def __init__(self): + self._interfaces = set([CLIENT, CLIENT_HANDLER, + CLIENT_INTERFACE_REQUESTS, PROPERTIES_IFACE, + CLIENT_APPROVER]) + + bus = dbus.Bus() + bus_name = dbus.service.BusName(SUGAR_CLIENT_SERVICE, bus=bus) + + dbus.service.Object.__init__(self, bus_name, SUGAR_CLIENT_PATH) + DBusProperties.__init__(self) + + self._implement_property_get(CLIENT, { + 'Interfaces': lambda: list(self._interfaces), + }) + self._implement_property_get(CLIENT_HANDLER, { + 'HandlerChannelFilter': self.__get_filters_cb, + }) + self._implement_property_get(CLIENT_APPROVER, { + 'ApproverChannelFilter': self.__get_filters_cb, + }) + + self.got_channel = dispatch.Signal() + self.got_dispatch_operation = dispatch.Signal() + + def __get_filters_cb(self): + logging.debug('__get_filters_cb') + filter_dict = dbus.Dictionary({}, signature='sv') + return dbus.Array([filter_dict], signature='a{sv}') + + @dbus.service.method(dbus_interface=CLIENT_HANDLER, + in_signature='ooa(oa{sv})aota{sv}', out_signature='') + def HandleChannels(self, account, connection, channels, requests_satisfied, + user_action_time, handler_info): + logging.debug('HandleChannels\n%r\n%r\n%r\n%r\n%r\n%r\n', account, + connection, channels, requests_satisfied, + user_action_time, handler_info) + for channel in channels: + self.got_channel.send(self, account=account, + connection=connection, channel=channel) + + @dbus.service.method(dbus_interface=CLIENT_INTERFACE_REQUESTS, + in_signature='oa{sv}', out_signature='') + def AddRequest(self, request, properties): + logging.debug('AddRequest\n%r\n%r', request, properties) + + @dbus.service.method(dbus_interface=CLIENT_APPROVER, + in_signature='a(oa{sv})oa{sv}', out_signature='', + async_callbacks=('success_cb', 'error_cb_')) + def AddDispatchOperation(self, channels, dispatch_operation_path, + properties, success_cb, error_cb_): + success_cb() + try: + logging.debug('AddDispatchOperation\n%r\n%r\n%r', channels, + dispatch_operation_path, properties) + + self.got_dispatch_operation.send(self, channels=channels, + dispatch_operation_path=dispatch_operation_path, + properties=properties) + except Exception, e: + logging.exception(e) + +_instance = None + +def get_instance(): + global _instance + if not _instance: + _instance = TelepathyClient() + return _instance diff --git a/src/jarabe/util/telepathy/connection_watcher.py b/src/jarabe/util/telepathy/connection_watcher.py index 4a4c6e0..391bdd5 100644 --- a/src/jarabe/util/telepathy/connection_watcher.py +++ b/src/jarabe/util/telepathy/connection_watcher.py @@ -93,6 +93,14 @@ class ConnectionWatcher(gobject.GObject): def get_connections(self): return self._connections.values() +_instance = None + +def get_instance(): + global _instance + if _instance is None: + _instance = ConnectionWatcher() + return _instance + if __name__ == '__main__': dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) diff --git a/src/jarabe/view/buddyicon.py b/src/jarabe/view/buddyicon.py index 13edb2c..37b9167 100644 --- a/src/jarabe/view/buddyicon.py +++ b/src/jarabe/view/buddyicon.py @@ -25,33 +25,34 @@ class BuddyIcon(CanvasIcon): self._greyed_out = False self._buddy = buddy - self._buddy.connect('appeared', self._buddy_presence_change_cb) - self._buddy.connect('disappeared', self._buddy_presence_change_cb) - self._buddy.connect('color-changed', self._buddy_presence_change_cb) - - palette = BuddyMenu(buddy) - self.set_palette(palette) + self._buddy.connect('notify::present', self.__buddy_notify_present_cb) + self._buddy.connect('notify::color', self.__buddy_notify_color_cb) self._update_color() - def _buddy_presence_change_cb(self, buddy, color=None): + def create_palette(self): + return BuddyMenu(self._buddy) + + def __buddy_notify_present_cb(self, buddy, pspec): # Update the icon's color when the buddy comes and goes self._update_color() - def _update_color(self): + def __buddy_notify_color_cb(self, buddy, pspec): + self._update_color() + def _update_color(self): # keep the icon in the palette in sync with the view palette = self.get_palette() - palette_icon = palette.props.icon - if self._greyed_out: self.props.stroke_color = '#D5D5D5' self.props.fill_color = style.COLOR_TRANSPARENT.get_svg() - palette_icon.props.stroke_color = '#D5D5D5' - palette_icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg() + if palette is not None: + palette.props.icon.props.stroke_color = self.props.stroke_color + palette.props.icon.props.fill_color = self.props.fill_color else: self.props.xo_color = self._buddy.get_color() - palette_icon.props.xo_color = self._buddy.get_color() + if palette is not None: + palette.props.icon.props.xo_color = self._buddy.get_color() def set_filter(self, query): self._greyed_out = (self._buddy.get_nick().lower().find(query) == -1) \ diff --git a/src/jarabe/view/buddymenu.py b/src/jarabe/view/buddymenu.py index 4637751..0ba6cc1 100644 --- a/src/jarabe/view/buddymenu.py +++ b/src/jarabe/view/buddymenu.py @@ -1,4 +1,5 @@ # Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2010 Collabora Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,6 +20,7 @@ from gettext import gettext as _ import gtk import gconf +import dbus from sugar.graphics.palette import Palette from sugar.graphics.menuitem import MenuItem @@ -42,8 +44,7 @@ class BuddyMenu(Palette): self._active_activity_changed_hid = None self.connect('destroy', self.__destroy_cb) - self._buddy.connect('icon-changed', self._buddy_icon_changed_cb) - self._buddy.connect('nick-changed', self._buddy_nick_changed_cb) + self._buddy.connect('notify::nick', self.__buddy_notify_nick_cb) if buddy.is_owner(): self._add_my_items() @@ -54,8 +55,7 @@ class BuddyMenu(Palette): if self._active_activity_changed_hid is not None: home_model = shell.get_model() home_model.disconnect(self._active_activity_changed_hid) - self._buddy.disconnect_by_func(self._buddy_icon_changed_cb) - self._buddy.disconnect_by_func(self._buddy_nick_changed_cb) + self._buddy.disconnect_by_func(self.__buddy_notify_nick_cb) def _add_buddy_items(self): if friends.get_model().has_buddy(self._buddy): @@ -115,9 +115,9 @@ class BuddyMenu(Palette): panel.show() def _update_invite_menu(self, activity): - buddy_activity = self._buddy.get_current_activity() + buddy_activity = self._buddy.props.current_activity if buddy_activity is not None: - buddy_activity_id = buddy_activity.props.id + buddy_activity_id = buddy_activity.activity_id else: buddy_activity_id = None @@ -139,11 +139,8 @@ class BuddyMenu(Palette): def _cur_activity_changed_cb(self, home_model, activity_model): self._update_invite_menu(activity_model) - def _buddy_icon_changed_cb(self, buddy): - pass - - def _buddy_nick_changed_cb(self, buddy, nick): - self.set_primary_text(nick) + def __buddy_notify_nick_cb(self, buddy, pspec): + self.set_primary_text(buddy.props.nick) def _make_friend_cb(self, menuitem): friends.get_model().make_friend(self._buddy) @@ -155,7 +152,17 @@ class BuddyMenu(Palette): activity = shell.get_model().get_active_activity() service = activity.get_service() if service: - buddy = self._buddy.get_buddy() - service.Invite(buddy.props.key) + try: + service.InviteContact(self._buddy.props.account, + self._buddy.props.contact_id) + except dbus.DBusException, e: + expected_exceptions = [ + 'org.freedesktop.DBus.Error.UnknownMethod', + 'org.freedesktop.DBus.Python.NotImplementedError'] + if e.get_dbus_name() in expected_exceptions: + logging.warning('Trying deprecated Activity.Invite') + service.Invite(self._buddy.props.key) + else: + raise else: logging.error('Invite failed, activity service not ') diff --git a/src/jarabe/view/service.py b/src/jarabe/view/service.py index bb71694..7af778a 100644 --- a/src/jarabe/view/service.py +++ b/src/jarabe/view/service.py @@ -1,4 +1,5 @@ # Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2010 Collabora Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,12 +21,10 @@ import dbus import gtk from jarabe.model import shell -from jarabe.model import owner from jarabe.model import bundleregistry _DBUS_SERVICE = "org.laptop.Shell" _DBUS_SHELL_IFACE = "org.laptop.Shell" -_DBUS_OWNER_IFACE = "org.laptop.Shell.Owner" _DBUS_PATH = "/org/laptop/Shell" class UIService(dbus.service.Object): @@ -54,15 +53,6 @@ class UIService(dbus.service.Object): self._shell_model = shell.get_model() - def start(self): - owner_model = owner.get_model() - owner_model.connect('nick-changed', self._owner_nick_changed_cb) - owner_model.connect('icon-changed', self._owner_icon_changed_cb) - owner_model.connect('color-changed', self._owner_color_changed_cb) - - self._shell_model.connect('active-activity-changed', - self._cur_activity_changed_cb) - @dbus.service.method(_DBUS_SHELL_IFACE, in_signature="s", out_signature="s") def GetBundlePath(self, bundle_id): @@ -97,35 +87,3 @@ class UIService(dbus.service.Object): def NotifyLaunchFailure(self, activity_id): shell.get_model().notify_launch_failed(activity_id) - @dbus.service.signal(_DBUS_OWNER_IFACE, signature="s") - def ColorChanged(self, color): - pass - - def _owner_color_changed_cb(self, new_color): - self.ColorChanged(new_color.to_string()) - - @dbus.service.signal(_DBUS_OWNER_IFACE, signature="s") - def NickChanged(self, nick): - pass - - def _owner_nick_changed_cb(self, new_nick): - self.NickChanged(new_nick) - - @dbus.service.signal(_DBUS_OWNER_IFACE, signature="ay") - def IconChanged(self, icon_data): - pass - - def _owner_icon_changed_cb(self, new_icon): - self.IconChanged(dbus.ByteArray(new_icon)) - - @dbus.service.signal(_DBUS_OWNER_IFACE, signature="s") - def CurrentActivityChanged(self, activity_id): - pass - - def _cur_activity_changed_cb(self, shell_model, new_activity): - new_id = "" - if new_activity: - new_id = new_activity.get_activity_id() - if new_id: - self.CurrentActivityChanged(new_id) - -- cgit v0.9.1