Tell us your story
Tell us your story
How has offline Wikipedia affected you? The Wikimedia Foundation (the non-profit that supports Wikipedia) is looking for personal, diverse and inspiring stories about how offline Wikipedia affects the world. If you have a personal story that you would like to share, please contact: stories@kiwix.org. Thank you!

Black&White Project/Kiwix for Sugar/Porting

From Kiwix
Jump to: navigation, search

This page relates the portage or Kiwix into the Sugar platform. It might be of interest to others willing to port Xulrunner apps to Sugar.

Static

Sugar Activities are mostly distributed using the Activities Wiki, as .xo bundle. A .xo bundle is an archive containing your app. There is no dependency definition so you need to bundle everything.

This is the reason why we compiled all our XPCOM components statically for the Sugar version.

Also, Sugar does ship XulRunner at the moment but its version changes frequently. To avoid XR version conflict or removal of XR (browser development seems to have swtich to the Surf activity based on webkit), you probably want to distribute your own version of XulRunner inside your .xo file.

Sugar Activity

A Sugar activity is a folder named Kiwix.activity which resides in one of the activities placeholder (/usr/share/activities/, ~/Activities, etc). It must contain the following:

  • activity/ folder
  • activity/activity.info description file
  • activity/activity.svg icon file

Those are the basic elements which are covered easily by sample activities.

What you need for a XR activity are:

  • lib/libsugarize.so a shared link library used to proxy X calls to Sugar.

Available at [1]

  • bin/SugarKiwix a bash script which will LD_PRELOAD the library and launch XR
#!/bin/sh
while [ -n "$2" ] ; do
     case "$1" in
         -b | --bundle-id)     export SUGAR_BUNDLE_ID="$2" ;;
         -a | --activity-id)   export SUGAR_ACTIVITY_ID="$2" ;;
         -o | --object-id)     export SUGAR_OBJECT_ID="$2" ;;
         -u | --uri)           export SUGAR_URI="$2" ;;
         *) echo unknown argument $1 $2 ;;
     esac
     shift;shift
done
export LD_PRELOAD="$SUGAR_BUNDLE_PATH/lib/libsugarize.so"
export NET_WM_NAME="Kiwix"
exec $SUGAR_BUNDLE_PATH/kiwix
  • setup.py not mandatory. A python script allowing easy packaging in .xo file.
#!/usr/bin/env python

from sugar.activity import bundlebuilder

bundlebuilder.start()
  • activity/activity.info
[Activity]
name = Kiwix
bundle_id = org.kiwix.Kiwix
exec = sugarKiwix
icon = activity-kiwix
activity_version = 1
show_launcher = yes

Activity Icon

Activity Icon must respond to very specific requirements:

  • Icon should be made from a 75px square frame and fit properly
  • Icon is composed of two elements: strokes and fills.
  • All strokes must be same color, all fills same color.
  • Stroke color and Fill color will be dynamically changed so don't choose something else than black/white.
  • Strokes must be 3.5px large.

When running under Sugar, background is transparent, all strokes gets one color and all fills another.

It is important that both stroke and fills exists as the colors reflect the user interacting with the activity.

Kiwix icon in the application ring

Activity-kiwix.svg

Kiwix Icon is composed of strokes and fills

  • one stroke to shape the bird
  • one stroke to shape the ball
  • one stoke to represent the eye.
  • one fill inside the bird
  • one fill inside the ball

Build .XO

Building the .xo file is easy.

  1. prepare your folder so that it's named Kiwix.activity
  2. Inside this folder, place your application.ini, chrome, xulrunner, etc.
  3. On that same folder, place the activity subfolder, setup.py, lib/ and bin/
  4. launch xo build script
cd Kiwix.activity
python ./setup.py dist_xo

That will create a Kiwix-x.xo in the dist/ sub folder. x represents the build version. This version number is specified in your activity/activity.info file. It is important as it's the only way to express versions in the Sugar world: the Journal and the Activities Manager.

