mirror of
https://github.com/gnif/LookingGlass.git
synced 2024-11-21 21:17:19 +00:00
[client] wayland: split Wayland display server into modules
The Wayland display server is getting unwieldy due to the sheer size. To make it easier to edit in the future, I split it into many components based on logical boundaries.
This commit is contained in:
parent
1ba1108099
commit
5649d1ad95
@ -10,7 +10,16 @@ pkg_check_modules(DISPLAYSERVER_Wayland_PKGCONFIG REQUIRED
|
|||||||
#)
|
#)
|
||||||
|
|
||||||
add_library(displayserver_Wayland STATIC
|
add_library(displayserver_Wayland STATIC
|
||||||
|
clipboard.c
|
||||||
|
cursor.c
|
||||||
|
gl.c
|
||||||
|
idle.c
|
||||||
|
input.c
|
||||||
|
poll.c
|
||||||
|
state.c
|
||||||
|
registry.c
|
||||||
wayland.c
|
wayland.c
|
||||||
|
window.c
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(displayserver_Wayland
|
target_link_libraries(displayserver_Wayland
|
||||||
|
477
client/displayservers/Wayland/clipboard.c
Normal file
477
client/displayservers/Wayland/clipboard.c
Normal file
@ -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 <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
100
client/displayservers/Wayland/cursor.c
Normal file
100
client/displayservers/Wayland/cursor.c
Normal file
@ -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 <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
171
client/displayservers/Wayland/gl.c
Normal file
171
client/displayservers/Wayland/gl.c
Normal file
@ -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 <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <EGL/egl.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
#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
|
59
client/displayservers/Wayland/idle.c
Normal file
59
client/displayservers/Wayland/idle.c
Normal file
@ -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 <stdbool.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
393
client/displayservers/Wayland/input.c
Normal file
393
client/displayservers/Wayland/input.c
Normal file
@ -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 <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
#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();
|
||||||
|
}
|
177
client/displayservers/Wayland/poll.c
Normal file
177
client/displayservers/Wayland/poll.c
Normal file
@ -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 <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
89
client/displayservers/Wayland/registry.c
Normal file
89
client/displayservers/Wayland/registry.c
Normal file
@ -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 <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
24
client/displayservers/Wayland/state.c
Normal file
24
client/displayservers/Wayland/state.c
Normal file
@ -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;
|
File diff suppressed because it is too large
Load Diff
219
client/displayservers/Wayland/wayland.h
Normal file
219
client/displayservers/Wayland/wayland.h
Normal file
@ -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 <stdbool.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
||||||
|
# include <wayland-egl.h>
|
||||||
|
# include <EGL/egl.h>
|
||||||
|
# include <EGL/eglext.h>
|
||||||
|
#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);
|
171
client/displayservers/Wayland/window.c
Normal file
171
client/displayservers/Wayland/window.c
Normal file
@ -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 <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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
|
Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#ifndef _H_LG_COMMON_COUNTEDBUFFER_
|
||||||
|
#define _H_LG_COMMON_COUNTEDBUFFER_
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
struct CountedBuffer {
|
struct CountedBuffer {
|
||||||
@ -28,3 +31,5 @@ struct CountedBuffer {
|
|||||||
struct CountedBuffer * countedBufferNew(size_t size);
|
struct CountedBuffer * countedBufferNew(size_t size);
|
||||||
void countedBufferAddRef(struct CountedBuffer * buffer);
|
void countedBufferAddRef(struct CountedBuffer * buffer);
|
||||||
void countedBufferRelease(struct CountedBuffer ** buffer);
|
void countedBufferRelease(struct CountedBuffer ** buffer);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user