Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/jarabe/__init__.py1
-rw-r--r--src/jarabe/config.py.in2
-rw-r--r--src/jarabe/controlpanel/__init__.py1
-rw-r--r--src/jarabe/controlpanel/cmd.py39
-rw-r--r--src/jarabe/controlpanel/gui.py69
-rw-r--r--src/jarabe/controlpanel/inlinealert.py10
-rw-r--r--src/jarabe/controlpanel/sectionview.py13
-rw-r--r--src/jarabe/controlpanel/toolbar.py9
-rw-r--r--src/jarabe/desktop/Makefile.am2
-rw-r--r--src/jarabe/desktop/__init__.py1
-rw-r--r--src/jarabe/desktop/activitieslist.py55
-rw-r--r--src/jarabe/desktop/favoriteslayout.py142
-rw-r--r--src/jarabe/desktop/favoritesview.py88
-rw-r--r--src/jarabe/desktop/friendview.py41
-rw-r--r--src/jarabe/desktop/grid.py11
-rw-r--r--src/jarabe/desktop/groupbox.py13
-rw-r--r--src/jarabe/desktop/homebox.py21
-rw-r--r--src/jarabe/desktop/homewindow.py37
-rw-r--r--src/jarabe/desktop/keydialog.py61
-rw-r--r--src/jarabe/desktop/meshbox.py865
-rw-r--r--src/jarabe/desktop/networkviews.py721
-rw-r--r--src/jarabe/desktop/schoolserver.py65
-rw-r--r--src/jarabe/desktop/snowflakelayout.py3
-rw-r--r--src/jarabe/desktop/spreadlayout.py4
-rw-r--r--src/jarabe/desktop/transitionbox.py13
-rw-r--r--src/jarabe/frame/__init__.py2
-rw-r--r--src/jarabe/frame/activitiestray.py308
-rw-r--r--src/jarabe/frame/clipboard.py20
-rw-r--r--src/jarabe/frame/clipboardicon.py8
-rw-r--r--src/jarabe/frame/clipboardmenu.py7
-rw-r--r--src/jarabe/frame/clipboardobject.py17
-rw-r--r--src/jarabe/frame/clipboardpanelwindow.py14
-rw-r--r--src/jarabe/frame/clipboardtray.py18
-rw-r--r--src/jarabe/frame/devicestray.py11
-rw-r--r--src/jarabe/frame/eventarea.py14
-rw-r--r--src/jarabe/frame/frame.py21
-rw-r--r--src/jarabe/frame/frameinvoker.py2
-rw-r--r--src/jarabe/frame/framewindow.py1
-rw-r--r--src/jarabe/frame/friendstray.py105
-rw-r--r--src/jarabe/frame/notification.py12
-rw-r--r--src/jarabe/frame/zoomtoolbar.py2
-rw-r--r--src/jarabe/intro/Makefile.am4
-rw-r--r--src/jarabe/intro/__init__.py1
-rw-r--r--src/jarabe/intro/colorpicker.py1
-rw-r--r--src/jarabe/intro/default-picture.pngbin10442 -> 0 bytes
-rw-r--r--src/jarabe/intro/window.py50
-rw-r--r--src/jarabe/journal/Makefile.am1
-rw-r--r--src/jarabe/journal/detailview.py4
-rw-r--r--src/jarabe/journal/expandedentry.py28
-rw-r--r--src/jarabe/journal/journalactivity.py49
-rw-r--r--src/jarabe/journal/journalentrybundle.py4
-rw-r--r--src/jarabe/journal/journaltoolbox.py98
-rw-r--r--src/jarabe/journal/journalwindow.py (renamed from src/jarabe/desktop/myicon.py)27
-rw-r--r--src/jarabe/journal/keepicon.py1
-rw-r--r--src/jarabe/journal/listmodel.py111
-rw-r--r--src/jarabe/journal/listview.py83
-rw-r--r--src/jarabe/journal/misc.py138
-rw-r--r--src/jarabe/journal/modalalert.py7
-rw-r--r--src/jarabe/journal/model.py201
-rw-r--r--src/jarabe/journal/objectchooser.py10
-rw-r--r--src/jarabe/journal/palettes.py54
-rw-r--r--src/jarabe/journal/volumestoolbar.py85
-rw-r--r--src/jarabe/model/Makefile.am7
-rw-r--r--src/jarabe/model/__init__.py1
-rw-r--r--src/jarabe/model/adhoc.py294
-rw-r--r--src/jarabe/model/buddy.py342
-rw-r--r--src/jarabe/model/bundleregistry.py90
-rw-r--r--src/jarabe/model/filetransfer.py68
-rw-r--r--src/jarabe/model/friends.py85
-rw-r--r--src/jarabe/model/invites.py276
-rw-r--r--src/jarabe/model/mimeregistry.py1
-rw-r--r--src/jarabe/model/neighborhood.py1149
-rw-r--r--src/jarabe/model/network.py389
-rw-r--r--src/jarabe/model/notifications.py21
-rw-r--r--src/jarabe/model/olpcmesh.py28
-rw-r--r--src/jarabe/model/owner.py113
-rw-r--r--src/jarabe/model/screen.py6
-rw-r--r--src/jarabe/model/session.py19
-rw-r--r--src/jarabe/model/shell.py255
-rw-r--r--src/jarabe/model/sound.py11
-rw-r--r--src/jarabe/model/telepathyclient.py103
-rw-r--r--src/jarabe/util/__init__.py1
-rw-r--r--src/jarabe/util/emulator.py54
-rw-r--r--src/jarabe/util/telepathy/__init__.py1
-rw-r--r--src/jarabe/util/telepathy/connection_watcher.py18
-rw-r--r--src/jarabe/view/__init__.py1
-rw-r--r--src/jarabe/view/buddyicon.py29
-rw-r--r--src/jarabe/view/buddymenu.py54
-rw-r--r--src/jarabe/view/keyhandler.py63
-rw-r--r--src/jarabe/view/launcher.py23
-rw-r--r--src/jarabe/view/palettes.py50
-rw-r--r--src/jarabe/view/pulsingicon.py4
-rw-r--r--src/jarabe/view/service.py69
-rw-r--r--src/jarabe/view/tabbinghandler.py3
-rw-r--r--src/jarabe/view/viewsource.py31
95 files changed, 4880 insertions, 2585 deletions
diff --git a/src/jarabe/__init__.py b/src/jarabe/__init__.py
index 41b4b1c..ed2f639 100644
--- a/src/jarabe/__init__.py
+++ b/src/jarabe/__init__.py
@@ -23,4 +23,3 @@ refer to a command-line "shell" interface.
# 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
-
diff --git a/src/jarabe/config.py.in b/src/jarabe/config.py.in
index 6c418e9..d22ee9a 100644
--- a/src/jarabe/config.py.in
+++ b/src/jarabe/config.py.in
@@ -14,7 +14,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-# pylint: disable-msg=C0301
+# pylint: disable=C0301
prefix = '@prefix@'
data_path = '@prefix@/share/sugar/data'
diff --git a/src/jarabe/controlpanel/__init__.py b/src/jarabe/controlpanel/__init__.py
index a9dd95a..85f6a24 100644
--- a/src/jarabe/controlpanel/__init__.py
+++ b/src/jarabe/controlpanel/__init__.py
@@ -13,4 +13,3 @@
# 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
-
diff --git a/src/jarabe/controlpanel/cmd.py b/src/jarabe/controlpanel/cmd.py
index 7144b33..fe8f1a4 100644
--- a/src/jarabe/controlpanel/cmd.py
+++ b/src/jarabe/controlpanel/cmd.py
@@ -18,20 +18,21 @@ import sys
import getopt
import os
from gettext import gettext as _
-import traceback
import logging
from jarabe import config
+
_RESTART = 1
-_same_option_warning = _("sugar-control-panel: WARNING, found more than"
- " one option with the same name: %s module: %r")
-_no_option_error = _("sugar-control-panel: key=%s not an available option")
-_general_error = _("sugar-control-panel: %s")
+_same_option_warning = _('sugar-control-panel: WARNING, found more than one'
+ ' option with the same name: %s module: %r')
+_no_option_error = _('sugar-control-panel: key=%s not an available option')
+_general_error = _('sugar-control-panel: %s')
+
def cmd_help():
- '''Print the help to the screen'''
+ """Print the help to the screen"""
# TRANS: Translators, there's a empty line at the end of this string,
# which must appear in the translated string (msgstr) as well.
print _('Usage: sugar-control-panel [ option ] key [ args ... ] \n\
@@ -45,14 +46,16 @@ def cmd_help():
-c key clear the current value for the key \n\
')
+
def note_restart():
- '''Instructions how to restart sugar'''
+ """Instructions how to restart sugar"""
print _('To apply your changes you have to restart sugar.\n' +
'Hit ctrl+alt+erase on the keyboard to trigger a restart.')
+
def load_modules():
- '''Build a list of pointers to available modules and import them.
- '''
+ """Build a list of pointers to available modules and import them.
+ """
modules = []
path = os.path.join(config.ext_path, 'cpsection')
@@ -65,16 +68,16 @@ def load_modules():
module = __import__('.'.join(('cpsection', item, 'model')),
globals(), locals(), ['model'])
except Exception:
- logging.error('Exception while loading extension:\n' + \
- ''.join(traceback.format_exception(*sys.exc_info())))
+ logging.exception('Exception while loading extension:')
else:
modules.append(module)
return modules
+
def main():
try:
- options, args = getopt.getopt(sys.argv[1:], "h:s:g:c:l", [])
+ options, args = getopt.getopt(sys.argv[1:], 'h:s:g:c:l', [])
except getopt.GetoptError:
cmd_help()
sys.exit(2)
@@ -87,7 +90,7 @@ def main():
for option, key in options:
found = 0
- if option in ("-h"):
+ if option in ('-h'):
for module in modules:
method = getattr(module, 'set_' + key, None)
if method:
@@ -98,7 +101,7 @@ def main():
print _(_same_option_warning % (key, module))
if found == 0:
print _(_no_option_error % key)
- if option in ("-l"):
+ if option in ('-l'):
for module in modules:
methods = dir(module)
print '%s:' % module.__name__.split('.')[1]
@@ -106,9 +109,9 @@ def main():
if method.startswith('get_'):
print ' %s' % method[4:]
elif method.startswith('clear_'):
- print " %s (use the -c argument with this option)" \
+ print ' %s (use the -c argument with this option)' \
% method[6:]
- if option in ("-g"):
+ if option in ('-g'):
for module in modules:
method = getattr(module, 'print_' + key, None)
if method:
@@ -122,7 +125,7 @@ def main():
print _(_same_option_warning % (key, module))
if found == 0:
print _(_no_option_error % key)
- if option in ("-s"):
+ if option in ('-s'):
for module in modules:
method = getattr(module, 'set_' + key, None)
if method:
@@ -139,7 +142,7 @@ def main():
print _(_same_option_warning % (key, module))
if found == 0:
print _(_no_option_error % key)
- if option in ("-c"):
+ if option in ('-c'):
for module in modules:
method = getattr(module, 'clear_' + key, None)
if method:
diff --git a/src/jarabe/controlpanel/gui.py b/src/jarabe/controlpanel/gui.py
index 51d9820..2f55951 100644
--- a/src/jarabe/controlpanel/gui.py
+++ b/src/jarabe/controlpanel/gui.py
@@ -17,8 +17,6 @@
import os
import logging
from gettext import gettext as _
-import sys
-import traceback
import gobject
import gtk
@@ -32,8 +30,9 @@ from jarabe.controlpanel.toolbar import MainToolbar
from jarabe.controlpanel.toolbar import SectionToolbar
from jarabe import config
+
_logger = logging.getLogger('ControlPanel')
-_MAX_COLUMNS = 5
+
class ControlPanel(gtk.Window):
__gtype_name__ = 'SugarControlPanel'
@@ -41,6 +40,9 @@ class ControlPanel(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
+ self._max_columns = int(0.285 * (float(gtk.gdk.screen_width()) /
+ style.GRID_CELL_SIZE - 3))
+
self.set_border_width(style.LINE_WIDTH)
offset = style.GRID_CELL_SIZE
width = gtk.gdk.screen_width() - offset * 2
@@ -74,7 +76,7 @@ class ControlPanel(gtk.Window):
self.add(self._vbox)
self._vbox.show()
- self.connect("realize", self.__realize_cb)
+ self.connect('realize', self.__realize_cb)
self._options = self._get_options()
self._current_option = None
@@ -110,6 +112,7 @@ class ControlPanel(gtk.Window):
self._table = gtk.Table()
self._table.set_col_spacings(style.GRID_CELL_SIZE)
+ self._table.set_row_spacings(style.GRID_CELL_SIZE)
self._table.set_border_width(style.GRID_CELL_SIZE)
self._scrolledwindow = gtk.ScrolledWindow()
@@ -134,8 +137,17 @@ class ControlPanel(gtk.Window):
except ImportError:
del self._options['keyboard']
- row = 0
- column = 2
+ # If the screen width only supports two columns, start
+ # placing from the second row.
+ if self._max_columns == 2:
+ row = 1
+ column = 0
+ else:
+ # About Me and About my computer are hardcoded below to use the
+ # first two slots so we need to leave them free.
+ row = 0
+ column = 2
+
options = self._options.keys()
options.sort()
@@ -157,7 +169,7 @@ class ControlPanel(gtk.Window):
column, column + 1,
row, row + 1)
column += 1
- if column == _MAX_COLUMNS:
+ if column == self._max_columns:
column = 0
row += 1
@@ -214,11 +226,16 @@ class ControlPanel(gtk.Window):
globals(), locals(), ['model'])
model = ModelWrapper(mod)
- self._section_view = view_class(model,
- self._options[option]['alerts'])
+ try:
+ self.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+ self._section_view = view_class(model,
+ self._options[option]['alerts'])
+
+ self._set_canvas(self._section_view)
+ self._section_view.show()
+ finally:
+ self.get_window().set_cursor(None)
- self._set_canvas(self._section_view)
- self._section_view.show()
self._section_view.connect('notify::is-valid',
self.__valid_section_cb)
self._section_view.connect('request-close',
@@ -227,13 +244,13 @@ class ControlPanel(gtk.Window):
style.COLOR_WHITE.get_gdk_color())
def set_section_view_auto_close(self):
- '''Automatically close the control panel if there is "nothing to do"
- '''
+ """Automatically close the control panel if there is "nothing to do"
+ """
self._section_view.auto_close = True
def _get_options(self):
- '''Get the available option information from the extensions
- '''
+ """Get the available option information from the extensions
+ """
options = {}
path = os.path.join(config.ext_path, 'cpsection')
@@ -259,11 +276,9 @@ class ControlPanel(gtk.Window):
keywords.append(item)
options[item]['keywords'] = keywords
else:
- _logger.error('There is no CLASS constant specifieds ' \
- 'in the view file \'%s\'.' % item)
+ _logger.error('no CLASS attribute in %r', item)
except Exception:
- logging.error('Exception while loading extension:\n' + \
- ''.join(traceback.format_exception(*sys.exc_info())))
+ logging.exception('Exception while loading extension:')
return options
@@ -333,6 +348,7 @@ class ControlPanel(gtk.Window):
section_is_valid = section_view.props.is_valid
self._section_toolbar.accept_button.set_sensitive(section_is_valid)
+
class ModelWrapper(object):
def __init__(self, module):
self._module = module
@@ -360,18 +376,15 @@ class ModelWrapper(object):
except Exception, detail:
_logger.debug('Error undo option: %s', detail)
+
class _SectionIcon(gtk.EventBox):
- __gtype_name__ = "SugarSectionIcon"
+ __gtype_name__ = 'SugarSectionIcon'
__gproperties__ = {
- 'icon-name' : (str, None, None, None,
- gobject.PARAM_READWRITE),
- 'pixel-size' : (object, None, None,
- gobject.PARAM_READWRITE),
- 'xo-color' : (object, None, None,
- gobject.PARAM_READWRITE),
- 'title' : (str, None, None, None,
- gobject.PARAM_READWRITE)
+ 'icon-name': (str, None, None, None, gobject.PARAM_READWRITE),
+ 'pixel-size': (object, None, None, gobject.PARAM_READWRITE),
+ 'xo-color': (object, None, None, gobject.PARAM_READWRITE),
+ 'title': (str, None, None, None, gobject.PARAM_READWRITE),
}
def __init__(self, **kwargs):
diff --git a/src/jarabe/controlpanel/inlinealert.py b/src/jarabe/controlpanel/inlinealert.py
index b1880da..f970af4 100644
--- a/src/jarabe/controlpanel/inlinealert.py
+++ b/src/jarabe/controlpanel/inlinealert.py
@@ -21,6 +21,7 @@ import pango
from sugar.graphics import style
from sugar.graphics.icon import Icon
+
class InlineAlert(gtk.HBox):
"""UI interface for Inline alerts
@@ -36,11 +37,9 @@ class InlineAlert(gtk.HBox):
__gtype_name__ = 'SugarInlineAlert'
__gproperties__ = {
- 'msg' : (str, None, None, None,
- gobject.PARAM_READWRITE),
- 'icon' : (object, None, None,
- gobject.PARAM_WRITABLE)
- }
+ 'msg': (str, None, None, None, gobject.PARAM_READWRITE),
+ 'icon': (object, None, None, gobject.PARAM_WRITABLE),
+ }
def __init__(self, **kwargs):
@@ -80,4 +79,3 @@ class InlineAlert(gtk.HBox):
def do_get_property(self, pspec):
if pspec.name == 'msg':
return self._msg
-
diff --git a/src/jarabe/controlpanel/sectionview.py b/src/jarabe/controlpanel/sectionview.py
index 4de27a2..4b5751f 100644
--- a/src/jarabe/controlpanel/sectionview.py
+++ b/src/jarabe/controlpanel/sectionview.py
@@ -18,18 +18,17 @@ import gobject
import gtk
from gettext import gettext as _
+
class SectionView(gtk.VBox):
__gtype_name__ = 'SugarSectionView'
__gsignals__ = {
- 'request-close': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([]))
- }
+ 'request-close': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
__gproperties__ = {
- 'is_valid' : (bool, None, None, True,
- gobject.PARAM_READWRITE)
- }
+ 'is_valid': (bool, None, None, True, gobject.PARAM_READWRITE),
+ }
_APPLY_TIMEOUT = 1000
@@ -51,5 +50,5 @@ class SectionView(gtk.VBox):
return self._is_valid
def undo(self):
- '''Undo here the changes that have been made in this section.'''
+ """Undo here the changes that have been made in this section."""
pass
diff --git a/src/jarabe/controlpanel/toolbar.py b/src/jarabe/controlpanel/toolbar.py
index 320a8eb..fca34a0 100644
--- a/src/jarabe/controlpanel/toolbar.py
+++ b/src/jarabe/controlpanel/toolbar.py
@@ -25,6 +25,7 @@ from sugar.graphics.toolbutton import ToolButton
from sugar.graphics import iconentry
from sugar.graphics import style
+
class MainToolbar(gtk.Toolbar):
""" Main toolbar of the control panel
"""
@@ -36,8 +37,9 @@ class MainToolbar(gtk.Toolbar):
([])),
'search-changed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
- ([str]))
+ ([str])),
}
+
def __init__(self):
gtk.Toolbar.__init__(self)
@@ -83,6 +85,7 @@ class MainToolbar(gtk.Toolbar):
def __stop_clicked_cb(self, button):
self.emit('stop-clicked')
+
class SectionToolbar(gtk.Toolbar):
""" Toolbar of the sections of the control panel
"""
@@ -94,8 +97,9 @@ class SectionToolbar(gtk.Toolbar):
([])),
'accept-clicked': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
- ([]))
+ ([])),
}
+
def __init__(self):
gtk.Toolbar.__init__(self)
@@ -154,4 +158,3 @@ class SectionToolbar(gtk.Toolbar):
def __accept_button_clicked_cb(self, widget, data=None):
self.emit('accept-clicked')
-
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/__init__.py b/src/jarabe/desktop/__init__.py
index a9dd95a..85f6a24 100644
--- a/src/jarabe/desktop/__init__.py
+++ b/src/jarabe/desktop/__init__.py
@@ -13,4 +13,3 @@
# 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
-
diff --git a/src/jarabe/desktop/activitieslist.py b/src/jarabe/desktop/activitieslist.py
index 87f2af0..0370ef3 100644
--- a/src/jarabe/desktop/activitieslist.py
+++ b/src/jarabe/desktop/activitieslist.py
@@ -30,19 +30,18 @@ from sugar.graphics.icon import Icon, CellRendererIcon
from sugar.graphics.xocolor import XoColor
from sugar.graphics.menuitem import MenuItem
from sugar.graphics.alert import Alert
-from sugar.activity import activityfactory
-from sugar.activity.activityhandle import ActivityHandle
from jarabe.model import bundleregistry
from jarabe.view.palettes import ActivityPalette
-from jarabe.view import launcher
+from jarabe.journal import misc
+
class ActivitiesTreeView(gtk.TreeView):
__gtype_name__ = 'SugarActivitiesTreeView'
__gsignals__ = {
- 'erase-activated' : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([str]))
+ 'erase-activated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([str])),
}
def __init__(self):
@@ -143,13 +142,7 @@ class ActivitiesTreeView(gtk.TreeView):
registry = bundleregistry.get_registry()
bundle = registry.get_bundle(row[ListModel.COLUMN_BUNDLE_ID])
- activity_id = activityfactory.create_activity_id()
-
- client = gconf.client_get_default()
- xo_color = XoColor(client.get_string('/desktop/sugar/user/color'))
-
- launcher.add_launcher(activity_id, bundle.get_icon(), xo_color)
- activityfactory.create(bundle, ActivityHandle(activity_id))
+ misc.launch(bundle)
def set_filter(self, query):
self._query = query.lower()
@@ -159,6 +152,7 @@ class ActivitiesTreeView(gtk.TreeView):
title = model[tree_iter][ListModel.COLUMN_TITLE]
return title is not None and title.lower().find(self._query) > -1
+
class ListModel(gtk.TreeModelSort):
__gtype_name__ = 'SugarListModel'
@@ -172,7 +166,7 @@ class ListModel(gtk.TreeModelSort):
COLUMN_DATE_TEXT = 7
def __init__(self):
- self._model = gtk.ListStore(str, bool, str, str, int, str, int, str)
+ self._model = gtk.ListStore(str, bool, str, str, str, str, int, str)
self._model_filter = self._model.filter_new()
gtk.TreeModelSort.__init__(self, self._model_filter)
@@ -243,6 +237,7 @@ class ListModel(gtk.TreeModelSort):
def refilter(self):
self._model_filter.refilter()
+
class CellRendererFavorite(CellRendererIcon):
__gtype_name__ = 'SugarCellRendererFavorite'
@@ -257,12 +252,13 @@ class CellRendererFavorite(CellRendererIcon):
self.props.prelit_stroke_color = style.COLOR_BUTTON_GREY.get_svg()
self.props.prelit_fill_color = style.COLOR_BUTTON_GREY.get_svg()
+
class CellRendererActivityIcon(CellRendererIcon):
__gtype_name__ = 'SugarCellRendererActivityIcon'
__gsignals__ = {
- 'erase-activated' : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([str]))
+ 'erase-activated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([str])),
}
def __init__(self, tree_view):
@@ -295,6 +291,7 @@ class CellRendererActivityIcon(CellRendererIcon):
def __erase_activated_cb(self, palette, bundle_id):
self.emit('erase-activated', bundle_id)
+
class ActivitiesList(gtk.VBox):
__gtype_name__ = 'SugarActivitiesList'
@@ -376,14 +373,15 @@ class ActivitiesList(gtk.VBox):
if response_id == gtk.RESPONSE_OK:
registry = bundleregistry.get_registry()
bundle = registry.get_bundle(bundle_id)
- registry.uninstall(bundle)
+ registry.uninstall(bundle, delete_profile=True)
+
class ActivityListPalette(ActivityPalette):
__gtype_name__ = 'SugarActivityListPalette'
__gsignals__ = {
- 'erase-activated' : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([str]))
+ 'erase-activated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([str])),
}
def __init__(self, activity_info):
@@ -406,13 +404,7 @@ class ActivityListPalette(ActivityPalette):
self._favorite_item.show()
if activity_info.is_user_activity():
- menu_item = MenuItem(_('Erase'), 'list-remove')
- menu_item.connect('activate', self.__erase_activate_cb)
- self.menu.append(menu_item)
- menu_item.show()
-
- if not os.access(activity_info.get_path(), os.W_OK):
- menu_item.props.sensitive = False
+ self._add_erase_option(registry, activity_info)
registry = bundleregistry.get_registry()
self._activity_changed_sid = registry.connect('bundle_changed',
@@ -421,6 +413,16 @@ class ActivityListPalette(ActivityPalette):
self.connect('destroy', self.__destroy_cb)
+ def _add_erase_option(self, registry, activity_info):
+ menu_item = MenuItem(_('Erase'), 'list-remove')
+ menu_item.connect('activate', self.__erase_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ if not os.access(activity_info.get_path(), os.W_OK) or \
+ registry.is_activity_protected(self._bundle_id):
+ menu_item.props.sensitive = False
+
def __destroy_cb(self, palette):
self.disconnect(self._activity_changed_sid)
@@ -433,7 +435,7 @@ class ActivityListPalette(ActivityPalette):
else:
label.set_text(_('Make favorite'))
client = gconf.client_get_default()
- xo_color = XoColor(client.get_string("/desktop/sugar/user/color"))
+ xo_color = XoColor(client.get_string('/desktop/sugar/user/color'))
self._favorite_icon.props.xo_color = xo_color
@@ -453,4 +455,3 @@ class ActivityListPalette(ActivityPalette):
def __erase_activate_cb(self, menu_item):
self.emit('erase-activated', self._bundle_id)
-
diff --git a/src/jarabe/desktop/favoriteslayout.py b/src/jarabe/desktop/favoriteslayout.py
index 85e1b59..360c147 100644
--- a/src/jarabe/desktop/favoriteslayout.py
+++ b/src/jarabe/desktop/favoriteslayout.py
@@ -1,4 +1,5 @@
# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2010 Sugar Labs
#
# 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
@@ -28,10 +29,18 @@ from sugar.graphics import style
from jarabe.model import bundleregistry
from jarabe.desktop.grid import Grid
+
_logger = logging.getLogger('FavoritesLayout')
_CELL_SIZE = 4
_BASE_SCALE = 1000
+_INTERMEDIATE_B = (style.STANDARD_ICON_SIZE + style.SMALL_ICON_SIZE) / 2
+_INTERMEDIATE_A = (style.STANDARD_ICON_SIZE + _INTERMEDIATE_B) / 2
+_INTERMEDIATE_C = (_INTERMEDIATE_B + style.SMALL_ICON_SIZE) / 2
+_ICON_SIZES = [style.MEDIUM_ICON_SIZE, style.STANDARD_ICON_SIZE,
+ _INTERMEDIATE_A, _INTERMEDIATE_B, _INTERMEDIATE_C,
+ style.SMALL_ICON_SIZE]
+
class FavoritesLayout(gobject.GObject, hippo.CanvasLayout):
"""Base class of the different layout types."""
@@ -81,7 +90,8 @@ class FavoritesLayout(gobject.GObject, hippo.CanvasLayout):
if icon not in self.box.get_children():
raise ValueError('Child not in box.')
- if not(hasattr(icon, 'get_bundle_id') and hasattr(icon, 'get_version')):
+ if not (hasattr(icon, 'get_bundle_id') and
+ hasattr(icon, 'get_version')):
logging.debug('Not an activity icon %r', icon)
return
@@ -101,6 +111,7 @@ class FavoritesLayout(gobject.GObject, hippo.CanvasLayout):
def allow_dnd(self):
return False
+
class RandomLayout(FavoritesLayout):
"""Lay out icons randomly; try to nudge them around to resolve overlaps."""
@@ -181,13 +192,19 @@ class RandomLayout(FavoritesLayout):
def allow_dnd(self):
return True
+
_MINIMUM_RADIUS = style.XLARGE_ICON_SIZE / 2 + style.DEFAULT_SPACING + \
style.STANDARD_ICON_SIZE * 2
_MAXIMUM_RADIUS = (gtk.gdk.screen_height() - style.GRID_CELL_SIZE) / 2 - \
style.STANDARD_ICON_SIZE - style.DEFAULT_SPACING
+_ICON_SPACING_FACTORS = [1.5, 1.4, 1.3, 1.2, 1.1, 1.0]
+_SPIRAL_SPACING_FACTORS = [1.5, 1.5, 1.5, 1.4, 1.3, 1.2]
+_MIMIMUM_RADIUS_ENCROACHMENT = 0.75
+_INITIAL_ANGLE = math.pi
+
class RingLayout(FavoritesLayout):
- """Lay out icons in a ring around the XO man."""
+ """Lay out icons in a ring or spiral around the XO man."""
__gtype_name__ = 'RingLayout'
icon_name = 'view-radial'
@@ -201,6 +218,7 @@ class RingLayout(FavoritesLayout):
def __init__(self):
FavoritesLayout.__init__(self)
self._locked_children = {}
+ self._spiral_mode = False
def append(self, icon, locked=False):
FavoritesLayout.append(self, icon, locked)
@@ -221,33 +239,78 @@ class RingLayout(FavoritesLayout):
self._locked_children[child] = (x, y)
def _calculate_radius_and_icon_size(self, children_count):
- # what's the radius required without downscaling?
- distance = style.STANDARD_ICON_SIZE + style.DEFAULT_SPACING
+ """ Adjust the ring or spiral radius and icon size as needed. """
+ self._spiral_mode = False
+ distance = style.MEDIUM_ICON_SIZE + style.DEFAULT_SPACING * \
+ _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.MEDIUM_ICON_SIZE)]
+ radius = max(children_count * distance / (2 * math.pi),
+ _MINIMUM_RADIUS)
+ if radius < _MAXIMUM_RADIUS:
+ return radius, style.MEDIUM_ICON_SIZE
+
+ distance = style.STANDARD_ICON_SIZE + style.DEFAULT_SPACING * \
+ _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.STANDARD_ICON_SIZE)]
+ radius = max(children_count * distance / (2 * math.pi),
+ _MINIMUM_RADIUS)
+ if radius < _MAXIMUM_RADIUS:
+ return radius, style.STANDARD_ICON_SIZE
+
+ self._spiral_mode = True
icon_size = style.STANDARD_ICON_SIZE
- # circumference is 2*pi*r; we want this to be at least
- # 'children_count * distance'
- radius = children_count * distance / (2 * math.pi)
- # limit computed radius to reasonable bounds.
- radius = max(radius, _MINIMUM_RADIUS)
- radius = min(radius, _MAXIMUM_RADIUS)
- # recompute icon size from limited radius
- if children_count > 0:
- icon_size = (2 * math.pi * radius / children_count) \
- - style.DEFAULT_SPACING
- # limit adjusted icon size.
- icon_size = max(icon_size, style.SMALL_ICON_SIZE)
- icon_size = min(icon_size, style.MEDIUM_ICON_SIZE)
+ angle_, radius = self._calculate_angle_and_radius(children_count,
+ icon_size)
+ while radius > _MAXIMUM_RADIUS:
+ i = _ICON_SIZES.index(icon_size)
+ if i < len(_ICON_SIZES) - 1:
+ icon_size = _ICON_SIZES[i + 1]
+ angle_, radius = self._calculate_angle_and_radius(
+ children_count, icon_size)
+ else:
+ break
return radius, icon_size
- def _calculate_position(self, radius, icon_size, index, children_count,
- sin=math.sin, cos=math.cos):
+ def _calculate_position(self, radius, icon_size, icon_index,
+ children_count, sin=math.sin, cos=math.cos):
+ """ Calculate an icon position on a circle or a spiral. """
width, height = self.box.get_allocation()
- angle = index * (2 * math.pi / children_count) - math.pi / 2
- x = radius * cos(angle) + (width - icon_size) / 2
- y = radius * sin(angle) + (height - icon_size -
- (style.GRID_CELL_SIZE/2) ) / 2
+ if self._spiral_mode:
+ min_width_, box_width = self.box.get_width_request()
+ min_height_, box_height = self.box.get_height_request(box_width)
+ angle, radius = self._calculate_angle_and_radius(icon_index,
+ icon_size)
+ x, y = self._convert_from_polar_to_cartesian(angle, radius,
+ icon_size,
+ width, height)
+ else:
+ angle = icon_index * (2 * math.pi / children_count) - math.pi / 2
+ x = radius * cos(angle) + (width - icon_size) / 2
+ y = radius * sin(angle) + (height - icon_size - \
+ (style.GRID_CELL_SIZE / 2)) / 2
+ return x, y
+
+ def _convert_from_polar_to_cartesian(self, angle, radius, icon_size, width,
+ height):
+ """ Convert angle, radius to x, y """
+ x = int(math.sin(angle) * radius)
+ y = int(math.cos(angle) * radius)
+ x = - x + (width - icon_size) / 2
+ y = y + (height - icon_size - (style.GRID_CELL_SIZE / 2)) / 2
return x, y
+ def _calculate_angle_and_radius(self, icon_count, icon_size):
+ """ Based on icon_count and icon_size, calculate radius and angle. """
+ spiral_spacing = _SPIRAL_SPACING_FACTORS[_ICON_SIZES.index(icon_size)]
+ icon_spacing = icon_size + style.DEFAULT_SPACING * \
+ _ICON_SPACING_FACTORS[_ICON_SIZES.index(icon_size)]
+ angle = _INITIAL_ANGLE
+ radius = _MINIMUM_RADIUS - (icon_size * _MIMIMUM_RADIUS_ENCROACHMENT)
+ for i_ in range(icon_count):
+ circumference = radius * 2 * math.pi
+ n = circumference / icon_spacing
+ angle += (2 * math.pi / n)
+ radius += (float(icon_spacing) * spiral_spacing / n)
+ return angle, radius
+
def _get_children_in_ring(self):
children_in_ring = [child for child in self.box.get_layout_children() \
if child not in self._locked_children]
@@ -294,6 +357,7 @@ class RingLayout(FavoritesLayout):
else:
return 0
+
_SUNFLOWER_CONSTANT = style.STANDARD_ICON_SIZE * .75
"""Chose a constant such that STANDARD_ICON_SIZE icons are nicely spaced."""
@@ -319,6 +383,7 @@ This is the golden angle: http://en.wikipedia.org/wiki/Golden_angle
Calculation: math.radians(360) / ( _GOLDEN_RATIO * _GOLDEN_RATIO )
"""
+
class SunflowerLayout(RingLayout):
"""Spiral layout based on Fibonacci ratio in phyllotaxis.
@@ -346,7 +411,8 @@ class SunflowerLayout(RingLayout):
return None, style.STANDARD_ICON_SIZE
def adjust_index(self, i):
- """Skip floret indices which end up outside the desired bounding box."""
+ """Skip floret indices which end up outside the desired bounding box.
+ """
for idx in self.skipped_indices:
if i < idx:
break
@@ -377,7 +443,7 @@ class SunflowerLayout(RingLayout):
# removed to make room for the "active activity" icon.
x = r * cos(phi) + (width - icon_size) / 2
y = r * sin(phi) + (height - icon_size - \
- (style.GRID_CELL_SIZE / 2) ) / 2
+ (style.GRID_CELL_SIZE / 2)) / 2
# skip allocations outside the allocation box.
# give up once we can't fit
@@ -385,10 +451,12 @@ class SunflowerLayout(RingLayout):
if y < 0 or y > (height - icon_size) or \
x < 0 or x > (width - icon_size):
self.skipped_indices.append(index)
- continue # try again
+ # try again
+ continue
return x, y
+
class BoxLayout(RingLayout):
"""Lay out icons in a square around the XO man."""
@@ -421,14 +489,16 @@ class BoxLayout(RingLayout):
return (90 - d) / 45.
if d < 225:
return -1
- return cos_d(360 - d) # mirror around 180
+ # mirror around 180
+ return cos_d(360 - d)
cos = lambda r: cos_d(math.degrees(r))
sin = lambda r: cos_d(math.degrees(r) - 90)
- return RingLayout._calculate_position\
- (self, radius, icon_size, index, children_count,
- sin=sin, cos=cos)
+ return RingLayout._calculate_position(self, radius, icon_size, index,
+ children_count, sin=sin,
+ cos=cos)
+
class TriangleLayout(RingLayout):
"""Lay out icons in a triangle around the XO man."""
@@ -467,7 +537,8 @@ class TriangleLayout(RingLayout):
return (d + 90) / 120.
if d <= 90:
return (90 - d) / 60.
- return -cos_d(180 - d) # mirror around 90
+ # mirror around 90
+ return -cos_d(180 - d)
sqrt_3 = math.sqrt(3)
@@ -478,11 +549,12 @@ class TriangleLayout(RingLayout):
return ((d + 90) / 120.) * sqrt_3 - 1
if d <= 90:
return sqrt_3 - 1
- return sin_d(180 - d) # mirror around 90
+ # mirror around 90
+ return sin_d(180 - d)
cos = lambda r: cos_d(math.degrees(r))
sin = lambda r: sin_d(math.degrees(r))
- return RingLayout._calculate_position\
- (self, radius, icon_size, index, children_count,
- sin=sin, cos=cos)
+ return RingLayout._calculate_position(self, radius, icon_size, index,
+ children_count, sin=sin,
+ cos=cos)
diff --git a/src/jarabe/desktop/favoritesview.py b/src/jarabe/desktop/favoritesview.py
index aca945a..b4a4e75 100644
--- a/src/jarabe/desktop/favoritesview.py
+++ b/src/jarabe/desktop/favoritesview.py
@@ -30,25 +30,23 @@ from sugar.graphics.menuitem import MenuItem
from sugar.graphics.alert import Alert
from sugar.graphics.xocolor import XoColor
from sugar.activity import activityfactory
-from sugar.activity.activityhandle import ActivityHandle
-from sugar.presence import presenceservice
from sugar import dispatch
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 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')
_ICON_DND_TARGET = ('activity-icon', gtk.TARGET_SAME_WIDGET, 0)
@@ -62,6 +60,9 @@ LAYOUT_MAP = {favoriteslayout.RingLayout.key: favoriteslayout.RingLayout,
`FavoritesLayout` which implement the layouts. Additional information
about the layout can be accessed with fields of the class."""
+_favorites_settings = None
+
+
class FavoritesView(hippo.Canvas):
__gtype_name__ = 'SugarFavoritesView'
@@ -82,7 +83,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)
@@ -172,7 +173,8 @@ class FavoritesView(hippo.Canvas):
height = allocation.height
min_w_, my_icon_width = self._my_icon.get_width_request()
- min_h_, my_icon_height = self._my_icon.get_height_request(my_icon_width)
+ min_h_, my_icon_height = self._my_icon.get_height_request(
+ my_icon_width)
x = (width - my_icon_width) / 2
y = (height - my_icon_height - style.GRID_CELL_SIZE) / 2
self._layout.move_icon(self._my_icon, x, y, locked=True)
@@ -190,7 +192,8 @@ class FavoritesView(hippo.Canvas):
# TODO: Dnd methods. This should be merged somehow inside hippo-canvas.
def __button_press_event_cb(self, widget, event):
if event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS:
- self._last_clicked_icon = self._get_icon_at_coords(event.x, event.y)
+ self._last_clicked_icon = self._get_icon_at_coords(event.x,
+ event.y)
if self._last_clicked_icon is not None:
self._pressed_button = event.button
self._press_start_x = event.x
@@ -203,9 +206,9 @@ class FavoritesView(hippo.Canvas):
icon_x, icon_y = icon.get_context().translate_to_widget(icon)
icon_width, icon_height = icon.get_allocation()
- if (x >= icon_x ) and (x <= icon_x + icon_width) and \
- (y >= icon_y ) and (y <= icon_y + icon_height) and \
- isinstance(icon, ActivityIcon):
+ if (x >= icon_x) and (x <= icon_x + icon_width) and \
+ (y >= icon_y) and (y <= icon_y + icon_height) and \
+ isinstance(icon, ActivityIcon):
return icon
return None
@@ -274,7 +277,7 @@ class FavoritesView(hippo.Canvas):
def _set_layout(self, layout):
if layout not in LAYOUT_MAP:
- logging.warn('Unknown favorites layout: %r' % layout)
+ logging.warn('Unknown favorites layout: %r', layout)
layout = favoriteslayout.RingLayout.key
assert layout in LAYOUT_MAP
@@ -327,7 +330,7 @@ class FavoritesView(hippo.Canvas):
alert.props.title = _('Registration Successful')
alert.props.msg = _('You are now registered ' \
'with your school server.')
- self._my_icon.remove_register_menu()
+ self._my_icon.set_registered()
ok_icon = Icon(icon_name='dialog-ok')
alert.add_button(gtk.RESPONSE_OK, _('Ok'), ok_icon)
@@ -387,7 +390,7 @@ class ActivityIcon(CanvasIcon):
break
def _get_last_activity_async(self, bundle_id, properties):
- query = {'activity': bundle_id}
+ query = {'activity': bundle_id}
datastore.find(query, sorting=['+timestamp'],
limit=self._MAX_RESUME_ENTRIES,
properties=properties,
@@ -395,8 +398,8 @@ class ActivityIcon(CanvasIcon):
error_handler=self.__get_last_activity_error_handler_cb)
def __get_last_activity_reply_handler_cb(self, entries, total_count):
- # If there's a problem with the DS index, we may get entries not related
- # to this activity.
+ # If there's a problem with the DS index, we may get entries not
+ # related to this activity.
checked_entries = []
for entry in entries:
if entry['activity'] == self.bundle_id:
@@ -484,16 +487,7 @@ class ActivityIcon(CanvasIcon):
if self._resume_mode and self._journal_entries:
self._resume(self._journal_entries[0])
else:
- client = gconf.client_get_default()
- xo_color = XoColor(client.get_string('/desktop/sugar/user/color'))
-
- activity_id = activityfactory.create_activity_id()
- launcher.add_launcher(activity_id,
- self._activity_info.get_icon(),
- xo_color)
-
- handle = ActivityHandle(activity_id)
- activityfactory.create(self._activity_info, handle)
+ misc.launch(self._activity_info)
def get_bundle_id(self):
return self._activity_info.get_bundle_id()
@@ -516,6 +510,7 @@ class ActivityIcon(CanvasIcon):
self._resume_mode = resume_mode
self._update()
+
class FavoritePalette(ActivityPalette):
__gtype_name__ = 'SugarFavoritePalette'
@@ -564,6 +559,7 @@ class FavoritePalette(ActivityPalette):
if entry is not None:
self.emit('entry-activate', entry)
+
class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem):
def __init__(self):
CanvasIcon.__init__(self, cache=True)
@@ -602,17 +598,18 @@ 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, ([]))
+ 'register-activate': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([])),
}
- def __init__(self, scale):
- MyIcon.__init__(self, scale)
- self._power_manager = None
+ def __init__(self, size):
+ BuddyIcon.__init__(self, buddy=get_owner_instance(), size=size)
+
self._palette_enabled = False
self._register_menu = None
@@ -621,17 +618,20 @@ class _MyIcon(MyIcon):
self._palette_enabled = True
return
- presence_service = presenceservice.get_instance()
- owner = BuddyModel(buddy=presence_service.get_owner())
- palette = BuddyMenu(owner)
+ palette = BuddyMenu(get_owner_instance())
client = gconf.client_get_default()
backup_url = client.get_string('/desktop/sugar/backup_url')
+
if not backup_url:
self._register_menu = MenuItem(_('Register'), 'media-record')
- self._register_menu.connect('activate', self.__register_activate_cb)
- palette.menu.append(self._register_menu)
- self._register_menu.show()
+ else:
+ self._register_menu = MenuItem(_('Register again'),
+ 'media-record')
+
+ self._register_menu.connect('activate', self.__register_activate_cb)
+ palette.menu.append(self._register_menu)
+ self._register_menu.show()
return palette
@@ -641,12 +641,17 @@ class _MyIcon(MyIcon):
def __register_activate_cb(self, menuitem):
self.emit('register-activate')
- def remove_register_menu(self):
+ def set_registered(self):
self.palette.menu.remove(self._register_menu)
+ self._register_menu = MenuItem(_('Register again'), 'media-record')
+ self._register_menu.connect('activate', self.__register_activate_cb)
+ self.palette.menu.append(self._register_menu)
+ self._register_menu.show()
+
class FavoritesSetting(object):
- _FAVORITES_KEY = "/desktop/sugar/desktop/favorites_layout"
+ _FAVORITES_KEY = '/desktop/sugar/desktop/favorites_layout'
def __init__(self):
client = gconf.client_get_default()
@@ -672,7 +677,6 @@ class FavoritesSetting(object):
layout = property(get_layout, set_layout)
-_favorites_settings = None
def get_settings():
global _favorites_settings
diff --git a/src/jarabe/desktop/friendview.py b/src/jarabe/desktop/friendview.py
index 4c5f1c8..8dab35f 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. <http://www.collabora.co.uk/>
#
# 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,17 +19,15 @@ import hippo
from sugar.graphics.icon import CanvasIcon
from sugar.graphics import style
-from sugar.presence import presenceservice
from jarabe.view.buddyicon import BuddyIcon
from jarabe.model import bundleregistry
+
class FriendView(hippo.CanvasBox):
def __init__(self, buddy, **kwargs):
hippo.CanvasBox.__init__(self, **kwargs)
- self._pservice = presenceservice.get_instance()
-
self._buddy = buddy
self._buddy_icon = BuddyIcon(buddy)
self._buddy_icon.props.size = style.LARGE_ICON_SIZE
@@ -37,14 +36,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 +55,30 @@ 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/grid.py b/src/jarabe/desktop/grid.py
index f3412c9..eab4033 100644
--- a/src/jarabe/desktop/grid.py
+++ b/src/jarabe/desktop/grid.py
@@ -22,17 +22,19 @@ import gtk
from sugar import _sugarext
+
_PLACE_TRIALS = 20
_MAX_WEIGHT = 255
_REFRESH_RATE = 200
_MAX_COLLISIONS_PER_REFRESH = 20
+
class Grid(_sugarext.Grid):
__gsignals__ = {
- 'child-changed' : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT]))
+ 'child-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
}
+
def __init__(self, width, height):
gobject.GObject.__init__(self)
@@ -185,7 +187,8 @@ class Grid(_sugarext.Grid):
for c in self._children:
intersection = child_rect.intersect(self._child_rects[c])
if c != child and intersection.width > 0:
- if c not in self._locked_children and c not in self._collisions:
+ if (c not in self._locked_children and
+ c not in self._collisions):
collision_found = True
self._collisions.append(c)
diff --git a/src/jarabe/desktop/groupbox.py b/src/jarabe/desktop/groupbox.py
index 76c2981..ed8f8ae 100644
--- a/src/jarabe/desktop/groupbox.py
+++ b/src/jarabe/desktop/groupbox.py
@@ -23,18 +23,19 @@ 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
+
class GroupBox(hippo.Canvas):
__gtype_name__ = 'SugarGroupBox'
+
def __init__(self):
- logging.debug("STARTUP: Loading the group view")
+ logging.debug('STARTUP: Loading the group view')
gobject.GObject.__init__(self)
@@ -48,15 +49,13 @@ class GroupBox(hippo.Canvas):
self._box.set_layout(self._layout)
client = gconf.client_get_default()
- color = XoColor(client.get_string("/desktop/sugar/user/color"))
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
self._owner_icon = CanvasIcon(icon_name='computer-xo', cache=True,
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/homebox.py b/src/jarabe/desktop/homebox.py
index 85279ff..661326e 100644
--- a/src/jarabe/desktop/homebox.py
+++ b/src/jarabe/desktop/homebox.py
@@ -30,16 +30,18 @@ from sugar.graphics.icon import Icon
from jarabe.desktop import favoritesview
from jarabe.desktop.activitieslist import ActivitiesList
+
_FAVORITES_VIEW = 0
_LIST_VIEW = 1
_AUTOSEARCH_TIMEOUT = 1000
+
class HomeBox(gtk.VBox):
__gtype_name__ = 'SugarHomeBox'
def __init__(self):
- logging.debug("STARTUP: Loading the home view")
+ logging.debug('STARTUP: Loading the home view')
gobject.GObject.__init__(self)
@@ -57,7 +59,7 @@ class HomeBox(gtk.VBox):
def show_software_updates_alert(self):
alert = Alert()
updater_icon = Icon(icon_name='module-updater',
- pixel_size = style.STANDARD_ICON_SIZE)
+ pixel_size=style.STANDARD_ICON_SIZE)
alert.props.icon = updater_icon
updater_icon.show()
alert.props.title = _('Software Update')
@@ -125,7 +127,7 @@ class HomeBox(gtk.VBox):
else:
raise ValueError('Invalid view: %r' % view)
- _REDRAW_TIMEOUT = 5 * 60 * 1000 # 5 minutes
+ _REDRAW_TIMEOUT = 5 * 60 * 1000 # 5 minutes
def resume(self):
pass
@@ -144,16 +146,15 @@ class HomeBox(gtk.VBox):
def set_resume_mode(self, resume_mode):
self._favorites_view.set_resume_mode(resume_mode)
+
class HomeToolbar(gtk.Toolbar):
__gtype_name__ = 'SugarHomeToolbar'
__gsignals__ = {
- 'query-changed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
+ 'query-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([str])),
- 'view-changed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([object]))
+ 'view-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
}
def __init__(self):
@@ -239,13 +240,14 @@ class HomeToolbar(gtk.Toolbar):
if self._autosearch_timer:
gobject.source_remove(self._autosearch_timer)
self._autosearch_timer = gobject.timeout_add(_AUTOSEARCH_TIMEOUT,
- self.__autosearch_timer_cb)
+ self.__autosearch_timer_cb)
def __autosearch_timer_cb(self):
self._autosearch_timer = None
self.search_entry.activate()
return False
+
class FavoritesButton(RadioToolButton):
__gtype_name__ = 'SugarFavoritesButton'
@@ -295,4 +297,3 @@ class FavoritesButton(RadioToolButton):
def _update_icon(self):
self.props.named_icon = favoritesview.LAYOUT_MAP[self._layout]\
.icon_name
-
diff --git a/src/jarabe/desktop/homewindow.py b/src/jarabe/desktop/homewindow.py
index d830ed0..07deff7 100644
--- a/src/jarabe/desktop/homewindow.py
+++ b/src/jarabe/desktop/homewindow.py
@@ -16,6 +16,7 @@
import logging
+import gobject
import gtk
from sugar.graphics import style
@@ -28,11 +29,15 @@ from jarabe.desktop.transitionbox import TransitionBox
from jarabe.model.shell import ShellModel
from jarabe.model import shell
-_HOME_PAGE = 0
-_GROUP_PAGE = 1
-_MESH_PAGE = 2
+
+_HOME_PAGE = 0
+_GROUP_PAGE = 1
+_MESH_PAGE = 2
_TRANSITION_PAGE = 3
+_instance = None
+
+
class HomeWindow(gtk.Window):
def __init__(self):
logging.debug('STARTUP: Loading the desktop window')
@@ -45,8 +50,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)
@@ -73,13 +80,16 @@ class HomeWindow(gtk.Window):
self.__zoom_level_changed_cb)
def _deactivate_view(self, level):
- group = palettegroup.get_group("default")
+ group = palettegroup.get_group('default')
group.popdown()
if level == ShellModel.ZOOM_HOME:
self._home_box.suspend()
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()
@@ -178,11 +188,22 @@ class HomeWindow(gtk.Window):
def get_home_box(self):
return self._home_box
-_instance = None
+ def busy_during_delayed_action(self, action):
+ """Use busy cursor during execution of action, scheduled via idle_add.
+ """
+ def action_wrapper(old_cursor):
+ try:
+ action()
+ finally:
+ self.get_window().set_cursor(old_cursor)
+
+ old_cursor = self.get_window().get_cursor()
+ self.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+ gobject.idle_add(action_wrapper, old_cursor)
+
def get_instance():
global _instance
if not _instance:
_instance = HomeWindow()
return _instance
-
diff --git a/src/jarabe/desktop/keydialog.py b/src/jarabe/desktop/keydialog.py
index 1e6d17a..6241b9b 100644
--- a/src/jarabe/desktop/keydialog.py
+++ b/src/jarabe/desktop/keydialog.py
@@ -24,8 +24,14 @@ import dbus
from jarabe.model import network
from jarabe.model.network import Secrets
+
IW_AUTH_ALG_OPEN_SYSTEM = 'open'
-IW_AUTH_ALG_SHARED_KEY = 'shared'
+IW_AUTH_ALG_SHARED_KEY = 'shared'
+
+WEP_PASSPHRASE = 1
+WEP_HEX = 2
+WEP_ASCII = 3
+
def string_is_hex(key):
is_hex = True
@@ -34,6 +40,7 @@ def string_is_hex(key):
is_hex = False
return is_hex
+
def string_is_ascii(string):
try:
string.encode('ascii')
@@ -41,12 +48,14 @@ def string_is_ascii(string):
except UnicodeEncodeError:
return False
+
def string_to_hex(passphrase):
key = ''
for c in passphrase:
key += '%02x' % ord(c)
return key
+
def hash_passphrase(passphrase):
# passphrase must have a length of 64
if len(passphrase) > 64:
@@ -57,16 +66,18 @@ def hash_passphrase(passphrase):
passphrase = hashlib.md5(passphrase).digest()
return string_to_hex(passphrase)[:26]
+
class CanceledKeyRequestError(dbus.DBusException):
def __init__(self):
dbus.DBusException.__init__(self)
self._dbus_error_name = network.NM_SETTINGS_IFACE + '.CanceledError'
+
class KeyDialog(gtk.Dialog):
def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, settings,
response):
gtk.Dialog.__init__(self, flags=gtk.DIALOG_MODAL)
- self.set_title("Wireless Key Required")
+ self.set_title('Wireless Key Required')
self._settings = settings
self._response = response
@@ -108,9 +119,6 @@ class KeyDialog(gtk.Dialog):
def get_response_object(self):
return self._response
-WEP_PASSPHRASE = 1
-WEP_HEX = 2
-WEP_ASCII = 3
class WEPKeyDialog(KeyDialog):
def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, settings,
@@ -120,9 +128,9 @@ class WEPKeyDialog(KeyDialog):
# WEP key type
self.key_store = gtk.ListStore(str, int)
- self.key_store.append(["Passphrase (128-bit)", WEP_PASSPHRASE])
- self.key_store.append(["Hex (40/128-bit)", WEP_HEX])
- self.key_store.append(["ASCII (40/128-bit)", WEP_ASCII])
+ self.key_store.append(['Passphrase (128-bit)', WEP_PASSPHRASE])
+ self.key_store.append(['Hex (40/128-bit)', WEP_HEX])
+ self.key_store.append(['ASCII (40/128-bit)', WEP_ASCII])
self.key_combo = gtk.ComboBox(self.key_store)
cell = gtk.CellRendererText()
@@ -132,7 +140,7 @@ class WEPKeyDialog(KeyDialog):
self.key_combo.connect('changed', self._key_combo_changed_cb)
hbox = gtk.HBox()
- hbox.pack_start(gtk.Label(_("Key Type:")))
+ hbox.pack_start(gtk.Label(_('Key Type:')))
hbox.pack_start(self.key_combo)
hbox.show_all()
self.vbox.pack_start(hbox)
@@ -142,8 +150,8 @@ class WEPKeyDialog(KeyDialog):
# WEP authentication mode
self.auth_store = gtk.ListStore(str, str)
- self.auth_store.append(["Open System", IW_AUTH_ALG_OPEN_SYSTEM])
- self.auth_store.append(["Shared Key", IW_AUTH_ALG_SHARED_KEY])
+ self.auth_store.append(['Open System', IW_AUTH_ALG_OPEN_SYSTEM])
+ self.auth_store.append(['Shared Key', IW_AUTH_ALG_SHARED_KEY])
self.auth_combo = gtk.ComboBox(self.auth_store)
cell = gtk.CellRendererText()
@@ -152,7 +160,7 @@ class WEPKeyDialog(KeyDialog):
self.auth_combo.set_active(0)
hbox = gtk.HBox()
- hbox.pack_start(gtk.Label(_("Authentication Type:")))
+ hbox.pack_start(gtk.Label(_('Authentication Type:')))
hbox.pack_start(self.auth_combo)
hbox.show_all()
@@ -179,8 +187,8 @@ class WEPKeyDialog(KeyDialog):
def print_security(self):
(key, auth_alg) = self._get_security()
- print "Key: %s" % key
- print "Auth: %d" % auth_alg
+ print 'Key: %s' % key
+ print 'Auth: %d' % auth_alg
def create_security(self):
(key, auth_alg) = self._get_security()
@@ -209,6 +217,7 @@ class WEPKeyDialog(KeyDialog):
self.set_response_sensitive(gtk.RESPONSE_OK, valid)
+
class WPAKeyDialog(KeyDialog):
def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, settings,
response):
@@ -217,7 +226,7 @@ class WPAKeyDialog(KeyDialog):
self.add_key_entry()
self.store = gtk.ListStore(str)
- self.store.append([_("WPA & WPA2 Personal")])
+ self.store.append([_('WPA & WPA2 Personal')])
self.combo = gtk.ComboBox(self.store)
cell = gtk.CellRendererText()
@@ -226,7 +235,7 @@ class WPAKeyDialog(KeyDialog):
self.combo.set_active(0)
self.hbox = gtk.HBox()
- self.hbox.pack_start(gtk.Label(_("Wireless Security:")))
+ self.hbox.pack_start(gtk.Label(_('Wireless Security:')))
self.hbox.pack_start(self.combo)
self.hbox.show_all()
@@ -246,21 +255,21 @@ class WPAKeyDialog(KeyDialog):
from subprocess import Popen, PIPE
p = Popen(['wpa_passphrase', ssid, key], stdout=PIPE)
for line in p.stdout:
- if line.strip().startswith("psk="):
+ if line.strip().startswith('psk='):
real_key = line.strip()[4:]
if p.wait() != 0:
- raise RuntimeError("Error hashing passphrase")
+ raise RuntimeError('Error hashing passphrase')
if real_key and len(real_key) != 64:
real_key = None
if not real_key:
- raise RuntimeError("Invalid key")
+ raise RuntimeError('Invalid key')
return real_key
def print_security(self):
key = self._get_security()
- print "Key: %s" % key
+ print 'Key: %s' % key
def create_security(self):
secrets = Secrets(self._settings)
@@ -281,6 +290,7 @@ class WPAKeyDialog(KeyDialog):
self.set_response_sensitive(gtk.RESPONSE_OK, valid)
return False
+
def create(ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, response):
if wpa_flags == network.NM_802_11_AP_SEC_NONE and \
rsn_flags == network.NM_802_11_AP_SEC_NONE:
@@ -290,13 +300,15 @@ def create(ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, response):
key_dialog = WPAKeyDialog(ssid, flags, wpa_flags, rsn_flags,
dev_caps, settings, response)
- key_dialog.connect("response", _key_dialog_response_cb)
- key_dialog.connect("destroy", _key_dialog_destroy_cb)
+ key_dialog.connect('response', _key_dialog_response_cb)
+ key_dialog.connect('destroy', _key_dialog_destroy_cb)
key_dialog.show_all()
+
def _key_dialog_destroy_cb(key_dialog, data=None):
_key_dialog_response_cb(key_dialog, gtk.RESPONSE_CANCEL)
+
def _key_dialog_response_cb(key_dialog, response_id):
response = key_dialog.get_response_object()
secrets = None
@@ -308,10 +320,9 @@ def _key_dialog_response_cb(key_dialog, response_id):
response.set_error(CanceledKeyRequestError())
elif response_id == gtk.RESPONSE_OK:
if not secrets:
- raise RuntimeError("Invalid security arguments.")
+ raise RuntimeError('Invalid security arguments.')
response.set_secrets(secrets)
else:
- raise RuntimeError("Unhandled key dialog response %d" % response_id)
+ raise RuntimeError('Unhandled key dialog response %d' % response_id)
key_dialog.destroy()
-
diff --git a/src/jarabe/desktop/meshbox.py b/src/jarabe/desktop/meshbox.py
index a04922b..ad4b873 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 One Laptop per Child
+# Copyright (C) 2009-2010 One Laptop per Child
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
#
# 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,41 +19,34 @@
from gettext import gettext as _
import logging
-import hashlib
import dbus
import hippo
+import glib
import gobject
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 import shell
-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
+from jarabe.journal import misc
+
_NM_SERVICE = 'org.freedesktop.NetworkManager'
_NM_IFACE = 'org.freedesktop.NetworkManager'
@@ -66,522 +60,7 @@ _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 self._name_encodes_colors():
- encoded_color = self._name.split("#", 1)
- if len(encoded_color) == 2:
- self._color = xocolor.XoColor('#' + encoded_color[1])
- 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 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 initial_ap.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
-
- 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 _name_encodes_colors(self):
- """Match #XXXXXX,#YYYYYY at the end of the network name"""
- return self._name[-7] == '#' and self._name[-8] == ',' \
- and self._name[-15] == '#'
-
- 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=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 state == network.DEVICE_STATE_ACTIVATED:
- connection = network.find_connection_by_ssid(self._name)
- if connection:
- 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 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):
- 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_OLPC_MESH_IFACE)
+_AUTOSEARCH_TIMEOUT = 1000
class ActivityView(hippo.CanvasBox):
@@ -589,6 +68,9 @@ class ActivityView(hippo.CanvasBox):
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
@@ -598,31 +80,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_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=self._model.activity.props.name,
+ 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')
@@ -637,42 +118,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)
+ return key in self._icons
+
+ def __buddy_added_cb(self, activity, buddy):
+ self._add_buddy(buddy)
- def add_buddy_icon(self, key, icon):
- self._icons[key] = icon
+ 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):
- shell_model = shell.get_model()
- activity = shell_model.get_activity_by_id(self._model.get_id())
- if activity:
- activity.get_window().activate(gtk.get_current_event_time())
- return
-
- bundle_id = self._model.get_bundle_id()
- bundle = bundleregistry.get_registry().get_bundle(bundle_id)
-
- launcher.add_launcher(self._model.get_id(),
- bundle.get_icon(),
- self._model.get_color())
-
- handle = ActivityHandle(self._model.get_id())
- activityfactory.create(bundle, handle)
+ 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()
@@ -683,31 +152,13 @@ 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
-
class MeshToolbar(gtk.Toolbar):
__gtype_name__ = 'MeshToolbar'
__gsignals__ = {
- 'query-changed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([str]))
+ 'query-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([str])),
}
def __init__(self):
@@ -770,15 +221,23 @@ class MeshToolbar(gtk.Toolbar):
return False
-class DeviceObserver(object):
- def __init__(self, box, device):
- self._box = box
+class DeviceObserver(gobject.GObject):
+ __gsignals__ = {
+ 'access-point-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'access-point-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ }
+
+ def __init__(self, device):
+ gobject.GObject.__init__(self)
self._bus = dbus.SystemBus()
- self._device = device
+ self.device = device
- wireless = dbus.Interface(self._device, _NM_WIRELESS_IFACE)
- wireless.GetAccessPoints(reply_handler=self._get_access_points_reply_cb,
- error_handler=self._get_access_points_error_cb)
+ wireless = dbus.Interface(device, _NM_WIRELESS_IFACE)
+ wireless.GetAccessPoints(
+ reply_handler=self._get_access_points_reply_cb,
+ error_handler=self._get_access_points_error_cb)
self._bus.add_signal_receiver(self.__access_point_added_cb,
signal_name='AccessPointAdded',
@@ -792,35 +251,42 @@ class DeviceObserver(object):
def _get_access_points_reply_cb(self, access_points_o):
for ap_o in access_points_o:
ap = self._bus.get_object(_NM_SERVICE, ap_o)
- self._box.add_access_point(self._device, ap)
+ self.emit('access-point-added', ap)
def _get_access_points_error_cb(self, err):
logging.error('Failed to get access points: %s', err)
def __access_point_added_cb(self, access_point_o):
ap = self._bus.get_object(_NM_SERVICE, access_point_o)
- self._box.add_access_point(self._device, ap)
+ self.emit('access-point-added', ap)
def __access_point_removed_cb(self, access_point_o):
- self._box.remove_access_point(access_point_o)
+ self.emit('access-point-removed', access_point_o)
def disconnect(self):
self._bus.remove_signal_receiver(self.__access_point_added_cb,
signal_name='AccessPointAdded',
- path=self._device.object_path,
+ path=self.device.object_path,
dbus_interface=_NM_WIRELESS_IFACE)
self._bus.remove_signal_receiver(self.__access_point_removed_cb,
signal_name='AccessPointRemoved',
- path=self._device.object_path,
+ path=self.device.object_path,
dbus_interface=_NM_WIRELESS_IFACE)
class NetworkManagerObserver(object):
+
+ _SHOW_ADHOC_GCONF_KEY = '/desktop/sugar/network/adhoc'
+
def __init__(self, box):
self._box = box
self._bus = None
self._devices = {}
self._netmgr = None
+ self._olpc_mesh_device_o = None
+
+ client = gconf.client_get_default()
+ self._have_adhoc_networks = client.get_bool(self._SHOW_ADHOC_GCONF_KEY)
def listen(self):
try:
@@ -840,6 +306,9 @@ class NetworkManagerObserver(object):
self._bus.add_signal_receiver(self.__device_removed_cb,
signal_name='DeviceRemoved',
dbus_interface=_NM_IFACE)
+ self._bus.add_signal_receiver(self.__properties_changed_cb,
+ signal_name='PropertiesChanged',
+ dbus_interface=_NM_IFACE)
settings = network.get_settings()
if settings is not None:
@@ -849,13 +318,12 @@ class NetworkManagerObserver(object):
# FIXME It would be better to do all of this async, but I cannot think
# of a good way to. NM could really use some love here.
- netmgr_props = dbus.Interface(
- self._netmgr, 'org.freedesktop.DBus.Properties')
+ netmgr_props = dbus.Interface(self._netmgr, dbus.PROPERTIES_IFACE)
active_connections_o = netmgr_props.Get(_NM_IFACE, 'ActiveConnections')
for conn_o in active_connections_o:
obj = self._bus.get_object(_NM_IFACE, conn_o)
- props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ props = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
state = props.Get(_NM_ACTIVE_CONN_IFACE, 'State')
if state == network.NM_ACTIVE_CONNECTION_STATE_ACTIVATING:
ap_o = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject')
@@ -867,8 +335,8 @@ class NetworkManagerObserver(object):
settings = kwargs['connection'].get_settings()
net.create_keydialog(settings, kwargs['response'])
if not found:
- logging.error('Could not determine AP for'
- ' specific object %s' % conn_o)
+ logging.error('Could not determine AP for specific object'
+ ' %s', conn_o)
def __get_devices_reply_cb(self, devices_o):
for dev_o in devices_o:
@@ -879,12 +347,19 @@ class NetworkManagerObserver(object):
def _check_device(self, device_o):
device = self._bus.get_object(_NM_SERVICE, device_o)
- props = dbus.Interface(device, 'org.freedesktop.DBus.Properties')
+ props = dbus.Interface(device, dbus.PROPERTIES_IFACE)
device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType')
if device_type == network.DEVICE_TYPE_802_11_WIRELESS:
- self._devices[device_o] = DeviceObserver(self._box, device)
+ self._devices[device_o] = DeviceObserver(device)
+ self._devices[device_o].connect('access-point-added',
+ self.__ap_added_cb)
+ self._devices[device_o].connect('access-point-removed',
+ self.__ap_removed_cb)
+ if self._have_adhoc_networks:
+ self._box.add_adhoc_networks(device)
elif device_type == network.DEVICE_TYPE_802_11_OLPC_MESH:
+ self._olpc_mesh_device_o = device_o
self._box.enable_olpc_mesh(device)
def _get_device_path_error_cb(self, err):
@@ -898,23 +373,41 @@ class NetworkManagerObserver(object):
observer = self._devices[device_o]
observer.disconnect()
del self._devices[device_o]
+ if self._have_adhoc_networks:
+ self._box.remove_adhoc_networks()
return
-
- device = self._bus.get_object(_NM_SERVICE, device_o)
- props = dbus.Interface(device, 'org.freedesktop.DBus.Properties')
- device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType')
- if device_type == network.DEVICE_TYPE_802_11_OLPC_MESH:
- self._box.disable_olpc_mesh(device)
+
+ if self._olpc_mesh_device_o == device_o:
+ self._box.disable_olpc_mesh(device_o)
+
+ def __ap_added_cb(self, device_observer, access_point):
+ self._box.add_access_point(device_observer.device, access_point)
+
+ def __ap_removed_cb(self, device_observer, access_point_o):
+ self._box.remove_access_point(access_point_o)
+
+ def __properties_changed_cb(self, properties):
+ if 'WirelessHardwareEnabled' in properties:
+ if properties['WirelessHardwareEnabled']:
+ if not self._have_adhoc_networks:
+ self._box.remove_adhoc_networks()
+ elif properties['WirelessHardwareEnabled']:
+ for device in self._devices:
+ if self._have_adhoc_networks:
+ self._box.add_adhoc_networks(device)
+
class MeshBox(gtk.VBox):
__gtype_name__ = 'SugarMeshBox'
def __init__(self):
- logging.debug("STARTUP: Loading the mesh view")
+ logging.debug('STARTUP: Loading the mesh view')
gobject.GObject.__init__(self)
self.wireless_networks = {}
+ self._adhoc_manager = None
+ self._adhoc_networks = []
self._model = neighborhood.get_model()
self._buddies = {}
@@ -942,11 +435,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)
@@ -970,24 +462,22 @@ 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):
+ 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
@@ -996,36 +486,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)
@@ -1034,58 +511,70 @@ 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,
# creating one if it doesn't already exist
def _add_ap_to_network(self, ap):
- hash = ap.network_hash()
- if hash in self.wireless_networks:
- self.wireless_networks[hash].add_ap(ap)
+ hash_value = ap.network_hash()
+ if hash_value in self.wireless_networks:
+ self.wireless_networks[hash_value].add_ap(ap)
else:
# this is a new network
icon = WirelessNetworkView(ap)
- self.wireless_networks[hash] = icon
+ self.wireless_networks[hash_value] = icon
self._layout.add(icon)
if hasattr(icon, 'set_filter'):
icon.set_filter(self._query)
- def _remove_net_if_empty(self, net, hash):
+ def _remove_net_if_empty(self, net, hash_value):
# remove a network if it has no APs left
if net.num_aps() == 0:
net.disconnect()
self._layout.remove(net)
- del self.wireless_networks[hash]
+ del self.wireless_networks[hash_value]
- def _ap_props_changed_cb(self, ap, old_hash):
+ def _ap_props_changed_cb(self, ap, old_hash_value):
# if we have mesh hardware, ignore OLPC mesh networks that appear as
# normal wifi networks
if len(self._mesh) > 0 and ap.mode == network.NM_802_11_MODE_ADHOC \
- and ap.name == "olpc-mesh":
- logging.debug("ignoring OLPC mesh IBSS")
+ and ap.name == 'olpc-mesh':
+ logging.debug('ignoring OLPC mesh IBSS')
ap.disconnect()
return
- if old_hash is None: # new AP finished initializing
+ if self._adhoc_manager is not None and \
+ network.is_sugar_adhoc_network(ap.name) and \
+ ap.mode == network.NM_802_11_MODE_ADHOC:
+ if old_hash_value is None:
+ # new Ad-hoc network finished initializing
+ self._adhoc_manager.add_access_point(ap)
+ # we are called as well in other cases but we do not need to
+ # act here as we don't display signal strength for Ad-hoc networks
+ return
+
+ if old_hash_value is None:
+ # new AP finished initializing
self._add_ap_to_network(ap)
return
- hash = ap.network_hash()
- if old_hash == hash:
+ hash_value = ap.network_hash()
+ if old_hash_value == hash_value:
# no change in network identity, so just update signal strengths
- self.wireless_networks[hash].update_strength()
+ self.wireless_networks[hash_value].update_strength()
return
# properties change includes a change of the identity of the network
# that it is on. so create this as a new network.
- self.wireless_networks[old_hash].remove_ap(ap)
- self._remove_net_if_empty(self.wireless_networks[old_hash], old_hash)
+ self.wireless_networks[old_hash_value].remove_ap(ap)
+ self._remove_net_if_empty(self.wireless_networks[old_hash_value],
+ old_hash_value)
self._add_ap_to_network(ap)
def add_access_point(self, device, ap_o):
@@ -1094,6 +583,11 @@ class MeshBox(gtk.VBox):
ap.initialize()
def remove_access_point(self, ap_o):
+ if self._adhoc_manager is not None:
+ if self._adhoc_manager.is_sugar_adhoc_access_point(ap_o):
+ self._adhoc_manager.remove_access_point(ap_o)
+ return
+
# we don't keep an index of ap object path to network, but since
# we'll only ever have a handful of networks, just try them all...
for net in self.wireless_networks.values():
@@ -1108,7 +602,26 @@ class MeshBox(gtk.VBox):
# it's not an error if the AP isn't found, since we might have ignored
# it (e.g. olpc-mesh adhoc network)
- logging.debug('Can not remove access point %s' % ap_o)
+ logging.debug('Can not remove access point %s', ap_o)
+
+ def add_adhoc_networks(self, device):
+ if self._adhoc_manager is None:
+ self._adhoc_manager = get_adhoc_manager_instance()
+ self._adhoc_manager.start_listening(device)
+ self._add_adhoc_network_icon(1)
+ self._add_adhoc_network_icon(6)
+ self._add_adhoc_network_icon(11)
+ self._adhoc_manager.autoconnect()
+
+ def remove_adhoc_networks(self):
+ for icon in self._adhoc_networks:
+ self._layout.remove(icon)
+ self._adhoc_networks = []
+
+ def _add_adhoc_network_icon(self, channel):
+ icon = SugarAdhocView(channel)
+ self._layout.add(icon)
+ self._adhoc_networks.append(icon)
def _add_olpc_mesh_icon(self, mesh_mgr, channel):
icon = OlpcMeshView(mesh_mgr, channel)
@@ -1123,15 +636,15 @@ class MeshBox(gtk.VBox):
# the OLPC mesh can be recognised as a "normal" wifi network. remove
# any such normal networks if they have been created
- for hash, net in self.wireless_networks.iteritems():
+ for hash_value, net in self.wireless_networks.iteritems():
if not net.is_olpc_mesh():
continue
- logging.debug("removing OLPC mesh IBSS")
+ logging.debug('removing OLPC mesh IBSS')
net.remove_all_aps()
net.disconnect()
self._layout.remove(net)
- del self.wireless_networks[hash]
+ del self.wireless_networks[hash_value]
def disable_olpc_mesh(self, mesh_device):
for icon in self._mesh:
diff --git a/src/jarabe/desktop/networkviews.py b/src/jarabe/desktop/networkviews.py
new file mode 100644
index 0000000..99d46b6
--- /dev/null
+++ b/src/jarabe/desktop/networkviews.py
@@ -0,0 +1,721 @@
+# 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._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
+ self._update_badge()
+
+ interface_props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE)
+ 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()
+ self._update_icon()
+ self._update_badge()
+
+ 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()
+ elif self._active_ap is not None:
+ # revert to showing state of strongest AP again
+ self._active_ap = None
+ self.update_strength()
+
+ 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_state()
+ self._update_color()
+ self._update_badge()
+
+ def __get_device_state_error_cb(self, err):
+ logging.error('Error getting the device state: %s', err)
+
+ def _update_icon(self):
+ 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 self._device_state == network.DEVICE_STATE_ACTIVATED and \
+ self._active_ap is not None:
+ 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 self._device_state == network.DEVICE_STATE_ACTIVATED and \
+ self._active_ap is not None:
+ 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
+
+ def _update_badge(self):
+ 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
+
+ def _update_state(self):
+ if self._active_ap is not None:
+ state = self._device_state
+ else:
+ state = network.DEVICE_STATE_UNKNOWN
+
+ 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:
+ 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()
+ 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):
+ if self._mode == network.NM_802_11_MODE_INFRA:
+ connection = network.find_connection_by_ssid(self._name)
+ if connection:
+ connection.disable_autoconnect()
+
+ ap_paths = self._access_points.keys()
+ network.disconnect_access_points(ap_paths)
+
+ 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_icon()
+ 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_icon()
+
+ 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)
+
+ 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.pulsing = False
+ 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()
+ else:
+ color = '%s,%s' % (profile.get_color().get_stroke_color(),
+ style.COLOR_TRANSPARENT.get_svg())
+ self._state_color = XoColor(color)
+
+ if not self._greyed_out:
+ 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._active = False
+ device = mesh_mgr.mesh_device
+
+ self.connect('button-release-event', self.__button_release_event_cb)
+
+ interface_props = dbus.Interface(device, dbus.PROPERTIES_IFACE)
+ 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/schoolserver.py b/src/jarabe/desktop/schoolserver.py
index fc9ddeb..aea2357 100644
--- a/src/jarabe/desktop/schoolserver.py
+++ b/src/jarabe/desktop/schoolserver.py
@@ -16,8 +16,9 @@
import logging
from gettext import gettext as _
-from xmlrpclib import ServerProxy, Error
+import xmlrpclib
import socket
+import httplib
import os
from string import ascii_uppercase
import random
@@ -29,15 +30,17 @@ import gconf
from sugar import env
from sugar.profile import get_profile
-REGISTER_URL = 'http://schoolserver:8080/'
+_REGISTER_URL = 'http://schoolserver:8080/'
+_REGISTER_TIMEOUT = 8
-def generate_serial_number():
+
+def _generate_serial_number():
""" Generates a serial number based on 3 random uppercase letters
and the last 8 digits of the current unix seconds. """
serial_part1 = []
- for y_ in range(3) :
+ for y_ in range(3):
serial_part1.append(random.choice(ascii_uppercase))
serial_part1 = ''.join(serial_part1)
@@ -46,7 +49,8 @@ def generate_serial_number():
return serial
-def store_identifiers(serial_number, uuid, backup_url):
+
+def _store_identifiers(serial_number, uuid_, backup_url):
""" Stores the serial number, uuid and backup_url
in the identifier folder inside the profile directory
so that these identifiers can be used for backup. """
@@ -64,7 +68,7 @@ def store_identifiers(serial_number, uuid, backup_url):
if os.path.exists(os.path.join(identifier_path, 'uuid')):
os.remove(os.path.join(identifier_path, 'uuid'))
uuid_file = open(os.path.join(identifier_path, 'uuid'), 'w')
- uuid_file.write(uuid)
+ uuid_file.write(uuid_)
uuid_file.close()
if os.path.exists(os.path.join(identifier_path, 'backup_url')):
@@ -73,33 +77,56 @@ def store_identifiers(serial_number, uuid, backup_url):
backup_url_file.write(backup_url)
backup_url_file.close()
+
class RegisterError(Exception):
pass
-def register_laptop(url=REGISTER_URL):
+
+class _TimeoutHTTP(httplib.HTTP):
+
+ def __init__(self, host='', port=None, strict=None, timeout=None):
+ if port == 0:
+ port = None
+ # FIXME: Depending on undocumented internals that can break between
+ # Python releases. Please have a look at SL #2350
+ self._setup(self._connection_class(host,
+ port, strict, timeout=_REGISTER_TIMEOUT))
+
+
+class _TimeoutTransport(xmlrpclib.Transport):
+
+ def make_connection(self, host):
+ host, extra_headers, x509_ = self.get_host_info(host)
+ return _TimeoutHTTP(host, timeout=_REGISTER_TIMEOUT)
+
+
+def register_laptop(url=_REGISTER_URL):
profile = get_profile()
client = gconf.client_get_default()
- if have_ofw_tree():
- sn = read_ofw('mfg-data/SN')
- uuid_ = read_ofw('mfg-data/U#')
+ if _have_ofw_tree():
+ sn = _read_ofw('mfg-data/SN')
+ uuid_ = _read_ofw('mfg-data/U#')
sn = sn or 'SHF00000000'
uuid_ = uuid_ or '00000000-0000-0000-0000-000000000000'
else:
- sn = generate_serial_number()
+ sn = _generate_serial_number()
uuid_ = str(uuid.uuid1())
- setting_name = '/desktop/sugar/collaboration/jabber_server'
- jabber_server = client.get_string(setting_name)
- store_identifiers(sn, uuid_, jabber_server)
+
+ setting_name = '/desktop/sugar/collaboration/jabber_server'
+ jabber_server = client.get_string(setting_name)
+ _store_identifiers(sn, uuid_, jabber_server)
+
+ if jabber_server:
url = 'http://' + jabber_server + ':8080/'
nick = client.get_string('/desktop/sugar/user/nick')
- server = ServerProxy(url)
+ server = xmlrpclib.ServerProxy(url, _TimeoutTransport())
try:
data = server.register(sn, nick, uuid_, profile.pubkey)
- except (Error, socket.error):
+ except (xmlrpclib.Error, TypeError, socket.error):
logging.exception('Registration: cannot connect to server')
raise RegisterError(_('Cannot connect to the server.'))
@@ -114,10 +141,12 @@ def register_laptop(url=REGISTER_URL):
return True
-def have_ofw_tree():
+
+def _have_ofw_tree():
return os.path.exists('/ofw')
-def read_ofw(path):
+
+def _read_ofw(path):
path = os.path.join('/ofw', path)
if not os.path.exists(path):
return None
diff --git a/src/jarabe/desktop/snowflakelayout.py b/src/jarabe/desktop/snowflakelayout.py
index 5782cff..e4963ba 100644
--- a/src/jarabe/desktop/snowflakelayout.py
+++ b/src/jarabe/desktop/snowflakelayout.py
@@ -21,11 +21,14 @@ import hippo
from sugar.graphics import style
+
_BASE_DISTANCE = style.zoom(25)
_CHILDREN_FACTOR = style.zoom(3)
+
class SnowflakeLayout(gobject.GObject, hippo.CanvasLayout):
__gtype_name__ = 'SugarSnowflakeLayout'
+
def __init__(self):
gobject.GObject.__init__(self)
self._nflakes = 0
diff --git a/src/jarabe/desktop/spreadlayout.py b/src/jarabe/desktop/spreadlayout.py
index ffc5bc7..9200361 100644
--- a/src/jarabe/desktop/spreadlayout.py
+++ b/src/jarabe/desktop/spreadlayout.py
@@ -22,10 +22,13 @@ from sugar.graphics import style
from jarabe.desktop.grid import Grid
+
_CELL_SIZE = 4.0
+
class SpreadLayout(gobject.GObject, hippo.CanvasLayout):
__gtype_name__ = 'SugarSpreadLayout'
+
def __init__(self):
gobject.GObject.__init__(self)
self._box = None
@@ -80,4 +83,3 @@ class SpreadLayout(gobject.GObject, hippo.CanvasLayout):
def _grid_child_changed_cb(self, grid, child):
child.emit_request_changed()
-
diff --git a/src/jarabe/desktop/transitionbox.py b/src/jarabe/desktop/transitionbox.py
index af17cfb..4042044 100644
--- a/src/jarabe/desktop/transitionbox.py
+++ b/src/jarabe/desktop/transitionbox.py
@@ -20,7 +20,9 @@ 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):
@@ -34,8 +36,10 @@ class _Animation(animator.Animation):
d = (self.end_size - self.start_size) * current
self._icon.props.size = self.start_size + d
+
class _Layout(gobject.GObject, hippo.CanvasLayout):
__gtype_name__ = 'SugarTransitionBoxLayout'
+
def __init__(self):
gobject.GObject.__init__(self)
self._box = None
@@ -59,12 +63,12 @@ class _Layout(gobject.GObject, hippo.CanvasLayout):
y + (height - child_height) / 2,
child_width, child_height, origin_changed)
+
class TransitionBox(hippo.Canvas):
__gtype_name__ = 'SugarTransitionBox'
__gsignals__ = {
- 'completed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([]))
+ 'completed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
}
def __init__(self):
@@ -77,7 +81,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/__init__.py b/src/jarabe/frame/__init__.py
index d7aec3d..b3e4b80 100644
--- a/src/jarabe/frame/__init__.py
+++ b/src/jarabe/frame/__init__.py
@@ -16,8 +16,10 @@
from jarabe.frame.frame import Frame
+
_view = None
+
def get_view():
global _view
if not _view:
diff --git a/src/jarabe/frame/activitiestray.py b/src/jarabe/frame/activitiestray.py
index b5762ee..6e08fc0 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. <http://www.collabora.co.uk/>
#
# 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,20 +34,16 @@ 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
import jarabe.frame
@@ -59,6 +56,7 @@ class ActivityButton(RadioToolButton):
self.set_palette_invoker(FrameWidgetInvoker(self))
self._home_activity = home_activity
+ self._notify_launch_hid = None
self._icon = PulsingIcon()
self._icon.props.base_color = home_activity.get_icon_color()
@@ -72,13 +70,12 @@ class ActivityButton(RadioToolButton):
self.set_icon_widget(self._icon)
self._icon.show()
- if home_activity.props.launching:
+ if home_activity.props.launch_status == shell.Activity.LAUNCHING:
self._icon.props.pulsing = True
- self._notify_launching_hid = home_activity.connect( \
- 'notify::launching', self.__notify_launching_cb)
- else:
- self._notify_launching_hid = None
- self._notif_icon = None
+ self._notify_launch_hid = home_activity.connect( \
+ 'notify::launch-status', self.__notify_launch_status_cb)
+ elif home_activity.props.launch_status == shell.Activity.LAUNCH_FAILED:
+ self._on_failed_launch()
def create_palette(self):
if self._home_activity.is_journal():
@@ -88,26 +85,64 @@ class ActivityButton(RadioToolButton):
palette.set_group_id('frame')
self.set_palette(palette)
- def __notify_launching_cb(self, home_activity, pspec):
- if not home_activity.props.launching:
+ def _on_failed_launch(self):
+ # TODO http://bugs.sugarlabs.org/ticket/2007
+ pass
+
+ def __notify_launch_status_cb(self, home_activity, pspec):
+ home_activity.disconnect(self._notify_launch_hid)
+ self._notify_launch_hid = None
+ if home_activity.props.launch_status == shell.Activity.LAUNCH_FAILED:
+ self._on_failed_launch()
+ else:
self._icon.props.pulsing = False
- home_activity.disconnect(self._notify_launching_hid)
-class BaseInviteButton(ToolButton):
+
+class InviteButton(ToolButton):
+ """Invite to shared activity"""
def __init__(self, invite):
ToolButton.__init__(self)
+
self._invite = invite
+ self.connect('clicked', self.__clicked_cb)
+ self.connect('destroy', self.__destroy_cb)
+
+ bundle_registry = bundleregistry.get_registry()
+ bundle = bundle_registry.get_bundle(invite.get_bundle_id())
+
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()
- self.connect('clicked', self.__clicked_cb)
- self.connect('destroy', self.__destroy_cb)
+ palette = InvitePalette(invite)
+ palette.props.invoker = FrameWidgetInvoker(self)
+ palette.set_group_id('frame')
+ self.set_palette(palette)
+
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 = 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 __button_release_event_cb(self, icon, event):
self.emit('clicked')
@@ -118,118 +153,23 @@ class BaseInviteButton(ToolButton):
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()
- else:
- self._icon.props.icon_name = 'image-missing'
-
- palette = ActivityInvitePalette(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
- else:
- self._notif_icon.props.icon_name = 'image-missing'
-
- palette = ActivityInvitePalette(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."""
+ self._invite.join()
- shell_model = shell.get_model()
- activity = shell_model.get_activity_by_id(self._activity_model.get_id())
- if activity:
- activity.get_window().activate(gtk.get_current_event_time())
- return
-
- registry = bundleregistry.get_registry()
- bundle = registry.get_bundle(self._bundle_id)
-
- launcher.add_launcher(self._activity_model.get_id(),
- bundle.get_icon(),
- self._activity_model.get_color())
- handle = ActivityHandle(self._activity_model.get_id())
- activityfactory.create(bundle, handle)
+class InvitePalette(Palette):
+ """Palette for frame or notification icon for invites."""
-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)
-
- 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'
-
- palette = PrivateInvitePalette(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):
- """Start the activity with private channel."""
- activityfactory.create_with_uri(self._bundle, self._private_channel)
-
-class BaseInvitePalette(Palette):
- """Palette for frame or notification icon for invites."""
- def __init__(self):
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)
@@ -240,72 +180,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):
- handle = ActivityHandle(self._activity_model.get_id())
- activityfactory.create(self._bundle, handle)
+ 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):
- activityfactory.create_with_uri(self._bundle, 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):
@@ -318,13 +208,14 @@ class ActivitiesTray(HTray):
self._home_model = shell.get_model()
self._home_model.connect('activity-added', self.__activity_added_cb)
- self._home_model.connect('activity-removed', self.__activity_removed_cb)
+ self._home_model.connect('activity-removed',
+ self.__activity_removed_cb)
self._home_model.connect('active-activity-changed',
self.__activity_changed_cb)
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)
@@ -388,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])
@@ -432,6 +313,7 @@ class ActivitiesTray(HTray):
self.add_item(button)
button.show()
+
class BaseTransferButton(ToolButton):
"""Button with a notification attached
"""
@@ -468,6 +350,7 @@ class BaseTransferButton(ToolButton):
filetransfer.FT_REASON_LOCAL_STOPPED:
self.remove()
+
class IncomingTransferButton(BaseTransferButton):
"""UI element representing an ongoing incoming file transfer
"""
@@ -490,7 +373,7 @@ class IncomingTransferButton(BaseTransferButton):
self.notif_icon.props.icon_name = icon_name
break
- icon_color = XoColor(file_transfer.buddy.props.color)
+ icon_color = file_transfer.buddy.props.color
self.props.icon_widget.props.xo_color = icon_color
self.notif_icon.props.xo_color = icon_color
@@ -515,18 +398,18 @@ class IncomingTransferButton(BaseTransferButton):
self._ds_object.metadata['buddies'] = ''
self._ds_object.metadata['preview'] = ''
self._ds_object.metadata['icon-color'] = \
- file_transfer.buddy.props.color
+ file_transfer.buddy.props.color.to_string()
self._ds_object.metadata['mime_type'] = file_transfer.mime_type
elif file_transfer.props.state == filetransfer.FT_STATE_COMPLETED:
logging.debug('__notify_state_cb COMPLETED')
self._ds_object.metadata['progress'] = '100'
self._ds_object.file_path = file_transfer.destination_path
- datastore.write(self._ds_jobject, transfer_ownership=True,
+ datastore.write(self._ds_object, transfer_ownership=True,
reply_handler=self.__reply_handler_cb,
error_handler=self.__error_handler_cb)
elif file_transfer.props.state == filetransfer.FT_STATE_CANCELLED:
logging.debug('__notify_state_cb CANCELLED')
- object_id = self._jobject.object_id
+ object_id = self._ds_object.object_id
if object_id is not None:
self._ds_object.destroy()
datastore.delete(object_id)
@@ -536,17 +419,19 @@ class IncomingTransferButton(BaseTransferButton):
progress = file_transfer.props.transferred_bytes / \
file_transfer.file_size
self._ds_object.metadata['progress'] = str(progress * 100)
- datastore.write(self._ds_object.object_id, update_mtime=False)
+ datastore.write(self._ds_object, update_mtime=False)
def __reply_handler_cb(self):
- logging.debug('__reply_handler_cb %r', self._object_id)
+ logging.debug('__reply_handler_cb %r', self._ds_object.object_id)
def __error_handler_cb(self, error):
- logging.debug('__error_handler_cb %r %s', self._object_id, error)
+ logging.debug('__error_handler_cb %r %s', self._ds_object.object_id,
+ error)
def __dismiss_clicked_cb(self, palette):
self.remove()
+
class OutgoingTransferButton(BaseTransferButton):
"""UI element representing an ongoing outgoing file transfer
"""
@@ -564,7 +449,7 @@ class OutgoingTransferButton(BaseTransferButton):
break
client = gconf.client_get_default()
- icon_color = XoColor(client.get_string("/desktop/sugar/user/color"))
+ icon_color = XoColor(client.get_string('/desktop/sugar/user/color'))
self.props.icon_widget.props.xo_color = icon_color
self.notif_icon.props.xo_color = icon_color
@@ -582,14 +467,14 @@ class OutgoingTransferButton(BaseTransferButton):
def __dismiss_clicked_cb(self, palette):
self.remove()
+
class BaseTransferPalette(Palette):
"""Base palette class for frame or notification icon for file transfers
"""
- __gtype_name__ = "SugarBaseTransferPalette"
+ __gtype_name__ = 'SugarBaseTransferPalette'
__gsignals__ = {
- 'dismiss-clicked': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([])),
+ 'dismiss-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
}
def __init__(self, file_transfer):
@@ -644,10 +529,12 @@ class BaseTransferPalette(Palette):
total = self._format_size(self.file_transfer.file_size)
self.progress_label.props.label = _('%s of %s') % (transferred, total)
+
class IncomingTransferPalette(BaseTransferPalette):
"""Palette for frame or notification icon for incoming file transfers
"""
- __gtype_name__ = "SugarIncomingTransferPalette"
+ __gtype_name__ = 'SugarIncomingTransferPalette'
+
def __init__(self, file_transfer):
BaseTransferPalette.__init__(self, file_transfer)
@@ -770,10 +657,11 @@ class IncomingTransferPalette(BaseTransferPalette):
def __dismiss_activate_cb(self, menu_item):
self.emit('dismiss-clicked')
+
class OutgoingTransferPalette(BaseTransferPalette):
"""Palette for frame or notification icon for outgoing file transfers
"""
- __gtype_name__ = "SugarOutgoingTransferPalette"
+ __gtype_name__ = 'SugarOutgoingTransferPalette'
def __init__(self, file_transfer):
BaseTransferPalette.__init__(self, file_transfer)
diff --git a/src/jarabe/frame/clipboard.py b/src/jarabe/frame/clipboard.py
index 3b9f745..be2b902 100644
--- a/src/jarabe/frame/clipboard.py
+++ b/src/jarabe/frame/clipboard.py
@@ -26,6 +26,10 @@ from sugar import mime
from jarabe.frame.clipboardobject import ClipboardObject, Format
+
+_instance = None
+
+
class Clipboard(gobject.GObject):
__gsignals__ = {
@@ -34,7 +38,7 @@ class Clipboard(gobject.GObject):
'object-deleted': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([int])),
'object-state-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
- ([object]))
+ ([object])),
}
def __init__(self):
@@ -69,7 +73,7 @@ class Clipboard(gobject.GObject):
+ ' with path at ' + new_uri)
else:
cb_object.add_format(Format(format_type, data, on_disk))
- logging.debug('Added in-memory format of type ' + format_type + '.')
+ logging.debug('Added in-memory format of type %s.', format_type)
self.emit('object-state-changed', cb_object)
@@ -82,9 +86,9 @@ class Clipboard(gobject.GObject):
def set_object_percent(self, object_id, percent):
cb_object = self._objects[object_id]
if percent < 0 or percent > 100:
- raise ValueError("invalid percentage")
+ raise ValueError('invalid percentage')
if cb_object.get_percent() > percent:
- raise ValueError("invalid percentage; less than current percent")
+ raise ValueError('invalid percentage; less than current percent')
if cb_object.get_percent() == percent:
# ignore setting same percentage
return
@@ -126,21 +130,21 @@ class Clipboard(gobject.GObject):
def _copy_file(self, original_uri):
uri = urlparse.urlparse(original_uri)
- path_, file_name = os.path.split(uri.path)
+ path = uri.path # pylint: disable=E1101
+ directory_, file_name = os.path.split(path)
root, ext = os.path.splitext(file_name)
if not ext or ext == '.':
- mime_type = mime.get_for_file(uri.path)
+ mime_type = mime.get_for_file(path)
ext = '.' + mime.get_primary_extension(mime_type)
f_, new_file_path = tempfile.mkstemp(ext, root)
del f_
- shutil.copyfile(uri.path, new_file_path)
+ shutil.copyfile(path, new_file_path)
os.chmod(new_file_path, 0644)
return 'file://' + new_file_path
-_instance = None
def get_instance():
global _instance
diff --git a/src/jarabe/frame/clipboardicon.py b/src/jarabe/frame/clipboardicon.py
index 279db08..aa72d8a 100644
--- a/src/jarabe/frame/clipboardicon.py
+++ b/src/jarabe/frame/clipboardicon.py
@@ -31,6 +31,7 @@ from jarabe.frame.frameinvoker import FrameWidgetInvoker
from jarabe.frame.notification import NotificationIcon
import jarabe.frame
+
class ClipboardIcon(RadioToolButton):
__gtype_name__ = 'SugarClipboardIcon'
@@ -71,7 +72,8 @@ class ClipboardIcon(RadioToolButton):
def _drag_data_get_cb(self, widget, context, selection, target_type,
event_time):
- logging.debug('_drag_data_get_cb: requested target ' + selection.target)
+ logging.debug('_drag_data_get_cb: requested target %s',
+ selection.target)
data = self._cb_object.get_formats()[selection.target].get_data()
selection.set(selection.target, 8, data)
@@ -79,8 +81,8 @@ class ClipboardIcon(RadioToolButton):
logging.debug('ClipboardIcon._put_in_clipboard')
if self._cb_object.get_percent() < 100:
- raise ValueError('Object is not complete,' \
- ' cannot be put into the clipboard.')
+ raise ValueError('Object is not complete, cannot be put into the'
+ ' clipboard.')
targets = self._get_targets()
if targets:
diff --git a/src/jarabe/frame/clipboardmenu.py b/src/jarabe/frame/clipboardmenu.py
index b998110..d11538d 100644
--- a/src/jarabe/frame/clipboardmenu.py
+++ b/src/jarabe/frame/clipboardmenu.py
@@ -35,6 +35,7 @@ from jarabe.frame import clipboard
from jarabe.journal import misc
from jarabe.model import bundleregistry
+
class ClipboardMenu(Palette):
def __init__(self, cb_object):
@@ -212,7 +213,8 @@ class ClipboardMenu(Palette):
if most_significant_mime_type == 'text/uri-list':
uris = mime.split_uri_list(format_.get_data())
if len(uris) == 1 and uris[0].startswith('file://'):
- file_path = urlparse.urlparse(uris[0]).path
+ parsed_url = urlparse.urlparse(uris[0])
+ file_path = parsed_url.path # pylint: disable=E1101
transfer_ownership = False
mime_type = mime.get_for_file(file_path)
else:
@@ -221,7 +223,8 @@ class ClipboardMenu(Palette):
mime_type = 'text/uri-list'
else:
if format_.is_on_disk():
- file_path = urlparse.urlparse(format_.get_data()).path
+ parsed_url = urlparse.urlparse(format_.get_data())
+ file_path = parsed_url.path # pylint: disable=E1101
transfer_ownership = False
mime_type = mime.get_for_file(file_path)
else:
diff --git a/src/jarabe/frame/clipboardobject.py b/src/jarabe/frame/clipboardobject.py
index e9403f9..407af2f 100644
--- a/src/jarabe/frame/clipboardobject.py
+++ b/src/jarabe/frame/clipboardobject.py
@@ -24,6 +24,7 @@ from gettext import gettext as _
from sugar import mime
from sugar.bundle.activitybundle import ActivityBundle
+
class ClipboardObject(object):
def __init__(self, object_path, name):
@@ -105,15 +106,18 @@ class ClipboardObject(object):
if format_ == 'text/uri-list':
data = self._formats['text/uri-list'].get_data()
uri = urlparse.urlparse(mime.split_uri_list(data)[0], 'file')
- if uri.scheme == 'file':
- if os.path.exists(uri.path):
- format_ = mime.get_for_file(uri.path)
+ scheme = uri.scheme # pylint: disable=E1101
+ if scheme == 'file':
+ path = uri.path # pylint: disable=E1101
+ if os.path.exists(path):
+ format_ = mime.get_for_file(path)
else:
- format_ = mime.get_from_file_name(uri.path)
+ format_ = mime.get_from_file_name(path)
logging.debug('Chose %r!', format_)
return format_
+
class Format(object):
def __init__(self, mime_type, data, on_disk):
@@ -126,8 +130,9 @@ class Format(object):
def destroy(self):
if self._on_disk:
uri = urlparse.urlparse(self._data)
- if os.path.isfile(uri.path):
- os.remove(uri.path)
+ path = uri.path # pylint: disable=E1101
+ if os.path.isfile(path):
+ os.remove(path)
def get_type(self):
return self._type
diff --git a/src/jarabe/frame/clipboardpanelwindow.py b/src/jarabe/frame/clipboardpanelwindow.py
index ac324f4..f5d537c 100644
--- a/src/jarabe/frame/clipboardpanelwindow.py
+++ b/src/jarabe/frame/clipboardpanelwindow.py
@@ -25,6 +25,7 @@ from jarabe.frame.clipboardtray import ClipboardTray
from jarabe.frame import clipboard
+
class ClipboardPanelWindow(FrameWindow):
def __init__(self, frame, orientation):
FrameWindow.__init__(self, orientation)
@@ -35,7 +36,7 @@ class ClipboardPanelWindow(FrameWindow):
# NOTE: we need to keep a reference to gtk.Clipboard in order to keep
# listening to it.
self._clipboard = gtk.Clipboard()
- self._clipboard.connect("owner-change", self._owner_change_cb)
+ self._clipboard.connect('owner-change', self._owner_change_cb)
self._clipboard_tray = ClipboardTray()
canvas_widget = hippo.CanvasWidget(widget=self._clipboard_tray)
@@ -43,14 +44,14 @@ class ClipboardPanelWindow(FrameWindow):
# Receiving dnd drops
self.drag_dest_set(0, [], 0)
- self.connect("drag_motion", self._clipboard_tray.drag_motion_cb)
- self.connect("drag_leave", self._clipboard_tray.drag_leave_cb)
- self.connect("drag_drop", self._clipboard_tray.drag_drop_cb)
- self.connect("drag_data_received",
+ self.connect('drag_motion', self._clipboard_tray.drag_motion_cb)
+ self.connect('drag_leave', self._clipboard_tray.drag_leave_cb)
+ self.connect('drag_drop', self._clipboard_tray.drag_drop_cb)
+ self.connect('drag_data_received',
self._clipboard_tray.drag_data_received_cb)
def _owner_change_cb(self, x_clipboard, event):
- logging.debug("owner_change_cb")
+ logging.debug('owner_change_cb')
if self._clipboard_tray.owns_clipboard():
return
@@ -100,4 +101,3 @@ class ClipboardPanelWindow(FrameWindow):
selection.type,
selection.data,
on_disk=False)
-
diff --git a/src/jarabe/frame/clipboardtray.py b/src/jarabe/frame/clipboardtray.py
index 8beb6a8..f49b799 100644
--- a/src/jarabe/frame/clipboardtray.py
+++ b/src/jarabe/frame/clipboardtray.py
@@ -27,6 +27,7 @@ from sugar.graphics import style
from jarabe.frame import clipboard
from jarabe.frame.clipboardicon import ClipboardIcon
+
class _ContextMap(object):
"""Maps a drag context to the clipboard object involved in the dragging."""
def __init__(self):
@@ -40,8 +41,8 @@ class _ContextMap(object):
def get_object_id(self, context):
"""Retrieves the object_id associated with context.
- Will release the association when this function was called as many times
- as the number of data_types that this clipboard object contains.
+ Will release the association when this function was called as many
+ times as the number of data_types that this clipboard object contains.
"""
[object_id, data_types_left] = self._context_map[context]
@@ -56,6 +57,7 @@ class _ContextMap(object):
def has_context(self, context):
return context in self._context_map
+
class ClipboardTray(tray.VTray):
MAX_ITEMS = gtk.gdk.screen_height() / style.GRID_CELL_SIZE - 2
@@ -154,7 +156,7 @@ class ClipboardTray(tray.VTray):
if 'XdndDirectSave0' in context.targets:
window = context.source_window
prop_type, format_, filename = \
- window.property_get('XdndDirectSave0','text/plain')
+ window.property_get('XdndDirectSave0', 'text/plain')
# FIXME query the clipboard service for a filename?
base_dir = tempfile.gettempdir()
@@ -192,12 +194,13 @@ class ClipboardTray(tray.VTray):
if selection.data == 'S':
window = context.source_window
- prop_type, format_, dest = \
- window.property_get('XdndDirectSave0', 'text/plain')
+ prop_type, format_, dest = window.property_get(
+ 'XdndDirectSave0', 'text/plain')
clipboardservice = clipboard.get_instance()
- clipboardservice.add_object_format( \
- object_id, 'XdndDirectSave0', dest, on_disk=True)
+ clipboardservice.add_object_format(object_id,
+ 'XdndDirectSave0',
+ dest, on_disk=True)
else:
self._add_selection(object_id, selection)
@@ -213,4 +216,3 @@ class ClipboardTray(tray.VTray):
return True
else:
return False
-
diff --git a/src/jarabe/frame/devicestray.py b/src/jarabe/frame/devicestray.py
index 72affe3..c5db639 100644
--- a/src/jarabe/frame/devicestray.py
+++ b/src/jarabe/frame/devicestray.py
@@ -15,14 +15,13 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import os
-import sys
-import traceback
import logging
from sugar.graphics import tray
from jarabe import config
+
class DevicesTray(tray.HTray):
def __init__(self):
tray.HTray.__init__(self, align=tray.ALIGN_TO_END)
@@ -35,14 +34,14 @@ class DevicesTray(tray.HTray):
locals(), [module_name])
mod.setup(self)
except Exception:
- logging.error('Exception while loading extension:\n' + \
- ''.join(traceback.format_exception(*sys.exc_info())))
+ logging.exception('Exception while loading extension:')
def add_device(self, view):
index = 0
- relative_index = getattr(view, "FRAME_POSITION_RELATIVE", -1)
+ relative_index = getattr(view, 'FRAME_POSITION_RELATIVE', -1)
for item in self.get_children():
- current_relative_index = getattr(item, "FRAME_POSITION_RELATIVE", 0)
+ current_relative_index = getattr(item, 'FRAME_POSITION_RELATIVE',
+ 0)
if current_relative_index >= relative_index:
index += 1
else:
diff --git a/src/jarabe/frame/eventarea.py b/src/jarabe/frame/eventarea.py
index 166aaf5..1b5bf86 100644
--- a/src/jarabe/frame/eventarea.py
+++ b/src/jarabe/frame/eventarea.py
@@ -19,14 +19,14 @@ import gobject
import wnck
import gconf
+
_MAX_DELAY = 1000
+
class EventArea(gobject.GObject):
__gsignals__ = {
- 'enter': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([])),
- 'leave': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([]))
+ 'enter': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'leave': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
}
def __init__(self):
@@ -37,10 +37,11 @@ class EventArea(gobject.GObject):
self._sids = {}
client = gconf.client_get_default()
self._edge_delay = client.get_int('/desktop/sugar/frame/edge_delay')
- self._corner_delay = client.get_int('/desktop/sugar/frame/corner_delay')
+ self._corner_delay = client.get_int('/desktop/sugar/frame'
+ '/corner_delay')
right = gtk.gdk.screen_width() - 1
- bottom = gtk.gdk.screen_height() -1
+ bottom = gtk.gdk.screen_height() - 1
width = gtk.gdk.screen_width() - 2
height = gtk.gdk.screen_height() - 2
@@ -94,6 +95,7 @@ class EventArea(gobject.GObject):
invisible.connect('drag_leave', self._drag_leave_cb)
invisible.realize()
+ # pylint: disable=E1101
invisible.window.set_events(gtk.gdk.POINTER_MOTION_MASK |
gtk.gdk.ENTER_NOTIFY_MASK |
gtk.gdk.LEAVE_NOTIFY_MASK)
diff --git a/src/jarabe/frame/frame.py b/src/jarabe/frame/frame.py
index 55f866f..079eeeb 100644
--- a/src/jarabe/frame/frame.py
+++ b/src/jarabe/frame/frame.py
@@ -35,6 +35,7 @@ from jarabe.frame.clipboardpanelwindow import ClipboardPanelWindow
from jarabe.frame.notification import NotificationIcon, NotificationWindow
from jarabe.model import notifications
+
TOP_RIGHT = 0
TOP_LEFT = 1
BOTTOM_RIGHT = 2
@@ -43,6 +44,7 @@ BOTTOM_LEFT = 3
_FRAME_HIDING_DELAY = 500
_NOTIFICATION_DURATION = 5000
+
class _Animation(animator.Animation):
def __init__(self, frame, end):
start = frame.current_position
@@ -52,6 +54,7 @@ class _Animation(animator.Animation):
def next_frame(self, current):
self._frame.move(current)
+
class _MouseListener(object):
def __init__(self, frame):
self._frame = frame
@@ -79,6 +82,7 @@ class _MouseListener(object):
self._hide_sid = gobject.timeout_add(
_FRAME_HIDING_DELAY, self._hide_frame_timeout_cb)
+
class _KeyListener(object):
def __init__(self, frame):
self._frame = frame
@@ -90,13 +94,14 @@ class _KeyListener(object):
else:
self._frame.show(Frame.MODE_KEYBOARD)
+
class Frame(object):
- MODE_MOUSE = 0
+ MODE_MOUSE = 0
MODE_KEYBOARD = 1
MODE_NON_INTERACTIVE = 2
def __init__(self):
- logging.debug("STARTUP: Loading the frame")
+ logging.debug('STARTUP: Loading the frame')
self.mode = None
self._palette_group = palettegroup.get_group('frame')
@@ -173,12 +178,12 @@ class Frame(object):
def _create_top_panel(self):
panel = self._create_panel(gtk.POS_TOP)
- # TODO: setting box_width and hippo.PACK_EXPAND looks like a hack to me.
- # Why hippo isn't respecting the request size of these controls?
+ # TODO: setting box_width and hippo.PACK_EXPAND looks like a hack to
+ # me. Why hippo isn't respecting the request size of these controls?
zoom_toolbar = ZoomToolbar()
panel.append(hippo.CanvasWidget(widget=zoom_toolbar,
- box_width=4*style.GRID_CELL_SIZE))
+ box_width=4 * style.GRID_CELL_SIZE))
zoom_toolbar.show()
activities_tray = ActivitiesTray()
@@ -193,7 +198,8 @@ class Frame(object):
# TODO: same issue as in _create_top_panel()
devices_tray = DevicesTray()
- panel.append(hippo.CanvasWidget(widget=devices_tray), hippo.PACK_EXPAND)
+ panel.append(hippo.CanvasWidget(widget=devices_tray),
+ hippo.PACK_EXPAND)
devices_tray.show()
return panel
@@ -322,7 +328,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']
@@ -348,4 +354,3 @@ class Frame(object):
# Do nothing for now. Our notification UI is so simple, there's no
# point yet.
pass
-
diff --git a/src/jarabe/frame/frameinvoker.py b/src/jarabe/frame/frameinvoker.py
index e4a13e1..a4abfa8 100644
--- a/src/jarabe/frame/frameinvoker.py
+++ b/src/jarabe/frame/frameinvoker.py
@@ -19,6 +19,7 @@ import gtk
from sugar.graphics import style
from sugar.graphics.palette import WidgetInvoker
+
def _get_screen_area():
frame_thickness = style.GRID_CELL_SIZE
@@ -28,6 +29,7 @@ def _get_screen_area():
return gtk.gdk.Rectangle(x, y, width, height)
+
class FrameWidgetInvoker(WidgetInvoker):
def __init__(self, widget):
WidgetInvoker.__init__(self, widget, widget.child)
diff --git a/src/jarabe/frame/framewindow.py b/src/jarabe/frame/framewindow.py
index a7d8fe7..c77e76c 100644
--- a/src/jarabe/frame/framewindow.py
+++ b/src/jarabe/frame/framewindow.py
@@ -19,6 +19,7 @@ import hippo
from sugar.graphics import style
+
class FrameWindow(gtk.Window):
__gtype_name__ = 'SugarFrameWindow'
diff --git a/src/jarabe/frame/friendstray.py b/src/jarabe/frame/friendstray.py
index b5437e5..31a9809 100644
--- a/src/jarabe/frame/friendstray.py
+++ b/src/jarabe/frame/friendstray.py
@@ -14,13 +14,16 @@
# 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):
@@ -32,46 +35,32 @@ class FriendIcon(TrayIcon):
self.palette.props.icon_visible = False
self.palette.set_group_id('frame')
+
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):
+ if buddy.props.key in self._buddies:
return
- model = BuddyModel(buddy=buddy)
-
- icon = FriendIcon(model)
+ icon = FriendIcon(buddy)
self.add_item(icon)
icon.show()
self._buddies[buddy.props.key] = icon
def remove_buddy(self, buddy):
- if not self._buddies.has_key(buddy.props.key):
+ if buddy.props.key not in self._buddies:
return
self.remove_item(self._buddies[buddy.props.key])
@@ -83,39 +72,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 +96,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/frame/notification.py b/src/jarabe/frame/notification.py
index 83dc27e..3471e2c 100644
--- a/src/jarabe/frame/notification.py
+++ b/src/jarabe/frame/notification.py
@@ -22,13 +22,14 @@ from sugar.graphics.xocolor import XoColor
from jarabe.view.pulsingicon import PulsingIcon
+
class NotificationIcon(gtk.EventBox):
__gtype_name__ = 'SugarNotificationIcon'
__gproperties__ = {
- 'xo-color' : (object, None, None, gobject.PARAM_READWRITE),
- 'icon-name' : (str, None, None, None, gobject.PARAM_READWRITE),
- 'icon-filename' : (str, None, None, None, gobject.PARAM_READWRITE)
+ 'xo-color': (object, None, None, gobject.PARAM_READWRITE),
+ 'icon-name': (str, None, None, None, gobject.PARAM_READWRITE),
+ 'icon-filename': (str, None, None, None, gobject.PARAM_READWRITE),
}
_PULSE_TIMEOUT = 3
@@ -45,7 +46,8 @@ class NotificationIcon(gtk.EventBox):
self.add(self._icon)
self._icon.show()
- gobject.timeout_add_seconds(self._PULSE_TIMEOUT, self.__stop_pulsing_cb)
+ gobject.timeout_add_seconds(self._PULSE_TIMEOUT,
+ self.__stop_pulsing_cb)
self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)
@@ -80,6 +82,7 @@ class NotificationIcon(gtk.EventBox):
palette = property(_get_palette, _set_palette)
+
class NotificationWindow(gtk.Window):
__gtype_name__ = 'SugarNotificationWindow'
@@ -97,4 +100,3 @@ class NotificationWindow(gtk.Window):
color = gtk.gdk.color_parse(style.COLOR_TOOLBAR_GREY.get_html())
self.modify_bg(gtk.STATE_NORMAL, color)
-
diff --git a/src/jarabe/frame/zoomtoolbar.py b/src/jarabe/frame/zoomtoolbar.py
index 2ed3c54..6c10c61 100644
--- a/src/jarabe/frame/zoomtoolbar.py
+++ b/src/jarabe/frame/zoomtoolbar.py
@@ -26,6 +26,7 @@ from sugar.graphics.radiotoolbutton import RadioToolButton
from jarabe.frame.frameinvoker import FrameWidgetInvoker
from jarabe.model import shell
+
class ZoomToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
@@ -86,4 +87,3 @@ class ZoomToolbar(gtk.Toolbar):
self._activity_button.props.active = True
else:
raise ValueError('Invalid zoom level: %r' % (new_level))
-
diff --git a/src/jarabe/intro/Makefile.am b/src/jarabe/intro/Makefile.am
index a9fb96b..2ea7cea 100644
--- a/src/jarabe/intro/Makefile.am
+++ b/src/jarabe/intro/Makefile.am
@@ -1,7 +1,3 @@
-imagedir = $(pythondir)/jarabe/intro
-image_DATA = default-picture.png
-
-EXTRA_DIST = $(conf_DATA) $(image_DATA)
sugardir = $(pythondir)/jarabe/intro
sugar_PYTHON = \
__init__.py \
diff --git a/src/jarabe/intro/__init__.py b/src/jarabe/intro/__init__.py
index ca4f64d..d2932f1 100644
--- a/src/jarabe/intro/__init__.py
+++ b/src/jarabe/intro/__init__.py
@@ -8,6 +8,7 @@ from sugar.profile import get_profile
from jarabe.intro.window import IntroWindow
from jarabe.intro.window import create_profile
+
def check_profile():
profile = get_profile()
diff --git a/src/jarabe/intro/colorpicker.py b/src/jarabe/intro/colorpicker.py
index a939857..997199b 100644
--- a/src/jarabe/intro/colorpicker.py
+++ b/src/jarabe/intro/colorpicker.py
@@ -20,6 +20,7 @@ from sugar.graphics.icon import CanvasIcon
from sugar.graphics import style
from sugar.graphics.xocolor import XoColor
+
class ColorPicker(hippo.CanvasBox, hippo.CanvasItem):
def __init__(self, **kwargs):
hippo.CanvasBox.__init__(self, **kwargs)
diff --git a/src/jarabe/intro/default-picture.png b/src/jarabe/intro/default-picture.png
deleted file mode 100644
index e26b9b0..0000000
--- a/src/jarabe/intro/default-picture.png
+++ /dev/null
Binary files differ
diff --git a/src/jarabe/intro/window.py b/src/jarabe/intro/window.py
index 35c0cda..df19fbf 100644
--- a/src/jarabe/intro/window.py
+++ b/src/jarabe/intro/window.py
@@ -32,38 +32,34 @@ from sugar.graphics.xocolor import XoColor
from jarabe.intro import colorpicker
+
_BACKGROUND_COLOR = style.COLOR_WHITE
-def create_profile(name, color=None, pixbuf=None):
- if not pixbuf:
- path = os.path.join(os.path.dirname(__file__), 'default-picture.png')
- pixbuf = gtk.gdk.pixbuf_new_from_file(path)
+def create_profile(name, color=None):
if not color:
color = XoColor()
- icon_path = os.path.join(env.get_profile_path(), "buddy-icon.jpg")
- pixbuf.save(icon_path, "jpeg", {"quality":"85"})
-
client = gconf.client_get_default()
- client.set_string("/desktop/sugar/user/nick", name)
- client.set_string("/desktop/sugar/user/color", color.to_string())
+ client.set_string('/desktop/sugar/user/nick', name)
+ client.set_string('/desktop/sugar/user/color', color.to_string())
+ client.suggest_sync()
# Generate keypair
import commands
- keypath = os.path.join(env.get_profile_path(), "owner.key")
+ keypath = os.path.join(env.get_profile_path(), 'owner.key')
if not os.path.isfile(keypath):
cmd = "ssh-keygen -q -t dsa -f %s -C '' -N ''" % keypath
(s, o) = commands.getstatusoutput(cmd)
if s != 0:
- logging.error("Could not generate key pair: %d %s", s, o)
+ logging.error('Could not generate key pair: %d %s', s, o)
else:
- logging.error("Keypair exists, skip generation.")
+ logging.error('Keypair exists, skip generation.')
+
class _Page(hippo.CanvasBox):
__gproperties__ = {
- 'valid' : (bool, None, None, False,
- gobject.PARAM_READABLE)
+ 'valid': (bool, None, None, False, gobject.PARAM_READABLE),
}
def __init__(self, **kwargs):
@@ -81,6 +77,7 @@ class _Page(hippo.CanvasBox):
def activate(self):
pass
+
class _NamePage(_Page):
def __init__(self, intro):
_Page.__init__(self, xalign=hippo.ALIGNMENT_CENTER,
@@ -90,7 +87,7 @@ class _NamePage(_Page):
self._intro = intro
- label = hippo.CanvasText(text=_("Name:"))
+ label = hippo.CanvasText(text=_('Name:'))
self.append(label)
self._entry = CanvasEntry(box_width=style.zoom(300))
@@ -118,6 +115,7 @@ class _NamePage(_Page):
def activate(self):
self._entry.props.widget.grab_focus()
+
class _ColorPage(_Page):
def __init__(self, **kwargs):
_Page.__init__(self, xalign=hippo.ALIGNMENT_CENTER,
@@ -125,7 +123,7 @@ class _ColorPage(_Page):
spacing=style.DEFAULT_SPACING,
yalign=hippo.ALIGNMENT_CENTER, **kwargs)
- self._label = hippo.CanvasText(text=_("Click to change color:"),
+ self._label = hippo.CanvasText(text=_('Click to change color:'),
xalign=hippo.ALIGNMENT_CENTER)
self.append(self._label)
@@ -138,10 +136,11 @@ class _ColorPage(_Page):
def get_color(self):
return self._cp.get_color()
+
class _IntroBox(hippo.CanvasBox):
__gsignals__ = {
'done': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT]))
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
}
PAGE_NAME = 0
@@ -166,13 +165,9 @@ class _IntroBox(hippo.CanvasBox):
self._page = self.PAGE_COLOR
if default_nick == 'system':
pwd_entry = pwd.getpwuid(os.getuid())
- if pwd_entry.pw_gecos:
- nick = pwd_entry.pw_gecos.split(',')[0]
- self._name_page.set_name(nick)
- else:
- self._name_page.set_name(pwd_entry.pw_name)
- else:
- self._name_page.set_name(default_nick)
+ default_nick = (pwd_entry.pw_gecos.split(',')[0] or
+ pwd_entry.pw_name)
+ self._name_page.set_name(default_nick)
self._setup_page()
@@ -255,6 +250,7 @@ class _IntroBox(hippo.CanvasBox):
self.emit('done', name, color)
+
class IntroWindow(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
@@ -282,16 +278,16 @@ class IntroWindow(gtk.Window):
return False
def __key_press_cb(self, widget, event):
- if gtk.gdk.keyval_name(event.keyval) == "Return":
+ if gtk.gdk.keyval_name(event.keyval) == 'Return':
self._intro_box.next()
return True
- elif gtk.gdk.keyval_name(event.keyval) == "Escape":
+ elif gtk.gdk.keyval_name(event.keyval) == 'Escape':
self._intro_box.back()
return True
return False
-if __name__ == "__main__":
+if __name__ == '__main__':
w = IntroWindow()
w.show()
w.connect('destroy', gtk.main_quit)
diff --git a/src/jarabe/journal/Makefile.am b/src/jarabe/journal/Makefile.am
index f4bf273..ba29062 100644
--- a/src/jarabe/journal/Makefile.am
+++ b/src/jarabe/journal/Makefile.am
@@ -6,6 +6,7 @@ sugar_PYTHON = \
journalactivity.py \
journalentrybundle.py \
journaltoolbox.py \
+ journalwindow.py \
keepicon.py \
listmodel.py \
listview.py \
diff --git a/src/jarabe/journal/detailview.py b/src/jarabe/journal/detailview.py
index b4a2339..aa8c039 100644
--- a/src/jarabe/journal/detailview.py
+++ b/src/jarabe/journal/detailview.py
@@ -27,11 +27,12 @@ from sugar.graphics.icon import CanvasIcon
from jarabe.journal.expandedentry import ExpandedEntry
from jarabe.journal import model
+
class DetailView(gtk.VBox):
__gtype_name__ = 'DetailView'
__gsignals__ = {
- 'go-back-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
+ 'go-back-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
}
def __init__(self, **kwargs):
@@ -84,6 +85,7 @@ class DetailView(gtk.VBox):
metadata = gobject.property(
type=object, getter=get_metadata, setter=set_metadata)
+
class BackBar(hippo.CanvasBox):
def __init__(self):
hippo.CanvasBox.__init__(self,
diff --git a/src/jarabe/journal/expandedentry.py b/src/jarabe/journal/expandedentry.py
index c8e40c1..fe2f320 100644
--- a/src/jarabe/journal/expandedentry.py
+++ b/src/jarabe/journal/expandedentry.py
@@ -36,6 +36,7 @@ from jarabe.journal.palettes import ObjectPalette, BuddyPalette
from jarabe.journal import misc
from jarabe.journal import model
+
class Separator(hippo.CanvasBox, hippo.CanvasItem):
def __init__(self, orientation):
hippo.CanvasBox.__init__(self,
@@ -46,6 +47,7 @@ class Separator(hippo.CanvasBox, hippo.CanvasItem):
else:
self.props.box_height = style.LINE_WIDTH
+
class BuddyList(hippo.CanvasBox):
def __init__(self, buddies):
hippo.CanvasBox.__init__(self, xalign=hippo.ALIGNMENT_START,
@@ -61,6 +63,7 @@ class BuddyList(hippo.CanvasBox):
hbox.append(icon)
self.append(hbox)
+
class ExpandedEntry(hippo.CanvasBox):
def __init__(self):
hippo.CanvasBox.__init__(self)
@@ -209,9 +212,7 @@ class ExpandedEntry(hippo.CanvasBox):
height = style.zoom(240)
box = hippo.CanvasBox()
- if self._metadata.has_key('preview') and \
- len(self._metadata['preview']) > 4:
-
+ if len(self._metadata.get('preview', '')) > 4:
if self._metadata['preview'][1:4] == 'PNG':
preview_data = self._metadata['preview']
else:
@@ -260,8 +261,9 @@ class ExpandedEntry(hippo.CanvasBox):
lines = [
_('Kind: %s') % (self._metadata.get('mime_type') or _('Unknown'),),
_('Date: %s') % (self._format_date(),),
- _('Size: %s') % (format_size(model.get_file_size(
- self._metadata['uid'])),)]
+ _('Size: %s') % (format_size(int(self._metadata.get('filesize',
+ model.get_file_size(self._metadata['uid']))))),
+ ]
for line in lines:
text = hippo.CanvasText(text=line,
@@ -279,10 +281,15 @@ class ExpandedEntry(hippo.CanvasBox):
def _format_date(self):
if 'timestamp' in self._metadata:
- timestamp = float(self._metadata['timestamp'])
- return time.strftime('%x', time.localtime(timestamp))
- else:
- return _('No date')
+ try:
+ timestamp = float(self._metadata['timestamp'])
+ except (ValueError, TypeError):
+ logging.warning('Invalid timestamp for %r: %r',
+ self._metadata['uid'],
+ self._metadata['timestamp'])
+ else:
+ return time.strftime('%x', time.localtime(timestamp))
+ return _('No date')
def _create_buddy_list(self):
@@ -300,8 +307,7 @@ class ExpandedEntry(hippo.CanvasBox):
vbox.append(text)
- if self._metadata.has_key('buddies') and \
- self._metadata['buddies']:
+ if self._metadata.get('buddies'):
buddies = simplejson.loads(self._metadata['buddies']).values()
vbox.append(BuddyList(buddies))
return vbox
diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py
index 0559560..a33038a 100644
--- a/src/jarabe/journal/journalactivity.py
+++ b/src/jarabe/journal/journalactivity.py
@@ -17,8 +17,6 @@
import logging
from gettext import gettext as _
-import sys
-import traceback
import uuid
import gtk
@@ -27,6 +25,8 @@ import statvfs
import os
from sugar.graphics.window import Window
+from sugar.graphics.alert import ErrorAlert
+
from sugar.bundle.bundle import ZipExtractException, RegistrationException
from sugar import env
from sugar.activity import activityfactory
@@ -42,6 +42,8 @@ from jarabe.journal.journalentrybundle import JournalEntryBundle
from jarabe.journal.objectchooser import ObjectChooser
from jarabe.journal.modalalert import ModalAlert
from jarabe.journal import model
+from jarabe.journal.journalwindow import JournalWindow
+
J_DBUS_SERVICE = 'org.laptop.Journal'
J_DBUS_INTERFACE = 'org.laptop.Journal'
@@ -50,6 +52,9 @@ J_DBUS_PATH = '/org/laptop/Journal'
_SPACE_TRESHOLD = 52428800
_BUNDLE_ID = 'org.laptop.JournalActivity'
+_journal = None
+
+
class JournalActivityDBusService(dbus.service.Object):
def __init__(self, parent):
self._parent = parent
@@ -79,7 +84,8 @@ class JournalActivityDBusService(dbus.service.Object):
chooser.destroy()
del chooser
- @dbus.service.method(J_DBUS_INTERFACE, in_signature='is', out_signature='s')
+ @dbus.service.method(J_DBUS_INTERFACE, in_signature='is',
+ out_signature='s')
def ChooseObject(self, parent_xid, what_filter=''):
chooser_id = uuid.uuid4().hex
if parent_xid > 0:
@@ -92,18 +98,19 @@ class JournalActivityDBusService(dbus.service.Object):
return chooser_id
- @dbus.service.signal(J_DBUS_INTERFACE, signature="ss")
+ @dbus.service.signal(J_DBUS_INTERFACE, signature='ss')
def ObjectChooserResponse(self, chooser_id, object_id):
pass
- @dbus.service.signal(J_DBUS_INTERFACE, signature="s")
+ @dbus.service.signal(J_DBUS_INTERFACE, signature='s')
def ObjectChooserCancelled(self, chooser_id):
pass
-class JournalActivity(Window):
+
+class JournalActivity(JournalWindow):
def __init__(self):
- logging.debug("STARTUP: Loading the journal")
- Window.__init__(self)
+ logging.debug('STARTUP: Loading the journal')
+ JournalWindow.__init__(self)
self.set_title(_('Journal'))
@@ -138,6 +145,15 @@ class JournalActivity(Window):
self._critical_space_alert = None
self._check_available_space()
+ def __volume_error_cb(self, gobject, message, severity):
+ alert = ErrorAlert(title=severity, msg=message)
+ alert.connect('response', self.__alert_response_cb)
+ self.add_alert(alert)
+ alert.show()
+
+ def __alert_response_cb(self, alert, response_id):
+ self.remove_alert(alert)
+
def __realize_cb(self, window):
wm.set_bundle_id(window.window, _BUNDLE_ID)
activity_id = activityfactory.create_activity_id()
@@ -161,6 +177,7 @@ class JournalActivity(Window):
self._volumes_toolbar = VolumesToolbar()
self._volumes_toolbar.connect('volume-changed',
self.__volume_changed_cb)
+ self._volumes_toolbar.connect('volume-error', self.__volume_error_cb)
self._main_view.pack_start(self._volumes_toolbar, expand=False)
search_toolbar = self._main_toolbox.search_toolbar
@@ -171,7 +188,8 @@ class JournalActivity(Window):
self._secondary_view = gtk.VBox()
self._detail_toolbox = DetailToolbox()
- entry_toolbar = self._detail_toolbox.entry_toolbar
+ self._detail_toolbox.entry_toolbar.connect('volume-error',
+ self.__volume_error_cb)
self._detail_view = DetailView()
self._detail_view.connect('go-back-clicked', self.__go_back_clicked_cb)
@@ -210,8 +228,7 @@ class JournalActivity(Window):
try:
self._detail_toolbox.entry_toolbar.set_metadata(metadata)
except Exception:
- logging.error('Exception while displaying entry:\n' + \
- ''.join(traceback.format_exception(*sys.exc_info())))
+ logging.exception('Exception while displaying entry:')
self.set_toolbar_box(self._detail_toolbox)
self._detail_toolbox.show()
@@ -219,8 +236,7 @@ class JournalActivity(Window):
try:
self._detail_view.props.metadata = metadata
except Exception:
- logging.error('Exception while displaying entry:\n' + \
- ''.join(traceback.format_exception(*sys.exc_info())))
+ logging.exception('Exception while displaying entry:')
self.set_canvas(self._secondary_view)
self._secondary_view.show()
@@ -314,11 +330,11 @@ class JournalActivity(Window):
self._list_view.set_is_visible(visible)
def _check_available_space(self):
- ''' Check available space on device
+ """Check available space on device
If the available space is below 50MB an alert will be
shown which encourages to delete old journal entries.
- '''
+ """
if self._critical_space_alert:
return
@@ -345,7 +361,6 @@ class JournalActivity(Window):
self.show_main_view()
self.search_grab_focus()
-_journal = None
def get_journal():
global _journal
@@ -354,6 +369,6 @@ def get_journal():
_journal.show()
return _journal
+
def start():
get_journal()
-
diff --git a/src/jarabe/journal/journalentrybundle.py b/src/jarabe/journal/journalentrybundle.py
index 41777c7..c220c09 100644
--- a/src/jarabe/journal/journalentrybundle.py
+++ b/src/jarabe/journal/journalentrybundle.py
@@ -25,6 +25,7 @@ from sugar.bundle.bundle import Bundle, MalformedBundleException
from jarabe.journal import model
+
class JournalEntryBundle(Bundle):
"""A Journal entry bundle
@@ -41,7 +42,7 @@ class JournalEntryBundle(Bundle):
Bundle.__init__(self, path)
def install(self, uid=''):
- if os.environ.has_key('SUGAR_ACTIVITY_ROOT'):
+ if 'SUGAR_ACTIVITY_ROOT' in os.environ:
install_dir = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'],
'data')
else:
@@ -91,4 +92,3 @@ class JournalEntryBundle(Bundle):
def is_installed(self):
# These bundles can be reinstalled as many times as desired.
return False
-
diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py
index 61671bc..cdf998d 100644
--- a/src/jarabe/journal/journaltoolbox.py
+++ b/src/jarabe/journal/journaltoolbox.py
@@ -43,6 +43,7 @@ from jarabe.model import bundleregistry
from jarabe.journal import misc
from jarabe.journal import model
+
_AUTOSEARCH_TIMEOUT = 1000
_ACTION_ANYTIME = 0
@@ -68,13 +69,13 @@ class MainToolbox(Toolbox):
self.add_toolbar(_('Search'), self.search_toolbar)
self.search_toolbar.show()
+
class SearchToolbar(gtk.Toolbar):
__gtype_name__ = 'SearchToolbar'
__gsignals__ = {
- 'query-changed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([object]))
+ 'query-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
}
def __init__(self):
@@ -109,6 +110,14 @@ class SearchToolbar(gtk.Toolbar):
self.insert(tool_item, -1)
tool_item.show()
+ self._sorting_button = SortingButton()
+ self._sorting_button.connect('clicked',
+ self.__sorting_button_clicked_cb)
+ self.insert(self._sorting_button, -1)
+ self._sorting_button.connect('sort-property-changed',
+ self.__sort_changed_cb)
+ self._sorting_button.show()
+
# TODO: enable it when the DS supports saving the buddies.
#self._with_search_combo = self._get_with_search_combo()
#tool_item = ToolComboBox(self._with_search_combo)
@@ -191,6 +200,14 @@ class SearchToolbar(gtk.Toolbar):
if text:
query['query'] = text
+ property_, order = self._sorting_button.get_current_sort()
+
+ if order == gtk.SORT_ASCENDING:
+ sign = '+'
+ else:
+ sign = '-'
+ query['order_by'] = [sign + property_]
+
return query
def _get_date_range(self):
@@ -213,6 +230,12 @@ class SearchToolbar(gtk.Toolbar):
def _combo_changed_cb(self, combo):
self._update_if_needed()
+ def __sort_changed_cb(self, button):
+ self._update_if_needed()
+
+ def __sorting_button_clicked_cb(self, button):
+ self._sorting_button.palette.popup(immediate=True, state=1)
+
def _update_if_needed(self):
new_query = self._build_query()
if self._query != new_query:
@@ -320,6 +343,7 @@ class SearchToolbar(gtk.Toolbar):
self._when_search_combo.set_active(0)
self._favorite_button.props.active = False
+
class DetailToolbox(Toolbox):
def __init__(self):
Toolbox.__init__(self)
@@ -328,7 +352,13 @@ class DetailToolbox(Toolbox):
self.add_toolbar('', self.entry_toolbar)
self.entry_toolbar.show()
+
class EntryToolbar(gtk.Toolbar):
+ __gsignals__ = {
+ 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([str, str])),
+ }
+
def __init__(self):
gtk.Toolbar.__init__(self)
@@ -398,7 +428,22 @@ class EntryToolbar(gtk.Toolbar):
misc.resume(self._metadata, service_name)
def _copy_menu_item_activate_cb(self, menu_item, mount_point):
- model.copy(self._metadata, mount_point)
+ file_path = model.get_file(self._metadata['uid'])
+
+ if not file_path or not os.path.exists(file_path):
+ logging.warn('Entries without a file cannot be copied.')
+ self.emit('volume-error',
+ _('Entries without a file cannot be copied.'),
+ _('Warning'))
+ return
+
+ try:
+ model.copy(self._metadata, mount_point)
+ except IOError, e:
+ logging.exception('Error while copying the entry. %s', e.strerror)
+ self.emit('volume-error',
+ _('Error while copying the entry. %s') % e.strerror,
+ _('Error'))
def _refresh_copy_palette(self):
palette = self._copy.get_palette()
@@ -456,3 +501,48 @@ class EntryToolbar(gtk.Toolbar):
activity_info.get_bundle_id())
palette.menu.append(menu_item)
menu_item.show()
+
+
+class SortingButton(ToolButton):
+ __gtype_name__ = 'JournalSortingButton'
+
+ __gsignals__ = {
+ 'sort-property-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ }
+
+ _SORT_OPTIONS = [
+ ('timestamp', 'view-lastedit', _('Sort by date modified')),
+ ('creation_time', 'view-created', _('Sort by date created')),
+ ('filesize', 'view-size', _('Sort by size')),
+ ]
+
+ def __init__(self):
+ ToolButton.__init__(self)
+
+ self._property = 'timestamp'
+ self._order = gtk.SORT_ASCENDING
+
+ self.props.tooltip = _('Sort view')
+ self.props.icon_name = 'view-lastedit'
+
+ for property_, icon, label in self._SORT_OPTIONS:
+ button = MenuItem(icon_name=icon, text_label=label)
+ button.connect('activate',
+ self.__sort_type_changed_cb,
+ property_,
+ icon)
+ button.show()
+ self.props.palette.menu.insert(button, -1)
+
+ def __sort_type_changed_cb(self, widget, property_, icon_name):
+ self._property = property_
+ #FIXME: Implement sorting order
+ self._order = gtk.SORT_ASCENDING
+ self.emit('sort-property-changed')
+
+ self.props.icon_name = icon_name
+
+ def get_current_sort(self):
+ return (self._property, self._order)
diff --git a/src/jarabe/desktop/myicon.py b/src/jarabe/journal/journalwindow.py
index 4a4ad95..31bc790 100644
--- a/src/jarabe/desktop/myicon.py
+++ b/src/jarabe/journal/journalwindow.py
@@ -1,4 +1,5 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
+#Copyright (C) 2010 Software for Education, Entertainment and Training
+#Activities
#
# 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,15 +15,19 @@
# 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.window import Window
-from sugar.graphics.icon import CanvasIcon
-from sugar.graphics.xocolor import XoColor
+_journal_window = None
-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)
+
+class JournalWindow(Window):
+
+ def __init__(self):
+
+ global _journal_window
+ Window.__init__(self)
+ _journal_window = self
+
+
+def get_journal_window():
+ return _journal_window
diff --git a/src/jarabe/journal/keepicon.py b/src/jarabe/journal/keepicon.py
index 2c692c6..1253afc 100644
--- a/src/jarabe/journal/keepicon.py
+++ b/src/jarabe/journal/keepicon.py
@@ -22,6 +22,7 @@ from sugar.graphics.icon import CanvasIcon
from sugar.graphics import style
from sugar.graphics.xocolor import XoColor
+
class KeepIcon(CanvasIcon):
def __init__(self, keep):
CanvasIcon.__init__(self, icon_name='emblem-favorite',
diff --git a/src/jarabe/journal/listmodel.py b/src/jarabe/journal/listmodel.py
index 07f8544..3902eba 100644
--- a/src/jarabe/journal/listmodel.py
+++ b/src/jarabe/journal/listmodel.py
@@ -19,6 +19,7 @@ import logging
import simplejson
import gobject
import gtk
+from gettext import gettext as _
from sugar.graphics.xocolor import XoColor
from sugar.graphics import style
@@ -27,20 +28,18 @@ from sugar import util
from jarabe.journal import model
from jarabe.journal import misc
+
DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
+
class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource):
__gtype_name__ = 'JournalListModel'
__gsignals__ = {
- 'ready': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([])),
- 'progress': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([])),
+ 'ready': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'progress': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
}
COLUMN_UID = 0
@@ -48,22 +47,28 @@ class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource):
COLUMN_ICON = 2
COLUMN_ICON_COLOR = 3
COLUMN_TITLE = 4
- COLUMN_DATE = 5
- COLUMN_PROGRESS = 6
- COLUMN_BUDDY_1 = 7
- COLUMN_BUDDY_2 = 8
- COLUMN_BUDDY_3 = 9
-
- _COLUMN_TYPES = {COLUMN_UID: str,
- COLUMN_FAVORITE: bool,
- COLUMN_ICON: str,
- COLUMN_ICON_COLOR: object,
- COLUMN_TITLE: str,
- COLUMN_DATE: str,
- COLUMN_PROGRESS: int,
- COLUMN_BUDDY_1: object,
- COLUMN_BUDDY_3: object,
- COLUMN_BUDDY_2: object}
+ COLUMN_TIMESTAMP = 5
+ COLUMN_CREATION_TIME = 6
+ COLUMN_FILESIZE = 7
+ COLUMN_PROGRESS = 8
+ COLUMN_BUDDY_1 = 9
+ COLUMN_BUDDY_2 = 10
+ COLUMN_BUDDY_3 = 11
+
+ _COLUMN_TYPES = {
+ COLUMN_UID: str,
+ COLUMN_FAVORITE: bool,
+ COLUMN_ICON: str,
+ COLUMN_ICON_COLOR: object,
+ COLUMN_TITLE: str,
+ COLUMN_TIMESTAMP: str,
+ COLUMN_CREATION_TIME: str,
+ COLUMN_FILESIZE: str,
+ COLUMN_PROGRESS: int,
+ COLUMN_BUDDY_1: object,
+ COLUMN_BUDDY_3: object,
+ COLUMN_BUDDY_2: object,
+ }
_PAGE_SIZE = 10
@@ -135,25 +140,64 @@ class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource):
xo_color = misc.get_icon_color(metadata)
self._cached_row.append(xo_color)
- title = gobject.markup_escape_text(metadata.get('title', None))
- self._cached_row.append('<b>%s</b>' % title)
+ title = gobject.markup_escape_text(metadata.get('title',
+ _('Untitled')))
+ self._cached_row.append('<b>%s</b>' % (title, ))
- timestamp = int(metadata.get('timestamp', 0))
- self._cached_row.append(util.timestamp_to_elapsed_string(timestamp))
+ try:
+ timestamp = float(metadata.get('timestamp', 0))
+ except (TypeError, ValueError):
+ timestamp_content = _('Unknown')
+ else:
+ timestamp_content = util.timestamp_to_elapsed_string(timestamp)
+ self._cached_row.append(timestamp_content)
- self._cached_row.append(int(metadata.get('progress', 100)))
+ try:
+ creation_time = float(metadata.get('creation_time'))
+ except (TypeError, ValueError):
+ self._cached_row.append(_('Unknown'))
+ else:
+ self._cached_row.append(
+ util.timestamp_to_elapsed_string(float(creation_time)))
- if metadata.get('buddies', ''):
- buddies = simplejson.loads(metadata['buddies']).values()
+ try:
+ size = int(metadata.get('filesize'))
+ except (TypeError, ValueError):
+ self._cached_row.append(_('Unknown'))
else:
+ self._cached_row.append(util.format_size(size))
+
+ try:
+ progress = int(float(metadata.get('progress', 100)))
+ except (TypeError, ValueError):
+ progress = 100
+ self._cached_row.append(progress)
+
+ buddies = []
+ if metadata.get('buddies'):
+ try:
+ buddies = simplejson.loads(metadata['buddies']).values()
+ except simplejson.decoder.JSONDecodeError, exception:
+ logging.warning('Cannot decode buddies for %r: %s',
+ metadata['uid'], exception)
+
+ if not isinstance(buddies, list):
+ logging.warning('Content of buddies for %r is not a list: %r',
+ metadata['uid'], buddies)
buddies = []
for n_ in xrange(0, 3):
if buddies:
- nick, color = buddies.pop(0)
- self._cached_row.append((nick, XoColor(color)))
- else:
- self._cached_row.append(None)
+ try:
+ nick, color = buddies.pop(0)
+ except (AttributeError, ValueError), exception:
+ logging.warning('Malformed buddies for %r: %s',
+ metadata['uid'], exception)
+ else:
+ self._cached_row.append((nick, XoColor(color)))
+ continue
+
+ self._cached_row.append(None)
return self._cached_row[column]
@@ -198,4 +242,3 @@ class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource):
return True
return False
-
diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py
index 9e19f70..0aee1b7 100644
--- a/src/jarabe/journal/listview.py
+++ b/src/jarabe/journal/listview.py
@@ -34,11 +34,13 @@ from jarabe.journal.palettes import ObjectPalette, BuddyPalette
from jarabe.journal import model
from jarabe.journal import misc
+
UPDATE_INTERVAL = 300
MESSAGE_EMPTY_JOURNAL = 0
MESSAGE_NO_MATCH = 1
+
class TreeView(gtk.TreeView):
__gtype_name__ = 'JournalTreeView'
@@ -47,8 +49,8 @@ class TreeView(gtk.TreeView):
self.set_headers_visible(False)
def do_size_request(self, requisition):
- # HACK: We tell the model that the view is just resizing so it can avoid
- # hitting both D-Bus and disk.
+ # HACK: We tell the model that the view is just resizing so it can
+ # avoid hitting both D-Bus and disk.
tree_model = self.get_model()
if tree_model is not None:
tree_model.view_is_resizing = True
@@ -58,13 +60,12 @@ class TreeView(gtk.TreeView):
if tree_model is not None:
tree_model.view_is_resizing = False
+
class BaseListView(gtk.Bin):
__gtype_name__ = 'JournalBaseListView'
__gsignals__ = {
- 'clear-clicked': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([]))
+ 'clear-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
}
def __init__(self):
@@ -81,7 +82,8 @@ class BaseListView(gtk.Bin):
self.connect('destroy', self.__destroy_cb)
self._scrolled_window = gtk.ScrolledWindow()
- self._scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self._scrolled_window.set_policy(gtk.POLICY_NEVER,
+ gtk.POLICY_AUTOMATIC)
self.add(self._scrolled_window)
self._scrolled_window.show()
@@ -97,7 +99,7 @@ class BaseListView(gtk.Bin):
self.cell_title = None
self.cell_icon = None
self._title_column = None
- self.date_column = None
+ self.sort_column = None
self._add_columns()
self.tree_view.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
@@ -141,7 +143,8 @@ class BaseListView(gtk.Bin):
column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
column.props.fixed_width = self.cell_icon.props.width
column.pack_start(self.cell_icon)
- column.add_attribute(self.cell_icon, 'file-name', ListModel.COLUMN_ICON)
+ column.add_attribute(self.cell_icon, 'file-name',
+ ListModel.COLUMN_ICON)
column.add_attribute(self.cell_icon, 'xo-color',
ListModel.COLUMN_ICON_COLOR)
self.tree_view.append_column(column)
@@ -163,7 +166,8 @@ class BaseListView(gtk.Bin):
buddies_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
self.tree_view.append_column(buddies_column)
- for column_index in [ListModel.COLUMN_BUDDY_1, ListModel.COLUMN_BUDDY_2,
+ for column_index in [ListModel.COLUMN_BUDDY_1,
+ ListModel.COLUMN_BUDDY_2,
ListModel.COLUMN_BUDDY_3]:
cell_icon = CellRendererBuddy(self.tree_view,
column_index=column_index)
@@ -190,15 +194,16 @@ class BaseListView(gtk.Bin):
date = util.timestamp_to_elapsed_string(timestamp)
date_width = self._get_width_for_string(date)
- self.date_column = gtk.TreeViewColumn()
- self.date_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
- self.date_column.props.fixed_width = date_width
- self.date_column.set_alignment(1)
- self.date_column.props.resizable = True
- self.date_column.props.clickable = True
- self.date_column.pack_start(cell_text)
- self.date_column.add_attribute(cell_text, 'text', ListModel.COLUMN_DATE)
- self.tree_view.append_column(self.date_column)
+ self.sort_column = gtk.TreeViewColumn()
+ self.sort_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ self.sort_column.props.fixed_width = date_width
+ self.sort_column.set_alignment(1)
+ self.sort_column.props.resizable = True
+ self.sort_column.props.clickable = True
+ self.sort_column.pack_start(cell_text)
+ self.sort_column.add_attribute(cell_text, 'text',
+ ListModel.COLUMN_TIMESTAMP)
+ self.tree_view.append_column(self.sort_column)
def _get_width_for_string(self, text):
# Add some extra margin
@@ -252,11 +257,16 @@ class BaseListView(gtk.Bin):
def update_with_query(self, query_dict):
logging.debug('ListView.update_with_query')
+ if 'order_by' not in query_dict:
+ query_dict['order_by'] = ['+timestamp']
+ if query_dict['order_by'] != self._query.get('order_by'):
+ property_ = query_dict['order_by'][0][1:]
+ cell_text = self.sort_column.get_cell_renderers()[0]
+ self.sort_column.set_attributes(cell_text,
+ text=getattr(ListModel, 'COLUMN_' + property_.upper(),
+ ListModel.COLUMN_TIMESTAMP))
self._query = query_dict
- if 'order_by' not in self._query:
- self._query['order_by'] = ['+timestamp']
-
self.refresh()
def refresh(self):
@@ -316,12 +326,9 @@ class BaseListView(gtk.Bin):
def _is_query_empty(self):
# FIXME: This is a hack, we shouldn't have to update this every time
# a new search term is added.
- if self._query.get('query', '') or self._query.get('mime_type', '') or \
- self._query.get('keep', '') or self._query.get('mtime', '') or \
- self._query.get('activity', ''):
- return False
- else:
- return True
+ return not (self._query.get('query') or self._query.get('mime_type') or
+ self._query.get('keep') or self._query.get('mtime') or
+ self._query.get('activity'))
def __model_progress_cb(self, tree_model):
if self._progress_bar is None:
@@ -365,8 +372,8 @@ class BaseListView(gtk.Bin):
icon = CanvasIcon(size=style.LARGE_ICON_SIZE,
icon_name='activity-journal',
- stroke_color = style.COLOR_BUTTON_GREY.get_svg(),
- fill_color = style.COLOR_TRANSPARENT.get_svg())
+ stroke_color=style.COLOR_BUTTON_GREY.get_svg(),
+ fill_color=style.COLOR_TRANSPARENT.get_svg())
box.append(icon)
if message == MESSAGE_EMPTY_JOURNAL:
@@ -379,7 +386,7 @@ class BaseListView(gtk.Bin):
text = hippo.CanvasText(text=text,
xalign=hippo.ALIGNMENT_CENTER,
font_desc=style.FONT_BOLD.get_pango_desc(),
- color = style.COLOR_BUTTON_GREY.get_int())
+ color=style.COLOR_BUTTON_GREY.get_int())
box.append(text)
if message == MESSAGE_NO_MATCH:
@@ -415,7 +422,7 @@ class BaseListView(gtk.Bin):
while True:
x, y, width, height = self.tree_view.get_cell_area(path,
- self.date_column)
+ self.sort_column)
x, y = self.tree_view.convert_tree_to_widget_coords(x, y)
self.tree_view.queue_draw_area(x, y, width, height)
if path == end_path:
@@ -455,13 +462,13 @@ class BaseListView(gtk.Bin):
self.update_dates()
return True
+
class ListView(BaseListView):
__gtype_name__ = 'JournalListView'
__gsignals__ = {
- 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([object]))
+ 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
}
def __init__(self):
@@ -533,6 +540,7 @@ class ListView(BaseListView):
def __editing_canceled_cb(self, cell):
self.cell_title.props.editable = False
+
class CellRendererFavorite(CellRendererIcon):
__gtype_name__ = 'JournalCellRendererFavorite'
@@ -547,6 +555,7 @@ class CellRendererFavorite(CellRendererIcon):
self.props.prelit_stroke_color = style.COLOR_BUTTON_GREY.get_svg()
self.props.prelit_fill_color = style.COLOR_BUTTON_GREY.get_svg()
+
class CellRendererDetail(CellRendererIcon):
__gtype_name__ = 'JournalCellRendererDetail'
@@ -563,12 +572,12 @@ class CellRendererDetail(CellRendererIcon):
self.props.prelit_stroke_color = style.COLOR_TRANSPARENT.get_svg()
self.props.prelit_fill_color = style.COLOR_BLACK.get_svg()
+
class CellRendererActivityIcon(CellRendererIcon):
__gtype_name__ = 'JournalCellRendererActivityIcon'
__gsignals__ = {
- 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
+ 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([str])),
}
@@ -605,6 +614,7 @@ class CellRendererActivityIcon(CellRendererIcon):
show_palette = gobject.property(type=bool, default=True,
setter=set_show_palette)
+
class CellRendererBuddy(CellRendererIcon):
__gtype_name__ = 'JournalCellRendererBuddy'
@@ -638,4 +648,3 @@ class CellRendererBuddy(CellRendererIcon):
self.props.xo_color = xo_color
buddy = gobject.property(type=object, setter=set_buddy)
-
diff --git a/src/jarabe/journal/misc.py b/src/jarabe/journal/misc.py
index 24ad216..1431d5f 100644
--- a/src/jarabe/journal/misc.py
+++ b/src/jarabe/journal/misc.py
@@ -27,8 +27,10 @@ from sugar.activity import activityfactory
from sugar.activity.activityhandle import ActivityHandle
from sugar.graphics.icon import get_icon_file_name
from sugar.graphics.xocolor import XoColor
+from sugar.graphics.alert import ConfirmationAlert
from sugar import mime
from sugar.bundle.activitybundle import ActivityBundle
+from sugar.bundle.bundle import AlreadyInstalledException
from sugar.bundle.contentbundle import ContentBundle
from sugar import util
@@ -36,6 +38,8 @@ from jarabe.view import launcher
from jarabe.model import bundleregistry, shell
from jarabe.journal.journalentrybundle import JournalEntryBundle
from jarabe.journal import model
+from jarabe.journal import journalwindow
+
def _get_icon_for_mime(mime_type):
generic_types = mime.get_all_generic_types()
@@ -52,6 +56,7 @@ def _get_icon_for_mime(mime_type):
if file_name is not None:
return file_name
+
def get_icon_name(metadata):
file_name = None
@@ -81,35 +86,46 @@ def get_icon_name(metadata):
return file_name
+
def get_date(metadata):
""" Convert from a string in iso format to a more human-like format. """
- if metadata.has_key('timestamp'):
- timestamp = float(metadata['timestamp'])
- return util.timestamp_to_elapsed_string(timestamp)
- elif metadata.has_key('mtime'):
- ti = time.strptime(metadata['mtime'], "%Y-%m-%dT%H:%M:%S")
- return util.timestamp_to_elapsed_string(time.mktime(ti))
- else:
- return _('No date')
+ if 'timestamp' in metadata:
+ try:
+ timestamp = float(metadata['timestamp'])
+ except (TypeError, ValueError):
+ logging.warning('Invalid timestamp: %r', metadata['timestamp'])
+ else:
+ return util.timestamp_to_elapsed_string(timestamp)
+
+ if 'mtime' in metadata:
+ try:
+ ti = time.strptime(metadata['mtime'], '%Y-%m-%dT%H:%M:%S')
+ except (TypeError, ValueError):
+ logging.warning('Invalid mtime: %r', metadata['mtime'])
+ else:
+ return util.timestamp_to_elapsed_string(time.mktime(ti))
+
+ return _('No date')
+
def get_bundle(metadata):
try:
if is_activity_bundle(metadata):
- file_path = util.TempFilePath(model.get_file(metadata['uid']))
+ file_path = model.get_file(metadata['uid'])
if not os.path.exists(file_path):
logging.warning('Invalid path: %r', file_path)
return None
return ActivityBundle(file_path)
elif is_content_bundle(metadata):
- file_path = util.TempFilePath(model.get_file(metadata['uid']))
+ file_path = model.get_file(metadata['uid'])
if not os.path.exists(file_path):
logging.warning('Invalid path: %r', file_path)
return None
return ContentBundle(file_path)
elif is_journal_bundle(metadata):
- file_path = util.TempFilePath(model.get_file(metadata['uid']))
+ file_path = model.get_file(metadata['uid'])
if not os.path.exists(file_path):
logging.warning('Invalid path: %r', file_path)
return None
@@ -120,6 +136,7 @@ def get_bundle(metadata):
logging.exception('Incorrect bundle')
return None
+
def _get_activities_for_mime(mime_type):
registry = bundleregistry.get_registry()
result = registry.get_activities_for_type(mime_type)
@@ -130,6 +147,7 @@ def _get_activities_for_mime(mime_type):
result.append(activity)
return result
+
def get_activities(metadata):
activities = []
@@ -148,6 +166,7 @@ def get_activities(metadata):
return activities
+
def resume(metadata, bundle_id=None):
registry = bundleregistry.get_registry()
@@ -159,19 +178,16 @@ def resume(metadata, bundle_id=None):
bundle = ActivityBundle(file_path)
if not registry.is_installed(bundle):
logging.debug('Installing activity bundle')
- registry.install(bundle)
+ try:
+ registry.install(bundle)
+ except AlreadyInstalledException:
+ _downgrade_option_alert(bundle)
+ return
else:
logging.debug('Upgrading activity bundle')
registry.upgrade(bundle)
- logging.debug('activityfactory.creating bundle with id %r',
- bundle.get_bundle_id())
- installed_bundle = registry.get_bundle(bundle.get_bundle_id())
- if installed_bundle:
- activityfactory.create(installed_bundle)
- else:
- logging.error('Bundle %r is not installed.',
- bundle.get_bundle_id())
+ _launch_bundle(bundle)
elif is_content_bundle(metadata) and bundle_id is None:
@@ -192,17 +208,10 @@ def resume(metadata, bundle_id=None):
logging.debug('activityfactory.creating with uri %s', uri)
activity_bundle = registry.get_bundle(activities[0].get_bundle_id())
- activityfactory.create_with_uri(activity_bundle, bundle.get_start_uri())
+ launch(activity_bundle, uri=uri)
else:
activity_id = metadata.get('activity_id', '')
- if activity_id:
- shell_model = shell.get_model()
- activity = shell_model.get_activity_by_id(activity_id)
- if activity:
- activity.get_window().activate(gtk.get_current_event_time())
- return
-
if bundle_id is None:
activities = get_activities(metadata)
if not activities:
@@ -213,36 +222,91 @@ def resume(metadata, bundle_id=None):
bundle = registry.get_bundle(bundle_id)
-
if metadata.get('mountpoint', '/') == '/':
object_id = metadata['uid']
else:
object_id = model.copy(metadata, '/')
- if activity_id:
- launcher.add_launcher(activity_id, bundle.get_icon(),
- get_icon_color(metadata))
- handle = ActivityHandle(object_id=object_id,
- activity_id=activity_id)
- activityfactory.create(bundle, handle)
- else:
- activityfactory.create_with_object_id(bundle, object_id)
+ launch(bundle, activity_id=activity_id, object_id=object_id,
+ color=get_icon_color(metadata))
+
+
+def _launch_bundle(bundle):
+ registry = bundleregistry.get_registry()
+ logging.debug('activityfactory.creating bundle with id %r',
+ bundle.get_bundle_id())
+ installed_bundle = registry.get_bundle(bundle.get_bundle_id())
+ if installed_bundle:
+ launch(installed_bundle)
+ else:
+ logging.error('Bundle %r is not installed.',
+ bundle.get_bundle_id())
+
+
+def launch(bundle, activity_id=None, object_id=None, uri=None, color=None,
+ invited=False):
+ if activity_id is None or not activity_id:
+ activity_id = activityfactory.create_activity_id()
+
+ logging.debug('launch bundle_id=%s activity_id=%s object_id=%s uri=%s',
+ bundle.get_bundle_id(), activity_id, object_id, uri)
+
+ shell_model = shell.get_model()
+ activity = shell_model.get_activity_by_id(activity_id)
+ if activity is not None:
+ logging.debug('re-launch %r', activity.get_window())
+ activity.get_window().activate(gtk.get_current_event_time())
+ return
+
+ if color is None:
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ launcher.add_launcher(activity_id, bundle.get_icon(), color)
+ activity_handle = ActivityHandle(activity_id=activity_id,
+ object_id=object_id, uri=uri, invited=invited)
+ activityfactory.create(bundle, activity_handle)
+
+
+def _downgrade_option_alert(bundle):
+ alert = ConfirmationAlert()
+ alert.props.title = _('Older Version Of %s Activity') % (bundle.get_name())
+ alert.props.msg = _('Do you want to downgrade to version %s') % \
+ bundle.get_activity_version()
+ alert.connect('response', _downgrade_alert_response_cb, bundle)
+ journalwindow.get_journal_window().add_alert(alert)
+ alert.show()
+
+
+def _downgrade_alert_response_cb(alert, response_id, bundle):
+ if response_id is gtk.RESPONSE_OK:
+ journalwindow.get_journal_window().remove_alert(alert)
+ registry = bundleregistry.get_registry()
+ registry.install(bundle, force_downgrade=True)
+ _launch_bundle(bundle)
+ elif response_id is gtk.RESPONSE_CANCEL:
+ journalwindow.get_journal_window().remove_alert(alert)
+
def is_activity_bundle(metadata):
mime_type = metadata.get('mime_type', '')
return mime_type == ActivityBundle.MIME_TYPE or \
mime_type == ActivityBundle.DEPRECATED_MIME_TYPE
+
def is_content_bundle(metadata):
return metadata.get('mime_type', '') == ContentBundle.MIME_TYPE
+
def is_journal_bundle(metadata):
return metadata.get('mime_type', '') == JournalEntryBundle.MIME_TYPE
+
def is_bundle(metadata):
return is_activity_bundle(metadata) or is_content_bundle(metadata) or \
is_journal_bundle(metadata)
+
def get_icon_color(metadata):
if metadata is None or not 'icon-color' in metadata:
client = gconf.client_get_default()
diff --git a/src/jarabe/journal/modalalert.py b/src/jarabe/journal/modalalert.py
index c7c6a0a..6880941 100644
--- a/src/jarabe/journal/modalalert.py
+++ b/src/jarabe/journal/modalalert.py
@@ -22,6 +22,7 @@ from sugar.graphics.icon import Icon
from sugar.graphics import style
from sugar.graphics.xocolor import XoColor
+
class ModalAlert(gtk.Window):
__gtype_name__ = 'SugarModalAlert'
@@ -84,14 +85,12 @@ class ModalAlert(gtk.Window):
self.add(self._main_view)
self._main_view.show()
- self.connect("realize", self.__realize_cb)
+ self.connect('realize', self.__realize_cb)
def __realize_cb(self, widget):
self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
self.window.set_accept_focus(True)
def __show_journal_cb(self, button):
- '''The opener will listen on the destroy signal
- '''
+ """The opener will listen on the destroy signal"""
self.destroy()
-
diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py
index ffc62e0..320e577 100644
--- a/src/jarabe/journal/model.py
+++ b/src/jarabe/journal/model.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007-2008, One Laptop Per Child
+# Copyright (C) 2007-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
@@ -16,11 +16,13 @@
import logging
import os
+import errno
from datetime import datetime
import time
import shutil
-from stat import S_IFMT, S_IFDIR, S_IFREG
+from stat import S_IFLNK, S_IFMT, S_IFDIR, S_IFREG
import re
+from operator import itemgetter
import gobject
import dbus
@@ -31,18 +33,25 @@ from sugar import dispatch
from sugar import mime
from sugar import util
+
DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
# Properties the journal cares about.
-PROPERTIES = ['uid', 'title', 'mtime', 'timestamp', 'keep', 'buddies',
- 'icon-color', 'mime_type', 'progress', 'activity', 'mountpoint',
- 'activity_id', 'bundle_id']
+PROPERTIES = ['activity', 'activity_id', 'buddies', 'bundle_id',
+ 'creation_time', 'filesize', 'icon-color', 'keep', 'mime_type',
+ 'mountpoint', 'mtime', 'progress', 'timestamp', 'title', 'uid']
MIN_PAGES_TO_CACHE = 3
MAX_PAGES_TO_CACHE = 5
+_datastore = None
+created = dispatch.Signal()
+updated = dispatch.Signal()
+deleted = dispatch.Signal()
+
+
class _Cache(object):
__gtype_name__ = 'model_Cache'
@@ -73,7 +82,7 @@ class BaseResultSet(object):
"""
def __init__(self, query, page_size):
- self._total_count = -1
+ self._total_count = -1
self._position = -1
self._query = query
self._page_size = page_size
@@ -108,8 +117,6 @@ class BaseResultSet(object):
self._position = position
def read(self):
- logging.debug('ResultSet.read position: %r', self._position)
-
if self._position == -1:
self.seek(0)
@@ -142,7 +149,8 @@ class BaseResultSet(object):
self._cache.append_all(entries)
self._offset = offset
- elif remaining_forward_entries <= 0 and remaining_backwards_entries > 0:
+ elif (remaining_forward_entries <= 0 and
+ remaining_backwards_entries > 0):
# Add one page to the end of cache
logging.debug('appending one more page, offset: %r',
@@ -184,11 +192,10 @@ class BaseResultSet(object):
objects_excess = len(self._cache) - cache_limit
if objects_excess > 0:
del self._cache[-objects_excess:]
- else:
- logging.debug('cache hit and no need to grow the cache')
return self._cache[self._position - self._offset]
+
class DatastoreResultSet(BaseResultSet):
"""Encapsulates the result of a query on the datastore
"""
@@ -216,6 +223,7 @@ class DatastoreResultSet(BaseResultSet):
return entries, total_count
+
class InplaceResultSet(BaseResultSet):
"""Encapsulates the result of a query on a mount point
"""
@@ -223,7 +231,9 @@ class InplaceResultSet(BaseResultSet):
BaseResultSet.__init__(self, query, page_size)
self._mount_point = mount_point
self._file_list = None
- self._pending_directories = 0
+ self._pending_directories = []
+ self._visited_directories = []
+ self._pending_files = []
self._stopped = False
query_text = query.get('query', '')
@@ -246,15 +256,27 @@ class InplaceResultSet(BaseResultSet):
self._mime_types = query.get('mime_type', [])
+ self._sort = query.get('order_by', ['+timestamp'])[0]
+
def setup(self):
self._file_list = []
- self._recurse_dir(self._mount_point)
+ self._pending_directories = [self._mount_point]
+ self._visited_directories = []
+ self._pending_files = []
+ gobject.idle_add(self._scan)
def stop(self):
self._stopped = True
def setup_ready(self):
- self._file_list.sort(lambda a, b: b[2] - a[2])
+ if self._sort[1:] == 'filesize':
+ keygetter = itemgetter(3)
+ else:
+ # timestamp
+ keygetter = itemgetter(2)
+ self._file_list.sort(lambda a, b: cmp(b, a),
+ key=keygetter,
+ reverse=(self._sort[0] == '-'))
self.ready.send(self)
def find(self, query):
@@ -267,13 +289,13 @@ class InplaceResultSet(BaseResultSet):
t = time.time()
offset = int(query.get('offset', 0))
- limit = int(query.get('limit', len(self._file_list)))
+ limit = int(query.get('limit', len(self._file_list)))
total_count = len(self._file_list)
files = self._file_list[offset:offset + limit]
entries = []
- for file_path, stat, mtime_ in files:
+ for file_path, stat, mtime_, size_ in files:
metadata = _get_file_metadata(file_path, stat)
metadata['mountpoint'] = self._mount_point
entries.append(metadata)
@@ -282,75 +304,115 @@ class InplaceResultSet(BaseResultSet):
return entries, total_count
- def _recurse_dir(self, dir_path):
- self._pending_directories += 1
- gobject.idle_add(self._idle_recurse_dir, dir_path)
+ def _scan(self):
+ if self._stopped:
+ return False
- def _idle_recurse_dir(self, dir_path):
- try:
- self._real_recurse_dir(dir_path)
- finally:
- self._pending_directories -= 1
- if self._pending_directories == 0:
- self.setup_ready()
+ self.progress.send(self)
- def _real_recurse_dir(self, dir_path):
- if self._stopped:
- return
+ if self._pending_files:
+ self._scan_a_file()
+ return True
+
+ if self._pending_directories:
+ self._scan_a_directory()
+ return True
+
+ self.setup_ready()
+ self._visited_directories = []
+ return False
+
+ def _scan_a_file(self):
+ full_path = self._pending_files.pop(0)
try:
- dirs = os.listdir(dir_path)
- except Exception:
- logging.exception('Error reading directory %r', dir_path)
- dirs = []
+ stat = os.lstat(full_path)
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ logging.exception(
+ 'Error reading metadata of file %r', full_path)
+ return
+
+ if S_IFMT(stat.st_mode) == S_IFLNK:
+ try:
+ link = os.readlink(full_path)
+ except OSError, e:
+ logging.exception(
+ 'Error reading target of link %r', full_path)
+ return
+
+ if not os.path.abspath(link).startswith(self._mount_point):
+ return
- for entry in dirs:
- if entry.startswith('.'):
- continue
- full_path = dir_path + '/' + entry
try:
stat = os.stat(full_path)
- if S_IFMT(stat.st_mode) == S_IFDIR:
- self._recurse_dir(full_path)
- elif S_IFMT(stat.st_mode) == S_IFREG:
- add_to_list = True
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ logging.exception(
+ 'Error reading metadata of linked file %r', full_path)
+ return
+
+ if S_IFMT(stat.st_mode) == S_IFDIR:
+ id_tuple = stat.st_ino, stat.st_dev
+ if not id_tuple in self._visited_directories:
+ self._visited_directories.append(id_tuple)
+ self._pending_directories.append(full_path)
+ return
+
+ if S_IFMT(stat.st_mode) != S_IFREG:
+ return
- if self._regex is not None and \
- not self._regex.match(full_path):
- add_to_list = False
+ if self._regex is not None and \
+ not self._regex.match(full_path):
+ return
- if None not in [self._date_start, self._date_end] and \
- (stat.st_mtime < self._date_start or
- stat.st_mtime > self._date_end):
- add_to_list = False
+ if self._date_start is not None and stat.st_mtime < self._date_start:
+ return
- if self._mime_types:
- mime_type = gio.content_type_guess(filename=full_path)
- if mime_type not in self._mime_types:
- add_to_list = False
+ if self._date_end is not None and stat.st_mtime > self._date_end:
+ return
- if add_to_list:
- file_info = (full_path, stat, int(stat.st_mtime))
- self._file_list.append(file_info)
+ if self._mime_types:
+ mime_type = gio.content_type_guess(filename=full_path)
+ if mime_type not in self._mime_types:
+ return
- self.progress.send(self)
+ file_info = (full_path, stat, int(stat.st_mtime), stat.st_size)
+ self._file_list.append(file_info)
+
+ return
+
+ def _scan_a_directory(self):
+ dir_path = self._pending_directories.pop(0)
+
+ try:
+ entries = os.listdir(dir_path)
+ except OSError, e:
+ if e.errno != errno.EACCES:
+ logging.exception('Error reading directory %r', dir_path)
+ return
+
+ for entry in entries:
+ if entry.startswith('.'):
+ continue
+ self._pending_files.append(dir_path + '/' + entry)
+ return
- except Exception:
- logging.exception('Error reading file %r', full_path)
def _get_file_metadata(path, stat):
client = gconf.client_get_default()
return {'uid': path,
'title': os.path.basename(path),
'timestamp': stat.st_mtime,
+ 'filesize': stat.st_size,
'mime_type': gio.content_type_guess(filename=path),
'activity': '',
'activity_id': '',
'icon-color': client.get_string('/desktop/sugar/user/color'),
'description': path}
-_datastore = None
+
def _get_datastore():
global _datastore
if _datastore is None:
@@ -364,15 +426,19 @@ def _get_datastore():
return _datastore
+
def _datastore_created_cb(object_id):
created.send(None, object_id=object_id)
+
def _datastore_updated_cb(object_id):
updated.send(None, object_id=object_id)
+
def _datastore_deleted_cb(object_id):
deleted.send(None, object_id=object_id)
+
def find(query_, page_size):
"""Returns a ResultSet
"""
@@ -387,6 +453,7 @@ def find(query_, page_size):
else:
return InplaceResultSet(query, page_size, mount_points[0])
+
def _get_mount_point(path):
dir_path = os.path.dirname(path)
while True:
@@ -395,6 +462,7 @@ def _get_mount_point(path):
else:
dir_path = dir_path.rsplit(os.sep, 1)[0]
+
def get(object_id):
"""Returns the metadata for an object
"""
@@ -407,6 +475,7 @@ def get(object_id):
metadata['mountpoint'] = '/'
return metadata
+
def get_file(object_id):
"""Returns the file for an object
"""
@@ -421,6 +490,7 @@ def get_file(object_id):
else:
return None
+
def get_file_size(object_id):
"""Return the file size for an object
"""
@@ -436,12 +506,14 @@ def get_file_size(object_id):
return 0
+
def get_unique_values(key):
"""Returns a list with the different values a property has taken
"""
empty_dict = dbus.Dictionary({}, signature='ss')
return _get_datastore().get_uniquevaluesfor(key, empty_dict)
+
def delete(object_id):
"""Removes an object from persistent storage
"""
@@ -451,6 +523,7 @@ def delete(object_id):
else:
_get_datastore().delete(object_id)
+
def copy(metadata, mount_point):
"""Copies an object to another mount point
"""
@@ -462,6 +535,7 @@ def copy(metadata, mount_point):
return write(metadata, file_path, transfer_ownership=False)
+
def write(metadata, file_path='', update_mtime=True, transfer_ownership=True):
"""Creates or updates an entry for that id
"""
@@ -496,6 +570,7 @@ def write(metadata, file_path='', update_mtime=True, transfer_ownership=True):
return object_id
+
def _get_file_name(title, mime_type):
file_name = title
@@ -520,11 +595,12 @@ def _get_file_name(title, mime_type):
return file_name
+
def _get_unique_file_name(mount_point, file_name):
if os.path.exists(os.path.join(mount_point, file_name)):
i = 1
+ name, extension = os.path.splitext(file_name)
while len(file_name) <= 255:
- name, extension = os.path.splitext(file_name)
file_name = name + '_' + str(i) + extension
if not os.path.exists(os.path.join(mount_point, file_name)):
break
@@ -532,10 +608,7 @@ def _get_unique_file_name(mount_point, file_name):
return file_name
+
def is_editable(metadata):
mountpoint = metadata.get('mountpoint', '/')
return mountpoint == '/'
-
-created = dispatch.Signal()
-updated = dispatch.Signal()
-deleted = dispatch.Signal()
diff --git a/src/jarabe/journal/objectchooser.py b/src/jarabe/journal/objectchooser.py
index 49af3e6..ecb8ecf 100644
--- a/src/jarabe/journal/objectchooser.py
+++ b/src/jarabe/journal/objectchooser.py
@@ -29,14 +29,13 @@ from jarabe.journal.listmodel import ListModel
from jarabe.journal.journaltoolbox import SearchToolbar
from jarabe.journal.volumestoolbar import VolumesToolbar
+
class ObjectChooser(gtk.Window):
__gtype_name__ = 'ObjectChooser'
__gsignals__ = {
- 'response': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([int]))
+ 'response': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([int])),
}
def __init__(self, parent=None, what_filter=''):
@@ -136,6 +135,7 @@ class ObjectChooser(gtk.Window):
visible = event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED
self._list_view.set_is_visible(visible)
+
class TitleBox(VolumesToolbar):
__gtype_name__ = 'TitleBox'
@@ -162,6 +162,7 @@ class TitleBox(VolumesToolbar):
self.insert(tool_item, -1)
tool_item.show()
+
class ChooserListView(BaseListView):
__gtype_name__ = 'ChooserListView'
@@ -187,7 +188,7 @@ class ChooserListView(BaseListView):
if event.window != tree_view.get_bin_window():
return False
- pos = tree_view.get_path_at_pos(event.x, event.y)
+ pos = tree_view.get_path_at_pos(int(event.x), int(event.y))
if pos is None:
return False
@@ -196,4 +197,3 @@ class ChooserListView(BaseListView):
self.emit('entry-activated', uid)
return False
-
diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py
index 0e7702d..9ae1afb 100644
--- a/src/jarabe/journal/palettes.py
+++ b/src/jarabe/journal/palettes.py
@@ -28,20 +28,19 @@ from sugar.graphics.icon import Icon
from sugar.graphics.xocolor import XoColor
from sugar import mime
-from jarabe.model import bundleregistry
from jarabe.model import friends
from jarabe.model import filetransfer
from jarabe.model import mimeregistry
from jarabe.journal import misc
from jarabe.journal import model
+
class ObjectPalette(Palette):
__gtype_name__ = 'ObjectPalette'
__gsignals__ = {
- 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
+ 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([str])),
}
@@ -54,7 +53,7 @@ class ObjectPalette(Palette):
activity_icon.props.file = misc.get_icon_name(metadata)
activity_icon.props.xo_color = misc.get_icon_color(metadata)
- if metadata.has_key('title'):
+ if 'title' in metadata:
title = gobject.markup_escape_text(metadata['title'])
else:
title = _('Untitled')
@@ -62,22 +61,29 @@ class ObjectPalette(Palette):
Palette.__init__(self, primary_text=title,
icon=activity_icon)
- if metadata.get('activity_id', ''):
- resume_label = _('Resume')
- resume_with_label = _('Resume with')
- else:
- resume_label = _('Start')
- resume_with_label = _('Start with')
- menu_item = MenuItem(resume_label, 'activity-start')
- menu_item.connect('activate', self.__start_activate_cb)
- self.menu.append(menu_item)
- menu_item.show()
+ if misc.get_activities(metadata) or misc.is_bundle(metadata):
+ if metadata.get('activity_id', ''):
+ resume_label = _('Resume')
+ resume_with_label = _('Resume with')
+ else:
+ resume_label = _('Start')
+ resume_with_label = _('Start with')
+ menu_item = MenuItem(resume_label, 'activity-start')
+ menu_item.connect('activate', self.__start_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
- menu_item = MenuItem(resume_with_label, 'activity-start')
- self.menu.append(menu_item)
- menu_item.show()
- start_with_menu = StartWithMenu(self._metadata)
- menu_item.set_submenu(start_with_menu)
+ menu_item = MenuItem(resume_with_label, 'activity-start')
+ self.menu.append(menu_item)
+ menu_item.show()
+ start_with_menu = StartWithMenu(self._metadata)
+ menu_item.set_submenu(start_with_menu)
+
+ else:
+ menu_item = MenuItem(_('No activity to start entry'))
+ menu_item.set_sensitive(False)
+ self.menu.append(menu_item)
+ menu_item.show()
client = gconf.client_get_default()
color = XoColor(client.get_string('/desktop/sugar/user/color'))
@@ -128,11 +134,6 @@ class ObjectPalette(Palette):
self._temp_file_path = None
def __erase_activate_cb(self, menu_item):
- registry = bundleregistry.get_registry()
-
- bundle = misc.get_bundle(self._metadata)
- if bundle is not None and registry.is_installed(bundle):
- registry.uninstall(bundle)
model.delete(self._metadata['uid'])
def __detail_activate_cb(self, menu_item):
@@ -152,12 +153,13 @@ class ObjectPalette(Palette):
filetransfer.start_transfer(buddy, file_name, title, description,
mime_type)
+
class FriendsMenu(gtk.Menu):
__gtype_name__ = 'JournalFriendsMenu'
__gsignals__ = {
- 'friend-selected' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
- ([object])),
+ 'friend-selected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
}
def __init__(self):
diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py
index 74b974c..2d842f1 100644
--- a/src/jarabe/journal/volumestoolbar.py
+++ b/src/jarabe/journal/volumestoolbar.py
@@ -15,6 +15,8 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import logging
+import os
+import statvfs
from gettext import gettext as _
import gobject
@@ -25,17 +27,20 @@ import gconf
from sugar.graphics.radiotoolbutton import RadioToolButton
from sugar.graphics.palette import Palette
from sugar.graphics.xocolor import XoColor
+from sugar import env
from jarabe.journal import model
from jarabe.view.palettes import VolumePalette
+
class VolumesToolbar(gtk.Toolbar):
__gtype_name__ = 'VolumesToolbar'
__gsignals__ = {
- 'volume-changed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([str]))
+ 'volume-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([str])),
+ 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([str, str])),
}
def __init__(self):
@@ -44,7 +49,6 @@ class VolumesToolbar(gtk.Toolbar):
self._mount_removed_hid = None
button = JournalButton()
- button.set_palette(Palette(_('Journal')))
button.connect('toggled', self._button_toggled_cb)
self.insert(button, 0)
button.show()
@@ -61,10 +65,10 @@ class VolumesToolbar(gtk.Toolbar):
def _set_up_volumes(self):
volume_monitor = gio.volume_monitor_get()
- self._mount_added_hid = \
- volume_monitor.connect('mount-added', self.__mount_added_cb)
- self._mount_removed_hid = \
- volume_monitor.connect('mount-removed', self.__mount_removed_cb)
+ self._mount_added_hid = volume_monitor.connect('mount-added',
+ self.__mount_added_cb)
+ self._mount_removed_hid = volume_monitor.connect('mount-removed',
+ self.__mount_removed_cb)
for mount in volume_monitor.get_mounts():
self._add_button(mount)
@@ -81,6 +85,7 @@ class VolumesToolbar(gtk.Toolbar):
button = VolumeButton(mount)
button.props.group = self._volume_buttons[0]
button.connect('toggled', self._button_toggled_cb)
+ button.connect('volume-error', self.__volume_error_cb)
position = self.get_item_index(self._volume_buttons[-1]) + 1
self.insert(button, position)
button.show()
@@ -90,6 +95,9 @@ class VolumesToolbar(gtk.Toolbar):
if len(self.get_children()) > 1:
self.show()
+ def __volume_error_cb(self, button, strerror, severity):
+ self.emit('volume-error', strerror, severity)
+
def _button_toggled_cb(self, button):
if button.props.active:
self.emit('volume-changed', button.mount_point)
@@ -122,7 +130,13 @@ class VolumesToolbar(gtk.Toolbar):
button = self._get_button_for_mount(mount)
button.props.active = True
+
class BaseButton(RadioToolButton):
+ __gsignals__ = {
+ 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([str, str])),
+ }
+
def __init__(self, mount_point):
RadioToolButton.__init__(self)
@@ -133,11 +147,26 @@ class BaseButton(RadioToolButton):
gtk.gdk.ACTION_COPY)
self.connect('drag-data-received', self._drag_data_received_cb)
- def _drag_data_received_cb(self, widget, drag_context, x, y, selection_data,
- info, timestamp):
+ def _drag_data_received_cb(self, widget, drag_context, x, y,
+ selection_data, info, timestamp):
object_id = selection_data.data
metadata = model.get(object_id)
- model.copy(metadata, self.mount_point)
+ file_path = model.get_file(metadata['uid'])
+ if not file_path or not os.path.exists(file_path):
+ logging.warn('Entries without a file cannot be copied.')
+ self.emit('volume-error',
+ _('Entries without a file cannot be copied.'),
+ _('Warning'))
+ return
+
+ try:
+ model.copy(metadata, self.mount_point)
+ except IOError, e:
+ logging.exception('Error while copying the entry. %s', e.strerror)
+ self.emit('volume-error',
+ _('Error while copying the entry. %s') % e.strerror,
+ _('Error'))
+
class VolumeButton(BaseButton):
def __init__(self, mount):
@@ -169,6 +198,7 @@ class VolumeButton(BaseButton):
#palette.set_group_id('frame')
return palette
+
class JournalButton(BaseButton):
def __init__(self):
BaseButton.__init__(self, mount_point='/')
@@ -179,3 +209,36 @@ class JournalButton(BaseButton):
color = XoColor(client.get_string('/desktop/sugar/user/color'))
self.props.xo_color = color
+ def create_palette(self):
+ palette = JournalButtonPalette(self)
+ return palette
+
+
+class JournalButtonPalette(Palette):
+
+ def __init__(self, mount):
+ Palette.__init__(self, _('Journal'))
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+ vbox.show()
+
+ self._progress_bar = gtk.ProgressBar()
+ vbox.add(self._progress_bar)
+ self._progress_bar.show()
+
+ self._free_space_label = gtk.Label()
+ self._free_space_label.set_alignment(0.5, 0.5)
+ vbox.add(self._free_space_label)
+ self._free_space_label.show()
+
+ self.connect('popup', self.__popup_cb)
+
+ def __popup_cb(self, palette):
+ stat = os.statvfs(env.get_profile_path())
+ free_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL]
+ total_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BLOCKS]
+
+ fraction = (total_space - free_space) / float(total_space)
+ self._progress_bar.props.fraction = fraction
+ self._free_space_label.props.label = _('%(free_space)d MB Free') % \
+ {'free_space': free_space / (1024 * 1024)}
diff --git a/src/jarabe/model/Makefile.am b/src/jarabe/model/Makefile.am
index e9f0700..92e8712 100644
--- a/src/jarabe/model/Makefile.am
+++ b/src/jarabe/model/Makefile.am
@@ -1,5 +1,6 @@
sugardir = $(pythondir)/jarabe/model
sugar_PYTHON = \
+ adhoc.py \
__init__.py \
buddy.py \
bundleregistry.py \
@@ -7,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/__init__.py b/src/jarabe/model/__init__.py
index a9dd95a..85f6a24 100644
--- a/src/jarabe/model/__init__.py
+++ b/src/jarabe/model/__init__.py
@@ -13,4 +13,3 @@
# 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
-
diff --git a/src/jarabe/model/adhoc.py b/src/jarabe/model/adhoc.py
new file mode 100644
index 0000000..8842a5c
--- /dev/null
+++ b/src/jarabe/model/adhoc.py
@@ -0,0 +1,294 @@
+# Copyright (C) 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
+
+import logging
+
+import dbus
+import gobject
+
+from jarabe.model import network
+from jarabe.model.network import Settings
+from sugar.util import unique_id
+from jarabe.model.network import IP4Config
+
+
+_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_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint'
+_NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active'
+
+_adhoc_manager_instance = None
+
+
+def get_adhoc_manager_instance():
+ global _adhoc_manager_instance
+ if _adhoc_manager_instance is None:
+ _adhoc_manager_instance = AdHocManager()
+ return _adhoc_manager_instance
+
+
+class AdHocManager(gobject.GObject):
+ """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. If Sugar sees no "known" network when it
+ starts, it does autoconnect to an Ad-hoc network.
+
+ """
+
+ __gsignals__ = {
+ 'members-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
+ 'state-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
+ }
+
+ _AUTOCONNECT_TIMEOUT = 30
+ _CHANNEL_1 = 1
+ _CHANNEL_6 = 6
+ _CHANNEL_11 = 11
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self._bus = dbus.SystemBus()
+ self._device = None
+ self._idle_source = 0
+ self._listening_called = 0
+ self._device_state = network.DEVICE_STATE_UNKNOWN
+
+ self._current_channel = None
+ self._networks = {self._CHANNEL_1: None,
+ self._CHANNEL_6: None,
+ self._CHANNEL_11: None}
+
+ def start_listening(self, device):
+ self._listening_called += 1
+ if self._listening_called > 1:
+ raise RuntimeError('The start listening method can' \
+ ' only be called once.')
+
+ self._device = device
+ props = dbus.Interface(device, dbus.PROPERTIES_IFACE)
+ self._device_state = props.Get(_NM_DEVICE_IFACE, 'State')
+
+ 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 stop_listening(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)
+
+ def __device_state_changed_cb(self, new_state, old_state, reason):
+ self._device_state = new_state
+ self._update_state()
+
+ def __wireless_properties_changed_cb(self, properties):
+ if 'ActiveAccessPoint' in properties and \
+ properties['ActiveAccessPoint'] != '/':
+ active_ap = self._bus.get_object(_NM_SERVICE,
+ properties['ActiveAccessPoint'])
+ props = dbus.Interface(active_ap, dbus.PROPERTIES_IFACE)
+ props.GetAll(_NM_ACCESSPOINT_IFACE, byte_arrays=True,
+ reply_handler=self.__get_all_ap_props_reply_cb,
+ error_handler=self.__get_all_ap_props_error_cb)
+
+ def __get_all_ap_props_reply_cb(self, properties):
+ if properties['Mode'] == network.NM_802_11_MODE_ADHOC and \
+ 'Frequency' in properties:
+ frequency = properties['Frequency']
+ self._current_channel = network.frequency_to_channel(frequency)
+ else:
+ self._current_channel = None
+ self._update_state()
+
+ def __get_all_ap_props_error_cb(self, err):
+ logging.error('Error getting the access point properties: %s', err)
+
+ def _update_state(self):
+ self.emit('state-changed', self._current_channel, self._device_state)
+
+ def _have_configured_connections(self):
+ return len(network.get_settings().connections) > 0
+
+ def autoconnect(self):
+ """Autoconnect to an Ad-hoc network"""
+ if self._device_state != network.DEVICE_STATE_DISCONNECTED:
+ return
+ elif self._have_configured_connections():
+ self._autoconnect_adhoc_timer()
+ else:
+ self._autoconnect_adhoc()
+
+ def _autoconnect_adhoc_timer(self):
+ """Start a timer which basically looks for 30 seconds of inactivity
+ on the device, then does autoconnect to an Ad-hoc network.
+
+ """
+ if self._idle_source != 0:
+ gobject.source_remove(self._idle_source)
+ self._idle_source = gobject.timeout_add_seconds( \
+ self._AUTOCONNECT_TIMEOUT, self.__idle_check_cb)
+
+ def __idle_check_cb(self):
+ if self._device_state == network.DEVICE_STATE_DISCONNECTED:
+ logging.debug('Connect to Ad-hoc network due to inactivity.')
+ self._autoconnect_adhoc()
+ return False
+
+ def _autoconnect_adhoc(self):
+ """First we try if there is an Ad-hoc network that is used by other
+ learners in the area, if not we default to channel 1.
+
+ """
+ if self._networks[self._CHANNEL_1] is not None:
+ self._connect(self._CHANNEL_1)
+ elif self._networks[self._CHANNEL_6] is not None:
+ self._connect(self._CHANNEL_6)
+ elif self._networks[self._CHANNEL_11] is not None:
+ self._connect(self._CHANNEL_11)
+ else:
+ self._connect(self._CHANNEL_1)
+
+ def activate_channel(self, channel):
+ """Activate a sugar Ad-hoc network.
+
+ Keyword arguments:
+ channel -- Channel to connect to (should be 1, 6, 11)
+
+ """
+ self._connect(channel)
+
+ def _connect(self, channel):
+ name = 'Ad-hoc Network %d' % channel
+ connection = network.find_connection_by_ssid(name)
+ if connection is None:
+ settings = Settings()
+ settings.connection.id = name
+ settings.connection.uuid = unique_id()
+ settings.connection.type = '802-11-wireless'
+ settings.wireless.ssid = dbus.ByteArray(name)
+ settings.wireless.band = 'bg'
+ settings.wireless.channel = channel
+ settings.wireless.mode = 'adhoc'
+ settings.ip4_config = IP4Config()
+ settings.ip4_config.method = 'link-local'
+
+ connection = network.add_connection(name, 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 deactivate_active_channel(self):
+ """Deactivate the current active channel."""
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+
+ netmgr_props = dbus.Interface(netmgr, dbus.PROPERTIES_IFACE)
+ netmgr_props.Get(_NM_IFACE, 'ActiveConnections', \
+ reply_handler=self.__get_active_connections_reply_cb,
+ error_handler=self.__get_active_connections_error_cb)
+
+ def __get_active_connections_reply_cb(self, active_connections_o):
+ for connection_o in active_connections_o:
+ obj = self._bus.get_object(_NM_IFACE, connection_o)
+ props = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
+ state = props.Get(_NM_ACTIVE_CONN_IFACE, 'State')
+ if state == network.NM_ACTIVE_CONNECTION_STATE_ACTIVATED:
+ access_point_o = props.Get(_NM_ACTIVE_CONN_IFACE,
+ 'SpecificObject')
+ if access_point_o != '/':
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr.DeactivateConnection(connection_o)
+
+ def __get_active_connections_error_cb(self, err):
+ logging.error('Error getting the active connections: %s', err)
+
+ def __activate_reply_cb(self, connection):
+ logging.debug('Ad-hoc network created: %s', connection)
+
+ def __activate_error_cb(self, err):
+ logging.error('Failed to create Ad-hoc network: %s', err)
+
+ def add_access_point(self, access_point):
+ """Add an access point to a network and notify the view to idicate
+ the member change.
+
+ Keyword arguments:
+ access_point -- Access Point
+
+ """
+ if access_point.name.endswith(' 1'):
+ self._networks[self._CHANNEL_1] = access_point
+ self.emit('members-changed', self._CHANNEL_1, True)
+ elif access_point.name.endswith(' 6'):
+ self._networks[self._CHANNEL_6] = access_point
+ self.emit('members-changed', self._CHANNEL_6, True)
+ elif access_point.name.endswith('11'):
+ self._networks[self._CHANNEL_11] = access_point
+ self.emit('members-changed', self._CHANNEL_11, True)
+
+ def is_sugar_adhoc_access_point(self, ap_object_path):
+ """Checks whether an access point is part of a sugar Ad-hoc network.
+
+ Keyword arguments:
+ ap_object_path -- Access Point object path
+
+ Return: Boolean
+
+ """
+ for access_point in self._networks.values():
+ if access_point is not None:
+ if access_point.model.object_path == ap_object_path:
+ return True
+ return False
+
+ def remove_access_point(self, ap_object_path):
+ """Remove an access point from a sugar Ad-hoc network.
+
+ Keyword arguments:
+ ap_object_path -- Access Point object path
+
+ """
+ for channel in self._networks:
+ if self._networks[channel] is not None:
+ if self._networks[channel].model.object_path == ap_object_path:
+ self.emit('members-changed', channel, False)
+ self._networks[channel] = None
+ break
diff --git a/src/jarabe/model/buddy.py b/src/jarabe/model/buddy.py
index 5978bae..c580e68 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. <http://www.collabora.co.uk/>
#
# 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,220 @@
# 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'
+_owner_instance = None
+
+
+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._current_activity = None
- def get_key(self):
- return self._key
+ gobject.GObject.__init__(self, **kwargs)
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
- def get_buddy(self):
- return self._buddy
+ 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
+
+
+class OwnerBuddyModel(BaseBuddyModel):
+ __gtype_name__ = 'SugarOwnerBuddyModel'
+
+ def __init__(self):
+ BaseBuddyModel.__init__(self)
+
+ 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(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, buddy, pspec):
+ self._sync_properties()
+
+ def __current_activity_changed_cb(self, buddy, 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 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
+ self._handle = None
+
+ BaseBuddyModel.__init__(self, **kwargs)
- def is_present(self):
- if self._buddy:
- return True
+ 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_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)
+
+ def get_handle(self):
+ return self._handle
+
+ def set_handle(self, handle):
+ self._handle = handle
+
+ handle = gobject.property(type=object, getter=get_handle,
+ setter=set_handle)
diff --git a/src/jarabe/model/bundleregistry.py b/src/jarabe/model/bundleregistry.py
index 86a2738..84d55c0 100644
--- a/src/jarabe/model/bundleregistry.py
+++ b/src/jarabe/model/bundleregistry.py
@@ -17,14 +17,15 @@
import os
import logging
-import traceback
+import gconf
import gobject
import gio
import simplejson
from sugar.bundle.activitybundle import ActivityBundle
from sugar.bundle.contentbundle import ContentBundle
+from sugar.bundle.bundleversion import NormalizedVersion
from jarabe.journal.journalentrybundle import JournalEntryBundle
from sugar.bundle.bundle import MalformedBundleException, \
AlreadyInstalledException, RegistrationException
@@ -33,16 +34,20 @@ from sugar import env
from jarabe import config
from jarabe.model import mimeregistry
+
+_instance = None
+
+
class BundleRegistry(gobject.GObject):
"""Tracks the available activity bundles"""
__gsignals__ = {
- 'bundle-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT])),
+ 'bundle-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
'bundle-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'bundle-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT]))
+ ([gobject.TYPE_PYOBJECT])),
}
def __init__(self):
@@ -66,6 +71,14 @@ class BundleRegistry(gobject.GObject):
self._last_defaults_mtime = -1
self._favorite_bundles = {}
+ client = gconf.client_get_default()
+ self._protected_activities = client.get_list(
+ '/desktop/sugar/protected_activities',
+ gconf.VALUE_STRING)
+
+ if self._protected_activities is None:
+ self._protected_activities = []
+
try:
self._load_favorites()
except Exception:
@@ -144,14 +157,16 @@ class BundleRegistry(gobject.GObject):
return
for bundle_id in default_activities:
- max_version = -1
+ max_version = '0'
for bundle in self._bundles:
if bundle.get_bundle_id() == bundle_id and \
- max_version < bundle.get_activity_version():
+ NormalizedVersion(max_version) < \
+ NormalizedVersion(bundle.get_activity_version()):
max_version = bundle.get_activity_version()
key = self._get_favorite_key(bundle_id, max_version)
- if max_version > -1 and key not in self._favorite_bundles:
+ if NormalizedVersion(max_version) > NormalizedVersion('0') and \
+ key not in self._favorite_bundles:
self._favorite_bundles[key] = None
logging.debug('After merging: %r', self._favorite_bundles)
@@ -164,7 +179,7 @@ class BundleRegistry(gobject.GObject):
if bundle.get_bundle_id() == bundle_id:
return bundle
return None
-
+
def __iter__(self):
return self._bundles.__iter__()
@@ -185,19 +200,18 @@ class BundleRegistry(gobject.GObject):
if os.path.isdir(bundle_dir):
bundles[bundle_dir] = os.stat(bundle_dir).st_mtime
except Exception:
- logging.error('Error while processing installed activity ' \
- 'bundle %s:\n%s' % \
- (bundle_dir, traceback.format_exc()))
+ logging.exception('Error while processing installed activity'
+ ' bundle %s:', bundle_dir)
bundle_dirs = bundles.keys()
bundle_dirs.sort(lambda d1, d2: cmp(bundles[d1], bundles[d2]))
for folder in bundle_dirs:
try:
self._add_bundle(folder)
- except Exception, e:
- logging.error('Error while processing installed activity ' \
- 'bundle %s:\n%s' % \
- (folder, traceback.format_exc()))
+ except:
+ # pylint: disable=W0702
+ logging.exception('Error while processing installed activity'
+ ' bundle %s:', folder)
def add_bundle(self, bundle_path, install_mime_type=False):
bundle = self._add_bundle(bundle_path, install_mime_type)
@@ -224,8 +238,8 @@ class BundleRegistry(gobject.GObject):
installed = self.get_bundle(bundle_id)
if installed is not None:
- if installed.get_activity_version() >= \
- bundle.get_activity_version():
+ if NormalizedVersion(installed.get_activity_version()) >= \
+ NormalizedVersion(bundle.get_activity_version()):
logging.debug('Skip old version for %s', bundle_id)
return None
else:
@@ -251,8 +265,7 @@ class BundleRegistry(gobject.GObject):
default_bundle = None
for bundle in self._bundles:
- if bundle.get_mime_types() and mime_type in bundle.get_mime_types():
-
+ if mime_type in (bundle.get_mime_types() or []):
if bundle.get_bundle_id() == default_bundle_id:
default_bundle = bundle
elif self.get_default_for_type(mime_type) == \
@@ -267,10 +280,7 @@ class BundleRegistry(gobject.GObject):
return result
def get_default_for_type(self, mime_type):
- if self._mime_defaults.has_key(mime_type):
- return self._mime_defaults[mime_type]
- else:
- return None
+ return self._mime_defaults.get(mime_type)
def _find_bundle(self, bundle_id, version):
for bundle in self._bundles:
@@ -302,10 +312,14 @@ class BundleRegistry(gobject.GObject):
key = self._get_favorite_key(bundle_id, version)
return key in self._favorite_bundles
+ def is_activity_protected(self, bundle_id):
+ return bundle_id in self._protected_activities
+
def set_bundle_position(self, bundle_id, version, x, y):
key = self._get_favorite_key(bundle_id, version)
if key not in self._favorite_bundles:
- raise ValueError('Bundle %s %s not favorite' % (bundle_id, version))
+ raise ValueError('Bundle %s %s not favorite' %
+ (bundle_id, version))
if self._favorite_bundles[key] is None:
self._favorite_bundles[key] = {}
@@ -346,19 +360,22 @@ class BundleRegistry(gobject.GObject):
for installed_bundle in self._bundles:
if bundle.get_bundle_id() == installed_bundle.get_bundle_id() and \
- bundle.get_activity_version() == \
- installed_bundle.get_activity_version():
+ NormalizedVersion(bundle.get_activity_version()) <= \
+ NormalizedVersion(installed_bundle.get_activity_version()):
return True
return False
- def install(self, bundle, uid=None):
+ def install(self, bundle, uid=None, force_downgrade=False):
activities_path = env.get_user_activities_path()
for installed_bundle in self._bundles:
if bundle.get_bundle_id() == installed_bundle.get_bundle_id() and \
- bundle.get_activity_version() <= \
- installed_bundle.get_activity_version():
- raise AlreadyInstalledException
+ NormalizedVersion(bundle.get_activity_version()) <= \
+ NormalizedVersion(installed_bundle.get_activity_version()):
+ if not force_downgrade:
+ raise AlreadyInstalledException
+ else:
+ self.uninstall(installed_bundle, force=True)
elif bundle.get_bundle_id() == installed_bundle.get_bundle_id():
self.uninstall(installed_bundle, force=True)
@@ -376,7 +393,7 @@ class BundleRegistry(gobject.GObject):
elif not self.add_bundle(install_path):
raise RegistrationException
- def uninstall(self, bundle, force=False):
+ def uninstall(self, bundle, force=False, delete_profile=False):
# TODO treat ContentBundle in special way
# needs rethinking while fixing ContentBundle support
if isinstance(bundle, ContentBundle) or \
@@ -399,7 +416,7 @@ class BundleRegistry(gobject.GObject):
install_path = act.get_path()
- bundle.uninstall(install_path, force)
+ bundle.uninstall(install_path, force, delete_profile)
if not self.remove_bundle(install_path):
raise RegistrationException
@@ -415,20 +432,17 @@ class BundleRegistry(gobject.GObject):
try:
self.uninstall(bundle, force=True)
except Exception:
- logging.error('Uninstall failed, still trying to install ' \
- 'newer bundle:\n' + \
- traceback.format_exc())
+ logging.exception('Uninstall failed, still trying to install'
+ ' newer bundle:')
else:
- logging.warning('Unable to uninstall system activity, ' \
+ logging.warning('Unable to uninstall system activity, '
'installing upgraded version in user activities')
self.install(bundle)
-_instance = None
def get_registry():
global _instance
if not _instance:
_instance = BundleRegistry()
return _instance
-
diff --git a/src/jarabe/model/filetransfer.py b/src/jarabe/model/filetransfer.py
index 46b246d..710c3a4 100644
--- a/src/jarabe/model/filetransfer.py
+++ b/src/jarabe/model/filetransfer.py
@@ -31,6 +31,8 @@ from sugar.presence import presenceservice
from sugar import dispatch
from jarabe.util.telepathy import connection_watcher
+from jarabe.model import neighborhood
+
FT_STATE_NONE = 0
FT_STATE_PENDING = 1
@@ -51,14 +53,18 @@ FT_REASON_REMOTE_ERROR = 6
CHANNEL_TYPE_FILE_TRANSFER = \
'org.freedesktop.Telepathy.Channel.Type.FileTransfer'
+new_file_transfer = dispatch.Signal()
+
+
# TODO Move to use splice_async() in Sugar 0.88
class StreamSplicer(gobject.GObject):
- _CHUNK_SIZE = 10240 # 10K
+ _CHUNK_SIZE = 10240 # 10K
__gsignals__ = {
'finished': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
([])),
}
+
def __init__(self, input_stream, output_stream):
gobject.GObject.__init__(self)
@@ -104,6 +110,7 @@ class StreamSplicer(gobject.GObject):
gobject.PRIORITY_LOW,
user_data=data)
+
class BaseFileTransfer(gobject.GObject):
def __init__(self, connection):
@@ -140,11 +147,7 @@ class BaseFileTransfer(gobject.GObject):
self.mime_type = props['ContentType']
handle = channel_properties.Get(CHANNEL, 'TargetHandle')
- presence_service = presenceservice.get_instance()
- self.buddy = presence_service.get_buddy_by_telepathy_handle(
- self._connection.service_name,
- self._connection.object_path,
- handle)
+ self.buddy = neighborhood.get_model().get_buddy_by_handle(handle)
def __transferred_bytes_changed_cb(self, transferred_bytes):
logging.debug('__transferred_bytes_changed_cb %r', transferred_bytes)
@@ -179,6 +182,7 @@ class BaseFileTransfer(gobject.GObject):
def cancel(self):
self.channel[CHANNEL].Close()
+
class IncomingFileTransfer(BaseFileTransfer):
def __init__(self, connection, object_path, props):
BaseFileTransfer.__init__(self, connection)
@@ -223,6 +227,7 @@ class IncomingFileTransfer(BaseFileTransfer):
self._splicer = StreamSplicer(input_stream, output_stream)
self._splicer.start()
+
class OutgoingFileTransfer(BaseFileTransfer):
def __init__(self, buddy, file_name, title, description, mime_type):
@@ -240,20 +245,18 @@ class OutgoingFileTransfer(BaseFileTransfer):
self._splicer = None
self._output_stream = None
- self.buddy = buddy.get_buddy()
+ self.buddy = buddy
self.title = title
self.file_size = os.stat(file_name).st_size
self.description = description
self.mime_type = mime_type
def __connection_ready_cb(self, connection):
- handle = self._get_buddy_handle()
-
requests = connection[CONNECTION_INTERFACE_REQUESTS]
object_path, properties_ = requests.CreateChannel({
CHANNEL + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER,
CHANNEL + '.TargetHandleType': CONNECTION_HANDLE_TYPE_CONTACT,
- CHANNEL + '.TargetHandle': handle,
+ CHANNEL + '.TargetHandle': self.buddy.handle,
CHANNEL_TYPE_FILE_TRANSFER + '.ContentType': self.mime_type,
CHANNEL_TYPE_FILE_TRANSFER + '.Filename': self.title,
CHANNEL_TYPE_FILE_TRANSFER + '.Size': self.file_size,
@@ -267,21 +270,6 @@ class OutgoingFileTransfer(BaseFileTransfer):
SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, '',
byte_arrays=True)
- def _get_buddy_handle(self):
- object_path = self.buddy.object_path()
-
- bus = dbus.SessionBus()
- remote_object = bus.get_object('org.laptop.Sugar.Presence', object_path)
- ps_buddy = dbus.Interface(remote_object,
- 'org.laptop.Sugar.Presence.Buddy')
-
- handles = ps_buddy.GetTelepathyHandles()
- logging.debug('_get_buddy_handle %r', handles)
-
- bus_name, object_path, handle = handles[0]
-
- return handle
-
def __notify_state_cb(self, file_transfer, pspec):
logging.debug('__notify_state_cb %r', self.props.state)
if self.props.state == FT_STATE_OPEN:
@@ -303,6 +291,7 @@ class OutgoingFileTransfer(BaseFileTransfer):
def cancel(self):
self.channel[CHANNEL].Close()
+
def _new_channels_cb(connection, channels):
for object_path, props in channels:
if props[CHANNEL + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER and \
@@ -314,35 +303,39 @@ def _new_channels_cb(connection, channels):
object_path, props)
new_file_transfer.send(None, file_transfer=incoming_file_transfer)
+
def _monitor_connection(connection):
logging.debug('connection added %r', 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):
outgoing_file_transfer = OutgoingFileTransfer(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)
@@ -359,18 +352,17 @@ def file_transfer_available():
return False
-new_file_transfer = dispatch.Signal()
if __name__ == '__main__':
import tempfile
- input_stream = gio.File('/home/tomeu/isos/Soas2-200904031934.iso').read()
- output_stream = gio.File(tempfile.mkstemp()[1]).append_to()
+ test_file_name = '/home/tomeu/isos/Soas2-200904031934.iso'
+ test_input_stream = gio.File(test_file_name).read()
+ test_output_stream = gio.File(tempfile.mkstemp()[1]).append_to()
# TODO: Use splice_async when it gets implemented
- splicer = StreamSplicer(input_stream, output_stream)
+ splicer = StreamSplicer(test_input_stream, test_output_stream)
splicer.start()
loop = gobject.MainLoop()
loop.run()
-
diff --git a/src/jarabe/model/friends.py b/src/jarabe/model/friends.py
index b7bf7f1..192f683 100644
--- a/src/jarabe/model/friends.py
+++ b/src/jarabe/model/friends.py
@@ -21,15 +21,83 @@ from ConfigParser import ConfigParser
import gobject
import dbus
-from jarabe.model.buddy import BuddyModel
from sugar import env
+from sugar.graphics.xocolor import XoColor
+
+from jarabe.model.buddy import BuddyModel
+from jarabe.model import neighborhood
+
+
+_model = None
+
+
+class FriendBuddyModel(BuddyModel):
+ __gtype_name__ = 'SugarFriendBuddyModel'
+
+ _NOT_PRESENT_COLOR = '#D5D5D5,#FFFFFF'
+
+ def __init__(self, nick, key):
+ self._online_buddy = None
+
+ BuddyModel.__init__(self, nick=nick, key=key)
+
+ neighborhood_model = neighborhood.get_model()
+ neighborhood_model.connect('buddy-added', self.__buddy_added_cb)
+ neighborhood_model.connect('buddy-removed', self.__buddy_removed_cb)
+
+ buddy = neighborhood_model.get_buddy_by_key(key)
+ if buddy is not None:
+ self._set_online_buddy(buddy)
+
+ def __buddy_added_cb(self, model_, buddy):
+ if buddy.key != self.key:
+ return
+ self._set_online_buddy(buddy)
+
+ def _set_online_buddy(self, buddy):
+ self._online_buddy = buddy
+ self._online_buddy.connect('notify::color', self.__notify_color_cb)
+ self.notify('color')
+ self.notify('present')
+
+ def __buddy_removed_cb(self, model_, buddy):
+ if buddy.key != self.key:
+ return
+ self._online_buddy = None
+ self.notify('color')
+ self.notify('present')
+
+ def __notify_color_cb(self, buddy, pspec):
+ self.notify('color')
+
+ def is_present(self):
+ return self._online_buddy is not None
+
+ present = gobject.property(type=bool, default=False, getter=is_present)
+
+ def get_color(self):
+ if self._online_buddy is not None:
+ return self._online_buddy.color
+ else:
+ return XoColor(FriendBuddyModel._NOT_PRESENT_COLOR)
+
+ color = gobject.property(type=object, getter=get_color)
+
+ def get_handle(self):
+ if self._online_buddy is not None:
+ return self._online_buddy.handle
+ else:
+ return None
+
+ handle = gobject.property(type=object, getter=get_handle)
+
class Friends(gobject.GObject):
__gsignals__ = {
- 'friend-added': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([object])),
- 'friend-removed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([str]))
+ 'friend-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ 'friend-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([str])),
}
def __init__(self):
@@ -41,7 +109,7 @@ class Friends(gobject.GObject):
self.load()
def has_buddy(self, buddy):
- return self._friends.has_key(buddy.get_key())
+ return buddy.get_key() in self._friends
def add_friend(self, buddy_info):
self._friends[buddy_info.get_key()] = buddy_info
@@ -49,6 +117,7 @@ class Friends(gobject.GObject):
def make_friend(self, buddy):
if not self.has_buddy(buddy):
+ buddy = FriendBuddyModel(key=buddy.key, nick=buddy.nick)
self.add_friend(buddy)
self.save()
@@ -70,7 +139,7 @@ class Friends(gobject.GObject):
# HACK: don't screw up on old friends files
if len(key) < 20:
continue
- buddy = BuddyModel(key=key, nick=cp.get(key, 'nick'))
+ buddy = FriendBuddyModel(key=key, nick=cp.get(key, 'nick'))
self.add_friend(buddy)
except Exception:
logging.exception('Error parsing friends file')
@@ -82,7 +151,6 @@ class Friends(gobject.GObject):
section = friend.get_key()
cp.add_section(section)
cp.set(section, 'nick', friend.get_nick())
- cp.set(section, 'color', friend.get_color().to_string())
fileobject = open(self._path, 'w')
cp.write(fileobject)
@@ -113,7 +181,6 @@ class Friends(gobject.GObject):
reply_handler=friends_synced,
error_handler=friends_synced_error)
-_model = None
def get_model():
global _model
diff --git a/src/jarabe/model/invites.py b/src/jarabe/model/invites.py
index c918308..d2a2e0c 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. <http://www.collabora.co.uk/>
#
# 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,110 +15,227 @@
# 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.
+import logging
+from functools import partial
- bundle_id: string, e.g. 'org.laptop.Chat'
- """
- self._bundle_id = bundle_id
+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
- def get_bundle_id(self):
- return self._bundle_id
+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
-class ActivityInvite(BaseInvite):
- """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
+CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \
+ 'org.laptop.Telepathy.ActivityProperties'
+_instance = None
-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.
+class ActivityInvite(object):
+ """Invitation to a shared activity."""
+ 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
+ if activity_properties is not None:
+ self._activity_properties = activity_properties
+ else:
+ self._activity_properties = {}
- def get_private_channel(self):
- """Telepathy channel info from private invitation"""
- return self._private_channel
+ 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__ = {
- 'invite-added': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([object])),
- 'invite-removed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([object]))
+ 'invite-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ 'invite-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
}
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
+ 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 = 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
- 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()]
+ del self._dispatch_operations[invite.dispatch_operation_path]
self.emit('invite-removed', invite)
- def remove_private_invite(self, invite):
- del self._dict[invite.get_private_channel()]
- 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 remove_private_channel(self, private_channel):
- invite = self._dict.get(private_channel)
- if invite is not None:
- self.remove_private_invite(invite)
+ def __iter__(self):
+ return self._dispatch_operations.values().__iter__()
- def _owner_joined_cb(self, owner, activity):
- self.remove_activity(activity.props.id)
- 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/mimeregistry.py b/src/jarabe/model/mimeregistry.py
index 537f6f3..7fb5bcf 100644
--- a/src/jarabe/model/mimeregistry.py
+++ b/src/jarabe/model/mimeregistry.py
@@ -21,6 +21,7 @@ import gconf
_DEFAULTS_KEY = '/desktop/sugar/journal/defaults'
_GCONF_INVALID_CHARS = re.compile('[^a-zA-Z0-9-_/.]')
+
_instance = None
diff --git a/src/jarabe/model/neighborhood.py b/src/jarabe/model/neighborhood.py
index 53e5581..ca4c5bf 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. <http://www.collabora.co.uk/>
#
# 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,257 +14,992 @@
# 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
+from hashlib import sha1
+
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, \
+ CHANNEL_TYPE_FILE_TRANSFER, \
+ CLIENT, \
+ CONNECTION, \
+ CONNECTION_INTERFACE_ALIASING, \
+ CONNECTION_INTERFACE_CONTACTS, \
+ CONNECTION_INTERFACE_CONTACT_CAPABILITIES, \
+ CONNECTION_INTERFACE_REQUESTS, \
+ CONNECTION_INTERFACE_SIMPLE_PRESENCE
+from telepathy.constants import HANDLE_TYPE_CONTACT, \
+ 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
-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'
+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'
+
+CONNECTION_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
+CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \
+ 'org.laptop.Telepathy.ActivityProperties'
-NB_RANDOM_BUDDIES = 20
-NB_RANDOM_ACTIVITIES = 40
+_QUERY_DBUS_TIMEOUT = 200
+"""
+Time in seconds to wait when querying contact properties. Some jabber servers
+will be very slow in returning these queries, so just be patient.
+"""
-class ActivityModel:
- def __init__(self, act, bundle):
- self.activity = act
- self.bundle = bundle
+_model = None
+
+
+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 get_id(self):
- return self.activity.props.id
+ def __init__(self, activity_id, room_handle):
+ gobject.GObject.__init__(self)
- def get_icon_name(self):
- return self.bundle.get_icon()
+ 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_color(self):
- return XoColor(self.activity.props.color)
+ return self._color
+
+ def set_color(self, color):
+ self._color = 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 == '/':
+ self._check_registration_error()
+ return
+
+ self._prepare_connection(connection_path)
+
+ def _check_registration_error(self):
+ """
+ See if a previous connection attempt failed and we need to unset
+ the register flag.
+ """
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, self.object_path)
+ obj.Get(ACCOUNT, 'ConnectionError',
+ reply_handler=self.__got_connection_error_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Account.GetConnectionError'))
+
+ def __got_connection_error_cb(self, error):
+ logging.debug('_Account.__got_connection_error_cb %r', error)
+ if error == 'org.freedesktop.Telepathy.Error.RegistrationExists':
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, self.object_path)
+ obj.UpdateParameters({'register': False}, [],
+ dbus_interface=ACCOUNT)
+
+ 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._check_registration_error()
+ 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():
+ if contact_id is not None:
+ 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
+
+ if CONNECTION_INTERFACE_CONTACT_CAPABILITIES in self._connection:
+ interface = CONNECTION_INTERFACE_CONTACT_CAPABILITIES
+ connection = self._connection[interface]
+ client_name = CLIENT + '.Sugar.FileTransfer'
+ file_transfer_channel_class = {
+ CHANNEL + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER,
+ CHANNEL + '.TargetHandleType': HANDLE_TYPE_CONTACT}
+ capabilities = []
+ connection.UpdateCapabilities(
+ [(client_name, [file_transfer_channel_class], capabilities)],
+ reply_handler=self.__update_capabilities_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Connection.UpdateCapabilities'))
+
+ connection = self._connection[CONNECTION_INTERFACE_ALIASING]
+ connection.connect_to_signal('AliasesChanged',
+ self.__aliases_changed_cb)
+
+ 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 __update_capabilities_cb(self):
+ pass
+
+ 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)
+ properties = {CONNECTION_INTERFACE_ALIASING + '/alias': alias}
+ self.emit('buddy-updated', self._buddy_handles[handle],
+ properties)
+
+ 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:
+ contact_id = self._buddy_handles[handle]
+ del self._buddy_handles[handle]
+ self.emit('buddy-removed', contact_id)
+
+ def __buddy_info_updated_cb(self, handle, properties):
+ logging.debug('_Account.__buddy_info_updated_cb %r', handle)
+ self.emit('buddy-updated', self._buddy_handles[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):
+ 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', handle)
+ 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,
+ timeout=_QUERY_DBUS_TIMEOUT)
+
+ connection.GetActivities(
+ handle,
+ reply_handler=partial(self.__got_activities_cb,
+ handle),
+ error_handler=partial(self.__error_handler_cb,
+ 'BuddyInfo.GetActivities'),
+ timeout=_QUERY_DBUS_TIMEOUT)
+
+ connection.GetCurrentActivity(
+ handle,
+ reply_handler=partial(self.__get_current_activity_cb,
+ handle),
+ error_handler=partial(self.__error_handler_cb,
+ 'BuddyInfo.GetCurrentActivity'),
+ timeout=_QUERY_DBUS_TIMEOUT)
+
+ self.emit('buddy-added', contact_id, nick, handle)
+
+ 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=dbus.PROPERTIES_IFACE)
+
+ def __set_enabled_cb(self):
+ logging.debug('_Account.__set_enabled_cb success')
- def get_bundle_id(self):
- return self.bundle.get_bundle_id()
class Neighborhood(gobject.GObject):
__gsignals__ = {
- 'activity-added': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])),
- 'activity-removed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])),
- '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])),
- 'buddy-removed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT]))
+ 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ 'activity-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):
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
+
+ client = gconf.client_get_default()
+ client.add_dir('/desktop/sugar/collaboration',
+ gconf.CLIENT_PRELOAD_NONE)
+ client.notify_add('/desktop/sugar/collaboration/jabber_server',
+ self.__jabber_server_changed_cb)
+ client.add_dir('/desktop/sugar/user/nick', gconf.CLIENT_PRELOAD_NONE)
+ client.notify_add('/desktop/sugar/user/nick', self.__nick_changed_cb)
+
+ 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')
+
+ params = {
+ 'nickname': nick,
+ 'first-name': '',
+ 'last-name': '',
+ 'jid': self._get_jabber_account_id(),
+ 'published-name': nick,
+ }
+
+ properties = {
+ ACCOUNT + '.Enabled': True,
+ ACCOUNT + '.Nickname': nick,
+ 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': self._get_jabber_account_id(),
+ 'password': key_hash,
+ 'server': server,
+ 'resource': 'sugar',
+ 'require-encryption': True,
+ 'ignore-ssl-errors': True,
+ 'register': True,
+ 'old-ssl': True,
+ 'port': dbus.UInt32(5223),
+ }
+
+ properties = {
+ ACCOUNT + '.Enabled': True,
+ ACCOUNT + '.Nickname': nick,
+ 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 _get_jabber_account_id(self):
+ public_key_hash = sha1(get_profile().pubkey).hexdigest()
+ client = gconf.client_get_default()
+ server = client.get_string('/desktop/sugar/collaboration'
+ '/jabber_server')
+ return '%s@%s' % (public_key_hash, server)
+
+ def __jabber_server_changed_cb(self, client, timestamp, entry, *extra):
+ logging.debug('__jabber_server_changed_cb')
+
+ bus = dbus.Bus()
+ account = bus.get_object(ACCOUNT_MANAGER_SERVICE,
+ self._server_account.object_path)
+
+ server = client.get_string(
+ '/desktop/sugar/collaboration/jabber_server')
+ account_id = self._get_jabber_account_id()
+ needs_reconnect = account.UpdateParameters({'server': server,
+ 'account': account_id,
+ 'register': True},
+ dbus.Array([], 's'),
+ dbus_interface=ACCOUNT)
+ if needs_reconnect:
+ account.Reconnect()
+
+ self._update_jid()
+
+ def __nick_changed_cb(self, client, timestamp, entry, *extra):
+ logging.debug('__nick_changed_cb')
+
+ nick = client.get_string('/desktop/sugar/user/nick')
+ for account in self._server_account, self._link_local_account:
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, account.object_path)
+ obj.Set(ACCOUNT, 'Nickname', nick, dbus_interface=PROPERTIES_IFACE)
+
+ self._update_jid()
+
+ def _update_jid(self):
+ bus = dbus.Bus()
+ account = bus.get_object(ACCOUNT_MANAGER_SERVICE,
+ self._link_local_account.object_path)
+
+ account_id = self._get_jabber_account_id()
+ needs_reconnect = account.UpdateParameters({'jid': account_id},
+ dbus.Array([], 's'),
+ dbus_interface=ACCOUNT)
+ if needs_reconnect:
+ account.Reconnect()
+
+ def __buddy_added_cb(self, account, contact_id, nick, handle):
+ 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)
+ buddy = BuddyModel(
+ nick=nick,
+ account=account.object_path,
+ contact_id=contact_id,
+ handle=handle)
+ self._buddies[contact_id] = buddy
+
+ def __buddy_updated_cb(self, account, contact_id, properties):
+ logging.debug('__buddy_updated_cb %r', contact_id)
+ if contact_id is None:
+ # Don't know the contact-id yet, will get the full state later
+ return
- def get_activities(self):
- return self._activities.values()
+ if contact_id not in self._buddies:
+ logging.debug('__buddy_updated_cb Unknown buddy with contact_id'
+ ' %r', contact_id)
+ return
- def get_buddies(self):
- return self._buddies.values()
+ buddy = self._buddies[contact_id]
- def _buddy_activity_changed_cb(self, model, cur_activity):
- if not self._buddies.has_key(model.get_buddy().object_path()):
- 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)
+ is_new = buddy.props.key is None and 'key' in properties
+
+ if 'color' in properties:
+ buddy.props.color = XoColor(properties['color'])
+
+ if 'key' in properties:
+ buddy.props.key = properties['key']
+
+ if 'nick' in properties:
+ buddy.props.nick = properties['nick']
- def _buddy_appeared_cb(self, pservice, buddy):
- if self._buddies.has_key(buddy.object_path()):
+ if is_new:
+ self.emit('buddy-added', buddy)
+
+ 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
- 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)
+ buddy = self._buddies[contact_id]
+ del self._buddies[contact_id]
- cur_activity = buddy.props.current_activity
- if cur_activity:
- self._buddy_activity_changed_cb(model, cur_activity)
+ if buddy.props.key is not None:
+ 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
+
+ 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]
+
+ if activity.props.bundle is not None:
+ 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 self.has_activity(presence_activity.props.id):
+
+ 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_buddy_by_key(self, key):
+ for buddy in self._buddies.values():
+ if buddy.key == key:
+ return buddy
+ return None
+
+ def get_buddy_by_handle(self, contact_handle):
+ for buddy in self._buddies.values():
+ if not buddy.is_owner() and buddy.handle == contact_handle:
+ return buddy
+ return None
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
def get_model():
global _model
diff --git a/src/jarabe/model/network.py b/src/jarabe/model/network.py
index 3a949da..f265ae4 100644
--- a/src/jarabe/model/network.py
+++ b/src/jarabe/model/network.py
@@ -1,6 +1,6 @@
# Copyright (C) 2008 Red Hat, Inc.
# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
-# Copyright (C) 2009 One Laptop per Child
+# Copyright (C) 2009-2010 One Laptop per Child
# Copyright (C) 2009 Paraguay Educa, Martin Abente
# Copyright (C) 2010 Plan Ceibal, Daniel Castelo
#
@@ -18,19 +18,23 @@
# 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 os
import time
import dbus
+import dbus.service
import gobject
import ConfigParser
import gconf
+import ctypes
from sugar import dispatch
from sugar import env
from sugar.util import unique_id
+
DEVICE_TYPE_802_3_ETHERNET = 1
DEVICE_TYPE_802_11_WIRELESS = 2
DEVICE_TYPE_GSM_MODEM = 3
@@ -54,6 +58,49 @@ NM_ACTIVE_CONNECTION_STATE_UNKNOWN = 0
NM_ACTIVE_CONNECTION_STATE_ACTIVATING = 1
NM_ACTIVE_CONNECTION_STATE_ACTIVATED = 2
+
+NM_DEVICE_STATE_REASON_UNKNOWN = 0
+NM_DEVICE_STATE_REASON_NONE = 1
+NM_DEVICE_STATE_REASON_NOW_MANAGED = 2
+NM_DEVICE_STATE_REASON_NOW_UNMANAGED = 3
+NM_DEVICE_STATE_REASON_CONFIG_FAILED = 4
+NM_DEVICE_STATE_REASON_CONFIG_UNAVAILABLE = 5
+NM_DEVICE_STATE_REASON_CONFIG_EXPIRED = 6
+NM_DEVICE_STATE_REASON_NO_SECRETS = 7
+NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT = 8
+NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED = 9
+NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED = 10
+NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT = 11
+NM_DEVICE_STATE_REASON_PPP_START_FAILED = 12
+NM_DEVICE_STATE_REASON_PPP_DISCONNECT = 13
+NM_DEVICE_STATE_REASON_PPP_FAILED = 14
+NM_DEVICE_STATE_REASON_DHCP_START_FAILED = 15
+NM_DEVICE_STATE_REASON_DHCP_ERROR = 16
+NM_DEVICE_STATE_REASON_DHCP_FAILED = 17
+NM_DEVICE_STATE_REASON_SHARED_START_FAILED = 18
+NM_DEVICE_STATE_REASON_SHARED_FAILED = 19
+NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED = 20
+NM_DEVICE_STATE_REASON_AUTOIP_ERROR = 21
+NM_DEVICE_STATE_REASON_AUTOIP_FAILED = 22
+NM_DEVICE_STATE_REASON_MODEM_BUSY = 23
+NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE = 24
+NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER = 25
+NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT = 26
+NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED = 27
+NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED = 28
+NM_DEVICE_STATE_REASON_GSM_APN_FAILED = 29
+NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING = 30
+NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED = 31
+NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT = 32
+NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED = 33
+NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED = 34
+NM_DEVICE_STATE_REASON_FIRMWARE_MISSING = 35
+NM_DEVICE_STATE_REASON_REMOVED = 36
+NM_DEVICE_STATE_REASON_SLEEPING = 37
+NM_DEVICE_STATE_REASON_CONNECTION_REMOVED = 38
+NM_DEVICE_STATE_REASON_USER_REQUESTED = 39
+NM_DEVICE_STATE_REASON_CARRIER = 40
+
NM_802_11_AP_FLAGS_NONE = 0x00000000
NM_802_11_AP_FLAGS_PRIVACY = 0x00000001
@@ -83,11 +130,16 @@ NM_802_11_DEVICE_CAP_RSN = 0x00000020
SETTINGS_SERVICE = 'org.freedesktop.NetworkManagerUserSettings'
+NM_SERVICE = 'org.freedesktop.NetworkManager'
+NM_IFACE = 'org.freedesktop.NetworkManager'
+NM_PATH = '/org/freedesktop/NetworkManager'
+NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device'
NM_SETTINGS_PATH = '/org/freedesktop/NetworkManagerSettings'
NM_SETTINGS_IFACE = 'org.freedesktop.NetworkManagerSettings'
NM_CONNECTION_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection'
NM_SECRETS_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection.Secrets'
NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint'
+NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active'
GSM_USERNAME_PATH = '/desktop/sugar/network/gsm/username'
GSM_PASSWORD_PATH = '/desktop/sugar/network/gsm/password'
@@ -99,6 +151,137 @@ GSM_PUK_PATH = '/desktop/sugar/network/gsm/puk'
_nm_settings = None
_conn_counter = 0
+_nm_device_state_reason_description = None
+
+
+def get_error_by_reason(reason):
+ global _nm_device_state_reason_description
+
+ if _nm_device_state_reason_description is None:
+ _nm_device_state_reason_description = {
+ NM_DEVICE_STATE_REASON_UNKNOWN:
+ _('The reason for the device state change is unknown.'),
+ NM_DEVICE_STATE_REASON_NONE:
+ _('The state change is normal.'),
+ NM_DEVICE_STATE_REASON_NOW_MANAGED:
+ _('The device is now managed.'),
+ NM_DEVICE_STATE_REASON_NOW_UNMANAGED:
+ _('The device is no longer managed.'),
+ NM_DEVICE_STATE_REASON_CONFIG_FAILED:
+ _('The device could not be readied for configuration.'),
+ NM_DEVICE_STATE_REASON_CONFIG_UNAVAILABLE:
+ _('IP configuration could not be reserved '
+ '(no available address, timeout, etc).'),
+ NM_DEVICE_STATE_REASON_CONFIG_EXPIRED:
+ _('The IP configuration is no longer valid.'),
+ NM_DEVICE_STATE_REASON_NO_SECRETS:
+ _('Secrets were required, but not provided.'),
+ NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT:
+ _('The 802.1X supplicant disconnected from '
+ 'the access point or authentication server.'),
+ NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED:
+ _('Configuration of the 802.1X supplicant failed.'),
+ NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED:
+ _('The 802.1X supplicant quit or failed unexpectedly.'),
+ NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT:
+ _('The 802.1X supplicant took too long to authenticate.'),
+ NM_DEVICE_STATE_REASON_PPP_START_FAILED:
+ _('The PPP service failed to start within the allowed time.'),
+ NM_DEVICE_STATE_REASON_PPP_DISCONNECT:
+ _('The PPP service disconnected unexpectedly.'),
+ NM_DEVICE_STATE_REASON_PPP_FAILED:
+ _('The PPP service quit or failed unexpectedly.'),
+ NM_DEVICE_STATE_REASON_DHCP_START_FAILED:
+ _('The DHCP service failed to start within the allowed time.'),
+ NM_DEVICE_STATE_REASON_DHCP_ERROR:
+ _('The DHCP service reported an unexpected error.'),
+ NM_DEVICE_STATE_REASON_DHCP_FAILED:
+ _('The DHCP service quit or failed unexpectedly.'),
+ NM_DEVICE_STATE_REASON_SHARED_START_FAILED:
+ _('The shared connection service failed to start.'),
+ NM_DEVICE_STATE_REASON_SHARED_FAILED:
+ _('The shared connection service quit or failed'
+ ' unexpectedly.'),
+ NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED:
+ _('The AutoIP service failed to start.'),
+ NM_DEVICE_STATE_REASON_AUTOIP_ERROR:
+ _('The AutoIP service reported an unexpected error.'),
+ NM_DEVICE_STATE_REASON_AUTOIP_FAILED:
+ _('The AutoIP service quit or failed unexpectedly.'),
+ NM_DEVICE_STATE_REASON_MODEM_BUSY:
+ _('Dialing failed because the line was busy.'),
+ NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE:
+ _('Dialing failed because there was no dial tone.'),
+ NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER:
+ _('Dialing failed because there was no carrier.'),
+ NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT:
+ _('Dialing timed out.'),
+ NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED:
+ _('Dialing failed.'),
+ NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED:
+ _('Modem initialization failed.'),
+ NM_DEVICE_STATE_REASON_GSM_APN_FAILED:
+ _('Failed to select the specified GSM APN'),
+ NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING:
+ _('Not searching for networks.'),
+ NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED:
+ _('Network registration was denied.'),
+ NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT:
+ _('Network registration timed out.'),
+ NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED:
+ _('Failed to register with the requested GSM network.'),
+ NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED:
+ _('PIN check failed.'),
+ NM_DEVICE_STATE_REASON_FIRMWARE_MISSING:
+ _('Necessary firmware for the device may be missing.'),
+ NM_DEVICE_STATE_REASON_REMOVED:
+ _('The device was removed.'),
+ NM_DEVICE_STATE_REASON_SLEEPING:
+ _('NetworkManager went to sleep.'),
+ NM_DEVICE_STATE_REASON_CONNECTION_REMOVED:
+ _("The device's active connection was removed "
+ "or disappeared."),
+ NM_DEVICE_STATE_REASON_USER_REQUESTED:
+ _('A user or client requested the disconnection.'),
+ NM_DEVICE_STATE_REASON_CARRIER:
+ _("The device's carrier/link changed.")}
+
+ return _nm_device_state_reason_description[reason]
+
+
+def frequency_to_channel(frequency):
+ """Returns the channel matching a given radio channel frequency. If a
+ frequency is not in the dictionary channel 1 will be returned.
+
+ Keyword arguments:
+ frequency -- The radio channel frequency in MHz.
+
+ Return: Channel
+
+ """
+ ftoc = {2412: 1, 2417: 2, 2422: 3, 2427: 4,
+ 2432: 5, 2437: 6, 2442: 7, 2447: 8,
+ 2452: 9, 2457: 10, 2462: 11, 2467: 12,
+ 2472: 13}
+ if frequency not in ftoc:
+ logging.warning('The frequency %s can not be mapped to a channel, '
+ 'defaulting to channel 1.', frequency)
+ return 1
+ return ftoc[frequency]
+
+
+def is_sugar_adhoc_network(ssid):
+ """Checks whether an access point is a sugar Ad-hoc network.
+
+ Keyword arguments:
+ ssid -- Ssid of the access point.
+
+ Return: Boolean
+
+ """
+ return ssid.startswith('Ad-hoc Network')
+
+
class WirelessSecurity(object):
def __init__(self):
self.key_mgmt = None
@@ -118,14 +301,16 @@ class WirelessSecurity(object):
wireless_security['group'] = self.group
return wireless_security
+
class Wireless(object):
- nm_name = "802-11-wireless"
+ nm_name = '802-11-wireless'
def __init__(self):
self.ssid = None
self.security = None
self.mode = None
self.band = None
+ self.channel = None
def get_dict(self):
wireless = {'ssid': self.ssid}
@@ -135,11 +320,13 @@ class Wireless(object):
wireless['mode'] = self.mode
if self.band:
wireless['band'] = self.band
+ if self.channel:
+ wireless['channel'] = self.channel
return wireless
class OlpcMesh(object):
- nm_name = "802-11-olpc-mesh"
+ nm_name = '802-11-olpc-mesh'
def __init__(self, channel, anycast_addr):
self.channel = channel
@@ -147,12 +334,12 @@ class OlpcMesh(object):
def get_dict(self):
ret = {
- "ssid": dbus.ByteArray("olpc-mesh"),
- "channel": self.channel,
+ 'ssid': dbus.ByteArray('olpc-mesh'),
+ 'channel': self.channel,
}
if self.anycast_addr:
- ret["dhcp-anycast-address"] = dbus.ByteArray(self.anycast_addr)
+ ret['dhcp-anycast-address'] = dbus.ByteArray(self.anycast_addr)
return ret
@@ -173,6 +360,7 @@ class Connection(object):
connection['timestamp'] = self.timestamp
return connection
+
class IP4Config(object):
def __init__(self):
self.method = None
@@ -183,6 +371,7 @@ class IP4Config(object):
ip4_config['method'] = self.method
return ip4_config
+
class Serial(object):
def __init__(self):
self.baud = None
@@ -195,6 +384,7 @@ class Serial(object):
return serial
+
class Ppp(object):
def __init__(self):
pass
@@ -203,6 +393,7 @@ class Ppp(object):
ppp = {}
return ppp
+
class Gsm(object):
def __init__(self):
self.apn = None
@@ -221,6 +412,7 @@ class Gsm(object):
return gsm
+
class Settings(object):
def __init__(self, wireless_cfg=None):
self.connection = Connection()
@@ -243,6 +435,7 @@ class Settings(object):
settings['ipv4'] = self.ip4_config.get_dict()
return settings
+
class Secrets(object):
def __init__(self, settings):
self.settings = settings
@@ -268,6 +461,7 @@ class Secrets(object):
return settings
+
class SettingsGsm(object):
def __init__(self):
self.connection = Connection()
@@ -287,22 +481,24 @@ class SettingsGsm(object):
return settings
+
class SecretsGsm(object):
def __init__(self):
self.password = None
self.pin = None
self.puk = None
-
+
def get_dict(self):
secrets = {}
if self.password is not None:
secrets['password'] = self.password
if self.pin is not None:
secrets['pin'] = self.pin
- if self.puk is not None:
+ if self.puk is not None:
secrets['puk'] = self.puk
return {'gsm': secrets}
+
class NMSettings(dbus.service.Object):
def __init__(self):
bus = dbus.SystemBus()
@@ -330,10 +526,19 @@ class NMSettings(dbus.service.Object):
self.secrets_request.send(self, connection=sender,
response=kwargs['response'])
+ def clear_wifi_connections(self):
+ for uuid in self.connections.keys():
+ conn = self.connections[uuid]
+ if conn._settings.connection.type == \
+ NM_CONNECTION_TYPE_802_11_WIRELESS:
+ conn.Removed()
+ self.connections.pop(uuid)
+
+
class SecretsResponse(object):
- ''' Intermediate object to report the secrets from the dialog
+ """Intermediate object to report the secrets from the dialog
back to the connection object and which will inform NM
- '''
+ """
def __init__(self, connection, reply_cb, error_cb):
self._connection = connection
self._reply_cb = reply_cb
@@ -346,6 +551,7 @@ class SecretsResponse(object):
def set_error(self, error):
self._error_cb(error)
+
class NMSettingsConnection(dbus.service.Object):
def __init__(self, path, settings, secrets):
bus = dbus.SystemBus()
@@ -358,27 +564,57 @@ class NMSettingsConnection(dbus.service.Object):
self._settings = settings
self._secrets = secrets
+ @dbus.service.signal(dbus_interface=NM_CONNECTION_IFACE,
+ signature='')
+ def Removed(self):
+ pass
+
+ @dbus.service.signal(dbus_interface=NM_CONNECTION_IFACE,
+ signature='a{sa{sv}}')
+ def Updated(self, settings):
+ pass
+
def set_connected(self):
if self._settings.connection.type == NM_CONNECTION_TYPE_GSM:
self._settings.connection.timestamp = int(time.time())
- else:
- if not self._settings.connection.autoconnect:
- self._settings.connection.autoconnect = True
- self._settings.connection.timestamp = int(time.time())
- if self._settings.connection.type == NM_CONNECTION_TYPE_802_11_WIRELESS:
- self.save()
+ elif not self._settings.connection.autoconnect:
+ self._settings.connection.autoconnect = True
+ self._settings.connection.timestamp = int(time.time())
+ if (self._settings.connection.type ==
+ NM_CONNECTION_TYPE_802_11_WIRELESS):
+ self.Updated(self._settings.get_dict())
+ self.save()
+
+ try:
+ # try to flush resolver cache - SL#1940
+ # ctypes' syntactic sugar does not work
+ # so we must get the func ptr explicitly
+ libc = ctypes.CDLL('libc.so.6')
+ res_init = getattr(libc, '__res_init')
+ res_init(None)
+ except:
+ # pylint: disable=W0702
+ logging.exception('Error calling libc.__res_init')
+
+ def disable_autoconnect(self):
+ if self._settings.connection.type != NM_CONNECTION_TYPE_GSM and \
+ self._settings.connection.autoconnect:
+ self._settings.connection.autoconnect = False
+ self._settings.connection.timestamp = None
+ self.Updated(self._settings.get_dict())
+ self.save()
def set_secrets(self, secrets):
self._secrets = secrets
- if self._settings.connection.type == NM_CONNECTION_TYPE_802_11_WIRELESS:
+ if self._settings.connection.type == \
+ NM_CONNECTION_TYPE_802_11_WIRELESS:
self.save()
def get_settings(self):
return self._settings
def save(self):
- profile_path = env.get_profile_path()
- config_path = os.path.join(profile_path, 'nm', 'connections.cfg')
+ config_path = _get_wifi_connections_path()
config = ConfigParser.ConfigParser()
try:
@@ -443,22 +679,31 @@ class NMSettingsConnection(dbus.service.Object):
in_signature='sasb', out_signature='a{sa{sv}}')
def GetSecrets(self, setting_name, hints, request_new, reply, error):
logging.debug('Secrets requested for connection %s request_new=%s',
- self.path, request_new)
- if request_new or self._secrets is None:
- # request_new is for example the case when the pw on the AP changes
- response = SecretsResponse(self, reply, error)
- try:
- self.secrets_request.send(self, response=response)
- except Exception:
- logging.exception('Error requesting the secrets via dialog')
+ self.path, request_new)
+ if self._settings.connection.type is not NM_CONNECTION_TYPE_GSM:
+ if request_new or self._secrets is None:
+ # request_new is for example the case when the pw on the AP
+ # changes
+ response = SecretsResponse(self, reply, error)
+ try:
+ self.secrets_request.send(self, response=response)
+ except Exception:
+ logging.exception('Error requesting the secrets via'
+ ' dialog')
+ else:
+ reply(self._secrets.get_dict())
else:
- reply(self._secrets.get_dict())
+ if not request_new:
+ reply(self._secrets.get_dict())
+ else:
+ raise Exception('The stored GSM secret has already been'
+ ' supplied')
class AccessPoint(gobject.GObject):
__gsignals__ = {
'props-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT]))
+ ([gobject.TYPE_PYOBJECT])),
}
def __init__(self, device, model):
@@ -475,10 +720,10 @@ class AccessPoint(gobject.GObject):
self.wpa_flags = 0
self.rsn_flags = 0
self.mode = 0
+ self.channel = 0
def initialize(self):
- model_props = dbus.Interface(self.model,
- 'org.freedesktop.DBus.Properties')
+ model_props = dbus.Interface(self.model, dbus.PROPERTIES_IFACE)
model_props.GetAll(NM_ACCESSPOINT_IFACE, byte_arrays=True,
reply_handler=self._ap_properties_changed_cb,
error_handler=self._get_all_props_error_cb)
@@ -523,7 +768,7 @@ class AccessPoint(gobject.GObject):
else:
fl |= 1 << 6
- hashstr = str(fl) + "@" + self.name
+ hashstr = str(fl) + '@' + self.name
return hash(hashstr)
def _update_properties(self, properties):
@@ -544,6 +789,9 @@ class AccessPoint(gobject.GObject):
self.rsn_flags = properties['RsnFlags']
if 'Mode' in properties:
self.mode = properties['Mode']
+ if 'Frequency' in properties:
+ self.channel = frequency_to_channel(properties['Frequency'])
+
self._initialized = True
self.emit('props-changed', old_hash)
@@ -559,6 +807,7 @@ class AccessPoint(gobject.GObject):
path=self.model.object_path,
dbus_interface=NM_ACCESSPOINT_IFACE)
+
def get_settings():
global _nm_settings
if _nm_settings is None:
@@ -569,17 +818,20 @@ def get_settings():
load_connections()
return _nm_settings
+
def find_connection_by_ssid(ssid):
connections = get_settings().connections
for conn_index in connections:
connection = connections[conn_index]
- if connection._settings.connection.type == NM_CONNECTION_TYPE_802_11_WIRELESS:
- if connection._settings.wireless.ssid == ssid:
- return connection
+ if connection._settings.connection.type == \
+ NM_CONNECTION_TYPE_802_11_WIRELESS and \
+ connection._settings.wireless.ssid == ssid:
+ return connection
return None
+
def add_connection(uuid, settings, secrets=None):
global _conn_counter
@@ -590,19 +842,26 @@ def add_connection(uuid, settings, secrets=None):
_nm_settings.add_connection(uuid, conn)
return conn
-def load_wifi_connections():
+
+def _get_wifi_connections_path():
profile_path = env.get_profile_path()
- config_path = os.path.join(profile_path, 'nm', 'connections.cfg')
+ return os.path.join(profile_path, 'nm', 'connections.cfg')
- config = ConfigParser.ConfigParser()
+
+def _create_wifi_connections(config_path):
+ if not os.path.exists(os.path.dirname(config_path)):
+ os.makedirs(os.path.dirname(config_path), 0755)
+ f = open(config_path, 'w')
+ f.close()
+
+
+def load_wifi_connections():
+ config_path = _get_wifi_connections_path()
if not os.path.exists(config_path):
- if not os.path.exists(os.path.dirname(config_path)):
- os.makedirs(os.path.dirname(config_path), 0755)
- f = open(config_path, 'w')
- config.write(f)
- f.close()
+ _create_wifi_connections(config_path)
+ config = ConfigParser.ConfigParser()
try:
if not config.read(config_path):
logging.error('Error reading the nm config file')
@@ -672,10 +931,10 @@ def load_gsm_connection():
if username and number and apn:
settings = SettingsGsm()
- settings.gsm.username = username
+ settings.gsm.username = username
settings.gsm.number = number
settings.gsm.apn = apn
-
+
secrets = SecretsGsm()
secrets.pin = pin
secrets.puk = puk
@@ -693,12 +952,14 @@ def load_gsm_connection():
except Exception:
logging.exception('Error adding gsm connection to NMSettings.')
else:
- logging.exception("No gsm connection was set in GConf.")
+ logging.warning('No gsm connection was set in GConf.')
+
def load_connections():
load_wifi_connections()
load_gsm_connection()
+
def find_gsm_connection():
connections = get_settings().connections
@@ -708,3 +969,39 @@ def find_gsm_connection():
logging.debug('There is no gsm connection in the NMSettings.')
return None
+
+
+def have_wifi_connections():
+ return bool(get_settings().connections)
+
+
+def clear_wifi_connections():
+ if _nm_settings is not None:
+ _nm_settings.clear_wifi_connections()
+
+ config_path = _get_wifi_connections_path()
+ _create_wifi_connections(config_path)
+
+
+def disconnect_access_points(ap_paths):
+ """
+ Disconnect all devices connected to any of the given access points.
+ """
+ bus = dbus.SystemBus()
+ netmgr_obj = bus.get_object(NM_SERVICE, NM_PATH)
+ netmgr = dbus.Interface(netmgr_obj, NM_IFACE)
+ netmgr_props = dbus.Interface(netmgr, dbus.PROPERTIES_IFACE)
+ active_connection_paths = netmgr_props.Get(NM_IFACE, 'ActiveConnections')
+
+ for conn_path in active_connection_paths:
+ conn_obj = bus.get_object(NM_IFACE, conn_path)
+ conn_props = dbus.Interface(conn_obj, dbus.PROPERTIES_IFACE)
+ ap_path = conn_props.Get(NM_ACTIVE_CONN_IFACE, 'SpecificObject')
+ if ap_path == '/' or ap_path not in ap_paths:
+ continue
+
+ dev_paths = conn_props.Get(NM_ACTIVE_CONN_IFACE, 'Devices')
+ for dev_path in dev_paths:
+ dev_obj = bus.get_object(NM_SERVICE, dev_path)
+ dev = dbus.Interface(dev_obj, NM_DEVICE_IFACE)
+ dev.Disconnect()
diff --git a/src/jarabe/model/notifications.py b/src/jarabe/model/notifications.py
index 10a0237..ec14056 100644
--- a/src/jarabe/model/notifications.py
+++ b/src/jarabe/model/notifications.py
@@ -23,9 +23,13 @@ from sugar import dispatch
from jarabe import config
-_DBUS_SERVICE = "org.freedesktop.Notifications"
-_DBUS_IFACE = "org.freedesktop.Notifications"
-_DBUS_PATH = "/org/freedesktop/Notifications"
+
+_DBUS_SERVICE = 'org.freedesktop.Notifications'
+_DBUS_IFACE = 'org.freedesktop.Notifications'
+_DBUS_PATH = '/org/freedesktop/Notifications'
+
+_instance = None
+
class NotificationService(dbus.service.Object):
def __init__(self):
@@ -43,7 +47,8 @@ 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])
+ '<app_icon>', summary, body, actions, '<hints>',
+ expire_timeout])
if replaces_id > 0:
notification_id = replaces_id
@@ -73,16 +78,14 @@ class NotificationService(dbus.service.Object):
def GetServerInformation(self, name, vendor, version):
return 'Sugar Shell', 'Sugar', config.version
-
- @dbus.service.signal(_DBUS_IFACE, signature="uu")
+ @dbus.service.signal(_DBUS_IFACE, signature='uu')
def NotificationClosed(self, notification_id, reason):
pass
- @dbus.service.signal(_DBUS_IFACE, signature="us")
+ @dbus.service.signal(_DBUS_IFACE, signature='us')
def ActionInvoked(self, notification_id, action_key):
pass
-_instance = None
def get_service():
global _instance
@@ -90,6 +93,6 @@ def get_service():
_instance = NotificationService()
return _instance
+
def init():
get_service()
-
diff --git a/src/jarabe/model/olpcmesh.py b/src/jarabe/model/olpcmesh.py
index cbd7ddd..f070100 100644
--- a/src/jarabe/model/olpcmesh.py
+++ b/src/jarabe/model/olpcmesh.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 One Laptop per Child
+# 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
@@ -24,13 +24,14 @@ from jarabe.model.network import Settings
from jarabe.model.network import OlpcMesh as OlpcMeshSettings
from sugar.util import unique_id
+
_NM_SERVICE = 'org.freedesktop.NetworkManager'
_NM_IFACE = 'org.freedesktop.NetworkManager'
_NM_PATH = '/org/freedesktop/NetworkManager'
_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device'
_NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh'
-_XS_ANYCAST = "\xc0\x27\xc0\x27\xc0\x00"
+_XS_ANYCAST = '\xc0\x27\xc0\x27\xc0\x00'
DEVICE_STATE_UNKNOWN = 0
DEVICE_STATE_UNMANAGED = 1
@@ -43,6 +44,7 @@ DEVICE_STATE_IP_CONFIG = 7
DEVICE_STATE_ACTIVATED = 8
DEVICE_STATE_FAILED = 9
+
class OlpcMeshManager(object):
def __init__(self, mesh_device):
self._bus = dbus.SystemBus()
@@ -56,14 +58,12 @@ class OlpcMeshManager(object):
"""
- props = dbus.Interface(self.mesh_device,
- 'org.freedesktop.DBus.Properties')
+ props = dbus.Interface(self.mesh_device, dbus.PROPERTIES_IFACE)
props.Get(_NM_DEVICE_IFACE, 'State',
reply_handler=self.__get_mesh_state_reply_cb,
error_handler=self.__get_state_error_cb)
- props = dbus.Interface(self.eth_device,
- 'org.freedesktop.DBus.Properties')
+ props = dbus.Interface(self.eth_device, dbus.PROPERTIES_IFACE)
props.Get(_NM_DEVICE_IFACE, 'State',
reply_handler=self.__get_eth_state_reply_cb,
error_handler=self.__get_state_error_cb)
@@ -83,13 +83,12 @@ class OlpcMeshManager(object):
self._eth_device_state = DEVICE_STATE_UNKNOWN
if self._have_configured_connections():
- self._start_automesh()
- else:
self._start_automesh_timer()
+ else:
+ self._start_automesh()
def _get_companion_device(self):
- props = dbus.Interface(self.mesh_device,
- 'org.freedesktop.DBus.Properties')
+ props = dbus.Interface(self.mesh_device, dbus.PROPERTIES_IFACE)
eth_device_o = props.Get(_NM_OLPC_MESH_IFACE, 'Companion')
return self._bus.get_object(_NM_SERVICE, eth_device_o)
@@ -119,7 +118,7 @@ class OlpcMeshManager(object):
def __eth_device_state_changed_cb(self, new_state, old_state, reason):
"""If a connection is activated on the eth device, stop trying our
automatic connections.
-
+
"""
self._eth_device_state = new_state
self._maybe_schedule_idle_check()
@@ -147,7 +146,7 @@ class OlpcMeshManager(object):
def _idle_check(self):
if self._mesh_device_state == DEVICE_STATE_DISCONNECTED \
and self._eth_device_state == DEVICE_STATE_DISCONNECTED:
- logging.debug("starting automesh due to inactivity")
+ logging.debug('starting automesh due to inactivity')
self._start_automesh()
return False
@@ -170,8 +169,8 @@ class OlpcMeshManager(object):
logging.error('Failed to activate connection: %s', err)
def _activate_connection(self, channel, anycast_address=None):
- logging.debug("activate channel %d anycast %s",
- (channel, repr(anycast_address)))
+ logging.debug('activate channel %d anycast %r',
+ channel, anycast_address)
proxy = self._bus.get_object(_NM_SERVICE, _NM_PATH)
network_manager = dbus.Interface(proxy, _NM_IFACE)
connection = self._make_connection(channel, anycast_address)
@@ -211,4 +210,3 @@ class OlpcMeshManager(object):
self._connection_queue.append((6, _XS_ANYCAST))
self._connection_queue.append((1, _XS_ANYCAST))
self._try_next_connection_from_queue()
-
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/screen.py b/src/jarabe/model/screen.py
index 4403c1c..7d34d45 100644
--- a/src/jarabe/model/screen.py
+++ b/src/jarabe/model/screen.py
@@ -18,12 +18,14 @@ import logging
import dbus
+
_HARDWARE_MANAGER_INTERFACE = 'org.freedesktop.ohm.Keystore'
_HARDWARE_MANAGER_SERVICE = 'org.freedesktop.ohm'
_HARDWARE_MANAGER_OBJECT_PATH = '/org/freedesktop/ohm/Keystore'
_ohm_service = None
+
def _get_ohm():
global _ohm_service
if _ohm_service is None:
@@ -35,9 +37,9 @@ def _get_ohm():
return _ohm_service
+
def set_dcon_freeze(frozen):
try:
- _get_ohm().SetKey("display.dcon_freeze", frozen)
+ _get_ohm().SetKey('display.dcon_freeze', frozen)
except dbus.DBusException:
logging.error('Cannot unfreeze the DCON')
-
diff --git a/src/jarabe/model/session.py b/src/jarabe/model/session.py
index 9e0f087..9b277ff 100644
--- a/src/jarabe/model/session.py
+++ b/src/jarabe/model/session.py
@@ -24,8 +24,10 @@ import logging
from sugar import session
from sugar import env
+
_session_manager = None
+
class SessionManager(session.SessionManager):
MODE_LOGOUT = 0
MODE_SHUTDOWN = 1
@@ -53,15 +55,15 @@ class SessionManager(session.SessionManager):
elif self._logout_mode != self.MODE_LOGOUT:
try:
bus = dbus.SystemBus()
- proxy = bus.get_object('org.freedesktop.Hal',
- '/org/freedesktop/Hal/devices/computer')
+ proxy = bus.get_object('org.freedesktop.ConsoleKit',
+ '/org/freedesktop/ConsoleKit/Manager')
pm = dbus.Interface(proxy,
- 'org.freedesktop.Hal.Device.SystemPowerManagement')
+ 'org.freedesktop.ConsoleKit.Manager')
if self._logout_mode == self.MODE_SHUTDOWN:
- pm.Shutdown()
+ pm.Stop()
elif self._logout_mode == self.MODE_REBOOT:
- pm.Reboot()
+ pm.Restart()
except:
logging.exception('Can not stop sugar')
self.session.cancel_shutdown()
@@ -73,14 +75,15 @@ class SessionManager(session.SessionManager):
def _close_emulator(self):
gtk.main_quit()
- if os.environ.has_key('SUGAR_EMULATOR_PID'):
+ if 'SUGAR_EMULATOR_PID' in os.environ:
pid = int(os.environ['SUGAR_EMULATOR_PID'])
os.kill(pid, signal.SIGTERM)
- # Need to call this ASAP so the atexit handlers get called before we get
- # killed by the X (dis)connection
+ # Need to call this ASAP so the atexit handlers get called before we
+ # get killed by the X (dis)connection
sys.exit()
+
def get_session_manager():
global _session_manager
diff --git a/src/jarabe/model/shell.py b/src/jarabe/model/shell.py
index e03e0f7..63f6173 100644
--- a/src/jarabe/model/shell.py
+++ b/src/jarabe/model/shell.py
@@ -27,13 +27,16 @@ 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'
+_SERVICE_INTERFACE = 'org.laptop.Activity'
+
+_model = None
-_SERVICE_NAME = "org.laptop.Activity"
-_SERVICE_PATH = "/org/laptop/Activity"
-_SERVICE_INTERFACE = "org.laptop.Activity"
class Activity(gobject.GObject):
"""Activity which appears in the "Home View" of the Sugar shell
@@ -46,10 +49,9 @@ class Activity(gobject.GObject):
__gtype_name__ = 'SugarHomeActivity'
- __gproperties__ = {
- 'launching' : (bool, None, None, False,
- gobject.PARAM_READWRITE),
- }
+ LAUNCHING = 0
+ LAUNCH_FAILED = 1
+ LAUNCHED = 2
def __init__(self, activity_info, activity_id, window=None):
"""Initialise the HomeActivity
@@ -60,19 +62,20 @@ class Activity(gobject.GObject):
the "type" of activity being created.
activity_id -- unique identifier for this instance
of the activity type
- window -- Main WnckWindow of the activity
+ _windows -- WnckWindows registered for the activity. The lowest
+ one in the stack is the main window.
"""
gobject.GObject.__init__(self)
- self._window = None
+ self._windows = []
self._service = None
self._activity_id = activity_id
self._activity_info = activity_info
self._launch_time = time.time()
- self._launching = True
+ self._launch_status = Activity.LAUNCHING
if window is not None:
- self.set_window(window)
+ self.add_window(window)
self._retrieve_service()
@@ -81,18 +84,32 @@ class Activity(gobject.GObject):
bus = dbus.SessionBus()
self._name_owner_changed_handler = bus.add_signal_receiver(
self._name_owner_changed_cb,
- signal_name="NameOwnerChanged",
- dbus_interface="org.freedesktop.DBus")
+ signal_name='NameOwnerChanged',
+ dbus_interface='org.freedesktop.DBus')
- def set_window(self, window):
- """Set the window for the activity
+ self._launch_completed_hid = get_model().connect('launch-completed',
+ self.__launch_completed_cb)
+ self._launch_failed_hid = get_model().connect('launch-failed',
+ self.__launch_failed_cb)
- We allow resetting the window for an activity so that we
- can replace the launcher once we get its real window.
- """
+ def get_launch_status(self):
+ return self._launch_status
+
+ launch_status = gobject.property(getter=get_launch_status)
+
+ def add_window(self, window):
+ """Add a window to the windows stack."""
if not window:
- raise ValueError("window must be valid")
- self._window = window
+ raise ValueError('window must be valid')
+ self._windows.append(window)
+
+ def remove_window_by_xid(self, xid):
+ """Remove a window from the windows stack."""
+ for wnd in self._windows:
+ if wnd.get_xid() == xid:
+ self._windows.remove(wnd)
+ return True
+ return False
def get_service(self):
"""Get the activity service
@@ -106,8 +123,8 @@ class Activity(gobject.GObject):
def get_title(self):
"""Retrieve the application's root window's suggested title"""
- if self._window:
- return self._window.get_name()
+ if self._windows:
+ return self._windows[0].get_name()
else:
return ''
@@ -135,21 +152,19 @@ 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"))
+ return XoColor(client.get_string('/desktop/sugar/user/color'))
def get_activity_id(self):
"""Retrieve the "activity_id" passed in to our constructor
@@ -161,31 +176,44 @@ class Activity(gobject.GObject):
def get_xid(self):
"""Retrieve the X-windows ID of our root window"""
- if self._window is not None:
- return self._window.get_xid()
+ if self._windows:
+ return self._windows[0].get_xid()
else:
return None
+ def has_xid(self, xid):
+ """Check if an X-window with the given xid is in the windows stack"""
+ if self._windows:
+ for wnd in self._windows:
+ if wnd.get_xid() == xid:
+ return True
+ return False
+
def get_window(self):
"""Retrieve the X-windows root window of this application
- This was stored by the set_window method, which was
+ This was stored by the add_window method, which was
called by HomeModel._add_activity, which was called
via a callback that looks for all 'window-opened'
events.
+ We keep a stack of the windows. The lowest window in the
+ stack that is still valid we consider the main one.
+
HomeModel currently uses a dbus service query on the
activity to determine to which HomeActivity the newly
launched window belongs.
"""
- return self._window
+ if self._windows:
+ return self._windows[0]
+ return None
def get_type(self):
"""Retrieve the activity bundle id for future reference"""
- if self._window is None:
+ if not self._windows:
return None
else:
- return wm.get_bundle_id(self._window)
+ return wm.get_bundle_id(self._windows[0])
def is_journal(self):
"""Returns boolean if the activity is of type JournalActivity"""
@@ -201,7 +229,9 @@ class Activity(gobject.GObject):
def get_pid(self):
"""Returns the activity's PID"""
- return self._window.get_pid()
+ if not self._windows:
+ return None
+ return self._windows[0].get_pid()
def get_bundle_path(self):
"""Returns the activity's bundle directory"""
@@ -220,18 +250,10 @@ class Activity(gobject.GObject):
def equals(self, activity):
if self._activity_id and activity.get_activity_id():
return self._activity_id == activity.get_activity_id()
- if self._window.get_xid() and activity.get_xid():
- return self._window.get_xid() == activity.get_xid()
+ if self._windows[0].get_xid() and activity.get_xid():
+ return self._windows[0].get_xid() == activity.get_xid()
return False
- def do_set_property(self, pspec, value):
- if pspec.name == 'launching':
- self._launching = value
-
- def do_get_property(self, pspec):
- if pspec.name == 'launching':
- return self._launching
-
def _get_service_name(self):
if self._activity_id:
return _SERVICE_NAME + self._activity_id
@@ -245,17 +267,24 @@ class Activity(gobject.GObject):
try:
bus = dbus.SessionBus()
proxy = bus.get_object(self._get_service_name(),
- _SERVICE_PATH + "/" + self._activity_id)
+ _SERVICE_PATH + '/' + self._activity_id)
self._service = dbus.Interface(proxy, _SERVICE_INTERFACE)
except dbus.DBusException:
self._service = None
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"""
@@ -268,7 +297,24 @@ class Activity(gobject.GObject):
pass
def _set_active_error(self, err):
- logging.error("set_active() failed: %s", err)
+ logging.error('set_active() failed: %s', err)
+
+ def _set_launch_status(self, value):
+ get_model().disconnect(self._launch_completed_hid)
+ get_model().disconnect(self._launch_failed_hid)
+ self._launch_completed_hid = None
+ self._launch_failed_hid = None
+ self._launch_status = value
+ self.notify('launch_status')
+
+ def __launch_completed_cb(self, model, home_activity):
+ if home_activity is self:
+ self._set_launch_status(Activity.LAUNCHED)
+
+ def __launch_failed_cb(self, model, home_activity):
+ if home_activity is self:
+ self._set_launch_status(Activity.LAUNCH_FAILED)
+
class ShellModel(gobject.GObject):
"""Model of the shell (activity management)
@@ -286,27 +332,22 @@ class ShellModel(gobject.GObject):
"""
__gsignals__ = {
- 'activity-added': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT])),
- 'activity-removed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT])),
+ 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'activity-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
'active-activity-changed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT])),
+ ([gobject.TYPE_PYOBJECT])),
'tabbing-activity-changed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT])),
- 'launch-started': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT])),
- 'launch-completed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT])),
- 'launch-failed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT]))
+ gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'launch-started': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'launch-completed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'launch-failed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
}
ZOOM_MESH = 0
@@ -331,10 +372,20 @@ 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)
+ def get_launcher(self, activity_id):
+ return self._launchers.get(str(activity_id))
+
+ def register_launcher(self, activity_id, launcher):
+ self._launchers[activity_id] = launcher
+
+ def unregister_launcher(self, activity_id):
+ if activity_id in self._launchers:
+ del self._launchers[activity_id]
+
def _update_zoom_level(self, window):
if window.get_window_type() == wnck.WINDOW_DIALOG:
return
@@ -426,7 +477,7 @@ class ShellModel(gobject.GObject):
def set_tabbing_activity(self, activity):
"""Sets the activity that is currently highlighted during tabbing"""
self._tabbing_activity = activity
- self.emit("tabbing-activity-changed", self._tabbing_activity)
+ self.emit('tabbing-activity-changed', self._tabbing_activity)
def _set_active_activity(self, home_activity):
if self._active_activity == home_activity:
@@ -454,6 +505,21 @@ class ShellModel(gobject.GObject):
return self._activities.index(obj)
def _window_opened_cb(self, screen, window):
+ """Handle the callback for the 'window opened' event.
+
+ Most activities will register 2 windows during
+ their lifetime: the launcher window, and the 'main'
+ app window.
+
+ When the main window appears, we send a signal to
+ the launcher window to close.
+
+ Some activities (notably non-native apps) open several
+ windows during their lifetime, switching from one to
+ the next as the 'main' window. We use a stack to track
+ them.
+
+ """
if window.get_window_type() == wnck.WINDOW_NORMAL:
home_activity = None
@@ -476,31 +542,36 @@ class ShellModel(gobject.GObject):
window.maximize()
if not home_activity:
+ logging.debug('first window registered for %s' % activity_id)
home_activity = Activity(activity_info, activity_id, window)
self._add_activity(home_activity)
else:
- home_activity.set_window(window)
-
- if wm.get_sugar_window_type(window) != 'launcher':
- home_activity.props.launching = False
- if not home_activity.is_journal():
- self.emit('launch-completed', home_activity)
+ logging.debug('window registered for %s' % activity_id)
+ home_activity.add_window(window)
+ if wm.get_sugar_window_type(window) != 'launcher' \
+ and home_activity.get_launch_status() == Activity.LAUNCHING:
+ self.emit('launch-completed', home_activity)
startup_time = time.time() - home_activity.get_launch_time()
- logging.debug('%s launched in %f seconds.',
- home_activity.get_type(), startup_time)
+ logging.debug('%s launched in %f seconds.' %
+ (activity_id, startup_time))
if self._active_activity is None:
self._set_active_activity(home_activity)
def _window_closed_cb(self, screen, window):
if window.get_window_type() == wnck.WINDOW_NORMAL:
- if self._get_activity_by_xid(window.get_xid()) is not None:
- self._remove_activity_by_xid(window.get_xid())
+ xid = window.get_xid()
+ activity = self._get_activity_by_xid(xid)
+ if activity is not None:
+ activity.remove_window_by_xid(xid)
+ if activity.get_window() is None:
+ logging.debug('last window gone - remove activity %s' % activity)
+ self._remove_activity(activity)
def _get_activity_by_xid(self, xid):
for home_activity in self._activities:
- if home_activity.get_xid() == xid:
+ if home_activity.has_xid(xid):
return home_activity
return None
@@ -545,13 +616,6 @@ class ShellModel(gobject.GObject):
self.emit('activity-removed', home_activity)
self._activities.remove(home_activity)
- def _remove_activity_by_xid(self, xid):
- home_activity = self._get_activity_by_xid(xid)
- if home_activity:
- self._remove_activity(home_activity)
- else:
- logging.error('Model for window %d does not exist.', xid)
-
def notify_launch(self, activity_id, service_name):
registry = get_registry()
activity_info = registry.get_bundle(service_name)
@@ -560,7 +624,6 @@ class ShellModel(gobject.GObject):
" was not found in the bundle registry."
% service_name)
home_activity = Activity(activity_info, activity_id)
- home_activity.props.launching = True
self._add_activity(home_activity)
self._set_active_activity(home_activity)
@@ -575,11 +638,12 @@ class ShellModel(gobject.GObject):
def notify_launch_failed(self, activity_id):
home_activity = self.get_activity_by_id(activity_id)
if home_activity:
- logging.debug("Activity %s (%s) launch failed", activity_id,
+ logging.debug('Activity %s (%s) launch failed', activity_id,
home_activity.get_type())
- if home_activity.props.launching:
+ if self.get_launcher(activity_id) is not None:
self.emit('launch-failed', home_activity)
else:
+ # activity sent failure notification after closing launcher
self._remove_activity(home_activity)
else:
logging.error('Model for activity id %s does not exist.',
@@ -592,18 +656,15 @@ class ShellModel(gobject.GObject):
logging.debug('Activity %s has been closed already.', activity_id)
return False
- if home_activity.props.launching:
+ if self.get_launcher(activity_id) is not None:
logging.debug('Activity %s still launching, assuming it failed.',
activity_id)
self.notify_launch_failed(activity_id)
return False
-_model = None
-
def get_model():
global _model
if _model is None:
_model = ShellModel()
return _model
-
diff --git a/src/jarabe/model/sound.py b/src/jarabe/model/sound.py
index 65090a4..9e1e748 100644
--- a/src/jarabe/model/sound.py
+++ b/src/jarabe/model/sound.py
@@ -20,17 +20,23 @@ from sugar import env
from sugar import _sugarext
from sugar import dispatch
+
VOLUME_STEP = 10
muted_changed = dispatch.Signal()
volume_changed = dispatch.Signal()
+_volume = _sugarext.VolumeAlsa()
+
+
def get_muted():
return _volume.get_mute()
+
def get_volume():
return _volume.get_volume()
+
def set_volume(new_volume):
old_volume = _volume.get_volume()
_volume.set_volume(new_volume)
@@ -38,6 +44,7 @@ def set_volume(new_volume):
volume_changed.send(None)
save()
+
def set_muted(new_state):
old_state = _volume.get_mute()
_volume.set_mute(new_state)
@@ -45,14 +52,14 @@ def set_muted(new_state):
muted_changed.send(None)
save()
+
def save():
if env.is_emulator() is False:
client = gconf.client_get_default()
client.set_int('/desktop/sugar/sound/volume', get_volume())
+
def restore():
if env.is_emulator() is False:
client = gconf.client_get_default()
set_volume(client.get_int('/desktop/sugar/sound/volume'))
-
-_volume = _sugarext.VolumeAlsa()
diff --git a/src/jarabe/model/telepathyclient.py b/src/jarabe/model/telepathyclient.py
new file mode 100644
index 0000000..c6fbac1
--- /dev/null
+++ b/src/jarabe/model/telepathyclient.py
@@ -0,0 +1,103 @@
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# 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'
+
+_instance = None
+
+
+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)
+
+
+def get_instance():
+ global _instance
+ if not _instance:
+ _instance = TelepathyClient()
+ return _instance
diff --git a/src/jarabe/util/__init__.py b/src/jarabe/util/__init__.py
index 1610dd0..9c80ecb 100644
--- a/src/jarabe/util/__init__.py
+++ b/src/jarabe/util/__init__.py
@@ -16,4 +16,3 @@
# 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
-
diff --git a/src/jarabe/util/emulator.py b/src/jarabe/util/emulator.py
index d9dca7f..fda1b59 100644
--- a/src/jarabe/util/emulator.py
+++ b/src/jarabe/util/emulator.py
@@ -20,6 +20,7 @@ import subprocess
import sys
import time
from optparse import OptionParser
+from gettext import gettext as _
import gtk
import gobject
@@ -29,36 +30,37 @@ from sugar import env
ERROR_NO_DISPLAY = 30
ERROR_NO_SERVER = 31
+default_dimensions = (800, 600)
-default_dimensions = (800, 600)
def _run_xephyr(display, dpi, dimensions, fullscreen):
- cmd = [ 'Xephyr' ]
+ cmd = ['Xephyr']
cmd.append(':%d' % display)
- cmd.append('-ac')
+ cmd.append('-ac')
+ cmd += ['-title', _('Sugar in a window')]
screen_size = (gtk.gdk.screen_width(), gtk.gdk.screen_height())
if (not dimensions) and (fullscreen is None) and \
- (screen_size < default_dimensions) :
+ (screen_size <= default_dimensions):
# no forced settings, screen too small => fit screen
fullscreen = True
- elif (not dimensions) :
+ elif not dimensions:
# screen is big enough or user has en/disabled fullscreen manually
# => use default size (will get ignored for fullscreen)
dimensions = '%dx%d' % default_dimensions
- if not dpi :
+ if not dpi:
dpi = gtk.settings_get_default().get_property('gtk-xft-dpi') / 1024
- if fullscreen :
+ if fullscreen:
cmd.append('-fullscreen')
- if dimensions :
+ if dimensions:
cmd.append('-screen')
cmd.append(dimensions)
- if dpi :
+ if dpi:
cmd.append('-dpi')
cmd.append('%d' % dpi)
@@ -71,15 +73,13 @@ def _run_xephyr(display, dpi, dimensions, fullscreen):
sys.stderr.write('Error executing server: %s\n' % (exc, ))
return None
- os.environ['DISPLAY'] = ":%d" % (display)
- os.environ['SUGAR_EMULATOR_PID'] = str(pipe.pid)
return pipe
def _check_server(display):
result = subprocess.call(['xdpyinfo', '-display', ':%d' % display],
- stdout=open(os.devnull, "w"),
- stderr=open(os.devnull, "w"))
+ stdout=open(os.devnull, 'w'),
+ stderr=open(os.devnull, 'w'))
return result == 0
@@ -98,17 +98,17 @@ def _start_xephyr(dpi, dimensions, fullscreen):
if not _check_server(display):
pipe = _run_xephyr(display, dpi, dimensions, fullscreen)
if pipe is None:
- return None
+ return None, None
for i_ in range(10):
if _check_server(display):
- return pipe
+ return pipe, display
time.sleep(0.1)
_kill_pipe(pipe)
- return None
+ return None, None
def _start_window_manager():
@@ -118,21 +118,31 @@ def _start_window_manager():
gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH)
-def _setup_env():
+
+def _setup_env(display, scaling, emulator_pid):
os.environ['SUGAR_EMULATOR'] = 'yes'
os.environ['GABBLE_LOGFILE'] = os.path.join(
env.get_profile_path(), 'logs', 'telepathy-gabble.log')
os.environ['SALUT_LOGFILE'] = os.path.join(
env.get_profile_path(), 'logs', 'telepathy-salut.log')
+ os.environ['MC_LOGFILE'] = os.path.join(
+ env.get_profile_path(), 'logs', 'mission-control.log')
os.environ['STREAM_ENGINE_LOGFILE'] = os.path.join(
env.get_profile_path(), 'logs', 'telepathy-stream-engine.log')
+ os.environ['DISPLAY'] = ':%d' % (display)
+ os.environ['SUGAR_EMULATOR_PID'] = emulator_pid
+ os.environ['MC_ACCOUNT_DIR'] = os.path.join(
+ env.get_profile_path(), 'accounts')
+
+ if scaling:
+ os.environ['SUGAR_SCALING'] = scaling
def main():
"""Script-level operations"""
parser = OptionParser()
- parser.add_option('-d', '--dpi', dest='dpi', type="int",
+ parser.add_option('-d', '--dpi', dest='dpi', type='int',
help='Emulator dpi')
parser.add_option('-s', '--scaling', dest='scaling',
help='Sugar scaling in %')
@@ -150,16 +160,14 @@ def main():
sys.stderr.write('DISPLAY not set, cannot connect to host X server.\n')
return ERROR_NO_DISPLAY
- _setup_env()
-
- server = _start_xephyr(options.dpi, options.dimensions, options.fullscreen)
+ server, display = _start_xephyr(options.dpi, options.dimensions,
+ options.fullscreen)
if server is None:
sys.stderr.write('Failed to start server. Please check output above'
' for any error message.\n')
return ERROR_NO_SERVER
- if options.scaling:
- os.environ['SUGAR_SCALING'] = options.scaling
+ _setup_env(display, options.scaling, str(server.pid))
command = ['dbus-launch', '--exit-with-session']
diff --git a/src/jarabe/util/telepathy/__init__.py b/src/jarabe/util/telepathy/__init__.py
index 387d09c..eee4abb 100644
--- a/src/jarabe/util/telepathy/__init__.py
+++ b/src/jarabe/util/telepathy/__init__.py
@@ -16,4 +16,3 @@
# 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
-
diff --git a/src/jarabe/util/telepathy/connection_watcher.py b/src/jarabe/util/telepathy/connection_watcher.py
index 4a4c6e0..96af1cf 100644
--- a/src/jarabe/util/telepathy/connection_watcher.py
+++ b/src/jarabe/util/telepathy/connection_watcher.py
@@ -28,12 +28,16 @@ from telepathy.interfaces import CONN_INTERFACE
from telepathy.constants import CONNECTION_STATUS_CONNECTED, \
CONNECTION_STATUS_DISCONNECTED
+
+_instance = None
+
+
class ConnectionWatcher(gobject.GObject):
__gsignals__ = {
'connection-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'connection-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT]))
+ ([gobject.TYPE_PYOBJECT])),
}
def __init__(self, bus=None):
@@ -93,14 +97,22 @@ class ConnectionWatcher(gobject.GObject):
def get_connections(self):
return self._connections.values()
+
+def get_instance():
+ global _instance
+ if _instance is None:
+ _instance = ConnectionWatcher()
+ return _instance
+
+
if __name__ == '__main__':
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
def connection_added_cb(conn_watcher, conn):
- print "new connection", conn.service_name
+ print 'new connection', conn.service_name
def connection_removed_cb(conn_watcher, conn):
- print "removed connection", conn.service_name
+ print 'removed connection', conn.service_name
watcher = ConnectionWatcher()
watcher.connect('connection-added', connection_added_cb)
diff --git a/src/jarabe/view/__init__.py b/src/jarabe/view/__init__.py
index a9dd95a..85f6a24 100644
--- a/src/jarabe/view/__init__.py
+++ b/src/jarabe/view/__init__.py
@@ -13,4 +13,3 @@
# 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
-
diff --git a/src/jarabe/view/buddyicon.py b/src/jarabe/view/buddyicon.py
index 13edb2c..a274605 100644
--- a/src/jarabe/view/buddyicon.py
+++ b/src/jarabe/view/buddyicon.py
@@ -19,42 +19,45 @@ from sugar.graphics import style
from jarabe.view.buddymenu import BuddyMenu
+
class BuddyIcon(CanvasIcon):
def __init__(self, buddy, size=style.STANDARD_ICON_SIZE):
CanvasIcon.__init__(self, icon_name='computer-xo', size=size)
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)
+ self._buddy.connect('notify::present', self.__buddy_notify_present_cb)
+ self._buddy.connect('notify::color', self.__buddy_notify_color_cb)
- palette = BuddyMenu(buddy)
- self.set_palette(palette)
+ self.palette_invoker.cache_palette = False
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) \
and not self._buddy.is_owner()
self._update_color()
-
diff --git a/src/jarabe/view/buddymenu.py b/src/jarabe/view/buddymenu.py
index 4637751..f824e70 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. <http://www.collabora.co.uk/>
#
# 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
@@ -28,6 +30,8 @@ from jarabe.model import shell
from jarabe.model import friends
from jarabe.model.session import get_session_manager
from jarabe.controlpanel.gui import ControlPanel
+import jarabe.desktop.homewindow
+
class BuddyMenu(Palette):
def __init__(self, buddy):
@@ -42,8 +46,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 +57,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):
@@ -86,6 +88,12 @@ class BuddyMenu(Palette):
client = gconf.client_get_default()
+ if client.get_bool('/desktop/sugar/show_restart'):
+ item = MenuItem(_('Restart'), 'system-restart')
+ item.connect('activate', self.__reboot_activate_cb)
+ self.menu.append(item)
+ item.show()
+
if client.get_bool('/desktop/sugar/show_logout'):
item = MenuItem(_('Logout'), 'system-logout')
item.connect('activate', self.__logout_activate_cb)
@@ -97,17 +105,18 @@ class BuddyMenu(Palette):
self.menu.append(item)
item.show()
+ def _quit(self, action):
+ home_window = jarabe.desktop.homewindow.get_instance()
+ home_window.busy_during_delayed_action(action)
+
def __logout_activate_cb(self, menu_item):
- session_manager = get_session_manager()
- session_manager.logout()
+ self._quit(get_session_manager().logout)
def __reboot_activate_cb(self, menu_item):
- session_manager = get_session_manager()
- session_manager.reboot()
+ self._quit(get_session_manager().reboot)
def __shutdown_activate_cb(self, menu_item):
- session_manager = get_session_manager()
- session_manager.shutdown()
+ self._quit(get_session_manager().shutdown)
def __controlpanel_activate_cb(self, menu_item):
panel = ControlPanel()
@@ -115,9 +124,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 +148,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 +161,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/keyhandler.py b/src/jarabe/view/keyhandler.py
index 8c07975..d79bfe6 100644
--- a/src/jarabe/view/keyhandler.py
+++ b/src/jarabe/view/keyhandler.py
@@ -17,7 +17,6 @@
import os
import logging
-import traceback
import dbus
import gtk
@@ -32,39 +31,46 @@ from jarabe.model.shell import ShellModel
from jarabe import config
from jarabe.journal import journalactivity
+
_VOLUME_STEP = sound.VOLUME_STEP
_VOLUME_MAX = 100
_TABBING_MODIFIER = gtk.gdk.MOD1_MASK
+
_actions_table = {
- 'F1' : 'zoom_mesh',
- 'F2' : 'zoom_group',
- 'F3' : 'zoom_home',
- 'F4' : 'zoom_activity',
- 'XF86AudioMute' : 'volume_mute',
- 'F11' : 'volume_down',
- 'XF86AudioLowerVolume' : 'volume_down',
- 'F12' : 'volume_up',
- 'XF86AudioRaiseVolume' : 'volume_up',
- '<alt>F11' : 'volume_min',
- '<alt>F12' : 'volume_max',
- '0x93' : 'frame',
- '<alt>Tab' : 'next_window',
- '<alt><shift>Tab' : 'previous_window',
- '<alt>Escape' : 'close_window',
- '0xDC' : 'open_search',
+ 'F1': 'zoom_mesh',
+ 'F2': 'zoom_group',
+ 'F3': 'zoom_home',
+ 'F4': 'zoom_activity',
+ 'F5': 'open_search',
+ 'F6': 'frame',
+ 'XF86AudioMute': 'volume_mute',
+ 'F11': 'volume_down',
+ 'XF86AudioLowerVolume': 'volume_down',
+ 'F12': 'volume_up',
+ 'XF86AudioRaiseVolume': 'volume_up',
+ '<alt>F11': 'volume_min',
+ '<alt>F12': 'volume_max',
+ '0x93': 'frame',
+ '<alt>Tab': 'next_window',
+ '<alt><shift>Tab': 'previous_window',
+ '<alt>Escape': 'close_window',
+ '0xDC': 'open_search',
# the following are intended for emulator users
- '<alt><shift>f' : 'frame',
- '<alt><shift>q' : 'quit_emulator',
- 'XF86Search' : 'open_search',
- '<alt><shift>o' : 'open_search',
- '<alt><shift>s' : 'say_text',
+ '<alt><shift>f': 'frame',
+ '<alt><shift>q': 'quit_emulator',
+ 'XF86Search': 'open_search',
+ '<alt><shift>o': 'open_search',
+ '<alt><shift>s': 'say_text',
}
SPEECH_DBUS_SERVICE = 'org.laptop.Speech'
SPEECH_DBUS_PATH = '/org/laptop/Speech'
SPEECH_DBUS_INTERFACE = 'org.laptop.Speech'
+_instance = None
+
+
class KeyHandler(object):
def __init__(self, frame):
self._frame = frame
@@ -93,8 +99,7 @@ class KeyHandler(object):
raise ValueError('Key %r is already bound' % key)
_actions_table[key] = module
except Exception:
- logging.error('Exception while loading extension:\n' + \
- traceback.format_exc())
+ logging.exception('Exception while loading extension:')
self._key_grabber.grab_keys(_actions_table.keys())
@@ -124,11 +129,11 @@ class KeyHandler(object):
def _primary_selection_cb(self, clipboard, text, user_data):
logging.debug('KeyHandler._primary_selection_cb: %r', text)
if text:
- self._get_speech_proxy().SayText(text, reply_handler=lambda: None, \
+ self._get_speech_proxy().SayText(text, reply_handler=lambda: None,
error_handler=self._on_speech_err)
def handle_say_text(self, event_time):
- clipboard = gtk.clipboard_get(selection="PRIMARY")
+ clipboard = gtk.clipboard_get(selection='PRIMARY')
clipboard.request_text(self._primary_selection_cb)
def handle_previous_window(self, event_time):
@@ -195,7 +200,7 @@ class KeyHandler(object):
if self._tabbing_handler.is_tabbing():
# Only accept window tabbing events, everything else
# cancels the tabbing operation.
- if not action in ["next_window", "previous_window"]:
+ if not action in ['next_window', 'previous_window']:
self._tabbing_handler.stop(event_time)
return True
@@ -218,7 +223,7 @@ class KeyHandler(object):
return False
def _key_released_cb(self, grabber, keycode, state, event_time):
- logging.debug('_key_released_cb: %i %i' % (keycode, state))
+ logging.debug('_key_released_cb: %i %i', keycode, state)
if self._tabbing_handler.is_tabbing():
# We stop tabbing and switch to the new window as soon as the
# modifier key is raised again.
@@ -228,7 +233,6 @@ class KeyHandler(object):
return True
return False
-_instance = None
def setup(frame):
global _instance
@@ -237,4 +241,3 @@ def setup(frame):
del _instance
_instance = KeyHandler(frame)
-
diff --git a/src/jarabe/view/launcher.py b/src/jarabe/view/launcher.py
index 422a49a..89251e5 100644
--- a/src/jarabe/view/launcher.py
+++ b/src/jarabe/view/launcher.py
@@ -156,9 +156,6 @@ class _Animation(animator.Animation):
self._icon.props.size = int(self.start_size + d)
-_launchers = {}
-
-
def setup():
model = shell.get_model()
model.connect('launch-started', __launch_started_cb)
@@ -167,14 +164,15 @@ def setup():
def add_launcher(activity_id, icon_path, icon_color):
+ model = shell.get_model()
- if activity_id in _launchers:
+ if model.get_launcher(activity_id) is not None:
return
launch_window = LaunchWindow(activity_id, icon_path, icon_color)
launch_window.show()
- _launchers[activity_id] = launch_window
+ model.register_launcher(activity_id, launch_window)
def __launch_started_cb(home_model, home_activity):
@@ -184,7 +182,7 @@ def __launch_started_cb(home_model, home_activity):
def __launch_failed_cb(home_model, home_activity):
activity_id = home_activity.get_activity_id()
- launcher = _launchers.get(activity_id)
+ launcher = shell.get_model().get_launcher(activity_id)
if launcher is None:
logging.error('Launcher for %s is missing', activity_id)
@@ -209,8 +207,11 @@ def __launch_completed_cb(home_model, home_activity):
def _destroy_launcher(home_activity):
activity_id = home_activity.get_activity_id()
- if activity_id in _launchers:
- _launchers[activity_id].destroy()
- del _launchers[activity_id]
- else:
- logging.error('Launcher for %s is missing', activity_id)
+ launcher = shell.get_model().get_launcher(activity_id)
+ if launcher is None:
+ if not home_activity.is_journal():
+ logging.error('Launcher was not registered for %s', activity_id)
+ return
+
+ shell.get_model().unregister_launcher(activity_id)
+ launcher.destroy()
diff --git a/src/jarabe/view/palettes.py b/src/jarabe/view/palettes.py
index 2beceff..d9c1f6b 100644
--- a/src/jarabe/view/palettes.py
+++ b/src/jarabe/view/palettes.py
@@ -28,31 +28,42 @@ from sugar.graphics.menuitem import MenuItem
from sugar.graphics.icon import Icon
from sugar.graphics import style
from sugar.graphics.xocolor import XoColor
-from sugar.activity import activityfactory
-from sugar.activity.activityhandle import ActivityHandle
from jarabe.model import shell
-from jarabe.view import launcher
from jarabe.view.viewsource import setup_view_source
+from jarabe.journal import misc
+
class BasePalette(Palette):
def __init__(self, home_activity):
Palette.__init__(self)
- if home_activity.props.launching:
- home_activity.connect('notify::launching',
- self._launching_changed_cb)
+ self._notify_launch_hid = None
+
+ if home_activity.props.launch_status == shell.Activity.LAUNCHING:
+ self._notify_launch_hid = home_activity.connect( \
+ 'notify::launch-status', self.__notify_launch_status_cb)
self.set_primary_text(_('Starting...'))
+ elif home_activity.props.launch_status == shell.Activity.LAUNCH_FAILED:
+ self._on_failed_launch()
else:
self.setup_palette()
- def _launching_changed_cb(self, home_activity, pspec):
- if not home_activity.props.launching:
- self.setup_palette()
-
def setup_palette(self):
raise NotImplementedError
+ def _on_failed_launch(self):
+ self.set_primary_text(_('Activity failed to start'))
+
+ def __notify_launch_status_cb(self, home_activity, pspec):
+ home_activity.disconnect(self._notify_launch_hid)
+ self._notify_launch_hid = None
+ if home_activity.props.launch_status == shell.Activity.LAUNCH_FAILED:
+ self._on_failed_launch()
+ else:
+ self.setup_palette()
+
+
class CurrentActivityPalette(BasePalette):
def __init__(self, home_activity):
self._home_activity = home_activity
@@ -97,10 +108,6 @@ class CurrentActivityPalette(BasePalette):
self._home_activity.get_window().activate( \
gtk.get_current_event_time())
- def __active_window_changed_cb(self, screen, previous_window=None):
- setup_view_source()
- self._screen.disconnect(self._active_window_changed_sid)
-
def __stop_activate_cb(self, menu_item):
self._home_activity.get_window().close(1)
@@ -112,7 +119,7 @@ class ActivityPalette(Palette):
self._activity_info = activity_info
client = gconf.client_get_default()
- color = XoColor(client.get_string("/desktop/sugar/user/color"))
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
activity_icon = Icon(file=activity_info.get_icon(),
xo_color=color,
icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
@@ -133,17 +140,8 @@ class ActivityPalette(Palette):
def __start_activate_cb(self, menu_item):
self.popdown(immediate=True)
+ misc.launch(self._activity_info)
- client = gconf.client_get_default()
- xo_color = XoColor(client.get_string('/desktop/sugar/user/color'))
-
- activity_id = activityfactory.create_activity_id()
- launcher.add_launcher(activity_id,
- self._activity_info.get_icon(),
- xo_color)
-
- handle = ActivityHandle(activity_id)
- activityfactory.create(self._activity_info, handle)
class JournalPalette(BasePalette):
def __init__(self, home_activity):
@@ -196,6 +194,7 @@ class JournalPalette(BasePalette):
self._free_space_label.props.label = _('%(free_space)d MB Free') % \
{'free_space': free_space / (1024 * 1024)}
+
class VolumePalette(Palette):
def __init__(self, mount):
Palette.__init__(self, label=mount.get_name())
@@ -245,4 +244,3 @@ class VolumePalette(Palette):
self._progress_bar.props.fraction = fraction
self._free_space_label.props.label = _('%(free_space)d MB Free') % \
{'free_space': free_space / (1024 * 1024)}
-
diff --git a/src/jarabe/view/pulsingicon.py b/src/jarabe/view/pulsingicon.py
index 43ec358..392a404 100644
--- a/src/jarabe/view/pulsingicon.py
+++ b/src/jarabe/view/pulsingicon.py
@@ -21,9 +21,11 @@ import gobject
from sugar.graphics.icon import Icon, CanvasIcon
+
_INTERVAL = 100
_STEP = math.pi / 10 # must be a fraction of pi, for clean caching
+
class Pulser(object):
def __init__(self, icon):
self._pulse_hid = None
@@ -83,6 +85,7 @@ class Pulser(object):
return True
+
class PulsingIcon(Icon):
__gtype_name__ = 'SugarPulsingIcon'
@@ -161,6 +164,7 @@ class PulsingIcon(Icon):
if self._palette is not None:
self._palette.destroy()
+
class CanvasPulsingIcon(CanvasIcon):
__gtype_name__ = 'SugarCanvasPulsingIcon'
diff --git a/src/jarabe/view/service.py b/src/jarabe/view/service.py
index fbcc961..29e46b2 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. <http://www.collabora.co.uk/>
#
# 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,13 +21,13 @@ 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"
+
+_DBUS_SERVICE = 'org.laptop.Shell'
+_DBUS_SHELL_IFACE = 'org.laptop.Shell'
+_DBUS_PATH = '/org/laptop/Shell'
+
class UIService(dbus.service.Object):
"""Provides d-bus service to script the shell's operations
@@ -54,17 +55,8 @@ 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")
+ in_signature='s', out_signature='s')
def GetBundlePath(self, bundle_id):
bundle = bundleregistry.get_registry().get_bundle(bundle_id)
if bundle:
@@ -73,59 +65,26 @@ class UIService(dbus.service.Object):
return ''
@dbus.service.method(_DBUS_SHELL_IFACE,
- in_signature="s", out_signature="b")
+ in_signature='s', out_signature='b')
def ActivateActivity(self, activity_id):
- """Switch to the window related to this activity_id and return a boolean
- indicating if there is a real (ie. not a launcher window) activity
- already open.
+ """Switch to the window related to this activity_id and return a
+ boolean indicating if there is a real (ie. not a launcher window)
+ activity already open.
"""
activity = self._shell_model.get_activity_by_id(activity_id)
if activity is not None and activity.get_window() is not None:
activity.get_window().activate(gtk.get_current_event_time())
- return not activity.props.launching
+ return self._shell_model.get_launcher(activity_id) is None
return False
@dbus.service.method(_DBUS_SHELL_IFACE,
- in_signature="ss", out_signature="")
+ in_signature='ss', out_signature='')
def NotifyLaunch(self, bundle_id, activity_id):
shell.get_model().notify_launch(activity_id, bundle_id)
@dbus.service.method(_DBUS_SHELL_IFACE,
- in_signature="s", out_signature="")
+ in_signature='s', out_signature='')
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)
-
diff --git a/src/jarabe/view/tabbinghandler.py b/src/jarabe/view/tabbinghandler.py
index f52bda3..0889792 100644
--- a/src/jarabe/view/tabbinghandler.py
+++ b/src/jarabe/view/tabbinghandler.py
@@ -21,8 +21,10 @@ import gtk
from jarabe.model import shell
+
_RAISE_DELAY = 250
+
class TabbingHandler(object):
def __init__(self, frame, modifier):
self._frame = frame
@@ -145,4 +147,3 @@ class TabbingHandler(object):
def is_tabbing(self):
return self._tabbing
-
diff --git a/src/jarabe/view/viewsource.py b/src/jarabe/view/viewsource.py
index 9905713..a1c0be3 100644
--- a/src/jarabe/view/viewsource.py
+++ b/src/jarabe/view/viewsource.py
@@ -17,7 +17,6 @@
import os
import logging
-import traceback
from gettext import gettext as _
import gobject
@@ -37,11 +36,13 @@ from sugar.bundle.activitybundle import ActivityBundle
from sugar.datastore import datastore
from sugar import mime
+
_SOURCE_FONT = pango.FontDescription('Monospace %d' % style.FONT_SIZE)
_logger = logging.getLogger('ViewSource')
map_activity_to_window = {}
+
def setup_view_source(activity):
service = activity.get_service()
if service is not None:
@@ -52,9 +53,9 @@ def setup_view_source(activity):
expected_exceptions = ['org.freedesktop.DBus.Error.UnknownMethod',
'org.freedesktop.DBus.Python.NotImplementedError']
if e.get_dbus_name() not in expected_exceptions:
- logging.error(traceback.format_exc())
+ logging.exception('Exception occured in HandleViewSource():')
except Exception:
- logging.error(traceback.format_exc())
+ logging.exception('Exception occured in HandleViewSource():')
window_xid = activity.get_xid()
if window_xid is None:
@@ -76,9 +77,9 @@ def setup_view_source(activity):
expected_exceptions = ['org.freedesktop.DBus.Error.UnknownMethod',
'org.freedesktop.DBus.Python.NotImplementedError']
if e.get_dbus_name() not in expected_exceptions:
- logging.error(traceback.format_exc())
+ logging.exception('Exception occured in GetDocumentPath():')
except Exception:
- logging.error(traceback.format_exc())
+ logging.exception('Exception occured in GetDocumentPath():')
if bundle_path is None and document_path is None:
_logger.debug('Activity without bundle_path nor document_path')
@@ -89,6 +90,7 @@ def setup_view_source(activity):
map_activity_to_window[window_xid] = view_source
view_source.show()
+
class ViewSource(gtk.Window):
__gtype_name__ = 'SugarViewSource'
@@ -129,10 +131,10 @@ class ViewSource(gtk.Window):
file_name = ''
activity_bundle = ActivityBundle(bundle_path)
- command = activity_bundle.get_command()
+ command = activity_bundle.get_command()
if len(command.split(' ')) > 1:
name = command.split(' ')[1].split('.')[0]
- file_name = name + '.py'
+ file_name = name + '.py'
path = os.path.join(activity_bundle.get_path(), file_name)
self._selected_file = path
@@ -195,6 +197,7 @@ class ViewSource(gtk.Window):
else:
self._source_display.file_path = None
+
class DocumentButton(RadioToolButton):
__gtype_name__ = 'SugarDocumentButton'
@@ -244,22 +247,20 @@ class DocumentButton(RadioToolButton):
error_handler=self.__internal_save_error_cb)
def __internal_save_cb(self):
- logging.debug("Saved Source object to datastore.")
+ logging.debug('Saved Source object to datastore.')
self._jobject.destroy()
def __internal_save_error_cb(self, err):
logging.debug('Error saving Source object to datastore: %s', err)
self._jobject.destroy()
+
class Toolbar(gtk.Toolbar):
__gtype_name__ = 'SugarViewSourceToolbar'
__gsignals__ = {
- 'stop-clicked': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([])),
- 'source-selected': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
+ 'stop-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'source-selected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([str])),
}
@@ -309,7 +310,6 @@ class Toolbar(gtk.Toolbar):
stop = ToolButton(icon_name='dialog-cancel')
stop.set_tooltip(_('Close'))
stop.connect('clicked', self.__stop_clicked_cb)
- stop.show()
self.insert(stop, -1)
stop.show()
@@ -340,6 +340,7 @@ class Toolbar(gtk.Toolbar):
if button.props.active:
self.emit('source-selected', path)
+
class FileViewer(gtk.ScrolledWindow):
__gtype_name__ = 'SugarFileViewer'
@@ -406,6 +407,7 @@ class FileViewer(gtk.ScrolledWindow):
file_path = model.get_value(tree_iter, 1)
self.emit('file-selected', file_path)
+
class SourceDisplay(gtk.ScrolledWindow):
__gtype_name__ = 'SugarSourceDisplay'
@@ -462,4 +464,3 @@ class SourceDisplay(gtk.ScrolledWindow):
return self._file_path
file_path = property(_get_file_path, _set_file_path)
-