Friday, July 29, 2005

Giving Python legs with XULRunner

Python and XUL are both great languages. Combining them with PyXPCOM and XULRunner promises to be a great combination for creating cross platform GUI applications e.g. Komodo. This post shows how to build and test XULRunner (CVS Head,July 29th 2005) with Python (2.3.5) on Gentoo using gcc 3.4.4 borrowing from work by Christian Persch and Paul Rouget among Others.
  1. You must link against the Python dynamic library (libpython2.3.so on my machine), (you need to be root to do this)
    # cd /usr/lib/python2.3/config && ln -s /usr/lib/libpthon2.3.so .
    doing this prevents this common error on xulrunner startup:
    Fatal Python error: Interpreter not initialized (version mismatch?)
  2. Create and move to a directory where you will do the build (not as root) e.g.
  3. $ mkdir ~/xulrunner && cd ~/xulrunner
  4. Execute these commands to build XULRunner with PyXPCOM
    $ wget -c http://homepage.eircom.net/~rachra/build_xulrunner_pyxpcom.sh
    $ chmod 755 build_xulrunner_pyxpcom.sh
    $ ./build_xulrunner_pyxpcom.sh
    build_xulrunner_pyxpcom.sh is shown below
       1:#!/bin/sh
       2:DATE=`date +"%d-%m-%y"`
       3:base=`pwd`
       4:export CVSROOT=:pserver:anonymous:anonymous@cvs-mirror.mozilla.org:/cvsroot
       5:MOZCONFIG=$base/mozilla/.mozconfig
       6:
       7:#----- remove existing build
       8:rm -rf $base/mozilla
       9:
      10:#----- checkout mozilla from cvs HEAD and create .mozconfig
      11:cvs login || exit $?
      12:cd $base
      13:cvs checkout mozilla/client.mk || exit $?
      14:cat << EOF > $MOZCONFIG
      15:export MOZILLA_OFFICIAL=1
      16:mk_add_options MOZILLA_OFFICIAL=1
      17:mk_add_options MOZ_CO_PROJECT=xulrunner
      18:ac_add_options --enable-application=xulrunner
      19:ac_add_options --disable-debug
      20:ac_add_options --disable-tests
      21:ac_add_options --disable-optimize
      22:ac_add_options --enable-default-toolkit=gtk2
      23:ac_add_options --enable-xft
      24:ac_add_options --disable-freetype2
      25:ac_add_options --enable-extensions=python/xpcom
      26:EOF
      27:
      28:cd mozilla
      29:make -f client.mk checkout || exit $?
      30:
      31:#----- patch the python source http://live.gnome.org/Epiphany_2fEphyPython_2fPyXPCOM
      32:cd $base
      33:wget -c http://www.gnome.org/~chpe/pyxpcom2.diff
      34:cd $base/mozilla/extensions/python/xpcom
      35:patch -p0 < $base/pyxpcom2.diff
      36:cd $base/mozilla
      37:
      38:#----- build mozilla
      39:make -f client.mk build || exit $?
    
    sit back and wait for an hour or two while XULRunner and PyXPCOM build.
  5. Test your xulrunner build
    $ cd ~/xulrunner/mozilla/dist/bin
    $ ./xulrunner ../xpi-stage/simple/application.ini
    You should see a GUI with a button to increment the number in a text field.
  6. Test your PyXPCOM integration with this XULRunner app I've created (A Firefox extension with the same code can be found here). This app uses the PyXPCOM test component (~/xulrunner/mozilla/dist/bin/components/py_test_component.py). Instructions on creating XULRunner apps can be found here and here.
    $ cd ~/xulrunner/mozilla/dist/xpi-stage
    $ wget -c http://homepage.eircom.net/~rachra/pyxpcomxul.zip
    $ unzip pyxpcomxul.zip
    $ cd ~/xulrunner/mozilla/dist/bin
    $ ./xulrunner ../xpi-stage/pyxpcomxul/application.ini
    You should see a GUI with some text and one button, clicking the button will bring up a message box that will tell you if PyXPCOM integration has succeeded or failed.
    Fragment of py_test_component.py that gets called
    252:    def GetStrings(self):
    253:        # void GetStrings(out PRUint32 count,
    254:        #            [retval, array, size_is(count)] out string str);
    255:        return "Hello from the Python test component".split()
    
    pyxpcomxul.zip contains the following files:
    pyxpcomxul/application.ini
       1:#filter substitution
       2:[App]
       3:;
       4:; This field specifies your organization's name.  This field is recommended,
       5:; but optional.
       6:Vendor=Propylon
       7:;
       8:; This field specifies your application's name.  This field is required.
       9:Name=pyxpcomxul
      10:;
      11:; This field specifies your application's version.  This field is optional.
      12:Version=0.1
      13:;
      14:; This field specifies your application's build ID (timestamp).  This field is
      15:; required.
      16:BuildID=20050728
      17:;
      18:; This field specifies a compact copyright notice for your application.  This
      19:; field is optional.
      20:Copyright=Copyright (c) 2005 Mozilla
      21:;
      22:; This ID is just an example.  Every XUL app ought to have it's own unique ID.
      23:; You can use the microsoft "guidgen" or "uuidgen" tools, or go on
      24:; irc.mozilla.org and /msg botbot uuid.  This field is optional.
      25:ID={604f89f5-b138-40f9-9c67-f9ae064d6ef3}
      26:
      27:[Gecko]
      28:;
      29:; This field is required.  It specifies the minimum Gecko version that this
      30:; application requires.  Specifying 1.8 matches all releases with a version
      31:; prefixed by 1.8 (e.g., 1.8a4, 1.8b, 1.8.2).
      32:MinVersion=1.8
      33:;
      34:; This field is optional.  It specifies the maximum Gecko version that this
      35:; application requires.  It should be specified if your application uses
      36:; unfrozen interfaces.  Specifying 1.8 matches all releases with a version
      37:; prefixed by 1.8 (e.g., 1.8a4, 1.8b, 1.8.2).
      38:MaxVersion=1.8
      39:
      40:
    
    pyxpcomxul/defaults/preferences/pyxpcomxul-prefs.js
       1:pref("toolkit.defaultChromeURI", "chrome://pyxpcomxul/content/pyxpcomxul.xul");
       2:pref("general.useragent.extra.pyxpcomxul", "PyXPCOMXUL/0.1");
    
    pyxpcomxul/chrome/chrome.manifest
    content pyxpcomxul jar:pyxpcomxul.jar!/pyxpcomxul/content/
    pyxpcomxul/chrome/pyxpcomxul.jar!/pyxpcomxul/content/pyxpcomxul.xul
       1:<?xml version="1.0"?>
       2:<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
       3:<window
       4:    id="pyxpcomtest-window"
       5:    title="PyXPCOMTest"
       6:    orient="horizontal"
       7:    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
       8:    <script type="application/x-JavaScript"  src="chrome://pyxpcomxul/content/pyxpcom.js"/>
       9:    <keyset>
      10:        <key id="close-window" modifiers="accel" key="W" oncommand="window.close();"/>
      11:        <key id="quit" modifiers="accel" key="Q" oncommand="window.close();"/>
      12:        <key id="push-button" modifiers="accel" key="T" oncommand="testPythonInterface();"/>
      13:    </keyset>
      14:    <vbox>
      15:        <label>This is the PyXPCOM test component, click on the button. 
      16:        (relies on component py_test_component.py)</label>
      17:        <button id="testPyXPCOM" label="Test PyXPCOM" 
      18:            accesskey="T" key="push-button" oncommand="testPythonInterface();"/>
      19:        <label>if you get the message "Hello,from,the,Python,test,component", 
      20:        you have connection to PyXPCOM objects.</label>
      21:    </vbox>
      22:</window>
    
    pyxpcomxul/chrome/pyxpcomxul.jar!/pyxpcomxul/content/pyxpcom.js
       1:var cls = this.Components.classes["Python.TestComponent"];
       2:var pyTestObj = cls.createInstance(Components.interfaces.nsIPythonTestInterface);
       3:
       4:function testPythonInterface()
       5:{   
       6:    try {
       7:        var j = new Object();
       8:        var k;
       9:        var retArray = pyTestObj.GetStrings(j, k);
      10:        alert("Test Succeeded: "+retArray);
      11:    }
      12:    catch (e) {
      13:        alert("Test Failed: "+e);
      14:    }
      15:}
    
  7. View the PyXPCOM integration code. The code that is being run is actually contained in the pyxpcomxul/chrome/pyxpcomxul.jar file
    $ cd ~/xulrunner/mozilla/dist/xpi-stage/pyxpcomxul/chrome
    $ unzip pyxpcomxul.jar
    $ jedit pyxpcomxul/content/pyxpcomxul.xul 
    $ jedit pyxpcomxul/content/pyxpcom.js 
    $ jedit ../../../bin/components/py_test_component.py
    See Activestate and IBM for a tutorial on writing PyXPCOM components.
  8. Modify and re-deploy code. Make any changes you like to the .xul or .js file (xcomponent can be found at ~/xulrunner/mozilla/dist/bin
    $ cd ~/xulrunner/mozilla/dist/xpi-stage/pyxpcomxul/chrome
    $ zip -r pyxpcomxul.jar pyxpcomxul/
    $ cd ~/xulrunner/mozilla/dist/bin
    $ ./xulrunner ../xpi-stage/pyxpcomxul/application.ini
    Modifications to the .py file are picked up immediately.
Things to do/ learn:
  • Should be possible to deploy python xpcomponents to ~/xulrunner/mozilla/dist/xpi-stage/pyxpcomxul/components directory, couldn't get this to work
  • Replace all Javascript with Python i.e. replace
    8:    <script type="application/x-JavaScript"  src="chrome://pyxpcomxul/content/pyxpcom.js"/>
    
    with
    8:    <script type="application/x-python"  src="chrome://pyxpcomxul/content/pyxpcom.py"/>
    
  • Updating of XULRunner components similar to Firefox extensions, not sure how this is achieved.

4 comments:

Anonymous said...

hi,

i was wondering if you know of any updated instruction on how to build xulrunner with pyxpcom (regarding the fact that all dev takes place now in the DOM_AGNOSTIC2_BRANCH), or if you managed to build a recent xulrunner with pyxpcom and care to share your experience.

thx for any pointer/info

Anonymous said...

any luck with getting this to compile on windows? i can't seem to get xulrunner to compile with python support.

Tim Almond said...

Michael,

Looks interesting. I'm going to try it out later. I've been looking forward to using Python with XML. I'm no fan of javascript.

Anonymous said...

That is so great!
It's also possible to run python in parallel on SMP: Parallel Python