Archived
1
0
Fork 0
This repository has been archived on 2025-09-02. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
skcblitz/axe_ork.h

4111 lines
116 KiB
C
Raw Permalink Normal View History

2025-09-02 14:43:36 +02:00
/*
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 <elmindreda@glfw.org>
*
* 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,
&notification))
{
ork_wait_xevent(NULL);
}
if (notification.xselection.property == None)
continue;
XCheckIfEvent(wcf.display,
&dummy,
ork_is_selnewvalue,
(XPointer) &notification);
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) &notification))
{
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, &current);
if (ork_compmodes(&current, 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);
}