8bcdb8f3dd
killing activities. We will need to implement some sort of shutdown notification system but... for now this works.
516 lines
16 KiB
Python
Executable File
516 lines
16 KiB
Python
Executable File
import dbus
|
|
import dbus.service
|
|
import dbus.glib
|
|
|
|
import pygtk
|
|
pygtk.require('2.0')
|
|
import gtk
|
|
import pango
|
|
import gobject
|
|
|
|
import sugar.util
|
|
from sugar.session.LogWriter import LogWriter
|
|
from sugar.shell.PresenceWindow import PresenceWindow
|
|
from sugar.chat.ChatWindow import ChatWindow
|
|
from sugar.shell.Owner import ShellOwner
|
|
from sugar.shell.StartPage import StartPage
|
|
from sugar.shell.WindowManager import WindowManager
|
|
from sugar.chat.GroupChat import GroupChat
|
|
|
|
class ActivityHost(dbus.service.Object):
|
|
|
|
def __init__(self, activity_container, activity_name, default_type, activity_id = None):
|
|
self.activity_name = activity_name
|
|
self.ellipsize_tab = False
|
|
|
|
self.activity_container = activity_container
|
|
|
|
if activity_id is None:
|
|
self.activity_id = sugar.util.unique_id()
|
|
else:
|
|
self.activity_id = activity_id
|
|
self._default_type = default_type
|
|
|
|
self.dbus_object_name = "/com/redhat/Sugar/Shell/Activities/%s" % self.activity_id
|
|
|
|
dbus.service.Object.__init__(self, activity_container.service, self.dbus_object_name)
|
|
self.socket = gtk.Socket()
|
|
self.socket.set_data("sugar-activity", self)
|
|
self.socket.show()
|
|
|
|
hbox = gtk.HBox(False, 4);
|
|
|
|
self.tab_activity_image = gtk.Image()
|
|
self.tab_activity_image.set_from_stock(gtk.STOCK_CONVERT, gtk.ICON_SIZE_MENU)
|
|
hbox.pack_start(self.tab_activity_image)
|
|
#self.tab_activity_image.show()
|
|
|
|
self.label_hbox = gtk.HBox(False, 4);
|
|
self.label_hbox.connect("style-set", self.__tab_label_style_set_cb)
|
|
hbox.pack_start(self.label_hbox)
|
|
|
|
self.tab_label = gtk.Label(self.activity_name)
|
|
self.tab_label.set_single_line_mode(True)
|
|
self.tab_label.set_alignment(0.0, 0.5)
|
|
self.tab_label.set_padding(0, 0)
|
|
self.tab_label.show()
|
|
|
|
close_image = gtk.Image()
|
|
close_image.set_from_stock (gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
|
|
close_image.show()
|
|
|
|
self.tab_close_button = gtk.Button()
|
|
rcstyle = gtk.RcStyle();
|
|
rcstyle.xthickness = rcstyle.ythickness = 0;
|
|
self.tab_close_button.modify_style (rcstyle);
|
|
self.tab_close_button.add(close_image)
|
|
self.tab_close_button.set_relief(gtk.RELIEF_NONE)
|
|
self.tab_close_button.set_focus_on_click(False)
|
|
self.tab_close_button.connect("clicked", self.tab_close_button_clicked)
|
|
|
|
self.label_hbox.pack_start(self.tab_label)
|
|
self.label_hbox.pack_start(self.tab_close_button, False, False, 0)
|
|
self.label_hbox.show()
|
|
|
|
hbox.show()
|
|
|
|
self._create_chat()
|
|
|
|
notebook = self.activity_container.notebook
|
|
index = notebook.append_page(self.socket, hbox)
|
|
notebook.set_current_page(index)
|
|
|
|
def _create_chat(self):
|
|
self._group_chat = GroupChat(self)
|
|
|
|
def get_chat(self):
|
|
return self._group_chat
|
|
|
|
def get_default_type(self):
|
|
return self._default_type
|
|
|
|
def __close_button_clicked_reply_cb(self):
|
|
pass
|
|
|
|
def __close_button_clicked_error_cb(self, error):
|
|
pass
|
|
|
|
def publish(self):
|
|
self._group_chat.publish()
|
|
self.peer_service.publish()
|
|
|
|
def tab_close_button_clicked(self, button):
|
|
self.peer_service.close_from_user(reply_handler = self.__close_button_clicked_reply_cb, \
|
|
error_handler = self.__close_button_clicked_error_cb)
|
|
|
|
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
|
|
in_signature="", \
|
|
out_signature="t")
|
|
def get_host_xembed_id(self):
|
|
window_id = self.socket.get_id()
|
|
#print "window_id = %d"%window_id
|
|
return window_id
|
|
|
|
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
|
|
in_signature="ss", \
|
|
out_signature="")
|
|
def set_peer_service_name(self, peer_service_name, peer_object_name):
|
|
#print "peer_service_name = %s, peer_object_name = %s"%(peer_service_name, peer_object_name)
|
|
self.__peer_service_name = peer_service_name
|
|
self.__peer_object_name = peer_object_name
|
|
self.peer_service = dbus.Interface(self.activity_container.bus.get_object( \
|
|
self.__peer_service_name, self.__peer_object_name), \
|
|
"com.redhat.Sugar.Activity")
|
|
|
|
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
|
|
in_signature="b", \
|
|
out_signature="")
|
|
def set_ellipsize_tab(self, ellipsize):
|
|
self.ellipsize_tab = True
|
|
self.update_tab_size()
|
|
|
|
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
|
|
in_signature="b", \
|
|
out_signature="")
|
|
def set_can_close(self, can_close):
|
|
if can_close:
|
|
self.tab_close_button.show()
|
|
else:
|
|
self.tab_close_button.hide()
|
|
|
|
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
|
|
in_signature="b", \
|
|
out_signature="")
|
|
def set_tab_show_icon(self, show_icon):
|
|
if show_icon:
|
|
self.tab_activity_image.show()
|
|
else:
|
|
self.tab_activity_image.hide()
|
|
|
|
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
|
|
in_signature="b", \
|
|
out_signature="")
|
|
def set_has_changes(self, has_changes):
|
|
if has_changes:
|
|
attrs = pango.AttrList()
|
|
attrs.insert(pango.AttrForeground(50000, 0, 0, 0, -1))
|
|
attrs.insert(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1))
|
|
self.tab_label.set_attributes(attrs)
|
|
else:
|
|
self.tab_label.set_attributes(pango.AttrList())
|
|
|
|
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
|
|
in_signature="s", \
|
|
out_signature="")
|
|
def set_tab_text(self, text):
|
|
self.tab_label.set_text(text)
|
|
|
|
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
|
|
in_signature="ayibiiii", \
|
|
out_signature="")
|
|
def set_tab_icon(self, data, colorspace, has_alpha, bits_per_sample, width, height, rowstride):
|
|
#print "width=%d, height=%d"%(width, height)
|
|
#print " data = ", data
|
|
pixstr = ""
|
|
for c in data:
|
|
# Work around for a bug in dbus < 0.61 where integers
|
|
# are not correctly marshalled
|
|
if c < 0:
|
|
c += 256
|
|
pixstr += chr(c)
|
|
|
|
pixbuf = gtk.gdk.pixbuf_new_from_data(pixstr, colorspace, has_alpha, bits_per_sample, width, height, rowstride)
|
|
#print pixbuf
|
|
self.tab_activity_image.set_from_pixbuf(pixbuf)
|
|
|
|
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
|
|
in_signature="", \
|
|
out_signature="")
|
|
def shutdown(self):
|
|
#print "shutdown"
|
|
for owner, activity in self.activity_container.activities[:]:
|
|
if activity == self:
|
|
self.activity_container.activities.remove((owner, activity))
|
|
|
|
for i in range(self.activity_container.notebook.get_n_pages()):
|
|
child = self.activity_container.notebook.get_nth_page(i)
|
|
if child == self.socket:
|
|
#print "found child"
|
|
self.activity_container.notebook.remove_page(i)
|
|
break
|
|
|
|
del self
|
|
|
|
def get_host_activity_id(self):
|
|
"""Real function that the shell should use for getting the
|
|
activity's ID."""
|
|
return self.activity_id
|
|
|
|
def get_id(self):
|
|
"""Interface-type function to match activity.Activity's
|
|
get_id() function."""
|
|
return self.activity_id
|
|
|
|
def get_object_path(self):
|
|
return self.dbus_object_name
|
|
|
|
def update_tab_size(self):
|
|
if self.ellipsize_tab:
|
|
self.tab_label.set_ellipsize(pango.ELLIPSIZE_END)
|
|
|
|
context = self.label_hbox.get_pango_context()
|
|
font_desc = self.label_hbox.style.font_desc
|
|
metrics = context.get_metrics(font_desc, context.get_language())
|
|
char_width = metrics.get_approximate_digit_width()
|
|
[w, h] = self.__get_close_icon_size()
|
|
tab_width = 15 * pango.PIXELS(char_width) + 2 * w
|
|
self.label_hbox.set_size_request(tab_width, -1);
|
|
else:
|
|
self.tab_label.set_ellipsize(pango.ELLIPSIZE_NONE)
|
|
self.label_hbox.set_size_request(-1, -1)
|
|
|
|
def __get_close_icon_size(self):
|
|
settings = self.label_hbox.get_settings()
|
|
return gtk.icon_size_lookup_for_settings(settings, gtk.ICON_SIZE_MENU)
|
|
|
|
def __tab_label_style_set_cb(self, widget, previous_style):
|
|
[w, h] = self.__get_close_icon_size()
|
|
self.tab_close_button.set_size_request (w + 5, h + 2)
|
|
self.update_tab_size()
|
|
|
|
class ActivityContainerSignalHelper(gobject.GObject):
|
|
"""A gobject whose sole purpose is to distribute signals for
|
|
an ActivityContainer object."""
|
|
|
|
__gsignals__ = {
|
|
'local-activity-started': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
|
|
'local-activity-ended': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT]))
|
|
}
|
|
|
|
def __init__(self, parent):
|
|
gobject.GObject.__init__(self)
|
|
self._parent = parent
|
|
|
|
def activity_started(self, activity_id):
|
|
self.emit('local-activity-started', self._parent, activity_id)
|
|
|
|
def activity_ended(self, activity_id):
|
|
self.emit('local-activity-ended', self._parent, activity_id)
|
|
|
|
class ActivityContainer(dbus.service.Object):
|
|
|
|
def __init__(self, service, bus):
|
|
self.activities = []
|
|
|
|
self.bus = bus
|
|
self.service = service
|
|
|
|
self._signal_helper = ActivityContainerSignalHelper(self)
|
|
|
|
dbus.service.Object.__init__(self, self.service, "/com/redhat/Sugar/Shell/ActivityContainer")
|
|
bus.add_signal_receiver(self.name_owner_changed, dbus_interface = "org.freedesktop.DBus", signal_name = "NameOwnerChanged")
|
|
|
|
self.window = gtk.Window()
|
|
self.window.connect("key-press-event", self.__key_press_event_cb)
|
|
self.window.set_title("OLPC Sugar")
|
|
|
|
self._fullscreen = False
|
|
|
|
self.notebook = gtk.Notebook()
|
|
|
|
tab_label = gtk.Label("Everyone")
|
|
self._start_page = StartPage(self._signal_helper)
|
|
self.notebook.append_page(self._start_page, tab_label)
|
|
self._start_page.show()
|
|
|
|
self.notebook.show()
|
|
self.notebook.connect("switch-page", self.notebook_tab_changed)
|
|
self.window.add(self.notebook)
|
|
|
|
self.window.connect("destroy", lambda w: gtk.main_quit())
|
|
|
|
self.current_activity = None
|
|
|
|
# Create our owner service
|
|
self._owner = ShellOwner()
|
|
|
|
self._presence_window = PresenceWindow(self)
|
|
self._presence_window.set_transient_for(self.window)
|
|
self._presence_window.set_decorated(False)
|
|
self._presence_window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DOCK)
|
|
self._presence_window.set_skip_taskbar_hint(True)
|
|
|
|
wm = WindowManager(self._presence_window)
|
|
|
|
wm.set_width(0.15, WindowManager.SCREEN_RELATIVE)
|
|
wm.set_height(1.0, WindowManager.SCREEN_RELATIVE)
|
|
wm.set_position(WindowManager.LEFT)
|
|
wm.manage()
|
|
|
|
self._chat_window = ChatWindow()
|
|
self._chat_window.set_transient_for(self.window)
|
|
self._chat_window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DOCK)
|
|
self._chat_window.set_decorated(False)
|
|
self._chat_window.set_skip_taskbar_hint(True)
|
|
|
|
self._chat_wm = WindowManager(self._chat_window)
|
|
|
|
self._chat_wm.set_width(0.5, WindowManager.SCREEN_RELATIVE)
|
|
self._chat_wm.set_height(0.5, WindowManager.SCREEN_RELATIVE)
|
|
self._chat_wm.set_position(WindowManager.TOP)
|
|
self._chat_wm.manage()
|
|
|
|
def show(self):
|
|
self.window.show()
|
|
|
|
def __focus_reply_cb(self):
|
|
pass
|
|
|
|
def __focus_error_cb(self, error):
|
|
pass
|
|
|
|
def set_current_activity(self, activity):
|
|
self.current_activity = activity
|
|
self._presence_window.set_activity(activity)
|
|
|
|
if activity:
|
|
host_chat = activity.get_chat()
|
|
self._chat_window.set_chat(host_chat)
|
|
|
|
# For some reason the substitution screw up window position
|
|
self._chat_wm.update()
|
|
|
|
def notebook_tab_changed(self, notebook, page, page_number):
|
|
new_activity = notebook.get_nth_page(page_number).get_data("sugar-activity")
|
|
|
|
if self.current_activity != None:
|
|
if self.has_activity(self.current_activity):
|
|
self.current_activity.peer_service.lost_focus(reply_handler = self.__focus_reply_cb, error_handler = self.__focus_error_cb)
|
|
|
|
#if self.has_activity(new_activity):
|
|
self.set_current_activity(new_activity)
|
|
|
|
if self.current_activity != None:
|
|
if self.has_activity(self.current_activity):
|
|
self.current_activity.peer_service.got_focus(reply_handler = self.__focus_reply_cb, error_handler = self.__focus_error_cb)
|
|
|
|
|
|
def has_activity(self, activity_to_check_for):
|
|
for owner, activity in self.activities[:]:
|
|
if activity_to_check_for == activity:
|
|
return True
|
|
return False
|
|
|
|
|
|
def name_owner_changed(self, service_name, old_service_name, new_service_name):
|
|
#print "in name_owner_changed: svc=%s oldsvc=%s newsvc=%s"%(service_name, old_service_name, new_service_name)
|
|
for owner, activity in self.activities[:]:
|
|
if owner == old_service_name:
|
|
activity_id = activity.get_host_activity_id()
|
|
self._signal_helper.activity_ended(activity_id)
|
|
self.activities.remove((owner, activity))
|
|
#self.__print_activities()
|
|
|
|
|
|
@dbus.service.method("com.redhat.Sugar.Shell.ActivityContainer", \
|
|
in_signature="ss", \
|
|
out_signature="s", \
|
|
sender_keyword="sender")
|
|
def add_activity(self, activity_name, default_type, sender):
|
|
#print "hello world, activity_name = '%s', sender = '%s'"%(activity_name, sender)
|
|
activity = ActivityHost(self, activity_name, default_type)
|
|
self.activities.append((sender, activity))
|
|
|
|
activity_id = activity.get_host_activity_id()
|
|
self._signal_helper.activity_started(activity_id)
|
|
|
|
self.current_activity = activity
|
|
return activity_id
|
|
|
|
@dbus.service.method("com.redhat.Sugar.Shell.ActivityContainer", \
|
|
in_signature="sss", \
|
|
sender_keyword="sender")
|
|
def add_activity_with_id(self, activity_name, default_type, activity_id, sender):
|
|
activity = ActivityHost(self, activity_name, default_type, activity_id)
|
|
self.activities.append((sender, activity))
|
|
activity_id = activity.get_host_activity_id()
|
|
self._signal_helper.activity_started(activity_id)
|
|
self.current_activity = activity
|
|
|
|
def __print_activities(self):
|
|
print "__print_activities: %d activities registered" % len(self.activities)
|
|
i = 0
|
|
for owner, activity in self.activities:
|
|
print " %d: owner=%s activity_object_name=%s" % (i, owner, activity.dbus_object_name)
|
|
i += 1
|
|
|
|
def __key_press_event_cb(self, window, event):
|
|
if event.keyval == gtk.keysyms.F11:
|
|
if self._fullscreen:
|
|
window.unfullscreen()
|
|
self._fullscreen = False
|
|
else:
|
|
window.fullscreen()
|
|
self._fullscreen = True
|
|
|
|
class ConsoleLogger(dbus.service.Object):
|
|
def __init__(self):
|
|
session_bus = dbus.SessionBus()
|
|
bus_name = dbus.service.BusName('com.redhat.Sugar.Logger', bus=session_bus)
|
|
object_path = '/com/redhat/Sugar/Logger'
|
|
dbus.service.Object.__init__(self, bus_name, object_path)
|
|
|
|
self._window = gtk.Window()
|
|
self._window.set_title("Console")
|
|
self._window.set_default_size(640, 480)
|
|
self._window.connect("delete_event", lambda w, e: w.hide_on_delete())
|
|
|
|
self._nb = gtk.Notebook()
|
|
self._window.add(self._nb)
|
|
self._nb.show()
|
|
|
|
self._consoles = {}
|
|
|
|
def set_parent_window(self, window):
|
|
window.connect("key-press-event", self.__key_press_event_cb)
|
|
self._window.connect("key-press-event", self.__key_press_event_cb)
|
|
|
|
def __key_press_event_cb(self, window, event):
|
|
if event.keyval == gtk.keysyms.d and \
|
|
event.state & gtk.gdk.CONTROL_MASK:
|
|
if self._window.get_property('visible'):
|
|
self._window.hide()
|
|
else:
|
|
self._window.show()
|
|
|
|
def _create_console(self, application):
|
|
sw = gtk.ScrolledWindow()
|
|
sw.set_policy(gtk.POLICY_AUTOMATIC,
|
|
gtk.POLICY_AUTOMATIC)
|
|
|
|
console = gtk.TextView()
|
|
console.set_wrap_mode(gtk.WRAP_WORD)
|
|
|
|
sw.add(console)
|
|
console.show()
|
|
|
|
self._nb.append_page(sw, gtk.Label(application))
|
|
sw.show()
|
|
|
|
return console
|
|
|
|
@dbus.service.method('com.redhat.Sugar.Logger')
|
|
def log(self, application, message):
|
|
if self._consoles.has_key(application):
|
|
console = self._consoles[application]
|
|
else:
|
|
console = self._create_console(application)
|
|
self._consoles[application] = console
|
|
|
|
buf = console.get_buffer()
|
|
buf.insert(buf.get_end_iter(), message)
|
|
|
|
class Shell(gobject.GObject):
|
|
__gsignals__ = {
|
|
'close': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
([])),
|
|
}
|
|
|
|
def __init__(self):
|
|
gobject.GObject.__init__(self)
|
|
|
|
def start(self):
|
|
console = ConsoleLogger()
|
|
|
|
log_writer = LogWriter("Shell", False)
|
|
log_writer.start()
|
|
|
|
session_bus = dbus.SessionBus()
|
|
service = dbus.service.BusName("com.redhat.Sugar.Shell", bus=session_bus)
|
|
|
|
activity_container = ActivityContainer(service, session_bus)
|
|
activity_container.window.connect('destroy', self.__activity_container_destroy_cb)
|
|
activity_container.show()
|
|
|
|
wm = WindowManager(activity_container.window)
|
|
wm.set_width(640, WindowManager.ABSOLUTE)
|
|
wm.set_height(480, WindowManager.ABSOLUTE)
|
|
wm.set_position(WindowManager.CENTER)
|
|
wm.show()
|
|
wm.manage()
|
|
|
|
console.set_parent_window(activity_container.window)
|
|
|
|
def __activity_container_destroy_cb(self, activity_container):
|
|
self.emit('close')
|
|
|
|
if __name__ == "__main__":
|
|
shell = Shell()
|
|
shell.start()
|
|
try:
|
|
gtk.main()
|
|
except KeyboardInterrupt:
|
|
print 'Ctrl+c pressed, exiting...'
|