#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 #include #include #include #ifdef CC_BUILD_XINPUT2 #include #endif #include #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 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