APPLET_WRITING 21.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
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 ...

12 13 14
Changes:
========

15 16 17
10/24/99) Took out the old panel size stuff and replaced it with the new
	 pixel_size stuff

18 19 20 21 22 23
5/30/99) Took out the old mico example, added .gnorba,.desktop examples
	 and explanation, and fixed the multiple applet support
	 documentation. Also fixed all the applet_widget_init and
	 applet_widget_new calls for the correct arguments. Hopefully it's
	 now correct.

24 25 26 27 28
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 ...

29 30 31 32 33 34 35 36 37
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.

38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

The Hello World of applets:
===========================

The simplest applet one can write would be along the lines of:

#include <config.h>
#include <gnome.h>
#include <applet-widget.h>

int
main(int argc, char **argv)
{
	GtkWidget *applet;
	GtkWidget *label;

54
	/* Initialize the i18n stuff */
55 56
        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
	textdomain (GETTEXT_PACKAGE);
57

58
	/* intialize, this will basically set up the applet, corba and
59
	   call gnome_init */
60
	applet_widget_init("hello_applet", NULL, argc, argv, NULL,0,NULL);
61 62

	/* create a new applet_widget */
63
	applet = applet_widget_new("hello_applet");
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
	/* 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
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
take care of restarting itself.

The .gnorba and .desktop files:
===============================

For the applet to be added to the menus, you need to install two files.
Your x.gnorba file goes into <prefix>/etc/CORBA/servers/ and the
x.desktop file goes into <prefix>/share/applets/<category>/.

Example files are

hello.desktop:

	[Desktop Entry]
	Name=Hello Applet
	Comment=An example Hello World type Applet
	Exec=hello_applet --activate-goad-server=hello_applet
	Icon=
	Terminal=0
	Type=Application

hello.gnorba:

	[hello_applet]
	type=exe
	repo_id=IDL:GNOME/Applet:1.0
	description=Hello Applet
	location_info=hello_applet

One thing to keep in mind is that the Exec line doesn't actually get
executed really. The panel will parse out the goad-server name and use
the standard gnome activation service to run the applet. For a simple
applet all you need to do is replace the hello_applet with the name of
your applet executable.
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147

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 <config.h>
#include <gnome.h>
#include <applet-widget.h>

static void
hello_there(AppletWidget *applet, gpointer data)
{
	g_print(_("Hello There"));
}

int
main(int argc, char **argv)
{
	GtkWidget *applet;
	GtkWidget *label;

148
	/* Initialize the i18n stuff */
149 150
        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
	textdomain (GETTEXT_PACKAGE);
151

152
	/* intialize, this will basically set up the applet, corba and
153
	   call gnome_init */
154
	applet_widget_init("hello_applet", NULL, argc, argv, NULL, 0, NULL);
155 156

	/* create a new applet_widget */
157
	applet = applet_widget_new("hello_applet");
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
	/* 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.

192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
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);


235 236 237
Session Saving:
===============

238 239 240 241 242 243 244
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:

245 246
gnome_config_push_prefix(APPLET_WIDGET(applet)->privcfgpath);
hello = gnome_config_get_bool("section/hello=true");
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
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 <config.h>
#include <gnome.h>
#include <applet-widget.h>

/* useless variable that we want to save the state of*/
gint hello = TRUE;

/* sesion save signal handler*/
static gint
263 264
applet_save_session(GtkWidget *w,
		    const char *privcfgpath,
265 266
		    const char *globcfgpath)
{
267 268
	gnome_config_push_prefix(privcfgpath);
	gnome_config_set_string("section/hello",hello);
269
	gnome_config_pop_prefix();
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292

	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;

293
	/* Initialize the i18n stuff */
294 295
        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
	textdomain (GETTEXT_PACKAGE);
296

297
	/* intialize, this will basically set up the applet, corba and
298
	   call gnome_init */
299
	applet_widget_init("hello_applet", NULL, argc, argv, NULL, 0, NULL);
300 301

	/* create a new applet_widget */
302
	applet = applet_widget_new("hello_applet");
303 304 305 306 307 308 309
	/* 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 */
310 311
	gnome_config_push_prefix(APPLET_WIDGET(applet)->privcfgpath);
	hello = gnome_config_get_bool("section/hello=true");
312 313 314 315 316 317 318
	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 */
319 320
	gtk_signal_connect(GTK_OBJECT(applet),"save_session",
			   GTK_SIGNAL_FUNC(applet_save_session),
321 322 323 324 325 326 327 328 329 330 331 332 333
			   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;
}

334
That's basically it. Make sure you return FALSE from the save_session
335 336 337 338 339 340
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
341 342 343 344 345 346 347 348 349
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.

350 351 352 353 354

gnome_config_push_prefix(APPLET_WIDGET(applet)->globcfgpath);
hello = gnome_config_get_bool("all_hello_applets/hello=true");
gnome_config_pop_prefix();

355
Similiarly for the save_session.
356

357 358
NOTE:
When you update your configuration in some properties dialog, or however
359
else, you should call applet_widget_sync_config(AppletWidget *applet),
360 361 362 363 364
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)

365 366 367
Panel Orientation:
==================

368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
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 <config.h>
#include <gnome.h>
#include <applet-widget.h>

/*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;

394
	/* Initialize the i18n stuff */
395 396
        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
	textdomain (GETTEXT_PACKAGE);
397

398
	/* intialize, this will basically set up the applet, corba and
399
	   call gnome_init */
400
	applet_widget_init("hello_applet", NULL, argc, argv, NULL, 0, NULL);
401 402

	/* create a new applet_widget */
403
	applet = applet_widget_new("hello_applet");
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
	/* 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,
436 437
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.
438 439 440 441

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.
442

443 444 445
Panel Size:
===========
(not in 1.0)
446

447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
One things that is very new in the panel now is the Size support. The
panel supports the following sizes:

Tiny: 24 pixels
Standard: 48 pixels
Large: 64 pixels
Huge: 80 pixels

It would be nice to let your applet pick it's layout so that it doesn't
stretch the panel out of it's preffered size (the panel is always as
thick as the thickest applet)

The way this works is very similiar to the way orientation works so
here is an example:

462
How to what size the panel on which your applet sits is, fairly simply.
463
You bind the "change_pixel_size" signal to the applet, so to modify our
464
original hello applet, we'd do:
465 466 467 468 469

#include <config.h>
#include <gnome.h>
#include <applet-widget.h>

470
#ifdef HAVE_PANEL_PIXEL_SIZE
471
/*this is when the panel size changes*/
472
static void
473
applet_change_pixel_size(GtkWidget *w, int size, gpointer data)
474
{
475
	printf("Got size of %d pixels\n",size);
476
}
477
#endif
478 479 480 481 482 483 484

int
main(int argc, char **argv)
{
	GtkWidget *applet;
	GtkWidget *label;

485
	/* Initialize the i18n stuff */
486 487
        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
	textdomain (GETTEXT_PACKAGE);
488

489 490
	/* intialize, this will basically set up the applet, corba and
	   call gnome_init */
491
	applet_widget_init("hello_applet", NULL, argc, argv, NULL, 0, NULL);
492 493

	/* create a new applet_widget */
494
	applet = applet_widget_new("hello_applet");
495 496 497 498 499 500 501 502 503
	/* 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);

504
#ifdef HAVE_PANEL_PIXEL_SIZE
505 506
	/*we have to bind change_pixel_size before we do applet_widget_add 
	  since we need to get an initial change_pixel_size signal to set our
507
	  initial size, and we get that during the _add call*/
508 509
	gtk_signal_connect(GTK_OBJECT(applet),"change_pixel_size",
			   GTK_SIGNAL_FUNC(applet_change_pixel_size),
510 511 512
			   NULL);
#endif

513 514 515 516 517 518 519 520 521 522 523
	/* 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;
}

524
Notice the "#ifdef HAVE_PANEL_PIXEL_SIZE" line, this will make sure your applet
525
compiles correctly even on a panel from gnome-core 1.0 which doesn't have
526 527 528 529 530 531 532 533 534 535 536 537 538
support for multiple sizes.  Note that in gnome-core 1.1 release there was
another implementation of panel sizes which is now deprecated, so you should
use the method above.

If you want to say compare to the standard sizes (You shouldn't assume that
they are the only ones that exist!), you can use the PIXEL_SIZE_TINY,
PIXEL_SIZE_STANDARD, PIXEL_SIZE_LARGE and PIXEL_SIZE_HUGE constants as in

if(size < PIXEL_SIZE_STANDARD) {
	... do something for very small applet ...
} else {
	... do something else for standard size applet ...
}
539 540 541 542

Rebinding events (Rebuilding the widget structure on the applet):
=================================================================
(not in 1.0)
543

544 545 546 547
Sometimes you want to change the way the applet looks after it has already
been added to the panel, and you want the right and middle mouse button
clicks to still work. In this case you need to notify the panel that it
should try to rebind the events. You do this with:
548

549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
#ifdef HAVE_APPLET_BIND_EVENTS
applet_widget_bind_events(APPLET_WIDGET(applet),GTK_WIDGET(widget));
#endif

which will bind mouseclicks (2nd and 3rd button) on the "widget".

Note that this is NOT in the panel in gnome-core 1.0, so if you use this
feature, make sure to put the "#ifdef HAVE_APPLET_BIND_EVENTS" around the
code.

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. We will create a factory corba service that can create new
instances of the applet.
569 570 571 572 573 574

#include <config.h>
#include <gnome.h>
#include <applet-widget.h>


575 576 577
/*when we get a command to start a new widget*/
static GtkWidget *
applet_start_new_applet(const gchar *goad_id, const gchar **params, gint nparams)
578 579 580 581
{
	GtkWidget *applet;
	GtkWidget *label;

582 583
	/*if we weren't asked to start hello_applet, just return*/
	if(strcmp(goad_id, "hello_applet")!=0) return NULL;
584

585 586
	/*now we do the same exact thing as we do in the main function for
	  creating the applet*/
587 588

	/* create a new applet_widget */
589
	applet = applet_widget_new("hello_applet");
590 591 592 593
	if (!applet)
		g_error("Can't create applet!\n");

	/* create the widget we are going to put on the applet */
594
	label = gtk_label_new(_("Hello There!"));
595 596 597 598 599 600 601
	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);

602 603
	/* return the applet widget from this function */
	return applet;
604 605 606 607 608
}

int
main(int argc, char **argv)
{
609 610 611
	GtkWidget *applet;
	GtkWidget *label;
	gchar *goad_id;
612 613

	/* Initialize the i18n stuff */
614 615
        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
	textdomain (GETTEXT_PACKAGE);
616

617 618 619
	/* intialize, this will basically set up the applet, corba and
	   call gnome_init */
	applet_widget_init("hello_applet", NULL, argc, argv, NULL, 0, NULL);
620

621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
	/*make new factory and get us the goad_id that was used to start us*/
	applet_factory_new("hello_applet_factory", NULL, applet_start_new_applet);
	goad_id = (gchar *)goad_server_activation_id();

	/*if the goad_id was hello_applet, we create a new instance of
	  our applet, otherwise don't do anything*/
	if(goad_id && strcmp(goad_id, "hello_applet")==0) {
		/* create a new applet_widget */
		applet = applet_widget_new("hello_applet");
		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);
641

642
	}
643 644

	/* special corba main loop */
645
	applet_widget_gtk_main ();
646 647 648 649

	return 0;
}

