Merge branch 'master' of git+ssh://dev.laptop.org/git/sugar

Conflicts:

	NEWS
This commit is contained in:
Simon McVittie 2007-10-30 12:38:32 +00:00
commit 4391d4777a
81 changed files with 824 additions and 3481 deletions

7
NEWS
View File

@ -1,5 +1,12 @@
* #4503: Do some standard Tubes boilerplate in sugar.presence, so activities
don't have to (smcv)
* #4428 Revert to the trial-3 frame behavior (marco)
* Initial push for the sugar control panel (erikos)
* #4358: pydoc strings for sugar.activity.Activity
Snapshot 176262f2e9
* Added morgs NotifyAlert (timed one button alert) to the alert api (erikos)
Snapshot fdb4e49b14

View File

@ -45,7 +45,6 @@ AC_OUTPUT([
Makefile
bin/Makefile
data/Makefile
data/icons/Makefile
lib/Makefile
lib/sugar/Makefile
lib/sugar/activity/Makefile
@ -67,22 +66,5 @@ shell/view/home/Makefile
shell/model/Makefile
shell/model/devices/Makefile
shell/model/devices/network/Makefile
services/console/lib/Makefile
services/console/lib/graphics/Makefile
services/console/lib/procmem/Makefile
services/console/lib/net/Makefile
services/console/lib/ui/Makefile
services/console/Makefile
services/console/interface/Makefile
services/console/interface/xo/Makefile
services/console/interface/memphis/plugins/clean_size/Makefile
services/console/interface/memphis/plugins/smaps/Makefile
services/console/interface/memphis/plugins/Makefile
services/console/interface/memphis/plugins/memphis_init/Makefile
services/console/interface/memphis/plugins/cpu/Makefile
services/console/interface/memphis/Makefile
services/console/interface/network/Makefile
services/console/interface/logviewer/Makefile
services/console/interface/xserver/Makefile
po/Makefile.in
])

View File

@ -1,5 +1,3 @@
SUBDIRS = icons
sugar.gtkrc: gtkrc.em
$(srcdir)/em.py -D theme=\'sugar\' $(srcdir)/gtkrc.em > \
$(top_builddir)/data/sugar.gtkrc

View File

@ -1,9 +0,0 @@
iconsdir = $(pkgdatadir)/data/icons
icons_DATA = \
arrow_NE.svg \
arrow_NW.svg \
arrow_SE.svg \
arrow_SW.svg
EXTRA_DIST = \
$(icons_DATA)

View File

@ -1,7 +0,0 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="arrow_x5F_NE">
<g display="inline">
<g>
<path d="M20.154,15.718c-0.896,0-1.794,0.344-2.481,1.028c-1.373,1.372-1.373,3.598,0,4.969c0.686,0.686,1.585,1.026,2.481,1.026 h7.137L16.745,33.29c-0.633,0.635-1.026,1.514-1.029,2.482c0.003,1.939,1.576,3.512,3.514,3.512c0.972,0,1.848-0.395,2.483-1.028 l10.546-10.545v7.136c0,0.898,0.342,1.799,1.029,2.482c1.369,1.373,3.595,1.371,4.965,0c0.687-0.686,1.028-1.588,1.031-2.482 V15.716L20.154,15.718z"/>
</g>
</g>
</g></svg>

Before

Width:  |  Height:  |  Size: 839 B

View File

@ -1,7 +0,0 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="arrow_x5F_NW">
<g display="inline">
<g>
<path d="M38.255,33.287L27.71,22.741h7.136c0.898,0,1.799-0.342,2.482-1.029c1.373-1.369,1.371-3.595,0-4.965 c-0.686-0.687-1.588-1.029-2.482-1.031H15.715l0.002,19.13c0,0.896,0.344,1.794,1.028,2.481c1.372,1.373,3.598,1.373,4.969,0 c0.686-0.686,1.026-1.585,1.026-2.481v-7.137L33.29,38.255c0.635,0.633,1.514,1.026,2.482,1.029 c1.939-0.003,3.512-1.576,3.512-3.514C39.284,34.799,38.889,33.923,38.255,33.287z"/>
</g>
</g>
</g></svg>

Before

Width:  |  Height:  |  Size: 845 B

View File

@ -1,7 +0,0 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="arrow_x5F_SE">
<g display="inline">
<g>
<path d="M39.282,20.154c0-0.896-0.344-1.794-1.028-2.481c-1.372-1.373-3.598-1.373-4.969,0c-0.686,0.686-1.026,1.585-1.026,2.481 v7.137L21.709,16.745c-0.635-0.633-1.514-1.026-2.482-1.029c-1.939,0.003-3.512,1.576-3.512,3.514 c0,0.972,0.395,1.848,1.028,2.483l10.545,10.546h-7.136c-0.898,0-1.799,0.342-2.482,1.029c-1.373,1.369-1.371,3.595,0,4.965 c0.686,0.687,1.588,1.028,2.482,1.031h19.131L39.282,20.154z"/>
</g>
</g>
</g></svg>

Before

Width:  |  Height:  |  Size: 844 B

View File

@ -1,7 +0,0 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="arrow_x5F_SW">
<g>
<g>
<path d="M34.845,32.259h-7.137L38.254,21.71c0.633-0.635,1.026-1.514,1.029-2.482c-0.003-1.939-1.576-3.512-3.514-3.512 c-0.972,0-1.848,0.395-2.483,1.028L22.741,27.289v-7.136c0-0.898-0.342-1.799-1.029-2.482c-1.369-1.373-3.595-1.371-4.965,0 c-0.687,0.686-1.029,1.588-1.031,2.482v19.131l19.13-0.002c0.896,0,1.794-0.344,2.481-1.028c1.373-1.372,1.373-3.598,0-4.969 C36.641,32.6,35.742,32.259,34.845,32.259z"/>
</g>
</g>
</g></svg>

Before

Width:  |  Height:  |  Size: 827 B

View File

@ -1,7 +1,31 @@
"""Base class for Python-coded activities
"""Base class for activities written in Python
This is currently the only reference for what an
This is currently the only definitive reference for what an
activity must do to participate in the Sugar desktop.
A Basic Activity
All activities must implement a class derived from 'Activity' in this class.
The convention is to call it ActivitynameActivity, but this is not required as
the activity.info file associated with your activity will tell the sugar-shell
which class to start.
For example the most minimal Activity:
from sugar.activity import activity
class ReadActivity(activity.Activity):
pass
To get a real, working activity, you will at least have to implement:
__init__(), read_file() and write_file()
Aditionally, you will probably need a at least a Toolbar so you can have some
interesting buttons for the user, like for example 'exit activity'
See the methods of the Activity class below for more information on what you
will need for a real activity.
"""
# Copyright (C) 2006-2007 Red Hat, Inc.
#
@ -49,6 +73,11 @@ SCOPE_INVITE_ONLY = "invite" # shouldn't be shown in UI, it's implicit when you
SCOPE_NEIGHBORHOOD = "public"
class ActivityToolbar(gtk.Toolbar):
"""The Activity toolbar with the Journal entry title, sharing, Keep and Stop buttons
All activities should have this toolbar. It is easiest to add it to your
Activity by using the ActivityToolbox.
"""
def __init__(self, activity):
gtk.Toolbar.__init__(self)
@ -169,6 +198,38 @@ class ActivityToolbar(gtk.Toolbar):
self._update_share()
class EditToolbar(gtk.Toolbar):
"""Provides the standard edit toolbar for Activities.
Members:
undo -- the undo button
redo -- the redo button
copy -- the copy button
paste -- the paste button
separator -- A separator between undo/redo and copy/paste
This class only provides the 'edit' buttons in a standard layout, your activity
will need to either hide buttons which make no sense for your Activity, or you
need to connect the button events to your own callbacks:
## Example from Read.activity:
# Create the edit toolbar:
self._edit_toolbar = EditToolbar(self._view)
# Hide undo and redo, they're not needed
self._edit_toolbar.undo.props.visible = False
self._edit_toolbar.redo.props.visible = False
# Hide the separator too:
self._edit_toolbar.separator.props.visible = False
# As long as nothing is selected, copy needs to be insensitive:
self._edit_toolbar.copy.set_sensitive(False)
# When the user clicks the button, call _edit_toolbar_copy_cb()
self._edit_toolbar.copy.connect('clicked', self._edit_toolbar_copy_cb)
# Add the edit toolbar:
toolbox.add_toolbar(_('Edit'), self._edit_toolbar)
# And make it visible:
self._edit_toolbar.show()
"""
def __init__(self):
gtk.Toolbar.__init__(self)
@ -198,6 +259,23 @@ class EditToolbar(gtk.Toolbar):
self.paste.show()
class ActivityToolbox(Toolbox):
"""Creates the Toolbox for the Activity
By default, the toolbox contains only the ActivityToolbar. After creating the
toolbox, you can add your activity specific toolbars, for example the
EditToolbar.
To add the ActivityToolbox to your Activity in MyActivity.__init__() do:
# Create the Toolbar with the ActivityToolbar:
toolbox = activity.ActivityToolbox(self)
... your code, inserting all other toolbars you need, like EditToolbar ...
# Add the toolbox to the activity frame:
self.set_toolbox(toolbox)
# And make it visible:
toolbox.show()
"""
def __init__(self, activity):
Toolbox.__init__(self)
@ -209,7 +287,71 @@ class ActivityToolbox(Toolbox):
return self._activity_toolbar
class Activity(Window, gtk.Container):
"""Base Activity class that all other Activities derive from."""
"""This is the base Activity class that all other Activities derive from. This is where your activity starts.
To get a working Activity:
0. Derive your Activity from this class:
class MyActivity(activity.Activity):
...
1. implement an __init__() method for your Activity class.
Use your init method to create your own ActivityToolbar which will
contain some standard buttons:
toolbox = activity.ActivityToolbox(self)
Add extra Toolbars to your toolbox.
You should setup Activity sharing here too.
Finaly, your Activity may need some resources which you can claim
here too.
The __init__() method is also used to make the distinction between
being resumed from the Journal, or starting with a blank document.
2. Implement read_file() and write_file()
Most activities revolve around creating and storing Journal entries.
For example, Write: You create a document, it is saved to the Journal
and then later you resume working on the document.
read_file() and write_file() will be called by sugar to tell your
Activity that it should load or save the document the user is working
on.
3. Implement our Activity Toolbars.
The Toolbars are added to your Activity in step 1 (the toolbox), but
you need to implement them somewhere. Now is a good time.
There are a number of standard Toolbars. The most basic one, the one
your almost absolutely MUST have is the ActivityToolbar. Without
this, you're not really making a proper Sugar Activity (which may be
okay, but you should really stop and think about why not!) You do
this with the ActivityToolbox(self) call in step 1.
Usually, you will also need the standard EditToolbar. This is the one
which has the standard copy and paste buttons. You need to derive
your own EditToolbar class from sugar.EditToolbar:
class EditToolbar(activity.EditToolbar):
...
See EditToolbar for the methods you should implement in your class.
Finaly, your Activity will very likely need some activity specific
buttons and options you can create your own toolbars by deriving a
class from gtk.Toolbar:
class MySpecialToolbar(gtk.Toolbar):
...
4. Use your creativity. Make your Activity something special and share
it with your friends!
Read through the methods of the Activity class below, to learn more about
how to make an Activity work.
Hint: A good and simple Activity to learn from is the Read activity. To
create your own activity, you may want to copy it and use it as a template.
"""
__gtype_name__ = 'SugarActivity'
__gsignals__ = {
@ -248,6 +390,11 @@ class Activity(Window, gtk.Container):
Creates an ActivityService (self._bus) servicing
this application.
Usage:
If your Activity implements __init__(), it should call
the base class __init()__ before doing Activity specific things.
"""
Window.__init__(self)
@ -360,12 +507,25 @@ class Activity(Window, gtk.Container):
return self._max_participants
def get_id(self):
"""Returns the activity id of the current instance of your activity.
The activity id is sort-of-like the unix process id (PID). However,
unlike PIDs it is only different for each new instance (with
create_jobject = True set) and stays the same everytime a user
resumes an activity. This is also the identity of your Activity to other
XOs for use when sharing.
"""
return self._activity_id
def get_bundle_id(self):
"""Returns the bundle_id from the activity.info file"""
return os.environ['SUGAR_BUNDLE_ID']
def set_canvas(self, canvas):
"""Sets the 'work area' of your activity with the canvas of your choice.
One commonly used canvas is gtk.ScrolledWindow
"""
Window.set_canvas(self, canvas)
canvas.connect('map', self.__canvas_map_cb)
@ -380,10 +540,17 @@ class Activity(Window, gtk.Container):
logging.debug("Error creating activity datastore object: %s" % err)
def get_activity_root(self):
"""
Return the appropriate location in the fs where to store activity related
data that doesn't pertain to the current execution of the activity and
thus cannot go into the DataStore.
"""Returns a path for saving Activity specific preferences, etc.
Returns a path to the location in the filesystem where the activity can
store activity related data that doesn't pertain to the current
execution of the activity and thus cannot go into the DataStore.
Currently, this will return something like ~/.sugar/default/MyActivityName/
Activities should ONLY save settings, user preferences and other data
which isn't specific to a journal item here. If (meta-)data is in anyway
specific to a journal entry, it MUST be stored in the DataStore.
"""
if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \
os.environ['SUGAR_ACTIVITY_ROOT']:
@ -395,6 +562,17 @@ class Activity(Window, gtk.Container):
"""
Subclasses implement this method if they support resuming objects from
the journal. 'file_path' is the file to read from.
You should immediately open the file from the file_path, because the
file_name will be deleted immediately after returning from read_file().
Once the file has been opened, you do not have to read it immediately:
After you have opened it, the file will only be really gone when you
close it.
Although not required, this is also a good time to read all meta-data:
the file itself cannot be changed externally, but the title, description
and other metadata['tags'] may change. So if it is important for you to
notice changes, this is the time to record the originals.
"""
raise NotImplementedError
@ -402,6 +580,17 @@ class Activity(Window, gtk.Container):
"""
Subclasses implement this method if they support saving data to objects
in the journal. 'file_path' is the file to write to.
If the user did make changes, you should create the file_path and save
all document data to it.
Additionally, you should also write any metadata needed to resume your
activity. For example, the Read activity saves the current page and zoom
level, so it can display the page.
Note: Currently, the file_path *WILL* be different from the one you
received in file_read(). Even if you kept the file_path from file_read()
open until now, you must still write the entire file to this file_path.
"""
raise NotImplementedError
@ -466,7 +655,13 @@ class Activity(Window, gtk.Container):
self._preview = self._get_preview()
def save(self):
"""Request that the activity is saved to the Journal."""
"""Request that the activity is saved to the Journal.
This method is called by the close() method below. In general,
activities should not override this method. This method is part of the
public API of an Acivity, and should behave in standard ways. Use your
own implementation of write_file() to save your Activity specific data.
"""
logging.debug('Activity.save: %r' % self._jobject.object_id)
@ -507,6 +702,11 @@ class Activity(Window, gtk.Container):
error_handler=self.__save_error_cb)
def copy(self):
"""Request that the activity 'Keep in Journal' the current state of the activity.
Activities should not override this method. Instead, like save() do any
copy work that needs to be done in write_file()
"""
logging.debug('Activity.copy: %r' % self._jobject.object_id)
self._preview = self._get_preview()
self.save()
@ -570,6 +770,12 @@ class Activity(Window, gtk.Container):
logging.error('Cannot invite %s, no such buddy.' % buddy_key)
def invite(self, buddy_key):
"""Invite a buddy to join this Activity.
Side Effects:
Calls self.share(True) to privately share the activity if it wasn't
shared before.
"""
self._invites_queue.append(buddy_key)
if (self._shared_activity is None
@ -598,6 +804,11 @@ class Activity(Window, gtk.Container):
self._pservice.share_activity(self, private=private)
def close(self):
"""Request that the activity be stopped and saved to the Journal
Activities should not override this method, but should implement write_file() to
do any state saving instead.
"""
self.save()
if self._shared_activity:
@ -617,6 +828,17 @@ class Activity(Window, gtk.Container):
return True
def get_metadata(self):
"""Returns the jobject metadata or None if there is no jobject.
Activities can set metadata in write_file() using:
self.metadata['MyKey'] = "Something"
and retrieve metadata in read_file() using:
self.metadata.get('MyKey', 'aDefaultValue')
Note: Make sure your activity works properly if one or more of the
metadata items is missing. Never assume they will all be present.
"""
if self._jobject:
return self._jobject.metadata
else:
@ -625,12 +847,10 @@ class Activity(Window, gtk.Container):
metadata = property(get_metadata, None)
def get_bundle_name():
"""Return the bundle name for the current process' bundle
"""
"""Return the bundle name for the current process' bundle"""
return os.environ['SUGAR_BUNDLE_NAME']
def get_bundle_path():
"""Return the bundle path for the current process' bundle
"""
"""Return the bundle path for the current process' bundle"""
return os.environ['SUGAR_BUNDLE_PATH']

