]> git.mxchange.org Git - flightgear.git/commitdiff
Complete X11 clipboard support.
authorThomas Geymayer <tomgey@gmail.com>
Sun, 5 Aug 2012 09:19:24 +0000 (11:19 +0200)
committerThomas Geymayer <tomgey@gmail.com>
Sun, 5 Aug 2012 09:19:24 +0000 (11:19 +0200)
 - Now ClipboarX11 also supports writing to the clipboard and
   sending the data to another application if requested.

src/Scripting/ClipboardX11.cxx
src/Scripting/NasalClipboard.cxx
src/Scripting/NasalClipboard.hxx
src/Scripting/NasalSys.cxx

index 6108c52425ab260384561adade4bb14643625038..5e005011cc5db625c7670f82fdd54e191923ee12 100644 (file)
 // along with this program; if not, write to the Free Software
 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
+/*
+ * See the following links for more information on X11 clipboard:
+ *
+ * http://www.jwz.org/doc/x-cut-and-paste.html
+ * http://michael.toren.net/mirrors/doc/X-copy+paste.txt
+ * https://github.com/kfish/xsel/blob/master/xsel.c
+ */
+
 #include "NasalClipboard.hxx"
 
 #include <simgear/debug/logstream.hxx>
@@ -37,13 +45,34 @@ class ClipboardX11:
         0, 0
       ) ),
       _atom_targets( XInternAtom(_display, "TARGETS", False) ),
