The GNOME Desktop uses a number of CORBA interfaces. These interface definitions may be found in gnome-libs/idl and gnome-core/idl. They are more thouroughfully listed in Appendix C.
Table 5-2. Gnome CORBA interfaces
Interface filename | Interface use |
---|---|
desktop-viewer.idl | A set of interfaces which define a text viewer. Any text viewer implementing these interfaces may be used to display text by the file manager for example. |
desktop-editor.idl | A set of interfaces which define a text editor. This interface inherits the desktop-viewer interface. Any text editor implementing these interfaces may be used to display and edit text by the file manager for example. |
Table.idl | A simple set of interfaces to define arrays. I never found any use to this. |
gnome-unknown.idl | This interface defines the COM Unknown interface. It is used as a basis of the bonobo objects. This will be discussed later in the part on the bonobo. |
GnomeObject.idl | It is used as a basis of the bonobo objects. This will be discussed later in the part on the bonobo. |
gnome-factory.idl | A simple interface to a factory object. |
gnome-panel.idl | The interface to the GNOME Panel. This interface is really complex and big. We'll see this later. |
help-browser.idl | A simple interface to an html browser to display the help documentation. This interface is implemented by the help-browser. |
All of these interfaces are REALLY simple except for the Panel interfaces. The Panel's complexity is interesting because it solves some problems common to Bonobo. The 2 main problems are: 1) how to display in an application a widget build in another application (running in another address space) and 2) how to create object instances.
GNOME provides a C api to wrap the CORBA calls into more developer-friendly C calls.
The Factory interface is used to provide a set of construct mechanisms. In OOP, Objects are often created by a special function. This function is called a constructor. For example, it would be good to have a constructor for our Panel Applets. This is generally what is being done and the Factory interface provides an abstraction layer on top of the construct mechanism.
// The factory interface. module GNOME { typedef sequence<string> stringlist; interface GenericFactory { exception CannotActivate { }; boolean supports(in string obj_goad_id); Object create_object(in string goad_id, in stringlist params) raises(CannotActivate); }; }; |
An object should wrap its constructor function behind am implementation of GNOME::GenericFactory::create_object(). Then, it is possible to ask the factory to generate a CORBA::Object reference for the newly created object. Here is some sample code which sets up a factory for a FruitsBasket::Apple object. This code was created with the --skeleton-impl parameter of orbit-idl. It allows you to very easily implement a factory as the *__create functions are already constructors.
// idl interface #include <gnome-factory.idl> module FruitsBasket { interface AppleFactory : GNOME::GenericFactory {}; interface Apple { void EatMe (); }; }; /********** C Factory skeleton implementation ***********/ #include "apple.h" /*** App-specific servant structures ***/ typedef struct { POA_FruitsBasket_AppleFactory servant; PortableServer_POA poa; } impl_POA_FruitsBasket_AppleFactory; typedef struct { POA_FruitsBasket_Apple servant; PortableServer_POA poa; } impl_POA_FruitsBasket_Apple; /*** Implementation stub prototypes ***/ static void impl_FruitsBasket_AppleFactory__destroy(impl_POA_FruitsBasket_AppleFactory * servant, CORBA_Environment * ev); static CORBA_boolean impl_FruitsBasket_AppleFactory_supports(impl_POA_FruitsBasket_AppleFactory * servant, CORBA_char * obj_goad_id, CORBA_Environment * ev); static CORBA_Object impl_FruitsBasket_AppleFactory_create_object(impl_POA_FruitsBasket_AppleFactory * servant, CORBA_char * goad_id, GNOME_stringlist * params, CORBA_Environment * ev); static void impl_FruitsBasket_Apple__destroy(impl_POA_FruitsBasket_Apple * servant, CORBA_Environment * ev); static void impl_FruitsBasket_Apple_EatMe(impl_POA_FruitsBasket_Apple * servant, CORBA_Environment * ev); /*** epv structures ***/ static PortableServer_ServantBase__epv impl_FruitsBasket_AppleFactory_base_epv = { NULL, /* _private data */ NULL, /* finalize routine */ NULL, /* default_POA routine */ }; static POA_FruitsBasket_AppleFactory__epv impl_FruitsBasket_AppleFactory_epv = { NULL, /* _private */ }; static POA_GNOME_GenericFactory__epv impl_FruitsBasket_AppleFactory_GNOME_GenericFactory_epv = { NULL, /* _private */ (gpointer) & impl_FruitsBasket_AppleFactory_supports, (gpointer) & impl_FruitsBasket_AppleFactory_create_object, }; static PortableServer_ServantBase__epv impl_FruitsBasket_Apple_base_epv = { NULL, /* _private data */ NULL, /* finalize routine */ NULL, /* default_POA routine */ }; static POA_FruitsBasket_Apple__epv impl_FruitsBasket_Apple_epv = { NULL, /* _private */ (gpointer) & impl_FruitsBasket_Apple_EatMe, }; /*** vepv structures ***/ static POA_FruitsBasket_AppleFactory__vepv impl_FruitsBasket_AppleFactory_vepv = { &impl_FruitsBasket_AppleFactory_base_epv, &impl_FruitsBasket_AppleFactory_GNOME_GenericFactory_epv, &impl_FruitsBasket_AppleFactory_epv, }; static POA_FruitsBasket_Apple__vepv impl_FruitsBasket_Apple_vepv = { &impl_FruitsBasket_Apple_base_epv, &impl_FruitsBasket_Apple_epv, }; /*** Stub implementations ***/ /* This is the Factory constructor. * It must be called from the main function so that * factory can create new Apple objects */ static FruitsBasket_AppleFactory impl_FruitsBasket_AppleFactory__create(PortableServer_POA poa, CORBA_Environment * ev) { FruitsBasket_AppleFactory retval; impl_POA_FruitsBasket_AppleFactory *newservant; PortableServer_ObjectId *objid; newservant = g_new0(impl_POA_FruitsBasket_AppleFactory, 1); newservant->servant.vepv = &impl_FruitsBasket_AppleFactory_vepv; newservant->poa = poa; POA_FruitsBasket_AppleFactory__init((PortableServer_Servant) newservant, ev); objid = PortableServer_POA_activate_object(poa, newservant, ev); CORBA_free(objid); retval = PortableServer_POA_servant_to_reference(poa, newservant, ev); return retval; } static void impl_FruitsBasket_AppleFactory__destroy(impl_POA_FruitsBasket_AppleFactory * servant, CORBA_Environment * ev) { PortableServer_ObjectId *objid; objid = PortableServer_POA_servant_to_id(servant->poa, servant, ev); PortableServer_POA_deactivate_object(servant->poa, objid, ev); CORBA_free(objid); POA_FruitsBasket_AppleFactory__fini((PortableServer_Servant) servant, ev); g_free(servant); } static CORBA_boolean impl_FruitsBasket_AppleFactory_supports(impl_POA_FruitsBasket_AppleFactory * servant, CORBA_char * obj_goad_id, CORBA_Environment * ev) { CORBA_boolean retval; return retval; } static CORBA_Object impl_FruitsBasket_AppleFactory_create_object(impl_POA_FruitsBasket_AppleFactory * servant, CORBA_char * goad_id, GNOME_stringlist * params, CORBA_Environment * ev) { CORBA_Object retval; /* here, the only things to do are to create or reuse * a poa and a poamanager and register with them * the Apple thanks to the * impl_FruitsBasket_Apple__create */ retval = impl_FruitsBasket_Apple__create (servant->poa, ev); return retval; } /* This is the Apple constructor which just needs to be * called by the create_object function of the factory * instead of being directly called by the main function */ static FruitsBasket_Apple impl_FruitsBasket_Apple__create(PortableServer_POA poa, CORBA_Environment * ev) { FruitsBasket_Apple retval; impl_POA_FruitsBasket_Apple *newservant; PortableServer_ObjectId *objid; newservant = g_new0(impl_POA_FruitsBasket_Apple, 1); newservant->servant.vepv = &impl_FruitsBasket_Apple_vepv; newservant->poa = poa; POA_FruitsBasket_Apple__init((PortableServer_Servant) newservant, ev); objid = PortableServer_POA_activate_object(poa, newservant, ev); CORBA_free(objid); retval = PortableServer_POA_servant_to_reference(poa, newservant, ev); return retval; } static void impl_FruitsBasket_Apple__destroy(impl_POA_FruitsBasket_Apple * servant, CORBA_Environment * ev) { PortableServer_ObjectId *objid; objid = PortableServer_POA_servant_to_id(servant->poa, servant, ev); PortableServer_POA_deactivate_object(servant->poa, objid, ev); CORBA_free(objid); POA_FruitsBasket_Apple__fini((PortableServer_Servant) servant, ev); g_free(servant); } static void impl_FruitsBasket_Apple_EatMe(impl_POA_FruitsBasket_Apple * servant, CORBA_Environment * ev) { } void main (int argc, char **argv) { CORBA_Environment ev; CORBA_ORB orb; CORBA_Object root_poa; PortableServer_POAManager root_poa_manager; CosNaming_NamingContext name_server; CORBA_Object obj; CORBA_exception_init (&ev); orb = gnome_CORBA_init ("An Apple", "1.0", &argc, argv, GNORBA_INIT_SERVER_FUNC, &ev); root_poa = CORBA_ORB_resolve_initial_references (orb, "RootPOA", &ev); root_poa_manager = PortableServer_POA__get_the_POAManager ( (PortableServer_POA) root_poa, &ev); PortableServer_POAManager_activate (root_poa_manager, &ev); /* this function will create all the other * needed interfaces */ obj = impl_FruitsBasket_AppleFactory__create ((PortableServer_POA) root_poa, &ev); /* register against the GOAD */ name_server = gnome_name_service_get (); /* This is really important: make sure you use these 2 functions when registering */ goad_server_register (name_server, obj, goad_server_activation_id(), "server", &ev); /* finished !! */ CORBA_exception_free (&ev); gtk_main (); return 0; } |
Once you have a CORBA::Object already implemented, it is generally a matter of minutes to add factory support to it: all the things which were originally done in the main function are now done in the implementation of GNOME::GenericFactory::create_object while a GNOME::GenericFactory object is created in the main function instead of the original object.
The only difficult thing is to use goad_server_register to register in the name service and goad_server_activation_id to get the proper registration name.
If you had the following apple.gnorba file before adding Factory support, you should now use a newer apple.gnorba file.
# old .gnorba file [Apple] type=exe repo_id=IDL:GNOME/Apple:1.0 description=A nice Apple location_info=apple |
#new .gnorba file [Apple_factory] type=exe repo_id=IDL:GNOME/GenericFactory:1.0 description=internal location_info=apple [Apple] type=factory repo_id=IDL:GNOME/Apple:1.0 description=A nice Apple location_info=Apple_factory |
To understand the subtle differences between the first version and the second one, one must look at the GOAD code.
In the first case, if we called goad_server_activate on the Apple GoadServer, the GOAD would call goad_server_activate_exe, an internal function of the GOAD which does a fork/exec to launch the program as stated by the location_info. Here, the apple program would instantiate our object and a servant for it and would register it in the name service. It would be possible for the GOAD to sit in a loop to wait for the new CORBA::Object to register but instead, it uses a pipe to read the IOR string output by our client on stdout (this is more efficient. dixit Eliot Lee, the code author).
In the second case, if we called goad_server_activate on the Apple GoadServer, the GOAD would call goad_server_activate_factory, another internal function of the GOAD. This function will first make sure the factory is started by calling goad_server_activate_with_id on the Apple location_info field. This will end by calling goad_server_activate_exe on Apple_factory if the factory is not already running. Then, the goad_server_activate_factory function calls the create_object method of the factory object returned by goad_server_activate_with_id.
I strongly suggest you to look at gnome-libs/libgnorba/goad.c to get convinced of this.
On the whole, using factories is really nice and not much work: it is sometimes necessary and offers many advantages. The most difficult thing is to set up the .gnorba file to make sure the scenario detailed just a few lines ago will work. It should be noted that for example, applets have some C api calls to ease the use of factories: applet_factory_new is one of these.
The GNOME Panel has three interfaces: GNOME::Panel, GNOME::PanelSpot and GNOME::Applet. GNOME::Panel and PanelSpot must be implemented by the Panel itself while GNOME::Applet should be implemented by the Applets themselves.
The Panel interface is defined as follows:
module GNOME { struct Color { unsigned short red, green, blue; }; typedef short AppletId; typedef short PanelId; interface Panel { enum OrientType { ORIENT_UP, ORIENT_DOWN, ORIENT_LEFT, ORIENT_RIGHT }; enum BackType { BACK_NONE, BACK_COLOR, BACK_PIXMAP }; union BackInfoType switch(BackType) { case BACK_COLOR: Color c; case BACK_PIXMAP: string pmap; }; enum SizeType { SIZE_TINY, SIZE_STANDARD, SIZE_LARGE, SIZE_HUGE }; PanelSpot add_applet(in Applet panel_applet, in string goad_id, out string cfgpath, out string globcfgpath, out unsigned long winid); oneway void quit (); readonly attribute boolean in_drag; }; }; |
This interface will init a GNOME::PanelSpot which is what the Applet talks to. The idea is that the applet builds a widget to be displayed and the panel gives the applet a window to draw this widget onto. The applet also tells the panel what menu items should appear when right clicking and the panel tells the applet what menu item was clicked so that the correct callback is called. All this is made through the PanelSpot and the Applet interface.
If I am an applet, I must first make sure that my implementation of the Applet interface is working and is registered against the ORB. Then, I call the GNOME::Panel::add_applet function to tell the Panel that I want to be on the Panel.
Upon return, it will return a GNOME::PanelSpot interface and a unsigned long winid.
The GTK Plug and Socket widgets are used to convert this winid in something useful. The Socket widget will generate a window on which things can be drawn. The Socket widget is converted to a winid with the GTK_WINDOW_XWINDOW(widget) macro (from gtk/gtkx.h) and the Plug widget will convert back this winid to a GtkWidget. The panel creates a socket and passes the winid back to the applet asking for a window on the panel.Then, the applet will add its widgets to the corresponding gtk_plug widget.
Because i believe that the gtk_plug/gtk_socket/winid system is very important in understanding how things work, i wrote some test code. (see appendix D)
Hopefully, all this complexity is hidden behind C api functions designed for the Applet interface.
To handle the right click context menu, one needs some work. All events on the Socket window are sent to the socket widget which has no knowledge of the plug widget drawing things on it. This means that the socket cannot pass the events to the plug widget and to the application which handles the inside of the socket window. Events must thus be handled with another system. The right click event is handled through the following interfaces:
module GNOME { interface PanelSpot { // gosh !! i removed many things from here !! oneway void add_callback (in string callback_name, in string stock_item, in string menuitem_text); oneway void remove_callback (in string callback_name); }; interface Applet { // gosh !! i removed many things from here !! oneway void do_callback (in string callback_name); }; }; |
One should note that most function interface declarations are oneway to save network bandwidth and system load.
The Applet can build the menu to be displayed upon right click with the GNOME::PanelSpot::add_callback, GNOME::PanelSpot::remove_callback. Then, the Panel will call GNOME::Applet::do_callback when the user has clicked on a menu item.
The PanelSpot and Applet interfaces are much bigger than what i showed of them: there are many other things to take care of: the orientation changes, the size changes, tooltips showing, background color/pixmap... I leave you the rest to look at and spend some time on. It is hopefully much simpler :)
Hopefully, once more, all the complexity of CORBA is hidden behind C functions which are more or less wrappers around the CORBA code. This is what the libapplet library does. (gnome-core/panel/applet-widget.c and applet-widget.h)