View File

@ -348,22 +348,6 @@ def cmd_release(bundle_name, manifest):
print 'Creating the bundle...'
cmd_dist(bundle_name, manifest)
if os.environ.has_key('ACTIVITIES_REPOSITORY'):
print 'Uploading to the activities repository...'
repo = os.environ['ACTIVITIES_REPOSITORY']
server, path = repo.split(':')
retcode = subprocess.call(['ssh', server, 'rm',
'%s/%s*' % (path, bundle_name)])
if retcode:
print 'ERROR - cannot remove old bundles from the repository.'
bundle_path = os.path.join(_get_source_path(),
_get_package_name(bundle_name))
retcode = subprocess.call(['scp', bundle_path, repo])
if retcode:
print 'ERROR - cannot upload the bundle to the repository.'
print 'Done.'
def cmd_clean():

View File

@ -32,9 +32,8 @@ class Alert(gtk.EventBox, gobject.GObject):
Alerts are used inside the activity window instead of being a
separate popup window. They do not hide canvas content. You can
use add_alert(widget) and remove_alert(widget) inside your activity
to add and remove the alert. You can set the position (bottom=-1,
top=0,1) for alerts global for the window by changing alert_position,
default is bottom.
to add and remove the alert. The position of the alert is below the
toolbox or top in fullscreen mode.
Properties:
'title': the title of the alert,
@ -225,3 +224,31 @@ class TimeoutAlert(Alert):
self._response(gtk.RESPONSE_OK)
return False
return True
class NotifyAlert(Alert):
"""Timeout alert with only an "OK" button - just for notifications"""
def __init__(self, timeout=5, **kwargs):
Alert.__init__(self, **kwargs)
self._timeout = timeout
self._timeout_text = _TimeoutIcon(
text=self._timeout,
color=style.COLOR_BUTTON_GREY.get_int(),
background_color=style.COLOR_WHITE.get_int())
canvas = hippo.Canvas()
canvas.set_root(self._timeout_text)
canvas.show()
self.add_button(gtk.RESPONSE_OK, _('OK'), canvas)
gobject.timeout_add(1000, self.__timeout)
def __timeout(self):
self._timeout -= 1
self._timeout_text.props.text = self._timeout
if self._timeout == 0:
self._response(gtk.RESPONSE_OK)
return False
return True

View File

@ -1 +1 @@
SUBDIRS = shell console
SUBDIRS = shell

View File

@ -1,20 +0,0 @@
SUBDIRS = interface lib
servicedir = $(datadir)/dbus-1/services
service_in_files = org.laptop.sugar.Console.service.in
service_DATA = $(service_in_files:.service.in=.service)
$(service_DATA): $(service_in_files) Makefile
@sed -e "s|\@bindir\@|$(bindir)|" $< > $@
sugardir = $(pkgdatadir)/services/console
sugar_PYTHON = \
__init__.py \
console.py \
label.py
bin_SCRIPTS = sugar-console
DISTCLEANFILES = $(service_DATA)
EXTRA_DIST = $(service_in_files) $(bin_SCRIPTS)

View File

@ -1,12 +0,0 @@
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.

View File

@ -1 +0,0 @@

View File

@ -1,94 +0,0 @@
# Copyright (C) 2006, Eduardo Silva (edsiper@gmail.com).
#
# 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 dbus
import dbus.glib
import dbus.service
import os
import sys
import gtk
import gobject
sys.path.append(os.path.dirname(__file__))
sys.path.append(os.path.dirname(__file__) + '/lib')
sys.path.append(os.path.dirname(__file__) + '/interface')
CONSOLE_BUS = 'org.laptop.sugar.Console'
CONSOLE_PATH = '/org/laptop/sugar/Console'
CONSOLE_IFACE = 'org.laptop.sugar.Console'
class Console:
def __init__(self):
# Main Window
self.window = gtk.Window()
self.window.set_title('Developer console')
self.window.connect("delete-event", self._delete_event_cb)
self.default_width = gtk.gdk.screen_width() * 95 / 100
self.default_height = gtk.gdk.screen_height() * 95 / 100
self.window.set_default_size(self.default_width, self.default_height)
self.window.realize()
self.window.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
# Notebook
self.notebook = gtk.Notebook()
self._load_interface('xo', 'XO Resources')
self._load_interface('network', 'Network')
self._load_interface('xserver', 'X Server')
self._load_interface('memphis', 'Memphis')
self._load_interface('logviewer', 'Log Viewer')
self._load_interface('ps_watcher', 'Presence')
main_hbox = gtk.HBox()
main_hbox.pack_start(self.notebook, True, True, 0)
main_hbox.show()
self.notebook.show()
self.window.add(main_hbox)
def _load_interface(self, interface, label):
mod = __import__(interface)
widget = mod.Interface().widget
widget.show()
self.notebook.append_page(widget, gtk.Label(label))
def _delete_event_cb(self, window, gdkevent):
gtk.main_quit()
class Service(dbus.service.Object):
def __init__(self, bus, object_path=CONSOLE_PATH):
dbus.service.Object.__init__(self, bus, object_path)
self._console = Console()
@dbus.service.method(CONSOLE_IFACE)
def ToggleVisibility(self):
window = self._console.window
if not window.props.visible:
window.present()
else:
window.hide()
bus = dbus.SessionBus()
name = dbus.service.BusName(CONSOLE_BUS, bus)
obj = Service(name)
gtk.main()

View File

@ -1,6 +0,0 @@
SUBDIRS = memphis network logviewer xo xserver
sugardir = $(pkgdatadir)/services/console/interface
sugar_PYTHON = \
__init__.py \
ps_watcher.py

View File

@ -1,4 +0,0 @@
sugardir = $(pkgdatadir)/services/console/interface/logviewer
sugar_PYTHON = \
__init__.py \
logviewer.py

View File

@ -1 +0,0 @@
from logviewer import Interface

View File

@ -1,228 +0,0 @@
# Copyright (C) 2006, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# Rewritten by Eduardo Silva, edsiper@gmail.com
import os
import pygtk
import gtk
import gobject
import pango
import gnomevfs
from sugar import env
class MultiLogView(gtk.VBox):
def __init__(self, path, extra_files):
self._logs_path = path
self._active_log = None
self._extra_files = extra_files
# Creating Main treeview with Actitivities list
self._tv_menu = gtk.TreeView()
self._tv_menu.connect('cursor-changed', self._load_log)
self._tv_menu.set_rules_hint(True)
# Set width
box_width = gtk.gdk.screen_width() * 80 / 100
self._tv_menu.set_size_request(box_width*25/100, 0)
self._store_menu = gtk.TreeStore(str)
self._tv_menu.set_model(self._store_menu)
self._add_column(self._tv_menu, 'Sugar logs', 0)
self._logs = {}
# Activities menu
self.hbox = gtk.HBox(False, 3)
self.hbox.pack_start(self._tv_menu, True, True, 0)
# Activity log, set width
self._view = LogView()
self._view.set_size_request(box_width*75/100, 0)
self.hbox.pack_start(self._view, True, True, 0)
self.hbox.show_all()
self._configure_watcher()
self._create_log_view()
def _configure_watcher(self):
# Setting where gnomeVFS will be watching
gnomevfs.monitor_add('file://' + self._logs_path,
gnomevfs.MONITOR_DIRECTORY,
self._log_file_changed_cb)
for f in self._extra_files:
gnomevfs.monitor_add('file://' + f,
gnomevfs.MONITOR_FILE,
self._log_file_changed_cb)
def _log_file_changed_cb(self, monitor_uri, info_uri, event):
path = info_uri.split('file://')[-1]
filename = self._get_filename_from_path(path)
if event == gnomevfs.MONITOR_EVENT_CHANGED:
self._logs[filename].update()
elif event == gnomevfs.MONITOR_EVENT_DELETED:
self._delete_log_file_view(filename)
elif event == gnomevfs.MONITOR_EVENT_CREATED:
self._add_log_file(path)
# Load the log information in View (textview)
def _load_log(self, treeview):
treeselection = treeview.get_selection()
treestore, iter = treeselection.get_selected()
# Get current selection
act_log = self._store_menu.get_value(iter, 0)
# Set buffer and scroll down
self._view.textview.set_buffer(self._logs[act_log])
self._view.textview.scroll_to_mark(self._logs[act_log].get_insert(), 0)
self._active_log = act_log
def _create_log_view(self):
# Searching log files
for logfile in os.listdir(self._logs_path):
full_log_path = os.path.join(self._logs_path, logfile)
self._add_log_file(full_log_path)
for ext in self._extra_files:
self._add_log_file(ext)
return True
def _delete_log_file_view(self, logkey):
self._store_menu.remove(self._logs[logkey].iter)
del self._logs[logkey]
def _get_filename_from_path(self, path):
return path.split('/')[-1]
def _add_log_file(self, path):
if os.path.isdir(path):
return False
if not os.path.exists(path):
print "ERROR: %s don't exists"
return False
logfile = self._get_filename_from_path(path)
if not self._logs.has_key(logfile):
iter = self._add_log_row(logfile)
model = LogBuffer(path, iter)
self._logs[logfile] = model
self._logs[logfile].update()
written = self._logs[logfile]._written
# Load the first iter
if self._active_log == None:
self._active_log = logfile
iter = self._tv_menu.get_model().get_iter_root()
self._tv_menu.get_selection().select_iter(iter)
self._load_log(self._tv_menu)
if written > 0 and self._active_log == logfile:
self._view.textview.scroll_to_mark(self._logs[logfile].get_insert(), 0)
def _add_log_row(self, name):
return self._insert_row(self._store_menu, None, name)
# Add a new column to the main treeview, (code from Memphis)
def _add_column(self, treeview, column_name, index):
cell = gtk.CellRendererText()
col_tv = gtk.TreeViewColumn(column_name, cell, text=index)
col_tv.set_resizable(True)
col_tv.set_property('clickable', True)
treeview.append_column(col_tv)
# Set the last column index added
self.last_col_index = index
# Insert a Row in our TreeView
def _insert_row(self, store, parent, name):
iter = store.insert_before(parent, None)
index = 0
store.set_value(iter, index , name)
return iter
class LogBuffer(gtk.TextBuffer):
def __init__(self, logfile, iter=None):
gtk.TextBuffer.__init__(self)
self._logfile = logfile
self._pos = 0
self.iter = iter
self.update()
def update(self):
try:
f = open(self._logfile, 'r')
init_pos = self._pos
f.seek(self._pos)
self.insert(self.get_end_iter(), f.read())
self._pos = f.tell()
f.close()
self._written = (self._pos - init_pos)
except:
self.insert(self.get_end_iter(), "Console error: can't open the file\n")
self._written = 0
class LogView(gtk.ScrolledWindow):
def __init__(self):
gtk.ScrolledWindow.__init__(self)
self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
self.textview = gtk.TextView()
self.textview.set_wrap_mode(gtk.WRAP_WORD)
font = pango.FontDescription('Sans 8')
font.set_weight(pango.WEIGHT_LIGHT)
self.textview.modify_font(font)
# Set background color
bgcolor = gtk.gdk.color_parse("#FFFFFF")
self.textview.modify_base(gtk.STATE_NORMAL, bgcolor)
self.textview.set_editable(False)
self.add(self.textview)
self.textview.show()
class Interface:
def __init__(self):
# Main path to watch: ~/.sugar/someuser/logs...
main_path = os.path.join(env.get_profile_path(), 'logs')
# extra files to watch in logviewer
ext_files = []
ext_files.append("/var/log/Xorg.0.log")
ext_files.append("/var/log/syslog")
ext_files.append("/var/log/messages")
viewer = MultiLogView(main_path, ext_files)
self.widget = viewer.hbox

View File

@ -1,8 +0,0 @@
SUBDIRS = plugins
sugardir = $(pkgdatadir)/services/console/interface/memphis
sugar_PYTHON = \
__init__.py \
memphis.py \
plugin.py

View File

@ -1 +0,0 @@
from memphis import Interface

View File

