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;
};
struct _Ecore_Fd_Handler Ecore_Fd_Handler
A handle for Fd handlers.
Definition: Ecore_Common.h:1380
Eo Ecore_Timer
A handle for timers.
Definition: Ecore_Common.h:3079

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");
}
#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;
{
printf("An error has occurred. Stop watching this fd and quit.\n");
ctxt->handler = NULL;
}
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
Fd Error mask.
Definition: Ecore_Common.h:1390
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
#define ECORE_CALLBACK_CANCEL
Return value to remove a callback.
Definition: Ecore_Common.h:152
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:

nbytes = read(fd, buf, sizeof(buf));
if (nbytes == 0)
{
printf("Nothing to read, exiting...\n");
ctxt->handler = NULL;
}
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);
}
static Eina_Bool
_timer_cb(void *data EINA_UNUSED)
{
printf("Timer expired after 5 seconds...\n");
}
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,
_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);
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_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
Fd Read mask.
Definition: Ecore_Common.h:1388
EAPI int ecore_init(void)
Sets up connections, signal handlers, sockets etc.
Definition: ecore.c:230
#define ECORE_CALLBACK_RENEW
Return value to keep a callback.
Definition: Ecore_Common.h:153
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");
if (ctxt.handler)
if (ctxt.timer)
ecore_timer_del(ctxt.timer);
return 0;
}
void * ecore_main_fd_handler_del(Ecore_Fd_Handler *fd_handler)
Marks an FD handler for deletion.
Definition: ecore_main.c:1480
EAPI int ecore_shutdown(void)
Shuts down connections, signal handlers sockets etc.
Definition: ecore.c:371
void ecore_main_loop_begin(void)
Runs the application main loop.
Definition: ecore_main.c:1311
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.