diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Window_X11.c |
initial commit
Diffstat (limited to 'src/Window_X11.c')
-rw-r--r-- | src/Window_X11.c | 1459 |
1 files changed, 1459 insertions, 0 deletions
diff --git a/src/Window_X11.c b/src/Window_X11.c new file mode 100644 index 0000000..d38b4af --- /dev/null +++ b/src/Window_X11.c @@ -0,0 +1,1459 @@ +#include "Core.h" +#if CC_WIN_BACKEND == CC_WIN_BACKEND_X11 +#include "_WindowBase.h" +#include "String.h" +#include "Funcs.h" +#include "Bitmap.h" +#include "Options.h" +#include "Errors.h" +#include "Utils.h" +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/XKBlib.h> +#include <X11/XF86keysym.h> +#ifdef CC_BUILD_XINPUT2 +#include <X11/extensions/XInput2.h> +#endif +#include <stdio.h> + +#ifdef X_HAVE_UTF8_STRING +#define CC_BUILD_XIM +/* XIM support based off details described in */ +/* https://tedyin.com/posts/a-brief-intro-to-linux-input-method-framework/ */ +#endif + +#define _NET_WM_STATE_REMOVE 0 +#define _NET_WM_STATE_ADD 1 +#define _NET_WM_STATE_TOGGLE 2 + +static Display* win_display; +static Window win_rootWin, win_handle; +static XVisualInfo win_visual; +#ifdef CC_BUILD_XIM +static XIM win_xim; +static XIC win_xic; +#endif + +static Atom wm_destroy, net_wm_state, net_wm_ping; +static Atom net_wm_state_minimized; +static Atom net_wm_state_fullscreen; + +static Atom xa_clipboard, xa_targets, xa_utf8_string, xa_data_sel; +static Atom xa_atom = 4; +static cc_bool grabCursor; +static long win_eventMask = StructureNotifyMask | /* SubstructureNotifyMask | */ + ExposureMask | KeyReleaseMask | KeyPressMask | KeymapStateMask | + PointerMotionMask | FocusChangeMask | ButtonPressMask | ButtonReleaseMask | + EnterWindowMask | LeaveWindowMask | PropertyChangeMask; + +static int MapNativeKey(KeySym key, unsigned int state) { + if (key >= XK_0 && key <= XK_9) { return '0' + (key - XK_0); } + if (key >= XK_A && key <= XK_Z) { return 'A' + (key - XK_A); } + if (key >= XK_a && key <= XK_z) { return 'A' + (key - XK_a); } + + if (key >= XK_F1 && key <= XK_F24) { return CCKEY_F1 + (key - XK_F1); } + if (key >= XK_KP_0 && key <= XK_KP_9) { return CCKEY_KP0 + (key - XK_KP_0); } + + /* Same Num Lock behaviour as Windows and text editors */ + if (key >= XK_KP_Home && key <= XK_KP_Delete && !(state & Mod2Mask)) { + if (key == XK_KP_Home) return CCKEY_HOME; + if (key == XK_KP_Up) return CCKEY_UP; + if (key == XK_KP_Page_Up) return CCKEY_PAGEUP; + + if (key == XK_KP_Left) return CCKEY_LEFT; + if (key == XK_KP_Insert) return CCKEY_INSERT; + if (key == XK_KP_Right) return CCKEY_RIGHT; + + if (key == XK_KP_End) return CCKEY_END; + if (key == XK_KP_Down) return CCKEY_DOWN; + if (key == XK_KP_Page_Down) return CCKEY_PAGEDOWN; + } + + switch (key) { + case XF86XK_AudioLowerVolume: return CCKEY_VOLUME_DOWN; + case XF86XK_AudioMute: return CCKEY_VOLUME_MUTE; + case XF86XK_AudioRaiseVolume: return CCKEY_VOLUME_UP; + + case XF86XK_AudioPlay: return CCKEY_MEDIA_PLAY; + case XF86XK_AudioStop: return CCKEY_MEDIA_STOP; + case XF86XK_AudioPrev: return CCKEY_MEDIA_PREV; + case XF86XK_AudioNext: return CCKEY_MEDIA_NEXT; + + case XF86XK_HomePage: return CCKEY_BROWSER_HOME; + case XF86XK_Mail: return CCKEY_LAUNCH_MAIL; + case XF86XK_Search: return CCKEY_BROWSER_SEARCH; + case XF86XK_Calculator: return CCKEY_LAUNCH_CALC; + + case XF86XK_Back: return CCKEY_BROWSER_PREV; + case XF86XK_Forward: return CCKEY_BROWSER_NEXT; + case XF86XK_Stop: return CCKEY_BROWSER_STOP; + case XF86XK_Refresh: return CCKEY_BROWSER_REFRESH; + case XF86XK_Sleep: return CCKEY_SLEEP; + case XF86XK_Favorites: return CCKEY_BROWSER_FAVORITES; + case XF86XK_AudioMedia: return CCKEY_LAUNCH_MEDIA; + case XF86XK_MyComputer: return CCKEY_LAUNCH_APP1; + } + + /* A chromebook user reported issues with pressing some keys: */ + /* tilde - "Unknown key press: (8000060, 800007E) */ + /* quote - "Unknown key press: (8000027, 8000022) */ + /* Note if 8000 is stripped, you get '0060' (XK_grave) and 0027 (XK_apostrophe) */ + /* ChromeOS seems to also mask to 0xFFFF, so I also do so here */ + /* https://chromium.googlesource.com/chromium/src/+/lkgr/ui/events/keycodes/keyboard_code_conversion_x.cc */ + key &= 0xFFFF; + + switch (key) { + case XK_Escape: return CCKEY_ESCAPE; + case XK_Return: return CCKEY_ENTER; + case XK_space: return CCKEY_SPACE; + case XK_BackSpace: return CCKEY_BACKSPACE; + + case XK_Shift_L: return CCKEY_LSHIFT; + case XK_Shift_R: return CCKEY_RSHIFT; + case XK_Alt_L: return CCKEY_LALT; + case XK_Alt_R: return CCKEY_RALT; + case XK_Control_L: return CCKEY_LCTRL; + case XK_Control_R: return CCKEY_RCTRL; + case XK_Super_L: return CCKEY_LWIN; + case XK_Super_R: return CCKEY_RWIN; + case XK_Meta_L: return CCKEY_LWIN; + case XK_Meta_R: return CCKEY_RWIN; + + case XK_Menu: return CCKEY_MENU; + case XK_Tab: return CCKEY_TAB; + case XK_minus: return CCKEY_MINUS; + case XK_plus: return CCKEY_EQUALS; + case XK_equal: return CCKEY_EQUALS; + + case XK_Caps_Lock: return CCKEY_CAPSLOCK; + case XK_Num_Lock: return CCKEY_NUMLOCK; + + case XK_Pause: return CCKEY_PAUSE; + case XK_Break: return CCKEY_PAUSE; + case XK_Scroll_Lock: return CCKEY_SCROLLLOCK; + case XK_Insert: return CCKEY_INSERT; + case XK_Print: return CCKEY_PRINTSCREEN; + case XK_Sys_Req: return CCKEY_PRINTSCREEN; + + case XK_backslash: return CCKEY_BACKSLASH; + case XK_bar: return CCKEY_BACKSLASH; + case XK_braceleft: return CCKEY_LBRACKET; + case XK_bracketleft: return CCKEY_LBRACKET; + case XK_braceright: return CCKEY_RBRACKET; + case XK_bracketright: return CCKEY_RBRACKET; + case XK_colon: return CCKEY_SEMICOLON; + case XK_semicolon: return CCKEY_SEMICOLON; + case XK_quoteright: return CCKEY_QUOTE; + case XK_quotedbl: return CCKEY_QUOTE; + case XK_quoteleft: return CCKEY_TILDE; + case XK_asciitilde: return CCKEY_TILDE; + + case XK_comma: return CCKEY_COMMA; + case XK_less: return CCKEY_COMMA; + case XK_period: return CCKEY_PERIOD; + case XK_greater: return CCKEY_PERIOD; + case XK_slash: return CCKEY_SLASH; + case XK_question: return CCKEY_SLASH; + + case XK_Left: return CCKEY_LEFT; + case XK_Down: return CCKEY_DOWN; + case XK_Right: return CCKEY_RIGHT; + case XK_Up: return CCKEY_UP; + + case XK_Delete: return CCKEY_DELETE; + case XK_Home: return CCKEY_HOME; + case XK_End: return CCKEY_END; + case XK_Page_Up: return CCKEY_PAGEUP; + case XK_Page_Down: return CCKEY_PAGEDOWN; + + case XK_KP_Add: return CCKEY_KP_PLUS; + case XK_KP_Subtract: return CCKEY_KP_MINUS; + case XK_KP_Multiply: return CCKEY_KP_MULTIPLY; + case XK_KP_Divide: return CCKEY_KP_DIVIDE; + case XK_KP_Decimal: return CCKEY_KP_DECIMAL; + case XK_KP_Insert: return CCKEY_KP0; + case XK_KP_End: return CCKEY_KP1; + case XK_KP_Down: return CCKEY_KP2; + case XK_KP_Page_Down: return CCKEY_KP3; + case XK_KP_Left: return CCKEY_KP4; + case XK_KP_Begin: return CCKEY_KP5; + case XK_KP_Right: return CCKEY_KP6; + case XK_KP_Home: return CCKEY_KP7; + case XK_KP_Up: return CCKEY_KP8; + case XK_KP_Page_Up: return CCKEY_KP9; + case XK_KP_Delete: return CCKEY_KP_DECIMAL; + case XK_KP_Enter: return CCKEY_KP_ENTER; + + case XK_ISO_Level3_Shift: return CCKEY_RALT; /* AltGr mode switch on some European keyboard layouts */ + } + return INPUT_NONE; +} + +/* NOTE: This may not be entirely accurate, because user can configure keycode mappings */ +static const cc_uint8 keycodeMap[136] = { +/* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, +/* 08 */ 0, CCKEY_ESCAPE, '1', '2', '3', '4', '5', '6', +/* 10 */ '7', '8', '9', '0', CCKEY_MINUS, CCKEY_EQUALS, CCKEY_BACKSPACE, CCKEY_TAB, +/* 18 */ 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', +/* 20 */ 'O', 'P', CCKEY_LBRACKET, CCKEY_RBRACKET, CCKEY_ENTER, CCKEY_LCTRL, 'A', 'S', +/* 28 */ 'D', 'F', 'G', 'H', 'J', 'K', 'L', CCKEY_SEMICOLON, +/* 30 */ CCKEY_QUOTE, CCKEY_TILDE, CCKEY_LSHIFT, CCKEY_BACKSLASH, 'Z', 'X', 'C', 'V', +/* 38 */ 'B', 'N', 'M', CCKEY_PERIOD, CCKEY_COMMA, CCKEY_SLASH, CCKEY_RSHIFT, CCKEY_KP_MULTIPLY, +/* 40 */ CCKEY_LALT, CCKEY_SPACE, CCKEY_CAPSLOCK, CCKEY_F1, CCKEY_F2, CCKEY_F3, CCKEY_F4, CCKEY_F5, +/* 48 */ CCKEY_F6, CCKEY_F7, CCKEY_F8, CCKEY_F9, CCKEY_F10, CCKEY_NUMLOCK, CCKEY_SCROLLLOCK, CCKEY_KP7, +/* 50 */ CCKEY_KP8, CCKEY_KP9, CCKEY_KP_MINUS, CCKEY_KP4, CCKEY_KP5, CCKEY_KP6, CCKEY_KP_PLUS, CCKEY_KP1, +/* 58 */ CCKEY_KP2, CCKEY_KP3, CCKEY_KP0, CCKEY_KP_DECIMAL, 0, 0, 0, CCKEY_F11, +/* 60 */ CCKEY_F12, 0, 0, 0, 0, 0, 0, 0, +/* 68 */ 0, 0, 0, 0, CCKEY_RALT, CCKEY_RCTRL, CCKEY_HOME, CCKEY_UP, +/* 70 */ CCKEY_PAGEUP, CCKEY_LEFT, CCKEY_RIGHT, CCKEY_END, CCKEY_DOWN, CCKEY_PAGEDOWN, CCKEY_INSERT, CCKEY_DELETE, +/* 78 */ 0, 0, 0, 0, 0, 0, 0, CCKEY_PAUSE, +/* 80 */ 0, 0, 0, 0, 0, CCKEY_LWIN, 0, CCKEY_RWIN +}; + +static int MapNativeKeycode(unsigned int keycode) { + return keycode < Array_Elems(keycodeMap) ? keycodeMap[keycode] : 0; +} + +static void RegisterAtoms(void) { + Display* display = win_display; + wm_destroy = XInternAtom(display, "WM_DELETE_WINDOW", true); + net_wm_state = XInternAtom(display, "_NET_WM_STATE", false); + net_wm_ping = XInternAtom(display, "_NET_WM_PING", false); + net_wm_state_minimized = XInternAtom(display, "_NET_WM_STATE_MINIMIZED", false); + net_wm_state_fullscreen = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", false); + + xa_clipboard = XInternAtom(display, "CLIPBOARD", false); + xa_targets = XInternAtom(display, "TARGETS", false); + xa_utf8_string = XInternAtom(display, "UTF8_STRING", false); + xa_data_sel = XInternAtom(display, "CC_SEL_DATA", false); +} + +static void RefreshWindowBounds(int width, int height) { + if (width != Window_Main.Width || height != Window_Main.Height) { + Window_Main.Width = width; + Window_Main.Height = height; + Event_RaiseVoid(&WindowEvents.Resized); + } +} + +typedef int (*X11_ErrorHandler)(Display* dpy, XErrorEvent* ev); +typedef int (*X11_IOErrorHandler)(Display* dpy); +static X11_ErrorHandler realXErrorHandler; +static X11_IOErrorHandler realXIOErrorHandler; + +static void LogXErrorCore(const char* msg) { + char traceBuffer[2048]; + cc_string trace; + Platform_LogConst(msg); + + String_InitArray(trace, traceBuffer); + Logger_Backtrace(&trace, NULL); + Platform_Log(traceBuffer, trace.length); +} + +static int LogXError(Display* dpy, XErrorEvent* ev) { + LogXErrorCore("== unhandled X11 error =="); + return realXErrorHandler(dpy, ev); +} + +static int LogXIOError(Display* dpy) { + LogXErrorCore("== unhandled XIO error =="); + return realXIOErrorHandler(dpy); +} + +static void HookXErrors(void) { + realXErrorHandler = XSetErrorHandler(LogXError); + realXIOErrorHandler = XSetIOErrorHandler(LogXIOError); +} + + +/*########################################################################################################################* +*--------------------------------------------------Public implementation--------------------------------------------------* +*#########################################################################################################################*/ +#if defined CC_BUILD_EGL || !(CC_GFX_BACKEND & CC_GFX_BACKEND_GL_MASK) +static XVisualInfo GLContext_SelectVisual(void) { + XVisualInfo info; + cc_result res; + int screen = DefaultScreen(win_display); + + res = XMatchVisualInfo(win_display, screen, 24, TrueColor, &info) || + XMatchVisualInfo(win_display, screen, 32, TrueColor, &info); + + if (!res) Logger_Abort("Selecting visual"); + return info; +} +#else +static XVisualInfo GLContext_SelectVisual(void); +#endif + +void Window_PreInit(void) { } +void Window_Init(void) { + Display* display = XOpenDisplay(NULL); + int screen; + + if (!display) Logger_Abort("Failed to open the X11 display. No X server running?"); + screen = DefaultScreen(display); + HookXErrors(); + + win_display = display; + win_rootWin = RootWindow(display, screen); + Input.Sources = INPUT_SOURCE_NORMAL; + + /* TODO: Use Xinerama and XRandR for querying these */ + DisplayInfo.Width = DisplayWidth(display, screen); + DisplayInfo.Height = DisplayHeight(display, screen); + DisplayInfo.Depth = DefaultDepth(display, screen); + DisplayInfo.ScaleX = 1; + DisplayInfo.ScaleY = 1; +} + +void Window_Free(void) { } + +#ifdef CC_BUILD_ICON +/* See misc/linux/linux_icon_gen.cs for how to generate this file */ +#include "../misc/linux/CCIcon_X11.h" + +static void ApplyIcon(void) { + Atom net_wm_icon = XInternAtom(win_display, "_NET_WM_ICON", false); + Atom xa_cardinal = XInternAtom(win_display, "CARDINAL", false); + + XChangeProperty(win_display, win_handle, net_wm_icon, xa_cardinal, 32, PropModeReplace, + (unsigned char*)CCIcon_Data, CCIcon_Size); +} +#else +static void ApplyIcon(void) { } +#endif + +static void DoCreateWindow(int width, int height) { + XSetWindowAttributes attributes = { 0 }; + XSizeHints hints = { 0 }; + Atom protocols[2]; + int supported, x, y; + Window focus; + int focusRevert; + + x = Display_CentreX(width); + y = Display_CentreY(height); + RegisterAtoms(); + win_visual = GLContext_SelectVisual(); + + Platform_Log1("Created window (visual id: %h)", &win_visual.visualid); + attributes.colormap = XCreateColormap(win_display, win_rootWin, win_visual.visual, AllocNone); + attributes.event_mask = win_eventMask; + + win_handle = XCreateWindow(win_display, win_rootWin, x, y, width, height, + 0, win_visual.depth /* CopyFromParent*/, InputOutput, win_visual.visual, + CWColormap | CWEventMask, &attributes); + if (!win_handle) Logger_Abort("XCreateWindow failed"); + +#ifdef CC_BUILD_XIM + win_xim = XOpenIM(win_display, NULL, NULL, NULL); + win_xic = XCreateIC(win_xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win_handle, NULL); +#endif + + /* Set hints to try to force WM to create window at requested x,y */ + /* Without this, some WMs will instead place the window whereever */ + hints.base_width = width; + hints.base_height = height; + hints.flags = PSize | PPosition; + XSetWMNormalHints(win_display, win_handle, &hints); + + /* Register for window destroy notification */ + protocols[0] = wm_destroy; + protocols[1] = net_wm_ping; + XSetWMProtocols(win_display, win_handle, protocols, 2); + + /* Request that auto-repeat is only set on devices that support it physically. + This typically means that it's turned off for keyboards (which is what we want). + We prefer this method over XAutoRepeatOff/On, because the latter needs to + be reset before the program exits. */ + XkbSetDetectableAutoRepeat(win_display, true, &supported); + + RefreshWindowBounds(width, height); + Window_Main.Exists = true; + Window_Main.Handle = (void*)win_handle; + grabCursor = Options_GetBool(OPT_GRAB_CURSOR, false); + + /* So right name appears in e.g. Ubuntu Unity launchbar */ + XClassHint hint = { 0 }; + #ifdef CC_BUILD_FLATPAK + hint.res_name = "net.classicube.flatpak.client"; + hint.res_class = "net.classicube.flatpak.client"; + #else + hint.res_name = GAME_APP_TITLE; + hint.res_class = GAME_APP_TITLE; + #endif + XSetClassHint(win_display, win_handle, &hint); + ApplyIcon(); + + /* Check for focus initially, in case WM doesn't send a FocusIn event */ + XGetInputFocus(win_display, &focus, &focusRevert); + if (focus == win_handle) Window_Main.Focused = true; +} +void Window_Create2D(int width, int height) { DoCreateWindow(width, height); } +void Window_Create3D(int width, int height) { DoCreateWindow(width, height); } + +void Window_SetTitle(const cc_string* title) { + char str[NATIVE_STR_LEN]; + String_EncodeUtf8(str, title); + XStoreName(win_display, win_handle, str); +} + +static char clipboard_copy_buffer[256]; +static char clipboard_paste_buffer[256]; +static cc_string clipboard_copy_text = String_FromArray(clipboard_copy_buffer); +static cc_string clipboard_paste_text = String_FromArray(clipboard_paste_buffer); +static cc_bool clipboard_paste_received; + +void Clipboard_GetText(cc_string* value) { + Window owner = XGetSelectionOwner(win_display, xa_clipboard); + int i; + if (!owner) return; /* no window owner */ + + XConvertSelection(win_display, xa_clipboard, xa_utf8_string, xa_data_sel, win_handle, 0); + clipboard_paste_received = false; + clipboard_paste_text.length = 0; + + /* wait up to 1 second for SelectionNotify event to arrive */ + for (i = 0; i < 100; i++) { + Window_ProcessEvents(0.0); + if (clipboard_paste_received) { + String_AppendString(value, &clipboard_paste_text); + return; + } else { + Thread_Sleep(10); + } + } +} + +void Clipboard_SetText(const cc_string* value) { + String_Copy(&clipboard_copy_text, value); + XSetSelectionOwner(win_display, xa_clipboard, win_handle, 0); +} + +int Window_GetWindowState(void) { + cc_bool fullscreen = false, minimised = false; + Atom prop_type; + unsigned long items, after; + unsigned char* data = NULL; + int i, prop_format; + Atom* list; + + XGetWindowProperty(win_display, win_handle, + net_wm_state, 0, 256, false, xa_atom, &prop_type, + &prop_format, &items, &after, &data); + + if (!data) return WINDOW_STATE_NORMAL; + list = (Atom*)data; + + for (i = 0; i < items; i++) { + Atom atom = list[i]; + + if (atom == net_wm_state_minimized) { + minimised = true; + } else if (atom == net_wm_state_fullscreen) { + fullscreen = true; + } + } + XFree(data); + + if (fullscreen) return WINDOW_STATE_FULLSCREEN; + if (minimised) return WINDOW_STATE_MINIMISED; + return WINDOW_STATE_NORMAL; +} + +static void ToggleFullscreen(long op) { + XEvent ev = { 0 }; + ev.xclient.type = ClientMessage; + ev.xclient.window = win_handle; + ev.xclient.message_type = net_wm_state; + ev.xclient.format = 32; + ev.xclient.data.l[0] = op; + ev.xclient.data.l[1] = net_wm_state_fullscreen; + + XSendEvent(win_display, win_rootWin, false, + SubstructureRedirectMask | SubstructureNotifyMask, &ev); + XSync(win_display, false); + XRaiseWindow(win_display, win_handle); + Window_ProcessEvents(0.0); +} + +cc_result Window_EnterFullscreen(void) { + ToggleFullscreen(_NET_WM_STATE_ADD); return 0; +} +cc_result Window_ExitFullscreen(void) { + ToggleFullscreen(_NET_WM_STATE_REMOVE); return 0; +} + +int Window_IsObscured(void) { return 0; } + +void Window_Show(void) { XMapWindow(win_display, win_handle); } + +void Window_SetSize(int width, int height) { + XResizeWindow(win_display, win_handle, width, height); + Window_ProcessEvents(0.0); +} + +void Window_RequestClose(void) { + XEvent ev = { 0 }; + ev.type = ClientMessage; + ev.xclient.format = 32; + ev.xclient.display = win_display; + ev.xclient.window = win_handle; + ev.xclient.data.l[0] = wm_destroy; + + XSendEvent(win_display, win_handle, false, 0, &ev); + XFlush(win_display); +} + +static int MapNativeMouse(int button) { + if (button == 1) return CCMOUSE_L; + if (button == 2) return CCMOUSE_M; + if (button == 3) return CCMOUSE_R; + + if (button == 8) return CCMOUSE_X1; + if (button == 9) return CCMOUSE_X2; + if (button == 10) return CCMOUSE_X3; + if (button == 11) return CCMOUSE_X4; + if (button == 12) return CCMOUSE_X5; + if (button == 13) return CCMOUSE_X6; + + /* Mouse horizontal and vertical scroll */ + if (button >= 4 && button <= 7) return 0; + Platform_Log1("Unknown mouse button: %i", &button); + return 0; +} + +static int TryGetKey(XKeyEvent* ev) { + KeySym keysym1 = XLookupKeysym(ev, 0); + KeySym keysym2 = XLookupKeysym(ev, 1); + + int key = MapNativeKey(keysym1, ev->state); + if (!key) key = MapNativeKey(keysym2, ev->state); + if (key) return key; + + Platform_Log3("Unknown key %i (%x, %x)", &ev->keycode, &keysym1, &keysym2); + /* The user may be using a keyboard layout such as cryllic - */ + /* fallback to trying to conver the raw scancodes instead */ + return MapNativeKeycode(ev->keycode); +} + +static Atom Window_GetSelectionProperty(XEvent* e) { + Atom prop = e->xselectionrequest.property; + if (prop) return prop; + + /* For obsolete clients. See ICCCM spec, selections chapter for reasoning. */ + return e->xselectionrequest.target; +} + +static Bool FilterEvent(Display* d, XEvent* e, XPointer w) { + return + e->xany.window == (Window)w || + !e->xany.window || /* KeymapNotify events don't have a window */ + e->type == GenericEvent; /* For XInput events */ +} + +static void HandleWMDestroy(void) { + Platform_LogConst("Exit message received."); + Event_RaiseVoid(&WindowEvents.Closing); + + /* sync and discard all events queued */ + XSync(win_display, true); + XDestroyWindow(win_display, win_handle); + Window_Main.Exists = false; +} + +static void HandleWMPing(XEvent* e) { + e->xany.window = win_rootWin; + XSendEvent(win_display, win_rootWin, false, + SubstructureRedirectMask | SubstructureNotifyMask, e); +} +static void HandleGenericEvent(XEvent* e); + +void Window_ProcessEvents(float delta) { + XEvent e; + Window focus; + int focusRevert; + int i, btn, key, status; + + while (Window_Main.Exists) { + if (!XCheckIfEvent(win_display, &e, FilterEvent, (XPointer)win_handle)) break; + if (XFilterEvent(&e, None) == True) continue; + + switch (e.type) { + case GenericEvent: + HandleGenericEvent(&e); break; + case ClientMessage: + if (e.xclient.data.l[0] == wm_destroy) { + HandleWMDestroy(); + } else if (e.xclient.data.l[0] == net_wm_ping) { + HandleWMPing(&e); + } + break; + + case DestroyNotify: + Platform_LogConst("Window destroyed"); + Window_Main.Exists = false; + break; + + case ConfigureNotify: + RefreshWindowBounds(e.xconfigure.width, e.xconfigure.height); + break; + + case Expose: + if (e.xexpose.count == 0) Event_RaiseVoid(&WindowEvents.RedrawNeeded); + break; + + case LeaveNotify: + XGetInputFocus(win_display, &focus, &focusRevert); + if (focus == PointerRoot) { + Window_Main.Focused = false; Event_RaiseVoid(&WindowEvents.FocusChanged); + } + break; + + case EnterNotify: + XGetInputFocus(win_display, &focus, &focusRevert); + if (focus == PointerRoot) { + Window_Main.Focused = true; Event_RaiseVoid(&WindowEvents.FocusChanged); + } + break; + + case KeyPress: + { + char data[64]; + key = TryGetKey(&e.xkey); + if (key) Input_SetPressed(key); + +#ifdef CC_BUILD_XIM + cc_codepoint cp; + char* chars = data; + Status status_type; + + status = Xutf8LookupString(win_xic, &e.xkey, data, Array_Elems(data), NULL, &status_type); + while (status > 0) { + i = Convert_Utf8ToCodepoint(&cp, chars, status); + if (!i) break; + + Event_RaiseInt(&InputEvents.Press, cp); + status -= i; chars += i; + } +#else + /* Treat the 8 bit characters as first 256 unicode codepoints */ + /* This only really works for latin keys (e.g. so some finnish keys still work) */ + status = XLookupString(&e.xkey, data, Array_Elems(data), NULL, NULL); + for (i = 0; i < status; i++) { + Event_RaiseInt(&InputEvents.Press, (cc_uint8)data[i]); + } +#endif + } break; + + case KeyRelease: + key = TryGetKey(&e.xkey); + if (key) Input_SetReleased(key); + break; + + case ButtonPress: + btn = MapNativeMouse(e.xbutton.button); + if (btn) Input_SetPressed(btn); + else if (e.xbutton.button == 4) Mouse_ScrollVWheel( +1); + else if (e.xbutton.button == 5) Mouse_ScrollVWheel( -1); + else if (e.xbutton.button == 6) Mouse_ScrollHWheel(+1); + else if (e.xbutton.button == 7) Mouse_ScrollHWheel(-1); + break; + + case ButtonRelease: + btn = MapNativeMouse(e.xbutton.button); + if (btn) Input_SetReleased(btn); + break; + + case MotionNotify: + Pointer_SetPosition(0, e.xmotion.x, e.xmotion.y); + break; + + case FocusIn: + case FocusOut: + /* Don't lose focus when another app grabs key or mouse */ + if (e.xfocus.mode == NotifyGrab || e.xfocus.mode == NotifyUngrab) break; + + Window_Main.Focused = e.type == FocusIn; + Event_RaiseVoid(&WindowEvents.FocusChanged); + /* TODO: Keep track of keyboard when focus is lost */ + if (!Window_Main.Focused) Input_Clear(); + break; + + case MappingNotify: + if (e.xmapping.request == MappingModifier || e.xmapping.request == MappingKeyboard) { + Platform_LogConst("keyboard mapping refreshed"); + XRefreshKeyboardMapping(&e.xmapping); + } + break; + + case PropertyNotify: + if (e.xproperty.atom == net_wm_state) { + Event_RaiseVoid(&WindowEvents.StateChanged); + } + break; + + case SelectionNotify: + if (e.xselection.selection == xa_clipboard && e.xselection.target == xa_utf8_string && e.xselection.property == xa_data_sel) { + Atom prop_type; + int prop_format; + unsigned long items, after; + cc_uint8* data = NULL; + + XGetWindowProperty(win_display, win_handle, xa_data_sel, 0, 1024, false, 0, + &prop_type, &prop_format, &items, &after, &data); + XDeleteProperty(win_display, win_handle, xa_data_sel); + + if (data && items && prop_type == xa_utf8_string) { + clipboard_paste_received = true; + clipboard_paste_text.length = 0; + String_AppendUtf8(&clipboard_paste_text, data, items); + } + if (data) XFree(data); + } + break; + + case SelectionRequest: + { + XEvent reply = { 0 }; + reply.xselection.type = SelectionNotify; + reply.xselection.send_event = true; + reply.xselection.display = win_display; + reply.xselection.requestor = e.xselectionrequest.requestor; + reply.xselection.selection = e.xselectionrequest.selection; + reply.xselection.target = e.xselectionrequest.target; + reply.xselection.property = 0; + reply.xselection.time = e.xselectionrequest.time; + + if (e.xselectionrequest.selection == xa_clipboard && e.xselectionrequest.target == xa_utf8_string && clipboard_copy_text.length) { + reply.xselection.property = Window_GetSelectionProperty(&e); + char str[800]; + int len = String_EncodeUtf8(str, &clipboard_copy_text); + + XChangeProperty(win_display, reply.xselection.requestor, reply.xselection.property, xa_utf8_string, 8, + PropModeReplace, (unsigned char*)str, len); + } else if (e.xselectionrequest.selection == xa_clipboard && e.xselectionrequest.target == xa_targets) { + reply.xselection.property = Window_GetSelectionProperty(&e); + + Atom data[2] = { xa_utf8_string, xa_targets }; + XChangeProperty(win_display, reply.xselection.requestor, reply.xselection.property, xa_atom, 32, + PropModeReplace, (unsigned char*)data, 2); + } + XSendEvent(win_display, e.xselectionrequest.requestor, true, 0, &reply); + } break; + } + } +} + +void Window_ProcessGamepads(float delta) { } + +static void Cursor_GetRawPos(int* x, int* y) { + Window rootW, childW; + int childX, childY; + unsigned int mask; + XQueryPointer(win_display, win_rootWin, &rootW, &childW, x, y, &childX, &childY, &mask); +} + +void Cursor_SetPosition(int x, int y) { + XWarpPointer(win_display, None, win_handle, 0, 0, 0, 0, x, y); + XFlush(win_display); /* TODO: not sure if XFlush call is necessary */ +} + +static Cursor blankCursor; +static void Cursor_DoSetVisible(cc_bool visible) { + if (visible) { + XUndefineCursor(win_display, win_handle); + } else { + if (!blankCursor) { + char data = 0; + XColor col = { 0 }; + Pixmap pixmap = XCreateBitmapFromData(win_display, win_handle, &data, 1, 1); + blankCursor = XCreatePixmapCursor(win_display, pixmap, pixmap, &col, &col, 0, 0); + XFreePixmap(win_display, pixmap); + } + XDefineCursor(win_display, win_handle, blankCursor); + } +} + + +/*########################################################################################################################* +*-----------------------------------------------------X11 message box-----------------------------------------------------* +*#########################################################################################################################*/ +struct X11MessageBox { + Window win; + Display* dpy; + GC gc; + unsigned long white, black, background; + unsigned long btnBorder, highlight, shadow; +}; + +static unsigned long X11_Col(struct X11MessageBox* m, cc_uint8 r, cc_uint8 g, cc_uint8 b) { + Colormap cmap = XDefaultColormap(m->dpy, DefaultScreen(m->dpy)); + XColor col = { 0 }; + col.red = r << 8; + col.green = g << 8; + col.blue = b << 8; + col.flags = DoRed | DoGreen | DoBlue; + + XAllocColor(m->dpy, cmap, &col); + return col.pixel; +} + +static void X11MessageBox_Init(struct X11MessageBox* m) { + m->black = BlackPixel(m->dpy, DefaultScreen(m->dpy)); + m->white = WhitePixel(m->dpy, DefaultScreen(m->dpy)); + m->background = X11_Col(m, 206, 206, 206); + + m->btnBorder = X11_Col(m, 60, 60, 60); + m->highlight = X11_Col(m, 144, 144, 144); + m->shadow = X11_Col(m, 49, 49, 49); + + m->win = XCreateSimpleWindow(m->dpy, DefaultRootWindow(m->dpy), 0, 0, 100, 100, + 0, m->black, m->background); + XSelectInput(m->dpy, m->win, ExposureMask | StructureNotifyMask | + KeyReleaseMask | PointerMotionMask | + ButtonPressMask | ButtonReleaseMask ); + + m->gc = XCreateGC(m->dpy, m->win, 0, NULL); + XSetForeground(m->dpy, m->gc, m->black); + XSetBackground(m->dpy, m->gc, m->background); +} + +static void X11MessageBox_Free(struct X11MessageBox* m) { + XFreeGC(m->dpy, m->gc); + XDestroyWindow(m->dpy, m->win); +} + +struct X11Textbox { + int x, y, width, height; + int lineHeight, descent; + const char* text; +}; + +static void X11Textbox_Measure(struct X11Textbox* t, XFontStruct* font) { + cc_string str = String_FromReadonly(t->text), line; + XCharStruct overall; + int direction, ascent, descent, lines = 0; + + for (; str.length; lines++) { + String_UNSAFE_SplitBy(&str, '\n', &line); + XTextExtents(font, line.buffer, line.length, &direction, &ascent, &descent, &overall); + t->width = max(overall.width, t->width); + } + + t->lineHeight = ascent + descent; + t->descent = descent; + t->height = t->lineHeight * lines; +} + +static void X11Textbox_Draw(struct X11Textbox* t, struct X11MessageBox* m) { + cc_string str = String_FromReadonly(t->text), line; + int y = t->y + t->lineHeight - t->descent; /* TODO: is -descent even right? */ + + for (; str.length; y += t->lineHeight) { + String_UNSAFE_SplitBy(&str, '\n', &line); + XDrawString(m->dpy, m->win, m->gc, t->x, y, line.buffer, line.length); + } +} + +struct X11Button { + int x, y, width, height; + cc_bool clicked; + struct X11Textbox text; +}; + +static void X11Button_Draw(struct X11Button* b, struct X11MessageBox* m) { + struct X11Textbox* t; + int begX, endX, begY, endY; + + XSetForeground(m->dpy, m->gc, m->btnBorder); + XDrawRectangle(m->dpy, m->win, m->gc, b->x, b->y, + b->width, b->height); + + t = &b->text; + begX = b->x + 1; endX = b->x + b->width - 1; + begY = b->y + 1; endY = b->y + b->height - 1; + + if (b->clicked) { + XSetForeground(m->dpy, m->gc, m->highlight); + XDrawRectangle(m->dpy, m->win, m->gc, begX, begY, + endX - begX, endY - begY); + } else { + XSetForeground(m->dpy, m->gc, m->white); + XDrawLine(m->dpy, m->win, m->gc, begX, begY, + endX - 1, begY); + XDrawLine(m->dpy, m->win, m->gc, begX, begY, + begX, endY - 1); + + XSetForeground(m->dpy, m->gc, m->highlight); + XDrawLine(m->dpy, m->win, m->gc, begX + 1, endY - 1, + endX - 1, endY - 1); + XDrawLine(m->dpy, m->win, m->gc, endX - 1, begY + 1, + endX - 1, endY - 1); + + XSetForeground(m->dpy, m->gc, m->shadow); + XDrawLine(m->dpy, m->win, m->gc, begX, endY, endX, endY); + XDrawLine(m->dpy, m->win, m->gc, endX, begY, endX, endY); + } + + XSetForeground(m->dpy, m->gc, m->black); + t->x = b->x + b->clicked + (b->width - t->width) / 2; + t->y = b->y + b->clicked + (b->height - t->height) / 2; + X11Textbox_Draw(t, m); +} + +static int X11Button_Contains(struct X11Button* b, int x, int y) { + return x >= b->x && x < (b->x + b->width) && + y >= b->y && y < (b->y + b->height); +} + +static Bool X11_FilterEvent(Display* d, XEvent* e, XPointer w) { return e->xany.window == (Window)w; } +static void X11_MessageBox(const char* title, const char* text, struct X11MessageBox* m) { + struct X11Button ok = { 0 }; + struct X11Textbox body = { 0 }; + + Atom protocols[2]; + XFontStruct* font; + int x, y, width, height; + XSizeHints hints = { 0 }; + int mouseX = -1, mouseY = -1, over; + XEvent e; + + X11MessageBox_Init(m); + XMapWindow(m->dpy, m->win); + XStoreName(m->dpy, m->win, title); + + protocols[0] = XInternAtom(m->dpy, "WM_DELETE_WINDOW", false); + protocols[1] = XInternAtom(m->dpy, "_NET_WM_PING", false); + XSetWMProtocols(m->dpy, m->win, protocols, 2); + + font = XQueryFont(m->dpy, XGContextFromGC(m->gc)); + if (!font) return; + + /* Compute size of widgets */ + body.text = text; + X11Textbox_Measure(&body, font); + ok.text.text = "OK"; + X11Textbox_Measure(&ok.text, font); + ok.width = ok.text.width + 70; + ok.height = ok.text.height + 10; + + /* Compute size and position of window */ + width = body.width + 20; + height = body.height + 20 + ok.height + 20; + x = DisplayWidth (m->dpy, DefaultScreen(m->dpy))/2 - width/2; + y = DisplayHeight(m->dpy, DefaultScreen(m->dpy))/2 - height/2; + XMoveResizeWindow(m->dpy, m->win, x, y, width, height); + + /* Adjust bounds of widgets */ + body.x = 10; body.y = 10; + ok.x = width/2 - ok.width/2; + ok.y = height - ok.height - 10; + + /* This marks the window as popup window of the main window */ + /* http://tronche.com/gui/x/icccm/sec-4.html#WM_TRANSIENT_FOR */ + /* Depending on WM, removes minimise and doesn't show in taskbar */ + if (win_handle) XSetTransientForHint(m->dpy, m->win, win_handle); + + XFreeFontInfo(NULL, font, 1); + XUnmapWindow(m->dpy, m->win); /* Make window non resizeable */ + + hints.flags = PSize | PMinSize | PMaxSize; + hints.min_width = hints.max_width = hints.base_width = width; + hints.min_height = hints.max_height = hints.base_height = height; + + XSetWMNormalHints(m->dpy, m->win, &hints); + XMapRaised(m->dpy, m->win); + XFlush(m->dpy); + + for (;;) { + /* The naive solution is to use XNextEvent(m->dpy, &e) here. */ + /* However this causes issues as that removes events that */ + /* should have been delivered to the main game window. */ + /* (e.g. breaks initial window resize with i3 WM) */ + XIfEvent(m->dpy, &e, X11_FilterEvent, (XPointer)m->win); + + switch (e.type) + { + case ButtonPress: + case ButtonRelease: + if (e.xbutton.button != Button1) break; + over = X11Button_Contains(&ok, mouseX, mouseY); + + if (ok.clicked && e.type == ButtonRelease) { + if (over) return; + } + ok.clicked = e.type == ButtonPress && over; + /* fallthrough to redraw window */ + + case Expose: + case MapNotify: + XClearWindow(m->dpy, m->win); + X11Textbox_Draw(&body, m); + X11Button_Draw(&ok, m); + XFlush(m->dpy); + break; + + case KeyRelease: + if (XLookupKeysym(&e.xkey, 0) == XK_Escape) return; + break; + + case ClientMessage: + /* { WM_DELETE_WINDOW, _NET_WM_PING } */ + if (e.xclient.data.l[0] == protocols[0]) return; + if (e.xclient.data.l[0] == protocols[1]) HandleWMPing(&e); + break; + + case MotionNotify: + mouseX = e.xmotion.x; mouseY = e.xmotion.y; + break; + } + } +} + +static void ShowDialogCore(const char* title, const char* msg) { + struct X11MessageBox m = { 0 }; + m.dpy = win_display; + + /* Failing to create a display means can't display a message box. */ + /* However the user might have launched the game through terminal, */ + /* so fallback to console instead of just dying from a segfault */ + if (!m.dpy) { + Platform_LogConst("### MESSAGE ###"); + Platform_LogConst(title); + Platform_LogConst(msg); + return; + } + + X11_MessageBox(title, msg, &m); + X11MessageBox_Free(&m); + XFlush(m.dpy); /* flush so window disappears immediately */ +} + +static cc_result OpenSaveFileDialog(const char* args, FileDialogCallback callback, const char* defaultExt) { + cc_string path; char pathBuffer[1024]; + char result[4096] = { 0 }; + int len, i; + /* TODO this doesn't detect when Zenity doesn't exist */ + FILE* fp = popen(args, "r"); + if (!fp) return 0; + + /* result from zenity is normally just one string */ + while (fgets(result, sizeof(result), fp)) { } + pclose(fp); + + len = String_Length(result); + if (!len) return 0; + + String_InitArray(path, pathBuffer); + String_AppendUtf8(&path, result, len); + + /* Add default file extension if necessary */ + if (defaultExt) { + cc_string file = path; + Utils_UNSAFE_GetFilename(&file); + if (String_IndexOf(&file, '.') == -1) String_AppendConst(&path, defaultExt); + } + callback(&path); + return 0; +} + +cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { + const char* const* filters = args->filters; + cc_string path; char pathBuffer[1024]; + int i; + + String_InitArray_NT(path, pathBuffer); + String_Format1(&path, "zenity --file-selection --file-filter='%c (", args->description); + + for (i = 0; filters[i]; i++) + { + if (i) String_Append(&path, ','); + String_Format1(&path, "*%c", filters[i]); + } + String_AppendConst(&path, ") |"); + + for (i = 0; filters[i]; i++) + { + String_Format1(&path, " *%c", filters[i]); + } + String_AppendConst(&path, "'"); + + path.buffer[path.length] = '\0'; + return OpenSaveFileDialog(path.buffer, args->Callback, NULL); +} + +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { + const char* const* titles = args->titles; + const char* const* fileExts = args->filters; + cc_string path; char pathBuffer[1024]; + int i; + + String_InitArray_NT(path, pathBuffer); + String_AppendConst(&path, "zenity --file-selection"); + for (i = 0; fileExts[i]; i++) + { + String_Format3(&path, " --file-filter='%c (*%c) | *%c'", titles[i], fileExts[i], fileExts[i]); + } + String_AppendConst(&path, " --save --confirm-overwrite"); + + /* TODO: Utf8 encode filename */ + if (args->defaultName.length) { + String_Format1(&path, " --filename='%s'", &args->defaultName); + } + + path.buffer[path.length] = '\0'; + return OpenSaveFileDialog(path.buffer, args->Callback, fileExts[0]); +} + +static GC fb_gc; +static XImage* fb_image; +static void* fb_data; +static int fb_fast; + +void Window_AllocFramebuffer(struct Bitmap* bmp, int width, int height) { + if (!fb_gc) fb_gc = XCreateGC(win_display, win_handle, 0, NULL); + + bmp->scan0 = (BitmapCol*)Mem_Alloc(width * height, 4, "window pixels"); + bmp->width = width; + bmp->height = height; + + /* X11 requires that the image to draw has same depth as window */ + /* Easy for 24/32 bit case, but much trickier with other depths */ + /* (have to do a manual and slow second blit for other depths) */ + fb_fast = win_visual.depth == 24 || win_visual.depth == 32; + fb_data = fb_fast ? bmp->scan0 : Mem_Alloc(width * height, 4, "window blit"); + + fb_image = XCreateImage(win_display, win_visual.visual, + win_visual.depth, ZPixmap, 0, fb_data, + width, height, 32, 0); +} + +static void BlitFramebuffer(int x1, int y1, int width, int height, struct Bitmap* bmp) { + unsigned char* dst; + BitmapCol* row; + BitmapCol src; + cc_uint32 pixel; + int R, G, B, A; + int x, y; + + for (y = y1; y < y1 + height; y++) { + row = Bitmap_GetRow(bmp, y); + dst = ((unsigned char*)fb_image->data) + y * fb_image->bytes_per_line; + + for (x = x1; x < x1 + width; x++) { + src = row[x]; + R = BitmapCol_R(src); + G = BitmapCol_G(src); + B = BitmapCol_B(src); + A = BitmapCol_A(src); + + switch (win_visual.depth) + { + case 30: /* R10 G10 B10 A2 */ + pixel = (R << 2) | ((G << 2) << 10) | ((B << 2) << 20) | ((A >> 6) << 30); + ((cc_uint32*)dst)[x] = pixel; + break; + case 16: /* B5 G6 R5 */ + pixel = (B >> 3) | ((G >> 2) << 5) | ((R >> 3) << 11); + ((cc_uint16*)dst)[x] = pixel; + break; + case 15: /* B5 G5 R5 */ + pixel = (B >> 3) | ((G >> 3) << 5) | ((R >> 3) << 10); + ((cc_uint16*)dst)[x] = pixel; + break; + case 8: /* B2 G3 R3 */ + pixel = (B >> 6) | ((G >> 5) << 2) | ((R >> 5) << 5); + ((cc_uint8*) dst)[x] = pixel; + break; + } + } + } +} + +void Window_DrawFramebuffer(Rect2D r, struct Bitmap* bmp) { + /* Convert 32 bit depth to window depth when required */ + if (!fb_fast) BlitFramebuffer(r.x, r.y, r.width, r.height, bmp); + + XPutImage(win_display, win_handle, fb_gc, fb_image, + r.x, r.y, r.x, r.y, r.width, r.height); +} + +void Window_FreeFramebuffer(struct Bitmap* bmp) { + XFree(fb_image); + Mem_Free(bmp->scan0); + if (bmp->scan0 != fb_data) Mem_Free(fb_data); +} + +void OnscreenKeyboard_Open(struct OpenKeyboardArgs* args) { } +void OnscreenKeyboard_SetText(const cc_string* text) { } +void OnscreenKeyboard_Draw2D(Rect2D* r, struct Bitmap* bmp) { } +void OnscreenKeyboard_Draw3D(void) { } +void OnscreenKeyboard_Close(void) { } + +static cc_bool rawMouseInited, rawMouseSupported; +static int xiOpcode; + +#ifdef CC_BUILD_XINPUT2 +static void CheckMovementDelta(double dx, double dy) { + /* Despite the assumption that XI_RawMotion is relative, */ + /* unfortunately there's a few buggy corner cases out there */ + /* where absolute coordinates are provided instead. */ + /* The ugly code belows tries to detect these corner cases, */ + /* and disables XInput2 when that happens */ + static int valid, fails; + + if (valid) return; + /* The default window resolution is 854 x 480, so if there's */ + /* a delta less than half of that, then it's almost certain */ + /* that the provided coordinates are relative.*/ + if (dx < 300 || dy < 200) { valid = true; return; } + + if (fails++ < 20) return; + /* Checked over 20 times now, but no relative coordinates, */ + /* so give up trying to use XInput2 anymore. */ + Platform_LogConst("Buggy XInput2 detected, disabling it.."); + rawMouseSupported = false; +} + +static void HandleGenericEvent(XEvent* e) { + const double* values; + XIRawEvent* ev; + double dx, dy; + + if (!rawMouseSupported || e->xcookie.extension != xiOpcode) return; + if (!XGetEventData(win_display, &e->xcookie)) return; + + if (e->xcookie.evtype == XI_RawMotion && Input.RawMode) { + ev = (XIRawEvent*)e->xcookie.data; + values = ev->raw_values; + + /* Raw motion events may not always have values for both axes */ + dx = XIMaskIsSet(ev->valuators.mask, 0) ? *values++ : 0; + dy = XIMaskIsSet(ev->valuators.mask, 1) ? *values++ : 0; + + CheckMovementDelta(dx, dy); + /* Using 0.5f here makes the sensitivity about same as normal cursor movement */ + Event_RaiseRawMove(&PointerEvents.RawMoved, dx * 0.5f, dy * 0.5f); + } + XFreeEventData(win_display, &e->xcookie); +} + +static void InitRawMouse(void) { + XIEventMask evmask; + unsigned char masks[XIMaskLen(XI_LASTEVENT)] = { 0 }; + int ev, err, major, minor; + + if (!XQueryExtension(win_display, "XInputExtension", &xiOpcode, &ev, &err)) { + Platform_LogConst("XInput unsupported"); + return; + } + + /* Only XInput 2.0 is actually required. However, 2.0 has the annoying */ + /* behaviour where raw input is NOT delivered while pointer is grabbed. */ + /* (i.e. if you press mouse button, no more raw mouse movement events) */ + /* http://wine.1045685.n8.nabble.com/PATCH-0-9-Implement-DInput8-mouse-using-RawInput-and-XInput2-RawEvents-only-td6016923.html */ + /* Thankfully XInput >= 2.1 corrects this behaviour */ + /* http://who-t.blogspot.com/2011/09/whats-new-in-xi-21-raw-events.html */ + major = 2; minor = 2; + if (XIQueryVersion(win_display, &major, &minor) != Success) { + Platform_Log2("Only XInput %i.%i supported", &major, &minor); + return; + } + + /* Sometimes XIQueryVersion will report Success, even though the */ + /* supported version is only 2.0! So make sure to handle that. */ + if (major < 2 || minor < 2) { + Platform_Log2("Only XInput %i.%i supported", &major, &minor); + return; + } + + XISetMask(masks, XI_RawMotion); + evmask.deviceid = XIAllMasterDevices; + evmask.mask_len = sizeof(masks); + evmask.mask = masks; + + XISelectEvents(win_display, win_rootWin, &evmask, 1); + rawMouseSupported = true; +} +#else +static void HandleGenericEvent(XEvent* e) { } +static void InitRawMouse(void) { } +#endif + +void Window_EnableRawMouse(void) { + DefaultEnableRawMouse(); + if (!rawMouseInited) InitRawMouse(); + rawMouseInited = true; + + if (!grabCursor) return; + XGrabPointer(win_display, win_handle, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, win_handle, blankCursor, CurrentTime); +} + +void Window_UpdateRawMouse(void) { + if (rawMouseSupported) { + /* Handled by XI_RawMotion generic event */ + CentreMousePosition(); + } else { + DefaultUpdateRawMouse(); + } +} + +void Window_DisableRawMouse(void) { + DefaultDisableRawMouse(); + if (!grabCursor) return; + XUngrabPointer(win_display, CurrentTime); +} + + +/*########################################################################################################################* +*-------------------------------------------------------glX OpenGL--------------------------------------------------------* +*#########################################################################################################################*/ +#if (CC_GFX_BACKEND & CC_GFX_BACKEND_GL_MASK) && !defined CC_BUILD_EGL +#include <GL/glx.h> +static GLXContext ctx_handle; +typedef int (*FP_SWAPINTERVAL)(int interval); +typedef Bool (*FP_QUERYRENDERER)(int attribute, unsigned int* value); +static FP_SWAPINTERVAL swapIntervalMESA, swapIntervalSGI; +static FP_QUERYRENDERER queryRendererMESA; + +void GLContext_Create(void) { + static const cc_string vsync_mesa = String_FromConst("GLX_MESA_swap_control"); + static const cc_string vsync_sgi = String_FromConst("GLX_SGI_swap_control"); + static const cc_string info_mesa = String_FromConst("GLX_MESA_query_renderer"); + + const char* raw_exts; + cc_string exts; + ctx_handle = glXCreateContext(win_display, &win_visual, NULL, true); + + if (!ctx_handle) { + Platform_LogConst("Context create failed. Trying indirect..."); + ctx_handle = glXCreateContext(win_display, &win_visual, NULL, false); + } + if (!ctx_handle) Logger_Abort("Failed to create OpenGL context"); + + if (!glXIsDirect(win_display, ctx_handle)) { + Platform_LogConst("== WARNING: Context is not direct =="); + } + if (!glXMakeCurrent(win_display, win_handle, ctx_handle)) { + Logger_Abort("Failed to make OpenGL context current."); + } + + /* GLX may return non-null function pointers that don't actually work */ + /* So we need to manually check the extensions string for support */ + raw_exts = glXQueryExtensionsString(win_display, DefaultScreen(win_display)); + exts = String_FromReadonly(raw_exts); + + if (String_CaselessContains(&exts, &vsync_mesa)) { + swapIntervalMESA = (FP_SWAPINTERVAL)GLContext_GetAddress("glXSwapIntervalMESA"); + } + if (String_CaselessContains(&exts, &vsync_sgi)) { + swapIntervalSGI = (FP_SWAPINTERVAL)GLContext_GetAddress("glXSwapIntervalSGI"); + } + if (String_CaselessContains(&exts, &info_mesa)) { + queryRendererMESA = (FP_QUERYRENDERER)GLContext_GetAddress("glXQueryCurrentRendererIntegerMESA"); + } +} + +void GLContext_Update(void) { } +cc_bool GLContext_TryRestore(void) { return true; } +void GLContext_Free(void) { + if (!ctx_handle) return; + glXMakeCurrent(win_display, None, NULL); + glXDestroyContext(win_display, ctx_handle); + ctx_handle = NULL; +} + +void* GLContext_GetAddress(const char* function) { + return (void*)glXGetProcAddress((const GLubyte*)function); +} + +cc_bool GLContext_SwapBuffers(void) { + glXSwapBuffers(win_display, win_handle); + return true; +} + +void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) { + int res = 0; + if (swapIntervalMESA) { + res = swapIntervalMESA(vsync); + } else if (swapIntervalSGI) { + res = swapIntervalSGI(vsync); + } + if (res) Platform_Log1("Set VSync failed, error: %i", &res); +} + +void GLContext_GetApiInfo(cc_string* info) { + unsigned int vram, acc; + if (!queryRendererMESA) return; + + queryRendererMESA(0x8186, &acc); /* GLX_RENDERER_ACCELERATED_MESA */ + queryRendererMESA(0x8187, &vram); /* GLX_RENDERER_VIDEO_MEMORY_MESA */ + String_Format2(info, "VRAM: %i MB, %c", &vram, + acc ? "HW accelerated" : "no HW acceleration"); +} + +static void GetAttribs(struct GraphicsMode* mode, int* attribs, int depth) { + int i = 0; + /* See http://www-01.ibm.com/support/knowledgecenter/ssw_aix_61/com.ibm.aix.opengl/doc/openglrf/glXChooseFBConfig.htm%23glxchoosefbconfig */ + /* See http://www-01.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.opengl/doc/openglrf/glXChooseVisual.htm%23b5c84be452rree */ + /* for the attribute declarations. Note that the attributes are different than those used in glxChooseVisual */ + + /* TODO always use RGBA? need to test 8bpp displays */ + if (DisplayInfo.Depth >= 15) { attribs[i++] = GLX_RGBA; } + attribs[i++] = GLX_RED_SIZE; attribs[i++] = mode->R; + attribs[i++] = GLX_GREEN_SIZE; attribs[i++] = mode->G; + attribs[i++] = GLX_BLUE_SIZE; attribs[i++] = mode->B; + attribs[i++] = GLX_ALPHA_SIZE; attribs[i++] = mode->A; + attribs[i++] = GLX_DEPTH_SIZE; attribs[i++] = depth; + + attribs[i++] = GLX_DOUBLEBUFFER; + attribs[i++] = 0; +} + +static XVisualInfo GLContext_SelectVisual(void) { + int attribs[20]; + int major, minor; + XVisualInfo* visual = NULL; + + int fbcount, screen; + GLXFBConfig* fbconfigs; + XVisualInfo info; + struct GraphicsMode mode; + + InitGraphicsMode(&mode); + GetAttribs(&mode, attribs, GLCONTEXT_DEFAULT_DEPTH); + screen = DefaultScreen(win_display); + + if (!glXQueryVersion(win_display, &major, &minor)) { + Platform_LogConst("glXQueryVersion failed"); + } else if (major >= 1 && minor >= 3) { + /* ChooseFBConfig returns an array of GLXFBConfig opaque structures */ + fbconfigs = glXChooseFBConfig(win_display, screen, attribs, &fbcount); + if (fbconfigs && fbcount) { + /* Use the first GLXFBConfig from the fbconfigs array (best match) */ + visual = glXGetVisualFromFBConfig(win_display, *fbconfigs); + XFree(fbconfigs); + } + } + + if (!visual) { + Platform_LogConst("Falling back to glXChooseVisual."); + visual = glXChooseVisual(win_display, screen, attribs); + } + /* Some really old devices will only supply 16 bit depths */ + if (!visual) { + GetAttribs(&mode, attribs, 16); + visual = glXChooseVisual(win_display, screen, attribs); + } + if (!visual) Logger_Abort("Requested GraphicsMode not available."); + + info = *visual; + XFree(visual); + return info; +} +#endif +#endif |