mirror of
https://github.com/gnif/LookingGlass.git
synced 2024-11-10 08:38:20 +00:00
16f88a5285
As the X11 code is all self contained now and can be disabled at configure time there is no longer any need to check if SDL has X11 XInput support.
858 lines
20 KiB
C
858 lines
20 KiB
C
/*
|
|
Looking Glass - KVM FrameRelay (KVMFR) Client
|
|
Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
|
|
https://looking-glass.hostfission.com
|
|
|
|
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 <SDL2/SDL.h>
|
|
#include <X11/Xlib.h>
|
|
#include <GL/glx.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <X11/extensions/XInput2.h>
|
|
#include <X11/extensions/Xfixes.h>
|
|
|
|
#include "app.h"
|
|
#include "common/debug.h"
|
|
|
|
struct X11DSState
|
|
{
|
|
Display * display;
|
|
Window window;
|
|
int xinputOp;
|
|
|
|
bool pointerGrabbed;
|
|
bool keyboardGrabbed;
|
|
|
|
// clipboard members
|
|
Atom aSelection;
|
|
Atom aCurSelection;
|
|
Atom aTargets;
|
|
Atom aSelData;
|
|
Atom aIncr;
|
|
Atom aTypes[LG_CLIPBOARD_DATA_NONE];
|
|
LG_ClipboardData type;
|
|
bool haveRequest;
|
|
|
|
bool incrStart;
|
|
unsigned int lowerBound;
|
|
|
|
// XFixes vars
|
|
int eventBase;
|
|
int errorBase;
|
|
};
|
|
|
|
static const char * atomTypes[] =
|
|
{
|
|
"UTF8_STRING",
|
|
"image/png",
|
|
"image/bmp",
|
|
"image/tiff",
|
|
"image/jpeg"
|
|
};
|
|
|
|
static struct X11DSState x11;
|
|
|
|
// forwards
|
|
static void x11CBSelectionRequest(const XSelectionRequestEvent e);
|
|
static void x11CBSelectionClear(const XSelectionClearEvent e);
|
|
static void x11CBSelectionIncr(const XPropertyEvent e);
|
|
static void x11CBSelectionNotify(const XSelectionEvent e);
|
|
static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e);
|
|
|
|
static bool x11Init(SDL_SysWMinfo * info)
|
|
{
|
|
memset(&x11, 0, sizeof(x11));
|
|
x11.display = info->info.x11.display;
|
|
x11.window = info->info.x11.window;
|
|
|
|
int event, error;
|
|
|
|
int major = 2;
|
|
int minor = 3;
|
|
if (XIQueryVersion(x11.display, &major, &minor) != Success)
|
|
{
|
|
DEBUG_ERROR("Failed to query the XInput version");
|
|
return false;
|
|
}
|
|
|
|
DEBUG_INFO("X11 XInput %d.%d in use", major, minor);
|
|
|
|
XQueryExtension(x11.display, "XInputExtension", &x11.xinputOp, &event, &error);
|
|
|
|
int num_masks;
|
|
XIEventMask * mask = XIGetSelectedEvents(x11.display, x11.window, &num_masks);
|
|
if (!mask)
|
|
{
|
|
DEBUG_ERROR("Failed to get the XInput event mask");
|
|
return false;
|
|
}
|
|
|
|
for(int i = 0; i < num_masks; ++i)
|
|
{
|
|
XISetMask(mask[i].mask, XI_ButtonPress );
|
|
XISetMask(mask[i].mask, XI_ButtonRelease);
|
|
XISetMask(mask[i].mask, XI_Motion );
|
|
XISetMask(mask[i].mask, XI_KeyPress );
|
|
XISetMask(mask[i].mask, XI_KeyRelease );
|
|
}
|
|
|
|
if (XISelectEvents(x11.display, x11.window, mask, num_masks) != Success)
|
|
{
|
|
XFree(mask);
|
|
DEBUG_ERROR("Failed to select the xinput events");
|
|
return false;
|
|
}
|
|
|
|
XFree(mask);
|
|
|
|
Atom NETWM_BYPASS_COMPOSITOR = XInternAtom(x11.display,
|
|
"NETWM_BYPASS_COMPOSITOR", False);
|
|
|
|
unsigned long value = 1;
|
|
XChangeProperty(
|
|
x11.display,
|
|
x11.window,
|
|
NETWM_BYPASS_COMPOSITOR,
|
|
XA_CARDINAL,
|
|
32,
|
|
PropModeReplace,
|
|
(unsigned char *)&value,
|
|
1
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void x11Startup(void)
|
|
{
|
|
}
|
|
|
|
static void x11Shutdown(void)
|
|
{
|
|
}
|
|
|
|
static void x11Free(void)
|
|
{
|
|
}
|
|
|
|
static bool x11GetProp(LG_DSProperty prop, void *ret)
|
|
{
|
|
if (prop != LG_DS_MAX_MULTISAMPLE)
|
|
return false;
|
|
|
|
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;
|
|
}
|
|
|
|
XCloseDisplay(dpy);
|
|
|
|
*(int*)ret = maxSamples;
|
|
return true;
|
|
}
|
|
|
|
static bool x11EventFilter(SDL_Event * event)
|
|
{
|
|
/* prevent the default processing for the following events */
|
|
switch(event->type)
|
|
{
|
|
case SDL_WINDOWEVENT:
|
|
switch(event->window.event)
|
|
{
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
case SDL_WINDOWEVENT_RESIZED:
|
|
case SDL_WINDOWEVENT_CLOSE:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
#if SDL_VIDEO_DRIVER_X11_XINPUT2
|
|
case SDL_KEYDOWN:
|
|
case SDL_KEYUP:
|
|
case SDL_MOUSEMOTION:
|
|
case SDL_MOUSEBUTTONDOWN:
|
|
case SDL_MOUSEBUTTONUP:
|
|
case SDL_MOUSEWHEEL:
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
if (event->type != SDL_SYSWMEVENT)
|
|
return false;
|
|
|
|
XEvent xe = event->syswm.msg->msg.x11.event;
|
|
switch(xe.type)
|
|
{
|
|
case ConfigureNotify:
|
|
{
|
|
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);
|
|
|
|
app_updateWindowPos(x, y);
|
|
app_handleResizeEvent(xe.xconfigure.width, xe.xconfigure.height);
|
|
return true;
|
|
}
|
|
|
|
case EnterNotify:
|
|
{
|
|
int x, y;
|
|
Window child;
|
|
XTranslateCoordinates(
|
|
x11.display,
|
|
DefaultRootWindow(x11.display),
|
|
x11.window,
|
|
xe.xcrossing.x_root, xe.xcrossing.y_root,
|
|
&x, &y,
|
|
&child);
|
|
|
|
app_updateCursorPos(x, y);
|
|
app_handleWindowEnter();
|
|
return true;
|
|
}
|
|
|
|
case LeaveNotify:
|
|
{
|
|
if (xe.xcrossing.mode != NotifyNormal)
|
|
return true;
|
|
|
|
int x, y;
|
|
Window child;
|
|
XTranslateCoordinates(x11.display,
|
|
DefaultRootWindow(x11.display),
|
|
x11.window,
|
|
xe.xcrossing.x_root, xe.xcrossing.y_root,
|
|
&x, &y,
|
|
&child);
|
|
|
|
app_updateCursorPos(x, y);
|
|
app_handleWindowLeave();
|
|
return true;
|
|
}
|
|
|
|
case GenericEvent:
|
|
{
|
|
XGenericEventCookie *cookie = (XGenericEventCookie*)&xe.xcookie;
|
|
if (cookie->extension != x11.xinputOp)
|
|
return false;
|
|
|
|
if (!app_inputEnabled())
|
|
return true;
|
|
|
|
switch(cookie->evtype)
|
|
{
|
|
case XI_KeyPress:
|
|
{
|
|
XIDeviceEvent *device = cookie->data;
|
|
app_handleKeyPress(device->detail - 8);
|
|
return true;
|
|
}
|
|
|
|
case XI_KeyRelease:
|
|
{
|
|
XIDeviceEvent *device = cookie->data;
|
|
app_handleKeyRelease(device->detail - 8);
|
|
return true;
|
|
}
|
|
|
|
case XI_ButtonPress:
|
|
{
|
|
XIDeviceEvent *device = cookie->data;
|
|
app_updateCursorPos(device->event_x, device->event_y);
|
|
|
|
app_handleButtonPress(
|
|
device->detail > 5 ? device->detail - 2 : device->detail);
|
|
return true;
|
|
}
|
|
|
|
case XI_ButtonRelease:
|
|
{
|
|
XIDeviceEvent *device = cookie->data;
|
|
app_updateCursorPos(device->event_x, device->event_y);
|
|
|
|
app_handleButtonRelease(
|
|
device->detail > 5 ? device->detail - 2 : device->detail);
|
|
return true;
|
|
}
|
|
|
|
case XI_Motion:
|
|
{
|
|
if (!app_cursorInWindow())
|
|
return true;
|
|
|
|
XIDeviceEvent *device = cookie->data;
|
|
app_updateCursorPos(device->event_x, device->event_y);
|
|
return true;
|
|
}
|
|
|
|
case XI_RawMotion:
|
|
{
|
|
if (!app_cursorInWindow())
|
|
return true;
|
|
|
|
XIRawEvent *raw = cookie->data;
|
|
double raw_axis[2];
|
|
double axis[2];
|
|
|
|
/* select the active validators for the X & Y axis */
|
|
double *valuator = raw->valuators.values;
|
|
double *r_value = raw->raw_values;
|
|
int count = 0;
|
|
for(int i = 0; i < raw->valuators.mask_len * 8; ++i)
|
|
{
|
|
if (XIMaskIsSet(raw->valuators.mask, i))
|
|
{
|
|
raw_axis[count] = *r_value;
|
|
axis [count] = *valuator;
|
|
++count;
|
|
|
|
if (count == 2)
|
|
break;
|
|
|
|
++valuator;
|
|
++r_value;
|
|
}
|
|
}
|
|
|
|
/* filter out scroll wheel and other events */
|
|
if (count < 2)
|
|
return true;
|
|
|
|
/* 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 true;
|
|
|
|
prev_time = raw->time;
|
|
prev_axis[0] = axis[0];
|
|
prev_axis[1] = axis[1];
|
|
|
|
if (app_cursorIsGrabbed())
|
|
{
|
|
if (app_cursorWantsRaw())
|
|
app_handleMouseGrabbed(raw_axis[0], raw_axis[1]);
|
|
else
|
|
app_handleMouseGrabbed(axis[0], axis[1]);
|
|
}
|
|
else
|
|
if (app_cursorInWindow())
|
|
app_handleMouseNormal(axis[0], axis[1]);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// clipboard events
|
|
case SelectionRequest:
|
|
x11CBSelectionRequest(xe.xselectionrequest);
|
|
return true;
|
|
|
|
case SelectionClear:
|
|
x11CBSelectionClear(xe.xselectionclear);
|
|
return true;
|
|
|
|
case SelectionNotify:
|
|
x11CBSelectionNotify(xe.xselection);
|
|
return true;
|
|
|
|
case PropertyNotify:
|
|
if (xe.xproperty.display != x11.display ||
|
|
xe.xproperty.window != x11.window ||
|
|
xe.xproperty.atom != x11.aSelData ||
|
|
xe.xproperty.state != PropertyNewValue ||
|
|
x11.lowerBound == 0)
|
|
return false;
|
|
|
|
x11CBSelectionIncr(xe.xproperty);
|
|
return true;
|
|
|
|
default:
|
|
if (xe.type == x11.eventBase + XFixesSelectionNotify)
|
|
{
|
|
XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&xe;
|
|
x11CBXFixesSelectionNotify(*sne);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void x11GrabPointer(void)
|
|
{
|
|
if (x11.pointerGrabbed)
|
|
return;
|
|
|
|
XGrabPointer(
|
|
x11.display,
|
|
x11.window,
|
|
true,
|
|
None,
|
|
GrabModeAsync,
|
|
GrabModeAsync,
|
|
x11.window,
|
|
None,
|
|
CurrentTime);
|
|
|
|
x11.pointerGrabbed = true;
|
|
}
|
|
|
|
static void x11UngrabPointer(void)
|
|
{
|
|
if (!x11.pointerGrabbed)
|
|
return;
|
|
|
|
XUngrabPointer(x11.display, CurrentTime);
|
|
x11.pointerGrabbed = false;
|
|
}
|
|
|
|
static void x11GrabKeyboard(void)
|
|
{
|
|
if (x11.keyboardGrabbed)
|
|
return;
|
|
|
|
XGrabKeyboard(
|
|
x11.display,
|
|
x11.window,
|
|
true,
|
|
GrabModeAsync,
|
|
GrabModeAsync,
|
|
CurrentTime);
|
|
|
|
x11.keyboardGrabbed = true;
|
|
}
|
|
|
|
static void x11UngrabKeyboard(void)
|
|
{
|
|
if (!x11.keyboardGrabbed)
|
|
return;
|
|
|
|
XUngrabKeyboard(x11.display, CurrentTime);
|
|
x11.keyboardGrabbed = false;
|
|
}
|
|
|
|
static void x11WarpPointer(int x, int y, bool exiting)
|
|
{
|
|
XWarpPointer(
|
|
x11.display,
|
|
None,
|
|
x11.window,
|
|
0, 0, 0, 0,
|
|
x, y);
|
|
|
|
XSync(x11.display, False);
|
|
}
|
|
|
|
static bool x11CBInit()
|
|
{
|
|
x11.aSelection = XInternAtom(x11.display, "CLIPBOARD" , False);
|
|
x11.aTargets = XInternAtom(x11.display, "TARGETS" , False);
|
|
x11.aSelData = XInternAtom(x11.display, "SEL_DATA" , False);
|
|
x11.aIncr = XInternAtom(x11.display, "INCR" , False);
|
|
x11.aCurSelection = BadValue;
|
|
|
|
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
|
{
|
|
x11.aTypes[i] = XInternAtom(x11.display, atomTypes[i], False);
|
|
if (x11.aTypes[i] == BadAlloc || x11.aTypes[i] == BadValue)
|
|
{
|
|
DEBUG_ERROR("failed to get atom for type: %s", atomTypes[i]);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// use xfixes to get clipboard change notifications
|
|
if (!XFixesQueryExtension(x11.display, &x11.eventBase, &x11.errorBase))
|
|
{
|
|
DEBUG_ERROR("failed to initialize xfixes");
|
|
return false;
|
|
}
|
|
|
|
XFixesSelectSelectionInput(x11.display, x11.window,
|
|
XA_PRIMARY, XFixesSetSelectionOwnerNotifyMask);
|
|
XFixesSelectSelectionInput(x11.display, x11.window,
|
|
x11.aSelection, XFixesSetSelectionOwnerNotifyMask);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void x11CBReplyFn(void * opaque, LG_ClipboardData type,
|
|
uint8_t * data, uint32_t size)
|
|
{
|
|
XEvent *s = (XEvent *)opaque;
|
|
|
|
XChangeProperty(
|
|
x11.display ,
|
|
s->xselection.requestor,
|
|
s->xselection.property ,
|
|
s->xselection.target ,
|
|
8,
|
|
PropModeReplace,
|
|
data,
|
|
size);
|
|
|
|
XSendEvent(x11.display, s->xselection.requestor, 0, 0, s);
|
|
XFlush(x11.display);
|
|
free(s);
|
|
}
|
|
|
|
static void x11CBSelectionRequest(const XSelectionRequestEvent e)
|
|
{
|
|
XEvent * s = (XEvent *)malloc(sizeof(XEvent));
|
|
s->xselection.type = SelectionNotify;
|
|
s->xselection.requestor = e.requestor;
|
|
s->xselection.selection = e.selection;
|
|
s->xselection.target = e.target;
|
|
s->xselection.property = e.property;
|
|
s->xselection.time = e.time;
|
|
|
|
if (!x11.haveRequest)
|
|
goto nodata;
|
|
|
|
// target list requested
|
|
if (e.target == x11.aTargets)
|
|
{
|
|
Atom targets[2];
|
|
targets[0] = x11.aTargets;
|
|
targets[1] = x11.aTypes[x11.type];
|
|
|
|
XChangeProperty(
|
|
e.display,
|
|
e.requestor,
|
|
e.property,
|
|
XA_ATOM,
|
|
32,
|
|
PropModeReplace,
|
|
(unsigned char*)targets,
|
|
sizeof(targets) / sizeof(Atom));
|
|
|
|
goto send;
|
|
}
|
|
|
|
// look to see if we can satisfy the data type
|
|
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
|
if (x11.aTypes[i] == e.target && x11.type == i)
|
|
{
|
|
// request the data
|
|
app_clipboardRequest(x11CBReplyFn, s);
|
|
return;
|
|
}
|
|
|
|
nodata:
|
|
// report no data
|
|
s->xselection.property = None;
|
|
|
|
send:
|
|
XSendEvent(x11.display, e.requestor, 0, 0, s);
|
|
XFlush(x11.display);
|
|
free(s);
|
|
}
|
|
|
|
static void x11CBSelectionClear(const XSelectionClearEvent e)
|
|
{
|
|
if (e.selection != XA_PRIMARY && e.selection != x11.aSelection)
|
|
return;
|
|
|
|
x11.aCurSelection = BadValue;
|
|
app_clipboardRelease();
|
|
return;
|
|
}
|
|
|
|
static void x11CBSelectionIncr(const XPropertyEvent e)
|
|
{
|
|
Atom type;
|
|
int format;
|
|
unsigned long itemCount, after;
|
|
unsigned char *data;
|
|
|
|
if (XGetWindowProperty(
|
|
e.display,
|
|
e.window,
|
|
e.atom,
|
|
0, ~0L, // start and length
|
|
True, // delete the property
|
|
x11.aIncr,
|
|
&type,
|
|
&format,
|
|
&itemCount,
|
|
&after,
|
|
&data) != Success)
|
|
{
|
|
DEBUG_INFO("GetProp Failed");
|
|
app_clipboardNotify(LG_CLIPBOARD_DATA_NONE, 0);
|
|
goto out;
|
|
}
|
|
|
|
LG_ClipboardData dataType;
|
|
for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType)
|
|
if (x11.aTypes[dataType] == type)
|
|
break;
|
|
|
|
if (dataType == LG_CLIPBOARD_DATA_NONE)
|
|
{
|
|
DEBUG_WARN("clipboard data (%s) not in a supported format",
|
|
XGetAtomName(x11.display, type));
|
|
|
|
x11.lowerBound = 0;
|
|
app_clipboardNotify(LG_CLIPBOARD_DATA_NONE, 0);
|
|
goto out;
|
|
}
|
|
|
|
if (x11.incrStart)
|
|
{
|
|
app_clipboardNotify(dataType, x11.lowerBound);
|
|
x11.incrStart = false;
|
|
}
|
|
|
|
XFree(data);
|
|
data = NULL;
|
|
|
|
if (XGetWindowProperty(
|
|
e.display,
|
|
e.window,
|
|
e.atom,
|
|
0, ~0L, // start and length
|
|
True, // delete the property
|
|
type,
|
|
&type,
|
|
&format,
|
|
&itemCount,
|
|
&after,
|
|
&data) != Success)
|
|
{
|
|
DEBUG_ERROR("XGetWindowProperty Failed");
|
|
app_clipboardNotify(LG_CLIPBOARD_DATA_NONE, 0);
|
|
goto out;
|
|
}
|
|
|
|
app_clipboardData(dataType, data, itemCount);
|
|
x11.lowerBound -= itemCount;
|
|
|
|
out:
|
|
if (data)
|
|
XFree(data);
|
|
}
|
|
|
|
static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e)
|
|
{
|
|
// check if the selection is valid and it isn't ourself
|
|
if ((e.selection != XA_PRIMARY && e.selection != x11.aSelection) ||
|
|
e.owner == x11.window || e.owner == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// remember which selection we are working with
|
|
x11.aCurSelection = e.selection;
|
|
XConvertSelection(
|
|
x11.display,
|
|
e.selection,
|
|
x11.aTargets,
|
|
x11.aTargets,
|
|
x11.window,
|
|
CurrentTime);
|
|
|
|
return;
|
|
}
|
|
|
|
static void x11CBSelectionNotify(const XSelectionEvent e)
|
|
{
|
|
if (e.property == None)
|
|
return;
|
|
|
|
Atom type;
|
|
int format;
|
|
unsigned long itemCount, after;
|
|
unsigned char *data;
|
|
|
|
if (XGetWindowProperty(
|
|
e.display,
|
|
e.requestor,
|
|
e.property,
|
|
0, ~0L, // start and length
|
|
True , // delete the property
|
|
AnyPropertyType,
|
|
&type,
|
|
&format,
|
|
&itemCount,
|
|
&after,
|
|
&data) != Success)
|
|
{
|
|
app_clipboardNotify(LG_CLIPBOARD_DATA_NONE, 0);
|
|
goto out;
|
|
}
|
|
|
|
if (type == x11.aIncr)
|
|
{
|
|
x11.incrStart = true;
|
|
x11.lowerBound = *(unsigned int *)data;
|
|
goto out;
|
|
}
|
|
|
|
// the target list
|
|
if (e.property == x11.aTargets)
|
|
{
|
|
// the format is 32-bit and we must have data
|
|
// this is technically incorrect however as it's
|
|
// an array of padded 64-bit values
|
|
if (!data || format != 32)
|
|
goto out;
|
|
|
|
// see if we support any of the targets listed
|
|
const uint64_t * targets = (const uint64_t *)data;
|
|
for(unsigned long i = 0; i < itemCount; ++i)
|
|
{
|
|
for(int n = 0; n < LG_CLIPBOARD_DATA_NONE; ++n)
|
|
if (x11.aTypes[n] == targets[i])
|
|
{
|
|
// we have a match, so send the notification
|
|
app_clipboardNotify(n, 0);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
// no matches
|
|
app_clipboardNotify(LG_CLIPBOARD_DATA_NONE, 0);
|
|
goto out;
|
|
}
|
|
|
|
if (e.property == x11.aSelData)
|
|
{
|
|
LG_ClipboardData dataType;
|
|
for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType)
|
|
if (x11.aTypes[dataType] == type)
|
|
break;
|
|
|
|
if (dataType == LG_CLIPBOARD_DATA_NONE)
|
|
{
|
|
DEBUG_WARN("clipboard data (%s) not in a supported format",
|
|
XGetAtomName(x11.display, type));
|
|
goto out;
|
|
}
|
|
|
|
app_clipboardData(dataType, data, itemCount);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
if (data)
|
|
XFree(data);
|
|
}
|
|
|
|
static void x11CBNotice(LG_ClipboardData type)
|
|
{
|
|
x11.haveRequest = true;
|
|
x11.type = type;
|
|
XSetSelectionOwner(x11.display, XA_PRIMARY , x11.window, CurrentTime);
|
|
XSetSelectionOwner(x11.display, x11.aSelection, x11.window, CurrentTime);
|
|
XFlush(x11.display);
|
|
}
|
|
|
|
static void x11CBRelease(void)
|
|
{
|
|
x11.haveRequest = false;
|
|
XSetSelectionOwner(x11.display, XA_PRIMARY , None, CurrentTime);
|
|
XSetSelectionOwner(x11.display, x11.aSelection, None, CurrentTime);
|
|
XFlush(x11.display);
|
|
}
|
|
|
|
static void x11CBRequest(LG_ClipboardData type)
|
|
{
|
|
if (x11.aCurSelection == BadValue)
|
|
return;
|
|
|
|
XConvertSelection(
|
|
x11.display,
|
|
x11.aCurSelection,
|
|
x11.aTypes[type],
|
|
x11.aSelData,
|
|
x11.window,
|
|
CurrentTime);
|
|
}
|
|
|
|
struct LG_DisplayServerOps LGDS_X11 =
|
|
{
|
|
.subsystem = SDL_SYSWM_X11,
|
|
.init = x11Init,
|
|
.startup = x11Startup,
|
|
.shutdown = x11Shutdown,
|
|
.free = x11Free,
|
|
.getProp = x11GetProp,
|
|
.eventFilter = x11EventFilter,
|
|
.grabPointer = x11GrabPointer,
|
|
.ungrabPointer = x11UngrabPointer,
|
|
.grabKeyboard = x11GrabKeyboard,
|
|
.ungrabKeyboard = x11UngrabKeyboard,
|
|
.warpPointer = x11WarpPointer,
|
|
|
|
.cbInit = x11CBInit,
|
|
.cbNotice = x11CBNotice,
|
|
.cbRelease = x11CBRelease,
|
|
.cbRequest = x11CBRequest
|
|
};
|