Xfce Foundation Classes
 « Main Page

The Main Event Loop

Table of Contents

  1. The GLIb Main Loop
  2. Main Contexts
  3. Event Sources
  4. Timeout Sources
  5. The Timeout Signal
  6. Idle Sources
  7. The Idle Signal
  8. Monitoring IO
  9. The IO Signal
  10. The GTK Main Loop
  11. Key Snooper Functions
  12. The Quit Signal
  13. Example Main Functions

The GLib Main Loop

The GLib main event loop is a low-level generic abstraction that manages all the available sources of events for GLib and GTK+ applications. These events can come from any number of different types of sources such as file descriptors (plain files, pipes or sockets) and timeouts.  New sources can be added using G::Source::attach(). GLib allows multiple independent sets of sources to be handled in different threads, each source is associated with a G::MainContext. A main context can only be running in a single thread, but sources can be added to it and removed from it, from other threads.

Each event source is assigned a priority. The default priority, G::PRIORITY_DEFAULT, has a value of 0. Values less than 0 denote higher priorities. Values greater than 0 denote lower priorities. Events from high priority sources are always processed before events from lower priority sources. Idle functions can also be added, and assigned a priority. These will be run whenever no events with a higher priority are ready to be processed.

A G::MainLoop represents a single invocation of GLib main event loop and can be created with one of the following constructors:

MainLoop();

MainLoop(G::MainContext& context);


The first constructor creates a main loop using the default G::MainContext. The second constructor creates a main loop using the specified G::MainContext.

After adding the initial event sources, run() is called to run the event loop:

void run();

Once run() is invoked the main loop will iterate until quit() is called:

void quit();

While the main loop is running, it continuously checks for new events from each of the event sources and dispatches them. If one of the events from one of the sources leads to a call to quit() to exit the main loop, run() returns.

It is possible to create new instances of G::MainLoop recursively. This is often used in GTK+ applications when showing modal dialog boxes. Note that event sources are associated with a particular G::MainContext, and will be checked and dispatched for all main loops associated with that context.

One of the unusual features of the GLib main loop functionality is that new types of event source can be created and used in addition to the built-in types of event source. A new source type is created by deriving it from G::CustomSource, but for most purposes the built-in event sources should suffice: G::ChildWatchSource, G::TimeoutSource, G::IdleSource and G::IOSource.

Main Contexts

A G::MainContext is an object that represents a set of event sources to be processed in a main loop. Different main contexts must be used for different threads but sources can be added to a main context from any thread.

A main context is created with the following constructor:

MainContext();

Applications that need only one context can use the default main context instead, by calling the following function:

Pointer<MainContext> get_default();

A single iteration of a main context can be run by calling:

bool iteration(bool may_block);

If 'may_block' is set to true and no event sources are ready to be processed, the context will wait for a source to become ready, dispatching the highest priority events sources that are ready. iteration() will return true if any events were dispatched.

If you need more control over exactly how the details of the main event loop work, for instance, when integrating G::MainLoop with an external main loop, you can call the component methods of iteration() directly. These methods are prepare(), query(), check() and dispatch(), and they must be called in that sequence. The sequence can be stopped at any time, and you can return to prepare(), but you must never skip a step or call one of the methods twice (except for query()). Whenever prepare() is invoked, any pending sources are cleared, and checking starts again from scratch.

Event Sources

A G::Source object corresponds to a single source of input to be processed by the main loop. GLib provides several built-in source types, represented by the following XFC classes: ChildWatchSource, TimeoutSource, IdleSource and IOSource. In most cases these built-in source types should be adequate. When they aren't, or when you need something more powerful, you can create a custom event source by subclassing G::CustomSource.

When a source is created it isn't associated with any main context but must be explicitly attached to a context by calling the G::Source attach method:

unsigned int attach(MainContext *context = 0);

If 'context' is null the source is attached to the default main context; otherwise it's attached to the specified main context. The return value is an integer that identifies the source within the context.

If a source is attached to a specific main context, G::Source::is_attached() returns true:

bool is_attached(G::MainContext& context) const;

Event sources processed in the main loop are dispatched according to their priority, which can be set and retrieved with the following two methods respectively:

void set_priority(int priority);

int get_priority() const;


The 'priority' can be one of the following values:
  • PRIORITY_HIGH - use this for high priority event sources; It is not used within GLib or GTK+ (value -100).
  • PRIORITY_DEFAULT - use this for default priority event sources; In GLib this priority is used when adding timeout functions (value 0).
  • PRIORITY_HIGH_IDLE - use this for high priority idle functions (value 100);
  • PRIORITY_DEFAULT_IDLE - use this for default priority idle functions; In GLib this priority is used when adding idle functions (value 200).
  • PRIORITY_LOW - use this for very low priority background tasks; It is not used within GLib or GTK+ (value 300).