@ -1,235 +0,0 @@
# Copyright (C) 2006, Eduardo Silva (edsiper@gmail.com).
#
# 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 sys
import os
import string
import wnck
import plugin
from procmem import proc
try:
import gtk
import gtk.gdk
import gobject
except:
sys.exit(1)
class Interface:
store_data_types = []
store_data_types_details = []
def __init__(self):
# Our GtkTree (Treeview)
self.treeview = gtk.TreeView()
self.treeview.show()
self.button_start = gtk.Button('Start Memphis')
self.button_stop = gtk.Button('Stop Memphis')
fixed = gtk.Fixed()
fixed.add(self.button_start)
fixed.add(self.button_stop)
vbox = gtk.VBox(False)
vbox.set_border_width(5)
vbox.pack_start(fixed, True, True, 0)
# Our GtkTree (Treeview)
self.treeview = gtk.TreeView()
t_width = gtk.gdk.screen_width()
t_height = gtk.gdk.screen_height() * 83 / 100
self.treeview.set_size_request(t_width, t_height)
vbox.pack_start(self.treeview, True, True, 0)
vbox.show_all()
self.widget = vbox
# Loading plugins
self.plg = plugin.Plugin()
# TOP data types (columns)
self.store_data_types = []
for plg in self.plg.list:
plg_data = plg.INTERNALS
# Give plugin object to plugin
plg.INTERNALS['Plg'] = self.plg
# Creating a store model and loading process data to Treeview
# self.store_data_types, ex [int, str, str, str, int,...]
#self.store = gtk.TreeStore(*self.store_data_types)
self.data = Data(self, self.treeview, self.plg.list)
self.button_stop.hide()
self.button_start.connect('clicked', self.data._start_memphis)
self.button_stop.connect('clicked', self.data._stop_memphis)
class Data:
last_col_index = 0
store_data_cols = []
store_data_types = []
store_data_types_details = []
_running_status = False
def __init__(self, interface, treeview, plg_list):
self.interface = interface
# Top data types
self.plg_list = plg_list
for plg in self.plg_list:
if plg.INTERNALS['top_data'] != None:
last_dt = len(self.store_data_types)
if last_dt > 0:
last_dt -= 1
len_dt = len(plg.INTERNALS['top_data'])
self.store_data_types_details.append({"plugin": plg, "init": last_dt, "end": last_dt + len_dt})
for dt in plg.INTERNALS['top_data']:
self.store_data_types.append(dt)
for col in plg.INTERNALS['top_cols']:
self.store_data_cols.append(col)
# Set global treeview
self.treeview = treeview
# Basic columns
index = 0
for column_name in self.store_data_cols:
self.add_column(column_name, index)
index += 1
self.store = gtk.TreeStore(*self.store_data_types)
treeview.set_model(self.store)
def _start_memphis(self, button):
# Update information every 1.5 second
button.hide()
self.interface.button_stop.show()
self._running_status = True
self._gid = gobject.timeout_add(1500, self.load_data, self.treeview)
def _stop_memphis(self, button):
gobject.source_remove(self._gid)
self._running_status = False
button.hide()
self.interface.button_start.show()
# Add a new column to the main treeview
def add_column(self, column_name, index):
cell = gtk.CellRendererText()
col_tv = gtk.TreeViewColumn(column_name, cell, text=index)
col_tv.set_resizable(True)
col_tv.connect('clicked', self.sort_column_clicked)
col_tv.set_property('clickable', True)
self.treeview.append_column(col_tv)
# Set the last column index added
self.last_col_index = index
# Sorting
def sort_column_clicked(self, TreeViewColumn):
cols = self.treeview.get_columns()
# Searching column index
index = 0
for col in cols:
if col == TreeViewColumn:
break
index += 1
self.store.set_sort_column_id(index, gtk.SORT_DESCENDING)
def load_data(self, treeview):
self.store.clear()
# Getting procfs data
self.procdata = proc.ProcInfo()
self.process_list = []
pids = []
screen = wnck.screen_get_default()
windows = screen.get_windows()
current_pid = os.getpid()
for win in windows:
pid = int(win.get_pid())
if current_pid != pid:
pids.append(pid)
self.process_list = set(pids)
# Sort rows using pid
#self.process_list.sort(key=operator.itemgetter('pid'))
self.process_iter = []
for pid in self.process_list:
pi = self.build_row(self.store, None, self.procdata, pid)
self.process_iter.append(pi)
treeview.set_rules_hint(True)
treeview.expand_all()
return self._running_status
def build_row(self, store, parent_iter, proc_data, pid):
data = []
pinfo = proc_data.MemoryInfo(pid)
# Look for plugins that need to update the top data treeview
for plg in self.plg_list:
plg_data = []
if plg.INTERNALS['top_data'] != None:
# data = [xxx, yyy,zzz,...]
plg_data = plg.info.plg_on_top_data_refresh(plg, pinfo)
for field in plg_data:
data.append(field)
pi = self.insert_row(store, parent_iter, data)
return pi
# Insert a Row in our TreeView
def insert_row(self, store, parent, row_data):
iter = store.insert_after(parent, None)
index = 0
for data in row_data:
store.set_value(iter, index , data)
index += 1
return iter

View File

@ -1,48 +0,0 @@
###############################################
# Memphis Plugin Support
###############################################
import sys, os
from procmem import proc, proc_smaps, analysis
class Plugin:
# Plugin list
list = []
proc = proc.ProcInfo()
internal_plugin = "memphis_init"
plg_path = os.path.dirname(os.path.abspath(__file__)) + "/plugins"
# Frequency timer, managed by main program
freq_timer = 0
def __init__(self):
sys.path.insert(0, self.plg_path)
# Including memphis plugin
self.list.append(__import__(self.internal_plugin))
if os.path.isdir(self.plg_path):
# around dir entries
for plg in os.listdir(self.plg_path):
if plg == self.internal_plugin:
continue
if os.path.isdir(self.plg_path + "/" + plg):
p = __import__(plg)
self.list.append(__import__(plg))
# Parse /proc/PID/smaps information
def proc_get_smaps(self, pid):
return proc_smaps.ProcSmaps(pid)
# Parse /proc/PID/maps information
def proc_get_maps(self, pid):
return proc_smaps.ProcMaps(pid)
def proc_analysis(self, pid):
return analysis.Analysis(pid)

View File

@ -1,4 +0,0 @@
SUBDIRS = clean_size cpu smaps memphis_init
sugardir = $(pkgdatadir)/services/console/interface/memphis/plugins
sugar_PYTHON =

View File

@ -1,6 +0,0 @@
sugardir = $(pkgdatadir)/services/console/interface/memphis/plugins/clean_size
sugar_PYTHON = \
README \
__init__.py \
info.py

View File

@ -1,2 +0,0 @@
This plugin give support to get the clean size memory usage
by process using the /proc/PID/maps file.

View File

@ -1,16 +0,0 @@
import info
INTERNALS = {
# Basic information
'PLGNAME': "Clean Size",
'TABNAME': None,
'AUTHOR': "Eduardo Silva",
'DESC': "Print the approx real memory usage",
# Plugin API
'Plg': None, # Plugin object
'top_data': [int], # Top data types needed by memphis core plugin
'top_cols': ["Approx Real Usage (kb)"]
}

View File

@ -1,15 +0,0 @@
###########################################################
# Main function:
# -----------------
# self: self plugin object
# mself: memphis object / principal class
# pinfo: row with information about current tracing process
############################################################
def plg_on_top_data_refresh(self, pinfo):
# Get clean size
maps = self.INTERNALS['Plg'].proc_get_maps(pinfo['pid'])
size = (maps.clean_size/1024)
return [size]

View File

@ -1,6 +0,0 @@
sugardir = $(pkgdatadir)/services/console/interface/memphis/plugins/cpu
sugar_PYTHON = \
README \
__init__.py \
info.py

View File

@ -1,2 +0,0 @@
This plugin give support to draw the Virtual Memory Size
usage by the current tracing process.

View File

@ -1,23 +0,0 @@
import os
import info
INTERNALS = {
'PLGNAME': "cpu",
'TABNAME': None,
'AUTHOR': "Eduardo Silva",
'DESC': "Print CPU usage",
# Plugin API
'Plg': None, # Plugin object
'current_plg': None, # Current plugin object
'current_page': None, # Current page number
# Top process view requirements
'top_data': [int], # Top data types needed by memphis core plugin
'top_cols': ["%CPU "] # Column names
}
# Get CPU frequency
cpu_hz = os.sysconf(2)
pids_ujiffies = {}

View File

@ -1,48 +0,0 @@
###########################################################
# Main function:
# -----------------
# self: self plugin object
# mself: memphis object / principal class
# pinfo: row with information about current tracing process
############################################################
def plg_on_top_data_refresh(self, pinfo):
PI = self.INTERNALS['Plg'].proc
pid = pinfo['pid']
# Get JIFFIES CPU usage
used_jiffies = pinfo['utime'] + pinfo['stime']
last_ujiffies = get_pid_ujiffies(self, pid)
cpu_usage = PI.get_CPU_usage(self.cpu_hz, used_jiffies, pinfo['start_time'])
# Get PERCENT CPU usage
if last_ujiffies == 0.0:
pcpu = 0.0
set_pid_ujiffies(self, pid, cpu_usage['used_jiffies'])
data = [int(pcpu)]
return data
used_jiffies = cpu_usage['used_jiffies'] - last_ujiffies
# Available jiffies are
avail_jiffies = (500/1000.0)*self.cpu_hz # 500 = 0.5 second
pcpu = ((used_jiffies*100)/avail_jiffies)
set_pid_ujiffies(self, pid, cpu_usage['used_jiffies'])
data = [int(pcpu)]
return data
def get_pid_ujiffies(self, pid):
if pid in self.pids_ujiffies:
return self.pids_ujiffies[pid]
else:
set_pid_ujiffies(self, pid, 0)
return self.pids_ujiffies[pid]
def set_pid_ujiffies(self, pid, ujiffies):
self.pids_ujiffies[pid] = ujiffies

View File

@ -1,6 +0,0 @@
sugardir = $(pkgdatadir)/services/console/interface/memphis/plugins/memphis_init
sugar_PYTHON = \
README \
__init__.py \
info.py

View File

@ -1,2 +0,0 @@
This plugin give support to draw the Virtual Memory Size
usage by the current tracing process.

View File

@ -1,15 +0,0 @@
import info
INTERNALS = {
'PLGNAME': "memphis",
'TABNAME': None,
'AUTHOR': "Eduardo Silva",
'DESC': "Print basic process information",
# Plugin API
'Plg': None, # Plugin object
# Top process view requirements
'top_data': [int, str, str], # Top data types needed by memphis core plugin
'top_cols': ["PID", "Process Name", "Status"] # Column names
}

View File

@ -1,14 +0,0 @@
###########################################################
# Main function:
# -----------------
# self: self plugin object
# mself: memphis object / principal class
# pinfo: row with information about current tracing process
############################################################
def plg_on_top_data_refresh(self, ppinfo):
data = [ppinfo['pid'], ppinfo['name'], ppinfo['state_name']]
return data

View File

@ -1,6 +0,0 @@
sugardir = $(pkgdatadir)/services/console/interface/memphis/plugins/dirty_size
sugar_PYTHON = \
README \
__init__.py \
info.py

View File

@ -1,2 +0,0 @@
This plugin give support to get the public and shared dirty memory usage
by process using the /proc/PID/smaps file.

View File

@ -1,17 +0,0 @@
import info
INTERNALS = {
# Basic information
'PLGNAME': "SMaps",
'TABNAME': None, # No tabbed plugin
'AUTHOR': "Eduardo Silva",
'DESC': "Get dirty size and reference memory usage",
# Plugin API
'Plg': None, # Plugin object
'top_data': [int, int], # Top data types needed by memphis core plugin
'top_cols': ["PDRSS (kb)", "Referenced (kb)"]
}

View File

@ -1,19 +0,0 @@
###########################################################
# Main function:
# -----------------
# self: self plugin object
# mself: memphis object / principal class
# pinfo: row with information about current tracing process
############################################################
def plg_on_top_data_refresh(self, ppinfo):
smaps = get_data(self, ppinfo['pid'])
# memphis need an array
return [smaps['private_dirty'], smaps['referenced']]
def get_data(pself, pid):
ProcAnalysis = pself.INTERNALS['Plg'].proc_analysis(pid)
return ProcAnalysis.SMaps()

View File

@ -1,4 +0,0 @@
sugardir = $(pkgdatadir)/services/console/interface/network
sugar_PYTHON = \
__init__.py \
network.py

View File

@ -1 +0,0 @@
from network import Interface

View File

@ -1,103 +0,0 @@
# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import gobject
from net.device import Device
from ui.treeview import TreeView
class NetworkView(TreeView):
def __init__(self):
col_names = []
col_names.append({'index': 0, 'name': 'Interface'})
col_names.append({'index': 1, 'name': 'IP Address'})
col_names.append({'index': 2, 'name': 'NetMask'})
col_names.append({'index': 3, 'name': 'MAC Address'})
col_names.append({'index': 4, 'name': 'Bytes Recv'})
col_names.append({'index': 5, 'name': 'Bytes Sent'})
col_names.append({'index': 6, 'name': 'Packets Recv'})
col_names.append({'index': 7, 'name': 'Packets Sent'})
self._iface_iter = []
cols_type = [str, str, str, str, str, str, str, str]
TreeView.__init__(self, cols_type, col_names)
self._dev = Device()
self.show_all()
gobject.timeout_add(1500, self._update_data)
def _update_data(self):
interfaces = self._dev.get_interfaces()
for iface in interfaces:
info = self._dev.get_iface_info(iface['interface'])
row = []
row.append({'index':0, 'info': iface['interface']})
if info[0]:
row.append({'index':1, 'info': info[0]})
if info[1]:
row.append({'index':2, 'info': info[1]})
if info[2]:
row.append({'index':3, 'info': info[2]})
row.append({'index': 4, 'info': iface['bytes_sent']})
row.append({'index': 5, 'info': iface['packets_sent']})
row.append({'index': 6, 'info': iface['bytes_recv']})
row.append({'index': 7, 'info': iface['packets_recv']})
iter = self._get_iface_iter(iface['interface'])
if not iter:
iter = self.add_row(row)
self._set_iface_iter(iter, iface['interface'])
else:
self.update_row(iter, row)
self._clear_down_interfaces(interfaces)
return True
def _set_iface_iter(self, iter, iface):
self._iface_iter.append([iter, iface])
def _remove_iface_iter(self, search_iter):
i = 0
for [iter, interface] in self._iface_iter:
if iter == search_iter:
del self._iface_iter[i]
return
i+=1
def _get_iface_iter(self, iface):
for [iter, interface] in self._iface_iter:
if iface == interface:
return iter
return None
def _clear_down_interfaces(self, interfaces):
for [iter, iface] in self._iface_iter:
found = False
for dev in interfaces:
if dev['interface']==iface:
found = True
break
if not found:
self.remove_row(iter)
self._remove_iface_iter(iter)
class Interface(object):
def __init__(self):
self.widget = NetworkView()

View File

