Tobii Interaction Library SDK

[Back to the SDK start page]

C Sample Walkthrough

This section describes the typical workflow when using the C Binding API of the Interaction Library SDK.

Let's assume the eye tracker is on the main screen and that the resolution is 2560x1440 pixels.

We want to tell the Interaction Library about 4 regions on screen (A, B, C and D in the picture below) for which we want to know when the user looks at them (or not). As our gaze enters and leaves them, the Interaction Library will emit "got focus" and "lost focus" events that we can react to in our client application.

Such a screen region is called an interactor in the Interaction Library API.

A screen with 4 "corner" interactors A, B, C and D:
2560
+-------+-------------------+-------+
| | | |
| A | 500 500 | B |
| | | |
+-------+ +-------+
| 500 500 |
| | 1440
| 500 500 |
+-------+ +-------+
| | | |
| C | 500 500 | D |
| | | |
+-------+------------------ +-------+

In the following, error checking is omitted for readability (most API functions return an IL_Result value, where IL_Result_Ok is the generic "success" value).

Make sure that the compiler and linker can find the Interaction Library C Binding API headers and dynamic libraries, then include the main library header file:

#include <stdio.h>
#include <inttypes.h> // for PRId64 and timestamps
#include <interaction_lib/interaction_lib_c.h>

To get a way of breaking out of the "update loop" (described further down), we track how many times the same interactor gets focus consecutively using a simple struct.

typedef struct focus_context
{
IL_InteractorId id;
size_t count;
} focus_context;

When we tell the Interaction Library (further down) that we want to get interactor focus events, we supply this callback function.

void gaze_focus_callback(IL_GazeFocusEvent evt, void* context)
{
printf("Interactor: %" PRIu64 ", focused: %s, timestamp: %" PRId64 " us\n",
evt.id,
evt.hasFocus ? "true" : "false",
if (evt.hasFocus)
{
focus_context* focus = context;
focus->count = focus->id == evt.id ? focus->count + 1 : 1;
focus->id = evt.id;
}
}

Create an instance of the Interaction Library implementation with IL_CreateInteractionLib(). Please note that the IL_Context created must be deleted using IL_DestroyInteractionLib().

int main(int argc, char* argv[])
{
// create the interaction library
IL_Context* intlib = NULL;
IL_CreateInteractionLib(&intlib, IL_FieldOfUse_Interactive);

Tell the Interaction Library our screen size with IL_CoordinateTransformAddOrUpdateDisplayArea() and which origo our interactor coordinates are relative to with IL_CoordinateTransformSetOriginOffset(). The latter is most useful if you want to use interactors with window local coordinates; simply update the offset as the window moves rather than all interactors.

const float width = 2560.0f;
const float height = 1440.0f;
const float offset = 0.0f;
IL_CoordinateTransformAddOrUpdateDisplayArea(intlib, width, height, 0, 0, 0, 0, NULL);
IL_CoordinateTransformSetOriginOffset(intlib, offset, offset);

Tell the Interaction Library where our interactors are located with IL_AddOrUpdateInteractor(). This must be done between matching calls to IL_BeginInteractorUpdates() and IL_CommitInteractorUpdates(), as changes will be handled in safe and optimized batches. An interactor is identified by a unique client supplied ID. Also, interactors can overlap each other and be stacked in z-order but we leave them all at 0 in this sample.

const IL_InteractorId idA = 0;
const IL_InteractorId idB = 1;
const IL_InteractorId idC = 2;
const IL_InteractorId idD = 3;
const float size = 500.0f;
const IL_Rectangle rectA = { 0, 0, size, size };
const IL_Rectangle rectB = { width - size, 0, size, size };
const IL_Rectangle rectC = { 0, height - size, size, size };
const IL_Rectangle rectD = { width - size, height - size, size, size };
const float z = 0.0f;
IL_AddOrUpdateInteractor(intlib, idA, rectA, z);
IL_AddOrUpdateInteractor(intlib, idB, rectB, z);
IL_AddOrUpdateInteractor(intlib, idC, rectC, z);
IL_AddOrUpdateInteractor(intlib, idD, rectD, z);

Tell the Interaction Library we want to get interactor focus events with IL_SubscribeGazeFocusEvents(). When our gaze enters and leaves the interactors, the callback function provided will be called by the Interaction Library and the event information is printed to standard output.

focus_context focus = { IL_EMPTYINTERACTORID, 0 };
IL_SubscribeGazeFocusEvents(intlib, gaze_focus_callback, &focus);

Tell the Interaction Library to continuously evaluate eye tracker gaze data and call any client event subscribers with IL_WaitAndUpdate(). In the event that no device is connected, IL_WaitAndUpdate() will attempt to find and connect to a tracker once per second. Since IL_WaitAndUpdate() will often block the calling thread, you would normally put this loop on its own thread. IL_WaitAndUpdate() is a convenience method that handles device connections, waiting for data and updating the Interaction Library to perform all calculation and trigger relevant callbacks all in one method. If you need granular control over updates and/or non-blocking calls use a suitable combination of the various other control methods available in the API.

printf("Starting interaction library update loop.\n");
const size_t max_focus_count = 3;
while (focus.count < max_focus_count)
{
IL_WaitAndUpdate(intlib, 1000);
}
printf("Interactor %" PRIu64 " got focused %zu times\n", focus.id, focus.count);

Remember to clean up after us:

return 0;
}

Finally:

  • Compile and run the sample code. Its entirety can be found here: C Binding Sample Complete Code
  • Observe how gaze focus "enter" and "leave" event information gets printed to standard output.
  • Focus the same interactor 3 times in a row to exit the sample program.