Sugar almanac

From OLPC
Revision as of 18:49, 24 October 2008 by RafaelOrtiz (talk | contribs) (Internationalization)
Jump to: navigation, search
  Sugar Almanac

Sugar Almanac for Developers

Sugar Almanac Main Page

Package: sugar

sugar.env

sugar.profile

sugar.mime

Package: sugar.activity

sugar.activity.activity

sugar.activity.registry

Package: sugar.graphics

sugar.graphics.alert

sugar.graphics.toolbutton

sugar.graphics.toolbox

Package: sugar.datastore

sugar.datastore.datastore

Logging

sugar.logger

Notes on using Python Standard Logging in Sugar

Internationalization

Internationalization in Sugar

How do I get additional help beyond this almanac?


Now, on to the actual almanac ...


Getting Started

How do I structure my files so that they are a valid sugar activity?

Information on activity bundle structure can be found here: Activity bundles

How do I make an icon for my activity?

Information on what you need to do can be found here: Making Sugar Icons

Package: sugar

Package: sugar.activity

Package: sugar.datastore

Package: sugar.graphics

Package: sugar.presence

Clipboard

Logging

Internationalization

Text and Graphics for Sugar Activities

Audio & Video

Mouse

How do I change the mouse cursor in my activity to the wait cursor?

In your activity subclass:

self.window.set_cursor( gtk.gdk.Cursor(gtk.gdk.WATCH) )

and to switch it back to the default:

self.window.set_cursor( None );

How do I track the position of the mouse?

There are many different reasons you might want to track the position of the mouse in your activity, ranging from the entertaining ([[1]]) to the functional (hiding certain windows when the mouse hasn't moved for a couple of seconds and making those ui elements re-appear when the mouse has moved again). Here is one way you can implement this functionality:


		...
		self.hideWidgetsTime = time.time()
		self.mx = -1
		self.my = -1
		self.HIDE_WIDGET_TIMEOUT_ID = gobject.timeout_add( 500, self.mouseMightHaveMovedCb )

	def _mouseMightHaveMovedCb( self ):
		x, y = self.get_pointer()
		passedTime = 0

		if (x != self.mx or y != self.my):
			self.hideWidgetsTime = time.time()
			if (self.hiddenWidgets):
				self.showWidgets()
				self.hiddenWidgets = False
		else:
			passedTime = time.time() - self.hideWidgetsTime


		if (passedTime >= 3):
			if (not self.hiddenWidgets):
				self.hideWidgets()
				self.hiddenWidgets = True

		self.mx = x
		self.my = y
		return True

Miscellaneous

The tasks below are random useful techniques that have come up as I write code and documentation for this reference. They have yet to be categorized, but will be as a sufficient set of related entries are written.


How do I know when my activity is "active" or not?

You can set an event using the VISIBILITY_NOTIFY_MASK constant in order to know when your activity changes visibility. Then in the callback for this event, you simply compare the event's state to gtk-defined variables for activity visibility. See the GDK Visibility State Constants section of gtk.gdk.Constants for more information.

        # Notify when the visibility state changes by calling self.__visibility_notify_cb
        # (PUT THIS IN YOUR ACTIVITY CODE - EG. THE __init__() METHOD)
        self.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
        self.connect("visibility-notify-event", self.__visibility_notify_cb)
    ...
    # Callback method for when the activity's visibility changes
    def __visibility_notify_cb(self, window, event):
        if event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED:
            print "I am not visible"
        elif event.state in [gtk.gdk.VISIBILITY_UNOBSCURED, gtk.gdk.VISIBILITY_PARTIAL]:
            print "I am visible"

How do I get the amount of free space available on disk under the /home directory tree?

The following function uses the statvfs module. The following code demonstrates how to get the total amount of free space under /home.

    #### Method: getFreespaceKb, returns the available freespace in kilobytes. 
    def getFreespaceKb(self):
        stat = os.statvfs("/home")
        freebytes  = stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL]
        freekb = freebytes / 1024
        return freekb

Note, however, that assuming anything about "/home" is a bad idea, better use os.environ['HOME'] instead. Rainbow will put your actual files elsewhere, some on ramdisks, some on flash. Be clear about which filesystem's free space you actually care about.

How do I know whether my activity is running on a physical XO?

Sugar runs on ordinary computers as well as on XO's. While your activity is typically going to be run on a real XO, some people will indeed run it elsewhere. Normally you shouldn't write your activity to care whether it's on an XO or not. If for some odd reason, you need to care, the easiest way to tell if you are on a physical XO is to check whether /sys/power/olpc-pm, an essential power management file for the XO, exists. <ref>reliably detecting if running on an XO</ref> <ref>OLPC Power Management Interface</ref>

import os
...
      #Print out a boolean value that tells us whether we are on an XO or not. 
      print os.path.exists('/sys/power/olpc-pm')

How do I know the current language setting on my XO?

The system variable 'LANG' tells you which language is currently active on the XO. The following code shows how to look at the value of this variable.

import os
...
       _logger.debug(os.environ['LANG'])

How do I repeatedly call a specific method after N number of seconds?

The gobject.timeout_add() function allows you to invoke a callback method after a certain amount of time. If you want to repeatedly call a method, simply keep invoking the gobject.timeout_add function in your callback itself. The code below is a simple example, where the callback function is named repeatedly_call. Note that the timing of the callbacks are approximate. To get the process going, you should make an initial call to repeatedly_call() somewhere in your code.

You can see a more substantive example of this pattern in use when we regularly update the time displayed on a pango layout object.


	#This method calls itself ROUGHLY every 1 second 
	def repeatedly_call(self):
		now = datetime.datetime.now()
		gobject.timeout_add(self.repeat_period_msec, self.repeatedly_update_time)


