Add a basic Presence Service tab to the developer console.
* Newly added activities and buddies appear in bold for 5 seconds. * Removed activities and buddies stay in the list with strikethrough applied for 5 seconds, after which they vanish. * Columns other than "object path" don't do anything. * In the object path column, the common prefix is replaced by ".../".
This commit is contained in:
parent
9ac5d38e90
commit
0afad46773
12
services/console/README
Normal file
12
services/console/README
Normal file
@ -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.
|
@ -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()
|
||||
|
@ -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
|
||||
|
344
services/console/interface/ps_watcher.py
Normal file
344
services/console/interface/ps_watcher.py
Normal file
@ -0,0 +1,344 @@
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
|
||||
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.iter = self.ps_watcher.add_buddy(self)
|
||||
|
||||
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)
|
||||
self.activities_list.insert_column_with_attributes(0, 'Object path',
|
||||
CellRendererText(), text=ACT_COL_PATH,
|
||||
weight=ACT_COL_WEIGHT, strikethrough=ACT_COL_STRIKE)
|
||||
self.activities_list.insert_column_with_attributes(1, 'ID',
|
||||
CellRendererText(), text=ACT_COL_ID,
|
||||
weight=ACT_COL_WEIGHT, strikethrough=ACT_COL_STRIKE)
|
||||
self.activities_list.insert_column_with_attributes(2, 'Color',
|
||||
CellRendererText(), text=ACT_COL_COLOR,
|
||||
weight=ACT_COL_WEIGHT, strikethrough=ACT_COL_STRIKE)
|
||||
self.activities_list.insert_column_with_attributes(3, 'Type',
|
||||
CellRendererText(), text=ACT_COL_TYPE, weight=ACT_COL_WEIGHT,
|
||||
strikethrough=ACT_COL_STRIKE)
|
||||
self.activities_list.insert_column_with_attributes(4, 'Name',
|
||||
CellRendererText(), text=ACT_COL_NAME, weight=ACT_COL_WEIGHT,
|
||||
strikethrough=ACT_COL_STRIKE)
|
||||
|
||||
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)
|
||||
|
||||
self.pack_start(Label('Buddies:'), False, False)
|
||||
self.buddies_list = TreeView(self.buddies_list_store)
|
||||
self.buddies_list.insert_column_with_attributes(0, 'Object path',
|
||||
CellRendererText(), text=BUDDY_COL_PATH,
|
||||
weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE)
|
||||
self.buddies_list.insert_column_with_attributes(1, 'Nick',
|
||||
CellRendererText(), text=BUDDY_COL_NICK,
|
||||
weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE)
|
||||
self.buddies_list.insert_column_with_attributes(2, 'Owner',
|
||||
CellRendererToggle(), active=BUDDY_COL_OWNER)
|
||||
self.buddies_list.insert_column_with_attributes(3, 'Color',
|
||||
CellRendererText(), text=BUDDY_COL_COLOR,
|
||||
weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE)
|
||||
self.buddies_list.insert_column_with_attributes(4, 'IPv4',
|
||||
CellRendererText(), text=BUDDY_COL_IP4,
|
||||
weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE)
|
||||
self.buddies_list.insert_column_with_attributes(5, 'CurAct',
|
||||
CellRendererText(), text=BUDDY_COL_CUR_ACT,
|
||||
weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE)
|
||||
|
||||
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))
|
||||
|
||||
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())
|
Loading…
Reference in New Issue
Block a user