XR App Customization

If you followed the previous steps, you now have a working Sugar activity. It launches in full screen and is able to quit.

Now, in order to comply with the Sugar HIG, you would consider some of the following UI adjustment. The easiest way to do so is by:

  • Using a dedicated skin and set it as default for your Sugar build
  • Using switches in your Javascript code.

You can detect a Sugar client with the following:

isSugar: function() {
        var environment = Components.classes["@mozilla.org/process/environment;1"].getService(Components.interfaces.nsIEnvironment);
        return environment.exists("SUGAR_BUNDLE_PATH");
    }

U.I Adjustments

  • Remove menubar. There is no menubar in Sugar apps.
  • Remove Status bar.
  • Fix the resolution.

The OLPC XO has a very high definition (up to 200dpi). Since XR exports the system's dpi to its rendering engine, it makes all your UI look over-sized. Fix this with the following property (adjust as you want) on ~defaults/preferences/preferences.js

pref("layout.css.dpi", 96);
  • Use toolbars only. The standard way of displaying an App on Sugar is one toolbar at top (with possible sub-toolbars) and the content area on the rest of the screen.
  • Toolbar at top.

Native Activity

An alternative to spending a lot of time adjusting your U.I, is to write a Sugar activity from scratch and embed Gecko. If you don't use much of XUL/Javascript for your interface, it's a good solution as it gives you native widgets.

We did not go this way as we have a lot of U.I code and we can't afford to maintain duplicate code in separate languages (Sugar activities are to be written in python).

Toolbars

Toolbars are the main way user will interact with the different features of the App. While there is no static rule about what should be in there, you might consider the following:

  1. Quit button at the end of the bar. The button could raise the activity logger dialog to record a session and assign meta data.
  2. Stock icons are available in SVG format with URLs like: moz-icon://stock/activity-stop?size=toolbar. Note that if you need to mix stock sugar icon with another one you made, you will have to switch to bitmaps for all.

Deep integration

If you need to integrate deeper with sugar (create sessions or other journal entries for example), you would have to write a component for that purpose. Since Sugar libraries are only available on Python for now, you would need to write a python component and use pyxpcom.

Warning: A bunch of Sugar API relies on Glib and a window which you won't have control over (I think).

  • Write your component. Just like a C++ component, write the IDL[2] file and generate the xpt. Example python component [3] using sugar:
#!/usr/bin/env python
# encoding=utf-8

from xpcom import components, verbose

class SugarBridge:
    _com_interfaces_ = components.interfaces.ISugarBridge
    _reg_clsid_ = "{080787ae-ddff-11e0-aa79-000c2983f4e1}"
    _reg_contractid_ = "@kiwix.org/SugarBridge"
    def __init__(self):
        print("sugar bridge started")
        self.testAttr = "nop"

    def test(self):
        try:
            from sugar import env
            print(u"Activities: %s" % env.get_user_activities_path())
        except Exception as e:
            print(u"oops: %r" % e)
  • register it in chrome.manifest like a JS component.
component {080787ae-ddff-11e0-aa79-000c2983f4e1} components/sugar_bridge.py
contract @kiwix.org/SugarBridge {080787ae-ddff-11e0-aa79-000c2983f4e1}
  • Install pythonext [4]. Extract the extension in your extensions/ directory. You can just place the XPI inside and launch XR with EnableExtensionManager=1 to do so.
  • create a pylib directory on your app root.
  • copy/link any external python lib inside that pylib/ folder.
ln -s /usr/share/pyshared/sugar pylib/
  • Use it like any other component
    if (Components.classes["@kiwix.org/SugarBridge"] == undefined)
	dump("Unable to register the SugarBridge XPCOM, Kiwix won't be optimized for Sugar.
");
    else {
        var sugar = Components.classes["@kiwix.org/SugarBridge"]
		.createInstance(Components.interfaces.ISugarBridge);
        sugar.test();
    }