Negative values have a higher priority than positive ones, with the most negative value having the highest the priority and the most positive value having the lowest priority. While the main loop is being run, a source will be dispatched if it is ready to be dispatched and no sources with a higher priority are ready to be dispatched.

Note, GTK+ uses PRIORITY_HIGH_IDLE + 10 for window resizes and PRIORITY_HIGH_IDLE + 20 for window redraws, to ensure that any pending resizes are processed before any pending redraws, so that widgets are not redrawn twice unnecessarily; you will interfere with GTK+ if you use a priority above PRIORITY_HIGH_IDLE + 10 (that is, GTK_PRIORITY_RESIZE).

Event sources can be called recursively, that is, while being dispatched a source wont be blocked until the dispatch returns, but instead will be processed normally. You can set and retrieve the 'can_recurse' property with the following two methods respectively:

void set_can_recurse(bool can_recurse);

bool get_can_recurse() const;


As well as calling ref() and unref() on a source, sources can be explicitly destroyed with the following method:

void destroy();

Calling destroy() removes a source from its main context, if any, and marks it as destroyed. After calling this method the source cannot be subsequently added to another context.

Timeout Sources

Adding a timeout source to a G::MainContext is a convenient way to you register a slot to be called at regular intervals, with a given priority. Once connected, the slot is called repeatedly until its function returns false, at which point the timeout is automatically destroyed and will not be called again. The first call to the slot will be at the end of the first interval.

A timeout source can be created with one of the following constructors:

TimeoutSource(unsigned int interval);

TimeoutSource(const SourceSlot& slot, unsigned int interval);


The both constructors create a timeout source that can be associated with any main context. The SlotType is a callback slot of type sigc::slot<bool>. The 'interval' is the time between calls to the callback slot, in milliseconds. G::Source::attach() must be called to attach the source to a context.
   
If you do not pass a slot to the timeout source constructor, you can connect a callback slot later by calling:

sigc::connection connect(const SourceSlot& slot);   

The Timeout Signal

For convenience, XFC provides a timeout signal that lets you connect a callback slot to the default main context, without having to create a timeout source.  The timeout signal is declared in the G:: namespace like this:

class TimeoutSignal : public sigc::trackable
{
public:
    typedef sigc::slot<bool> SlotType;

    sigc::connection connect(const SlotType& slot, unsigned int interval, int priority = PRIORITY_DEFAULT_IDLE);
};

extern TimeoutSignal timeout_signal;


The interval argument passed to connect() is the time in milliseconds that is to elapse between each call. Your slot will be called every 'interval' milliseconds until its function returns false, at which point the timeout connection is destroyed and will not be called again. The connect method returns a sigc::connection class. Unlike widget signals, you must keep a copy of this connection and specifically call sigc::connection::disconnect() when the timeout is no longer required.
 
Function slots that connect to the timeout_signal have the following prototype:

bool MyClass::signal_handler();

The code to set up a timeout connection should look something like this. In your class interface include a sigc::connection and a timeout signal handler.

class MyClass : public Gtk::Window
{
    sigc::connection timeout_connection;
 
protected:
    bool on_timeout();
 
public:
    MyClass();
    ~MyClass();
};

then in your class implementation connect your signal handler to the timeout signal in the constructor and disconnect it in the destructor :

MyClass::MyClass()
{
    ...
    timeout_connection = G::timeout_signal.connect(sigc::mem_fun(this, &MyClass::on_timeout), 150);
}
 
MyClass::~MyClass()
{
    timeout_connection.disconnect();
}
 
bool
MyClass::on_timeout()
 {
     // put your code here.
 
     // return true to continue calling this method; return false to end the timeout.
     return true;
 }

Note that timeout functions may be delayed, due to the processing of other event sources, so they should not be relied on for precise timing. After each call to your timeout slot, the time of the next timeout is recalculated based on the current time and the given interval (it does not try to 'catch up' time lost in delays).

Idle Sources

What if you have a method which you want called when nothing else is happening? That's what an IdleSource does. It's a source that is called whenever there are no higher priority events pending.

An idle source can be created with one of the following constructors:

IdleSource();

IdleSource(const SourceSlot& slot);

 
Both constructors create an idle source that can be associated with any main context. The SlotType is a callback slot of type sigc::slot<bool>. G::Source::attach() must be called to attach the new source to a context.

If you do not pass a slot to the idle source constructor, you can connect a callback slot later by calling:

sigc::connection connect(const SourceSlot& slot);

Once connected, the slot will continue to be called until it returns false, at which point it is automatically removed from the list of event sources and will not be called again.

The Idle Signal

