2021-06-06 01:26:18 +00:00
|
|
|
/**
|
|
|
|
* Looking Glass
|
2023-10-20 04:36:34 +00:00
|
|
|
* Copyright © 2017-2023 The Looking Glass Authors
|
2021-06-06 01:26:18 +00:00
|
|
|
* 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
|
|
|
|
*/
|
2021-03-26 22:21:32 +00:00
|
|
|
|
|
|
|
#include "clipboard.h"
|
|
|
|
#include "x11.h"
|
2021-03-26 23:05:57 +00:00
|
|
|
#include "atoms.h"
|
2021-03-26 22:21:32 +00:00
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include <X11/Xlib.h>
|
|
|
|
#include <X11/Xatom.h>
|
|
|
|
|
|
|
|
#include "app.h"
|
|
|
|
#include "common/debug.h"
|
|
|
|
|
|
|
|
struct X11ClipboardState
|
|
|
|
{
|
|
|
|
Atom aCurSelection;
|
|
|
|
Atom aTypes[LG_CLIPBOARD_DATA_NONE];
|
|
|
|
LG_ClipboardData type;
|
|
|
|
bool haveRequest;
|
|
|
|
|
|
|
|
bool incrStart;
|
|
|
|
unsigned int lowerBound;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char * atomTypes[] =
|
|
|
|
{
|
|
|
|
"UTF8_STRING",
|
|
|
|
"image/png",
|
|
|
|
"image/bmp",
|
|
|
|
"image/tiff",
|
|
|
|
"image/jpeg"
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct X11ClipboardState x11cb;
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
2023-11-11 00:36:24 +00:00
|
|
|
bool x11CBEventThread(const XEvent * xe)
|
2021-03-26 22:21:32 +00:00
|
|
|
{
|
2023-11-11 00:36:24 +00:00
|
|
|
switch(xe->type)
|
2021-03-26 22:21:32 +00:00
|
|
|
{
|
|
|
|
case SelectionRequest:
|
2023-11-11 00:36:24 +00:00
|
|
|
x11CBSelectionRequest(xe->xselectionrequest);
|
2021-04-29 02:24:23 +00:00
|
|
|
return true;
|
2021-03-26 22:21:32 +00:00
|
|
|
|
|
|
|
case SelectionClear:
|
2023-11-11 00:36:24 +00:00
|
|
|
x11CBSelectionClear(xe->xselectionclear);
|
2021-04-29 02:24:23 +00:00
|
|
|
return true;
|
2021-03-26 22:21:32 +00:00
|
|
|
|
|
|
|
case SelectionNotify:
|
2023-11-11 00:36:24 +00:00
|
|
|
x11CBSelectionNotify(xe->xselection);
|
2021-04-29 02:24:23 +00:00
|
|
|
return true;
|
2021-03-26 22:21:32 +00:00
|
|
|
|
|
|
|
case PropertyNotify:
|
2023-11-11 00:36:24 +00:00
|
|
|
if (xe->xproperty.state != PropertyNewValue)
|
2021-05-27 08:37:17 +00:00
|
|
|
break;
|
|
|
|
|
2023-11-11 00:36:24 +00:00
|
|
|
if (xe->xproperty.atom == x11atoms.SEL_DATA)
|
2021-03-26 22:21:32 +00:00
|
|
|
{
|
|
|
|
if (x11cb.lowerBound == 0)
|
2021-04-29 02:24:23 +00:00
|
|
|
return true;
|
2021-03-26 22:21:32 +00:00
|
|
|
|
2023-11-11 00:36:24 +00:00
|
|
|
x11CBSelectionIncr(xe->xproperty);
|
2021-04-29 02:24:23 +00:00
|
|
|
return true;
|
2021-03-26 22:21:32 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2023-11-11 00:36:24 +00:00
|
|
|
if (xe->type == x11.eventBase + XFixesSelectionNotify)
|
2021-03-26 22:21:32 +00:00
|
|
|
{
|
|
|
|
XFixesSelectionNotifyEvent * sne = (XFixesSelectionNotifyEvent *)&xe;
|
|
|
|
x11CBXFixesSelectionNotify(*sne);
|
2021-04-29 02:24:23 +00:00
|
|
|
return true;
|
2021-03-26 22:21:32 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2021-04-29 02:24:23 +00:00
|
|
|
|
|
|
|
return false;
|
2021-03-26 22:21:32 +00:00
|
|
|
}
|
|
|
|
|
2022-09-26 05:16:01 +00:00
|
|
|
bool x11CBInit(void)
|
2021-03-26 22:21:32 +00:00
|
|
|
{
|
|
|
|
x11cb.aCurSelection = BadValue;
|
|
|
|
for(int i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
|
|
|
{
|
|
|
|
x11cb.aTypes[i] = XInternAtom(x11.display, atomTypes[i], False);
|
|
|
|
if (x11cb.aTypes[i] == BadAlloc || x11cb.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,
|
2021-03-26 23:05:57 +00:00
|
|
|
x11atoms.CLIPBOARD, XFixesSetSelectionOwnerNotifyMask);
|
2021-03-26 22:21:32 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2021-08-15 22:55:19 +00:00
|
|
|
XEvent * s = malloc(sizeof(*s));
|
2022-03-06 23:13:54 +00:00
|
|
|
if (!s)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("out of memory");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-26 22:21:32 +00:00
|
|
|
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 (!x11cb.haveRequest)
|
|
|
|
goto nodata;
|
|
|
|
|
|
|
|
// target list requested
|
2021-03-26 23:05:57 +00:00
|
|
|
if (e.target == x11atoms.TARGETS)
|
2021-03-26 22:21:32 +00:00
|
|
|
{
|
|
|
|
Atom targets[2];
|
2021-03-26 23:05:57 +00:00
|
|
|
targets[0] = x11atoms.TARGETS;
|
2021-03-26 22:21:32 +00:00
|
|
|
targets[1] = x11cb.aTypes[x11cb.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 (x11cb.aTypes[i] == e.target && x11cb.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)
|
|
|
|
{
|
2022-01-12 19:40:47 +00:00
|
|
|
if (e.selection != x11atoms.CLIPBOARD)
|
2021-03-26 22:21:32 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
x11cb.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
|
2021-03-26 23:05:57 +00:00
|
|
|
x11atoms.INCR,
|
2021-03-26 22:21:32 +00:00
|
|
|
&type,
|
|
|
|
&format,
|
|
|
|
&itemCount,
|
|
|
|
&after,
|
|
|
|
&data) != Success)
|
|
|
|
{
|
|
|
|
DEBUG_INFO("GetProp Failed");
|
2021-05-30 01:37:31 +00:00
|
|
|
app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0);
|
2021-03-26 22:21:32 +00:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
LG_ClipboardData dataType;
|
|
|
|
for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType)
|
|
|
|
if (x11cb.aTypes[dataType] == type)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (dataType == LG_CLIPBOARD_DATA_NONE)
|
|
|
|
{
|
|
|
|
DEBUG_WARN("clipboard data (%s) not in a supported format",
|
|
|
|
XGetAtomName(x11.display, type));
|
|
|
|
|
|
|
|
x11cb.lowerBound = 0;
|
2021-05-30 01:37:31 +00:00
|
|
|
app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0);
|
2021-03-26 22:21:32 +00:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (x11cb.incrStart)
|
|
|
|
{
|
2021-05-30 01:37:31 +00:00
|
|
|
app_clipboardNotifySize(dataType, x11cb.lowerBound);
|
2021-03-26 22:21:32 +00:00
|
|
|
x11cb.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");
|
2021-05-30 01:37:31 +00:00
|
|
|
app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0);
|
2021-03-26 22:21:32 +00:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
app_clipboardData(dataType, data, itemCount);
|
|
|
|
x11cb.lowerBound -= itemCount;
|
|
|
|
|
|
|
|
out:
|
|
|
|
if (data)
|
|
|
|
XFree(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void x11CBXFixesSelectionNotify(const XFixesSelectionNotifyEvent e)
|
|
|
|
{
|
|
|
|
// check if the selection is valid and it isn't ourself
|
2022-01-12 19:40:47 +00:00
|
|
|
if (e.selection != x11atoms.CLIPBOARD ||
|
2021-03-26 22:21:32 +00:00
|
|
|
e.owner == x11.window || e.owner == 0)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// remember which selection we are working with
|
|
|
|
x11cb.aCurSelection = e.selection;
|
|
|
|
XConvertSelection(
|
|
|
|
x11.display,
|
|
|
|
e.selection,
|
2021-03-26 23:05:57 +00:00
|
|
|
x11atoms.TARGETS,
|
|
|
|
x11atoms.TARGETS,
|
2021-03-26 22:21:32 +00:00
|
|
|
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)
|
|
|
|
{
|
2021-05-30 01:37:31 +00:00
|
|
|
app_clipboardNotifySize(LG_CLIPBOARD_DATA_NONE, 0);
|
2021-03-26 22:21:32 +00:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2021-03-26 23:05:57 +00:00
|
|
|
if (type == x11atoms.INCR)
|
2021-03-26 22:21:32 +00:00
|
|
|
{
|
|
|
|
x11cb.incrStart = true;
|
|
|
|
x11cb.lowerBound = *(unsigned int *)data;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
// the target list
|
2021-03-26 23:05:57 +00:00
|
|
|
if (e.property == x11atoms.TARGETS)
|
2021-03-26 22:21:32 +00:00
|
|
|
{
|
|
|
|
// 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;
|
|
|
|
|
2021-05-30 01:37:31 +00:00
|
|
|
int typeCount = 0;
|
|
|
|
LG_ClipboardData types[itemCount];
|
|
|
|
|
2021-03-26 22:21:32 +00:00
|
|
|
// 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 (x11cb.aTypes[n] == targets[i])
|
2021-05-30 01:37:31 +00:00
|
|
|
types[typeCount++] = n;
|
2021-03-26 22:21:32 +00:00
|
|
|
}
|
|
|
|
|
2021-05-30 01:37:31 +00:00
|
|
|
app_clipboardNotifyTypes(types, typeCount);
|
2021-03-26 22:21:32 +00:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2021-03-26 23:05:57 +00:00
|
|
|
if (e.property == x11atoms.SEL_DATA)
|
2021-03-26 22:21:32 +00:00
|
|
|
{
|
|
|
|
LG_ClipboardData dataType;
|
|
|
|
for(dataType = 0; dataType < LG_CLIPBOARD_DATA_NONE; ++dataType)
|
|
|
|
if (x11cb.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);
|
|
|
|
}
|
|
|
|
|
|
|
|
void x11CBNotice(LG_ClipboardData type)
|
|
|
|
{
|
|
|
|
x11cb.haveRequest = true;
|
|
|
|
x11cb.type = type;
|
2021-03-26 23:05:57 +00:00
|
|
|
XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, x11.window, CurrentTime);
|
2021-03-26 22:21:32 +00:00
|
|
|
XFlush(x11.display);
|
|
|
|
}
|
|
|
|
|
|
|
|
void x11CBRelease(void)
|
|
|
|
{
|
|
|
|
x11cb.haveRequest = false;
|
2021-03-26 23:05:57 +00:00
|
|
|
XSetSelectionOwner(x11.display, x11atoms.CLIPBOARD, None, CurrentTime);
|
2021-03-26 22:21:32 +00:00
|
|
|
XFlush(x11.display);
|
|
|
|
}
|
|
|
|
|
|
|
|
void x11CBRequest(LG_ClipboardData type)
|
|
|
|
{
|
|
|
|
if (x11cb.aCurSelection == BadValue)
|
|
|
|
return;
|
|
|
|
|
|
|
|
XConvertSelection(
|
|
|
|
x11.display,
|
|
|
|
x11cb.aCurSelection,
|
|
|
|
x11cb.aTypes[type],
|
2021-03-26 23:05:57 +00:00
|
|
|
x11atoms.SEL_DATA,
|
2021-03-26 22:21:32 +00:00
|
|
|
x11.window,
|
|
|
|
CurrentTime);
|
|
|
|
}
|