This is example 4. This example demonstrates the following:

  1. Using the threaded interface to monitor the eye-tracker

  2. Opening/closing the eye-tracker

  3. Printing eye-tracker mode-change events

This example and example 3 are two versions of the same program; this version uses the threaded interface, whereas example 3 uses the waitable interface. For an introduction please see The threaded v.s. waitable interface in the General procedure page.

Source code

Source file: example4.c

ClientData

We’re defining a client data struct to be passed all event handling functions. In some of the other example programs extra attributes are added; check the source.

typedef struct _ClientData {
    EyeVecTrackerMode trackermode;  // Current tracker mode.
    int showmodechanges;            // If set, show tracker mode changes.
} ClientData;

Initialization and clean-up

The initialization part (adapted from example 1) now includes a function call eyevec_create_thread(). This is called ahead of eyevec_initialize(). To clean up we call eyevec_destroy_thread(). This is called following eyevec_cleanup().

int main(void)
{
    // Create an idle EyeVec object.
    EyeVec* eyevec = eyevec_create();

    // Setup a client data struct to be passed to all event handling functions.
    ClientData clientdata; (1)
    clientdata.trackermode = EYEVEC_TRACKER_MODE_OFF;
    clientdata.showmodechanges = 1;

    // Create event queue monitoring thread. In this example we're providing
    // a callback function for mode changes only.
    EyeVecResult err = eyevec_create_thread(eyevec, (2)
        onModeChange,
        NULL,   // onDisplayData,
        NULL,   // onEyeData,
        NULL,   // onBlinkEvent,
        NULL,   // onSaccadeEvent,
        NULL,   // onFixationEvent,
        NULL,   // onTestItemEvent,
        &clientdata);
    printError("eyevec_create_thread()", err);
    if (err) return EXIT_FAILURE;

    // Setup communication with the eyevec-control/server application.
    err = eyevec_initialize(eyevec, true);
    printError("eyevec_initialize()", err);
    if (err) return EXIT_FAILURE;

    .
    .

    printf("Type q to quit, ? for help.\n");
    int ret = inputLoop(eyevec, &clientdata); (3)

    .
    .

    // Terminate communication with the eyevec-control/server application.
    err = eyevec_cleanup(eyevec);
    printError("eyevec_cleanup()", err);

    // Destroy event queue monitoring thread.
    err = eyevec_destroy_thread(eyevec); (4)
    printError("eyevec_destroy_thread()", err);

    // Destroy the EyeVec object.
    eyevec_destroy(eyevec);

    return (ret >= 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
1 Setup client data to be passed to event handling functions.
2 Create event queue monitoring thread.
3 Pass EyeVec object and client data to inputLoop().
4 Destroy the event queue monitoring thread.

inputLoop()

The input loop is basically the same as in example 2 except that we call the processInput() function with the EyeVec object and client data struct.

static int inputLoop(EyeVec* eyevec, ClientData* clientdata)
{
#if defined(EYEVEC_PLATFORM_WINDOWS)
    HANDLE console = GetStdHandle(STD_INPUT_HANDLE);
#else
    int console = fileno(stdin);
#endif

    int ret = 0;
    while (true) {
        ret = waitForInput1(console, -1);
        if (quit_by_signal) break;  // Interrupted by user, quit.
        if (ret == 0) continue;     // Timeout, keep going.
        if (ret < 0) break;         // Error.

        // Get user input (single character).
        int ch = 0;
        if (kbhit()) {
            ch = getch();
            if (ch < 0) break;
        }
        if (ch == '\n') printf("\n");
        if (ch == 0 || !isprint(ch)) continue;  // Ignore non-printables.

        printf("[%c]\n", ch);
        ret = processInput(eyevec, clientdata, ch); (1)
        if (ret != 0) break;
    }

    return (ret >= 0) ? 0 : ret;
}
1 Call processInput() with EyeVec object and client data.

onModeChange()

The onModeChange() function is called by the event queue monitoring thread when a mode-change event is received. In this case we’ll just print a mode-change message. Apart from a cast (see below) this function is the same as in example 3.

This function is called from the event queue monitoring thread which means that interacting with the application might require the use of a synchronization primitive such as a mutex.
static int onModeChange(EyeVec* eyevec, int64_t eventtime,
    const EyeVecModeChangeEventData* modedata, void* cldata) (1)
{
    (void)eyevec;

    ClientData* clientdata = (ClientData*)cldata; (1)
    if (clientdata == NULL) return 0;

    clientdata->trackermode = modedata->newmode;

    if (clientdata->showmodechanges) {
        printf("onModeChange:\n");
        printf("    eventtime:                  %" PRId64 "\n",
            eventtime);
        printf("    oldmode:                    %s\n",
            eyevec_tracker_mode_string(modedata->oldmode));
        printf("    newmode:                    %s\n",
            eyevec_tracker_mode_string(modedata->newmode));
    }

    // Handle change from oldmode to newmode.
    // .
    // .
    // .

    return 0;
}
1 Client data is passed as a void pointer so requires cast to ClientData.

processInput()

The following single character commands are implemented here:
q: quit
o: open/close device
m: toggle show mode-change events

When you press o then eyevec_open() or eyevec_close() will be called depending on whether the device is currently open or not. Doing so should result in onModeChange() function being called on each mode-change. No change compared to example 3.

static int processInput(EyeVec* eyevec, ClientData* clientdata, int ch)
{
    .
    .
    .
    else if (ch == 'o') {
        if (eyevec_is_open(eyevec)) { (1)
            err = eyevec_close(eyevec); (2)
            printError("eyevec_close()", err);
        }
        else {
            err = eyevec_open(eyevec); (3)
            printError("eyevec_open()", err);
        }
    }
    else if (ch == 'm') {
        clientdata->showmodechanges = !clientdata->showmodechanges;
        printf("showmodechanges=%d\n", clientdata->showmodechanges);
    }
    .
    .
    .
}
1 Check if eye-tracker is currently open.
2 Close eye-tracker.
3 Open eye-tracker.

Running

This example uses the utility functions from the utils directory. Make sure to build those first before compiling this example program.

After a succesful build run the program:

  1. Press o to call eyevec_open(). Eye-tracker should go from off mode to idle mode.

  2. Press o to call eyevec_close(). Eye-tracker should go from idle mode to off mode.

  3. Press q to quit.

Output might look like this (empty lines added for clarity):

eyevec_create_thread(): OK
eyevec_initialize(): OK
Type q to quit, ? for help.

[o]
onModeChange:
    eventtime:                  1750411073779373
    oldmode:                    TRACKER_MODE_OFF
    newmode:                    TRACKER_MODE_IDLE
eyevec_open(): OK

[o]
onModeChange:
    eventtime:                  1750411075119864
    oldmode:                    TRACKER_MODE_IDLE
    newmode:                    TRACKER_MODE_OFF
eyevec_close(): OK

[q]
eyevec_cleanup(): OK
eyevec_destroy_thread(): OK

As you can see on opening the device the program shows the eye-tracker mode changing from TRACKER_MODE_OFF to TRACKER_MODE_IDLE. On closure it goes from from TRACKER_MODE_IDLE to TRACKER_MODE_OFF. So in the state diagram we’re going from the gray circle to the blue circle on open, and from the from the blue circle back to the gray circle on close.