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