For convenience, XFC provides an idle signal that lets you connect a callback slot to the default main context, without having to create an idle source. The idle signal is declared in the G:: namespace like this:

class IdleSignal : public sigc::trackable
{
public:
    typedef sigc::slot<bool> SlotType;

    sigc::connection connect(const SlotType& slot, int priority = PRIORITY_DEFAULT_IDLE);
};

extern IdleSignal idle_signal;


The SlotType is a callback slot of type sigc::slot<bool>. The connect method returns a sigc::connection class. Unlike widget signals, you must keep a copy of this connection and specifically call sigc::connection::disconnect() when idle processing is no longer required. If a connected slot returns false it is automatically removed from the list of event sources and will not be called again.

Function slots that connect to the idle_signal have the following prototype:

bool MyClass::signal_handler();

If your using a different context, for example, inside a thread, you should use an IdleSource object instead.

Monitoring IO

IOSource is an event source that's dispatched when a specific condition is met for the given file descriptor channel. For example, if the condition is G::IO_IN, the source will be dispatched when there's data available for reading.

An io source can be created with one of the following constructors:

IOSource(G::IOChannel& channel, G::IOConditionField condition);

IOSource(G::IOChannel& channel, G::IOConditionField condition, const IOSlot& slot);


Both constructors create an io source that can be associated with any main context. The IOSlot is a callback slot of type sigc::slot<bool, IOConditionField>. The 'condition' can be one or more of the following values from the G::IOCondition enum:
  • IO_IN - there is data to read.
  • IO_OUT - data can be written (without blocking).
  • IO_PRI - there is urgent data to read.
  • IO_ERR - an error condition.
  • IO_HUP - hung up (the connection has been broken, usually for pipes and sockets).
  • IO_NVAL - invalid request. The file descriptor is not open.
G::Source::attach() must be called to attach the new source to a context. If you do not pass a slot to the IOSource constructor, you can connect a callback slot later by calling:

sigc::connection connect(const IOSlot& slot);

Once connected, the slot will be called whenever the requested condition on the G::IOChannel is satisfied. If the slot returns false it is automatically removed from the list of event sources and will not be called again.

The IO Signal

For convenience, XFC provides an io signal that lets you connect a callback slot to the default main context, without having to create a IOSource.  The io signal is declared in the G:: namespace like this:

class IOSignal : public sigc::trackable
{
public:
    typedef sigc::slot<bool, IOConditionField> SlotType;

    sigc::connection connect(G::IOChannel& channel, G::IOConditionField condition, const SlotType& slot, int priority);
};

extern IOSignal io_signal;


The SlotType is a callback slot of type sigc::slot<bool, IOConditionField>. The 'condition' is the condition to watch for and the 'priority' is the priority of the io source. The connect method returns a sigc::connection class. Unlike widget signals, you must keep a copy of this connection and specifically call sigc::connection::disconnect() when channel monitoring is no longer required. If a connected slot returns false it is automatically removed from the list of event sources and will not be called again.

Function slots that connect to the io_signal have the following prototype:

bool MyClass::signal_handler(G::IOConditionField);

If your using a different context, for example, inside a thread, you should use an IOSource object instead.

The GTK Main Loop

The GTK+ main event loop is primarily implemented by GLib, which has a generic main loop abstraction. GTK+ attaches the GLib main loop to GDK's X server connection, and presents a convenient interface to the user. In XFC, the main loop and its related functions are declared in the 'Main::' namespace.

Before using GTK+, you need to initialize it; initialization connects to the window system display, and parses some standard command line arguments from argc and argv.

void init(int *argc, char ***argv);

bool init_check(int *argc, char ***argv);

The init() functions initialize everything needed to operate the GTK+ toolkit. You should call init() before using any other XFC methods in your GUI applications. If errors occur, init() will exit your application. To avoid this, you can use init_check() which allows you to recover from a failed GTK+ initialization. init_check() does the same work as init() except that it does not terminate the application if the GUI can't be initialized. Instead it returns false on failure. This way your application can fall back to some other means of communication with the user - for example a command line interface.

You can register a callback slot to be called when the main loop is started:

void init_add(const sigc::slot<bool>& callback);

Once invoked, the callback slot is removed from the list of slots to call, and is not called again.

Before you use a thread related method, you must call this next method to initialize the thread system:

void threads_init(GThreadFunctions *vtable = 0);

The 'vtable' argument is a function table of type GThreadFunctions, that provides the entry points for the thread system to use. Most of the time the default functions are appropriate so you can just set vtable to null. Main::threads_init() is a convenience function that calls both G::Thread::init() and gdk_threads_init(). G::Thread::init() initializes the GLib thread system and gdk_threads_init() initializes GDK so that it can be used with multiple threads. After calling threads_init() you can call Gdk::Mutex::lock() and Gdk::Mutex::unlock() to lock and unlock critical sections of code.

