//======================================================================== // GLFW 3.3 X11 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2016 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #include "internal.h" #include #include #include #include #include #include #include #include #include // Action for EWMH client messages #define _NET_WM_STATE_REMOVE 0 #define _NET_WM_STATE_ADD 1 #define _NET_WM_STATE_TOGGLE 2 // Additional mouse button names for XButtonEvent #define Button6 6 #define Button7 7 // Wait for data to arrive using select // This avoids blocking other threads via the per-display Xlib lock that also // covers GLX functions // static GLFWbool waitForEvent(double* timeout) { fd_set fds; const int fd = ConnectionNumber(_glfw.x11.display); int count = fd + 1; FD_ZERO(&fds); FD_SET(fd, &fds); #if defined(__linux__) FD_SET(_glfw.linux_js.inotify, &fds); if (fd < _glfw.linux_js.inotify) count = _glfw.linux_js.inotify + 1; #endif for (;;) { if (timeout) { const long seconds = (long) *timeout; const long microseconds = (long) ((*timeout - seconds) * 1e6); struct timeval tv = { seconds, microseconds }; const uint64_t base = _glfwPlatformGetTimerValue(); const int result = select(count, &fds, NULL, NULL, &tv); const int error = errno; *timeout -= (_glfwPlatformGetTimerValue() - base) / (double) _glfwPlatformGetTimerFrequency(); if (result > 0) return GLFW_TRUE; if ((result == -1 && error == EINTR) || *timeout <= 0.0) return GLFW_FALSE; } else if (select(count, &fds, NULL, NULL, NULL) != -1 || errno != EINTR) return GLFW_TRUE; } } // Waits until a VisibilityNotify event arrives for the specified window or the // timeout period elapses (ICCCM section 4.2.2) // static GLFWbool waitForVisibilityNotify(_GLFWwindow* window) { XEvent dummy; double timeout = 0.1; while (!XCheckTypedWindowEvent(_glfw.x11.display, window->x11.handle, VisibilityNotify, &dummy)) { if (!waitForEvent(&timeout)) return GLFW_FALSE; } return GLFW_TRUE; } // Returns whether the window is iconified // static int getWindowState(_GLFWwindow* window) { int result = WithdrawnState; struct { CARD32 state; Window icon; } *state = NULL; if (_glfwGetWindowPropertyX11(window->x11.handle, _glfw.x11.WM_STATE, _glfw.x11.WM_STATE, (unsigned char**) &state) >= 2) { result = state->state; } if (state) XFree(state); return result; } // Returns whether the event is a selection event // static Bool isSelectionEvent(Display* display, XEvent* event, XPointer pointer) { if (event->xany.window != _glfw.x11.helperWindowHandle) return False; return event->type == SelectionRequest || event->type == SelectionNotify || event->type == SelectionClear; } // Returns whether it is a _NET_FRAME_EXTENTS event for the specified window // static Bool isFrameExtentsEvent(Display* display, XEvent* event, XPointer pointer) { _GLFWwindow* window = (_GLFWwindow*) pointer; return event->type == PropertyNotify && event->xproperty.state == PropertyNewValue && event->xproperty.window == window->x11.handle && event->xproperty.atom == _glfw.x11.NET_FRAME_EXTENTS; } // Translates a GLFW standard cursor to a font cursor shape // static int translateCursorShape(int shape) { switch (shape) { case GLFW_ARROW_CURSOR: return XC_left_ptr; case GLFW_IBEAM_CURSOR: return XC_xterm; case GLFW_CROSSHAIR_CURSOR: return XC_crosshair; case GLFW_HAND_CURSOR: return XC_hand1; case GLFW_HRESIZE_CURSOR: return XC_sb_h_double_arrow; case GLFW_VRESIZE_CURSOR: return XC_sb_v_double_arrow; } return 0; } // Translates an X event modifier state mask // static int translateState(int state) { int mods = 0; if (state & ShiftMask) mods |= GLFW_MOD_SHIFT; if (state & ControlMask) mods |= GLFW_MOD_CONTROL; if (state & Mod1Mask) mods |= GLFW_MOD_ALT; if (state & Mod4Mask) mods |= GLFW_MOD_SUPER; return mods; } // Translates an X11 key code to a GLFW key token // static int translateKey(int scancode) { // Use the pre-filled LUT (see createKeyTables() in x11_init.c) if (scancode < 0 || scancode > 255) return GLFW_KEY_UNKNOWN; return _glfw.x11.keycodes[scancode]; } // Return the GLFW window corresponding to the specified X11 window // static _GLFWwindow* findWindowByHandle(Window handle) { _GLFWwindow* window; if (XFindContext(_glfw.x11.display, handle, _glfw.x11.context, (XPointer*) &window) != 0) { return NULL; } return window; } // Sends an EWMH or ICCCM event to the window manager // static void sendEventToWM(_GLFWwindow* window, Atom type, long a, long b, long c, long d, long e) { XEvent event; memset(&event, 0, sizeof(event)); event.type = ClientMessage; event.xclient.window = window->x11.handle; event.xclient.format = 32; // Data is 32-bit longs event.xclient.message_type = type; event.xclient.data.l[0] = a; event.xclient.data.l[1] = b; event.xclient.data.l[2] = c; event.xclient.data.l[3] = d; event.xclient.data.l[4] = e; XSendEvent(_glfw.x11.display, _glfw.x11.root, False, SubstructureNotifyMask | SubstructureRedirectMask, &event); } // Updates the normal hints according to the window settings // static void updateNormalHints(_GLFWwindow* window, int width, int height) { XSizeHints* hints = XAllocSizeHints(); if (!window->monitor) { if (window->resizable) { if (window->minwidth != GLFW_DONT_CARE && window->minheight != GLFW_DONT_CARE) { hints->flags |= PMinSize; hints->min_width = window->minwidth; hints->min_height = window->minheight; } if (window->maxwidth != GLFW_DONT_CARE && window->maxheight != GLFW_DONT_CARE) { hints->flags |= PMaxSize; hints->max_width = window->maxwidth; hints->max_height = window->maxheight; } if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE) { hints->flags |= PAspect; hints->min_aspect.x = hints->max_aspect.x = window->numer; hints->min_aspect.y = hints->max_aspect.y = window->denom; } } else { hints->flags |= (PMinSize | PMaxSize); hints->min_width = hints->max_width = width; hints->min_height = hints->max_height = height; } } hints->flags |= PWinGravity; hints->win_gravity = StaticGravity; XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints); XFree(hints); } // Updates the full screen status of the window // static void updateWindowMode(_GLFWwindow* window) { if (window->monitor) { if (_glfw.x11.xinerama.available && _glfw.x11.NET_WM_FULLSCREEN_MONITORS) { sendEventToWM(window, _glfw.x11.NET_WM_FULLSCREEN_MONITORS, window->monitor->x11.index, window->monitor->x11.index, window->monitor->x11.index, window->monitor->x11.index, 0); } if (_glfw.x11.NET_WM_STATE && _glfw.x11.NET_WM_STATE_FULLSCREEN) { sendEventToWM(window, _glfw.x11.NET_WM_STATE, _NET_WM_STATE_ADD, _glfw.x11.NET_WM_STATE_FULLSCREEN, 0, 1, 0); } else { // This is the butcher's way of removing window decorations // Setting the override-redirect attribute on a window makes the // window manager ignore the window completely (ICCCM, section 4) // The good thing is that this makes undecorated full screen windows // easy to do; the bad thing is that we have to do everything // manually and some things (like iconify/restore) won't work at // all, as those are tasks usually performed by the window manager XSetWindowAttributes attributes; attributes.override_redirect = True; XChangeWindowAttributes(_glfw.x11.display, window->x11.handle, CWOverrideRedirect, &attributes); window->x11.overrideRedirect = GLFW_TRUE; } // Enable compositor bypass { const unsigned long value = 1; XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &value, 1); } } else { if (_glfw.x11.xinerama.available && _glfw.x11.NET_WM_FULLSCREEN_MONITORS) { XDeleteProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_FULLSCREEN_MONITORS); } if (_glfw.x11.NET_WM_STATE && _glfw.x11.NET_WM_STATE_FULLSCREEN) { sendEventToWM(window, _glfw.x11.NET_WM_STATE, _NET_WM_STATE_REMOVE, _glfw.x11.NET_WM_STATE_FULLSCREEN, 0, 1, 0); } else { XSetWindowAttributes attributes; attributes.override_redirect = False; XChangeWindowAttributes(_glfw.x11.display, window->x11.handle, CWOverrideRedirect, &attributes); window->x11.overrideRedirect = GLFW_FALSE; } // Disable compositor bypass { XDeleteProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_BYPASS_COMPOSITOR); } } } // Splits and translates a text/uri-list into separate file paths // NOTE: This function destroys the provided string // static char** parseUriList(char* text, int* count) { const char* prefix = "file://"; char** paths = NULL; char* line; *count = 0; while ((line = strtok(text, "\r\n"))) { text = NULL; if (line[0] == '#') continue; if (strncmp(line, prefix, strlen(prefix)) == 0) line += strlen(prefix); (*count)++; char* path = calloc(strlen(line) + 1, 1); paths = realloc(paths, *count * sizeof(char*)); paths[*count - 1] = path; while (*line) { if (line[0] == '%' && line[1] && line[2]) { const char digits[3] = { line[1], line[2], '\0' }; *path = strtol(digits, NULL, 16); line += 2; } else *path = *line; path++; line++; } } return paths; } // Centers the cursor over the window client area // static void centerCursor(_GLFWwindow* window) { int width, height; _glfwPlatformGetWindowSize(window, &width, &height); _glfwPlatformSetCursorPos(window, width / 2.0, height / 2.0); } // Updates the cursor image according to its cursor mode // static void updateCursorImage(_GLFWwindow* window) { if (window->cursorMode == GLFW_CURSOR_NORMAL) { if (window->cursor) { XDefineCursor(_glfw.x11.display, window->x11.handle, window->cursor->x11.handle); } else XUndefineCursor(_glfw.x11.display, window->x11.handle); } else { XDefineCursor(_glfw.x11.display, window->x11.handle, _glfw.x11.hiddenCursorHandle); } } // Create the X11 window (and its colormap) // static GLFWbool createNativeWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, Visual* visual, int depth) { // Create a colormap based on the visual used by the current context window->x11.colormap = XCreateColormap(_glfw.x11.display, _glfw.x11.root, visual, AllocNone); // Create the actual window { XSetWindowAttributes wa; const unsigned long wamask = CWBorderPixel | CWColormap | CWEventMask; wa.colormap = window->x11.colormap; wa.border_pixel = 0; wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask | ExposureMask | FocusChangeMask | VisibilityChangeMask | EnterWindowMask | LeaveWindowMask | PropertyChangeMask; _glfwGrabErrorHandlerX11(); window->x11.handle = XCreateWindow(_glfw.x11.display, _glfw.x11.root, 0, 0, wndconfig->width, wndconfig->height, 0, // Border width depth, // Color depth InputOutput, visual, wamask, &wa); _glfwReleaseErrorHandlerX11(); if (!window->x11.handle) { _glfwInputErrorX11(GLFW_PLATFORM_ERROR, "X11: Failed to create window"); return GLFW_FALSE; } XSaveContext(_glfw.x11.display, window->x11.handle, _glfw.x11.context, (XPointer) window); } if (!wndconfig->decorated) _glfwPlatformSetWindowDecorated(window, GLFW_FALSE); if (_glfw.x11.NET_WM_STATE && !window->monitor) { Atom states[3]; int count = 0; if (wndconfig->floating) { if (_glfw.x11.NET_WM_STATE_ABOVE) states[count++] = _glfw.x11.NET_WM_STATE_ABOVE; } if (wndconfig->maximized) { if (_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT && _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) { states[count++] = _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT; states[count++] = _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ; window->x11.maximized = GLFW_TRUE; } } if (count) { XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_STATE, XA_ATOM, 32, PropModeReplace, (unsigned char*) &states, count); } } // Declare the WM protocols supported by GLFW { Atom protocols[] = { _glfw.x11.WM_DELETE_WINDOW, _glfw.x11.NET_WM_PING }; XSetWMProtocols(_glfw.x11.display, window->x11.handle, protocols, sizeof(protocols) / sizeof(Atom)); } // Declare our PID { const pid_t pid = getpid(); XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_PID, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &pid, 1); } if (_glfw.x11.NET_WM_WINDOW_TYPE && _glfw.x11.NET_WM_WINDOW_TYPE_NORMAL) { Atom type = _glfw.x11.NET_WM_WINDOW_TYPE_NORMAL; XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_WINDOW_TYPE, XA_ATOM, 32, PropModeReplace, (unsigned char*) &type, 1); } // Set ICCCM WM_HINTS property { XWMHints* hints = XAllocWMHints(); if (!hints) { _glfwInputError(GLFW_OUT_OF_MEMORY, "X11: Failed to allocate WM hints"); return GLFW_FALSE; } hints->flags = StateHint; hints->initial_state = NormalState; XSetWMHints(_glfw.x11.display, window->x11.handle, hints); XFree(hints); } updateNormalHints(window, wndconfig->width, wndconfig->height); // Set ICCCM WM_CLASS property // HACK: Until a mechanism for specifying the application name is added, the // initial window title is used as the window class name if (strlen(wndconfig->title)) { XClassHint* hint = XAllocClassHint(); hint->res_name = (char*) wndconfig->title; hint->res_class = (char*) wndconfig->title; XSetClassHint(_glfw.x11.display, window->x11.handle, hint); XFree(hint); } if (_glfw.x11.XdndAware) { // Announce support for Xdnd (drag and drop) const Atom version = 5; XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*) &version, 1); } _glfwPlatformSetWindowTitle(window, wndconfig->title); if (_glfw.x11.im) { window->x11.ic = XCreateIC(_glfw.x11.im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, window->x11.handle, XNFocusWindow, window->x11.handle, NULL); } _glfwPlatformGetWindowPos(window, &window->x11.xpos, &window->x11.ypos); _glfwPlatformGetWindowSize(window, &window->x11.width, &window->x11.height); return GLFW_TRUE; } // Set the specified property to the selection converted to the requested target // static Atom writeTargetToProperty(const XSelectionRequestEvent* request) { int i; const Atom formats[] = { _glfw.x11.UTF8_STRING, _glfw.x11.COMPOUND_STRING, XA_STRING }; const int formatCount = sizeof(formats) / sizeof(formats[0]); if (request->property == None) { // The requester is a legacy client (ICCCM section 2.2) // We don't support legacy clients, so fail here return None; } if (request->target == _glfw.x11.TARGETS) { // The list of supported targets was requested const Atom targets[] = { _glfw.x11.TARGETS, _glfw.x11.MULTIPLE, _glfw.x11.UTF8_STRING, _glfw.x11.COMPOUND_STRING, XA_STRING }; XChangeProperty(_glfw.x11.display, request->requestor, request->property, XA_ATOM, 32, PropModeReplace, (unsigned char*) targets, sizeof(targets) / sizeof(targets[0])); return request->property; } if (request->target == _glfw.x11.MULTIPLE) { // Multiple conversions were requested Atom* targets; unsigned long i, count; count = _glfwGetWindowPropertyX11(request->requestor, request->property, _glfw.x11.ATOM_PAIR, (unsigned char**) &targets); for (i = 0; i < count; i += 2) { int j; for (j = 0; j < formatCount; j++) { if (targets[i] == formats[j]) break; } if (j < formatCount) { XChangeProperty(_glfw.x11.display, request->requestor, targets[i + 1], targets[i], 8, PropModeReplace, (unsigned char*) _glfw.x11.clipboardString, strlen(_glfw.x11.clipboardString)); } else targets[i + 1] = None; } XChangeProperty(_glfw.x11.display, request->requestor, request->property, _glfw.x11.ATOM_PAIR, 32, PropModeReplace, (unsigned char*) targets, count); XFree(targets); return request->property; } if (request->target == _glfw.x11.SAVE_TARGETS) { // The request is a check whether we support SAVE_TARGETS // It should be handled as a no-op side effect target XChangeProperty(_glfw.x11.display, request->requestor, request->property, _glfw.x11.NULL_, 32, PropModeReplace, NULL, 0); return request->property; } // Conversion to a data target was requested for (i = 0; i < formatCount; i++) { if (request->target == formats[i]) { // The requested target is one we support XChangeProperty(_glfw.x11.display, request->requestor, request->property, request->target, 8, PropModeReplace, (unsigned char*) _glfw.x11.clipboardString, strlen(_glfw.x11.clipboardString)); return request->property; } } // The requested target is not supported return None; } static void handleSelectionClear(XEvent* event) { free(_glfw.x11.clipboardString); _glfw.x11.clipboardString = NULL; } static void handleSelectionRequest(XEvent* event) { const XSelectionRequestEvent* request = &event->xselectionrequest; XEvent reply; memset(&reply, 0, sizeof(reply)); reply.xselection.property = writeTargetToProperty(request); reply.xselection.type = SelectionNotify; reply.xselection.display = request->display; reply.xselection.requestor = request->requestor; reply.xselection.selection = request->selection; reply.xselection.target = request->target; reply.xselection.time = request->time; XSendEvent(_glfw.x11.display, request->requestor, False, 0, &reply); } // Make the specified window and its video mode active on its monitor // static GLFWbool acquireMonitor(_GLFWwindow* window) { GLFWbool status; if (_glfw.x11.saver.count == 0) { // Remember old screen saver settings XGetScreenSaver(_glfw.x11.display, &_glfw.x11.saver.timeout, &_glfw.x11.saver.interval, &_glfw.x11.saver.blanking, &_glfw.x11.saver.exposure); // Disable screen saver XSetScreenSaver(_glfw.x11.display, 0, 0, DontPreferBlanking, DefaultExposures); } if (!window->monitor->window) _glfw.x11.saver.count++; status = _glfwSetVideoModeX11(window->monitor, &window->videoMode); if (window->x11.overrideRedirect) { int xpos, ypos; GLFWvidmode mode; // Manually position the window over its monitor _glfwPlatformGetMonitorPos(window->monitor, &xpos, &ypos); _glfwPlatformGetVideoMode(window->monitor, &mode); XMoveResizeWindow(_glfw.x11.display, window->x11.handle, xpos, ypos, mode.width, mode.height); } _glfwInputMonitorWindow(window->monitor, window); return status; } // Remove the window and restore the original video mode // static void releaseMonitor(_GLFWwindow* window) { if (window->monitor->window != window) return; _glfwInputMonitorWindow(window->monitor, NULL); _glfwRestoreVideoModeX11(window->monitor); _glfw.x11.saver.count--; if (_glfw.x11.saver.count == 0) { // Restore old screen saver settings XSetScreenSaver(_glfw.x11.display, _glfw.x11.saver.timeout, _glfw.x11.saver.interval, _glfw.x11.saver.blanking, _glfw.x11.saver.exposure); } } // Decode a Unicode code point from a UTF-8 stream // Based on cutef8 by Jeff Bezanson (Public Domain) // #if defined(X_HAVE_UTF8_STRING) static unsigned int decodeUTF8(const char** s) { unsigned int ch = 0, count = 0; static const unsigned int offsets[] = { 0x00000000u, 0x00003080u, 0x000e2080u, 0x03c82080u, 0xfa082080u, 0x82082080u }; do { ch = (ch << 6) + (unsigned char) **s; (*s)++; count++; } while ((**s & 0xc0) == 0x80); assert(count <= 6); return ch - offsets[count - 1]; } #endif /*X_HAVE_UTF8_STRING*/ // Process the specified X event // static void processEvent(XEvent *event) { _GLFWwindow* window = NULL; int keycode = 0; Bool filtered = False; // HACK: Save scancode as some IMs clear the field in XFilterEvent if (event->type == KeyPress || event->type == KeyRelease) keycode = event->xkey.keycode; if (_glfw.x11.im) filtered = XFilterEvent(event, None); if (_glfw.x11.randr.available) { if (event->type == _glfw.x11.randr.eventBase + RRNotify) { XRRUpdateConfiguration(event); _glfwPollMonitorsX11(); return; } } if (event->type != GenericEvent) { window = findWindowByHandle(event->xany.window); if (window == NULL) { // This is an event for a window that has already been destroyed return; } } switch (event->type) { case KeyPress: { const int key = translateKey(keycode); const int mods = translateState(event->xkey.state); const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT)); if (window->x11.ic) { // HACK: Ignore duplicate key press events generated by ibus // Corresponding release events are filtered out by the // GLFW key repeat logic if (window->x11.lastKeyCode != keycode || window->x11.lastKeyTime != event->xkey.time) { if (keycode) _glfwInputKey(window, key, keycode, GLFW_PRESS, mods); } window->x11.lastKeyCode = keycode; window->x11.lastKeyTime = event->xkey.time; if (!filtered) { int count; Status status; #if defined(X_HAVE_UTF8_STRING) char buffer[100]; char* chars = buffer; count = Xutf8LookupString(window->x11.ic, &event->xkey, buffer, sizeof(buffer) - 1, NULL, &status); if (status == XBufferOverflow) { chars = calloc(count + 1, 1); count = Xutf8LookupString(window->x11.ic, &event->xkey, chars, count, NULL, &status); } if (status == XLookupChars || status == XLookupBoth) { const char* c = chars; chars[count] = '\0'; while (c - chars < count) _glfwInputChar(window, decodeUTF8(&c), mods, plain); } #else /*X_HAVE_UTF8_STRING*/ wchar_t buffer[16]; wchar_t* chars = buffer; count = XwcLookupString(window->x11.ic, &event->xkey, buffer, sizeof(buffer) / sizeof(wchar_t), NULL, &status); if (status == XBufferOverflow) { chars = calloc(count, sizeof(wchar_t)); count = XwcLookupString(window->x11.ic, &event->xkey, chars, count, NULL, &status); } if (status == XLookupChars || status == XLookupBoth) { int i; for (i = 0; i < count; i++) _glfwInputChar(window, chars[i], mods, plain); } #endif /*X_HAVE_UTF8_STRING*/ if (chars != buffer) free(chars); } } else { KeySym keysym; XLookupString(&event->xkey, NULL, 0, &keysym, NULL); _glfwInputKey(window, key, keycode, GLFW_PRESS, mods); const long character = _glfwKeySym2Unicode(keysym); if (character != -1) _glfwInputChar(window, character, mods, plain); } return; } case KeyRelease: { const int key = translateKey(keycode); const int mods = translateState(event->xkey.state); if (!_glfw.x11.xkb.detectable) { // HACK: Key repeat events will arrive as KeyRelease/KeyPress // pairs with similar or identical time stamps // The key repeat logic in _glfwInputKey expects only key // presses to repeat, so detect and discard release events if (XEventsQueued(_glfw.x11.display, QueuedAfterReading)) { XEvent next; XPeekEvent(_glfw.x11.display, &next); if (next.type == KeyPress && next.xkey.window == event->xkey.window && next.xkey.keycode == keycode) { // HACK: The time of repeat events sometimes doesn't // match that of the press event, so add an // epsilon // Toshiyuki Takahashi can press a button // 16 times per second so it's fairly safe to // assume that no human is pressing the key 50 // times per second (value is ms) if ((next.xkey.time - event->xkey.time) < 20) { // This is very likely a server-generated key repeat // event, so ignore it return; } } } } _glfwInputKey(window, key, keycode, GLFW_RELEASE, mods); return; } case ButtonPress: { const int mods = translateState(event->xbutton.state); if (event->xbutton.button == Button1) _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, mods); else if (event->xbutton.button == Button2) _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, mods); else if (event->xbutton.button == Button3) _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS, mods); // Modern X provides scroll events as mouse button presses else if (event->xbutton.button == Button4) _glfwInputScroll(window, 0.0, 1.0); else if (event->xbutton.button == Button5) _glfwInputScroll(window, 0.0, -1.0); else if (event->xbutton.button == Button6) _glfwInputScroll(window, 1.0, 0.0); else if (event->xbutton.button == Button7) _glfwInputScroll(window, -1.0, 0.0); else { // Additional buttons after 7 are treated as regular buttons // We subtract 4 to fill the gap left by scroll input above _glfwInputMouseClick(window, event->xbutton.button - Button1 - 4, GLFW_PRESS, mods); } return; } case ButtonRelease: { const int mods = translateState(event->xbutton.state); if (event->xbutton.button == Button1) { _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, mods); } else if (event->xbutton.button == Button2) { _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_RELEASE, mods); } else if (event->xbutton.button == Button3) { _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_RELEASE, mods); } else if (event->xbutton.button > Button7) { // Additional buttons after 7 are treated as regular buttons // We subtract 4 to fill the gap left by scroll input above _glfwInputMouseClick(window, event->xbutton.button - Button1 - 4, GLFW_RELEASE, mods); } return; } case EnterNotify: { // HACK: This is a workaround for WMs (KWM, Fluxbox) that otherwise // ignore the defined cursor for hidden cursor mode if (window->cursorMode == GLFW_CURSOR_HIDDEN) _glfwPlatformSetCursorMode(window, GLFW_CURSOR_HIDDEN); _glfwInputCursorEnter(window, GLFW_TRUE); return; } case LeaveNotify: { _glfwInputCursorEnter(window, GLFW_FALSE); return; } case MotionNotify: { const int x = event->xmotion.x; const int y = event->xmotion.y; if (x != window->x11.warpCursorPosX || y != window->x11.warpCursorPosY) { // The cursor was moved by something other than GLFW if (window->cursorMode == GLFW_CURSOR_DISABLED) { if (_glfw.x11.disabledCursorWindow != window) return; const int dx = x - window->x11.lastCursorPosX; const int dy = y - window->x11.lastCursorPosY; _glfwInputCursorPos(window, window->virtualCursorPosX + dx, window->virtualCursorPosY + dy); } else _glfwInputCursorPos(window, x, y); } window->x11.lastCursorPosX = x; window->x11.lastCursorPosY = y; return; } case ConfigureNotify: { if (event->xconfigure.width != window->x11.width || event->xconfigure.height != window->x11.height) { _glfwInputFramebufferSize(window, event->xconfigure.width, event->xconfigure.height); _glfwInputWindowSize(window, event->xconfigure.width, event->xconfigure.height); window->x11.width = event->xconfigure.width; window->x11.height = event->xconfigure.height; } if (event->xconfigure.x != window->x11.xpos || event->xconfigure.y != window->x11.ypos) { if (window->x11.overrideRedirect || event->xany.send_event) { _glfwInputWindowPos(window, event->xconfigure.x, event->xconfigure.y); window->x11.xpos = event->xconfigure.x; window->x11.ypos = event->xconfigure.y; } } return; } case ClientMessage: { // Custom client message, probably from the window manager if (filtered) return; if (event->xclient.message_type == None) return; if (event->xclient.message_type == _glfw.x11.WM_PROTOCOLS) { const Atom protocol = event->xclient.data.l[0]; if (protocol == None) return; if (protocol == _glfw.x11.WM_DELETE_WINDOW) { // The window manager was asked to close the window, for example by // the user pressing a 'close' window decoration button _glfwInputWindowCloseRequest(window); } else if (protocol == _glfw.x11.NET_WM_PING) { // The window manager is pinging the application to ensure it's // still responding to events XEvent reply = *event; reply.xclient.window = _glfw.x11.root; XSendEvent(_glfw.x11.display, _glfw.x11.root, False, SubstructureNotifyMask | SubstructureRedirectMask, &reply); } } else if (event->xclient.message_type == _glfw.x11.XdndEnter) { // A drag operation has entered the window // TODO: Check if UTF-8 string is supported by the source } else if (event->xclient.message_type == _glfw.x11.XdndDrop) { // The drag operation has finished dropping on // the window, ask to convert it to a UTF-8 string _glfw.x11.xdnd.source = event->xclient.data.l[0]; XConvertSelection(_glfw.x11.display, _glfw.x11.XdndSelection, _glfw.x11.UTF8_STRING, _glfw.x11.XdndSelection, window->x11.handle, CurrentTime); } else if (event->xclient.message_type == _glfw.x11.XdndPosition) { // The drag operation has moved over the window const int absX = (event->xclient.data.l[2] >> 16) & 0xFFFF; const int absY = (event->xclient.data.l[2]) & 0xFFFF; int x, y; _glfwPlatformGetWindowPos(window, &x, &y); _glfwInputCursorPos(window, absX - x, absY - y); // Reply that we are ready to copy the dragged data XEvent reply; memset(&reply, 0, sizeof(reply)); reply.type = ClientMessage; reply.xclient.window = event->xclient.data.l[0]; reply.xclient.message_type = _glfw.x11.XdndStatus; reply.xclient.format = 32; reply.xclient.data.l[0] = window->x11.handle; reply.xclient.data.l[1] = 1; // Always accept the dnd with no rectangle reply.xclient.data.l[2] = 0; // Specify an empty rectangle reply.xclient.data.l[3] = 0; reply.xclient.data.l[4] = _glfw.x11.XdndActionCopy; XSendEvent(_glfw.x11.display, event->xclient.data.l[0], False, NoEventMask, &reply); XFlush(_glfw.x11.display); } return; } case SelectionNotify: { if (event->xselection.property) { // The converted data from the drag operation has arrived char* data; const int result = _glfwGetWindowPropertyX11(event->xselection.requestor, event->xselection.property, event->xselection.target, (unsigned char**) &data); if (result) { int i, count; char** paths = parseUriList(data, &count); _glfwInputDrop(window, count, (const char**) paths); for (i = 0; i < count; i++) free(paths[i]); free(paths); } if (data) XFree(data); XEvent reply; memset(&reply, 0, sizeof(reply)); reply.type = ClientMessage; reply.xclient.window = _glfw.x11.xdnd.source; reply.xclient.message_type = _glfw.x11.XdndFinished; reply.xclient.format = 32; reply.xclient.data.l[0] = window->x11.handle; reply.xclient.data.l[1] = result; reply.xclient.data.l[2] = _glfw.x11.XdndActionCopy; // Reply that all is well XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source, False, NoEventMask, &reply); XFlush(_glfw.x11.display); } return; } case FocusIn: { if (window->cursorMode == GLFW_CURSOR_DISABLED) _glfwPlatformSetCursorMode(window, GLFW_CURSOR_DISABLED); if (event->xfocus.mode == NotifyGrab || event->xfocus.mode == NotifyUngrab) { // Ignore focus events from popup indicator windows, window menu // key chords and window dragging return; } if (window->x11.ic) XSetICFocus(window->x11.ic); _glfwInputWindowFocus(window, GLFW_TRUE); return; } case FocusOut: { if (window->cursorMode == GLFW_CURSOR_DISABLED) _glfwPlatformSetCursorMode(window, GLFW_CURSOR_NORMAL); if (event->xfocus.mode == NotifyGrab || event->xfocus.mode == NotifyUngrab) { // Ignore focus events from popup indicator windows, window menu // key chords and window dragging return; } if (window->x11.ic) XUnsetICFocus(window->x11.ic); if (window->monitor && window->autoIconify) _glfwPlatformIconifyWindow(window); _glfwInputWindowFocus(window, GLFW_FALSE); return; } case Expose: { _glfwInputWindowDamage(window); return; } case PropertyNotify: { if (event->xproperty.state != PropertyNewValue) return; if (event->xproperty.atom == _glfw.x11.WM_STATE) { const int state = getWindowState(window); if (state != IconicState && state != NormalState) return; const GLFWbool iconified = (state == IconicState); if (window->x11.iconified != iconified) { if (window->monitor) { if (iconified) releaseMonitor(window); else acquireMonitor(window); } window->x11.iconified = iconified; _glfwInputWindowIconify(window, iconified); } } else if (event->xproperty.atom == _glfw.x11.NET_WM_STATE) { const GLFWbool maximized = _glfwPlatformWindowMaximized(window); if (window->x11.maximized != maximized) { window->x11.maximized = maximized; _glfwInputWindowMaximize(window, maximized); } } return; } case SelectionClear: { handleSelectionClear(event); return; } case SelectionRequest: { handleSelectionRequest(event); return; } case DestroyNotify: return; } } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// // Retrieve a single window property of the specified type // Inspired by fghGetWindowProperty from freeglut // unsigned long _glfwGetWindowPropertyX11(Window window, Atom property, Atom type, unsigned char** value) { Atom actualType; int actualFormat; unsigned long itemCount, bytesAfter; XGetWindowProperty(_glfw.x11.display, window, property, 0, LONG_MAX, False, type, &actualType, &actualFormat, &itemCount, &bytesAfter, value); return itemCount; } // Push contents of our selection to clipboard manager // void _glfwPushSelectionToManagerX11(void) { XConvertSelection(_glfw.x11.display, _glfw.x11.CLIPBOARD_MANAGER, _glfw.x11.SAVE_TARGETS, None, _glfw.x11.helperWindowHandle, CurrentTime); for (;;) { XEvent event; while (XCheckIfEvent(_glfw.x11.display, &event, isSelectionEvent, NULL)) { switch (event.type) { case SelectionRequest: handleSelectionRequest(&event); break; case SelectionClear: handleSelectionClear(&event); break; case SelectionNotify: { if (event.xselection.target == _glfw.x11.SAVE_TARGETS) { // This means one of two things; either the selection was // not owned, which means there is no clipboard manager, or // the transfer to the clipboard manager has completed // In either case, it means we are done here return; } break; } } } waitForEvent(NULL); } } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { Visual* visual; int depth; if (ctxconfig->client == GLFW_NO_API) { visual = DefaultVisual(_glfw.x11.display, _glfw.x11.screen); depth = DefaultDepth(_glfw.x11.display, _glfw.x11.screen); } else { if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) { if (!_glfwInitGLX()) return GLFW_FALSE; if (!_glfwChooseVisualGLX(ctxconfig, fbconfig, &visual, &depth)) return GLFW_FALSE; } else { if (!_glfwInitEGL()) return GLFW_FALSE; if (!_glfwChooseVisualEGL(ctxconfig, fbconfig, &visual, &depth)) return GLFW_FALSE; } } if (!createNativeWindow(window, wndconfig, visual, depth)) return GLFW_FALSE; if (ctxconfig->client != GLFW_NO_API) { if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) { if (!_glfwCreateContextGLX(window, ctxconfig, fbconfig)) return GLFW_FALSE; } else { if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) return GLFW_FALSE; } } if (window->monitor) { _glfwPlatformShowWindow(window); updateWindowMode(window); if (!acquireMonitor(window)) return GLFW_FALSE; centerCursor(window); } XFlush(_glfw.x11.display); return GLFW_TRUE; } void _glfwPlatformDestroyWindow(_GLFWwindow* window) { if (_glfw.x11.disabledCursorWindow == window) _glfw.x11.disabledCursorWindow = NULL; if (window->monitor) releaseMonitor(window); if (window->x11.ic) { XDestroyIC(window->x11.ic); window->x11.ic = NULL; } if (window->context.destroy) window->context.destroy(window); if (window->x11.handle) { XDeleteContext(_glfw.x11.display, window->x11.handle, _glfw.x11.context); XUnmapWindow(_glfw.x11.display, window->x11.handle); XDestroyWindow(_glfw.x11.display, window->x11.handle); window->x11.handle = (Window) 0; } if (window->x11.colormap) { XFreeColormap(_glfw.x11.display, window->x11.colormap); window->x11.colormap = (Colormap) 0; } XFlush(_glfw.x11.display); } void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) { #if defined(X_HAVE_UTF8_STRING) Xutf8SetWMProperties(_glfw.x11.display, window->x11.handle, title, title, NULL, 0, NULL, NULL, NULL); #else // This may be a slightly better fallback than using XStoreName and // XSetIconName, which always store their arguments using STRING XmbSetWMProperties(_glfw.x11.display, window->x11.handle, title, title, NULL, 0, NULL, NULL, NULL); #endif XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_NAME, _glfw.x11.UTF8_STRING, 8, PropModeReplace, (unsigned char*) title, strlen(title)); XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_ICON_NAME, _glfw.x11.UTF8_STRING, 8, PropModeReplace, (unsigned char*) title, strlen(title)); XFlush(_glfw.x11.display); } void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const GLFWimage* images) { if (count) { int i, j, longCount = 0; for (i = 0; i < count; i++) longCount += 2 + images[i].width * images[i].height; long* icon = calloc(longCount, sizeof(long)); long* target = icon; for (i = 0; i < count; i++) { *target++ = images[i].width; *target++ = images[i].height; for (j = 0; j < images[i].width * images[i].height; j++) { *target++ = (images[i].pixels[j * 4 + 0] << 16) | (images[i].pixels[j * 4 + 1] << 8) | (images[i].pixels[j * 4 + 2] << 0) | (images[i].pixels[j * 4 + 3] << 24); } } XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) icon, longCount); free(icon); } else { XDeleteProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_ICON); } XFlush(_glfw.x11.display); } void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) { Window dummy; int x, y; XTranslateCoordinates(_glfw.x11.display, window->x11.handle, _glfw.x11.root, 0, 0, &x, &y, &dummy); if (xpos) *xpos = x; if (ypos) *ypos = y; } void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos) { // HACK: Explicitly setting PPosition to any value causes some WMs, notably // Compiz and Metacity, to honor the position of unmapped windows if (!_glfwPlatformWindowVisible(window)) { long supplied; XSizeHints* hints = XAllocSizeHints(); if (XGetWMNormalHints(_glfw.x11.display, window->x11.handle, hints, &supplied)) { hints->flags |= PPosition; hints->x = hints->y = 0; XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints); } XFree(hints); } XMoveWindow(_glfw.x11.display, window->x11.handle, xpos, ypos); XFlush(_glfw.x11.display); } void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) { XWindowAttributes attribs; XGetWindowAttributes(_glfw.x11.display, window->x11.handle, &attribs); if (width) *width = attribs.width; if (height) *height = attribs.height; } void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) { if (window->monitor) { if (window->monitor->window == window) acquireMonitor(window); } else { if (!window->resizable) updateNormalHints(window, width, height); XResizeWindow(_glfw.x11.display, window->x11.handle, width, height); } XFlush(_glfw.x11.display); } void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, int minwidth, int minheight, int maxwidth, int maxheight) { int width, height; _glfwPlatformGetWindowSize(window, &width, &height); updateNormalHints(window, width, height); XFlush(_glfw.x11.display); } void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) { int width, height; _glfwPlatformGetWindowSize(window, &width, &height); updateNormalHints(window, width, height); XFlush(_glfw.x11.display); } void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) { _glfwPlatformGetWindowSize(window, width, height); } void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom) { long* extents = NULL; if (window->monitor || !window->decorated) return; if (_glfw.x11.NET_FRAME_EXTENTS == None) return; if (!_glfwPlatformWindowVisible(window) && _glfw.x11.NET_REQUEST_FRAME_EXTENTS) { XEvent event; double timeout = 0.5; // Ensure _NET_FRAME_EXTENTS is set, allowing glfwGetWindowFrameSize to // function before the window is mapped sendEventToWM(window, _glfw.x11.NET_REQUEST_FRAME_EXTENTS, 0, 0, 0, 0, 0); // HACK: Use a timeout because earlier versions of some window managers // (at least Unity, Fluxbox and Xfwm) failed to send the reply // They have been fixed but broken versions are still in the wild // If you are affected by this and your window manager is NOT // listed above, PLEASE report it to their and our issue trackers while (!XCheckIfEvent(_glfw.x11.display, &event, isFrameExtentsEvent, (XPointer) window)) { if (!waitForEvent(&timeout)) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: The window manager has a broken _NET_REQUEST_FRAME_EXTENTS implementation; please report this issue"); return; } } } if (_glfwGetWindowPropertyX11(window->x11.handle, _glfw.x11.NET_FRAME_EXTENTS, XA_CARDINAL, (unsigned char**) &extents) == 4) { if (left) *left = extents[0]; if (top) *top = extents[2]; if (right) *right = extents[1]; if (bottom) *bottom = extents[3]; } if (extents) XFree(extents); } void _glfwPlatformIconifyWindow(_GLFWwindow* window) { if (window->x11.overrideRedirect) { // Override-redirect windows cannot be iconified or restored, as those // tasks are performed by the window manager _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Iconification of full screen windows requires a WM that supports EWMH full screen"); return; } XIconifyWindow(_glfw.x11.display, window->x11.handle, _glfw.x11.screen); XFlush(_glfw.x11.display); } void _glfwPlatformRestoreWindow(_GLFWwindow* window) { if (window->x11.overrideRedirect) { // Override-redirect windows cannot be iconified or restored, as those // tasks are performed by the window manager _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Iconification of full screen windows requires a WM that supports EWMH full screen"); return; } if (_glfwPlatformWindowIconified(window)) { XMapWindow(_glfw.x11.display, window->x11.handle); waitForVisibilityNotify(window); } else if (_glfwPlatformWindowVisible(window)) { if (_glfw.x11.NET_WM_STATE && _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT && _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) { sendEventToWM(window, _glfw.x11.NET_WM_STATE, _NET_WM_STATE_REMOVE, _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT, _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ, 1, 0); } } XFlush(_glfw.x11.display); } void _glfwPlatformMaximizeWindow(_GLFWwindow* window) { if (_glfw.x11.NET_WM_STATE && _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT && _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) { sendEventToWM(window, _glfw.x11.NET_WM_STATE, _NET_WM_STATE_ADD, _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT, _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ, 1, 0); XFlush(_glfw.x11.display); } } void _glfwPlatformShowWindow(_GLFWwindow* window) { if (_glfwPlatformWindowVisible(window)) return; XMapWindow(_glfw.x11.display, window->x11.handle); waitForVisibilityNotify(window); } void _glfwPlatformHideWindow(_GLFWwindow* window) { XUnmapWindow(_glfw.x11.display, window->x11.handle); XFlush(_glfw.x11.display); } void _glfwPlatformFocusWindow(_GLFWwindow* window) { if (_glfw.x11.NET_ACTIVE_WINDOW) sendEventToWM(window, _glfw.x11.NET_ACTIVE_WINDOW, 1, 0, 0, 0, 0); else { XRaiseWindow(_glfw.x11.display, window->x11.handle); XSetInputFocus(_glfw.x11.display, window->x11.handle, RevertToParent, CurrentTime); } XFlush(_glfw.x11.display); } void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate) { if (window->monitor == monitor) { if (monitor) { if (monitor->window == window) acquireMonitor(window); } else { XMoveResizeWindow(_glfw.x11.display, window->x11.handle, xpos, ypos, width, height); } return; } if (window->monitor) releaseMonitor(window); _glfwInputWindowMonitorChange(window, monitor); updateNormalHints(window, width, height); updateWindowMode(window); if (window->monitor) { XMapRaised(_glfw.x11.display, window->x11.handle); if (waitForVisibilityNotify(window)) acquireMonitor(window); } else { XMoveResizeWindow(_glfw.x11.display, window->x11.handle, xpos, ypos, width, height); } XFlush(_glfw.x11.display); } int _glfwPlatformWindowFocused(_GLFWwindow* window) { Window focused; int state; XGetInputFocus(_glfw.x11.display, &focused, &state); return window->x11.handle == focused; } int _glfwPlatformWindowIconified(_GLFWwindow* window) { return getWindowState(window) == IconicState; } int _glfwPlatformWindowVisible(_GLFWwindow* window) { XWindowAttributes wa; XGetWindowAttributes(_glfw.x11.display, window->x11.handle, &wa); return wa.map_state == IsViewable; } int _glfwPlatformWindowMaximized(_GLFWwindow* window) { Atom* states; unsigned long i; GLFWbool maximized = GLFW_FALSE; const unsigned long count = _glfwGetWindowPropertyX11(window->x11.handle, _glfw.x11.NET_WM_STATE, XA_ATOM, (unsigned char**) &states); for (i = 0; i < count; i++) { if (states[i] == _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT || states[i] == _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) { maximized = GLFW_TRUE; break; } } if (states) XFree(states); return maximized; } void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled) { int width, height; _glfwPlatformGetWindowSize(window, &width, &height); updateNormalHints(window, width, height); } void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled) { if (enabled) { XDeleteProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.MOTIF_WM_HINTS); } else { struct { unsigned long flags; unsigned long functions; unsigned long decorations; long input_mode; unsigned long status; } hints; hints.flags = 2; // Set decorations hints.decorations = 0; // No decorations XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.MOTIF_WM_HINTS, _glfw.x11.MOTIF_WM_HINTS, 32, PropModeReplace, (unsigned char*) &hints, sizeof(hints) / sizeof(long)); } } void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) { if (!_glfw.x11.NET_WM_STATE || !_glfw.x11.NET_WM_STATE_ABOVE) return; if (_glfwPlatformWindowVisible(window)) { const Atom action = enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; sendEventToWM(window, _glfw.x11.NET_WM_STATE, action, _glfw.x11.NET_WM_STATE_ABOVE, 0, 1, 0); } else { Atom* states; unsigned long i, count; count = _glfwGetWindowPropertyX11(window->x11.handle, _glfw.x11.NET_WM_STATE, XA_ATOM, (unsigned char**) &states); if (!states) return; if (enabled) { for (i = 0; i < count; i++) { if (states[i] == _glfw.x11.NET_WM_STATE_ABOVE) break; } if (i == count) { XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_STATE, XA_ATOM, 32, PropModeAppend, (unsigned char*) &_glfw.x11.NET_WM_STATE_ABOVE, 1); } } else { for (i = 0; i < count; i++) { if (states[i] == _glfw.x11.NET_WM_STATE_ABOVE) { states[i] = states[count - 1]; count--; } } XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_STATE, XA_ATOM, 32, PropModeReplace, (unsigned char*) &states, count); } XFree(states); } XFlush(_glfw.x11.display); } void _glfwPlatformPollEvents(void) { _glfwPollJoystickEvents(); int count = XPending(_glfw.x11.display); while (count--) { XEvent event; XNextEvent(_glfw.x11.display, &event); processEvent(&event); } if (_glfw.x11.disabledCursorWindow) centerCursor(_glfw.x11.disabledCursorWindow); XFlush(_glfw.x11.display); } void _glfwPlatformWaitEvents(void) { while (!XPending(_glfw.x11.display)) waitForEvent(NULL); _glfwPlatformPollEvents(); } void _glfwPlatformWaitEventsTimeout(double timeout) { while (!XPending(_glfw.x11.display)) { if (!waitForEvent(&timeout)) break; } _glfwPlatformPollEvents(); } void _glfwPlatformPostEmptyEvent(void) { XEvent event; memset(&event, 0, sizeof(event)); event.type = ClientMessage; event.xclient.window = _glfw.x11.helperWindowHandle; event.xclient.format = 32; // Data is 32-bit longs event.xclient.message_type = _glfw.x11.NULL_; XSendEvent(_glfw.x11.display, _glfw.x11.helperWindowHandle, False, 0, &event); XFlush(_glfw.x11.display); } void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) { Window root, child; int rootX, rootY, childX, childY; unsigned int mask; XQueryPointer(_glfw.x11.display, window->x11.handle, &root, &child, &rootX, &rootY, &childX, &childY, &mask); if (xpos) *xpos = childX; if (ypos) *ypos = childY; } void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) { // Store the new position so it can be recognized later window->x11.warpCursorPosX = (int) x; window->x11.warpCursorPosY = (int) y; XWarpPointer(_glfw.x11.display, None, window->x11.handle, 0,0,0,0, (int) x, (int) y); XFlush(_glfw.x11.display); } void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) { if (mode == GLFW_CURSOR_DISABLED) { _glfw.x11.disabledCursorWindow = window; _glfwPlatformGetCursorPos(window, &_glfw.x11.restoreCursorPosX, &_glfw.x11.restoreCursorPosY); centerCursor(window); XGrabPointer(_glfw.x11.display, window->x11.handle, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, window->x11.handle, _glfw.x11.hiddenCursorHandle, CurrentTime); } else if (_glfw.x11.disabledCursorWindow == window) { _glfw.x11.disabledCursorWindow = NULL; XUngrabPointer(_glfw.x11.display, CurrentTime); _glfwPlatformSetCursorPos(window, _glfw.x11.restoreCursorPosX, _glfw.x11.restoreCursorPosY); } updateCursorImage(window); XFlush(_glfw.x11.display); } const char* _glfwPlatformGetKeyName(int key, int scancode) { KeySym keysym; int extra; if (!_glfw.x11.xkb.available) return NULL; if (key != GLFW_KEY_UNKNOWN) scancode = _glfw.x11.scancodes[key]; if (!_glfwIsPrintable(_glfw.x11.keycodes[scancode])) return NULL; keysym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, 0, 0); if (keysym == NoSymbol) return NULL; XkbTranslateKeySym(_glfw.x11.display, &keysym, 0, _glfw.x11.keyName, sizeof(_glfw.x11.keyName), &extra); if (!strlen(_glfw.x11.keyName)) return NULL; return _glfw.x11.keyName; } int _glfwPlatformGetKeyScancode(int key) { return _glfw.x11.scancodes[key]; } int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot) { cursor->x11.handle = _glfwCreateCursorX11(image, xhot, yhot); if (!cursor->x11.handle) return GLFW_FALSE; return GLFW_TRUE; } int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape) { cursor->x11.handle = XCreateFontCursor(_glfw.x11.display, translateCursorShape(shape)); if (!cursor->x11.handle) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to create standard cursor"); return GLFW_FALSE; } return GLFW_TRUE; } void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) { if (cursor->x11.handle) XFreeCursor(_glfw.x11.display, cursor->x11.handle); } void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) { if (window->cursorMode == GLFW_CURSOR_NORMAL) { updateCursorImage(window); XFlush(_glfw.x11.display); } } void _glfwPlatformSetClipboardString(_GLFWwindow* window, const char* string) { free(_glfw.x11.clipboardString); _glfw.x11.clipboardString = strdup(string); XSetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD, _glfw.x11.helperWindowHandle, CurrentTime); if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD) != _glfw.x11.helperWindowHandle) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to become owner of clipboard selection"); } } const char* _glfwPlatformGetClipboardString(_GLFWwindow* window) { size_t i; const Atom formats[] = { _glfw.x11.UTF8_STRING, _glfw.x11.COMPOUND_STRING, XA_STRING }; const size_t formatCount = sizeof(formats) / sizeof(formats[0]); if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD) == _glfw.x11.helperWindowHandle) { // Instead of doing a large number of X round-trips just to put this // string into a window property and then read it back, just return it return _glfw.x11.clipboardString; } free(_glfw.x11.clipboardString); _glfw.x11.clipboardString = NULL; for (i = 0; i < formatCount; i++) { char* data; XEvent event; XConvertSelection(_glfw.x11.display, _glfw.x11.CLIPBOARD, formats[i], _glfw.x11.GLFW_SELECTION, _glfw.x11.helperWindowHandle, CurrentTime); while (!XCheckTypedWindowEvent(_glfw.x11.display, _glfw.x11.helperWindowHandle, SelectionNotify, &event)) { waitForEvent(NULL); } if (event.xselection.property == None) continue; if (_glfwGetWindowPropertyX11(event.xselection.requestor, event.xselection.property, event.xselection.target, (unsigned char**) &data)) { _glfw.x11.clipboardString = strdup(data); } if (data) XFree(data); XDeleteProperty(_glfw.x11.display, event.xselection.requestor, event.xselection.property); if (_glfw.x11.clipboardString) break; } if (_glfw.x11.clipboardString == NULL) { _glfwInputError(GLFW_FORMAT_UNAVAILABLE, "X11: Failed to convert clipboard to string"); } return _glfw.x11.clipboardString; } void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) { if (!_glfw.vk.KHR_surface) return; if (!_glfw.vk.KHR_xcb_surface || !_glfw.x11.x11xcb.handle) { if (!_glfw.vk.KHR_xlib_surface) return; } extensions[0] = "VK_KHR_surface"; // NOTE: VK_KHR_xcb_surface is preferred due to some early ICDs exposing but // not correctly implementing VK_KHR_xlib_surface if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle) extensions[1] = "VK_KHR_xcb_surface"; else extensions[1] = "VK_KHR_xlib_surface"; } int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhysicalDevice device, uint32_t queuefamily) { VisualID visualID = XVisualIDFromVisual(DefaultVisual(_glfw.x11.display, _glfw.x11.screen)); if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle) { PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR vkGetPhysicalDeviceXcbPresentationSupportKHR = (PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR) vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceXcbPresentationSupportKHR"); if (!vkGetPhysicalDeviceXcbPresentationSupportKHR) { _glfwInputError(GLFW_API_UNAVAILABLE, "X11: Vulkan instance missing VK_KHR_xcb_surface extension"); return GLFW_FALSE; } xcb_connection_t* connection = _glfw.x11.x11xcb.XGetXCBConnection(_glfw.x11.display); if (!connection) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to retrieve XCB connection"); return GLFW_FALSE; } return vkGetPhysicalDeviceXcbPresentationSupportKHR(device, queuefamily, connection, visualID); } else { PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR = (PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR) vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceXlibPresentationSupportKHR"); if (!vkGetPhysicalDeviceXlibPresentationSupportKHR) { _glfwInputError(GLFW_API_UNAVAILABLE, "X11: Vulkan instance missing VK_KHR_xlib_surface extension"); return GLFW_FALSE; } return vkGetPhysicalDeviceXlibPresentationSupportKHR(device, queuefamily, _glfw.x11.display, visualID); } } VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, _GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface) { if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle) { VkResult err; VkXcbSurfaceCreateInfoKHR sci; PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR; xcb_connection_t* connection = _glfw.x11.x11xcb.XGetXCBConnection(_glfw.x11.display); if (!connection) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to retrieve XCB connection"); return VK_ERROR_EXTENSION_NOT_PRESENT; } vkCreateXcbSurfaceKHR = (PFN_vkCreateXcbSurfaceKHR) vkGetInstanceProcAddr(instance, "vkCreateXcbSurfaceKHR"); if (!vkCreateXcbSurfaceKHR) { _glfwInputError(GLFW_API_UNAVAILABLE, "X11: Vulkan instance missing VK_KHR_xcb_surface extension"); return VK_ERROR_EXTENSION_NOT_PRESENT; } memset(&sci, 0, sizeof(sci)); sci.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; sci.connection = connection; sci.window = window->x11.handle; err = vkCreateXcbSurfaceKHR(instance, &sci, allocator, surface); if (err) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to create Vulkan XCB surface: %s", _glfwGetVulkanResultString(err)); } return err; } else { VkResult err; VkXlibSurfaceCreateInfoKHR sci; PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR; vkCreateXlibSurfaceKHR = (PFN_vkCreateXlibSurfaceKHR) vkGetInstanceProcAddr(instance, "vkCreateXlibSurfaceKHR"); if (!vkCreateXlibSurfaceKHR) { _glfwInputError(GLFW_API_UNAVAILABLE, "X11: Vulkan instance missing VK_KHR_xlib_surface extension"); return VK_ERROR_EXTENSION_NOT_PRESENT; } memset(&sci, 0, sizeof(sci)); sci.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; sci.dpy = _glfw.x11.display; sci.window = window->x11.handle; err = vkCreateXlibSurfaceKHR(instance, &sci, allocator, surface); if (err) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to create Vulkan X11 surface: %s", _glfwGetVulkanResultString(err)); } return err; } } ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI Display* glfwGetX11Display(void) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); return _glfw.x11.display; } GLFWAPI Window glfwGetX11Window(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(None); return window->x11.handle; }