PXML support for file associations, and spitting out extra .desktop files for them...
[pandora-libraries.git] / lib / pnd_desktop.c
1
2 #include <stdio.h> /* for FILE etc */
3 #include <string.h>
4 #include <unistd.h> /* for unlink */
5 #include <stdlib.h> /* for free */
6 #include <sys/stat.h> /* for stat */
7
8 #include "pnd_apps.h"
9 #include "pnd_container.h"
10 #include "pnd_pxml.h"
11 #include "pnd_discovery.h"
12 #include "pnd_pndfiles.h"
13 #include "pnd_conf.h"
14 #include "pnd_desktop.h"
15 #include "pnd_logger.h"
16
17 unsigned char pnd_emit_dotdesktop ( char *targetpath, char *pndrun, pnd_disco_t *p ) {
18   char filename [ FILENAME_MAX ];
19   char buffer [ 1024 ];
20   FILE *f;
21
22   // specification
23   // http://standards.freedesktop.org/desktop-entry-spec
24
25   // validation
26
27   if ( ! p -> unique_id ) {
28     pnd_log ( PND_LOG_DEFAULT, "Can't emit dotdesktop for %s, missing unique-id\n", targetpath );
29     return ( 0 );
30   }
31
32   if ( ! p -> exec ) {
33     pnd_log ( PND_LOG_DEFAULT, "Can't emit dotdesktop for %s, missing exec\n", targetpath );
34     return ( 0 );
35   }
36
37   if ( ! targetpath ) {
38     pnd_log ( PND_LOG_DEFAULT, "Can't emit dotdesktop for %s, missing target path\n", targetpath );
39     return ( 0 );
40   }
41
42   if ( ! pndrun ) {
43     pnd_log ( PND_LOG_DEFAULT, "Can't emit dotdesktop for %s, missing pnd_run.sh\n", targetpath );
44     return ( 0 );
45   }
46
47   // set up
48
49   sprintf ( filename, "%s/%s#%u.desktop", targetpath, p -> unique_id, p -> subapp_number );
50
51   // emit
52
53   //printf ( "EMIT DOTDESKTOP '%s'\n", filename );
54
55   f = fopen ( filename, "w" );
56
57   if ( ! f ) {
58     return ( 0 );
59   }
60
61   fprintf ( f, "%s\n", PND_DOTDESKTOP_HEADER );
62
63   if ( p -> title_en ) {
64     snprintf ( buffer, 1020, "Name=%s\n", p -> title_en );
65     fprintf ( f, "%s", buffer );
66   }
67
68   fprintf ( f, "Type=Application\n" );
69   fprintf ( f, "Version=1.0\n" );
70
71   if ( p -> icon ) {
72     snprintf ( buffer, 1020, "Icon=%s\n", p -> icon );
73     fprintf ( f, "%s", buffer );
74   }
75
76   if ( p -> unique_id ) {
77     fprintf ( f, "X-Pandora-UID=%s\n", p -> unique_id );
78   }
79
80 #if 1
81   if ( p -> desc_en && p -> desc_en [ 0 ] ) {
82     snprintf ( buffer, 1020, "Comment=%s\n", p -> desc_en ); // no [en] needed I suppose, yet
83     fprintf ( f, "%s", buffer );
84   }
85 #endif
86
87   if ( p -> preview_pic1 ) {
88     fprintf ( f, "X-Pandora-Preview-Pic-1=%s\n", p -> preview_pic1 );
89   }
90
91   if ( p -> clockspeed ) {
92     fprintf ( f, "X-Pandora-Clockspeed=%s\n", p -> clockspeed );
93   }
94
95   if ( p -> startdir ) {
96     fprintf ( f, "X-Pandora-Startdir=%s\n", p -> startdir );
97   }
98
99   if ( p -> main_category ) {
100     fprintf ( f, "X-Pandora-MainCategory=%s\n", p -> main_category );
101   }
102   if ( p -> main_category1 ) {
103     fprintf ( f, "X-Pandora-MainCategory1=%s\n", p -> main_category1 );
104   }
105   if ( p -> main_category2 ) {
106     fprintf ( f, "X-Pandora-MainCategory2=%s\n", p -> main_category2 );
107   }
108
109   if ( p -> alt_category ) {
110     fprintf ( f, "X-Pandora-AltCategory=%s\n", p -> alt_category );
111   }
112   if ( p -> alt_category1 ) {
113     fprintf ( f, "X-Pandora-AltCategory1=%s\n", p -> alt_category1 );
114   }
115   if ( p -> alt_category2 ) {
116     fprintf ( f, "X-Pandora-AltCategory2=%s\n", p -> alt_category2 );
117   }
118   if ( p -> info_filename ) {
119     fprintf ( f, "X-Pandora-Info-Filename=%s\n", p -> info_filename );
120   }
121   if ( p -> info_name ) {
122     fprintf ( f, "X-Pandora-Info-Name=%s\n", p -> info_name );
123   }
124
125 #if 0 // we let pnd_run.sh command line handle this instead of in .desktop
126   if ( p -> startdir ) {
127     snprintf ( buffer, 1020, "Path=%s\n", p -> startdir );
128     fprintf ( f, "%s", buffer );
129   } else {
130     fprintf ( f, "Path=%s\n", PND_DEFAULT_WORKDIR );
131   }
132 #endif
133
134   if ( p -> exec ) {
135     char *nohup;
136
137     if ( p -> option_no_x11 ) {
138       nohup = "/usr/bin/nohup ";
139     } else {
140       nohup = "";
141     }
142
143     // basics
144     if ( p -> object_type == pnd_object_type_directory ) {
145       snprintf ( buffer, 1020, "Exec=%s%s -p \"%s\" -e \"%s\" -b \"%s\"",
146                  nohup, pndrun, p -> object_path, p -> exec,
147                  p -> appdata_dirname ? p -> appdata_dirname : p -> unique_id );
148     } else if ( p -> object_type == pnd_object_type_pnd ) {
149       snprintf ( buffer, 1020, "Exec=%s%s -p \"%s/%s\" -e \"%s\" -b \"%s\"",
150                  nohup, pndrun, p -> object_path, p -> object_filename, p -> exec,
151                  p -> appdata_dirname ? p -> appdata_dirname : p -> unique_id );
152     }
153
154     // start dir
155     if ( p -> startdir ) {
156       strncat ( buffer, " -s ", 1020 );
157       strncat ( buffer, p -> startdir, 1020 );
158     }
159
160     // args
161     if ( p -> execargs ) {
162       char argbuf [ 1024 ];
163       snprintf ( argbuf, 1000, " -a \"%s\"", p -> execargs );
164       strncat ( buffer, argbuf, 1020 );
165     }
166
167     // clockspeed
168     if ( p -> clockspeed && atoi ( p -> clockspeed ) != 0 ) {
169       strncat ( buffer, " -c ", 1020 );
170       strncat ( buffer, p -> clockspeed, 1020 );
171     }
172
173     // exec options
174     if ( pnd_pxml_get_x11 ( p -> option_no_x11 ) == pnd_pxml_x11_stop ) {
175       strncat ( buffer, " -x ", 1020 );
176     }
177
178     // newline
179     strncat ( buffer, "\n", 1020 );
180
181     // emit
182     fprintf ( f, "%s", buffer );
183
184     // and lets copy in some stuff in case it makes .desktop consumers life easier
185     if ( p -> exec ) { fprintf ( f, "X-Pandora-Exec=%s\n", p -> exec ); }
186     if ( p -> appdata_dirname ) { fprintf ( f, "X-Pandora-Appdata-Dirname=%s\n", p -> appdata_dirname ); }
187     if ( p -> execargs ) { fprintf ( f, "X-Pandora-ExecArgs=%s\n", p -> execargs ); }
188     if ( p -> object_flags & PND_DISCO_FLAG_OVR ) { fprintf ( f, "X-Pandora-Object-Flag-OVR=%s\n", "Yes" ); }
189     if ( p -> object_type == pnd_object_type_pnd ) {
190       fprintf ( f, "X-Pandora-Object-Path=%s\n", p -> object_path );
191       fprintf ( f, "X-Pandora-Object-Filename=%s\n", p -> object_filename );
192     }
193
194   }
195
196   // categories
197   {
198     char cats [ 512 ] = "";
199     int n;
200     pnd_conf_handle c;
201     char *confpath;
202
203     // uuuuh, defaults?
204     // "Application" used to be in the standard and is commonly supported still
205     // Utility and Network should ensure the app is visible 'somewhere' :/
206     char *defaults = PND_DOTDESKTOP_DEFAULT_CATEGORY;
207
208     // determine searchpath (for conf, not for apps)
209     confpath = pnd_conf_query_searchpath();
210
211     // inhale the conf file
212     c = pnd_conf_fetch_by_id ( pnd_conf_categories, confpath );
213
214     // if we can find a default category set, pull it in; otherwise assume
215     // the hardcoded one
216     if ( pnd_conf_get_as_char ( c, "default" ) ) {
217       defaults = pnd_conf_get_as_char ( c, "default" );
218     }
219
220     // ditch the confpath
221     free ( confpath );
222
223     // attempt mapping
224     if ( c ) {
225
226       n = pnd_map_dotdesktop_categories ( c, cats, 511, p );
227
228       if ( n ) {
229         fprintf ( f, "Categories=%s\n", cats );
230       } else {
231         fprintf ( f, "Categories=%s\n", defaults );
232       }
233
234     } else {
235       fprintf ( f, "Categories=%s\n", defaults );
236     }
237
238   }
239
240   fprintf ( f, "%s\n", PND_DOTDESKTOP_SOURCE ); // should we need to know 'who' created the file during trimming
241
242   fclose ( f );
243
244   return ( 1 );
245 }
246
247 unsigned char pnd_emit_dotinfo ( char *targetpath, char *pndrun, pnd_disco_t *p ) {
248   char filename [ FILENAME_MAX ];
249   char buffer [ 1024 ];
250   FILE *f;
251   char *viewer, *searchpath;
252   pnd_conf_handle desktoph;
253
254   // specification
255   // http://standards.freedesktop.org/desktop-entry-spec
256
257   // validation
258   //
259
260   // viewer
261   searchpath = pnd_conf_query_searchpath();
262
263   desktoph = pnd_conf_fetch_by_id ( pnd_conf_desktop, searchpath );
264
265   if ( ! desktoph ) {
266     return ( 0 );
267   }
268
269   viewer = pnd_conf_get_as_char ( desktoph, "info.viewer" );
270
271   if ( ! viewer ) {
272     return ( 0 ); // no way to view the file
273   }
274
275   // etc
276   if ( ! p -> unique_id ) {
277     pnd_log ( PND_LOG_DEFAULT, "Can't emit dotdesktop for %s, missing unique-id\n", targetpath );
278     return ( 0 );
279   }
280
281   if ( ! p -> info_filename ) {
282     pnd_log ( PND_LOG_DEFAULT, "Can't emit dotdesktop for %s, missing info_filename\n", targetpath );
283     return ( 0 );
284   }
285
286   if ( ! p -> info_name ) {
287     pnd_log ( PND_LOG_DEFAULT, "Can't emit dotdesktop for %s, missing info_name\n", targetpath );
288     return ( 0 );
289   }
290
291   if ( ! targetpath ) {
292     pnd_log ( PND_LOG_DEFAULT, "Can't emit dotdesktop for %s, missing target path\n", targetpath );
293     return ( 0 );
294   }
295
296   if ( ! pndrun ) {
297     pnd_log ( PND_LOG_DEFAULT, "Can't emit dotdesktop for %s, missing pnd_run.sh\n", targetpath );
298     return ( 0 );
299   }
300
301   // set up
302
303   sprintf ( filename, "%s/%s#%uinfo.desktop", targetpath, p -> unique_id, p -> subapp_number );
304
305   // emit
306
307   f = fopen ( filename, "w" );
308
309   if ( ! f ) {
310     return ( 0 );
311   }
312
313   fprintf ( f, "%s\n", PND_DOTDESKTOP_HEADER );
314
315   if ( p -> info_name ) {
316     snprintf ( buffer, 1020, "Name=%s\n", p -> info_name );
317     fprintf ( f, "%s", buffer );
318   }
319
320   fprintf ( f, "Type=Application\n" );
321   fprintf ( f, "Version=1.0\n" );
322
323 #if 0
324   if ( p -> icon ) {
325     snprintf ( buffer, 1020, "Icon=%s\n", p -> icon );
326     fprintf ( f, "%s", buffer );
327   }
328 #endif
329
330   if ( p -> unique_id ) {
331     fprintf ( f, "X-Pandora-UID=%s\n", p -> unique_id );
332   }
333
334   if ( p -> title_en && p -> title_en [ 0 ] ) {
335     snprintf ( buffer, 1020, "Comment=Automatic menu info entry for %s\n", p -> title_en );
336     fprintf ( f, "%s", buffer );
337   }
338
339 #if 0 // we let pnd_run.sh command line handle this instead of in .desktop
340   if ( p -> startdir ) {
341     snprintf ( buffer, 1020, "Path=%s\n", p -> startdir );
342     fprintf ( f, "%s", buffer );
343   } else {
344     fprintf ( f, "Path=%s\n", PND_DEFAULT_WORKDIR );
345   }
346 #endif
347
348   // exec line
349   char args [ 1001 ];
350   char *pargs = args;
351   char *viewerargs = pnd_conf_get_as_char ( desktoph, "info.viewer_args" );
352   if ( viewerargs && viewerargs [ 0 ] ) {
353     snprintf ( pargs, 1001, "%s %s",
354                pnd_conf_get_as_char ( desktoph, "info.viewer_args" ), p -> info_filename );
355   } else {
356     pargs = NULL;
357     // WARNING: This might not be quite right; if no viewer-args, shouldn't we still append the info-filename? likewise,
358     //          even if we do have view-args, shouldn't we check if filename is present?
359   }
360
361   char pndfile [ 1024 ];
362   if ( p -> object_type == pnd_object_type_directory ) {
363     // for PXML-app-dir, pnd_run.sh doesn't want the PXML.xml.. it just wants the dir-name
364     strncpy ( pndfile, p -> object_path, 1000 );
365   } else if ( p -> object_type == pnd_object_type_pnd ) {
366     // pnd_run.sh wants the full path and filename for the .pnd file
367     snprintf ( pndfile, 1020, "%s/%s", p -> object_path, p -> object_filename );
368   }
369
370   pnd_apps_exec_info_t info;
371   info.viewer = viewer;
372   info.args = pargs;
373
374   if ( ! pnd_apps_exec_disco ( pndrun, p, PND_EXEC_OPTION_NORUN | PND_EXEC_OPTION_INFO, &info ) ) {
375     return ( 0 );
376   }
377
378   fprintf ( f, "Exec=%s\n", pnd_apps_exec_runline() );
379
380   if ( pnd_conf_get_as_char ( desktoph, "info.category" ) ) {
381     fprintf ( f, "Categories=%s\n", pnd_conf_get_as_char ( desktoph, "info.category" ) );
382   } else {
383     fprintf ( f, "Categories=Documentation\n" );
384   }
385
386   fprintf ( f, "%s\n", PND_DOTDESKTOP_SOURCE ); // should we need to know 'who' created the file during trimming
387
388   fclose ( f );
389
390   return ( 1 );
391 }
392
393 unsigned char pnd_emit_dotassoc ( char *targetpath, char *pndrun, pnd_disco_t *p ) {
394
395   if ( p -> associationitem1_command ) {
396     if ( ! _pnd_emit_dotassoc ( targetpath, pndrun, p, 1 ) ) {
397       return ( 0 );
398     }
399   }
400
401   if ( p -> associationitem2_command ) {
402     if ( ! _pnd_emit_dotassoc ( targetpath, pndrun, p, 2 ) ) {
403       return ( 0 );
404     }
405   }
406
407   if ( p -> associationitem3_command ) {
408     if ( ! _pnd_emit_dotassoc ( targetpath, pndrun, p, 3 ) ) {
409       return ( 0 );
410     }
411   }
412
413   return ( 1 );
414 }
415
416 unsigned char _pnd_emit_dotassoc ( char *targetpath, char *pndrun, pnd_disco_t *p, unsigned char i ) {
417   char filename [ FILENAME_MAX ];
418   char buffer [ 1024 ];
419   FILE *f;
420
421   char *command;
422   char *args;
423   char *filetype;
424   char *mimename;
425
426   if ( i == 1 ) {
427     command = p -> associationitem1_command;
428     args = p -> associationitem1_args;
429     filetype = p -> associationitem1_filetype;
430     mimename = p -> associationitem1_name;
431   } else if ( i == 2 ) {
432     command = p -> associationitem2_command;
433     args = p -> associationitem2_args;
434     filetype = p -> associationitem2_filetype;
435     mimename = p -> associationitem2_name;
436   } else if ( i == 3 ) {
437     command = p -> associationitem3_command;
438     args = p -> associationitem3_args;
439     filetype = p -> associationitem3_filetype;
440     mimename = p -> associationitem3_name;
441   } else {
442     return ( 0 ); // w-t-f
443   }
444
445   // specification
446   // http://standards.freedesktop.org/desktop-entry-spec
447
448   // validation
449
450   if ( ! p -> unique_id ) {
451     pnd_log ( PND_LOG_DEFAULT, "Can't emit dotdesktop for %s, missing unique-id\n", targetpath );
452     return ( 0 );
453   }
454
455   if ( ! command ) {
456     pnd_log ( PND_LOG_DEFAULT, "Can't emit dotdesktop for %s, missing exec\n", targetpath );
457     return ( 0 );
458   }
459
460   if ( ! filetype ) {
461     pnd_log ( PND_LOG_DEFAULT, "Can't emit dotdesktop for %s, missing MIME-type\n", targetpath );
462     return ( 0 );
463   }
464
465   if ( ! targetpath ) {
466     pnd_log ( PND_LOG_DEFAULT, "Can't emit dotdesktop for %s, missing target path\n", targetpath );
467     return ( 0 );
468   }
469
470   if ( ! pndrun ) {
471     pnd_log ( PND_LOG_DEFAULT, "Can't emit dotdesktop for %s, missing pnd_run.sh\n", targetpath );
472     return ( 0 );
473   }
474
475   // set up
476
477   sprintf ( filename, "%s/%s#%u#%u.desktop", targetpath, p -> unique_id, p -> subapp_number, i );
478
479   // emit
480
481   //printf ( "EMIT DOTDESKTOP '%s'\n", filename );
482
483   f = fopen ( filename, "w" );
484
485   if ( ! f ) {
486     return ( 0 );
487   }
488
489   fprintf ( f, "%s\n", PND_DOTDESKTOP_HEADER );
490
491   fprintf ( f, "NoDisplay=false\n" ); // file association entries shouldn't live in the menus; but if 'true', xfce won't show this association in the dialog.. stupid!
492
493   if ( p -> title_en ) {
494     snprintf ( buffer, 1020, "Name=%s\n", mimename );
495     fprintf ( f, "%s", buffer );
496   }
497
498   fprintf ( f, "Type=Application\n" );
499   fprintf ( f, "Version=1.0\n" );
500
501   if ( p -> icon ) {
502     snprintf ( buffer, 1020, "Icon=%s\n", p -> icon );
503     fprintf ( f, "%s", buffer );
504   }
505
506   if ( p -> unique_id ) {
507     fprintf ( f, "X-Pandora-UID=%s\n", p -> unique_id );
508   }
509
510   if ( p -> clockspeed ) {
511     fprintf ( f, "X-Pandora-Clockspeed=%s\n", p -> clockspeed );
512   }
513
514   if ( p -> startdir ) {
515     fprintf ( f, "X-Pandora-Startdir=%s\n", p -> startdir );
516   }
517
518   if ( filetype ) {
519     pnd_log ( PND_LOG_DEFAULT, "  EmitDesktopAssoc: Found file association request in PXML (%s) for mimetype (%s)\n", p -> title_en, filetype );
520     fprintf ( f, "MimeType=%s\n", filetype );
521   }
522
523   if ( command ) {
524     char *nohup;
525
526     if ( p -> option_no_x11 ) {
527       nohup = "/usr/bin/nohup ";
528     } else {
529       nohup = "";
530     }
531
532     // basics
533     if ( p -> object_type == pnd_object_type_directory ) {
534       snprintf ( buffer, 1020, "Exec=%s%s -p \"%s\" -e \"%s\" -b \"%s\"",
535                  nohup, pndrun, p -> object_path, command,
536                  p -> appdata_dirname ? p -> appdata_dirname : p -> unique_id );
537     } else if ( p -> object_type == pnd_object_type_pnd ) {
538       snprintf ( buffer, 1020, "Exec=%s%s -p \"%s/%s\" -e \"%s\" -b \"%s\"",
539                  nohup, pndrun, p -> object_path, p -> object_filename, command,
540                  p -> appdata_dirname ? p -> appdata_dirname : p -> unique_id );
541     }
542
543     // start dir
544     if ( p -> startdir ) {
545       strncat ( buffer, " -s ", 1020 );
546       strncat ( buffer, p -> startdir, 1020 );
547     }
548
549     // args (pxml args)
550     if ( p -> execargs ) {
551       char argbuf [ 1024 ];
552       snprintf ( argbuf, 1000, " -a \"%s\"", p -> execargs );
553       strncat ( buffer, argbuf, 1020 );
554     }
555
556     // clockspeed
557     if ( p -> clockspeed && atoi ( p -> clockspeed ) != 0 ) {
558       strncat ( buffer, " -c ", 1020 );
559       strncat ( buffer, p -> clockspeed, 1020 );
560     }
561
562     // exec options
563     if ( pnd_pxml_get_x11 ( p -> option_no_x11 ) == pnd_pxml_x11_stop ) {
564       strncat ( buffer, " -x ", 1020 );
565     }
566
567     // args (mimetype args)
568     if ( args ) {
569       char argbuf [ 1024 ];
570       snprintf ( argbuf, 1000, " -- %s", args );
571       strncat ( buffer, argbuf, 1020 );
572     }
573
574     // newline
575     strncat ( buffer, "\n", 1020 );
576
577     // emit
578     fprintf ( f, "%s", buffer );
579
580     // and lets copy in some stuff in case it makes .desktop consumers life easier
581     if ( command ) { fprintf ( f, "X-Pandora-Exec=%s\n", command ); }
582     if ( args ) { fprintf ( f, "X-Pandora-ExecArgs=%s\n", args ); }
583     if ( p -> appdata_dirname ) { fprintf ( f, "X-Pandora-Appdata-Dirname=%s\n", p -> appdata_dirname ); }
584     if ( p -> object_flags & PND_DISCO_FLAG_OVR ) { fprintf ( f, "X-Pandora-Object-Flag-OVR=%s\n", "Yes" ); }
585     if ( p -> object_type == pnd_object_type_pnd ) {
586       fprintf ( f, "X-Pandora-Object-Path=%s\n", p -> object_path );
587       fprintf ( f, "X-Pandora-Object-Filename=%s\n", p -> object_filename );
588     }
589
590   }
591
592   fprintf ( f, "Categories=Other\n" ); // NoDisplay means can't confirm choice in xfce, so how do we hide this out of the way?
593
594   fprintf ( f, "%s\n", PND_DOTDESKTOP_SOURCE ); // should we need to know 'who' created the file during trimming
595
596   fclose ( f );
597
598   return ( 1 );
599 } // dotassoc
600
601 unsigned char pnd_emit_icon ( char *targetpath, pnd_disco_t *p ) {
602   //#define BITLEN (8*1024)
603 #define BITLEN (64*1024)
604   char buffer [ FILENAME_MAX ]; // target filename
605   char from [ FILENAME_MAX ];   // source filename
606   unsigned char bits [ BITLEN ];
607   unsigned int bitlen;
608   FILE *pnd, *target;
609
610   // prelim .. if a pnd file, and no offset found, discovery code didn't locate icon.. so bail.
611   if ( ( p -> object_type == pnd_object_type_pnd ) &&
612        ( ! p -> pnd_icon_pos ) )
613   {
614     return ( 0 ); // discover code didn't find it, so FAIL
615   }
616
617   // determine filename for target
618   sprintf ( buffer, "%s/%s.png", targetpath, p -> unique_id /*, p -> subapp_number*/ ); // target
619
620   /* first.. open the source file, by type of application:
621    * are we looking through a pnd file or a dir?
622    */
623   if ( p -> object_type == pnd_object_type_directory ) {
624     sprintf ( from, "%s/%s", p -> object_path, p -> icon );
625   } else if ( p -> object_type == pnd_object_type_pnd ) {
626     sprintf ( from, "%s/%s", p -> object_path, p -> object_filename );
627   }
628
629   pnd = fopen ( from, "rb" );
630
631   if ( ! pnd ) {
632     pnd_log ( PND_LOG_DEFAULT, "    Emit icon, couldn't open source\n" );
633     return ( 0 );
634   }
635
636   unsigned int len;
637
638   target = fopen ( buffer, "wb" );
639
640   if ( ! target ) {
641     fclose ( pnd );
642     pnd_log ( PND_LOG_DEFAULT, "    Emit icon, couldn't open target\n" );
643     return ( 0 );
644   }
645
646   fseek ( pnd, 0, SEEK_END );
647   len = ftell ( pnd );
648   //fseek ( pnd, 0, SEEK_SET );
649
650   fseek ( pnd, p -> pnd_icon_pos, SEEK_SET );
651
652   len -= p -> pnd_icon_pos;
653
654   pnd_log ( PND_LOG_DEFAULT, "    Emit icon, length: %u\n", len );
655
656   while ( len ) {
657
658     if ( len > (BITLEN) ) {
659       bitlen = (BITLEN);
660     } else {
661       bitlen = len;
662     }
663
664     if ( fread ( bits, bitlen, 1, pnd ) != 1 ) {
665       fclose ( pnd );
666       fclose ( target );
667       unlink ( buffer );
668       pnd_log ( PND_LOG_DEFAULT, "    Emit icon, bad read\n" );
669       return ( 0 );
670     }
671
672 #if 0
673     {
674       unsigned int i = 0;
675       char bigbuffer [ 200 * 1024 ] = "\0";
676       char b [ 10 ];
677       pnd_log ( PND_LOG_DEFAULT, "    Read hexdump\n" );
678       while ( i < bitlen ) {
679         sprintf ( b, "%x,", bits [ i ] );
680         strcat ( bigbuffer, b );
681         i++;
682       }
683       pnd_log ( PND_LOG_DEFAULT, bigbuffer );
684     }
685 #endif
686
687     if ( fwrite ( bits, bitlen, 1, target ) != 1 ) {
688       fclose ( pnd );
689       fclose ( target );
690       unlink ( buffer );
691       pnd_log ( PND_LOG_DEFAULT, "    Emit icon, bad write\n" );
692       return ( 0 );
693     }
694
695     len -= bitlen;
696     //pnd_log ( PND_LOG_DEFAULT, "    Emit icon, next block, length: %u\n", len );
697   } // while
698
699   fclose ( pnd );
700   fclose ( target );
701
702   //pnd_log ( PND_LOG_DEFAULT, "    Emit icon, done.\n" );
703
704   return ( 1 );
705 }
706
707 #if 1 // we switched direction to freedesktop standard categories
708 // if no categories herein, return 0; otherwise, if some category-like-text, return 1
709 int pnd_map_dotdesktop_categories ( pnd_conf_handle c, char *target_buffer, unsigned short int len, pnd_disco_t *d ) {
710   char *t;
711   char *match;
712
713   // clear target so we can easily append
714   memset ( target_buffer, '\0', len );
715
716   // for each main-cat and sub-cat, including alternates, just append them all together
717   // we'll try mapping them, since the categories file is there, but we'll default to
718   // copying over; this lets the conf file do merging or renaming of cagtegories, which
719   // could still be useful, but we can leave the conf file empty to effect a pure
720   // trusted-PXML-copying
721
722   // it would be sort of cumbersome to copy all the freedesktop.org defined categories (as
723   // there are hundreds), and would also mean new ones and peoples custom ones would
724   // flop
725
726   /* attempt primary category chain
727    */
728   #define MAPCAT(field)                                     \
729     if ( ( t = d -> field ) ) {                             \
730       match = pnd_map_dotdesktop_category ( c, t );         \
731       strncat ( target_buffer, match ? match : t, len );    \
732       strncat ( target_buffer, ";", len );                  \
733     }
734
735   MAPCAT(main_category);
736   MAPCAT(main_category1);
737   MAPCAT(main_category2);
738   MAPCAT(alt_category);
739   MAPCAT(alt_category1);
740   MAPCAT(alt_category2);
741
742   if ( target_buffer [ 0 ] ) {
743     return ( 1 ); // I guess its 'good'?
744   }
745
746 #if 0
747   if ( ( t = d -> main_category ) ) {
748     match = pnd_map_dotdesktop_category ( c, t );
749     strncat ( target_buffer, match ? match : t, len );
750     strncat ( target_buffer, ";", len );
751   }
752 #endif
753
754   return ( 0 );
755 }
756 #endif
757
758 #if 0 // we switched direction
759 //int pnd_map_dotdesktop_categories ( pnd_conf_handle c, char *target_buffer, unsigned short int len, pnd_pxml_handle h ) {
760 int pnd_map_dotdesktop_categories ( pnd_conf_handle c, char *target_buffer, unsigned short int len, pnd_disco_t *d ) {
761   unsigned short int n = 0; // no. matches
762   char *t;
763   char *match;
764
765   // clear target so we can easily append
766   memset ( target_buffer, '\0', len );
767
768   /* attempt primary category chain
769    */
770   match = NULL;
771
772   if ( ( t = d -> main_category ) ) {
773     match = pnd_map_dotdesktop_category ( c, t );
774   }
775
776   if ( ( ! match ) &&
777        ( t = d -> main_category1 ) )
778   {
779     match = pnd_map_dotdesktop_category ( c, t );
780   }
781
782   if ( ( ! match ) &&
783        ( t = d -> main_category2 ) )
784   {
785     match = pnd_map_dotdesktop_category ( c, t );
786   }
787
788   if ( match ) {
789     strncat ( target_buffer, match, len );
790     len -= strlen ( target_buffer );
791     n += 1;
792   }
793
794   /* attempt secondary category chain
795    */
796   match = NULL;
797
798   if ( ( t = d -> alt_category ) ) {
799     match = pnd_map_dotdesktop_category ( c, t );
800   }
801
802   if ( ( ! match ) &&
803        ( t = d -> alt_category1 ) )
804   {
805     match = pnd_map_dotdesktop_category ( c, t );
806   }
807
808   if ( ( ! match ) &&
809        ( t = d -> alt_category2 ) )
810   {
811     match = pnd_map_dotdesktop_category ( c, t );
812   }
813
814   if ( match ) {
815     if ( target_buffer [ 0 ] != '\0' && len > 0 ) {
816       strcat ( target_buffer, ";" );
817       len -= 1;
818     }
819     strncat ( target_buffer, match, len );
820     len -= strlen ( target_buffer );
821     n += 1;
822   }
823
824 #if 0 // doh, originally I was using pxml-t till I realized I pre-boned myself on that one
825   match = NULL;
826
827   if ( ( t = pnd_pxml_get_main_category ( h ) ) ) {
828     match = pnd_map_dotdesktop_category ( c, t );
829   }
830
831   if ( ( ! match ) &&
832        ( t = pnd_pxml_get_subcategory1 ( h ) ) )
833   {
834     match = pnd_map_dotdesktop_category ( c, t );
835   }
836
837   if ( ( ! match ) &&
838        ( t = pnd_pxml_get_subcategory2 ( h ) ) )
839   {
840     match = pnd_map_dotdesktop_category ( c, t );
841   }
842
843   if ( match ) {
844     strncat ( target_buffer, match, len );
845     len -= strlen ( target_buffer );
846     n += 1;
847   }
848
849   /* attempt secondary category chain
850    */
851   match = NULL;
852
853   if ( ( t = pnd_pxml_get_altcategory ( h ) ) ) {
854     match = pnd_map_dotdesktop_category ( c, t );
855   }
856
857   if ( ( ! match ) &&
858        ( t = pnd_pxml_get_altsubcategory1 ( h ) ) )
859   {
860     match = pnd_map_dotdesktop_category ( c, t );
861   }
862
863   if ( ( ! match ) &&
864        ( t = pnd_pxml_get_altsubcategory2 ( h ) ) )
865   {
866     match = pnd_map_dotdesktop_category ( c, t );
867   }
868
869   if ( match ) {
870     if ( target_buffer [ 0 ] != '\0' && len > 0 ) {
871       strcat ( target_buffer, ";" );
872       len -= 1;
873     }
874     strncat ( target_buffer, match, len );
875     len -= strlen ( target_buffer );
876     n += 1;
877   }
878 #endif
879
880   if ( n && len ) {
881     strcat ( target_buffer, ";" );
882   }
883
884   return ( n );
885 }
886 #endif
887
888 // given category 'foo', look it up in the provided config map. return the char* reference, or NULL
889 char *pnd_map_dotdesktop_category ( pnd_conf_handle c, char *single_category ) {
890   char *key;
891   char *ret;
892
893   key = malloc ( strlen ( single_category ) + 4 + 1 );
894
895   sprintf ( key, "map.%s", single_category );
896
897   ret = pnd_conf_get_as_char ( c, key );
898
899   free ( key );
900
901   return ( ret );
902 }
903
904 unsigned char *pnd_emit_icon_to_buffer ( pnd_disco_t *p, unsigned int *r_buflen ) {
905   // this is shamefully mostly a copy of emit_icon() above; really, need to refactor that to use this routine
906   // with a fwrite at the end...
907   char from [ FILENAME_MAX ];   // source filename
908   char bits [ 8 * 1024 ];
909   unsigned int bitlen;
910   FILE *pnd = NULL;
911   unsigned char *target = NULL, *targiter = NULL;
912
913   // prelim .. if a pnd file, and no offset found, discovery code didn't locate icon.. so bail.
914   if ( ( p -> object_type == pnd_object_type_pnd ) &&
915        ( ! p -> pnd_icon_pos ) )
916   {
917     return ( NULL ); // discover code didn't find it, so FAIL
918   }
919
920   /* first.. open the source file, by type of application:
921    * are we looking through a pnd file or a dir?
922    */
923   if ( p -> object_type == pnd_object_type_directory ) {
924     sprintf ( from, "%s/%s", p -> object_path, p -> icon );
925   } else if ( p -> object_type == pnd_object_type_pnd ) {
926     sprintf ( from, "%s/%s", p -> object_path, p -> object_filename );
927   }
928
929   pnd = fopen ( from, "r" );
930
931   if ( ! pnd ) {
932     return ( NULL );
933   }
934
935   // determine length of file, then adjust by icon position to find begin of icon
936   unsigned int len;
937
938   fseek ( pnd, 0, SEEK_END );
939   len = ftell ( pnd );
940   //fseek ( pnd, 0, SEEK_SET );
941
942   fseek ( pnd, p -> pnd_icon_pos, SEEK_SET );
943
944   len -= p -> pnd_icon_pos;
945
946   // create target buffer
947   target = malloc ( len );
948
949   if ( ! target ) {
950     fclose ( pnd );
951     return ( 0 );
952   }
953
954   targiter = target;
955
956   if ( r_buflen ) {
957     *r_buflen = len;
958   }
959
960   // copy over icon to target
961   while ( len ) {
962
963     if ( len > (8*1024) ) {
964       bitlen = (8*1024);
965     } else {
966       bitlen = len;
967     }
968
969     if ( fread ( bits, bitlen, 1, pnd ) != 1 ) {
970       fclose ( pnd );
971       free ( target );
972       return ( NULL );
973     }
974
975     memmove ( targiter, bits, bitlen );
976     targiter += bitlen;
977
978     len -= bitlen;
979   } // while
980
981   fclose ( pnd );
982
983   return ( target );
984 }
985
986 // parse_dotdesktop() can be used to read a libpnd generated .desktop and return a limited
987 // but useful disco-t structure back; possibly useful for scanning .desktops rather than
988 // scanning pnd-files?
989 pnd_disco_t *pnd_parse_dotdesktop ( char *ddpath, unsigned int flags ) {
990
991   // will verify the .desktop has the libpnd-marking on it (X-Pandora-Source): PND_DOTDESKTOP_SOURCE
992
993   // attempt to extract..
994   // - unique-id (from filename or field)
995   // - subapp number (from filename)
996   // - exec required info
997   // - icon path
998   // - name (title-en)
999   // - comment (desc-en)
1000   // - option_no_x11
1001   // - object path
1002   // - appdata name (or unique-id if not present)
1003   // - start dir
1004   // - args
1005   // - clockspeed
1006   // - categories
1007
1008   char pndpath [ 1024 ];
1009   bzero ( pndpath, 1024 );
1010
1011   // filter on filename?
1012   if ( flags & PND_DOTDESKTOP_LIBPND_ONLY ) {
1013     // too bad we didn't put some libpnd token at the front of the filename or something
1014     // hell, we should cleanse unique-id to ensure its not full of special chars like '*' and '..'.. eep!
1015     if ( strrchr ( ddpath, '#' ) == NULL ) { // but if requiring libpnd, we can at least check for #subapp-number
1016       return ( NULL );
1017     }
1018   }
1019
1020   if ( strstr ( ddpath, ".desktop" ) == NULL ) {
1021     return ( NULL ); // no .desktop in filename, must be something else... skip!
1022   }
1023
1024   if ( strstr ( ddpath, "info.desktop" ) != NULL ) {
1025     // ".....info.desktop" is the 'document help' (README) emitted from a pnd, not an actual app; minimenu rather
1026     // expects the doc-info as part of the main app, not a separate app.. so lets drop it here, to avoid doubling up
1027     // the number of applications, needlessly..
1028     return ( NULL );
1029   }
1030
1031   // determine file length
1032   struct stat statbuf;
1033
1034   if ( stat ( ddpath, &statbuf) < 0 ) {
1035     return ( NULL ); // couldn't open
1036   }
1037
1038   // buffers..
1039   char dd [ 1024 ];
1040   unsigned char libpnd_origin = 0;
1041
1042   // disco
1043   pnd_disco_t *p = malloc ( sizeof(pnd_disco_t) );
1044   if ( ! p ) {
1045     return ( NULL );
1046   }
1047   bzero ( p, sizeof(pnd_disco_t) );
1048
1049   // inhale file
1050   FILE *f = fopen ( ddpath, "r" );
1051
1052   if ( ! f ) {
1053     return ( NULL ); // not up or shut up!
1054   }
1055
1056   while ( fgets ( dd, 1024, f ) ) {
1057     char *nl = strchr ( dd, '\n' );
1058     if ( nl ) {
1059       *nl = '\0';
1060     }
1061
1062     // grep
1063     //
1064
1065     if ( strncmp ( dd, "Name=", 5 ) == 0 ) {
1066       p -> title_en = strdup ( dd + 5 );
1067     } else if ( strncmp ( dd, "Name[en]=", 9 ) == 0 ) {
1068       p -> title_en = strdup ( dd + 9 );
1069     } else if ( strncmp ( dd, "Icon=", 5 ) == 0 ) {
1070       p -> icon = strdup ( dd + 5 );
1071     } else if ( strcmp ( dd, PND_DOTDESKTOP_SOURCE ) == 0 ) {
1072       libpnd_origin = 1;
1073     } else if ( strncmp ( dd, "X-Pandora-UID=", 14 ) == 0 ) {
1074       p -> unique_id = strdup ( dd + 14 );
1075     } else if ( strncmp ( dd, "X-Pandora-Preview-Pic-1=", 24 ) == 0 ) {
1076       p -> preview_pic1 = strdup ( dd + 24 );
1077     } else if ( strncmp ( dd, "X-Pandora-Clockspeed=", 21 ) == 0 ) {
1078       p -> clockspeed = strdup ( dd + 21 );
1079     } else if ( strncmp ( dd, "X-Pandora-Startdir=", 19 ) == 0 ) {
1080       p -> startdir = strdup ( dd + 19 );
1081     } else if ( strncmp ( dd, "X-Pandora-Appdata-Dirname=", 26 ) == 0 ) {
1082       p -> appdata_dirname = strdup ( dd + 26 );
1083     } else if ( strncmp ( dd, "X-Pandora-ExecArgs=", 19 ) == 0 ) {
1084       p -> execargs = strdup ( dd + 19 );
1085     } else if ( strncmp ( dd, "X-Pandora-Exec=", 15 ) == 0 ) {
1086       p -> exec = strdup ( dd + 15 );
1087     } else if ( strncmp ( dd, "X-Pandora-Object-Path=", 22 ) == 0 ) {
1088       p -> object_path = strdup ( dd + 22 );
1089     } else if ( strncmp ( dd, "X-Pandora-Object-Filename=", 26 ) == 0 ) {
1090       p -> object_filename = strdup ( dd + 26 );
1091     } else if ( strncmp ( dd, "X-Pandora-Object-Flag-OVR=", 26 ) == 0 ) {
1092       p -> object_flags |= PND_DISCO_FLAG_OVR;
1093
1094     } else if ( strncmp ( dd, "X-Pandora-MainCategory=", 23 ) == 0 ) {
1095       p -> main_category = strdup ( dd + 23 );
1096     } else if ( strncmp ( dd, "X-Pandora-MainCategory1=", 24 ) == 0 ) {
1097       p -> main_category1 = strdup ( dd + 24 );
1098     } else if ( strncmp ( dd, "X-Pandora-MainCategory2=", 24 ) == 0 ) {
1099       p -> main_category2 = strdup ( dd + 24 );
1100
1101     } else if ( strncmp ( dd, "X-Pandora-AltCategory=", 22 ) == 0 ) {
1102       p -> alt_category = strdup ( dd + 22 );
1103     } else if ( strncmp ( dd, "X-Pandora-AltCategory1=", 23 ) == 0 ) {
1104       p -> alt_category1 = strdup ( dd + 23 );
1105     } else if ( strncmp ( dd, "X-Pandora-AltCategory2=", 23 ) == 0 ) {
1106       p -> alt_category2 = strdup ( dd + 23 );
1107
1108     } else if ( strncmp ( dd, "X-Pandora-Info-Filename=", 24 ) == 0 ) {
1109       p -> info_filename = strdup ( dd + 24 );
1110     } else if ( strncmp ( dd, "X-Pandora-Info-Name=", 20 ) == 0 ) {
1111       p -> info_name = strdup ( dd + 20 );
1112
1113     } else if ( strncmp ( dd, "Comment=", 8 ) == 0 ) {
1114       p -> desc_en = strdup ( dd + 8 );
1115     } else if ( strncmp ( dd, "Comment[en]=", 12 ) == 0 ) {
1116       p -> desc_en = strdup ( dd + 12 );
1117     } else if ( strncmp ( dd, "Exec=", 5 ) == 0 ) {
1118
1119       char *e = strstr ( dd, " -e " );
1120
1121       if ( e ) {
1122         // probably libpnd app
1123 #if 0 // no needed due to above X-Pandora attributes
1124
1125         if ( e ) {
1126           e += 5;
1127
1128           char *space = strchr ( e, ' ' );
1129           p -> exec = strndup ( e, space - e - 1 );
1130         }
1131
1132         char *b = strstr ( dd, " -b " );
1133         if ( b ) {
1134           b += 5;
1135           char *space = strchr ( b, '\0' );
1136           p -> appdata_dirname = strndup ( b, space - b - 1 );
1137         }
1138
1139         char *p = strstr ( dd, " -p " );
1140         if ( p ) {
1141           p += 5;
1142           char *space = strchr ( p, ' ' );
1143           strncpy ( pndpath, p, space - p - 1 );
1144         }
1145 #endif
1146
1147       } else {
1148         // probably not libpnd app
1149         p -> exec = strdup ( dd + 5 );
1150       }
1151
1152 #if 0 // ignore; using X- categories now
1153     } else if ( strncmp ( dd, "Categories=", 11 ) == 0 ) {
1154       // HACK; only honours first category
1155       char *semi = strchr ( dd, ';' );
1156       if ( semi ) {
1157         p -> main_category = strndup ( dd + 11, semi - dd + 11 );
1158       } else {
1159         p -> main_category = strdup ( dd + 11 );
1160       }
1161       semi = strchr ( p -> main_category, ';' );
1162       if ( semi ) {
1163         *semi = '\0';
1164       }
1165 #endif
1166
1167     }
1168
1169     //
1170     // /grep
1171
1172   } // while
1173
1174   fclose ( f );
1175
1176   // filter
1177   if ( ! libpnd_origin ) {
1178
1179     // convenience flag
1180     if ( flags & PND_DOTDESKTOP_LIBPND_ONLY ) {
1181       pnd_disco_destroy ( p );
1182       free ( p );
1183       return ( NULL );
1184     }
1185
1186   } else {
1187     p -> object_flags |= PND_DISCO_LIBPND_DD; // so caller can do something if it wishes
1188   }
1189
1190   // filter on content
1191   if ( ( ! p -> title_en ) ||
1192        ( ! p -> exec )
1193      )
1194   {
1195     pnd_disco_destroy ( p );
1196     free ( p );
1197     return ( NULL );
1198   }
1199
1200   if ( ! p -> unique_id ) {
1201     if ( flags & PND_DOTDESKTOP_LIBPND_ONLY ) {
1202       pnd_disco_destroy ( p );
1203       free ( p );
1204       return ( NULL );
1205     } else {
1206       char hack [ 100 ];
1207       snprintf ( hack, 100, "inode-%lu", statbuf.st_ino );
1208       p -> unique_id = strdup ( hack );
1209     }
1210   }
1211
1212   // additional
1213   p -> object_type = pnd_object_type_pnd;
1214
1215 #if 0 // nolonger needed due to above X-Pandora attributes
1216   char *source;
1217   if ( pndpath [ 0 ] ) {
1218     source = pndpath;
1219   } else {
1220     source = ddpath;
1221   }
1222
1223   char *slash = strrchr ( source, '/' );
1224   if ( slash ) {
1225     p -> object_path = strndup ( source, slash - source );
1226     p -> object_filename = strdup ( slash + 1 );
1227   } else {
1228     p -> object_path = "./";
1229     p -> object_filename = strdup ( source );
1230   }
1231 #endif
1232
1233   // lame guards, in case of lazy consumers and broken .desktop files
1234   if ( p -> object_path == NULL ) {
1235     p -> object_path = strdup ( "/tmp" );
1236   }
1237   if ( p -> object_filename == NULL ) {
1238     p -> object_filename = strdup ( "" ); // force bad filename
1239   }
1240
1241   // return disco-t
1242   return ( p );
1243 }