1 /* VIM: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * gui-clipboard.c: Implements the X11 based copy/paste operations
4  *
5  * Author:
6  *  Miguel de Icaza (miguel@gnu.org)
7  *  Jody Goldberg (jody@gnome.org)
8  */
9 #include <gnumeric-config.h>
10 #include <gnumeric.h>
11 #include <gui-clipboard.h>
12 
13 #include <gui-util.h>
14 #include <clipboard.h>
15 #include <command-context-stderr.h>
16 #include <selection.h>
17 #include <application.h>
18 #include <workbook-control.h>
19 #include <wbc-gtk.h>
20 #include <workbook-priv.h>
21 #include <workbook.h>
22 #include <workbook-view.h>
23 #include <ranges.h>
24 #include <sheet.h>
25 #include <sheet-style.h>
26 #include <sheet-object.h>
27 #include <sheet-control-gui.h>
28 #include <sheet-view.h>
29 #include <commands.h>
30 #include <value.h>
31 #include <number-match.h>
32 #include <dialogs/dialog-stf.h>
33 #include <stf-parse.h>
34 #include <mstyle.h>
35 #include <gnm-format.h>
36 #include <gnumeric-conf.h>
37 #include <xml-sax.h>
38 #include <gutils.h>
39 
40 #include <goffice/goffice.h>
41 #include <gsf/gsf-input-memory.h>
42 #include <gsf/gsf-output-memory.h>
43 #include <gsf/gsf-utils.h>
44 #include <glib/gi18n-lib.h>
45 #include <libxml/globals.h>
46 #include <locale.h>
47 #include <string.h>
48 #include <unistd.h>
49 
50 #define APP_CLIP_DISP_KEY "clipboard-displays"
51 
52 #define EXCEL_FILE_OPENER "Gnumeric_Excel:excel"
53 #define EXCEL_FILE_SAVER "Gnumeric_Excel:excel_biff8"
54 #define HTML_FILE_OPENER "Gnumeric_html:html"
55 #define HTML_FILE_SAVER "Gnumeric_html:xhtml_range"
56 #define OOO_FILE_OPENER "Gnumeric_OpenCalc:openoffice"
57 
58 // ----------------------------------------------------------------------------
59 
60 static gboolean debug_clipboard;
61 static gboolean debug_clipboard_dump;
62 static gboolean debug_clipboard_undump;
63 
64 // ----------------------------------------------------------------------------
65 
66 enum {
67 	ATOM_GNUMERIC,
68 	ATOM_GOFFICE_GRAPH,
69 	// ----------
70 	ATOM_UTF8_STRING,
71 	ATOM_STRING,
72 	ATOM_COMPOUND_TEXT,
73 	ATOM_TEXT_HTML,
74 	ATOM_TEXT_HTML_WINDOWS,
75 	// ----------
76 	ATOM_BIFF8,
77 	ATOM_BIFF8_OO,
78 	ATOM_BIFF8_CITRIX,
79 	ATOM_BIFF5,
80 	ATOM_BIFF,
81 	// ----------
82 	ATOM_OOO,
83 	ATOM_OOO_WINDOWS,
84 	ATOM_OOO11,
85 	// ----------
86 	ATOM_IMAGE_SVGXML,
87 	ATOM_IMAGE_XWMF,
88 	ATOM_IMAGE_XEMF,
89 	ATOM_IMAGE_PNG,
90 	ATOM_IMAGE_JPEG,
91 	ATOM_IMAGE_BMP,
92 	// ----------
93 	ATOM_TEXT_URI_LIST,
94 	ATOM_GNOME_COPIED_FILES,
95 	ATOM_KDE_CUT_FILES,
96 	// ----------
97 	ATOM_SAVE_TARGETS,
98 };
99 
100 static const char *const atom_names[] = {
101 	"application/x-gnumeric",
102 	"application/x-goffice-graph",
103 	// ----------
104 	"UTF8_STRING",
105 	"STRING",
106 	"COMPOUND_TEXT",
107 	"text/html",
108 	"HTML Format",
109 	// ----------
110 	"Biff8",
111 	"application/x-openoffice-biff-8;windows_formatname=\"Biff8\"",
112 	"_CITRIX_Biff8",
113 	"Biff5",
114 	"Biff",
115 	// ----------
116 	"application/x-openoffice;windows_formatname=\"Star Embed Source (XML)\"",
117 	"Star Embed Source (XML)",
118 	"application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"",
119 	// ----------
120 	"image/svg+xml",
121 	"image/x-wmf",
122 	"image/x-emf",
123 	"image/png",
124 	"image/jpeg",
125 	"image/bmp",
126 	// ----------
127 	"text/uri-list",
128 	"x-special/gnome-copied-files",
129 	"application/x-kde-cutselection",
130 	// ----------
131 	"SAVE_TARGETS",
132 };
133 
134 static GdkAtom atoms[G_N_ELEMENTS(atom_names)];
135 
136 typedef enum {
137 	INFO_UNKNOWN,
138 	INFO_GNUMERIC,
139 	INFO_EXCEL,
140 	INFO_OOO,
141 	INFO_GENERIC_TEXT,
142 	INFO_HTML,
143 	INFO_OBJECT,
144 	INFO_IMAGE,
145 } AtomInfoType;
146 
147 static GtkTargetList *generic_text_targets;
148 static GtkTargetList *image_targets;
149 
150 // ----------------------------------------------------------------------------
151 
152 typedef struct {
153 	WBCGtk *wbcg;
154 	GnmPasteTarget *paste_target;
155 } GnmGtkClipboardCtxt;
156 
157 static void
gnm_gtk_clipboard_context_free(GnmGtkClipboardCtxt * ctxt)158 gnm_gtk_clipboard_context_free (GnmGtkClipboardCtxt *ctxt)
159 {
160 	g_free (ctxt->paste_target);
161 	g_free (ctxt);
162 }
163 
164 /*
165  * Emacs hack:
166  * (x-get-selection-internal 'CLIPBOARD 'TARGETS)
167  */
168 
169 static gboolean
has_file_opener(const char * id)170 has_file_opener (const char *id)
171 {
172 	return go_file_opener_for_id (id) != NULL;
173 }
174 
175 static gboolean
has_file_saver(const char * id)176 has_file_saver (const char *id)
177 {
178 	return go_file_saver_for_id (id) != NULL;
179 }
180 
181 
182 
183 static void
paste_from_gnumeric(GtkSelectionData * selection_data,GdkAtom target,gconstpointer data,gssize size)184 paste_from_gnumeric (GtkSelectionData *selection_data, GdkAtom target,
185 		     gconstpointer data, gssize size)
186 {
187 	if (size < 0)
188 		size = 0;
189 
190 	if (debug_clipboard_dump) {
191 		g_file_set_contents ("paste-from-gnumeric.dat",
192 				     data, size, NULL);
193 	}
194 
195 	if (debug_clipboard) {
196 		char *target_name = gdk_atom_name (target);
197 		g_printerr ("clipboard %s of %d bytes\n",
198 			    target_name, (int)size);
199 		g_free (target_name);
200 	}
201 
202 	gtk_selection_data_set (selection_data, target, 8, data, size);
203 }
204 
205 static void
paste_to_gnumeric(GtkSelectionData * sel,const char * typ)206 paste_to_gnumeric (GtkSelectionData *sel, const char *typ)
207 {
208 	GdkAtom target = gtk_selection_data_get_target (sel);
209 	gconstpointer buffer = gtk_selection_data_get_data (sel);
210 	int sel_len = gtk_selection_data_get_length (sel);
211 
212 	if (sel_len < 0)
213 		sel_len = 0;
214 
215 	if (debug_clipboard) {
216 		int maxlen = 1024;
217 		char *name = gdk_atom_name (target);
218 		g_printerr ("Received %d bytes of %s for target %s\n",
219 			    sel_len, typ, name);
220 		g_free (name);
221 		if (sel_len > 0) {
222 			gsf_mem_dump (buffer, MIN (sel_len, maxlen));
223 			if (sel_len > maxlen)
224 				g_printerr ("...\n");
225 		}
226 	}
227 
228 	if (debug_clipboard_dump) {
229 		g_file_set_contents ("paste-to-gnumeric.dat",
230 				     buffer, sel_len, NULL);
231 	}
232 }
233 
234 
235 /* See if this is a "single line + line end", a "multiline" or a "tab separated"
236  * string. If this is _not_ the case we won't invoke the STF, it is
237  * unlikely that the user will actually need it in this case. */
238 static gboolean
text_is_single_cell(gchar const * data,int data_len)239 text_is_single_cell (gchar const *data, int data_len)
240 {
241 	int i;
242 
243 	for (i = 0; i < data_len; i++)
244 		if (data[i] == '\n' || data[i] == '\t')
245 			return FALSE;
246 	return TRUE;
247 }
248 
249 
250 static GnmCellRegion *
text_to_cell_region(WBCGtk * wbcg,gchar const * data,int data_len,char const * opt_encoding,gboolean fixed_encoding)251 text_to_cell_region (WBCGtk *wbcg,
252 		     gchar const *data, int data_len,
253 		     char const *opt_encoding,
254 		     gboolean fixed_encoding)
255 {
256 	Workbook *wb = wb_control_get_workbook (GNM_WBC (wbcg));
257 	DialogStfResult_t *dialogresult;
258 	GnmCellRegion *cr = NULL;
259 	gboolean oneline;
260 	char *data_converted = NULL;
261 
262 	if (!data) {
263 		/*
264 		 * See Redhat #1160975.
265 		 *
266 		 * I'm unsure why someone gets NULL here, but this is better
267 		 * than a crash.
268 		 */
269 		data = "";
270 		data_len = 0;
271 	}
272 
273 	oneline = text_is_single_cell (data, data_len);
274 
275 	if (oneline && (opt_encoding == NULL || strcmp (opt_encoding, "UTF-8") != 0)) {
276 		size_t bytes_written;
277 		char const *enc = opt_encoding ? opt_encoding : "ASCII";
278 
279 		data_converted = g_convert (data, data_len,
280 					    "UTF-8", enc,
281 					    NULL, &bytes_written, NULL);
282 		if (data_converted) {
283 			data = data_converted;
284 			data_len = bytes_written;
285 		} else {
286 			/* Force STF import since we don't know the charset.  */
287 			oneline = FALSE;
288 			fixed_encoding = FALSE;
289 		}
290 	}
291 
292 	if (oneline) {
293 		GODateConventions const *date_conv = workbook_date_conv (wb);
294 		GnmCellCopy *cc = gnm_cell_copy_new (
295 			(cr = gnm_cell_region_new (NULL)), 0, 0);
296 		char *tmp = g_strndup (data, data_len);
297 
298 		g_free (data_converted);
299 
300 		cc->val = format_match (tmp, NULL, date_conv);
301 		if (cc->val)
302 			g_free (tmp);
303 		else
304 			cc->val = value_new_string_nocopy (tmp);
305 		cc->texpr = NULL;
306 
307 		cr->cols = cr->rows = 1;
308 	} else {
309 		dialogresult = stf_dialog (wbcg, opt_encoding, fixed_encoding,
310 					   NULL, FALSE,
311 					   _("clipboard"), data, data_len);
312 
313 		if (dialogresult != NULL) {
314 			cr = stf_parse_region (dialogresult->parseoptions,
315 					       dialogresult->text, NULL, wb);
316 			g_return_val_if_fail (cr != NULL, gnm_cell_region_new (NULL));
317 
318 			stf_dialog_result_attach_formats_to_cr (dialogresult, cr);
319 
320 			stf_dialog_result_free (dialogresult);
321 		} else
322 			cr = gnm_cell_region_new (NULL);
323 	}
324 
325 	return cr;
326 }
327 
328 static void
text_content_received(GtkClipboard * clipboard,GtkSelectionData * sel,gpointer closure)329 text_content_received (GtkClipboard *clipboard, GtkSelectionData *sel,
330 		       gpointer closure)
331 {
332 	GnmGtkClipboardCtxt *ctxt = closure;
333 	WBCGtk *wbcg = ctxt->wbcg;
334 	WorkbookControl	   *wbc  = GNM_WBC (wbcg);
335 	GnmPasteTarget	   *pt   = ctxt->paste_target;
336 	GnmCellRegion *content = NULL;
337 	GdkAtom target = gtk_selection_data_get_target (sel);
338 	int sel_len = gtk_selection_data_get_length (sel);
339 
340 	paste_to_gnumeric (sel, "text");
341 
342 	/* Nothing on clipboard? */
343 	if (sel_len < 0) {
344 		;
345 	} else if (target == atoms[ATOM_UTF8_STRING]) {
346 		content = text_to_cell_region (wbcg, (const char *)gtk_selection_data_get_data (sel),
347 					       sel_len, "UTF-8", TRUE);
348 	} else if (target == atoms[ATOM_COMPOUND_TEXT]) {
349 		/* COMPOUND_TEXT is icky.  Just let GTK+ do the work.  */
350 		char *data_utf8 = (char *)gtk_selection_data_get_text (sel);
351 		content = text_to_cell_region (wbcg, data_utf8, strlen (data_utf8), "UTF-8", TRUE);
352 		g_free (data_utf8);
353 	} else if (target == atoms[ATOM_STRING]) {
354 		char const *locale_encoding;
355 		g_get_charset (&locale_encoding);
356 
357 		content = text_to_cell_region (wbcg, (const char *)gtk_selection_data_get_data (sel),
358 					       sel_len, locale_encoding, FALSE);
359 	}
360 	if (content) {
361 		/*
362 		 * if the conversion from the X selection -> a cellregion
363 		 * was canceled this may have content sized -1,-1
364 		 */
365 		if (content->cols > 0 && content->rows > 0)
366 			cmd_paste_copy (wbc, pt, content);
367 
368 		/* Release the resources we used */
369 		cellregion_unref (content);
370 	}
371 
372 	gnm_gtk_clipboard_context_free (ctxt);
373 }
374 
375 static void
utf8_content_received(GtkClipboard * clipboard,const gchar * text,gpointer closure)376 utf8_content_received (GtkClipboard *clipboard, const gchar *text,
377 		       gpointer closure)
378 {
379 	GnmGtkClipboardCtxt *ctxt = closure;
380 	WBCGtk *wbcg = ctxt->wbcg;
381 	WorkbookControl	   *wbc  = GNM_WBC (wbcg);
382 	GnmPasteTarget	   *pt   = ctxt->paste_target;
383 	GnmCellRegion *content = NULL;
384 
385 	/* Nothing on clipboard? */
386 	if (!text || strlen(text) == 0) {
387 		;
388 	} else {
389 		content = text_to_cell_region (wbcg, text, strlen(text), "UTF-8", TRUE);
390 	}
391 	if (content) {
392 		/*
393 		 * if the conversion from the X selection -> a cellregion
394 		 * was canceled this may have content sized -1,-1
395 		 */
396 		if (content->cols > 0 && content->rows > 0)
397 			cmd_paste_copy (wbc, pt, content);
398 
399 		/* Release the resources we used */
400 		cellregion_unref (content);
401 	}
402 
403 	gnm_gtk_clipboard_context_free (ctxt);
404 }
405 
406 /*
407  * Use the file_opener plugin service to read into a temporary workbook, in
408  * order to copy from it to the paste target. A temporary sheet would do just
409  * as well, but the file_opener service makes workbooks, not sheets.
410  *
411  * We use the file_opener service by wrapping the selection data in a GsfInput,
412  * and calling workbook_view_new_from_input.
413  **/
414 static GnmCellRegion *
table_cellregion_read(WorkbookControl * wbc,char const * reader_id,GnmPasteTarget * pt,const guchar * buffer,int length)415 table_cellregion_read (WorkbookControl *wbc, char const *reader_id,
416 		       GnmPasteTarget *pt, const guchar *buffer, int length)
417 {
418 	WorkbookView *wb_view = NULL;
419 	Workbook *wb = NULL;
420 	GnmCellRegion *ret = NULL;
421 	const GOFileOpener *reader = go_file_opener_for_id (reader_id);
422 	GOIOContext *ioc;
423 	GsfInput *input;
424 
425 	if (!reader) {
426 		// Likely cause: plugin not loaded
427 		g_warning ("No file opener for %s", reader_id);
428 		return NULL;
429 	}
430 
431 	ioc = go_io_context_new (GO_CMD_CONTEXT (wbc));
432 	input = gsf_input_memory_new (buffer, length, FALSE);
433 	wb_view = workbook_view_new_from_input  (input, NULL, reader, ioc, NULL);
434 	if (go_io_error_occurred (ioc) || wb_view == NULL) {
435 		go_io_error_display (ioc);
436 		goto out;
437 	}
438 
439 	wb = wb_view_get_workbook (wb_view);
440 	if (workbook_sheet_count (wb) > 0) {
441 		GnmRange r;
442 		Sheet *tmpsheet = workbook_sheet_by_index (wb, 0);
443 		GnmRange *rp = g_object_get_data (G_OBJECT (tmpsheet),
444 						  "DIMENSION");
445 		if (rp) {
446 			r = *rp;
447 		} else {
448 			// File format didn't tell us the range being
449 			// pasted.  Looking at you, LibreOffice!
450 			// Make a guess.
451 
452 			GnmRange fullr;
453 			GnmStyle **col_defaults =
454 				sheet_style_most_common (tmpsheet, TRUE);
455 
456 			range_init_full_sheet (&fullr, tmpsheet);
457 
458 			r = sheet_get_cells_extent (tmpsheet);
459 			sheet_style_get_nondefault_extent
460 				(tmpsheet, &r, &fullr, col_defaults);
461 
462 			g_free (col_defaults);
463 
464 			// Just in case there was absolutely nothing in
465 			// tmpsheet:
466 			if (r.start.col > r.end.col)
467 				range_init (&r, 0, 0, 0, 0);
468 		}
469 		ret = clipboard_copy_range (tmpsheet, &r);
470 	}
471 
472 	/* This isn't particularly right, but we are going to delete
473 	   the workbook shortly.  See #490479.  */
474 	WORKBOOK_FOREACH_SHEET (wb, sheet, {
475 		cellregion_invalidate_sheet (ret, sheet);
476 	});
477 
478 out:
479 	if (wb_view)
480 		g_object_unref (wb_view);
481 	if (wb)
482 		g_object_unref (wb);
483 	g_object_unref (ioc);
484 	g_object_unref (input);
485 
486 	return ret;
487 }
488 
489 static void
image_content_received(GtkClipboard * clipboard,GtkSelectionData * sel,gpointer closure)490 image_content_received (GtkClipboard *clipboard, GtkSelectionData *sel,
491 			gpointer closure)
492 {
493 	GnmGtkClipboardCtxt *ctxt = closure;
494 	WBCGtk *wbcg = ctxt->wbcg;
495 	GnmPasteTarget *pt = ctxt->paste_target;
496 	int sel_len = gtk_selection_data_get_length (sel);
497 
498 	paste_to_gnumeric (sel, "image");
499 
500 	if (sel_len > 0) {
501 		scg_paste_image (wbcg_cur_scg (wbcg), &pt->range,
502 				 gtk_selection_data_get_data (sel), sel_len);
503 	}
504 
505 	gnm_gtk_clipboard_context_free (ctxt);
506 }
507 
508 static void
urilist_content_received(GtkClipboard * clipboard,GtkSelectionData * sel,gpointer closure)509 urilist_content_received (GtkClipboard *clipboard, GtkSelectionData *sel,
510 			  gpointer closure)
511 {
512 	GnmGtkClipboardCtxt *ctxt = closure;
513 	WBCGtk *wbcg = ctxt->wbcg;
514 	GnmPasteTarget *pt = ctxt->paste_target;
515 	int sel_len = gtk_selection_data_get_length (sel);
516 
517 	paste_to_gnumeric (sel, "urilist");
518 
519 	if (sel_len > 0) {
520 		char *text = g_strndup (gtk_selection_data_get_data (sel), sel_len);
521 		GSList *uris = go_file_split_urls (text);
522 		GSList *l;
523 		g_free (text);
524 
525 		for (l = uris; l; l = l->next) {
526 			const char *uri = l->data;
527 			GsfInput *input;
528 			gsf_off_t size;
529 			gconstpointer data;
530 			char *mime;
531 			gboolean qimage;
532 
533 			if (g_str_equal (uri, "copy"))
534 				continue;
535 			mime = go_get_mime_type (uri);
536 			qimage = (strncmp (mime, "image/", 6) == 0);
537 			g_free (mime);
538 			if (!qimage)
539 				continue;
540 
541 			input = go_file_open (uri, NULL);
542 			if (!input)
543 				continue;
544 			size = gsf_input_size (input);
545 			data = gsf_input_read (input, size, NULL);
546 			if (data)
547 				scg_paste_image (wbcg_cur_scg (wbcg), &pt->range,
548 						 data, size);
549 			g_object_unref (input);
550 		}
551 
552 		g_slist_free_full (uris, g_free);
553 
554 	}
555 
556 	gnm_gtk_clipboard_context_free (ctxt);
557 }
558 
559 static void
parse_ms_headers(const char * data,size_t length,size_t * start,size_t * end)560 parse_ms_headers (const char *data, size_t length, size_t *start, size_t *end)
561 {
562 	GHashTable *headers = g_hash_table_new_full
563 		(g_str_hash, g_str_equal, g_free, g_free);
564 	size_t limit = length;
565 	size_t i = 0;
566 	char *key = NULL;
567 	char *value = NULL;
568 	long sf, ef;
569 	const char *v;
570 
571 	while (i < limit && data[i] != '<') {
572 		size_t j, k;
573 
574 		for (j = i; j < limit; j++) {
575 			if (data[j] == ':') {
576 				key = g_strndup (data + i, j - i);
577 				break;
578 			}
579 			if (g_ascii_isspace (data[j]))
580 				goto bad;
581 		}
582 		if (j >= limit)
583 			goto bad;
584 		j++;
585 
586 		for (k = j; k < limit; k++) {
587 			if (data[k] == '\n' || data[k] == '\r') {
588 				value = g_strndup (data + j, k - j);
589 				break;
590 			}
591 		}
592 		if (k >= limit)
593 			goto bad;
594 		while (g_ascii_isspace (data[k]))
595 			k++;
596 
597 		i = k;
598 
599 		if (debug_clipboard)
600 			g_printerr ("MS HTML Header [%s] => [%s]\n", key, value);
601 
602 		if (strcmp (key, "StartHTML") == 0) {
603 			long l = strtol (value, NULL, 10);
604 			limit = MIN (limit, (size_t)MAX (0, l));
605 		}
606 
607 		g_hash_table_replace (headers, key, value);
608 		key = value = NULL;
609 	}
610 
611 	v = g_hash_table_lookup (headers, "StartFragment");
612 	sf = v ? strtol (v, NULL, 10) : -1;
613 	if (sf < (long)limit)
614 		goto bad;
615 
616 	v = g_hash_table_lookup (headers, "EndFragment");
617 	ef = v ? strtol (v, NULL, 10) : -1;
618 	if (ef < sf || ef > (long)length)
619 		goto bad;
620 
621 	*start = sf;
622 	*end = ef;
623 	goto out;
624 
625  bad:
626 	g_free (key);
627 	g_free (value);
628 	*start = 0;
629 	*end = length;
630 
631  out:
632 	g_hash_table_destroy (headers);
633 }
634 
635 static void
table_content_received(GtkClipboard * clipboard,GtkSelectionData * sel,gpointer closure)636 table_content_received (GtkClipboard *clipboard, GtkSelectionData *sel,
637 			gpointer closure)
638 {
639 	GnmGtkClipboardCtxt *ctxt = closure;
640 	WBCGtk *wbcg = ctxt->wbcg;
641 	WorkbookControl	   *wbc  = GNM_WBC (wbcg);
642 	GnmPasteTarget	   *pt   = ctxt->paste_target;
643 	GnmCellRegion *content = NULL;
644 	GdkAtom target = gtk_selection_data_get_target (sel);
645 	const guint8 *buffer = gtk_selection_data_get_data (sel);
646 	int sel_len = gtk_selection_data_get_length (sel);
647 
648 	paste_to_gnumeric (sel, "table");
649 
650 	/* Nothing on clipboard? */
651 	if (sel_len < 0) {
652 		;
653 	} else if (target == atoms[ATOM_GNUMERIC]) {
654 		/* The data is the gnumeric specific XML interchange format */
655 		GOIOContext *io_context =
656 			go_io_context_new (GO_CMD_CONTEXT (wbcg));
657 		content = gnm_xml_cellregion_read
658 			(wbc, io_context,
659 			 pt->sheet,
660 			 (const char *)buffer, sel_len);
661 		g_object_unref (io_context);
662 	} else if (target == atoms[ATOM_OOO] ||
663 		   target == atoms[ATOM_OOO_WINDOWS] ||
664 		   target == atoms[ATOM_OOO11]) {
665 		content = table_cellregion_read (wbc, OOO_FILE_OPENER,
666 						 pt, buffer,
667 						 sel_len);
668 	} else if (target == atoms[ATOM_TEXT_HTML] ||
669 		   target == atoms[ATOM_TEXT_HTML_WINDOWS]) {
670 		size_t start = 0, end = sel_len;
671 
672 		if (target == atoms[ATOM_TEXT_HTML_WINDOWS]) {
673 			/* See bug 143084 */
674 			parse_ms_headers (buffer, sel_len, &start, &end);
675 		}
676 
677 		content = table_cellregion_read (wbc, HTML_FILE_OPENER,
678 						 pt,
679 						 buffer + start,
680 						 end - start);
681 	} else if (target == atoms[ATOM_BIFF8] ||
682 		   target == atoms[ATOM_BIFF8_CITRIX] ||
683 		   target == atoms[ATOM_BIFF8_OO] ||
684 		   target == atoms[ATOM_BIFF5] ||
685 		   target == atoms[ATOM_BIFF]) {
686 		content = table_cellregion_read (wbc, EXCEL_FILE_OPENER,
687 						 pt, buffer,
688 						 sel_len);
689 	}
690 	if (content) {
691 		/*
692 		 * if the conversion from the X selection -> a cellregion
693 		 * was canceled this may have content sized -1,-1
694 		 */
695 		if ((content->cols > 0 && content->rows > 0) ||
696 		    content->objects != NULL)
697 			cmd_paste_copy (wbc, pt, content);
698 
699 		/* Release the resources we used */
700 		cellregion_unref (content);
701 	}
702 
703 	gnm_gtk_clipboard_context_free (ctxt);
704 }
705 
706 static gboolean
find_in_table(GdkAtom * targets,int n,GdkAtom a)707 find_in_table (GdkAtom *targets, int n, GdkAtom a)
708 {
709 	int i;
710 	for (i = 0; i < n; i++)
711 		if (targets[i] == a)
712 			return TRUE;
713 	return FALSE;
714 }
715 
716 /**
717  * x_targets_received:
718  *
719  * Invoked when the selection has been received by our application.
720  * This is triggered by a call we do to gtk_clipboard_request_contents.
721  *
722  * We try to import a spreadsheet/table, next an image, and finally fall back
723  * to a string format if the others fail, e.g. for html which does not
724  * contain a table.
725  */
726 static void
x_targets_received(GtkClipboard * clipboard,GdkAtom * targets,gint n_targets,gpointer closure)727 x_targets_received (GtkClipboard *clipboard, GdkAtom *targets,
728 		    gint n_targets, gpointer closure)
729 {
730 	GnmGtkClipboardCtxt *ctxt = closure;
731 	int i;
732 	unsigned ui;
733 
734 	// In order of preference
735 	static const struct {
736 		int a;
737 		const char *opener_id;
738 	} table_fmts[] = {
739 		{ ATOM_GNUMERIC, NULL },
740 		{ ATOM_BIFF8, EXCEL_FILE_OPENER },
741 		{ ATOM_BIFF8_CITRIX, EXCEL_FILE_OPENER },
742 		{ ATOM_OOO, OOO_FILE_OPENER },
743 		{ ATOM_OOO11, OOO_FILE_OPENER },
744 		{ ATOM_OOO_WINDOWS, OOO_FILE_OPENER },
745 		{ ATOM_BIFF5, EXCEL_FILE_OPENER },
746 		{ ATOM_BIFF, EXCEL_FILE_OPENER },
747 		{ ATOM_TEXT_HTML, HTML_FILE_OPENER },
748 		{ ATOM_TEXT_HTML_WINDOWS, HTML_FILE_OPENER },
749 	};
750 
751 	// In order of preference
752 	static const int uri_list_fmts[] = {
753 		ATOM_TEXT_URI_LIST,
754 		ATOM_GNOME_COPIED_FILES,
755 		ATOM_KDE_CUT_FILES,
756 	};
757 
758 	// In order of preference
759 	static const int string_fmts[] = {
760 		ATOM_UTF8_STRING,
761 		ATOM_STRING,
762 		ATOM_COMPOUND_TEXT
763 	};
764 
765 	// Nothing on clipboard?  Try text.
766 	if (targets == NULL || n_targets == 0) {
767 		gtk_clipboard_request_text (clipboard, utf8_content_received,
768 					    ctxt);
769 		return;
770 	}
771 
772 	if (debug_clipboard) {
773 		int j;
774 
775 		for (j = 0; j < n_targets; j++) {
776 			char *name = gdk_atom_name (targets[j]);
777 			g_printerr ("Clipboard target %d is %s\n",
778 				    j, name);
779 			g_free (name);
780 		}
781 	}
782 
783 	// First look for anything that can be considered a spreadsheet
784 	for (ui = 0; ui < G_N_ELEMENTS(table_fmts); ui++) {
785 		GdkAtom atom = atoms[table_fmts[ui].a];
786 		const char *opener = table_fmts[ui].opener_id;
787 		if ((opener == NULL || has_file_opener (opener)) &&
788 		    find_in_table (targets, n_targets, atom)) {
789 			gtk_clipboard_request_contents (clipboard, atom,
790 							table_content_received,
791 							ctxt);
792 			return;
793 		}
794 	}
795 
796 	// Try an image format
797 	for (i = 0; i < n_targets; i++) {
798 		GdkAtom atom = targets[i];
799 		if (gtk_target_list_find (image_targets, atom, NULL)) {
800 			gtk_clipboard_request_contents (clipboard, atom,
801 							image_content_received,
802 							ctxt);
803 			return;
804 		}
805 	}
806 
807 	// Try a uri list format
808 	for (ui = 0; ui < G_N_ELEMENTS (uri_list_fmts); ui++) {
809 		GdkAtom atom = atoms[uri_list_fmts[ui]];
810 		if (find_in_table (targets, n_targets, atom)) {
811 			gtk_clipboard_request_contents (clipboard, atom,
812 							urilist_content_received,
813 							ctxt);
814 			return;
815 		}
816 	}
817 
818 	// Try a string format
819 	for (ui = 0; ui < G_N_ELEMENTS (string_fmts); ui++) {
820 		GdkAtom atom = atoms[string_fmts[ui]];
821 		if (find_in_table (targets, n_targets, atom)) {
822 			gtk_clipboard_request_contents (clipboard, atom,
823 							text_content_received,
824 							ctxt);
825 			return;
826 		}
827 	}
828 
829 	// Give up
830 	gnm_gtk_clipboard_context_free (ctxt);
831 }
832 
833 /* Cheezy implementation: paste into a temporary workbook, save that. */
834 static guchar *
table_cellregion_write(GOCmdContext * ctx,GnmCellRegion * cr,const char * saver_id,int * size)835 table_cellregion_write (GOCmdContext *ctx, GnmCellRegion *cr,
836 			const char *saver_id, int *size)
837 {
838 	guchar *ret = NULL;
839 	const GOFileSaver *saver;
840 	GsfOutput *output;
841 	GOIOContext *ioc;
842 	Workbook *wb;
843 	WorkbookView *wb_view;
844 	Sheet *sheet;
845 	GnmPasteTarget pt;
846 	GnmRange r;
847 
848 	if (debug_clipboard_undump) {
849 		gsize siz;
850 		gchar *contents;
851 		if (g_file_get_contents ("paste-from-gnumeric.dat", &contents,
852 					 &siz, NULL)) {
853 			g_printerr ("Sending %d prepackaged bytes.\n",
854 				    (int)siz);
855 			*size = siz;
856 			return (guchar *)contents;
857 		}
858 	}
859 
860 	*size = 0;
861 
862 	saver = go_file_saver_for_id (saver_id);
863 	if (!saver) {
864 		// Likely cause: plugin not loaded
865 		g_printerr ("Failed to get saver for %s for clipboard use.\n",
866 			    saver_id);
867 		return NULL;
868 	}
869 
870 	output = gsf_output_memory_new ();
871 	ioc = go_io_context_new (ctx);
872 
873 	{
874 		int cols = cr->cols;
875 		int rows = cr->rows;
876 		gnm_sheet_suggest_size (&cols, &rows);
877 		wb = workbook_new ();
878 		workbook_sheet_add (wb, -1, cols, rows);
879 	}
880 
881 	wb_view = workbook_view_new (wb);
882 
883 	sheet = workbook_sheet_by_index (wb, 0);
884 	range_init (&r, 0, 0, cr->cols - 1, cr->rows - 1);
885 
886 	paste_target_init (&pt, sheet, &r,
887 			   PASTE_AS_VALUES | PASTE_FORMATS |
888 			   PASTE_COMMENTS | PASTE_OBJECTS);
889 	if (clipboard_paste_region (cr, &pt, ctx) == FALSE) {
890 		go_file_saver_save (saver, ioc, GO_VIEW (wb_view), output);
891 		if (!go_io_error_occurred (ioc)) {
892 			GsfOutputMemory *omem = GSF_OUTPUT_MEMORY (output);
893 			gsf_off_t osize = gsf_output_size (output);
894 			const guint8 *data = gsf_output_memory_get_bytes (omem);
895 
896 			*size = osize;
897 			if (*size == osize) {
898 				ret = g_memdup (data, *size);
899 			} else {
900 				g_warning ("Overflow");	/* Far fetched! */
901 			}
902 		}
903 	}
904 	if (!gsf_output_is_closed (output))
905 		gsf_output_close (output);
906 	g_object_unref (wb_view);
907 	g_object_unref (wb);
908 	g_object_unref (ioc);
909 	g_object_unref (output);
910 
911 	return ret;
912 }
913 
914 static guchar *
image_write(GnmCellRegion * cr,gchar const * mime_type,int * size)915 image_write (GnmCellRegion *cr, gchar const *mime_type, int *size)
916 {
917 	guchar *ret = NULL;
918 	SheetObject *so = NULL;
919 	char *format;
920 	GsfOutput *output;
921 	GsfOutputMemory *omem;
922 	gsf_off_t osize;
923 	GSList *l;
924 
925 	*size = -1;
926 
927 	g_return_val_if_fail (cr->objects != NULL, NULL);
928 	so = GNM_SO (cr->objects->data);
929 	g_return_val_if_fail (so != NULL, NULL);
930 
931 	for (l = cr->objects; l != NULL; l = l->next) {
932 		if (GNM_IS_SO_IMAGEABLE (GNM_SO (l->data))) {
933 			so = GNM_SO (l->data);
934 			break;
935 		}
936 	}
937 	if (so == NULL) {
938 		// This shouldn't happen
939 		g_warning ("non-imageable object requested as image\n");
940 		return ret;
941 	}
942 
943 	format = go_mime_to_image_format (mime_type);
944 	if (!format) {
945 		// This shouldn't happen
946 		g_warning ("No image format for %s\n", mime_type);
947 		return ret;
948 	}
949 
950 	output = gsf_output_memory_new ();
951 	omem   = GSF_OUTPUT_MEMORY (output);
952 	sheet_object_write_image (so, format, 150.0, output, NULL);
953 	osize = gsf_output_size (output);
954 
955 	*size = osize;
956 	if (*size == osize) {
957 		ret = g_malloc (*size);
958 		memcpy (ret, gsf_output_memory_get_bytes (omem), *size);
959 	} else {
960 		g_warning ("Overflow");	/* Far fetched! */
961 	}
962 	gsf_output_close (output);
963 	g_object_unref (output);
964 	g_free (format);
965 
966 	return ret;
967 }
968 
969 static guchar *
object_write(GnmCellRegion * cr,gchar const * mime_type,int * size)970 object_write (GnmCellRegion *cr, gchar const *mime_type, int *size)
971 {
972 	guchar *ret = NULL;
973 	SheetObject *so = NULL;
974 	GsfOutput *output;
975 	GsfOutputMemory *omem;
976 	gsf_off_t osize;
977 	GSList *l;
978 
979 	*size = -1;
980 
981 	g_return_val_if_fail (cr->objects != NULL, NULL);
982 	so = GNM_SO (cr->objects->data);
983 	g_return_val_if_fail (so != NULL, NULL);
984 
985 	for (l = cr->objects; l != NULL; l = l->next) {
986 		if (GNM_IS_SO_EXPORTABLE (GNM_SO (l->data))) {
987 			so = GNM_SO (l->data);
988 			break;
989 		}
990 	}
991 	if (so == NULL) {
992 		g_warning ("non exportable object requested\n");
993 		return ret;
994 	}
995 	output = gsf_output_memory_new ();
996 	omem   = GSF_OUTPUT_MEMORY (output);
997 	sheet_object_write_object (so, mime_type, output, NULL,
998 				   gnm_conventions_default);
999 	osize = gsf_output_size (output);
1000 
1001 	*size = osize;
1002 	if (*size == osize)
1003 		ret = g_memdup (gsf_output_memory_get_bytes (omem), *size);
1004 	else
1005 		g_warning ("Overflow");	/* Far fetched! */
1006 	gsf_output_close (output);
1007 	g_object_unref (output);
1008 
1009 	return ret;
1010 }
1011 
1012 /*
1013  * x_clipboard_get_cb
1014  *
1015  * Callback invoked when another application requests we render the selection.
1016  */
1017 static void
x_clipboard_get_cb(GtkClipboard * gclipboard,GtkSelectionData * selection_data,guint info_,G_GNUC_UNUSED gpointer app)1018 x_clipboard_get_cb (GtkClipboard *gclipboard,
1019 		    GtkSelectionData *selection_data,
1020 		    guint info_, G_GNUC_UNUSED gpointer app)
1021 {
1022 	gboolean to_gnumeric = FALSE, content_needs_free = FALSE;
1023 	GnmCellRegion *clipboard = gnm_app_clipboard_contents_get ();
1024 	Sheet *sheet = gnm_app_clipboard_sheet_get ();
1025 	GnmRange const *a = gnm_app_clipboard_area_get ();
1026 	GOCmdContext *ctx = gnm_cmd_context_stderr_new ();
1027 	GdkAtom target = gclipboard
1028 		? gtk_selection_data_get_target (selection_data)
1029 		: gtk_selection_data_get_data_type (selection_data); // testing
1030 	AtomInfoType info = info_;
1031 	gchar *target_name = gdk_atom_name (target);
1032 
1033 	if (debug_clipboard)
1034 		g_printerr ("clipboard requested, target=%s\n", target_name);
1035 
1036 	/*
1037 	 * There are 4 cases. What variables are valid depends on case:
1038 	 * source is
1039 	 *   a cut: clipboard NULL, sheet, area non-NULL.
1040          *   a copy: clipboard, sheet, area all non-NULL.
1041 	 *   a cut, source closed: clipboard, sheet, area all NULL.
1042 	 *   a copy, source closed: clipboard non-NULL, sheet, area non-NULL.
1043 	 *
1044 	 * If the source is a cut, we copy it for pasting.  We
1045 	 * postpone clearing it until after the selection has been
1046 	 * rendered to the requested format.
1047 	 */
1048 	if (clipboard == NULL && sheet != NULL) {
1049 		content_needs_free = TRUE;
1050 		clipboard = clipboard_copy_range (sheet, a);
1051 	}
1052 
1053 	if (clipboard == NULL)
1054 		goto out;
1055 
1056 	/* What format does the other application want? */
1057 	if (target == atoms[ATOM_GNUMERIC]) {
1058 		GsfOutputMemory *output  = gnm_cellregion_to_xml (clipboard);
1059 		if (output) {
1060 			gsf_off_t size = gsf_output_size (GSF_OUTPUT (output));
1061 			gconstpointer data = gsf_output_memory_get_bytes (output);
1062 
1063 			paste_from_gnumeric (selection_data, target,
1064 					     data, size);
1065 			g_object_unref (output);
1066 			to_gnumeric = TRUE;
1067 		}
1068 	} else if (info == INFO_HTML) {
1069 		int size;
1070 		guchar *buffer = table_cellregion_write (ctx, clipboard,
1071 							 HTML_FILE_SAVER,
1072 							 &size);
1073 		paste_from_gnumeric (selection_data, target, buffer, size);
1074 		g_free (buffer);
1075 	} else if (info == INFO_EXCEL) {
1076 		int size;
1077 		guchar *buffer = table_cellregion_write (ctx, clipboard,
1078 							 EXCEL_FILE_SAVER,
1079 							 &size);
1080 		paste_from_gnumeric (selection_data, target, buffer, size);
1081 		g_free (buffer);
1082 	} else if (target == atoms[ATOM_GOFFICE_GRAPH] ||
1083 	           g_slist_find_custom (go_components_get_mime_types (), target_name, (GCompareFunc) strcmp) != NULL) {
1084 		int size;
1085 		guchar *buffer = object_write (clipboard, target_name, &size);
1086 		paste_from_gnumeric (selection_data, target, buffer, size);
1087 		g_free (buffer);
1088 	} else if (info == INFO_IMAGE) {
1089 		int size;
1090 		guchar *buffer = image_write (clipboard, target_name, &size);
1091 		paste_from_gnumeric (selection_data, target, buffer, size);
1092 		g_free (buffer);
1093 	} else if (target == atoms[ATOM_SAVE_TARGETS]) {
1094 		// We implicitly registered this target when calling
1095 		// gtk_clipboard_set_can_store. We're supposed to ignore it.
1096 	} else if (info == INFO_GENERIC_TEXT) {
1097 		Workbook *wb = clipboard->origin_sheet->workbook;
1098 		GString *res = cellregion_to_string (clipboard,
1099 			TRUE, workbook_date_conv (wb));
1100 		if (res != NULL) {
1101 			if (debug_clipboard)
1102 				g_message ("clipboard text of %d bytes",
1103 					   (int)res->len);
1104 			gtk_selection_data_set_text (selection_data,
1105 						     res->str, res->len);
1106 			g_string_free (res, TRUE);
1107 		} else {
1108 			if (debug_clipboard)
1109 				g_message ("clipboard empty text");
1110 			gtk_selection_data_set_text (selection_data, "", 0);
1111 		}
1112 	} else
1113 		gtk_selection_data_set_text (selection_data, "", 0);
1114 
1115 	/*
1116 	 * If this was a CUT operation we need to clear the content that
1117 	 * was pasted into another application and release the stuff on
1118 	 * the clipboard
1119 	 */
1120 	if (content_needs_free) {
1121 		/* If the other app was a gnumeric, emulate a cut */
1122 		if (to_gnumeric) {
1123 			GOUndo *redo, *undo;
1124 			GnmSheetRange *sr    = gnm_sheet_range_new (sheet, a);
1125 			SheetView const *sv  = gnm_app_clipboard_sheet_view_get ();
1126 			SheetControl *sc     = g_ptr_array_index (sv->controls, 0);
1127 			WorkbookControl *wbc = sc_wbc (sc);
1128 			char *name;
1129 			char *text;
1130 
1131 			redo = sheet_clear_region_undo
1132 				(sr,
1133 				 CLEAR_VALUES|CLEAR_COMMENTS|CLEAR_RECALC_DEPS);
1134 			undo = clipboard_copy_range_undo (sheet, a);
1135 			name = undo_range_name (sheet, a);
1136 			text = g_strdup_printf (_("Cut of %s"), name);
1137 			g_free (name);
1138 			cmd_generic (wbc, text, undo, redo);
1139 			g_free (text);
1140 			gnm_app_clipboard_clear (TRUE);
1141 		}
1142 
1143 		cellregion_unref (clipboard);
1144 	}
1145  out:
1146 	g_free (target_name);
1147 	g_object_unref (ctx);
1148 }
1149 
1150 /**
1151  * x_clipboard_clear_cb:
1152  *
1153  * Callback for the "we lost the X selection" signal.
1154  */
1155 static void
x_clipboard_clear_cb(GtkClipboard * clipboard,gpointer app_)1156 x_clipboard_clear_cb (GtkClipboard *clipboard, gpointer app_)
1157 {
1158 	if (debug_clipboard)
1159 		g_printerr ("Lost clipboard ownership.\n");
1160 
1161 	gnm_app_clipboard_clear (FALSE);
1162 }
1163 
1164 void
gnm_x_request_clipboard(WBCGtk * wbcg,GnmPasteTarget const * pt)1165 gnm_x_request_clipboard (WBCGtk *wbcg, GnmPasteTarget const *pt)
1166 {
1167 	GnmGtkClipboardCtxt *ctxt;
1168 	GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (wbcg_toplevel (wbcg)));
1169 	GtkClipboard *clipboard =
1170 		gtk_clipboard_get_for_display
1171 		(display,
1172 		 gnm_conf_get_cut_and_paste_prefer_clipboard ()
1173 		 ? GDK_SELECTION_CLIPBOARD
1174 		 : GDK_SELECTION_PRIMARY);
1175 
1176 	ctxt = g_new (GnmGtkClipboardCtxt, 1);
1177 	ctxt->wbcg = wbcg;
1178 	ctxt->paste_target = g_memdup (pt, sizeof (*pt));
1179 
1180 	/* Query the formats, This will callback x_targets_received */
1181 	gtk_clipboard_request_targets (clipboard,
1182 				       x_targets_received, ctxt);
1183 }
1184 
1185 static void
cb_clear_target_entry(gpointer te_)1186 cb_clear_target_entry (gpointer te_)
1187 {
1188 	GtkTargetEntry *te = te_;
1189 	g_free (te->target);
1190 }
1191 
1192 static void
add_target(GArray * targets,const char * target,int flags,AtomInfoType info)1193 add_target (GArray *targets, const char *target, int flags, AtomInfoType info)
1194 {
1195 	GtkTargetEntry t;
1196 	t.target = g_strdup (target);
1197 	t.flags = flags;
1198 	t.info = info;
1199 	g_array_append_val (targets, t);
1200 }
1201 
1202 static gboolean
is_clipman_target(const char * target)1203 is_clipman_target (const char *target)
1204 {
1205 	return (g_str_equal (target, atom_names[ATOM_GNUMERIC]) ||
1206 		g_str_equal (target, atom_names[ATOM_GOFFICE_GRAPH]) ||
1207 		g_str_equal (target, atom_names[ATOM_TEXT_HTML]) ||
1208 		g_str_equal (target, atom_names[ATOM_UTF8_STRING]) ||
1209 		g_str_equal (target, atom_names[ATOM_BIFF8_OO]) ||
1210 		g_str_equal (target, atom_names[ATOM_IMAGE_SVGXML]) ||
1211 		g_str_equal (target, atom_names[ATOM_IMAGE_XWMF]) ||
1212 		g_str_equal (target, atom_names[ATOM_IMAGE_XEMF]) ||
1213 		g_str_equal (target, atom_names[ATOM_IMAGE_PNG]) ||
1214 		g_str_equal (target, atom_names[ATOM_IMAGE_JPEG]));
1215 }
1216 
1217 /* Restrict the	set of formats offered to clipboard manager. */
1218 static void
set_clipman_targets(GdkDisplay * disp,GArray * targets)1219 set_clipman_targets (GdkDisplay *disp, GArray *targets)
1220 {
1221 	GArray *allowed = g_array_new (FALSE, FALSE, sizeof (GtkTargetEntry));
1222 	unsigned ui;
1223 
1224 	g_array_set_clear_func (allowed, cb_clear_target_entry);
1225 
1226 	for (ui = 0; ui < targets->len; ui++) {
1227 		GtkTargetEntry *te = &g_array_index (targets, GtkTargetEntry, ui);
1228 		if (is_clipman_target (te->target))
1229 			add_target (allowed, te->target, te->flags, te->info);
1230 	}
1231 
1232 	gtk_clipboard_set_can_store
1233 		(gtk_clipboard_get_for_display
1234 		 (disp, GDK_SELECTION_CLIPBOARD),
1235 		 &g_array_index (allowed, GtkTargetEntry, 0),
1236 		 allowed->len);
1237 
1238 	g_array_free (allowed, TRUE);
1239 }
1240 
1241 static void
add_target_list(GArray * targets,GtkTargetList * src,AtomInfoType info)1242 add_target_list (GArray *targets, GtkTargetList *src, AtomInfoType info)
1243 {
1244 	int i, n;
1245 	GtkTargetEntry *entries = gtk_target_table_new_from_list (src, &n);
1246 
1247 	for (i = 0; i < n; i++) {
1248 		GtkTargetEntry *te = entries + i;
1249 		add_target (targets, te->target, te->flags,
1250 			    info == INFO_UNKNOWN ? te->info : info);
1251 	}
1252 
1253 	gtk_target_table_free (entries, n);
1254 }
1255 
1256 gboolean
gnm_x_claim_clipboard(GdkDisplay * display)1257 gnm_x_claim_clipboard (GdkDisplay *display)
1258 {
1259 	GnmCellRegion *content = gnm_app_clipboard_contents_get ();
1260 	SheetObject *imageable = NULL, *exportable = NULL;
1261 	GArray *targets = g_array_new (FALSE, FALSE, sizeof (GtkTargetEntry));
1262 	gboolean ret;
1263 	GObject *app = gnm_app_get_app ();
1264 	gboolean no_cells = (!content) || (content->cols <= 0 || content->rows <= 0);
1265 
1266 	g_array_set_clear_func (targets, cb_clear_target_entry);
1267 
1268 	if (no_cells) {
1269 		GSList *ptr = content ? content->objects : NULL;
1270 
1271 		add_target (targets, atom_names[ATOM_GNUMERIC], 0, INFO_GNUMERIC);
1272 
1273 		for (; ptr != NULL; ptr = ptr->next) {
1274 			SheetObject *candidate = GNM_SO (ptr->data);
1275 			if (exportable == NULL && GNM_IS_SO_EXPORTABLE (candidate))
1276 				exportable = candidate;
1277 			if (imageable == NULL && GNM_IS_SO_IMAGEABLE (candidate))
1278 				imageable = candidate;
1279 		}
1280 	} else {
1281 		add_target (targets, atom_names[ATOM_GNUMERIC], 0, INFO_GNUMERIC);
1282 		if (has_file_saver (EXCEL_FILE_SAVER)) {
1283 			add_target (targets, atom_names[ATOM_BIFF8], 0, INFO_EXCEL);
1284 			add_target (targets, atom_names[ATOM_BIFF8_CITRIX], 0, INFO_EXCEL);
1285 			add_target (targets, atom_names[ATOM_BIFF8_OO], 0, INFO_EXCEL);
1286 		}
1287 		if (has_file_saver (HTML_FILE_SAVER)) {
1288 #ifdef G_OS_WIN32
1289 			add_target (targets, atom_names[ATOM_TEXT_HTML_WINDOWS], 0, INFO_HTML);
1290 #else
1291 			add_target (targets, atom_names[ATOM_TEXT_HTML], 0, INFO_HTML);
1292 #endif
1293 		}
1294 		add_target (targets, atom_names[ATOM_UTF8_STRING], 0, INFO_GENERIC_TEXT);
1295 		add_target (targets, atom_names[ATOM_COMPOUND_TEXT], 0, INFO_GENERIC_TEXT);
1296 		add_target (targets, atom_names[ATOM_STRING], 0, INFO_GENERIC_TEXT);
1297 	}
1298 
1299 	if (exportable) {
1300 		GtkTargetList *tl =
1301 			sheet_object_exportable_get_target_list (exportable);
1302 		add_target_list (targets, tl, INFO_OBJECT);
1303 		gtk_target_list_unref (tl);
1304 	}
1305 
1306 	if (imageable) {
1307 		GtkTargetList *tl =
1308 			sheet_object_get_target_list (imageable);
1309 		add_target_list (targets, tl, INFO_IMAGE);
1310 		gtk_target_list_unref (tl);
1311 	}
1312 
1313 	/* Register a x_clipboard_clear_cb only for CLIPBOARD, not for
1314 	 * PRIMARY */
1315 	ret = gtk_clipboard_set_with_owner (
1316 		gtk_clipboard_get_for_display (display, GDK_SELECTION_CLIPBOARD),
1317 		&g_array_index(targets,GtkTargetEntry,0), targets->len,
1318 		x_clipboard_get_cb,
1319 		x_clipboard_clear_cb,
1320 		app);
1321 	if (ret) {
1322 		if (debug_clipboard) {
1323 			unsigned ui;
1324 			g_printerr ("Clipboard successfully claimed.\n");
1325 			g_printerr ("Clipboard targets offered: ");
1326 			for (ui = 0; ui < targets->len; ui++) {
1327 				g_printerr ("%s%s",
1328 					    (ui ? ", " : ""),
1329 					    g_array_index(targets,GtkTargetEntry,ui).target);
1330 			}
1331 			g_printerr ("\n");
1332 		}
1333 
1334 		g_object_set_data_full (app, APP_CLIP_DISP_KEY,
1335 					g_slist_prepend (g_object_steal_data (app, APP_CLIP_DISP_KEY),
1336 							 display),
1337 					(GDestroyNotify)g_slist_free);
1338 
1339 		set_clipman_targets (display, targets);
1340 		(void)gtk_clipboard_set_with_owner (
1341 			gtk_clipboard_get_for_display (display,
1342 						       GDK_SELECTION_PRIMARY),
1343 			&g_array_index(targets,GtkTargetEntry,0), targets->len,
1344 			x_clipboard_get_cb,
1345 			NULL,
1346 			app);
1347 	} else {
1348 		if (debug_clipboard)
1349 			g_printerr ("Failed to claim clipboard.\n");
1350 	}
1351 
1352 	g_array_free (targets, TRUE);
1353 
1354 	return ret;
1355 }
1356 
1357 void
gnm_x_disown_clipboard(void)1358 gnm_x_disown_clipboard (void)
1359 {
1360 	GObject *app = gnm_app_get_app ();
1361 	GSList *displays = g_object_steal_data (app, APP_CLIP_DISP_KEY);
1362 	GSList *l;
1363 
1364 	for (l = displays; l; l = l->next) {
1365 		GdkDisplay *display = l->data;
1366 		gtk_selection_owner_set_for_display (display, NULL,
1367 						     GDK_SELECTION_PRIMARY,
1368 						     GDK_CURRENT_TIME);
1369 		gtk_selection_owner_set_for_display (display, NULL,
1370 						     GDK_SELECTION_CLIPBOARD,
1371 						     GDK_CURRENT_TIME);
1372 	}
1373 	g_slist_free (displays);
1374 }
1375 
1376 /* Hand clipboard off to clipboard manager. To be called before workbook
1377  * object is destroyed.
1378  */
1379 void
gnm_x_store_clipboard_if_needed(Workbook * wb)1380 gnm_x_store_clipboard_if_needed (Workbook *wb)
1381 {
1382 	Sheet *sheet = gnm_app_clipboard_sheet_get ();
1383 	WBCGtk *wbcg = NULL;
1384 
1385 	g_return_if_fail (GNM_IS_WORKBOOK (wb));
1386 
1387 	if (sheet && sheet->workbook == wb) {
1388 		WORKBOOK_FOREACH_CONTROL (wb, view, control, {
1389 			if (GNM_IS_WBC_GTK (control)) {
1390 				wbcg = WBC_GTK (control);
1391 			}
1392 		});
1393 
1394 		if (wbcg) {
1395 			GtkClipboard *clip = gtk_clipboard_get_for_display
1396 				(gtk_widget_get_display
1397 				 (GTK_WIDGET (wbcg_toplevel (wbcg))),
1398 				 GDK_SELECTION_CLIPBOARD);
1399 			if (gtk_clipboard_get_owner (clip) == gnm_app_get_app ()) {
1400 				if (debug_clipboard)
1401 					g_printerr ("Handing off clipboard\n");
1402 				gtk_clipboard_store (clip);
1403 			}
1404 		}
1405 	}
1406 }
1407 
1408 GBytes *
gui_clipboard_test(const char * fmt)1409 gui_clipboard_test (const char *fmt)
1410 {
1411 	GtkClipboard *gclipboard = NULL;
1412 	gpointer app = NULL;
1413 	GtkSelectionData *selection_data;
1414 	guint info;
1415 	unsigned ui;
1416 	GdkAtom atom = NULL;
1417 	const guchar *data;
1418 	gint len;
1419 	GBytes *res;
1420 
1421 	for (ui = 0; ui < G_N_ELEMENTS (atom_names); ui++) {
1422 		if (g_str_equal (fmt, atom_names[ui])) {
1423 			atom = atoms[ui];
1424 			break;
1425 		}
1426 	}
1427 	if (!atom)
1428 		return NULL;
1429 
1430 	switch (ui) {
1431 	case ATOM_GNUMERIC:
1432 		info = INFO_GNUMERIC;
1433 		break;
1434 	case ATOM_UTF8_STRING:
1435 	case ATOM_STRING:
1436 	case ATOM_COMPOUND_TEXT:
1437 		info = INFO_GENERIC_TEXT;
1438 		break;
1439 	case ATOM_TEXT_HTML:
1440 	case ATOM_TEXT_HTML_WINDOWS:
1441 		info = INFO_HTML;
1442 		break;
1443 	case ATOM_BIFF8:
1444 	case ATOM_BIFF8_OO:
1445 	case ATOM_BIFF8_CITRIX:
1446 	case ATOM_BIFF5:
1447 	case ATOM_BIFF:
1448 		info = INFO_EXCEL;
1449 		break;
1450 	case ATOM_OOO:
1451 	case ATOM_OOO_WINDOWS:
1452 	case ATOM_OOO11:
1453 		info = INFO_OOO;
1454 		break;
1455 	case ATOM_IMAGE_SVGXML:
1456 	case ATOM_IMAGE_XWMF:
1457 	case ATOM_IMAGE_XEMF:
1458 	case ATOM_IMAGE_PNG:
1459 	case ATOM_IMAGE_JPEG:
1460 	case ATOM_IMAGE_BMP:
1461 		info = INFO_IMAGE;
1462 		break;
1463 	default:
1464 		g_printerr ("Unknown info type\n");
1465 		info = INFO_UNKNOWN;
1466 	}
1467 
1468 	{
1469 		// This is more than a little bit dirty.  There is no good
1470 		// way to create a GtkSelectionData.
1471 		void *empty = g_new0 (char, 1000000);
1472 		selection_data = gtk_selection_data_copy (empty);
1473 		g_free (empty);
1474 	}
1475 
1476 	gtk_selection_data_set (selection_data, atom, 8, NULL, 0);
1477 	// No way to set target???
1478 
1479 	x_clipboard_get_cb (gclipboard, selection_data, info, app);
1480 	data = gtk_selection_data_get_data_with_length (selection_data, &len);
1481 	res = g_bytes_new (data, len);
1482 	gtk_selection_data_free (selection_data);
1483 	return res;
1484 }
1485 
1486 
1487 /**
1488  * gui_clipboard_init: (skip)
1489  */
1490 void
gui_clipboard_init(void)1491 gui_clipboard_init (void)
1492 {
1493 	unsigned ui;
1494 
1495 	debug_clipboard = gnm_debug_flag ("clipboard");
1496 	debug_clipboard_dump = gnm_debug_flag ("clipboard-dump");
1497 	debug_clipboard_undump = gnm_debug_flag ("clipboard-undump");
1498 
1499 	for (ui = 0; ui < G_N_ELEMENTS (atoms); ui++)
1500 		atoms[ui] = gdk_atom_intern_static_string (atom_names[ui]);
1501 
1502 	generic_text_targets = gtk_target_list_new (NULL, 0);
1503 	gtk_target_list_add_text_targets (generic_text_targets, INFO_GENERIC_TEXT);
1504 
1505 	image_targets = gtk_target_list_new (NULL, 0);
1506 	gtk_target_list_add_image_targets (image_targets, 0, FALSE);
1507 }
1508 
1509 /**
1510  * gui_clipboard_shutdown: (skip)
1511  */
1512 void
gui_clipboard_shutdown(void)1513 gui_clipboard_shutdown (void)
1514 {
1515 	gtk_target_list_unref (generic_text_targets);
1516 	gtk_target_list_unref (image_targets);
1517 }
1518