diff options
Diffstat (limited to 'src/wayland-client.c')
-rw-r--r-- | src/wayland-client.c | 311 |
1 files changed, 259 insertions, 52 deletions
diff --git a/src/wayland-client.c b/src/wayland-client.c index bea73e0..ec01cdd 100644 --- a/src/wayland-client.c +++ b/src/wayland-client.c @@ -21,6 +21,8 @@ * OF THIS SOFTWARE. */ +#define _GNU_SOURCE + #include <stdlib.h> #include <stdint.h> #include <stddef.h> @@ -83,6 +85,10 @@ struct wl_display { struct wl_event_queue queue; struct wl_list event_queue_list; pthread_mutex_t mutex; + + int reader_count; + uint32_t read_serial; + pthread_cond_t reader_cond; }; /** \endcond */ @@ -521,6 +527,8 @@ wl_display_connect_to_fd(int fd) wl_event_queue_init(&display->queue, display); wl_list_init(&display->event_queue_list); pthread_mutex_init(&display->mutex, NULL); + pthread_cond_init(&display->reader_cond, NULL); + display->reader_count = 0; wl_map_insert_new(&display->objects, 0, NULL); @@ -535,14 +543,19 @@ wl_display_connect_to_fd(int fd) display->proxy.refcount = 1; display->connection = wl_connection_create(display->fd); - if (display->connection == NULL) { - wl_map_release(&display->objects); - close(display->fd); - free(display); - return NULL; - } + if (display->connection == NULL) + goto err_connection; return display; + + err_connection: + pthread_mutex_destroy(&display->mutex); + pthread_cond_destroy(&display->reader_cond); + wl_map_release(&display->objects); + close(display->fd); + free(display); + + return NULL; } /** Connect to a Wayland display @@ -597,6 +610,7 @@ wl_display_disconnect(struct wl_display *display) wl_map_release(&display->objects); wl_event_queue_release(&display->queue); pthread_mutex_destroy(&display->mutex); + pthread_cond_destroy(&display->reader_cond); if (display->fd > 0) close(display->fd); @@ -849,65 +863,207 @@ dispatch_event(struct wl_display *display, struct wl_event_queue *queue) } static int -dispatch_queue(struct wl_display *display, - struct wl_event_queue *queue, int block) +read_events(struct wl_display *display) { - int len, size, count, ret; - - pthread_mutex_lock(&display->mutex); - - if (display->last_error) - goto err_unlock; - - ret = wl_connection_flush(display->connection); - if (ret < 0 && errno != EAGAIN) { - display_fatal_error(display, errno); - goto err_unlock; - } - - if (block && wl_list_empty(&queue->event_list) && - pthread_equal(display->display_thread, pthread_self())) { - len = wl_connection_read(display->connection); - if (len == -1) { - display_fatal_error(display, errno); - goto err_unlock; - } else if (len == 0) { - display_fatal_error(display, EPIPE); - goto err_unlock; + int total, rem, size; + uint32_t serial; + + display->reader_count--; + if (display->reader_count == 0) { + total = wl_connection_read(display->connection); + if (total == -1) { + if (errno != EAGAIN) + display_fatal_error(display, errno); + return -1; } - while (len >= 8) { - size = queue_event(display, len); + for (rem = total; rem >= 8; rem -= size) { + size = queue_event(display, rem); if (size == -1) { display_fatal_error(display, errno); - goto err_unlock; + return -1; } else if (size == 0) { break; } - len -= size; } - } else if (block && wl_list_empty(&queue->event_list)) { - pthread_cond_wait(&queue->cond, &display->mutex); - if (display->last_error) - goto err_unlock; + + display->read_serial++; + pthread_cond_broadcast(&display->reader_cond); + } else { + serial = display->read_serial; + while (display->read_serial == serial) + pthread_cond_wait(&display->reader_cond, + &display->mutex); } + return 0; +} + +/** Read events from display file descriptor + * + * \param display The display context object + * \return 0 on success or -1 on error. In case of error errno will + * be set accordingly + * + * This will read events from the file descriptor for the display. + * This function does not dispatch events, it only reads and queues + * events into their corresponding event queues. If no data is + * avilable on the file descriptor, wl_display_read_events() returns + * immediately. To dispatch events that may have been queued, call + * wl_display_dispatch_pending() or + * wl_display_dispatch_queue_pending(). + * + * Before calling this function, wl_display_prepare_read() must be + * called first. + * + * \memberof wl_display + */ +WL_EXPORT int +wl_display_read_events(struct wl_display *display) +{ + int ret; + + pthread_mutex_lock(&display->mutex); + + ret = read_events(display); + + pthread_mutex_unlock(&display->mutex); + + return ret; +} + +static int +dispatch_queue(struct wl_display *display, struct wl_event_queue *queue) +{ + int count; + + if (display->last_error) + goto err; + for (count = 0; !wl_list_empty(&queue->event_list); count++) { dispatch_event(display, queue); if (display->last_error) - goto err_unlock; + goto err; } - pthread_mutex_unlock(&display->mutex); - return count; -err_unlock: +err: errno = display->last_error; - pthread_mutex_unlock(&display->mutex); return -1; } +WL_EXPORT int +wl_display_prepare_read_queue(struct wl_display *display, + struct wl_event_queue *queue) +{ + int ret; + + pthread_mutex_lock(&display->mutex); + + if (!wl_list_empty(&queue->event_list)) { + errno = EAGAIN; + ret = -1; + } else { + display->reader_count++; + ret = 0; + } + + pthread_mutex_unlock(&display->mutex); + + return ret; +} + +/** Prepare to read events after polling file descriptor + * + * \param display The display context object + * \return 0 on success or -1 if event queue was not empty + * + * This function must be called before reading from the file + * descriptor using wl_display_read_events(). Calling + * wl_display_prepare_read() announces the calling threads intention + * to read and ensures that until the thread is ready to read and + * calls wl_display_read_events(), no other thread will read from the + * file descriptor. This only succeeds if the event queue is empty + * though, and if there are undispatched events in the queue, -1 is + * returned and errno set to EBUSY. + * + * If a thread successfully calls wl_display_prepare_read(), it must + * either call wl_display_read_events() when it's ready or cancel the + * read intention by calling wl_display_cancel_read(). + * + * Use this function before polling on the display fd or to integrate + * the fd into a toolkit event loop in a race-free way. Typically, a + * toolkit will call wl_display_dispatch_pending() before sleeping, to + * make sure it doesn't block with unhandled events. Upon waking up, + * it will assume the file descriptor is readable and read events from + * the fd by calling wl_display_dispatch(). Simplified, we have: + * + * wl_display_dispatch_pending(display); + * wl_display_flush(display); + * poll(fds, nfds, -1); + * wl_display_dispatch(display); + * + * There are two races here: first, before blocking in poll(), the fd + * could become readable and another thread reads the events. Some of + * these events may be for the main queue and the other thread will + * queue them there and then the main thread will go to sleep in + * poll(). This will stall the application, which could be waiting + * for a event to kick of the next animation frame, for example. + * + * The other race is immediately after poll(), where another thread + * could preempt and read events before the main thread calls + * wl_display_dispatch(). This call now blocks and starves the other + * fds in the event loop. + * + * A correct sequence would be: + * + * while (wl_display_prepare_read(display) != 0) + * wl_display_dispatch_pending(display); + * wl_display_flush(display); + * poll(fds, nfds, -1); + * wl_display_read_events(display); + * wl_display_dispatch_pending(display); + * + * Here we call wl_display_prepare_read(), which ensures that between + * returning from that call and eventually calling + * wl_display_read_events(), no other thread will read from the fd and + * queue events in our queue. If the call to + * wl_display_prepare_read() fails, we dispatch the pending events and + * try again until we're successful. + * + * \memberof wl_display + */ +WL_EXPORT int +wl_display_prepare_read(struct wl_display *display) +{ + return wl_display_prepare_read_queue(display, &display->queue); +} + +/** Release exclusive access to display file descriptor + * + * \param display The display context object + * + * This releases the exclusive access. Useful for canceling the lock + * when a timed out poll returns fd not readable and we're not going + * to read from the fd anytime soon. + * + * \memberof wl_display + */ +WL_EXPORT void +wl_display_cancel_read(struct wl_display *display) +{ + pthread_mutex_lock(&display->mutex); + + display->reader_count--; + if (display->reader_count == 0) { + display->read_serial++; + pthread_cond_broadcast(&display->reader_cond); + } + + pthread_mutex_unlock(&display->mutex); +} + /** Dispatch events in an event queue * * \param display The display context object @@ -928,7 +1084,50 @@ WL_EXPORT int wl_display_dispatch_queue(struct wl_display *display, struct wl_event_queue *queue) { - return dispatch_queue(display, queue, 1); + struct pollfd pfd[2]; + int ret; + + pthread_mutex_lock(&display->mutex); + + ret = dispatch_queue(display, queue); + if (ret == -1) + goto err_unlock; + if (ret > 0) { + pthread_mutex_unlock(&display->mutex); + return ret; + } + + ret = wl_connection_flush(display->connection); + if (ret < 0 && errno != EAGAIN) { + display_fatal_error(display, errno); + goto err_unlock; + } + + display->reader_count++; + + pthread_mutex_unlock(&display->mutex); + + pfd[0].fd = display->fd; + pfd[0].events = POLLIN; + if (poll(pfd, 1, -1) == -1) + return -1; + + pthread_mutex_lock(&display->mutex); + + if (read_events(display) == -1) + goto err_unlock; + + ret = dispatch_queue(display, queue); + if (ret == -1) + goto err_unlock; + + pthread_mutex_unlock(&display->mutex); + + return ret; + + err_unlock: + pthread_mutex_unlock(&display->mutex); + return -1; } /** Dispatch pending events in an event queue @@ -948,7 +1147,18 @@ WL_EXPORT int wl_display_dispatch_queue_pending(struct wl_display *display, struct wl_event_queue *queue) { - return dispatch_queue(display, queue, 0); + pthread_mutex_lock(&display->mutex); + + if (dispatch_queue(display, queue) == -1) + goto err_unlock; + + pthread_mutex_unlock(&display->mutex); + + return 0; + + err_unlock: + pthread_mutex_unlock(&display->mutex); + return -1; } /** Process incoming events @@ -967,7 +1177,8 @@ wl_display_dispatch_queue_pending(struct wl_display *display, * or not. For dispatching main queue events without blocking, see \ref * wl_display_dispatch_pending(). * - * \note Calling this makes the current thread the main one. + * \note Calling this will release the display file descriptor if this + * thread acquired it using wl_display_acquire_fd(). * * \sa wl_display_dispatch_pending(), wl_display_dispatch_queue() * @@ -976,9 +1187,7 @@ wl_display_dispatch_queue_pending(struct wl_display *display, WL_EXPORT int wl_display_dispatch(struct wl_display *display) { - display->display_thread = pthread_self(); - - return dispatch_queue(display, &display->queue, 1); + return wl_display_dispatch_queue(display, &display->queue); } /** Dispatch main queue events without reading from the display fd @@ -1022,9 +1231,7 @@ wl_display_dispatch(struct wl_display *display) WL_EXPORT int wl_display_dispatch_pending(struct wl_display *display) { - display->display_thread = pthread_self(); - - return dispatch_queue(display, &display->queue, 0); + return wl_display_dispatch_queue_pending(display, &display->queue); } /** Retrieve the last error occurred on a display |