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