@ -1,729 +0,0 @@
# 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, TextView, VPaned
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
ACT_COL_CONN = 7
ACT_COL_CHANNELS = 8
ACT_COL_BUDDIES = 9
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
BUDDY_COL_ACTIVITIES = 9
BUDDY_COL_HANDLES = 10
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.conn = '?'
self.channels = None
self.buddies = None
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)
self.iface.connect_to_signal('NewChannel', self._on_new_channel)
self.iface.GetChannels(reply_handler=self._on_get_channels_success,
error_handler=self._on_get_channels_failure)
self.iface.connect_to_signal('BuddyJoined', self._on_buddy_joined)
self.iface.connect_to_signal('BuddyLeft', self._on_buddy_left)
self.iface.GetJoinedBuddies(reply_handler=self._on_get_buddies_success,
error_handler=self._on_get_buddies_failure)
def _on_buddy_joined(self, buddy):
if self.buddies is None:
return
if buddy.startswith('/org/laptop/Sugar/Presence/Buddies/'):
buddy = '.../' + buddy[35:]
self.ps_watcher.log('INFO: Activity %s emitted BuddyJoined("%s") '
'or mentioned the buddy in GetJoinedBuddies',
self.object_path, buddy)
self.buddies.append(buddy)
self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_BUDDIES,
' '.join(self.buddies))
def _on_buddy_left(self, buddy):
if self.buddies is None:
return
if buddy.startswith('/org/laptop/Sugar/Presence/Buddies/'):
buddy = '.../' + buddy[35:]
self.ps_watcher.log('INFO: Activity %s emitted BuddyLeft("%s")',
self.object_path, buddy)
try:
self.buddies.remove(buddy)
except ValueError:
pass
self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_BUDDIES,
' '.join(self.buddies))
def _on_get_buddies_success(self, buddies):
self.buddies = []
for buddy in buddies:
self._on_buddy_joined(buddy)
def _on_get_buddies_failure(self, e):
self.log('ERROR: <Activity %s>.GetJoinedBuddies(): %s',
self.object_path, e)
self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_BUDDIES,
'!')
def _on_new_channel(self, channel):
if self.channels is None:
return
if channel.startswith(self.full_conn):
channel = '...' + channel[len(self.full_conn):]
self.ps_watcher.log('INFO: Activity %s emitted NewChannel("%s") '
'or mentioned the channel in GetChannels()',
self.object_path, channel)
self.channels.append(channel)
# FIXME: listen for Telepathy Closed signal!
self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_CHANNELS,
' '.join(self.channels))
def _on_get_channels_success(self, service, conn, channels):
self.full_conn = conn
if conn.startswith('/org/freedesktop/Telepathy/Connection/'):
self.conn = '.../' + conn[38:]
else:
self.conn = conn
self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_CONN,
self.conn)
self.channels = []
for channel in channels:
self._on_new_channel(channel)
self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_CHANNELS,
' '.join(self.channels))
def _on_get_channels_failure(self, e):
self.ps_watcher.log('ERROR: <Activity %s>.GetChannels(): %s',
self.object_path, e)
self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_CONN,
'!')
self.ps_watcher.activities_list_store.set(self.iter, ACT_COL_CHANNELS,
'!')
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):
self.ps_watcher.log('ERROR: <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):
self.ps_watcher.log('ERROR: <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):
self.ps_watcher.log('ERROR: <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):
self.ps_watcher.log('ERROR: <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.activities = None
self.handles = None
self.iter = self.ps_watcher.add_buddy(self)
self.iface.connect_to_signal('PropertyChanged', self._on_props_changed,
byte_arrays=True)
self.ps_watcher.log('Calling <Buddy %s>.GetProperties()', object_path)
self.iface.GetProperties(reply_handler=self._on_get_props_success,
error_handler=self._on_get_props_failure,
byte_arrays=True)
self.iface.connect_to_signal('JoinedActivity', self._on_joined)
self.iface.connect_to_signal('LeftActivity', self._on_left)
self.ps_watcher.log('Calling <Buddy %s>.GetJoinedActivities()',
object_path)
self.iface.GetJoinedActivities(reply_handler=self._on_get_acts_success,
error_handler=self._on_get_acts_failure)
self.iface.connect_to_signal('TelepathyHandleAdded',
self._on_handle_added)
self.iface.connect_to_signal('TelepathyHandleRemoved',
self._on_handle_removed)
self.ps_watcher.log('Calling <Buddy %s>.GetTelepathyHandles()',
object_path)
self.iface.GetTelepathyHandles(
reply_handler=self._on_get_handles_success,
error_handler=self._on_get_handles_failure)
def _on_handle_added(self, service, conn, handle):
if self.handles is None:
return
self.ps_watcher.log('INFO: Buddy %s emitted Telepathy HandleAdded('
'"%s", "%s", %u) or mentioned the handle in '
'GetTelepathyHandles()',
self.object_path, service, conn, handle)
if conn.startswith('/org/freedesktop/Telepathy/Connection/'):
conn = '.../' + conn[38:]
self.handles.append('%u@%s' % (handle, conn))
self.ps_watcher.buddies_list_store.set(self.iter,
BUDDY_COL_HANDLES,
' '.join(self.handles))
def _on_handle_removed(self, service, conn, handle):
if self.handles is None:
return
if conn.startswith('/org/freedesktop/Telepathy/Connection/'):
conn = '.../' + conn[38:]
self.ps_watcher.log('INFO: Buddy %s emitted HandleRemoved("%s", '
'"%s", %u)', self.object_path, service, conn,
handle)
try:
self.handles.remove('%u@%s' % (handle, conn))
except ValueError:
pass
self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_HANDLES,
' '.join(self.handles))
def _on_get_handles_success(self, handles):
self.handles = []
for service, conn, handle in handles:
self._on_handle_added(service, conn, handle)
def _on_get_handles_failure(self, e):
self.ps_watcher.log('ERROR: <Buddy %s>.GetTelepathyHandles(): %s',
self.object_path, e)
self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_HANDLES,
'!')
def _on_joined(self, act):
if self.activities is None:
return
if act.startswith('/org/laptop/Sugar/Presence/Activities/'):
act = '.../' + act[38:]
self.ps_watcher.log('INFO: Buddy %s emitted ActivityJoined("%s") '
'or mentioned it in GetJoinedActivities()',
self.object_path, act)
self.activities.append(act)
self.ps_watcher.buddies_list_store.set(self.iter,
BUDDY_COL_ACTIVITIES,
' '.join(self.activities))
def _on_left(self, act):
if self.activities is None:
return
if act.startswith('/org/laptop/Sugar/Presence/Activities/'):
act = '.../' + act[38:]
self.ps_watcher.log('INFO: Buddy %s emitted ActivityLeft("%s")',
self.object_path, act)
try:
self.activities.remove(act)
except ValueError:
pass
self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_ACTIVITIES,
' '.join(self.activities))
def _on_get_acts_success(self, activities):
self.activities = []
for act in activities:
self._on_joined(act)
def _on_get_acts_failure(self, e):
self.ps_watcher.log('ERROR: <Buddy %s>.GetJoinedActivities(): %s',
self.object_path, e)
self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_ACTIVITIES,
'!')
def _on_props_changed(self, props):
try:
logger.debug('PropertyChanged(%s, %s)', self, props)
self.ps_watcher.log('INFO: <Buddy %s> emitted PropertyChanged(%r)',
self.object_path, props)
self._props_changed(props)
except Exception, e:
self.ps_watcher.log('INTERNAL ERROR: %s', e)
def _on_get_props_success(self, props):
try:
logger.debug('GetProperties(%s, %s)', self, props)
self.ps_watcher.log('INFO: <Buddy %s>.GetProperties() -> %r',
self.object_path, props)
self._props_changed(props)
except Exception, e:
self.ps_watcher.log('INTERNAL ERROR: %s', e)
def _props_changed(self, props):
logger.debug('Begin _props_changed')
if 'nick' in props:
self.nick = props.get('nick', '?')
self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_NICK,
self.nick)
if 'owner' in props:
self.owner = bool(props.get('owner', False))
self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_OWNER,
self.owner)
if 'color' in props:
self.color = props.get('color', '?')
self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_COLOR,
self.color)
if 'ip4-address' in props:
self.ipv4 = props.get('ip4-address', '?')
self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_IP4,
self.ipv4)
if 'current-activity' in props:
self.cur_act = props.get('current-activity', '?')
self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_CUR_ACT,
self.cur_act)
if 'key' in props:
key = props.get('key', None)
if key:
self.keyid = '%d bytes, sha1 %s' % (len(key),
sha1(key).hexdigest())
else:
# could be '' (present, empty value) or None (absent). Either way:
self.keyid = '?'
self.ps_watcher.buddies_list_store.set(self.iter, BUDDY_COL_KEY_ID,
self.keyid)
logger.debug('End _props_changed')
def _on_get_props_failure(self, e):
self.ps_watcher.log('ERROR: <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, log):
VBox.__init__(self)
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)
self.log = log
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
str, # conn
str, # channels
str, # buddies
)
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)
c = self.activities_list.insert_column_with_attributes(5, 'Connection',
CellRendererText(), text=ACT_COL_CONN, weight=ACT_COL_WEIGHT,
strikethrough=ACT_COL_STRIKE)
c.set_resizable(True)
c.set_sort_column_id(ACT_COL_CONN)
c = self.activities_list.insert_column_with_attributes(6, 'Channels',
CellRendererText(), text=ACT_COL_CHANNELS, weight=ACT_COL_WEIGHT,
strikethrough=ACT_COL_STRIKE)
c.set_resizable(True)
c = self.activities_list.insert_column_with_attributes(7, 'Buddies',
CellRendererText(), text=ACT_COL_BUDDIES, weight=ACT_COL_WEIGHT,
strikethrough=ACT_COL_STRIKE)
c.set_resizable(True)
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, 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, 'Pubkey',
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)
c = self.buddies_list.insert_column_with_attributes(7, 'Activities',
CellRendererText(), text=BUDDY_COL_ACTIVITIES,
weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE)
c.set_resizable(True)
c.set_sort_column_id(BUDDY_COL_ACTIVITIES)
c = self.buddies_list.insert_column_with_attributes(8, 'Handles',
CellRendererText(), text=BUDDY_COL_HANDLES,
weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE)
c.set_resizable(True)
c.set_sort_column_id(BUDDY_COL_HANDLES)
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):
self.log('INFO: PS GetActivities() returned %r', paths)
self.activities = {}
for path in paths:
self.activities[path] = ActivityWatcher(self, path)
def _on_get_activities_failure(self, e):
self.log('ERROR: 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, act.conn, '?', '?'))
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
self.log('INFO: PS emitted ActivityAppeared("%s")', path)
self.activities[path] = ActivityWatcher(self, path)
def _on_activity_disappeared(self, path):
if self.activities is None:
return
self.log('INFO: PS emitted ActivityDisappeared("%s")', path)
act = self.activities.get(path)
if act is None:
self.log('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):
self.log('INFO: PS emitted ActivityInvitation("%s")', path)
def _on_private_invitation(self, bus_name, conn, channel):
self.log('INFO: PS emitted PrivateInvitation("%s", "%s", "%s")',
bus_name, conn, channel)
def _on_get_buddies_success(self, paths):
self.log('INFO: PS GetBuddies() returned %r', paths)
self.buddies = {}
for path in paths:
self.buddies[path] = BuddyWatcher(self, path)
def _on_get_buddies_failure(self, e):
self.log('ERROR: 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
self.log('INFO: PS emitted BuddyAppeared("%s")', path)
self.buddies[path] = BuddyWatcher(self, path)
def _on_buddy_disappeared(self, path):
if self.buddies is None:
return
self.log('INFO: PS emitted BuddyDisappeared("%s")', path)
b = self.buddies.get(path)
if b is None:
self.log('ERROR: 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
logger.debug('Running...')
self.label = Label('Looking for Presence Service...')
self.errors = ListStore(str)
errors_tree = TreeView(model=self.errors)
errors_tree.insert_column_with_attributes(0, 'Log', CellRendererText(),
text=0)
scroller = ScrolledWindow()
scroller.add(errors_tree)
self.paned = VPaned()
self.paned.pack1(scroller)
self.pack_start(self.label, False, False)
self.pack_end(self.paned)
bus.watch_name_owner(PS_NAME, self.on_name_owner_change)
self.ps_watcher = Label('-')
self.paned.pack2(self.ps_watcher)
self.show_all()
def log(self, format, *args):
self.errors.append((format % args,))
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.paned.remove(self.ps_watcher)
self.ps_watcher = PresenceServiceWatcher(self.bus, owner,
self.log)
self.paned.pack2(self.ps_watcher)
self.show_all()
else:
self.label.set_text('Presence Service not running')
if self.ps_watcher is not None:
self.paned.remove(self.ps_watcher)
self.ps_watcher = Label('-')
self.paned.pack2(self.ps_watcher)
except Exception, e:
self.log('ERROR: %s', e)
class Interface(object):
def __init__(self):
self.widget = PresenceServiceNameWatcher(dbus.SessionBus())

View File

@ -1,7 +0,0 @@
sugardir = $(pkgdatadir)/services/console/interface/xo
sugar_PYTHON = \
__init__.py \
xo.py \
cpu.py \
system.py \
nandflash.py

View File

@ -1,2 +0,0 @@
from xo import Interface

View File

@ -1,111 +0,0 @@
# Copyright (C) 2007, Eduardo Silva (edsiper@gmail.com).
#
# 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 os
import sys
import gtk
import string
import gobject
import cairo
import procmem
from graphics.frequency import HorizontalGraphic
class CPU_Usage:
_CPU_HZ = 0
_last_jiffies = 0
_times = 0
def __init__(self):
self._CPU_HZ = os.sysconf(2)
def _get_CPU_data(self):
# Uptime info
stat_file = "/proc/stat"
try:
infile = file(stat_file, "r")
except:
print "Error trying uptime file"
return -1
stat_line = infile.readline()
cpu_info = string.split(stat_line, ' ')
infile.close()
return cpu_info
def _get_CPU_usage(self):
cpu_info = self._get_CPU_data()
used_jiffies = (int(cpu_info[2]) + int(cpu_info[3]) + int(cpu_info[4]))
if self._times ==0:
self._last_jiffies = used_jiffies
self._times +=1
return 0
new_ujiffies = (used_jiffies - self._last_jiffies)
new_ajiffies = ((self.frequency/1000) * self._CPU_HZ)
if new_ajiffies <= 0:
pcpu = 0.0
else:
pcpu = ((new_ujiffies*100)/new_ajiffies)
if pcpu >100:
pcpu = 100
self._times +=1
self._last_jiffies = used_jiffies
return pcpu
class XO_CPU(gtk.Frame):
_frequency_timer = 1
def __init__(self):
gtk.Frame.__init__(self, 'System CPU Usage')
self.set_border_width(10)
width = (gtk.gdk.screen_width() * 99 / 100) - 50
height = (gtk.gdk.screen_height() * 15 / 100) - 20
# Create graphic
self._graphic = HorizontalGraphic()
self._graphic.set_size_request(width, height)
fixed = gtk.Fixed()
fixed.set_border_width(10)
fixed.add(self._graphic)
self.add(fixed)
self._DRW_CPU = CPU_Usage()
self._DRW_CPU.frequency = 1000 # 1 Second
gobject.timeout_add(self._DRW_CPU.frequency, self._update_cpu_usage)
def _update_cpu_usage(self):
print "update XO CPU"
self._cpu = self._DRW_CPU._get_CPU_usage()
self.set_label('System CPU Usage: ' + str(self._cpu) + '%')
# Draw the value into the graphic
self._graphic.draw_value(self._cpu)
return True

View File

@ -1,119 +0,0 @@
# Copyright (C) 2007, Eduardo Silva (edsiper@gmail.com).
#
# 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 gtk
import gobject
from os import statvfs
from label import Label
from graphics.box import *
class XO_NandFlash(gtk.Fixed):
_MOUNT_POINT = '/'
def __init__(self):
gtk.Fixed.__init__(self)
self._frame_text = 'Nand Flash'
self._frame = gtk.Frame(self._frame_text)
self.set_border_width(10)
self._nandflash_box = BoxGraphic(color_mode=COLOR_MODE_REVERSE)
self._nandflash_box.set_size_request(70, 150)
fixed = gtk.Fixed();
fixed.set_border_width(10)
fixed.add(self._nandflash_box)
hbox = gtk.HBox(False, 0)
hbox.pack_start(fixed, False, False, 4)
# Battery info
table = gtk.Table(2, 3)
table.set_border_width(5)
table.set_col_spacings(7)
table.set_row_spacings(7)
label_total_size = Label('Total: ' , Label.DESCRIPTION)
self._label_total_value = Label('0 KB', Label.DESCRIPTION)
label_used_size = Label('Used: ' , Label.DESCRIPTION)
self._label_used_value = Label('0 KB', Label.DESCRIPTION)
label_free_size = Label('Free: ' , Label.DESCRIPTION)
self._label_free_value = Label('0 KB', Label.DESCRIPTION)
# Total
table.attach(label_total_size, 0, 1, 0, 1)
table.attach(self._label_total_value, 1,2, 0,1)
# Used
table.attach(label_used_size, 0, 2, 1, 2)
table.attach(self._label_used_value, 1,3, 1,2)
# Free
table.attach(label_free_size, 0, 3, 2, 3)
table.attach(self._label_free_value, 1,4, 2,3)
alignment = gtk.Alignment(0,0,0,0)
alignment.add(table)
hbox.pack_start(alignment, False, False, 0)
self._frame.add(hbox)
self.add(self._frame)
self.show()
self.update_status()
def update_status(self):
nand = StorageDevice(self._MOUNT_POINT)
# Byte values
total = (nand.f_bsize*nand.f_blocks)
free = (nand.f_bsize*nand.f_bavail)
used = (total - free)
self._label_total_value.set_label(str(total/1024) + ' KB')
self._label_used_value.set_label(str(used/1024) + ' KB')
self._label_free_value.set_label(str(free/1024) + ' KB')
self._usage_percent = ((used*100)/total)
frame_label = self._frame_text + ': ' + str(self._usage_percent) + '%'
self._frame.set_label(frame_label)
self._nandflash_box.set_capacity(self._usage_percent)
class StorageDevice:
f_bsize = 0
f_frsize = 0
f_blocks = 0
f_bfree = 0
f_bavail = 0
f_files = 0
f_ffree = 0
f_favail = 0
f_flag = 0
f_namemax = 0
def __init__(self, mount_point):
self.f_bsize, self.f_frsize, self.f_blocks, self.f_bfree, \
self.f_bavail, self.f_files, self.f_ffree, \
self.f_favail, self.f_flag, self.f_namemax = statvfs(mount_point)
"""
w = gtk.Window()
a = XO_NandFlash()
w.add(a)
w.show_all()
gtk.main()
"""

View File

@ -1,91 +0,0 @@
# Copyright (C) 2007, Eduardo Silva (edsiper@gmail.com).
#
# 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 os
import gtk
import pango
from label import Label
from label import Style
class XO_System(gtk.Fixed):
def __init__(self):
gtk.Fixed.__init__(self)
self.set_border_width(12)
table = gtk.Table(2, 2)
table.set_border_width(15)
table.set_col_spacings(7)
table.set_row_spacings(7)
# BUILD
build = self._read_file('/boot/olpc_build')
label_build = Label('OLPC Build:', Label.DESCRIPTION)
label_build_value = Label(str(build), Label.DESCRIPTION)
# KERNEL
sysinfo = os.uname()
label_kernel = Label('Kernel Version:', Label.DESCRIPTION)
label_kernel_value = Label(sysinfo[0] + '-' + sysinfo[2],\
Label.DESCRIPTION)
# FIRMWARE
firmware = self._read_file('/ofw/openprom/model')
label_firmware = Label('XO Firmware:', Label.DESCRIPTION)
label_firmware_value = Label(firmware, Label.DESCRIPTION)
# SERIAL NUMBER
serial = self._read_file('/ofw/serial-number')
label_serial = Label('XO Serial Number:', Label.DESCRIPTION)
label_serial_value = Label(serial, Label.DESCRIPTION)
# OLPC Build
table.attach(label_build, 0, 1, 0, 1)
table.attach(label_build_value, 1,2, 0,1)
# Kernel Version
table.attach(label_kernel, 0, 1, 1, 2)
table.attach(label_kernel_value, 1, 2, 1, 2)
# XO Firmware
table.attach(label_firmware, 0, 1, 2, 3)
table.attach(label_firmware_value, 1, 2, 2, 3)
# XO Serial Number
table.attach(label_serial, 0, 1, 3, 4)
table.attach(label_serial_value, 1, 2, 3, 4)
frame = gtk.Frame('System Information')
style = Style()
style.set_title_font(frame);
frame.add(table)
self.add(frame)
self.show_all()
def _read_file(self, path):
try:
f = open(path, 'r')
value = f.read()
f.close()
value = value.split('\n')[0]
if value[len(value) - 1] == '\x00':
value = value[:len(value) - 1]
return value
except:
return "None"

View File

@ -1,57 +0,0 @@
# Copyright (C) 2007, Eduardo Silva (edsiper@gmail.com).
#
# 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 os
import gtk
import gobject
import gtk.gdk
import cairo
import string
from cpu import XO_CPU
from system import XO_System
from nandflash import XO_NandFlash
class Interface:
def __init__(self):
self.widget = self.vbox = gtk.VBox(False, 3)
# System information
xo_system = XO_System()
self.vbox.pack_start(xo_system, False, False, 0)
# CPU usage / Graph
xo_cpu = XO_CPU()
self.vbox.pack_start(xo_cpu, False, False, 0)
# Graphics: Battery Status, NandFlash
self._xo_nandflash = XO_NandFlash()
hbox = gtk.HBox(False, 2)
hbox.pack_start(self._xo_nandflash, False, False, 0)
self.vbox.pack_start(hbox, False, False, 0)
self.vbox.show_all()
# Update every 5 seconds
gobject.timeout_add(5000, self._update_components)
def _update_components(self):
self._xo_nandflash.update_status()
return True

View File

@ -1,4 +0,0 @@
sugardir = $(pkgdatadir)/services/console/interface/xserver
sugar_PYTHON = \
__init__.py \
xserver.py

View File

@ -1 +0,0 @@
from xserver import Interface

View File

@ -1,111 +0,0 @@
# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import gobject
from pyxres import XRes
from ui.treeview import TreeView
class XorgView(TreeView):
def __init__(self):
col_names = []
col_names.append({'index': 0, 'name': 'PID'})
col_names.append({'index': 1, 'name': 'Resource Base'})
col_names.append({'index': 2, 'name': 'Pixmap Bytes'})
col_names.append({'index': 3, 'name': 'Other'})
col_names.append({'index': 4, 'name': 'Total'})
col_names.append({'index': 5, 'name': 'Window Name'})
self._window_iter = []
cols_type = [str, str, str, str, str, str]
TreeView.__init__(self, cols_type, col_names)
self._xres = XRes()
self._display = self._xres.open_display()
self.show_all()
gobject.timeout_add(1500, self._update_data)
def _nice_bytes(self, bytes):
prefix = "B"
value = bytes
if bytes/1024:
prefix = "K"
value = (bytes/1024)
return "%s%s" % (value, prefix)
def _update_data(self):
windows = self._xres.get_windows(self._display)
print windows
for w in windows:
row = []
row.append({'index':0, 'info': w.pid})
bytes = self._nice_bytes(w.pixmap_bytes)
obytes = self._nice_bytes(w.other_bytes)
tbytes = self._nice_bytes(w.pixmap_bytes+w.other_bytes)
row.append({'index':1, 'info': hex(w.resource_base)})
row.append({'index':2, 'info': bytes})
row.append({'index':3, 'info': obytes})
row.append({'index':4, 'info': tbytes})
row.append({'index':5, 'info': w.wm_name})
iter = self._get_window_iter(w.pid)
if not iter:
iter = self.add_row(row)
self._set_window_iter(iter, w.pid)
else:
self.update_row(iter, row)
self._clear_down_windows(windows)
return True
def _set_window_iter(self, iter, pid):
self._window_iter.append([iter, pid])
def _remove_iface_iter(self, search_iter):
i = 0
for [iter, pid] in self._window_iter:
if iter == search_iter:
del self._window_iter[i]
return
i+=1
def _get_window_iter(self, wpid):
for [iter, pid] in self._window_iter:
if wpid == pid:
return iter
return None
def _clear_down_windows(self, windows):
for [iter, pid] in self._window_iter:
found = False
for w in windows:
if w.pid == pid:
found = True
break
if not found:
self.remove_row(iter)
self._remove_window_iter(iter)
class Interface(object):
def __init__(self):
self.widget = XorgView()

View File

@ -1,51 +0,0 @@
# Copyright (C) 2007, Eduardo Silva (edsiper@gmail.com).
#
# 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 gtk
import pango
class Label(gtk.Label):
TITLE = 0
DESCRIPTION = 1
def __init__(self, text, font_type):
gtk.Label.__init__(self)
self.set_text(text)
self.set_alignment(0.0, 0.5)
s = {
self.TITLE: self._set_title_font,
self.DESCRIPTION: self._set_description_font
}[font_type]()
def _set_title_font(self):
font = pango.FontDescription('Sans 12')
font.set_weight(pango.WEIGHT_NORMAL)
self.modify_font(font)
def _set_description_font(self):
font = pango.FontDescription('Sans 8')
font.set_weight(pango.WEIGHT_NORMAL)
self.modify_font(font)
class Style:
def set_title_font(self, object):
font = pango.FontDescription('Sans 20')
font.set_weight(pango.WEIGHT_NORMAL)
object.modify_font(font)

View File

@ -1,6 +0,0 @@
SUBDIRS = procmem graphics net ui
sugardir = $(pkgdatadir)/services/console/lib
sugar_PYTHON = \
pyxres.py

View File

@ -1,7 +0,0 @@
sugardir = $(pkgdatadir)/services/console/lib/graphics
sugar_PYTHON = \
__init__.py \
box.py \
frequency.py

View File

@ -1,101 +0,0 @@
# Copyright (C) 2007, Eduardo Silva (edsiper@gmail.com).
#
# 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 gtk
import gobject
import cairo
COLOR_MODE_NORMAL = 0
COLOR_MODE_REVERSE = 1
class BoxGraphic(gtk.DrawingArea):
__gtype_name__ = 'ConsoleBoxGraphic'
__gproperties__ = {
'color-mode': (gobject.TYPE_INT, None, None, 0, 1, COLOR_MODE_NORMAL,
gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY)
}
_color_status_high = [0, 0, 0]
_color_status_medium = [0, 0, 0]
_color_status_low = [0, 0, 0]
_limit_high = 0
_limit_medium = 0
_limit_low = 0
def __init__(self, **kwargs):
gobject.GObject.__init__(self, **kwargs)
gtk.DrawingArea.__init__(self)
self.connect("expose-event", self.do_expose)
self.connect('size-allocate', self._change_size_cb)
def do_expose(self, widget, event):
context = widget.window.cairo_create()
context.rectangle(0, 0, self._width, self._height)
context.set_source_rgb (0,0,0)
context.fill_preserve()
context.stroke()
self._draw_content(context, self._percent)
def do_set_property(self, pspec, value):
if pspec.name == 'color-mode':
self._configure(mode=value)
else:
raise AssertionError
def set_capacity(self, percent):
self._percent = percent
self.queue_draw()
def _configure(self, mode):
# Normal mode configure the box as a battery
# full is good, empty is bad
if mode == COLOR_MODE_NORMAL:
self._color_status_high = [0, 1, 0]
self._color_status_medium = [1,1,0]
self._color_status_low = [1,0,0]
self._limit_high = 60
self._limit_medium = 10
# Reverse mode configure the box as a storage device
# full is bad, empty is good
elif mode == COLOR_MODE_REVERSE:
self._color_status_high = [1,0,0]
self._color_status_medium = [1,1,0]
self._color_status_low = [0, 1, 0]
self._limit_high = 85
self._limit_medium = 40
def _draw_content(self, context, percent):
usage_height = (percent*self._height)/100
context.rectangle(0, self._height - usage_height, self._width, self._height)
if self._percent > self._limit_high:
context.set_source_rgb(*self._color_status_high)
elif self._percent >= self._limit_medium and self._percent <= self._limit_high:
context.set_source_rgb(*self._color_status_medium)
elif self._percent < self._limit_medium:
context.set_source_rgb(*self._color_status_low)
context.fill_preserve()
def _change_size_cb(self, widget, allocation):
self._width = allocation.width
self._height = allocation.height
self.queue_draw()

View File

@ -1,147 +0,0 @@
# Copyright (C) 2007, Eduardo Silva (edsiper@gmail.com).
#
# 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 gtk
class HorizontalGraphic(gtk.DrawingArea):
_MARGIN = 5
_LINE_WIDTH = 2
_GRAPH_OFFSET = 7
_range_x = []
_range_y = []
_frequency_timer = 0
def __init__(self):
gtk.DrawingArea.__init__(self)
self._width = 0
self._height = 0
self._buffer = [0]
self.connect('expose-event', self.do_expose)
self.connect('size-allocate', self._change_size_cb)
def do_expose(self, widget, event):
context = widget.window.cairo_create()
context.rectangle(0, 0, self._width - 1, self._height - 1)
context.set_source_rgb (0,0,0)
context.fill_preserve()
context.set_line_width(self._LINE_WIDTH)
if event.area.x == 0:
draw_all = True
self._draw_border_lines(context)
context.stroke()
else:
draw_all = False
context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
context.clip()
context.set_source_rgb(1, 1, 1)
self._draw_buffer(event, widget, context, draw_all)
context.stroke()
self._updated = False
return False
def draw_value(self, percent):
redraw_all = False
if (len(self._buffer) + 1) *self._GRAPH_OFFSET >= self._width:
redraw_all = True
self._buffer = [self._buffer[-1]]
length = 1
else:
length = len(self._buffer) - 1
self._buffer.append(percent)
self._updated = True
if redraw_all:
area_x = 0
area_y = 0
height = self._height
width = self._width
else:
area_x = self._graph_x + (length*self._GRAPH_OFFSET)
area_y = self._graph_y
width = self._GRAPH_OFFSET*2
height = self._graph_height
self.queue_draw_area(area_x, area_y, width, height)
self._frequency_timer += 1
return True
def _draw_border_lines(self, context):
context.set_source_rgb(1, 1, 1)
self._draw_line(context, self._MARGIN, self._MARGIN, self._MARGIN, self._height - self._MARGIN)
self._draw_line(context, self._MARGIN, self._height - self._MARGIN - 1, self._width - self._MARGIN, self._height - self._MARGIN - 1)
def _draw_line(self, context, from_x, from_y, to_x, to_y):
context.move_to(from_x, from_y)
context.line_to(to_x, to_y)
def _draw_buffer(self, event, drwarea, context, draw_all=True):
buffer_offset = 0
freq = 1 # Frequency timer
length = len(self._buffer)
if length == 0:
return
# Context properties
context.set_line_width(self._LINE_WIDTH)
context.set_source_rgb(0,1,0)
if draw_all == True:
buffer_offset = 0
freq = 0
else:
freq = buffer_offset = (event.area.x/self._GRAPH_OFFSET)
for percent in self._buffer[buffer_offset:length]:
if buffer_offset == 0:
from_y = self._get_y(self._buffer[0])
from_x = self._graph_x
else:
from_y = self._get_y(self._buffer[buffer_offset-1])
from_x = (freq * self._GRAPH_OFFSET)
to_x = (freq+1) * self._GRAPH_OFFSET
to_y = self._get_y(percent)
self._draw_line(context, from_x, from_y, to_x, to_y)
buffer_offset+=1
freq+=1
context.stroke()
def _get_y(self, percent):
if percent==0:
percent = 1
graph_y = ((self._height)/(100 - 1))*(percent - 1)
y = self._LINE_WIDTH + abs(abs(self._height - graph_y) - self._MARGIN*2)
return int(y)
def _change_size_cb(self, widget, allocation):
self._width = allocation.width
self._height = allocation.height
self._graph_x = self._MARGIN + self._LINE_WIDTH
self._graph_y = self._MARGIN
self._graph_width = self._width - (self._MARGIN*2 + self._LINE_WIDTH)
self._graph_height = self._height - ((self._MARGIN*2 + self._LINE_WIDTH))

View File

@ -1,7 +0,0 @@
sugardir = $(pkgdatadir)/services/console/lib/net
sugar_PYTHON = \
__init__.py \
device.py

View File

@ -1,94 +0,0 @@
# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
#
# 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 socket
import fcntl
import struct
import string
class Device:
def __init__(self):
self._dev = self.get_interfaces()
def get_interfaces(self):
netdevfile = "/proc/net/dev"
dev = []
try:
infile = file(netdevfile, "r")
except:
print "Error trying " + netdevfile
skip = 0
for line in infile:
# Skip first two lines
skip += 1
if skip <= 2:
continue
iface = string.split(line, ":",1)
arr = string.split(iface[1])
if len(arr) < 10:
continue
info = {'interface': iface[0].strip(), \
'bytes_recv': arr[0],\
'bytes_sent': arr[8],\
'packets_recv': arr[1],
'packets_sent': arr[9]}
dev.append(info)
return dev
def get_iface_info(self, ifname):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
hwaddr = []
try:
ip = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, \
struct.pack('256s', ifname[:15]))[20:24])
except:
ip = None
try:
netmask = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x891b, \
struct.pack('256s', ifname[:15]))[20:24])
except:
netmask = None
try:
mac = []
info = fcntl.ioctl(s.fileno(), 0x8927, \
struct.pack('256s', ifname[:15]))
for char in info[18:24]:
hdigit = hex(ord(char))[2:]
if len(hdigit):
mac.append(hdigit)
except:
mac = None
mac_string = self.mac_to_string(mac)
return [ip, netmask, mac_string]
def mac_to_string(self, hexa):
string = ''
for value in hexa:
if len(string)==0:
string = value
else:
string += ':'+value
return string

