Fix X11 selections (#55)

This fixes all the issues with the X11 selection in addition to switching the clipboard to always be UTF-8.
This commit is contained in:
DS 2021-08-30 21:44:56 +02:00 committed by GitHub
parent 75b4c05741
commit 9c4b6f25ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 245 additions and 83 deletions

View File

@ -11,6 +11,7 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <wchar.h>
namespace irr
{
@ -36,6 +37,7 @@ outside the string class for explicit use.
template <typename T, typename TAlloc = irrAllocator<T> >
class string;
static size_t multibyteToWString(string<wchar_t>& destination, const char* source, u32 sourceSize);
static size_t wStringToMultibyte(string<c8>& destination, const wchar_t* source, u32 sourceSize);
inline s32 isdigit(s32 c);
enum eLocaleID
@ -1424,6 +1426,7 @@ public:
}
friend size_t multibyteToWString(string<wchar_t>& destination, const char* source, u32 sourceSize);
friend size_t wStringToMultibyte(string<c8>& destination, const wchar_t* source, u32 sourceSize);
private:
@ -1517,6 +1520,53 @@ static size_t multibyteToWString(string<wchar_t>& destination, const char* sourc
}
}
//! Same as multibyteToWString, but the other way around
static inline size_t wStringToMultibyte(string<c8>& destination, const core::string<wchar_t>& source)
{
return wStringToMultibyte(destination, source.c_str(), (u32)source.size());
}
//! Same as multibyteToWString, but the other way around
static inline size_t wStringToMultibyte(string<c8>& destination, const wchar_t* source)
{
const u32 s = source ? (u32)wcslen(source) : 0;
return wStringToMultibyte(destination, source, s);
}
//! Same as multibyteToWString, but the other way around
static size_t wStringToMultibyte(string<c8>& destination, const wchar_t* source, u32 sourceSize)
{
if ( sourceSize )
{
destination.reserve(sourceSize+1);
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable: 4996) // 'wcstombs': This function or variable may be unsafe. Consider using wcstombs_s instead.
#endif
const size_t written = wcstombs(destination.array, source, (size_t)sourceSize);
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
if ( written != (size_t)-1 )
{
destination.used = (u32)written+1;
destination.array[destination.used-1] = 0;
}
else
{
// Likely character which got converted until the invalid character was encountered are in destination now.
// And it seems even 0-terminated, but I found no documentation anywhere that this (the 0-termination) is guaranteed :-(
destination.clear();
}
return written;
}
else
{
destination.clear();
return 0;
}
}
} // end namespace core
} // end namespace irr

View File

