FlightGear next
ClipboardX11.cxx
Go to the documentation of this file.
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#include <cassert>
35
37 public NasalClipboard
38{
39 public:
41 // REVIEW: Memory Leak - 55,397 (104 direct, 55,293 indirect) bytes in 1 blocks are definitely lost
42 _display( XOpenDisplay(NULL) ),
43 _window( XCreateSimpleWindow(
45 DefaultRootWindow(_display),
46 0, 0, 1, 1, // dummy dimensions -> window will never be mapped
47 0,
48 0, 0
49 ) ),
50 _atom_targets( XInternAtom(_display, "TARGETS", False) ),
51 _atom_text( XInternAtom(_display, "TEXT", False) ),
52 _atom_utf8( XInternAtom(_display, "UTF8_STRING", False) ),
53 _atom_primary( XInternAtom(_display, "PRIMARY", False) ),
54 _atom_clipboard( XInternAtom(_display, "CLIPBOARD", False) )
55 {
56 assert(_display);
57 }
58
59 virtual ~ClipboardX11()
60 {
61 // Ensure we get rid of any selection ownership
62 if( XGetSelectionOwner(_display, _atom_primary) )
63 XSetSelectionOwner(_display, _atom_primary, None, CurrentTime);
64 if( XGetSelectionOwner(_display, _atom_clipboard) )
65 XSetSelectionOwner(_display, _atom_clipboard, None, CurrentTime);
66 }
67
71 virtual void update()
72 {
73 while( XPending(_display) )
74 {
75 XEvent event;
76 XNextEvent(_display, &event);
77 handleEvent(event);
78 }
79 }
80
84 virtual std::string getText(Type type)
85 {
86 Atom atom_type = typeToAtom(type);
87
88 //Request a list of possible conversions
89 XConvertSelection( _display, atom_type, _atom_targets, atom_type,
90 _window, CurrentTime );
91
92 Atom requested_type = None;
93 bool sent_request = false;
94
95 for(int cnt = 0; cnt < 5;)
96 {
97 XEvent event;
98 XNextEvent(_display, &event);
99
100 if( event.type != SelectionNotify )
101 {
102 handleEvent(event);
103 continue;
104 }
105
106 Atom target = event.xselection.target;
107 if(event.xselection.property == None)
108 {
109 if( target == _atom_targets )
110 // If TARGETS can not be converted no selection is available
111 break;
112
113 SG_LOG
114 (
115 SG_NASAL,
116 SG_WARN,
117 "ClipboardX11::getText: Conversion failed: "
118 "target=" << getAtomName(target)
119 );
120 break;
121 }
122
123 //If we're being given a list of targets (possible conversions)
124 if(target == _atom_targets && !sent_request)
125 {
126 sent_request = true;
127 requested_type = XA_STRING; // TODO select other type
128 XConvertSelection( _display, atom_type, requested_type, atom_type,
129 _window, CurrentTime );
130 }
131 else if(target == requested_type)
132 {
133 Property prop = readProperty(_window, atom_type);
134 if( prop.format != 8 )
135 {
136 SG_LOG
137 (
138 SG_NASAL,
139 SG_WARN,
140 "ClipboardX11::getText: can only handle 8-bit data (is "
141 << prop.format << "-bit) -> retry "
142 << cnt++
143 );
144 XFree(prop.data);
145 continue;
146 }
147
148 std::string result((const char*)prop.data, prop.num_items);
149 XFree(prop.data);
150
151 return result;
152 }
153 else
154 {
155 SG_LOG
156 (
157 SG_NASAL,
158 SG_WARN,
159 "ClipboardX11::getText: wrong target: " << getAtomName(target)
160 );
161 break;
162 }
163 }
164
165 return std::string();
166 }
167
171 virtual bool setText(const std::string& text, Type type)
172 {
173 Atom atom_type = typeToAtom(type);
174 XSetSelectionOwner(_display, atom_type, _window, CurrentTime);
175 if( XGetSelectionOwner(_display, atom_type) != _window )
176 {
177 SG_LOG
178 (
179 SG_NASAL,
180 SG_ALERT,
181 "ClipboardX11::setText: failed to get selection owner!"
182 );
183 return false;
184 }
185
186 // We need to store the text for sending it to another application upon
187 // request
188 if( type == CLIPBOARD )
189 _clipboard = text;
190 else
191 _selection = text;
192
193 return true;
194 }
195
196 protected:
197
198 Display *_display;
199 Window _window;
205
206 std::string _clipboard,
208
209 void handleEvent(const XEvent& event)
210 {
211 switch( event.type )
212 {
213 case SelectionRequest:
214 handleSelectionRequest(event.xselectionrequest);
215 break;
216 case SelectionClear:
217 if( event.xselectionclear.selection == _atom_clipboard )
218 _clipboard.clear();
219 else
220 _selection.clear();
221 break;
222 default:
223 SG_LOG
224 (
225 SG_NASAL,
226 SG_WARN,
227 "ClipboardX11: unexpected XEvent: " << event.type
228 );
229 break;
230 }
231 }
232
233 void handleSelectionRequest(const XSelectionRequestEvent& sel_req)
234 {
235 SG_LOG
236 (
237 SG_NASAL,
238 SG_DEBUG,
239 "ClipboardX11: handle selection request: "
240 "selection=" << getAtomName(sel_req.selection) << ", "
241 "target=" << getAtomName(sel_req.target)
242 );
243
244 const std::string& buf = sel_req.selection == _atom_clipboard
245 ? _clipboard
246 : _selection;
247
248 // Prepare response to notify whether we have written to the property or
249 // are unable to do the conversion
250 XSelectionEvent response;
251 response.type = SelectionNotify;
252 response.display = sel_req.display;
253 response.requestor = sel_req.requestor;
254 response.selection = sel_req.selection;
255 response.target = sel_req.target;
256 response.property = sel_req.property;
257 response.time = sel_req.time;
258
259 if( sel_req.target == _atom_targets )
260 {
261 static Atom supported[] = {
262 XA_STRING,
265 };
266
268 (
269 sel_req.requestor,
270 sel_req.property,
271 sel_req.target,
272 &supported,
273 sizeof(supported)
274 );
275 }
276 else if( sel_req.target == XA_STRING
277 || sel_req.target == _atom_text
278 || sel_req.target == _atom_utf8 )
279 {
281 (
282 sel_req.requestor,
283 sel_req.property,
284 sel_req.target,
285 buf.data(),
286 buf.size()
287 );
288 }
289 else
290 {
291 // Notify requestor that we are unable to perform requested conversion
292 response.property = None;
293 }
294
295 XSendEvent(_display, sel_req.requestor, False, 0, (XEvent*)&response);
296 }
297
298 void changeProperty( Window w,
299 Atom prop,
300 Atom type,
301 const void *data,
302 size_t size )
303 {
304 XChangeProperty
305 (
306 _display, w, prop, type, 8, PropModeReplace,
307 static_cast<const unsigned char*>(data), size
308 );
309 }
310
311 struct Property
312 {
313 unsigned char *data;
315 unsigned long num_items;
316 Atom type;
317 };
318
319 // Get all data from a property
320 Property readProperty(Window w, Atom property)
321 {
322 Atom actual_type;
323 int actual_format;
324 unsigned long nitems;
325 unsigned long bytes_after;
326 unsigned char *ret = 0;
327
328 int read_bytes = 1024;
329
330 //Keep trying to read the property until there are no
331 //bytes unread.
332 do
333 {
334 if( ret )
335 XFree(ret);
336
337 XGetWindowProperty
338 (
339 _display, w, property, 0, read_bytes, False, AnyPropertyType,
340 &actual_type, &actual_format, &nitems, &bytes_after,
341 &ret
342 );
343
344 read_bytes *= 2;
345 } while( bytes_after );
346
347 Property p = {ret, actual_format, nitems, actual_type};
348 return p;
349 }
350
351 std::string getAtomName(Atom atom) const
352 {
353 return atom == None ? "None" : XGetAtomName(_display, atom);
354 }
355
356 Atom typeToAtom(Type type) const
357 {
358 return type == CLIPBOARD ? _atom_clipboard : _atom_primary;
359 }
360
361};
362
363//------------------------------------------------------------------------------
365{
366 return NasalClipboard::Ptr(new ClipboardX11);
367}
#define p(x)
virtual void update()
We need to run an event queue to check for selection request.
std::string getAtomName(Atom atom) const
virtual ~ClipboardX11()
void handleEvent(const XEvent &event)
virtual bool setText(const std::string &text, Type type)
Set clipboard contents as text.
virtual std::string getText(Type type)
Get clipboard contents as text.
std::string _clipboard
std::string _selection
void changeProperty(Window w, Atom prop, Atom type, const void *data, size_t size)
Property readProperty(Window w, Atom property)
void handleSelectionRequest(const XSelectionRequestEvent &sel_req)
Display * _display
Atom typeToAtom(Type type) const
static Ptr create()
Implementation supplied by actual platform implementation.
@ CLIPBOARD
Standard clipboard as supported by nearly all operating systems.
std::shared_ptr< NasalClipboard > Ptr