1 #include "unix/guts.h"
2 #include "Application.h"
3 #include "Clipboard.h"
4 #include "Icon.h"
5
6 #define WIN PComponent(application)-> handle
7
8 #define CF_NAME(x) (guts. clipboard_formats[(x)*3])
9 #define CF_TYPE(x) (guts. clipboard_formats[(x)*3+1])
10 #define CF_FORMAT(x) (guts. clipboard_formats[(x)*3+2])
11 #define CF_ASSIGN(i,a,b,c) CF_NAME(i)=(a);CF_TYPE(i)=(b);CF_FORMAT(i)=((Atom)c)
12
13 Bool
prima_init_clipboard_subsystem(char * error_buf)14 prima_init_clipboard_subsystem(char * error_buf)
15 {
16 guts. clipboards = hash_create();
17
18 if ( !(guts. clipboard_formats = malloc( cfCOUNT * 3 * sizeof(Atom)))) {
19 sprintf( error_buf, "No memory");
20 return false;
21 }
22 guts. clipboard_formats_count = cfCOUNT;
23 #if (cfText != 0) || (cfBitmap != 1) || (cfUTF8 != 2)
24 #error broken clipboard type formats
25 #endif
26
27 CF_ASSIGN(cfText, XA_STRING, XA_STRING, 8);
28 CF_ASSIGN(cfUTF8, UTF8_STRING, UTF8_STRING, 8);
29 CF_ASSIGN(cfBitmap, XA_PIXMAP, XA_PIXMAP, CF_32);
30 CF_ASSIGN(cfTargets, CF_TARGETS, XA_ATOM, CF_32);
31
32 /* XXX - bitmaps and indexed pixmaps may have the associated colormap or pixel values
33 CF_ASSIGN(cfPalette, XA_COLORMAP, XA_ATOM, CF_32);
34 CF_ASSIGN(cfForeground, CF_FOREGROUND, CF_PIXEL, CF_32);
35 CF_ASSIGN(cfBackground, CF_BACKGROUND, CF_PIXEL, CF_32);
36 */
37
38 guts. clipboard_event_timeout = 2000;
39 return true;
40 }
41
42 PList
apc_get_standard_clipboards(void)43 apc_get_standard_clipboards( void)
44 {
45 PList l = plist_create( 4, 1);
46 if (!l) return NULL;
47 list_add( l, (Handle)duplicate_string( "Primary"));
48 list_add( l, (Handle)duplicate_string( "Secondary"));
49 list_add( l, (Handle)duplicate_string( "Clipboard"));
50 list_add( l, (Handle)duplicate_string( "XdndSelection"));
51 return l;
52 }
53
54 Bool
apc_clipboard_create(Handle self)55 apc_clipboard_create( Handle self)
56 {
57 PClipboard c = (PClipboard)self;
58 int i;
59 DEFCC;
60
61 if ( strcmp(c->name, "XdndSelection") != 0 ) {
62 char *name, *x;
63 name = x = duplicate_string( c-> name);
64 while (*x) {
65 *x = toupper(*x);
66 x++;
67 }
68 XX-> selection = XInternAtom( DISP, name, false);
69 free( name);
70 } else {
71 XX-> selection = XdndSelection;
72 }
73
74 if ( hash_fetch( guts.clipboards, &XX->selection, sizeof(XX->selection))) {
75 warn("This clipboard is already present");
76 return false;
77 }
78
79 if ( !( XX-> internal = malloc( sizeof( ClipboardDataItem) * cfCOUNT))) {
80 warn("Not enough memory");
81 return false;
82 }
83 if ( !( XX-> external = malloc( sizeof( ClipboardDataItem) * cfCOUNT))) {
84 free( XX-> internal);
85 warn("Not enough memory");
86 return false;
87 }
88 bzero( XX-> internal, sizeof( ClipboardDataItem) * cfCOUNT);
89 bzero( XX-> external, sizeof( ClipboardDataItem) * cfCOUNT);
90
91 XX->internal[cfTargets].name = CF_NAME(cfTargets);
92
93 for ( i = 0; i < cfCOUNT; i++)
94 XX->internal[i].immediate = XX->external[i].immediate = true;
95
96 hash_store( guts.clipboards, &XX->selection, sizeof(XX->selection), (void*)self);
97
98 if ( XX-> selection == XdndSelection )
99 guts. xdnd_clipboard = self;
100
101 return true;
102 }
103
104 static void
clipboard_free_data(void * data,int size,Handle id)105 clipboard_free_data( void * data, int size, Handle id)
106 {
107 if ( size <= 0) {
108 if ( size == 0 && data != NULL) free( data);
109 return;
110 }
111 if ( id == cfBitmap) {
112 int i;
113 Pixmap * p = (Pixmap*) data;
114 for ( i = 0; i < size/sizeof(Pixmap); i++, p++)
115 if ( *p)
116 XFreePixmap( DISP, *p);
117 }
118 free( data);
119 }
120
121 /*
122 each clipboard type can be represented by a set of
123 X properties pairs, where each is X name and X type.
124 get_typename() returns such pairs by the index.
125 */
126 static Atom
get_typename(Handle id,int index,Atom * type)127 get_typename( Handle id, int index, Atom * type)
128 {
129 if ( type) *type = None;
130 switch ( id) {
131 case cfText:
132 if ( index > 1) return None;
133 if ( index == 1) {
134 if ( type) *type = PLAINTEXT_MIME;
135 return PLAINTEXT_MIME;
136 }
137 case cfUTF8:
138 if ( index > 1) return None;
139 if ( index == 1) {
140 if ( type) *type = UTF8_MIME;
141 return UTF8_MIME;
142 }
143 case cfBitmap:
144 if ( index > 1) return None;
145 if ( index == 1) {
146 if ( type) *type = XA_BITMAP;
147 return XA_BITMAP;
148 }
149 case cfTargets:
150 if ( index > 1) return None;
151 if ( index == 1) {
152 if ( type) *type = CF_TARGETS;
153 return CF_NAME(id);
154 }
155 }
156 if ( index > 0) return None;
157 if ( type) *type = CF_TYPE(id);
158 return CF_NAME(id);
159 }
160
161 void
prima_clipboard_kill_item(PClipboardDataItem item,Handle id)162 prima_clipboard_kill_item( PClipboardDataItem item, Handle id)
163 {
164 item += id;
165 clipboard_free_data( item-> data, item-> size, id);
166 if ( item-> image ) unprotect_object( item-> image );
167 item-> image = NULL_HANDLE;
168 item-> data = NULL;
169 item-> size = 0;
170 item-> name = get_typename( id, 0, NULL);
171 item-> immediate = true;
172 }
173
174 /*
175 Deletes a transfer record from pending xfer chain.
176 */
177 static void
delete_xfer(PClipboardSysData cc,ClipboardXfer * xfer)178 delete_xfer( PClipboardSysData cc, ClipboardXfer * xfer)
179 {
180 ClipboardXferKey key;
181 CLIPBOARD_XFER_KEY( key, xfer-> requestor, xfer-> property);
182 if ( guts. clipboard_xfers) {
183 IV refcnt;
184 hash_delete( guts. clipboard_xfers, key, sizeof( key), false);
185 refcnt = PTR2IV( hash_fetch( guts. clipboard_xfers, &xfer-> requestor, sizeof(XWindow)));
186 if ( --refcnt == 0) {
187 XSelectInput( DISP, xfer-> requestor, 0);
188 hash_delete( guts. clipboard_xfers, &xfer-> requestor, sizeof(XWindow), false);
189 } else {
190 if ( refcnt < 0) refcnt = 0;
191 hash_store( guts. clipboard_xfers, &xfer-> requestor, sizeof(XWindow), INT2PTR(void*, refcnt));
192 }
193 }
194 if ( cc-> xfers)
195 list_delete( cc-> xfers, ( Handle) xfer);
196 if ( xfer-> data_detached && xfer-> data_master)
197 clipboard_free_data( xfer-> data, xfer-> size, xfer-> id);
198 free( xfer);
199 }
200
201 Bool
apc_clipboard_destroy(Handle self)202 apc_clipboard_destroy( Handle self)
203 {
204 DEFCC;
205 int i;
206
207 if ( guts. xdnd_clipboard == self )
208 guts. xdnd_clipboard = NULL_HANDLE;
209
210 if (XX-> selection == None) return true;
211
212 if ( XX-> xfers) {
213 for ( i = 0; i < XX-> xfers-> count; i++)
214 delete_xfer( XX, ( ClipboardXfer*) XX-> xfers-> items[i]);
215 plist_destroy( XX-> xfers);
216 }
217
218 for ( i = 0; i < guts. clipboard_formats_count; i++) {
219 if ( XX-> external) prima_clipboard_kill_item( XX-> external, i);
220 if ( XX-> internal) prima_clipboard_kill_item( XX-> internal, i);
221 }
222
223 free( XX-> external);
224 free( XX-> internal);
225 hash_delete( guts.clipboards, &XX->selection, sizeof(XX->selection), false);
226
227 XX-> selection = None;
228 return true;
229 }
230
231 Bool
apc_clipboard_open(Handle self)232 apc_clipboard_open( Handle self)
233 {
234 DEFCC;
235 if ( XX-> xdnd_receiving ) return true;
236 if ( XX-> opened) return false;
237 XX-> opened = true;
238
239 if ( !XX-> inside_event) XX-> need_write = false;
240
241 return true;
242 }
243
244 Bool
apc_clipboard_close(Handle self)245 apc_clipboard_close( Handle self)
246 {
247 DEFCC;
248 if ( XX-> xdnd_receiving ) return true; /* XXX */
249 if ( !XX-> opened) return false;
250 XX-> opened = false;
251
252 /* check if UTF8 is present and Text is not, and downgrade */
253 if ( XX-> need_write &&
254 XX-> internal[cfUTF8]. size > 0 &&
255 XX-> internal[cfText]. size == 0) {
256 Byte * src = XX-> internal[cfUTF8]. data;
257 int len = utf8_length( src, src + XX-> internal[cfUTF8]. size);
258 if (( XX-> internal[cfText]. data = malloc( len))) {
259 STRLEN charlen;
260 U8 *dst, *end = src + XX-> internal[cfUTF8]. size;
261 dst = XX-> internal[cfText]. data;
262 XX-> internal[cfText]. size = len;
263 while ( len--) {
264 register UV u = prima_utf8_uvchr_end(src, end, &charlen);
265 *(dst++) = ( u < 0x7f) ? u : '?'; /* XXX employ $LANG and iconv() */
266 src += charlen;
267 }
268 }
269 }
270
271
272 if ( !XX-> inside_event) {
273 int i;
274 for ( i = 0; i < guts. clipboard_formats_count; i++)
275 prima_clipboard_kill_item( XX-> external, i);
276 if ( XX-> need_write && (!XX->xdnd_receiving || XX->xdnd_sending))
277 if ( XGetSelectionOwner( DISP, XX-> selection) != WIN)
278 XSetSelectionOwner( DISP, XX-> selection, WIN, CurrentTime);
279 }
280
281 return true;
282 }
283
284 /*
285 Detaches data for pending transfers from XX, so eventual changes
286 to XX->internal would not affect them. detach_xfers() should be
287 called before clipboard_kill_item(XX-> internal), otherwise
288 there's a chance of coredump.
289 */
290 void
prima_detach_xfers(PClipboardSysData XX,Handle id,Bool clear_original_data)291 prima_detach_xfers( PClipboardSysData XX, Handle id, Bool clear_original_data)
292 {
293 int i, got_master = 0, got_anything = 0;
294 if ( !XX-> xfers) return;
295 for ( i = 0; i < XX-> xfers-> count; i++) {
296 ClipboardXfer * x = ( ClipboardXfer *) XX-> xfers-> items[i];
297 if ( x-> data_detached || x-> id != id) continue;
298 got_anything = 1;
299 if ( !got_master) {
300 x-> data_master = true;
301 got_master = 1;
302 }
303 x-> data_detached = true;
304 }
305 if ( got_anything && clear_original_data) {
306 XX-> internal[id]. data = NULL;
307 XX-> internal[id]. size = 0;
308 XX-> internal[id]. name = get_typename( id, 0, NULL);
309 }
310 }
311
312 Bool
apc_clipboard_clear(Handle self)313 apc_clipboard_clear( Handle self)
314 {
315 DEFCC;
316 int i;
317
318 for ( i = 0; i < guts. clipboard_formats_count; i++) {
319 prima_detach_xfers( XX, i, true);
320 prima_clipboard_kill_item( XX-> internal, i);
321 prima_clipboard_kill_item( XX-> external, i);
322 }
323
324 if ( XX-> inside_event) {
325 XX-> need_write = true;
326 } else if ( !XX->xdnd_receiving || XX->xdnd_sending) {
327 XWindow owner = XGetSelectionOwner( DISP, XX-> selection);
328 XX-> need_write = false;
329 if ( owner != None && owner != WIN)
330 XSetSelectionOwner( DISP, XX-> selection, None, CurrentTime);
331 }
332
333 return true;
334 }
335
336 typedef struct {
337 Atom selection;
338 long mask;
339 } SelectionProcData;
340
341 #define SELECTION_NOTIFY_MASK 1
342 #define PROPERTY_NOTIFY_MASK 2
343
344 static int
selection_filter(Display * disp,XEvent * ev,SelectionProcData * data)345 selection_filter( Display * disp, XEvent * ev, SelectionProcData * data)
346 {
347 switch ( ev-> type) {
348 case PropertyNotify:
349 return (data-> mask & PROPERTY_NOTIFY_MASK) && (data-> selection == ev-> xproperty. atom);
350 case SelectionRequest:
351 case SelectionClear:
352 case MappingNotify:
353 return true;
354 case SelectionNotify:
355 return (data-> mask & SELECTION_NOTIFY_MASK) && (data-> selection == ev-> xselection. selection);
356 case ClientMessage:
357 if ( ev-> xclient. window == WIN ||
358 ev-> xclient. window == guts. root ||
359 ev-> xclient. window == None) return true;
360 if ( hash_fetch( guts.windows, (void*)&ev-> xclient. window,
361 sizeof(ev-> xclient. window))) return false;
362 return true;
363 }
364 return false;
365 }
366
367 #define CFDATA_NONE 0
368 #define CFDATA_NOT_ACQUIRED (-1)
369 #define CFDATA_ERROR (-2)
370
371 int
prima_read_property(XWindow window,Atom property,Atom * type,int * format,unsigned long * size,unsigned char ** data,Bool delete_property)372 prima_read_property( XWindow window, Atom property, Atom * type, int * format,
373 unsigned long * size, unsigned char ** data, Bool delete_property)
374 {
375 int ret = ( *size > 0) ? RPS_PARTIAL : RPS_ERROR;
376 unsigned char * prop, *a1;
377 unsigned long n, left, offs = 0, new_size, big_offs = *size;
378
379 XCHECKPOINT;
380 Cdebug("clipboard: read_property: %s\n", XGetAtomName(DISP, property));
381 while ( 1) {
382 if ( XGetWindowProperty( DISP, window, property,
383 offs, guts. limits. request_length - 4, false,
384 AnyPropertyType,
385 type, format, &n, &left, &prop) != Success) {
386 if ( delete_property )
387 XDeleteProperty( DISP, window, property);
388 Cdebug("clipboard:fail\n");
389 return ret;
390 }
391 XCHECKPOINT;
392 Cdebug("clipboard: type=0x%x(%s) fmt=%d n=%d left=%d\n",
393 *type, XGetAtomName(DISP,*type), *format, n, left);
394
395 if ( *format == 32) *format = CF_32;
396
397 if ( *type == 0 ) return RPS_NODATA;
398
399 new_size = n * *format / 8;
400
401 if ( new_size > 0) {
402 if ( !( a1 = realloc( *data, big_offs + offs * 4 + new_size))) {
403 warn("Not enough memory: %ld bytes\n", offs * 4 + new_size);
404 if ( delete_property )
405 XDeleteProperty( DISP, window, property);
406 XFree( prop);
407 return ret;
408 }
409 *data = a1;
410 memcpy( *data + big_offs + offs * 4, prop, new_size);
411 *size = big_offs + (offs * 4) + new_size;
412 if ( *size > INT_MAX) *size = INT_MAX;
413 offs += new_size / 4;
414 ret = RPS_PARTIAL;
415 }
416 XFree( prop);
417 if ( left <= 0 || *size == INT_MAX || n * *format == 0) break;
418 }
419
420 if ( delete_property )
421 XDeleteProperty( DISP, window, property);
422 XCHECKPOINT;
423
424 return RPS_OK;
425 }
426
427 static Bool
query_datum(Handle self,Handle id,Atom query_target,Atom query_type)428 query_datum( Handle self, Handle id, Atom query_target, Atom query_type)
429 {
430 DEFCC;
431 XEvent ev;
432 Atom type;
433 int format, rps;
434 SelectionProcData spd;
435 unsigned long size = 0, incr = 0, old_size, delay;
436 long timestamp;
437 unsigned char * data;
438 struct timeval start_time, timeout;
439 XWindow window;
440
441 /* init */
442 if ( query_target == None) return false;
443 window = XX-> xdnd_receiving ? PWidget(guts.xdndr_receiver)->handle : WIN;
444 if ( window == None ) return false; /* don't operate on XDND clipboard outside the drag */
445 timestamp = XX-> xdnd_receiving ? guts.xdndr_timestamp : guts.last_time;
446
447 data = malloc(0);
448 XX-> external[id]. size = CFDATA_ERROR;
449 gettimeofday( &start_time, NULL);
450 XCHECKPOINT;
451
452 Cdebug("clipboard:convert %s from %08x on %s\n", XGetAtomName( DISP, query_target), window, XGetAtomName(DISP, XX->selection));
453 XDeleteProperty( DISP, WIN, XX-> selection);
454 XConvertSelection( DISP, XX-> selection, query_target, XX-> selection, window, timestamp);
455 XFlush( DISP);
456 XCHECKPOINT;
457
458 /* wait for SelectionNotify */
459 spd. selection = XX-> selection;
460 spd. mask = SELECTION_NOTIFY_MASK;
461 while ( 1) {
462 Bool ok;
463
464 ok = XCheckIfEvent( DISP, &ev, (XIfEventProcType)selection_filter, (char*)&spd);
465 if ( !ok ) {
466 gettimeofday( &timeout, NULL);
467 delay = (( timeout. tv_sec - start_time. tv_sec) * 1000 +
468 ( timeout. tv_usec - start_time. tv_usec) / 1000);
469 if ( delay > guts. clipboard_event_timeout) {
470 Cdebug("clipboard:selection timeout\n");
471 goto FAIL;
472 }
473 XFlush( DISP);
474 continue;
475 }
476 gettimeofday( &timeout, NULL);
477 delay = 2 * (( timeout. tv_sec - start_time. tv_sec) * 1000 +
478 ( timeout. tv_usec - start_time. tv_usec) / 1000);
479 if ( ev. type != SelectionNotify) {
480 prima_handle_event( &ev, NULL);
481 continue;
482 }
483 if ( ev. xselection. property == None) goto FAIL;
484 Cdebug("clipboard:read SelectionNotify %s %s\n",
485 XGetAtomName(DISP, ev. xselection. property),
486 XGetAtomName(DISP, ev. xselection. target));
487 if ( prima_read_property( window, ev. xselection. property, &type, &format, &size, &data, 1) > RPS_PARTIAL)
488 goto FAIL;
489 XFlush( DISP);
490 break;
491 }
492 XCHECKPOINT;
493
494 if ( type != XA_INCR) { /* ordinary, single-property selection */
495 if ( format != CF_FORMAT(id) || type != query_type) {
496 if ( format != CF_FORMAT(id))
497 Cdebug("clipboard: id=%d: formats mismatch: got %d, want %d\n", id, format, CF_FORMAT(id));
498 if ( type != query_type)
499 Cdebug("clipboard: id=%d: types mismatch: got %s, want %s\n", id,
500 XGetAtomName(DISP,type), XGetAtomName(DISP,query_type));
501 return false;
502 }
503 XX-> external[id]. size = size;
504 XX-> external[id]. data = data;
505 XX-> external[id]. name = query_target;
506 return true;
507 }
508
509 /* setup INCR */
510 if ( format != CF_32 || size < 4) goto FAIL;
511 incr = (unsigned long) *(( Atom*) data);
512 if ( incr == 0) goto FAIL;
513 size = 0;
514 spd. mask = PROPERTY_NOTIFY_MASK;
515
516 while ( 1) {
517 /* wait for PropertyNotify */
518 while ( XCheckIfEvent( DISP, &ev, (XIfEventProcType)selection_filter, (char*)&spd) == False) {
519 gettimeofday( &timeout, NULL);
520 if ((( timeout. tv_sec - start_time. tv_sec) * 1000 +
521 ( timeout. tv_usec - start_time. tv_usec) / 1000) > delay)
522 goto END_LOOP;
523 }
524 if ( ev. type != PropertyNotify) {
525 prima_handle_event( &ev, NULL);
526 continue;
527 }
528 if ( ev. xproperty. state != PropertyNewValue) continue;
529 start_time = timeout;
530 old_size = size;
531
532 rps = prima_read_property( window, ev. xproperty. atom, &type, &format, &size, &data, 1);
533 XFlush( DISP);
534 if ( rps == RPS_NODATA) continue;
535 if ( rps == RPS_ERROR) goto FAIL;
536 if ( format != CF_FORMAT(id) || type != CF_TYPE(id)) return false;
537 if ( size > incr || /* read all in INCR */
538 rps == RPS_PARTIAL || /* failed somewhere */
539 ( size == incr && old_size == size) /* wait for empty PropertyNotify otherwise */
540 ) break;
541 }
542 END_LOOP:
543 XCHECKPOINT;
544
545 XX-> external[id]. size = size;
546 XX-> external[id]. data = data;
547 XX-> external[id]. name = query_target;
548 return true;
549
550 FAIL:
551 XCHECKPOINT;
552 free( data);
553 return false;
554 }
555
556 static Bool
query_data(Handle self,Handle id)557 query_data( Handle self, Handle id)
558 {
559 DEFCC;
560 Atom name, type;
561 int index = 0;
562 Bool filter_by_targets = id != cfTargets && XX-> external[cfTargets]. size > 0;
563
564 /* query all types */
565 while (( name = get_typename( id, index++, &type)) != None) {
566 if ( filter_by_targets ) {
567 Bool found = false;
568 int i, length = XX->external[cfTargets].size;
569 Atom * data = (Atom*) XX->external[cfTargets].data;
570 for ( i = 0; i < length / sizeof(Atom); i++) {
571 if ( data[i] != name) continue;
572 found = true;
573 break;
574 }
575 if ( !found ) continue;
576 }
577
578 if ( query_datum( self, id, name, type)) return true;
579 }
580 return false;
581 }
582
583 static Atom
find_atoms(Atom * data,int length,int id)584 find_atoms( Atom * data, int length, int id)
585 {
586 int i, index = 0;
587 Atom name;
588
589 while (( name = get_typename( id, index++, NULL)) != None) {
590 for ( i = 0; i < length / sizeof(Atom); i++) {
591 if ( data[i] == name)
592 return name;
593 }
594 }
595 return None;
596 }
597
598 void
prima_clipboard_query_targets(Handle self)599 prima_clipboard_query_targets( Handle self)
600 {
601 DEFCC;
602
603 if ( !XX->xdnd_receiving ) {
604 if ( XX-> external[cfTargets]. size != 0)
605 return;
606 query_data( self, cfTargets);
607 }
608
609 /* read TARGETS, which is an array of ATOMs */
610 if ( XX-> external[cfTargets].size > 0) {
611 int i, size = XX-> external[cfTargets].size;
612 Atom * data = ( Atom*)(XX-> external[cfTargets]. data);
613 Atom ret;
614
615 Cdebug("clipboard targets:");
616 for ( i = 0; i < size/sizeof(Atom); i++)
617 Cdebug("%s\n", XGetAtomName( DISP, data[i]));
618
619 /* find our index for TARGETS[i], assign CFDATA_NOT_ACQUIRED to it */
620 for ( i = 0; i < guts. clipboard_formats_count; i++) {
621 if ( i == cfTargets) continue;
622 ret = find_atoms( data, size, i);
623 if (
624 XX-> external[i]. size == 0 ||
625 XX-> external[i]. size == CFDATA_ERROR
626 ) {
627 XX-> external[i]. size = CFDATA_NOT_ACQUIRED;
628 XX-> external[i]. name = ret;
629 }
630 }
631 }
632 }
633
634 Bool
apc_clipboard_has_format(Handle self,Handle id)635 apc_clipboard_has_format( Handle self, Handle id)
636 {
637 DEFCC;
638 if ( id >= guts. clipboard_formats_count) return false;
639
640 if ( XX-> inside_event) {
641 return XX-> internal[id]. size > 0 || !XX->internal[id].immediate || XX-> external[id]. size > 0;
642 } else {
643 if ( XX-> internal[id]. size > 0 || !XX->internal[id].immediate) return true;
644 prima_clipboard_query_targets(self);
645 if ( XX-> external[cfTargets]. size > 0) {
646 return find_atoms(
647 (Atom*) XX-> external[cfTargets]. data,
648 XX-> external[cfTargets]. size, id
649 ) != None;
650 }
651
652 if ( XX-> external[id]. size > 0 ||
653 XX-> external[id]. size == CFDATA_NOT_ACQUIRED)
654 return true;
655
656 if ( XX-> external[id]. size == CFDATA_ERROR)
657 return false;
658
659 /* selection owner does not support TARGETS, so peek */
660 if ( XX-> external[cfTargets]. size == 0 && XX-> internal[id]. size == 0)
661 return query_data( self, id);
662 }
663 return false;
664 }
665
666 PList
apc_clipboard_get_formats(Handle self)667 apc_clipboard_get_formats( Handle self)
668 {
669 DEFCC;
670 int id;
671 PList list = plist_create(guts.clipboard_formats_count, 8);
672 Byte visited[1024]; /* 8K formats */
673
674 bzero( visited, sizeof(visited));
675
676 if ( !XX-> inside_event ) {
677 int i, j, size;
678 Atom * data;
679
680 prima_clipboard_query_targets(self);
681 size = XX-> external[cfTargets].size;
682 data = ( Atom*)(XX-> external[cfTargets]. data);
683 if ( size > 0 && data != NULL ) {
684 /* TARGETS supported */
685 for ( i = 0; i < size/sizeof(Atom); i++, data++) {
686 Atom atom = None;
687 char *name = NULL;
688 /* try to map back f.ex. text/plain to Text */
689 for ( j = 0; j < guts.clipboard_formats_count; j++) {
690 if (*data == XX->external[j].name) {
691 atom = CF_NAME(j);
692 if (atom == XA_STRING )
693 name = "Text";
694 else if ( atom == XA_BITMAP)
695 name = "Image";
696 else if ( atom == UTF8_STRING )
697 name = "UTF8";
698 }
699 if ( atom != None || name != NULL ) {
700 int ofs = j >> 3;
701 if ( ofs < 1024 ) visited[ofs] |= 1 << (j & 7);
702 }
703 }
704 if ( atom == None ) atom = *data;
705 if ( name == NULL ) name = XGetAtomName(DISP, atom);
706 list_add( list, (Handle) duplicate_string(name));
707 }
708 }
709 }
710
711 for ( id = 0; id < guts. clipboard_formats_count; id++) {
712 int ofs = id >> 3;
713 int was_visited = ( ofs < 1024 ) ? visited[ofs] & (1 << (id & 7)) : 0;
714 if (XX-> internal[id]. size > 0 || !XX->internal[id]. immediate || XX-> external[id]. size > 0) {
715 char * name;
716 if ( was_visited ) continue;
717 switch ( id ) {
718 case cfText:
719 name = "Text";
720 break;
721 case cfUTF8:
722 name = "UTF8";
723 break;
724 case cfBitmap:
725 name = "Image";
726 break;
727 default:
728 name = XGetAtomName(DISP, XX->internal[id].name);
729 }
730 list_add( list, (Handle) duplicate_string(name));
731 }
732 }
733
734 return list;
735 }
736
737 static Bool
738 fill_target( Handle self, Atom target );
739
740 static Bool
741 fill_bitmap( Handle self );
742
743 Bool
apc_clipboard_get_data(Handle self,Handle id,PClipboardDataRec c)744 apc_clipboard_get_data( Handle self, Handle id, PClipboardDataRec c)
745 {
746 DEFCC;
747 STRLEN size;
748 unsigned char * data;
749 Bool imm;
750
751 if ( id >= guts. clipboard_formats_count) return false;
752
753 if ( !XX-> inside_event) {
754 if ( XX-> internal[id]. size == 0) {
755 if ( XX-> external[id]. size == CFDATA_NOT_ACQUIRED) {
756 if ( !query_data( self, id)) return false;
757 }
758 if ( XX-> external[id]. size == CFDATA_ERROR) return false;
759 }
760 }
761 if ( XX-> internal[id]. size == CFDATA_ERROR) return false;
762
763 if ( XX-> internal[id]. size > 0 || !XX-> internal[id]. immediate) {
764 size = XX-> internal[id]. size;
765 data = XX-> internal[id]. data;
766 imm = XX-> internal[id]. immediate;
767 } else {
768 size = XX-> external[id]. size;
769 data = XX-> external[id]. data;
770 imm = true;
771 }
772 if ( size == 0 || data == NULL) {
773 Bool ret;
774 if ( imm ) return false;
775 if ( id == cfBitmap ) {
776 ret = fill_bitmap(self);
777 } else {
778 Atom name, type;
779 int index = 0;
780 while (( name = get_typename( id, index++, &type)) != None) {
781 ret = fill_target(self, name);
782 if ( ret ) break;
783 }
784 }
785
786 if ( !ret ) return false;
787 size = XX-> internal[id]. size;
788 data = XX-> internal[id]. data;
789 }
790
791 switch ( id) {
792 case cfBitmap:
793 if ( XX-> internal[cfBitmap].image) {
794 c->image = XX->internal[cfBitmap].image;
795 } else if ( XX->external[cfBitmap].size > 0 ) {
796 XWindow foo;
797 Pixmap px = *(( Pixmap*)( XX-> external[id]. data ));
798 unsigned int dummy, x, y, d;
799 int bar;
800 if ( !XGetGeometry( DISP, px, &foo, &bar, &bar, &x, &y, &dummy, &d))
801 return false;
802 c->image = (Handle) create_object("Prima::Image", "iii",
803 "width", x,
804 "height", y,
805 "type", (d == 1) ? imBW : guts.qdepth
806 );
807 if ( !prima_std_query_image( c->image, px)) {
808 Object_destroy(c->image);
809 return false;
810 }
811 }
812 break;
813 case cfText:
814 case cfUTF8: {
815 void * ret = malloc( size);
816 if ( !ret) {
817 warn("Not enough memory: %d bytes\n", (int)size);
818 return false;
819 }
820 memcpy( ret, data, size);
821 c-> data = ret;
822 c-> length = size;
823 break;}
824 default: {
825 void * ret = malloc( size);
826 if ( !ret) {
827 warn("Not enough memory: %d bytes\n", (int)size);
828 return false;
829 }
830 memcpy( ret, data, size);
831 c-> data = ( Byte * ) ret;
832 c-> length = size;
833 break;}
834 }
835 return true;
836 }
837
838 Bool
apc_clipboard_set_data(Handle self,Handle id,PClipboardDataRec c)839 apc_clipboard_set_data( Handle self, Handle id, PClipboardDataRec c)
840 {
841 DEFCC;
842 if ( id >= guts. clipboard_formats_count) return false;
843
844 if ( id >= cfTargets && id < cfCOUNT ) return false;
845 prima_detach_xfers( XX, id, true);
846 prima_clipboard_kill_item( XX-> internal, id);
847
848 switch ( id) {
849 case cfBitmap:
850 if (( XX-> internal[id]. image = c-> image) != NULL_HANDLE) {
851 protect_object( XX-> internal[id]. image );
852 XX-> internal[id]. immediate = false;
853 }
854 break;
855 default:
856 if ( c-> length < 0 ) {
857 XX-> internal[id]. immediate = false;
858 } else {
859 if ( !( XX-> internal[id]. data = malloc( c-> length)))
860 return false;
861 XX-> internal[id]. size = c-> length;
862 memcpy( XX-> internal[id]. data, c-> data, c-> length);
863 }
864 break;
865 }
866 XX-> need_write = true;
867 return true;
868 }
869
870 static Bool
expand_clipboards(Handle self,int keyLen,void * key,void * dummy)871 expand_clipboards( Handle self, int keyLen, void * key, void * dummy)
872 {
873 DEFCC;
874 PClipboardDataItem f;
875
876 if ( !( f = realloc( XX-> internal,
877 sizeof( ClipboardDataItem) * guts. clipboard_formats_count))) {
878 guts. clipboard_formats_count--;
879 return true;
880 }
881 f[ guts. clipboard_formats_count-1].size = 0;
882 f[ guts. clipboard_formats_count-1].data = NULL;
883 f[ guts. clipboard_formats_count-1].name = CF_NAME(guts. clipboard_formats_count-1);
884 f[ guts. clipboard_formats_count-1].immediate = true;
885 f[ guts. clipboard_formats_count-1].image = NULL_HANDLE;
886 XX-> internal = f;
887 if ( !( f = realloc( XX-> external,
888 sizeof( ClipboardDataItem) * guts. clipboard_formats_count))) {
889 guts. clipboard_formats_count--;
890 return true;
891 }
892 f[ guts. clipboard_formats_count-1].size = 0;
893 f[ guts. clipboard_formats_count-1].data = NULL;
894 f[ guts. clipboard_formats_count-1].name = CF_NAME(guts. clipboard_formats_count-1);
895 f[ guts. clipboard_formats_count-1].immediate = true; /* unused */
896 f[ guts. clipboard_formats_count-1].image = NULL_HANDLE; /* unused */
897 XX-> external = f;
898 return false;
899 }
900
901 Handle
apc_clipboard_register_format(Handle self,const char * format)902 apc_clipboard_register_format( Handle self, const char* format)
903 {
904 int i;
905 Atom x = XInternAtom( DISP, format, false);
906 Atom *f;
907
908 for ( i = 0; i < guts. clipboard_formats_count; i++) {
909 if ( x == CF_NAME(i))
910 return i;
911 }
912
913 if ( !( f = realloc( guts. clipboard_formats,
914 sizeof( Atom) * 3 * ( guts. clipboard_formats_count + 1))))
915 return false;
916
917 guts. clipboard_formats = f;
918 CF_ASSIGN( guts. clipboard_formats_count, x, x, 8);
919 guts. clipboard_formats_count++;
920
921 if ( hash_first_that( guts. clipboards, (void*)expand_clipboards, NULL, NULL, NULL))
922 return -1;
923
924 return guts. clipboard_formats_count - 1;
925 }
926
927 Bool
apc_clipboard_deregister_format(Handle self,Handle id)928 apc_clipboard_deregister_format( Handle self, Handle id)
929 {
930 return true;
931 }
932
933 ApiHandle
apc_clipboard_get_handle(Handle self)934 apc_clipboard_get_handle( Handle self)
935 {
936 return C(self)-> selection;
937 }
938
939 Bool
apc_clipboard_is_dnd(Handle self)940 apc_clipboard_is_dnd( Handle self)
941 {
942 return guts. xdnd_clipboard == self;
943 }
944
945 static Bool
delete_xfers(Handle self,int keyLen,void * key,XWindow * window)946 delete_xfers( Handle self, int keyLen, void * key, XWindow * window)
947 {
948 DEFCC;
949 if ( XX-> xfers) {
950 int i;
951 for ( i = 0; i < XX-> xfers-> count; i++)
952 delete_xfer( XX, ( ClipboardXfer*) XX-> xfers-> items[i]);
953 }
954 hash_delete( guts. clipboard_xfers, window, sizeof( XWindow), false);
955 return false;
956 }
957
958 int
prima_clipboard_fill_targets(Handle self)959 prima_clipboard_fill_targets( Handle self)
960 {
961 DEFCC;
962 int i, count = 0, have_utf8 = 0, have_plaintext = 0;
963 Atom * ci;
964 prima_detach_xfers( XX, cfTargets, true);
965 prima_clipboard_kill_item( XX-> internal, cfTargets);
966
967 for ( i = 0; i < guts. clipboard_formats_count; i++) {
968 if ( i != cfTargets && (XX-> internal[i]. size > 0 || !XX-> internal[i]. immediate)) {
969 count++;
970 if ( i == cfUTF8) {
971 count++;
972 have_utf8 = 1;
973 }
974 if ( i == cfText) {
975 count++;
976 have_plaintext = 1;
977 }
978 }
979 }
980 if ( count == 0 ) return 0;
981
982 if (( XX-> internal[cfTargets]. data = malloc( count * sizeof( Atom)))) {
983 Cdebug("clipboard: fill targets: ", guts. clipboard_formats_count);
984 XX-> internal[cfTargets]. size = count * sizeof( Atom);
985 ci = (Atom*)XX-> internal[cfTargets]. data;
986 for ( i = 0; i < guts. clipboard_formats_count; i++) {
987 if ( i != cfTargets && ( XX-> internal[i]. size > 0 || !XX-> internal[i]. immediate)) {
988 *(ci++) = CF_NAME(i);
989 Cdebug("%s ", XGetAtomName(DISP, CF_NAME(i)));
990 }
991 }
992 if ( have_utf8) {
993 *(ci++) = UTF8_MIME;
994 Cdebug("UTF8_MIME ");
995 }
996 if ( have_plaintext) {
997 *(ci++) = PLAINTEXT_MIME;
998 Cdebug("PLAINTEXT_MIME ");
999 }
1000 Cdebug("\n");
1001 }
1002 return count;
1003 }
1004
1005 static Bool
fill_bitmap(Handle self)1006 fill_bitmap( Handle self )
1007 {
1008 Pixmap px;
1009 PClipboardDataItem c;
1010
1011 c = &C(self)->internal[cfBitmap];
1012 if ( !c-> image) return false;
1013
1014 px = prima_std_pixmap( c-> image, CACHE_LOW_RES);
1015 if ( !px) return false;
1016
1017 if ( !( c-> data = malloc( sizeof( px)))) {
1018 XFreePixmap( DISP, px);
1019 return false;
1020 }
1021
1022 c->immediate = true;
1023 c->size = sizeof(px);
1024 memcpy( c->data, &px, sizeof(px));
1025
1026 return true;
1027 }
1028
1029 static Bool
fill_target(Handle self,Atom target)1030 fill_target( Handle self, Atom target )
1031 {
1032 PClipboardSysData CC = C(self);
1033
1034 Bool event = CC-> inside_event;
1035 Bool flag = exception_block(true);
1036 Event ev = { cmClipboard };
1037 int h_stage = PObject(self)->stage;
1038
1039 CC-> inside_event = true;
1040 CC-> opened = true;
1041 protect_object(self);
1042
1043 ev.gen.p = XGetAtomName(DISP, target);
1044 CComponent(self)->message(self, &ev);
1045
1046 unprotect_object(self);
1047 exception_block(flag);
1048 if ( h_stage == csDead ) return false;
1049
1050 CC-> opened = false;
1051 CC-> inside_event = event;
1052 if ( exception_charged()) return false;
1053
1054 return true;
1055 }
1056
1057 static void
handle_selection_request(XEvent * ev,XWindow win,Handle self)1058 handle_selection_request( XEvent *ev, XWindow win, Handle self)
1059 {
1060 XEvent xe;
1061 int i, id = -1;
1062 Atom
1063 prop = ev-> xselectionrequest. property,
1064 target = ev-> xselectionrequest. target;
1065 self = ( Handle) hash_fetch( guts. clipboards, &ev-> xselectionrequest. selection, sizeof( Atom));
1066
1067 guts. last_time = ev-> xselectionrequest. time;
1068 xe. type = SelectionNotify;
1069 xe. xselection. send_event = true;
1070 xe. xselection. serial = ev-> xselectionrequest. serial;
1071 xe. xselection. display = ev-> xselectionrequest. display;
1072 xe. xselection. requestor = ev-> xselectionrequest. requestor;
1073 xe. xselection. selection = ev-> xselectionrequest. selection;
1074 xe. xselection. target = target;
1075 xe. xselection. property = None;
1076 xe. xselection. time = ev-> xselectionrequest. time;
1077
1078 Cdebug("clipboard: from %08x %s at %s\n", ev-> xselectionrequest. requestor,
1079 XGetAtomName( DISP, ev-> xselectionrequest. target),
1080 XGetAtomName( DISP, ev-> xselectionrequest. property)
1081 );
1082
1083 if ( self) {
1084 PClipboardSysData CC = C(self);
1085 int format, utf8_mime = 0, plaintext_mime = 0;
1086
1087 for ( i = 0; i < guts. clipboard_formats_count; i++) {
1088 if ( xe. xselection. target == CC-> internal[i]. name) {
1089 id = i;
1090 break;
1091 } else if ( i == cfUTF8 && xe. xselection. target == UTF8_MIME) {
1092 id = i;
1093 utf8_mime = 1;
1094 break;
1095 } else if ( i == cfText && xe. xselection. target == PLAINTEXT_MIME) {
1096 id = i;
1097 plaintext_mime = 1;
1098 break;
1099 }
1100 }
1101 if ( id < 0) goto SEND_EMPTY;
1102 for ( i = 0; i < guts. clipboard_formats_count; i++)
1103 prima_clipboard_kill_item( CC-> external, i);
1104
1105 CC-> target = xe. xselection. target;
1106 CC-> need_write = false;
1107
1108 if ( !CC-> internal[id]. immediate && id != cfTargets ) {
1109 Bool ret;
1110 ret = ( id == cfBitmap ) ? fill_bitmap(self) : fill_target(self, target);
1111 if ( !ret ) goto SEND_EMPTY;
1112 }
1113
1114 format = CF_FORMAT(id);
1115 target = CF_TYPE( id);
1116 if ( utf8_mime) target = UTF8_MIME;
1117 if ( plaintext_mime) target = PLAINTEXT_MIME;
1118
1119 if ( id == cfTargets)
1120 prima_clipboard_fill_targets(self);
1121
1122 if ( CC-> internal[id]. size > 0) {
1123 Atom incr;
1124 int mode = PropModeReplace;
1125 unsigned char * data = CC-> internal[id]. data;
1126 unsigned long size = CC-> internal[id]. size * 8 / format;
1127 if ( CC-> internal[id]. size > guts. limits. request_length - 4) {
1128 int ok = 0;
1129 int reqlen = guts. limits. request_length - 4;
1130 /* INCR */
1131 if ( !guts. clipboard_xfers)
1132 guts. clipboard_xfers = hash_create();
1133 if ( !CC-> xfers)
1134 CC-> xfers = plist_create( 1, 1);
1135 if ( CC-> xfers && guts. clipboard_xfers) {
1136 ClipboardXfer * x = malloc( sizeof( ClipboardXfer));
1137 if ( x) {
1138 IV refcnt;
1139 ClipboardXferKey key;
1140
1141 bzero( x, sizeof( ClipboardXfer));
1142 list_add( CC-> xfers, ( Handle) x);
1143 x-> size = CC-> internal[id]. size;
1144 x-> data = CC-> internal[id]. data;
1145 x-> blocks = ( x-> size / reqlen ) + ( x-> size % reqlen) ? 1 : 0;
1146 x-> requestor = xe. xselection. requestor;
1147 x-> property = prop;
1148 x-> target = xe. xselection. target;
1149 x-> self = self;
1150 x-> format = format;
1151 x-> id = id;
1152 gettimeofday( &x-> time, NULL);
1153
1154 CLIPBOARD_XFER_KEY( key, x-> requestor, x-> property);
1155 hash_store( guts. clipboard_xfers, key, sizeof(key), (void*) x);
1156 refcnt = PTR2IV( hash_fetch( guts. clipboard_xfers, &x-> requestor, sizeof( XWindow)));
1157 if ( refcnt++ == 0)
1158 XSelectInput( DISP, x-> requestor, PropertyChangeMask|StructureNotifyMask);
1159 hash_store( guts. clipboard_xfers, &x-> requestor, sizeof(XWindow), INT2PTR( void*, refcnt));
1160
1161 format = CF_32;
1162 size = 1;
1163 incr = ( Atom) CC-> internal[id]. size;
1164 data = ( unsigned char*) &incr;
1165 ok = 1;
1166 target = XA_INCR;
1167 Cdebug("clipboard: init INCR for %08x %d\n", x-> requestor, x-> property);
1168 }
1169 }
1170 if ( !ok) size = reqlen;
1171 }
1172
1173 if ( format == CF_32) format = 32;
1174 XChangeProperty(
1175 xe. xselection. display,
1176 xe. xselection. requestor,
1177 prop, target, format, mode, data, size);
1178 Cdebug("clipboard: store prop %s f=%d s=%d\n", XGetAtomName( DISP, prop), format, size);
1179 xe. xselection. property = prop;
1180 }
1181
1182 /* content of PIXMAP or BITMAP is seemingly gets invalidated
1183 after the selection transfer, unlike the string data format */
1184 if ( id == cfBitmap) {
1185 bzero( CC-> internal[id].data, CC-> internal[id].size);
1186 bzero( CC-> external[id].data, CC-> external[id].size);
1187 prima_clipboard_kill_item( CC-> internal, id);
1188 prima_clipboard_kill_item( CC-> external, id);
1189 }
1190 }
1191 SEND_EMPTY:
1192
1193 XSendEvent( xe.xselection.display, xe.xselection.requestor, false, 0, &xe);
1194 XFlush( DISP);
1195 Cdebug("clipboard:id %d, SelectionNotify to %08x , %s %s\n", id, xe.xselection.requestor,
1196 xe. xselection. property ? XGetAtomName( DISP, xe. xselection. property) : "None",
1197 XGetAtomName( DISP, xe. xselection. target));
1198 exception_check_raise();
1199 }
1200
1201 static void
handle_selection_clear(XEvent * ev,XWindow win,Handle self)1202 handle_selection_clear( XEvent *ev, XWindow win, Handle self)
1203 {
1204 guts. last_time = ev-> xselectionclear. time;
1205 if ( XGetSelectionOwner( DISP, ev-> xselectionclear. selection) != WIN) {
1206 Handle c = ( Handle) hash_fetch( guts. clipboards,
1207 &ev-> xselectionclear. selection, sizeof( Atom));
1208 guts. last_time = ev-> xselectionclear. time;
1209 if (c) {
1210 int i;
1211 C(c)-> selection_owner = NULL_HANDLE;
1212 for ( i = 0; i < guts. clipboard_formats_count; i++) {
1213 prima_detach_xfers( C(c), i, true);
1214 prima_clipboard_kill_item( C(c)-> external, i);
1215 prima_clipboard_kill_item( C(c)-> internal, i);
1216 }
1217 }
1218 }
1219 }
1220
1221 static void
handle_property_notify(XEvent * ev,XWindow win,Handle self)1222 handle_property_notify( XEvent *ev, XWindow win, Handle self)
1223 {
1224 unsigned long offs, size, reqlen = guts. limits. request_length - 4, format;
1225 ClipboardXfer * x = ( ClipboardXfer *) self;
1226 PClipboardSysData CC = C(x-> self);
1227
1228 if ( ev-> xproperty. state != PropertyDelete)
1229 return;
1230
1231 offs = x-> offset * reqlen;
1232 if ( offs >= x-> size) { /* clear termination */
1233 size = 0;
1234 offs = 0;
1235 } else {
1236 size = x-> size - offs;
1237 if ( size > reqlen) size = reqlen;
1238 }
1239 Cdebug("clipboard: put %d %d in %08x %d\n", x-> offset, size, x-> requestor, x-> property);
1240 if ( x-> format > 8) size /= 2;
1241 if ( x-> format > 16) size /= 2;
1242 format = ( x-> format == CF_32) ? 32 : x-> format;
1243 XChangeProperty( DISP, x-> requestor, x-> property, x-> target,
1244 format, PropModeReplace,
1245 x-> data + offs, size);
1246 XFlush( DISP);
1247 x-> offset++;
1248 if ( size == 0) delete_xfer( CC, x);
1249 }
1250
1251 static void
handle_destroy_notify(XEvent * ev,XWindow win,Handle self)1252 handle_destroy_notify( XEvent *ev, XWindow win, Handle self)
1253 {
1254 Cdebug("clipboard: destroy xfers at %08x\n", ev-> xdestroywindow. window);
1255 hash_first_that( guts. clipboards, (void*)delete_xfers, (void*) &ev-> xdestroywindow. window, NULL, NULL);
1256 XFlush( DISP);
1257 }
1258
1259 void
prima_handle_selection_event(XEvent * ev,XWindow win,Handle self)1260 prima_handle_selection_event( XEvent *ev, XWindow win, Handle self)
1261 {
1262 XCHECKPOINT;
1263 switch ( ev-> type) {
1264 case SelectionRequest:
1265 handle_selection_request(ev, win, self);
1266 break;
1267 case SelectionClear:
1268 handle_selection_clear(ev, win, self);
1269 break;
1270 case PropertyNotify:
1271 handle_property_notify(ev, win, self);
1272 break;
1273 case DestroyNotify:
1274 handle_destroy_notify(ev, win, self);
1275 break;
1276 }
1277 XCHECKPOINT;
1278 }
1279
1280