NOTE: FIXME:!!!!!!!!!!!!! This is broken now, needs fixing!!!! This is a little document that describes the "art" of applet writing. Introduction: ============= Applets are basically gtk applications with one major difference, they have a window which sits inside the panel. Also the panel "takes care" of the applets by providing them with stuff such as sessions saving, and taking care of their space on the panel, taking care of restarting them, etc ... etc ... etc ... Changes: ======== 7/23/98) The applets should call applet_widget_sync_config, so that their changes can be noticed by the panel and synced to disk immediately it's not completely neccessary as everything will be saved when logging out, but it makes it nice for crashes, etc ... 7/3/98) the session_save signal is now being phased out, you need to use save_session signal which has basically the same interface, but uses privcfgpath instead of cfgpath. cfgpath variable is also being phased out and should not be used, you should use privcfgpath instead. The change is basically that for privcfgpath and save_session you add "section/key" to the path instead of just "key". The old stuff is still in for compatibility reasons but will disappear soon. The Hello World of applets: =========================== The simplest applet one can write would be along the lines of: #include #include #include int main(int argc, char **argv) { GtkWidget *applet; GtkWidget *label; /* Initialize the i18n stuff */ bindtextdomain (PACKAGE, GNOMELOCALEDIR); textdomain (PACKAGE); /* intialize, this will basically set up the applet, corba and call gnome_init */ applet_widget_init_defaults("hello_applet", NULL, argc, argv, 0, NULL, argv[0]); /* create a new applet_widget */ applet = applet_widget_new(); /* in the rare case that the communication with the panel failed, error out */ if (!applet) g_error("Can't create applet!\n"); /* create the widget we are going to put on the applet */ label = gtk_label_new(_("Hello There!")); gtk_widget_show(label); /* add the widget to the applet-widget, and thereby actually putting it "onto" the panel */ applet_widget_add (APPLET_WIDGET (applet), label); gtk_widget_show (applet); /* special corba main loop */ applet_widget_gtk_main (); return 0; } This creates an applet which just sits on the panel, not really doing anything, in real life the label would be substituted by something which actually does something useful. as you can see the applet doesn't really take care of restarting itself, it just passes the argv[0] parameter to the panel, in the applet_widget_init_defaults call and that's the way the panel will restart it next time. Now to the more interesting stuff. Applet Menu: ============ When the user right clicks on the applet, a menu appears, this is all handeled by the panel, so in order to add items to it you sue a special interface to "add callbacks" to the menu. A very simple example would be (making our hello applet even more feature full): #include #include #include static void hello_there(AppletWidget *applet, gpointer data) { g_print(_("Hello There")); } int main(int argc, char **argv) { GtkWidget *applet; GtkWidget *label; /* Initialize the i18n stuff */ bindtextdomain (PACKAGE, GNOMELOCALEDIR); textdomain (PACKAGE); /* intialize, this will basically set up the applet, corba and call gnome_init */ applet_widget_init_defaults("hello_applet", NULL, argc, argv, 0, NULL, argv[0]); /* create a new applet_widget */ applet = applet_widget_new(); /* in the rare case that the communication with the panel failed, error out */ if (!applet) g_error("Can't create applet!\n"); /* create the widget we are going to put on the applet */ label = gtk_label_new(_("Hello There!")); gtk_widget_show(label); /* add the widget to the applet-widget, and thereby actually putting it "onto" the panel */ applet_widget_add (APPLET_WIDGET (applet), label); gtk_widget_show (applet); /* add an item to the applet menu */ applet_widget_register_callback(APPLET_WIDGET(applet), "hello", _("Hello There"), hello_there, NULL); /* special corba main loop */ applet_widget_gtk_main (); return 0; } Now the user will see a "Hello There" menu item on the applet menu, and when selected, the applet will print "Hello There". Useful huh? Note that the second argument to the register_callback is just a string identifier of this callback, and can really be whatever you want. But it should NOT be translated as the label (the 3rd argument) should be. Advanced Menu Stuff: ==================== It is also possible to have submenus, remove menus and use gnome-stock icons on the menus. Submenus: To do submenus, you have to first call applet_widget_register_callback_dir, which only takes the callback name and the menu text. The callback name should end with '/'. The callback name works as a "path" for the submenus. So to add a submenu "Foo" and in item "Bar" (into the submenu "Foo") you would do applet_widget_register_callback_dir(APPLET_WIDGET(applet), "foo/", _("Foo")); applet_widget_register_callback(APPLET_WIDGET(applet), "foo/bar", _("Bar"), bar_callback, NULL); Deleting: To delete a menu item, just call applet_widget_unregister_callback or applet_widget_unregister_callback_dir, with the proper callback name. Stock Icons: You use the _stock derivatives of the callback functions and pass an extra argument with the GNOME_STOCK_MENU_* type. For example to add an about menu item: applet_widget_register_stock_callback(APPLET_WIDGET(applet), "about", GNOME_STOCK_MENU_ABOUT, _("About..."), about_cb, NULL); Session Saving: =============== The panel is session manager aware but the applets don't have to be, they can depend on the panel to save their information in a proper place. Basically session saving has two parts, loading the info, and saving the info. Loading is pretty simple, after you do applet_widget_new, you can get the correct paths to load your properties from the widget's structure. For example: gnome_config_push_prefix(APPLET_WIDGET(applet)->privcfgpath); hello = gnome_config_get_bool("section/hello=true"); gnome_config_pop_prefix(); will do the trick. For saving it's a little bit more complicated but not by much, let's make our original example save a global variable hello. #include #include #include /* useless variable that we want to save the state of*/ gint hello = TRUE; /* sesion save signal handler*/ static gint applet_save_session(GtkWidget *w, const char *privcfgpath, const char *globcfgpath) { gnome_config_push_prefix(privcfgpath); gnome_config_set_string("section/hello",hello); gnome_config_pop_prefix(); gnome_config_sync(); /* you need to use the drop_all here since we're all writing to one file, without it, things might not work too well */ gnome_config_drop_all(); /* make sure you return FALSE, otherwise your applet might not work compeltely, there are very few circumstances where you want to return TRUE. This behaves similiar to GTK events, in that if you return FALSE it means that you haven't done everything yourself, meaning you want the panel to save your other state such as the panel you are on, position, parameter, etc ... */ return FALSE; } int main(int argc, char **argv) { GtkWidget *applet; GtkWidget *label; /* Initialize the i18n stuff */ bindtextdomain (PACKAGE, GNOMELOCALEDIR); textdomain (PACKAGE); /* intialize, this will basically set up the applet, corba and call gnome_init */ applet_widget_init_defaults("hello_applet", NULL, argc, argv, 0, NULL, argv[0]); /* create a new applet_widget */ applet = applet_widget_new(); /* in the rare case that the communication with the panel failed, error out */ if (!applet) g_error("Can't create applet!\n"); /* read the contents of the stored value of hello from the config file */ gnome_config_push_prefix(APPLET_WIDGET(applet)->privcfgpath); hello = gnome_config_get_bool("section/hello=true"); gnome_config_pop_prefix(); /* create the widget we are going to put on the applet */ label = gtk_label_new(_("Hello There!")); gtk_widget_show(label); /* bind the session save signal */ gtk_signal_connect(GTK_OBJECT(applet),"save_session", GTK_SIGNAL_FUNC(applet_save_session), NULL); /* add the widget to the applet-widget, and thereby actually putting it "onto" the panel */ applet_widget_add (APPLET_WIDGET (applet), label); gtk_widget_show (applet); /* special corba main loop */ applet_widget_gtk_main (); return 0; } That's basically it. Make sure you return FALSE from the save_session handler, else the panel will not remember your applet next time. Also note the presence of gnome_config_drop_all, that needs to be done, especially for multi applets (discussed below), or your info might get lost. If you need to store information global to all applets you can use the globcfgpath counterpart of privcfgpath, which gives you a path to a file which is the same for all applets. IMPORTANT! Make sure you only use two levels of config path below privcfgpath/globcfgpath. Which means you only tack on "section/key". Also don't just use "key". You need to tack on both the section and the key, no more, no less. gnome_config_push_prefix(APPLET_WIDGET(applet)->globcfgpath); hello = gnome_config_get_bool("all_hello_applets/hello=true"); gnome_config_pop_prefix(); Similiarly for the save_session. NOTE: When you update your configuration in some properties dialog, or however else, you should call applet_widget_config_sync(AppletWidget *applet), it will tell the panel to send a session save signal to the applet with the correct paths etc. This is not 100% neccessary, but makes it nice so that configuration is not lost during crashes (when panel couldn't do it's complete save during shutdown) Panel Orientation: ================== How to tell which way the panel on which your applet sits is oriented, fairly simply. You bind the "change_orient" signal to the applet, so to modify our original hello applet, we'd do: #include #include #include /*this is when the panel orientation changes*/ static void applet_change_orient(GtkWidget *w, PanelOrientType o, gpointer data) { switch(o) { case ORIENT_UP: puts("ORIENT UP"); break; case ORIENT_DOWN: puts("ORIENT DOWN"); break; case ORIENT_LEFT: puts("ORIENT LEFT"); break; case ORIENT_RIGHT: puts("ORIENT RIGHT"); break; } } int main(int argc, char **argv) { GtkWidget *applet; GtkWidget *label; /* Initialize the i18n stuff */ bindtextdomain (PACKAGE, GNOMELOCALEDIR); textdomain (PACKAGE); /* intialize, this will basically set up the applet, corba and call gnome_init */ applet_widget_init_defaults("hello_applet", NULL, argc, argv, 0, NULL, argv[0]); /* create a new applet_widget */ applet = applet_widget_new(); /* in the rare case that the communication with the panel failed, error out */ if (!applet) g_error("Can't create applet!\n"); /* create the widget we are going to put on the applet */ label = gtk_label_new(_("Hello There!")); gtk_widget_show(label); /*we have to bind change_orient before we do applet_widget_add since we need to get an initial change_orient signal to set our initial oriantation, and we get that during the _add call*/ gtk_signal_connect(GTK_OBJECT(applet),"change_orient", GTK_SIGNAL_FUNC(applet_change_orient), NULL); /* add the widget to the applet-widget, and thereby actually putting it "onto" the panel */ applet_widget_add (APPLET_WIDGET (applet), label); gtk_widget_show (applet); /* special corba main loop */ applet_widget_gtk_main (); return 0; } Now we get a signal every time the panel changes it's orientation and we can change ours as well. The different values represent the orientation a menu/drawer would take were it on the panel, not the actual position of the panel. If the Panel "sits" on the bottom edge of the screen you will get ORIENT_UP, if it sits on the left edge, you get ORIENT_RIGHT, and so on, if the panel is a vertical drawer you get ORIENT_RIGHT or ORIENT_LEFT, if it's a horizontal drawer you get ORIENT_UP or ORIENT_DOWN. Also note that you should bind the event before you do applet_widget_add, as the event will be triggered during the add, so that you can set your initial orientation. Multiple Applet Support: ======================== Having one process per applet might be ok, but when you have many applets it can be quite a hit on the memory. so why not manage multiple applets from one process, even different types of applets. Ok here's how it's done. For a simple example let's modify our original hello applet to make it possible to have multiple instances of it from just one executable. #include #include #include /*when we get a command to start a new widget*/ static void applet_start_new_applet(const gchar *param, gpointer data) { GtkWidget *applet; applet = applet_widget_new_with_param(param); if (!applet) g_error("Can't create applet!\n"); /* create the widget we are going to put on the applet */ label = gtk_label_new(_("Hello There!")); gtk_widget_show(label); /* add the widget to the applet-widget, and thereby actually putting it "onto" the panel */ applet_widget_add (APPLET_WIDGET (applet), label); gtk_widget_show (applet); } int main(int argc, char **argv) { GtkWidget *applet; GtkWidget *label; /* Initialize the i18n stuff */ bindtextdomain (PACKAGE, GNOMELOCALEDIR); textdomain (PACKAGE); /* intialize, this will basically set up the applet, corba and call gnome_init */ applet_widget_init("hello_applet", NULL, argc, argv, 0, NULL, argv[0], TRUE,TRUE,applet_start_new_applet,NULL); /* create a new applet_widget */ applet = applet_widget_new(); /* in the rare case that the communication with the panel failed, error out */ if (!applet) g_error("Can't create applet!\n"); /* create the widget we are going to put on the applet */ label = gtk_label_new(_("Hello There!")); gtk_widget_show(label); /* add the widget to the applet-widget, and thereby actually putting it "onto" the panel */ applet_widget_add (APPLET_WIDGET (applet), label); gtk_widget_show (applet); /* special corba main loop */ applet_widget_gtk_main (); return 0; } You will note in the above that all that is changed is that we use the full syntax for the applet_widget_init function, and added a callback function for starting a new applet. That's all that's needed really. Of course the applet can't do things globally, but most likely you will want to store the widget data in a structure which you store in the user_data of the applet widget. See the clock applet for an example of this. If you want to put more applet types into one executable, you will need to use a command line argument. This might change in the future since I think the current way is kind of hackish, but I need to figure out a clean interface for this, and I haven't thought of one that would work well yet. Anyway it's fairly simple to do. Here's a simple hello applet (again), but it handeles a "Good Bye" applet as well. #include #include #include #include /* These are the arguments that our application supports. */ static struct argp_option arguments[] = { { "hello", -1, NULL, 0, N_("Start in hello mode"), 1 }, { "goodbye", -1, NULL, 0, N_("Start in goodbye mode"), 1 }, { NULL, 0, NULL, 0, NULL, 0 } }; /* Forward declaration of the function that gets called when one of our arguments is recognized. */ /* we ignore the arguments */ static error_t parse_an_arg (int key, char *arg, struct argp_state *state) { return 0; } /* This structure defines our parser. It can be used to specify some options for how our parsing function should be called. */ static struct argp parser = { arguments, /* Options. */ parse_an_arg, /* The parser function. */ NULL, /* Some docs. */ NULL, /* Some more docs. */ NULL, /* Child arguments -- gnome_init fills this in for us. */ NULL, /* Help filter. */ NULL /* Translation domain; for the app it can always be NULL. */ }; /* start a new instance of the hello applet */ static void make_hello_applet(gchar *param) { GtkWidget *applet; GtkWidget *label; /* create a new applet_widget */ applet = applet_widget_new_with_param(param); /* in the rare case that the communication with the panel failed, error out */ if (!applet) g_error("Can't create applet!\n"); /* create the widget we are going to put on the applet */ label = gtk_label_new(_("Hello There!")); gtk_widget_show(label); /* add the widget to the applet-widget, and thereby actually putting it "onto" the panel */ applet_widget_add (APPLET_WIDGET (applet), label); gtk_widget_show (applet); } /* start a new instance of the goodbye applet */ static void make_goodbye_applet(gchar *param) { GtkWidget *applet; GtkWidget *label; /* create a new applet_widget */ applet = applet_widget_new_with_param(param); /* in the rare case that the communication with the panel failed, error out */ if (!applet) g_error("Can't create applet!\n"); /* create the widget we are going to put on the applet */ label = gtk_label_new(_("GoodBye!")); gtk_widget_show(label); /* add the widget to the applet-widget, and thereby actually putting it "onto" the panel */ applet_widget_add (APPLET_WIDGET (applet), label); gtk_widget_show (applet); } /* start a new applet depending on the param string */ static void make_new_applet(const gchar *param) { if(strstr(param,"--hello")) make_hello_applet(param); else /* --goodbye */ make_goodbye_applet(param); } /*when we get a command to start a new widget*/ static void applet_start_new_applet(const gchar *param, gpointer data) { make_new_applet(param); } int main(int argc, char **argv) { gchar *param; /* Initialize the i18n stuff */ bindtextdomain (PACKAGE, GNOMELOCALEDIR); textdomain (PACKAGE); /*we make a param string, instead of first parsing the params with argp, we will allways use a string, since the panel will only give us a string */ param = make_param_string(argc,argv); applet_widget_init("hello_goodbye_applet", &parser, argc, argv, 0, NULL, argv[0],TRUE,TRUE,applet_start_new_applet, NULL); /* start a new applet using param */ make_new_applet(param); g_free(param); /* special corba main loop */ applet_widget_gtk_main(); return 0; } Not as simple as the last one, but still simple enough. We have to set up the the argp argument stuff, even though we ignore it. The make_hello_applet and make_goodbye_applet could really be merged together. But the way I envision such a setup is that those functions are in separate files along with all the functionality of that particular applet. After that you only create two .desktop entries, one with: "hello_goodbye_applet --hello" and one with: "hello_goodbye_applet --goodbye" Building the applets: ===================== Here's a simple makefile you can use (this one is for the fish applet) if you want to compile applets outside of the gnome-core source tree. It was sent to me by John Ellis . ------------------8<------------------- #set PREFIX to gnome's location: PREFIX = /usr BINARY = fish_applet DESKTOP_FILE = fish_applet.desktop DESKTOP_DIR = Amusements # i18n does not like this, but: PACKAGE = gnome-core include $(PREFIX)/lib/gnomeConf.sh #Parse out those nasty quotes. GNOME_INCLUDEDIR_PARSED = $(subst ",,$(GNOME_INCLUDEDIR)) GNOME_LIBS_PARSED = $(subst ",,$(GNOME_LIBS)) GNOMEUI_LIBS_PARSED = $(subst ",,$(GNOMEUI_LIBS)) GTK_INCLUDE = `gtk-config --cflags` GTK_LIB = `gtk-config --libs` CFLAGS = $(GTK_INCLUDE) $(GNOME_INCLUDEDIR_PARSED) -I. LDFLAGS = $(GTK_LIB) $(GNOMEUI_LIBS_PARSED) $(PREFIX)/lib/libpanel_applet.so \ -lmicocoss2.0.5 -lmicoaux2.0.5 -lmico2.0.5 CC = gcc -Wall $(CFLAGS) CCPLUS = c++ -Wall OBJS = fish.o $(BINARY): $(OBJS) $(CCPLUS) $(OBJS) -o $(BINARY) $(LDFLAGS) fish.o : fish.c echo "#define PACKAGE \"$(PACKAGE)\"" > config.h $(CC) -c fish.c install: install -c $(BINARY) $(PREFIX)/bin/$(BINARY) install -c $(DESKTOP_FILE) $(PREFIX)/share/applets/$(DESKTOP_DIR)/$(DESK TOP_FILE) uninstall: rm -f $(PREFIX)/bin/$(BINARY) rm -f $(PREFIX)/share/applets/$(DESKTOP_DIR)/$(DESKTOP_FILE) clean: rm -f *.o *~ $(BINARY) core ------------------8<------------------- It's all quite simple isn't it? George