ecore fd handlers - Monitoring file descriptors

This is a very simple example where we will start monitoring the stdin of the program and, whenever there's something to be read, we call our callback that will read it.

Check the full code for this example here.

This seems to be stupid, since a similar result could be achieved by the following code:

while (nbytes = read(STDIN_FILENO, buf, sizeof(buf)))
{
buf[nbytes - 1] = '\0';
printf("Read %zd bytes from input: \"%s\"\n", nbytes - 1, buf);
}

However, the above code is blocking, and won't allow you to do anything else other than reading the input. Of course there are other methods to do a non-blocking reading, like setting the file descriptor to non-blocking and keep looping always checking if there's something to be read, and do other things otherwise. Or use a `select` call to watch for more than one file descriptor at the same time.

The advantage of using an Ecore_Fd_Handler is that you can monitor a file descriptor, while still iterating on the Ecore main loop. It will allow you to have timers working and expiring, events still being processed when received, idlers doing its work when there's nothing happening, and whenever there's something to be read from the file descriptor, your callback will be called. And it's everything monitored in the same main loop, no threads are needed, thus reducing the complexity of the program and any overhead caused by the use of threads.

Now let's start our program. First we just declare a context structure that will be passed to our callback, with pointers to our handler and to a timer that will be used later:

/*
* gcc -o ecore_fd_handler_example ecore_fd_handler_example.c `pkg-config --cflags --libs ecore`
*/
#include <Ecore.h>
#include <unistd.h>
struct context
{
Ecore_Fd_Handler *handler;
Ecore_Timer *timer;
};
Ecore_Fd_Handler
struct _Ecore_Fd_Handler Ecore_Fd_Handler
A handle for Fd handlers.
Definition Ecore_Common.h:1386
Ecore_Timer
Eo Ecore_Timer
A handle for timers.
Definition Ecore_Common.h:3085

Then we will declare a prepare_callback that is called before any fd_handler set in the program, and before the main loop select function is called. Just use one if you really know that you need it. We are just putting it here to exemplify its usage:

static void
_fd_prepare_cb(void *data EINA_UNUSED, Ecore_Fd_Handler *handler EINA_UNUSED)
{
printf("prepare_cb called.\n");
}
EINA_UNUSED
#define EINA_UNUSED
Used to indicate that a function parameter is purposely unused.
Definition eina_types.h:339

Now, our fd handler. In its arguments, the `data` pointer will have any data passed to it when it was registered, and the `handler` pointer will contain the fd handler returned by the ecore_main_fd_handler_add() call. It can be used, for example, to retrieve which file descriptor triggered this callback, since it could be added to more than one file descriptor, or to check what type of activity there's in the file descriptor.

The code is very simple: we first check if the type of activity was an error. It probably won't happen with the default input, but could be the case of a network socket detecting a disconnection. Next, we get the file descriptor from this handler (as said before, the callback could be added to more than one file descriptor), and read it since we know that it shouldn't block, because our fd handler told us that there's some activity on it. If the result of the read was 0 bytes, we know that it's an end of file (EOF), so we can finish reading the input. Otherwise we just print the content read from it:

