Table of Contents:
This guide describes creating an agent using the Centerbury legacy application framework. This framework is deprecated and therefore not recommended for new development.
An agent is a non graphical program which runs in the background. Agents can be used for playing music or computing expensive operations like indexing large databases, for example.
Following is a step-by-step guide for creating an agent. This guide is based on the agent sample application available in the Apertis repos. It will be handy to have the sample application code open while reading through this guide.
Our sample application is composed of two classes which will be explained in more detail below:
HlwAgent
: A wrapper class that manages the process itself and the D-Bus objectHlwTickBoard
: A child class that implements the D-Bus interface
Let’s get started!
Agents are GApplications
The GApplication
is the recommended base class for agents, just as it is for graphical applications.
One of the advantages of being derived from GApplication
is that most of the work of registering with D-Bus is handled by the class, so you need only override a few virtual functions in order to get a running agent.
The HlwAgent
class is the entry point for running the agent. This class is defined as:
G_DEFINE_TYPE (HlwAgent, hlw_agent, G_TYPE_APPLICATION)
Create and launch an HlwAgent
:
app = G_APPLICATION (g_object_new (HLW_TYPE_AGENT,
"application-id", "org.apertis.HelloWorld.Agent",
"flags", G_APPLICATION_IS_SERVICE,
NULL));
g_application_run (app, argc, argv);
The application_id
property of the GApplication is used to provide the D-Bus bus name. This id should be unique so it is recommend that you use @BUDNLE_ID@.Agent
. GApplicationFlags
should be set to G_APPLICATION_IS_SERVICE
. See the description of flags for more information.
In the HlwAgent
class, the dbus_register
and dbus_unregister
virtual functions should be overridden to catch whether the requested D-Bus name is available to use, andactivate
should be overridden as well, which is a mandatory virtual function for all applications and agents.
static void
hlw_agent_class_init (HlwAgentClass *klass)
{
GApplicationClass *app_class = G_APPLICATION_CLASS (klass);
...
app_class->activate = hlw_agent_activate;
app_class->dbus_register = hlw_agent_dbus_register;
app_class->dbus_unregister = hlw_agent_dbus_unregister;
...
}
To cause HlwAgent
to run in the background and prevent termination by the ending of the main function, the reference count should be held and released properly. In a graphical application, it’s clear that g_application_hold ()
should be called when the window appears and g_application_release ()
when the window disappears, but unlike a graphical program, it’s a bit less clear when to manage reference count in an agent. Fortunately, in the dbus_register
function, we can assume that the agent is ready to export extra objects on the bus, making it a good place to take the reference with g_application_hold ()
, and in dbus_unregister
we can release the reference count with g_application_release ()
.
static gboolean
hlw_agent_dbus_register (GApplication *app,
GDBusConnection *connection,
const gchar *object_path,
GError **error)
{
...
g_application_hold (app);
...
}
static void
hlw_agent_dbus_unregister (GApplication *app,
GDBusConnection *connection,
const gchar *object_path)
{
...
g_application_release (app);
...
}
Once the activate
virtual function is overridden, we will have a runnable agent skeleton.
D-Bus Interface
Using the GApplication
class allows our agent to run as stand-alone non-graphical program, but it isn’t yet able to interact with the outside world. It is recommended that D-Bus be used to communicate with other processes on the system. D-Bus code and documentation can be easily created by gdbus-codegen
. For more details of the usages of the generator, refer to gdbus-codegen.
D-Bus XML Schema
To make our agent application D-Bus aware, let’s introduce a simple XML D-Bus interface.
Our interface will contain the following:
- method: ToggleTick
- property: CurrentTick
By calling ToggleTick
, an auto-incremented tick count function is enabled or disabled. Once the function is enabled, the property, CurrentTick
, is increased by 1 every second.
<node name="/org/apertis/helloworld/agent/tickboard">
<interface name="org.apertis.HelloWorld.Agent.TickBoard">
<method name="ToggleTick"/>
<property name="CurrentTick" type="i" access="read"/>
</interface>
</node>
To generate the D-Bus code at make time, we introduce a simple rule to the Automake Makefile.am
.
helloworld-agent/%.c: helloworld-agent/%.xml Makefile
$(AM_V_GEN)$(GDBUS_CODEGEN) \
--c-namespace=HlwDBus \
--interface-prefix=org.apertis.HelloWorld.Agent \
--generate-c-code helloworld-agent/$* $<
Note that --interface-prefix
should be the same as the application-id
of the agent.
D-Bus function implementation
The D-Bus skeleton is generated by gdbus-codegen
according to the XML schema. Now we need to create actual behaviors for when the D-Bus APIs are called. In our example, the HlwDBusTickBoardSkeleton
object and the HlwDBusTickBoard
interface are created. Although there are various approaches to implementing this interface, being a child of the D-Bus skeleton object will help show how the generated virtual functions should be filled in.
G_DEFINE_TYPE_WITH_CODE (HlwTickBoard, hlw_tick_board,
HLW_DBUS_TYPE_TICK_BOARD_SKELETON,
G_IMPLEMENT_INTERFACE (HLW_DBUS_TYPE_TICK_BOARD,
hlw_tick_board_tick_board_iface_init))
Next, handle_toggle_tick
of HlwDbusTickBoardIface
should be overridden.
static gboolean
hlw_tick_board_handle_toggle_tick (HlwDBusTickBoard *object,
GDBusMethodInvocation *invocation)
{
...
hlw_dbus_tick_board_complete_toggle_tick (object, invocation);
return TRUE;
}
static void
hlw_tick_board_tick_board_iface_init (HlwDBusTickBoardIface *iface)
{
iface->handle_toggle_tick = hlw_tick_board_handle_toggle_tick;
}
Exporting the interface to D-Bus
Once the implementation of the D-Bus interface is completed, it needs to be exported on the bus. HlwAgent
is already registered on the bus so the HlwTickBoard
, which is a child object of the D-Bus skeleton, can be easily exported in the dbus_register
function.
static gboolean
hlw_agent_dbus_register (GApplication *app,
GDBusConnection *connection,
const gchar *object_path,
GError **error)
{
gboolean ret;
...
/* chain up */
ret = G_APPLICATION_CLASS (hlw_agent_parent_class)->dbus_register (
app,
connection,
object_path,
error);
if (ret &&
!g_dbus_interface_skeleton_export (
G_DBUS_INTERFACE_SKELETON (self->tick_board),
connection,
"/org/apertis/HelloWorld/Agent/TickBoard",
error))
{
g_warning ("Failed to export TickBoard D-Bus interface (reason: %s)",
(*error)->message);
}
return ret;
}
Note that it is recommended that an application registers as above before exporting any other interfaces to D-Bus.
Apparmor profile
An agent must be granted different Apparmor permissions than those granted to standard application bundles. Several additional rules are required to allow an agent to access the bus.
/Applications/@BUNDLE_ID@/** {
#include <abstractions/chaiwala-base>
#include <abstractions/dbus-session-strict>
/Applications/@BUNDLE_ID@/{bin,libexec}/* pix,
/Applications/@BUNDLE_ID@/{bin,lib,libexec}/{,**} mr,
/Applications/@BUNDLE_ID@/share/{,**} r,
owner /var/Applications/@BUNDLE_ID@/users/** rwk,
dbus send
bus=session
path=/org/freedesktop/DBus
interface=org.freedesktop.DBus
member="RequestName"
peer=(name=org.freedesktop.DBus),
dbus bind
bus=session
name="@BUNDLE_ID@",
dbus bind
bus=session
name="@BUNDLE_ID@.Agent",
dbus (send, receive)
bus=session
peer=(label=/Applications/@BUNDLE_ID@/**),
signal receive peer=/usr/bin/canterbury,
}