]> git.lizzy.rs Git - irrlicht.git/commitdiff
Fix X11 selections (#55)
authorDS <vorunbekannt75@web.de>
Mon, 30 Aug 2021 19:44:56 +0000 (21:44 +0200)
committerGitHub <noreply@github.com>
Mon, 30 Aug 2021 19:44:56 +0000 (21:44 +0200)
This fixes all the issues with the X11 selection in addition to switching the clipboard to always be UTF-8.

include/irrString.h
source/Irrlicht/CGUIEditBox.cpp
source/Irrlicht/CIrrDeviceLinux.cpp
source/Irrlicht/CIrrDeviceLinux.h
source/Irrlicht/COSOperator.cpp
source/Irrlicht/COSOperator.h

index 0b57864af2ca36d9001fdb07b7ac24e0562e7f9f..a871c24c0d209db7d3cc224e3bbead7f161018c9 100644 (file)
@@ -11,6 +11,7 @@
 #include <stdio.h>\r
 #include <string.h>\r
 #include <stdlib.h>\r
+#include <wchar.h>\r
 \r
 namespace irr\r
 {\r
@@ -36,6 +37,7 @@ outside the string class for explicit use.
 template <typename T, typename TAlloc = irrAllocator<T> >\r
 class string;\r
 static size_t multibyteToWString(string<wchar_t>& destination, const char* source, u32 sourceSize);\r
+static size_t wStringToMultibyte(string<c8>& destination, const wchar_t* source, u32 sourceSize);\r
 inline s32 isdigit(s32 c);\r
 \r
 enum eLocaleID\r
@@ -1424,6 +1426,7 @@ public:
        }\r
 \r
        friend size_t multibyteToWString(string<wchar_t>& destination, const char* source, u32 sourceSize);\r
+       friend size_t wStringToMultibyte(string<c8>& destination, const wchar_t* source, u32 sourceSize);\r
 \r
 private:\r
 \r
@@ -1517,6 +1520,53 @@ static size_t multibyteToWString(string<wchar_t>& destination, const char* sourc
        }\r
 }\r
 \r
+//! Same as multibyteToWString, but the other way around\r
+static inline size_t wStringToMultibyte(string<c8>& destination, const core::string<wchar_t>& source)\r
+{\r
+       return wStringToMultibyte(destination, source.c_str(), (u32)source.size());\r
+}\r
+\r
+//! Same as multibyteToWString, but the other way around\r
+static inline size_t wStringToMultibyte(string<c8>& destination, const wchar_t* source)\r
+{\r
+       const u32 s = source ? (u32)wcslen(source) : 0;\r
+       return wStringToMultibyte(destination, source, s);\r
+}\r
+\r
+//! Same as multibyteToWString, but the other way around\r
+static size_t wStringToMultibyte(string<c8>& destination, const wchar_t* source, u32 sourceSize)\r
+{\r
+       if ( sourceSize )\r
+       {\r
+               destination.reserve(sourceSize+1);\r
+#if defined(_MSC_VER)\r
+#pragma warning(push)\r
+#pragma warning(disable: 4996) // 'wcstombs': This function or variable may be unsafe. Consider using wcstombs_s instead.\r
+#endif\r
+               const size_t written = wcstombs(destination.array, source, (size_t)sourceSize);\r
+#if defined(_MSC_VER)\r
+#pragma warning(pop)\r
+#endif\r
+               if ( written != (size_t)-1 )\r
+               {\r
+                       destination.used = (u32)written+1;\r
+                       destination.array[destination.used-1] = 0;\r
+               }\r
+               else\r
+               {\r
+                       // Likely character which got converted until the invalid character was encountered are in destination now.\r
+                       // And it seems even 0-terminated, but I found no documentation anywhere that this (the 0-termination) is guaranteed :-(\r
+                       destination.clear();\r
+               }\r
+               return written;\r
+       }\r
+       else\r
+       {\r
+               destination.clear();\r
+               return 0;\r
+       }\r
+}\r
+\r
 \r
 } // end namespace core\r
 } // end namespace irr\r
