1 /*********************************/
2 /* */
3 /* Gtk file dialog */
4 /* */
5 /*********************************/
6
7 #include <sys/param.h>
8 #include "unix/guts.h"
9 #include "img.h"
10
11 #if !(defined(__FreeBSD__ ) && PERL_REVISION == 5 && PERL_VERSION < 22)
12 /* Observed hangs on 2 processors with DBUS session running */
13 #define SAFE_DBUS
14 #endif
15
16 #ifdef WITH_GTK
17
18 #undef GT
19
20 #undef dirty
21
22 #define Window XWindow
23
24 #ifndef WITH_GTK_NONX11
25 #include <gdk/gdkx.h>
26 #endif
27 #include <gtk/gtk.h>
28
29 static int gtk_initialized = 0;
30 static GApplication* gtk_app = NULL;
31 static GtkWidget* gtk_dialog = NULL;
32 static char gtk_dialog_title[256];
33 static char* gtk_dialog_title_ptr = NULL;
34 static Bool gtk_select_multiple = FALSE;
35 static Bool gtk_overwrite_prompt = FALSE;
36 static Bool gtk_show_hidden_files = FALSE;
37 static char gtk_current_folder[MAXPATHLEN+1];
38 static char* gtk_current_folder_ptr = NULL;
39 static List* gtk_filters = NULL;
40 static int gtk_filter_index = 0;
41
42 static GdkDisplay * display = NULL;
43
44 static Color
gdk_color(GdkColor * c)45 gdk_color(GdkColor * c)
46 {
47 return ((c->red >> 8) << 16) | ((c->green >> 8) << 8) | (c->blue >> 8);
48 }
49
50
51 typedef struct {
52 GType (*func)(void);
53 char * name;
54 char * gtk_class;
55 int prima_class;
56 Font * prima_font;
57 } GTFStruct;
58
59 #define GT(x) gtk_##x##_get_type, #x
60
61 static GTFStruct widget_types[] = {
62 { GT(button), "GtkButton", wcButton , NULL },
63 { GT(check_button), "GtkCheckButton", wcCheckBox , NULL },
64 { GT(combo_box), "GtkCombo", wcCombo , NULL },
65 { GT(dialog), "GtkDialog", wcDialog , NULL },
66 { GT(entry), "GtkEditable", wcEdit , NULL },
67 { GT(entry), "GtkEntry", wcInputLine , NULL },
68 { GT(label), "GtkLabel", wcLabel , &guts. default_msg_font },
69 { GT(menu), "GtkMenuItem", wcMenu , &guts. default_menu_font },
70 { GT(menu_item), "GtkMenuItem", wcPopup , NULL },
71 { GT(check_button), "GtkRadioButton", wcRadio , NULL },
72 { GT(scrollbar), "GtkScrollBar", wcScrollBar , NULL },
73 { GT(widget), "GtkWidget", wcWidget , &guts. default_widget_font },
74 { GT(window), "GtkWindow", wcWindow , &guts. default_caption_font },
75 { GT(widget), "GtkWidget", wcApplication , &guts. default_font },
76 #if GTK_MAJOR_VERSION == 2
77 { GT(list), "GtkList", wcListBox , NULL },
78 { GT(ruler), "GtkRuler", wcSlider , NULL },
79 #else
80 { GT(list_box), "GtkListBox", wcListBox , NULL },
81 { GT(spin_button), "GtkSpinButton", wcSlider , NULL },
82 #endif
83 };
84 #undef GT
85
86 #if GTK_MAJOR_VERSION == 3
87 GdkDisplay *
my_gdk_display_open_default(void)88 my_gdk_display_open_default (void)
89 {
90 GdkDisplay *display;
91
92 display = gdk_display_get_default ();
93 if (display)
94 return display;
95
96 display = gdk_display_open (gdk_get_display_arg_name ());
97
98 return display;
99 }
100 #endif
101
102 #ifdef SAFE_DBUS
103 /* GIO wants that callback, even empty */
gtk_application_activate(GApplication * app)104 static void gtk_application_activate (GApplication *app) {}
105 #endif
106
107 Display*
prima_gtk_init(void)108 prima_gtk_init(void)
109 {
110 int i, argc = 0;
111 Display *ret;
112 GtkSettings * settings;
113 Color ** stdcolors;
114 PangoWeight weight;
115
116 switch ( gtk_initialized) {
117 case -1:
118 return NULL;
119 case 1:
120 #ifdef WITH_GTK_NONX11
121 {
122 }
123 return (void*)1;
124 #else
125 return gdk_x11_display_get_xdisplay(display);
126 #endif
127 }
128
129 #ifdef WITH_GTK_NONX11
130 {
131 char * display_str = getenv("DISPLAY");
132 if ( display_str ) {
133 struct stat s;
134 if ((stat( display_str, &s) < 0) || !S_ISSOCK(s.st_mode)) /* not a socket */
135 return (void*)0;
136 }
137 }
138 #endif
139
140 #if PERL_REVISION == 5 && PERL_VERSION == 20
141 /* perl bug in 5.20.0, see more at https://rt.perl.org/Ticket/Display.html?id=122105 */
142 gtk_disable_setlocale();
143 #endif
144 if ( !gtk_parse_args (&argc, NULL) || (
145 display =
146 #if GTK_MAJOR_VERSION == 2
147 gdk_display_open_default_libgtk_only()
148 #else
149 my_gdk_display_open_default()
150 #endif
151 ) == NULL) {
152 gtk_initialized = -1;
153 return false;
154 } else {
155 gtk_initialized = 1;
156 XSetErrorHandler( guts.main_error_handler );
157 #ifdef WITH_GTK_NONX11
158 ret = (void*)1;
159 #else
160 ret = gdk_x11_display_get_xdisplay(display);
161 #endif
162 }
163 #if PERL_REVISION == 5 && PERL_VERSION >= 22
164 /* https://rt.perl.org/Ticket/Display.html?id=133945 */
165 sync_locale();
166 #endif
167
168 #if defined(SAFE_DBUS) && (GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 34))
169 gtk_app = g_application_new ("org.prima", G_APPLICATION_NON_UNIQUE);
170 g_signal_connect (gtk_app, "activate", G_CALLBACK (gtk_application_activate), NULL);
171 if ( !g_application_register (gtk_app, NULL, NULL)) {
172 g_object_unref (gtk_app);
173 gtk_app = NULL;
174 }
175 #endif
176
177 settings = gtk_settings_get_default();
178 stdcolors = prima_standard_colors();
179 #if GTK_MAJOR_VERSION == 2
180 for ( i = 0; i < sizeof(widget_types)/sizeof(GTFStruct); i++) {
181 GTFStruct * s = widget_types + i;
182 Color * c = stdcolors[ s-> prima_class >> 16 ];
183 Font * f = s->prima_font;
184 GtkStyle * t = gtk_rc_get_style_by_paths(settings, NULL, s->gtk_class, s->func());
185 int selected = (
186 s->prima_class == wcRadio ||
187 s->prima_class == wcCheckBox ||
188 s->prima_class == wcButton
189 ) ? GTK_STATE_ACTIVE : GTK_STATE_SELECTED;
190 if ( t == NULL ) {
191 Pdebug("cannot query gtk style for %s\n", s->name);
192 t = gtk_rc_get_style_by_paths(settings, NULL, NULL, GTK_TYPE_WIDGET);
193 if ( !t ) continue;
194 }
195 c[ciFore] = gdk_color( t-> fg + GTK_STATE_NORMAL );
196 c[ciBack] = gdk_color( t-> bg + GTK_STATE_NORMAL );
197 c[ciDisabledText] = gdk_color( t-> fg + GTK_STATE_INSENSITIVE );
198 c[ciDisabled] = gdk_color( t-> bg + GTK_STATE_INSENSITIVE );
199
200 if ( s-> prima_class == wcMenu || s-> prima_class == wcPopup) {
201 /* Observed on Centos7 - GTK_STATE_SELECTED gives white
202 on white, while GTK_STATE_PRELIGHT gives correct colors.
203 OTOH, on Ubuntu it is other way around. Without digging
204 too much into GTK guts, just select the one that gives
205 best contrast */
206
207 int da, db;
208 Color ca1, ca2, cb1, cb2;
209 ca1 = gdk_color( t-> fg + selected );
210 ca2 = gdk_color( t-> bg + selected );
211 da =
212 abs( (int)(ca1 & 0xff)-(int)(ca2 & 0xff) ) +
213 abs( (int)((ca1 & 0xff00)>>8)-(int)((ca2 & 0xff00)>>8) ) +
214 abs( (int)((ca1 & 0xff0000)>>16)-(int)((ca2 & 0xff0000)>>16) )
215 ;
216 cb1 = gdk_color( t-> fg + GTK_STATE_PRELIGHT );
217 cb2 = gdk_color( t-> bg + GTK_STATE_PRELIGHT );
218 db =
219 abs( (int)(cb1 & 0xff)-(int)(cb2 & 0xff) ) +
220 abs( (int)((cb1 & 0xff00)>>8)-(int)((cb2 & 0xff00)>>8) ) +
221 abs( (int)((cb1 & 0xff0000)>>16)-(int)((cb2 & 0xff0000)>>16) )
222 ;
223 c[ciHiliteText] = (da > db) ? ca1 : cb1;
224 c[ciHilite] = (da > db) ? ca2 : cb2;
225 } else {
226 c[ciHiliteText] = gdk_color( t-> fg + selected );
227 c[ciHilite] = gdk_color( t-> bg + selected );
228 }
229 Pdebug("gtk-color: %s %06x %06x %06x %06x %06x\n", s->name, c[0], c[1], c[2], c[3], c[4], c[5]);
230
231 if ( !f) continue;
232 bzero(f, sizeof(Font));
233 strncpy( f->name, pango_font_description_get_family(t->font_desc), 255);
234 f->name[255]=0;
235 /* does gnome ignore X resolution? */
236 f-> size = pango_font_description_get_size(t->font_desc) / PANGO_SCALE * (96.0 / guts. resolution. y) + .5;
237 weight = pango_font_description_get_weight(t->font_desc);
238 if ( weight <= PANGO_WEIGHT_LIGHT ) f-> style |= fsThin;
239 if ( weight >= PANGO_WEIGHT_BOLD ) f-> style |= fsBold;
240 if ( pango_font_description_get_style(t->font_desc) == PANGO_STYLE_ITALIC)
241 f-> style |= fsItalic;
242 strcpy( f->encoding, "Default" );
243 f-> undef. width = f-> undef. height = f-> undef. pitch = f-> undef. vector = 1;
244 apc_font_pick( application, f, f);
245 #define DEBUG_FONT(font) f->height,f->width,f->size,f->name,f->encoding
246 Fdebug("gtk-font (%s): %d.[w=%d,s=%d].%s.%s\n", s->name, DEBUG_FONT(f));
247 }
248 #endif
249 return ret;
250 }
251
252 Bool
prima_gtk_done(void)253 prima_gtk_done(void)
254 {
255 if ( gtk_filters) {
256 int i;
257 for ( i = 0; i < gtk_filters-> count; i++)
258 g_object_unref(( GObject*) gtk_filters-> items[i]);
259 plist_destroy( gtk_filters);
260 gtk_filters = NULL;
261 }
262 if ( gtk_app ) {
263 g_object_unref( gtk_app );
264 gtk_app = NULL;
265 }
266 gtk_initialized = 0;
267 return true;
268 }
269
270 #ifndef WITH_GTK_NONX11
271 static void
set_transient_for(void)272 set_transient_for(void)
273 {
274 Handle toplevel = prima_find_toplevel_window(NULL_HANDLE);
275 if ( toplevel ) {
276 GdkWindow * g = NULL;
277
278 #if GTK_MAJOR_VERSION > 2 || (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 14)
279 g = gtk_widget_get_window(GTK_WIDGET(gtk_dialog));
280 #else
281 g = gtk_dialog->window;
282 #endif
283 if ( g ) {
284 Window w = GDK_WINDOW_XID(g);
285 if ( w )
286 XSetTransientForHint( DISP, w, PWidget(toplevel)-> handle);
287 }
288 }
289 }
290 #endif
291
292
293 static gboolean
do_events(gpointer data)294 do_events(gpointer data)
295 {
296 int* stage = ( int*) data;
297 static struct timeval last_event = {0,0}, t;
298 if ( gtk_dialog != NULL && !*stage ) {
299 *stage = 1;
300 #ifdef WITH_GTK_NONX11
301 gtk_window_present(GTK_WINDOW(gtk_dialog));
302 #else
303 set_transient_for();
304 #endif
305 }
306 if (( t.tv_sec - last_event.tv_sec) * 1000000 + t.tv_usec - last_event.tv_usec > 10000) {
307 last_event = t;
308 prima_one_loop_round( WAIT_NEVER, true);
309 }
310 return gtk_dialog != NULL;
311 }
312
ignore_errors(Display * d,XErrorEvent * ev)313 static int ignore_errors(Display *d, XErrorEvent *ev) { return 0; }
314
315 static char *
gtk_openfile(Bool open)316 gtk_openfile( Bool open)
317 {
318 char *result = NULL;
319 struct MsgDlg message_dlg, **storage;
320 int stage = 0;
321
322 if ( gtk_dialog) return NULL; /* we're not reentrant */
323
324 XFlush(DISP);
325 XCHECKPOINT;
326 XSetErrorHandler(ignore_errors);
327
328 gtk_dialog = gtk_file_chooser_dialog_new (
329 gtk_dialog_title_ptr ?
330 gtk_dialog_title_ptr :
331 ( open ? "Open File" : "Save File"),
332 NULL,
333 open ? GTK_FILE_CHOOSER_ACTION_OPEN : GTK_FILE_CHOOSER_ACTION_SAVE,
334 #if GTK_MAJOR_VERSION == 3
335 "_Cancel",
336 #else
337 GTK_STOCK_CANCEL,
338 #endif
339 GTK_RESPONSE_CANCEL,
340 #if GTK_MAJOR_VERSION == 3
341 "_Open",
342 #else
343 GTK_STOCK_OPEN,
344 #endif
345 GTK_RESPONSE_ACCEPT,
346 NULL);
347 #ifdef WITH_GTK_NONX11
348 gtk_window_set_position( GTK_WINDOW(gtk_dialog), GTK_WIN_POS_CENTER);
349 #endif
350
351 gtk_file_chooser_set_local_only( GTK_FILE_CHOOSER (gtk_dialog), TRUE);
352 if (open)
353 gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER (gtk_dialog), gtk_select_multiple);
354
355 #if GTK_MAJOR_VERSION > 2 || (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 8)
356 gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER (gtk_dialog), gtk_overwrite_prompt);
357 gtk_file_chooser_set_show_hidden( GTK_FILE_CHOOSER (gtk_dialog), gtk_show_hidden_files);
358 #endif
359 if ( gtk_current_folder_ptr)
360 gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER (gtk_dialog), gtk_current_folder_ptr);
361
362 if ( gtk_filters) {
363 int i;
364 for ( i = 0; i < gtk_filters-> count; i++) {
365 gtk_file_chooser_add_filter(
366 GTK_FILE_CHOOSER (gtk_dialog),
367 GTK_FILE_FILTER (gtk_filters-> items[i])
368 );
369 if ( i == gtk_filter_index)
370 gtk_file_chooser_set_filter(
371 GTK_FILE_CHOOSER (gtk_dialog),
372 GTK_FILE_FILTER (gtk_filters-> items[i])
373 );
374 }
375 }
376
377 /* lock prima interactions */
378 bzero( &message_dlg, sizeof(message_dlg));
379 storage = &guts. message_boxes;
380 while ( *storage) storage = &((*storage)-> next);
381 *storage = &message_dlg;
382
383 g_idle_add( do_events, &stage);
384
385 if (gtk_dialog_run (GTK_DIALOG (gtk_dialog)) == GTK_RESPONSE_ACCEPT) {
386
387 /* files */
388 if ( gtk_select_multiple) {
389 int size;
390 char * ptr;
391 GSList *names, *iter;
392
393 names = gtk_file_chooser_get_filenames ( GTK_FILE_CHOOSER (gtk_dialog));
394
395 /* count total length with escaped spaces and backslashes */
396 size = 1;
397 iter = names;
398 while ( iter) {
399 char * c = (char*) iter-> data;
400 while ( *c) {
401 if ( *c == ' ' || *c == '\\')
402 size++;
403 size++;
404 c++;
405 }
406 size++;
407 iter = iter-> next;
408 }
409
410 if (( result = ptr = malloc( size))) {
411 /* copy and encode */
412 iter = names;
413 while ( iter) {
414 char * c = (char*) iter-> data;
415 while ( *c) {
416 if ( *c == ' ' || *c == '\\')
417 *(ptr++) = '\\';
418 *(ptr++) = *c;
419 c++;
420 }
421 iter = iter-> next;
422 *(ptr++) = ' ';
423 }
424 *(ptr - 1) = 0;
425 } else {
426 warn("gtk_openfile: cannot allocate %d bytes of memory", size);
427 }
428
429 /* free */
430 iter = names;
431 while ( iter) {
432 g_free( iter-> data);
433 iter = iter-> next;
434 }
435 g_slist_free( names);
436 } else {
437 char * filename = gtk_file_chooser_get_filename ( GTK_FILE_CHOOSER (gtk_dialog));
438 result = duplicate_string( filename);
439 g_free (filename);
440 }
441
442 /* directory */
443 {
444 char * d = gtk_file_chooser_get_current_folder( GTK_FILE_CHOOSER (gtk_dialog));
445 if ( d) {
446 strncpy( gtk_current_folder, d, MAXPATHLEN);
447 gtk_current_folder_ptr = gtk_current_folder;
448 g_free( d);
449 } else {
450 gtk_current_folder_ptr = NULL;
451 }
452 }
453
454 /* filter index */
455 gtk_filter_index = 0;
456 if ( gtk_filters) {
457 int i;
458 Handle f = ( Handle) gtk_file_chooser_get_filter( GTK_FILE_CHOOSER (gtk_dialog));
459 for ( i = 0; i < gtk_filters-> count; i++)
460 if ( gtk_filters-> items[i] == f) {
461 gtk_filter_index = i;
462 break;
463 }
464
465 }
466 }
467
468 if ( gtk_filters) {
469 plist_destroy( gtk_filters);
470 gtk_filters = NULL;
471 }
472
473 *storage = message_dlg. next; /* unlock */
474
475 gtk_widget_destroy (gtk_dialog);
476 gtk_dialog = NULL;
477
478 while ( gtk_events_pending()) gtk_main_iteration();
479
480 XSync(DISP, false);
481 XCHECKPOINT;
482 XSetErrorHandler(guts.main_error_handler);
483
484 return result;
485 }
486
487 char *
prima_gtk_openfile(char * params)488 prima_gtk_openfile( char * params)
489 {
490 if ( !DISP)
491 return NULL;
492 if( !prima_gtk_init())
493 return NULL;
494
495 if ( strncmp( params, "directory", 9) == 0) {
496 params += 9;
497 if ( *params == '=') {
498 params++;
499 if ( *params == 0) {
500 gtk_current_folder_ptr = NULL;
501 } else {
502 gtk_current_folder_ptr = gtk_current_folder;
503 strncpy( gtk_current_folder, params, MAXPATHLEN);
504 gtk_current_folder[MAXPATHLEN] = 0;
505 }
506 } else
507 return duplicate_string( gtk_current_folder_ptr);
508 } else if ( strncmp( params, "filters=", 8) == 0) {
509 params += 8;
510 if ( gtk_filters) {
511 int i;
512 for ( i = 0; i < gtk_filters-> count; i++)
513 g_object_unref(( GObject*) gtk_filters-> items[i]);
514 plist_destroy( gtk_filters);
515 gtk_filters = NULL;
516 }
517 if ( *params != 0) {
518 gtk_filters = plist_create(8, 8);
519
520 /* copy \0\0-terminated string */
521 while ( *params) {
522 char * pattern;
523 GtkFileFilter * f = gtk_file_filter_new();
524
525 /* name */
526 gtk_file_filter_set_name( f, params);
527 while ( *params) params++;
528 params++;
529
530 /* semicolon-separated shell globs */
531 pattern = ( char *) params;
532 while ( *params) {
533 if ( *params == ';') {
534 *params = 0;
535 gtk_file_filter_add_pattern( f, pattern);
536 pattern = params + 1;
537 }
538 params++;
539 }
540 gtk_file_filter_add_pattern( f, pattern);
541
542 list_add( gtk_filters, (Handle) f);
543
544 params++;
545 }
546 }
547 } else if ( strncmp( params, "filterindex", 11) == 0) {
548 params += 11;
549 if ( *params == '=') {
550 int fi = 0;
551 sscanf( params + 1, "%d", &fi);
552 gtk_filter_index = fi;
553 } else {
554 char buf[25];
555 sprintf( buf, "%d", gtk_filter_index);
556 return duplicate_string( buf);
557 }
558 } else if ( strncmp( params, "multi_select=", 13) == 0) {
559 params += 13;
560 gtk_select_multiple = (*params != '0');
561 } else if ( strncmp( params, "overwrite_prompt=", 17) == 0) {
562 params += 17;
563 gtk_overwrite_prompt = (*params != '0');
564 } else if (
565 ( strncmp( params, "open", 4) == 0) ||
566 ( strncmp( params, "save", 4) == 0)
567 ) {
568 return gtk_openfile( strncmp( params, "open", 4) == 0);
569 } else if ( strncmp( params, "show_hidden=", 12) == 0) {
570 params += 12;
571 gtk_show_hidden_files = (*params != '0');
572 } else if ( strncmp( params, "title=", 6) == 0) {
573 params += 6;
574 if ( *params == 0) {
575 gtk_dialog_title_ptr = NULL;
576 } else {
577 gtk_dialog_title_ptr = gtk_dialog_title;
578 strncpy( gtk_dialog_title, params, 255);
579 gtk_dialog_title[255] = 0;
580 }
581 } else {
582 warn("gtk.OpenFile: Unknown function %s", params);
583 }
584
585 return NULL;
586 }
587
588 /* Thanks to Cosimo Cecchi @ gnome-screenshot for the code below */
589 Bool
prima_gtk_application_get_bitmap(Handle self,Handle image,int x,int y,int xLen,int yLen)590 prima_gtk_application_get_bitmap( Handle self, Handle image, int x, int y, int xLen, int yLen)
591 {
592 #if defined(SAFE_DBUS) && (GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 34))
593 DEFXX;
594 int i, found_png;
595 PList codecs;
596 GVariant *params, *results;
597 GError * error = NULL;
598 GDBusConnection *conn;
599 char filename[256];
600
601 /* do we have png? it seems gnome only saves scheenshots as pngs */
602 codecs = plist_create( 16, 16);
603 apc_img_codecs( codecs);
604 found_png = false;
605 for ( i = 0; i < codecs-> count; i++) {
606 PImgCodec c = ( PImgCodec ) codecs-> items[ i];
607 if ( strcmp( c-> info-> fileShortType, "PNG" ) == 0 ) {
608 found_png = true;
609 break;
610 }
611 }
612 plist_destroy( codecs);
613 if ( !found_png ) {
614 Mdebug("PNG decoder not found\n");
615 return false;
616 }
617
618 /* execute gnome shell screenshot */
619 snprintf(filename, 256, "/tmp/%d-sc.png", (int) getpid());
620 params = g_variant_new("(iiiibs)",
621 x, XX->size.y - y - yLen, xLen, yLen,
622 0, filename);
623
624 if (!( conn = g_application_get_dbus_connection (g_application_get_default ()))) {
625 Mdebug("cannot get dbus connection\n");
626 return false;
627 }
628
629 results = g_dbus_connection_call_sync (conn,
630 "org.gnome.Shell.Screenshot",
631 "/org/gnome/Shell/Screenshot",
632 "org.gnome.Shell.Screenshot",
633 "ScreenshotArea",
634 params,
635 NULL,
636 G_DBUS_CALL_FLAGS_NONE,
637 -1,
638 NULL,
639 &error
640 );
641 if ( results )
642 g_variant_unref( results );
643 if (error != NULL) {
644 Mdebug("cannot get gnome shell screenshot\n");
645 g_error_free (error);
646 return false;
647 }
648
649 /* load */
650 codecs = apc_img_load( image, filename, false, NULL, NULL, NULL);
651 unlink( filename );
652 if ( !codecs ) {
653 Mdebug("error loading png back\n");
654 return false;
655 }
656 plist_destroy(codecs);
657
658 return true;
659 #else
660 return false;
661 #endif
662 }
663
664 #endif
665