diff --git a/client/displayservers/Wayland/CMakeLists.txt b/client/displayservers/Wayland/CMakeLists.txt index 724d9897..bfefd0c0 100644 --- a/client/displayservers/Wayland/CMakeLists.txt +++ b/client/displayservers/Wayland/CMakeLists.txt @@ -10,7 +10,16 @@ pkg_check_modules(DISPLAYSERVER_Wayland_PKGCONFIG REQUIRED #) add_library(displayserver_Wayland STATIC + clipboard.c + cursor.c + gl.c + idle.c + input.c + poll.c + state.c + registry.c wayland.c + window.c ) target_link_libraries(displayserver_Wayland diff --git a/client/displayservers/Wayland/clipboard.c b/client/displayservers/Wayland/clipboard.c new file mode 100644 index 00000000..4e8457b3 --- /dev/null +++ b/client/displayservers/Wayland/clipboard.c @@ -0,0 +1,477 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com) +Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca) +https://looking-glass.io + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "wayland.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "app.h" +#include "common/debug.h" + +static const char * textMimetypes[] = +{ + "text/plain", + "text/plain;charset=utf-8", + "TEXT", + "STRING", + "UTF8_STRING", + NULL, +}; + +static const char * pngMimetypes[] = +{ + "image/png", + NULL, +}; + +static const char * bmpMimetypes[] = +{ + "image/bmp", + "image/x-bmp", + "image/x-MS-bmp", + "image/x-win-bitmap", + NULL, +}; + +static const char * tiffMimetypes[] = +{ + "image/tiff", + NULL, +}; + +static const char * jpegMimetypes[] = +{ + "image/jpeg", + NULL, +}; + +static const char ** cbTypeToMimetypes(enum LG_ClipboardData type) +{ + switch (type) + { + case LG_CLIPBOARD_DATA_TEXT: + return textMimetypes; + case LG_CLIPBOARD_DATA_PNG: + return pngMimetypes; + case LG_CLIPBOARD_DATA_BMP: + return bmpMimetypes; + case LG_CLIPBOARD_DATA_TIFF: + return tiffMimetypes; + case LG_CLIPBOARD_DATA_JPEG: + return jpegMimetypes; + default: + DEBUG_ERROR("invalid clipboard type"); + abort(); + } +} + +static bool containsMimetype(const char ** mimetypes, + const char * needle) +{ + for (const char ** mimetype = mimetypes; *mimetype; mimetype++) + if (!strcmp(needle, *mimetype)) + return true; + + return false; +} + +static bool mimetypeEndswith(const char * mimetype, const char * what) +{ + size_t mimetypeLen = strlen(mimetype); + size_t whatLen = strlen(what); + + if (mimetypeLen < whatLen) + return false; + + return !strcmp(mimetype + mimetypeLen - whatLen, what); +} + +static bool isTextMimetype(const char * mimetype) +{ + if (containsMimetype(textMimetypes, mimetype)) + return true; + + char * text = "text/"; + if (!strncmp(mimetype, text, strlen(text))) + return true; + + if (mimetypeEndswith(mimetype, "script") || + mimetypeEndswith(mimetype, "xml") || + mimetypeEndswith(mimetype, "yaml")) + return true; + + if (strstr(mimetype, "json")) + return true; + + return false; +} + +static enum LG_ClipboardData mimetypeToCbType(const char * mimetype) +{ + if (isTextMimetype(mimetype)) + return LG_CLIPBOARD_DATA_TEXT; + + if (containsMimetype(pngMimetypes, mimetype)) + return LG_CLIPBOARD_DATA_PNG; + + if (containsMimetype(bmpMimetypes, mimetype)) + return LG_CLIPBOARD_DATA_BMP; + + if (containsMimetype(tiffMimetypes, mimetype)) + return LG_CLIPBOARD_DATA_TIFF; + + if (containsMimetype(jpegMimetypes, mimetype)) + return LG_CLIPBOARD_DATA_JPEG; + + return LG_CLIPBOARD_DATA_NONE; +} + +// Destination client handlers. + +static void dataOfferHandleOffer(void * data, struct wl_data_offer * offer, + const char * mimetype) +{ + enum LG_ClipboardData type = mimetypeToCbType(mimetype); + // We almost never prefer text/html, as that's used to represent rich text. + // Since we can't copy or paste rich text, we should instead prefer actual + // images or plain text. + if (type != LG_CLIPBOARD_DATA_NONE && + (wlCb.pendingType == LG_CLIPBOARD_DATA_NONE || + strstr(wlCb.pendingMimetype, "html"))) + { + wlCb.pendingType = type; + if (wlCb.pendingMimetype) + free(wlCb.pendingMimetype); + wlCb.pendingMimetype = strdup(mimetype); + } + + if (!strcmp(mimetype, wlCb.lgMimetype)) + wlCb.isSelfCopy = true; +} + +static void dataOfferHandleSourceActions(void * data, + struct wl_data_offer * offer, uint32_t sourceActions) +{ + // Do nothing. +} + +static void dataOfferHandleAction(void * data, struct wl_data_offer * offer, + uint32_t dndAction) +{ + // Do nothing. +} + +static const struct wl_data_offer_listener dataOfferListener = { + .offer = dataOfferHandleOffer, + .source_actions = dataOfferHandleSourceActions, + .action = dataOfferHandleAction, +}; + +static void dataDeviceHandleDataOffer(void * data, + struct wl_data_device * dataDevice, struct wl_data_offer * offer) +{ + wlCb.pendingType = LG_CLIPBOARD_DATA_NONE; + wlCb.isSelfCopy = false; + wl_data_offer_add_listener(offer, &dataOfferListener, NULL); +} + +static void clipboardReadCancel(struct ClipboardRead * data, bool freeBuf) +{ + waylandEpollUnregister(data->fd); + close(data->fd); + wl_data_offer_destroy(data->offer); + if (freeBuf) + free(data->buf); + free(data); + wlCb.currentRead = NULL; +} + +static void clipboardReadCallback(uint32_t events, void * opaque) +{ + struct ClipboardRead * data = opaque; + if (events & EPOLLERR) + { + clipboardReadCancel(data, true); + return; + } + + ssize_t result = read(data->fd, data->buf + data->numRead, data->size - data->numRead); + if (result < 0) + { + DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno)); + clipboardReadCancel(data, true); + return; + } + + if (result == 0) + { + data->buf[data->numRead] = 0; + wlCb.stashedType = data->type; + wlCb.stashedSize = data->numRead; + wlCb.stashedContents = data->buf; + + clipboardReadCancel(data, false); + app_clipboardNotify(wlCb.stashedType, 0); + return; + } + + data->numRead += result; + if (data->numRead >= data->size) + { + data->size *= 2; + void * nbuf = realloc(data->buf, data->size); + if (!nbuf) { + DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno)); + clipboardReadCancel(data, true); + return; + } + + data->buf = nbuf; + } +} + +static void dataDeviceHandleSelection(void * opaque, + struct wl_data_device * dataDevice, struct wl_data_offer * offer) +{ + if (wlCb.pendingType == LG_CLIPBOARD_DATA_NONE || wlCb.isSelfCopy || !offer) + return; + + if (wlCb.currentRead) + clipboardReadCancel(wlCb.currentRead, true); + + int fds[2]; + if (pipe(fds) < 0) + { + DEBUG_ERROR("Failed to get a clipboard pipe: %s", strerror(errno)); + abort(); + } + + wl_data_offer_receive(offer, wlCb.pendingMimetype, fds[1]); + close(fds[1]); + free(wlCb.pendingMimetype); + wlCb.pendingMimetype = NULL; + + wl_display_roundtrip(wlWm.display); + + if (wlCb.stashedContents) + { + free(wlCb.stashedContents); + wlCb.stashedContents = NULL; + } + + struct ClipboardRead * data = malloc(sizeof(struct ClipboardRead)); + if (!data) + { + DEBUG_ERROR("Failed to allocate memory to read clipboard"); + close(fds[0]); + return; + } + + data->fd = fds[0]; + data->size = 4096; + data->numRead = 0; + data->buf = malloc(data->size); + data->offer = offer; + data->type = wlCb.pendingType; + + if (!data->buf) + { + DEBUG_ERROR("Failed to allocate memory to receive clipboard data"); + close(data->fd); + free(data); + return; + } + + if (!waylandEpollRegister(data->fd, clipboardReadCallback, data, EPOLLIN)) + { + DEBUG_ERROR("Failed to register clipboard read into epoll: %s", strerror(errno)); + close(data->fd); + free(data->buf); + free(data); + } + + wlCb.currentRead = data; +} + +static const struct wl_data_device_listener dataDeviceListener = { + .data_offer = dataDeviceHandleDataOffer, + .selection = dataDeviceHandleSelection, +}; + +bool waylandCBInit(void) +{ + memset(&wlCb, 0, sizeof(wlCb)); + + if (!wlWm.dataDeviceManager) + { + DEBUG_ERROR("Missing wl_data_device_manager interface"); + return false; + } + + wlCb.dataDevice = wl_data_device_manager_get_data_device( + wlWm.dataDeviceManager, wlWm.seat); + if (!wlCb.dataDevice) + { + DEBUG_ERROR("Failed to get data device"); + return false; + } + + wlCb.stashedType = LG_CLIPBOARD_DATA_NONE; + wl_data_device_add_listener(wlCb.dataDevice, &dataDeviceListener, NULL); + + snprintf(wlCb.lgMimetype, sizeof(wlCb.lgMimetype), + "application/x-looking-glass-copy;pid=%d", getpid()); + + return true; +} + +void waylandCBRequest(LG_ClipboardData type) +{ + // We only notified once, so it must be this. + assert(type == wlCb.stashedType); + app_clipboardData(wlCb.stashedType, wlCb.stashedContents, wlCb.stashedSize); +} + +struct ClipboardWrite +{ + int fd; + size_t pos; + struct CountedBuffer * buffer; +}; + +static void clipboardWriteCallback(uint32_t events, void * opaque) +{ + struct ClipboardWrite * data = opaque; + if (events & EPOLLERR) + goto error; + + ssize_t written = write(data->fd, data->buffer->data + data->pos, data->buffer->size - data->pos); + if (written < 0) + { + if (errno != EPIPE) + DEBUG_ERROR("Failed to write clipboard data: %s", strerror(errno)); + goto error; + } + + data->pos += written; + if (data->pos < data->buffer->size) + return; + +error: + waylandEpollUnregister(data->fd); + close(data->fd); + countedBufferRelease(&data->buffer); + free(data); +} + +static void dataSourceHandleSend(void * data, struct wl_data_source * source, + const char * mimetype, int fd) +{ + struct WCBTransfer * transfer = (struct WCBTransfer *) data; + if (containsMimetype(transfer->mimetypes, mimetype)) + { + // Consider making this do non-blocking sends to not stall the Wayland + // event loop if it becomes a problem. This is "fine" in the sense that + // wl-copy also stalls like this, but it's not necessary. + fcntl(fd, F_SETFL, 0); + + struct ClipboardWrite * data = malloc(sizeof(struct ClipboardWrite)); + if (!data) + { + DEBUG_ERROR("Out of memory trying to allocate ClipboardWrite"); + goto error; + } + + data->fd = fd; + data->pos = 0; + data->buffer = transfer->data; + countedBufferAddRef(transfer->data); + waylandEpollRegister(fd, clipboardWriteCallback, data, EPOLLOUT); + return; + } + +error: + close(fd); +} + +static void dataSourceHandleCancelled(void * data, + struct wl_data_source * source) +{ + struct WCBTransfer * transfer = (struct WCBTransfer *) data; + countedBufferRelease(&transfer->data); + free(transfer); + wl_data_source_destroy(source); +} + +static const struct wl_data_source_listener dataSourceListener = { + .send = dataSourceHandleSend, + .cancelled = dataSourceHandleCancelled, +}; + +static void waylandCBReplyFn(void * opaque, LG_ClipboardData type, + uint8_t * data, uint32_t size) +{ + struct WCBTransfer * transfer = malloc(sizeof(struct WCBTransfer)); + if (!transfer) + { + DEBUG_ERROR("Out of memory when allocating WCBTransfer"); + return; + } + + transfer->mimetypes = cbTypeToMimetypes(type); + transfer->data = countedBufferNew(size); + if (!transfer->data) + { + DEBUG_ERROR("Out of memory when allocating clipboard buffer"); + free(transfer); + return; + } + memcpy(transfer->data->data, data, size); + + struct wl_data_source * source = + wl_data_device_manager_create_data_source(wlWm.dataDeviceManager); + wl_data_source_add_listener(source, &dataSourceListener, transfer); + for (const char ** mimetype = transfer->mimetypes; *mimetype; mimetype++) + wl_data_source_offer(source, *mimetype); + wl_data_source_offer(source, wlCb.lgMimetype); + + wl_data_device_set_selection(wlCb.dataDevice, source, + wlWm.keyboardEnterSerial); +} + +void waylandCBNotice(LG_ClipboardData type) +{ + wlCb.haveRequest = true; + wlCb.type = type; + app_clipboardRequest(waylandCBReplyFn, NULL); +} + +void waylandCBRelease(void) +{ + wlCb.haveRequest = false; +} diff --git a/client/displayservers/Wayland/cursor.c b/client/displayservers/Wayland/cursor.c new file mode 100644 index 00000000..ce0c3980 --- /dev/null +++ b/client/displayservers/Wayland/cursor.c @@ -0,0 +1,100 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com) +Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca) +https://looking-glass.io + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#define _GNU_SOURCE +#include "wayland.h" + +#include +#include + +#include +#include +#include +#include + +#include "common/debug.h" + +static const uint32_t cursorBitmap[] = { + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, + 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, +}; + +static struct wl_buffer * createCursorBuffer(void) +{ + int fd = memfd_create("lg-cursor", 0); + if (fd < 0) + { + DEBUG_ERROR("Failed to create cursor shared memory: %d", errno); + return NULL; + } + + struct wl_buffer * result = NULL; + + if (ftruncate(fd, sizeof cursorBitmap) < 0) + { + DEBUG_ERROR("Failed to ftruncate cursor shared memory: %d", errno); + goto fail; + } + + void * shm_data = mmap(NULL, sizeof cursorBitmap, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (shm_data == MAP_FAILED) + { + DEBUG_ERROR("Failed to map memory for cursor: %d", errno); + goto fail; + } + + struct wl_shm_pool * pool = wl_shm_create_pool(wlWm.shm, fd, sizeof cursorBitmap); + result = wl_shm_pool_create_buffer(pool, 0, 4, 4, 16, WL_SHM_FORMAT_XRGB8888); + wl_shm_pool_destroy(pool); + + memcpy(shm_data, cursorBitmap, sizeof cursorBitmap); + munmap(shm_data, sizeof cursorBitmap); + +fail: + close(fd); + return result; +} + +bool waylandCursorInit(void) +{ + if (!wlWm.compositor) + { + DEBUG_ERROR("Compositor missing wl_compositor, will not proceed"); + return false; + } + + struct wl_buffer * cursorBuffer = createCursorBuffer(); + if (cursorBuffer) + { + wlWm.cursor = wl_compositor_create_surface(wlWm.compositor); + wl_surface_attach(wlWm.cursor, cursorBuffer, 0, 0); + wl_surface_commit(wlWm.cursor); + } + + return true; +} + +void waylandShowPointer(bool show) +{ + wlWm.showPointer = show; + wl_pointer_set_cursor(wlWm.pointer, wlWm.pointerEnterSerial, show ? wlWm.cursor : NULL, 0, 0); +} diff --git a/client/displayservers/Wayland/gl.c b/client/displayservers/Wayland/gl.c new file mode 100644 index 00000000..3f991a15 --- /dev/null +++ b/client/displayservers/Wayland/gl.c @@ -0,0 +1,171 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com) +Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca) +https://looking-glass.io + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "wayland.h" + +#include +#include + +#include +#include + +#include "app.h" +#include "common/debug.h" + +#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL) +#include "egl_dynprocs.h" + +bool waylandEGLInit(int w, int h) +{ + wlWm.eglWindow = wl_egl_window_create(wlWm.surface, w, h); + if (!wlWm.eglWindow) + { + DEBUG_ERROR("Failed to create EGL window"); + return false; + } + return true; +} + +EGLDisplay waylandGetEGLDisplay(void) +{ + EGLNativeDisplayType native = (EGLNativeDisplayType) wlWm.display; + + const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS); + + if (strstr(early_exts, "EGL_KHR_platform_wayland") != NULL && + g_egl_dynProcs.eglGetPlatformDisplay) + { + DEBUG_INFO("Using eglGetPlatformDisplay"); + return g_egl_dynProcs.eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, native, NULL); + } + + if (strstr(early_exts, "EGL_EXT_platform_wayland") != NULL && + g_egl_dynProcs.eglGetPlatformDisplayEXT) + { + DEBUG_INFO("Using eglGetPlatformDisplayEXT"); + return g_egl_dynProcs.eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, native, NULL); + } + + DEBUG_INFO("Using eglGetDisplay"); + return eglGetDisplay(native); +} + +void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface) +{ + eglSwapBuffers(display, surface); + + if (wlWm.resizeSerial) + { + wl_egl_window_resize(wlWm.eglWindow, wlWm.width, wlWm.height, 0, 0); + + struct wl_region * region = wl_compositor_create_region(wlWm.compositor); + wl_region_add(region, 0, 0, wlWm.width, wlWm.height); + wl_surface_set_opaque_region(wlWm.surface, region); + wl_region_destroy(region); + + app_handleResizeEvent(wlWm.width, wlWm.height, (struct Border) {0, 0, 0, 0}); + xdg_surface_ack_configure(wlWm.xdgSurface, wlWm.resizeSerial); + wlWm.resizeSerial = 0; + } +} +#endif + +#ifdef ENABLE_EGL +EGLNativeWindowType waylandGetEGLNativeWindow(void) +{ + return (EGLNativeWindowType) wlWm.eglWindow; +} +#endif + +#ifdef ENABLE_OPENGL +bool waylandOpenGLInit(void) +{ + EGLint attr[] = + { + EGL_BUFFER_SIZE , 24, + EGL_CONFORMANT , EGL_OPENGL_BIT, + EGL_RENDERABLE_TYPE , EGL_OPENGL_BIT, + EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, + EGL_RED_SIZE , 8, + EGL_GREEN_SIZE , 8, + EGL_BLUE_SIZE , 8, + EGL_SAMPLE_BUFFERS , 0, + EGL_SAMPLES , 0, + EGL_NONE + }; + + wlWm.glDisplay = waylandGetEGLDisplay(); + + int maj, min; + if (!eglInitialize(wlWm.glDisplay, &maj, &min)) + { + DEBUG_ERROR("Unable to initialize EGL"); + return false; + } + + if (wlWm.glDisplay == EGL_NO_DISPLAY) + { + DEBUG_ERROR("Failed to get EGL display (eglError: 0x%x)", eglGetError()); + return false; + } + + EGLint num_config; + if (!eglChooseConfig(wlWm.glDisplay, attr, &wlWm.glConfig, 1, &num_config)) + { + DEBUG_ERROR("Failed to choose config (eglError: 0x%x)", eglGetError()); + return false; + } + + wlWm.glSurface = eglCreateWindowSurface(wlWm.glDisplay, wlWm.glConfig, wlWm.eglWindow, NULL); + if (wlWm.glSurface == EGL_NO_SURFACE) + { + DEBUG_ERROR("Failed to create EGL surface (eglError: 0x%x)", eglGetError()); + return false; + } + + return true; +} + +LG_DSGLContext waylandGLCreateContext(void) +{ + eglBindAPI(EGL_OPENGL_API); + return eglCreateContext(wlWm.glDisplay, wlWm.glConfig, EGL_NO_CONTEXT, NULL); +} + +void waylandGLDeleteContext(LG_DSGLContext context) +{ + eglDestroyContext(wlWm.glDisplay, context); +} + +void waylandGLMakeCurrent(LG_DSGLContext context) +{ + eglMakeCurrent(wlWm.glDisplay, wlWm.glSurface, wlWm.glSurface, context); +} + +void waylandGLSetSwapInterval(int interval) +{ + eglSwapInterval(wlWm.glDisplay, interval); +} + +void waylandGLSwapBuffers(void) +{ + waylandEGLSwapBuffers(wlWm.glDisplay, wlWm.glSurface); +} +#endif diff --git a/client/displayservers/Wayland/idle.c b/client/displayservers/Wayland/idle.c new file mode 100644 index 00000000..7baebb55 --- /dev/null +++ b/client/displayservers/Wayland/idle.c @@ -0,0 +1,59 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com) +Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca) +https://looking-glass.io + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "wayland.h" + +#include +#include + +#include "common/debug.h" + +bool waylandIdleInit(void) +{ + if (!wlWm.idleInhibitManager) + DEBUG_WARN("zwp_idle_inhibit_manager_v1 not exported by compositor, will " + "not be able to suppress idle states"); + return true; +} + +void waylandIdleFree(void) +{ + if (wlWm.idleInhibitManager) + { + waylandUninhibitIdle(); + zwp_idle_inhibit_manager_v1_destroy(wlWm.idleInhibitManager); + } +} + +void waylandInhibitIdle(void) +{ + if (wlWm.idleInhibitManager && !wlWm.idleInhibitor) + wlWm.idleInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor( + wlWm.idleInhibitManager, wlWm.surface); +} + +void waylandUninhibitIdle(void) +{ + if (wlWm.idleInhibitor) + { + zwp_idle_inhibitor_v1_destroy(wlWm.idleInhibitor); + wlWm.idleInhibitor = NULL; + } +} diff --git a/client/displayservers/Wayland/input.c b/client/displayservers/Wayland/input.c new file mode 100644 index 00000000..714f8d79 --- /dev/null +++ b/client/displayservers/Wayland/input.c @@ -0,0 +1,393 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com) +Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca) +https://looking-glass.io + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "wayland.h" + +#include +#include +#include + +#include + +#include "app.h" +#include "common/debug.h" + +// Mouse-handling listeners. + +static void pointerMotionHandler(void * data, struct wl_pointer * pointer, + uint32_t serial, wl_fixed_t sxW, wl_fixed_t syW) +{ + wlWm.cursorX = wl_fixed_to_double(sxW); + wlWm.cursorY = wl_fixed_to_double(syW); + app_updateCursorPos(wlWm.cursorX, wlWm.cursorY); + + if (!wlWm.warpSupport && !wlWm.relativePointer) + app_handleMouseBasic(); +} + +static void pointerEnterHandler(void * data, struct wl_pointer * pointer, + uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW, + wl_fixed_t syW) +{ + app_handleEnterEvent(true); + + wl_pointer_set_cursor(pointer, serial, wlWm.showPointer ? wlWm.cursor : NULL, 0, 0); + wlWm.pointerEnterSerial = serial; + + wlWm.cursorX = wl_fixed_to_double(sxW); + wlWm.cursorY = wl_fixed_to_double(syW); + app_updateCursorPos(wlWm.cursorX, wlWm.cursorY); + + if (wlWm.warpSupport) + { + app_handleMouseRelative(0.0, 0.0, 0.0, 0.0); + return; + } + + if (wlWm.relativePointer) + return; + + app_resyncMouseBasic(); + app_handleMouseBasic(); +} + +static void pointerLeaveHandler(void * data, struct wl_pointer * pointer, + uint32_t serial, struct wl_surface * surface) +{ + app_handleEnterEvent(false); +} + +static void pointerAxisHandler(void * data, struct wl_pointer * pointer, + uint32_t serial, uint32_t axis, wl_fixed_t value) +{ + int button = value > 0 ? + 5 /* SPICE_MOUSE_BUTTON_DOWN */ : + 4 /* SPICE_MOUSE_BUTTON_UP */; + app_handleButtonPress(button); + app_handleButtonRelease(button); +} + +static int mapWaylandToSpiceButton(uint32_t button) +{ + switch (button) + { + case BTN_LEFT: + return 1; // SPICE_MOUSE_BUTTON_LEFT + case BTN_MIDDLE: + return 2; // SPICE_MOUSE_BUTTON_MIDDLE + case BTN_RIGHT: + return 3; // SPICE_MOUSE_BUTTON_RIGHT + case BTN_SIDE: + return 6; // SPICE_MOUSE_BUTTON_SIDE + case BTN_EXTRA: + return 7; // SPICE_MOUSE_BUTTON_EXTRA + } + + return 0; // SPICE_MOUSE_BUTTON_INVALID +} + +static void pointerButtonHandler(void *data, struct wl_pointer *pointer, + uint32_t serial, uint32_t time, uint32_t button, uint32_t stateW) +{ + button = mapWaylandToSpiceButton(button); + + if (stateW == WL_POINTER_BUTTON_STATE_PRESSED) + app_handleButtonPress(button); + else + app_handleButtonRelease(button); +} + +static const struct wl_pointer_listener pointerListener = { + .enter = pointerEnterHandler, + .leave = pointerLeaveHandler, + .motion = pointerMotionHandler, + .button = pointerButtonHandler, + .axis = pointerAxisHandler, +}; + +static void relativePointerMotionHandler(void * data, + struct zwp_relative_pointer_v1 *pointer, uint32_t timeHi, uint32_t timeLo, + wl_fixed_t dxW, wl_fixed_t dyW, wl_fixed_t dxUnaccelW, + wl_fixed_t dyUnaccelW) +{ + wlWm.cursorX += wl_fixed_to_double(dxW); + wlWm.cursorY += wl_fixed_to_double(dyW); + app_updateCursorPos(wlWm.cursorX, wlWm.cursorY); + + app_handleMouseRelative( + wl_fixed_to_double(dxW), + wl_fixed_to_double(dyW), + wl_fixed_to_double(dxUnaccelW), + wl_fixed_to_double(dyUnaccelW)); +} + +static const struct zwp_relative_pointer_v1_listener relativePointerListener = { + .relative_motion = relativePointerMotionHandler, +}; + +// Keyboard-handling listeners. + +static void keyboardKeymapHandler(void * data, struct wl_keyboard * keyboard, + uint32_t format, int fd, uint32_t size) +{ + close(fd); +} + +static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard, + uint32_t serial, struct wl_surface * surface, struct wl_array * keys) +{ + app_handleFocusEvent(true); + wlWm.keyboardEnterSerial = serial; + + uint32_t * key; + wl_array_for_each(key, keys) + app_handleKeyPress(*key); +} + +static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard, + uint32_t serial, struct wl_surface * surface) +{ + app_handleFocusEvent(false); +} + +static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard, + uint32_t serial, uint32_t time, uint32_t key, uint32_t state) +{ + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) + app_handleKeyPress(key); + else + app_handleKeyRelease(key); +} + +static void keyboardModifiersHandler(void * data, + struct wl_keyboard * keyboard, uint32_t serial, uint32_t modsDepressed, + uint32_t modsLatched, uint32_t modsLocked, uint32_t group) +{ + // Do nothing. +} + +static const struct wl_keyboard_listener keyboardListener = { + .keymap = keyboardKeymapHandler, + .enter = keyboardEnterHandler, + .leave = keyboardLeaveHandler, + .key = keyboardKeyHandler, + .modifiers = keyboardModifiersHandler, +}; + +// Seat-handling listeners. + +static void handlePointerCapability(uint32_t capabilities) +{ + bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER; + if (!hasPointer && wlWm.pointer) + { + wl_pointer_destroy(wlWm.pointer); + wlWm.pointer = NULL; + } + else if (hasPointer && !wlWm.pointer) + { + wlWm.pointer = wl_seat_get_pointer(wlWm.seat); + wl_pointer_add_listener(wlWm.pointer, &pointerListener, NULL); + } +} + +static void handleKeyboardCapability(uint32_t capabilities) +{ + bool hasKeyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; + if (!hasKeyboard && wlWm.keyboard) + { + wl_keyboard_destroy(wlWm.keyboard); + wlWm.keyboard = NULL; + } + else if (hasKeyboard && !wlWm.keyboard) + { + wlWm.keyboard = wl_seat_get_keyboard(wlWm.seat); + wl_keyboard_add_listener(wlWm.keyboard, &keyboardListener, NULL); + } +} + +static void seatCapabilitiesHandler(void * data, struct wl_seat * seat, + uint32_t capabilities) +{ + wlWm.capabilities = capabilities; + handlePointerCapability(capabilities); + handleKeyboardCapability(capabilities); +} + +static void seatNameHandler(void * data, struct wl_seat * seat, + const char * name) +{ + // Do nothing. +} + +static const struct wl_seat_listener seatListener = { + .capabilities = seatCapabilitiesHandler, + .name = seatNameHandler, +}; + +bool waylandInputInit(void) +{ + if (!wlWm.seat) + { + DEBUG_ERROR("Compositor missing wl_seat, will not proceed"); + return false; + } + + if (wlWm.warpSupport && (!wlWm.relativePointerManager || !wlWm.pointerConstraints)) + { + DEBUG_WARN("Cursor warp is requested, but cannot be honoured due to lack " + "of zwp_relative_pointer_manager_v1 or zwp_pointer_constraints_v1"); + wlWm.warpSupport = false; + } + + if (!wlWm.relativePointerManager) + DEBUG_WARN("zwp_relative_pointer_manager_v1 not exported by compositor, " + "mouse will not be captured"); + + if (!wlWm.pointerConstraints) + DEBUG_WARN("zwp_pointer_constraints_v1 not exported by compositor, mouse " + "will not be captured"); + + if (!wlWm.keyboardInhibitManager) + DEBUG_WARN("zwp_keyboard_shortcuts_inhibit_manager_v1 not exported by " + "compositor, keyboard will not be grabbed"); + + wl_seat_add_listener(wlWm.seat, &seatListener, NULL); + wl_display_roundtrip(wlWm.display); + + if (wlWm.warpSupport) + { + wlWm.relativePointer = + zwp_relative_pointer_manager_v1_get_relative_pointer( + wlWm.relativePointerManager, wlWm.pointer); + zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, + &relativePointerListener, NULL); + } + + return true; +} + +void waylandInputFree(void) +{ + waylandUngrabPointer(); + wl_pointer_destroy(wlWm.pointer); + wl_keyboard_destroy(wlWm.keyboard); + wl_seat_destroy(wlWm.seat); +} + +void waylandGrabPointer(void) +{ + if (!wlWm.relativePointerManager || !wlWm.pointerConstraints) + return; + + if (!wlWm.warpSupport && !wlWm.relativePointer) + { + wlWm.relativePointer = + zwp_relative_pointer_manager_v1_get_relative_pointer( + wlWm.relativePointerManager, wlWm.pointer); + zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, + &relativePointerListener, NULL); + } + + if (!wlWm.confinedPointer) + { + wlWm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer( + wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, NULL, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + } +} + +void waylandUngrabPointer(void) +{ + if (wlWm.confinedPointer) + { + zwp_confined_pointer_v1_destroy(wlWm.confinedPointer); + wlWm.confinedPointer = NULL; + } + + if (!wlWm.warpSupport) + { + if (!wlWm.relativePointer) + { + wlWm.relativePointer = + zwp_relative_pointer_manager_v1_get_relative_pointer( + wlWm.relativePointerManager, wlWm.pointer); + zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, + &relativePointerListener, NULL); + } + + app_resyncMouseBasic(); + app_handleMouseBasic(); + } +} + +void waylandGrabKeyboard(void) +{ + if (wlWm.keyboardInhibitManager && !wlWm.keyboardInhibitor) + { + wlWm.keyboardInhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts( + wlWm.keyboardInhibitManager, wlWm.surface, wlWm.seat); + } +} + +void waylandUngrabKeyboard(void) +{ + if (wlWm.keyboardInhibitor) + { + zwp_keyboard_shortcuts_inhibitor_v1_destroy(wlWm.keyboardInhibitor); + wlWm.keyboardInhibitor = NULL; + } +} + +void waylandWarpPointer(int x, int y, bool exiting) +{ + if (x < 0) x = 0; + else if (x >= wlWm.width) x = wlWm.width - 1; + if (y < 0) y = 0; + else if (y >= wlWm.height) y = wlWm.height - 1; + + struct wl_region * region = wl_compositor_create_region(wlWm.compositor); + wl_region_add(region, x, y, 1, 1); + + if (wlWm.confinedPointer) + { + zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, region); + wl_surface_commit(wlWm.surface); + zwp_confined_pointer_v1_set_region(wlWm.confinedPointer, NULL); + } + else + { + struct zwp_confined_pointer_v1 * confine; + confine = zwp_pointer_constraints_v1_confine_pointer( + wlWm.pointerConstraints, wlWm.surface, wlWm.pointer, region, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + wl_surface_commit(wlWm.surface); + zwp_confined_pointer_v1_destroy(confine); + } + + wl_surface_commit(wlWm.surface); + wl_region_destroy(region); +} + +void waylandRealignPointer(void) +{ + if (!wlWm.warpSupport) + app_resyncMouseBasic(); +} diff --git a/client/displayservers/Wayland/poll.c b/client/displayservers/Wayland/poll.c new file mode 100644 index 00000000..b1b8f1bd --- /dev/null +++ b/client/displayservers/Wayland/poll.c @@ -0,0 +1,177 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com) +Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca) +https://looking-glass.io + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "wayland.h" + +#include +#include + +#include +#include +#include + +#include "common/debug.h" +#include "common/locking.h" + +#define EPOLL_EVENTS 10 // Maximum number of fds we can process at once in waylandWait + +static void waylandDisplayCallback(uint32_t events, void * opaque) +{ + if (events & EPOLLERR) + wl_display_cancel_read(wlWm.display); + else + wl_display_read_events(wlWm.display); + wl_display_dispatch_pending(wlWm.display); +} + +bool waylandPollInit(void) +{ + wlWm.epollFd = epoll_create1(EPOLL_CLOEXEC); + if (wlWm.epollFd < 0) + { + DEBUG_ERROR("Failed to initialize epoll: %s", strerror(errno)); + return false; + } + + wl_list_init(&wlWm.poll); + wl_list_init(&wlWm.pollFree); + LG_LOCK_INIT(wlWm.pollLock); + LG_LOCK_INIT(wlWm.pollFreeLock); + + wlWm.displayFd = wl_display_get_fd(wlWm.display); + if (!waylandEpollRegister(wlWm.displayFd, waylandDisplayCallback, NULL, EPOLLIN)) + { + DEBUG_ERROR("Failed register display to epoll: %s", strerror(errno)); + return false; + } + + return true; +} + +void waylandWait(unsigned int time) +{ + while (wl_display_prepare_read(wlWm.display)) + wl_display_dispatch_pending(wlWm.display); + wl_display_flush(wlWm.display); + + struct epoll_event events[EPOLL_EVENTS]; + int count; + if ((count = epoll_wait(wlWm.epollFd, events, EPOLL_EVENTS, time)) < 0) + { + if (errno != EINTR) + DEBUG_INFO("epoll failed: %s", strerror(errno)); + wl_display_cancel_read(wlWm.display); + return; + } + + bool sawDisplay = false; + for (int i = 0; i < count; ++i) { + struct WaylandPoll * poll = events[i].data.ptr; + if (!poll->removed) + poll->callback(events[i].events, poll->opaque); + if (poll->fd == wlWm.displayFd) + sawDisplay = true; + } + + if (!sawDisplay) + wl_display_cancel_read(wlWm.display); + + INTERLOCKED_SECTION(wlWm.pollFreeLock, + { + struct WaylandPoll * node; + struct WaylandPoll * temp; + wl_list_for_each_safe(node, temp, &wlWm.pollFree, link) + { + wl_list_remove(&node->link); + free(node); + } + }); +} + +static void waylandEpollRemoveNode(struct WaylandPoll * node) +{ + INTERLOCKED_SECTION(wlWm.pollLock, + { + wl_list_remove(&node->link); + }); +} + +bool waylandEpollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events) +{ + struct WaylandPoll * node = malloc(sizeof(struct WaylandPoll)); + if (!node) + return false; + + node->fd = fd; + node->removed = false; + node->callback = callback; + node->opaque = opaque; + + INTERLOCKED_SECTION(wlWm.pollLock, + { + wl_list_insert(&wlWm.poll, &node->link); + }); + + if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_ADD, fd, &(struct epoll_event) { + .events = events, + .data = (epoll_data_t) { .ptr = node }, + }) < 0) + { + waylandEpollRemoveNode(node); + free(node); + return false; + } + + return true; +} + +bool waylandEpollUnregister(int fd) +{ + struct WaylandPoll * node = NULL; + INTERLOCKED_SECTION(wlWm.pollLock, + { + wl_list_for_each(node, &wlWm.poll, link) + { + if (node->fd == fd) + break; + } + }); + + if (!node) + { + DEBUG_ERROR("Attempt to unregister a fd that was not registered: %d", fd); + return false; + } + + node->removed = true; + if (epoll_ctl(wlWm.epollFd, EPOLL_CTL_DEL, fd, NULL) < 0) + { + DEBUG_ERROR("Failed to unregistered from epoll: %s", strerror(errno)); + return false; + } + + waylandEpollRemoveNode(node); + + INTERLOCKED_SECTION(wlWm.pollFreeLock, + { + wl_list_insert(&wlWm.pollFree, &node->link); + }); + return true; +} diff --git a/client/displayservers/Wayland/registry.c b/client/displayservers/Wayland/registry.c new file mode 100644 index 00000000..1de13c18 --- /dev/null +++ b/client/displayservers/Wayland/registry.c @@ -0,0 +1,89 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com) +Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca) +https://looking-glass.io + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "wayland.h" + +#include +#include + +#include + +#include "common/debug.h" + +static void registryGlobalHandler(void * data, struct wl_registry * registry, + uint32_t name, const char * interface, uint32_t version) +{ + if (!strcmp(interface, wl_seat_interface.name) && !wlWm.seat) + wlWm.seat = wl_registry_bind(wlWm.registry, name, &wl_seat_interface, 1); + else if (!strcmp(interface, wl_shm_interface.name)) + wlWm.shm = wl_registry_bind(wlWm.registry, name, &wl_shm_interface, 1); + else if (!strcmp(interface, wl_compositor_interface.name)) + wlWm.compositor = wl_registry_bind(wlWm.registry, name, &wl_compositor_interface, 4); + else if (!strcmp(interface, xdg_wm_base_interface.name)) + wlWm.xdgWmBase = wl_registry_bind(wlWm.registry, name, &xdg_wm_base_interface, 1); + else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name)) + wlWm.xdgDecorationManager = wl_registry_bind(wlWm.registry, name, + &zxdg_decoration_manager_v1_interface, 1); + else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name)) + wlWm.relativePointerManager = wl_registry_bind(wlWm.registry, name, + &zwp_relative_pointer_manager_v1_interface, 1); + else if (!strcmp(interface, zwp_pointer_constraints_v1_interface.name)) + wlWm.pointerConstraints = wl_registry_bind(wlWm.registry, name, + &zwp_pointer_constraints_v1_interface, 1); + else if (!strcmp(interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name)) + wlWm.keyboardInhibitManager = wl_registry_bind(wlWm.registry, name, + &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1); + else if (!strcmp(interface, wl_data_device_manager_interface.name)) + wlWm.dataDeviceManager = wl_registry_bind(wlWm.registry, name, + &wl_data_device_manager_interface, 3); + else if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name)) + wlWm.idleInhibitManager = wl_registry_bind(wlWm.registry, name, + &zwp_idle_inhibit_manager_v1_interface, 1); +} + +static void registryGlobalRemoveHandler(void * data, + struct wl_registry * registry, uint32_t name) +{ + // Do nothing. +} + +static const struct wl_registry_listener registryListener = { + .global = registryGlobalHandler, + .global_remove = registryGlobalRemoveHandler, +}; + +bool waylandRegistryInit(void) +{ + wlWm.registry = wl_display_get_registry(wlWm.display); + if (!wlWm.registry) + { + DEBUG_ERROR("Unable to find wl_registry"); + return false; + } + + wl_registry_add_listener(wlWm.registry, ®istryListener, NULL); + wl_display_roundtrip(wlWm.display); + return true; +} + +void waylandRegistryFree(void) +{ + wl_registry_destroy(wlWm.registry); +} diff --git a/client/displayservers/Wayland/state.c b/client/displayservers/Wayland/state.c new file mode 100644 index 00000000..799174a4 --- /dev/null +++ b/client/displayservers/Wayland/state.c @@ -0,0 +1,24 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com) +Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca) +https://looking-glass.io + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "wayland.h" + +struct WaylandDSState wlWm; +struct WCBState wlCb; diff --git a/client/displayservers/Wayland/wayland.c b/client/displayservers/Wayland/wayland.c index 83d053d0..7bdb7047 100644 --- a/client/displayservers/Wayland/wayland.c +++ b/client/displayservers/Wayland/wayland.c @@ -18,6 +18,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA */ #define _GNU_SOURCE +#include "wayland.h" + #include #include #include @@ -51,122 +53,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA #include "wayland-relative-pointer-unstable-v1-client-protocol.h" #include "wayland-idle-inhibit-unstable-v1-client-protocol.h" -#define EPOLL_EVENTS 10 // Maximum number of fds we can process at once in waylandWait - -typedef void (*WaylandPollCallback)(uint32_t events, void * opaque); - -struct WaylandPoll -{ - int fd; - bool removed; - WaylandPollCallback callback; - void * opaque; - struct wl_list link; -}; - -struct WaylandDSState -{ - bool pointerGrabbed; - bool keyboardGrabbed; - - struct wl_display * display; - struct wl_surface * surface; - struct wl_registry * registry; - struct wl_seat * seat; - struct wl_shm * shm; - struct wl_compositor * compositor; - - int32_t width, height; - bool fullscreen; - uint32_t resizeSerial; - bool configured; - bool warpSupport; - double cursorX, cursorY; - -#ifdef ENABLE_EGL - struct wl_egl_window * eglWindow; -#endif - -#ifdef ENABLE_OPENGL - EGLDisplay glDisplay; - EGLConfig glConfig; - EGLSurface glSurface; -#endif - - struct xdg_wm_base * xdgWmBase; - struct xdg_surface * xdgSurface; - struct xdg_toplevel * xdgToplevel; - struct zxdg_decoration_manager_v1 * xdgDecorationManager; - struct zxdg_toplevel_decoration_v1 * xdgToplevelDecoration; - - struct wl_surface * cursor; - - struct wl_data_device_manager * dataDeviceManager; - struct wl_data_device * dataDevice; - - uint32_t capabilities; - - struct wl_keyboard * keyboard; - struct zwp_keyboard_shortcuts_inhibit_manager_v1 * keyboardInhibitManager; - struct zwp_keyboard_shortcuts_inhibitor_v1 * keyboardInhibitor; - uint32_t keyboardEnterSerial; - - struct wl_pointer * pointer; - struct zwp_relative_pointer_manager_v1 * relativePointerManager; - struct zwp_pointer_constraints_v1 * pointerConstraints; - struct zwp_relative_pointer_v1 * relativePointer; - struct zwp_confined_pointer_v1 * confinedPointer; - bool showPointer; - uint32_t pointerEnterSerial; - - struct zwp_idle_inhibit_manager_v1 * idleInhibitManager; - struct zwp_idle_inhibitor_v1 * idleInhibitor; - - struct wl_list poll; // WaylandPoll::link - struct wl_list pollFree; // WaylandPoll::link - LG_Lock pollLock; - LG_Lock pollFreeLock; - int epollFd; - int displayFd; -}; - -struct WCBTransfer -{ - struct CountedBuffer * data; - const char ** mimetypes; -}; - -struct ClipboardRead -{ - int fd; - size_t size; - size_t numRead; - uint8_t * buf; - enum LG_ClipboardData type; - struct wl_data_offer * offer; -}; - -struct WCBState -{ - char lgMimetype[64]; - - enum LG_ClipboardData pendingType; - char * pendingMimetype; - bool isSelfCopy; - - enum LG_ClipboardData stashedType; - uint8_t * stashedContents; - ssize_t stashedSize; - - bool haveRequest; - LG_ClipboardData type; - - struct ClipboardRead * currentRead; -}; - -static struct WaylandDSState wm; -static struct WCBState wcb; - static struct Option waylandOptions[] = { { @@ -179,447 +65,6 @@ static struct Option waylandOptions[] = {0} }; -static const uint32_t cursorBitmap[] = { - 0x000000, 0x000000, 0x000000, 0x000000, - 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, - 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, - 0x000000, 0x000000, 0x000000, 0x000000, -}; - -static struct wl_buffer * createCursorBuffer(void) -{ - int fd = memfd_create("lg-cursor", 0); - if (fd < 0) - { - DEBUG_ERROR("Failed to create cursor shared memory: %d", errno); - return NULL; - } - - struct wl_buffer * result = NULL; - - if (ftruncate(fd, sizeof cursorBitmap) < 0) - { - DEBUG_ERROR("Failed to ftruncate cursor shared memory: %d", errno); - goto fail; - } - - void * shm_data = mmap(NULL, sizeof cursorBitmap, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (shm_data == MAP_FAILED) - { - DEBUG_ERROR("Failed to map memory for cursor: %d", errno); - goto fail; - } - - struct wl_shm_pool * pool = wl_shm_create_pool(wm.shm, fd, sizeof cursorBitmap); - result = wl_shm_pool_create_buffer(pool, 0, 4, 4, 16, WL_SHM_FORMAT_XRGB8888); - wl_shm_pool_destroy(pool); - - memcpy(shm_data, cursorBitmap, sizeof cursorBitmap); - munmap(shm_data, sizeof cursorBitmap); - -fail: - close(fd); - return result; -} - -// XDG WM base listeners. - -static void xdgWmBasePing(void * data, struct xdg_wm_base * xdgWmBase, uint32_t serial) -{ - xdg_wm_base_pong(xdgWmBase, serial); -} - -static const struct xdg_wm_base_listener xdgWmBaseListener = { - .ping = xdgWmBasePing, -}; - -// Registry-handling listeners. - -static void registryGlobalHandler(void * data, struct wl_registry * registry, - uint32_t name, const char * interface, uint32_t version) -{ - if (!strcmp(interface, wl_seat_interface.name) && !wm.seat) - wm.seat = wl_registry_bind(wm.registry, name, &wl_seat_interface, 1); - else if (!strcmp(interface, wl_shm_interface.name)) - wm.shm = wl_registry_bind(wm.registry, name, &wl_shm_interface, 1); - else if (!strcmp(interface, wl_compositor_interface.name)) - wm.compositor = wl_registry_bind(wm.registry, name, &wl_compositor_interface, 4); - else if (!strcmp(interface, xdg_wm_base_interface.name)) - wm.xdgWmBase = wl_registry_bind(wm.registry, name, &xdg_wm_base_interface, 1); - else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name)) - wm.xdgDecorationManager = wl_registry_bind(wm.registry, name, - &zxdg_decoration_manager_v1_interface, 1); - else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name)) - wm.relativePointerManager = wl_registry_bind(wm.registry, name, - &zwp_relative_pointer_manager_v1_interface, 1); - else if (!strcmp(interface, zwp_pointer_constraints_v1_interface.name)) - wm.pointerConstraints = wl_registry_bind(wm.registry, name, - &zwp_pointer_constraints_v1_interface, 1); - else if (!strcmp(interface, zwp_keyboard_shortcuts_inhibit_manager_v1_interface.name)) - wm.keyboardInhibitManager = wl_registry_bind(wm.registry, name, - &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1); - else if (!strcmp(interface, wl_data_device_manager_interface.name)) - wm.dataDeviceManager = wl_registry_bind(wm.registry, name, - &wl_data_device_manager_interface, 3); - else if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name)) - wm.idleInhibitManager = wl_registry_bind(wm.registry, name, - &zwp_idle_inhibit_manager_v1_interface, 1); -} - -static void registryGlobalRemoveHandler(void * data, - struct wl_registry * registry, uint32_t name) -{ - // Do nothing. -} - -static const struct wl_registry_listener registryListener = { - .global = registryGlobalHandler, - .global_remove = registryGlobalRemoveHandler, -}; - -// Mouse-handling listeners. - -static void pointerMotionHandler(void * data, struct wl_pointer * pointer, - uint32_t serial, wl_fixed_t sxW, wl_fixed_t syW) -{ - wm.cursorX = wl_fixed_to_double(sxW); - wm.cursorY = wl_fixed_to_double(syW); - app_updateCursorPos(wm.cursorX, wm.cursorY); - - if (!wm.warpSupport && !wm.relativePointer) - app_handleMouseBasic(); -} - -static void pointerEnterHandler(void * data, struct wl_pointer * pointer, - uint32_t serial, struct wl_surface * surface, wl_fixed_t sxW, - wl_fixed_t syW) -{ - app_handleEnterEvent(true); - - wl_pointer_set_cursor(pointer, serial, wm.showPointer ? wm.cursor : NULL, 0, 0); - wm.pointerEnterSerial = serial; - - wm.cursorX = wl_fixed_to_double(sxW); - wm.cursorY = wl_fixed_to_double(syW); - app_updateCursorPos(wm.cursorX, wm.cursorY); - - if (wm.warpSupport) - { - app_handleMouseRelative(0.0, 0.0, 0.0, 0.0); - return; - } - - if (wm.relativePointer) - return; - - app_resyncMouseBasic(); - app_handleMouseBasic(); -} - -static void pointerLeaveHandler(void * data, struct wl_pointer * pointer, - uint32_t serial, struct wl_surface * surface) -{ - app_handleEnterEvent(false); -} - -static void pointerAxisHandler(void * data, struct wl_pointer * pointer, - uint32_t serial, uint32_t axis, wl_fixed_t value) -{ - int button = value > 0 ? - 5 /* SPICE_MOUSE_BUTTON_DOWN */ : - 4 /* SPICE_MOUSE_BUTTON_UP */; - app_handleButtonPress(button); - app_handleButtonRelease(button); -} - -static int mapWaylandToSpiceButton(uint32_t button) -{ - switch (button) - { - case BTN_LEFT: - return 1; // SPICE_MOUSE_BUTTON_LEFT - case BTN_MIDDLE: - return 2; // SPICE_MOUSE_BUTTON_MIDDLE - case BTN_RIGHT: - return 3; // SPICE_MOUSE_BUTTON_RIGHT - case BTN_SIDE: - return 6; // SPICE_MOUSE_BUTTON_SIDE - case BTN_EXTRA: - return 7; // SPICE_MOUSE_BUTTON_EXTRA - } - - return 0; // SPICE_MOUSE_BUTTON_INVALID -} - -static void pointerButtonHandler(void *data, struct wl_pointer *pointer, - uint32_t serial, uint32_t time, uint32_t button, uint32_t stateW) -{ - button = mapWaylandToSpiceButton(button); - - if (stateW == WL_POINTER_BUTTON_STATE_PRESSED) - app_handleButtonPress(button); - else - app_handleButtonRelease(button); -} - -static const struct wl_pointer_listener pointerListener = { - .enter = pointerEnterHandler, - .leave = pointerLeaveHandler, - .motion = pointerMotionHandler, - .button = pointerButtonHandler, - .axis = pointerAxisHandler, -}; - -static void relativePointerMotionHandler(void * data, - struct zwp_relative_pointer_v1 *pointer, uint32_t timeHi, uint32_t timeLo, - wl_fixed_t dxW, wl_fixed_t dyW, wl_fixed_t dxUnaccelW, - wl_fixed_t dyUnaccelW) -{ - wm.cursorX += wl_fixed_to_double(dxW); - wm.cursorY += wl_fixed_to_double(dyW); - app_updateCursorPos(wm.cursorX, wm.cursorY); - - app_handleMouseRelative( - wl_fixed_to_double(dxW), - wl_fixed_to_double(dyW), - wl_fixed_to_double(dxUnaccelW), - wl_fixed_to_double(dyUnaccelW)); -} - -static const struct zwp_relative_pointer_v1_listener relativePointerListener = { - .relative_motion = relativePointerMotionHandler, -}; - -static void waylandInhibitIdle(void) -{ - if (wm.idleInhibitManager && !wm.idleInhibitor) - wm.idleInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor( - wm.idleInhibitManager, wm.surface); -} - -static void waylandUninhibitIdle(void) -{ - if (wm.idleInhibitor) - { - zwp_idle_inhibitor_v1_destroy(wm.idleInhibitor); - wm.idleInhibitor = NULL; - } -} - -// Keyboard-handling listeners. - -static void keyboardKeymapHandler(void * data, struct wl_keyboard * keyboard, - uint32_t format, int fd, uint32_t size) -{ - close(fd); -} - -static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard, - uint32_t serial, struct wl_surface * surface, struct wl_array * keys) -{ - app_handleFocusEvent(true); - wm.keyboardEnterSerial = serial; - - uint32_t * key; - wl_array_for_each(key, keys) - app_handleKeyPress(*key); -} - -static void keyboardLeaveHandler(void * data, struct wl_keyboard * keyboard, - uint32_t serial, struct wl_surface * surface) -{ - app_handleFocusEvent(false); -} - -static void keyboardKeyHandler(void * data, struct wl_keyboard * keyboard, - uint32_t serial, uint32_t time, uint32_t key, uint32_t state) -{ - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) - app_handleKeyPress(key); - else - app_handleKeyRelease(key); -} - -static void keyboardModifiersHandler(void * data, - struct wl_keyboard * keyboard, uint32_t serial, uint32_t modsDepressed, - uint32_t modsLatched, uint32_t modsLocked, uint32_t group) -{ - // Do nothing. -} - -static const struct wl_keyboard_listener keyboardListener = { - .keymap = keyboardKeymapHandler, - .enter = keyboardEnterHandler, - .leave = keyboardLeaveHandler, - .key = keyboardKeyHandler, - .modifiers = keyboardModifiersHandler, -}; - -// Seat-handling listeners. - -static void handlePointerCapability(uint32_t capabilities) -{ - bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER; - if (!hasPointer && wm.pointer) - { - wl_pointer_destroy(wm.pointer); - wm.pointer = NULL; - } - else if (hasPointer && !wm.pointer) - { - wm.pointer = wl_seat_get_pointer(wm.seat); - wl_pointer_add_listener(wm.pointer, &pointerListener, NULL); - } -} - -static void handleKeyboardCapability(uint32_t capabilities) -{ - bool hasKeyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; - if (!hasKeyboard && wm.keyboard) - { - wl_keyboard_destroy(wm.keyboard); - wm.keyboard = NULL; - } - else if (hasKeyboard && !wm.keyboard) - { - wm.keyboard = wl_seat_get_keyboard(wm.seat); - wl_keyboard_add_listener(wm.keyboard, &keyboardListener, NULL); - } -} - -static void seatCapabilitiesHandler(void * data, struct wl_seat * seat, - uint32_t capabilities) -{ - wm.capabilities = capabilities; - handlePointerCapability(capabilities); - handleKeyboardCapability(capabilities); -} - -static void seatNameHandler(void * data, struct wl_seat * seat, - const char * name) -{ - // Do nothing. -} - -static const struct wl_seat_listener seatListener = { - .capabilities = seatCapabilitiesHandler, - .name = seatNameHandler, -}; - -// Surface-handling listeners. - -static void xdgSurfaceConfigure(void * data, struct xdg_surface * xdgSurface, - uint32_t serial) -{ - if (wm.configured) - wm.resizeSerial = serial; - else - { - xdg_surface_ack_configure(xdgSurface, serial); - wm.configured = true; - } -} - -static const struct xdg_surface_listener xdgSurfaceListener = { - .configure = xdgSurfaceConfigure, -}; - -static void xdgToplevelConfigure(void * data, struct xdg_toplevel * xdgToplevel, - int32_t width, int32_t height, struct wl_array * states) -{ - wm.width = width; - wm.height = height; - wm.fullscreen = false; - - enum xdg_toplevel_state * state; - wl_array_for_each(state, states) - { - if (*state == XDG_TOPLEVEL_STATE_FULLSCREEN) - wm.fullscreen = true; - } -} - -static void xdgToplevelClose(void * data, struct xdg_toplevel * xdgToplevel) -{ - app_handleCloseEvent(); -} - -static void waylandEpollRemoveNode(struct WaylandPoll * node) -{ - INTERLOCKED_SECTION(wm.pollLock, - { - wl_list_remove(&node->link); - }); -} - -static bool waylandEpollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events) -{ - struct WaylandPoll * node = malloc(sizeof(struct WaylandPoll)); - if (!node) - return false; - - node->fd = fd; - node->removed = false; - node->callback = callback; - node->opaque = opaque; - - INTERLOCKED_SECTION(wm.pollLock, - { - wl_list_insert(&wm.poll, &node->link); - }); - - if (epoll_ctl(wm.epollFd, EPOLL_CTL_ADD, fd, &(struct epoll_event) { - .events = events, - .data = (epoll_data_t) { .ptr = node }, - }) < 0) - { - waylandEpollRemoveNode(node); - free(node); - return false; - } - - return true; -} - -static bool waylandEpollUnregister(int fd) -{ - struct WaylandPoll * node = NULL; - INTERLOCKED_SECTION(wm.pollLock, - { - wl_list_for_each(node, &wm.poll, link) - { - if (node->fd == fd) - break; - } - }); - - if (!node) - { - DEBUG_ERROR("Attempt to unregister a fd that was not registered: %d", fd); - return false; - } - - node->removed = true; - if (epoll_ctl(wm.epollFd, EPOLL_CTL_DEL, fd, NULL) < 0) - { - DEBUG_ERROR("Failed to unregistered from epoll: %s", strerror(errno)); - return false; - } - - waylandEpollRemoveNode(node); - - INTERLOCKED_SECTION(wm.pollFreeLock, - { - wl_list_insert(&wm.pollFree, &node->link); - }); - return true; -} - -static const struct xdg_toplevel_listener xdgToplevelListener = { - .configure = xdgToplevelConfigure, - .close = xdgToplevelClose, -}; - static bool waylandEarlyInit(void) { // Request to receive EPIPE instead of SIGPIPE when one end of a pipe @@ -641,170 +86,42 @@ static bool waylandProbe(void) return getenv("WAYLAND_DISPLAY") != NULL; } -static EGLDisplay waylandGetEGLDisplay(void); -static void waylandDisplayCallback(uint32_t events, void * opaque); - static bool waylandInit(const LG_DSInitParams params) { - memset(&wm, 0, sizeof(wm)); + memset(&wlWm, 0, sizeof(wlWm)); - wm.warpSupport = option_get_bool("wayland", "warpSupport"); + wlWm.warpSupport = option_get_bool("wayland", "warpSupport"); - wm.epollFd = epoll_create1(EPOLL_CLOEXEC); - if (wm.epollFd < 0) - { - DEBUG_ERROR("Failed to initialize epoll: %s", strerror(errno)); + wlWm.display = wl_display_connect(NULL); + + if (!waylandPollInit()) return false; - } - wm.display = wl_display_connect(NULL); - wm.registry = wl_display_get_registry(wm.display); - - wl_list_init(&wm.poll); - wl_list_init(&wm.pollFree); - LG_LOCK_INIT(wm.pollLock); - LG_LOCK_INIT(wm.pollFreeLock); - wm.displayFd = wl_display_get_fd(wm.display); - if (!waylandEpollRegister(wm.displayFd, waylandDisplayCallback, NULL, EPOLLIN)) - { - DEBUG_ERROR("Failed register display to epoll: %s", strerror(errno)); + if (!waylandRegistryInit()) return false; - } - wl_registry_add_listener(wm.registry, ®istryListener, NULL); - wl_display_roundtrip(wm.display); - - if (!wm.seat || !wm.dataDeviceManager || !wm.compositor || !wm.xdgWmBase) - { - DEBUG_ERROR("Compositor missing a required interface, will not proceed"); + if (!waylandIdleInit()) return false; - } - if (!wm.relativePointerManager) - DEBUG_WARN("zwp_relative_pointer_manager_v1 not exported by compositor, " - "mouse will not be captured"); - if (!wm.pointerConstraints) - DEBUG_WARN("zwp_pointer_constraints_v1 not exported by compositor, mouse " - "will not be captured"); - if (!wm.keyboardInhibitManager) - DEBUG_WARN("zwp_keyboard_shortcuts_inhibit_manager_v1 not exported by " - "compositor, keyboard will not be grabbed"); - if (!wm.idleInhibitManager) - DEBUG_WARN("zwp_idle_inhibit_manager_v1 not exported by compositor, will " - "not be able to suppress idle states"); + if (!waylandInputInit()) + return false; - if (wm.warpSupport && (!wm.relativePointerManager || !wm.pointerConstraints)) - { - DEBUG_WARN("Cursor warp is requested, but cannot be honoured due to lack " - "of zwp_relative_pointer_manager_v1 or zwp_pointer_constraints_v1"); - wm.warpSupport = false; - } + if (!waylandWindowInit(params.title, params.fullscreen, params.maximize, params.borderless)) + return false; - wl_seat_add_listener(wm.seat, &seatListener, NULL); - xdg_wm_base_add_listener(wm.xdgWmBase, &xdgWmBaseListener, NULL); - wl_display_roundtrip(wm.display); + if (!waylandEGLInit(params.w, params.h)) + return false; - wm.dataDevice = wl_data_device_manager_get_data_device( - wm.dataDeviceManager, wm.seat); - - if (wm.warpSupport) - { - wm.relativePointer = - zwp_relative_pointer_manager_v1_get_relative_pointer( - wm.relativePointerManager, wm.pointer); - zwp_relative_pointer_v1_add_listener(wm.relativePointer, - &relativePointerListener, NULL); - } - - wm.surface = wl_compositor_create_surface(wm.compositor); - wm.eglWindow = wl_egl_window_create(wm.surface, params.w, params.h); - wm.xdgSurface = xdg_wm_base_get_xdg_surface(wm.xdgWmBase, wm.surface); - xdg_surface_add_listener(wm.xdgSurface, &xdgSurfaceListener, NULL); - - wm.xdgToplevel = xdg_surface_get_toplevel(wm.xdgSurface); - xdg_toplevel_add_listener(wm.xdgToplevel, &xdgToplevelListener, NULL); - xdg_toplevel_set_title(wm.xdgToplevel, params.title); - xdg_toplevel_set_app_id(wm.xdgToplevel, "looking-glass-client"); - - if (params.fullscreen) - xdg_toplevel_set_fullscreen(wm.xdgToplevel, NULL); - - if (params.maximize) - xdg_toplevel_set_maximized(wm.xdgToplevel); - - wl_surface_commit(wm.surface); - - if (wm.xdgDecorationManager) - { - wm.xdgToplevelDecoration = zxdg_decoration_manager_v1_get_toplevel_decoration( - wm.xdgDecorationManager, wm.xdgToplevel); - if (wm.xdgToplevelDecoration) - { - zxdg_toplevel_decoration_v1_set_mode(wm.xdgToplevelDecoration, - params.borderless ? - ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE : - ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); - } - } - - struct wl_buffer * cursorBuffer = createCursorBuffer(); - if (cursorBuffer) - { - wm.cursor = wl_compositor_create_surface(wm.compositor); - wl_surface_attach(wm.cursor, cursorBuffer, 0, 0); - wl_surface_commit(wm.cursor); - } + if (!waylandCursorInit()) + return false; #ifdef ENABLE_OPENGL - if (params.opengl) - { - EGLint attr[] = - { - EGL_BUFFER_SIZE , 24, - EGL_CONFORMANT , EGL_OPENGL_BIT, - EGL_RENDERABLE_TYPE , EGL_OPENGL_BIT, - EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, - EGL_RED_SIZE , 8, - EGL_GREEN_SIZE , 8, - EGL_BLUE_SIZE , 8, - EGL_SAMPLE_BUFFERS , 0, - EGL_SAMPLES , 0, - EGL_NONE - }; - - wm.glDisplay = waylandGetEGLDisplay(); - - int maj, min; - if (!eglInitialize(wm.glDisplay, &maj, &min)) - { - DEBUG_ERROR("Unable to initialize EGL"); - return false; - } - - if (wm.glDisplay == EGL_NO_DISPLAY) - { - DEBUG_ERROR("Failed to get EGL display (eglError: 0x%x)", eglGetError()); - return false; - } - - EGLint num_config; - if (!eglChooseConfig(wm.glDisplay, attr, &wm.glConfig, 1, &num_config)) - { - DEBUG_ERROR("Failed to choose config (eglError: 0x%x)", eglGetError()); - return false; - } - - wm.glSurface = eglCreateWindowSurface(wm.glDisplay, wm.glConfig, wm.eglWindow, NULL); - if (wm.glSurface == EGL_NO_SURFACE) - { - DEBUG_ERROR("Failed to create EGL surface (eglError: 0x%x)", eglGetError()); - return false; - } - } + if (params.opengl && !waylandOpenGLInit()) + return false; #endif - wm.width = params.w; - wm.height = params.h; + wlWm.width = params.w; + wlWm.height = params.h; return true; } @@ -817,721 +134,26 @@ static void waylandShutdown(void) { } -#ifdef ENABLE_EGL -static EGLNativeWindowType waylandGetEGLNativeWindow(void) -{ - return (EGLNativeWindowType) wm.eglWindow; -} -#endif - -#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL) -static EGLDisplay waylandGetEGLDisplay(void) -{ - EGLNativeDisplayType native = (EGLNativeDisplayType) wm.display; - - const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS); - - if (strstr(early_exts, "EGL_KHR_platform_wayland") != NULL && - g_egl_dynProcs.eglGetPlatformDisplay) - { - DEBUG_INFO("Using eglGetPlatformDisplay"); - return g_egl_dynProcs.eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, native, NULL); - } - - if (strstr(early_exts, "EGL_EXT_platform_wayland") != NULL && - g_egl_dynProcs.eglGetPlatformDisplayEXT) - { - DEBUG_INFO("Using eglGetPlatformDisplayEXT"); - return g_egl_dynProcs.eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, native, NULL); - } - - DEBUG_INFO("Using eglGetDisplay"); - return eglGetDisplay(native); -} - -static void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface) -{ - eglSwapBuffers(display, surface); - - if (wm.resizeSerial) - { - wl_egl_window_resize(wm.eglWindow, wm.width, wm.height, 0, 0); - - struct wl_region * region = wl_compositor_create_region(wm.compositor); - wl_region_add(region, 0, 0, wm.width, wm.height); - wl_surface_set_opaque_region(wm.surface, region); - wl_region_destroy(region); - - app_handleResizeEvent(wm.width, wm.height, (struct Border) {0, 0, 0, 0}); - xdg_surface_ack_configure(wm.xdgSurface, wm.resizeSerial); - wm.resizeSerial = 0; - } -} -#endif - -#ifdef ENABLE_OPENGL -static LG_DSGLContext waylandGLCreateContext(void) -{ - eglBindAPI(EGL_OPENGL_API); - return eglCreateContext(wm.glDisplay, wm.glConfig, EGL_NO_CONTEXT, NULL); -} - -static void waylandGLDeleteContext(LG_DSGLContext context) -{ - eglDestroyContext(wm.glDisplay, context); -} - -static void waylandGLMakeCurrent(LG_DSGLContext context) -{ - eglMakeCurrent(wm.glDisplay, wm.glSurface, wm.glSurface, context); -} - -static void waylandGLSetSwapInterval(int interval) -{ - eglSwapInterval(wm.glDisplay, interval); -} - -static void waylandGLSwapBuffers(void) -{ - waylandEGLSwapBuffers(wm.glDisplay, wm.glSurface); -} -#endif - -static void waylandShowPointer(bool show) -{ - wm.showPointer = show; - wl_pointer_set_cursor(wm.pointer, wm.pointerEnterSerial, show ? wm.cursor : NULL, 0, 0); -} - -static void waylandWait(unsigned int time) -{ - while (wl_display_prepare_read(wm.display)) - wl_display_dispatch_pending(wm.display); - wl_display_flush(wm.display); - - struct epoll_event events[EPOLL_EVENTS]; - int count; - if ((count = epoll_wait(wm.epollFd, events, EPOLL_EVENTS, time)) < 0) - { - if (errno != EINTR) - DEBUG_INFO("epoll failed: %s", strerror(errno)); - wl_display_cancel_read(wm.display); - return; - } - - bool sawDisplay = false; - for (int i = 0; i < count; ++i) { - struct WaylandPoll * poll = events[i].data.ptr; - if (!poll->removed) - poll->callback(events[i].events, poll->opaque); - if (poll->fd == wm.displayFd) - sawDisplay = true; - } - - if (!sawDisplay) - wl_display_cancel_read(wm.display); - - INTERLOCKED_SECTION(wm.pollFreeLock, - { - struct WaylandPoll * node; - struct WaylandPoll * temp; - wl_list_for_each_safe(node, temp, &wm.pollFree, link) - { - wl_list_remove(&node->link); - free(node); - } - }); -} - -static void waylandDisplayCallback(uint32_t events, void * opaque) -{ - if (events & EPOLLERR) - wl_display_cancel_read(wm.display); - else - wl_display_read_events(wm.display); - wl_display_dispatch_pending(wm.display); -} - -static void waylandSetWindowSize(int x, int y) -{ - // FIXME: implement. -} - -static void waylandSetFullscreen(bool fs) -{ - if (fs) - xdg_toplevel_set_fullscreen(wm.xdgToplevel, NULL); - else - xdg_toplevel_unset_fullscreen(wm.xdgToplevel); -} - -static bool waylandGetFullscreen(void) -{ - return wm.fullscreen; -} - -static void waylandGrabPointer(void) -{ - if (!wm.relativePointerManager || !wm.pointerConstraints) - return; - - if (!wm.warpSupport && !wm.relativePointer) - { - wm.relativePointer = - zwp_relative_pointer_manager_v1_get_relative_pointer( - wm.relativePointerManager, wm.pointer); - zwp_relative_pointer_v1_add_listener(wm.relativePointer, - &relativePointerListener, NULL); - } - - if (!wm.confinedPointer) - { - wm.confinedPointer = zwp_pointer_constraints_v1_confine_pointer( - wm.pointerConstraints, wm.surface, wm.pointer, NULL, - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); - } -} - -static void waylandUngrabPointer(void) -{ - if (wm.confinedPointer) - { - zwp_confined_pointer_v1_destroy(wm.confinedPointer); - wm.confinedPointer = NULL; - } - - if (!wm.warpSupport) - { - if (!wm.relativePointer) - { - wm.relativePointer = - zwp_relative_pointer_manager_v1_get_relative_pointer( - wm.relativePointerManager, wm.pointer); - zwp_relative_pointer_v1_add_listener(wm.relativePointer, - &relativePointerListener, NULL); - } - - app_resyncMouseBasic(); - app_handleMouseBasic(); - } -} - -static void waylandGrabKeyboard(void) -{ - if (wm.keyboardInhibitManager && !wm.keyboardInhibitor) - { - wm.keyboardInhibitor = zwp_keyboard_shortcuts_inhibit_manager_v1_inhibit_shortcuts( - wm.keyboardInhibitManager, wm.surface, wm.seat); - } -} - -static void waylandUngrabKeyboard(void) -{ - if (wm.keyboardInhibitor) - { - zwp_keyboard_shortcuts_inhibitor_v1_destroy(wm.keyboardInhibitor); - wm.keyboardInhibitor = NULL; - } -} - -static void waylandWarpPointer(int x, int y, bool exiting) -{ - if (x < 0) x = 0; - else if (x >= wm.width) x = wm.width - 1; - if (y < 0) y = 0; - else if (y >= wm.height) y = wm.height - 1; - - struct wl_region * region = wl_compositor_create_region(wm.compositor); - wl_region_add(region, x, y, 1, 1); - - if (wm.confinedPointer) - { - zwp_confined_pointer_v1_set_region(wm.confinedPointer, region); - wl_surface_commit(wm.surface); - zwp_confined_pointer_v1_set_region(wm.confinedPointer, NULL); - } - else - { - struct zwp_confined_pointer_v1 * confine; - confine = zwp_pointer_constraints_v1_confine_pointer( - wm.pointerConstraints, wm.surface, wm.pointer, region, - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); - wl_surface_commit(wm.surface); - zwp_confined_pointer_v1_destroy(confine); - } - - wl_surface_commit(wm.surface); - wl_region_destroy(region); -} - -static void waylandRealignPointer(void) -{ - if (!wm.warpSupport) - app_resyncMouseBasic(); -} - -static bool waylandIsValidPointerPos(int x, int y) -{ - return x >= 0 && x < wm.width && y >= 0 && y < wm.height; -} - static void waylandFree(void) { - waylandUngrabPointer(); - - if (wm.idleInhibitManager) - { - waylandUninhibitIdle(); - zwp_idle_inhibit_manager_v1_destroy(wm.idleInhibitManager); - } - - wl_surface_destroy(wm.surface); - wl_pointer_destroy(wm.pointer); - wl_seat_destroy(wm.seat); - wl_registry_destroy(wm.registry); - wl_display_disconnect(wm.display); + waylandIdleFree(); + waylandWindowFree(); + waylandInputFree(); + waylandRegistryFree(); + wl_display_disconnect(wlWm.display); } static bool waylandGetProp(LG_DSProperty prop, void * ret) { if (prop == LG_DS_WARP_SUPPORT) { - *(enum LG_DSWarpSupport*)ret = wm.warpSupport ? LG_DS_WARP_SURFACE : LG_DS_WARP_NONE; + *(enum LG_DSWarpSupport*)ret = wlWm.warpSupport ? LG_DS_WARP_SURFACE : LG_DS_WARP_NONE; return true; } return false; } -static const char * textMimetypes[] = -{ - "text/plain", - "text/plain;charset=utf-8", - "TEXT", - "STRING", - "UTF8_STRING", - NULL, -}; - -static const char * pngMimetypes[] = -{ - "image/png", - NULL, -}; - -static const char * bmpMimetypes[] = -{ - "image/bmp", - "image/x-bmp", - "image/x-MS-bmp", - "image/x-win-bitmap", - NULL, -}; - -static const char * tiffMimetypes[] = -{ - "image/tiff", - NULL, -}; - -static const char * jpegMimetypes[] = -{ - "image/jpeg", - NULL, -}; - -static const char ** cbTypeToMimetypes(enum LG_ClipboardData type) -{ - switch (type) - { - case LG_CLIPBOARD_DATA_TEXT: - return textMimetypes; - case LG_CLIPBOARD_DATA_PNG: - return pngMimetypes; - case LG_CLIPBOARD_DATA_BMP: - return bmpMimetypes; - case LG_CLIPBOARD_DATA_TIFF: - return tiffMimetypes; - case LG_CLIPBOARD_DATA_JPEG: - return jpegMimetypes; - default: - DEBUG_ERROR("invalid clipboard type"); - abort(); - } -} - -static bool containsMimetype(const char ** mimetypes, - const char * needle) -{ - for (const char ** mimetype = mimetypes; *mimetype; mimetype++) - if (!strcmp(needle, *mimetype)) - return true; - - return false; -} - -static bool mimetypeEndswith(const char * mimetype, const char * what) -{ - size_t mimetypeLen = strlen(mimetype); - size_t whatLen = strlen(what); - - if (mimetypeLen < whatLen) - return false; - - return !strcmp(mimetype + mimetypeLen - whatLen, what); -} - -static bool isTextMimetype(const char * mimetype) -{ - if (containsMimetype(textMimetypes, mimetype)) - return true; - - char * text = "text/"; - if (!strncmp(mimetype, text, strlen(text))) - return true; - - if (mimetypeEndswith(mimetype, "script") || - mimetypeEndswith(mimetype, "xml") || - mimetypeEndswith(mimetype, "yaml")) - return true; - - if (strstr(mimetype, "json")) - return true; - - return false; -} - -static enum LG_ClipboardData mimetypeToCbType(const char * mimetype) -{ - if (isTextMimetype(mimetype)) - return LG_CLIPBOARD_DATA_TEXT; - - if (containsMimetype(pngMimetypes, mimetype)) - return LG_CLIPBOARD_DATA_PNG; - - if (containsMimetype(bmpMimetypes, mimetype)) - return LG_CLIPBOARD_DATA_BMP; - - if (containsMimetype(tiffMimetypes, mimetype)) - return LG_CLIPBOARD_DATA_TIFF; - - if (containsMimetype(jpegMimetypes, mimetype)) - return LG_CLIPBOARD_DATA_JPEG; - - return LG_CLIPBOARD_DATA_NONE; -} - -// Destination client handlers. - -static void dataOfferHandleOffer(void * data, struct wl_data_offer * offer, - const char * mimetype) -{ - enum LG_ClipboardData type = mimetypeToCbType(mimetype); - // We almost never prefer text/html, as that's used to represent rich text. - // Since we can't copy or paste rich text, we should instead prefer actual - // images or plain text. - if (type != LG_CLIPBOARD_DATA_NONE && - (wcb.pendingType == LG_CLIPBOARD_DATA_NONE || - strstr(wcb.pendingMimetype, "html"))) - { - wcb.pendingType = type; - if (wcb.pendingMimetype) - free(wcb.pendingMimetype); - wcb.pendingMimetype = strdup(mimetype); - } - - if (!strcmp(mimetype, wcb.lgMimetype)) - wcb.isSelfCopy = true; -} - -static void dataOfferHandleSourceActions(void * data, - struct wl_data_offer * offer, uint32_t sourceActions) -{ - // Do nothing. -} - -static void dataOfferHandleAction(void * data, struct wl_data_offer * offer, - uint32_t dndAction) -{ - // Do nothing. -} - -static const struct wl_data_offer_listener dataOfferListener = { - .offer = dataOfferHandleOffer, - .source_actions = dataOfferHandleSourceActions, - .action = dataOfferHandleAction, -}; - -static void dataDeviceHandleDataOffer(void * data, - struct wl_data_device * dataDevice, struct wl_data_offer * offer) -{ - wcb.pendingType = LG_CLIPBOARD_DATA_NONE; - wcb.isSelfCopy = false; - wl_data_offer_add_listener(offer, &dataOfferListener, NULL); -} - -static void clipboardReadCancel(struct ClipboardRead * data, bool freeBuf) -{ - waylandEpollUnregister(data->fd); - close(data->fd); - wl_data_offer_destroy(data->offer); - if (freeBuf) - free(data->buf); - free(data); - wcb.currentRead = NULL; -} - -static void clipboardReadCallback(uint32_t events, void * opaque) -{ - struct ClipboardRead * data = opaque; - if (events & EPOLLERR) - { - clipboardReadCancel(data, true); - return; - } - - ssize_t result = read(data->fd, data->buf + data->numRead, data->size - data->numRead); - if (result < 0) - { - DEBUG_ERROR("Failed to read from clipboard: %s", strerror(errno)); - clipboardReadCancel(data, true); - return; - } - - if (result == 0) - { - data->buf[data->numRead] = 0; - wcb.stashedType = data->type; - wcb.stashedSize = data->numRead; - wcb.stashedContents = data->buf; - - clipboardReadCancel(data, false); - app_clipboardNotify(wcb.stashedType, 0); - return; - } - - data->numRead += result; - if (data->numRead >= data->size) - { - data->size *= 2; - void * nbuf = realloc(data->buf, data->size); - if (!nbuf) { - DEBUG_ERROR("Failed to realloc clipboard buffer: %s", strerror(errno)); - clipboardReadCancel(data, true); - return; - } - - data->buf = nbuf; - } -} - -static void dataDeviceHandleSelection(void * opaque, - struct wl_data_device * dataDevice, struct wl_data_offer * offer) -{ - if (wcb.pendingType == LG_CLIPBOARD_DATA_NONE || wcb.isSelfCopy || !offer) - return; - - if (wcb.currentRead) - clipboardReadCancel(wcb.currentRead, true); - - int fds[2]; - if (pipe(fds) < 0) - { - DEBUG_ERROR("Failed to get a clipboard pipe: %s", strerror(errno)); - abort(); - } - - wl_data_offer_receive(offer, wcb.pendingMimetype, fds[1]); - close(fds[1]); - free(wcb.pendingMimetype); - wcb.pendingMimetype = NULL; - - wl_display_roundtrip(wm.display); - - if (wcb.stashedContents) - { - free(wcb.stashedContents); - wcb.stashedContents = NULL; - } - - struct ClipboardRead * data = malloc(sizeof(struct ClipboardRead)); - if (!data) - { - DEBUG_ERROR("Failed to allocate memory to read clipboard"); - close(fds[0]); - return; - } - - data->fd = fds[0]; - data->size = 4096; - data->numRead = 0; - data->buf = malloc(data->size); - data->offer = offer; - data->type = wcb.pendingType; - - if (!data->buf) - { - DEBUG_ERROR("Failed to allocate memory to receive clipboard data"); - close(data->fd); - free(data); - return; - } - - if (!waylandEpollRegister(data->fd, clipboardReadCallback, data, EPOLLIN)) - { - DEBUG_ERROR("Failed to register clipboard read into epoll: %s", strerror(errno)); - close(data->fd); - free(data->buf); - free(data); - } - - wcb.currentRead = data; -} - -static const struct wl_data_device_listener dataDeviceListener = { - .data_offer = dataDeviceHandleDataOffer, - .selection = dataDeviceHandleSelection, -}; - -static void waylandCBRequest(LG_ClipboardData type) -{ - // We only notified once, so it must be this. - assert(type == wcb.stashedType); - app_clipboardData(wcb.stashedType, wcb.stashedContents, wcb.stashedSize); -} - -static bool waylandCBInit(void) -{ - memset(&wcb, 0, sizeof(wcb)); - - wcb.stashedType = LG_CLIPBOARD_DATA_NONE; - wl_data_device_add_listener(wm.dataDevice, &dataDeviceListener, NULL); - - snprintf(wcb.lgMimetype, sizeof(wcb.lgMimetype), - "application/x-looking-glass-copy;pid=%d", getpid()); - - return true; -} - -struct ClipboardWrite -{ - int fd; - size_t pos; - struct CountedBuffer * buffer; -}; - -static void clipboardWriteCallback(uint32_t events, void * opaque) -{ - struct ClipboardWrite * data = opaque; - if (events & EPOLLERR) - goto error; - - ssize_t written = write(data->fd, data->buffer->data + data->pos, data->buffer->size - data->pos); - if (written < 0) - { - if (errno != EPIPE) - DEBUG_ERROR("Failed to write clipboard data: %s", strerror(errno)); - goto error; - } - - data->pos += written; - if (data->pos < data->buffer->size) - return; - -error: - waylandEpollUnregister(data->fd); - close(data->fd); - countedBufferRelease(&data->buffer); - free(data); -} - -static void dataSourceHandleSend(void * data, struct wl_data_source * source, - const char * mimetype, int fd) -{ - struct WCBTransfer * transfer = (struct WCBTransfer *) data; - if (containsMimetype(transfer->mimetypes, mimetype)) - { - // Consider making this do non-blocking sends to not stall the Wayland - // event loop if it becomes a problem. This is "fine" in the sense that - // wl-copy also stalls like this, but it's not necessary. - fcntl(fd, F_SETFL, 0); - - struct ClipboardWrite * data = malloc(sizeof(struct ClipboardWrite)); - if (!data) - { - DEBUG_ERROR("Out of memory trying to allocate ClipboardWrite"); - goto error; - } - - data->fd = fd; - data->pos = 0; - data->buffer = transfer->data; - countedBufferAddRef(transfer->data); - waylandEpollRegister(fd, clipboardWriteCallback, data, EPOLLOUT); - return; - } - -error: - close(fd); -} - -static void dataSourceHandleCancelled(void * data, - struct wl_data_source * source) -{ - struct WCBTransfer * transfer = (struct WCBTransfer *) data; - countedBufferRelease(&transfer->data); - free(transfer); - wl_data_source_destroy(source); -} - -static const struct wl_data_source_listener dataSourceListener = { - .send = dataSourceHandleSend, - .cancelled = dataSourceHandleCancelled, -}; - -static void waylandCBReplyFn(void * opaque, LG_ClipboardData type, - uint8_t * data, uint32_t size) -{ - struct WCBTransfer * transfer = malloc(sizeof(struct WCBTransfer)); - if (!transfer) - { - DEBUG_ERROR("Out of memory when allocating WCBTransfer"); - return; - } - - transfer->mimetypes = cbTypeToMimetypes(type); - transfer->data = countedBufferNew(size); - if (!transfer->data) - { - DEBUG_ERROR("Out of memory when allocating clipboard buffer"); - free(transfer); - return; - } - memcpy(transfer->data->data, data, size); - - struct wl_data_source * source = - wl_data_device_manager_create_data_source(wm.dataDeviceManager); - wl_data_source_add_listener(source, &dataSourceListener, transfer); - for (const char ** mimetype = transfer->mimetypes; *mimetype; mimetype++) - wl_data_source_offer(source, *mimetype); - wl_data_source_offer(source, wcb.lgMimetype); - - wl_data_device_set_selection(wm.dataDevice, source, - wm.keyboardEnterSerial); -} - -static void waylandCBNotice(LG_ClipboardData type) -{ - wcb.haveRequest = true; - wcb.type = type; - app_clipboardRequest(waylandCBReplyFn, NULL); -} - -static void waylandCBRelease(void) -{ - wcb.haveRequest = false; -} - struct LG_DisplayServerOps LGDS_Wayland = { .setup = waylandSetup, diff --git a/client/displayservers/Wayland/wayland.h b/client/displayservers/Wayland/wayland.h new file mode 100644 index 00000000..147f4e6a --- /dev/null +++ b/client/displayservers/Wayland/wayland.h @@ -0,0 +1,219 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com) +Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca) +https://looking-glass.io + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include + +#include + +#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL) +# include +# include +# include +#endif + +#include "common/locking.h" +#include "common/countedbuffer.h" +#include "interface/displayserver.h" + +#include "wayland-xdg-shell-client-protocol.h" +#include "wayland-xdg-decoration-unstable-v1-client-protocol.h" +#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h" +#include "wayland-pointer-constraints-unstable-v1-client-protocol.h" +#include "wayland-relative-pointer-unstable-v1-client-protocol.h" +#include "wayland-idle-inhibit-unstable-v1-client-protocol.h" + +typedef void (*WaylandPollCallback)(uint32_t events, void * opaque); + +struct WaylandPoll +{ + int fd; + bool removed; + WaylandPollCallback callback; + void * opaque; + struct wl_list link; +}; + +struct WaylandDSState +{ + bool pointerGrabbed; + bool keyboardGrabbed; + + struct wl_display * display; + struct wl_surface * surface; + struct wl_registry * registry; + struct wl_seat * seat; + struct wl_shm * shm; + struct wl_compositor * compositor; + + int32_t width, height; + bool fullscreen; + uint32_t resizeSerial; + bool configured; + bool warpSupport; + double cursorX, cursorY; + +#ifdef ENABLE_EGL + struct wl_egl_window * eglWindow; +#endif + +#ifdef ENABLE_OPENGL + EGLDisplay glDisplay; + EGLConfig glConfig; + EGLSurface glSurface; +#endif + + struct xdg_wm_base * xdgWmBase; + struct xdg_surface * xdgSurface; + struct xdg_toplevel * xdgToplevel; + struct zxdg_decoration_manager_v1 * xdgDecorationManager; + struct zxdg_toplevel_decoration_v1 * xdgToplevelDecoration; + + struct wl_surface * cursor; + + struct wl_data_device_manager * dataDeviceManager; + + uint32_t capabilities; + + struct wl_keyboard * keyboard; + struct zwp_keyboard_shortcuts_inhibit_manager_v1 * keyboardInhibitManager; + struct zwp_keyboard_shortcuts_inhibitor_v1 * keyboardInhibitor; + uint32_t keyboardEnterSerial; + + struct wl_pointer * pointer; + struct zwp_relative_pointer_manager_v1 * relativePointerManager; + struct zwp_pointer_constraints_v1 * pointerConstraints; + struct zwp_relative_pointer_v1 * relativePointer; + struct zwp_confined_pointer_v1 * confinedPointer; + bool showPointer; + uint32_t pointerEnterSerial; + + struct zwp_idle_inhibit_manager_v1 * idleInhibitManager; + struct zwp_idle_inhibitor_v1 * idleInhibitor; + + struct wl_list poll; // WaylandPoll::link + struct wl_list pollFree; // WaylandPoll::link + LG_Lock pollLock; + LG_Lock pollFreeLock; + int epollFd; + int displayFd; +}; + +struct WCBTransfer +{ + struct CountedBuffer * data; + const char ** mimetypes; +}; + +struct ClipboardRead +{ + int fd; + size_t size; + size_t numRead; + uint8_t * buf; + enum LG_ClipboardData type; + struct wl_data_offer * offer; +}; + +struct WCBState +{ + struct wl_data_device * dataDevice; + char lgMimetype[64]; + + enum LG_ClipboardData pendingType; + char * pendingMimetype; + bool isSelfCopy; + + enum LG_ClipboardData stashedType; + uint8_t * stashedContents; + ssize_t stashedSize; + + bool haveRequest; + LG_ClipboardData type; + + struct ClipboardRead * currentRead; +}; + +extern struct WaylandDSState wlWm; +extern struct WCBState wlCb; + +// clipboard module +bool waylandCBInit(void); +void waylandCBRequest(LG_ClipboardData type); +void waylandCBNotice(LG_ClipboardData type); +void waylandCBRelease(void); + +// cursor module +bool waylandCursorInit(void); +void waylandShowPointer(bool show); + +// gl module +#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL) +bool waylandEGLInit(int w, int h); +EGLDisplay waylandGetEGLDisplay(void); +void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface); +#endif + +#ifdef ENABLE_EGL +EGLNativeWindowType waylandGetEGLNativeWindow(void); +#endif + +#ifdef ENABLE_OPENGL +bool waylandOpenGLInit(void); +LG_DSGLContext waylandGLCreateContext(void); +void waylandGLDeleteContext(LG_DSGLContext context); +void waylandGLMakeCurrent(LG_DSGLContext context); +void waylandGLSetSwapInterval(int interval); +void waylandGLSwapBuffers(void); +#endif + +// idle module +bool waylandIdleInit(void); +void waylandIdleFree(void); +void waylandInhibitIdle(void); +void waylandUninhibitIdle(void); + +// input module +bool waylandInputInit(void); +void waylandInputFree(void); +void waylandGrabKeyboard(void); +void waylandGrabPointer(void); +void waylandUngrabKeyboard(void); +void waylandUngrabPointer(void); +void waylandRealignPointer(void); +void waylandWarpPointer(int x, int y, bool exiting); + +// poll module +bool waylandPollInit(void); +void waylandWait(unsigned int time); +bool waylandEpollRegister(int fd, WaylandPollCallback callback, void * opaque, uint32_t events); +bool waylandEpollUnregister(int fd); + +// registry module +bool waylandRegistryInit(void); +void waylandRegistryFree(void); + +// window module +bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless); +void waylandWindowFree(void); +void waylandSetWindowSize(int x, int y); +void waylandSetFullscreen(bool fs); +bool waylandGetFullscreen(void); +bool waylandIsValidPointerPos(int x, int y); diff --git a/client/displayservers/Wayland/window.c b/client/displayservers/Wayland/window.c new file mode 100644 index 00000000..c75cf4c7 --- /dev/null +++ b/client/displayservers/Wayland/window.c @@ -0,0 +1,171 @@ +/* +Looking Glass - KVM FrameRelay (KVMFR) Client +Copyright (C) 2021 Guanzhong Chen (quantum2048@gmail.com) +Copyright (C) 2021 Tudor Brindus (contact@tbrindus.ca) +https://looking-glass.io + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "wayland.h" + +#include +#include + +#include + +#include "app.h" +#include "common/debug.h" + +// XDG WM base listeners. + +static void xdgWmBasePing(void * data, struct xdg_wm_base * xdgWmBase, uint32_t serial) +{ + xdg_wm_base_pong(xdgWmBase, serial); +} + +static const struct xdg_wm_base_listener xdgWmBaseListener = { + .ping = xdgWmBasePing, +}; + +// Surface-handling listeners. + +static void xdgSurfaceConfigure(void * data, struct xdg_surface * xdgSurface, + uint32_t serial) +{ + if (wlWm.configured) + wlWm.resizeSerial = serial; + else + { + xdg_surface_ack_configure(xdgSurface, serial); + wlWm.configured = true; + } +} + +static const struct xdg_surface_listener xdgSurfaceListener = { + .configure = xdgSurfaceConfigure, +}; + +// XDG Surface listeners. + +static void xdgToplevelConfigure(void * data, struct xdg_toplevel * xdgToplevel, + int32_t width, int32_t height, struct wl_array * states) +{ + wlWm.width = width; + wlWm.height = height; + wlWm.fullscreen = false; + + enum xdg_toplevel_state * state; + wl_array_for_each(state, states) + { + if (*state == XDG_TOPLEVEL_STATE_FULLSCREEN) + wlWm.fullscreen = true; + } +} + +static void xdgToplevelClose(void * data, struct xdg_toplevel * xdgToplevel) +{ + app_handleCloseEvent(); +} + +static const struct xdg_toplevel_listener xdgToplevelListener = { + .configure = xdgToplevelConfigure, + .close = xdgToplevelClose, +}; + +bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless) +{ + if (!wlWm.compositor) + { + DEBUG_ERROR("Compositor missing wl_compositor, will not proceed"); + return false; + } + + if (!wlWm.xdgWmBase) + { + DEBUG_ERROR("Compositor missing xdg_wm_base, will not proceed"); + return false; + } + + xdg_wm_base_add_listener(wlWm.xdgWmBase, &xdgWmBaseListener, NULL); + //wl_display_roundtrip(wlWm.display); + + wlWm.surface = wl_compositor_create_surface(wlWm.compositor); + if (!wlWm.surface) + { + DEBUG_ERROR("Failed to create wl_surface"); + return false; + } + + wlWm.xdgSurface = xdg_wm_base_get_xdg_surface(wlWm.xdgWmBase, wlWm.surface); + xdg_surface_add_listener(wlWm.xdgSurface, &xdgSurfaceListener, NULL); + + wlWm.xdgToplevel = xdg_surface_get_toplevel(wlWm.xdgSurface); + xdg_toplevel_add_listener(wlWm.xdgToplevel, &xdgToplevelListener, NULL); + xdg_toplevel_set_title(wlWm.xdgToplevel, title); + xdg_toplevel_set_app_id(wlWm.xdgToplevel, "looking-glass-client"); + + if (fullscreen) + xdg_toplevel_set_fullscreen(wlWm.xdgToplevel, NULL); + + if (maximize) + xdg_toplevel_set_maximized(wlWm.xdgToplevel); + + wl_surface_commit(wlWm.surface); + + if (wlWm.xdgDecorationManager) + { + wlWm.xdgToplevelDecoration = zxdg_decoration_manager_v1_get_toplevel_decoration( + wlWm.xdgDecorationManager, wlWm.xdgToplevel); + if (wlWm.xdgToplevelDecoration) + { + zxdg_toplevel_decoration_v1_set_mode(wlWm.xdgToplevelDecoration, + borderless ? + ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE : + ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + } + } + + return true; +} + +void waylandWindowFree(void) +{ + wl_surface_destroy(wlWm.surface); +} + +void waylandSetWindowSize(int x, int y) +{ + // FIXME: implement. +} + +void waylandSetFullscreen(bool fs) +{ + if (fs) + xdg_toplevel_set_fullscreen(wlWm.xdgToplevel, NULL); + else + xdg_toplevel_unset_fullscreen(wlWm.xdgToplevel); +} + +bool waylandGetFullscreen(void) +{ + return wlWm.fullscreen; +} + +bool waylandIsValidPointerPos(int x, int y) +{ + return x >= 0 && x < wlWm.width && y >= 0 && y < wlWm.height; +} + + diff --git a/common/include/common/countedbuffer.h b/common/include/common/countedbuffer.h index 83ab1a94..3787221a 100644 --- a/common/include/common/countedbuffer.h +++ b/common/include/common/countedbuffer.h @@ -17,6 +17,9 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#ifndef _H_LG_COMMON_COUNTEDBUFFER_ +#define _H_LG_COMMON_COUNTEDBUFFER_ + #include struct CountedBuffer { @@ -28,3 +31,5 @@ struct CountedBuffer { struct CountedBuffer * countedBufferNew(size_t size); void countedBufferAddRef(struct CountedBuffer * buffer); void countedBufferRelease(struct CountedBuffer ** buffer); + +#endif