#include "../../SDL_internal.h"
#if SDL_VIDEO_DRIVER_WAYLAND
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include "../SDL_sysvideo.h"
#include "SDL_mouse.h"
#include "../../events/SDL_mouse_c.h"
#include "SDL_waylandvideo.h"
#include "../SDL_pixels_c.h"
#include "SDL_waylandevents_c.h"
#include "wayland-cursor.h"
#include "SDL_waylandmouse.h"
#include "SDL_hints.h"
#include "../../SDL_hints_c.h"
static int
Wayland_SetRelativeMouseMode(SDL_bool enabled);
typedef struct {
struct wl_buffer *buffer;
struct wl_surface *surface;
int hot_x, hot_y;
int w, h;
SDL_SystemCursor system_cursor;
void *shm_data;
} Wayland_CursorData;
#ifdef SDL_USE_LIBDBUS
#include "../../core/linux/SDL_dbus.h"
static DBusMessage*
wayland_read_dbus_setting(SDL_DBusContext *dbus, const char *key)
{
static const char *iface = "org.gnome.desktop.interface";
DBusMessage *reply = NULL;
DBusMessage *msg = dbus->message_new_method_call("org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Settings",
"Read");
if (msg) {
if (dbus->message_append_args(msg, DBUS_TYPE_STRING, &iface, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) {
reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, NULL);
}
dbus->message_unref(msg);
}
return reply;
}
static SDL_bool
wayland_parse_dbus_reply(SDL_DBusContext *dbus, DBusMessage *reply, int type, void *value)
{
DBusMessageIter iter[3];
dbus->message_iter_init(reply, &iter[0]);
if (dbus->message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) {
return SDL_FALSE;
}
dbus->message_iter_recurse(&iter[0], &iter[1]);
if (dbus->message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) {
return SDL_FALSE;
}
dbus->message_iter_recurse(&iter[1], &iter[2]);
if (dbus->message_iter_get_arg_type(&iter[2]) != type) {
return SDL_FALSE;
}
dbus->message_iter_get_basic(&iter[2], value);
return SDL_TRUE;
}
static SDL_bool
wayland_dbus_read_cursor_size(int *size)
{
static const char *cursor_size_value = "cursor-size";
DBusMessage *reply;
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if (!dbus || !size) {
return SDL_FALSE;
}
if ((reply = wayland_read_dbus_setting(dbus, cursor_size_value))) {
if (wayland_parse_dbus_reply(dbus, reply, DBUS_TYPE_INT32, size)) {
dbus->message_unref(reply);
return SDL_TRUE;
}
dbus->message_unref(reply);
}
return SDL_FALSE;
}
static SDL_bool
wayland_dbus_read_cursor_theme(char **theme)
{
static const char *cursor_theme_value = "cursor-theme";
DBusMessage *reply;
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if (!dbus || !theme) {
return SDL_FALSE;
}
if ((reply = wayland_read_dbus_setting(dbus, cursor_theme_value))) {
const char *temp;
if (wayland_parse_dbus_reply(dbus, reply, DBUS_TYPE_STRING, &temp)) {
*theme = SDL_strdup(temp);
dbus->message_unref(reply);
return SDL_TRUE;
}
dbus->message_unref(reply);
}
return SDL_FALSE;
}
#endif
static SDL_bool
wayland_get_system_cursor(SDL_VideoData *vdata, Wayland_CursorData *cdata, float *scale)
{
struct wl_cursor_theme *theme = NULL;
struct wl_cursor *cursor;
char *xcursor_size;
int size = 0;
SDL_Window *focus;
SDL_WindowData *focusdata;
int i;
if ((xcursor_size = SDL_getenv("XCURSOR_SIZE"))) {
size = SDL_atoi(xcursor_size);
}
#if SDL_USE_LIBDBUS
if (size <= 0) {
wayland_dbus_read_cursor_size(&size);
}
#endif
if (size <= 0) {
size = 24;
}
focus = SDL_GetMouse()->focus;
if (focus == NULL) {
return SDL_FALSE;
}
focusdata = focus->driverdata;
*scale = SDL_ceilf(focusdata->scale_factor);
size *= *scale;
for (i = 0; i < vdata->num_cursor_themes; i += 1) {
if (vdata->cursor_themes[i].size == size) {
theme = vdata->cursor_themes[i].theme;
break;
}
}
if (theme == NULL) {
char *xcursor_theme = NULL;
SDL_bool free_theme_str = SDL_FALSE;
vdata->cursor_themes = SDL_realloc(vdata->cursor_themes,
sizeof(SDL_WaylandCursorTheme) * (vdata->num_cursor_themes + 1));
if (vdata->cursor_themes == NULL) {
SDL_OutOfMemory();
return SDL_FALSE;
}
xcursor_theme = SDL_getenv("XCURSOR_THEME");
#if SDL_USE_LIBDBUS
if (xcursor_theme == NULL) {
free_theme_str = wayland_dbus_read_cursor_theme(&xcursor_theme);
}
#endif
theme = WAYLAND_wl_cursor_theme_load(xcursor_theme, size, vdata->shm);
vdata->cursor_themes[vdata->num_cursor_themes].size = size;
vdata->cursor_themes[vdata->num_cursor_themes++].theme = theme;
if (free_theme_str) {
SDL_free(xcursor_theme);
}
}
switch(cdata->system_cursor)
{
case SDL_SYSTEM_CURSOR_ARROW:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "left_ptr");
break;
case SDL_SYSTEM_CURSOR_IBEAM:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "xterm");
break;
case SDL_SYSTEM_CURSOR_WAIT:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "watch");
break;
case SDL_SYSTEM_CURSOR_CROSSHAIR:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "tcross");
break;
case SDL_SYSTEM_CURSOR_WAITARROW:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "watch");
break;
case SDL_SYSTEM_CURSOR_SIZENWSE:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "top_left_corner");
break;
case SDL_SYSTEM_CURSOR_SIZENESW:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "top_right_corner");
break;
case SDL_SYSTEM_CURSOR_SIZEWE:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "sb_h_double_arrow");
break;
case SDL_SYSTEM_CURSOR_SIZENS:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "sb_v_double_arrow");
break;
case SDL_SYSTEM_CURSOR_SIZEALL:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "fleur");
break;
case SDL_SYSTEM_CURSOR_NO:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "pirate");
break;
case SDL_SYSTEM_CURSOR_HAND:
cursor = WAYLAND_wl_cursor_theme_get_cursor(theme, "hand2");
break;
default:
SDL_assert(0);
return SDL_FALSE;
}
cdata->buffer = WAYLAND_wl_cursor_image_get_buffer(cursor->images[0]);
cdata->hot_x = cursor->images[0]->hotspot_x;
cdata->hot_y = cursor->images[0]->hotspot_y;
cdata->w = cursor->images[0]->width;
cdata->h = cursor->images[0]->height;
return SDL_TRUE;
}
static int
wayland_create_tmp_file(off_t size)
{
static const char template[] = "/sdl-shared-XXXXXX";
char *xdg_path;
char tmp_path[PATH_MAX];
int fd;
xdg_path = SDL_getenv("XDG_RUNTIME_DIR");
if (!xdg_path) {
return -1;
}
SDL_strlcpy(tmp_path, xdg_path, PATH_MAX);
SDL_strlcat(tmp_path, template, PATH_MAX);
fd = mkostemp(tmp_path, O_CLOEXEC);
if (fd < 0)
return -1;
if (ftruncate(fd, size) < 0) {
close(fd);
return -1;
}
return fd;
}
static void
mouse_buffer_release(void *data, struct wl_buffer *buffer)
{
}
static const struct wl_buffer_listener mouse_buffer_listener = {
mouse_buffer_release
};
static int
create_buffer_from_shm(Wayland_CursorData *d,
int width,
int height,
uint32_t format)
{
SDL_VideoDevice *vd = SDL_GetVideoDevice();
SDL_VideoData *data = (SDL_VideoData *) vd->driverdata;
struct wl_shm_pool *shm_pool;
int stride = width * 4;
int size = stride * height;
int shm_fd;
shm_fd = wayland_create_tmp_file(size);
if (shm_fd < 0)
{
return SDL_SetError("Creating mouse cursor buffer failed.");
}
d->shm_data = mmap(NULL,
size,
PROT_READ | PROT_WRITE,
MAP_SHARED,
shm_fd,
0);
if (d->shm_data == MAP_FAILED) {
d->shm_data = NULL;
close (shm_fd);
return SDL_SetError("mmap() failed.");
}
SDL_assert(d->shm_data != NULL);
shm_pool = wl_shm_create_pool(data->shm, shm_fd, size);
d->buffer = wl_shm_pool_create_buffer(shm_pool,
0,
width,
height,
stride,
format);
wl_buffer_add_listener(d->buffer,
&mouse_buffer_listener,
d);
wl_shm_pool_destroy (shm_pool);
close (shm_fd);
return 0;
}
static SDL_Cursor *
Wayland_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
{
SDL_Cursor *cursor;
cursor = SDL_calloc(1, sizeof (*cursor));
if (cursor) {
SDL_VideoDevice *vd = SDL_GetVideoDevice ();
SDL_VideoData *wd = (SDL_VideoData *) vd->driverdata;
Wayland_CursorData *data = SDL_calloc (1, sizeof (Wayland_CursorData));
if (!data) {
SDL_OutOfMemory();
SDL_free(cursor);
return NULL;
}
cursor->driverdata = (void *) data;
if (create_buffer_from_shm (data,
surface->w,
surface->h,
WL_SHM_FORMAT_ARGB8888) < 0)
{
SDL_free (cursor->driverdata);
SDL_free (cursor);
return NULL;
}
SDL_PremultiplyAlpha(surface->w, surface->h,
surface->format->format, surface->pixels, surface->pitch,
SDL_PIXELFORMAT_ARGB8888, data->shm_data, surface->w * 4);
data->surface = wl_compositor_create_surface(wd->compositor);
wl_surface_set_user_data(data->surface, NULL);
data->hot_x = hot_x;
data->hot_y = hot_y;
data->w = surface->w;
data->h = surface->h;
} else {
SDL_OutOfMemory();
}
return cursor;
}
static SDL_Cursor *
Wayland_CreateSystemCursor(SDL_SystemCursor id)
{
SDL_VideoData *data = SDL_GetVideoDevice()->driverdata;
SDL_Cursor *cursor;
cursor = SDL_calloc(1, sizeof (*cursor));
if (cursor) {
Wayland_CursorData *cdata = SDL_calloc (1, sizeof (Wayland_CursorData));
if (!cdata) {
SDL_OutOfMemory();
SDL_free(cursor);
return NULL;
}
cursor->driverdata = (void *) cdata;
cdata->surface = wl_compositor_create_surface(data->compositor);
wl_surface_set_user_data(cdata->surface, NULL);
cdata->system_cursor = id;
} else {
SDL_OutOfMemory();
}
return cursor;
}
static SDL_Cursor *
Wayland_CreateDefaultCursor()
{
return Wayland_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
}
static void
Wayland_FreeCursorData(Wayland_CursorData *d)
{
if (d->buffer) {
if (d->shm_data) {
wl_buffer_destroy(d->buffer);
}
d->buffer = NULL;
}
if (d->surface) {
wl_surface_destroy(d->surface);
d->surface = NULL;
}
}
static void
Wayland_FreeCursor(SDL_Cursor *cursor)
{
if (!cursor) {
return;
}
if (!cursor->driverdata) {
return;
}
Wayland_FreeCursorData((Wayland_CursorData *) cursor->driverdata);
SDL_free(cursor->driverdata);
SDL_free(cursor);
}
static int
Wayland_ShowCursor(SDL_Cursor *cursor)
{
SDL_VideoDevice *vd = SDL_GetVideoDevice();
SDL_VideoData *d = vd->driverdata;
struct SDL_WaylandInput *input = d->input;
struct wl_pointer *pointer = d->pointer;
float scale = 1.0f;
if (!pointer)
return -1;
if (cursor)
{
Wayland_CursorData *data = cursor->driverdata;
if (data->shm_data == NULL) {
if (!wayland_get_system_cursor(d, data, &scale)) {
return -1;
}
}
wl_surface_set_buffer_scale(data->surface, scale);
wl_pointer_set_cursor(pointer,
input->pointer_enter_serial,
data->surface,
data->hot_x / scale,
data->hot_y / scale);
wl_surface_attach(data->surface, data->buffer, 0, 0);
wl_surface_damage(data->surface, 0, 0, data->w, data->h);
wl_surface_commit(data->surface);
input->cursor_visible = SDL_TRUE;
if (input->relative_mode_override) {
Wayland_input_unlock_pointer(input);
input->relative_mode_override = SDL_FALSE;
}
}
else
{
input->cursor_visible = SDL_FALSE;
wl_pointer_set_cursor(pointer, input->pointer_enter_serial, NULL, 0, 0);
}
return 0;
}
static void
Wayland_WarpMouse(SDL_Window *window, int x, int y)
{
SDL_VideoDevice *vd = SDL_GetVideoDevice();
SDL_VideoData *d = vd->driverdata;
struct SDL_WaylandInput *input = d->input;
if (input->cursor_visible == SDL_TRUE) {
SDL_Unsupported();
} else if (input->warp_emulation_prohibited) {
SDL_Unsupported();
} else {
if (!d->relative_mouse_mode) {
Wayland_input_lock_pointer(input);
input->relative_mode_override = SDL_TRUE;
}
}
}
static int
Wayland_WarpMouseGlobal(int x, int y)
{
return SDL_Unsupported();
}
static int
Wayland_SetRelativeMouseMode(SDL_bool enabled)
{
SDL_VideoDevice *vd = SDL_GetVideoDevice();
SDL_VideoData *data = (SDL_VideoData *) vd->driverdata;
if (enabled) {
if (data->input->relative_mode_override)
data->input->relative_mode_override = SDL_FALSE;
data->input->warp_emulation_prohibited = SDL_TRUE;
return Wayland_input_lock_pointer(data->input);
} else {
return Wayland_input_unlock_pointer(data->input);
}
}
static void SDLCALL
Wayland_EmulateMouseWarpChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)userdata;
input->warp_emulation_prohibited = !SDL_GetStringBoolean(hint, !input->warp_emulation_prohibited);
}
#if 0#endif
void
Wayland_InitMouse(void)
{
SDL_Mouse *mouse = SDL_GetMouse();
SDL_VideoDevice *vd = SDL_GetVideoDevice();
SDL_VideoData *d = vd->driverdata;
struct SDL_WaylandInput *input = d->input;
mouse->CreateCursor = Wayland_CreateCursor;
mouse->CreateSystemCursor = Wayland_CreateSystemCursor;
mouse->ShowCursor = Wayland_ShowCursor;
mouse->FreeCursor = Wayland_FreeCursor;
mouse->WarpMouse = Wayland_WarpMouse;
mouse->WarpMouseGlobal = Wayland_WarpMouseGlobal;
mouse->SetRelativeMouseMode = Wayland_SetRelativeMouseMode;
input->relative_mode_override = SDL_FALSE;
input->cursor_visible = SDL_TRUE;
SDL_SetDefaultCursor(Wayland_CreateDefaultCursor());
SDL_AddHintCallback(SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP,
Wayland_EmulateMouseWarpChanged, input);
}
void
Wayland_FiniMouse(SDL_VideoData *data)
{
struct SDL_WaylandInput *input = data->input;
int i;
for (i = 0; i < data->num_cursor_themes; i += 1) {
WAYLAND_wl_cursor_theme_destroy(data->cursor_themes[i].theme);
}
data->num_cursor_themes = 0;
SDL_free(data->cursor_themes);
data->cursor_themes = NULL;
SDL_DelHintCallback(SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP,
Wayland_EmulateMouseWarpChanged, input);
}
#endif