index 5d25601ac1173d1c43749767d7a6f3a47c606db3..9c95eff4a161bd55bb8fb00bb2b17486880312c9 100644 (file)
@@ -300,7 +300,7 @@ bool CGUIEditBox::processKey(const SEvent& event)
                                const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;\r
 \r
                                core::stringc s;\r
-                               s = Text.subString(realmbgn, realmend - realmbgn).c_str();\r
+                               wStringToMultibyte(s, Text.subString(realmbgn, realmend - realmbgn));\r
                                Operator->copyToClipboard(s.c_str());\r
                        }\r
                        break;\r
@@ -313,7 +313,7 @@ bool CGUIEditBox::processKey(const SEvent& event)
 \r
                                // copy\r
                                core::stringc sc;\r
-                               sc = Text.subString(realmbgn, realmend - realmbgn).c_str();\r
+                               wStringToMultibyte(sc, Text.subString(realmbgn, realmend - realmbgn));\r
                                Operator->copyToClipboard(sc.c_str());\r
 \r
                                if (isEnabled())\r
@@ -341,8 +341,8 @@ bool CGUIEditBox::processKey(const SEvent& event)
                                const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;\r
                                const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;\r
 \r
-                               // add new character\r
-                               const c8p = Operator->getTextFromClipboard();\r
+                               // add the string\r
+                               const c8 *p = Operator->getTextFromClipboard();\r
                                if (p)\r
                                {\r
                                        irr::core::stringw widep;\r
index 3e7f5bfd1d135d0361040eb500e8609625f45d25..c23a02b716e9c787c0e17626026d99b55e879bba 100644 (file)
@@ -90,6 +90,7 @@ namespace
        Atom X_ATOM_CLIPBOARD;\r
        Atom X_ATOM_TARGETS;\r
        Atom X_ATOM_UTF8_STRING;\r
+       Atom X_ATOM_UTF8_MIME_TYPE;\r
        Atom X_ATOM_TEXT;\r
        Atom X_ATOM_NETWM_MAXIMIZE_VERT;\r
        Atom X_ATOM_NETWM_MAXIMIZE_HORZ;\r
@@ -1010,49 +1011,104 @@ bool CIrrDeviceLinux::run()
 \r
                        case SelectionRequest:\r
                                {\r
-                                       XEvent respond;\r
                                        XSelectionRequestEvent *req = &(event.xselectionrequest);\r
-                                       if (  req->target == XA_STRING)\r
-                                       {\r
-                                               XChangeProperty (XDisplay,\r
+\r
+                                       auto send_response = [this, req](Atom property) {\r
+                                               XEvent response;\r
+                                               response.xselection.type = SelectionNotify;\r
+                                               response.xselection.display = req->display;\r
+                                               response.xselection.requestor = req->requestor;\r
+                                               response.xselection.selection = req->selection;\r
+                                               response.xselection.target = req->target;\r
+                                               response.xselection.property = property;\r
+                                               response.xselection.time = req->time;\r
+                                               XSendEvent (XDisplay, req->requestor, 0, 0, &response);\r
+                                               XFlush (XDisplay);\r
+                                       };\r
+                                       auto send_response_refuse = [&send_response] {\r
+                                               send_response(None);\r
+                                       };\r
+\r
+                                       // sets the required property to data of type type and\r
+                                       // sends the according response\r
+                                       auto set_property_and_notify = [this, req, &send_response]\r
+                                                       (Atom type, int format, const void *data, u32 data_size) {\r
+                                               XChangeProperty(XDisplay,\r
                                                                req->requestor,\r
-                                                               req->property, req->target,\r
-                                                               8, // format\r
+                                                               req->property,\r
+                                                               type,\r
+                                                               format,\r
                                                                PropModeReplace,\r
-                                                               (unsigned char*) Clipboard.c_str(),\r
-                                                               Clipboard.size());\r
-                                               respond.xselection.property = req->property;\r
+                                                               (const unsigned char *)data,\r
+                                                               data_size);\r
+                                               send_response(req->property);\r
+                                       };\r
+\r
+                                       if (req->selection != X_ATOM_CLIPBOARD ||\r
+                                                       req->owner != XWindow) {\r
+                                               // we are not the owner, refuse request\r
+                                               send_response_refuse();\r
+                                               break;\r
                                        }\r
-                                       else if ( req->target == X_ATOM_TARGETS )\r
-                                       {\r
-                                               long data[2];\r
 \r
-                                               data[0] = X_ATOM_TEXT;\r
-                                               data[1] = XA_STRING;\r
-\r
-                                               XChangeProperty (XDisplay, req->requestor,\r
-                                                               req->property, req->target,\r
-                                                               8, PropModeReplace,\r
-                                                               (unsigned char *) &data,\r
-                                                               sizeof (data));\r
-                                               respond.xselection.property = req->property;\r
+                                       // for debugging:\r
+                                       //~ {\r
+                                               //~ char *target_name = XGetAtomName(XDisplay, req->target);\r
+                                               //~ fprintf(stderr, "CIrrDeviceLinux::run: target: %s (=%ld)\n",\r
+                                                               //~ target_name, req->target);\r
+                                               //~ XFree(target_name);\r
+                                       //~ }\r
+\r
+                                       if (req->property == None) {\r
+                                               // req is from obsolete client, use target as property name\r
+                                               // and X_ATOM_UTF8_STRING as type\r
+                                               // Note: this was not tested and might be incorrect\r
+                                               os::Printer::log("CIrrDeviceLinux::run: SelectionRequest from obsolete client",\r
+                                                               ELL_WARNING);\r
+                                               XChangeProperty(XDisplay,\r
+                                                               req->requestor,\r
+                                                               req->target, X_ATOM_UTF8_STRING,\r
+                                                               8, // format = 8-bit\r
+                                                               PropModeReplace,\r
+                                                               (unsigned char *)Clipboard.c_str(),\r
+                                                               Clipboard.size());\r
+                                               send_response(req->target);\r
+                                               break;\r
                                        }\r
-                                       else\r
-                                       {\r
-                                               respond.xselection.property= None;\r
+\r
+                                       if (req->target == X_ATOM_TARGETS) {\r
+                                               Atom data[] = {\r
+                                                       X_ATOM_TARGETS,\r
+                                                       X_ATOM_TEXT,\r
+                                                       X_ATOM_UTF8_STRING,\r
+                                                       X_ATOM_UTF8_MIME_TYPE\r
+                                               };\r
+                                               set_property_and_notify(\r
+                                                               XA_ATOM,\r
+                                                               32, // Atom is long, we need to set 32 for longs\r
+                                                               &data,\r
+                                                               sizeof(data) / sizeof(*data)\r
+                                                       );\r
+\r
+                                       } else if (req->target == X_ATOM_TEXT ||\r
+                                                       req->target == X_ATOM_UTF8_STRING ||\r
+                                                       req->target == X_ATOM_UTF8_MIME_TYPE) {\r
+                                               set_property_and_notify(\r
+                                                               X_ATOM_UTF8_STRING,\r
+                                                               8,\r
+                                                               Clipboard.c_str(),\r
+                                                               Clipboard.size()\r
+                                                       );\r
+\r
+                                       } else {\r
+                                               // refuse the request\r
+                                               send_response_refuse();\r
                                        }\r
-                                       respond.xselection.type= SelectionNotify;\r
-                                       respond.xselection.display= req->display;\r
-                                       respond.xselection.requestor= req->requestor;\r
-                                       respond.xselection.selection=req->selection;\r
-                                       respond.xselection.target= req->target;\r
-                                       respond.xselection.time = req->time;\r
-                                       XSendEvent (XDisplay, req->requestor,0,0,&respond);\r
-                                       XFlush (XDisplay);\r
                                }\r
                                break;\r
+\r
 #if defined(_IRR_LINUX_X11_XINPUT2_)\r
-                               case GenericEvent:\r
+                       case GenericEvent:\r
                                {\r
                                        XGenericEventCookie *cookie = &event.xcookie;\r
                                        if (XGetEventData(XDisplay, cookie) && cookie->extension == XI_EXTENSIONS_OPCODE && XI_EXTENSIONS_OPCODE\r
@@ -1799,58 +1855,105 @@ bool CIrrDeviceLinux::getGammaRamp( f32 &red, f32 &green, f32 &blue, f32 &bright
 \r
 \r
 //! gets text from the clipboard\r
-//! \return Returns 0 if no string is in there.\r
-const c8CIrrDeviceLinux::getTextFromClipboard() const\r
+//! \return Returns 0 if no string is in there, otherwise utf-8 text.\r
+const c8 *CIrrDeviceLinux::getTextFromClipboard() const\r
 {\r
 #if defined(_IRR_COMPILE_WITH_X11_)\r
-       Window ownerWindow = XGetSelectionOwner (XDisplay, X_ATOM_CLIPBOARD);\r
-       if ( ownerWindow ==  XWindow )\r
-       {\r
+       Window ownerWindow = XGetSelectionOwner(XDisplay, X_ATOM_CLIPBOARD);\r
+       if (ownerWindow == XWindow) {\r
                return Clipboard.c_str();\r
        }\r
+\r
        Clipboard = "";\r
-       if (ownerWindow != None )\r
-       {\r
-               XConvertSelection (XDisplay, X_ATOM_CLIPBOARD, XA_STRING, XA_PRIMARY, ownerWindow, CurrentTime);\r
-               XFlush (XDisplay);\r
-\r
-               // check for data\r
-               Atom type;\r
-               int format;\r
-               unsigned long numItems, bytesLeft, dummy;\r
-               unsigned char *data;\r
-               XGetWindowProperty (XDisplay, ownerWindow,\r
-                               XA_PRIMARY, // property name\r
-                               0, // offset\r
-                               0, // length (we only check for data, so 0)\r
-                               0, // Delete 0==false\r
-                               AnyPropertyType, // AnyPropertyType or property identifier\r
-                               &type, // return type\r
-                               &format, // return format\r
-                               &numItems, // number items\r
-                               &bytesLeft, // remaining bytes for partial reads\r
-                               &data); // data\r
-               if ( bytesLeft > 0 )\r
-               {\r
-                       // there is some data to get\r
-                       int result = XGetWindowProperty (XDisplay, ownerWindow, XA_PRIMARY, 0,\r
-                                                                               bytesLeft, 0, AnyPropertyType, &type, &format,\r
-                                                                               &numItems, &dummy, &data);\r
-                       if (result == Success)\r
-                               Clipboard = (irr::c8*)data;\r
-                       XFree (data);\r
-               }\r
+\r
+       if (ownerWindow == None) {\r
+               return Clipboard.c_str();\r
+       }\r
+\r
+       // delete the property to be set beforehand\r
+       XDeleteProperty(XDisplay, XWindow, XA_PRIMARY);\r
+\r
+       XConvertSelection(XDisplay, X_ATOM_CLIPBOARD, X_ATOM_UTF8_STRING, XA_PRIMARY,\r
+                       XWindow, CurrentTime);\r
+       XFlush(XDisplay);\r
+\r
+       // wait for event via a blocking call\r
+       XEvent event_ret;\r
+       XIfEvent(XDisplay, &event_ret, [](Display *_display, XEvent *event, XPointer arg) {\r
+               return (Bool) (event->type == SelectionNotify &&\r
+                               event->xselection.requestor == *(Window *)arg &&\r
+                               event->xselection.selection == X_ATOM_CLIPBOARD &&\r
+                               event->xselection.target == X_ATOM_UTF8_STRING);\r
+       }, (XPointer)&XWindow);\r
+\r
+       _IRR_DEBUG_BREAK_IF(!(event_ret.type == SelectionNotify &&\r
+                       event_ret.xselection.requestor == XWindow &&\r
+                       event_ret.xselection.selection == X_ATOM_CLIPBOARD &&\r
+                       event_ret.xselection.target == X_ATOM_UTF8_STRING));\r
+\r
+       Atom property_set = event_ret.xselection.property;\r
+       if (event_ret.xselection.property == None) {\r
+               // request failed => empty string\r
+               return Clipboard.c_str();\r
+       }\r
+\r
+       // check for data\r
+       Atom type;\r
+       int format;\r
+       unsigned long numItems, bytesLeft, dummy;\r
+       unsigned char *data = nullptr;\r
+       XGetWindowProperty (XDisplay, XWindow,\r
+                       property_set, // property name\r
+                       0, // offset\r
+                       0, // length (we only check for data, so 0)\r
+                       0, // Delete 0==false\r
+                       AnyPropertyType, // AnyPropertyType or property identifier\r
+                       &type, // return type\r
+                       &format, // return format\r
+                       &numItems, // number items\r
+                       &bytesLeft, // remaining bytes for partial reads\r
+                       &data); // data\r
+       if (data) {\r
+               XFree(data);\r
+               data = nullptr;\r
        }\r
 \r
+       // for debugging:\r
+       //~ {\r
+               //~ char *type_name = XGetAtomName(XDisplay, type);\r
+               //~ fprintf(stderr, "CIrrDeviceLinux::getTextFromClipboard: actual type: %s (=%ld)\n",\r
+                               //~ type_name, type);\r
+               //~ XFree(type_name);\r
+       //~ }\r
+\r
+       if (type != X_ATOM_UTF8_STRING && type != X_ATOM_UTF8_MIME_TYPE) {\r
+               os::Printer::log("CIrrDeviceLinux::getTextFromClipboard: did not get utf-8 string",\r
+                               ELL_WARNING);\r
+               return Clipboard.c_str();\r
+       }\r
+\r
+       if (bytesLeft > 0) {\r
+               // there is some data to get\r
+               int result = XGetWindowProperty (XDisplay, XWindow, property_set, 0,\r
+                                                                       bytesLeft, 0, AnyPropertyType, &type, &format,\r
+                                                                       &numItems, &dummy, &data);\r
+               if (result == Success)\r
+                       Clipboard = (irr::c8 *)data;\r
+               XFree (data);\r
+       }\r
+\r
+       // delete the property again, to inform the owner about the successful transfer\r
+       XDeleteProperty(XDisplay, XWindow, property_set);\r
+\r
        return Clipboard.c_str();\r
 \r
 #else\r
-       return 0;\r
+       return nullptr;\r
 #endif\r
 }\r
 \r
 //! copies text to the clipboard\r
-void CIrrDeviceLinux::copyToClipboard(const c8text) const\r
+void CIrrDeviceLinux::copyToClipboard(const c8 *text) const\r
 {\r
 #if defined(_IRR_COMPILE_WITH_X11_)\r
        // Actually there is no clipboard on X but applications just say they own the clipboard and return text when asked.\r
@@ -1858,6 +1961,10 @@ void CIrrDeviceLinux::copyToClipboard(const c8* text) const
        Clipboard = text;\r
        XSetSelectionOwner (XDisplay, X_ATOM_CLIPBOARD, XWindow, CurrentTime);\r
        XFlush (XDisplay);\r
+       Window owner = XGetSelectionOwner(XDisplay, X_ATOM_CLIPBOARD);\r
+       if (owner != XWindow) {\r
+               os::Printer::log("CIrrDeviceLinux::copyToClipboard: failed to set owner", ELL_WARNING);\r
+       }\r
 #endif\r
 }\r
 \r
@@ -1900,8 +2007,9 @@ void CIrrDeviceLinux::initXAtoms()
 #ifdef _IRR_COMPILE_WITH_X11_\r
        X_ATOM_CLIPBOARD = XInternAtom(XDisplay, "CLIPBOARD", False);\r
        X_ATOM_TARGETS = XInternAtom(XDisplay, "TARGETS", False);\r
-       X_ATOM_UTF8_STRING = XInternAtom (XDisplay, "UTF8_STRING", False);\r
-       X_ATOM_TEXT = XInternAtom (XDisplay, "TEXT", False);\r
+       X_ATOM_UTF8_STRING = XInternAtom(XDisplay, "UTF8_STRING", False);\r
+       X_ATOM_UTF8_MIME_TYPE = XInternAtom(XDisplay, "text/plain;charset=utf-8", False);\r
+       X_ATOM_TEXT = XInternAtom(XDisplay, "TEXT", False);\r
        X_ATOM_NETWM_MAXIMIZE_VERT = XInternAtom(XDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", true);\r
        X_ATOM_NETWM_MAXIMIZE_HORZ = XInternAtom(XDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", true);\r
        X_ATOM_NETWM_STATE = XInternAtom(XDisplay, "_NET_WM_STATE", true);\r
index 066140e896e0376b85bf0956de7947a15207adf6..52dec838a4ac34c119ba8d5b6203933dc9f240e7 100644 (file)
@@ -108,12 +108,13 @@ namespace irr
                virtual bool getGammaRamp( f32 &red, f32 &green, f32 &blue, f32 &brightness, f32 &contrast ) _IRR_OVERRIDE_;\r
 \r
                //! gets text from the clipboard\r
-               //! \return Returns 0 if no string is in there.\r
-               virtual const c8getTextFromClipboard() const;\r
+               //! \return Returns 0 if no string is in there, otherwise utf-8 text.\r
+               virtual const c8 *getTextFromClipboard() const;\r
 \r
                //! copies text to the clipboard\r
                //! This sets the clipboard selection and _not_ the primary selection which you have on X on the middle mouse button.\r
-               virtual void copyToClipboard(const c8* text) const;\r
+               //! @param text The text in utf-8\r
+               virtual void copyToClipboard(const c8 *text) const;\r
 \r
                //! Remove all messages pending in the system message loop\r
                virtual void clearSystemMessages() _IRR_OVERRIDE_;\r
@@ -425,6 +426,7 @@ namespace irr
                XIM XInputMethod;\r
                XIC XInputContext;\r
                bool HasNetWM;\r
+               // text is utf-8\r
                mutable core::stringc Clipboard;\r
 #endif\r
                u32 Width, Height;\r
index 0867b3d4fea47d502cd1961e75806219fa49183a..64f841f40aa548a41f588071d3529f11c4ebb7e9 100644 (file)
@@ -56,7 +56,8 @@ const core::stringc& COSOperator::getOperatingSystemVersion() const
 \r
 \r
 //! copies text to the clipboard\r
-void COSOperator::copyToClipboard(const c8* text) const\r
+//! \param text: text in utf-8\r
+void COSOperator::copyToClipboard(const c8 *text) const\r
 {\r
        if (strlen(text)==0)\r
                return;\r
@@ -103,7 +104,7 @@ void COSOperator::copyToClipboard(const c8* text) const
 \r
 \r
 //! gets text from the clipboard\r
-//! \return Returns 0 if no string is in there.\r
+//! \return Returns 0 if no string is in there, otherwise an utf-8 string.\r
 const c8* COSOperator::getTextFromClipboard() const\r
 {\r
 #if defined(_IRR_XBOX_PLATFORM_)\r
index e76ba01a0f7b9d69b4b49f21089aaceebc0d5781..2d47695f345a1e17fa43651ae3c3de7d76773457 100644 (file)
@@ -27,10 +27,11 @@ public:
        virtual const core::stringc& getOperatingSystemVersion() const _IRR_OVERRIDE_;\r
 \r
        //! copies text to the clipboard\r
-       virtual void copyToClipboard(const c8* text) const _IRR_OVERRIDE_;\r
+       //! \param text: text in utf-8\r
+       virtual void copyToClipboard(const c8 *text) const _IRR_OVERRIDE_;\r
 \r
        //! gets text from the clipboard\r
-       //! \return Returns 0 if no string is in there.\r
+       //! \return Returns 0 if no string is in there, otherwise an utf-8 string.\r
        virtual const c8* getTextFromClipboard() const _IRR_OVERRIDE_;\r
 \r
        //! gets the total and available system RAM in kB\r