1 /* $Id: clipboard.cpp 48153 2011-01-01 15:57:50Z mordante $ */
2 /*
3    Copyright (C) 2003 - 2011 by David White <dave@whitevine.net>
4    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
5 
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY.
12 
13    See the COPYING file for more details.
14 */
15 
16 /** @file */
17 
18 #include "clipboard.hpp"
19 #include <algorithm>
20 #include <iostream>
21 
22 #if (defined(_X11) || defined(__DragonFly__)) && !defined(__APPLE__) && !defined(__ANDROID__)
23 #define CLIPBOARD_FUNCS_DEFINED
24 
25 #include "SDL_syswm.h"
26 
27 #include <unistd.h>
28 
29 /**
30  The following are two classes which wrap the SDL's interface to X,
31  including locking/unlocking, and which manage the atom internment.
32  They exist mainly to make the actual clipboard code somewhat readable.
33 */
34 class XHelper
35 {
36 private:
XHelper()37 	XHelper() :
38 		wmInf_(),
39 		acquireCount_(0)
40 	{
41 		acquire();
42 
43 		// Intern some atoms;
44 		const char* atoms[] = {
45 			"CLIPBOARD",
46 			"TEXT",
47 			"COMPOUND_TEXT",
48 			"UTF8_STRING",
49 			"WESNOTH_PASTE",
50 			"TARGETS"
51 		};
52 
53 		XInternAtoms(dpy(), const_cast<char**>(reinterpret_cast<const char**>(atoms)), 6, false, atomTable_);
54 
55 		release();
56 	}
57 
58 	static XHelper* s_instance_;
59 
60 	SDL_SysWMinfo wmInf_;
61 
62 	Atom          atomTable_[6];
63 	int           acquireCount_;
64 public:
instance()65 	static XHelper* instance()
66 	{
67 		if (!s_instance_)
68 			s_instance_ = new XHelper;
69 		return s_instance_;
70 	}
71 
72 
XA_CLIPBOARD() const73 	Atom XA_CLIPBOARD() const
74 	{
75 		return atomTable_[0];
76 	}
77 
XA_TEXT() const78 	Atom XA_TEXT() const
79 	{
80 		return atomTable_[1];
81 	}
82 
XA_COMPOUND_TEXT() const83 	Atom XA_COMPOUND_TEXT() const
84 	{
85 		return atomTable_[2];
86 	}
87 
UTF8_STRING() const88 	Atom UTF8_STRING() const
89 	{
90 		return atomTable_[3];
91 	}
92 
WES_PASTE() const93 	Atom WES_PASTE() const
94 	{
95 		return atomTable_[4];
96 	}
97 
XA_TARGETS() const98 	Atom XA_TARGETS() const
99 	{
100 		return atomTable_[5];
101 	}
102 
dpy() const103 	Display* dpy() const
104 	{
105 		return wmInf_.info.x11.display;
106 	}
107 
window() const108 	Window window() const
109 	{
110 		return wmInf_.info.x11.window;
111 	}
112 
acquire()113 	void acquire()
114 	{
115 		++acquireCount_;
116 		if (acquireCount_ == 1) {
117 			SDL_VERSION  (&wmInf_.version);
118 			SDL_GetWMInfo(&wmInf_);
119 
120 			wmInf_.info.x11.lock_func();
121 		}
122 	}
123 
release()124 	void release()
125 	{
126 		--acquireCount_;
127 		if (acquireCount_ == 0)
128 			wmInf_.info.x11.unlock_func();
129 	}
130 };
131 
132 XHelper* XHelper::s_instance_ = 0;
133 
134 class UseX
135 {
136 public:
UseX()137 	UseX()
138 	{
139 		XHelper::instance()->acquire();
140 	}
141 
~UseX()142 	~UseX()
143 	{
144 		XHelper::instance()->release();
145 	}
146 
operator ->()147 	XHelper* operator->()
148 	{
149 		return XHelper::instance();
150 	}
151 };
152 
153 /**
154  Note: unfortunately, SDL does not keep track of event timestamps.
155  This means we are forced to use CurrentTime in many spots and
156  are unable to perform many safety checks.
157  Hence, the code below is not compliant to the ICCCM, and
158  may ocassionally suffer from race conditions if an X client
159  is connected to the server over a slow/high-latency link.
160  This implementation is also very minimal.
161  The text is assumed to be reasonably small, as INCR transactions
162  are not supported.
163  MULTIPLE is not supported either.
164 
165  We provide UTF8_STRING, COMPOUND_TEXT, and TEXT,
166  and try to grab all of them, plus STRING (which is latin1).
167 */
168 
169 
170 /**
171  We primarily. keep a copy of the string to response to data requests,
172  but it also has an another function: in case we're both the source
173  and destination, we just copy it across; this is so that we don't
174  have to handle SelectionRequest events while waiting for SelectionNotify.
175  To make this work, however, this gets cleared when we loose CLIPBOARD.
176 */
177 static std::string clipboard_string;
178 
179 /**
180  The following string is used for the mouse selection aka PRIMARY
181  Unix behaviour is mouse selection is stored in primary
182  active selection goes to CLIPBOARD.
183 */
184 static std::string primary_string;
185 
clipboard_handle_event(const SDL_Event & event)186 bool clipboard_handle_event(const SDL_Event& event)
187 {
188 	if(event.type != SDL_SYSWMEVENT) {
189 		return false;
190 	}
191 
192 	XEvent& xev = event.syswm.msg->event.xevent;
193 	if (xev.type == SelectionRequest) {
194 		UseX x11;
195 
196 		// Since wesnoth does not notify us of selections,
197 		// we set both selection + clipboard when copying.
198 		if ((xev.xselectionrequest.owner     == x11->window()) &&
199 		    ((xev.xselectionrequest.selection == XA_PRIMARY) ||
200 		     (xev.xselectionrequest.selection == x11->XA_CLIPBOARD()))) {
201 			XEvent responseEvent;
202 			responseEvent.xselection.type      = SelectionNotify;
203 			responseEvent.xselection.display   = x11->dpy();
204 			responseEvent.xselection.requestor = xev.xselectionrequest.requestor;
205 			responseEvent.xselection.selection = xev.xselectionrequest.selection;
206 			responseEvent.xselection.target    = xev.xselectionrequest.target;
207 			responseEvent.xselection.property  = None; //nothing available, by default
208 			responseEvent.xselection.time      = xev.xselectionrequest.time;
209 
210 			//std::cout<<"Request for target:"<<XGetAtomName(x11->dpy(), xev.xselectionrequest.target)<<"\n";
211 
212 			//### presently don't handle XA_STRING as it must be latin1
213 
214 			if (xev.xselectionrequest.target == x11->XA_TARGETS()) {
215 				responseEvent.xselection.property = xev.xselectionrequest.property;
216 
217 				static Atom supported[] = {
218 					x11->XA_TEXT(),
219 					x11->XA_COMPOUND_TEXT(),
220 					x11->UTF8_STRING(),
221 					x11->XA_TARGETS()
222 				};
223 
224 				XChangeProperty(x11->dpy(), responseEvent.xselection.requestor,
225 					xev.xselectionrequest.property, XA_ATOM, 32, PropModeReplace,
226 					const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(supported)), 4);
227 			}
228 
229 			// The encoding of XA_TEXT and XA_COMPOUND_TEXT is not specified
230 			// by the ICCCM... So we assume wesnoth native/utf-8 for simplicity.
231 			// Modern apps are going to use UTF8_STRING anyway.
232 			if (xev.xselectionrequest.target == x11->XA_TEXT()
233 			    	|| xev.xselectionrequest.target == x11->XA_COMPOUND_TEXT()
234 			    	|| xev.xselectionrequest.target == x11->UTF8_STRING()) {
235 
236 				responseEvent.xselection.property = xev.xselectionrequest.property;
237 
238 				std::string& selection = (xev.xselectionrequest.selection == XA_PRIMARY) ?
239 					primary_string : clipboard_string;
240 
241 				XChangeProperty(x11->dpy(), responseEvent.xselection.requestor,
242 					xev.xselectionrequest.property,
243 					xev.xselectionrequest.target, 8, PropModeReplace,
244 					reinterpret_cast<const unsigned char*>(selection.c_str()), selection.length());
245 			}
246 
247 			XSendEvent(x11->dpy(), xev.xselectionrequest.requestor, False, NoEventMask,
248 			   &responseEvent);
249 			XFlush(x11->dpy());
250 		}
251 
252 		return true;
253 	}
254 
255 	if (xev.type == SelectionClear) {
256 		//We no longer own the clipboard, don't try in-process C&P
257 		UseX x11;
258 
259 		if(xev.xselectionclear.selection == x11->XA_CLIPBOARD()) {
260 			clipboard_string.clear();
261 		} else if(xev.xselectionclear.selection == XA_PRIMARY) {
262 			primary_string.clear();
263 		}
264 
265 		return true;
266 	}
267 
268 	return false;
269 }
270 
copy_to_clipboard(const std::string & text,const bool mouse)271 void copy_to_clipboard(const std::string& text, const bool mouse)
272 {
273 	if (text.empty()) {
274 		return;
275 	}
276 
277 	UseX x11;
278 
279 	if(mouse) {
280 		primary_string = text;
281 		XSetSelectionOwner(x11->dpy(), XA_PRIMARY, x11->window(), CurrentTime);
282 	} else {
283 		clipboard_string = text;
284 	//currently disabled due to crash bugs in X
285 		XSetSelectionOwner(x11->dpy(), x11->XA_CLIPBOARD(), x11->window(), CurrentTime);
286 	}
287 }
288 
289 /**
290  * Tries to grab a given target.
291  * Returns true if successful, false otherwise.
292  */
try_grab_target(Atom source,Atom target,std::string & ret)293 static bool try_grab_target(Atom source, Atom target, std::string& ret)
294 {
295 	UseX x11;
296 
297 	// Cleanup previous data
298 	XDeleteProperty(x11->dpy(), x11->window(), x11->WES_PASTE());
299 	XSync          (x11->dpy(), False);
300 
301 	//std::cout<<"We request target:"<<XGetAtomName(x11->dpy(), target)<<"\n";
302 
303 	// Request information
304 	XConvertSelection(x11->dpy(), source, target,
305 	                  x11->WES_PASTE(), x11->window(), CurrentTime);
306 
307 	// Wait (with timeout) for a response SelectionNotify
308 	for (int attempt = 0; attempt < 15; attempt++) {
309 		if (XPending(x11->dpy())) {
310 			XEvent selectNotify;
311 			while (XCheckTypedWindowEvent(x11->dpy(), x11->window(), SelectionNotify, &selectNotify)) {
312 				if (selectNotify.xselection.property == None)
313 					//Not supported. Say so.
314 					return false;
315 				else if (selectNotify.xselection.property == x11->WES_PASTE() &&
316 				         selectNotify.xselection.target   == target) {
317 					// The size
318 					unsigned long length = 0;
319 					unsigned char* data;
320 
321 					// These 3 XGetWindowProperty returns but we don't use
322 					Atom         typeRet;
323 					int          formatRet;
324 					unsigned long remaining;
325 
326 //					std::cout<<"Grab:"<<XGetAtomName(x11->dpy(), target)<<"\n";
327 
328 					// Grab the text out of the property
329 					XGetWindowProperty(x11->dpy(), x11->window(),
330 					                   selectNotify.xselection.property,
331 					                   0, 65535/4, True, target,
332 					                   &typeRet, &formatRet, &length, &remaining, &data);
333 
334 					if (data && length) {
335 						ret = reinterpret_cast<char*>(data);
336 						XFree(data);
337 						return true;
338 					} else {
339 						return false;
340 					}
341 				}
342 			}
343 		}
344 
345 		usleep(10000);
346 	}
347 
348 	// Timed out -- return empty string
349 	return false;
350 }
351 
copy_from_clipboard(const bool mouse)352 std::string copy_from_clipboard(const bool mouse)
353 {
354 	// in-wesnoth copy-paste
355 	if(mouse && !primary_string.empty()) {
356 		return primary_string;
357 	}
358 	if (!mouse && !clipboard_string.empty()) {
359 		return clipboard_string;
360 	}
361 
362 	UseX x11;
363 
364 	std::string ret;
365 	const Atom& source = mouse ?  XA_PRIMARY : x11->XA_CLIPBOARD();
366 
367 	if (try_grab_target(source, x11->UTF8_STRING(), ret))
368 		return ret;
369 
370 	if (try_grab_target(source, x11->XA_COMPOUND_TEXT(), ret))
371 		return ret;
372 
373 	if (try_grab_target(source, x11->XA_TEXT(), ret))
374 		return ret;
375 
376 	if (try_grab_target(source, XA_STRING, ret)) 	// acroread only provides this
377 		return ret;
378 
379 
380 	return "";
381 }
382 
383 #endif
384 #ifdef _WIN32
385 #include <windows.h>
386 #define CLIPBOARD_FUNCS_DEFINED
387 
clipboard_handle_event(const SDL_Event &)388 bool clipboard_handle_event(const SDL_Event& )
389 {
390 	return false;
391 }
392 
copy_to_clipboard(const std::string & text,const bool)393 void copy_to_clipboard(const std::string& text, const bool)
394 {
395 	if(text.empty()) {
396 		return;
397 	}
398 
399 	if(!OpenClipboard(NULL))
400 		return;
401 	EmptyClipboard();
402 
403 	// Convert newlines
404 	std::string str;
405 	str.reserve(text.size());
406 	std::string::const_iterator last = text.begin();
407 	while(last != text.end()) {
408 		if(*last != '\n') {
409 			str.push_back(*last);
410 		} else {
411 			str.append("\r\n");
412 		}
413 		++last;
414 	}
415 
416 	const HGLOBAL hglb = GlobalAlloc(GMEM_MOVEABLE, (str.size() + 1) * sizeof(TCHAR));
417 	if(hglb == NULL) {
418 		CloseClipboard();
419 		return;
420 	}
421 	char* const buffer = reinterpret_cast<char* const>(GlobalLock(hglb));
422 	strcpy(buffer, str.c_str());
423 	GlobalUnlock(hglb);
424 	SetClipboardData(CF_TEXT, hglb);
425 	CloseClipboard();
426 }
427 
copy_from_clipboard(const bool)428 std::string copy_from_clipboard(const bool)
429 {
430 	if(!IsClipboardFormatAvailable(CF_TEXT))
431 		return "";
432 	if(!OpenClipboard(NULL))
433 		return "";
434 
435 	HGLOBAL hglb = GetClipboardData(CF_TEXT);
436 	if(hglb == NULL) {
437 		CloseClipboard();
438 		return "";
439 	}
440 	char const * buffer = reinterpret_cast<char*>(GlobalLock(hglb));
441 	if(buffer == NULL) {
442 		CloseClipboard();
443 		return "";
444 	}
445 
446 	// Convert newlines
447 	std::string str(buffer);
448 	str.erase(std::remove(str.begin(),str.end(),'\r'),str.end());
449 
450 	GlobalUnlock(hglb);
451 	CloseClipboard();
452 	return str;
453 }
454 
455 #endif
456 
457 #ifdef __BEOS__
458 #include <Clipboard.h>
459 #define CLIPBOARD_FUNCS_DEFINED
460 
copy_to_clipboard(const std::string & text,const bool)461 void copy_to_clipboard(const std::string& text, const bool)
462 {
463 	BMessage *clip;
464 	if (be_clipboard->Lock())
465 	{
466 		be_clipboard->Clear();
467 		if ((clip = be_clipboard->Data()))
468 		{
469 			clip->AddData("text/plain", B_MIME_TYPE, text.c_str(), text.size()+1);
470 			be_clipboard->Commit();
471 		}
472 		be_clipboard->Unlock();
473 	}
474 }
475 
copy_from_clipboard(const bool)476 std::string copy_from_clipboard(const bool)
477 {
478 	const char* data;
479 	ssize_t size;
480 	BMessage *clip = NULL;
481 	if (be_clipboard->Lock())
482 	{
483 		clip = be_clipboard->Data();
484 		be_clipboard->Unlock();
485 	}
486 	if (clip != NULL && clip->FindData("text/plain", B_MIME_TYPE, (const void**)&data, &size) == B_OK)
487 		return (const char*)data;
488 	else
489 		return "";
490 }
491 #endif
492 
493 #ifdef __APPLE__
494     #include "TargetConditionals.h"
495 
496     #if TARGET_OS_IPHONE
497         //for now, do nothing
498 
499     #elif TARGET_OS_MAC
500     #define CLIPBOARD_FUNCS_DEFINED
501     #include <Carbon/Carbon.h>
502 
copy_to_clipboard(const std::string & text,const bool)503     void copy_to_clipboard(const std::string& text, const bool)
504     {
505         if(text.empty()) {
506             return;
507         }
508 
509         std::string new_str;
510         new_str.reserve(text.size());
511         for (unsigned int i = 0; i < text.size(); ++i)
512         {
513             if (text[i] == '\n')
514             {
515                 new_str.push_back('\r');
516             } else {
517                 new_str.push_back(text[i]);
518             }
519         }
520         OSStatus err = noErr;
521         PasteboardRef clipboard;
522         PasteboardSyncFlags syncFlags;
523         CFDataRef textData = NULL;
524         err = PasteboardCreate(kPasteboardClipboard, &clipboard);
525         if (err != noErr) return;
526         err = PasteboardClear(clipboard);
527         if (err != noErr) return;
528         syncFlags = PasteboardSynchronize(clipboard);
529         if ((syncFlags&kPasteboardModified) && !(syncFlags&kPasteboardClientIsOwner)) return;
530         textData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)new_str.c_str(), text.size());
531         PasteboardPutItemFlavor(clipboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), textData, 0);
532     }
533 
copy_from_clipboard(const bool)534     std::string copy_from_clipboard(const bool)
535     {
536         OSStatus err = noErr;
537         PasteboardRef clipboard;
538         PasteboardSyncFlags syncFlags;
539         ItemCount count;
540         err = PasteboardCreate(kPasteboardClipboard, &clipboard);
541         if (err != noErr) return "";
542         syncFlags = PasteboardSynchronize(clipboard);
543         if (syncFlags&kPasteboardModified) return "";
544         err = PasteboardGetItemCount(clipboard, &count);
545         if (err != noErr) return "";
546         for (UInt32 k = 1; k <= count; k++) {
547             PasteboardItemID itemID;
548             CFArrayRef flavorTypeArray;
549             CFIndex flavorCount;
550             err = PasteboardGetItemIdentifier(clipboard, k, &itemID);
551             if (err != noErr) return "";
552             err = PasteboardCopyItemFlavors(clipboard, itemID, &flavorTypeArray);
553             if (err != noErr) return "";
554             flavorCount = CFArrayGetCount(flavorTypeArray);
555             for (CFIndex j = 0; j < flavorCount; j++) {
556                 CFStringRef flavorType;
557                 CFDataRef flavorData;
558                 CFIndex flavorDataSize;
559                 flavorType = (CFStringRef)CFArrayGetValueAtIndex(flavorTypeArray, j);
560                 if (UTTypeConformsTo(flavorType, CFSTR("public.utf8-plain-text"))) {
561                     err = PasteboardCopyItemFlavorData(clipboard, itemID, flavorType, &flavorData);
562                     if (err != noErr) {
563                         CFRelease(flavorTypeArray);
564                         return "";
565                     }
566                     flavorDataSize = CFDataGetLength(flavorData);
567                     std::string str;
568                     str.reserve(flavorDataSize);
569                     str.resize(flavorDataSize);
570                     CFDataGetBytes(flavorData, CFRangeMake(0, flavorDataSize), (UInt8 *)str.data());
571                     for (unsigned int i = 0; i < str.size(); ++i) {
572                         if (str[i] == '\r') str[i] = '\n';
573                     }
574                     return str;
575                 }
576             }
577         }
578         return "";
579     }
580 
clipboard_handle_event(const SDL_Event &)581     bool clipboard_handle_event(const SDL_Event& )
582     {
583         return false;
584     }
585     #endif
586 #endif
587 
588 #ifndef CLIPBOARD_FUNCS_DEFINED
589 
copy_to_clipboard(const std::string &,const bool)590 void copy_to_clipboard(const std::string& /*text*/, const bool)
591 {
592 }
593 
copy_from_clipboard(const bool)594 std::string copy_from_clipboard(const bool)
595 {
596 	return "";
597 }
598 
clipboard_handle_event(const SDL_Event &)599 bool clipboard_handle_event(const SDL_Event& )
600 {
601 	return false;
602 }
603 
604 #endif
605 
init_clipboard()606 void init_clipboard()
607 {
608 #if (defined(_X11) || defined(__DragonFly__)) && !defined(__APPLE__) && !defined(__ANDROID__)
609 	SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
610 #endif
611 }
612 
clipboard_has_mouse_area()613 bool clipboard_has_mouse_area()
614 {
615 #if (defined(_X11) || defined(__DragonFly__)) && !defined(__APPLE__) && !defined(__ANDROID__)
616 	return true;
617 #else
618 	return false;
619 #endif
620 }
621 
622