To start an application you must  call run(), which runs the GTK+ main loop until quit() is called:

void run();

void quit();


You can nest calls to run(). In that case quit() will make the innermost invocation of the main loop return. All instances of the main loop are functionally identical; they are all watching the same connection to the X server and working from the same event queue. Main loop instances are used to block, halting a function's flow of control until some conditions are met. All GTK+ programs use this technique to keep main() from exiting while the application is running. The Gtk::Dialog::run() method uses a recursive main loop, so it doesn't return until the user clicks a dialog button.

Sometimes you want to process a few events, without handing the flow of control to main(). You can perform a single iteration of the main loop by calling:

bool iterate(bool blocking = true);

Set 'blocking' to true if you want to block if no events are pending. True is returned if quit() has been called for the innermost main loop.

A call to iterate() might process a single event, for example; it depends on what tasks are pending. You can check whether any events need to be processed by calling the events_pending() predicate.

bool events_pending();

Together, iterate() and pending()  allow you to temporarily return control to GTK+, so the GUI can "catch up." For example, during a long computation, you will want to display a progress bar; you must allow the GTK+ main loop to run periodically, so GTK+ can redraw the progress bar. Use this code:

// computation going on

while (events_pending())
     iterate();

// computation continued
 
When the user is doing nothing, GTK+ sits in the main loop and waits for input. If the user performs some action - say, a mouse click - then the main loop "wakes up" and delivers an event to GTK+. GTK+ forwards the event to one or more widgets. When widgets receive an event, they frequently emit one or more signals. Signals notify your program that 'something interesting happened' by invoking the callback slots you've connected to the signal. When your slot functions are invoked, you would typically take some action - for example, when an Open button is clicked you might display a FileChooserDialog. After a callback finishes, GTK+ will return to the main loop and await more user input.

Key Snooper Functions

Key snooper functions are called on all key events before delivering them normally, so they can be used to implement custom key event handling. The key_snooper_signal is declared in the Main:: namespace like this:

class KeySnooperSignal : public sigc::trackable
{
    std::vector<Connection*> connection_list;

public:
    typedef sigc::slot<bool, Gtk::Widget&, const Gdk::EventKey&> SlotType;

    ~KeySnooperSignal();
 
    sigc::connection connect(const SlotType& slot);
};

extern KeySnooperSignal key_snooper_signal;

The SlotType is a callback slot of type sigc::slot<bool, Gtk::Widget&, const Gdk::EventKey&>. The connection method returns a sigc::connection class. Unlike widget signals, you must keep a copy of this connection and specifically call sigc::connection::disconnect() when you no longer wish to snoop key events. 

Functions connected to the key_snooper_signal have the following prototype:

bool MyClass::signal_handler(Gtk::Widget& widget, const Gdk::EventKey& event);

The 'widget' argument is the widget to which the event will be delivered. The 'event' argument is the key event. It is the snooper's responsibility to pass the key event on to the widget, but care must be taken not to pass it twice. Return true to stop further snooping. Return false to continue.
 

The Quit Signal

You can connect a function to the 'quit_signal' to be called when an instance of the main loop is left. The quit_signal is declared in the Main:: namespace like this:

class QuitSignal : public sigc::trackable
{
public:
    typedef sigc::slot<bool> SlotType;

    sigc::connection connect(const SlotType& slot, unsigned int main_level = 0);
};

extern QuitSignal quit_signal;


The SlotType is a callback slot of type sigc::slot<bool> and 'main_level' is the level at which the termination slot shall be called. The connection method returns a sigc::connection class. You can keep a copy of this connection and specifically call sigc::connection::disconnect().

Example Main Functions

The main function is the entry point for a C/C++ application. In XFC the main function is responsible for initializing the GTK+ toolkit and running the main  loop. For most uses your main function wont need to do much more than this.

For single threaded applications your main function should look like this:

int main (int argc, char *argv[])
{
    using namespace Main;

    // init GTK+
    init(&argc, &argv);

    // create a window
    Window window;
    window.signal_destroy().connect(sigc::ptr_fun(&Xfc::Main::quit));
    window.show()

    // enter the main loop
    run();
    return 0;
}


and for multi-threaded applications it should look something like this:

int main (int argc, char *argv[])
{
    using namespace Main;

    // init thread support
    threads_init();

    // init GTK+
    init(&argc, &argv);

    // create a window
    Window window;
    window.sig_destroy().connect(sigc::ptr_fun(&Xfc::Main::quit));
    window.show()

    // enter the main loop
    Gdk::Mutex::lock();
    run();
    Gdk::Mutex::unlock();

    return 0;
}


Copyright © 2004-2005 The XFC Development Team Top
XFC 4.4