Dirk-Jan Binnema,
http://www.djcbsoftware.nl
dirk-jan@djcbsoftware.nl
Partly this is caused by reasons such as user conservatism and the need for compatibility with other users. However, it cannot be ignored that most free software was not written with the non-technical end-user in mind, and can be hard to use, no matter how powerful. Also, free software desktops do (or did) not offer some of the powerful idioms that Windows and MacOS do, such as the ability to create compound documents (i.e., a spreadsheet inside a word-processor document), or the copying/pasting of complex data through a clipboard.
Next to end-user concerns, there are also developer concerns. Writing polished software takes a lot of effort. Ideally, developers would be able to compose large parts of their programs from existing (polished, mature) building blocks (software components), instead of constant reinventing them in some suboptimal way. However, reuse in the free software world is quite limited, due to the lack of standardized interfaces, the use different languages and probably the 'Not Invented Here'-syndrome.
The GNOME-project [8] is an effort to create a full-featured free software desktop, and as part of that project, the Bonobo component framework was designed and implemented. Bonobo is an effort to help developers to create and (re)use software components. Furthermore, the Bonobo building blocks enable creating the powerful features that end-users have come to expect of their desktop.
This paper discusses the Bonobo architecture from a technical viewpoint, and gives some examples of its use. For a more practical, how-to approach, please refer to [1] and [11].
In the context of Bonobo, one could think of some program (the container) embedding an HTML-editing component. Now, if we press the 'Save'-button in the container, the component should write it's contents to disk: clearly, there should be some way for the container to tell the component. Note that the container and component may have been developed independently, so the contract should be sufficiently general to be usable in widely different scenarios.
public
, for example:
interface Foo { long add (in long x, in long y); string echo (in string msg); };Programming languages can access components which implement or expose this interface through the facilities provided by the language-specific CORBA-binding.
While CORBA is quite powerful, it doesn't specify anything directly useful for writing desktop-specific components. This is where Bonobo comes in. Bonobo components are really just a special kind of CORBA components, and Bonobo "contracts" are really just CORBA interfaces.
Unknown
-interface: all
Bonobo interfaces derive from Unknown
. The IDL for
Unknown
looks like this (the full IDL can be found in
Bonobo_Unknown.idl
in the Bonobo source distribution):
interface Unknown { /* increment the reference count */ void ref (); /* decrement the reference count */ void unref (); /* return a CORBA object with interface repoid, * or CORBA_NIL */ Unknown queryInterface (in string repoid); };
Unknown
does two things: it maintains a
reference count mechanism (ref
and unref
), and, more
interesting, it exposes the queryInterface
-method, which
enables us to switch to other interfaces this component exposes.
Together with the fact that all Bonobo interfaces derive from
Unknown
, this means that if we have any interface to some
Bonobo component, we can reach its other interfaces through
queryInterface
, as shown in figure .
Note that MyComponent
is never visible to the client: it is
only accessible through the interfaces. And using
queryInterface
, you can "hop" from one interface to the other
(in pseudo-code):
If1 my_if1; If2 my_if2; If3 my_if3; my_if1 = /* get a reference to the if1 interface */ my_if2 = my_if1.queryInterface ("if2") my_if3 = my_if2.queryInterface ("if3") my_if2.doSomeIf2Method (2.71828) my_if3.doSomeIf3Method ("foo")
Fortunately, Bonobo comes with a default implementation for all Bonobo interfaces, and with a support library; in many cases, there's hardly any CORBA showing through. Still, it's there when you need it.
Bonobo's default implementation is based on the GTK+-toolkit [14] and the ORBit CORBA implementation.
Components register themselves in OAF by installing an XML description
file (.oaf
) with their location information (i.e., where to find the
executable or shared library), and arbitrary other information.
Clients can query OAF for some object.
For example, suppose we want to activate some component which exposes
both the Bonobo Control
and the Nautilus ContentView
interfaces:
CORBA_Object obj = oaf_activate ("repo_ids.has_all(['IDL:Bonobo/Control:1.0'," "'IDL:Nautilus/ContentView:1.0']");It's also possible to do command-line queries, using
oaf-run-query
:
% oaf-run-query "bonobo:supported_mime_types.has('text/html')" number of results: 3 OAFIID:GNOME_GtkHTML_EBrowser OAFIID:GNOME_GtkHTML_Editor OAFIID:nautilus_mozilla_content_view:1ee70717-57bf-4079-aae5-922abdd576b1Here, we use the standardized
bonobo:supported_mime_types
-attribute,
for querying Bonobo components that support some MIME-type.
OAF is neither dependent on Bonobo nor on the X Window System. The
OAF-architecture allows for activating remote components
(components running on other machines) as well. However, the current
OAF-implementation does not support that. For the GNOME2 platform, OAF
has been renamed bonobo-activation
, but without adding any
Bonobo-dependency.
The interface definition for Bonobo controls can be found in
Bonobo_Control.idl
in the Bonobo source distribution, and is a
bit too long to reproduce here. Fortunately, the Bonobo GTK+
implementation wraps the interface quite nicely, and often we can avoid
using the interfaces directly.
Creating a Bonobo-control is very easy. Suppose we have
created a GTK+-widget of type MyWidget
, and want to create a
Bonobo-controls which offers its features to the outside world. Now,
what we will usually do is write a object factory, ie. an object
that produces other objects. In this case, it produces
MyWidget
-Bonobo-controls. In the C language, this could look like:
BonoboControl* control; GtkWidget* my_widget = my_gtk_widget_new (); gtk_widget_show (my_widget); control = bonobo_control_new (my_widget); return control;Note the absence of any CORBA-related code; the Bonobo/GTK+ implementation takes care of that. An easy way for component and container to communicate is using property bags. A property bag is a list of typed values, exposed by the component, and accessible by both component and container as shown in figure . Callback functions can be registered for changes to values in the property bag. Using property bags often avoids the necessity to add any custom methods to the control, thus avoiding the need to write a custom component implementing the
Control
-interface. For more
information, see [1].
If you do need to write you own custom Bonobo components however,
Bonobo provides the BonoboObject
wrapper and some useful macros
to make this is easy as possible. In that case, knowledge of the CORBA
C-binding is required though.
Before we can use MyWidget
in a container-application, we must
register it with OAF by writing an .oaf
-file. Check
[10] for details.
Using MyWidget
from a Bonobo container is also quite easy,
at least if you are familiar with GTK+-programming:
GtkWidget *bonobo_win, *control; BonoboUIContainer *uic; bonobo_win = bonobo_window_new ("test", "a small test"); /* connect a ui container to the application */ uic = bonobo_ui_container_new (); bonobo_ui_container_set_win (uic, BONOBO_WINDOW(bonobo_win)); /* get a widget, containing the control */ control = bonobo_widget_new_control (MYWIDGET_OAFIID, BONOBO_OBJREF (uic)); /* lights, camera, action */ gtk_widget_show_all (GTK_WIDGET(bonobo_win));Again, we won't discuss any details here, please refer to [1].
The solution to this problem is the use of compound documents. A compound document is formed by a hierarchy of documents (media types) which can be compound documents themselves, quite similar to a directory tree. For compound documents the "files" are called streams and the "directories" are called storages. Figure gives a schematic view of an example compound document.
The trick in supporting arbitrary media types in a document, is to expose all these media types as Bonobo components, and let them deal with all their complexity themselves. Now, as long as the application knows how to handle components with theEmbeddable
interface
(and probably a couple of others), it can handle them all, even
if they weren't even available when the application was written.
In a compound document, the various subparts are responsible for their own display, and read/write operations, and subparts recursively tell their subparts to do the same. For read/write operations (persistence), Bonobo offers compound files which mirror the document hierarchy, and can be thought of as 'file system in a file'. Thus, compound documents are stored in compound files.
Compound documents expose the Embeddable
-interface (and
possibly the Control
-interface), and probably the
Stream
/Storage
-interfaces for file operations.
An example of an application supporting compound documents is the Gnumeric spreadsheet application, in which you embed various component types. This support is still in the experimental stages though.
Moniker
-interface (which can
be found in Bonobo_Moniker.idl
).
Monikers can be constructed from some stringified representation, and a Bonobo component can then be resolved from this moniker:
Bonobo::Unknown control; moniker = bonobo_moniker_client_new_from_name (stringified_moniker); control = moniker->resolve (interface);For example, if we want to access a remote web document through the
Control
interface, and also through the Stream
interface, we can do:
moniker = bonobo_moniker_client_new_from_name ("http://www.gnome.org"); control = moniker->resolve ("IDL:Bonobo/Control:1.0"); stream = moniker->resolve ("IDL:Bonobo/Stream:1.0");
A very interesting property of monikers is the fact that they can be chained: each component of the chain is handled by a separate moniker, and each of these monikers returns a component to the moniker at its left side, for example:
file:myfile.gnumeric!SalesReport!totalsThis stringified moniker resolves to the
totals
-cell in the
SalesReport
-workbook in the myfile.gnumeric
spreadsheet
file. The file, workbook and cell part of the moniker are each handled
by separated monikers.
An increasing number of GNOME services are made accessible through monikers. Examples include:
bonobo-config
, which allows for querying and manipulation
of the desktop configuration;
new:
-moniker).
Writing Bonobo controls and containers with, say, Python [4], is very easy. For example, let's write a control factory:
def control_factory (factory, object_id): # Create a GtkWidget and show it my_widget = gtk.GtkLabel ('Sample Control') my_widget.show() # Create a control from the widget and return it control = bonobo.BonoboControl (my_widget) return controlBy using Bonobo, the painful and slow process of writing Perl/Python/Guile/... wrappers for libraries is not necessary anymore: when the language supports Bonobo, it can simply use Bonobo components written in other languages and vice versa.
Bonobo certainly isn't perfect, and some problems remain:
Unknown
), is not really a good fit for out-of-process and
remote components;
This document was generated using the LaTeX2HTML translator Version 2K.1beta (1.50)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html nluug-bonobo.tex
The translation was initiated by Dirk-Jan C. Binnema on 2001-11-28