4110 lines
116 KiB
C
4110 lines
116 KiB
C
/*
|
|
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,
|
|
¬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);
|
|
}
|