View File

@ -1,8 +0,0 @@
sugardir = $(pkgdatadir)/services/console/lib/procmem
sugar_PYTHON = \
__init__.py \
proc.py \
proc_smaps.py \
analysis.py

View File

@ -1,32 +0,0 @@
import proc, proc_smaps
class Analysis:
pid = 0
def __init__(self, pid):
self.pid = pid
def SMaps(self):
smaps = proc_smaps.ProcSmaps(self.pid)
private_dirty = 0
shared_dirty = 0
referenced = 0
for map in smaps.mappings:
private_dirty += map.private_dirty
shared_dirty += map.shared_dirty
referenced += map.referenced
smaps = {"private_dirty": int(private_dirty), \
"shared_dirty": int(shared_dirty),\
"referenced": int(referenced)}
return smaps
def ApproxRealMemoryUsage(self):
maps = proc_smaps.ProcMaps(self.pid)
size = (maps.clean_size/1024)
return size

View File

@ -1,109 +0,0 @@
import os
import re
import sys
import string
class ProcInfo:
dir_path = "/proc/" # Our cute Proc File System
status_file = "status"
stat_file = "stat"
proc_list = [] # Our PID list :D
proc_info = [] #
def __init__(self):
self.proc_list = self.Get_PID_List()
# Returns Process List
def Get_PID_List(self):
list = []
# Exists our procfs ?
if os.path.isdir(self.dir_path):
# around dir entries
for f in os.listdir(self.dir_path):
if os.path.isdir(self.dir_path+f) & str.isdigit(f):
list.append(int(f))
return list
def MemoryInfo(self, pid):
# Path
pidfile = self.dir_path + str(pid) + "/stat"
try:
infile = open(pidfile, "r")
except:
print "Error trying " + pidfile
return None
# Parsing data , check 'man 5 proc' for details
stat_data = infile.read()
infile.close()
process_name = self._get_process_name(stat_data)
data = self._get_safe_split(stat_data)
state_dic = {
'R': 'Running',
'S': 'Sleeping',
'D': 'Disk sleep',
'Z': 'Zombie',
'T': 'Traced/Stopped',
'W': 'Paging'
}
# user and group owners
pidstat = os.stat(pidfile)
info = {
'pid': int(data[0]), # Process ID
'name': process_name,
'state': data[2], # Process State, ex: R|S|D|Z|T|W
'state_name': state_dic[data[2]], # Process State name, ex: Running, sleeping, Zombie, etc
'ppid': int(data[3]), # Parent process ID
'utime': int(data[13]), # Used jiffies in user mode
'stime': int(data[14]), # Used jiffies in kernel mode
'start_time': int(data[21]), # Process time from system boot (jiffies)
'vsize': int(data[22]), # Virtual memory size used (bytes)
'rss': int(data[23])*4, # Resident Set Size (bytes)
'user_id': pidstat.st_uid, # process owner
'group_id': pidstat.st_gid # owner group
}
return info
# Return the process name
def _get_process_name(self, data):
m = re.search("\(.*\)", data)
return m.string[m.start()+1:m.end()-1]
def _get_safe_split(self, data):
new_data = re.sub("\(.*\)", '()', data)
return new_data.split()
# Returns the CPU usage expressed in Jiffies
def get_CPU_usage(self, cpu_hz, used_jiffies, start_time):
# Uptime info
uptime_file = self.dir_path + "/uptime"
try:
infile = file(uptime_file, "r")
except:
print "Error trying uptime file"
return None
uptime_line = infile.readline()
uptime = string.split(uptime_line, " ",2)
infile.close()
# System uptime, from /proc/uptime
uptime = float(uptime[0])
# Jiffies
avail_jiffies = (uptime * cpu_hz) - start_time
cpu_usage = {'used_jiffies': used_jiffies, 'avail_jiffies': avail_jiffies}
return cpu_usage