@ -300,7 +300,7 @@ bool CGUIEditBox::processKey(const SEvent& event)
const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
core::stringc s;
s = Text.subString(realmbgn, realmend - realmbgn).c_str();
wStringToMultibyte(s, Text.subString(realmbgn, realmend - realmbgn));
Operator->copyToClipboard(s.c_str());
}
break;
@ -313,7 +313,7 @@ bool CGUIEditBox::processKey(const SEvent& event)
// copy
core::stringc sc;
sc = Text.subString(realmbgn, realmend - realmbgn).c_str();
wStringToMultibyte(sc, Text.subString(realmbgn, realmend - realmbgn));
Operator->copyToClipboard(sc.c_str());
if (isEnabled())
@ -341,8 +341,8 @@ bool CGUIEditBox::processKey(const SEvent& event)
const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
// add new character
const c8* p = Operator->getTextFromClipboard();
// add the string
const c8 *p = Operator->getTextFromClipboard();
if (p)
{
irr::core::stringw widep;

View File

@ -90,6 +90,7 @@ namespace
Atom X_ATOM_CLIPBOARD;
Atom X_ATOM_TARGETS;
Atom X_ATOM_UTF8_STRING;
Atom X_ATOM_UTF8_MIME_TYPE;
Atom X_ATOM_TEXT;
Atom X_ATOM_NETWM_MAXIMIZE_VERT;
Atom X_ATOM_NETWM_MAXIMIZE_HORZ;
@ -1010,49 +1011,104 @@ bool CIrrDeviceLinux::run()
case SelectionRequest:
{
XEvent respond;
XSelectionRequestEvent *req = &(event.xselectionrequest);
if ( req->target == XA_STRING)
{
XChangeProperty (XDisplay,
auto send_response = [this, req](Atom property) {
XEvent response;
response.xselection.type = SelectionNotify;
response.xselection.display = req->display;
response.xselection.requestor = req->requestor;
response.xselection.selection = req->selection;
response.xselection.target = req->target;
response.xselection.property = property;
response.xselection.time = req->time;
XSendEvent (XDisplay, req->requestor, 0, 0, &response);
XFlush (XDisplay);
};
auto send_response_refuse = [&send_response] {
send_response(None);
};
// sets the required property to data of type type and
// sends the according response
auto set_property_and_notify = [this, req, &send_response]
(Atom type, int format, const void *data, u32 data_size) {
XChangeProperty(XDisplay,
req->requestor,
req->property, req->target,
8, // format
req->property,
type,
format,
PropModeReplace,
(unsigned char*) Clipboard.c_str(),
(const unsigned char *)data,
data_size);
send_response(req->property);
};
if (req->selection != X_ATOM_CLIPBOARD ||
req->owner != XWindow) {
// we are not the owner, refuse request
send_response_refuse();
break;
}
// for debugging:
//~ {
//~ char *target_name = XGetAtomName(XDisplay, req->target);
//~ fprintf(stderr, "CIrrDeviceLinux::run: target: %s (=%ld)\n",
//~ target_name, req->target);
//~ XFree(target_name);
//~ }
if (req->property == None) {
// req is from obsolete client, use target as property name
// and X_ATOM_UTF8_STRING as type
// Note: this was not tested and might be incorrect
os::Printer::log("CIrrDeviceLinux::run: SelectionRequest from obsolete client",
ELL_WARNING);
XChangeProperty(XDisplay,
req->requestor,
req->target, X_ATOM_UTF8_STRING,
8, // format = 8-bit
PropModeReplace,
(unsigned char *)Clipboard.c_str(),
Clipboard.size());
respond.xselection.property = req->property;
send_response(req->target);
break;
}
else if ( req->target == X_ATOM_TARGETS )
{
long data[2];
data[0] = X_ATOM_TEXT;
data[1] = XA_STRING;
if (req->target == X_ATOM_TARGETS) {
Atom data[] = {
X_ATOM_TARGETS,
X_ATOM_TEXT,
X_ATOM_UTF8_STRING,
X_ATOM_UTF8_MIME_TYPE
};
set_property_and_notify(
XA_ATOM,
32, // Atom is long, we need to set 32 for longs
&data,
sizeof(data) / sizeof(*data)
);
XChangeProperty (XDisplay, req->requestor,
req->property, req->target,
8, PropModeReplace,
(unsigned char *) &data,
sizeof (data));
respond.xselection.property = req->property;
} else if (req->target == X_ATOM_TEXT ||
req->target == X_ATOM_UTF8_STRING ||
req->target == X_ATOM_UTF8_MIME_TYPE) {
set_property_and_notify(
X_ATOM_UTF8_STRING,
8,
Clipboard.c_str(),
Clipboard.size()
);
} else {
// refuse the request
send_response_refuse();
}
else
{
respond.xselection.property= None;
}
respond.xselection.type= SelectionNotify;
respond.xselection.display= req->display;
respond.xselection.requestor= req->requestor;
respond.xselection.selection=req->selection;
respond.xselection.target= req->target;
respond.xselection.time = req->time;
XSendEvent (XDisplay, req->requestor,0,0,&respond);
XFlush (XDisplay);
}
break;
#if defined(_IRR_LINUX_X11_XINPUT2_)
case GenericEvent:
case GenericEvent:
{
XGenericEventCookie *cookie = &event.xcookie;
if (XGetEventData(XDisplay, cookie) && cookie->extension == XI_EXTENSIONS_OPCODE && XI_EXTENSIONS_OPCODE
@ -1799,58 +1855,105 @@ bool CIrrDeviceLinux::getGammaRamp( f32 &red, f32 &green, f32 &blue, f32 &bright
//! gets text from the clipboard
//! \return Returns 0 if no string is in there.
const c8* CIrrDeviceLinux::getTextFromClipboard() const
//! \return Returns 0 if no string is in there, otherwise utf-8 text.
const c8 *CIrrDeviceLinux::getTextFromClipboard() const
{
#if defined(_IRR_COMPILE_WITH_X11_)
Window ownerWindow = XGetSelectionOwner (XDisplay, X_ATOM_CLIPBOARD);
if ( ownerWindow == XWindow )
{
Window ownerWindow = XGetSelectionOwner(XDisplay, X_ATOM_CLIPBOARD);
if (ownerWindow == XWindow) {
return Clipboard.c_str();
}
Clipboard = "";
if (ownerWindow != None )
{
XConvertSelection (XDisplay, X_ATOM_CLIPBOARD, XA_STRING, XA_PRIMARY, ownerWindow, CurrentTime);
XFlush (XDisplay);
// check for data
Atom type;
int format;
unsigned long numItems, bytesLeft, dummy;
unsigned char *data;
XGetWindowProperty (XDisplay, ownerWindow,
XA_PRIMARY, // property name
0, // offset
0, // length (we only check for data, so 0)
0, // Delete 0==false
AnyPropertyType, // AnyPropertyType or property identifier
&type, // return type
&format, // return format
&numItems, // number items
&bytesLeft, // remaining bytes for partial reads
&data); // data
if ( bytesLeft > 0 )
{
// there is some data to get
int result = XGetWindowProperty (XDisplay, ownerWindow, XA_PRIMARY, 0,
bytesLeft, 0, AnyPropertyType, &type, &format,
&numItems, &dummy, &data);
if (result == Success)
Clipboard = (irr::c8*)data;
XFree (data);
}
Clipboard = "";
if (ownerWindow == None) {
return Clipboard.c_str();
}
// delete the property to be set beforehand
XDeleteProperty(XDisplay, XWindow, XA_PRIMARY);
XConvertSelection(XDisplay, X_ATOM_CLIPBOARD, X_ATOM_UTF8_STRING, XA_PRIMARY,
XWindow, CurrentTime);
XFlush(XDisplay);
// wait for event via a blocking call
XEvent event_ret;
XIfEvent(XDisplay, &event_ret, [](Display *_display, XEvent *event, XPointer arg) {
return (Bool) (event->type == SelectionNotify &&
event->xselection.requestor == *(Window *)arg &&
event->xselection.selection == X_ATOM_CLIPBOARD &&
event->xselection.target == X_ATOM_UTF8_STRING);
}, (XPointer)&XWindow);
_IRR_DEBUG_BREAK_IF(!(event_ret.type == SelectionNotify &&
event_ret.xselection.requestor == XWindow &&
event_ret.xselection.selection == X_ATOM_CLIPBOARD &&
event_ret.xselection.target == X_ATOM_UTF8_STRING));
Atom property_set = event_ret.xselection.property;
if (event_ret.xselection.property == None) {
// request failed => empty string
return Clipboard.c_str();
}
// check for data
Atom type;
int format;
unsigned long numItems, bytesLeft, dummy;
unsigned char *data = nullptr;
XGetWindowProperty (XDisplay, XWindow,
property_set, // property name
0, // offset
0, // length (we only check for data, so 0)
0, // Delete 0==false
AnyPropertyType, // AnyPropertyType or property identifier
&type, // return type
&format, // return format
&numItems, // number items
&bytesLeft, // remaining bytes for partial reads
&data); // data
if (data) {
XFree(data);
data = nullptr;
}
// for debugging:
//~ {
//~ char *type_name = XGetAtomName(XDisplay, type);
//~ fprintf(stderr, "CIrrDeviceLinux::getTextFromClipboard: actual type: %s (=%ld)\n",
//~ type_name, type);
//~ XFree(type_name);
//~ }
if (type != X_ATOM_UTF8_STRING && type != X_ATOM_UTF8_MIME_TYPE) {
os::Printer::log("CIrrDeviceLinux::getTextFromClipboard: did not get utf-8 string",
ELL_WARNING);
return Clipboard.c_str();
}
if (bytesLeft > 0) {
// there is some data to get
int result = XGetWindowProperty (XDisplay, XWindow, property_set, 0,
bytesLeft, 0, AnyPropertyType, &type, &format,
&numItems, &dummy, &data);
if (result == Success)
Clipboard = (irr::c8 *)data;
XFree (data);
}
// delete the property again, to inform the owner about the successful transfer
XDeleteProperty(XDisplay, XWindow, property_set);
return Clipboard.c_str();
#else
return 0;
return nullptr;
#endif
}
//! copies text to the clipboard
void CIrrDeviceLinux::copyToClipboard(const c8* text) const
void CIrrDeviceLinux::copyToClipboard(const c8 *text) const
{
#if defined(_IRR_COMPILE_WITH_X11_)
// Actually there is no clipboard on X but applications just say they own the clipboard and return text when asked.
@ -1858,6 +1961,10 @@ void CIrrDeviceLinux::copyToClipboard(const c8* text) const
Clipboard = text;
XSetSelectionOwner (XDisplay, X_ATOM_CLIPBOARD, XWindow, CurrentTime);
XFlush (XDisplay);
Window owner = XGetSelectionOwner(XDisplay, X_ATOM_CLIPBOARD);
if (owner != XWindow) {
os::Printer::log("CIrrDeviceLinux::copyToClipboard: failed to set owner", ELL_WARNING);
}
#endif
}
@ -1900,8 +2007,9 @@ void CIrrDeviceLinux::initXAtoms()
#ifdef _IRR_COMPILE_WITH_X11_
X_ATOM_CLIPBOARD = XInternAtom(XDisplay, "CLIPBOARD", False);
X_ATOM_TARGETS = XInternAtom(XDisplay, "TARGETS", False);
X_ATOM_UTF8_STRING = XInternAtom (XDisplay, "UTF8_STRING", False);
X_ATOM_TEXT = XInternAtom (XDisplay, "TEXT", False);
X_ATOM_UTF8_STRING = XInternAtom(XDisplay, "UTF8_STRING", False);
X_ATOM_UTF8_MIME_TYPE = XInternAtom(XDisplay, "text/plain;charset=utf-8", False);
X_ATOM_TEXT = XInternAtom(XDisplay, "TEXT", False);
X_ATOM_NETWM_MAXIMIZE_VERT = XInternAtom(XDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", true);
X_ATOM_NETWM_MAXIMIZE_HORZ = XInternAtom(XDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", true);
X_ATOM_NETWM_STATE = XInternAtom(XDisplay, "_NET_WM_STATE", true);

View File

@ -108,12 +108,13 @@ namespace irr
virtual bool getGammaRamp( f32 &red, f32 &green, f32 &blue, f32 &brightness, f32 &contrast ) _IRR_OVERRIDE_;
//! gets text from the clipboard
//! \return Returns 0 if no string is in there.
virtual const c8* getTextFromClipboard() const;
//! \return Returns 0 if no string is in there, otherwise utf-8 text.
virtual const c8 *getTextFromClipboard() const;
//! copies text to the clipboard
//! This sets the clipboard selection and _not_ the primary selection which you have on X on the middle mouse button.
virtual void copyToClipboard(const c8* text) const;
//! @param text The text in utf-8
virtual void copyToClipboard(const c8 *text) const;
//! Remove all messages pending in the system message loop
virtual void clearSystemMessages() _IRR_OVERRIDE_;
@ -425,6 +426,7 @@ namespace irr
XIM XInputMethod;
XIC XInputContext;
bool HasNetWM;
// text is utf-8
mutable core::stringc Clipboard;
#endif
u32 Width, Height;

View File

@ -56,7 +56,8 @@ const core::stringc& COSOperator::getOperatingSystemVersion() const
//! copies text to the clipboard
void COSOperator::copyToClipboard(const c8* text) const
//! \param text: text in utf-8
void COSOperator::copyToClipboard(const c8 *text) const
{
if (strlen(text)==0)
return;
@ -103,7 +104,7 @@ void COSOperator::copyToClipboard(const c8* text) const
//! gets text from the clipboard
//! \return Returns 0 if no string is in there.
//! \return Returns 0 if no string is in there, otherwise an utf-8 string.
const c8* COSOperator::getTextFromClipboard() const
{
#if defined(_IRR_XBOX_PLATFORM_)

View File

@ -27,10 +27,11 @@ public:
virtual const core::stringc& getOperatingSystemVersion() const _IRR_OVERRIDE_;
//! copies text to the clipboard
virtual void copyToClipboard(const c8* text) const _IRR_OVERRIDE_;
//! \param text: text in utf-8
virtual void copyToClipboard(const c8 *text) const _IRR_OVERRIDE_;
//! gets text from the clipboard
//! \return Returns 0 if no string is in there.
//! \return Returns 0 if no string is in there, otherwise an utf-8 string.
virtual const c8* getTextFromClipboard() const _IRR_OVERRIDE_;
//! gets the total and available system RAM in kB