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