/* axe_ork v.X - X11 GLX windows based on GLFW 3.3.8 - www.glfw.org - A library for OpenGL, window and input ** original license text below ** * Copyright (c) 2002-2006 Marcus Geelnard * Copyright (c) 2006-2019 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. */ void win_fbsize(window_t *win, int x, int y); void win_pos(window_t *win, int x, int y); void win_mouse(window_t *win, int x, int y); void win_scroll(window_t *win, int x, int y); void win_button(window_t *win, int btn, int act); void win_key(window_t *win, int key, int act); void win_char(window_t *win, uint code); void win_drop(window_t *win, int num, const char **paths); void win_redraw(window_t *win); void win_closed(window_t *win); void win_focus(window_t *win, byte focus); int ork_getglxattr(GLXFBConfig fbconfig, int attrib) { int value; glXGetFBConfigAttrib(wcf.display, fbconfig, attrib, &value); return value; } // by default, making a context non-current implicitly forces a pipeline flush void wcf_selcontext(window_t* window) { if (window) { if (!glXMakeCurrent(wcf.display, window->window, wcf.glx)) { loge(LOG_GLX, "Failed to make context current"); return; } } else { if (!glXMakeCurrent(wcf.display, None, NULL)) { loge(LOG_GLX, "Failed to clear current context"); return; } } wcf.current = window; } void wcf_swap(window_t* window) { glXSwapBuffers(wcf.display, window->window); } byte ork_isinstr(const char* string, const char* extensions) { const char* start = extensions; for (;;) { const char* where; const char* terminator; where = strstr(start, string); if (!where) return 0; terminator = where + strlen(string); if (where == start || *(where - 1) == ' ') { if (*terminator == ' ' || *terminator == '\0') break; } start = terminator; } return 1; } byte ork_ext_available(const char* extension) { const char* extensions = glXQueryExtensionsString(wcf.display, wcf.screen); if (extensions) { if (ork_isinstr(extension, extensions)) return 1; } return 0; } void *ork_glxproc(const char* procname) { if (wcf.GetProcAddress) return wcf.GetProcAddress((const byte*) procname); else if (wcf.GetProcAddressARB) return wcf.GetProcAddressARB((const byte*) procname); else return dlsym(wcf.handle, procname); } int ork_error_handler(Display *display, XErrorEvent* event) { if(wcf.display != display) return 0; wcf.error_code = event->error_code; return 0; } void ork_set_errhandler() { wcf.error_code = Success; wcf.error_handler = XSetErrorHandler(ork_error_handler); } void ork_unset_errhandler() { XSync(wcf.display, False); XSetErrorHandler(wcf.error_handler); wcf.error_handler = NULL; } #define set_attrib(a, v) \ { \ sys_assert(((size_t) index + 1) < sizeof(attribs) / sizeof(attribs[0])); \ attribs[index++] = a; \ attribs[index++] = v; \ } byte ork_create_glx(GLXFBConfig native) { int attribs[40]; ork_set_errhandler(); int index = 0; if(wcf.debug) { set_attrib(GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB); } // else if(wcf.ARB_create_context_no_error) { // set_attrib(GLX_CONTEXT_OPENGL_NO_ERROR_ARB, 1); // } set_attrib(None, None); wcf.glx = wcf.CreateContextAttribsARB(wcf.display, native, NULL, True, attribs); ork_unset_errhandler(); if (!wcf.glx) { char buffer[ERR_LOG_SIZE]; XGetErrorText(wcf.display, wcf.error_code, buffer, ERR_LOG_SIZE); loge(LOG_GLX, "Failed to create context: %s", buffer); return 0; } return 1; } #undef set_attrib byte ork_sel_glxcfg(Visual **visual, GLXFBConfig *native, int *depth) { XVisualInfo *result; GLXFBConfig *configs; int i, count; configs = glXGetFBConfigs(wcf.display, wcf.screen, &count); if(!configs || !count) { loge(LOG_GLX, "Konnte keine (0) FB-Konfiguration finden"); return 0; } for(i = 0; i < count; i++) { const GLXFBConfig n = configs[i]; if(!(ork_getglxattr(n, GLX_RENDER_TYPE) & GLX_RGBA_BIT)) continue; if(!(ork_getglxattr(n, GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT)) continue; if(!ork_getglxattr(n, GLX_DOUBLEBUFFER)) continue; if(ork_getglxattr(n, GLX_RED_SIZE) != 8) continue; if(ork_getglxattr(n, GLX_GREEN_SIZE) != 8) continue; if(ork_getglxattr(n, GLX_BLUE_SIZE) != 8) continue; if(ork_getglxattr(n, GLX_ALPHA_SIZE) != 8) continue; if(ork_getglxattr(n, GLX_DEPTH_SIZE) != 24) continue; if(ork_getglxattr(n, GLX_STENCIL_SIZE) != 8) continue; if(wcf.ARB_multisample && ork_getglxattr(n, GLX_SAMPLE_BUFFERS)) continue; result = glXGetVisualFromFBConfig(wcf.display, n); if(!result) continue; XRenderPictFormat* pf = XRenderFindVisualFormat(wcf.display, result->visual); if(!pf || !pf->direct.alphaMask) { XFree(result); continue; } *native = n; break; } XFree(configs); if(i < count) { logd(LOG_GLX, "FB-Konfiguration #%d Ausgewählt", i); } else { loge(LOG_GLX, "Konnte keine geeignete FB-Konfiguration finden"); return 0; } if(result->visual) { *visual = result->visual; *depth = result->depth; } else { *visual = DefaultVisual(wcf.display, wcf.screen); *depth = DefaultDepth(wcf.display, wcf.screen); } if((*depth) != 24 && (*depth) != 32) { loge(LOG_X11, "Ungültige Farbtiefe: %d Bits", *depth); XFree(result); return 0; } logd(LOG_X11, "Farbtiefe: %d Bits", *depth); XFree(result); return 1; } // Based on cutef8 by Jeff Bezanson (Public Domain) size_t ork_encode_utf(char* s, uint codepoint) { size_t count = 0; if (codepoint < 0x80) s[count++] = (char) codepoint; else if (codepoint < 0x800) { s[count++] = (codepoint >> 6) | 0xc0; s[count++] = (codepoint & 0x3f) | 0x80; } else if (codepoint < 0x10000) { s[count++] = (codepoint >> 12) | 0xe0; s[count++] = ((codepoint >> 6) & 0x3f) | 0x80; s[count++] = (codepoint & 0x3f) | 0x80; } else if (codepoint < 0x110000) { s[count++] = (codepoint >> 18) | 0xf0; s[count++] = ((codepoint >> 12) & 0x3f) | 0x80; s[count++] = ((codepoint >> 6) & 0x3f) | 0x80; s[count++] = (codepoint & 0x3f) | 0x80; } return count; } // Based on cutef8 by Jeff Bezanson (Public Domain) uint ork_decode_utf(const char** s) { uint codepoint = 0, count = 0; static const uint offsets[] = { 0x00000000u, 0x00003080u, 0x000e2080u, 0x03c82080u, 0xfa082080u, 0x82082080u }; do { codepoint = (codepoint << 6) + (byte) **s; (*s)++; count++; } while ((**s & 0xc0) == 0x80); sys_assert(count <= 6); return codepoint - offsets[count - 1]; } char* ork_latin1_utf(const char* source) { size_t size = 1; const char* sp; for (sp = source; *sp; sp++) size += (*sp & 0x80) ? 2 : 1; char* target = mem_zeros(size, MEM_MISC); char* tp = target; for (sp = source; *sp; sp++) tp += ork_encode_utf(tp, *sp); return target; } char** ork_parse_uri(char* text, int* count) { const char* prefix = "file://"; char** paths = NULL; char* line; *count = 0; while ((line = strtok(text, "\r\n"))) { char* path; text = NULL; if (line[0] == '#') continue; if (strncmp(line, prefix, strlen(prefix)) == 0) { line += strlen(prefix); // TODO: Validate hostname while (*line != '/') line++; } (*count)++; path = mem_zeros(strlen(line) + 1, MEM_MISC); paths = mem_realloc(paths, *count * sizeof(char*), MEM_MISC); paths[*count - 1] = path; while (*line) { if (line[0] == '%' && line[1] && line[2]) { const char digits[3] = { line[1], line[2], '\0' }; *path = (char) strtol(digits, NULL, 16); line += 2; } else *path = *line; path++; line++; } } return paths; } char* ork_strdup(const char* source) { const size_t length = strlen(source); char* result = mem_zeros(length + 1, MEM_MISC); strcpy(result, source); return result; } // Returns whether the event is a selection event Bool ork_is_selevent(Display* display, XEvent* event, XPointer pointer) { if (event->xany.window != wcf.helper_window) 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 Bool ork_is_frextevent(Display* display, XEvent* event, XPointer pointer) { window_t* window = (window_t*) pointer; return event->type == PropertyNotify && event->xproperty.state == PropertyNewValue && event->xproperty.window == window->handle && event->xproperty.atom == wcf.NET_FRAME_EXTENTS; } // Returns whether it is a property event for the specified selection transfer Bool ork_is_selnewvalue(Display* display, XEvent* event, XPointer pointer) { XEvent* notification = (XEvent*) pointer; return event->type == PropertyNotify && event->xproperty.state == PropertyNewValue && event->xproperty.window == notification->xselection.requestor && event->xproperty.atom == notification->xselection.property; } void ork_key(window_t* window, int key, int action) { if (key >= 0 && key <= KEYSYM_LAST) { byte repeated = 0; if (action == KEY_RELEASE && window->keys[key] == KEY_RELEASE) return; if (action == KEY_PRESS && window->keys[key] == KEY_PRESS) repeated = 1; window->keys[key] = (char) action; if (repeated) action = KEY_REPEAT; } win_key(window, key, action); } void ork_button(window_t* window, int button, int action) { if (button < 0 || button >= MOUSE_BTNS) return; window->buttons[button] = (char) action; win_button(window, button, action); } void ork_mouse(window_t* window, int xpos, int ypos) { if (window->virtual_cur_x == xpos && window->virtual_cur_y == ypos) return; window->virtual_cur_x = xpos; window->virtual_cur_y = ypos; win_mouse(window, xpos, ypos); } void ork_getcur(window_t* window, int* xpos, int* ypos) { Window root, child; int rootX, rootY, childX, childY; uint mask; XQueryPointer(wcf.display, window->handle, &root, &child, &rootX, &rootY, &childX, &childY, &mask); if (xpos) *xpos = childX; if (ypos) *ypos = childY; } void ork_setcur(window_t* window, int x, int y) { // Store the new position so it can be recognized later window->warp_cur_x = x; window->warp_cur_y = y; XWarpPointer(wcf.display, None, window->handle, 0,0,0,0, x, y); XFlush(wcf.display); } byte ork_focused(window_t* window) { Window focused; int state; XGetInputFocus(wcf.display, &focused, &state); return window->handle == focused; } void ork_updatecur(window_t* window) { if (window->cursor_set) { if (window->cursor) { XDefineCursor(wcf.display, window->handle, window->cursor->handle); } else XUndefineCursor(wcf.display, window->handle); } else { XDefineCursor(wcf.display, window->handle, wcf.empty_cursor); } } void wcf_getsize(window_t* window, int* width, int* height) { XWindowAttributes attribs; XGetWindowAttributes(wcf.display, window->handle, &attribs); if (width) *width = attribs.width; if (height) *height = attribs.height; } void ork_enablecur(window_t* window) { wcf.grab_cur_window = NULL; XUngrabPointer(wcf.display, CurrentTime); ork_setcur(window, wcf.restore_cur_x, wcf.restore_cur_y); ork_updatecur(window); } void ork_disablecur(window_t* window) { wcf.grab_cur_window = window; ork_getcur(window, &wcf.restore_cur_x, &wcf.restore_cur_y); ork_updatecur(window); int width, height; wcf_getsize(window, &width, &height); ork_setcur(window, width / 2.0, height / 2.0); XGrabPointer(wcf.display, window->handle, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, window->handle, wcf.empty_cursor, CurrentTime); } void wcf_grab(window_t* window, byte value) { if (window->cursor_set == value) return; window->cursor_set = value; ork_getcur(window, &window->virtual_cur_x, &window->virtual_cur_y); if (!value) { if (ork_focused(window)) ork_disablecur(window); } else if (wcf.grab_cur_window == window) ork_enablecur(window); else ork_updatecur(window); XFlush(wcf.display); } int wcf_getkey(window_t* window, int key) { if(key < 0 || key > KEYSYM_LAST) return KEY_RELEASE; return (int)window->keys[key]; } int wcf_getbutton(window_t* window, int button) { if(button < 0 || button >= MOUSE_BTNS) return KEY_RELEASE; return (int)window->buttons[button]; } void wcf_getcursor(window_t* window, int* xpos, int* ypos) { if (xpos) *xpos = 0; if (ypos) *ypos = 0; if (window->cursor_set ^ 1) { if (xpos) *xpos = window->virtual_cur_x; if (ypos) *ypos = window->virtual_cur_y; } else ork_getcur(window, xpos, ypos); } Cursor ork_createcur(const byte *image, int width, int height, int xhot, int yhot) { int i; Cursor cursor; if (!wcf.xcursor.handle) return None; XcursorImage* native = XcursorImageCreate(width, height); if (native == NULL) return None; native->xhot = xhot; native->yhot = yhot; const byte* source = image; XcursorPixel* target = native->pixels; for (i = 0; i < width * height; i++, target++, source += 4) { uint alpha = source[3]; *target = (alpha << 24) | ((byte) ((source[0] * alpha) / 255) << 16) | ((byte) ((source[1] * alpha) / 255) << 8) | ((byte) ((source[2] * alpha) / 255) << 0); } cursor = XcursorImageLoadCursor(wcf.display, native); XcursorImageDestroy(native); return cursor; } void wcf_set_curimg(window_t* window, cursor_t* cursor) { window->cursor = cursor; if (window->cursor_set) { ork_updatecur(window); XFlush(wcf.display); } } void wcf_destroy_curimg(cursor_t* cursor) { if (cursor == NULL) return; // Make sure the cursor is not being used by any window { window_t* window; for (window = wcf.window_list; window; window = window->next) { if (window->cursor == cursor) wcf_set_curimg(window, NULL); } } if (cursor->handle) XFreeCursor(wcf.display, cursor->handle); // Unlink cursor from global linked list { cursor_t** prev = &wcf.cursor_list; while (*prev != cursor) prev = &((*prev)->next); *prev = cursor->next; } mem_free(cursor); } /*! * The pixels are 32-bit, little-endian, non-premultiplied RGBA, i.e. eight * bits per channel with the red channel first. They are arranged canonically * as packed sequential rows, starting from the top-left corner. * * The cursor hotspot is specified in pixels, relative to the upper-left corner * of the cursor image. The X-axis points to the right and the Y-axis points down. */ cursor_t* wcf_create_curimg(const byte *image, int width, int height, int xhot, int yhot) { cursor_t* cursor = mem_zeros(sizeof(cursor_t), MEM_MISC); cursor->next = wcf.cursor_list; wcf.cursor_list = cursor; cursor->handle = ork_createcur(image, width, height, xhot, yhot); if (!cursor->handle) { wcf_destroy_curimg(cursor); return NULL; } return cursor; } byte ork_wait_poll(struct pollfd* fds, nfds_t count, double* timeout) { for (;;) { if (timeout) { const ulong base = tmr_time(); // const time_t seconds = (time_t) *timeout; // const long nanoseconds = (long) ((*timeout - seconds) * 1e9); // const struct timespec ts = { seconds, nanoseconds }; // const int result = ppoll(fds, count, &ts, NULL); const int milliseconds = (int) (*timeout * 1e3); const int result = poll(fds, count, milliseconds); const int error = errno; // clock_gettime may overwrite our error *timeout -= (tmr_time() - base) / 1000000.0; if (result > 0) return 1; else if (result == -1 && error != EINTR && error != EAGAIN) return 0; else if (*timeout <= 0.0) return 0; } else { const int result = poll(fds, count, -1); if (result > 0) return 1; else if (result == -1 && errno != EINTR && errno != EAGAIN) return 0; } } } byte ork_wait_xevent(double* timeout) { struct pollfd fd = { ConnectionNumber(wcf.display), POLLIN }; while (!XPending(wcf.display)) { if (!ork_wait_poll(&fd, 1, timeout)) return 0; } return 1; } void wcf_setclip(const char* string) { char* copy = ork_strdup(string); mem_free(wcf.clipboard_str); wcf.clipboard_str = copy; XSetSelectionOwner(wcf.display, wcf.CLIPBOARD, wcf.helper_window, CurrentTime); if (XGetSelectionOwner(wcf.display, wcf.CLIPBOARD) != wcf.helper_window) { loge(LOG_X11, "Failed to become owner of clipboard selection"); } } const char* wcf_getclip() { char** selectionString = &wcf.clipboard_str; const Atom targets[] = { wcf.UTF8_STRING, XA_STRING }; const size_t targetCount = sizeof(targets) / sizeof(targets[0]); if (XGetSelectionOwner(wcf.display, wcf.CLIPBOARD) == wcf.helper_window) { // 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 *selectionString; } mem_free(*selectionString); *selectionString = NULL; for (size_t i = 0; i < targetCount; i++) { char* data; Atom actualType; int actualFormat; ulong itemCount, bytesAfter; XEvent notification, dummy; XConvertSelection(wcf.display, wcf.CLIPBOARD, targets[i], wcf.WCF_SELECTION, wcf.helper_window, CurrentTime); while (!XCheckTypedWindowEvent(wcf.display, wcf.helper_window, SelectionNotify, ¬ification)) { ork_wait_xevent(NULL); } if (notification.xselection.property == None) continue; XCheckIfEvent(wcf.display, &dummy, ork_is_selnewvalue, (XPointer) ¬ification); XGetWindowProperty(wcf.display, notification.xselection.requestor, notification.xselection.property, 0, LONG_MAX, True, AnyPropertyType, &actualType, &actualFormat, &itemCount, &bytesAfter, (byte**) &data); if (actualType == wcf.INCR) { size_t size = 1; char* string = NULL; for (;;) { while (!XCheckIfEvent(wcf.display, &dummy, ork_is_selnewvalue, (XPointer) ¬ification)) { ork_wait_xevent(NULL); } XFree(data); XGetWindowProperty(wcf.display, notification.xselection.requestor, notification.xselection.property, 0, LONG_MAX, True, AnyPropertyType, &actualType, &actualFormat, &itemCount, &bytesAfter, (byte**) &data); if (itemCount) { size += itemCount; string = mem_realloc(string, size, MEM_MISC); string[size - itemCount - 1] = '\0'; strcat(string, data); } if (!itemCount) { if (string) { if (targets[i] == XA_STRING) { *selectionString = ork_latin1_utf(string); mem_free(string); } else *selectionString = string; } break; } } } else if (actualType == targets[i]) { if (targets[i] == XA_STRING) *selectionString = ork_latin1_utf(data); else *selectionString = ork_strdup(data); } XFree(data); if (*selectionString) break; } if (!*selectionString) { loge(LOG_X11, "Failed to convert selection to string"); } return *selectionString; } void wcf_sync(int interval) { if (!wcf.current) return; if (wcf.EXT_swap_control) { wcf.SwapIntervalEXT(wcf.display, wcf.current->window, interval); } else if (wcf.MESA_swap_control) wcf.SwapIntervalMESA(interval); else if (wcf.SGI_swap_control) { if (interval > 0) wcf.SwapIntervalSGI(interval); } } int ork_compmodes(const void* fp, const void* sp) { const vidmode_t* fm = fp; const vidmode_t* sm = sp; const int farea = fm->width * fm->height; const int sarea = sm->width * sm->height; // First sort on screen area if (farea != sarea) return farea - sarea; // Then sort on width if (fm->width != sm->width) return fm->width - sm->width; // Lastly sort on refresh rate return fm->refresh - sm->refresh; } const XRRModeInfo* ork_modeinfo(const XRRScreenResources* sr, RRMode id) { for (int i = 0; i < sr->nmode; i++) { if (sr->modes[i].id == id) return sr->modes + i; } return NULL; } vidmode_t ork_convmode(const XRRModeInfo* mi, const XRRCrtcInfo* ci) { vidmode_t mode; if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270) { mode.width = mi->height; mode.height = mi->width; } else { mode.width = mi->width; mode.height = mi->height; } mode.refresh = (mi->hTotal && mi->vTotal) ? (int) round((double) mi->dotClock / ((double) mi->hTotal * (double) mi->vTotal)) : 0; return mode; } void ork_getmode(monitor_t* monitor, vidmode_t* mode) { if (wcf.randr.available && !wcf.randr.monitor_broken) { XRRScreenResources* sr = XRRGetScreenResourcesCurrent(wcf.display, wcf.root); XRRCrtcInfo* ci = XRRGetCrtcInfo(wcf.display, sr, monitor->crtc); if (ci) { const XRRModeInfo* mi = ork_modeinfo(sr, ci->mode); if (mi) // mi can be NULL if the monitor has been disconnected *mode = ork_convmode(mi, ci); XRRFreeCrtcInfo(ci); } XRRFreeScreenResources(sr); } else { mode->width = DisplayWidth(wcf.display, wcf.screen); mode->height = DisplayHeight(wcf.display, wcf.screen); mode->refresh = 0; } } byte ork_getmodes(monitor_t* monitor) { int count; vidmode_t* modes; if (monitor->modes) return 1; count = 0; if (wcf.randr.available && !wcf.randr.monitor_broken) { XRRScreenResources* sr = XRRGetScreenResourcesCurrent(wcf.display, wcf.root); XRRCrtcInfo* ci = XRRGetCrtcInfo(wcf.display, sr, monitor->crtc); XRROutputInfo* oi = XRRGetOutputInfo(wcf.display, sr, monitor->output); modes = mem_zeros(oi->nmode * sizeof(vidmode_t), MEM_MISC); for (int i = 0; i < oi->nmode; i++) { const XRRModeInfo* mi = ork_modeinfo(sr, oi->modes[i]); if ((mi->modeFlags & RR_Interlace) != 0) continue; const vidmode_t mode = ork_convmode(mi, ci); int j; for (j = 0; j < count; j++) { if (ork_compmodes(modes + j, &mode) == 0) break; } // Skip duplicate modes if (j < count) continue; count++; modes[count - 1] = mode; } XRRFreeOutputInfo(oi); XRRFreeCrtcInfo(ci); XRRFreeScreenResources(sr); } else { count = 1; modes = mem_zeros(sizeof(vidmode_t), MEM_MISC); ork_getmode(monitor, modes); } if (!modes) return 0; qsort(modes, count, sizeof(vidmode_t), ork_compmodes); mem_free(monitor->modes); monitor->modes = modes; monitor->n_modes = count; return 1; } ulong ork_getwinprop(Window window, Atom property, Atom type, byte** value) { Atom actualType; int actualFormat; ulong itemCount, bytesAfter; XGetWindowProperty(wcf.display, window, property, 0, LONG_MAX, False, type, &actualType, &actualFormat, &itemCount, &bytesAfter, value); return itemCount; } byte ork_winvisible(window_t* window) { XWindowAttributes wa; XGetWindowAttributes(wcf.display, window->handle, &wa); return wa.map_state == IsViewable; } void ork_sendevent(window_t* window, Atom type, long a, long b, long c, long d, long e) { XEvent event = { ClientMessage }; event.xclient.window = window->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(wcf.display, wcf.root, False, SubstructureNotifyMask | SubstructureRedirectMask, &event); } void ork_winfloating(window_t* window, byte enabled) { if (!wcf.NET_WM_STATE || !wcf.NET_WM_STATE_ABOVE) return; if (ork_winvisible(window)) { const long action = enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; ork_sendevent(window, wcf.NET_WM_STATE, action, wcf.NET_WM_STATE_ABOVE, 0, 1, 0); } else { Atom* states = NULL; ulong i, count; count = ork_getwinprop(window->handle, wcf.NET_WM_STATE, XA_ATOM, (byte**) &states); // NOTE: We don't check for failure as this property may not exist yet // and that's fine (and we'll create it implicitly with append) if (enabled) { for (i = 0; i < count; i++) { if (states[i] == wcf.NET_WM_STATE_ABOVE) break; } if (i == count) { XChangeProperty(wcf.display, window->handle, wcf.NET_WM_STATE, XA_ATOM, 32, PropModeAppend, (byte*) &wcf.NET_WM_STATE_ABOVE, 1); } } else if (states) { for (i = 0; i < count; i++) { if (states[i] == wcf.NET_WM_STATE_ABOVE) break; } if (i < count) { states[i] = states[count - 1]; count--; XChangeProperty(wcf.display, window->handle, wcf.NET_WM_STATE, XA_ATOM, 32, PropModeReplace, (byte*) states, count); } } if (states) XFree(states); } XFlush(wcf.display); } void ork_updatemode(window_t* window) { if (window->monitor) { if (wcf.xinerama.available && wcf.NET_WM_FULLSCREEN_MONITORS) { ork_sendevent(window, wcf.NET_WM_FULLSCREEN_MONITORS, window->monitor->index, window->monitor->index, window->monitor->index, window->monitor->index, 0); } if (wcf.NET_WM_STATE && wcf.NET_WM_STATE_FULLSCREEN) { ork_sendevent(window, wcf.NET_WM_STATE, _NET_WM_STATE_ADD, wcf.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(wcf.display, window->handle, CWOverrideRedirect, &attributes); window->override_redir = 1; } // Enable compositor bypass // if (!window->transparent) // { // const ulong value = 1; // XChangeProperty(wcf.display, window->handle, // wcf.NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, 32, // PropModeReplace, (byte*) &value, 1); // } } else { if (wcf.xinerama.available && wcf.NET_WM_FULLSCREEN_MONITORS) { XDeleteProperty(wcf.display, window->handle, wcf.NET_WM_FULLSCREEN_MONITORS); } if (wcf.NET_WM_STATE && wcf.NET_WM_STATE_FULLSCREEN) { ork_sendevent(window, wcf.NET_WM_STATE, _NET_WM_STATE_REMOVE, wcf.NET_WM_STATE_FULLSCREEN, 0, 1, 0); } else { XSetWindowAttributes attributes; attributes.override_redirect = False; XChangeWindowAttributes(wcf.display, window->handle, CWOverrideRedirect, &attributes); window->override_redir = 0; } // Disable compositor bypass // if (!window->transparent) // { // XDeleteProperty(wcf.display, window->handle, // wcf.NET_WM_BYPASS_COMPOSITOR); // } } } void ork_updatehints(window_t* window, int width, int height) { XSizeHints* hints = XAllocSizeHints(); if (!window->monitor) { if (window->resizable) { if (window->minwidth != -1 && window->minheight != -1) { hints->flags |= PMinSize; hints->min_width = window->minwidth; hints->min_height = window->minheight; } if (window->maxwidth != -1 && window->maxheight != -1) { hints->flags |= PMaxSize; hints->max_width = window->maxwidth; hints->max_height = window->maxheight; } } 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(wcf.display, window->handle, hints); XFree(hints); } void ork_disabledecor(window_t* window) { struct { ulong flags; ulong functions; ulong decorations; long input_mode; ulong status; } hints = {0}; hints.flags = MWM_HINTS_DECORATIONS; hints.decorations = 0; XChangeProperty(wcf.display, window->handle, wcf.MOTIF_WM_HINTS, wcf.MOTIF_WM_HINTS, 32, PropModeReplace, (byte*) &hints, sizeof(hints) / sizeof(long)); } void ork_unset_monitor(window_t* window) { if (window->monitor->window != window) return; window->monitor->window = NULL; monitor_t* monitor = window->monitor; if (wcf.randr.available && !wcf.randr.monitor_broken) { if (monitor->old_mode == None) return; XRRScreenResources* sr = XRRGetScreenResourcesCurrent(wcf.display, wcf.root); XRRCrtcInfo* ci = XRRGetCrtcInfo(wcf.display, sr, monitor->crtc); XRRSetCrtcConfig(wcf.display, sr, monitor->crtc, CurrentTime, ci->x, ci->y, monitor->old_mode, ci->rotation, ci->outputs, ci->noutput); XRRFreeCrtcInfo(ci); XRRFreeScreenResources(sr); monitor->old_mode = None; } } void wcf_windowed(window_t* window, int xpos, int ypos, int width, int height) { if(window->monitor) { ork_disabledecor(window); ork_winfloating(window, window->floating); ork_unset_monitor(window); window->monitor = NULL; ork_updatehints(window, width, height); ork_updatemode(window); } else if (!window->resizable) { ork_updatehints(window, width, height); } XMoveResizeWindow(wcf.display, window->handle, xpos, ypos, width, height); XFlush(wcf.display); } void wcf_setpos(window_t* window, int xpos, int ypos) { if (window->monitor) return; // HACK: Explicitly setting PPosition to any value causes some WMs, notably // Compiz and Metacity, to honor the position of unmapped windows if (!ork_winvisible(window)) { long supplied; XSizeHints* hints = XAllocSizeHints(); if (XGetWMNormalHints(wcf.display, window->handle, hints, &supplied)) { hints->flags |= PPosition; hints->x = hints->y = 0; XSetWMNormalHints(wcf.display, window->handle, hints); } XFree(hints); } XMoveWindow(wcf.display, window->handle, xpos, ypos); XFlush(wcf.display); } void ork_update_monitor(monitor_t* monitor, byte added, byte first) { if (added) { wcf.n_monitors++; wcf.monitors = mem_realloc(wcf.monitors, sizeof(monitor_t*) * wcf.n_monitors, MEM_SYSTEM); if (first) { memmove(wcf.monitors + 1, wcf.monitors, ((size_t) wcf.n_monitors - 1) * sizeof(monitor_t*)); wcf.monitors[0] = monitor; } else wcf.monitors[wcf.n_monitors - 1] = monitor; } else { int i; window_t* window; for (window = wcf.window_list; window; window = window->next) { if (window->monitor == monitor) { int width, height; wcf_getsize(window, &width, &height); wcf_windowed(window, 0, 0, width, height); wcf_setpos(window, 0, 0); } } for (i = 0; i < wcf.n_monitors; i++) { if (wcf.monitors[i] == monitor) { wcf.n_monitors--; memmove(wcf.monitors + i, wcf.monitors + i + 1, ((size_t) wcf.n_monitors - i) * sizeof(monitor_t*)); break; } } if(monitor) { mem_free(monitor->modes); mem_free(monitor); } } } const vidmode_t* ork_select_mode(monitor_t* monitor, int width, int height, int refresh) { int i; uint sizeDiff, leastSizeDiff = UINT_MAX; uint rateDiff, leastRateDiff = UINT_MAX; const vidmode_t* current; const vidmode_t* closest = NULL; if (!ork_getmodes(monitor)) return NULL; for (i = 0; i < monitor->n_modes; i++) { current = monitor->modes + i; sizeDiff = abs((current->width - width) * (current->width - width) + (current->height - height) * (current->height - height)); if (refresh != -1) rateDiff = abs(current->refresh - refresh); else rateDiff = UINT_MAX - current->refresh; if ((sizeDiff < leastSizeDiff) || (sizeDiff == leastSizeDiff && rateDiff < leastRateDiff)) { closest = current; leastSizeDiff = sizeDiff; leastRateDiff = rateDiff; } } return closest; } monitor_t *ork_getmonitor() { if(!wcf.n_monitors) return NULL; return wcf.monitors[0]; } void ork_monitorpos(monitor_t* monitor, int* xpos, int* ypos) { if (wcf.randr.available && !wcf.randr.monitor_broken) { XRRScreenResources* sr = XRRGetScreenResourcesCurrent(wcf.display, wcf.root); XRRCrtcInfo* ci = XRRGetCrtcInfo(wcf.display, sr, monitor->crtc); if (ci) { if (xpos) *xpos = ci->x; if (ypos) *ypos = ci->y; XRRFreeCrtcInfo(ci); } XRRFreeScreenResources(sr); } } void wcf_getscrpos(int* xpos, int* ypos) { monitor_t* monitor = ork_getmonitor(); if (xpos) *xpos = 0; if (ypos) *ypos = 0; ork_monitorpos(monitor, xpos, ypos); } const vidmode_t* wcf_getmodes(int* count) { monitor_t* monitor = ork_getmonitor(); *count = 0; if (!ork_getmodes(monitor)) return NULL; *count = monitor->n_modes; return monitor->modes; } const vidmode_t* wcf_getmode() { monitor_t* monitor = ork_getmonitor(); ork_getmode(monitor, &monitor->current_mode); return &monitor->current_mode; } void ork_resetkeys(window_t* window) { int key, button; for (key = 0; key <= KEYSYM_LAST; key++) { if (window->keys[key] == KEY_PRESS) { ork_key(window, key, KEY_RELEASE); } } for (button = 0; button < MOUSE_BTNS; button++) { if (window->buttons[button] == KEY_PRESS) ork_button(window, button, KEY_RELEASE); } } void wcf_title(window_t* window, const char* title) { Xutf8SetWMProperties(wcf.display, window->handle, title, title, NULL, 0, NULL, NULL, NULL); XChangeProperty(wcf.display, window->handle, wcf.NET_WM_NAME, wcf.UTF8_STRING, 8, PropModeReplace, (byte*) title, strlen(title)); XChangeProperty(wcf.display, window->handle, wcf.NET_WM_ICON_NAME, wcf.UTF8_STRING, 8, PropModeReplace, (byte*) title, strlen(title)); XFlush(wcf.display); } void wcf_getpos(window_t* window, int* xpos, int* ypos) { Window dummy; int x, y; XTranslateCoordinates(wcf.display, window->handle, wcf.root, 0, 0, &x, &y, &dummy); if (xpos) *xpos = x; if (ypos) *ypos = y; } byte ork_create_xwin(window_t* window, const char* title, Visual* visual, int depth) { // Create a colormap based on the visual used by the current context window->colormap = XCreateColormap(wcf.display, wcf.root, visual, AllocNone); // window->transparent = ork_istransparent(visual); XSetWindowAttributes wa = { 0 }; wa.colormap = window->colormap; wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask | ExposureMask | FocusChangeMask | VisibilityChangeMask | EnterWindowMask | LeaveWindowMask | PropertyChangeMask; ork_set_errhandler(); window->parent = wcf.root; window->handle = XCreateWindow(wcf.display, wcf.root, 0, 0, // Position 16, 16, 0, // Border width depth, // Color depth InputOutput, visual, CWBorderPixel | CWColormap | CWEventMask, &wa); ork_unset_errhandler(); if (!window->handle) { char buffer[ERR_LOG_SIZE]; XGetErrorText(wcf.display, wcf.error_code, buffer, ERR_LOG_SIZE); loge(LOG_X11, "Failed to create window: %s", buffer); return 0; } XSaveContext(wcf.display, window->handle, wcf.context, (XPointer) window); ork_disabledecor(window); if (wcf.NET_WM_STATE && !window->monitor) { Atom states[3]; int count = 0; if (window->floating) { if (wcf.NET_WM_STATE_ABOVE) states[count++] = wcf.NET_WM_STATE_ABOVE; } if (count) { XChangeProperty(wcf.display, window->handle, wcf.NET_WM_STATE, XA_ATOM, 32, PropModeReplace, (byte*) states, count); } } // Declare the WM protocols supported { Atom protocols[] = { wcf.WM_DELETE_WINDOW, wcf.NET_WM_PING }; XSetWMProtocols(wcf.display, window->handle, protocols, sizeof(protocols) / sizeof(Atom)); } // Declare our PID { const long pid = getpid(); XChangeProperty(wcf.display, window->handle, wcf.NET_WM_PID, XA_CARDINAL, 32, PropModeReplace, (byte*) &pid, 1); } if (wcf.NET_WM_WINDOW_TYPE && wcf.NET_WM_WINDOW_TYPE_NORMAL) { Atom type = wcf.NET_WM_WINDOW_TYPE_NORMAL; XChangeProperty(wcf.display, window->handle, wcf.NET_WM_WINDOW_TYPE, XA_ATOM, 32, PropModeReplace, (byte*) &type, 1); } // Set ICCCM WM_HINTS property { XWMHints* hints = XAllocWMHints(); // if (!hints) OOM? hints->flags = StateHint; hints->initial_state = NormalState; XSetWMHints(wcf.display, window->handle, hints); XFree(hints); } ork_updatehints(window, 16, 16); // Set ICCCM WM_CLASS property { XClassHint* hint = XAllocClassHint(); if (strlen(title)) { hint->res_name = (char*)title; hint->res_class = (char*)title; } else { hint->res_name = (char*) "swcf-application"; hint->res_class = (char*) "SWCF-Application"; } XSetClassHint(wcf.display, window->handle, hint); XFree(hint); } // Announce support for Xdnd (drag and drop) { const Atom version = WCF_XDND_VERSION; XChangeProperty(wcf.display, window->handle, wcf.XdndAware, XA_ATOM, 32, PropModeReplace, (byte*) &version, 1); } wcf_title(window, title); window->ic = XCreateIC(wcf.im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, window->handle, XNFocusWindow, window->handle, NULL); if (window->ic) { ulong filter = 0; if (XGetICValues(window->ic, XNFilterEvents, &filter, NULL) == NULL) XSelectInput(wcf.display, window->handle, wa.event_mask | filter); } wcf_getpos(window, &window->xpos, &window->ypos); wcf_getsize(window, &window->width, &window->height); return 1; } /*! * No further callbacks will be called for that window. * * If the context of the specified window is current on the main thread, it is * detached before being destroyed. * * The context of the specified window must not be current on any other * thread when this function is called. */ void wcf_destroy(window_t* window) { // Clear all callbacks to avoid exposing a half torn-down window object // TODO: add deinit // The window's context must not be current on another thread when the window is destroyed if (window == wcf.current) wcf_selcontext(NULL); if (wcf.grab_cur_window == window) wcf.grab_cur_window = NULL; if (window->monitor) ork_unset_monitor(window); if (window->ic) { XDestroyIC(window->ic); window->ic = NULL; } if (window->window) { glXDestroyWindow(wcf.display, window->window); window->window = None; } if (wcf.glx && (wcf.window_list == window) && !window->next) { glXDestroyContext(wcf.display, wcf.glx); wcf.glx = NULL; } if (window->handle) { XDeleteContext(wcf.display, window->handle, wcf.context); XUnmapWindow(wcf.display, window->handle); XDestroyWindow(wcf.display, window->handle); window->handle = (Window) 0; } if (window->colormap) { XFreeColormap(wcf.display, window->colormap); window->colormap = (Colormap) 0; } XFlush(wcf.display); // Unlink window from global linked list { window_t** prev = &wcf.window_list; while (*prev != window) prev = &((*prev)->next); *prev = window->next; } mem_free(window); } /*! * Due to the asynchronous nature of X11, it may take a moment for * a window to reach its requested state. This means you may not be able to * query the final size, position or other attributes directly after window * creation. */ window_t* wcf_create(const char* title, byte resizable) { window_t* window = mem_zeros(sizeof(window_t), MEM_SYSTEM); window->next = wcf.window_list; wcf.window_list = window; window->video_width = 0; window->video_height = 0; window->video_refresh = -1; window->monitor = NULL; window->resizable = resizable; // (flags & WIN_FLAG_RESIZEABLE) != 0; window->floating = 0; // (flags & WIN_FLAG_FLOATING) != 0; window->cursor_set = 1; window->minwidth = -1; window->minheight = -1; window->maxwidth = -1; window->maxheight = -1; if (!ork_create_xwin(window, title, wcf.visual, wcf.depth)) { wcf_destroy(window); return NULL; } if (!window->ic) { loge(LOG_X11, "Kein XIC für Fenster vorhanden"); wcf_destroy(window); return NULL; } window->window = glXCreateWindow(wcf.display, wcf.native, window->handle, NULL); if (!window->window) { loge(LOG_GLX, "Konnte kein GLX-Fenster erstellen"); wcf_destroy(window); return 0; } wcf_selcontext(window); if(!window->next && !gl_init((void * (*)(const char *))ork_glxproc, wcf.debug)) { wcf_destroy(window); return 0; } glClear(GL_COLOR_BUFFER_BIT); wcf_swap(window); XFlush(wcf.display); return window; } Atom ork_writeprop(const XSelectionRequestEvent* request) { int i; char* selectionString = NULL; const Atom formats[] = { wcf.UTF8_STRING, XA_STRING }; const int formatCount = sizeof(formats) / sizeof(formats[0]); if (request->selection == wcf.PRIMARY) selectionString = wcf.primary_str; else selectionString = wcf.clipboard_str; 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 == wcf.TARGETS) { // The list of supported targets was requested const Atom targets[] = { wcf.TARGETS, wcf.MULTIPLE, wcf.UTF8_STRING, XA_STRING }; XChangeProperty(wcf.display, request->requestor, request->property, XA_ATOM, 32, PropModeReplace, (byte*) targets, sizeof(targets) / sizeof(targets[0])); return request->property; } if (request->target == wcf.MULTIPLE) { // Multiple conversions were requested Atom* targets; ulong i, count; count = ork_getwinprop(request->requestor, request->property, wcf.ATOM_PAIR, (byte**) &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(wcf.display, request->requestor, targets[i + 1], targets[i], 8, PropModeReplace, (byte *) selectionString, strlen(selectionString)); } else targets[i + 1] = None; } XChangeProperty(wcf.display, request->requestor, request->property, wcf.ATOM_PAIR, 32, PropModeReplace, (byte*) targets, count); XFree(targets); return request->property; } if (request->target == wcf.SAVE_TARGETS) { // The request is a check whether we support SAVE_TARGETS // It should be handled as a no-op side effect target XChangeProperty(wcf.display, request->requestor, request->property, wcf.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(wcf.display, request->requestor, request->property, request->target, 8, PropModeReplace, (byte *) selectionString, strlen(selectionString)); return request->property; } } // The requested target is not supported return None; } void ork_handle_select(XEvent* event) { const XSelectionRequestEvent* request = &event->xselectionrequest; XEvent reply = { SelectionNotify }; reply.xselection.property = ork_writeprop(request); 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(wcf.display, request->requestor, False, 0, &reply); } void ork_push_selection() { XConvertSelection(wcf.display, wcf.CLIPBOARD_MANAGER, wcf.SAVE_TARGETS, None, wcf.helper_window, CurrentTime); for (;;) { XEvent event; while (XCheckIfEvent(wcf.display, &event, ork_is_selevent, NULL)) { switch (event.type) { case SelectionRequest: ork_handle_select(&event); break; case SelectionNotify: { if (event.xselection.target == wcf.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; } } } ork_wait_xevent(NULL); } } void wcf_end() { int i; // TODO: deinit callback? while (wcf.window_list) wcf_destroy(wcf.window_list); while (wcf.cursor_list) wcf_destroy_curimg(wcf.cursor_list); for (i = 0; i < wcf.n_monitors; i++) { monitor_t* monitor = wcf.monitors[i]; if(monitor) { mem_free(monitor->modes); mem_free(monitor); } } mem_free(wcf.monitors); wcf.monitors = NULL; wcf.n_monitors = 0; if (wcf.helper_window) { if (XGetSelectionOwner(wcf.display, wcf.CLIPBOARD) == wcf.helper_window) { ork_push_selection(); } XDestroyWindow(wcf.display, wcf.helper_window); wcf.helper_window = None; } if (wcf.empty_cursor) { XFreeCursor(wcf.display, wcf.empty_cursor); wcf.empty_cursor = (Cursor) 0; } mem_free(wcf.primary_str); mem_free(wcf.clipboard_str); if (wcf.im) { XCloseIM(wcf.im); wcf.im = NULL; } if (wcf.display) { XCloseDisplay(wcf.display); wcf.display = NULL; } if (wcf.x11xcb.handle) { dlclose(wcf.x11xcb.handle); wcf.x11xcb.handle = NULL; } if (wcf.xcursor.handle) { dlclose(wcf.xcursor.handle); wcf.xcursor.handle = NULL; } if (wcf.randr.handle) { dlclose(wcf.randr.handle); wcf.randr.handle = NULL; } if (wcf.xinerama.handle) { dlclose(wcf.xinerama.handle); wcf.xinerama.handle = NULL; } if (wcf.xrender.handle) { dlclose(wcf.xrender.handle); wcf.xrender.handle = NULL; } if (wcf.vidmode.handle) { dlclose(wcf.vidmode.handle); wcf.vidmode.handle = NULL; } if (wcf.xi.handle) { dlclose(wcf.xi.handle); wcf.xi.handle = NULL; } // NOTE: These need to be unloaded after XCloseDisplay, as they register // cleanup callbacks that get called by that function if (wcf.handle) { dlclose(wcf.handle); wcf.handle = NULL; } memset(&wcf, 0, sizeof(wcf_t)); } byte ork_glx_init() { wcf.handle = DL_LOPEN("libGL.so.1"); if(!wcf.handle) wcf.handle = DL_LOPEN("libGL.so"); if(!wcf.handle) { loge(LOG_GLX, "Failed to load GLX"); return 0; } wcf.GetFBConfigs = dlsym(wcf.handle, "glXGetFBConfigs"); wcf.GetFBConfigAttrib = dlsym(wcf.handle, "glXGetFBConfigAttrib"); wcf.GetClientString = dlsym(wcf.handle, "glXGetClientString"); wcf.QueryExtension = dlsym(wcf.handle, "glXQueryExtension"); wcf.QueryVersion = dlsym(wcf.handle, "glXQueryVersion"); wcf.DestroyContext = dlsym(wcf.handle, "glXDestroyContext"); wcf.MakeCurrent = dlsym(wcf.handle, "glXMakeCurrent"); wcf.SwapBuffers = dlsym(wcf.handle, "glXSwapBuffers"); wcf.QueryExtensionsString = dlsym(wcf.handle, "glXQueryExtensionsString"); wcf.CreateNewContext = dlsym(wcf.handle, "glXCreateNewContext"); wcf.CreateWindow = dlsym(wcf.handle, "glXCreateWindow"); wcf.DestroyWindow = dlsym(wcf.handle, "glXDestroyWindow"); wcf.GetVisualFromFBConfig = dlsym(wcf.handle, "glXGetVisualFromFBConfig"); if (!wcf.GetFBConfigs || !wcf.GetFBConfigAttrib || !wcf.GetClientString || !wcf.QueryExtension || !wcf.QueryVersion || !wcf.DestroyContext || !wcf.MakeCurrent || !wcf.SwapBuffers || !wcf.QueryExtensionsString || !wcf.CreateNewContext || !wcf.CreateWindow || !wcf.DestroyWindow || !wcf.GetVisualFromFBConfig) { loge(LOG_GLX, "Failed to load required entry points"); return 0; } // NOTE: Unlike GLX 1.3 entry points these are not required to be present wcf.GetProcAddress = (PFNGLXGETPROCADDRESSPROC) dlsym(wcf.handle, "glXGetProcAddress"); wcf.GetProcAddressARB = (PFNGLXGETPROCADDRESSPROC) dlsym(wcf.handle, "glXGetProcAddressARB"); if (!glXQueryExtension(wcf.display, &wcf.error_base, &wcf.event_base)) { loge(LOG_GLX, "GLX extension not found"); return 0; } if (!glXQueryVersion(wcf.display, &wcf.major, &wcf.minor)) { loge(LOG_GLX, "Failed to query GLX version"); return 0; } if (wcf.major == 1 && wcf.minor < 3) { loge(LOG_GLX, "GLX version 1.3 is required"); return 0; } if (ork_ext_available("GLX_EXT_swap_control")) { wcf.SwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) ork_glxproc("glXSwapIntervalEXT"); if (wcf.SwapIntervalEXT) wcf.EXT_swap_control = 1; } if (ork_ext_available("GLX_SGI_swap_control")) { wcf.SwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC) ork_glxproc("glXSwapIntervalSGI"); if (wcf.SwapIntervalSGI) wcf.SGI_swap_control = 1; } if (ork_ext_available("GLX_MESA_swap_control")) { wcf.SwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) ork_glxproc("glXSwapIntervalMESA"); if (wcf.SwapIntervalMESA) wcf.MESA_swap_control = 1; } if (ork_ext_available("GLX_ARB_multisample")) wcf.ARB_multisample = 1; if (ork_ext_available("GLX_ARB_create_context")) wcf.CreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC) ork_glxproc("glXCreateContextAttribsARB"); if(!wcf.CreateContextAttribsARB) { loge(LOG_GLX, "GLX_ARB_create_context ist nicht verfügbar"); return 0; } // if (ork_ext_available("GLX_ARB_create_context_no_error")) // wcf.ARB_create_context_no_error = 1; return 1; } void ork_refresh_monitors() { if (wcf.randr.available && !wcf.randr.monitor_broken) { int disconnectedCount, screenCount = 0; monitor_t** disconnected = NULL; XineramaScreenInfo* screens = NULL; XRRScreenResources* sr = XRRGetScreenResourcesCurrent(wcf.display, wcf.root); RROutput primary = XRRGetOutputPrimary(wcf.display, wcf.root); if (wcf.xinerama.available) screens = XineramaQueryScreens(wcf.display, &screenCount); disconnectedCount = wcf.n_monitors; if (disconnectedCount) { disconnected = mem_zeros(wcf.n_monitors * sizeof(monitor_t*), MEM_MISC); memcpy(disconnected, wcf.monitors, wcf.n_monitors * sizeof(monitor_t*)); } for (int i = 0; i < sr->noutput; i++) { int j; XRROutputInfo* oi = XRRGetOutputInfo(wcf.display, sr, sr->outputs[i]); if (oi->connection != RR_Connected || oi->crtc == None) { XRRFreeOutputInfo(oi); continue; } for (j = 0; j < disconnectedCount; j++) { if (disconnected[j] && disconnected[j]->output == sr->outputs[i]) { disconnected[j] = NULL; break; } } if (j < disconnectedCount) { XRRFreeOutputInfo(oi); continue; } XRRCrtcInfo* ci = XRRGetCrtcInfo(wcf.display, sr, oi->crtc); monitor_t* monitor = mem_zeros(sizeof(monitor_t), MEM_SYSTEM); monitor->output = sr->outputs[i]; monitor->crtc = oi->crtc; for (j = 0; j < screenCount; j++) { if (screens[j].x_org == ci->x && screens[j].y_org == ci->y && screens[j].width == ci->width && screens[j].height == ci->height) { monitor->index = j; break; } } ork_update_monitor(monitor, 1, monitor->output == primary); XRRFreeOutputInfo(oi); XRRFreeCrtcInfo(ci); } XRRFreeScreenResources(sr); if (screens) XFree(screens); for (int i = 0; i < disconnectedCount; i++) { if (disconnected[i]) ork_update_monitor(disconnected[i], 0, 0); } mem_free(disconnected); } else { ork_update_monitor(mem_zeros(sizeof(monitor_t), MEM_SYSTEM), 1, 1); } } byte ork_xinput_valid() { byte found = 0; XIMStyles* styles = NULL; if (XGetIMValues(wcf.im, XNQueryInputStyle, &styles, NULL) != NULL) return 0; for (uint i = 0; i < styles->count_styles; i++) { if (styles->supported_styles[i] == (XIMPreeditNothing | XIMStatusNothing)) { found = 1; break; } } XFree(styles); return found; } void ork_create_tables() { int scancode, scancodeMin, scancodeMax; memset(wcf.keycodes, (char)-1, sizeof(wcf.keycodes)); // Use XKB to determine physical key locations independently of the // current keyboard layout XkbDescPtr desc = XkbGetMap(wcf.display, 0, XkbUseCoreKbd); XkbGetNames(wcf.display, XkbKeyNamesMask | XkbKeyAliasesMask, desc); scancodeMin = desc->min_key_code; scancodeMax = desc->max_key_code; const struct { char key; char* name; } keymap[] = { { KEYSYM_CIRCUMFLEX, "TLDE" }, { KEYSYM_1, "AE01" }, { KEYSYM_2, "AE02" }, { KEYSYM_3, "AE03" }, { KEYSYM_4, "AE04" }, { KEYSYM_5, "AE05" }, { KEYSYM_6, "AE06" }, { KEYSYM_7, "AE07" }, { KEYSYM_8, "AE08" }, { KEYSYM_9, "AE09" }, { KEYSYM_0, "AE10" }, { KEYSYM_SHARP_S, "AE11" }, { KEYSYM_ACUTE, "AE12" }, { KEYSYM_Q, "AD01" }, { KEYSYM_W, "AD02" }, { KEYSYM_E, "AD03" }, { KEYSYM_R, "AD04" }, { KEYSYM_T, "AD05" }, { KEYSYM_Z, "AD06" }, { KEYSYM_U, "AD07" }, { KEYSYM_I, "AD08" }, { KEYSYM_O, "AD09" }, { KEYSYM_P, "AD10" }, { KEYSYM_UE, "AD11" }, { KEYSYM_PLUS, "AD12" }, { KEYSYM_A, "AC01" }, { KEYSYM_S, "AC02" }, { KEYSYM_D, "AC03" }, { KEYSYM_F, "AC04" }, { KEYSYM_G, "AC05" }, { KEYSYM_H, "AC06" }, { KEYSYM_J, "AC07" }, { KEYSYM_K, "AC08" }, { KEYSYM_L, "AC09" }, { KEYSYM_OE, "AC10" }, { KEYSYM_AE, "AC11" }, { KEYSYM_Y, "AB01" }, { KEYSYM_X, "AB02" }, { KEYSYM_C, "AB03" }, { KEYSYM_V, "AB04" }, { KEYSYM_B, "AB05" }, { KEYSYM_N, "AB06" }, { KEYSYM_M, "AB07" }, { KEYSYM_COMMA, "AB08" }, { KEYSYM_PERIOD, "AB09" }, { KEYSYM_HYPHEN, "AB10" }, { KEYSYM_NUMBER_SIGN, "BKSL" }, { KEYSYM_LESS_THAN, "LSGT" }, { KEYSYM_SPACE, "SPCE" }, { KEYSYM_ESCAPE, "ESC" }, { KEYSYM_RETURN, "RTRN" }, { KEYSYM_TAB, "TAB" }, { KEYSYM_BACKSPACE, "BKSP" }, { KEYSYM_INSERT, "INS" }, { KEYSYM_DELETE, "DELE" }, { KEYSYM_RIGHT, "RGHT" }, { KEYSYM_LEFT, "LEFT" }, { KEYSYM_DOWN, "DOWN" }, { KEYSYM_UP, "UP" }, { KEYSYM_PAGE_UP, "PGUP" }, { KEYSYM_PAGE_DOWN, "PGDN" }, { KEYSYM_HOME, "HOME" }, { KEYSYM_END, "END" }, { KEYSYM_CAPS_LOCK, "CAPS" }, { KEYSYM_SCROLL_LOCK, "SCLK" }, { KEYSYM_NUM_LOCK, "NMLK" }, { KEYSYM_PRINT_SCREEN, "PRSC" }, { KEYSYM_PAUSE, "PAUS" }, { KEYSYM_F1, "FK01" }, { KEYSYM_F2, "FK02" }, { KEYSYM_F3, "FK03" }, { KEYSYM_F4, "FK04" }, { KEYSYM_F5, "FK05" }, { KEYSYM_F6, "FK06" }, { KEYSYM_F7, "FK07" }, { KEYSYM_F8, "FK08" }, { KEYSYM_F9, "FK09" }, { KEYSYM_F10, "FK10" }, { KEYSYM_F11, "FK11" }, { KEYSYM_F12, "FK12" }, { KEYSYM_KP_0, "KP0" }, { KEYSYM_KP_1, "KP1" }, { KEYSYM_KP_2, "KP2" }, { KEYSYM_KP_3, "KP3" }, { KEYSYM_KP_4, "KP4" }, { KEYSYM_KP_5, "KP5" }, { KEYSYM_KP_6, "KP6" }, { KEYSYM_KP_7, "KP7" }, { KEYSYM_KP_8, "KP8" }, { KEYSYM_KP_9, "KP9" }, { KEYSYM_KP_DECIMAL, "KPDL" }, { KEYSYM_KP_DIVIDE, "KPDV" }, { KEYSYM_KP_MULTIPLY, "KPMU" }, { KEYSYM_KP_SUBTRACT, "KPSU" }, { KEYSYM_KP_ADD, "KPAD" }, { KEYSYM_KP_ENTER, "KPEN" }, { KEYSYM_KP_EQUAL, "KPEQ" }, { KEYSYM_LEFT_SHIFT, "LFSH" }, { KEYSYM_LEFT_CONTROL, "LCTL" }, { KEYSYM_ALT, "LALT" }, { KEYSYM_LEFT_META, "LWIN" }, { KEYSYM_RIGHT_SHIFT, "RTSH" }, { KEYSYM_RIGHT_CONTROL, "RCTL" }, { KEYSYM_ALT_GRAPH, "RALT" }, { KEYSYM_ALT_GRAPH, "LVL3" }, { KEYSYM_ALT_GRAPH, "MDSW" }, { KEYSYM_RIGHT_META, "RWIN" }, { KEYSYM_MENU, "MENU" } }; // Find the X11 key code -> key code mapping for (scancode = scancodeMin; scancode <= scancodeMax; scancode++) { char key = -1; // Map the key name to a key code. Note: We use the US // keyboard layout. Because function keys aren't mapped correctly // when using traditional KeySym translations, they are mapped // here instead. for (int i = 0; i < sizeof(keymap) / sizeof(keymap[0]); i++) { if (strncmp(desc->names->keys[scancode].name, keymap[i].name, XkbKeyNameLength) == 0) { key = keymap[i].key; break; } } // Fall back to key aliases in case the key name did not match for (int i = 0; i < desc->names->num_key_aliases; i++) { if (key != -1) break; if (strncmp(desc->names->key_aliases[i].real, desc->names->keys[scancode].name, XkbKeyNameLength) != 0) { continue; } for (int j = 0; j < sizeof(keymap) / sizeof(keymap[0]); j++) { if (strncmp(desc->names->key_aliases[i].alias, keymap[j].name, XkbKeyNameLength) == 0) { key = keymap[j].key; break; } } } wcf.keycodes[scancode] = key; } XkbFreeNames(desc, XkbKeyNamesMask, True); XkbFreeKeyboard(desc, 0, True); int width; KeySym* keysyms = XGetKeyboardMapping(wcf.display, scancodeMin, scancodeMax - scancodeMin + 1, &width); for (scancode = scancodeMin; scancode <= scancodeMax; scancode++) { if (wcf.keycodes[scancode] < 0) { const size_t base = (scancode - scancodeMin) * width; wcf.keycodes[scancode] = (width > 1 && keysyms[base+1] == XK_KP_Decimal) ? KEYSYM_KP_DECIMAL : ((keysyms[base] == XK_Print) ? KEYSYM_PRINT_SCREEN : -1); } } XFree(keysyms); } Atom ork_getatom(Atom* supportedAtoms, ulong atomCount, const char* atomName) { const Atom atom = XInternAtom(wcf.display, atomName, False); for (ulong i = 0; i < atomCount; i++) { if (supportedAtoms[i] == atom) return atom; } return None; } void ork_check_ewmh() { // First we read the _NET_SUPPORTING_WM_CHECK property on the root window Window* windowFromRoot = NULL; if (!ork_getwinprop(wcf.root, wcf.NET_SUPPORTING_WM_CHECK, XA_WINDOW, (byte**) &windowFromRoot)) { return; } ork_set_errhandler(); // If it exists, it should be the XID of a top-level window // Then we look for the same property on that window Window* windowFromChild = NULL; if (!ork_getwinprop(*windowFromRoot, wcf.NET_SUPPORTING_WM_CHECK, XA_WINDOW, (byte**) &windowFromChild)) { XFree(windowFromRoot); return; } ork_unset_errhandler(); // If the property exists, it should contain the XID of the window if (*windowFromRoot != *windowFromChild) { XFree(windowFromRoot); XFree(windowFromChild); return; } XFree(windowFromRoot); XFree(windowFromChild); // We are now fairly sure that an EWMH-compliant WM is currently running // We can now start querying the WM about what features it supports by // looking in the _NET_SUPPORTED property on the root window // It should contain a list of supported EWMH protocol and state atoms Atom* supportedAtoms = NULL; const ulong atomCount = ork_getwinprop(wcf.root, wcf.NET_SUPPORTED, XA_ATOM, (byte**) &supportedAtoms); // See which of the atoms we support that are supported by the WM wcf.NET_WM_STATE = ork_getatom(supportedAtoms, atomCount, "_NET_WM_STATE"); wcf.NET_WM_STATE_ABOVE = ork_getatom(supportedAtoms, atomCount, "_NET_WM_STATE_ABOVE"); wcf.NET_WM_STATE_FULLSCREEN = ork_getatom(supportedAtoms, atomCount, "_NET_WM_STATE_FULLSCREEN"); wcf.NET_WM_FULLSCREEN_MONITORS = ork_getatom(supportedAtoms, atomCount, "_NET_WM_FULLSCREEN_MONITORS"); wcf.NET_WM_WINDOW_TYPE = ork_getatom(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE"); wcf.NET_WM_WINDOW_TYPE_NORMAL = ork_getatom(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL"); wcf.NET_CURRENT_DESKTOP = ork_getatom(supportedAtoms, atomCount, "_NET_CURRENT_DESKTOP"); wcf.NET_ACTIVE_WINDOW = ork_getatom(supportedAtoms, atomCount, "_NET_ACTIVE_WINDOW"); wcf.NET_FRAME_EXTENTS = ork_getatom(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS"); wcf.NET_REQUEST_FRAME_EXTENTS = ork_getatom(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS"); if (supportedAtoms) XFree(supportedAtoms); } byte ork_init_extensions() { wcf.vidmode.handle = DL_LOPEN("libXxf86vm.so.1"); if (wcf.vidmode.handle) { wcf.vidmode.QueryExtension = (PFN_XF86VidModeQueryExtension) dlsym(wcf.vidmode.handle, "XF86VidModeQueryExtension"); wcf.vidmode.available = XF86VidModeQueryExtension(wcf.display, &wcf.vidmode.event_base, &wcf.vidmode.error_base); } wcf.xi.handle = DL_LOPEN("libXi.so.6"); if (wcf.xi.handle) { wcf.xi.QueryVersion = (PFN_XIQueryVersion) dlsym(wcf.xi.handle, "XIQueryVersion"); wcf.xi.SelectEvents = (PFN_XISelectEvents) dlsym(wcf.xi.handle, "XISelectEvents"); if (XQueryExtension(wcf.display, "XInputExtension", &wcf.xi.major_opcode, &wcf.xi.event_base, &wcf.xi.error_base)) { wcf.xi.major = 2; wcf.xi.minor = 0; if (XIQueryVersion(wcf.display, &wcf.xi.major, &wcf.xi.minor) == Success) { wcf.xi.available = 1; } } } wcf.randr.handle = DL_LOPEN("libXrandr.so.2"); if (wcf.randr.handle) { wcf.randr.FreeCrtcInfo = (PFN_XRRFreeCrtcInfo) dlsym(wcf.randr.handle, "XRRFreeCrtcInfo"); wcf.randr.FreeOutputInfo = (PFN_XRRFreeOutputInfo) dlsym(wcf.randr.handle, "XRRFreeOutputInfo"); wcf.randr.FreeScreenResources = (PFN_XRRFreeScreenResources) dlsym(wcf.randr.handle, "XRRFreeScreenResources"); wcf.randr.GetCrtcInfo = (PFN_XRRGetCrtcInfo) dlsym(wcf.randr.handle, "XRRGetCrtcInfo"); wcf.randr.GetOutputInfo = (PFN_XRRGetOutputInfo) dlsym(wcf.randr.handle, "XRRGetOutputInfo"); wcf.randr.GetOutputPrimary = (PFN_XRRGetOutputPrimary) dlsym(wcf.randr.handle, "XRRGetOutputPrimary"); wcf.randr.GetScreenResourcesCurrent = (PFN_XRRGetScreenResourcesCurrent) dlsym(wcf.randr.handle, "XRRGetScreenResourcesCurrent"); wcf.randr.QueryExtension = (PFN_XRRQueryExtension) dlsym(wcf.randr.handle, "XRRQueryExtension"); wcf.randr.QueryVersion = (PFN_XRRQueryVersion) dlsym(wcf.randr.handle, "XRRQueryVersion"); wcf.randr.SelectInput = (PFN_XRRSelectInput) dlsym(wcf.randr.handle, "XRRSelectInput"); wcf.randr.SetCrtcConfig = (PFN_XRRSetCrtcConfig) dlsym(wcf.randr.handle, "XRRSetCrtcConfig"); wcf.randr.UpdateConfiguration = (PFN_XRRUpdateConfiguration) dlsym(wcf.randr.handle, "XRRUpdateConfiguration"); if (XRRQueryExtension(wcf.display, &wcf.randr.event_base, &wcf.randr.error_base)) { if (XRRQueryVersion(wcf.display, &wcf.randr.major, &wcf.randr.minor)) { // The RandR path requires at least version 1.3 if (wcf.randr.major > 1 || wcf.randr.minor >= 3) wcf.randr.available = 1; } else { loge(LOG_X11, "Failed to query RandR version"); } } } if (wcf.randr.available) { XRRScreenResources* sr = XRRGetScreenResourcesCurrent(wcf.display, wcf.root); if (!sr->ncrtc) { // A system without CRTCs is likely a system with broken RandR // Disable the RandR monitor path and fall back to core functions wcf.randr.monitor_broken = 1; } XRRFreeScreenResources(sr); } if (wcf.randr.available && !wcf.randr.monitor_broken) { XRRSelectInput(wcf.display, wcf.root, RROutputChangeNotifyMask); } wcf.xcursor.handle = DL_LOPEN("libXcursor.so.1"); if (wcf.xcursor.handle) { wcf.xcursor.ImageCreate = (PFN_XcursorImageCreate) dlsym(wcf.xcursor.handle, "XcursorImageCreate"); wcf.xcursor.ImageDestroy = (PFN_XcursorImageDestroy) dlsym(wcf.xcursor.handle, "XcursorImageDestroy"); wcf.xcursor.ImageLoadCursor = (PFN_XcursorImageLoadCursor) dlsym(wcf.xcursor.handle, "XcursorImageLoadCursor"); } wcf.xinerama.handle = DL_LOPEN("libXinerama.so.1"); if (wcf.xinerama.handle) { wcf.xinerama.IsActive = (PFN_XineramaIsActive) dlsym(wcf.xinerama.handle, "XineramaIsActive"); wcf.xinerama.QueryExtension = (PFN_XineramaQueryExtension) dlsym(wcf.xinerama.handle, "XineramaQueryExtension"); wcf.xinerama.QueryScreens = (PFN_XineramaQueryScreens) dlsym(wcf.xinerama.handle, "XineramaQueryScreens"); if (XineramaQueryExtension(wcf.display, &wcf.xinerama.major, &wcf.xinerama.minor)) { if (XineramaIsActive(wcf.display)) wcf.xinerama.available = 1; } } wcf.xkb.major = 1; wcf.xkb.minor = 0; wcf.xkb.available = XkbQueryExtension(wcf.display, &wcf.xkb.major_opcode, &wcf.xkb.event_base, &wcf.xkb.error_base, &wcf.xkb.major, &wcf.xkb.minor); if (wcf.xkb.available) { Bool supported; if (XkbSetDetectableAutoRepeat(wcf.display, True, &supported)) { if (supported) wcf.xkb.detectable = 1; } XkbStateRec state; if (XkbGetState(wcf.display, XkbUseCoreKbd, &state) == Success) wcf.xkb.group = (uint)state.group; XkbSelectEventDetails(wcf.display, XkbUseCoreKbd, XkbStateNotify, XkbGroupStateMask, XkbGroupStateMask); } wcf.x11xcb.handle = DL_LOPEN("libX11-xcb.so.1"); if (wcf.x11xcb.handle) { wcf.x11xcb.GetXCBConnection = (PFN_XGetXCBConnection) dlsym(wcf.x11xcb.handle, "XGetXCBConnection"); } wcf.xrender.handle = DL_LOPEN("libXrender.so.1"); if (wcf.xrender.handle) { wcf.xrender.QueryExtension = (PFN_XRenderQueryExtension) dlsym(wcf.xrender.handle, "XRenderQueryExtension"); wcf.xrender.QueryVersion = (PFN_XRenderQueryVersion) dlsym(wcf.xrender.handle, "XRenderQueryVersion"); wcf.xrender.FindVisualFormat = (PFN_XRenderFindVisualFormat) dlsym(wcf.xrender.handle, "XRenderFindVisualFormat"); if (XRenderQueryExtension(wcf.display, &wcf.xrender.error_base, &wcf.xrender.event_base)) { if (XRenderQueryVersion(wcf.display, &wcf.xrender.major, &wcf.xrender.minor)) { wcf.xrender.available = 1; } } } // Update the key code LUT // FIXME: We should listen to XkbMapNotify events to track changes to // the keyboard mapping. ork_create_tables(); // String format atoms wcf.NULL_ = XInternAtom(wcf.display, "NULL", False); wcf.UTF8_STRING = XInternAtom(wcf.display, "UTF8_STRING", False); wcf.ATOM_PAIR = XInternAtom(wcf.display, "ATOM_PAIR", False); // Custom selection property atom wcf.WCF_SELECTION = XInternAtom(wcf.display, "WCF_SELECTION", False); // ICCCM standard clipboard atoms wcf.TARGETS = XInternAtom(wcf.display, "TARGETS", False); wcf.MULTIPLE = XInternAtom(wcf.display, "MULTIPLE", False); wcf.PRIMARY = XInternAtom(wcf.display, "PRIMARY", False); wcf.INCR = XInternAtom(wcf.display, "INCR", False); wcf.CLIPBOARD = XInternAtom(wcf.display, "CLIPBOARD", False); // Clipboard manager atoms wcf.CLIPBOARD_MANAGER = XInternAtom(wcf.display, "CLIPBOARD_MANAGER", False); wcf.SAVE_TARGETS = XInternAtom(wcf.display, "SAVE_TARGETS", False); // Xdnd (drag and drop) atoms wcf.XdndAware = XInternAtom(wcf.display, "XdndAware", False); wcf.XdndEnter = XInternAtom(wcf.display, "XdndEnter", False); wcf.XdndPosition = XInternAtom(wcf.display, "XdndPosition", False); wcf.XdndStatus = XInternAtom(wcf.display, "XdndStatus", False); wcf.XdndActionCopy = XInternAtom(wcf.display, "XdndActionCopy", False); wcf.XdndDrop = XInternAtom(wcf.display, "XdndDrop", False); wcf.XdndFinished = XInternAtom(wcf.display, "XdndFinished", False); wcf.XdndSelection = XInternAtom(wcf.display, "XdndSelection", False); wcf.XdndTypeList = XInternAtom(wcf.display, "XdndTypeList", False); wcf.text_uri_list = XInternAtom(wcf.display, "text/uri-list", False); // ICCCM, EWMH and Motif window property atoms // These can be set safely even without WM support // The EWMH atoms that require WM support are handled in ork_check_ewmh wcf.WM_PROTOCOLS = XInternAtom(wcf.display, "WM_PROTOCOLS", False); wcf.WM_STATE = XInternAtom(wcf.display, "WM_STATE", False); wcf.WM_DELETE_WINDOW = XInternAtom(wcf.display, "WM_DELETE_WINDOW", False); wcf.NET_SUPPORTED = XInternAtom(wcf.display, "_NET_SUPPORTED", False); wcf.NET_SUPPORTING_WM_CHECK = XInternAtom(wcf.display, "_NET_SUPPORTING_WM_CHECK", False); wcf.NET_WM_ICON = XInternAtom(wcf.display, "_NET_WM_ICON", False); wcf.NET_WM_PING = XInternAtom(wcf.display, "_NET_WM_PING", False); wcf.NET_WM_PID = XInternAtom(wcf.display, "_NET_WM_PID", False); wcf.NET_WM_NAME = XInternAtom(wcf.display, "_NET_WM_NAME", False); wcf.NET_WM_ICON_NAME = XInternAtom(wcf.display, "_NET_WM_ICON_NAME", False); wcf.NET_WM_BYPASS_COMPOSITOR = XInternAtom(wcf.display, "_NET_WM_BYPASS_COMPOSITOR", False); wcf.NET_WM_WINDOW_OPACITY = XInternAtom(wcf.display, "_NET_WM_WINDOW_OPACITY", False); wcf.MOTIF_WM_HINTS = XInternAtom(wcf.display, "_MOTIF_WM_HINTS", False); // The compositing manager selection name contains the screen number { char name[32]; snprintf(name, sizeof(name), "_NET_WM_CM_S%u", wcf.screen); wcf.NET_WM_CM_Sx = XInternAtom(wcf.display, name, False); } // Detect whether an EWMH-conformant window manager is running ork_check_ewmh(); if(!wcf.xrender.available) loge(LOG_X11, "Die XRender-Erweiterung wurde nicht gefunden"); if(!wcf.xkb.available) loge(LOG_X11, "Die XKB-Erweiterung wurde nicht gefunden"); return wcf.xrender.available && wcf.xkb.available; } byte wcf_init(byte debug) { memset(&wcf, 0, sizeof(wcf_t)); wcf.debug = debug; // HACK: If the application has left the locale as "C" then both wide // character text input and explicit UTF-8 input via XIM will break // This sets the CTYPE part of the current locale from the environment // in the hope that it is set to something more sane than "C" if (strcmp(setlocale(LC_CTYPE, NULL), "C") == 0) setlocale(LC_CTYPE, ""); XInitThreads(); XrmInitialize(); wcf.display = XOpenDisplay(NULL); if (!wcf.display) { const char* display = getenv("DISPLAY"); if (display) { loge(LOG_X11, "Failed to open display %s", display); } else { loge(LOG_X11, "The DISPLAY environment variable is missing"); } wcf_end(); return 0; } wcf.screen = DefaultScreen(wcf.display); wcf.root = RootWindow(wcf.display, wcf.screen); wcf.context = XUniqueContext(); if (!ork_init_extensions()) { wcf_end(); return 0; } XSetWindowAttributes wa; wa.event_mask = PropertyChangeMask; wcf.helper_window = XCreateWindow(wcf.display, wcf.root, 0, 0, 1, 1, 0, 0, InputOnly, DefaultVisual(wcf.display, wcf.screen), CWEventMask, &wa); byte pixels[16 * 16 * 4] = { 0 }; wcf.empty_cursor = ork_createcur(pixels, 16, 16, 0, 0); if (XSupportsLocale()) { XSetLocaleModifiers(""); wcf.im = XOpenIM(wcf.display, 0, NULL, NULL); if (wcf.im) { if (!ork_xinput_valid()) { loge(LOG_X11, "Keine gültige Eingabemethode gefunden"); XCloseIM(wcf.im); wcf.im = NULL; } } else { loge(LOG_X11, "Konnte Eingabemethode nicht öffnen"); } } else { loge(LOG_X11, "Erweiterte Eingabe wird vom X-Server nicht unterstützt"); } if (!wcf.im) { wcf_end(); return 0; } if (!ork_glx_init()) { wcf_end(); return 0; } ork_refresh_monitors(); if (!ork_sel_glxcfg(&wcf.visual, &wcf.native, &wcf.depth)) { wcf_end(); return 0; } if (!ork_create_glx(wcf.native)) { wcf_end(); return 0; } return 1; } /*! * The pixels are 32-bit, little-endian, non-premultiplied RGBA, i.e. eight * bits per channel with the red channel first. They are arranged canonically * as packed sequential rows, starting from the top-left corner. * * The desired image sizes varies depending on platform and system settings. * The selected images will be rescaled as needed. Good sizes include 16x16, * 32x32 and 48x48. */ void wcf_icon(window_t* window, const byte *image, int width, int height) { if (image) { int j; int longCount = 2 + width * height; ulong* icon = mem_zeros(longCount * sizeof(ulong), MEM_IMAGE); ulong* target = icon; *target++ = width; *target++ = height; for (j = 0; j < width * height; j++) { *target++ = (((ulong) image[j * 4 + 0]) << 16) | (((ulong) image[j * 4 + 1]) << 8) | (((ulong) image[j * 4 + 2]) << 0) | (((ulong) image[j * 4 + 3]) << 24); } // NOTE: XChangeProperty expects 32-bit values like the image data above to be // placed in the 32 least significant bits of individual longs. This is // true even if long is 64-bit and a WM protocol calls for "packed" data. // This is because of a historical mistake that then became part of the Xlib // ABI. Xlib will pack these values into a regular array of 32-bit values // before sending it over the wire. XChangeProperty(wcf.display, window->handle, wcf.NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, (byte*) icon, longCount); mem_free(icon); } else { XDeleteProperty(wcf.display, window->handle, wcf.NET_WM_ICON); } XFlush(wcf.display); } void wcf_resize(window_t* window, int width, int height) { if (window->monitor) return; if (!window->resizable) ork_updatehints(window, width, height); XResizeWindow(wcf.display, window->handle, width, height); XFlush(wcf.display); } void wcf_limits(window_t* window, int minwidth, int minheight, int maxwidth, int maxheight) { window->minwidth = minwidth; window->minheight = minheight; window->maxwidth = maxwidth; window->maxheight = maxheight; if (window->monitor || !window->resizable) return; int width, height; wcf_getsize(window, &width, &height); ork_updatehints(window, width, height); XFlush(wcf.display); } void wcf_minimize(window_t* window) { if (window->override_redir) { // Override-redirect windows cannot be iconified or restored, as those // tasks are performed by the window manager loge(LOG_X11, "Iconification of full screen windows requires a WM that supports EWMH full screen"); return; } XIconifyWindow(wcf.display, window->handle, wcf.screen); XFlush(wcf.display); } int ork_getstate(window_t* window) { int result = WithdrawnState; struct { CARD32 state; Window icon; } *state = NULL; if (ork_getwinprop(window->handle, wcf.WM_STATE, wcf.WM_STATE, (byte**) &state) >= 2) { result = state->state; } if (state) XFree(state); return result; } byte ork_wait_visibility(window_t* window) { XEvent dummy; double timeout = 0.1; while (!XCheckTypedWindowEvent(wcf.display, window->handle, VisibilityNotify, &dummy)) { if (!ork_wait_xevent(&timeout)) return 0; } return 1; } void wcf_restore(window_t* window) { if (window->override_redir) { // Override-redirect windows cannot be iconified or restored, as those // tasks are performed by the window manager loge(LOG_X11, "Iconification of full screen windows requires a WM that supports EWMH full screen"); return; } if (ork_getstate(window) == IconicState) { XMapWindow(wcf.display, window->handle); ork_wait_visibility(window); } XFlush(wcf.display); } void wcf_show(window_t* window, byte focus) { if(window->monitor) return; if(!ork_winvisible(window)) { XMapWindow(wcf.display, window->handle); ork_wait_visibility(window); } if(focus) { if (wcf.NET_ACTIVE_WINDOW) ork_sendevent(window, wcf.NET_ACTIVE_WINDOW, 1, 0, 0, 0, 0); else if (ork_winvisible(window)) { XRaiseWindow(wcf.display, window->handle); XSetInputFocus(wcf.display, window->handle, RevertToParent, CurrentTime); } XFlush(wcf.display); } } void wcf_hide(window_t* window) { if(window->monitor) return; XUnmapWindow(wcf.display, window->handle); XFlush(wcf.display); } void wcf_floating(window_t* window, byte value) { if(window->floating == value) return; window->floating = value; if(!window->monitor) ork_winfloating(window, value); } void ork_setmode(monitor_t* monitor, int width, int height, int refresh) { if (wcf.randr.available && !wcf.randr.monitor_broken) { vidmode_t current; RRMode native = None; const vidmode_t* best = ork_select_mode(monitor, width, height, refresh); ork_getmode(monitor, ¤t); if (ork_compmodes(¤t, best) == 0) return; XRRScreenResources* sr = XRRGetScreenResourcesCurrent(wcf.display, wcf.root); XRRCrtcInfo* ci = XRRGetCrtcInfo(wcf.display, sr, monitor->crtc); XRROutputInfo* oi = XRRGetOutputInfo(wcf.display, sr, monitor->output); for (int i = 0; i < oi->nmode; i++) { const XRRModeInfo* mi = ork_modeinfo(sr, oi->modes[i]); if ((mi->modeFlags & RR_Interlace) != 0) continue; const vidmode_t mode = ork_convmode(mi, ci); if (ork_compmodes(best, &mode) == 0) { native = mi->id; break; } } if (native) { if (monitor->old_mode == None) monitor->old_mode = ci->mode; XRRSetCrtcConfig(wcf.display, sr, monitor->crtc, CurrentTime, ci->x, ci->y, native, ci->rotation, ci->outputs, ci->noutput); } XRRFreeOutputInfo(oi); XRRFreeCrtcInfo(ci); XRRFreeScreenResources(sr); } } void ork_set_monitor(window_t* window) { ork_setmode(window->monitor, window->video_width, window->video_height, window->video_refresh); if (window->override_redir) { int xpos, ypos; vidmode_t mode; // Manually position the window over its monitor ork_monitorpos(window->monitor, &xpos, &ypos); ork_getmode(window->monitor, &mode); XMoveResizeWindow(wcf.display, window->handle, xpos, ypos, mode.width, mode.height); } window->monitor->window = window; } void wcf_fullscreen(window_t* window, int width, int height, int refresh) { window->video_width = width; window->video_height = height; window->video_refresh = refresh; monitor_t* monitor = ork_getmonitor(); if (window->monitor == monitor) { if (monitor->window == window) ork_set_monitor(window); XFlush(wcf.display); return; } if (window->monitor) { ork_disabledecor(window); ork_winfloating(window, window->floating); ork_unset_monitor(window); } window->monitor = monitor; ork_updatehints(window, width, height); if (!ork_winvisible(window)) { XMapRaised(wcf.display, window->handle); ork_wait_visibility(window); } ork_updatemode(window); ork_set_monitor(window); XFlush(wcf.display); } byte wcf_hovered(window_t* window) { Window w = wcf.root; while (w) { Window root; int rootX, rootY, childX, childY; uint mask; ork_set_errhandler(); const Bool result = XQueryPointer(wcf.display, w, &root, &w, &rootX, &rootY, &childX, &childY, &mask); ork_unset_errhandler(); if (wcf.error_code == BadWindow) w = wcf.root; else if (!result) return 0; else if (w == window->handle) return 1; } return 0; } void ork_event(XEvent *event) { 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; filtered = XFilterEvent(event, None); if (wcf.randr.available) { if (event->type == wcf.randr.event_base + RRNotify) { XRRUpdateConfiguration(event); ork_refresh_monitors(); return; } } if (event->type == wcf.xkb.event_base + XkbEventCode) { if (((XkbEvent*) event)->any.xkb_type == XkbStateNotify && (((XkbEvent*) event)->state.changed & XkbGroupStateMask)) { wcf.xkb.group = ((XkbEvent*) event)->state.group; } return; } if (event->type == GenericEvent) { return; } if (event->type == SelectionRequest) { ork_handle_select(event); return; } window_t* window = NULL; if (XFindContext(wcf.display, event->xany.window, wcf.context, (XPointer*) &window) != 0) { // This is an event for a window that has already been destroyed return; } switch (event->type) { case ReparentNotify: { window->parent = event->xreparent.parent; return; } case KeyPress: { const char key = (keycode < 0 || keycode > 255) ? -1 : wcf.keycodes[keycode]; const int state = event->xkey.state; const byte plain = !(state & ControlMask) && !(state & Mod1Mask); // HACK: Do not report the key press events duplicated by XIM // Duplicate key releases are filtered out implicitly by // the key repeat logic in ork_key // A timestamp per key is used to handle simultaneous keys // NOTE: Always allow the first event for each key through // (the server never sends a timestamp of zero) // NOTE: Timestamp difference is compared to handle wrap-around Time diff = event->xkey.time - window->key_times[keycode]; if (diff == event->xkey.time || (diff > 0 && diff < ((Time)1 << 31))) { if (keycode) ork_key(window, key, KEY_PRESS); window->key_times[keycode] = event->xkey.time; } if (!filtered) { int count; Status status; char buffer[100]; char* chars = buffer; count = Xutf8LookupString(window->ic, &event->xkey, buffer, sizeof(buffer) - 1, NULL, &status); if (status == XBufferOverflow) { chars = mem_zeros(count + 1, MEM_MISC); count = Xutf8LookupString(window->ic, &event->xkey, chars, count, NULL, &status); } if (plain && (status == XLookupChars || status == XLookupBoth)) { const char* c = chars; uint codepoint; chars[count] = '\0'; while (c - chars < count) { codepoint = ork_decode_utf(&c); if(codepoint >= 32 && (codepoint <= 126 || codepoint >= 160)) win_char(window, codepoint); } } if (chars != buffer) mem_free(chars); } return; } case KeyRelease: { const char key = (keycode < 0 || keycode > 255) ? -1 : wcf.keycodes[keycode]; if (!wcf.xkb.detectable) { // HACK: Key repeat events will arrive as KeyRelease/KeyPress // pairs with similar or identical time stamps // The key repeat logic in ork_key expects only key // presses to repeat, so detect and discard release events if (XEventsQueued(wcf.display, QueuedAfterReading)) { XEvent next; XPeekEvent(wcf.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; } } } } ork_key(window, key, KEY_RELEASE); return; } case ButtonPress: { if (event->xbutton.button == Button1) ork_button(window, 0, KEY_PRESS); else if (event->xbutton.button == Button2) ork_button(window, 2, KEY_PRESS); else if (event->xbutton.button == Button3) ork_button(window, 1, KEY_PRESS); // Modern X provides scroll events as mouse button presses else if (event->xbutton.button == Button4) win_scroll(window, 0, 1); else if (event->xbutton.button == Button5) win_scroll(window, 0, -1); else if (event->xbutton.button == 6) win_scroll(window, 1, 0); else if (event->xbutton.button == 7) win_scroll(window, -1, 0); else { // Additional buttons after 7 are treated as regular buttons // We subtract 4 to fill the gap left by scroll input above ork_button(window, event->xbutton.button - Button1 - 4, KEY_PRESS); } return; } case ButtonRelease: { if (event->xbutton.button == Button1) { ork_button(window, 0, KEY_RELEASE); } else if (event->xbutton.button == Button2) { ork_button(window, 2, KEY_RELEASE); } else if (event->xbutton.button == Button3) { ork_button(window, 1, KEY_RELEASE); } else if (event->xbutton.button > 7) { // Additional buttons after 7 are treated as regular buttons // We subtract 4 to fill the gap left by scroll input above ork_button(window, event->xbutton.button - Button1 - 4, KEY_RELEASE); } return; } case EnterNotify: { // XEnterWindowEvent is XCrossingEvent const int x = event->xcrossing.x; const int y = event->xcrossing.y; ork_mouse(window, x, y); window->last_cur_x = x; window->last_cur_y = y; return; } case LeaveNotify: { return; } case MotionNotify: { const int x = event->xmotion.x; const int y = event->xmotion.y; if (x != window->warp_cur_x || y != window->warp_cur_y) { // The cursor was moved by something else if (window->cursor_set ^ 1) { if (wcf.grab_cur_window != window) return; const int dx = x - window->last_cur_x; const int dy = y - window->last_cur_y; ork_mouse(window, window->virtual_cur_x + dx, window->virtual_cur_y + dy); } else ork_mouse(window, x, y); } window->last_cur_x = x; window->last_cur_y = y; return; } case ConfigureNotify: { if (event->xconfigure.width != window->width || event->xconfigure.height != window->height) { win_fbsize(window, event->xconfigure.width, event->xconfigure.height); // win_size(window, // event->xconfigure.width, // event->xconfigure.height); window->width = event->xconfigure.width; window->height = event->xconfigure.height; } int xpos = event->xconfigure.x; int ypos = event->xconfigure.y; // NOTE: ConfigureNotify events from the server are in local // coordinates, so if we are reparented we need to translate // the position into root (screen) coordinates if (!event->xany.send_event && window->parent != wcf.root) { ork_set_errhandler(); Window dummy; XTranslateCoordinates(wcf.display, window->parent, wcf.root, xpos, ypos, &xpos, &ypos, &dummy); ork_unset_errhandler(); if (wcf.error_code == BadWindow) return; } if (xpos != window->xpos || ypos != window->ypos) { win_pos(window, xpos, ypos); window->xpos = xpos; window->ypos = ypos; } 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 == wcf.WM_PROTOCOLS) { const Atom protocol = event->xclient.data.l[0]; if (protocol == None) return; if (protocol == wcf.WM_DELETE_WINDOW) { // The window manager was asked to close the window, for // example by the user pressing a 'close' window decoration // button win_closed(window); } else if (protocol == wcf.NET_WM_PING) { // The window manager is pinging the application to ensure // it's still responding to events XEvent reply = *event; reply.xclient.window = wcf.root; XSendEvent(wcf.display, wcf.root, False, SubstructureNotifyMask | SubstructureRedirectMask, &reply); } } else if (event->xclient.message_type == wcf.XdndEnter) { // A drag operation has entered the window ulong i, count; Atom* formats = NULL; const byte list = event->xclient.data.l[1] & 1; wcf.xdnd.source = event->xclient.data.l[0]; wcf.xdnd.version = event->xclient.data.l[1] >> 24; wcf.xdnd.format = None; if (wcf.xdnd.version > WCF_XDND_VERSION) return; if (list) { count = ork_getwinprop(wcf.xdnd.source, wcf.XdndTypeList, XA_ATOM, (byte**) &formats); } else { count = 3; formats = (Atom*) event->xclient.data.l + 2; } for (i = 0; i < count; i++) { if (formats[i] == wcf.text_uri_list) { wcf.xdnd.format = wcf.text_uri_list; break; } } if (list && formats) XFree(formats); } else if (event->xclient.message_type == wcf.XdndDrop) { // The drag operation has finished by dropping on the window Time time = CurrentTime; if (wcf.xdnd.version > WCF_XDND_VERSION) return; if (wcf.xdnd.format) { if (wcf.xdnd.version >= 1) time = event->xclient.data.l[2]; // Request the chosen format from the source window XConvertSelection(wcf.display, wcf.XdndSelection, wcf.xdnd.format, wcf.XdndSelection, window->handle, time); } else if (wcf.xdnd.version >= 2) { XEvent reply = { ClientMessage }; reply.xclient.window = wcf.xdnd.source; reply.xclient.message_type = wcf.XdndFinished; reply.xclient.format = 32; reply.xclient.data.l[0] = window->handle; reply.xclient.data.l[1] = 0; // The drag was rejected reply.xclient.data.l[2] = None; XSendEvent(wcf.display, wcf.xdnd.source, False, NoEventMask, &reply); XFlush(wcf.display); } } else if (event->xclient.message_type == wcf.XdndPosition) { // The drag operation has moved over the window const int xabs = (event->xclient.data.l[2] >> 16) & 0xffff; const int yabs = (event->xclient.data.l[2]) & 0xffff; Window dummy; int xpos, ypos; if (wcf.xdnd.version > WCF_XDND_VERSION) return; XTranslateCoordinates(wcf.display, wcf.root, window->handle, xabs, yabs, &xpos, &ypos, &dummy); ork_mouse(window, xpos, ypos); XEvent reply = { ClientMessage }; reply.xclient.window = wcf.xdnd.source; reply.xclient.message_type = wcf.XdndStatus; reply.xclient.format = 32; reply.xclient.data.l[0] = window->handle; reply.xclient.data.l[2] = 0; // Specify an empty rectangle reply.xclient.data.l[3] = 0; if (wcf.xdnd.format) { // Reply that we are ready to copy the dragged data reply.xclient.data.l[1] = 1; // Accept with no rectangle if (wcf.xdnd.version >= 2) reply.xclient.data.l[4] = wcf.XdndActionCopy; } XSendEvent(wcf.display, wcf.xdnd.source, False, NoEventMask, &reply); XFlush(wcf.display); } return; } case SelectionNotify: { if (event->xselection.property == wcf.XdndSelection) { // The converted data from the drag operation has arrived char* data; const ulong result = ork_getwinprop(event->xselection.requestor, event->xselection.property, event->xselection.target, (byte**) &data); if (result) { int i, count; char** paths = ork_parse_uri(data, &count); win_drop(window, count, (const char**) paths); for (i = 0; i < count; i++) mem_free(paths[i]); mem_free(paths); } if (data) XFree(data); if (wcf.xdnd.version >= 2) { XEvent reply = { ClientMessage }; reply.xclient.window = wcf.xdnd.source; reply.xclient.message_type = wcf.XdndFinished; reply.xclient.format = 32; reply.xclient.data.l[0] = window->handle; reply.xclient.data.l[1] = result; reply.xclient.data.l[2] = wcf.XdndActionCopy; XSendEvent(wcf.display, wcf.xdnd.source, False, NoEventMask, &reply); XFlush(wcf.display); } } return; } case FocusIn: { 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->cursor_set ^ 1) ork_disablecur(window); XSetICFocus(window->ic); // ork_resetkeys(window, 1); wcf.active = window; win_focus(window, 1); return; } case FocusOut: { 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->cursor_set ^ 1) ork_enablecur(window); XUnsetICFocus(window->ic); if (window->monitor) wcf_minimize(window); if(wcf.active == window) wcf.active = NULL; win_focus(window, 0); ork_resetkeys(window); return; } case Expose: { win_redraw(window); return; } case PropertyNotify: { if (event->xproperty.state != PropertyNewValue) return; if (event->xproperty.atom == wcf.WM_STATE) { const int state = ork_getstate(window); if (state != IconicState && state != NormalState) return; const byte iconified = (state == IconicState); if (window->iconified != iconified) { if (window->monitor) { if (iconified) ork_unset_monitor(window); else ork_set_monitor(window); } window->iconified = iconified; } } return; } case DestroyNotify: return; } } /*! * On some platforms, a window move, resize or menu operation will cause event * processing to block. This is due to how event processing is designed on * those platforms. You can use the * [window refresh callback](@ref window_refresh) to redraw the contents of * your window when necessary during such operations. * * Do not assume that callbacks you set will _only_ be called in response to * event processing functions like this one. While it is necessary to poll for * events, window systems that require to register callbacks of its own * can pass events in response to many window system function calls. */ void wcf_poll() { XPending(wcf.display); while (XQLength(wcf.display)) { XEvent event; XNextEvent(wcf.display, &event); ork_event(&event); } window_t* window = wcf.grab_cur_window; if (window) { int width, height; wcf_getsize(window, &width, &height); // NOTE: Re-center the cursor only if it has moved since the last call, // to avoid breaking event waiting with MotionNotify if (window->last_cur_x != width / 2 || window->last_cur_y != height / 2) { ork_setcur(window, width / 2, height / 2); } } XFlush(wcf.display); }