diff --git a/include/irrString.h b/include/irrString.h index 0b57864a..a871c24c 100644 --- a/include/irrString.h +++ b/include/irrString.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace irr { @@ -36,6 +37,7 @@ outside the string class for explicit use. template > class string; static size_t multibyteToWString(string& destination, const char* source, u32 sourceSize); +static size_t wStringToMultibyte(string& destination, const wchar_t* source, u32 sourceSize); inline s32 isdigit(s32 c); enum eLocaleID @@ -1424,6 +1426,7 @@ public: } friend size_t multibyteToWString(string& destination, const char* source, u32 sourceSize); + friend size_t wStringToMultibyte(string& destination, const wchar_t* source, u32 sourceSize); private: @@ -1517,6 +1520,53 @@ static size_t multibyteToWString(string& destination, const char* sourc } } +//! Same as multibyteToWString, but the other way around +static inline size_t wStringToMultibyte(string& destination, const core::string& source) +{ + return wStringToMultibyte(destination, source.c_str(), (u32)source.size()); +} + +//! Same as multibyteToWString, but the other way around +static inline size_t wStringToMultibyte(string& 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& 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 diff --git a/source/Irrlicht/CGUIEditBox.cpp b/source/Irrlicht/CGUIEditBox.cpp index 5d25601a..9c95eff4 100644 --- a/source/Irrlicht/CGUIEditBox.cpp +++ b/source/Irrlicht/CGUIEditBox.cpp @@ -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; diff --git a/source/Irrlicht/CIrrDeviceLinux.cpp b/source/Irrlicht/CIrrDeviceLinux.cpp index 3e7f5bfd..c23a02b7 100644 --- a/source/Irrlicht/CIrrDeviceLinux.cpp +++ b/source/Irrlicht/CIrrDeviceLinux.cpp @@ -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); diff --git a/source/Irrlicht/CIrrDeviceLinux.h b/source/Irrlicht/CIrrDeviceLinux.h index 066140e8..52dec838 100644 --- a/source/Irrlicht/CIrrDeviceLinux.h +++ b/source/Irrlicht/CIrrDeviceLinux.h @@ -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; diff --git a/source/Irrlicht/COSOperator.cpp b/source/Irrlicht/COSOperator.cpp index 0867b3d4..64f841f4 100644 --- a/source/Irrlicht/COSOperator.cpp +++ b/source/Irrlicht/COSOperator.cpp @@ -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_) diff --git a/source/Irrlicht/COSOperator.h b/source/Irrlicht/COSOperator.h index e76ba01a..2d47695f 100644 --- a/source/Irrlicht/COSOperator.h +++ b/source/Irrlicht/COSOperator.h @@ -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