How do I update the current build version of code that is running on my XO?

There are several pages that give you instructions on how to install/update your current build.

  • If you already have a working build installed and an internet connection, first try olpc-update.
  • If that doesn't work, you can look at instructions for an Activated upgrade that can be done via USB] boot.

As the instructions on the pages linked above note, make sure to install your activities separately after you have upgraded to a specific base build.


I am developing on an XO laptop, but my keyboard and language settings are not ideal. How can I change them?

Internationalized laptops will often have settings that might slow you down while developing. To change around the language settings so you can better understand environment messages, use the Sugar Control Panel

Keyboard settings on internationalized laptops<ref>Keyboard layouts#OLPC keyboard layouts</ref> can also be suboptimal, especially as characters like "-" and "/" are in unfamiliar positions. You can use the setxkbmap command in the Terminal Activity to reset the type of keyboard input used and then attach a standard U.S. keyboard that will allow you to type normally. The command below sets the keyboard to the US mapping (it will reset to the default internationalized mapping upon restart).

setxkbmap us

My Python activity wants to use threads; how do I do that?

Early versions of Sugar (pre-"Update.1" release) would automatically configure Python, gobject, and glib for threaded operation. Due to the overhead imposed on every activity by those initializations, Sugar no longer does that. If you are writing an activity for a later release, and you want threads to work, start off your activity (just after the copyright comments) with:

import os
import logging

# Sugar does this but only in pre-update.1.
import gobject
gobject.threads_init()
#import dbus.mainloop.glib
#dbus.mainloop.glib.threads_init()

Comment out, or uncomment, the glib threads initialization, depending on whether you need it. (I don't know how to tell if you need it, except by trying it both ways.)

Then follow that with the rest of your imports (such as "import gtk"). In my activity (SimCity), the pygame sound player would not produce sound reliably unless I did this.

In SimCity, I have tested this to make sure it works in the *old* XO software releases as well as the new ones. The documentation for these threads_init functions is really bad, and doesn't say whether you can call them twice, nor does it say how you can tell if they've already been called so you could avoid calling them twice. Apparently it's pretty harmless in release 650.


How do I customize the title that is displayed for each instance of my activity?

By default, activity titles are just the generic activity names that you specify in your activity.info file. In some applications, you may want the activity title to be more dynamic.

For example, it makes sense to set the title for different browser sessions to the active web page being visited. That way, when you look back in the journal at the different browser sessions you have run in the previous few days, you can identify unique sessions based on the website you happened to be visiting at the time.

The code below shows how you can set the metadata for your activity to reflect a dynamic title based on whatever session criteria you feel is important. This example is adapted from the Browse activity, which sets activity instance titles based on the title of the current web page being visited.

        if self.metadata['mime_type'] == 'text/plain':
            if not self._jobject.metadata['title_set_by_user'] == '1':
                if self._browser.props.title:
                    # Set the title of this activity to be the current 
                    # title of the page being visited by the browser. 
                    self.metadata['title'] = self._browser.props.title

What packages are available on sugar to support game development?

If your activity will require tools that are typically needed to develop robust and clean video games, then you should utilize the pygame package. It can be readily imported into any activity:

import pygame
...

How do I detect when one of the game buttons on the laptop have been pressed?

The laptop game buttons (the circle, square, x, and check buttons next to the LCD) are encoded as page up, home, page down and end respectively. So, you can detect their press by listening for these specific events. For example, the code below listens for button presses and then just writes to an output widget which button was pressed.

...
    #### Initialize this activity. 
    def __init__(self, handle):
        ...
        self.connect('key-press-event', self._keyPressCb)
        ...

    #### Method _keyPressCb, which catches any presses of the game buttons. 
    def _keyPressCb(self, widget, event):

        keyname = gtk.gdk.keyval_name(event.keyval)
        
        if (keyname == 'KP_Page_Up'):
            self._chat += "\nCircle Pressed!"
            self._chat_buffer.set_text(self._chat)
        elif (keyname == 'KP_Page_Down'):
            self._chat += "\nX Pressed!"
            self._chat_buffer.set_text(self._chat)
        elif (keyname == 'KP_Home'):
            self._chat += "\nSquare Pressed!"
            self._chat_buffer.set_text(self._chat)
        elif (keyname == 'KP_End'):
            self._chat += "\nCheck Pressed!"
            self._chat_buffer.set_text(self._chat)

        return False;


How do I detect if one of the joystick buttons has been pressed?

This is the same process as detecting game buttons, except with different names for the keys. Again, you listen for "key-press-event" signals and then in your callback you check to see if the pressed button was one of the joystick keys.

    #### Initialize this activity. 
    def __init__(self, handle):
        ...
        self.connect('key-press-event', self._keyPressCb)
        ...

    #### Method _keyPressCb, which catches any presses of the game buttons. 
    def _keyPressCb(self, widget, event):

        keyname = gtk.gdk.keyval_name(event.keyval)
        
        if (keyname == 'KP_Up'):
            self._chat += "\nUp Pressed!"
            self._chat_buffer.set_text(self._chat)
        elif (keyname == 'KP_Down'):
            self._chat += "\nDown Pressed!"
            self._chat_buffer.set_text(self._chat)
        elif (keyname == 'KP_Left'):
            self._chat += "\nLeft Pressed!"
            self._chat_buffer.set_text(self._chat)
        elif (keyname == 'KP_Right'):
            self._chat += "\nRight Pressed!"
            self._chat_buffer.set_text(self._chat)

        return False;

Notes

<references />