View File

@ -1,138 +0,0 @@
####################################################################
# This class open the /proc/PID/maps and /proc/PID/smaps files
# to get useful information about the real memory usage
####################################################################
import os
# Parse the /proc/PID/smaps file
class ProcSmaps:
mappings = [] # Devices information
def __init__(self, pid):
smapfile = "/proc/%s/smaps" % pid
self.mappings = []
# Coded by Federico Mena (script)
try:
infile = open(smapfile, "r")
input = infile.read()
infile.close()
except:
print "Error trying " + smapfile
return
lines = input.splitlines()
num_lines = len (lines)
line_idx = 0
# 08065000-08067000 rw-p 0001c000 03:01 147613 /opt/gnome/bin/evolution-2.6
# Size: 8 kB
# Rss: 8 kB
# Shared_Clean: 0 kB
# Shared_Dirty: 0 kB
# Private_Clean: 8 kB
# Private_Dirty: 0 kB
# Referenced: 4 kb -> Introduced in kernel 2.6.22
while num_lines > 0:
fields = lines[line_idx].split (" ", 5)
if len (fields) == 6:
(offsets, permissions, bin_permissions, device, inode, name) = fields
else:
(offsets, permissions, bin_permissions, device, inode) = fields
name = ""
size = self.parse_smaps_size_line (lines[line_idx + 1])
rss = self.parse_smaps_size_line (lines[line_idx + 2])
shared_clean = self.parse_smaps_size_line (lines[line_idx + 3])
shared_dirty = self.parse_smaps_size_line (lines[line_idx + 4])
private_clean = self.parse_smaps_size_line (lines[line_idx + 5])
private_dirty = self.parse_smaps_size_line (lines[line_idx + 6])
referenced = self.parse_smaps_size_line (lines[line_idx + 7])
name = name.strip ()
mapping = Mapping (size, rss, shared_clean, shared_dirty, \
private_clean, private_dirty, referenced, permissions, name)
self.mappings.append (mapping)
num_lines -= 8
line_idx += 8
self._clear_reference(pid)
def _clear_reference(self, pid):
os.system("echo 1 > /proc/%s/clear_refs" % pid)
# Parses a line of the form "foo: 42 kB" and returns an integer for the "42" field
def parse_smaps_size_line (self, line):
# Rss: 8 kB
fields = line.split ()
return int(fields[1])
class Mapping:
def __init__ (self, size, rss, shared_clean, shared_dirty, \
private_clean, private_dirty, referenced, permissions, name):
self.size = size
self.rss = rss
self.shared_clean = shared_clean
self.shared_dirty = shared_dirty
self.private_clean = private_clean
self.private_dirty = private_dirty
self.referenced = referenced
self.permissions = permissions
self.name = name
# Parse /proc/PID/maps file to get the clean memory usage by process,
# we avoid lines with backed-files
class ProcMaps:
clean_size = 0
def __init__(self, pid):
mapfile = "/proc/%s/maps" % pid
try:
infile = open(mapfile, "r")
except:
print "Error trying " + mapfile
return None
sum = 0
to_data_do = {
"[anon]": self.parse_size_line,
"[heap]": self.parse_size_line
}
for line in infile:
arr = line.split()
# Just parse writable mapped areas
if arr[1][1] != "w":
continue
if len(arr) == 6:
# if we got a backed-file we skip this info
if os.path.isfile(arr[5]):
continue
else:
line_size = to_data_do.get(arr[5], self.skip)(line)
sum += line_size
else:
line_size = self.parse_size_line(line)
sum += line_size
infile.close()
self.clean_size = sum
def skip(self, line):
return 0
# Parse a maps line and return the mapped size
def parse_size_line(self, line):
start, end = line.split()[0].split('-')
size = int(end, 16) - int(start, 16)
return size

View File

@ -1,256 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
#
# 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 ctypes import *
# XText Property
class _XTextProperty(Structure):
pass
_XTextProperty._fields_ = [("value", c_char_p),\
("encoding", c_ulong),\
("format", c_int),\
("nitems", c_ulong)]
# XResType Structure
class _XResTypeStruct(Structure):
pass
_XResTypeStruct._fields_ = [("resource_type", c_ulong),\
("count", c_uint)]
_ATOMNAMES = ["PIXMAP",\
"WINDOW",\
"GC",\
"FONT",\
"GLYPHSET",\
"PICTURE",\
"COLORMAP ENTRY",\
"PASSIVE GRAB",\
"CURSOR",\
"_NET_CLIENT_LIST",\
"_NET_WM_PID",\
"_NET_WM_NAME",\
"UTF8_STRING",\
"WM_NAME", # FIXME!
"CARDINAL" # FIXME!
]
_ATOM_PIXMAP = 0
_ATOM_WINDOW = 1
_ATOM_GC = 2
_ATOM_FONT = 3
_ATOM_GLYPHSET = 4
_ATOM_PICTURE = 5
_ATOM_COLORMAP_ENTRY = 6
_ATOM_PASSIVE_GRAB = 7
_ATOM_CURSOR = 8
_ATOM_NET_CLIENT_LIST = 9
_ATOM_NET_WM_PID = 10
_ATOM_NET_WM_NAME = 11
_ATOM_UTF8_STRING = 12
_ATOM_WM_NAME = 13
_ATOM_CARDINAL = 14
# XText Property
class _XTextProperty(Structure):
pass
_XTextProperty._fields_ = [("value", c_char_p),\
("encoding", c_ulong),\
("format", c_int),\
("nitems", c_ulong)]
class XRes(object):
_XRESLIB = "libXRes.so"
_XMULIB = "libXmu.so.6"
def __init__(self):
self._lib = CDLL(self._XRESLIB)
self._lib_xmu = CDLL(self._XMULIB)
def _set_atoms(self, display):
self.atoms = []
for atom in _ATOMNAMES:
atom_value = self._lib.XInternAtom(display, atom, True)
self.atoms.append(atom_value)
def open_display(self, display=None):
display = self._lib.XOpenDisplay(display)
self._set_atoms(display)
return display
# Return an array with XRestTypes:
#
# XResType.resource_type (Atom_type)
# XResTyoe.count
def get_resources(self, display, resource_base):
n_types = c_long()
types = pointer(_XResTypeStruct())
self._lib.XResQueryClientResources(display, resource_base, \
byref(n_types), byref(types))
pytypes = []
for t in types[:n_types.value]:
pytypes.append(t)
return pytypes
def get_windows(self, display):
self._windows = []
root = self._lib.XDefaultRootWindow(display)
self._lookat(display, root)
return self._windows
def _lookat(self, display, win_root):
wp = self._get_window_properties (display, win_root)
if wp:
self._windows.append(wp)
w = None
dummy = self._Window()
children = self._Window()
nchildren = c_uint()
r = self._lib.XQueryTree(display, win_root, byref(dummy), \
byref(dummy), byref(children), byref(nchildren))
for client in children[:nchildren.value]:
cli = self._lib_xmu.XmuClientWindow (display, client)
if client is not None:
wp = self._get_window_properties (display, cli)
if wp:
self._windows.append(wp)
def _get_window_properties(self, display, w):
cliargv = c_char_p()
cliargc = c_long()
machtp = pointer(_XTextProperty())
nametp = _XTextProperty()
w_name = None
if not self._lib.XGetWMClientMachine (display, w, byref(machtp)):
machtp.value = None
machtp.encoding = None
if not self._lib.XGetCommand(display, w, byref(cliargv), byref(cliargc)):
return
if self._lib.XGetWMName(display, w, byref(nametp)):
w_name = nametp.value
bytes = c_ulong()
self._lib.XResQueryClientPixmapBytes(display, w, byref(bytes))
w_pixmaps = bytes.value
type = self._Atom()
format = c_int()
n_items = c_ulong()
bytes_after = c_int()
w_pid = pointer(c_long())
wname = c_char_p()
self._lib.XGetWindowProperty(display, w,\
self.atoms[_ATOM_NET_WM_PID],
0, 2L,\
False, self.atoms[_ATOM_CARDINAL],\
byref(type), byref(format), \
byref(n_items), byref(bytes_after), \
byref(w_pid))
# Calc aditional X resources by window
res = self.get_resources(display, w)
n_windows = 0
n_gcs = 0
n_pictures = 0
n_glyphsets = 0
n_fonts = 0
n_colormaps = 0
n_passive_grabs = 0
n_cursors = 0
n_other = 0
for r in res:
if r.resource_type == self.atoms[_ATOM_WINDOW]:
n_windows += r.count
elif r.resource_type == self.atoms[_ATOM_GC]:
n_gcs += r.count
elif r.resource_type == self.atoms[_ATOM_PICTURE]:
n_pictures += r.count
elif r.resource_type == self.atoms[_ATOM_GLYPHSET]:
n_glyphsets += r.count
elif r.resource_type == self.atoms[_ATOM_FONT]:
n_fonts += r.count
elif r.resource_type == self.atoms[_ATOM_COLORMAP_ENTRY]:
n_colormaps += r.count
elif r.resource_type == self.atoms[_ATOM_PASSIVE_GRAB]:
n_passive_grabs += r.count
elif r.resource_type == self.atoms[_ATOM_CURSOR]:
n_cursors += r.count
else:
n_other += r.count
other_bytes = n_windows * 24
other_bytes += n_gcs * 24
other_bytes += n_pictures * 24
other_bytes += n_glyphsets * 24
other_bytes += n_fonts * 1024
other_bytes += n_colormaps * 24
other_bytes += n_passive_grabs * 24
other_bytes += n_cursors * 24
other_bytes += n_other * 24
window = Window(w, w_pid.contents.value, w_pixmaps, \
n_windows, n_gcs, n_fonts, n_glyphsets, n_pictures,\
n_colormaps, n_passive_grabs, n_cursors, n_other,\
other_bytes, w_name)
return window
# Data types
def _Window(self):
return pointer(c_ulong())
def _Atom(self, data=0):
return pointer(c_ulong(data))
class Window(object):
def __init__(self, resource_base, pid, pixmap_bytes=0,\
n_windows=0, n_gcs=0, n_fonts=0, n_glyphsets=0, n_pictures=0,\
n_colormaps=0, n_passive_grabs=0, n_cursors=0, n_other=0,\
other_bytes=0, wm_name=None):
self.resource_base = resource_base
self.pid = pid
self.pixmap_bytes = pixmap_bytes
self.n_windows = n_windows
self.n_gcs = n_gcs
self.n_fonts = n_fonts
self.n_glyphsets = n_glyphsets
self.n_pictures = n_pictures
self.n_colormaps = n_colormaps
self.n_passive_grabs = n_passive_grabs
self.n_cursors = n_cursors
self.n_other = n_other
self.other_bytes = other_bytes
self.wm_name = wm_name

View File

@ -1,5 +0,0 @@
sugardir = $(pkgdatadir)/services/console/lib/ui
sugar_PYTHON = \
__init__.py \
treeview.py

View File

@ -1,73 +0,0 @@
# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import gtk
class TreeView(gtk.ScrolledWindow):
iters = [] # Iters index
# Create a window with a treeview object
#
# cols = List of dicts, ex:
#
# cols = []
# cols.append({'index': integer_index_position, 'name': string_col_name})
def __init__(self, cols_def, cols_name):
gtk.ScrolledWindow.__init__(self)
self._iters = []
self._treeview = gtk.TreeView()
# Creating column data types
self._store = gtk.TreeStore(*cols_def)
# Columns definition
cell = gtk.CellRendererText()
tv_cols = []
i=0
for col in cols_name:
col_tv = gtk.TreeViewColumn(col['name'], cell, text=i)
col_tv.set_reorderable(True)
col_tv.set_resizable(True)
tv_cols.append(col_tv)
i+=1
# Setting treeview properties
self._treeview.set_model(self._store)
self._treeview.set_enable_search(True)
self._treeview.set_rules_hint(True)
for col in tv_cols:
self._treeview.append_column(col)
self.add(self._treeview)
def add_row(self, cols_data):
iter = self._store.insert_after(None, None)
for col in cols_data:
print col['index'],col['info']
self._store.set_value(iter, int(col['index']) , col['info'])
self.iters.append(iter)
return iter
def update_row(self, iter, cols_data):
for col in cols_data:
self._store.set_value(iter, int(col['index']) , str(col['info']))
def remove_row(self, iter):
self._store.remove(iter)

