Files
LNXRNT/Kinc/Backends/System/Linux/Sources/kinc/backend/x11/system.c.h
2025-01-29 10:55:49 +01:00

1056 lines
34 KiB
C

#include "x11.h"
#include <X11/Xlib.h>
#include <kinc/input/keyboard.h>
#include <kinc/input/mouse.h>
#include <assert.h>
#include <ctype.h>
#include <dlfcn.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define Button6 6
#define Button7 7
struct kinc_x11_procs xlib = {0};
struct x11_context x11_ctx = {0};
static size_t clipboardStringSize = 1024;
static char *clipboardString = NULL;
char buffer[1024];
int kinc_x11_error_handler(Display *display, XErrorEvent *error_event) {
xlib.XGetErrorText(display, error_event->error_code, buffer, 1024);
kinc_log(KINC_LOG_LEVEL_ERROR, "X Error: %s", buffer);
kinc_debug_break();
return 0;
}
struct kinc_x11_window *window_from_window(Window window) {
for (int i = 0; i < MAXIMUM_WINDOWS; i++) {
if (x11_ctx.windows[i].window == window) {
return &x11_ctx.windows[i];
}
}
return NULL;
}
void kinc_internal_resize(int window_index, int width, int height);
static void init_pen_device(XDeviceInfo *info, struct x11_pen_device *pen, bool eraser);
static void load_lib(void **lib, const char *name);
bool kinc_x11_init() {
#undef LOAD_LIB
#undef LOAD_FUN
#define LOAD_LIB(name) \
{ \
load_lib(&x11_ctx.libs.name, #name); \
\
if (x11_ctx.libs.name == NULL) { \
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to load lib%s.so", #name); \
return false; \
} \
} \
// manually check for libX11, and return false if not present
// only error for further libs
load_lib(&x11_ctx.libs.X11, "X11");
if (x11_ctx.libs.X11 == NULL) {
return false;
}
LOAD_LIB(Xi);
LOAD_LIB(Xcursor);
LOAD_LIB(Xinerama);
LOAD_LIB(Xrandr);
#define LOAD_FUN(lib, symbol) \
xlib.symbol = dlsym(x11_ctx.libs.lib, #symbol); \
if (xlib.symbol == NULL) { \
kinc_log(KINC_LOG_LEVEL_ERROR, "Did not find symbol %s in library %s.", #symbol, #lib); \
}
LOAD_FUN(X11, XOpenDisplay)
LOAD_FUN(X11, XCloseDisplay)
LOAD_FUN(X11, XSetErrorHandler)
LOAD_FUN(X11, XGetErrorText)
LOAD_FUN(X11, XInternAtoms)
LOAD_FUN(X11, XPending)
LOAD_FUN(X11, XFlush)
LOAD_FUN(X11, XNextEvent)
LOAD_FUN(X11, XRefreshKeyboardMapping)
LOAD_FUN(X11, XwcLookupString)
LOAD_FUN(X11, XFilterEvent)
LOAD_FUN(X11, XConvertSelection)
LOAD_FUN(X11, XSetSelectionOwner)
LOAD_FUN(X11, XLookupString)
LOAD_FUN(X11, XkbKeycodeToKeysym)
LOAD_FUN(X11, XSendEvent)
LOAD_FUN(X11, XGetWindowProperty)
LOAD_FUN(X11, XFree)
LOAD_FUN(X11, XChangeProperty)
LOAD_FUN(X11, XDefineCursor)
LOAD_FUN(X11, XUndefineCursor)
LOAD_FUN(X11, XCreateBitmapFromData)
LOAD_FUN(X11, XCreatePixmapCursor)
LOAD_FUN(X11, XFreePixmap)
LOAD_FUN(Xcursor, XcursorLibraryLoadCursor)
LOAD_FUN(X11, XWarpPointer)
LOAD_FUN(X11, XQueryPointer)
LOAD_FUN(X11, XCreateColormap)
LOAD_FUN(X11, XCreateWindow)
LOAD_FUN(X11, XMoveWindow)
LOAD_FUN(X11, XResizeWindow)
LOAD_FUN(X11, XDestroyWindow)
LOAD_FUN(X11, XSetClassHint)
LOAD_FUN(X11, XSetLocaleModifiers)
LOAD_FUN(X11, XOpenIM)
LOAD_FUN(X11, XCloseIM)
LOAD_FUN(X11, XCreateIC)
LOAD_FUN(X11, XDestroyIC)
LOAD_FUN(X11, XSetICFocus)
LOAD_FUN(X11, XMapWindow)
LOAD_FUN(X11, XUnmapWindow)
LOAD_FUN(X11, XSetWMProtocols)
LOAD_FUN(X11, XPeekEvent)
LOAD_FUN(Xi, XListInputDevices)
LOAD_FUN(Xi, XFreeDeviceList)
LOAD_FUN(Xi, XOpenDevice)
LOAD_FUN(Xi, XCloseDevice)
LOAD_FUN(Xi, XSelectExtensionEvent)
LOAD_FUN(Xinerama, XineramaQueryExtension)
LOAD_FUN(Xinerama, XineramaIsActive)
LOAD_FUN(Xinerama, XineramaQueryScreens)
LOAD_FUN(Xrandr, XRRGetScreenResourcesCurrent)
LOAD_FUN(Xrandr, XRRGetOutputPrimary)
LOAD_FUN(Xrandr, XRRGetOutputInfo)
LOAD_FUN(Xrandr, XRRFreeOutputInfo)
LOAD_FUN(Xrandr, XRRGetCrtcInfo)
LOAD_FUN(Xrandr, XRRFreeCrtcInfo)
LOAD_FUN(Xrandr, XRRFreeScreenResources)
#undef LOAD_FUN
#undef LOAD_LIB
x11_ctx.display = xlib.XOpenDisplay(NULL);
if (!x11_ctx.display) {
return false;
}
xlib.XSetErrorHandler(kinc_x11_error_handler);
// this should be kept in sync with the x11_atoms struct
static char *atom_names[] = {
"XdndAware",
"XdndDrop",
"XdndEnter",
"text/uri-list",
"XdndStatus",
"XdndActionCopy",
"XdndSelection",
"CLIPBOARD",
"UTF8_STRING",
"XSEL_DATA",
"TARGETS",
"MULTIPLE",
"text/plain;charset=utf-8",
"WM_DELETE_WINDOW",
"_MOTIF_WM_HINTS",
"_NET_WM_NAME",
"_NET_WM_ICON_NAME",
"_NET_WM_STATE",
"_NET_WM_STATE_FULLSCREEN",
XI_MOUSE,
XI_TABLET,
XI_KEYBOARD,
XI_TOUCHSCREEN,
XI_TOUCHPAD,
XI_BUTTONBOX,
XI_BARCODE,
XI_TRACKBALL,
XI_QUADRATURE,
XI_ID_MODULE,
XI_ONE_KNOB,
XI_NINE_KNOB,
XI_KNOB_BOX,
XI_SPACEBALL,
XI_DATAGLOVE,
XI_EYETRACKER,
XI_CURSORKEYS,
XI_FOOTMOUSE,
XI_JOYSTICK,
};
assert((sizeof atom_names / sizeof atom_names[0]) == (sizeof(struct kinc_x11_atoms) / sizeof(Atom)));
xlib.XInternAtoms(x11_ctx.display, atom_names, sizeof atom_names / sizeof atom_names[0], False, (Atom *)&x11_ctx.atoms);
clipboardString = (char *)malloc(clipboardStringSize);
x11_ctx.pen.id = -1;
x11_ctx.eraser.id = -1;
int count;
XDeviceInfoPtr devices = (XDeviceInfoPtr)xlib.XListInputDevices(x11_ctx.display, &count);
for (int i = 0; i < count; i++) {
strncpy(buffer, devices[i].name, 1023);
buffer[1023] = 0;
for (int j = 0; buffer[j]; j++) {
buffer[j] = tolower(buffer[j]);
}
if (strstr(buffer, "stylus") || strstr(buffer, "pen") || strstr(buffer, "wacom")) {
init_pen_device(&devices[i], &x11_ctx.pen, false);
}
if (strstr(buffer, "eraser")) {
init_pen_device(&devices[i], &x11_ctx.eraser, true);
}
}
if (devices != NULL) {
xlib.XFreeDeviceList(devices);
}
return true;
}
void kinc_x11_shutdown() {
free(clipboardString);
xlib.XCloseDisplay(x11_ctx.display);
}
#include <kinc/input/pen.h>
static void init_pen_device(XDeviceInfo *info, struct x11_pen_device *pen, bool eraser) {
XDevice *device = xlib.XOpenDevice(x11_ctx.display, info->id);
XAnyClassPtr c = info->inputclassinfo;
for (int j = 0; j < device->num_classes; j++) {
if (c->class == ValuatorClass) {
XValuatorInfo *valuator_info = (XValuatorInfo *)c;
if (valuator_info->num_axes > 2) {
pen->maxPressure = valuator_info->axes[2].max_value;
}
pen->id = info->id;
DeviceMotionNotify(device, pen->motionEvent, pen->motionClass);
if (eraser) {
pen->press = kinc_internal_eraser_trigger_press;
pen->move = kinc_internal_eraser_trigger_move;
pen->release = kinc_internal_eraser_trigger_release;
}
else {
pen->press = kinc_internal_pen_trigger_press;
pen->move = kinc_internal_pen_trigger_move;
pen->release = kinc_internal_pen_trigger_release;
}
return;
}
c = (XAnyClassPtr)((uint8_t *)c + c->length);
}
xlib.XCloseDevice(x11_ctx.display, device);
}
static void check_pen_device(struct kinc_x11_window *window, XEvent *event, struct x11_pen_device *pen) {
if (event->type == pen->motionEvent) {
XDeviceMotionEvent *motion = (XDeviceMotionEvent *)(event);
if (motion->deviceid == pen->id) {
float p = (float)motion->axis_data[2] / (float)pen->maxPressure;
if (p > 0 && x11_ctx.pen.current_pressure == 0) {
pen->press(window->window_index, motion->x, motion->y, p);
}
else if (p == 0 && pen->current_pressure > 0) {
pen->release(window->window_index, motion->x, motion->y, p);
}
else if (p > 0) {
pen->move(window->window_index, motion->x, motion->y, p);
}
pen->current_pressure = p;
}
}
}
bool kinc_x11_handle_messages() {
static bool controlDown = false;
static int ignoreKeycode = 0;
static bool preventNextKeyDownEvent = false;
while (xlib.XPending(x11_ctx.display)) {
XEvent event;
xlib.XNextEvent(x11_ctx.display, &event);
Window window = event.xclient.window;
struct kinc_x11_window *k_window = window_from_window(window);
if (k_window == NULL) {
continue;
}
check_pen_device(k_window, &event, &x11_ctx.pen);
check_pen_device(k_window, &event, &x11_ctx.eraser);
switch (event.type) {
case MappingNotify: {
xlib.XRefreshKeyboardMapping(&event.xmapping);
} break;
case KeyPress: {
XKeyEvent *key = (XKeyEvent *)&event;
KeySym keysym;
wchar_t wchar;
bool wcConverted = xlib.XwcLookupString(k_window->xInputContext, key, &wchar, 1, &keysym, NULL);
bool isIgnoredKeySym = keysym == XK_Escape || keysym == XK_BackSpace || keysym == XK_Delete;
if (!controlDown && !xlib.XFilterEvent(&event, window) && !isIgnoredKeySym) {
if (wcConverted) {
kinc_internal_keyboard_trigger_key_press(wchar);
}
}
if (preventNextKeyDownEvent) {
// this keypress is a repeated keystroke and should not lead to a keydown-event
preventNextKeyDownEvent = false;
continue;
}
#define KEY(xkey, korekey) \
case xkey: \
kinc_internal_keyboard_trigger_key_down(korekey); \
break;
KeySym ksKey = xlib.XkbKeycodeToKeysym(x11_ctx.display, event.xkey.keycode, 0, 0);
if (ksKey == XK_Control_L || ksKey == XK_Control_R) {
controlDown = true;
}
else if (controlDown && (ksKey == XK_v || ksKey == XK_V)) {
xlib.XConvertSelection(x11_ctx.display, x11_ctx.atoms.CLIPBOARD, x11_ctx.atoms.UTF8_STRING, x11_ctx.atoms.XSEL_DATA, window, CurrentTime);
}
else if (controlDown && (ksKey == XK_c || ksKey == XK_C)) {
xlib.XSetSelectionOwner(x11_ctx.display, x11_ctx.atoms.CLIPBOARD, window, CurrentTime);
char *text = kinc_internal_copy_callback();
if (text != NULL)
kinc_x11_copy_to_clipboard(text);
}
else if (controlDown && (ksKey == XK_x || ksKey == XK_X)) {
xlib.XSetSelectionOwner(x11_ctx.display, x11_ctx.atoms.CLIPBOARD, window, CurrentTime);
char *text = kinc_internal_cut_callback();
if (text != NULL)
kinc_x11_copy_to_clipboard(text);
}
if (event.xkey.keycode == ignoreKeycode) {
break;
}
else {
ignoreKeycode = event.xkey.keycode;
}
if (ksKey < 97 || ksKey > 122) {
ksKey = keysym;
}
switch (ksKey) {
KEY(XK_Right, KINC_KEY_RIGHT)
KEY(XK_Left, KINC_KEY_LEFT)
KEY(XK_Up, KINC_KEY_UP)
KEY(XK_Down, KINC_KEY_DOWN)
KEY(XK_space, KINC_KEY_SPACE)
KEY(XK_BackSpace, KINC_KEY_BACKSPACE)
KEY(XK_Tab, KINC_KEY_TAB)
KEY(XK_Return, KINC_KEY_RETURN)
KEY(XK_Shift_L, KINC_KEY_SHIFT)
KEY(XK_Shift_R, KINC_KEY_SHIFT)
KEY(XK_Control_L, KINC_KEY_CONTROL)
KEY(XK_Control_R, KINC_KEY_CONTROL)
KEY(XK_Alt_L, KINC_KEY_ALT)
KEY(XK_Alt_R, KINC_KEY_ALT)
KEY(XK_Delete, KINC_KEY_DELETE)
KEY(XK_comma, KINC_KEY_COMMA)
KEY(XK_period, KINC_KEY_PERIOD)
KEY(XK_bracketleft, KINC_KEY_OPEN_BRACKET)
KEY(XK_bracketright, KINC_KEY_CLOSE_BRACKET)
KEY(XK_braceleft, KINC_KEY_OPEN_CURLY_BRACKET)
KEY(XK_braceright, KINC_KEY_CLOSE_CURLY_BRACKET)
KEY(XK_parenleft, KINC_KEY_OPEN_PAREN)
KEY(XK_parenright, KINC_KEY_CLOSE_PAREN)
KEY(XK_backslash, KINC_KEY_BACK_SLASH)
KEY(XK_apostrophe, KINC_KEY_QUOTE)
KEY(XK_colon, KINC_KEY_COLON)
KEY(XK_semicolon, KINC_KEY_SEMICOLON)
KEY(XK_minus, KINC_KEY_HYPHEN_MINUS)
KEY(XK_underscore, KINC_KEY_UNDERSCORE)
KEY(XK_slash, KINC_KEY_SLASH)
KEY(XK_bar, KINC_KEY_PIPE)
KEY(XK_question, KINC_KEY_QUESTIONMARK)
KEY(XK_less, KINC_KEY_LESS_THAN)
KEY(XK_greater, KINC_KEY_GREATER_THAN)
KEY(XK_asterisk, KINC_KEY_ASTERISK)
KEY(XK_ampersand, KINC_KEY_AMPERSAND)
KEY(XK_asciicircum, KINC_KEY_CIRCUMFLEX)
KEY(XK_percent, KINC_KEY_PERCENT)
KEY(XK_dollar, KINC_KEY_DOLLAR)
KEY(XK_numbersign, KINC_KEY_HASH)
KEY(XK_at, KINC_KEY_AT)
KEY(XK_exclam, KINC_KEY_EXCLAMATION)
KEY(XK_equal, KINC_KEY_EQUALS)
KEY(XK_plus, KINC_KEY_ADD)
KEY(XK_quoteleft, KINC_KEY_BACK_QUOTE)
KEY(XK_quotedbl, KINC_KEY_DOUBLE_QUOTE)
KEY(XK_asciitilde, KINC_KEY_TILDE)
KEY(XK_Pause, KINC_KEY_PAUSE)
KEY(XK_Scroll_Lock, KINC_KEY_SCROLL_LOCK)
KEY(XK_Home, KINC_KEY_HOME)
KEY(XK_Page_Up, KINC_KEY_PAGE_UP)
KEY(XK_Page_Down, KINC_KEY_PAGE_DOWN)
KEY(XK_End, KINC_KEY_END)
KEY(XK_Insert, KINC_KEY_INSERT)
KEY(XK_KP_Enter, KINC_KEY_RETURN)
KEY(XK_KP_Multiply, KINC_KEY_MULTIPLY)
KEY(XK_KP_Add, KINC_KEY_ADD)
KEY(XK_KP_Subtract, KINC_KEY_SUBTRACT)
KEY(XK_KP_Decimal, KINC_KEY_DECIMAL)
KEY(XK_KP_Divide, KINC_KEY_DIVIDE)
KEY(XK_KP_0, KINC_KEY_NUMPAD_0)
KEY(XK_KP_1, KINC_KEY_NUMPAD_1)
KEY(XK_KP_2, KINC_KEY_NUMPAD_2)
KEY(XK_KP_3, KINC_KEY_NUMPAD_3)
KEY(XK_KP_4, KINC_KEY_NUMPAD_4)
KEY(XK_KP_5, KINC_KEY_NUMPAD_5)
KEY(XK_KP_6, KINC_KEY_NUMPAD_6)
KEY(XK_KP_7, KINC_KEY_NUMPAD_7)
KEY(XK_KP_8, KINC_KEY_NUMPAD_8)
KEY(XK_KP_9, KINC_KEY_NUMPAD_9)
KEY(XK_KP_Insert, KINC_KEY_INSERT)
KEY(XK_KP_Delete, KINC_KEY_DELETE)
KEY(XK_KP_End, KINC_KEY_END)
KEY(XK_KP_Home, KINC_KEY_HOME)
KEY(XK_KP_Left, KINC_KEY_LEFT)
KEY(XK_KP_Up, KINC_KEY_UP)
KEY(XK_KP_Right, KINC_KEY_RIGHT)
KEY(XK_KP_Down, KINC_KEY_DOWN)
KEY(XK_KP_Page_Up, KINC_KEY_PAGE_UP)
KEY(XK_KP_Page_Down, KINC_KEY_PAGE_DOWN)
KEY(XK_Menu, KINC_KEY_CONTEXT_MENU)
KEY(XK_a, KINC_KEY_A)
KEY(XK_b, KINC_KEY_B)
KEY(XK_c, KINC_KEY_C)
KEY(XK_d, KINC_KEY_D)
KEY(XK_e, KINC_KEY_E)
KEY(XK_f, KINC_KEY_F)
KEY(XK_g, KINC_KEY_G)
KEY(XK_h, KINC_KEY_H)
KEY(XK_i, KINC_KEY_I)
KEY(XK_j, KINC_KEY_J)
KEY(XK_k, KINC_KEY_K)
KEY(XK_l, KINC_KEY_L)
KEY(XK_m, KINC_KEY_M)
KEY(XK_n, KINC_KEY_N)
KEY(XK_o, KINC_KEY_O)
KEY(XK_p, KINC_KEY_P)
KEY(XK_q, KINC_KEY_Q)
KEY(XK_r, KINC_KEY_R)
KEY(XK_s, KINC_KEY_S)
KEY(XK_t, KINC_KEY_T)
KEY(XK_u, KINC_KEY_U)
KEY(XK_v, KINC_KEY_V)
KEY(XK_w, KINC_KEY_W)
KEY(XK_x, KINC_KEY_X)
KEY(XK_y, KINC_KEY_Y)
KEY(XK_z, KINC_KEY_Z)
KEY(XK_1, KINC_KEY_1)
KEY(XK_2, KINC_KEY_2)
KEY(XK_3, KINC_KEY_3)
KEY(XK_4, KINC_KEY_4)
KEY(XK_5, KINC_KEY_5)
KEY(XK_6, KINC_KEY_6)
KEY(XK_7, KINC_KEY_7)
KEY(XK_8, KINC_KEY_8)
KEY(XK_9, KINC_KEY_9)
KEY(XK_0, KINC_KEY_0)
KEY(XK_Escape, KINC_KEY_ESCAPE)
KEY(XK_F1, KINC_KEY_F1)
KEY(XK_F2, KINC_KEY_F2)
KEY(XK_F3, KINC_KEY_F3)
KEY(XK_F4, KINC_KEY_F4)
KEY(XK_F5, KINC_KEY_F5)
KEY(XK_F6, KINC_KEY_F6)
KEY(XK_F7, KINC_KEY_F7)
KEY(XK_F8, KINC_KEY_F8)
KEY(XK_F9, KINC_KEY_F9)
KEY(XK_F10, KINC_KEY_F10)
KEY(XK_F11, KINC_KEY_F11)
KEY(XK_F12, KINC_KEY_F12)
}
break;
#undef KEY
}
case KeyRelease: {
XKeyEvent *key = (XKeyEvent *)&event;
// peek next-event to determine if this a repeated-keystroke
XEvent nev;
if (xlib.XPending(x11_ctx.display)) {
xlib.XPeekEvent(x11_ctx.display, &nev);
if (nev.type == KeyPress && nev.xkey.time == event.xkey.time && nev.xkey.keycode == event.xkey.keycode) {
// repeated keystroke! prevent this keyup-event and next keydown-event from being fired
preventNextKeyDownEvent = true;
continue;
}
}
KeySym keysym;
char c;
xlib.XLookupString(key, &c, 1, &keysym, NULL);
#define KEY(xkey, korekey) \
case xkey: \
kinc_internal_keyboard_trigger_key_up(korekey); \
break;
KeySym ksKey = xlib.XkbKeycodeToKeysym(x11_ctx.display, event.xkey.keycode, 0, 0);
if (ksKey == XK_Control_L || ksKey == XK_Control_R) {
controlDown = false;
}
if (event.xkey.keycode == ignoreKeycode) {
ignoreKeycode = 0;
}
if (ksKey < 97 || ksKey > 122) {
ksKey = keysym;
}
switch (ksKey) {
KEY(XK_Right, KINC_KEY_RIGHT)
KEY(XK_Left, KINC_KEY_LEFT)
KEY(XK_Up, KINC_KEY_UP)
KEY(XK_Down, KINC_KEY_DOWN)
KEY(XK_space, KINC_KEY_SPACE)
KEY(XK_BackSpace, KINC_KEY_BACKSPACE)
KEY(XK_Tab, KINC_KEY_TAB)
KEY(XK_Return, KINC_KEY_RETURN)
KEY(XK_Shift_L, KINC_KEY_SHIFT)
KEY(XK_Shift_R, KINC_KEY_SHIFT)
KEY(XK_Control_L, KINC_KEY_CONTROL)
KEY(XK_Control_R, KINC_KEY_CONTROL)
KEY(XK_Alt_L, KINC_KEY_ALT)
KEY(XK_Alt_R, KINC_KEY_ALT)
KEY(XK_Delete, KINC_KEY_DELETE)
KEY(XK_comma, KINC_KEY_COMMA)
KEY(XK_period, KINC_KEY_PERIOD)
KEY(XK_bracketleft, KINC_KEY_OPEN_BRACKET)
KEY(XK_bracketright, KINC_KEY_CLOSE_BRACKET)
KEY(XK_braceleft, KINC_KEY_OPEN_CURLY_BRACKET)
KEY(XK_braceright, KINC_KEY_CLOSE_CURLY_BRACKET)
KEY(XK_parenleft, KINC_KEY_OPEN_PAREN)
KEY(XK_parenright, KINC_KEY_CLOSE_PAREN)
KEY(XK_backslash, KINC_KEY_BACK_SLASH)
KEY(XK_apostrophe, KINC_KEY_QUOTE)
KEY(XK_colon, KINC_KEY_COLON)
KEY(XK_semicolon, KINC_KEY_SEMICOLON)
KEY(XK_minus, KINC_KEY_HYPHEN_MINUS)
KEY(XK_underscore, KINC_KEY_UNDERSCORE)
KEY(XK_slash, KINC_KEY_SLASH)
KEY(XK_bar, KINC_KEY_PIPE)
KEY(XK_question, KINC_KEY_QUESTIONMARK)
KEY(XK_less, KINC_KEY_LESS_THAN)
KEY(XK_greater, KINC_KEY_GREATER_THAN)
KEY(XK_asterisk, KINC_KEY_ASTERISK)
KEY(XK_ampersand, KINC_KEY_AMPERSAND)
KEY(XK_asciicircum, KINC_KEY_CIRCUMFLEX)
KEY(XK_percent, KINC_KEY_PERCENT)
KEY(XK_dollar, KINC_KEY_DOLLAR)
KEY(XK_numbersign, KINC_KEY_HASH)
KEY(XK_at, KINC_KEY_AT)
KEY(XK_exclam, KINC_KEY_EXCLAMATION)
KEY(XK_equal, KINC_KEY_EQUALS)
KEY(XK_plus, KINC_KEY_ADD)
KEY(XK_quoteleft, KINC_KEY_BACK_QUOTE)
KEY(XK_quotedbl, KINC_KEY_DOUBLE_QUOTE)
KEY(XK_asciitilde, KINC_KEY_TILDE)
KEY(XK_Pause, KINC_KEY_PAUSE)
KEY(XK_Scroll_Lock, KINC_KEY_SCROLL_LOCK)
KEY(XK_Home, KINC_KEY_HOME)
KEY(XK_Page_Up, KINC_KEY_PAGE_UP)
KEY(XK_Page_Down, KINC_KEY_PAGE_DOWN)
KEY(XK_End, KINC_KEY_END)
KEY(XK_Insert, KINC_KEY_INSERT)
KEY(XK_KP_Enter, KINC_KEY_RETURN)
KEY(XK_KP_Multiply, KINC_KEY_MULTIPLY)
KEY(XK_KP_Add, KINC_KEY_ADD)
KEY(XK_KP_Subtract, KINC_KEY_SUBTRACT)
KEY(XK_KP_Decimal, KINC_KEY_DECIMAL)
KEY(XK_KP_Divide, KINC_KEY_DIVIDE)
KEY(XK_KP_0, KINC_KEY_NUMPAD_0)
KEY(XK_KP_1, KINC_KEY_NUMPAD_1)
KEY(XK_KP_2, KINC_KEY_NUMPAD_2)
KEY(XK_KP_3, KINC_KEY_NUMPAD_3)
KEY(XK_KP_4, KINC_KEY_NUMPAD_4)
KEY(XK_KP_5, KINC_KEY_NUMPAD_5)
KEY(XK_KP_6, KINC_KEY_NUMPAD_6)
KEY(XK_KP_7, KINC_KEY_NUMPAD_7)
KEY(XK_KP_8, KINC_KEY_NUMPAD_8)
KEY(XK_KP_9, KINC_KEY_NUMPAD_9)
KEY(XK_KP_Insert, KINC_KEY_INSERT)
KEY(XK_KP_Delete, KINC_KEY_DELETE)
KEY(XK_KP_End, KINC_KEY_END)
KEY(XK_KP_Home, KINC_KEY_HOME)
KEY(XK_KP_Left, KINC_KEY_LEFT)
KEY(XK_KP_Up, KINC_KEY_UP)
KEY(XK_KP_Right, KINC_KEY_RIGHT)
KEY(XK_KP_Down, KINC_KEY_DOWN)
KEY(XK_KP_Page_Up, KINC_KEY_PAGE_UP)
KEY(XK_KP_Page_Down, KINC_KEY_PAGE_DOWN)
KEY(XK_Menu, KINC_KEY_CONTEXT_MENU)
KEY(XK_a, KINC_KEY_A)
KEY(XK_b, KINC_KEY_B)
KEY(XK_c, KINC_KEY_C)
KEY(XK_d, KINC_KEY_D)
KEY(XK_e, KINC_KEY_E)
KEY(XK_f, KINC_KEY_F)
KEY(XK_g, KINC_KEY_G)
KEY(XK_h, KINC_KEY_H)
KEY(XK_i, KINC_KEY_I)
KEY(XK_j, KINC_KEY_J)
KEY(XK_k, KINC_KEY_K)
KEY(XK_l, KINC_KEY_L)
KEY(XK_m, KINC_KEY_M)
KEY(XK_n, KINC_KEY_N)
KEY(XK_o, KINC_KEY_O)
KEY(XK_p, KINC_KEY_P)
KEY(XK_q, KINC_KEY_Q)
KEY(XK_r, KINC_KEY_R)
KEY(XK_s, KINC_KEY_S)
KEY(XK_t, KINC_KEY_T)
KEY(XK_u, KINC_KEY_U)
KEY(XK_v, KINC_KEY_V)
KEY(XK_w, KINC_KEY_W)
KEY(XK_x, KINC_KEY_X)
KEY(XK_y, KINC_KEY_Y)
KEY(XK_z, KINC_KEY_Z)
KEY(XK_1, KINC_KEY_1)
KEY(XK_2, KINC_KEY_2)
KEY(XK_3, KINC_KEY_3)
KEY(XK_4, KINC_KEY_4)
KEY(XK_5, KINC_KEY_5)
KEY(XK_6, KINC_KEY_6)
KEY(XK_7, KINC_KEY_7)
KEY(XK_8, KINC_KEY_8)
KEY(XK_9, KINC_KEY_9)
KEY(XK_0, KINC_KEY_0)
KEY(XK_Escape, KINC_KEY_ESCAPE)
KEY(XK_F1, KINC_KEY_F1)
KEY(XK_F2, KINC_KEY_F2)
KEY(XK_F3, KINC_KEY_F3)
KEY(XK_F4, KINC_KEY_F4)
KEY(XK_F5, KINC_KEY_F5)
KEY(XK_F6, KINC_KEY_F6)
KEY(XK_F7, KINC_KEY_F7)
KEY(XK_F8, KINC_KEY_F8)
KEY(XK_F9, KINC_KEY_F9)
KEY(XK_F10, KINC_KEY_F10)
KEY(XK_F11, KINC_KEY_F11)
KEY(XK_F12, KINC_KEY_F12)
}
break;
#undef KEY
}
case ButtonPress: {
XButtonEvent *button = (XButtonEvent *)&event;
int window_index = k_window->window_index;
switch (button->button) {
case Button1:
kinc_internal_mouse_trigger_press(window_index, 0, button->x, button->y);
break;
case Button2:
kinc_internal_mouse_trigger_press(window_index, 2, button->x, button->y);
break;
case Button3:
kinc_internal_mouse_trigger_press(window_index, 1, button->x, button->y);
break;
// buttons 4-7 are for mouse wheel events because why not
case Button4:
case Button5:
case Button6:
case Button7:
break;
default:
kinc_internal_mouse_trigger_press(window_index, button->button - Button1 - 4, button->x, button->y);
break;
}
break;
}
case ButtonRelease: {
XButtonEvent *button = (XButtonEvent *)&event;
int window_index = k_window->window_index;
switch (button->button) {
case Button1:
kinc_internal_mouse_trigger_release(window_index, 0, button->x, button->y);
break;
case Button2:
kinc_internal_mouse_trigger_release(window_index, 2, button->x, button->y);
break;
case Button3:
kinc_internal_mouse_trigger_release(window_index, 1, button->x, button->y);
break;
// Button4 and Button5 provide mouse wheel events because why not
case Button4:
kinc_internal_mouse_trigger_scroll(window_index, -1);
break;
case Button5:
kinc_internal_mouse_trigger_scroll(window_index, 1);
break;
// button 6 and 7 seem to be horizontal scrolling, which is not exposed in Kinc's api at the moment
case Button6:
case Button7:
break;
default:
kinc_internal_mouse_trigger_release(window_index, button->button - Button1 - 4, button->x, button->y);
break;
}
break;
}
case MotionNotify: {
XMotionEvent *motion = (XMotionEvent *)&event;
kinc_internal_mouse_trigger_move(k_window->window_index, motion->x, motion->y);
break;
}
case ConfigureNotify: {
if (event.xconfigure.width != k_window->width || event.xconfigure.height != k_window->height) {
k_window->width = event.xconfigure.width;
k_window->height = event.xconfigure.height;
kinc_internal_resize(k_window->window_index, event.xconfigure.width, event.xconfigure.height);
kinc_internal_call_resize_callback(k_window->window_index, event.xconfigure.width, event.xconfigure.height);
}
break;
}
case ClientMessage: {
if (event.xclient.message_type == x11_ctx.atoms.XdndEnter) {
Window source_window = event.xclient.data.l[0];
XEvent m;
memset(&m, 0, sizeof(m));
m.type = ClientMessage;
m.xclient.window = event.xclient.data.l[0];
m.xclient.message_type = x11_ctx.atoms.XdndStatus;
m.xclient.format = 32;
m.xclient.data.l[0] = window;
m.xclient.data.l[2] = 0;
m.xclient.data.l[3] = 0;
m.xclient.data.l[1] = 1;
m.xclient.data.l[4] = x11_ctx.atoms.XdndActionCopy;
xlib.XSendEvent(x11_ctx.display, source_window, false, NoEventMask, (XEvent *)&m);
xlib.XFlush(x11_ctx.display);
}
else if (event.xclient.message_type == x11_ctx.atoms.XdndDrop) {
xlib.XConvertSelection(x11_ctx.display, x11_ctx.atoms.XdndSelection, x11_ctx.atoms.XdndTextUriList, x11_ctx.atoms.XdndSelection, window,
event.xclient.data.l[2]);
}
else if (event.xclient.data.l[0] == x11_ctx.atoms.WM_DELETE_WINDOW) {
if (kinc_internal_call_close_callback(k_window->window_index)) {
kinc_window_destroy(k_window->window_index);
if (x11_ctx.num_windows <= 0) {
// no windows left, stop
kinc_stop();
}
}
}
}; break;
case SelectionNotify: {
if (event.xselection.selection == x11_ctx.atoms.CLIPBOARD) {
char *result;
unsigned long ressize, restail;
int resbits;
xlib.XGetWindowProperty(x11_ctx.display, window, x11_ctx.atoms.XSEL_DATA, 0, LONG_MAX / 4, False, AnyPropertyType, &x11_ctx.atoms.UTF8_STRING,
&resbits, &ressize, &restail, (unsigned char **)&result);
kinc_internal_paste_callback(result);
xlib.XFree(result);
}
else if (event.xselection.property == x11_ctx.atoms.XdndSelection) {
Atom type;
int format;
unsigned long numItems;
unsigned long bytesAfter = 1;
unsigned char *data = 0;
xlib.XGetWindowProperty(x11_ctx.display, event.xselection.requestor, event.xselection.property, 0, LONG_MAX, False, event.xselection.target,
&type, &format, &numItems, &bytesAfter, &data);
size_t pos = 0;
size_t len = 0;
while (pos < numItems) {
if (data[pos] == '\r') { // Found a file
wchar_t filePath[len + 1];
mbstowcs(filePath, buffer, len);
filePath[len] = 0;
kinc_internal_drop_files_callback(filePath + 7); // Strip file://
pos += 2; // Avoid \n
len = 0;
}
buffer[len++] = data[pos++];
}
xlib.XFree(data);
}
break;
}
case SelectionRequest: {
if (event.xselectionrequest.target == x11_ctx.atoms.TARGETS) {
XEvent send;
send.xselection.type = SelectionNotify;
send.xselection.requestor = event.xselectionrequest.requestor;
send.xselection.selection = event.xselectionrequest.selection;
send.xselection.target = event.xselectionrequest.target;
send.xselection.property = event.xselectionrequest.property;
send.xselection.time = event.xselectionrequest.time;
Atom available[] = {x11_ctx.atoms.TARGETS, x11_ctx.atoms.MULTIPLE, x11_ctx.atoms.TEXT_PLAIN, x11_ctx.atoms.UTF8_STRING};
xlib.XChangeProperty(x11_ctx.display, send.xselection.requestor, send.xselection.property, XA_ATOM, 32, PropModeReplace,
(unsigned char *)&available[0], 4);
xlib.XSendEvent(x11_ctx.display, send.xselection.requestor, True, 0, &send);
}
if (event.xselectionrequest.target == x11_ctx.atoms.TEXT_PLAIN || event.xselectionrequest.target == x11_ctx.atoms.UTF8_STRING) {
XEvent send;
send.xselection.type = SelectionNotify;
send.xselection.requestor = event.xselectionrequest.requestor;
send.xselection.selection = event.xselectionrequest.selection;
send.xselection.target = event.xselectionrequest.target;
send.xselection.property = event.xselectionrequest.property;
send.xselection.time = event.xselectionrequest.time;
xlib.XChangeProperty(x11_ctx.display, send.xselection.requestor, send.xselection.property, send.xselection.target, 8, PropModeReplace,
(const unsigned char *)clipboardString, strlen(clipboardString));
xlib.XSendEvent(x11_ctx.display, send.xselection.requestor, True, 0, &send);
}
break;
}
case Expose:
break;
case FocusIn: {
kinc_internal_foreground_callback();
break;
}
case FocusOut: {
controlDown = false;
ignoreKeycode = 0;
kinc_internal_background_callback();
break;
}
case LeaveNotify:
kinc_internal_mouse_trigger_leave_window(k_window->window_index);
break;
case EnterNotify:
x11_ctx.mouse.current_window = k_window->window_index;
kinc_internal_mouse_trigger_enter_window(k_window->window_index);
break;
}
}
return true;
}
void kinc_x11_copy_to_clipboard(const char *text) {
size_t textLength = strlen(text);
if (textLength >= clipboardStringSize) {
free(clipboardString);
clipboardStringSize = textLength + 1;
clipboardString = (char *)malloc(clipboardStringSize);
}
strcpy(clipboardString, text);
}
#ifdef KINC_EGL
EGLDisplay kinc_x11_egl_get_display() {
return eglGetDisplay(x11_ctx.display);
}
EGLNativeWindowType kinc_x11_egl_get_native_window(EGLDisplay display, EGLConfig config, int window_index) {
return (EGLNativeWindowType)x11_ctx.windows[window_index].window;
}
#endif
#ifdef KORE_VULKAN
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_xlib.h>
VkResult kinc_x11_vulkan_create_surface(VkInstance instance, int window_index, VkSurfaceKHR *surface) {
VkXlibSurfaceCreateInfoKHR info = {0};
info.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
info.pNext = NULL;
info.flags = 0;
info.dpy = x11_ctx.display;
info.window = x11_ctx.windows[window_index].window;
return vkCreateXlibSurfaceKHR(instance, &info, NULL, surface);
}
#include <assert.h>
void kinc_x11_vulkan_get_instance_extensions(const char **names, int *index, int max) {
assert(*index + 1 < max);
names[(*index)++] = VK_KHR_XLIB_SURFACE_EXTENSION_NAME;
}
VkBool32 kinc_x11_vulkan_get_physical_device_presentation_support(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex) {
return vkGetPhysicalDeviceXlibPresentationSupportKHR(physicalDevice, queueFamilyIndex, x11_ctx.display,
DefaultVisual(x11_ctx.display, DefaultScreen(x11_ctx.display))->visualid);
}
#endif
void kinc_x11_mouse_lock(int window) {
kinc_mouse_hide();
int width = kinc_window_width(window);
int height = kinc_window_height(window);
int x, y;
kinc_mouse_get_position(window, &x, &y);
// Guess the new position of X and Y
int newX = x;
int newY = y;
// Correct the position of the X coordinate
// if the mouse is out the window
if (x < 0) {
newX -= x;
}
else if (x > width) {
newX -= x - width;
}
// Correct the position of the Y coordinate
// if the mouse is out the window
if (y < 0) {
newY -= y;
}
else if (y > height) {
newY -= y - height;
}
// Force the mouse to stay inside the window
kinc_mouse_set_position(window, newX, newY);
}
void kinc_x11_mouse_unlock(void) {
kinc_mouse_show();
}
bool kinc_x11_mouse_can_lock(void) {
return true;
}
static bool mouseHidden = false;
void kinc_x11_mouse_show() {
struct kinc_x11_window *window = &x11_ctx.windows[x11_ctx.mouse.current_window];
if (mouseHidden) {
xlib.XUndefineCursor(x11_ctx.display, window->window);
mouseHidden = false;
}
}
void kinc_x11_mouse_hide() {
struct kinc_x11_window *window = &x11_ctx.windows[x11_ctx.mouse.current_window];
if (!mouseHidden) {
XColor col;
col.pixel = 0;
col.red = 0;
col.green = 0;
col.blue = 0;
col.flags = DoRed | DoGreen | DoBlue;
col.pad = 0;
char data[1] = {'\0'};
Pixmap blank = xlib.XCreateBitmapFromData(x11_ctx.display, window->window, data, 1, 1);
Cursor cursor = xlib.XCreatePixmapCursor(x11_ctx.display, blank, blank, &col, &col, 0, 0);
xlib.XDefineCursor(x11_ctx.display, window->window, cursor);
xlib.XFreePixmap(x11_ctx.display, blank);
mouseHidden = true;
}
}
void kinc_x11_mouse_set_cursor(int cursorIndex) {
struct kinc_x11_window *window = &x11_ctx.windows[x11_ctx.mouse.current_window];
if (!mouseHidden) {
Cursor cursor;
switch (cursorIndex) {
case 0: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "arrow");
break;
}
case 1: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "hand1");
break;
}
case 2: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "xterm");
break;
}
case 3: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "sb_h_double_arrow");
break;
}
case 4: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "sb_v_double_arrow");
break;
}
case 5: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "top_right_corner");
break;
}
case 6: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "bottom_right_corner");
break;
}
case 7: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "top_left_corner");
break;
}
case 8: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "bottom_left_corner");
break;
}
case 9: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "grab");
break;
}
case 10: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "grabbing");
break;
}
case 11: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "not-allowed");
break;
}
case 12: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "watch");
break;
}
case 13: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "crosshair");
break;
}
default: {
cursor = xlib.XcursorLibraryLoadCursor(x11_ctx.display, "arrow");
break;
}
}
xlib.XDefineCursor(x11_ctx.display, window->window, cursor);
}
}
void kinc_x11_mouse_set_position(int window_index, int x, int y) {
struct kinc_x11_window *window = &x11_ctx.windows[window_index];
xlib.XWarpPointer(x11_ctx.display, None, window->window, 0, 0, 0, 0, x, y);
xlib.XFlush(x11_ctx.display); // Flushes the output buffer, therefore updates the cursor's position.
}
void kinc_x11_mouse_get_position(int window_index, int *x, int *y) {
struct kinc_x11_window *window = &x11_ctx.windows[window_index];
Window inwin;
Window inchildwin;
int rootx, rooty;
unsigned int mask;
xlib.XQueryPointer(x11_ctx.display, window->window, &inwin, &inchildwin, &rootx, &rooty, x, y, &mask);
}