1 /*
2  * Schism Tracker - a cross-platform Impulse Tracker clone
3  * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
4  * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
5  * copyright (c) 2009 Storlek & Mrs. Brisby
6  * copyright (c) 2010-2012 Storlek
7  * URL: http://schismtracker.org/
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23 
24 #include "headers.h" /* always include this one first, kthx */
25 
26 #include "clippy.h"
27 #include "event.h"
28 
29 #include "util.h"
30 
31 #include "sdlmain.h"
32 
33 static char *_current_selection = NULL;
34 static char *_current_clipboard = NULL;
35 static struct widget *_widget_owner[16] = {NULL};
36 
37 static int has_sys_clip;
38 #if defined(WIN32)
39 static HWND SDL_Window, _hmem;
40 #elif defined(__QNXNTO__)
41 static unsigned short inputgroup;
42 #elif defined(USE_X11)
43 static Display *SDL_Display = NULL;
__construct($p_timestamp, $p_user_id, $p_issue_id, $p_filename, $p_type)44 static Window SDL_Window;
45 static void (*lock_display)(void);
46 static void (*unlock_display)(void);
47 static Atom atom_sel;
48 static Atom atom_clip;
49 static void __noop_v(void){};
50 #endif
51 
52 #ifdef MACOSX
53 extern const char *macosx_clippy_get(void);
54 extern void macosx_clippy_put(const char *buf);
55 #endif
56 
html()57 static void _clippy_copy_to_sys(int do_sel)
58 {
59 	int j;
60 	char *tmp;
61 	char *dst;
62 	char *freeme;
63 #if defined(__QNXNTO__)
64 	PhClipboardHdr clheader = {Ph_CLIPBOARD_TYPE_TEXT, 0, NULL};
65 	int *cldata;
66 	int status;
67 #endif
68 
69 	freeme = NULL;
70 	if (!_current_selection) {
71 		dst = NULL;
72 		j = 0;
73 	} else
74 #if defined(WIN32)
75 	{
76 		int i;
77 		/* need twice the space since newlines are replaced with \r\n */
78 		freeme = tmp = malloc(strlen(_current_selection)*2 + 1);
79 		if (!tmp) return;
80 		for (i = j = 0; _current_selection[i]; i++) {
81 			if (_current_selection[i] == '\r' || _current_selection[i] == '\n') {
82 				tmp[j++] = '\r';
83 				tmp[j++] = '\n';
84 			} else {
85 				tmp[j++] = _current_selection[i];
86 			}
87 		}
88 		tmp[j] = '\0';
89 	}
90 #else
91 	if (has_sys_clip) {
92 		int i;
93 		/* convert to local */
94 		freeme = dst = malloc(strlen(_current_selection)+4);
95 		if (!dst) return;
96 		for (i = j = 0; _current_selection[i]; i++) {
97 			dst[j] = _current_selection[i];
98 			if (dst[j] == '\r') dst[j] = '\n';
99 			j++;
100 		}
101 		dst[j] = '\0';
102 	} else {
103 		dst = NULL;
104 		j = 0;
105 	}
106 #endif
107 #if defined(USE_X11)
108 	if (has_sys_clip) {
109 		lock_display();
110 		if (!dst) dst = (char *) ""; /* blah */
111 		if (j < 0) j = 0;
112 		if (do_sel) {
113 			if (XGetSelectionOwner(SDL_Display, XA_PRIMARY) != SDL_Window) {
114 				XSetSelectionOwner(SDL_Display, XA_PRIMARY, SDL_Window, CurrentTime);
115 			}
116 			XChangeProperty(SDL_Display,
117 				DefaultRootWindow(SDL_Display),
118 				XA_CUT_BUFFER0, XA_STRING, 8,
119 				PropModeReplace, (unsigned char *)dst, j);
120 		} else {
121 			if (XGetSelectionOwner(SDL_Display, atom_clip) != SDL_Window) {
122 				XSetSelectionOwner(SDL_Display, atom_clip, SDL_Window, CurrentTime);
123 			}
124 			XChangeProperty(SDL_Display,
125 				DefaultRootWindow(SDL_Display),
126 				XA_CUT_BUFFER0, XA_STRING, 8,
127 				PropModeReplace, (unsigned char *)dst, j);
128 			XChangeProperty(SDL_Display,
129 				DefaultRootWindow(SDL_Display),
130 				XA_CUT_BUFFER1, XA_STRING, 8,
131 				PropModeReplace, (unsigned char *)dst, j);
132 		}
133 		unlock_display();
134 	}
135 #elif defined(WIN32)
136 	if (!do_sel && OpenClipboard(SDL_Window)) {
137 		_hmem = GlobalAlloc((GMEM_MOVEABLE|GMEM_DDESHARE), j+1);
138 		if (_hmem) {
139 			dst = (char *)GlobalLock(_hmem);
140 			if (dst) {
141 				/* this seems wrong, but msdn does this */
142 				memcpy(dst, tmp, j);
143 				dst[j] = '\0';
144 				GlobalUnlock(_hmem);
145 				EmptyClipboard();
146 				SetClipboardData(CF_TEXT, _hmem);
147 			}
148 		}
149 		CloseClipboard();
150 		_hmem = NULL;
151 		dst = 0;
152 	}
153 #elif defined(__QNXNTO__)
154 	if (!do_sel) {
155 		tmp = (char *)malloc(j+4);
156 		if (!tmp) {
157 			cldata=(int*)tmp;
158 			*cldata = Ph_CL_TEXT;
159 			if (dst) memcpy(tmp+4, dst, j);
160 			clheader.data = tmp;
161 #if (NTO_VERSION < 620)
162 			if (clheader.length > 65535) clheader.length=65535;
163 #endif
164 			clheader.length = j + 4;
165 #if (NTO_VERSION < 620)
166 			PhClipboardCopy(inputgroup, 1, &clheader);
167 #else
168 			PhClipboardWrite(inputgroup, 1, &clheader);
169 #endif
170 			free(tmp);
171 		}
172 	}
173 #elif defined(MACOSX)
174 	if (!do_sel) macosx_clippy_put(_current_clipboard);
175 #else
176 	// some other system -- linux without x11, maybe
177 	// pretend we used the param to silence warnings
178 	(void) do_sel;
179 #endif
180 	if (freeme)
181 		free(freeme);
182 }
183 
184 /* TODO: is the first parameter ever going to be used, or can we kill it? */
185 static void _string_paste(UNUSED int cb, const char *cbptr)
186 {
187 	SDL_Event event = {};
188 
189 	event.user.type = SCHISM_EVENT_PASTE;
190 	event.user.data1 = str_dup(cbptr); /* current_clipboard... is it safe? */
191 	if (!event.user.data1) return; /* eh... */
192 	if (SDL_PushEvent(&event) == -1) {
193 		free(event.user.data1);
194 	}
195 }
196 
197 
198 #if defined(USE_X11)
199 static int _x11_clip_filter(const SDL_Event *ev)
200 {
201 	XSelectionRequestEvent *req;
202 	XEvent sevent;
203 	Atom seln_type, seln_target;
204 	int seln_format;
205 	unsigned long nbytes;
206 	unsigned long overflow;
207 	unsigned char *seln_data;
208 	unsigned char *src;
209 
210 	if (ev->type != SDL_SYSWMEVENT) return 1;
211 	if (ev->syswm.msg->event.xevent.type == SelectionNotify) {
212 		sevent = ev->syswm.msg->event.xevent;
213 		if (sevent.xselection.requestor == SDL_Window) {
214 			lock_display();
215 			src = NULL;
216 			if (XGetWindowProperty(SDL_Display, SDL_Window, atom_sel,
217 						0, 9000, False, XA_STRING,
218 						(Atom *)&seln_type,
219 						(int *)&seln_format,
220 						(unsigned long *)&nbytes,
221 						(unsigned long *)&overflow,
222 						(unsigned char **)&src) == Success) {
223 				if (seln_type == XA_STRING) {
224 					if (_current_selection != _current_clipboard) {
225 						free(_current_clipboard);
226 					}
227 					_current_clipboard = strn_dup((const char *)src, nbytes);
228 					_string_paste(CLIPPY_BUFFER, _current_clipboard);
229 					_widget_owner[CLIPPY_BUFFER]
230 							= _widget_owner[CLIPPY_SELECT];
231 				}
232 				XFree(src);
233 			}
234 			unlock_display();
235 		}
236 		return 1;
237 	} else if (ev->syswm.msg->event.xevent.type == PropertyNotify) {
238 		sevent = ev->syswm.msg->event.xevent;
239 		return 1;
240 
241 	} else if (ev->syswm.msg->event.xevent.type != SelectionRequest) {
242 		return 1;
243 	}
244 
245 	req = &ev->syswm.msg->event.xevent.xselectionrequest;
246 	sevent.xselection.type = SelectionNotify;
247 	sevent.xselection.display = req->display;
248 	sevent.xselection.selection = req->selection;
249 	sevent.xselection.target = req->target;
250 	sevent.xselection.property = None;
251 	sevent.xselection.requestor = req->requestor;
252 	sevent.xselection.time = req->time;
253 	if (XGetWindowProperty(SDL_Display, DefaultRootWindow(SDL_Display),
254 			XA_CUT_BUFFER0, 0, 9000, False, req->target,
255 			&seln_target, &seln_format,
256 			&nbytes, &overflow, &seln_data) == Success) {
257 		if (seln_target == req->target) {
258 			if (seln_target == XA_STRING) {
259 				if (nbytes && seln_data[nbytes-1] == '\0')
260 					nbytes--;
261 			}
262 			XChangeProperty(SDL_Display, req->requestor, req->property,
263 				seln_target, seln_format, PropModeReplace,
264 				seln_data, nbytes);
265 			sevent.xselection.property = req->property;
266 		}
267 		XFree(seln_data);
268 	}
269 	XSendEvent(SDL_Display, req->requestor, False, 0, &sevent);
270 	XSync(SDL_Display, False);
271 	return 1;
272 }
273 
274 static int (*orig_xlib_err)(Display *d, XErrorEvent *e) = NULL;
275 static int handle_xlib_err(Display *d, XErrorEvent *e)
276 {
277 	/* X_SetSelectionOwner == 22 */
278 	if (e->error_code == BadWindow && e->request_code == 22) {
279 		/* return 0 here to avoid dying as the result of a nonfatal race condition */
280 		return 0;
281 	}
282 	if (orig_xlib_err) return orig_xlib_err(d,e);
283 	return 0;
284 }
285 
286 #endif
287 
288 
289 void clippy_init(void)
290 {
291 	SDL_SysWMinfo info = {};
292 
293 	has_sys_clip = 0;
294 	SDL_VERSION(&info.version);
295 	if (SDL_GetWMInfo(&info)) {
296 #if defined(USE_X11)
297 		if (info.subsystem == SDL_SYSWM_X11) {
298 			SDL_Display = info.info.x11.display;
299 			SDL_Window = info.info.x11.window;
300 			lock_display = info.info.x11.lock_func;
301 			unlock_display = info.info.x11.unlock_func;
302 			SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
303 			SDL_SetEventFilter(_x11_clip_filter);
304 			has_sys_clip = 1;
305 
306 			atom_sel = XInternAtom(SDL_Display, "SDL_SELECTION", False);
307 			atom_clip = XInternAtom(SDL_Display, "CLIPBOARD", False);
308 
309 			orig_xlib_err = XSetErrorHandler(handle_xlib_err);
310 		}
311 		if (!lock_display) lock_display = __noop_v;
312 		if (!unlock_display) unlock_display = __noop_v;
313 #elif defined(WIN32)
314 		has_sys_clip = 1;
315 		SDL_Window = info.window;
316 #elif defined(__QNXNTO__)
317 		has_sys_clip = 1;
318 		inputgroup = PhInputGroup(NULL);
319 #endif
320 	}
321 }
322 
323 static char *_internal_clippy_paste(int cb)
324 {
325 #if defined(MACOSX)
326 	char *src;
327 #endif
328 #if defined(USE_X11)
329 	Window owner;
330 	int getme;
331 #elif defined(WIN32)
332 	char *src;
333 	int clen;
334 #elif defined(__QNXNTO__)
335 	void *clhandle;
336 	PhClipHeader *clheader;
337 	int *cldata;
338 #endif
339 
340 	if (has_sys_clip) {
341 #if defined(USE_X11)
342 		if (cb == CLIPPY_SELECT) {
343 			getme = XA_PRIMARY;
344 		} else {
345 			getme = atom_clip;
346 		}
347 		lock_display();
348 		owner = XGetSelectionOwner(SDL_Display, getme);
349 		unlock_display();
350 		if (owner == None || owner == SDL_Window) {
351 			/* fall through to default implementation */
352 		} else {
353 			lock_display();
354 			XConvertSelection(SDL_Display, getme, XA_STRING, atom_sel, SDL_Window,
355 							CurrentTime);
356 			/* at some point in the near future, we'll get a SelectionNotify
357 			see _x11_clip_filter for more details;
358 
359 			because of this (otherwise) oddity, we take the selection immediately...
360 			*/
361 			unlock_display();
362 			return NULL;
363 		}
364 #else
365 		if (cb == CLIPPY_BUFFER) {
366 #if defined(WIN32)
367 			if (IsClipboardFormatAvailable(CF_TEXT) && OpenClipboard(SDL_Window)) {
368 				_hmem  = GetClipboardData(CF_TEXT);
369 				if (_hmem) {
370 					if (_current_selection != _current_clipboard) {
371 						free(_current_clipboard);
372 					}
373 					_current_clipboard = NULL;
374 					src = (char*)GlobalLock(_hmem);
375 					if (src) {
376 						clen = GlobalSize(_hmem);
377 						if (clen > 0) {
378 							_current_clipboard = strn_dup(src, clen);
379 						}
380 						GlobalUnlock(_hmem);
381 					}
382 				}
383 				CloseClipboard();
384 				_hmem = NULL;
385 			}
386 #elif defined(__QNXNTO__)
387 			if (_current_selection != _current_clipboard) {
388 				free(_current_clipboard);
389 			}
390 			_current_clipboard = NULL;
391 #if (NTO_VERSION < 620)
392 			clhandle = PhClipboardPasteStart(inputgroup);
393 			if (clhandle) {
394 				clheader = PhClipboardPasteType(clhandle,
395 								Ph_CLIPBOARD_TYPE_TEXT);
396 				if (clheader) {
397 					cldata = clheader->data;
398 					if (clheader->length > 4 && *cldata == Ph_CL_TEXT) {
399 						src = ((char *)clheader->data)+4;
400 						clen = clheader->length - 4;
401 						_current_clipboard = strn_dup(src, clen);
402 
403 					}
404 					PhClipboardPasteFinish(clhandle);
405 				}
406 			}
407 #else
408 			/* argh! qnx */
409 			clheader = PhClipboardRead(inputgroup, Ph_CLIPBOARD_TYPE_TEXT);
410 			if (clheader) {
411 				cldata = clheader->data;
412 				if (clheader->length > 4 && *cldata == Ph_CL_TEXT) {
413 					src = ((char *)clheader->data)+4;
414 					clen = clheader->length - 4;
415 					_current_clipboard = strn_dup(src, clen);
416 				}
417 			}
418 #endif /* NTO version selector */
419 		/* okay, we either own the buffer, or it's a selection for folks without */
420 #endif /* win32/qnx */
421 		}
422 #endif /* x11/others */
423 		/* fall through; the current window owns it */
424 	}
425 	if (cb == CLIPPY_SELECT) return _current_selection;
426 #ifdef MACOSX
427 	if (cb == CLIPPY_BUFFER) {
428 		src = str_dup(macosx_clippy_get());
429 		if (_current_clipboard != _current_selection) {
430 			free(_current_clipboard);
431 		}
432 		_current_clipboard = src;
433 		if (!src) return (char *) ""; /* FIXME: de-const-ing is bad */
434 		return _current_clipboard;
435 	}
436 #else
437 	if (cb == CLIPPY_BUFFER) return _current_clipboard;
438 #endif
439 	return NULL;
440 }
441 
442 
443 void clippy_paste(int cb)
444 {
445 	char *q;
446 	q = _internal_clippy_paste(cb);
447 	if (!q) return;
448 	_string_paste(cb, q);
449 }
450 
451 void clippy_select(struct widget *w, char *addr, int len)
452 {
453 	int i;
454 
455 	if (_current_selection != _current_clipboard) {
456 		free(_current_selection);
457 	}
458 	if (!addr) {
459 		_current_selection = NULL;
460 		_widget_owner[CLIPPY_SELECT] = NULL;
461 	} else {
462 		for (i = 0; addr[i] && (len < 0 || i < len); i++) {
463 			/* nothing */
464 		}
465 		_current_selection = strn_dup(addr, i);
466 		_widget_owner[CLIPPY_SELECT] = w;
467 
468 		/* update x11 Select (for xterms and stuff) */
469 		_clippy_copy_to_sys(1);
470 	}
471 }
472 struct widget *clippy_owner(int cb)
473 {
474 	if (cb == CLIPPY_SELECT || cb == CLIPPY_BUFFER)
475 		return _widget_owner[cb];
476 	return NULL;
477 }
478 
479 void clippy_yank(void)
480 {
481 	if (_current_selection != _current_clipboard) {
482 		free(_current_clipboard);
483 	}
484 	_current_clipboard = _current_selection;
485 	_widget_owner[CLIPPY_BUFFER] = _widget_owner[CLIPPY_SELECT];
486 
487 	if (_current_selection && strlen(_current_selection) > 0) {
488 		status_text_flash("Copied to selection buffer");
489 		_clippy_copy_to_sys(0);
490 	}
491 }
492