View File

@ -1,4 +0,0 @@
[D-BUS Service]
Name = org.laptop.sugar.Console
Exec = @bindir@/sugar-console

View File

@ -1,20 +0,0 @@
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import os
import sys
from sugar import env
from sugar import util
sys.path.append(env.get_service_path('console'))
# change to the user's home directory if it is set
# root if not
os.chdir(os.environ.get('HOME', '/'))
#set the process title so it shows up as sugar-console not python
util.set_proc_title('sugar-console')
import console

View File

@ -0,0 +1,428 @@
# Copyright (C) 2007, One Laptop Per Child
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
#
#
# The language config is based on the system-config-language
# (http://fedoraproject.org/wiki/SystemConfig/language) tool
# and the timezone config on the system-config-date
# (http://fedoraproject.org/wiki/SystemConfig/date) tool.
# Parts of the code were reused.
#
import os
import string
import shutil
from gettext import gettext as _
from sugar import profile
from sugar.graphics.xocolor import XoColor
_COLORS = {'red': {'dark':'#b20008', 'medium':'#e6000a', 'light':'#ffadce'},
'orange': {'dark':'#9a5200', 'medium':'#c97e00', 'light':'#ffc169'},
'yellow': {'dark':'#807500', 'medium':'#be9e00', 'light':'#fffa00'},
'green': {'dark':'#008009', 'medium':'#00b20d', 'light':'#8bff7a'},
'blue': {'dark':'#00588c', 'medium':'#005fe4', 'light':'#bccdff'},
'purple': {'dark':'#5e008c', 'medium':'#7f00bf', 'light':'#d1a3ff'}
}
_MODIFIERS = ('dark', 'medium', 'light')
_TIMEZONE_CONFIG = '/etc/sysconfig/clock'
_LANGUAGES = {
'Afrikaans/South_Africa': ('af_ZA', 'lat0-sun16'),
'Albanian': ('sq_AL.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Algeria': ('ar_DZ.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Bahrain': ('ar_BH.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Egypt': ('ar_EG.UTF-8', 'latarcyrheb-sun16'),
'Arabic/India': ('ar_IN.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Iraq': ('ar_IQ.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Jordan': ('ar_JO.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Kuwait': ('ar_KW.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Lebanon': ('ar_LB.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Libyan_Arab_Jamahiriya': ('ar_LY.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Morocco': ('ar_MA.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Oman': ('ar_OM.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Qatar': ('ar_QA.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Saudi_Arabia': ('ar_SA.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Sudan': ('ar_SD.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Syrian_Arab_Republic': ('ar_SY.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Tunisia': ('ar_TN.UTF-8', 'latarcyrheb-sun16'),
'Arabic/United_Arab_Emirates': ('ar_AE.UTF-8', 'latarcyrheb-sun16'),
'Arabic/Yemen': ('ar_YE.UTF-8', 'latarcyrheb-sun16'),
'Basque/Spain': ('eu_ES.UTF-8', 'latarcyrheb-sun16'),
'Belarusian': ('be_BY.UTF-8', 'latarcyrheb-sun16'),
'Bengali/BD': ('bn_BD.UTF-8', 'latarcyrheb-sun16'),
'Bengali/India': ('bn_IN.UTF-8', 'latarcyrheb-sun16'),
'Bosnian/Bosnia_and_Herzegowina': ('bs_BA', 'lat2-sun16'),
'Breton/France': ('br_FR', 'lat0-sun16'),
'Bulgarian': ('bg_BG.UTF-8', 'latarcyrheb-sun16'),
'Catalan/Spain': ('ca_ES.UTF-8', 'latarcyrheb-sun16'),
'Chinese/Hong_Kong': ('zh_HK.UTF-8', 'latarcyrheb-sun16'),
'Chinese/P.R._of_China': ('zh_CN.UTF-8', 'lat0-sun16'),
'Chinese/Taiwan': ('zh_TW.UTF-8', 'lat0-sun16'),
'Cornish/Britain': ('kw_GB.UTF-8', 'latarcyrheb-sun16'),
'Croatian': ('hr_HR.UTF-8', 'latarcyrheb-sun16'),
'Czech': ('cs_CZ.UTF-8', 'latarcyrheb-sun16'),
'Danish': ('da_DK.UTF-8', 'latarcyrheb-sun16'),
'Dutch/Belgium': ('nl_BE.UTF-8', 'latarcyrheb-sun16'),
'Dutch/Netherlands': ('nl_NL.UTF-8', 'latarcyrheb-sun16'),
'English/Australia': ('en_AU.UTF-8', 'latarcyrheb-sun16'),
'English/Botswana': ('en_BW.UTF-8', 'latarcyrheb-sun16'),
'English/Canada': ('en_CA.UTF-8', 'latarcyrheb-sun16'),
'English/Denmark': ('en_DK.UTF-8', 'latarcyrheb-sun16'),
'English/Great_Britain': ('en_GB.UTF-8', 'latarcyrheb-sun16'),
'English/Hong_Kong': ('en_HK.UTF-8', 'latarcyrheb-sun16'),
'English/India': ('en_IN.UTF-8', 'latarcyrheb-sun16'),
'English/Ireland': ('en_IE.UTF-8', 'latarcyrheb-sun16'),
'English/New_Zealand': ('en_NZ.UTF-8', 'latarcyrheb-sun16'),
'English/Philippines': ('en_PH.UTF-8', 'latarcyrheb-sun16'),
'English/Singapore': ('en_SG.UTF-8', 'latarcyrheb-sun16'),
'English/South_Africa': ('en_ZA.UTF-8', 'latarcyrheb-sun16'),
'English/USA': ('en_US.UTF-8', 'latarcyrheb-sun16'),
'English/Zimbabwe': ('en_ZW.UTF-8', 'latarcyrheb-sun16'),
'Estonian': ('et_EE.UTF-8', 'latarcyrheb-sun16'),
'Faroese/Faroe_Islands': ('fo_FO.UTF-8', 'latarcyrheb-sun16'),
'Finnish': ('fi_FI.UTF-8', 'latarcyrheb-sun16'),
'French/Belgium': ('fr_BE.UTF-8', 'latarcyrheb-sun16'),
'French/Canada': ('fr_CA.UTF-8', 'latarcyrheb-sun16'),
'French/France': ('fr_FR.UTF-8', 'latarcyrheb-sun16'),
'French/Luxemburg': ('fr_LU.UTF-8', 'latarcyrheb-sun16'),
'French/Switzerland': ('fr_CH.UTF-8', 'latarcyrheb-sun16'),
'Galician/Spain': ('gl_ES.UTF-8', 'latarcyrheb-sun16'),
'German/Austria': ('de_AT.UTF-8', 'latarcyrheb-sun16'),
'German/Belgium': ('de_BE.UTF-8', 'latarcyrheb-sun16'),
'German/Germany': ('de_DE.UTF-8', 'latarcyrheb-sun16'),
'German/Luxemburg': ('de_LU.UTF-8', 'latarcyrheb-sun16'),
'German/Switzerland': ('de_CH.UTF-8', 'latarcyrheb-sun16'),
'Greek': ('el_GR.UTF-8', 'latarcyrheb-sun16'),
'Greenlandic/Greenland': ('kl_GL.UTF-8', 'latarcyrheb-sun16'),
'Gujarati/India': ('gu_IN.UTF-8', 'latarcyrheb-sun16'),
'Hebrew/Israel': ('he_IL.UTF-8', 'latarcyrheb-sun16'),
'Hindi/India': ('hi_IN.UTF-8', 'latarcyrheb-sun16'),
'Hungarian': ('hu_HU.UTF-8', 'latarcyrheb-sun16'),
'Icelandic': ('is_IS.UTF-8', 'latarcyrheb-sun16'),
'Indonesian': ('id_ID.UTF-8', 'latarcyrheb-sun16'),
'Irish': ('ga_IE.UTF-8', 'latarcyrheb-sun16'),
'Italian/Italy': ('it_IT.UTF-8', 'latarcyrheb-sun16'),
'Italian/Switzerland': ('it_CH.UTF-8', 'latarcyrheb-sun16'),
'Japanese': ('ja_JP.UTF-8', 'lat0-sun16'),
'Korean/Republic_of_Korea': ('ko_KR.UTF-8', 'lat0-sun16'),
'Lao/Laos': ('lo_LA.UTF-8', 'latarcyrheb-sun16'),
'Latvian/Latvia': ('lv_LV.UTF-8', 'latarcyrheb-sun16'),
'Lithuanian': ('lt_LT.UTF-8', 'latarcyrheb-sun16'),
'Macedonian': ('mk_MK.UTF-8', 'latarcyrheb-sun16'),
'Malay/Malaysia': ('ms_MY.UTF-8', 'latarcyrheb-sun16'),
'Maltese/malta': ('mt_MT.UTF-8', 'latarcyrheb-sun16'),
'Manx/Britain': ('gv_GB.UTF-8', 'latarcyrheb-sun16'),
'Marathi/India': ('mr_IN.UTF-8', 'latarcyrheb-sun16'),
'Northern/Norway': ('se_NO', 'latarcyrheb-sun16'),
'Norwegian': ('nb_NO.UTF-8', 'latarcyrheb-sun16'),
'Norwegian,/Norway': ('nn_NO.UTF-8', 'latarcyrheb-sun16'),
'Occitan/France': ('oc_FR', 'lat0-sun16'),
'Oriya/India': ('or_IN.UTF-8', 'latarcyrheb-sun16'),
'Persian/Iran': ('fa_IR.UTF-8', 'latarcyrheb-sun16'),
'Polish': ('pl_PL.UTF-8', 'latarcyrheb-sun16'),
'Portuguese/Brasil': ('pt_BR.UTF-8', 'latarcyrheb-sun16'),
'Portuguese/Portugal': ('pt_PT.UTF-8', 'latarcyrheb-sun16'),
'Punjabi/India': ('pa_IN.UTF-8', 'latarcyrheb-sun16'),
'Romanian': ('ro_RO.UTF-8', 'latarcyrheb-sun16'),
'Russian': ('ru_RU.UTF-8', 'latarcyrheb-sun16'),
'Russian/Ukraine': ('ru_UA.UTF-8', 'latarcyrheb-sun16'),
'Serbian': ('sr_CS.UTF-8', 'latarcyrheb-sun16'),
'Serbian/Latin': ('sr_CS.UTF-8@Latn', 'latarcyrheb-sun16'),
'Slovak': ('sk_SK.UTF-8', 'latarcyrheb-sun16'),
'Slovenian/Slovenia': ('sl_SI.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Argentina': ('es_AR.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Bolivia': ('es_BO.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Chile': ('es_CL.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Colombia': ('es_CO.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Costa_Rica': ('es_CR.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Dominican_Republic': ('es_DO.UTF-8', 'latarcyrheb-sun16'),
'Spanish/El_Salvador': ('es_SV.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Equador': ('es_EC.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Guatemala': ('es_GT.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Honduras': ('es_HN.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Mexico': ('es_MX.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Nicaragua': ('es_NI.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Panama': ('es_PA.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Paraguay': ('es_PY.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Peru': ('es_PE.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Puerto_Rico': ('es_PR.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Spain': ('es_ES.UTF-8', 'latarcyrheb-sun16'),
'Spanish/USA': ('es_US.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Uruguay': ('es_UY.UTF-8', 'latarcyrheb-sun16'),
'Spanish/Venezuela': ('es_VE.UTF-8', 'latarcyrheb-sun16'),
'Swedish/Finland': ('sv_FI.UTF-8', 'latarcyrheb-sun16'),
'Swedish/Sweden': ('sv_SE.UTF-8', 'latarcyrheb-sun16'),
'Tagalog/Philippines': ('tl_PH', 'lat0-sun16'),
'Tamil/India': ('ta_IN.UTF-8', 'latarcyrheb-sun16'),
'Telugu/India': ('te_IN.UTF-8', 'latarcyrheb-sun16'),
'Thai': ('th_TH.UTF-8', 'latarcyrheb-sun16'),
'Turkish': ('tr_TR.UTF-8', 'latarcyrheb-sun16'),
'Ukrainian': ('uk_UA.UTF-8', 'latarcyrheb-sun16'),
'Urdu/Pakistan': ('ur_PK', 'latarcyrheb-sun16'),
'Uzbek/Uzbekistan': ('uz_UZ', 'lat0-sun16'),
'Walloon/Belgium': ('wa_BE@euro', 'lat0-sun16'),
'Welsh/Great_Britain': ('cy_GB.UTF-8', 'latarcyrheb-sun16'),
'Xhosa/South_Africa': ('xh_ZA.UTF-8', 'latarcyrheb-sun16'),
'Zulu/South_Africa': ('zu_ZA.UTF-8', 'latarcyrheb-sun16')
}
_timezones = []
def _initialize():
_timezones = _read_zonetab()
j=0
for timezone in _timezones:
set_timezone.__doc__ += timezone+', '
j+=1
if j%3 == 0:
set_timezone.__doc__ += '\n'
if not os.access(_TIMEZONE_CONFIG, os.R_OK):
#Theres no /etc/sysconfig/clock file, so make one
fd = open(_TIMEZONE_CONFIG, 'w')
f.write(' The ZONE parameter is only evaluated by sugarcontrol.\n')
f.write('The timezone of the system' +
' is defined by the contents of /etc/localtime.\n')
f.write('ZONE="America/NEW_York"\n')
f.close()
keys = _LANGUAGES.keys()
keys.sort()
i = 0
for key in keys:
set_language.__doc__ += key+', '
i+=1
if i%3 == 0:
set_language.__doc__ += '\n'
def get_jabber():
pro = profile.get_profile()
return pro.jabber_server
def print_jabber():
print get_jabber()
def set_jabber(server):
"""Set the jabber server
server : 'olpc.collabora.co.uk'
"""
pro = profile.get_profile()
pro.jabber_server = server
pro.save()
def get_color():
return profile.get_color()
def print_color():
color = get_color().to_string()
str = color.split(',')
for color in _COLORS:
for hue in _COLORS[color]:
if _COLORS[color][hue] == str[0]:
print 'stroke: color=%s hue=%s'%(color, hue)
if _COLORS[color][hue] == str[1]:
print 'fill: color=%s hue=%s'%(color, hue)
def set_color(stroke, fill, modstroke='medium', modfill='medium'):
"""Set the system color.
fill : 'red, orange, yellow, blue, purple'
stroke : 'red, orange, yellow, blue, purple'
modstroke : 'dark, medium, light'
modfill : ''dark, medium, light'
"""
if modstroke not in _MODIFIERS or modfill not in _MODIFIERS:
print (_("Error in specified color modifiers."))
return
if stroke not in _COLORS or fill not in _COLORS:
print (_("Error in specified colors."))
return
if modstroke == modfill:
if modfill == medium:
modfill = light
else:
modfill = medium
color = _COLORS[stroke][modstroke] + ',' + _COLORS[fill][modfill]
pro = profile.get_profile()
pro.color = XoColor(color)
pro.save()
def get_nick():
return profile.get_nick_name()
def print_nick():
print get_nick()
def set_nick(nick):
"""Set the nickname.
nick : 'erikos'
"""
pro = profile.get_profile()
pro.nick_name = nick
pro.save()
def get_radio(state):
return ''
def print_radio(self):
print get_radio()
def set_radio(state):
"""Turn Radio off
state : 'on/off'
"""
if state == 'on':
cmd = '/sbin/iwconfig eth0 txpower on'
handle = os.popen(cmd, 'r')
print string.join(handle.readlines())
handle.close()
elif state == 'off':
cmd = '/sbin/iwconfig eth0 txpower off'
handle = os.popen(cmd, 'r')
print string.join(handle.readlines())
handle.close()
else:
print (_("Error in specified radio argument use on/off."))
def get_timezone():
fd = open(_TIMEZONE_CONFIG, "r")
lines = fd.readlines()
fd.close()
try:
for line in lines:
line = string.strip(line)
if len (line) and line[0] == '#':
continue
try:
tokens = string.split(line, "=")
if tokens[0] == "ZONE":
timezone = string.replace(tokens[1], '"', '')
return timezone
except Exception, e:
print (_("get_timezone: %s") % e)
except Exception, e:
print (_("get_timezone: %s") % e)
return None
def print_timezone():
timezone = get_timezone()
if timezone is None:
print (_("Error in reading timezone"))
else:
print timezone
def set_timezone(timezone):
"""Set the system timezone
timezone :
"""
if timezone in _timezones:
fromfile = os.path.join("/usr/share/zoneinfo/", timezone)
try:
shutil.copyfile(fromfile, "/etc/localtime")
except OSError, (errno, msg):
print (_("Error copying timezone (from %s): %s")%(fromfile, msg))
return
try:
os.chmod("/etc/localtime", 0644)
except OSError, (errno, msg):
print (_("Changing permission of timezone: %s") % (msg))
return
# Write info to the /etc/sysconfig/clock file
fd = open(_TIMEZONE_CONFIG, "w")
fd.write('# The ZONE parameter is only evaluated by sugarcontrol.\n')
fd.write('# The timezone of the system ' +
'is defined by the contents of /etc/localtime.\n')
fd.write('ZONE="%s"\n' % timezone)
fd.close()
else:
print (_("Error timezone does not exist."))
def _read_zonetab(fn='/usr/share/zoneinfo/zone.tab'):
fd = open (fn, 'r')
lines = fd.readlines()
fd.close()
timezones = []
for line in lines:
if line.startswith('#'):
continue
line = line.split()
if len(line) > 1:
timezones.append(line[2])
timezones.sort()
return timezones
def _writeI18N(lang, sysfont):
path = '/etc/sysconfig/i18n'
if os.access(path, os.R_OK) == 0:
print(_("Could not access %s")%path)
else:
fd = open(path, 'w')
fd.write('LANG="' + lang + '"\n')
fd.write('SYSFONT="' + sysfont + '"\n')
fd.close()
def get_language():
originalFile = None
path = '/etc/sysconfig/i18n'
if os.access(path, os.R_OK) == 0:
return None
else:
fd = open(path, "r")
originalFile = fd.readlines()
fd.close()
lang = None
for line in originalFile:
if line[:5] == "LANG=":
lang = line[5:].replace('"', '')
lang = lang.strip()
if not lang:
lang = "en_US"
return lang
def print_language():
code = get_language()
for lang in _LANGUAGES:
if _LANGUAGES[lang][0] == code:
print lang
return
print (_("Language for code=%s could not be determined.")%code)
def set_language(language):
"""Set the system language.
languages :
"""
if language in _LANGUAGES:
_writeI18N(_LANGUAGES[language][0], _LANGUAGES[language][1])
else:
print (_("Sorry I do not speak \'%s\'.")%language)
# inilialize the docstrings for the timezone and language
_initialize()

View File

@ -0,0 +1,71 @@
#!/usr/bin/env python
# Copyright (C) 2007, One Laptop Per Child
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import getopt, sys
import control
def cmd_help():
print 'Usage: sugar-control [ option ] key [ args ... ] \n\
Control for the sugar environment. \n\
Options: \n\
-h, --help show this help message and exit \n\
-h key show information about this key \n\
-g key get the current value of the key \n\
-s key set the current value for the key \n\
'
def main():
try:
opts, args = getopt.getopt(sys.argv[1:], "h:s:g:", ["help"])
except getopt.GetoptError:
cmd_help()
sys.exit(2)
output = None
verbose = False
for opt, key in opts:
if opt in ("-h"):
method = getattr(control, 'set_' + key, None)
if method is None:
cmd_help()
sys.exit()
else:
print method.__doc__
if opt in ("-g"):
method = getattr(control, 'print_' + key, None)
if method is None:
cmd_help()
sys.exit()
else:
method()
if opt in ("-s"):
method = getattr(control, 'set_' + key, None)
if method is None:
cmd_help()
sys.exit()
else:
try:
method(*args)
except Exception, e:
print "sugar-control: %s"% e
if __name__ == '__main__':
main()

View File

@ -57,6 +57,7 @@ class Shell(gobject.GObject):
self._current_host = None
self._pending_host = None
self._screen_rotation = 0
self._zoom_level = ShellModel.ZOOM_HOME
self._key_handler = KeyHandler(self)
@ -73,6 +74,8 @@ class Shell(gobject.GObject):
home_model.connect('pending-activity-changed',
self._pending_activity_changed_cb)
self._model.connect('notify::zoom-level', self._zoom_level_changed_cb)
gobject.idle_add(self._start_journal_idle)
def _start_journal_idle(self):
@ -172,8 +175,7 @@ class Shell(gobject.GObject):
activity.get_service().TakeScreenshot()
def set_zoom_level(self, level):
old_level = self._model.get_zoom_level()
if level == old_level:
if level == self._zoom_level:
return
self.take_activity_screenshot()
@ -187,6 +189,17 @@ class Shell(gobject.GObject):
self._screen.toggle_showing_desktop(True)
self._home_window.set_zoom_level(level)
def _zoom_level_changed_cb(self, model, pspec):
new_level = model.props.zoom_level
if new_level == ShellModel.ZOOM_HOME:
self._frame.show(Frame.MODE_HOME)
if self._zoom_level == ShellModel.ZOOM_HOME:
self._frame.hide()
self._zoom_level = new_level
def toggle_activity_fullscreen(self):
if self._model.get_zoom_level() == ShellModel.ZOOM_ACTIVITY:
self.get_current_activity().toggle_fullscreen()

View File

@ -33,16 +33,11 @@ from view.frame.framewindow import FrameWindow
from view.frame.clipboardpanelwindow import ClipboardPanelWindow
from model.shellmodel import ShellModel
MODE_NONE = 0
MODE_MOUSE = 1
MODE_KEYBOARD = 2
MODE_FORCE = 3
_FRAME_HIDING_DELAY = 500
class _Animation(animator.Animation):
def __init__(self, frame, end):
start = frame.get_current_position()
start = frame.current_position
animator.Animation.__init__(self, start, end)
self._frame = frame
@ -55,19 +50,16 @@ class _MouseListener(object):
self._hide_sid = 0
def mouse_enter(self):
if self._frame.mode == MODE_NONE or \
self._frame.mode == MODE_MOUSE:
self._show_frame()
self._show_frame()
def mouse_leave(self):
if self._frame.mode == MODE_MOUSE:
if self._frame.mode == Frame.MODE_MOUSE:
self._hide_frame()
def _show_frame(self):
if self._hide_sid != 0:
gobject.source_remove(self._hide_sid)
self._frame.show()
self._frame.mode = MODE_MOUSE
self._frame.show(Frame.MODE_MOUSE)
def _hide_frame_timeout_cb(self):
self._frame.hide()
@ -80,52 +72,23 @@ class _MouseListener(object):
_FRAME_HIDING_DELAY, self._hide_frame_timeout_cb)
class _KeyListener(object):
_HIDDEN = 1
_SHOWN_PRESSED = 2
_SHOWN_REPEAT = 3
_SHOWN_RELEASED = 4
def __init__(self, frame):
self._frame = frame
self._state = _KeyListener._HIDDEN
def key_press(self):
if self._frame.mode != MODE_NONE and \
self._frame.mode != MODE_KEYBOARD:
return
if self._frame.visible:
self._frame.hide()
if self._frame.mode == Frame.MODE_KEYBOARD:
self._frame.hide()
else:
self._frame.show()
self._frame.mode = MODE_KEYBOARD
"""
if self._state == _KeyListener._HIDDEN:
self._frame.show()
self._frame.mode = MODE_KEYBOARD
self._state = _KeyListener._SHOWN_PRESSED
elif self._state == _KeyListener._SHOWN_PRESSED:
self._state = _KeyListener._SHOWN_REPEAT
elif self._state == _KeyListener._SHOWN_RELEASED:
self._frame.hide()
self._state = _KeyListener._HIDDEN
"""
def key_release(self):
pass
"""
if self._state == _KeyListener._SHOWN_PRESSED:
self._state = _KeyListener._SHOWN_RELEASED
elif self._state == _KeyListener._SHOWN_REPEAT:
self._frame.hide()
self._state = _KeyListener._HIDDEN
"""
self._frame.show(Frame.MODE_KEYBOARD)
class Frame(object):
MODE_MOUSE = 0
MODE_KEYBOARD = 1
MODE_HOME = 2
def __init__(self, shell):
self.mode = MODE_NONE
self.visible = False
self.mode = None
self._palette_group = palettegroup.get_group('frame')
self._palette_group.connect('popdown', self._palette_group_popdown_cb)
@ -136,7 +99,7 @@ class Frame(object):
self._bottom_panel = None
self._shell = shell
self._current_position = 0.0
self.current_position = 0.0
self._animator = None
self._event_area = EventArea()
@ -157,9 +120,12 @@ class Frame(object):
self._key_listener = _KeyListener(self)
self._mouse_listener = _MouseListener(self)
def hide(self, force=False):
if not self.visible:
return
self.move(1.0)
def is_visible(self):
return self.current_position != 0.0
def hide(self):
if self._animator:
self._animator.stop()
@ -169,16 +135,9 @@ class Frame(object):
self._event_area.show()
self.visible = False
if force:
self.mode = MODE_NONE
else:
self.mode = MODE_FORCE
self._animator.connect('completed', self._hide_completed_cb)
def show(self):
self.mode = MODE_FORCE
self.mode = None
def show(self, mode):
if self.visible:
return
if self._animator:
@ -186,19 +145,16 @@ class Frame(object):
self._shell.take_activity_screenshot()
self.mode = mode
self._animator = animator.Animator(0.5)
self._animator.add(_Animation(self, 1.0))
self._animator.start()
self._event_area.hide()
self.visible = True
def get_current_position(self):
return self._current_position
def move(self, pos):
self._current_position = pos
self.current_position = pos
self._update_position()
def _is_hover(self):
@ -266,21 +222,18 @@ class Frame(object):
screen_h = gtk.gdk.screen_height()
screen_w = gtk.gdk.screen_width()
self._move_panel(self._top_panel, self._current_position,
self._move_panel(self._top_panel, self.current_position,
0, - self._top_panel.size, 0, 0)
self._move_panel(self._bottom_panel, self._current_position,
self._move_panel(self._bottom_panel, self.current_position,
0, screen_h, 0, screen_h - self._bottom_panel.size)
self._move_panel(self._left_panel, self._current_position,
self._move_panel(self._left_panel, self.current_position,
- self._left_panel.size, 0, 0, 0)
self._move_panel(self._right_panel, self._current_position,
self._move_panel(self._right_panel, self.current_position,
screen_w, 0, screen_w - self._right_panel.size, 0)
def _hide_completed_cb(self, animator):
self.mode = MODE_NONE
def _size_changed_cb(self, screen):
self._update_position()
@ -316,6 +269,4 @@ class Frame(object):
def notify_key_press(self):
self._key_listener.key_press()
def notify_key_release(self):
self._key_listener.key_release()
visible = property(is_visible, None)

View File

@ -48,55 +48,17 @@ class HomeBox(hippo.CanvasBox, hippo.CanvasItem):
shell_model = shell.get_model()
top_box = hippo.CanvasBox(yalign=hippo.ALIGNMENT_START,
box_height=style.GRID_CELL_SIZE,
orientation=hippo.ORIENTATION_HORIZONTAL)
self.append(top_box, hippo.PACK_EXPAND)
nw_arrow = CanvasIcon(icon_name='arrow_NW',
xalign=hippo.ALIGNMENT_START)
top_box.append(nw_arrow)
arrows_separator = hippo.CanvasBox()
top_box.append(arrows_separator, hippo.PACK_EXPAND)
ne_arrow = CanvasIcon(icon_name='arrow_NE',
xalign=hippo.ALIGNMENT_END)
top_box.append(ne_arrow)
self._donut = ActivitiesDonut(shell)
self.append(self._donut)
bottom_box = hippo.CanvasBox(yalign=hippo.ALIGNMENT_END,
box_height=style.GRID_CELL_SIZE,
orientation=hippo.ORIENTATION_HORIZONTAL)
self.append(bottom_box, hippo.PACK_EXPAND)
self.append(self._donut, hippo.PACK_FIXED)
self._my_icon = _MyIcon(shell, style.XLARGE_ICON_SIZE)
self.append(self._my_icon, hippo.PACK_FIXED)
sw_arrow = CanvasIcon(icon_name='arrow_SW',
xalign=hippo.ALIGNMENT_START)
bottom_box.append(sw_arrow)
devices_box = _DevicesBox(shell_model.get_devices())
bottom_box.append(devices_box, hippo.PACK_EXPAND)
se_arrow = CanvasIcon(icon_name='arrow_SE',
xalign=hippo.ALIGNMENT_END)
bottom_box.append(se_arrow)
self._arrows = [ nw_arrow, ne_arrow, sw_arrow, se_arrow ]
self._devices_box = _DevicesBox(shell_model.get_devices())
self.append(self._devices_box, hippo.PACK_FIXED)
shell_model.connect('notify::state',
self._shell_state_changed_cb)
shell_model.connect('notify::zoom-level',
self._shell_zoom_level_changed_cb)
def _shell_zoom_level_changed_cb(self, model, pspec):
for arrow in self._arrows:
arrow.destroy()
self._arrows = []
def _shell_state_changed_cb(self, model, pspec):
# FIXME implement this
@ -106,9 +68,17 @@ class HomeBox(hippo.CanvasBox, hippo.CanvasItem):
def do_allocate(self, width, height, origin_changed):
hippo.CanvasBox.do_allocate(self, width, height, origin_changed)
[donut_width, donut_height] = self._donut.get_allocation()
self.set_position(self._donut, (width - donut_width) / 2,
(height - donut_height) / 2)
[icon_width, icon_height] = self._my_icon.get_allocation()
self.set_position(self._my_icon, (width - icon_width) / 2,
(height - icon_height) / 2)
[box_width, box_height] = self._devices_box.get_allocation()
self.set_position(self._devices_box, (width - icon_width) / 2,
height - style.GRID_CELL_SIZE * 3)
_REDRAW_TIMEOUT = 5 * 60 * 1000 # 5 minutes

View File

@ -45,8 +45,6 @@ _actions_table = {
'<ctrl>F11' : 'volume_min',
'<ctrl>F12' : 'volume_max',
'<alt>1' : 'screenshot',
'<alt>equal' : 'console',
'<alt>0' : 'console',
'<alt>f' : 'frame',
'0x93' : 'frame',
'<alt>o' : 'overlay',
@ -156,9 +154,6 @@ class KeyHandler(object):
def handle_screenshot(self):
self._shell.take_screenshot()
def handle_console(self):
gobject.idle_add(self._toggle_console_visibility_cb)
def handle_frame(self):
self._shell.get_frame().notify_key_press()
@ -222,10 +217,3 @@ class KeyHandler(object):
return True
return False
def _toggle_console_visibility_cb(self):
bus = dbus.SessionBus()
proxy = bus.get_object('org.laptop.sugar.Console',
'/org/laptop/sugar/Console')
console = dbus.Interface(proxy, 'org.laptop.sugar.Console')
console.ToggleVisibility()