mirror of
https://github.com/gnif/LookingGlass.git
synced 2024-11-24 22:37:19 +00:00
2039 lines
49 KiB
C
2039 lines
49 KiB
C
/**
|
|
* Looking Glass
|
|
* Copyright © 2017-2024 The Looking Glass Authors
|
|
* https://looking-glass.io
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "interface/displayserver.h"
|
|
|
|
#include "x11.h"
|
|
#include "atoms.h"
|
|
#include "clipboard.h"
|
|
#include "cursor.h"
|
|
|
|
#include "resources/icondata.h"
|
|
#include "resources/no-input-cursor/16.xcur.h"
|
|
#include "resources/no-input-cursor/32.xcur.h"
|
|
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/epoll.h>
|
|
#include <errno.h>
|
|
|
|
#include <X11/extensions/XInput2.h>
|
|
#include <X11/extensions/scrnsaver.h>
|
|
#include <X11/extensions/Xinerama.h>
|
|
#include <X11/extensions/Xpresent.h>
|
|
#include <X11/Xcursor/Xcursor.h>
|
|
|
|
#include <xkbcommon/xkbcommon.h>
|
|
|
|
#include <GL/glx.h>
|
|
#include <GL/glxext.h>
|
|
|
|
#ifdef ENABLE_EGL
|
|
#include <EGL/eglext.h>
|
|
#include "egl_dynprocs.h"
|
|
#include "eglutil.h"
|
|
#endif
|
|
|
|
#include "app.h"
|
|
#include "common/debug.h"
|
|
#include "common/time.h"
|
|
#include "common/event.h"
|
|
#include "util.h"
|
|
|
|
struct X11DSState x11;
|
|
|
|
struct MwmHints
|
|
{
|
|
unsigned long flags;
|
|
unsigned long functions;
|
|
unsigned long decorations;
|
|
long input_mode;
|
|
unsigned long status;
|
|
};
|
|
|
|
enum {
|
|
MWM_HINTS_FUNCTIONS = (1L << 0),
|
|
MWM_HINTS_DECORATIONS = (1L << 1),
|
|
|
|
MWM_FUNC_ALL = (1L << 0),
|
|
MWM_FUNC_RESIZE = (1L << 1),
|
|
MWM_FUNC_MOVE = (1L << 2),
|
|
MWM_FUNC_MINIMIZE = (1L << 3),
|
|
MWM_FUNC_MAXIMIZE = (1L << 4),
|
|
MWM_FUNC_CLOSE = (1L << 5)
|
|
};
|
|
|
|
// forwards
|
|
static void x11SetFullscreen(bool fs);
|
|
static int x11EventThread(void * unused);
|
|
static void x11XInputEvent(XGenericEventCookie *cookie);
|
|
static void x11XPresentEvent(XGenericEventCookie *cookie);
|
|
static void x11GrabPointer(void);
|
|
|
|
static void x11DoPresent(uint64_t msc)
|
|
{
|
|
static bool startup = true;
|
|
if (startup)
|
|
{
|
|
XPresentPixmap(
|
|
x11.display,
|
|
x11.window,
|
|
x11.presentPixmap,
|
|
x11.presentSerial++,
|
|
x11.presentRegion, // valid
|
|
x11.presentRegion, // update
|
|
0, // x_off,
|
|
0, // y_off,
|
|
None, // target_crtc
|
|
None, // wait_fence
|
|
None, // idle_fence
|
|
PresentOptionNone, // options
|
|
0, // target_msc,
|
|
0, // divisor,
|
|
0, // remainder,
|
|
NULL, // notifies
|
|
0 // nnotifies
|
|
);
|
|
startup = false;
|
|
return;
|
|
}
|
|
|
|
static bool first = true;
|
|
static uint64_t lastMsc = 0;
|
|
|
|
uint64_t refill;
|
|
if (!first)
|
|
{
|
|
const uint64_t delta = (lastMsc >= msc) ?
|
|
lastMsc - msc :
|
|
~0ULL - msc + lastMsc;
|
|
|
|
if (delta > 50)
|
|
return;
|
|
|
|
refill = 50 - delta;
|
|
}
|
|
else
|
|
{
|
|
refill = 50;
|
|
first = false;
|
|
lastMsc = msc;
|
|
}
|
|
|
|
if (refill < 25)
|
|
return;
|
|
|
|
for(int i = 0; i < refill; ++i)
|
|
{
|
|
XPresentPixmap(
|
|
x11.display,
|
|
x11.window,
|
|
x11.presentPixmap,
|
|
x11.presentSerial++,
|
|
x11.presentRegion, // valid
|
|
x11.presentRegion, // update
|
|
0, // x_off,
|
|
0, // y_off,
|
|
None, // target_crtc
|
|
None, // wait_fence
|
|
None, // idle_fence
|
|
PresentOptionNone, // options
|
|
++lastMsc, // target_msc,
|
|
0, // divisor,
|
|
0, // remainder,
|
|
NULL, // notifies
|
|
0 // nnotifies
|
|
);
|
|
}
|
|
}
|
|
|
|
static void x11Setup(void)
|
|
{
|
|
X11WM_Default.setup();
|
|
X11WM_i3 .setup();
|
|
}
|
|
|
|
static bool x11Probe(void)
|
|
{
|
|
return getenv("DISPLAY") != NULL;
|
|
}
|
|
|
|
static bool x11EarlyInit(void)
|
|
{
|
|
XInitThreads();
|
|
return true;
|
|
}
|
|
|
|
static void x11CheckEWMHSupport(void)
|
|
{
|
|
if (x11atoms._NET_SUPPORTING_WM_CHECK == None ||
|
|
x11atoms._NET_SUPPORTED == None)
|
|
return;
|
|
|
|
Atom type;
|
|
int fmt;
|
|
unsigned long num, bytes;
|
|
unsigned char *data;
|
|
|
|
if (XGetWindowProperty(x11.display, DefaultRootWindow(x11.display),
|
|
x11atoms._NET_SUPPORTING_WM_CHECK, 0, ~0L, False, XA_WINDOW,
|
|
&type, &fmt, &num, &bytes, &data) != Success || !data)
|
|
goto out;
|
|
|
|
Window * windowFromRoot = (Window *)data;
|
|
|
|
if (XGetWindowProperty(x11.display, *windowFromRoot,
|
|
x11atoms._NET_SUPPORTING_WM_CHECK, 0, ~0L, False, XA_WINDOW,
|
|
&type, &fmt, &num, &bytes, &data) != Success || !data)
|
|
goto out_root;
|
|
|
|
Window * windowFromChild = (Window *)data;
|
|
if (*windowFromChild != *windowFromRoot)
|
|
goto out_child;
|
|
|
|
if (XGetWindowProperty(x11.display, DefaultRootWindow(x11.display),
|
|
x11atoms._NET_SUPPORTED, 0, ~0L, False, AnyPropertyType,
|
|
&type, &fmt, &num, &bytes, &data) != Success || !data)
|
|
goto out_child;
|
|
|
|
Atom * supported = (Atom *)data;
|
|
unsigned long supportedCount = num;
|
|
|
|
if (XGetWindowProperty(x11.display, *windowFromRoot,
|
|
x11atoms._NET_WM_NAME, 0, ~0L, False, AnyPropertyType,
|
|
&type, &fmt, &num, &bytes, &data) != Success || !data)
|
|
goto out_supported;
|
|
|
|
char * wmName = (char *)data;
|
|
|
|
for(unsigned long i = 0; i < supportedCount; ++i)
|
|
{
|
|
if (supported[i] == x11atoms._NET_WM_STATE_FOCUSED)
|
|
x11.ewmhHasFocusEvent = true;
|
|
}
|
|
|
|
DEBUG_INFO("EWMH-compliant window manager detected: %s", wmName);
|
|
x11.ewmhSupport = true;
|
|
|
|
if (strcmp(wmName, "i3") == 0)
|
|
x11.wm = &X11WM_i3;
|
|
|
|
XFree(wmName);
|
|
out_supported:
|
|
XFree(supported);
|
|
out_child:
|
|
XFree(windowFromChild);
|
|
out_root:
|
|
XFree(windowFromRoot);
|
|
out:
|
|
return;
|
|
}
|
|
|
|
static int x11ErrorHandler(Display * display, XErrorEvent * error)
|
|
{
|
|
char errorText[1024];
|
|
XGetErrorText(display, error->error_code, errorText, sizeof(errorText));
|
|
|
|
DEBUG_ERROR("X11 Error: %s", errorText);
|
|
DEBUG_PRINT_BACKTRACE();
|
|
return 0;
|
|
}
|
|
|
|
static int x11IOErrorHandler(Display * display)
|
|
{
|
|
DEBUG_FATAL("Fatal X11 IO Error");
|
|
return 0;
|
|
}
|
|
|
|
static bool x11Init(const LG_DSInitParams params)
|
|
{
|
|
XIDeviceInfo *devinfo;
|
|
int count;
|
|
int event, error;
|
|
|
|
XSetErrorHandler(x11ErrorHandler);
|
|
XSetIOErrorHandler(x11IOErrorHandler);
|
|
|
|
memset(&x11, 0, sizeof(x11));
|
|
x11.xValuator = -1;
|
|
x11.yValuator = -1;
|
|
x11.display = XOpenDisplay(NULL);
|
|
x11.jitRender = params.jitRender;
|
|
x11.wm = &X11WM_Default;
|
|
|
|
XSetWindowAttributes swa =
|
|
{
|
|
.event_mask =
|
|
StructureNotifyMask |
|
|
PropertyChangeMask |
|
|
ExposureMask
|
|
};
|
|
unsigned long swaMask = CWEventMask;
|
|
|
|
#ifdef ENABLE_OPENGL
|
|
if (params.opengl)
|
|
{
|
|
GLint glXAttribs[] =
|
|
{
|
|
GLX_RGBA,
|
|
GLX_DOUBLEBUFFER ,
|
|
GLX_DEPTH_SIZE , 24,
|
|
GLX_STENCIL_SIZE , 0,
|
|
GLX_RED_SIZE , 8,
|
|
GLX_GREEN_SIZE , 8,
|
|
GLX_BLUE_SIZE , 8,
|
|
GLX_DEPTH_SIZE , 0,
|
|
GLX_SAMPLE_BUFFERS, 0,
|
|
GLX_SAMPLES , 0,
|
|
None
|
|
};
|
|
|
|
x11.visual = glXChooseVisual(x11.display,
|
|
XDefaultScreen(x11.display), glXAttribs);
|
|
|
|
if (!x11.visual)
|
|
{
|
|
DEBUG_ERROR("glXChooseVisual failed");
|
|
goto fail_display;
|
|
}
|
|
|
|
swa.colormap = XCreateColormap(x11.display, XDefaultRootWindow(x11.display),
|
|
x11.visual->visual, AllocNone);
|
|
swaMask |= CWColormap;
|
|
}
|
|
#endif
|
|
|
|
x11.window = XCreateWindow(
|
|
x11.display,
|
|
XDefaultRootWindow(x11.display),
|
|
params.x, params.y,
|
|
params.w, params.h,
|
|
0,
|
|
x11.visual ? x11.visual->depth : CopyFromParent,
|
|
InputOutput,
|
|
x11.visual ? x11.visual->visual : CopyFromParent,
|
|
swaMask,
|
|
&swa);
|
|
|
|
if (!x11.window)
|
|
{
|
|
DEBUG_ERROR("XCreateWindow failed");
|
|
goto fail_display;
|
|
}
|
|
|
|
XStoreName(x11.display, x11.window, params.title);
|
|
|
|
XClassHint hint =
|
|
{
|
|
.res_name = strdup(params.title),
|
|
.res_class = strdup("looking-glass-client")
|
|
};
|
|
XSetClassHint(x11.display, x11.window, &hint);
|
|
free(hint.res_name);
|
|
free(hint.res_class);
|
|
|
|
XSizeHints *xsh = XAllocSizeHints();
|
|
if (params.center)
|
|
{
|
|
xsh->flags |= PWinGravity;
|
|
xsh->win_gravity = 5; //Center
|
|
}
|
|
|
|
if (!params.resizable)
|
|
{
|
|
xsh->flags |= PMinSize | PMaxSize;
|
|
xsh->min_width = params.w;
|
|
xsh->max_width = params.w;
|
|
xsh->min_height = params.h;
|
|
xsh->max_height = params.h;
|
|
}
|
|
|
|
XSetWMNormalHints(x11.display, x11.window, xsh);
|
|
XFree(xsh);
|
|
|
|
X11AtomsInit();
|
|
XSetWMProtocols(x11.display, x11.window, &x11atoms.WM_DELETE_WINDOW, 1);
|
|
|
|
// check for Extended Window Manager Hints support
|
|
x11CheckEWMHSupport();
|
|
|
|
if (!x11.wm->init())
|
|
{
|
|
x11.wm = &X11WM_Default;
|
|
if (!x11.wm->init())
|
|
{
|
|
DEBUG_ERROR("Failed to initialize the X11 window manager subsystem");
|
|
goto fail_window;
|
|
}
|
|
}
|
|
|
|
if (x11atoms._NET_WM_PID)
|
|
{
|
|
pid_t pid = getpid();
|
|
XChangeProperty(
|
|
x11.display,
|
|
x11.window,
|
|
x11atoms._NET_WM_PID,
|
|
XA_CARDINAL,
|
|
32,
|
|
PropModeReplace,
|
|
(unsigned char *)&pid,
|
|
1
|
|
);
|
|
}
|
|
|
|
if (params.borderless)
|
|
{
|
|
if (x11atoms._MOTIF_WM_HINTS)
|
|
{
|
|
const struct MwmHints hints =
|
|
{
|
|
.flags = MWM_HINTS_DECORATIONS,
|
|
.decorations = 0
|
|
};
|
|
|
|
XChangeProperty(
|
|
x11.display,
|
|
x11.window,
|
|
x11atoms._MOTIF_WM_HINTS,
|
|
x11atoms._MOTIF_WM_HINTS,
|
|
32,
|
|
PropModeReplace,
|
|
(unsigned char *)&hints,
|
|
5
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// fallback to making a utility window, not ideal but better then nothing
|
|
XChangeProperty(
|
|
x11.display,
|
|
x11.window,
|
|
x11atoms._NET_WM_WINDOW_TYPE,
|
|
XA_ATOM,
|
|
32,
|
|
PropModeReplace,
|
|
(unsigned char *)&x11atoms._NET_WM_WINDOW_TYPE_UTILITY,
|
|
1
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
XChangeProperty(
|
|
x11.display,
|
|
x11.window,
|
|
x11atoms._NET_WM_WINDOW_TYPE,
|
|
XA_ATOM,
|
|
32,
|
|
PropModeReplace,
|
|
(unsigned char *)&x11atoms._NET_WM_WINDOW_TYPE_NORMAL,
|
|
1
|
|
);
|
|
}
|
|
|
|
if (params.maximize)
|
|
{
|
|
Atom wmState[2] =
|
|
{
|
|
x11atoms._NET_WM_STATE_MAXIMIZED_HORZ,
|
|
x11atoms._NET_WM_STATE_MAXIMIZED_VERT
|
|
};
|
|
|
|
XChangeProperty(
|
|
x11.display,
|
|
x11.window,
|
|
x11atoms._NET_WM_STATE,
|
|
XA_ATOM,
|
|
32,
|
|
PropModeReplace,
|
|
(unsigned char *)&wmState,
|
|
2
|
|
);
|
|
}
|
|
|
|
if (x11atoms._NET_REQUEST_FRAME_EXTENTS)
|
|
{
|
|
XEvent reqevent =
|
|
{
|
|
.xclient =
|
|
{
|
|
.type = ClientMessage,
|
|
.window = x11.window,
|
|
.format = 32,
|
|
.message_type = x11atoms._NET_REQUEST_FRAME_EXTENTS
|
|
}
|
|
};
|
|
|
|
XSendEvent(x11.display, DefaultRootWindow(x11.display), False,
|
|
SubstructureNotifyMask | SubstructureRedirectMask, &reqevent);
|
|
}
|
|
|
|
int major = 2;
|
|
int minor = 0;
|
|
if (XIQueryVersion(x11.display, &major, &minor) != Success)
|
|
{
|
|
DEBUG_ERROR("Failed to query the XInput version");
|
|
goto fail_window;
|
|
}
|
|
|
|
DEBUG_INFO("X11 XInput %d.%d in use", major, minor);
|
|
|
|
devinfo = XIQueryDevice(x11.display, XIAllDevices, &count);
|
|
if (!devinfo)
|
|
{
|
|
DEBUG_ERROR("XIQueryDevice failed");
|
|
goto fail_window;
|
|
}
|
|
|
|
Atom rel_x = XInternAtom(x11.display, "Rel X", True);
|
|
Atom rel_y = XInternAtom(x11.display, "Rel Y", True);
|
|
|
|
bool havePointer = false;
|
|
bool haveKeyboard = false;
|
|
for(int i = 0; i < count; ++i)
|
|
{
|
|
/* look for the master pointing device */
|
|
if (!havePointer && devinfo[i].use == XIMasterPointer)
|
|
{
|
|
for(int j = 0; j < devinfo[i].num_classes; ++j)
|
|
{
|
|
XIAnyClassInfo *cdevinfo =
|
|
(XIAnyClassInfo *)(devinfo[i].classes[j]);
|
|
if (cdevinfo->type == XIValuatorClass)
|
|
{
|
|
XIValuatorClassInfo *vdevinfo = (XIValuatorClassInfo *)cdevinfo;
|
|
if (vdevinfo->label == rel_x || (!vdevinfo->label &&
|
|
vdevinfo->number == 0 && vdevinfo->mode == XIModeRelative))
|
|
x11.xValuator = vdevinfo->number;
|
|
else if (vdevinfo->label == rel_y || (!vdevinfo->label &&
|
|
vdevinfo->number == 1 && vdevinfo->mode == XIModeRelative))
|
|
x11.yValuator = vdevinfo->number;
|
|
}
|
|
}
|
|
|
|
if (x11.xValuator >= 0 && x11.yValuator >= 0)
|
|
{
|
|
havePointer = true;
|
|
x11.pointerDev = devinfo[i].deviceid;
|
|
}
|
|
}
|
|
|
|
/* look for the master keyboard device */
|
|
if (!haveKeyboard && devinfo[i].use == XIMasterKeyboard)
|
|
for(int j = 0; j < devinfo[i].num_classes; ++j)
|
|
{
|
|
XIAnyClassInfo *cdevinfo =
|
|
(XIAnyClassInfo *)(devinfo[i].classes[j]);
|
|
if (cdevinfo->type == XIKeyClass)
|
|
{
|
|
haveKeyboard = true;
|
|
x11.keyboardDev = devinfo[i].deviceid;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!havePointer)
|
|
{
|
|
DEBUG_ERROR("Failed to find the master pointing device");
|
|
XIFreeDeviceInfo(devinfo);
|
|
goto fail_window;
|
|
}
|
|
|
|
if (!haveKeyboard)
|
|
{
|
|
DEBUG_ERROR("Failed to find the master keyboard device");
|
|
XIFreeDeviceInfo(devinfo);
|
|
goto fail_window;
|
|
}
|
|
|
|
XDisplayKeycodes(x11.display, &x11.minKeycode, &x11.maxKeycode);
|
|
x11.keysyms = XGetKeyboardMapping(x11.display, x11.minKeycode,
|
|
x11.maxKeycode - x11.minKeycode, &x11.symsPerKeycode);
|
|
|
|
XIFreeDeviceInfo(devinfo);
|
|
|
|
XQueryExtension(x11.display, "XInputExtension", &x11.xinputOp, &event, &error);
|
|
XIEventMask eventmask;
|
|
unsigned char mask[XIMaskLen(XI_LASTEVENT)] = { 0 };
|
|
|
|
eventmask.deviceid = XIAllMasterDevices;
|
|
eventmask.mask_len = sizeof(mask);
|
|
eventmask.mask = mask;
|
|
|
|
XISetMask(mask, XI_FocusIn );
|
|
XISetMask(mask, XI_FocusOut);
|
|
|
|
XISetMask(mask, XI_Enter );
|
|
XISetMask(mask, XI_Leave );
|
|
XISetMask(mask, XI_Motion );
|
|
XISetMask(mask, XI_KeyPress );
|
|
XISetMask(mask, XI_KeyRelease );
|
|
XISetMask(mask, XI_ButtonPress );
|
|
XISetMask(mask, XI_ButtonRelease);
|
|
|
|
if (XISelectEvents(x11.display, x11.window, &eventmask, 1) != Success)
|
|
{
|
|
XFree(mask);
|
|
DEBUG_ERROR("Failed to select the xinput events");
|
|
goto fail_window;
|
|
}
|
|
|
|
unsigned long value = 1;
|
|
XChangeProperty(
|
|
x11.display,
|
|
x11.window,
|
|
x11atoms._NET_WM_BYPASS_COMPOSITOR,
|
|
XA_CARDINAL,
|
|
32,
|
|
PropModeReplace,
|
|
(unsigned char *)&value,
|
|
1
|
|
);
|
|
|
|
XChangeProperty(
|
|
x11.display,
|
|
x11.window,
|
|
x11atoms._NET_WM_ICON,
|
|
XA_CARDINAL,
|
|
32,
|
|
PropModeReplace,
|
|
(unsigned char *)icondata,
|
|
sizeof(icondata) / sizeof(icondata[0])
|
|
);
|
|
|
|
/* create the blank cursor */
|
|
{
|
|
static char data[] = { 0x00 };
|
|
XColor dummy;
|
|
Pixmap temp = XCreateBitmapFromData(x11.display, x11.window, data, 1, 1);
|
|
x11.cursors[LG_POINTER_NONE] = XCreatePixmapCursor(x11.display, temp, temp,
|
|
&dummy, &dummy, 0, 0);
|
|
XFreePixmap(x11.display, temp);
|
|
}
|
|
|
|
XcursorImages * images;
|
|
if (params.largeCursorDot)
|
|
images = x11cursor_load(b_no_input_cursor_32_xcur,
|
|
b_no_input_cursor_32_xcur_size);
|
|
else
|
|
images = x11cursor_load(b_no_input_cursor_16_xcur,
|
|
b_no_input_cursor_16_xcur_size);
|
|
|
|
x11.cursors[LG_POINTER_SQUARE] =
|
|
XcursorImagesLoadCursor(x11.display, images);
|
|
XcursorImagesDestroy(images);
|
|
|
|
/* initialize the rest of the cursors */
|
|
const char * cursorLookup[LG_POINTER_COUNT] = {
|
|
NULL , // LG_POINTER_NONE
|
|
NULL , // LG_POINTER_SQUARE
|
|
"left_ptr" , // LG_POINTER_ARROW
|
|
"text" , // LG_POINTER_INPUT
|
|
"move" , // LG_POINTER_MOVE
|
|
"ns-resize" , // LG_POINTER_RESIZE_NS
|
|
"ew-resize" , // LG_POINTER_RESIZE_EW
|
|
"nesw-resize" , // LG_POINTER_RESIZE_NESW
|
|
"nwse-resize" , // LG_POINTER_RESIZE_NWSE
|
|
"hand" , // LG_POINTER_HAND
|
|
"not-allowed" , // LG_POINTER_NOT_ALLOWED
|
|
};
|
|
|
|
const char * fallbackLookup[LG_POINTER_COUNT] = {
|
|
NULL , // LG_POINTER_NONE
|
|
NULL , // LG_POINTER_SQUARE
|
|
"left_ptr" , // LG_POINTER_ARROW
|
|
"xterm" , // LG_POINTER_INPUT
|
|
"fluer" , // LG_POINTER_MOVE
|
|
"sb_v_double_arrow", // LG_POINTER_RESIZE_NS
|
|
"sb_h_double_arrow", // LG_POINTER_RESIZE_EW
|
|
"sizing" , // LG_POINTER_RESIZE_NESW
|
|
"sizing" , // LG_POINTER_RESIZE_NWSE
|
|
"hand2" , // LG_POINTER_HAND
|
|
"X_cursor" , // LG_POINTER_NOT_ALLOWED
|
|
};
|
|
|
|
for(int i = 0; i < LG_POINTER_COUNT; ++i)
|
|
{
|
|
if (!cursorLookup[i])
|
|
continue;
|
|
x11.cursors[i] = XcursorLibraryLoadCursor(x11.display, cursorLookup[i]);
|
|
if (!x11.cursors[i])
|
|
x11.cursors[i] = XcursorLibraryLoadCursor(x11.display, fallbackLookup[i]);
|
|
}
|
|
|
|
/* default to the square cursor */
|
|
XDefineCursor(x11.display, x11.window, x11.cursors[LG_POINTER_SQUARE]);
|
|
|
|
if (x11.jitRender)
|
|
{
|
|
x11.frameEvent = lgCreateEvent(true, 0);
|
|
XPresentQueryExtension(x11.display, &x11.xpresentOp, &event, &error);
|
|
XPresentSelectInput(x11.display, x11.window, PresentCompleteNotifyMask);
|
|
x11.presentPixmap = XCreatePixmap(x11.display, x11.window, 1, 1, 24);
|
|
x11.presentRegion = XFixesCreateRegion(x11.display, &(XRectangle){0}, 1);
|
|
}
|
|
|
|
XMapWindow(x11.display, x11.window);
|
|
XFlush(x11.display);
|
|
|
|
if (!params.center)
|
|
XMoveWindow(x11.display, x11.window, params.x, params.y);
|
|
|
|
if (params.fullscreen)
|
|
x11.doFullscreenOnExpose = true;
|
|
|
|
XSetLocaleModifiers(""); // Load XMODIFIERS
|
|
x11.xim = XOpenIM(x11.display, 0, 0, 0);
|
|
|
|
if (!x11.xim)
|
|
{
|
|
// disable IME
|
|
XSetLocaleModifiers("@im=none");
|
|
x11.xim = XOpenIM(x11.display, 0, 0, 0);
|
|
}
|
|
|
|
if (x11.xim)
|
|
{
|
|
x11.xic = XCreateIC(
|
|
x11.xim,
|
|
XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
|
|
XNClientWindow, x11.window,
|
|
XNFocusWindow, x11.window,
|
|
NULL
|
|
);
|
|
}
|
|
else
|
|
DEBUG_WARN("Failed to initialize X Input Method");
|
|
|
|
if (x11.xic)
|
|
{
|
|
XSetICFocus(x11.xic);
|
|
XSelectInput(x11.display, x11.window, StructureNotifyMask | ExposureMask |
|
|
PropertyChangeMask | KeyPressMask);
|
|
}
|
|
else
|
|
DEBUG_WARN("Failed to initialize X Input Context, typing will not work");
|
|
|
|
if (!lgCreateThread("X11EventThread", x11EventThread, NULL, &x11.eventThread))
|
|
{
|
|
DEBUG_ERROR("Failed to create the x11 event thread");
|
|
goto fail_window;
|
|
}
|
|
|
|
if (x11.jitRender)
|
|
x11DoPresent(0);
|
|
|
|
return true;
|
|
|
|
fail_window:
|
|
XDestroyWindow(x11.display, x11.window);
|
|
|
|
fail_display:
|
|
XCloseDisplay(x11.display);
|
|
|
|
return false;
|
|
}
|
|
|
|
static void x11Startup(void)
|
|
{
|
|
}
|
|
|
|
static void x11Shutdown(void)
|
|
{
|
|
if (x11.jitRender)
|
|
lgSignalEvent(x11.frameEvent);
|
|
}
|
|
|
|
static void x11Free(void)
|
|
{
|
|
lgJoinThread(x11.eventThread, NULL);
|
|
|
|
if (x11.jitRender)
|
|
{
|
|
lgFreeEvent(x11.frameEvent);
|
|
XFreePixmap(x11.display, x11.presentPixmap);
|
|
XFixesDestroyRegion(x11.display, x11.presentRegion);
|
|
}
|
|
|
|
if (x11.window)
|
|
XDestroyWindow(x11.display, x11.window);
|
|
|
|
for(int i = 0; i < LG_POINTER_COUNT; ++i)
|
|
if (x11.cursors[i])
|
|
XFreeCursor(x11.display, x11.cursors[i]);
|
|
|
|
if (x11.keysyms)
|
|
XFree(x11.keysyms);
|
|
|
|
x11.wm->deinit();
|
|
XCloseDisplay(x11.display);
|
|
}
|
|
|
|
static bool x11GetProp(LG_DSProperty prop, void *ret)
|
|
{
|
|
switch (prop)
|
|
{
|
|
case LG_DS_WARP_SUPPORT:
|
|
*(enum LG_DSWarpSupport*)ret = LG_DS_WARP_SCREEN;
|
|
return true;
|
|
|
|
case LG_DS_MAX_MULTISAMPLE:
|
|
{
|
|
Display * dpy = XOpenDisplay(NULL);
|
|
if (!dpy)
|
|
return false;
|
|
|
|
XVisualInfo queryTemplate;
|
|
queryTemplate.screen = 0;
|
|
|
|
int visualCount;
|
|
int maxSamples = -1;
|
|
XVisualInfo * visuals = XGetVisualInfo(dpy, VisualScreenMask,
|
|
&queryTemplate, &visualCount);
|
|
|
|
for (int i = 0; i < visualCount; i++)
|
|
{
|
|
XVisualInfo * visual = &visuals[i];
|
|
|
|
int res, supportsGL;
|
|
// Some GLX visuals do not use GL, and these must be ignored in our search.
|
|
if ((res = glXGetConfig(dpy, visual, GLX_USE_GL, &supportsGL)) != 0 ||
|
|
!supportsGL)
|
|
continue;
|
|
|
|
int sampleBuffers, samples;
|
|
if ((res = glXGetConfig(dpy, visual, GLX_SAMPLE_BUFFERS, &sampleBuffers)) != 0)
|
|
continue;
|
|
|
|
// Will be 1 if this visual supports multisampling
|
|
if (sampleBuffers != 1)
|
|
continue;
|
|
|
|
if ((res = glXGetConfig(dpy, visual, GLX_SAMPLES, &samples)) != 0)
|
|
continue;
|
|
|
|
// Track the largest number of samples supported
|
|
if (samples > maxSamples)
|
|
maxSamples = samples;
|
|
}
|
|
|
|
XFree(visuals);
|
|
XCloseDisplay(dpy);
|
|
|
|
*(int*)ret = maxSamples;
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static int x11EventThread(void * unused)
|
|
{
|
|
int epollfd = epoll_create1(0);
|
|
if (epollfd == -1)
|
|
{
|
|
DEBUG_ERROR("epolld_create1 failure");
|
|
return 0;
|
|
}
|
|
|
|
struct epoll_event ev = { .events = EPOLLIN };
|
|
const int fd = ConnectionNumber(x11.display);
|
|
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1)
|
|
{
|
|
close(epollfd);
|
|
DEBUG_ERROR("epoll_ctl failed");
|
|
return 0;
|
|
}
|
|
|
|
while(app_isRunning())
|
|
{
|
|
const uint64_t lastWMEvent = atomic_load(&x11.lastWMEvent);
|
|
if (x11.invalidateAll && microtime() - lastWMEvent > 100000UL)
|
|
{
|
|
x11.invalidateAll = false;
|
|
app_invalidateWindow(true);
|
|
}
|
|
|
|
if (!XPending(x11.display))
|
|
{
|
|
struct epoll_event events[1];
|
|
int nfds = epoll_wait(epollfd, events, 1, 100);
|
|
if (nfds == -1)
|
|
{
|
|
if (errno == EINTR)
|
|
continue;
|
|
|
|
close(epollfd);
|
|
DEBUG_ERROR("epoll_wait failure");
|
|
return 0;
|
|
}
|
|
|
|
if (nfds == 0 || !XPending(x11.display))
|
|
continue;
|
|
}
|
|
|
|
XEvent xe;
|
|
XNextEvent(x11.display, &xe);
|
|
|
|
// call the clipboard handling code
|
|
if (x11CBEventThread(&xe))
|
|
continue;
|
|
|
|
switch(xe.type)
|
|
{
|
|
case ClientMessage:
|
|
if (xe.xclient.data.l[0] == x11atoms.WM_DELETE_WINDOW)
|
|
app_handleCloseEvent();
|
|
continue;
|
|
|
|
case ConfigureNotify:
|
|
{
|
|
atomic_store(&x11.lastWMEvent, microtime());
|
|
|
|
int x, y;
|
|
|
|
/* the window may have been re-parented so we need to translate to
|
|
* ensure we get the screen top left position of the window */
|
|
Window child;
|
|
XTranslateCoordinates(
|
|
x11.display,
|
|
x11.window,
|
|
DefaultRootWindow(x11.display),
|
|
0, 0,
|
|
&x,
|
|
&y,
|
|
&child);
|
|
|
|
x11.rect.x = x;
|
|
x11.rect.y = y;
|
|
x11.rect.w = xe.xconfigure.width;
|
|
x11.rect.h = xe.xconfigure.height;
|
|
|
|
app_updateWindowPos(x, y);
|
|
|
|
if (x11.fullscreen)
|
|
{
|
|
struct Border border = {0};
|
|
app_handleResizeEvent(x11.rect.w, x11.rect.h, 1, border);
|
|
}
|
|
else
|
|
app_handleResizeEvent(x11.rect.w, x11.rect.h, 1, x11.border);
|
|
break;
|
|
}
|
|
|
|
case Expose:
|
|
{
|
|
atomic_store(&x11.lastWMEvent, microtime());
|
|
x11.invalidateAll = true;
|
|
if (x11.doFullscreenOnExpose)
|
|
{
|
|
x11SetFullscreen(true);
|
|
x11.doFullscreenOnExpose = false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case GenericEvent:
|
|
{
|
|
XGenericEventCookie *cookie = (XGenericEventCookie*)&xe.xcookie;
|
|
if (cookie->extension == x11.xinputOp)
|
|
{
|
|
XGetEventData(x11.display, cookie);
|
|
x11XInputEvent(cookie);
|
|
XFreeEventData(x11.display, cookie);
|
|
}
|
|
else if (cookie->extension == x11.xpresentOp)
|
|
{
|
|
XGetEventData(x11.display, cookie);
|
|
x11XPresentEvent(cookie);
|
|
XFreeEventData(x11.display, cookie);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PropertyNotify:
|
|
|
|
// ignore property events that are not for us
|
|
if (xe.xproperty.display != x11.display ||
|
|
xe.xproperty.window != x11.window ||
|
|
xe.xproperty.state != PropertyNewValue)
|
|
continue;
|
|
|
|
if (xe.xproperty.atom == x11atoms._NET_WM_STATE)
|
|
{
|
|
atomic_store(&x11.lastWMEvent, microtime());
|
|
|
|
Atom type;
|
|
int fmt;
|
|
unsigned long num, bytes;
|
|
unsigned char *data;
|
|
|
|
if (XGetWindowProperty(x11.display, x11.window,
|
|
x11atoms._NET_WM_STATE, 0, ~0L, False, AnyPropertyType,
|
|
&type, &fmt, &num, &bytes, &data) != Success)
|
|
break;
|
|
|
|
bool fullscreen = false;
|
|
bool focused = false;
|
|
for(unsigned long i = 0; i < num; ++i)
|
|
{
|
|
Atom prop = ((Atom *)data)[i];
|
|
if (prop == x11atoms._NET_WM_STATE_FULLSCREEN)
|
|
fullscreen = true;
|
|
else if (prop == x11atoms._NET_WM_STATE_FOCUSED)
|
|
focused = true;
|
|
}
|
|
|
|
if (x11.ewmhHasFocusEvent && x11.focused != focused)
|
|
{
|
|
x11.focused = focused;
|
|
app_handleFocusEvent(focused);
|
|
}
|
|
|
|
x11.fullscreen = fullscreen;
|
|
XFree(data);
|
|
break;
|
|
}
|
|
|
|
if (xe.xproperty.atom == x11atoms._NET_FRAME_EXTENTS)
|
|
{
|
|
atomic_store(&x11.lastWMEvent, microtime());
|
|
|
|
Atom type;
|
|
int fmt;
|
|
unsigned long num, bytes;
|
|
unsigned char *data;
|
|
|
|
if (XGetWindowProperty(x11.display, x11.window,
|
|
x11atoms._NET_FRAME_EXTENTS, 0, 4, False, AnyPropertyType,
|
|
&type, &fmt, &num, &bytes, &data) != Success)
|
|
break;
|
|
|
|
if (num >= 4)
|
|
{
|
|
long *cardinal = (long *)data;
|
|
x11.border.left = cardinal[0];
|
|
x11.border.right = cardinal[1];
|
|
x11.border.top = cardinal[2];
|
|
x11.border.bottom = cardinal[3];
|
|
app_handleResizeEvent(x11.rect.w, x11.rect.h, 1, x11.border);
|
|
}
|
|
|
|
XFree(data);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
close(epollfd);
|
|
return 0;
|
|
}
|
|
|
|
static enum Modifiers keySymToModifier(KeySym sym)
|
|
{
|
|
switch (sym)
|
|
{
|
|
case XK_Control_L: return MOD_CTRL_LEFT;
|
|
case XK_Control_R: return MOD_CTRL_RIGHT;
|
|
case XK_Shift_L: return MOD_SHIFT_LEFT;
|
|
case XK_Shift_R: return MOD_SHIFT_RIGHT;
|
|
case XK_Alt_L: return MOD_ALT_LEFT;
|
|
case XK_Alt_R: return MOD_ALT_RIGHT;
|
|
case XK_Super_L: return MOD_SUPER_LEFT;
|
|
case XK_Super_R: return MOD_SUPER_RIGHT;
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
static void updateModifiers(void)
|
|
{
|
|
app_handleKeyboardModifiers(
|
|
x11.modifiers[MOD_CTRL_LEFT] || x11.modifiers[MOD_CTRL_RIGHT],
|
|
x11.modifiers[MOD_SHIFT_LEFT] || x11.modifiers[MOD_SHIFT_RIGHT],
|
|
x11.modifiers[MOD_ALT_LEFT] || x11.modifiers[MOD_ALT_RIGHT],
|
|
x11.modifiers[MOD_SUPER_LEFT] || x11.modifiers[MOD_SUPER_RIGHT]
|
|
);
|
|
}
|
|
|
|
static void setFocus(bool focused, double x, double y)
|
|
{
|
|
if (x11.focused == focused)
|
|
return;
|
|
|
|
x11.focused = focused;
|
|
app_updateCursorPos(x, y);
|
|
app_handleFocusEvent(focused);
|
|
}
|
|
|
|
static int getCharcode(int detail)
|
|
{
|
|
if (detail < x11.minKeycode || detail > x11.maxKeycode)
|
|
return 0;
|
|
|
|
KeySym sym = x11.keysyms[(detail - x11.minKeycode) *
|
|
x11.symsPerKeycode];
|
|
sym = xkb_keysym_to_upper(sym);
|
|
return xkb_keysym_to_utf32(sym);
|
|
}
|
|
|
|
static void x11XInputEvent(XGenericEventCookie *cookie)
|
|
{
|
|
static int button_state = 0;
|
|
|
|
switch(cookie->evtype)
|
|
{
|
|
case XI_FocusIn:
|
|
{
|
|
XIFocusOutEvent *xie = cookie->data;
|
|
if (x11.ewmhHasFocusEvent)
|
|
{
|
|
// if meta ungrab for move/resize
|
|
if (xie->mode == XINotifyUngrab)
|
|
setFocus(true, xie->event_x, xie->event_y);
|
|
return;
|
|
}
|
|
|
|
atomic_store(&x11.lastWMEvent, microtime());
|
|
if (x11.focused)
|
|
return;
|
|
|
|
if (xie->mode != XINotifyNormal &&
|
|
xie->mode != XINotifyWhileGrabbed &&
|
|
xie->mode != XINotifyUngrab)
|
|
return;
|
|
|
|
|
|
setFocus(true, xie->event_x, xie->event_y);
|
|
return;
|
|
}
|
|
|
|
case XI_FocusOut:
|
|
{
|
|
XIFocusOutEvent *xie = cookie->data;
|
|
if (x11.ewmhHasFocusEvent)
|
|
{
|
|
// if meta grab for move/resize
|
|
if (xie->mode == XINotifyGrab)
|
|
setFocus(false, xie->event_x, xie->event_y);
|
|
return;
|
|
}
|
|
|
|
atomic_store(&x11.lastWMEvent, microtime());
|
|
if (!x11.focused)
|
|
return;
|
|
|
|
if (xie->mode != XINotifyNormal &&
|
|
xie->mode != XINotifyWhileGrabbed &&
|
|
xie->mode != XINotifyGrab)
|
|
return;
|
|
|
|
setFocus(false, xie->event_x, xie->event_y);
|
|
return;
|
|
}
|
|
|
|
case XI_Enter:
|
|
{
|
|
atomic_store(&x11.lastWMEvent, microtime());
|
|
XIEnterEvent *xie = cookie->data;
|
|
if (x11.entered || xie->event != x11.window ||
|
|
xie->mode != XINotifyNormal)
|
|
return;
|
|
|
|
app_updateCursorPos(xie->event_x, xie->event_y);
|
|
app_handleEnterEvent(true);
|
|
x11.entered = true;
|
|
return;
|
|
}
|
|
|
|
case XI_Leave:
|
|
{
|
|
atomic_store(&x11.lastWMEvent, microtime());
|
|
XILeaveEvent *xie = cookie->data;
|
|
|
|
if (!x11.entered || xie->event != x11.window ||
|
|
button_state != 0 || app_isCaptureMode() ||
|
|
xie->mode == NotifyGrab)
|
|
return;
|
|
|
|
app_updateCursorPos(xie->event_x, xie->event_y);
|
|
app_handleEnterEvent(false);
|
|
x11.entered = false;
|
|
|
|
/**
|
|
* Because there is a race with the pointer ungrab the enter event for the
|
|
* next window is sometimes sent with the mode NotifyUngrab, unfortunatly
|
|
* some window managers such as i3 will ignore these which breaks focus
|
|
* follows mouse mode. To correct this we generate and send a normal
|
|
* EnterNotify event.
|
|
*/
|
|
int root_x, root_y, win_x, win_y;
|
|
Window root_win, child_win;
|
|
unsigned int mask;
|
|
XQueryPointer(x11.display, DefaultRootWindow(x11.display), &root_win,
|
|
&child_win, &root_x, &root_y, &win_x, &win_y, &mask);
|
|
|
|
int target_x, target_y;
|
|
Window target_root;
|
|
XTranslateCoordinates(x11.display, DefaultRootWindow(x11.display),
|
|
child_win, root_x, root_y, &target_x, &target_y, &target_root);
|
|
|
|
XEvent event;
|
|
memset(&event, 0, sizeof(event));
|
|
event.type = EnterNotify;
|
|
event.xcrossing.serial = 0;
|
|
event.xcrossing.send_event = True;
|
|
event.xcrossing.display = x11.display;
|
|
event.xcrossing.window = child_win;
|
|
event.xcrossing.root = root_win;
|
|
event.xcrossing.subwindow = child_win;
|
|
event.xcrossing.time = CurrentTime;
|
|
event.xcrossing.mode = NotifyNormal;
|
|
event.xcrossing.detail = NotifyNonlinear;
|
|
event.xcrossing.same_screen = True;
|
|
event.xcrossing.focus = False;
|
|
event.xcrossing.x = target_x;
|
|
event.xcrossing.y = target_y;
|
|
event.xcrossing.x_root = root_x;
|
|
event.xcrossing.y_root = root_y;
|
|
|
|
XSendEvent(x11.display, child_win, True, EnterWindowMask, &event);
|
|
XFlush(x11.display);
|
|
return;
|
|
}
|
|
|
|
case XI_KeyPress:
|
|
{
|
|
if (!x11.focused || x11.keyboardGrabbed)
|
|
return;
|
|
|
|
XIDeviceEvent *device = cookie->data;
|
|
app_handleKeyPress(device->detail - x11.minKeycode,
|
|
getCharcode(device->detail));
|
|
|
|
if (!x11.xic || !app_isOverlayMode())
|
|
return;
|
|
|
|
char buffer[128];
|
|
KeySym sym;
|
|
Status status;
|
|
int count;
|
|
XKeyPressedEvent ev = {
|
|
.display = x11.display,
|
|
.window = x11.window,
|
|
.type = KeyPress,
|
|
.keycode = device->detail,
|
|
.state = device->mods.effective,
|
|
};
|
|
|
|
count = Xutf8LookupString(x11.xic, &ev, buffer, sizeof(buffer),
|
|
&sym, &status);
|
|
|
|
if (status == XBufferOverflow || count >= sizeof(buffer))
|
|
{
|
|
DEBUG_WARN("Typing too many characters at once, ignoring");
|
|
return;
|
|
}
|
|
|
|
if (status == XLookupChars || status == XLookupBoth)
|
|
{
|
|
buffer[count] = '\0';
|
|
app_handleKeyboardTyped(buffer);
|
|
}
|
|
|
|
if (status == XLookupKeySym || status == XLookupBoth)
|
|
{
|
|
int modifier = keySymToModifier(sym);
|
|
if (modifier >= 0)
|
|
{
|
|
x11.modifiers[modifier] = true;
|
|
updateModifiers();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
case XI_KeyRelease:
|
|
{
|
|
if (!x11.focused || x11.keyboardGrabbed)
|
|
return;
|
|
|
|
XIDeviceEvent *device = cookie->data;
|
|
app_handleKeyRelease(device->detail - x11.minKeycode,
|
|
getCharcode(device->detail));
|
|
|
|
if (!x11.xic || !app_isOverlayMode())
|
|
return;
|
|
|
|
XKeyPressedEvent ev = {
|
|
.display = x11.display,
|
|
.window = x11.window,
|
|
.type = KeyRelease,
|
|
.keycode = device->detail,
|
|
.state = device->mods.effective,
|
|
};
|
|
KeySym sym = XLookupKeysym(&ev, 0);
|
|
int modifier = keySymToModifier(sym);
|
|
|
|
if (modifier >= 0)
|
|
{
|
|
x11.modifiers[modifier] = false;
|
|
updateModifiers();
|
|
}
|
|
return;
|
|
}
|
|
|
|
case XI_RawKeyPress:
|
|
{
|
|
if (!x11.focused)
|
|
return;
|
|
|
|
XIRawEvent *raw = cookie->data;
|
|
app_handleKeyPress(raw->detail - x11.minKeycode,
|
|
getCharcode(raw->detail));
|
|
return;
|
|
}
|
|
|
|
case XI_RawKeyRelease:
|
|
{
|
|
if (!x11.focused)
|
|
return;
|
|
|
|
XIRawEvent *raw = cookie->data;
|
|
app_handleKeyRelease(raw->detail - x11.minKeycode,
|
|
getCharcode(raw->detail));
|
|
return;
|
|
}
|
|
|
|
case XI_ButtonPress:
|
|
{
|
|
if (!x11.focused || !x11.entered)
|
|
return;
|
|
|
|
XIDeviceEvent *device = cookie->data;
|
|
if (device->detail == 4)
|
|
app_handleWheelMotion(-0.5);
|
|
else if (device->detail == 5)
|
|
app_handleWheelMotion(0.5);
|
|
else
|
|
app_handleButtonPress(
|
|
device->detail > 5 ? device->detail - 2 : device->detail);
|
|
|
|
return;
|
|
}
|
|
|
|
case XI_ButtonRelease:
|
|
{
|
|
if (!x11.focused || !x11.entered)
|
|
return;
|
|
|
|
XIDeviceEvent *device = cookie->data;
|
|
app_handleButtonRelease(device->detail);
|
|
return;
|
|
}
|
|
|
|
case XI_RawButtonPress:
|
|
{
|
|
if (!x11.focused || !x11.entered)
|
|
return;
|
|
|
|
XIRawEvent *raw = cookie->data;
|
|
|
|
/* filter out duplicate events */
|
|
static Time prev_time = 0;
|
|
static unsigned int prev_detail = 0;
|
|
if (raw->time == prev_time && raw->detail == prev_detail)
|
|
return;
|
|
|
|
prev_time = raw->time;
|
|
prev_detail = raw->detail;
|
|
button_state |= (1 << raw->detail);
|
|
|
|
app_handleButtonPress(
|
|
raw->detail > 5 ? raw->detail - 2 : raw->detail);
|
|
return;
|
|
}
|
|
|
|
case XI_RawButtonRelease:
|
|
{
|
|
if (!x11.focused || !x11.entered)
|
|
return;
|
|
|
|
XIRawEvent *raw = cookie->data;
|
|
|
|
/* filter out duplicate events */
|
|
static Time prev_time = 0;
|
|
static unsigned int prev_detail = 0;
|
|
if (raw->time == prev_time && raw->detail == prev_detail)
|
|
return;
|
|
|
|
prev_time = raw->time;
|
|
prev_detail = raw->detail;
|
|
button_state &= ~(1 << raw->detail);
|
|
|
|
app_handleButtonRelease(
|
|
raw->detail > 5 ? raw->detail - 2 : raw->detail);
|
|
return;
|
|
}
|
|
|
|
case XI_Motion:
|
|
{
|
|
XIDeviceEvent *device = cookie->data;
|
|
app_updateCursorPos(device->event_x, device->event_y);
|
|
|
|
if (!x11.pointerGrabbed)
|
|
app_handleMouseRelative(0.0, 0.0, 0.0, 0.0);
|
|
return;
|
|
}
|
|
|
|
case XI_RawMotion:
|
|
{
|
|
if (!x11.focused || !x11.entered)
|
|
return;
|
|
|
|
XIRawEvent *raw = cookie->data;
|
|
double raw_axis[2] = { 0 };
|
|
double axis[2] = { 0 };
|
|
|
|
/* select the active validators for the X & Y axis */
|
|
double *valuator = raw->valuators.values;
|
|
double *r_value = raw->raw_values;
|
|
bool has_axes = false;
|
|
for(int i = 0; i < raw->valuators.mask_len * 8; ++i)
|
|
{
|
|
if (XIMaskIsSet(raw->valuators.mask, i))
|
|
{
|
|
if (i == x11.xValuator)
|
|
{
|
|
raw_axis[0] = *r_value;
|
|
axis [0] = *valuator;
|
|
has_axes = true;
|
|
}
|
|
else if (i == x11.yValuator)
|
|
{
|
|
raw_axis[1] = *r_value;
|
|
axis [1] = *valuator;
|
|
has_axes = true;
|
|
}
|
|
|
|
++valuator;
|
|
++r_value;
|
|
}
|
|
}
|
|
|
|
/* filter out events with no axis data */
|
|
if (!has_axes)
|
|
return;
|
|
|
|
/* filter out duplicate events */
|
|
static Time prev_time = 0;
|
|
static double prev_axis[2] = {0};
|
|
if (raw->time == prev_time &&
|
|
axis[0] == prev_axis[0] &&
|
|
axis[1] == prev_axis[1])
|
|
return;
|
|
|
|
prev_time = raw->time;
|
|
prev_axis[0] = axis[0];
|
|
prev_axis[1] = axis[1];
|
|
|
|
app_handleMouseRelative(axis[0], axis[1], raw_axis[0], raw_axis[1]);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#include <math.h>
|
|
|
|
static void x11XPresentEvent(XGenericEventCookie *cookie)
|
|
{
|
|
switch(cookie->evtype)
|
|
{
|
|
case PresentCompleteNotify:
|
|
{
|
|
XPresentCompleteNotifyEvent * e = cookie->data;
|
|
x11DoPresent(e->msc);
|
|
atomic_store(&x11.presentMsc, e->msc);
|
|
atomic_store(&x11.presentUst, e->ust);
|
|
lgSignalEvent(x11.frameEvent);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef ENABLE_EGL
|
|
static EGLDisplay x11GetEGLDisplay(void)
|
|
{
|
|
EGLDisplay ret;
|
|
const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS);
|
|
|
|
if (util_hasGLExt(early_exts, "EGL_KHR_platform_base") &&
|
|
g_egl_dynProcs.eglGetPlatformDisplay)
|
|
{
|
|
DEBUG_INFO("Using eglGetPlatformDisplay");
|
|
ret = g_egl_dynProcs.eglGetPlatformDisplay(EGL_PLATFORM_X11_KHR,
|
|
x11.display, NULL);
|
|
}
|
|
else if (util_hasGLExt(early_exts, "EGL_EXT_platform_base") &&
|
|
g_egl_dynProcs.eglGetPlatformDisplayEXT)
|
|
{
|
|
DEBUG_INFO("Using eglGetPlatformDisplayEXT");
|
|
ret = g_egl_dynProcs.eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_KHR,
|
|
x11.display, NULL);
|
|
}
|
|
else
|
|
{
|
|
DEBUG_INFO("Using eglGetDisplay");
|
|
ret = eglGetDisplay(x11.display);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static EGLNativeWindowType x11GetEGLNativeWindow(void)
|
|
{
|
|
return (EGLNativeWindowType)x11.window;
|
|
}
|
|
|
|
static void x11EGLSwapBuffers(EGLDisplay display, EGLSurface surface,
|
|
const struct Rect * damage, int count)
|
|
{
|
|
static struct SwapWithDamageData data = {0};
|
|
if (!data.init)
|
|
swapWithDamageInit(&data, display);
|
|
|
|
swapWithDamage(&data, display, surface, damage, count);
|
|
}
|
|
#endif
|
|
|
|
#ifdef ENABLE_OPENGL
|
|
static LG_DSGLContext x11GLCreateContext(void)
|
|
{
|
|
return (LG_DSGLContext)
|
|
glXCreateContext(x11.display, x11.visual, NULL, GL_TRUE);
|
|
}
|
|
|
|
static void x11GLDeleteContext(LG_DSGLContext context)
|
|
{
|
|
glXDestroyContext(x11.display, (GLXContext)context);
|
|
}
|
|
|
|
static void x11GLMakeCurrent(LG_DSGLContext context)
|
|
{
|
|
glXMakeCurrent(x11.display, x11.window, (GLXContext)context);
|
|
}
|
|
|
|
static void x11GLSetSwapInterval(int interval)
|
|
{
|
|
static PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = NULL;
|
|
if (!glXSwapIntervalEXT)
|
|
{
|
|
glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) glXGetProcAddressARB(
|
|
(const GLubyte *) "glXSwapIntervalEXT");
|
|
if (!glXSwapIntervalEXT)
|
|
DEBUG_FATAL("Failed to load glXSwapIntervalEXT");
|
|
}
|
|
glXSwapIntervalEXT(x11.display, x11.window, interval);
|
|
}
|
|
|
|
static void x11GLSwapBuffers(void)
|
|
{
|
|
glXSwapBuffers(x11.display, x11.window);
|
|
}
|
|
#endif
|
|
|
|
static bool x11WaitFrame(void)
|
|
{
|
|
/* wait until we are woken up by the present event */
|
|
lgWaitEvent(x11.frameEvent, TIMEOUT_INFINITE);
|
|
|
|
#define WARMUP_TIME 3000000 //2s
|
|
#define CALIBRATION_COUNT 400
|
|
const uint64_t ust = atomic_load(&x11.presentUst);
|
|
const uint64_t msc = atomic_load(&x11.presentMsc);
|
|
|
|
static bool warmup = true;
|
|
if (warmup)
|
|
{
|
|
static uint64_t expire = 0;
|
|
if (!expire)
|
|
{
|
|
DEBUG_INFO("Warming up...");
|
|
expire = ust + WARMUP_TIME;
|
|
}
|
|
|
|
if (ust < expire)
|
|
return false;
|
|
|
|
warmup = false;
|
|
DEBUG_INFO("Warmup done, doing calibration...");
|
|
}
|
|
|
|
/* calibrate a delay to push our presentation time forward as far as
|
|
* possible without skipping frames */
|
|
static int calibrate = 0;
|
|
static uint64_t lastts = 0;
|
|
static uint64_t lastmsc = 0;
|
|
static uint64_t delay = 0;
|
|
|
|
uint64_t deltamsc = 0;
|
|
if (lastts)
|
|
{
|
|
uint64_t deltats = ust - lastts;
|
|
deltamsc = msc - lastmsc;
|
|
|
|
if (calibrate == 0)
|
|
{
|
|
if (!delay)
|
|
delay = deltats / 2;
|
|
else
|
|
{
|
|
/* increase the delay until we see a skip */
|
|
if (deltamsc < 2)
|
|
delay += 100;
|
|
else
|
|
{
|
|
delay -= 100;
|
|
++calibrate;
|
|
deltamsc = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (calibrate < CALIBRATION_COUNT)
|
|
{
|
|
/* every skip we back off the delay */
|
|
if (deltamsc > 1)
|
|
{
|
|
/* prevent underflow */
|
|
if (delay - 100 < delay)
|
|
{
|
|
delay -= 100;
|
|
calibrate = 1;
|
|
deltamsc = 0;
|
|
}
|
|
else
|
|
{
|
|
/* if underflow, we are simply too slow, no delay */
|
|
delay = 0;
|
|
calibrate = CALIBRATION_COUNT;
|
|
}
|
|
}
|
|
|
|
/* if we have finished, print out the delay */
|
|
if (++calibrate == CALIBRATION_COUNT)
|
|
{
|
|
/* delays shorter then 1ms are unmaintainable */
|
|
if (delay < 1000)
|
|
{
|
|
delay = 0;
|
|
DEBUG_INFO("Calibration done, no delay required");
|
|
}
|
|
else
|
|
DEBUG_INFO("Calibration done, delay = %lu us", delay);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
lastts = ust;
|
|
lastmsc = msc;
|
|
|
|
/* adjustments if we are still seeing odd skips */
|
|
const uint64_t lastWMEvent = atomic_load(&x11.lastWMEvent);
|
|
const uint64_t eventDelta = ust > lastWMEvent ? ust - lastWMEvent : 0;
|
|
|
|
static int skipCount = 0;
|
|
static uint64_t lastSkipTime = 0;
|
|
|
|
if (skipCount > 0 && ust - lastSkipTime > 1000000UL)
|
|
skipCount = 0;
|
|
|
|
if (delay > 0 && deltamsc > 1 && eventDelta > 1000000UL)
|
|
{
|
|
/* only adjust if the last skip was less then 1s ago */
|
|
const bool flag = ust - lastSkipTime < 1000000UL;
|
|
lastSkipTime = ust;
|
|
|
|
if (flag && ++skipCount > 10)
|
|
{
|
|
if (delay - 500 < delay)
|
|
{
|
|
delay -= 500;
|
|
DEBUG_INFO("Excessing skipping detected, reduced calibration delay to %lu us", delay);
|
|
skipCount = 0;
|
|
}
|
|
else
|
|
{
|
|
delay = 0;
|
|
DEBUG_WARN("Excessive skipping detected, calibration delay disabled");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (delay)
|
|
{
|
|
struct timespec ts = { .tv_nsec = delay * 1000 };
|
|
while(nanosleep(&ts, &ts)) {};
|
|
}
|
|
|
|
/* force rendering until we have finished calibration so we can take into
|
|
* account how long it takes for the scene to render */
|
|
if (calibrate < CALIBRATION_COUNT)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static void x11StopWaitFrame(void)
|
|
{
|
|
lgSignalEvent(x11.frameEvent);
|
|
}
|
|
|
|
static void x11GuestPointerUpdated(double x, double y, double localX, double localY)
|
|
{
|
|
if (app_isCaptureMode() || !x11.entered)
|
|
return;
|
|
|
|
// avoid running too often
|
|
static uint64_t last_warp = 0;
|
|
uint64_t now = microtime();
|
|
if (now - last_warp < 10000)
|
|
return;
|
|
last_warp = now;
|
|
|
|
XIWarpPointer(
|
|
x11.display,
|
|
x11.pointerDev,
|
|
None,
|
|
x11.window,
|
|
0, 0, 0, 0,
|
|
localX, localY);
|
|
|
|
XSync(x11.display, False);
|
|
}
|
|
|
|
static void x11SetPointer(LG_DSPointer pointer)
|
|
{
|
|
XDefineCursor(x11.display, x11.window, x11.cursors[pointer]);
|
|
}
|
|
|
|
static void x11PrintGrabError(const char * type, int dev, Status ret)
|
|
{
|
|
const char * errStr;
|
|
switch(ret)
|
|
{
|
|
case AlreadyGrabbed : errStr = "AlreadyGrabbed" ; break;
|
|
case GrabNotViewable: errStr = "GrabNotViewable"; break;
|
|
case GrabFrozen : errStr = "GrabFrozen" ; break;
|
|
case GrabInvalidTime: errStr = "GrabInvalidTime"; break;
|
|
default:
|
|
errStr = "Unknown";
|
|
break;
|
|
}
|
|
|
|
DEBUG_ERROR("XIGrabDevice failed for %s dev %d with 0x%x (%s)",
|
|
type, dev, ret, errStr);
|
|
|
|
}
|
|
|
|
static void x11GrabPointer(void)
|
|
{
|
|
if (x11.pointerGrabbed)
|
|
return;
|
|
|
|
unsigned char mask_bits[XIMaskLen(XI_LASTEVENT)] = { 0 };
|
|
XIEventMask mask = {
|
|
x11.pointerDev,
|
|
sizeof(mask_bits),
|
|
mask_bits
|
|
};
|
|
|
|
XISetMask(mask.mask, XI_RawButtonPress );
|
|
XISetMask(mask.mask, XI_RawButtonRelease);
|
|
XISetMask(mask.mask, XI_RawMotion );
|
|
XISetMask(mask.mask, XI_Motion );
|
|
XISetMask(mask.mask, XI_Enter );
|
|
XISetMask(mask.mask, XI_Leave );
|
|
|
|
Status ret;
|
|
for(int retry = 0; retry < 10; ++retry)
|
|
{
|
|
ret = XIGrabDevice(
|
|
x11.display,
|
|
x11.pointerDev,
|
|
x11.window,
|
|
CurrentTime,
|
|
None,
|
|
XIGrabModeAsync,
|
|
XIGrabModeAsync,
|
|
XINoOwnerEvents,
|
|
&mask);
|
|
|
|
// on some WMs (i3) for an unknown reason the first grab attempt when
|
|
// switching to a desktop that has LG on it fails with GrabFrozen, however
|
|
// adding as short delay seems to resolve the issue.
|
|
if (ret == GrabFrozen && retry < 9)
|
|
{
|
|
usleep(100000);
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (ret != Success)
|
|
{
|
|
x11PrintGrabError("pointer", x11.pointerDev, ret);
|
|
return;
|
|
}
|
|
|
|
x11.pointerGrabbed = true;
|
|
}
|
|
|
|
static void x11UngrabPointer(void)
|
|
{
|
|
if (!x11.pointerGrabbed)
|
|
return;
|
|
|
|
XIUngrabDevice(x11.display, x11.pointerDev, CurrentTime);
|
|
XSync(x11.display, False);
|
|
|
|
x11.pointerGrabbed = false;
|
|
}
|
|
|
|
static void x11CapturePointer(void)
|
|
{
|
|
x11GrabPointer();
|
|
}
|
|
|
|
static void x11UncapturePointer(void)
|
|
{
|
|
/* we need to ungrab the pointer on the following conditions when exiting capture mode:
|
|
* - if the format is invalid as we do not know where the guest cursor is,
|
|
* which breaks edge detection as the cursor can not be warped out of the
|
|
* window when we release it.
|
|
* - if the user has opted to use captureInputOnly mode.
|
|
*/
|
|
if (!app_isFormatValid() || app_isCaptureOnlyMode())
|
|
x11UngrabPointer();
|
|
}
|
|
|
|
static void x11GrabKeyboard(void)
|
|
{
|
|
if (x11.keyboardGrabbed)
|
|
return;
|
|
|
|
unsigned char mask_bits[XIMaskLen (XI_LASTEVENT)] = { 0 };
|
|
XIEventMask mask = {
|
|
x11.keyboardDev,
|
|
sizeof(mask_bits),
|
|
mask_bits
|
|
};
|
|
|
|
XISetMask(mask.mask, XI_RawKeyPress );
|
|
XISetMask(mask.mask, XI_RawKeyRelease);
|
|
|
|
Status ret = XIGrabDevice(
|
|
x11.display,
|
|
x11.keyboardDev,
|
|
x11.window,
|
|
CurrentTime,
|
|
None,
|
|
XIGrabModeAsync,
|
|
XIGrabModeAsync,
|
|
XINoOwnerEvents,
|
|
&mask);
|
|
|
|
if (ret != Success)
|
|
{
|
|
x11PrintGrabError("keyboard", x11.keyboardDev, ret);
|
|
return;
|
|
}
|
|
|
|
x11.keyboardGrabbed = true;
|
|
}
|
|
|
|
static void x11UngrabKeyboard(void)
|
|
{
|
|
if (!x11.keyboardGrabbed)
|
|
return;
|
|
|
|
XIUngrabDevice(x11.display, x11.keyboardDev, CurrentTime);
|
|
XSync(x11.display, False);
|
|
|
|
x11.keyboardGrabbed = false;
|
|
}
|
|
|
|
static void x11WarpPointer(int x, int y, bool exiting)
|
|
{
|
|
XIWarpPointer(
|
|
x11.display,
|
|
x11.pointerDev,
|
|
None,
|
|
x11.window,
|
|
0, 0, 0, 0,
|
|
x, y);
|
|
|
|
XSync(x11.display, False);
|
|
}
|
|
|
|
static void x11RealignPointer(void)
|
|
{
|
|
app_handleMouseRelative(0.0, 0.0, 0.0, 0.0);
|
|
}
|
|
|
|
static bool x11IsValidPointerPos(int x, int y)
|
|
{
|
|
int screens;
|
|
XineramaScreenInfo *xinerama = XineramaQueryScreens(x11.display, &screens);
|
|
|
|
if(!xinerama)
|
|
return true;
|
|
|
|
bool ret = false;
|
|
for(int i = 0; i < screens; ++i)
|
|
if (x >= xinerama[i].x_org && x < xinerama[i].x_org + xinerama[i].width &&
|
|
y >= xinerama[i].y_org && y < xinerama[i].y_org + xinerama[i].height)
|
|
{
|
|
ret = true;
|
|
break;
|
|
}
|
|
|
|
XFree(xinerama);
|
|
return ret;
|
|
}
|
|
|
|
static void x11RequestActivation(void)
|
|
{
|
|
XEvent e =
|
|
{
|
|
.xclient = {
|
|
.type = ClientMessage,
|
|
.send_event = true,
|
|
.message_type = x11atoms._NET_WM_STATE,
|
|
.format = 32,
|
|
.window = x11.window,
|
|
.data.l = {
|
|
_NET_WM_STATE_ADD,
|
|
x11atoms._NET_WM_STATE_DEMANDS_ATTENTION,
|
|
0
|
|
}
|
|
}
|
|
};
|
|
|
|
XSendEvent(x11.display, DefaultRootWindow(x11.display), False,
|
|
SubstructureNotifyMask | SubstructureRedirectMask, &e);
|
|
}
|
|
|
|
static void x11InhibitIdle(void)
|
|
{
|
|
XScreenSaverSuspend(x11.display, true);
|
|
}
|
|
|
|
static void x11UninhibitIdle(void)
|
|
{
|
|
XScreenSaverSuspend(x11.display, false);
|
|
}
|
|
|
|
static void x11Wait(unsigned int time)
|
|
{
|
|
usleep(time * 1000U);
|
|
}
|
|
|
|
static void x11SetWindowSize(int w, int h)
|
|
{
|
|
XResizeWindow(x11.display, x11.window, w, h);
|
|
}
|
|
|
|
static void x11SetFullscreen(bool fs)
|
|
{
|
|
if (x11.fullscreen == fs)
|
|
return;
|
|
|
|
x11.wm->setFullscreen(fs);
|
|
}
|
|
|
|
static bool x11GetFullscreen(void)
|
|
{
|
|
return x11.fullscreen;
|
|
}
|
|
|
|
static void x11Minimize(void)
|
|
{
|
|
XIconifyWindow(x11.display, x11.window, XDefaultScreen(x11.display));
|
|
}
|
|
|
|
struct LG_DisplayServerOps LGDS_X11 =
|
|
{
|
|
.name = "X11",
|
|
.setup = x11Setup,
|
|
.probe = x11Probe,
|
|
.earlyInit = x11EarlyInit,
|
|
.init = x11Init,
|
|
.startup = x11Startup,
|
|
.shutdown = x11Shutdown,
|
|
.free = x11Free,
|
|
.getProp = x11GetProp,
|
|
#ifdef ENABLE_EGL
|
|
.getEGLDisplay = x11GetEGLDisplay,
|
|
.getEGLNativeWindow = x11GetEGLNativeWindow,
|
|
.eglSwapBuffers = x11EGLSwapBuffers,
|
|
#endif
|
|
#ifdef ENABLE_OPENGL
|
|
.glCreateContext = x11GLCreateContext,
|
|
.glDeleteContext = x11GLDeleteContext,
|
|
.glMakeCurrent = x11GLMakeCurrent,
|
|
.glSetSwapInterval = x11GLSetSwapInterval,
|
|
.glSwapBuffers = x11GLSwapBuffers,
|
|
#endif
|
|
.waitFrame = x11WaitFrame,
|
|
.stopWaitFrame = x11StopWaitFrame,
|
|
.guestPointerUpdated = x11GuestPointerUpdated,
|
|
.setPointer = x11SetPointer,
|
|
.grabPointer = x11GrabPointer,
|
|
.ungrabPointer = x11UngrabPointer,
|
|
.capturePointer = x11CapturePointer,
|
|
.uncapturePointer = x11UncapturePointer,
|
|
.grabKeyboard = x11GrabKeyboard,
|
|
.ungrabKeyboard = x11UngrabKeyboard,
|
|
.warpPointer = x11WarpPointer,
|
|
.realignPointer = x11RealignPointer,
|
|
.isValidPointerPos = x11IsValidPointerPos,
|
|
.requestActivation = x11RequestActivation,
|
|
.inhibitIdle = x11InhibitIdle,
|
|
.uninhibitIdle = x11UninhibitIdle,
|
|
.wait = x11Wait,
|
|
.setWindowSize = x11SetWindowSize,
|
|
.setFullscreen = x11SetFullscreen,
|
|
.getFullscreen = x11GetFullscreen,
|
|
.minimize = x11Minimize,
|
|
|
|
.cbInit = x11CBInit,
|
|
.cbNotice = x11CBNotice,
|
|
.cbRelease = x11CBRelease,
|
|
.cbRequest = x11CBRequest
|
|
};
|