The following code examples are used in this chapter:
The underlying system services in the maemo platform differ slightly from those used in desktop Linux distributions. This chapter gives an overview of the most important system services.
In practice, this means that all GnomeVFS file access methods are transparently available for both developer and end user just by using the framework for file operations. The API for file handling is also much more flexible than the standard platform offerings. It features, for example, asynchronous reading and writing, MIME type support and file monitoring.
All user-file access should be done with GnomeVFS in maemo applications, because file access can be remote. In fact, many applications that come with the operating system on the Internet tablets do make use of GnomeVFS. Access to files not visible to the user should be done directly for performance reasons.
A good hands-on starting point is taking a look at the GnomeVFS example in maemo-examples package. Detailed API information can be found in the GnomeVFS API reference[31].
GnomeVFS Example
In maemo, GnomeVFS also provides some filename case insensitivity support, so that the end users do not have to care about the UNIX filename conventions, which are case-sensitive.
The GnomeVFS interface attempts to provide a POSIX-like interface, so that when one would use open() with POSIX, gnome_vfs_open can be used instead. Instead of write(), there is gnome_vfs_write, etc. (for most functions). The GnomeVFS function names are sometimes a bit more verbose, but otherwise they attempt to implement the basic API. Some POSIX functions, such as mmap(), are impossible to implement in the user space, but normally this is not a big problem. Also some functions will fail to work properly over network connections and outside the local filesystem, since they might not always make sense there.
Shortly there will follow a simple example of using the GnomeVFS interface functions.
In order to save and load data, at least the following functions are needed:
In order to differentiate between different protocols, GnomeVFS uses Uniform Resource Location syntax when accessing resources. For example in file:///tmp/somefile.txt, the file:// is the protocol to use, and the rest is the location within that protocol space for the resource or file to manipulate. Protocols can be stacked inside a single URI, and the URI also supports username and password combinations (these are best demonstrated in the GnomeVFS API documentation).
The following simple demonstration will be using local files.
A simple application will be extended in the following ways:
N.B. Since GnomeVFS is a separate library from GLib, you will have to add the flags and library options that it requires. The pkg-config package name for the library is gnome-vfs-2.0.
/**
* hildon_helloworld-8.c
*
* This maemo code example is licensed under a MIT-style license,
* that can be found in the file called "License" in the same
* directory as this file.
* Copyright (c) 2007-2008 Nokia Corporation. All rights reserved.
*
* We add file loading support using GnomeVFS. Saving files using
* GnomeVFS is left as an exercise. We also add a small notification
* widget (HildonBanner).
*
* Look for lines with "NEW" or "MODIFIED" in them.
*/
#include <stdlib.h>
#include <hildon/hildon-program.h>
#include <hildon/hildon-color-button.h>
#include <hildon/hildon-find-toolbar.h>
#include <hildon/hildon-file-chooser-dialog.h>
/* A small notification window widget (NEW). */
#include <hildon/hildon-banner.h>
/* Pull in the GnomeVFS headers (NEW). */
#include <libgnomevfs/gnome-vfs.h>
/* Declare the two slant styles. */
enum {
STYLE_SLANT_NORMAL = 0,
STYLE_SLANT_ITALIC
};
/**
* The application state.
*/
typedef struct {
gboolean styleUseUnderline;
gboolean styleSlant;
GdkColor currentColor;
/* Pointer to the label so that we can modify its contents when a
file is loaded by the user (NEW). */
GtkWidget* textLabel;
gboolean fullScreen;
GtkWidget* findToolbar;
GtkWidget* mainToolbar;
gboolean findToolbarIsVisible;
gboolean mainToolbarIsVisible;
HildonProgram* program;
HildonWindow* window;
} ApplicationState;
/*... Listing cut for brevity ...*/
/**
* Utility function to print a GnomeVFS I/O related error message to
* standard error (not seen by the user in graphical mode) (NEW).
*/
static void dbgFileError(GnomeVFSResult errCode, const gchar* uri) {
g_printerr("Error while accessing '%s': %s\n", uri,
gnome_vfs_result_to_string(errCode));
}
/**
* MODIFIED (A LOT)
*
* We read in the file selected by the user if possible and set the
* contents of the file as the new Label content.
*
* If reading the file fails, the label will be left unchanged.
*/
static void cbActionOpen(GtkWidget* widget, ApplicationState* app) {
gchar* filename = NULL;
/* We need to use URIs with GnomeVFS, so declare one here. */
gchar* uri = NULL;
g_assert(app != NULL);
/* Bad things will happen if these two widgets don't exist. */
g_assert(GTK_IS_LABEL(app->textLabel));
g_assert(GTK_IS_WINDOW(app->window));
g_print("cbActionOpen invoked\n");
/* Ask the user to select a file to open. */
filename = runFileChooser(app, GTK_FILE_CHOOSER_ACTION_OPEN);
if (filename) {
/* This will point to loaded data buffer. */
gchar* buffer = NULL;
/* Pointer to a structure describing an open GnomeVFS "file". */
GnomeVFSHandle* fileHandle = NULL;
/* Structure to hold information about a "file", initialized to
zero. */
GnomeVFSFileInfo fileInfo = {};
/* Result code from the GnomeVFS operations. */
GnomeVFSResult result;
/* Size of the file (in bytes) that we'll read in. */
GnomeVFSFileSize fileSize = 0;
/* Number of bytes that were read in successfully. */
GnomeVFSFileSize readCount = 0;
g_print(" you chose to load file '%s'\n", filename);
/* Convert the filename into an GnomeVFS URI. */
uri = gnome_vfs_get_uri_from_local_path(filename);
/* We don't need the original filename anymore. */
g_free(filename);
filename = NULL;
/* Should not happen since we got a filename before. */
g_assert(uri != NULL);
/* Attempt to get file size first. We need to get information
about the file and aren't interested in other than the very
basic information, so we'll use the INFO_DEFAULT setting. */
result = gnome_vfs_get_file_info(uri, &fileInfo,
GNOME_VFS_FILE_INFO_DEFAULT);
if (result != GNOME_VFS_OK) {
/* There was a failure. Print a debug error message and break
out into error handling. */
dbgFileError(result, uri);
goto error;
}
/* We got the information (maybe). Let's check whether it
contains the data that we need. */
if (fileInfo.valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE) {
/* Yes, we got the file size. */
fileSize = fileInfo.size;
} else {
g_printerr("Couldn't get the size of file!\n");
goto error;
}
/* By now we have the file size to read in. Check for some limits
first. */
if (fileSize > 1024*100) {
g_printerr("Loading over 100KiB files is not supported!\n");
goto error;
}
/* Refuse to load empty files. */
if (fileSize == 0) {
g_printerr("Refusing to load an empty file\n");
goto error;
}
/* Allocate memory for the contents and fill it with zeroes.
NOTE:
We leave space for the terminating zero so that we can pass
this buffer as gchar to string functions and it is
guaranteed to be terminated, even if the file doesn't end
with binary zero (odds of that happening are small). */
buffer = g_malloc0(fileSize+1);
if (buffer == NULL) {
g_printerr("Failed to allocate %u bytes for buffer\n",
(guint)fileSize);
goto error;
}
/* Open the file.
Parameters:
- A pointer to the location where to store the address of the
new GnomeVFS file handle (created internally in open).
- uri: What to open (needs to be GnomeVFS URI).
- open-flags: Flags that tell what we plan to use the handle
for. This will affect how permissions are checked by the
Linux kernel. */
result = gnome_vfs_open(&fileHandle, uri, GNOME_VFS_OPEN_READ);
if (result != GNOME_VFS_OK) {
dbgFileError(result, uri);
goto error;
}
/* File opened succesfully, read its contents in. */
result = gnome_vfs_read(fileHandle, buffer, fileSize,
&readCount);
if (result != GNOME_VFS_OK) {
dbgFileError(result, uri);
goto error;
}
/* Verify that we got the amount of data that we requested.
NOTE:
With URIs it won't be an error to get less bytes than you
requested. Getting zero bytes will however signify an
End-of-File condition. */
if (fileSize != readCount) {
g_printerr("Failed to load the requested amount\n");
/* We could also attempt to read the missing data until we have
filled our buffer, but for simplicity, we'll flag this
condition as an error. */
goto error;
}
/* Whew, if we got this far, it means that we actually managed to
load the file into memory. Let's set the buffer contents as
the new label now. */
gtk_label_set_markup(GTK_LABEL(app->textLabel), buffer);
/* That's it! Display a message of great joy. For this we'll use
a dialog (non-modal) designed for displaying short
informational messages. It will linger around on the screen
for a while and then disappear (in parallel to our program
continuing). */
hildon_banner_show_information(GTK_WIDGET(app->window),
NULL, /* Use the default icon (info). */
"File loaded successfully");
/* Jump to the resource releasing phase. */
goto release;
error:
/* Display a failure message with a stock icon.
Please see http://maemo.org/api_refs/4.0/gtk/gtk-Stock-Items.html/
for a full listing of stock items. */
hildon_banner_show_information(GTK_WIDGET(app->window),
GTK_STOCK_DIALOG_ERROR, /* Use the stock error icon. */
"Failed to load the file");
release:
/* Close and free all resources that were allocated. */
if (fileHandle) gnome_vfs_close(fileHandle);
if (filename) g_free(filename);
if (uri) g_free(uri);
if (buffer) g_free(buffer);
/* Zero them all out to prevent stack-reuse-bugs. */
fileHandle = NULL;
filename = NULL;
uri = NULL;
buffer = NULL;
return;
} else {
g_print(" you didn't choose any file to open\n");
}
}
/**
* MODIFIED (kind of)
*
* Function to save the contents of the label (although it doesn't
* actually save the contents, on purpose). Use gtk_label_get_label
* to get a gchar pointer into the application label contents
* (including current markup), then use gnome_vfs_create and
* gnome_vfs_write to create the file (left as an exercise).
*/
static void cbActionSave(GtkWidget* widget, ApplicationState* app) {
gchar* filename = NULL;
g_assert(app != NULL);
g_print("cbActionSave invoked\n");
filename = runFileChooser(app, GTK_FILE_CHOOSER_ACTION_SAVE);
if (filename) {
g_print(" you chose to save into '%s'\n", filename);
/* Process saving .. */
g_free(filename);
filename = NULL;
} else {
g_print(" you didn't choose a filename to save to\n");
}
}
/*... Listing cut for brevity ...*/
/**
* MODIFIED
*
* Add support for GnomeVFS (it needs to be initialized before use)
* and add support for the Pango markup feature of the GtkLabel
* widget.
*/
int main(int argc, char** argv) {
ApplicationState aState = {};
GtkWidget* label = NULL;
GtkWidget* vbox = NULL;
GtkWidget* mainToolbar = NULL;
GtkWidget* findToolbar = NULL;
/* Initialize the GnomeVFS (NEW). */
if(!gnome_vfs_init()) {
g_error("Failed to initialize GnomeVFS-libraries, exiting\n");
}
/* Initialize the GTK+ */
gtk_init(&argc, &argv);
/* Setup the HildonProgram, HildonWindow and application name. */
aState.program = HILDON_PROGRAM(hildon_program_get_instance());
g_set_application_name("Hello Hildon!");
aState.window = HILDON_WINDOW(hildon_window_new());
hildon_program_add_window(aState.program,
HILDON_WINDOW(aState.window));
/* Create the label widget, with Pango marked up content (NEW). */
label = gtk_label_new("<b>Hello</b> <i>Hildon</i> (with Hildon"
"<sub>search</sub> <u>and</u> GnomeVFS "
"and other tricks<sup>tm</sup>)!");
/* Allow lines to wrap (NEW). */
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
/* Tell the GtkLabel widget to support the Pango markup (NEW). */
gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
/* Store the widget pointer into the application state so that the
contents can be replaced when a file will be loaded (NEW). */
aState.textLabel = label;
buildMenu(&aState);
vbox = gtk_vbox_new(FALSE, 0);
gtk_container_add(GTK_CONTAINER(aState.window), vbox);
gtk_box_pack_end(GTK_BOX(vbox), label, TRUE, TRUE, 0);
mainToolbar = buildToolbar(&aState);
findToolbar = buildFindToolbar(&aState);
aState.mainToolbar = mainToolbar;
aState.findToolbar = findToolbar;
/* Connect the termination signals. */
g_signal_connect(G_OBJECT(aState.window), "delete-event",
G_CALLBACK(cbEventDelete), &aState);
g_signal_connect(G_OBJECT(aState.window), "destroy",
G_CALLBACK(cbActionTopDestroy), &aState);
/* Show all widgets that are contained by the Window. */
gtk_widget_show_all(GTK_WIDGET(aState.window));
/* Add the toolbars to the Hildon Window. */
hildon_window_add_toolbar(HILDON_WINDOW(aState.window),
GTK_TOOLBAR(mainToolbar));
hildon_window_add_toolbar(HILDON_WINDOW(aState.window),
GTK_TOOLBAR(findToolbar));
/* Register a callback to handle key presses. */
g_signal_connect(G_OBJECT(aState.window), "key_press_event",
G_CALLBACK(cbKeyPressed), &aState);
g_print("main: calling gtk_main\n");
gtk_main();
g_print("main: returned from gtk_main and exiting with success\n");
return EXIT_SUCCESS;
}
In order to experiment with loading other content, a simple file can be created, containing Pango markup like this: echo "<b>Hello world</b>" > MyDocs/hello.txt , and then loading hello.txt.
As can be imagined, these examples have only scratched the surface of GnomeVFS that is quite a rich library, and contains a broad API and a large amount of plug-ins. Many things have been completely avoided, such as directory content iteration, the asynchronous interface, callback signaling on directory content changes etc. Please see GnomeVFS API [57] for more information. The API also contains some mini tutorials on various GnomeVFS topics, so it is well worth the time spent reading. It will also show that GnomeVFS has been overloaded with functions, which are not even file operation related (such as ZeroConf and creating TCP/IP connections etc.).
GTK+ does not have to be used in order to use GnomeVFS. One such example program is Midnight Commander (a Norton Commander clone, but better), which is a menu-based "text" mode program. GnomeVFS uses GLib though, so if using GnomeVFS, one should think about using GLib as well, as it will be loaded anyway.
A maemo-specific library called libosso provides helpful wrappers for D-BUS communication. It also contains the required functionality for every maemo application. Applications must be initialized using this library. With it, applications can connect to listen to system hardware state messages, such as "battery low". The library is used also for application state saving and auto-save functionality. Section LibOSSO Library 6.9.2 of the chapter Application Development of Maemo Reference Manual provides a good introduction to libosso.
D-Bus (the D originally stood for "Desktop") is a relatively new interprocess communication (IPC) mechanism designed to be used as a unified middleware layer in free desktop environments. Some example projects, where D-Bus is used, are GNOME and Hildon. Compared to other middleware layers for IPC, D-Bus lacks many of the more refined (and complicated) features, and thus is faster and simpler.
D-Bus does not directly compete with low-level IPC mechanisms, such as sockets, shared memory or message queues. Each of these mechanisms have their uses, which normally do not overlap the ones in D-Bus. Instead, D-Bus aims to provide higher level functionality, such as:
The design of D-Bus benefits from the long experience of using other middleware IPC solutions in the desktop arena, and this has allowed the design to be optimized. Also, it does not yet suffer from "creeping featurism", e.g. having extra features just to satisfy niche use cases.
All this said, the main problem area that D-Bus aims to solve is facilitating easy IPC between related (often graphical) desktop software applications.
D-Bus has a very important role in maemo, as it is the IPC mechanism to use when using the services provided in the platform (and devices). Providing services over D-Bus is also the easiest way to assure component re-use from other applications.
In D-Bus, the bus is a central concept. It is the channel through which applications can make the method calls, send signals and listen to signals. There are two pre-defined buses: the session bus and the system bus.
Normally only one system bus will exist, but there might be several session buses (one per each desktop session). Since in Internet Tablets all user applications will run with the same user id (user), there will only be one session bus as well.
A bus exists in the system in the form of a bus daemon, a process that specializes in passing messages from one process to another. The daemon will also forward notifications to all applications on the bus. At the lowest level, D-Bus only supports point-to-point communication, normally using the local domain sockets (AF_UNIX) between the application and the bus daemon. The point-to-point aspect of D-Bus is however abstracted by the bus daemon, which will implement addressing and message passing functionality, so that applications do not need to care about which specific process will receive each method call or notification.
The above means that sending a message using D-Bus will always involve the following steps (under normal conditions):
Coupled together, the above rules mean that if planning to transfer large amounts of data between processes, D-Bus will not be the most efficient way to do it. The most efficient way would be using some kind of shared memory arrangement. However, it is often quite complex to implement correctly.
In order for the messages to reach the intended recipient, the IPC mechanism needs to support some form of addressing. The addressing scheme in D-Bus has been designed to be flexible, but at the same time efficient. Each bus has its private name space, which is not directly related to any other bus.
In order to send a message, a destination address is needed. It is formed in a hierarchical manner from the following elements:
That about covers the most important rules in D-Bus addresses that one is likely to encounter. Below is an example of all four components that will also be used shortly to send a simple message (a method call) in the SDK:
#define SYSNOTE_NAME "org.freedesktop.Notifications" #define SYSNOTE_OPATH "/org/freedesktop/Notifications" #define SYSNOTE_IFACE "org.freedesktop.Notifications" #define SYSNOTE_NOTE "SystemNoteDialog"
Even if switching to use the LibOSSO RPC functions (which encapsulate a lot of the D-Bus machinery), operations will still be performed with all of the D-Bus naming components.
D-Bus has been selected as de facto IPC mechanism in maemo, to carry messages between the various software components. The main reason for this is that a lot of software developed for the GNOME environment is already exposing its functionality through D-Bus. Using a generic interface, which is not bound to any specific service, makes it also easier to deal with different software license requirements.
The SDK unfortunately does not come with a lot of software that is exposed via D-Bus, but this document will be using one component of the application framework as demonstration (it works also in the SDK).
An item of particular interest is asking the notification framework component to display a Note dialog. The dialog is modal, which means that users cannot proceed in their graphical environment, unless they first acknowledge the dialog. Normally such GUI decisions should be avoided, but later in this document it will be discussed why and when this feature can be useful. N.B. The SystemNoteDialog member is an extension to the draft org.freedesktop.Notifications specification, and as such, is not documented in that draft.
The notification server is listening for method calls on the
org.freedesktop.Notifications well-known name. The object that implements the necessary interface is located at /org/freedesktop/Notifications object path. The method to display the note dialog is called SystemNoteDialog, and is defined in the org.freedesktop.Notifications D-Bus interface.
D-Bus comes with a handy tool to experiment with method calls and signals: dbus-send. The following snippet will attempt to use it to display the dialog:
[sbox-DIABLO_X86: ~] > run-standalone.sh dbus-send --print-reply \ --type=method_call --dest=org.freedesktop.Notifications \ /org/freedesktop/Notifications org.freedesktop.Notifications Error org.freedesktop.DBus.Error.UnknownMethod: Method "Notifications" with signature "" on interface "org.freedesktop" doesn't exist
Parameters for dbus-send:
When using dbus-send, extra care needs to be taken, when specifying the interface and member names. The tool expects both of them to be combined into one parameter (without spaces in between). Thus, the command line needs to be modified a bit before a new try:
[sbox-DIABLO_X86: ~] > run-standalone.sh dbus-send --print-reply \ --type=method_call --dest=org.freedesktop.Notifications \ /org/freedesktop/Notifications org.freedesktop.Notifications.SystemNoteDialog Error org.freedesktop.DBus.Error.UnknownMethod: Method "SystemNoteDialog" with signature "" on interface "org.freedesktop.Notifications" doesn't exist
Seems that the RPC call is still missing something. Most RPC methods will expect a series of parameters (or arguments, as D-Bus calls them).
SystemNoteDialog expects these three parameters (in the following order):
Arguments are specified by giving the argument type and its contents separated with a colon as follows:
[sbox-DIABLO_X86: ~] > run-standalone.sh dbus-send --print-reply \ --type=method_call --dest=org.freedesktop.Notifications \ /org/freedesktop/Notifications org.freedesktop.Notifications.SystemNoteDialog \ string:'Hello, world!' uint32:0 string:'NAO OK!' method return sender=:1.1 -> dest=:1.15 uint32 4
Since dbus-send was asked to print replies, the reply will come out as a single unsigned integer, with value of 4. This is the unique number for this notification, and could be used with the CloseNotification method of the Notifications interface to pre-emptively close the dialog. It might be especially useful, if the software can notice that some warning condition has ended, and there is no need to bother the user with the warning anymore.
Assuming that the above command is run while the application framework is already running, the end result should more or less look like this:
If the command is repeated multiple times, one will notice that the notification service is capable of displaying only one dialog at a time. This makes sense, as the dialog is modal anyway. It can also be noticed that the method calls are queued somewhere, and not lost (i.e. the notification service will display all of the requested dialogs). The service also acknowledges the RPC method call without delay (which is not always the obvious thing to do), giving a different return value each time (incrementing by one each time).
The lowest level library to use for D-Bus programming is libdbus. Using this library directly is discouraged, mostly because it contains a lot of specific code to integrate into various main-loop designs that the higher level language bindings use.
The libdbus API reference documentation [57] contains a helpful note:
=== libdbus-example/dbus-example.c === /** * Uses the low-level libdbus which shouldn't be used directly. * As the D-Bus API reference puts it "If you use this low-level API * directly, you're signing up for some pain". */ === libdbus-example/dbus-example.c ===
At this point, this example will ignore the warnings, and use the library to implement a simple program that will replicate the dbus-send example that was seen before. In order to do this with the minimum amount of code, the code will not process (or expect) any responses to the method call. It will, however, demonstrate the bare minimum function calls that are needed to use to send messages on the bus.
The first step is to introduce the necessary header files.
=== libdbus-example/dbus-example.c === #include <dbus/dbus.h> /* Pull in all of D-Bus headers. */ #include <stdio.h> /* printf, fprintf, stderr */ #include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */ #include <assert.h> /* assert */ /* Symbolic defines for the D-Bus well-known name, interface, object path and method name that we're going to use. */ #define SYSNOTE_NAME "org.freedesktop.Notifications" #define SYSNOTE_OPATH "/org/freedesktop/Notifications" #define SYSNOTE_IFACE "org.freedesktop.Notifications" #define SYSNOTE_NOTE "SystemNoteDialog" === libdbus-example/dbus-example.c ===
Unlike the rest of the code in this material, dbus-example does not use GLib or other support libraries (other than libdbus). This explains why it uses printf and other functions that would normally be replaced with GLib equivalents.
Connecting to the session bus will (hopefully) yield a DBusConnection structure:
=== libdbus-example/dbus-example.c ===
/**
* The main program that demonstrates a simple "fire & forget" RPC
* method invocation.
*/
int main(int argc, char** argv) {
/* Structure representing the connection to a bus. */
DBusConnection* bus = NULL;
/* The method call message. */
DBusMessage* msg = NULL;
/* D-Bus will report problems and exceptions using the DBusError
structure. We'll allocate one in stack (so that we don't need to
free it explicitly. */
DBusError error;
/* Message to display. */
const char* dispMsg = "Hello World!";
/* Text to use for the acknowledgement button. "" means default. */
const char* buttonText = "";
/* Type of icon to use in the dialog (1 = OSSO_GN_ERROR). We could
have just used the symbolic version here as well, but that would
have required pulling the LibOSSO-header files. And this example
must work without LibOSSO, so this is why a number is used. */
int iconType = 1;
/* Clean the error state. */
dbus_error_init(&error);
printf("Connecting to Session D-Bus\n");
bus = dbus_bus_get(DBUS_BUS_SESSION, &error);
terminateOnError("Failed to open Session bus\n", &error);
assert(bus != NULL);
=== libdbus-example/dbus-example.c ===
N.B. Libdbus will attempt to share existing connection structures when the same process is connecting to the same bus. This is done to avoid the somewhat costly connection set-up time. Sharing connections is beneficial, when the program is using libraries that would also open their own connections to the same buses.
In order to communicate errors, libdbus uses DBusError structures, whose contents are pretty simple. The dbus_error_init will be used to guarantee that the error structure contains a non-error state before connecting to the bus. If there is an error, it will be handled in terminateOnError:
=== libdbus-example/dbus-example.c ===
/**
* Utility to terminate if given DBusError is set.
* Will print out the message and error before terminating.
*
* If error is not set, will do nothing.
*
* NOTE: In real applications you should spend a moment or two
* thinking about the exit-paths from your application and
* whether you need to close/unreference all resources that you
* have allocated. In this program, we rely on the kernel to do
* all necessary cleanup (closing sockets, releasing memory),
* but in real life you need to be more careful.
*
* One possible solution model to this is implemented in
* "flashlight", a simple program that is presented later.
*/
static void terminateOnError(const char* msg,
const DBusError* error) {
assert(msg != NULL);
assert(error != NULL);
if (dbus_error_is_set(error)) {
fprintf(stderr, msg);
fprintf(stderr, "DBusError.name: %s\n", error->name);
fprintf(stderr, "DBusError.message: %s\n", error->message);
/* If the program wouldn't exit because of the error, freeing the
DBusError needs to be done (with dbus_error_free(error)).
NOTE:
dbus_error_free(error) would only free the error if it was
set, so it is safe to use even when you're unsure. */
exit(EXIT_FAILURE);
}
}
=== libdbus-example/dbus-example.c ===
libdbus also contains some utility functions, so that everything does not have to be coded manually. One such utility is dbus_bus_name_has_owner that checks, whether there is at least some process that owns the given well-known name at that moment:
=== libdbus-example/dbus-example.c ===
/* Normally one would just do the RPC call immediately without
checking for name existence first. However, sometimes it's useful
to check whether a specific name even exists on a platform on
which you're planning to use D-Bus.
In our case it acts as a reminder to run this program using the
run-standalone.sh script when running in the SDK.
The existence check is not necessary if the recipient is
startable/activateable by D-Bus. In that case, if the recipient
is not already running, the D-Bus daemon will start the
recipient (a process that has been registered for that
well-known name) and then passes the message to it. This
automatic starting mechanism will avoid the race condition
discussed below and also makes sure that only one instance of
the service is running at any given time. */
printf("Checking whether the target name exists ("
SYSNOTE_NAME ")\n");
if (!dbus_bus_name_has_owner(bus, SYSNOTE_NAME, &error)) {
fprintf(stderr, "Name has no owner on the bus!\n");
return EXIT_FAILURE;
}
terminateOnError("Failed to check for name ownership\n", &error);
/* Someone on the Session bus owns the name. So we can proceed in
relative safety. There is a chance of a race. If the name owner
decides to drop out from the bus just after we check that it is
owned, our RPC call (below) will fail anyway. */
=== libdbus-example/dbus-example.c ===
Creating a method call using libdbus is slightly more tedious than using the higher-level interfaces, but not very difficult. The process is separated into two steps: creating a message structure, and appending the arguments to the message:
=== libdbus-example/dbus-example.c ===
/* Construct a DBusMessage that represents a method call.
Parameters will be added later. The internal type of the message
will be DBUS_MESSAGE_TYPE_METHOD_CALL. */
printf("Creating a message object\n");
msg = dbus_message_new_method_call(SYSNOTE_NAME, /* destination */
SYSNOTE_OPATH, /* obj. path */
SYSNOTE_IFACE, /* interface */
SYSNOTE_NOTE); /* method str */
if (msg == NULL) {
fprintf(stderr, "Ran out of memory when creating a message\n");
exit(EXIT_FAILURE);
}
/*... Listing cut for brevity ...*/
/* Add the arguments to the message. For the Note dialog, we need
three arguments:
arg0: (STRING) "message to display, in UTF-8"
arg1: (UINT32) type of dialog to display. We will use 1.
(libosso.h/OSSO_GN_ERROR).
arg2: (STRING) "text to use for the ack button". "" means
default text (OK in our case).
When listing the arguments, the type needs to be specified first
(by using the libdbus constants) and then a pointer to the
argument content needs to be given.
NOTE: It is always a pointer to the argument value, not the value
itself!
We terminate the list with DBUS_TYPE_INVALID. */
printf("Appending arguments to the message\n");
if (!dbus_message_append_args(msg,
DBUS_TYPE_STRING, &dispMsg,
DBUS_TYPE_UINT32, &iconType,
DBUS_TYPE_STRING, &buttonText,
DBUS_TYPE_INVALID)) {
fprintf(stderr, "Ran out of memory while constructing args\n");
exit(EXIT_FAILURE);
}
=== libdbus-example/dbus-example.c ===
When arguments are appended to the message, their content is copied, and possibly converted into a format that will be sent over the connection to the daemon. This process is called marshaling, and is a common feature to most RPC systems. The method call will require two parameters (as before), the first being the text to display, and the second one being the style of the icon to use. Parameters passed to libdbus are always passed by address. This is different from the higher level libraries, and this will be discussed later.
The arguments are encoded, so that their type code is followed by the pointer where the marshaling functions can find the content. The argument list is terminated with DBUS_TYPE_INVALID, so that the function knows where the argument list ends (since the function prototype ends with an ellipsis, ...).
=== libdbus-example/dbus-example.c ===
/* Set the "no-reply-wanted" flag into the message. This also means
that we cannot reliably know whether the message was delivered or
not, but since we don't have reply message handling here, it
doesn't matter. The "no-reply" is a potential flag for the remote
end so that they know that they don't need to respond to us.
If the no-reply flag is set, the D-Bus daemon makes sure that the
possible reply is discarded and not sent to us. */
dbus_message_set_no_reply(msg, TRUE);
=== libdbus-example/dbus-example.c ===
Setting the no-reply-flag effectively tells the bus daemon that even if there is a reply coming back for this RPC method, it is not wanted. In this case, the daemon will not send one.
Once the message is fully constructed, it can be added to the sending queue of the program. Messages are not sent immediately by libdbus. Normally this allows the message queue to accumulate to more than one message, and all of the messages will be sent at once to the daemon. This in turn cuts down the number of context switches necessary. In this case, this will be the only message that the program ever sends, so the send queue is instructed to be flushed immediately, and this will instruct the library to send all messages to the daemon without a delay:
=== libdbus-example/dbus-example.c ===
printf("Adding message to client's send-queue\n");
/* We could also get a serial number (dbus_uint32_t) for the message
so that we could correlate responses to sent messages later. In
our case there won't be a response anyway, so we don't care about
the serial, so we pass a NULL as the last parameter. */
if (!dbus_connection_send(bus, msg, NULL)) {
fprintf(stderr, "Ran out of memory while queueing message\n");
exit(EXIT_FAILURE);
}
printf("Waiting for send-queue to be sent out\n");
dbus_connection_flush(bus);
printf("Queue is now empty\n");
=== libdbus-example/dbus-example.c ===
After the message is sent, the reserved resources should be freed. Here, the first one to be freed is the message, and then the connection structure.
=== libdbus-example/dbus-example.c ===
printf("Cleaning up\n");
/* Free up the allocated message. Most D-Bus objects have internal
reference count and sharing possibility, so _unref() functions
are quite common. */
dbus_message_unref(msg);
msg = NULL;
/* Free-up the connection. libdbus attempts to share existing
connections for the same client, so instead of closing down a
connection object, it is unreferenced. The D-Bus library will
keep an internal reference to each shared connection, to
prevent accidental closing of shared connections before the
library is finalized. */
dbus_connection_unref(bus);
bus = NULL;
printf("Quitting (success)\n");
return EXIT_SUCCESS;
}
=== libdbus-example/dbus-example.c ===
After building the program, attempt to run it:
[sbox-DIABLO_X86: ~/libdbus-example] > ./dbus-example Connecting to Session D-Bus process 6120: D-Bus library appears to be incorrectly set up; failed to read machine uuid: Failed to open "/var/lib/dbus/machine-id": No such file or directory See the manual page for dbus-uuidgen to correct this issue. D-Bus not built with -rdynamic so unable to print a backtrace Aborted (core dumped)
The D-Bus library needs environmental variables set correctly in order to locate the session daemon. The command was not prepended with run-standalone.sh, and this caused the library to internally abort the execution. Normally, dbus_bus_get would have returned a NULL pointer and set the error structure, but the version on the 4.1 SDK will assert internally in this condition, and programs cannot avoid the abort. After correcting this, try again:
[sbox-DIABLO_X86: ~/libdbus-example] > run-standalone.sh ./dbus-example Connecting to Session D-Bus Checking whether the target name exists (org.freedesktop.Notifications) Creating a message object Appending arguments to the message Adding message to client's send-queue Waiting for send-queue to be sent out Queue is now empty Cleaning up Quitting (success) /dev/dsp: No such file or directory
The error message (about /dev/dsp) printed to the same terminal where AF was started is normal (in SDK). Displaying the Note dialog normally also causes an "Alert" sound to be played. The sound system has not been setup in the SDK, so the notification component complains about failing to open the sound device.
In order to get libdbus integrated into makefiles, pkg-config has to
be used. One possible solution is presented below (see section
GNU Make and Makefiles 4.2 in
chapter GNU Build System, if necessary):
=== libdbus-example/Makefile === # Define a list of pkg-config packages we want to use pkg_packages := dbus-glib-1 PKG_CFLAGS := $(shell pkg-config --cflags $(pkg_packages)) PKG_LDFLAGS := $(shell pkg-config --libs $(pkg_packages)) # Additional flags for the compiler: # -g : Add debugging symbols # -Wall : Enable most gcc warnings ADD_CFLAGS := -g -Wall # Combine user supplied, additional, and pkg-config flags CFLAGS := $(PKG_CFLAGS) $(ADD_CFLAGS) $(CFLAGS) LDFLAGS := $(PKG_LDFLAGS) $(LDFLAGS) === libdbus-example/Makefile ===
The above shows one possibility to integrate user-supplied variables into makefiles, so that they will still be passed along the toolchain. This allows the user to execute make with custom flags, overriding those that are introduced via other means. For example: "CFLAGS='-g0' make" would result in -g0 being interpreted after the -g that is in the Makefile, and this would lead to debugging symbols being disabled. Environmental variables can be taken into account in exactly the same way.
For more complicated programs, it is likely that multiple different CFLAGS settings are required for different object files or multiple different programs that are being built. In that case, the combining in each target rule would be performed separately. In this material, all the example programs are self-contained and rather simple, so the above mechanism will be used in all the example makefiles.
Besides the protection and isolation services, LibOSSO also provides useful utility functions to handle auto saving and state saving features of the platform, process hardware state and device mode changes, and other important events happening in Internet Tablets. It also provides convenient utility wrapper functions to send RPC method calls over the D-Bus. The feature set is aimed at covering the most common GUI application needs, and as such, will not be enough in all cases. In these cases, it will be necessary to use the GLib/D-Bus wrapper functions (or libdbus directly, which is not recommended).
The starting point here will be the header section of the example program:
=== libosso-example-sync/libosso-rpc-sync.c === #include <libosso.h> /*... Listing cut for brevity ...*/ #define SYSNOTE_NAME "org.freedesktop.Notifications" #define SYSNOTE_OPATH "/org/freedesktop/Notifications" #define SYSNOTE_IFACE "org.freedesktop.Notifications" #define SYSNOTE_NOTE "SystemNoteDialog" === libosso-example-sync/libosso-rpc-sync.c ===
LibOSSO by itself only requires the libosso.h header file to be included. The example will also use the exact same D-Bus well-known name, object path, interface name and method name as before.
When reading other source code that implements or uses D-Bus services, one might sometimes wonder, why the D-Bus interface name is using the same symbolic constant as the well-known name (in the above example SYSNOTE_IFACE would be omitted, and SYSNOTE_NAME would be used whenever an interface name would be required). If the service in question is not easily reusable or re-implementable, it might make sense to use an interface name that is as unique as the well-known name. This goes against the idea of defining interfaces, but is still quite common, and is the easy way out without bothering with difficult design decisions.
The following will take a look at how LibOSSO contexts are created, and how they are eventually released:
=== libosso-example-sync/libosso-rpc-sync.c ===
int main(int argc, char** argv) {
/* The LibOSSO context that we need to do RPC. */
osso_context_t* ossoContext = NULL;
g_print("Initializing LibOSSO\n");
/* The program name for registration is communicated from the
Makefile via a -D preprocessor directive. Since it doesn't
contain any dots in it, a prefix of "com.nokia." will be added
to it internally within osso_initialize(). */
ossoContext = osso_initialize(ProgName, "1.0", FALSE, NULL);
if (ossoContext == NULL) {
g_error("Failed to initialize LibOSSO\n");
}
g_print("Invoking the method call\n");
runRPC(ossoContext);
g_print("Shutting down LibOSSO\n");
/* Deinitialize LibOSSO. The function doesn't return status code so
we cannot know whether it succeeded or failed. We assume that it
always succeeds. */
osso_deinitialize(ossoContext);
ossoContext = NULL;
g_print("Quitting\n");
return EXIT_SUCCESS;
}
=== libosso-example-sync/libosso-rpc-sync.c ===
A LibOSSO context is a small structure, containing the necessary information for the LibOSSO functions to communicate over D-Bus (both session and system buses). When a context is created, the "application name" needs to be passed to osso_initialize. This name is used to register a name on the D-Bus, and this will keep the task killer from killing the process later on (assuming the application was started via the Task navigator). If the application name does not contain any dot characters in it, com.nokia. will be prepended to it automatically in LibOSSO. The application name is normally not visible to users, so this should not be a big problem. Application name collisions might be encountered, if some other application uses the same name (even without the dots), so it might be a good idea to provide a proper name based on a DNS domain you own or control. If planning to implement a service to clients over the D-Bus (with osso_rpc_set_cb-functions), it is necessary to be extra careful about the application name used here.
The version number is currently still unused, but 1.0 is recommended for the time being. The second to last parameter is obsolete, and has no effect, while the last parameter tells LibOSSO, which mainloop structure to integrate into. Using NULL here means that LibOSSO event processing will integrate into the default GMainLoop object created, as is most often desired.
Releasing the LibOSSO context will automatically close the connections to the D-Bus buses, and release all the allocated memory related to the connections and LibOSSO state. When using LibOSSO functions after this, a context will have to be reinitialized.
The following snippet shows the RPC call using LibOSSO, and also contains code suitable for dealing with possible errors in the launch, as well as the result of the RPC. The ossoErrorStr function is covered shortly, as is the utility function to print out the result structure.
=== libosso-example-sync/libosso-rpc-sync.c ===
/**
* Do the RPC call.
*
* Note that this function will block until the method call either
* succeeds, or fails. If the method call would take a long time to
* run, this would block the GUI of the program (which we don't have).
*
* Needs the LibOSSO state to do the launch.
*/
static void runRPC(osso_context_t* ctx) {
/* Message to display. */
const char* dispMsg = "Hello SystemNote!\nVia LibOSSO/sync.";
/* Icon type to use. */
gint iconType = OSSO_GN_ERROR;
/* Button label text to use, "" means leaving the defaults. */
const char* labelText = "";
/* Will hold the result from the RPC invocation function. */
osso_return_t result;
/* Will hold the result of the method call (or error). */
osso_rpc_t methodResult = {};
g_print("runRPC called\n");
g_assert(ctx != NULL);
/* Compared to the libdbus functions, LibOSSO provides conveniently
a function that will do the dispatch and also allows us to pass
the arguments all with one call.
The arguments for the "SystemNoteDialog" are the same as in
dbus-example.c (since it is the same service). You might also
notice that even if LibOSSO provides some convenience, it does
not completely isolate us from libdbus. We still supply the
argument types using D-Bus constants.
NOTE Do not pass the argument values by pointers as with libdbus,
instead pass them by value (as below). */
result = osso_rpc_run(ctx,
SYSNOTE_NAME, /* well-known name */
SYSNOTE_OPATH, /* object path */
SYSNOTE_IFACE, /* interface */
SYSNOTE_NOTE, /* method name */
&methodResult, /* method return value */
/* The arguments for the RPC. The types
are unchanged, but instead of passing
them via pointers, they're passed by
"value" instead. */
DBUS_TYPE_STRING, dispMsg,
DBUS_TYPE_UINT32, iconType,
DBUS_TYPE_STRING, labelText,
DBUS_TYPE_INVALID);
/* Check whether launching the RPC succeeded. */
if (result != OSSO_OK) {
g_error("Error launching the RPC (%s)\n",
ossoErrorStr(result));
/* We also terminate right away since there's nothing to do. */
}
g_print("RPC launched successfully\n");
/* Now decode the return data from the method call.
NOTE: If there is an error during RPC delivery, the return value
will be a string. It is not possible to differentiate that
condition from an RPC call that returns a string.
If a method returns "void", the type-field in the methodResult
will be set to DBUS_TYPE_INVALID. This is not an error. */
g_print("Method returns: ");
printOssoValue(&methodResult);
g_print("\n");
g_print("runRPC ending\n");
}
=== libosso-example-sync/libosso-rpc-sync.c ===
It is important to note that osso_rpc_run is a synchronous (blocking) call, which will wait for either the response from the method call, a timeout or an error. In this case, the method will be handled quickly, so it is not a big problem, but in many cases the methods will take some time to execute (and might require loading external resources), so this should be kept in mind. Asynchronous LibOSSO RPC functions will be covered shortly.
If the method call will return more than one return value (this is possible in D-Bus), LibOSSO currently does not provide a mechanism to return all of them (it will return the first value only).
Decoding the result code from the LibOSSO RPC functions is pretty straightforward, and is done in a separate utility:
=== libosso-example-sync/libosso-rpc-sync.c ===
/**
* Utility to return a pointer to a statically allocated string giving
* the textural representation of LibOSSO errors. Has no internal
* state (safe to use from threads).
*
* LibOSSO does not come with a function for this, so we define one
* ourselves.
*/
static const gchar* ossoErrorStr(osso_return_t errCode) {
switch (errCode) {
case OSSO_OK:
return "No error (OSSO_OK)";
case OSSO_ERROR:
return "Some kind of error occurred (OSSO_ERROR)";
case OSSO_INVALID:
return "At least one parameter is invalid (OSSO_INVALID)";
case OSSO_RPC_ERROR:
return "Osso RPC method returned an error (OSSO_RPC_ERROR)";
case OSSO_ERROR_NAME:
return "(undocumented error) (OSSO_ERROR_NAME)";
case OSSO_ERROR_NO_STATE:
return "No state file found to read (OSSO_ERROR_NO_STATE)";
case OSSO_ERROR_STATE_SIZE:
return "Size of state file unexpected (OSSO_ERROR_STATE_SIZE)";
default:
return "Unknown/Undefined";
}
}
=== libosso-example-sync/libosso-rpc-sync.c ===
Decoding the RPC return value is, however, slightly more complex, as the return value is a structure containing a typed union (type is encoded in the type field of the structure):
=== libosso-example-sync/libosso-rpc-sync.c ===
/**
* Utility to print out the type and content of given osso_rpc_t.
* It also demonstrates the types available when using LibOSSO for
* the RPC. Most simple types are available, but arrays are not
* (unfortunately).
*/
static void printOssoValue(const osso_rpc_t* val) {
g_assert(val != NULL);
switch (val->type) {
case DBUS_TYPE_BOOLEAN:
g_print("boolean:%s", (val->value.b == TRUE)?"TRUE":"FALSE");
break;
case DBUS_TYPE_DOUBLE:
g_print("double:%.3f", val->value.d);
break;
case DBUS_TYPE_INT32:
g_print("int32:%d", val->value.i);
break;
case DBUS_TYPE_UINT32:
g_print("uint32:%u", val->value.u);
break;
case DBUS_TYPE_STRING:
g_print("string:'%s'", val->value.s);
break;
case DBUS_TYPE_INVALID:
g_print("invalid/void");
break;
default:
g_print("unknown(type=%d)", val->type);
break;
}
}
=== libosso-example-sync/libosso-rpc-sync.c ===
N.B. LibOSSO RPC functions do not support array parameters either, so there is a restriction: the used method calls can only have simple parameters.
Now the example will be built and run. The end result is the now-familiar Note dialog.
[sbox-DIABLO_X86: ~/libosso-example-sync] > run-standalone.sh ./libosso-rpc-sync Initializing LibOSSO Invoking the method call runRPC called /dev/dsp: No such file or directory RPC launched successfully Method returns: uint32:8 runRPC ending Shutting down LibOSSO Quitting
The only difference is the location of the audio device error message. It will now appear before runRPC returns, since runRPC waits for RPC completion. This kind of ordering should never be relied on, because the RPC execution could also be delayed (and the message might appear at a later location when trying this program).
One point of interest in the Makefile (see section GNU Make and Makefiles 4.2 in
chapter GNU Build System) is the mechanism by which the ProgName define is set. It is often useful to separate the program name related information outside the source code, so that the code fragment may then be re-used more easily. In this case, there is control over the application name that is used when LibOSSO is initialized from the Makefile.
=== libosso-example-sync/Makefile ===
# define a list of pkg-config packages we want to use
pkg_packages := glib-2.0 libosso
# ... Listing cut for brevity ...
libosso-rpc-sync: libosso-rpc-sync.c
$(CC) $(CFLAGS) -DProgName=\"LibOSSOExample\" \
$< -o $@ $(LDFLAGS)
=== libosso-example-sync/Makefile ===
Sometimes the method call will take long time to run (or one cannot be sure, whether it might take long time to run). In these cases, it is advisable to use the asynchronous RPC utility functions in LibOSSO, instead of the synchronous ones. The biggest difference is that the method call will be split into two parts: launching of the RPC, and handling its result in a callback function. The same limitations with respect to method parameter types and the number of return values still apply.
In order for the callback to use LibOSSO functions and control the mainloop object, it is necessary to create a small application state. The state will be passed to the callback, when necessary.
=== libosso-example-async/libosso-rpc-async.c ===
/**
* Small application state so that we can pass both LibOSSO context
* and the mainloop around to the callbacks.
*/
typedef struct {
/* A mainloop object that will "drive" our example. */
GMainLoop* mainloop;
/* The LibOSSO context which we use to do RPC. */
osso_context_t* ossoContext;
} ApplicationState;
=== libosso-example-async/libosso-rpc-async.c ===
The osso_rpc_async_run function is used to launch the method call, and it will normally return immediately. If it returns an error, it will be probably a client-side error (since the RPC method has not returned by then). The callback function to handle the RPC response will be registered with the function, as will the name-space related parameters and the method call arguments:
=== libosso-example-async/libosso-rpc-async.c ===
/**
* We launch the RPC call from within a timer callback in order to
* make sure that a mainloop object will be running when the RPC will
* return (to avoid a nasty race condition).
*
* So, in essence this is a one-shot timer callback.
*
* In order to launch the RPC, it will need to get a valid LibOSSO
* context (which is carried via the userData/application state
* parameter).
*/
static gboolean launchRPC(gpointer userData) {
ApplicationState* state = (ApplicationState*)userData;
/* Message to display. */
const char* dispMsg = "Hello SystemNote!\nVia LibOSSO/async.";
/* Icon type to use. */
gint iconType = OSSO_GN_ERROR;
/* Button label text to use. */
const char* labelText = "Execute!";
/* Will hold the result from the RPC launch call. */
osso_return_t result;
g_print("launchRPC called\n");
g_assert(state != NULL);
/*... Listing cut for brevity ...*/
/* The only difference compared to the synchronous version is the
addition of the callback function parameter, and the user-data
parameter for data that will be passed to the callback. */
result = osso_rpc_async_run(state->ossoContext,
SYSNOTE_NAME, /* well-known name */
SYSNOTE_OPATH, /* object path */
SYSNOTE_IFACE, /* interface */
SYSNOTE_NOTE, /* method name */
rpcCompletedCallback, /* async cb */
state, /* user-data for cb */
/* The arguments for the RPC. */
DBUS_TYPE_STRING, dispMsg,
DBUS_TYPE_UINT32, iconType,
DBUS_TYPE_STRING, labelText,
DBUS_TYPE_INVALID);
/* Check whether launching the RPC succeeded (we don't know the
result from the RPC itself). */
if (result != OSSO_OK) {
g_error("Error launching the RPC (%s)\n",
ossoErrorStr(result));
/* We also terminate right away since there's nothing to do. */
}
g_print("RPC launched successfully\n");
g_print("launchRPC ending\n");
/* We only want to be called once, so ask the caller to remove this
callback from the timer launch list by returning FALSE. */
return FALSE;
}
=== libosso-example-async/libosso-rpc-async.c ===
The return from the RPC method is handled by a simple callback function that will need to always use the same parameter prototype. It will receive the return value, as well as the interface and method names. The latter two are useful, as the same callback function can be used to handle returns from multiple different (and simultaneous) RPC method calls.
The return value structure is allocated by LibOSSO, and will be freed once the callback returns, so it does not need to be handled manually.
=== libosso-example-async/libosso-rpc-async.c ===
/**
* Will be called from LibOSSO when the RPC return data is available.
* Will print out the result, and return. Note that it must not free
* the value, since it does not own it.
*
* The prototype (for reference) must be osso_rpc_async_f().
*
* The parameters for the callback are the D-Bus interface and method
* names (note that object path and well-known name are NOT
* communicated). The idea is that you can then reuse the same
* callback to process completions from multiple simple RPC calls.
*/
static void rpcCompletedCallback(const gchar* interface,
const gchar* method,
osso_rpc_t* retVal,
gpointer userData) {
ApplicationState* state = (ApplicationState*)userData;
g_print("rpcCompletedCallback called\n");
g_assert(interface != NULL);
g_assert(method != NULL);
g_assert(retVal != NULL);
g_assert(state != NULL);
g_print(" interface: %s\n", interface);
g_print(" method: %s\n", method);
/* NOTE If there is an error in the RPC delivery, the return value
will be a string. This is unfortunate if your RPC call is
supposed to return a string as well, since it is not
possible to differentiate between the two cases.
If a method returns "void", the type-field in the retVal
will be set to DBUS_TYPE_INVALID (it's not an error). */
g_print(" result: ");
printOssoValue(retVal);
g_print("\n");
/* Tell the main loop to terminate. */
g_main_loop_quit(state->mainloop);
g_print("rpcCompletedCallback done\n");
}
=== libosso-example-async/libosso-rpc-async.c ===
In this case, receiving the response to the method call will cause the main program to be terminated.
The application set-up logic is covered next:
=== libosso-example-async/libosso-rpc-async.c ===
int main(int argc, char** argv) {
/* Keep the application state in main's stack. */
ApplicationState state = {};
/* Keeps the results from LibOSSO functions for decoding. */
osso_return_t result;
/* Default timeout for RPC calls in LibOSSO. */
gint rpcTimeout;
g_print("Initializing LibOSSO\n");
state.ossoContext = osso_initialize(ProgName, "1.0", FALSE, NULL);
if (state.ossoContext == NULL) {
g_error("Failed to initialize LibOSSO\n");
}
/* Print out the default timeout value (which we don't change, but
could, with osso_rpc_set_timeout()). */
result = osso_rpc_get_timeout(state.ossoContext, &rpcTimeout);
if (result != OSSO_OK) {
g_error("Error getting default RPC timeout (%s)\n",
ossoErrorStr(result));
}
/* Interestingly the timeout seems to be -1, but is something else
(by default). -1 probably then means that "no timeout has been
set". */
g_print("Default RPC timeout is %d (units)\n", rpcTimeout);
g_print("Creating a mainloop object\n");
/* Create a GMainLoop with default context and initial condition of
not running (FALSE). */
state.mainloop = g_main_loop_new(NULL, FALSE);
if (state.mainloop == NULL) {
g_error("Failed to create a GMainLoop\n");
}
g_print("Adding timeout to launch the RPC in one second\n");
/* This could be replaced by g_idle_add(cb, &state), in order to
guarantee that the RPC would be launched only after the mainloop
has started. We opt for a timeout here (for no particular
reason). */
g_timeout_add(1000, (GSourceFunc)launchRPC, &state);
g_print("Starting mainloop processing\n");
g_main_loop_run(state.mainloop);
g_print("Out of mainloop, shutting down LibOSSO\n");
/* Deinitialize LibOSSO. */
osso_deinitialize(state.ossoContext);
state.ossoContext = NULL;
/* Free GMainLoop as well. */
g_main_loop_unref(state.mainloop);
state.mainloop = NULL;
g_print("Quitting\n");
return EXIT_SUCCESS;
}
=== libosso-example-async/libosso-rpc-async.c ===
The code includes an example on how to query the method call timeout value as well; however, timeout values are left unchanged in the program.
The RPC method call is launched in a slightly unorthodox way, via a timeout call that will launch one second after the mainloop processing starts. One could just as easily use g_idle_add, as long as the launching itself is performed after the mainloop processing starts. Since the method return value callback will terminate the mainloop, the mainloop needs to be active at that point. The only way to guarantee this is to launch the RPC after the mainloop is active.
Testing the program yields few surprises (other than the default timeout value being -1):
[sbox-DIABLO_X86: ~/libosso-example-async] > run-standalone.sh ./libosso-rpc-async Initializing LibOSSO Default RPC timeout is -1 (units) Creating a mainloop object Adding timeout to launch the RPC in one second Starting mainloop processing launchRPC called RPC launched successfully launchRPC ending rpcCompletedCallback called interface: org.freedesktop.Notifications method: SystemNoteDialog result: uint32:10 rpcCompletedCallback done Out of mainloop, shutting down LibOSSO Quitting /dev/dsp: No such file or directory
There is another shift to be noticed in the audio device error string. It is now displayed after all other messages (similar to the libdbus example). It seems that the audio playback is started "long after" the dialog itself is displayed, or maybe the method returns before SystemNote starts the dialog display. Again, one should not rely on exact timing, when dealing with D-Bus remote method calls.
The label text was modified slightly, to test that non-default labels will work.
The Makefile for this example does not contain anything new or special.
Since Internet Tablets are mobile devices, it is to be expected that people use them (and the software) while on the move, and also on airplanes and other places that might restrict network connectivity. If the program uses network connectivity, or needs to adapt to the conditions in the device better, it is necessary to handle changes between the different devices states. The changes between the states are normally initiated by the user of the device (when boarding an aircraft for example).
In order to demonstrate the handling of the most important device state, the next example implements a small utility program that will combine various utility functions from LibOSSO, as well as handle the changes in the device state. The state that is of particular interest is the "flight" mode. This mode is initiated by the user by switching the device into "Offline" mode. The internal name for this state, however, is "flight". Curiously enough, there is also a mode that is internally called "offline".
The following application is a simple utility program that keeps the backlight of the device turned on by periodically asking the system to delay the automatic display dimming functionality. Normally the backlight is turned off after a short period of inactivity, although this setting can be changed by the user. It is the goal of the application then to request a postponement of this mechanism (by 60 seconds at a time). Here an internal timer frequency of 45 seconds is chosen, so that it can always extend the time by 60 seconds (and be sure that the opportunity is not missed due to using a lower frequency than the maximum).
The program will also track the device mode, and once the device enters the flight mode, the program will terminate. Should the program be started when the device is already in flight mode, the program will refuse to run.
Since the program has no GUI of its own, Note dialogs and the infoprint facility will be used to display status information to the user. The Note is used to remind the user that leaving the program running will exhaust the battery. The infoprints are used when the application terminates, or if it refuses to run.
Most of the work required will be contained in the application set-up logic, which allows reducing the code in main significantly:
=== libosso-flashlight/flashlight.c ===
/**
* Main program:
*
* 1) Setup application state
* 2) Start mainloop
* 3) Release application state & terminate
*/
int main(int argc, char** argv) {
/* We'll keep one application state in our program and allocate
space for it from the stack. */
ApplicationState state = {};
g_print(PROGNAME ":main Starting\n");
/* Attempt to setup the application state and if something goes
wrong, do cleanup here and exit. */
if (setupAppState(&state) == FALSE) {
g_print(PROGNAME ":main Setup failed, doing cleanup\n");
releaseAppState(&state);
g_print(PROGNAME ":main Terminating with failure\n");
return EXIT_FAILURE;
}
g_print(PROGNAME ":main Starting mainloop processing\n");
g_main_loop_run(state.mainloop);
g_print(PROGNAME ":main Out of main loop (shutting down)\n");
/* We come here when the application state has the running flag set
to FALSE and the device state changed callback has decided to
terminate the program. Display a message to the user about
termination next. */
displayExitMessage(&state, ProgName " exiting");
/* Release the state and exit with success. */
releaseAppState(&state);
g_print(PROGNAME ":main Quitting\n");
return EXIT_SUCCESS;
}
=== libosso-flashlight/flashlight.c ===
In order for the device state callbacks to force a quit of the application, the LibOSSO context needs to be passed to it. Also access to the mainloop object is needed, as well as utilizing a flag to tell when the timer should just quit (since timers cannot be removed externally in GLib).
=== libosso-flashlight/flashlight.c ===
/* Application state.
Contains the necessary state to control the application lifetime
and use LibOSSO. */
typedef struct {
/* The GMainLoop that will run our code. */
GMainLoop* mainloop;
/* LibOSSO context that is necessary to use LibOSSO functions. */
osso_context_t* ossoContext;
/* Flag to tell the timer that it should stop running. Also utilized
to tell the main program that the device is already in Flight-
mode and the program shouldn't continue startup. */
gboolean running;
} ApplicationState;
=== libosso-flashlight/flashlight.c ===
All of the setup and start logic is implemented in setupAppState, and contains a significant number of steps that are all necessary:
=== libosso-flashlight/flashlight.c ===
/**
* Utility to setup the application state.
*
* 1) Initialize LibOSSO (this will connect to D-Bus)
* 2) Create a mainloop object
* 3) Register the device state change callback
* The callback will be called once immediately on registration.
* The callback will reset the state->running to FALSE when the
* program needs to terminate so we'll know whether the program
* should run at all. If not, display an error dialog.
* (This is the case if the device will be in "Flight"-mode when
* the program starts.)
* 4) Register the timer callback (which will keep the screen from
* blanking).
* 5) Un-blank the screen.
* 6) Display a dialog to the user (on the background) warning about
* battery drain.
* 7) Send the first "delay backlight dimming" command.
*
* Returns TRUE when everything went ok, FALSE when caller should call
* releaseAppState and terminate. The code below will print out the
* errors if necessary.
*/
static gboolean setupAppState(ApplicationState* state) {
osso_return_t result;
g_assert(state != NULL);
g_print(PROGNAME ":setupAppState starting\n");
/* Zero out the state. Has the benefit of setting all pointers to
NULLs and all gbooleans to FALSE. This is useful when we'll need
to determine what to release later. */
memset(state, 0, sizeof(ApplicationState));
g_print(PROGNAME ":setupAppState Initializing LibOSSO\n");
/*... Listing cut for brevity ...*/
state->ossoContext = osso_initialize(ProgName, "1.0", FALSE, NULL);
if (state->ossoContext == NULL) {
g_printerr(PROGNAME ": Failed to initialize LibOSSO\n");
return FALSE;
}
g_print(PROGNAME ":setupAppState Creating a GMainLoop object\n");
/* Create a new GMainLoop object, with default context (NULL) and
initial "running"-state set to FALSE. */
state->mainloop = g_main_loop_new(NULL, FALSE);
if (state->mainloop == NULL) {
g_printerr(PROGNAME ": Failed to create a GMainLoop\n");
return FALSE;
}
g_print(PROGNAME
":setupAddState Adding hw-state change callback.\n");
/* The callback will be called immediately with the state, so we
need to know whether we're in offline mode to start with. If so,
the callback will set the running-member to FALSE (and we'll
check it below). */
state->running = TRUE;
/* In order to receive information about device state and changes
in it, we register our callback here.
Parameters for the osso_hw_set_event_cb():
osso_context_t* : LibOSSO context object to use.
osso_hw_state_t* : Pointer to a device state type that we're
interested in. NULL for "all states".
osso_hw_cb_f* : Function to call on state changes.
gpointer : User-data passed to callback. */
result = osso_hw_set_event_cb(state->ossoContext,
NULL, /* We're interested in all. */
deviceStateChanged,
state);
if (result != OSSO_OK) {
g_printerr(PROGNAME
":setupAppState Failed to get state change CB\n");
/* Since we cannot reliably know when to terminate later on
without state information, we will refuse to run because of the
error. */
return FALSE;
}
/* We're in "Flight" mode? */
if (state->running == FALSE) {
g_print(PROGNAME ":setupAppState In offline, not continuing.\n");
displayExitMessage(state, ProgName " not available in Offline mode");
return FALSE;
}
g_print(PROGNAME ":setupAppState Adding blanking delay timer.\n");
if (g_timeout_add(45000,
(GSourceFunc)delayBlankingCallback,
state) == 0) {
/* If g_timeout_add returns 0, it signifies an invalid event
source id. This means that adding the timer failed. */
g_printerr(PROGNAME ": Failed to create a new timer callback\n");
return FALSE;
}
/* Un-blank the display (will always succeed in the SDK). */
g_print(PROGNAME ":setupAppState Unblanking the display\n");
result = osso_display_state_on(state->ossoContext);
if (result != OSSO_OK) {
g_printerr(PROGNAME ": Failed in osso_display_state_on (%s)\n",
ossoErrorStr(result));
/* If the RPC call fails, odds are that nothing else will work
either, so we decide to quit instead. */
return FALSE;
}
/* Display a "Note"-dialog with a WARNING icon.
The Dialog is MODAL, so user cannot do anything with the stylus
until the Ok is selected, or the Back-key is pressed. */
/*... Listing cut for brevity ...*/
/* Other icons available:
OSSO_GN_NOTICE: For general notices.
OSSO_GN_WARNING: For warning messages.
OSSO_GN_ERROR: For error messages.
OSSO_GN_WAIT: For messages about "delaying" for something (an
hourglass icon is displayed).
5: Animated progress indicator. */
/*... Listing cut for brevity ...*/
g_print(PROGNAME ":setupAppState Displaying Note dialog\n");
result = osso_system_note_dialog(state->ossoContext,
/* UTF-8 text into the dialog */
"Started " ProgName ".\n"
"Please remember to stop it when you're done, "
"in order to conserve battery power.",
/* Icon to use */
OSSO_GN_WARNING,
/* We're not interested in the RPC
return value. */
NULL);
if (result != OSSO_OK) {
g_error(PROGNAME ": Error displaying Note dialog (%s)\n",
ossoErrorStr(result));
}
g_print(PROGNAME ":setupAppState Requested for the dialog\n");
/* Then delay the blanking timeout so that our timer callback has a
chance to run before that. */
delayDisplayBlanking(state);
g_print(PROGNAME ":setupAppState Completed\n");
/* State set up. */
return TRUE;
}
=== libosso-flashlight/flashlight.c ===
The callback to handle device state changes is registered with the
osso_hw_set_event_cb, and it can also be seen how to force the backlight on (which is necessary so that the backlight dimming delay will accomplish something). Also the timer callback will be registered, and it will then start firing away after 45 seconds, and will keep delaying the backlight dimming and perform the first delay so that the backlight is not dimmed right away.
The callback function will always receive the new "hardware state" as well as the user data. It is also somewhat interesting to note that just by registering the callback, it will be triggered immediately. This will happen even before starting the mainloop in order to tell the application the initial state of the device, when the application starts. This can be utilized to determine whether the device is already in flight mode, and keep from starting the program if that is the case. Since it cannot always be known whether the mainloop is active (the callback can be triggered later on as well), the program also utilizes an additional flag to communicate the timer callback that it should eventually quit.
=== libosso-flashlight/flashlight.c ===
/**
* This callback will be called by LibOSSO whenever the device state
* changes. It will also be called right after registration to inform
* the current device state.
*
* The device state structure contains flags telling about conditions
* that might affect applications, as well as the mode of the device
* (for example telling whether the device is in "in-flight"-mode).
*/
static void deviceStateChanged(osso_hw_state_t* hwState,
gpointer data) {
ApplicationState* state = (ApplicationState*)data;
g_print(PROGNAME ":deviceStateChanged Starting\n");
printDeviceState(hwState);
/* If device is in/going into "flight-mode" (called "Offline" on
some devices), we stop our operation automatically. Obviously
this makes flashlight useless (as an application) if someone gets
stuck in a dark cargo bay of a plane with snakes.. But we still
need a way to shut down the application and react to device
changes, and this is the easiest state to test with.
Note that since offline mode will terminate network connections,
you will need to test this on the device itself, not over ssh. */
if (hwState->sig_device_mode_ind == OSSO_DEVMODE_FLIGHT) {
g_print(PROGNAME ":deviceStateChanged In/going into offline.\n");
/* Terminate the mainloop.
NOTE: Since this callback is executed immediately on
registration, the mainloop object is not yet "running",
hence calling quit on it will be ineffective! _quit only
works when the mainloop is running. */
g_main_loop_quit(state->mainloop);
/* We also set the running to correct state to fix the above
problem. */
state->running = FALSE;
}
}
=== libosso-flashlight/flashlight.c ===
The printDeviceState is a utility function to decode the device state structure that the callback will be invoked with. The state contains the device mode, but also gboolean flags, which tell the application to adapt to the environment in other ways (like memory pressure and o