]> git.mxchange.org Git - flightgear.git/blob - src/Scripting/ClipboardX11.cxx
Update for nasal::Ghost changes
[flightgear.git] / src / Scripting / ClipboardX11.cxx
1 // X11 implementation of clipboard access for Nasal
2 //
3 // Copyright (C) 2012  Thomas Geymayer <tomgey@gmail.com>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18
19 /*
20  * See the following links for more information on X11 clipboard:
21  *
22  * http://www.jwz.org/doc/x-cut-and-paste.html
23  * http://michael.toren.net/mirrors/doc/X-copy+paste.txt
24  * https://github.com/kfish/xsel/blob/master/xsel.c
25  */
26
27 #include "NasalClipboard.hxx"
28
29 #include <simgear/debug/logstream.hxx>
30
31 #include <X11/Xlib.h>
32 #include <X11/Xatom.h>
33
34 class ClipboardX11:
35   public NasalClipboard
36 {
37   public:
38     ClipboardX11():
39       _display( XOpenDisplay(NULL) ),
40       _window( XCreateSimpleWindow(
41         _display,
42         DefaultRootWindow(_display),
43         0, 0, 1, 1, // dummy dimensions -> window will never be mapped
44         0,
45         0, 0
46       ) ),
47       _atom_targets( XInternAtom(_display, "TARGETS", False) ),
48       _atom_text( XInternAtom(_display, "TEXT", False) ),
49       _atom_utf8( XInternAtom(_display, "UTF8_STRING", False) ),
50       _atom_primary( XInternAtom(_display, "PRIMARY", False) ),
51       _atom_clipboard( XInternAtom(_display, "CLIPBOARD", False) )
52     {
53       assert(_display);
54     }
55
56     virtual ~ClipboardX11()
57     {
58       // Ensure we get rid of any selection ownership
59       if( XGetSelectionOwner(_display, _atom_primary) )
60         XSetSelectionOwner(_display, _atom_primary, None, CurrentTime);
61       if( XGetSelectionOwner(_display, _atom_clipboard) )
62         XSetSelectionOwner(_display, _atom_clipboard, None, CurrentTime);
63     }
64
65     /**
66      * We need to run an event queue to check for selection request
67      */
68     virtual void update()
69     {
70       while( XPending(_display) )
71       {
72         XEvent event;
73         XNextEvent(_display, &event);
74         handleEvent(event);
75       }
76     }
77
78     /**
79      * Get clipboard contents as text
80      */
81     virtual std::string getText(Type type)
82     {
83       Atom atom_type = typeToAtom(type);
84
85       //Request a list of possible conversions
86       XConvertSelection( _display, atom_type, _atom_targets, atom_type,
87                          _window, CurrentTime );
88
89       Atom requested_type = None;
90       bool sent_request = false;
91
92       for(int cnt = 0; cnt < 5;)
93       {
94         XEvent event;
95         XNextEvent(_display, &event);
96
97         if( event.type != SelectionNotify )
98         {
99           handleEvent(event);
100           continue;
101         }
102
103         Atom target = event.xselection.target;
104         if(event.xselection.property == None)
105         {
106           if( target == _atom_targets )
107             // If TARGETS can not be converted no selection is available
108             break;
109
110           SG_LOG
111           (
112             SG_NASAL,
113             SG_WARN,
114             "ClipboardX11::getText: Conversion failed: "
115                                    "target=" << getAtomName(target)
116           );
117           break;
118         }
119
120         //If we're being given a list of targets (possible conversions)
121         if(target == _atom_targets && !sent_request)
122         {
123           sent_request = true;
124           requested_type = XA_STRING; // TODO select other type
125           XConvertSelection( _display, atom_type, requested_type, atom_type,
126                              _window, CurrentTime );
127         }
128         else if(target == requested_type)
129         {
130           Property prop = readProperty(_window, atom_type);
131           if( prop.format != 8 )
132           {
133             SG_LOG
134             (
135               SG_NASAL,
136               SG_WARN,
137               "ClipboardX11::getText: can only handle 8-bit data (is "
138                                    << prop.format << "-bit) -> retry "
139                                    << cnt++
140             );
141             XFree(prop.data);
142             continue;
143           }
144
145           std::string result((const char*)prop.data, prop.num_items);
146           XFree(prop.data);
147
148           return result;
149         }
150         else
151         {
152           SG_LOG
153           (
154             SG_NASAL,
155             SG_WARN,
156             "ClipboardX11::getText: wrong target: " << getAtomName(target)
157           );
158           break;
159         }
160       }
161
162       return std::string();
163     }
164
165     /**
166      * Set clipboard contents as text
167      */
168     virtual bool setText(const std::string& text, Type type)
169     {
170       Atom atom_type = typeToAtom(type);
171       XSetSelectionOwner(_display, atom_type, _window, CurrentTime);
172       if( XGetSelectionOwner(_display, atom_type) != _window )
173       {
174         SG_LOG
175         (
176           SG_NASAL,
177           SG_ALERT,
178           "ClipboardX11::setText: failed to get selection owner!"
179         );
180         return false;
181       }
182
183       // We need to store the text for sending it to another application upon
184       // request
185       if( type == CLIPBOARD )
186         _clipboard = text;
187       else
188         _selection = text;
189
190       return true;
191     }
192
193   protected:
194
195     Display    *_display;
196     Window      _window;
197     Atom        _atom_targets,
198                 _atom_text,
199                 _atom_utf8,
200                 _atom_primary,
201                 _atom_clipboard;
202
203     std::string _clipboard,
204                 _selection;
205
206     void handleEvent(const XEvent& event)
207     {
208       switch( event.type )
209       {
210         case SelectionRequest:
211           handleSelectionRequest(event.xselectionrequest);
212           break;
213         case SelectionClear:
214           if( event.xselectionclear.selection == _atom_clipboard )
215             _clipboard.clear();
216           else
217             _selection.clear();
218           break;
219         default:
220           SG_LOG
221           (
222             SG_NASAL,
223             SG_WARN,
224             "ClipboardX11: unexpected XEvent: " << event.type
225           );
226           break;
227       }
228     }
229
230     void handleSelectionRequest(const XSelectionRequestEvent& sel_req)
231     {
232       SG_LOG
233       (
234         SG_NASAL,
235         SG_DEBUG,
236         "ClipboardX11: handle selection request: "
237                       "selection=" << getAtomName(sel_req.selection) << ", "
238                       "target=" << getAtomName(sel_req.target)
239       );
240
241       const std::string& buf = sel_req.selection == _atom_clipboard
242                              ? _clipboard
243                              : _selection;
244
245       // Prepare response to notify whether we have written to the property or
246       // are unable to do the conversion
247       XSelectionEvent response;
248       response.type = SelectionNotify;
249       response.display = sel_req.display;
250       response.requestor = sel_req.requestor;
251       response.selection = sel_req.selection;
252       response.target = sel_req.target;
253       response.property = sel_req.property;
254       response.time = sel_req.time;
255
256       if( sel_req.target == _atom_targets )
257       {
258         static Atom supported[] = {
259           XA_STRING,
260           _atom_text,
261           _atom_utf8
262         };
263
264         changeProperty
265         (
266           sel_req.requestor,
267           sel_req.property,
268           sel_req.target,
269           &supported,
270           sizeof(supported)
271         );
272       }
273       else if(    sel_req.target == XA_STRING
274                || sel_req.target == _atom_text
275                || sel_req.target == _atom_utf8 )
276       {
277         changeProperty
278         (
279           sel_req.requestor,
280           sel_req.property,
281           sel_req.target,
282           buf.data(),
283           buf.size()
284         );
285       }
286       else
287       {
288         // Notify requestor that we are unable to perform requested conversion
289         response.property = None;
290       }
291
292       XSendEvent(_display, sel_req.requestor, False, 0, (XEvent*)&response);
293     }
294
295     void changeProperty( Window w,
296                          Atom prop,
297                          Atom type,
298                          const void *data,
299                          size_t size )
300     {
301       XChangeProperty
302       (
303         _display, w, prop, type, 8, PropModeReplace,
304         static_cast<const unsigned char*>(data), size
305       );
306     }
307
308     struct Property
309     {
310       unsigned char *data;
311       int format;
312       unsigned long num_items;
313       Atom type;
314     };
315
316     // Get all data from a property
317     Property readProperty(Window w, Atom property)
318     {
319       Atom actual_type;
320       int actual_format;
321       unsigned long nitems;
322       unsigned long bytes_after;
323       unsigned char *ret = 0;
324
325       int read_bytes = 1024;
326
327       //Keep trying to read the property until there are no
328       //bytes unread.
329       do
330       {
331         if( ret )
332           XFree(ret);
333
334         XGetWindowProperty
335         (
336           _display, w, property, 0, read_bytes, False, AnyPropertyType,
337           &actual_type, &actual_format, &nitems, &bytes_after,
338           &ret
339         );
340
341         read_bytes *= 2;
342       } while( bytes_after );
343
344       Property p = {ret, actual_format, nitems, actual_type};
345       return p;
346     }
347
348     std::string getAtomName(Atom atom) const
349     {
350       return atom == None ? "None" : XGetAtomName(_display, atom);
351     }
352
353     Atom typeToAtom(Type type) const
354     {
355       return type == CLIPBOARD ? _atom_clipboard : _atom_primary;
356     }
357
358 };
359
360 //------------------------------------------------------------------------------
361 NasalClipboard::Ptr NasalClipboard::create()
362 {
363   return NasalClipboard::Ptr(new ClipboardX11);
364 }