650 651 652
What you will notice is that what we do is just make a factory service
with applet_factory_new, to which we pass a function pointer to a function
that just creates new applets for us.
653

654 655 656
Now we need to create a .gnorba and .desktop files for an applet of this
type. The .desktop file is the exact same as for normal applets. The
.gnorba file however must now describe the factory as well.
657

658
hello.gnorba:
659

660 661 662 663 664
	[hello_applet_factory]
	type=exe
	repo_id=IDL:GNOME/GenericFactory:1.0
	description=Hello Applet
	location_info=hello_applet
665

666 667 668 669 670
	[hello_applet]
	type=factory
	repo_id=IDL:GNOME/Applet:1.0
	description=Hello Applet
	location_info=hello_applet_factory
671

672
That's it.
673

674 675 676 677 678 679
Now sometimes you may want to have two applets that have very similiar
functionality, but that appear to the user as two different applets, and
you want to manage them from the same process. This is extremely simple.
Just take the above example and add more types into the .gnorba file, then
wherever we check the goad_id, just add another "else if" to check for
another goad_id. Then in your desktops on the Exec line, you would have say
680

681
In one .desktop:
682

683
Exec=hello_applet --activate-goad-server=hello_version_1_applet
684

685
In another .desktop
686

687
Exec=hello_applet --activate-goad-server=hello_version_2_applet
688

689 690
Shared library applets:
=======================
691

692 693 694
It is possible to make applets which will not be separate processes, but
will be loaded directly into the panel. This makes the panel less stable
if the applet is less stable.
695

696 697 698 699 700 701 702 703
FIXME! Need explanation, examples.

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 <johne@bellatlantic.net>.
704

705
FIXME! We need a more up to date example.
706

707 708 709
It's all quite simple isn't it?

George <jirka@5z.com>