+      _atom_text( XInternAtom(_display, "TEXT", False) ),
+      _atom_utf8( XInternAtom(_display, "UTF8_STRING", False) ),
       _atom_primary( XInternAtom(_display, "PRIMARY", False) ),
       _atom_clipboard( XInternAtom(_display, "CLIPBOARD", False) )
     {
       assert(_display);
-      assert(_atom_targets != None);
-      assert(_atom_primary != None);
-      assert(_atom_clipboard != None);
+    }
+
+    virtual ~ClipboardX11()
+    {
+      // Ensure we get rid of any selection ownership
+      if( XGetSelectionOwner(_display, _atom_primary) )
+        XSetSelectionOwner(_display, _atom_primary, None, CurrentTime);
+      if( XGetSelectionOwner(_display, _atom_clipboard) )
+        XSetSelectionOwner(_display, _atom_clipboard, None, CurrentTime);
+    }
+
+    /**
+     * We need to run an event queue to check for selection request
+     */
+    virtual void update()
+    {
+      while( XPending(_display) )
+      {
+        XEvent event;
+        XNextEvent(_display, &event);
+        handleEvent(event);
+      }
     }
 
     /**
@@ -51,12 +80,11 @@ class ClipboardX11:
      */
     virtual std::string getText(Type type)
     {
-      Atom atom_type = (type == CLIPBOARD ? _atom_clipboard : _atom_primary);
+      Atom atom_type = typeToAtom(type);
 
       //Request a list of possible conversions
       XConvertSelection( _display, atom_type, _atom_targets, atom_type,
                          _window, CurrentTime );
-      XFlush(_display);
 
       Atom requested_type = None;
       bool sent_request = false;
@@ -66,67 +94,58 @@ class ClipboardX11:
         XEvent event;
         XNextEvent(_display, &event);
 
-        if( event.type == SelectionNotify )
+        if( event.type != SelectionNotify )
         {
-          Atom target = event.xselection.target;
-          if(event.xselection.property == None)
-          {
-            if( target == _atom_targets )
-              // If TARGETS can not be converted no selection is available
-              break;
+          handleEvent(event);
+          continue;
+        }
+
+        Atom target = event.xselection.target;
+        if(event.xselection.property == None)
+        {
+          if( target == _atom_targets )
+            // If TARGETS can not be converted no selection is available
+            break;
+
+          SG_LOG
+          (
+            SG_NASAL,
+            SG_WARN,
+            "ClipboardX11::getText: Conversion failed: "
+                                   "target=" << getAtomName(target)
+          );
+          break;
+        }
 
+        //If we're being given a list of targets (possible conversions)
+        if(target == _atom_targets && !sent_request)
+        {
+          sent_request = true;
+          requested_type = XA_STRING; // TODO select other type
+          XConvertSelection( _display, atom_type, requested_type, atom_type,
+                             _window, CurrentTime );
+        }
+        else if(target == requested_type)
+        {
+          Property prop = readProperty(_window, atom_type);
+          if( prop.format != 8 )
+          {
             SG_LOG
             (
               SG_NASAL,
               SG_WARN,
-              "ClipboardX11::getText: Conversion failed: "
-                                     "target=" << getAtomName(target)
+              "ClipboardX11::getText: can only handle 8-bit data (is "
+                                   << prop.format << "-bit) -> retry "
+                                   << cnt++
             );
-            break;
-          }
-          else
-          {
-            //If we're being given a list of targets (possible conversions)
-            if(target == _atom_targets && !sent_request)
-            {
-              sent_request = true;
-              requested_type = XA_STRING; // TODO select other type
-              XConvertSelection( _display, atom_type, requested_type, atom_type,
-                                 _window, CurrentTime );
-            }
-            else if(target == requested_type)
-            {
-              Property prop = readProperty(_window, atom_type);
-              if( prop.format != 8 )
-              {
-                SG_LOG
-                (
-                  SG_NASAL,
-                  SG_WARN,
-                  "ClipboardX11::getText: can only handle 8-bit data (is "
-                                       << prop.format << "-bit) -> retry "
-                                       << cnt++
-                );
-                XFree(prop.data);
-                continue;
-              }
-
-              std::string result((const char*)prop.data, prop.num_items);
-              XFree(prop.data);
-
-              return result;
-            }
-            else
-            {
-              SG_LOG
-              (
-                SG_NASAL,
-                SG_WARN,
-                "ClipboardX11::getText: wrong target: " << getAtomName(target)
-              );
-              break;
-            }
+            XFree(prop.data);
+            continue;
           }
+
+          std::string result((const char*)prop.data, prop.num_items);
+          XFree(prop.data);
+
+          return result;
         }
         else
         {
@@ -134,7 +153,7 @@ class ClipboardX11:
           (
             SG_NASAL,
             SG_WARN,
-            "ClipboardX11::getText: unexpected XEvent: " << event.type
+            "ClipboardX11::getText: wrong target: " << getAtomName(target)
           );
           break;
         }
@@ -148,13 +167,27 @@ class ClipboardX11:
      */
     virtual bool setText(const std::string& text, Type type)
     {
-      SG_LOG
-      (
-        SG_NASAL,
-        SG_ALERT,
-        "ClipboardX11::setText: not yet implemented!"
-      );
-      return false;
+      Atom atom_type = typeToAtom(type);
+      XSetSelectionOwner(_display, atom_type, _window, CurrentTime);
+      if( XGetSelectionOwner(_display, atom_type) != _window )
+      {
+        SG_LOG
+        (
+          SG_NASAL,
+          SG_ALERT,
+          "ClipboardX11::setText: failed to get selection owner!"
+        );
+        return false;
+      }
+
+      // We need to store the text for sending it to another application upon
+      // request
+      if( type == CLIPBOARD )
+        _clipboard = text;
+      else
+        _selection = text;
+
+      return true;
     }
 
   protected:
@@ -162,9 +195,116 @@ class ClipboardX11:
     Display    *_display;
     Window      _window;
     Atom        _atom_targets,
+                _atom_text,
+                _atom_utf8,
                 _atom_primary,
                 _atom_clipboard;
 
+    std::string _clipboard,
+                _selection;
+
+    void handleEvent(const XEvent& event)
+    {
+      switch( event.type )
+      {
+        case SelectionRequest:
+          handleSelectionRequest(event.xselectionrequest);
+          break;
+        case SelectionClear:
+          if( event.xselectionclear.selection == _atom_clipboard )
+            _clipboard.clear();
+          else
+            _selection.clear();
+          break;
+        default:
+          SG_LOG
+          (
+            SG_NASAL,
+            SG_WARN,
+            "ClipboardX11: unexpected XEvent: " << event.type
+          );
+          break;
+      }
+    }
+
+    void handleSelectionRequest(const XSelectionRequestEvent& sel_req)
+    {
+      SG_LOG
+      (
+        SG_NASAL,
+        SG_DEBUG,
+        "ClipboardX11: handle selection request: "
+                      "selection=" << getAtomName(sel_req.selection) << ", "
+                      "target=" << getAtomName(sel_req.target)
+      );
+
+      const std::string& buf = sel_req.selection == _atom_clipboard
+                             ? _clipboard
+                             : _selection;
+
+      // Prepare response to notify whether we have written to the property or
+      // are unable to do the conversion
+      XSelectionEvent response;
+      response.type = SelectionNotify;
+      response.display = sel_req.display;
+      response.requestor = sel_req.requestor;
+      response.selection = sel_req.selection;
+      response.target = sel_req.target;
+      response.property = sel_req.property;
+      response.time = sel_req.time;
+
+      if( sel_req.target == _atom_targets )
+      {
+        static Atom supported[] = {
+          XA_STRING,
+          _atom_text,
+          _atom_utf8
+        };
+
+        changeProperty
+        (
+          sel_req.requestor,
+          sel_req.property,
+          sel_req.target,
+          &supported,
+          sizeof(supported)
+        );
+      }
+      else if(    sel_req.target == XA_STRING
+               || sel_req.target == _atom_text
+               || sel_req.target == _atom_utf8 )
+      {
+        changeProperty
+        (
+          sel_req.requestor,
+          sel_req.property,
+          sel_req.target,
+          buf.data(),
+          buf.size()
+        );
+      }
+      else
+      {
+        // Notify requestor that we are unable to perform requested conversion
+        response.property = None;
+      }
+
+      XSendEvent(_display, sel_req.requestor, False, 0, (XEvent*)&response);
+    }
+
+    void changeProperty( Window w,
+                         Atom prop,
+                         Atom type,
+                         const void *data,
+                         size_t size )
+    {
+      XChangeProperty
+      (
+        _display, w, prop, type, 8, PropModeReplace,
+        static_cast<const unsigned char*>(data), size
+      );
+    }
+
     struct Property
     {
       unsigned char *data;
@@ -179,7 +319,7 @@ class ClipboardX11:
       int actual_format;
       unsigned long nitems;
       unsigned long bytes_after;
-      unsigned char *ret=0;
+      unsigned char *ret = 0;
 
       int read_bytes = 1024;
 
@@ -204,11 +344,16 @@ class ClipboardX11:
       return p;
     }
 
-    std::string getAtomName(Atom atom)
+    std::string getAtomName(Atom atom) const
     {
       return atom == None ? "None" : XGetAtomName(_display, atom);
     }
 
+    Atom typeToAtom(Type type) const
+    {
+      return type == CLIPBOARD ? _atom_clipboard : _atom_primary;
+    }
+
 };
 
 //------------------------------------------------------------------------------
index db148dce0a9875d24d61a91eba3d4b6a5d4d078a..6ac1462633893f365370e63afff85c578ab18f36 100644 (file)
@@ -34,18 +34,20 @@ static NasalClipboard::Type parseType(naContext c, int argc, naRef* args, int i)
 {
   if( argc > i )
   {
-    if( !naIsString(args[i]) )
-      naRuntimeError(c, "clipboard: invalid arg (not a string)");
-
-    std::string type_str( naStr_data(args[i]) );
-    boost::to_upper(type_str);
-
-    if( type_str == "CLIPBOARD" )
-      return NasalClipboard::CLIPBOARD;
-    else if( type_str == "PRIMARY" || type_str == "SELECTION" )
-      return NasalClipboard::PRIMARY;
-    else
-      naRuntimeError(c, "clipboard: unknown clipboard type");
+    if( naIsNum(args[i]) )
+    {
+      if( static_cast<int>(args[i].num) == NasalClipboard::CLIPBOARD )
+        return NasalClipboard::CLIPBOARD;
+      if( static_cast<int>(args[i].num) == NasalClipboard::PRIMARY )
+        return NasalClipboard::PRIMARY;
+    }
+
+    naRuntimeError
+    (
+      c,
+      "clipboard: invalid arg "
+      "(expected clipboard.CLIPBOARD or clipboard.SELECTION)"
+    );
   }
 
   return NasalClipboard::CLIPBOARD;
@@ -56,7 +58,7 @@ static naRef f_setClipboardText(naContext c, naRef me, int argc, naRef* args)
 {
   if( argc < 1 || argc > 2 )
     naRuntimeError( c, "clipboard.setText() expects 1 or 2 arguments: "
-                       "text, [, type = \"CLIPBOARD\"]" );
+                       "text, [, type = clipboard.CLIPBOARD]" );
 
   if( !naIsString(args[0]) )
     naRuntimeError(c, "clipboard.setText() invalid arg (arg 0 not a string)");
@@ -74,7 +76,7 @@ static naRef f_getClipboardText(naContext c, naRef me, int argc, naRef* args)
 {
   if( argc > 1 )
     naRuntimeError(c, "clipboard.getText() accepts max 1 arg: "
-                      "[type = \"CLIPBOARD\"]" );
+                      "[type = clipboard.CLIPBOARD]" );
 
   const std::string& text =
     NasalClipboard::getInstance()->getText(parseType(c, argc, args, 0));
@@ -86,11 +88,16 @@ static naRef f_getClipboardText(naContext c, naRef me, int argc, naRef* args)
 }
 
 //------------------------------------------------------------------------------
-// Table of extension functions, terminate with 0,0
+// Table of extension functions
 static struct {const char* name; naCFunction func; } funcs[] = {
   { "setText", f_setClipboardText },
-  { "getText", f_getClipboardText },
-  { 0,0 } // TERMINATION
+  { "getText", f_getClipboardText }
+};
+
+// Table of extension symbols
+static struct {const char* name; naRef val; } symbols[] = {
+  { "CLIPBOARD", naNum(NasalClipboard::CLIPBOARD) },
+  { "SELECTION", naNum(NasalClipboard::PRIMARY) }
 };
 
 //------------------------------------------------------------------------------
@@ -111,7 +118,7 @@ void NasalClipboard::init(FGNasalSys *nasal)
 
   nasal->globalsSet("clipboard", _clipboard_hash);
 
-  for(size_t i=0;funcs[i].name;i++)
+  for( size_t i = 0; i < sizeof(funcs)/sizeof(funcs[0]); ++i )
   {
     nasal->hashset
     (
@@ -120,7 +127,14 @@ void NasalClipboard::init(FGNasalSys *nasal)
       naNewFunc(nasal->context(), naNewCCode(nasal->context(), funcs[i].func))
     );
 
-    SG_LOG(SG_NASAL, SG_DEBUG, "Adding clipboard function: " << funcs[i].name );
+    SG_LOG(SG_NASAL, SG_DEBUG, "Adding clipboard function: " << funcs[i].name);
+  }
+
+  for( size_t i = 0; i < sizeof(symbols)/sizeof(symbols[0]); ++i )
+  {
+    nasal->hashset(_clipboard_hash, symbols[i].name, symbols[i].val);
+
+    SG_LOG(SG_NASAL, SG_DEBUG, "Adding clipboard symbol: " << symbols[i].name);
   }
 }
 
index fca3979c6ddab5aa311b874dafaaf0b50f3909b0..c99b1d54a4acf37916fbcd759b2767a4db8c0f9f 100644 (file)
@@ -41,6 +41,7 @@ class NasalClipboard
 
     typedef boost::shared_ptr<NasalClipboard> Ptr;
 
+    virtual void update() {}
     virtual std::string getText(Type type = CLIPBOARD) = 0;
     virtual bool setText( const std::string& text,
                           Type type = CLIPBOARD ) = 0;
index 5dd28ed1ba6a169f17adb83d7171a3b0259c0e7f..a3983015d753942ace0f7f2fe012a18c69fa76f4 100644 (file)
@@ -588,6 +588,9 @@ void FGNasalSys::init()
 
 void FGNasalSys::update(double)
 {
+    if( NasalClipboard::getInstance() )
+        NasalClipboard::getInstance()->update();
+
     if(!_dead_listener.empty()) {
         vector<FGNasalListener *>::iterator it, end = _dead_listener.end();
         for(it = _dead_listener.begin(); it != end; ++it) delete *it;