static Eina_Bool
_fd_handler_cb(void *data, Ecore_Fd_Handler *handler)
{
struct context *ctxt = data;
char buf[1024];
size_t nbytes;
int fd;
if (ecore_main_fd_handler_active_get(handler, ECORE_FD_ERROR))
{
printf("An error has occurred. Stop watching this fd and quit.\n");
ecore_main_loop_quit();
ctxt->handler = NULL;
return ECORE_CALLBACK_CANCEL;
}
ecore_main_fd_handler_active_get
Eina_Bool ecore_main_fd_handler_active_get(Ecore_Fd_Handler *fd_handler, Ecore_Fd_Handler_Flags flags)
Gets which flags are active on an FD handler.
Definition ecore_main.c:1643
ECORE_FD_ERROR
@ ECORE_FD_ERROR
Fd Error mask.
Definition Ecore_Common.h:1396
ecore_main_loop_quit
void ecore_main_loop_quit(void)
Quits the main loop once all the events currently on the queue have been processed.
Definition ecore_main.c:1321
ECORE_CALLBACK_CANCEL
#define ECORE_CALLBACK_CANCEL
Return value to remove a callback.
Definition Ecore_Common.h:152
Eina_Bool
unsigned char Eina_Bool
Type to mimic a boolean.
Definition eina_types.h:527

Also notice that this callback returns ECORE_CALLBACK_RENEW to keep being called, as almost all other Ecore callbacks, otherwise if it returns ECORE_CALLBACK_CANCEL then the file handler would be deleted.

Just to demonstrate that our program isn't blocking in the input read but still can process other Ecore events, we are going to setup an Ecore_Timer. This is its callback:

fd = ecore_main_fd_handler_fd_get(handler);
nbytes = read(fd, buf, sizeof(buf));
if (nbytes == 0)
{
printf("Nothing to read, exiting...\n");
ecore_main_loop_quit();
ctxt->handler = NULL;
return ECORE_CALLBACK_CANCEL;
}
ecore_main_fd_handler_fd_get
int ecore_main_fd_handler_fd_get(Ecore_Fd_Handler *fd_handler)
Retrieves the file descriptor that the given handler is handling.
Definition ecore_main.c:1627

Now in the main code we are going to initialize the library, and setup callbacks for the file descriptor, the prepare callback, and the timer:

buf[nbytes - 1] = '\0';
printf("Read %zd bytes from input: \"%s\"\n", nbytes - 1, buf);
return ECORE_CALLBACK_RENEW;
}
static Eina_Bool
_timer_cb(void *data EINA_UNUSED)
{
printf("Timer expired after 5 seconds...\n");
return ECORE_CALLBACK_RENEW;
}
int
main(void)
{
struct context ctxt = {0};
if (!ecore_init())
{
printf("ERROR: Cannot init Ecore!\n");
return -1;
}
ctxt.handler = ecore_main_fd_handler_add(STDIN_FILENO,
ECORE_FD_READ | ECORE_FD_ERROR,
_fd_handler_cb,
&ctxt, NULL, NULL);
ecore_main_fd_handler_prepare_callback_set(ctxt.handler, _fd_prepare_cb, &ctxt);
ctxt.timer = ecore_timer_add(5, _timer_cb, &ctxt);
ecore_main_fd_handler_prepare_callback_set
void ecore_main_fd_handler_prepare_callback_set(Ecore_Fd_Handler *fd_handler, Ecore_Fd_Prep_Cb func, const void *data)
Sets the prepare callback with data for a given Ecore_Fd_Handler.
Definition ecore_main.c:1602
ecore_main_fd_handler_add
Ecore_Fd_Handler * ecore_main_fd_handler_add(int fd, Ecore_Fd_Handler_Flags flags, Ecore_Fd_Cb func, const void *data, Ecore_Fd_Cb buf_func, const void *buf_data)
Adds a callback for activity on the given file descriptor.
Definition ecore_main.c:1449
ECORE_FD_READ
@ ECORE_FD_READ
Fd Read mask.
Definition Ecore_Common.h:1394
ecore_init
EAPI int ecore_init(void)
Sets up connections, signal handlers, sockets etc.
Definition ecore.c:230
ECORE_CALLBACK_RENEW
#define ECORE_CALLBACK_RENEW
Return value to keep a callback.
Definition Ecore_Common.h:153
ecore_timer_add
Ecore_Timer * ecore_timer_add(double in, Ecore_Task_Cb func, const void *data)
Creates a timer to call the given function in the given period of time.
Definition ecore_timer.c:189

Notice that the use of ecore_main_fd_handler_add() specifies what kind of activity we are monitoring. In this case, we want to monitor for read (since it's the standard input) and for errors. This is done by the flags ECORE_FD_READ and ECORE_FD_ERROR. For the three callbacks we are also giving a pointer to our context structure, which has pointers to the handlers added.

Then we can start the main loop and see everything happening:

printf("Starting the main loop. Type anything and hit <enter> to "
"activate the fd_handler callback, or CTRL+d to shutdown.\n");
ecore_main_loop_begin();
if (ctxt.handler)
ecore_main_fd_handler_del(ctxt.handler);
if (ctxt.timer)
ecore_timer_del(ctxt.timer);
ecore_shutdown();
return 0;
}
ecore_main_fd_handler_del
void * ecore_main_fd_handler_del(Ecore_Fd_Handler *fd_handler)
Marks an FD handler for deletion.
Definition ecore_main.c:1480
ecore_shutdown
EAPI int ecore_shutdown(void)
Shuts down connections, signal handlers sockets etc.
Definition ecore.c:371
ecore_main_loop_begin
void ecore_main_loop_begin(void)
Runs the application main loop.
Definition ecore_main.c:1311
ecore_timer_del
void * ecore_timer_del(Ecore_Timer *timer)
Deletes the specified timer from the timer list.
Definition ecore_timer.c:238

In the end we are just deleting the fd handler and the timer to demonstrate the API usage, since Ecore would already do it for us on its shutdown.