1 ////////////////////////////////////////////////////////////
2 //
3 // SFML - Simple and Fast Multimedia Library
4 // Copyright (C) 2007-2018 Laurent Gomila (laurent@sfml-dev.org)
5 //
6 // This software is provided 'as-is', without any express or implied warranty.
7 // In no event will the authors be held liable for any damages arising from the use of this software.
8 //
9 // Permission is granted to anyone to use this software for any purpose,
10 // including commercial applications, and to alter it and redistribute it freely,
11 // subject to the following restrictions:
12 //
13 // 1. The origin of this software must not be misrepresented;
14 // you must not claim that you wrote the original software.
15 // If you use this software in a product, an acknowledgment
16 // in the product documentation would be appreciated but is not required.
17 //
18 // 2. Altered source versions must be plainly marked as such,
19 // and must not be misrepresented as being the original software.
20 //
21 // 3. This notice may not be removed or altered from any source distribution.
22 //
23 ////////////////////////////////////////////////////////////
24
25 ////////////////////////////////////////////////////////////
26 // Headers
27 ////////////////////////////////////////////////////////////
28 #include <SFML/Window/Unix/ClipboardImpl.hpp>
29 #include <SFML/Window/Unix/Display.hpp>
30 #include <SFML/System/Clock.hpp>
31 #include <SFML/System/Err.hpp>
32 #include <X11/Xatom.h>
33 #include <vector>
34
35
36 namespace
37 {
38 // Filter the events received by windows (only allow those matching a specific window)
checkEvent(::Display *,XEvent * event,XPointer userData)39 Bool checkEvent(::Display*, XEvent* event, XPointer userData)
40 {
41 // Just check if the event matches the window
42 return event->xany.window == reinterpret_cast< ::Window >(userData);
43 }
44 }
45
46 namespace sf
47 {
48 namespace priv
49 {
50
51 ////////////////////////////////////////////////////////////
getString()52 String ClipboardImpl::getString()
53 {
54 return getInstance().getStringImpl();
55 }
56
57
58 ////////////////////////////////////////////////////////////
setString(const String & text)59 void ClipboardImpl::setString(const String& text)
60 {
61 getInstance().setStringImpl(text);
62 }
63
64
65 ////////////////////////////////////////////////////////////
processEvents()66 void ClipboardImpl::processEvents()
67 {
68 getInstance().processEventsImpl();
69 }
70
71
72 ////////////////////////////////////////////////////////////
ClipboardImpl()73 ClipboardImpl::ClipboardImpl() :
74 m_window (0),
75 m_requestResponded(false)
76 {
77 // Open a connection with the X server
78 m_display = OpenDisplay();
79
80 // Get the atoms we need to make use of the clipboard
81 m_clipboard = getAtom("CLIPBOARD", false);
82 m_targets = getAtom("TARGETS", false);
83 m_text = getAtom("TEXT", false);
84 m_utf8String = getAtom("UTF8_STRING", true );
85 m_targetProperty = getAtom("SFML_CLIPBOARD_TARGET_PROPERTY", false);
86
87 // Create a hidden window that will broker our clipboard interactions with X
88 m_window = XCreateSimpleWindow(m_display, DefaultRootWindow(m_display), 0, 0, 1, 1, 0, 0, 0);
89
90 // Register the events we are interested in
91 XSelectInput(m_display, m_window, SelectionNotify | SelectionClear | SelectionRequest);
92 }
93
94
95 ////////////////////////////////////////////////////////////
~ClipboardImpl()96 ClipboardImpl::~ClipboardImpl()
97 {
98 // Destroy the window
99 if (m_window)
100 {
101 XDestroyWindow(m_display, m_window);
102 XFlush(m_display);
103 }
104
105 // Close the connection with the X server
106 CloseDisplay(m_display);
107 }
108
109
110 ////////////////////////////////////////////////////////////
getInstance()111 ClipboardImpl& ClipboardImpl::getInstance()
112 {
113 static ClipboardImpl instance;
114
115 return instance;
116 }
117
118
119 ////////////////////////////////////////////////////////////
getStringImpl()120 String ClipboardImpl::getStringImpl()
121 {
122 // Check if anybody owns the current selection
123 if (XGetSelectionOwner(m_display, m_clipboard) == None)
124 {
125 m_clipboardContents.clear();
126
127 return m_clipboardContents;
128 }
129
130 // Process any already pending events
131 processEvents();
132
133 m_requestResponded = false;
134
135 // Request the current selection to be converted to UTF-8 (or STRING
136 // if UTF-8 is not available) and written to our window property
137 XConvertSelection(
138 m_display,
139 m_clipboard,
140 (m_utf8String != None) ? m_utf8String : XA_STRING,
141 m_targetProperty,
142 m_window,
143 CurrentTime
144 );
145
146 Clock clock;
147
148 // Wait for a response for up to 1000ms
149 while (!m_requestResponded && (clock.getElapsedTime().asMilliseconds() < 1000))
150 processEvents();
151
152 // If no response was received within the time period, clear our clipboard contents
153 if (!m_requestResponded)
154 m_clipboardContents.clear();
155
156 return m_clipboardContents;
157 }
158
159
160 ////////////////////////////////////////////////////////////
setStringImpl(const String & text)161 void ClipboardImpl::setStringImpl(const String& text)
162 {
163 m_clipboardContents = text;
164
165 // Set our window as the current owner of the selection
166 XSetSelectionOwner(m_display, m_clipboard, m_window, CurrentTime);
167
168 // Check if setting the selection owner was successful
169 if (XGetSelectionOwner(m_display, m_clipboard) != m_window)
170 err() << "Cannot set clipboard string: Unable to get ownership of X selection" << std::endl;
171 }
172
173
174 ////////////////////////////////////////////////////////////
processEventsImpl()175 void ClipboardImpl::processEventsImpl()
176 {
177 XEvent event;
178
179 // Pick out the events that are interesting for this window
180 while (XCheckIfEvent(m_display, &event, &checkEvent, reinterpret_cast<XPointer>(m_window)))
181 m_events.push_back(event);
182
183 // Handle the events for this window that we just picked out
184 while (!m_events.empty())
185 {
186 event = m_events.front();
187 m_events.pop_front();
188 processEvent(event);
189 }
190 }
191
192
193 ////////////////////////////////////////////////////////////
processEvent(XEvent & windowEvent)194 void ClipboardImpl::processEvent(XEvent& windowEvent)
195 {
196 switch (windowEvent.type)
197 {
198 case SelectionClear:
199 {
200 // We don't have any resources we need to clean up
201 // when losing selection ownership so we don't do
202 // anything when we receive SelectionClear
203 // We will still respond to any future SelectionRequest
204 // events since doing so doesn't really do any harm
205 break;
206 }
207 case SelectionNotify:
208 {
209 // Notification that the current selection owner
210 // has responded to our request
211
212 XSelectionEvent& selectionEvent = *reinterpret_cast<XSelectionEvent*>(&windowEvent.xselection);
213
214 m_clipboardContents.clear();
215
216 // If retrieving the selection fails or conversion is unsuccessful
217 // we leave the contents of the clipboard empty since we don't
218 // own it and we don't know what it could currently be
219 if ((selectionEvent.property == None) || (selectionEvent.selection != m_clipboard))
220 break;
221
222 Atom type;
223 int format;
224 unsigned long items;
225 unsigned long remainingBytes;
226 unsigned char* data = 0;
227
228 // The selection owner should have wrote the selection
229 // data to the specified window property
230 int result = XGetWindowProperty(
231 m_display,
232 m_window,
233 m_targetProperty,
234 0,
235 0x7fffffff,
236 False,
237 AnyPropertyType,
238 &type,
239 &format,
240 &items,
241 &remainingBytes,
242 &data
243 );
244
245 if (result == Success)
246 {
247 // We don't support INCR for now
248 // It is very unlikely that this will be returned
249 // for purely text data transfer anyway
250 if (type != getAtom("INCR", false))
251 {
252 // Only copy the data if the format is what we expect
253 if ((type == m_utf8String) && (format == 8))
254 {
255 m_clipboardContents = String::fromUtf8(data, data + items);
256 }
257 else if ((type == XA_STRING) && (format == 8))
258 {
259 // Convert from ANSI std::string to sf::String
260 m_clipboardContents = std::string(data, data + items);
261 }
262 }
263
264 XFree(data);
265
266 // The selection requestor must always delete the property themselves
267 XDeleteProperty(m_display, m_window, m_targetProperty);
268 }
269
270 m_requestResponded = true;
271
272 break;
273 }
274 case SelectionRequest:
275 {
276 // Respond to a request for our clipboard contents
277 XSelectionRequestEvent& selectionRequestEvent = *reinterpret_cast<XSelectionRequestEvent*>(&windowEvent.xselectionrequest);
278
279 // Our reply
280 XSelectionEvent selectionEvent;
281
282 selectionEvent.type = SelectionNotify;
283 selectionEvent.requestor = selectionRequestEvent.requestor;
284 selectionEvent.selection = selectionRequestEvent.selection;
285 selectionEvent.property = selectionRequestEvent.property;
286 selectionEvent.time = selectionRequestEvent.time;
287
288 if (selectionRequestEvent.selection == m_clipboard)
289 {
290 if (selectionRequestEvent.target == m_targets)
291 {
292 // Respond to a request for our valid conversion targets
293 std::vector<Atom> targets;
294
295 targets.push_back(m_targets);
296 targets.push_back(m_text);
297 targets.push_back(XA_STRING);
298
299 if (m_utf8String != None)
300 targets.push_back(m_utf8String);
301
302 XChangeProperty(
303 m_display,
304 selectionRequestEvent.requestor,
305 selectionRequestEvent.property,
306 XA_ATOM,
307 32,
308 PropModeReplace,
309 reinterpret_cast<unsigned char*>(&targets[0]),
310 targets.size()
311 );
312
313 // Notify the requestor that they can read the targets from their window property
314 selectionEvent.target = m_targets;
315
316 XSendEvent(m_display, selectionRequestEvent.requestor, True, NoEventMask, reinterpret_cast<XEvent*>(&selectionEvent));
317
318 break;
319 }
320 else if ((selectionRequestEvent.target == XA_STRING) || ((m_utf8String == None) && (selectionRequestEvent.target == m_text)))
321 {
322 // Respond to a request for conversion to a Latin-1 string
323 std::string data = m_clipboardContents.toAnsiString();
324
325 XChangeProperty(
326 m_display,
327 selectionRequestEvent.requestor,
328 selectionRequestEvent.property,
329 XA_STRING,
330 8,
331 PropModeReplace,
332 reinterpret_cast<const unsigned char*>(data.c_str()),
333 data.size()
334 );
335
336 // Notify the requestor that they can read the data from their window property
337 selectionEvent.target = XA_STRING;
338
339 XSendEvent(m_display, selectionRequestEvent.requestor, True, NoEventMask, reinterpret_cast<XEvent*>(&selectionEvent));
340
341 break;
342 }
343 else if ((m_utf8String != None) && ((selectionRequestEvent.target == m_utf8String) || (selectionRequestEvent.target == m_text)))
344 {
345 // Respond to a request for conversion to a UTF-8 string
346 // or an encoding of our choosing (we always choose UTF-8)
347 std::basic_string<Uint8> data = m_clipboardContents.toUtf8();
348
349 XChangeProperty(
350 m_display,
351 selectionRequestEvent.requestor,
352 selectionRequestEvent.property,
353 m_utf8String,
354 8,
355 PropModeReplace,
356 reinterpret_cast<const unsigned char*>(data.c_str()),
357 data.size()
358 );
359
360 // Notify the requestor that they can read the data from their window property
361 selectionEvent.target = m_utf8String;
362
363 XSendEvent(m_display, selectionRequestEvent.requestor, True, NoEventMask, reinterpret_cast<XEvent*>(&selectionEvent));
364
365 break;
366 }
367 }
368
369 // Notify the requestor that we could not respond to their request
370 selectionEvent.target = selectionRequestEvent.target;
371 selectionEvent.property = None;
372
373 XSendEvent(m_display, selectionRequestEvent.requestor, True, NoEventMask, reinterpret_cast<XEvent*>(&selectionEvent));
374
375 break;
376 }
377 default:
378 break;
379 }
380 }
381
382 } // namespace priv
383
384 } // namespace sf
385