diff options
Diffstat (limited to 'uwac/libuwac/uwac-display.c')
-rw-r--r-- | uwac/libuwac/uwac-display.c | 794 |
1 files changed, 794 insertions, 0 deletions
diff --git a/uwac/libuwac/uwac-display.c b/uwac/libuwac/uwac-display.c new file mode 100644 index 0000000..1b26e21 --- /dev/null +++ b/uwac/libuwac/uwac-display.c @@ -0,0 +1,794 @@ +/* + * Copyright © 2014 David FORT <contact@hardening-consulting.com> + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ +#include "uwac-priv.h" +#include "uwac-utils.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <assert.h> +#include <errno.h> +#include <time.h> +#include <unistd.h> +#include <sys/epoll.h> + +#include "uwac-os.h" +#include "wayland-cursor.h" + +#define TARGET_COMPOSITOR_INTERFACE 3U +#define TARGET_SHM_INTERFACE 1U +#define TARGET_SHELL_INTERFACE 1U +#define TARGET_DDM_INTERFACE 1U +#define TARGET_SEAT_INTERFACE 5U +#define TARGET_XDG_VERSION 5U /* The version of xdg-shell that we implement */ + +#if !defined(NDEBUG) +static const char* event_names[] = { + "new seat", "removed seat", "new output", "configure", "pointer enter", + "pointer leave", "pointer motion", "pointer buttons", "pointer axis", "keyboard enter", + "key", "touch frame begin", "touch up", "touch down", "touch motion", + "touch cancel", "touch frame end", "frame done", "close", NULL +}; +#endif + +static bool uwac_default_error_handler(UwacDisplay* display, UwacReturnCode code, const char* msg, + ...) +{ + va_list args; + va_start(args, msg); + vfprintf(stderr, "%s", args); + va_end(args); + return false; +} + +UwacErrorHandler uwacErrorHandler = uwac_default_error_handler; + +void UwacInstallErrorHandler(UwacErrorHandler handler) +{ + if (handler) + uwacErrorHandler = handler; + else + uwacErrorHandler = uwac_default_error_handler; +} + +static void cb_shm_format(void* data, struct wl_shm* wl_shm, uint32_t format) +{ + UwacDisplay* d = data; + + if (format == WL_SHM_FORMAT_RGB565) + d->has_rgb565 = true; + + d->shm_formats_nb++; + d->shm_formats = + xrealloc((void*)d->shm_formats, sizeof(enum wl_shm_format) * d->shm_formats_nb); + d->shm_formats[d->shm_formats_nb - 1] = format; +} + +static struct wl_shm_listener shm_listener = { cb_shm_format }; + +static void xdg_shell_ping(void* data, struct xdg_wm_base* xdg_wm_base, uint32_t serial) +{ + xdg_wm_base_pong(xdg_wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + xdg_shell_ping, +}; + +#ifdef BUILD_FULLSCREEN_SHELL +static void fullscreen_capability(void* data, + struct zwp_fullscreen_shell_v1* zwp_fullscreen_shell_v1, + uint32_t capability) +{ +} + +static const struct zwp_fullscreen_shell_v1_listener fullscreen_shell_listener = { + fullscreen_capability, +}; +#endif + +static void display_destroy_seat(UwacDisplay* d, uint32_t name) +{ + UwacSeat* seat = NULL; + UwacSeat* tmp = NULL; + wl_list_for_each_safe(seat, tmp, &d->seats, link) + { + if (seat->seat_id == name) + { + UwacSeatDestroy(seat); + } + } +} + +static void UwacSeatRegisterDDM(UwacSeat* seat) +{ + UwacDisplay* d = seat->display; + if (!d->data_device_manager) + return; + + if (!seat->data_device) + seat->data_device = + wl_data_device_manager_get_data_device(d->data_device_manager, seat->seat); +} + +static void UwacRegisterCursor(UwacSeat* seat) +{ + if (!seat || !seat->display || !seat->display->compositor) + return; + + seat->pointer_surface = wl_compositor_create_surface(seat->display->compositor); +} + +static void registry_handle_global(void* data, struct wl_registry* registry, uint32_t id, + const char* interface, uint32_t version) +{ + UwacDisplay* d = data; + UwacGlobal* global = NULL; + global = xzalloc(sizeof *global); + global->name = id; + global->interface = xstrdup(interface); + global->version = version; + wl_list_insert(d->globals.prev, &global->link); + + if (strcmp(interface, "wl_compositor") == 0) + { + d->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, + min(TARGET_COMPOSITOR_INTERFACE, version)); + } + else if (strcmp(interface, "wl_shm") == 0) + { + d->shm = + wl_registry_bind(registry, id, &wl_shm_interface, min(TARGET_SHM_INTERFACE, version)); + wl_shm_add_listener(d->shm, &shm_listener, d); + } + else if (strcmp(interface, "wl_output") == 0) + { + UwacOutput* output = NULL; + UwacOutputNewEvent* ev = NULL; + output = UwacCreateOutput(d, id, version); + + if (!output) + { + assert(uwacErrorHandler(d, UWAC_ERROR_NOMEMORY, "unable to create output\n")); + return; + } + + ev = (UwacOutputNewEvent*)UwacDisplayNewEvent(d, UWAC_EVENT_NEW_OUTPUT); + + if (ev) + ev->output = output; + } + else if (strcmp(interface, "wl_seat") == 0) + { + UwacSeatNewEvent* ev = NULL; + UwacSeat* seat = NULL; + seat = UwacSeatNew(d, id, min(version, TARGET_SEAT_INTERFACE)); + + if (!seat) + { + assert(uwacErrorHandler(d, UWAC_ERROR_NOMEMORY, "unable to create new seat\n")); + return; + } + + UwacSeatRegisterDDM(seat); + UwacSeatRegisterClipboard(seat); + UwacRegisterCursor(seat); + ev = (UwacSeatNewEvent*)UwacDisplayNewEvent(d, UWAC_EVENT_NEW_SEAT); + + if (!ev) + { + assert(uwacErrorHandler(d, UWAC_ERROR_NOMEMORY, "unable to create new seat event\n")); + return; + } + + ev->seat = seat; + } + else if (strcmp(interface, "wl_data_device_manager") == 0) + { + UwacSeat* seat = NULL; + UwacSeat* tmp = NULL; + + d->data_device_manager = wl_registry_bind(registry, id, &wl_data_device_manager_interface, + min(TARGET_DDM_INTERFACE, version)); + + wl_list_for_each_safe(seat, tmp, &d->seats, link) + { + UwacSeatRegisterDDM(seat); + UwacSeatRegisterClipboard(seat); + UwacRegisterCursor(seat); + } + } + else if (strcmp(interface, "wl_shell") == 0) + { + d->shell = wl_registry_bind(registry, id, &wl_shell_interface, + min(TARGET_SHELL_INTERFACE, version)); + } + else if (strcmp(interface, "xdg_wm_base") == 0) + { + d->xdg_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(d->xdg_base, &xdg_wm_base_listener, d); + } + else if (strcmp(interface, "wp_viewporter") == 0) + { + d->viewporter = wl_registry_bind(registry, id, &wp_viewporter_interface, 1); + } + else if (strcmp(interface, "zwp_keyboard_shortcuts_inhibit_manager_v1") == 0) + { + d->keyboard_inhibit_manager = + wl_registry_bind(registry, id, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1); + } + else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0) + { + d->deco_manager = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); + } + else if (strcmp(interface, "org_kde_kwin_server_decoration_manager") == 0) + { + d->kde_deco_manager = + wl_registry_bind(registry, id, &org_kde_kwin_server_decoration_manager_interface, 1); + } +#if BUILD_IVI + else if (strcmp(interface, "ivi_application") == 0) + { + d->ivi_application = wl_registry_bind(registry, id, &ivi_application_interface, 1); + } +#endif +#if BUILD_FULLSCREEN_SHELL + else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) + { + d->fullscreen_shell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1); + zwp_fullscreen_shell_v1_add_listener(d->fullscreen_shell, &fullscreen_shell_listener, d); + } +#endif +#if 0 + else if (strcmp(interface, "text_cursor_position") == 0) + { + d->text_cursor_position = wl_registry_bind(registry, id, &text_cursor_position_interface, 1); + } + else if (strcmp(interface, "workspace_manager") == 0) + { + //init_workspace_manager(d, id); + } + else if (strcmp(interface, "wl_subcompositor") == 0) + { + d->subcompositor = wl_registry_bind(registry, id, &wl_subcompositor_interface, 1); +#endif +} + +static void registry_handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) +{ + UwacDisplay* d = data; + UwacGlobal* global = NULL; + UwacGlobal* tmp = NULL; + wl_list_for_each_safe(global, tmp, &d->globals, link) + { + if (global->name != name) + continue; + +#if 0 + + if (strcmp(global->interface, "wl_output") == 0) + display_destroy_output(d, name); + +#endif + + if (strcmp(global->interface, "wl_seat") == 0) + { + UwacSeatRemovedEvent* ev = NULL; + display_destroy_seat(d, name); + ev = (UwacSeatRemovedEvent*)UwacDisplayNewEvent(d, UWAC_EVENT_REMOVED_SEAT); + + if (ev) + ev->id = name; + } + + wl_list_remove(&global->link); + free(global->interface); + free(global); + } +} + +static void UwacDestroyGlobal(UwacGlobal* global) +{ + free(global->interface); + wl_list_remove(&global->link); + free(global); +} + +static void* display_bind(UwacDisplay* display, uint32_t name, const struct wl_interface* interface, + uint32_t version) +{ + return wl_registry_bind(display->registry, name, interface, version); +} + +static const struct wl_registry_listener registry_listener = { registry_handle_global, + registry_handle_global_remove }; + +int UwacDisplayWatchFd(UwacDisplay* display, int fd, uint32_t events, UwacTask* task) +{ + struct epoll_event ep; + ep.events = events; + ep.data.ptr = task; + return epoll_ctl(display->epoll_fd, EPOLL_CTL_ADD, fd, &ep); +} + +static void UwacDisplayUnwatchFd(UwacDisplay* display, int fd) +{ + epoll_ctl(display->epoll_fd, EPOLL_CTL_DEL, fd, NULL); +} + +static void display_exit(UwacDisplay* display) +{ + display->running = false; +} + +static void display_dispatch_events(UwacTask* task, uint32_t events) +{ + UwacDisplay* display = container_of(task, UwacDisplay, dispatch_fd_task); + struct epoll_event ep; + int ret = 0; + display->display_fd_events = events; + + if ((events & EPOLLERR) || (events & EPOLLHUP)) + { + display_exit(display); + return; + } + + if (events & EPOLLIN) + { + ret = wl_display_dispatch(display->display); + + if (ret == -1) + { + display_exit(display); + return; + } + } + + if (events & EPOLLOUT) + { + ret = wl_display_flush(display->display); + + if (ret == 0) + { + ep.events = EPOLLIN | EPOLLERR | EPOLLHUP; + ep.data.ptr = &display->dispatch_fd_task; + epoll_ctl(display->epoll_fd, EPOLL_CTL_MOD, display->display_fd, &ep); + } + else if (ret == -1 && errno != EAGAIN) + { + display_exit(display); + return; + } + } +} + +UwacDisplay* UwacOpenDisplay(const char* name, UwacReturnCode* err) +{ + UwacDisplay* ret = NULL; + ret = (UwacDisplay*)xzalloc(sizeof(*ret)); + + if (!ret) + { + *err = UWAC_ERROR_NOMEMORY; + return NULL; + } + + wl_list_init(&ret->globals); + wl_list_init(&ret->seats); + wl_list_init(&ret->outputs); + wl_list_init(&ret->windows); + ret->display = wl_display_connect(name); + + if (ret->display == NULL) + { + fprintf(stderr, "failed to connect to Wayland display %s: %m\n", name); + *err = UWAC_ERROR_UNABLE_TO_CONNECT; + goto out_free; + } + + ret->epoll_fd = uwac_os_epoll_create_cloexec(); + + if (ret->epoll_fd < 0) + { + *err = UWAC_NOT_ENOUGH_RESOURCES; + goto out_disconnect; + } + + ret->display_fd = wl_display_get_fd(ret->display); + ret->registry = wl_display_get_registry(ret->display); + + if (!ret->registry) + { + *err = UWAC_ERROR_NOMEMORY; + goto out_close_epoll; + } + + wl_registry_add_listener(ret->registry, ®istry_listener, ret); + + if ((wl_display_roundtrip(ret->display) < 0) || (wl_display_roundtrip(ret->display) < 0)) + { + uwacErrorHandler(ret, UWAC_ERROR_UNABLE_TO_CONNECT, + "Failed to process Wayland connection: %m\n"); + *err = UWAC_ERROR_UNABLE_TO_CONNECT; + goto out_free_registry; + } + + ret->dispatch_fd_task.run = display_dispatch_events; + + if (UwacDisplayWatchFd(ret, ret->display_fd, EPOLLIN | EPOLLERR | EPOLLHUP, + &ret->dispatch_fd_task) < 0) + { + uwacErrorHandler(ret, UWAC_ERROR_INTERNAL, "unable to watch display fd: %m\n"); + *err = UWAC_ERROR_INTERNAL; + goto out_free_registry; + } + + ret->running = true; + ret->last_error = *err = UWAC_SUCCESS; + return ret; +out_free_registry: + wl_registry_destroy(ret->registry); +out_close_epoll: + close(ret->epoll_fd); +out_disconnect: + wl_display_disconnect(ret->display); +out_free: + free(ret); + return NULL; +} + +int UwacDisplayDispatch(UwacDisplay* display, int timeout) +{ + int ret = 0; + int count = 0; + UwacTask* task = NULL; + struct epoll_event ep[16]; + wl_display_dispatch_pending(display->display); + + if (!display->running) + return 0; + + ret = wl_display_flush(display->display); + + if (ret < 0 && errno == EAGAIN) + { + ep[0].events = (EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP); + ep[0].data.ptr = &display->dispatch_fd_task; + epoll_ctl(display->epoll_fd, EPOLL_CTL_MOD, display->display_fd, &ep[0]); + } + else if (ret < 0) + { + return -1; + } + + count = epoll_wait(display->epoll_fd, ep, ARRAY_LENGTH(ep), timeout); + + for (int i = 0; i < count; i++) + { + task = ep[i].data.ptr; + task->run(task, ep[i].events); + } + + return 1; +} + +UwacReturnCode UwacDisplayGetLastError(const UwacDisplay* display) +{ + return display->last_error; +} + +UwacReturnCode UwacCloseDisplay(UwacDisplay** pdisplay) +{ + UwacDisplay* display = NULL; + UwacSeat* seat = NULL; + UwacSeat* tmpSeat = NULL; + UwacWindow* window = NULL; + UwacWindow* tmpWindow = NULL; + UwacOutput* output = NULL; + UwacOutput* tmpOutput = NULL; + UwacGlobal* global = NULL; + UwacGlobal* tmpGlobal = NULL; + assert(pdisplay); + display = *pdisplay; + + if (!display) + return UWAC_ERROR_INVALID_DISPLAY; + + /* destroy windows */ + wl_list_for_each_safe(window, tmpWindow, &display->windows, link) + { + UwacDestroyWindow(&window); + } + /* destroy seats */ + wl_list_for_each_safe(seat, tmpSeat, &display->seats, link) + { + UwacSeatDestroy(seat); + } + /* destroy output */ + wl_list_for_each_safe(output, tmpOutput, &display->outputs, link) + { + UwacDestroyOutput(output); + } + /* destroy globals */ + wl_list_for_each_safe(global, tmpGlobal, &display->globals, link) + { + UwacDestroyGlobal(global); + } + + if (display->compositor) + wl_compositor_destroy(display->compositor); + + if (display->keyboard_inhibit_manager) + zwp_keyboard_shortcuts_inhibit_manager_v1_destroy(display->keyboard_inhibit_manager); + + if (display->deco_manager) + zxdg_decoration_manager_v1_destroy(display->deco_manager); + + if (display->kde_deco_manager) + org_kde_kwin_server_decoration_manager_destroy(display->kde_deco_manager); + +#ifdef BUILD_FULLSCREEN_SHELL + + if (display->fullscreen_shell) + zwp_fullscreen_shell_v1_destroy(display->fullscreen_shell); + +#endif +#ifdef BUILD_IVI + + if (display->ivi_application) + ivi_application_destroy(display->ivi_application); + +#endif + + if (display->xdg_toplevel) + xdg_toplevel_destroy(display->xdg_toplevel); + + if (display->xdg_base) + xdg_wm_base_destroy(display->xdg_base); + + if (display->shell) + wl_shell_destroy(display->shell); + + if (display->shm) + wl_shm_destroy(display->shm); + + if (display->viewporter) + wp_viewporter_destroy(display->viewporter); + + if (display->subcompositor) + wl_subcompositor_destroy(display->subcompositor); + + if (display->data_device_manager) + wl_data_device_manager_destroy(display->data_device_manager); + + free(display->shm_formats); + wl_registry_destroy(display->registry); + close(display->epoll_fd); + wl_display_disconnect(display->display); + + /* cleanup the event queue */ + while (display->push_queue) + { + UwacEventListItem* item = display->push_queue; + display->push_queue = item->tail; + free(item); + } + + free(display); + *pdisplay = NULL; + return UWAC_SUCCESS; +} + +int UwacDisplayGetFd(UwacDisplay* display) +{ + return display->epoll_fd; +} + +static const char* errorStrings[] = { + "success", + "out of memory error", + "unable to connect to wayland display", + "invalid UWAC display", + "not enough resources", + "timed out", + "not found", + "closed connection", + + "internal error", +}; + +const char* UwacErrorString(UwacReturnCode error) +{ + if (error < UWAC_SUCCESS || error >= UWAC_ERROR_LAST) + return "invalid error code"; + + return errorStrings[error]; +} + +UwacReturnCode UwacDisplayQueryInterfaceVersion(const UwacDisplay* display, const char* name, + uint32_t* version) +{ + const UwacGlobal* global = NULL; + const UwacGlobal* tmp = NULL; + + if (!display) + return UWAC_ERROR_INVALID_DISPLAY; + + wl_list_for_each_safe(global, tmp, &display->globals, link) + { + if (strcmp(global->interface, name) == 0) + { + if (version) + *version = global->version; + + return UWAC_SUCCESS; + } + } + return UWAC_NOT_FOUND; +} + +uint32_t UwacDisplayQueryGetNbShmFormats(UwacDisplay* display) +{ + if (!display) + { + return 0; + } + + if (!display->shm) + { + display->last_error = UWAC_NOT_FOUND; + return 0; + } + + display->last_error = UWAC_SUCCESS; + return display->shm_formats_nb; +} + +UwacReturnCode UwacDisplayQueryShmFormats(const UwacDisplay* display, enum wl_shm_format* formats, + int formats_size, int* filled) +{ + if (!display) + return UWAC_ERROR_INVALID_DISPLAY; + + *filled = min((int64_t)display->shm_formats_nb, formats_size); + memcpy(formats, (const void*)display->shm_formats, *filled * sizeof(enum wl_shm_format)); + return UWAC_SUCCESS; +} + +uint32_t UwacDisplayGetNbOutputs(const UwacDisplay* display) +{ + return wl_list_length(&display->outputs); +} + +const UwacOutput* UwacDisplayGetOutput(UwacDisplay* display, int index) +{ + int i = 0; + int display_count = 0; + UwacOutput* ret = NULL; + + if (!display) + return NULL; + + display_count = wl_list_length(&display->outputs); + if (display_count <= index) + return NULL; + + wl_list_for_each(ret, &display->outputs, link) + { + if (i == index) + break; + i++; + } + + if (!ret) + { + display->last_error = UWAC_NOT_FOUND; + return NULL; + } + + display->last_error = UWAC_SUCCESS; + return ret; +} + +UwacReturnCode UwacOutputGetResolution(const UwacOutput* output, UwacSize* resolution) +{ + if ((output->resolution.height <= 0) || (output->resolution.width <= 0)) + return UWAC_ERROR_INTERNAL; + + *resolution = output->resolution; + return UWAC_SUCCESS; +} + +UwacReturnCode UwacOutputGetPosition(const UwacOutput* output, UwacPosition* pos) +{ + *pos = output->position; + return UWAC_SUCCESS; +} + +UwacEvent* UwacDisplayNewEvent(UwacDisplay* display, int type) +{ + UwacEventListItem* ret = NULL; + + if (!display) + { + return 0; + } + + ret = xzalloc(sizeof(UwacEventListItem)); + + if (!ret) + { + assert(uwacErrorHandler(display, UWAC_ERROR_NOMEMORY, "unable to allocate a '%s' event", + event_names[type])); + display->last_error = UWAC_ERROR_NOMEMORY; + return 0; + } + + ret->event.type = type; + ret->tail = display->push_queue; + + if (ret->tail) + ret->tail->head = ret; + else + display->pop_queue = ret; + + display->push_queue = ret; + return &ret->event; +} + +bool UwacHasEvent(UwacDisplay* display) +{ + return display->pop_queue != NULL; +} + +UwacReturnCode UwacNextEvent(UwacDisplay* display, UwacEvent* event) +{ + UwacEventListItem* prevItem = NULL; + int ret = 0; + + if (!display) + return UWAC_ERROR_INVALID_DISPLAY; + + while (!display->pop_queue) + { + ret = UwacDisplayDispatch(display, 1 * 1000); + + if (ret < 0) + return UWAC_ERROR_INTERNAL; + else if (ret == 0) + return UWAC_ERROR_CLOSED; + } + + prevItem = display->pop_queue->head; + *event = display->pop_queue->event; + free(display->pop_queue); + display->pop_queue = prevItem; + + if (prevItem) + prevItem->tail = NULL; + else + display->push_queue = NULL; + + return UWAC_SUCCESS; +} |