Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NEWS4
-rw-r--r--services/console/README12
-rwxr-xr-xservices/console/console.py2
-rw-r--r--services/console/interface/Makefile.am6
-rw-r--r--services/console/interface/ps_watcher.py466
-rw-r--r--sugar/graphics/palette.py15
6 files changed, 499 insertions, 6 deletions
diff --git a/NEWS b/NEWS
index 9c8e096..72afe0f 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,10 @@
* Turn off logging by default. Logs may be re-enabled on a per-module basis
by adding environment variables like SHELL_DEBUG and RECORD_DEBUG to
the sugar environment
+
+Snapshot 088c7612e3
+
+* Don't follow the cursor when expanding to secondary palette. (marco)
* #2370 Update spanish translation. (marco)
* #2014 Add icons in the share dropdown in activities. (marco)
diff --git a/services/console/README b/services/console/README
new file mode 100644
index 0000000..a3aa6e1
--- /dev/null
+++ b/services/console/README
@@ -0,0 +1,12 @@
+Defining new tabs in the developer console
+==========================================
+
+The tabs are top-level packages inside 'interface/'.
+
+Each package used as a tab must have a class Interface, instantiatable
+with no arguments, with an attribute 'widget' that is a Gtk widget to be
+placed in the tab. That's it.
+
+Tabs are automatically run under the GLib main loop, dbus-python is set up
+to use it, and the shared dbus-python session-bus connection is expected to
+exist.
diff --git a/services/console/console.py b/services/console/console.py
index ec74b8d..32ff103 100755
--- a/services/console/console.py
+++ b/services/console/console.py
@@ -53,6 +53,7 @@ class Console:
self._load_interface('memphis', 'Memphis')
self._load_interface('logviewer', 'Log Viewer')
self._load_interface('terminal', 'Terminal')
+ self._load_interface('ps_watcher', 'Presence')
main_hbox = gtk.HBox()
main_hbox.pack_start(self.notebook, True, True, 0)
@@ -86,6 +87,7 @@ class Service(dbus.service.Object):
bus = dbus.SessionBus()
name = dbus.service.BusName(CONSOLE_BUS, bus)
+
obj = Service(name)
gtk.main()
diff --git a/services/console/interface/Makefile.am b/services/console/interface/Makefile.am
index 2654a4b..3328c59 100644
--- a/services/console/interface/Makefile.am
+++ b/services/console/interface/Makefile.am
@@ -1,6 +1,6 @@
SUBDIRS = memphis logviewer terminal xo
-sugardir = $(pkgdatadir)/shell/console/interface
+sugardir = $(pkgdatadir)/services/console/interface
sugar_PYTHON = \
- __init__.py
-
+ __init__.py \
+ ps_watcher.py
diff --git a/services/console/interface/ps_watcher.py b/services/console/interface/ps_watcher.py
new file mode 100644
index 0000000..b9138e1
--- /dev/null
+++ b/services/console/interface/ps_watcher.py
@@ -0,0 +1,466 @@
+# Copyright (C) 2007 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
+from hashlib import sha1
+
+import dbus
+from gtk import VBox, Label, TreeView, Expander, ListStore, CellRendererText,\
+ ScrolledWindow, CellRendererToggle
+from gobject import timeout_add
+
+
+logger = logging.getLogger('ps_watcher')
+logging.basicConfig(filename='/tmp/ps_watcher.log')
+logging.getLogger().setLevel(1)
+
+
+PS_NAME = 'org.laptop.Sugar.Presence'
+PS_PATH = '/org/laptop/Sugar/Presence'
+PS_IFACE = PS_NAME
+ACTIVITY_IFACE = PS_IFACE + '.Activity'
+BUDDY_IFACE = PS_IFACE + '.Buddy'
+
+# keep these in sync with the calls to ListStore()
+ACT_COL_PATH = 0
+ACT_COL_WEIGHT = 1
+ACT_COL_STRIKE = 2
+ACT_COL_ID = 3
+ACT_COL_COLOR = 4
+ACT_COL_TYPE = 5
+ACT_COL_NAME = 6
+BUDDY_COL_PATH = 0
+BUDDY_COL_WEIGHT = 1
+BUDDY_COL_STRIKE = 2
+BUDDY_COL_NICK = 3
+BUDDY_COL_OWNER = 4
+BUDDY_COL_COLOR = 5
+BUDDY_COL_IP4 = 6
+BUDDY_COL_CUR_ACT = 7
+BUDDY_COL_KEY_ID = 8
+
+
+class ActivityWatcher(object):
+
+ def __init__(self, ps_watcher, object_path):
+ self.ps_watcher = ps_watcher
+ self.bus = ps_watcher.bus
+ self.proxy = self.bus.get_object(self.ps_watcher.unique_name,
+ object_path)
+ self.iface = dbus.Interface(self.proxy, ACTIVITY_IFACE)
+ self.object_path = object_path
+ self.appearing = True
+ self.disappearing = False
+ timeout_add(5000, self._finish_appearing)
+
+ self.id = '?'
+ self.color = '?'
+ self.type = '?'
+ self.name = '?'
+
+ self.iter = self.ps_watcher.add_activity(self)
+
+ self.iface.GetId(reply_handler=self._on_get_id_success,
+ error_handler=self._on_get_id_failure)
+
+ self.iface.GetColor(reply_handler=self._on_get_color_success,
+ error_handler=self._on_get_color_failure)
+
+ self.iface.GetType(reply_handler=self._on_get_type_success,
+ error_handler=self._on_get_type_failure)
+
+ self.iface.GetName(reply_handler=self._on_get_name_success,
+ error_handler=self._on_get_name_failure)
+
+ def _on_get_id_success(self, ident):
+ self.id = ident
+ self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_ID, ident)
+
+ def _on_get_id_failure(self, e):
+ logger.warning('<Activity %s>.GetId(): %s', self.object_path, e)
+ self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_ID,
+ '!')
+
+ def _on_get_color_success(self, color):
+ self.color = color
+ self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_COLOR,
+ color)
+
+ def _on_get_color_failure(self, e):
+ logger.warning('<Activity %s>.GetColor(): %s', self.object_path, e)
+ self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_COLOR,
+ '!')
+
+ def _on_get_type_success(self, type_):
+ self.type = type_
+ self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_TYPE,
+ type_)
+
+ def _on_get_type_failure(self, e):
+ logger.warning('<Activity %s>.GetType(): %s', self.object_path, e)
+ self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_TYPE,
+ '!')
+
+ def _on_get_name_success(self, name):
+ self.name = name
+ self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_NAME,
+ name)
+
+ def _on_get_name_failure(self, e):
+ logger.warning('<Activity %s>.GetName(): %s', self.object_path, e)
+ self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_NAME,
+ '!')
+
+ def _finish_appearing(self):
+ self.appearing = False
+ self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_WEIGHT,
+ 400)
+ return False
+
+ def disappear(self):
+ self.disappearing = True
+ self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_STRIKE,
+ True)
+ timeout_add(5000, self._finish_disappearing)
+
+ def _finish_disappearing(self):
+ self.ps_watcher.remove_activity(self)
+ return False
+
+
+class BuddyWatcher(object):
+
+ def __init__(self, ps_watcher, object_path):
+ self.ps_watcher = ps_watcher
+ self.bus = ps_watcher.bus
+ self.proxy = self.bus.get_object(self.ps_watcher.unique_name,
+ object_path)
+ self.iface = dbus.Interface(self.proxy, BUDDY_IFACE)
+ self.object_path = object_path
+ self.appearing = True
+ self.disappearing = False
+ timeout_add(5000, self._finish_appearing)
+
+ self.nick = '?'
+ self.owner = False
+ self.color = '?'
+ self.ipv4 = '?'
+ self.cur_act = '?'
+ self.keyid = '?'
+
+ self.iter = self.ps_watcher.add_buddy(self)
+
+ self.iface.GetProperties(reply_handler=self._on_get_props_success,
+ error_handler=self._on_get_props_failure,
+ byte_arrays=True)
+
+ def _on_get_props_success(self, props):
+ # ignore key for now
+ self.nick = props.get('nick', '?')
+ self.owner = props.get('owner', False)
+ self.color = props.get('color', '?')
+ self.ipv4 = props.get('ip4-address', '?')
+ self.ipv4 = props.get('ip4-address', '?')
+ self.cur_act = props.get('current-activity', '?')
+ key = props.get('key', None)
+ if key is not None:
+ self.keyid = sha1(key).hexdigest()[:8] + '...'
+ else:
+ self.keyid = '?'
+ self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_NICK,
+ self.nick)
+ self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_OWNER,
+ self.owner)
+ self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_COLOR,
+ self.color)
+ self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_IP4,
+ self.ipv4)
+ self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_CUR_ACT,
+ self.cur_act)
+ self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_KEY_ID,
+ self.keyid)
+
+ def _on_get_props_failure(self, e):
+ logger.warning('<Buddy %s>.GetProperties(): %s', self.object_path, e)
+ self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_NICK, '!')
+ self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_OWNER,
+ False)
+ self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_COLOR, '!')
+ self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_IP4, '!')
+ self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_CUR_ACT,
+ '!')
+ self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_KEY_ID,
+ '!')
+
+
+ def _finish_appearing(self):
+ self.appearing = False
+ self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_WEIGHT,
+ 400)
+ return False
+
+ def disappear(self):
+ self.disappearing = True
+ self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_STRIKE,
+ True)
+ timeout_add(5000, self._finish_disappearing)
+
+ def _finish_disappearing(self):
+ self.ps_watcher.remove_buddy(self)
+ return False
+
+
+class PresenceServiceWatcher(VBox):
+
+ def __init__(self, bus, unique_name):
+ VBox.__init__(self)
+
+ logger.debug('Starting up PresenceServiceWatcher...')
+ self.bus = bus
+ self.unique_name = unique_name
+ self.proxy = bus.get_object(unique_name, PS_PATH)
+ self.iface = dbus.Interface(self.proxy, PS_IFACE)
+
+ logger.debug('Starting up PresenceServiceWatcher (2)...')
+
+ self.activities = None
+ self.iface.connect_to_signal('ActivityAppeared',
+ self._on_activity_appeared)
+ self.iface.connect_to_signal('ActivityDisappeared',
+ self._on_activity_disappeared)
+ self.iface.GetActivities(reply_handler=self._on_get_activities_success,
+ error_handler=self._on_get_activities_failure)
+
+ self.buddies = None
+ self.iface.connect_to_signal('BuddyAppeared',
+ self._on_buddy_appeared)
+ self.iface.connect_to_signal('BuddyDisappeared',
+ self._on_buddy_disappeared)
+ self.iface.GetBuddies(reply_handler=self._on_get_buddies_success,
+ error_handler=self._on_get_buddies_failure)
+
+ # keep this in sync with the ACT_COL_ constants
+ self.activities_list_store = ListStore(str, # object path
+ int, # weight (bold if new)
+ bool, # strikethrough (dead)
+ str, # ID
+ str, # color
+ str, # type
+ str, # name
+ )
+
+ self.pack_start(Label('Activities:'), False, False)
+
+ self.activities_list = TreeView(self.activities_list_store)
+ c = self.activities_list.insert_column_with_attributes(0,
+ 'Object path', CellRendererText(), text=ACT_COL_PATH,
+ weight=ACT_COL_WEIGHT, strikethrough=ACT_COL_STRIKE)
+ c.set_resizable(True)
+ c.set_sort_column_id(ACT_COL_PATH)
+ c = self.activities_list.insert_column_with_attributes(1, 'ID',
+ CellRendererText(), text=ACT_COL_ID,
+ weight=ACT_COL_WEIGHT, strikethrough=ACT_COL_STRIKE)
+ c.set_resizable(True)
+ c.set_sort_column_id(ACT_COL_ID)
+ c = self.activities_list.insert_column_with_attributes(2, 'Color',
+ CellRendererText(), text=ACT_COL_COLOR,
+ weight=ACT_COL_WEIGHT, strikethrough=ACT_COL_STRIKE)
+ c.set_resizable(True)
+ c.set_sort_column_id(ACT_COL_COLOR)
+ c = self.activities_list.insert_column_with_attributes(3, 'Type',
+ CellRendererText(), text=ACT_COL_TYPE, weight=ACT_COL_WEIGHT,
+ strikethrough=ACT_COL_STRIKE)
+ c.set_resizable(True)
+ c.set_sort_column_id(ACT_COL_TYPE)
+ c = self.activities_list.insert_column_with_attributes(4, 'Name',
+ CellRendererText(), text=ACT_COL_NAME, weight=ACT_COL_WEIGHT,
+ strikethrough=ACT_COL_STRIKE)
+ c.set_resizable(True)
+ c.set_sort_column_id(ACT_COL_NAME)
+
+ scroller = ScrolledWindow()
+ scroller.add(self.activities_list)
+ self.pack_start(scroller)
+
+ # keep this in sync with the BUDDY_COL_ constants
+ self.buddies_list_store = ListStore(str, int, bool, str, bool,
+ str, str, str, str)
+
+ self.pack_start(Label('Buddies:'), False, False)
+ self.buddies_list = TreeView(self.buddies_list_store)
+ c = self.buddies_list.insert_column_with_attributes(0, 'Object path',
+ CellRendererText(), text=BUDDY_COL_PATH,
+ weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE)
+ c.set_resizable(True)
+ c.set_sort_column_id(BUDDY_COL_PATH)
+ c = self.buddies_list.insert_column_with_attributes(1, 'Key ID',
+ CellRendererText(), text=BUDDY_COL_KEY_ID,
+ weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE)
+ c.set_resizable(True)
+ c.set_sort_column_id(BUDDY_COL_KEY_ID)
+ c = self.buddies_list.insert_column_with_attributes(2, 'Nick',
+ CellRendererText(), text=BUDDY_COL_NICK,
+ weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE)
+ c.set_resizable(True)
+ c.set_sort_column_id(BUDDY_COL_NICK)
+ c = self.buddies_list.insert_column_with_attributes(3, 'Owner',
+ CellRendererToggle(), active=BUDDY_COL_OWNER)
+ c = self.buddies_list.insert_column_with_attributes(4, 'Color',
+ CellRendererText(), text=BUDDY_COL_COLOR,
+ weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE)
+ c.set_resizable(True)
+ c.set_sort_column_id(BUDDY_COL_OWNER)
+ c = self.buddies_list.insert_column_with_attributes(5, 'IPv4',
+ CellRendererText(), text=BUDDY_COL_IP4,
+ weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE)
+ c.set_resizable(True)
+ c.set_sort_column_id(BUDDY_COL_IP4)
+ c = self.buddies_list.insert_column_with_attributes(6, 'CurAct',
+ CellRendererText(), text=BUDDY_COL_CUR_ACT,
+ weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE)
+ c.set_resizable(True)
+ c.set_sort_column_id(BUDDY_COL_CUR_ACT)
+
+ scroller = ScrolledWindow()
+ scroller.add(self.buddies_list)
+ self.pack_start(scroller)
+
+ self.iface.connect_to_signal('ActivityInvitation',
+ self._on_activity_invitation)
+ self.iface.connect_to_signal('PrivateInvitation',
+ self._on_private_invitation)
+
+ def _on_get_activities_success(self, paths):
+ logger.debug('PS GetActivities() returned %r', paths)
+ self.activities = {}
+ for path in paths:
+ self.activities[path] = ActivityWatcher(self, path)
+
+ def _on_get_activities_failure(self, e):
+ logger.warning('PS GetActivities() failed with %s', e)
+
+ def add_activity(self, act):
+ path = act.object_path
+ if path.startswith('/org/laptop/Sugar/Presence/Activities/'):
+ path = '.../' + path[38:]
+ return self.activities_list_store.append((path, 700, False,
+ act.id, act.color, act.type, act.name))
+
+ def remove_activity(self, act):
+ self.activities.pop(act.object_path, None)
+ self.activities_list_store.remove(act.iter)
+
+ def _on_activity_appeared(self, path):
+ if self.activities is None:
+ return
+ logger.debug('PS emitted ActivityAppeared("%s")', path)
+ self.activities[path] = ActivityWatcher(self, path)
+
+ def _on_activity_disappeared(self, path):
+ if self.activities is None:
+ return
+ logger.debug('PS emitted ActivityDisappeared("%s")', path)
+ act = self.activities.get(path)
+ if act is None:
+ logger.warning('Trying to remove activity "%s" which is already '
+ 'absent', path)
+ else:
+ # we don't remove the activity straight away, just cross it out
+ act.disappear()
+
+ def _on_activity_invitation(self, path):
+ logger.debug('PS emitted ActivityInvitation("%s")', path)
+
+ def _on_private_invitation(self, bus_name, conn, channel):
+ logger.debug('PS emitted PrivateInvitation("%s", "%s", "%s")',
+ bus_name, conn, channel)
+
+ def _on_get_buddies_success(self, paths):
+ logger.debug('PS GetBuddies() returned %r', paths)
+ self.buddies = {}
+ for path in paths:
+ self.buddies[path] = BuddyWatcher(self, path)
+
+ def _on_get_buddies_failure(self, e):
+ logger.warning('PS GetBuddies() failed with %s', e)
+
+ def add_buddy(self, b):
+ path = b.object_path
+ if path.startswith('/org/laptop/Sugar/Presence/Buddies/'):
+ path = '.../' + path[35:]
+ return self.buddies_list_store.append((path, 700, False,
+ b.nick, b.owner, b.color, b.ipv4, b.cur_act, b.keyid))
+
+ def remove_buddy(self, b):
+ self.buddies.pop(b.object_path, None)
+ self.buddies_list_store.remove(b.iter)
+
+ def _on_buddy_appeared(self, path):
+ if self.buddies is None:
+ return
+ logger.debug('PS emitted BuddyAppeared("%s")', path)
+ self.buddies[path] = BuddyWatcher(self, path)
+
+ def _on_buddy_disappeared(self, path):
+ if self.buddies is None:
+ return
+ logger.debug('PS emitted BuddyDisappeared("%s")', path)
+ b = self.buddies.get(path)
+ if b is None:
+ logger.warning('Trying to remove buddy "%s" which is already '
+ 'absent', path)
+ else:
+ # we don't remove the activity straight away, just cross it out
+ b.disappear()
+
+
+class PresenceServiceNameWatcher(VBox):
+
+ def __init__(self, bus):
+ VBox.__init__(self)
+
+ self.bus = bus
+
+ self.label = Label('Looking for Presence Service...')
+ bus.watch_name_owner(PS_NAME, self.on_name_owner_change)
+
+ self.pack_start(self.label, False, False)
+ self.ps_watcher = None
+
+ self.show_all()
+
+ def on_name_owner_change(self, owner):
+ try:
+ if owner:
+ self.label.set_text('Presence Service running: unique name %s'
+ % owner)
+ if self.ps_watcher is not None:
+ self.remove(self.ps_watcher)
+ self.ps_watcher = PresenceServiceWatcher(self.bus, owner)
+ self.pack_start(self.ps_watcher)
+ self.show_all()
+ else:
+ self.label.set_text('Presence Service not running')
+ if self.ps_watcher is not None:
+ self.remove(self.ps_watcher)
+ self.ps_watcher = None
+ except Exception, e:
+ logger.warning('%s', e)
+
+
+class Interface(object):
+ def __init__(self):
+ self.widget = PresenceServiceNameWatcher(dbus.SessionBus())
diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py
index 8996138..b779eec 100644
--- a/sugar/graphics/palette.py
+++ b/sugar/graphics/palette.py
@@ -69,6 +69,8 @@ class Palette(gobject.GObject):
gobject.GObject.__init__(self)
self._full_request = [0, 0]
+ self._cursor_x = 0
+ self._cursor_y = 0
self._state = self._SECONDARY
self._invoker = None
self._group_id = None
@@ -250,6 +252,12 @@ class Palette(gobject.GObject):
self._set_state(state)
+ def _update_cursor_position(self):
+ display = gtk.gdk.display_get_default()
+ screen, x, y, mask = display.get_pointer()
+ self._cursor_x = x
+ self._cursor_y = y
+
def _update_position(self):
x = y = 0
@@ -259,11 +267,11 @@ class Palette(gobject.GObject):
position = self._position
if position == self.AT_CURSOR:
- display = gtk.gdk.display_get_default()
- screen, x, y, mask = display.get_pointer()
dist = style.PALETTE_CURSOR_DISTANCE
+ rect = gtk.gdk.Rectangle(self._cursor_x - dist,
+ self._cursor_y - dist,
+ dist * 2, dist * 2)
- rect = gtk.gdk.Rectangle(x - dist, y - dist, dist * 2, dist * 2)
x, y = self._get_at_cursor_position(rect)
elif position == self.AROUND:
x, y = self._get_around_position()
@@ -282,6 +290,7 @@ class Palette(gobject.GObject):
if self._up:
return
+ self._update_cursor_position()
self._update_full_request()
self._invoker.connect_to_parent()