94d2e9bc94cc917e763c54cf00642533b6b3e011
[pandora-libraries.git] / minimenu / mmui.c
1
2 #include <stdio.h> /* for FILE etc */
3 #include <stdlib.h> /* for malloc */
4 #include <unistd.h> /* for unlink */
5 #include <limits.h> /* for PATH_MAX */
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #define __USE_GNU /* for strcasestr */
9 #include <string.h> /* for making ftw.h happy */
10 #include <time.h>
11 #include <ftw.h>
12 #include <ctype.h>
13
14 #include "SDL.h"
15 #include "SDL_audio.h"
16 #include "SDL_image.h"
17 #include "SDL_ttf.h"
18 #include "SDL_gfxPrimitives.h"
19 #include "SDL_rotozoom.h"
20 #include "SDL_thread.h"
21 #include <dirent.h>
22
23 #include "pnd_conf.h"
24 #include "pnd_logger.h"
25 #include "pnd_pxml.h"
26 #include "pnd_container.h"
27 #include "pnd_discovery.h"
28 #include "pnd_apps.h"
29 #include "pnd_device.h"
30 #include "../lib/pnd_pathiter.h"
31 #include "pnd_utility.h"
32 #include "pnd_pndfiles.h"
33 #include "pnd_notify.h"
34 #include "pnd_dbusnotify.h"
35
36 #include "mmenu.h"
37 #include "mmcat.h"
38 #include "mmcache.h"
39 #include "mmui.h"
40 #include "mmwrapcmd.h"
41 #include "mmconf.h"
42 #include "mmui_context.h"
43 #include "freedesktop_cats.h"
44
45 #define CHANGED_NOTHING     (0)
46 #define CHANGED_CATEGORY    (1<<0)  /* changed to different category */
47 #define CHANGED_SELECTION   (1<<1)  /* changed app selection */
48 #define CHANGED_DATAUPDATE  (1<<2)  /* deferred preview pic or icon came in */
49 #define CHANGED_APPRELOAD   (1<<3)  /* new set of applications entirely */
50 #define CHANGED_EVERYTHING  (1<<4)  /* redraw it all! */
51 unsigned int render_mask = CHANGED_EVERYTHING;
52
53 #define MIMETYPE_EXE "/usr/bin/file"     /* check for file type prior to invocation */
54
55 /* SDL
56  */
57 SDL_Surface *sdl_realscreen = NULL;
58 unsigned int sdl_ticks = 0;
59 SDL_Thread *g_preview_thread = NULL;
60 SDL_Thread *g_timer_thread = NULL;
61
62 enum { sdl_user_ticker = 0,
63        sdl_user_finishedpreview = 1,
64        sdl_user_finishedicon = 2,
65        sdl_user_checksd = 3,
66 };
67
68 /* app state
69  */
70 unsigned short int g_scale = 1; // 1 == noscale
71
72 SDL_Surface *g_imgcache [ IMG_MAX ];
73
74 TTF_Font *g_big_font = NULL;
75 TTF_Font *g_grid_font = NULL;
76 TTF_Font *g_detailtext_font = NULL;
77 TTF_Font *g_tab_font = NULL;
78
79 extern pnd_conf_handle g_conf;
80
81 /* current display state
82  */
83 int ui_rows_scrolled_down = 0;          // number of rows that should be missing from top of the display
84 mm_appref_t *ui_selected = NULL;
85 unsigned char ui_category = 0;          // current category
86 unsigned char ui_catshift = 0;          // how many cats are offscreen to the left
87 ui_context_t ui_display_context;        // display paramaters: see mmui_context.h
88 unsigned char ui_detail_hidden = 0;     // if >0, detail panel is hidden
89 // FUTURE: If multiple panels can be shown/hidden, convert ui_detail_hidden to a bitmask
90
91 static SDL_Surface *ui_scale_image ( SDL_Surface *s, unsigned int maxwidth, int maxheight ); // height -1 means ignore
92 static int ui_selected_index ( void );
93
94 unsigned char ui_setup ( void ) {
95
96   /* set up SDL
97    */
98
99   SDL_Init ( SDL_INIT_EVERYTHING | SDL_INIT_NOPARACHUTE );
100
101   SDL_JoystickOpen ( 0 ); // turn on joy-0
102
103   SDL_WM_SetCaption ( "mmenu", "mmenu" );
104
105   // hide the mouse cursor if we can
106   if ( SDL_ShowCursor ( -1 ) == 1 ) {
107     SDL_ShowCursor ( 0 );
108   }
109
110   atexit ( SDL_Quit );
111
112   // open up a surface
113   unsigned int svm = SDL_SWSURFACE /*| SDL_FULLSCREEN*/ /* 0*/;
114   if ( pnd_conf_get_as_int_d ( g_conf, "display.fullscreen", 0 ) > 0 ) {
115     svm |= SDL_FULLSCREEN;
116   }
117
118   sdl_realscreen =
119     SDL_SetVideoMode ( 800 * g_scale, 480 * g_scale, 16 /*bpp*/, svm );
120
121   if ( ! sdl_realscreen ) {
122     pnd_log ( pndn_error, "ERROR: Couldn't open SDL real screen; dieing." );
123     return ( 0 );
124   }
125
126   pnd_log ( pndn_debug, "Pixel format:" );
127   pnd_log ( pndn_debug, "bpp b: %u\n", sdl_realscreen -> format -> BitsPerPixel );
128   pnd_log ( pndn_debug, "bpp B: %u\n", sdl_realscreen -> format -> BytesPerPixel );
129   pnd_log ( pndn_debug, "R mask: %08x\n", sdl_realscreen -> format -> Rmask );
130   pnd_log ( pndn_debug, "G mask: %08x\n", sdl_realscreen -> format -> Gmask );
131   pnd_log ( pndn_debug, "B mask: %08x\n", sdl_realscreen -> format -> Bmask );
132
133 #if 0 // audio
134   {
135     SDL_AudioSpec fmt;
136
137     /* Set 16-bit stereo audio at 22Khz */
138     fmt.freq = 44100; //22050;
139     fmt.format = AUDIO_S16; //AUDIO_S16;
140     fmt.channels = 1;
141     fmt.samples = 2048;        /* A good value for games */
142     fmt.callback = mixaudio;
143     fmt.userdata = NULL;
144
145     /* Open the audio device and start playing sound! */
146     if ( SDL_OpenAudio ( &fmt, NULL ) < 0 ) {
147       zotlog ( "Unable to open audio: %s\n", SDL_GetError() );
148       exit ( 1 );
149     }
150
151     SDL_PauseAudio ( 0 );
152   }
153 #endif
154
155   // key repeat
156   SDL_EnableKeyRepeat ( 500, 150 );
157
158   // images
159   //IMG_Init ( IMG_INIT_JPG | IMG_INIT_PNG );
160
161   /* fonts
162    */
163
164   // init
165   if ( TTF_Init() == -1 ) {
166     pnd_log ( pndn_error, "ERROR: Couldn't set up SDL TTF lib\n" );
167     return ( 0 ); // couldn't set up SDL TTF
168   }
169
170   char fullpath [ PATH_MAX ];
171   // big font
172   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "minimenu.font" ) );
173   g_big_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "minimenu.font_ptsize", 24 ) );
174   if ( ! g_big_font ) {
175     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
176               pnd_conf_get_as_char ( g_conf, "minimenu.font" ), pnd_conf_get_as_int_d ( g_conf, "minimenu.font_ptsize", 24 ) );
177     return ( 0 ); // couldn't set up SDL TTF
178   }
179
180   // grid font
181   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "grid.font" ) );
182   g_grid_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "grid.font_ptsize", 10 ) );
183   if ( ! g_grid_font ) {
184     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
185               pnd_conf_get_as_char ( g_conf, "grid.font" ), pnd_conf_get_as_int_d ( g_conf, "grid.font_ptsize", 10 ) );
186     return ( 0 ); // couldn't set up SDL TTF
187   }
188
189   // detailtext font
190   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "detailtext.font" ) );
191   g_detailtext_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "detailtext.font_ptsize", 10 ) );
192   if ( ! g_detailtext_font ) {
193     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
194               pnd_conf_get_as_char ( g_conf, "detailtext.font" ), pnd_conf_get_as_int_d ( g_conf, "detailtext.font_ptsize", 10 ) );
195     return ( 0 ); // couldn't set up SDL TTF
196   }
197
198   // tab font
199   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "tabs.font" ) );
200   g_tab_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "tabs.font_ptsize", 10 ) );
201   if ( ! g_tab_font ) {
202     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
203               pnd_conf_get_as_char ( g_conf, "tabs.font" ), pnd_conf_get_as_int_d ( g_conf, "tabs.font_ptsize", 10 ) );
204     return ( 0 ); // couldn't set up SDL TTF
205   }
206
207   // determine display context
208   if ( pnd_conf_get_as_int_d ( g_conf, "display.show_detail_pane", 1 ) > 0 ) {
209     ui_detail_hidden = 0;
210   } else {
211     ui_detail_hidden = 1;
212   }
213   ui_recache_context ( &ui_display_context );
214
215   return ( 1 );
216 }
217
218 mm_imgcache_t g_imagecache [ IMG_TRUEMAX ] = {
219   { IMG_BACKGROUND_800480,    "graphics.IMG_BACKGROUND_800480" },
220   { IMG_BACKGROUND_TABMASK,   "graphics.IMG_BACKGROUND_TABMASK" },
221   { IMG_DETAIL_PANEL,         "graphics.IMG_DETAIL_PANEL" },
222   { IMG_DETAIL_BG,            "graphics.IMG_DETAIL_BG" },
223   { IMG_SELECTED_ALPHAMASK,   "graphics.IMG_SELECTED_ALPHAMASK" },
224   { IMG_TAB_SEL,              "graphics.IMG_TAB_SEL" },
225   { IMG_TAB_SEL_L,            "graphics.IMG_TAB_SEL_L" },
226   { IMG_TAB_SEL_R,            "graphics.IMG_TAB_SEL_R" },
227   { IMG_TAB_UNSEL,            "graphics.IMG_TAB_UNSEL" },
228   { IMG_TAB_UNSEL_L,          "graphics.IMG_TAB_UNSEL_L" },
229   { IMG_TAB_UNSEL_R,          "graphics.IMG_TAB_UNSEL_R" },
230   { IMG_TAB_LINE,             "graphics.IMG_TAB_LINE" },
231   { IMG_TAB_LINEL,            "graphics.IMG_TAB_LINEL" },
232   { IMG_TAB_LINER,            "graphics.IMG_TAB_LINER" },
233   { IMG_ICON_MISSING,         "graphics.IMG_ICON_MISSING" },
234   { IMG_SELECTED_HILITE,      "graphics.IMG_SELECTED_HILITE" },
235   { IMG_PREVIEW_MISSING,      "graphics.IMG_PREVIEW_MISSING" },
236   { IMG_ARROW_UP,             "graphics.IMG_ARROW_UP", },
237   { IMG_ARROW_DOWN,           "graphics.IMG_ARROW_DOWN", },
238   { IMG_ARROW_SCROLLBAR,      "graphics.IMG_ARROW_SCROLLBAR", },
239   { IMG_HOURGLASS,            "graphics.IMG_HOURGLASS", },
240   { IMG_FOLDER,               "graphics.IMG_FOLDER", },
241   { IMG_EXECBIN,              "graphics.IMG_EXECBIN", },
242   { IMG_MAX,                  NULL },
243 };
244
245 unsigned char ui_imagecache ( char *basepath ) {
246   unsigned int i;
247   char fullpath [ PATH_MAX ];
248
249   // loaded
250
251   for ( i = 0; i < IMG_MAX; i++ ) {
252
253     if ( g_imagecache [ i ].id != i ) {
254       pnd_log ( pndn_error, "ERROR: Internal table mismatch during caching [%u]\n", i );
255       exit ( -1 );
256     }
257
258     char *filename = pnd_conf_get_as_char ( g_conf, g_imagecache [ i ].confname );
259
260     if ( ! filename ) {
261       pnd_log ( pndn_error, "ERROR: Missing filename in conf for key: %s\n", g_imagecache [ i ].confname );
262       return ( 0 );
263     }
264
265     if ( filename [ 0 ] == '/' ) {
266       strncpy ( fullpath, filename, PATH_MAX );
267     } else {
268       sprintf ( fullpath, "%s/%s", basepath, filename );
269     }
270
271     if ( ! ( g_imagecache [ i ].i = IMG_Load ( fullpath ) ) ) {
272       pnd_log ( pndn_error, "ERROR: Couldn't load static cache image: %s\n", fullpath );
273       return ( 0 );
274     }
275
276   } // for
277
278   // generated
279   //g_imagecache [ IMG_SELECTED_ALPHAMASK ].i = SDL_CreateRGBSurface ( SDL_SWSURFACE, 60, 60, 32, 0xFF0000, 0x00FF00, 0xFF, 0xFF000000 );
280   //boxRGBA ( g_imagecache [ IMG_SELECTED_ALPHAMASK ].i, 0, 0, 60, 60, 100, 100, 100, 250 );
281
282   // post processing
283   //
284
285   // scale icons
286   g_imagecache [ IMG_ICON_MISSING ].i = ui_scale_image ( g_imagecache [ IMG_ICON_MISSING ].i, pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 ), -1 );
287   // scale text hilight
288   g_imagecache [ IMG_SELECTED_HILITE ].i = ui_scale_image ( g_imagecache [ IMG_SELECTED_HILITE ].i, pnd_conf_get_as_int_d ( g_conf, "grid.text_width", 50 ), -1 );
289   // scale preview no-pic
290   g_imagecache [ IMG_PREVIEW_MISSING ].i = ui_scale_image ( g_imagecache [ IMG_PREVIEW_MISSING ].i,
291                                                             pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 50 ),
292                                                             pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 50 ) );
293
294   // set alpha on detail panel
295   //SDL_SetAlpha ( g_imagecache [ IMG_DETAIL_BG ].i, SDL_SRCALPHA, pnd_conf_get_as_int_d ( g_conf, "display.detail_bg_alpha", 50 ) );
296
297   return ( 1 );
298 } // ui_imagecache
299
300 void ui_render ( void ) {
301
302   // 800x480:
303   // divide width: 550 / 250
304   // divide left side: 5 columns == 110px width
305   //   20px buffer either side == 70px wide icon + 20 + 20?
306
307   // what jobs to do during render?
308   //
309
310 #define R_BG     (1<<0)
311 #define R_TABS   (1<<1)
312 #define R_DETAIL (1<<2)
313 #define R_GRID   (1<<3)
314
315 #define R_ALL (R_BG|R_TABS|R_DETAIL|R_GRID)
316
317   unsigned int render_jobs_b = 0;
318
319   if ( render_mask & CHANGED_EVERYTHING ) {
320     render_jobs_b |= R_ALL;
321   }
322
323   if ( render_mask & CHANGED_SELECTION ) {
324     render_jobs_b |= R_GRID;
325     render_jobs_b |= R_DETAIL;
326   }
327
328   if ( render_mask & CHANGED_CATEGORY ) {
329     render_jobs_b |= R_ALL;
330   }
331
332   render_mask = CHANGED_NOTHING;
333
334   // render everything
335   //
336   unsigned int icon_rows;
337
338 #define MAXRECTS 200
339   SDL_Rect rects [ MAXRECTS ], src;
340   SDL_Rect *dest = rects;
341   bzero ( dest, sizeof(SDL_Rect)*MAXRECTS );
342
343   unsigned int row, displayrow, col;
344   mm_appref_t *appiter;
345
346   ui_context_t *c = &ui_display_context; // for convenience and shorthand
347
348   // how many total rows do we need?
349   if ( g_categorycount ) {
350     icon_rows = g_categories [ ui_category ] -> refcount / c -> col_max;
351     if ( g_categories [ ui_category ] -> refcount % c -> col_max > 0 ) {
352       icon_rows++;
353     }
354   } else {
355     icon_rows = 0;
356   }
357
358 #if 1
359   // if no selected app yet, select the first one
360   if ( ! ui_selected && pnd_conf_get_as_int_d ( g_conf, "minimenu.start_selected", 0 ) ) {
361     ui_selected = g_categories [ ui_category ] -> refs;
362   }
363 #endif
364
365   // reset touchscreen regions
366   if ( render_jobs_b ) {
367     ui_register_reset();
368   }
369
370   // ensure selection is visible
371   if ( ui_selected ) {
372
373     int index = ui_selected_index();
374     int topleft = c -> col_max * ui_rows_scrolled_down;
375     int botright = ( c -> col_max * ( ui_rows_scrolled_down + c -> row_max ) - 1 );
376
377     if ( index < topleft ) {
378       ui_rows_scrolled_down -= pnd_conf_get_as_int_d ( g_conf, "grid.scroll_increment", 1 );
379       render_jobs_b |= R_ALL;
380     } else if ( index > botright ) {
381       ui_rows_scrolled_down += pnd_conf_get_as_int_d ( g_conf, "grid.scroll_increment", 1 );
382       render_jobs_b |= R_ALL;
383     }
384
385     if ( ui_rows_scrolled_down < 0 ) {
386       ui_rows_scrolled_down = 0;
387     } else if ( ui_rows_scrolled_down > icon_rows ) {
388       ui_rows_scrolled_down = icon_rows;
389     }
390
391   } // ensire visible
392
393   // render background
394   if ( render_jobs_b & R_BG ) {
395
396     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
397       dest -> x = 0;
398       dest -> y = 0;
399       dest -> w = sdl_realscreen -> w;
400       dest -> h = sdl_realscreen -> h;
401       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, dest /* 0,0 */ );
402       dest++;
403     }
404
405     // tabmask
406     if ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i ) {
407       dest -> x = 0;
408       dest -> y = 0;
409       dest -> w = sdl_realscreen -> w;
410       dest -> h = sdl_realscreen -> h;
411       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i, NULL /* whole image */, sdl_realscreen, dest /* 0,0 */ );
412       dest++;
413     }
414
415   } // r_bg
416
417   // tabs
418   if ( g_imagecache [ IMG_TAB_SEL ].i && g_imagecache [ IMG_TAB_UNSEL ].i ) {
419     static unsigned int tab_width;
420     static unsigned int tab_height;
421     static unsigned int tab_offset_x;
422     static unsigned int tab_offset_y;
423     static unsigned int text_offset_x;
424     static unsigned int text_offset_y;
425     static unsigned int text_width;
426
427     static unsigned char tab_first_run = 1;
428     if ( tab_first_run ) {
429       tab_first_run = 0;
430       tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
431       tab_height = pnd_conf_get_as_int ( g_conf, "tabs.tab_height" );
432       tab_offset_x = pnd_conf_get_as_int ( g_conf, "tabs.tab_offset_x" );
433       tab_offset_y = pnd_conf_get_as_int ( g_conf, "tabs.tab_offset_y" );
434       text_offset_x = pnd_conf_get_as_int ( g_conf, "tabs.text_offset_x" );
435       text_offset_y = pnd_conf_get_as_int ( g_conf, "tabs.text_offset_y" );
436       text_width = pnd_conf_get_as_int ( g_conf, "tabs.text_width" );
437     }
438
439     unsigned int maxtab = ( c -> screen_width / tab_width ) < g_categorycount ? ( c -> screen_width / tab_width ) + ui_catshift : g_categorycount + ui_catshift;
440     unsigned int maxtabspot = ( c -> screen_width / tab_width );
441
442     if ( g_categorycount > 0 ) {
443
444       // draw tabs with categories
445       for ( col = ui_catshift;
446             col < maxtab;
447             col++ )
448       {
449
450         SDL_Surface *s;
451
452         // if this is the first (leftmost) tab, we use different artwork
453         // than if the other tabs (so skinner can link lines up nicely.)
454         if ( col == ui_catshift ) {
455           // leftmost tab, special case
456
457           if ( col == ui_category ) {
458             s = g_imagecache [ IMG_TAB_SEL_L ].i;
459           } else {
460             s = g_imagecache [ IMG_TAB_UNSEL_L ].i;
461           }
462
463         } else if ( col == maxtab - 1 ) {
464           // rightmost tab, special case
465
466           if ( col == ui_category ) {
467             s = g_imagecache [ IMG_TAB_SEL_R ].i;
468           } else {
469             s = g_imagecache [ IMG_TAB_UNSEL_R ].i;
470           }
471
472         } else {
473           // normal (not leftmost) tab
474
475           if ( col == ui_category ) {
476             s = g_imagecache [ IMG_TAB_SEL ].i;
477           } else {
478             s = g_imagecache [ IMG_TAB_UNSEL ].i;
479           }
480
481         } // first col, or not first col?
482
483         // draw tab
484         src.x = 0;
485         src.y = 0;
486 #if 0
487         src.w = tab_width;
488         if ( col == ui_category ) {
489           src.h = tab_selheight;
490         } else {
491           src.h = tab_height;
492         }
493 #else
494         src.w = s -> w;
495         src.h = s -> h;
496 #endif
497         dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width );
498         dest -> y = tab_offset_y;
499
500         // store touch info
501         ui_register_tab ( col, dest -> x, dest -> y, tab_width, tab_height );
502
503         if ( render_jobs_b & R_TABS ) {
504           //pnd_log ( pndn_debug, "tab %u at %ux%u\n", col, dest.x, dest.y );
505           SDL_BlitSurface ( s, &src, sdl_realscreen, dest );
506           dest++;
507
508           // draw tab line
509           if ( col == ui_category ) {
510             // no line for selected tab
511           } else {
512             if ( col - ui_catshift == 0 ) {
513               s = g_imagecache [ IMG_TAB_LINEL ].i;
514             } else if ( col - ui_catshift == maxtabspot - 1 ) {
515               s = g_imagecache [ IMG_TAB_LINER ].i;
516             } else {
517               s = g_imagecache [ IMG_TAB_LINE ].i;
518             }
519             dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width );
520             dest -> y = tab_offset_y + tab_height;
521             SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, dest );
522             dest++;
523           }
524
525           // draw text
526           SDL_Surface *rtext;
527           rtext = TTF_RenderText_Blended ( g_tab_font, g_categories [ col ] -> catname, c -> fontcolor );
528           src.x = 0;
529           src.y = 0;
530           src.w = rtext -> w < text_width ? rtext -> w : text_width;
531           src.h = rtext -> h;
532           dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width ) + text_offset_x;
533           dest -> y = tab_offset_y + text_offset_y;
534           SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
535           SDL_FreeSurface ( rtext );
536           dest++;
537
538         } // r_tabs
539
540       } // for
541
542     } // if we got categories
543
544     if ( render_jobs_b & R_TABS ) {
545
546       // draw tab lines under where tabs would be if we had categories
547       for ( /* foo */; col < maxtabspot; col++ ) {
548         SDL_Surface *s;
549
550         if ( col - ui_catshift == 0 ) {
551           s = g_imagecache [ IMG_TAB_LINEL ].i;
552         } else if ( col - ui_catshift == maxtabspot - 1 ) {
553           s = g_imagecache [ IMG_TAB_LINER ].i;
554         } else {
555           s = g_imagecache [ IMG_TAB_LINE ].i;
556         }
557         dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width );
558         dest -> y = tab_offset_y + tab_height;
559         SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, dest );
560         dest++;
561
562       } // for
563
564     } // r_tabs
565
566   } // tabs
567
568   // scroll bars and arrows
569   if ( render_jobs_b & R_BG ) {
570     unsigned char show_bar = 0;
571
572     // up?
573     if ( ui_rows_scrolled_down && g_imagecache [ IMG_ARROW_UP ].i ) {
574       dest -> x = c -> arrow_up_x;
575       dest -> y = c -> arrow_up_y;
576       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_UP ].i, NULL /* whole image */, sdl_realscreen, dest );
577       dest++;
578
579       show_bar = 1;
580     }
581
582     // down?
583     if ( ui_rows_scrolled_down + c -> row_max < icon_rows && g_imagecache [ IMG_ARROW_DOWN ].i ) {
584       dest -> x = c -> arrow_down_x;
585       dest -> y = c -> arrow_down_y;
586       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_DOWN ].i, NULL /* whole image */, sdl_realscreen, dest );
587       dest++;
588
589       show_bar = 1;
590     }
591
592     if ( show_bar ) {
593       // show scrollbar as well
594       src.x = 0;
595       src.y = 0;
596       src.w = c -> arrow_bar_clip_w;
597       src.h = c -> arrow_bar_clip_h;
598       dest -> x = c -> arrow_bar_x;
599       dest -> y = c -> arrow_bar_y;
600       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_SCROLLBAR ].i, &src /* whole image */, sdl_realscreen, dest );
601       dest++;
602     } // bar
603
604   } // r_bg, scroll bars
605
606   // render detail pane bg
607   if ( render_jobs_b & R_DETAIL && ui_detail_hidden == 0 ) {
608
609     if ( pnd_conf_get_as_int_d ( g_conf, "detailpane.show", 1 ) ) {
610
611       // render detail bg
612       if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
613         src.x = 0; // pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
614         src.y = 0; // pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
615         src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> w;
616         src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> h;
617         dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
618         dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
619         SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
620         dest++;
621       }
622
623       // render detail pane
624       if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
625         dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
626         dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
627         SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
628         dest++;
629       }
630
631     } // detailpane frame/bg
632
633   } // r_details
634
635   // anything to render?
636   if ( render_jobs_b & R_GRID && g_categorycount ) {
637
638     // if just rendering grid, and nothing else, better clear it first
639     if ( ! ( render_jobs_b & R_BG ) ) {
640       if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
641         src.x = c -> grid_offset_x;
642         src.y = c -> grid_offset_y + c -> sel_icon_offset_y;
643         src.w = c -> col_max * c -> cell_width;
644         src.h = c -> row_max * c -> cell_height;
645
646         dest -> x = c -> grid_offset_x;
647         dest -> y = c -> grid_offset_y + c -> sel_icon_offset_y;
648
649         SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, &src, sdl_realscreen, dest );
650         dest++;
651
652       }
653     }
654
655     if ( g_categories [ ui_category ] -> refs ) {
656
657       appiter = g_categories [ ui_category ] -> refs;
658       row = 0;
659       displayrow = 0;
660
661       // until we run out of apps, or run out of space
662       while ( appiter != NULL ) {
663
664         for ( col = 0; col < c -> col_max && appiter != NULL; col++ ) {
665
666           // do we even need to render it? or are we suppressing it due to rows scrolled off the top?
667           if ( row >= ui_rows_scrolled_down ) {
668
669             // selected? show hilights
670             if ( appiter == ui_selected ) {
671               SDL_Surface *s = g_imagecache [ IMG_SELECTED_ALPHAMASK ].i;
672               // icon
673               //dest -> x = grid_offset_x + ( col * cell_width ) + icon_offset_x + ( ( icon_max_width - s -> w ) / 2 );
674               dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> icon_offset_x + c -> sel_icon_offset_x;
675               //dest -> y = grid_offset_y + ( displayrow * cell_height ) + icon_offset_y + ( ( icon_max_height - s -> h ) / 2 );
676               dest -> y = c -> grid_offset_y + ( displayrow * c -> cell_height ) + c -> icon_offset_y + c -> sel_icon_offset_y;
677               SDL_BlitSurface ( s, NULL /* all */, sdl_realscreen, dest );
678               dest++;
679               // text
680               dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> text_clip_x;
681               dest -> y = c -> grid_offset_y + ( displayrow * c -> cell_height ) + c -> text_hilite_offset_y;
682               SDL_BlitSurface ( g_imagecache [ IMG_SELECTED_HILITE ].i, NULL /* all */, sdl_realscreen, dest );
683               dest++;
684             } // selected?
685
686             // show icon
687             mm_cache_t *ic = cache_query_icon ( appiter -> ref -> unique_id );
688             SDL_Surface *iconsurface;
689             if ( ic ) {
690               iconsurface = ic -> i;
691             } else {
692               //pnd_log ( pndn_warning, "WARNING: TBD: Need Missin-icon icon for '%s'\n", IFNULL(appiter -> ref -> title_en,"No Name") );
693
694               // no icon override; was this a pnd-file (show the unknown icon then), or was this generated from
695               // filesystem (file or directory icon)
696               if ( appiter -> ref -> object_flags & PND_DISCO_GENERATED ) {
697                 if ( appiter -> ref -> object_type == pnd_object_type_directory ) {
698                   iconsurface = g_imagecache [ IMG_FOLDER ].i;
699                 } else {
700                   iconsurface = g_imagecache [ IMG_EXECBIN ].i;
701                 }
702               } else {
703                 iconsurface = g_imagecache [ IMG_ICON_MISSING ].i;
704               }
705
706             }
707
708             // got an icon I hope?
709             if ( iconsurface ) {
710               //pnd_log ( pndn_debug, "Got an icon for '%s'\n", IFNULL(appiter -> ref -> title_en,"No Name") );
711
712               src.x = 0;
713               src.y = 0;
714               src.w = 60;
715               src.h = 60;
716               dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> icon_offset_x + (( c -> icon_max_width - iconsurface -> w ) / 2);
717               dest -> y = c -> grid_offset_y + ( displayrow * c -> cell_height ) + c -> icon_offset_y + (( c -> icon_max_height - iconsurface -> h ) / 2);
718
719               SDL_BlitSurface ( iconsurface, &src, sdl_realscreen, dest );
720
721               // store touch info
722               ui_register_app ( appiter, dest -> x, dest -> y, src.w, src.h );
723
724               dest++;
725
726             }
727
728             // show text
729             if ( appiter -> ref -> title_en ) {
730               SDL_Surface *rtext;
731               rtext = TTF_RenderText_Blended ( g_grid_font, appiter -> ref -> title_en, c -> fontcolor );
732               src.x = 0;
733               src.y = 0;
734               src.w = c -> text_width < rtext -> w ? c -> text_width : rtext -> w;
735               src.h = rtext -> h;
736               if ( rtext -> w > c -> text_width ) {
737                 dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> text_clip_x;
738               } else {
739                 dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> text_offset_x - ( rtext -> w / 2 );
740               }
741               dest -> y = c -> grid_offset_y + ( displayrow * c -> cell_height ) + c -> text_offset_y;
742               SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
743               SDL_FreeSurface ( rtext );
744               dest++;
745             }
746
747           } // display now? or scrolled away..
748
749           // next
750           appiter = appiter -> next;
751
752         } // for column 1...X
753
754         if ( row >= ui_rows_scrolled_down ) {
755           displayrow++;
756         }
757
758         row ++;
759
760         // are we done displaying rows?
761         if ( displayrow >= c -> row_max ) {
762           break;
763         }
764
765       } // while
766
767     } else {
768       // no apps to render?
769       pnd_log ( pndn_rem, "No applications to render?\n" );
770     } // apps to renser?
771
772   } // r_grid
773
774   // detail panel - show app details or blank-message
775   if ( render_jobs_b & R_DETAIL && ui_detail_hidden == 0 ) {
776
777     unsigned int cell_offset_x = pnd_conf_get_as_int ( g_conf, "detailtext.cell_offset_x" );
778     unsigned int cell_offset_y = pnd_conf_get_as_int ( g_conf, "detailtext.cell_offset_y" );
779     unsigned int cell_width = pnd_conf_get_as_int ( g_conf, "detailtext.cell_width" );
780
781     unsigned int desty = cell_offset_y;
782
783     if ( ui_selected ) {
784
785       char buffer [ 256 ];
786
787       // full name
788       if ( ui_selected -> ref -> title_en ) {
789         SDL_Surface *rtext;
790         rtext = TTF_RenderText_Blended ( g_detailtext_font, ui_selected -> ref -> title_en, c -> fontcolor );
791         src.x = 0;
792         src.y = 0;
793         src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
794         src.h = rtext -> h;
795         dest -> x = cell_offset_x;
796         dest -> y = desty;
797         SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
798         SDL_FreeSurface ( rtext );
799         dest++;
800         desty += src.h;
801       }
802
803       // category
804 #if 0
805       if ( ui_selected -> ref -> main_category ) {
806
807         sprintf ( buffer, "Category: %s", ui_selected -> ref -> main_category );
808
809         SDL_Surface *rtext;
810         rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, c -> fontcolor );
811         src.x = 0;
812         src.y = 0;
813         src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
814         src.h = rtext -> h;
815         dest -> x = cell_offset_x;
816         dest -> y = desty;
817         SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
818         SDL_FreeSurface ( rtext );
819         dest++;
820         desty += src.h;
821       }
822 #endif
823
824       // clock
825       if ( ui_selected -> ref -> clockspeed ) {
826
827         sprintf ( buffer, "CPU Clock: %s", ui_selected -> ref -> clockspeed );
828
829         SDL_Surface *rtext;
830         rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, c -> fontcolor );
831         src.x = 0;
832         src.y = 0;
833         src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
834         src.h = rtext -> h;
835         dest -> x = cell_offset_x;
836         dest -> y = desty;
837         SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
838         SDL_FreeSurface ( rtext );
839         dest++;
840         desty += src.h;
841       }
842
843       // show sub-app# on right side of cpu clock?
844       //if ( ui_selected -> ref -> subapp_number )
845       {
846         sprintf ( buffer, "(app#%u)", ui_selected -> ref -> subapp_number );
847
848         SDL_Surface *rtext;
849         rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
850         dest -> x = cell_offset_x + cell_width - rtext -> w;
851         dest -> y = desty - src.h;
852         SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
853         SDL_FreeSurface ( rtext );
854         dest++;
855       }
856
857       // info hint
858 #if 0 // merged into hint-line
859       if ( ui_selected -> ref -> info_filename ) {
860
861         sprintf ( buffer, "Documentation - hit Y" );
862
863         SDL_Surface *rtext;
864         rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, c -> fontcolor );
865         src.x = 0;
866         src.y = 0;
867         src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
868         src.h = rtext -> h;
869         dest -> x = cell_offset_x;
870         dest -> y = desty;
871         SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
872         SDL_FreeSurface ( rtext );
873         dest++;
874         desty += src.h;
875       }
876 #endif
877
878       // notes
879       if ( ui_selected -> ovrh ) {
880         char *n;
881         unsigned char i;
882         char buffer [ 50 ];
883
884         desty += 5; // a touch of spacing can't hurt
885
886         for ( i = 1; i < 4; i++ ) {
887           sprintf ( buffer, "Application-%u.note-%u", ui_selected -> ref -> subapp_number, i );
888           n = pnd_conf_get_as_char ( ui_selected -> ovrh, buffer );
889
890           if ( n ) {
891             SDL_Surface *rtext;
892             rtext = TTF_RenderText_Blended ( g_detailtext_font, n, c -> fontcolor );
893             src.x = 0;
894             src.y = 0;
895             src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
896             src.h = rtext -> h;
897             dest -> x = cell_offset_x;
898             dest -> y = desty;
899             SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
900             SDL_FreeSurface ( rtext );
901             dest++;
902             desty += rtext -> h;
903           }
904         } // for
905
906       } // r_detail -> notes
907
908       // preview pic
909       mm_cache_t *ic = cache_query_preview ( ui_selected -> ref -> unique_id );
910       SDL_Surface *previewpic;
911
912       if ( ic ) {
913         previewpic = ic -> i;
914       } else {
915         previewpic = g_imagecache [ IMG_PREVIEW_MISSING ].i;
916       }
917
918       if ( previewpic ) {
919         dest -> x = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_offset_x", 50 ) +
920           ( ( pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 50 ) - previewpic -> w ) / 2 );
921         dest -> y = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_offset_y", 50 );
922         SDL_BlitSurface ( previewpic, NULL /* whole image */, sdl_realscreen, dest );
923         dest++;
924       }
925
926     } else {
927
928       char *empty_message = "Press SELECT for menu";
929
930       SDL_Surface *rtext;
931
932       rtext = TTF_RenderText_Blended ( g_detailtext_font, empty_message, c -> fontcolor );
933
934       src.x = 0;
935       src.y = 0;
936       src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
937       src.h = rtext -> h;
938       dest -> x = cell_offset_x;
939       dest -> y = desty;
940       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
941       SDL_FreeSurface ( rtext );
942       dest++;
943
944       desty += src.h;
945
946       rtext = TTF_RenderText_Blended ( g_detailtext_font, "START or B to run app", c -> fontcolor );
947       dest -> x = cell_offset_x;
948       dest -> y = desty;
949       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
950       SDL_FreeSurface ( rtext );
951       dest++;
952       desty += src.h;
953
954       rtext = TTF_RenderText_Blended ( g_detailtext_font, "SPACE for app menu", c -> fontcolor );
955       dest -> x = cell_offset_x;
956       dest -> y = desty;
957       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
958       SDL_FreeSurface ( rtext );
959       dest++;
960       desty += src.h;
961
962       rtext = TTF_RenderText_Blended ( g_detailtext_font, "A to toggle details", c -> fontcolor );
963       dest -> x = cell_offset_x;
964       dest -> y = desty;
965       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
966       SDL_FreeSurface ( rtext );
967       dest++;
968       desty += src.h;
969
970     } // r_detail && selected?
971
972   } // r_detail
973
974   // extras
975   //
976
977   // battery
978   if ( render_jobs_b & R_BG ) {
979     static int last_battlevel = 0;
980     static unsigned char batterylevel = 0;
981     char buffer [ 100 ];
982
983     if ( time ( NULL ) - last_battlevel > 60 ) {
984       batterylevel = pnd_device_get_battery_gauge_perc();
985       last_battlevel = time ( NULL );
986     }
987
988     sprintf ( buffer, "Battery: %u%%", batterylevel );
989
990     SDL_Surface *rtext;
991     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
992     dest -> x = pnd_conf_get_as_int_d ( g_conf, "display.battery_x", 20 );
993     dest -> y = pnd_conf_get_as_int_d ( g_conf, "display.battery_y", 450 );
994     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
995     SDL_FreeSurface ( rtext );
996     dest++;
997   }
998
999   // hints
1000   if ( pnd_conf_get_as_char ( g_conf, "display.hintline" ) ) {
1001     char *buffer;
1002     unsigned int hintx, hinty;
1003     hintx = pnd_conf_get_as_int_d ( g_conf, "display.hint_x", 40 );
1004     hinty = pnd_conf_get_as_int_d ( g_conf, "display.hint_y", 450 );
1005     static unsigned int lastwidth = 3000;
1006
1007     if ( ui_selected && ui_selected -> ref -> info_filename ) {
1008       buffer = "Documentation - hit Y";
1009     } else {
1010       buffer = pnd_conf_get_as_char ( g_conf, "display.hintline" );
1011     }
1012
1013     SDL_Surface *rtext;
1014     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
1015
1016     // clear bg
1017     if ( ! ( render_jobs_b & R_BG ) ) {
1018       src.x = hintx;
1019       src.y = hinty;
1020       src.w = lastwidth;
1021       src.h = rtext -> h;
1022       dest -> x = hintx;
1023       dest -> y = hinty;
1024       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i, &src, sdl_realscreen, dest );
1025       dest++;
1026       lastwidth = rtext -> w;
1027     }
1028
1029     // now render text
1030     dest -> x = hintx;
1031     dest -> y = hinty;
1032     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
1033     SDL_FreeSurface ( rtext );
1034     dest++;
1035   }
1036
1037   // clock time
1038   if ( render_jobs_b & R_BG &&
1039        pnd_conf_get_as_int_d ( g_conf, "display.clock_x", -1 ) != -1 )
1040   {
1041     char buffer [ 50 ];
1042
1043     time_t t = time ( NULL );
1044     struct tm *tm = localtime ( &t );
1045     strftime ( buffer, 50, "%a %H:%M %F", tm );
1046
1047     SDL_Surface *rtext;
1048     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
1049     dest -> x = pnd_conf_get_as_int_d ( g_conf, "display.clock_x", 700 );
1050     dest -> y = pnd_conf_get_as_int_d ( g_conf, "display.clock_y", 450 );
1051     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
1052     SDL_FreeSurface ( rtext );
1053     dest++;
1054   }
1055
1056   // update all the rects and send it all to sdl
1057   // - at this point, we could probably just do 1 rect, of the
1058   //   whole screen, and be faster :/
1059   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
1060
1061 } // ui_render
1062
1063 void ui_process_input ( pnd_dbusnotify_handle dbh, pnd_notify_handle nh ) {
1064   SDL_Event event;
1065
1066   unsigned char ui_event = 0; // if we get a ui event, flip to 1 and break
1067   //static ui_sdl_button_e ui_mask = uisb_none; // current buttons down
1068
1069   while ( ( ! ui_event ) &&
1070           /*block_p ?*/ SDL_WaitEvent ( &event ) /*: SDL_PollEvent ( &event )*/ )
1071   {
1072
1073     switch ( event.type ) {
1074
1075     case SDL_USEREVENT:
1076       // update something
1077
1078       // the user-defined SDL events are all for threaded/delayed previews (and icons, which
1079       // generally are not used); if we're in wide mode, we can skip previews
1080       // to avoid slowing things down when they're not shown.
1081
1082       if ( event.user.code == sdl_user_ticker ) {
1083
1084         if ( ui_detail_hidden ) {
1085           break; // skip building previews when not showing them
1086         }
1087
1088         // timer went off, time to load something
1089         if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_previews_later", 0 ) ) {
1090
1091           if ( ! ui_selected ) {
1092             break;
1093           }
1094
1095           // load the preview pics now!
1096           pnd_disco_t *iter = ui_selected -> ref;
1097
1098           if ( iter -> preview_pic1 ) {
1099
1100             if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.threaded_preview", 0 ) ) {
1101               // load in bg thread, make user experience chuggy
1102
1103               g_preview_thread = SDL_CreateThread ( (void*)ui_threaded_defered_preview, iter );
1104
1105               if ( ! g_preview_thread ) {
1106                 pnd_log ( pndn_error, "ERROR: Couldn't create preview thread\n" );
1107               }
1108
1109             } else {
1110               // load it now, make user wait
1111
1112               if ( ! cache_preview ( iter, pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 ),
1113                                      pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 ) )
1114                  )
1115               {
1116                 pnd_log ( pndn_debug, "  Couldn't load preview pic: '%s' -> '%s'\n",
1117                           IFNULL(iter->title_en,"No Name"), iter -> preview_pic1 );
1118               }
1119
1120             } // threaded?
1121
1122           } // got a preview at all?
1123
1124           ui_event++;
1125         }
1126
1127       } else if ( event.user.code == sdl_user_finishedpreview ) {
1128
1129         // if we just finished the one we happen to be looking at, better redraw now; otherwise, if
1130         // we finished another, no big woop
1131         if ( ui_selected && event.user.data1 == ui_selected -> ref ) {
1132           ui_event++;
1133         }
1134
1135       } else if ( event.user.code == sdl_user_finishedicon ) {
1136         // redraw, so we can show the newly loaded icon
1137         ui_event++;
1138
1139       } else if ( event.user.code == sdl_user_checksd ) {
1140         // check if any inotify-type events occured, forcing us to rescan/re-disco the SDs
1141
1142         unsigned char watch_dbus = 0;
1143         unsigned char watch_inotify = 0;
1144
1145         if ( dbh ) {
1146           watch_dbus = pnd_dbusnotify_rediscover_p ( dbh );
1147         }
1148
1149         if ( nh ) {
1150           watch_inotify = pnd_notify_rediscover_p ( nh );
1151         }
1152
1153         if ( watch_dbus || watch_inotify ) {
1154           pnd_log ( pndn_debug, "dbusnotify detected SD event\n" );
1155           applications_free();
1156           applications_scan();
1157
1158           ui_event++;
1159         }
1160
1161       } // SDL user event
1162
1163       render_mask |= CHANGED_EVERYTHING;
1164
1165       break;
1166
1167 #if 0 // joystick motion
1168     case SDL_JOYAXISMOTION:
1169
1170       pnd_log ( PND_LOG_DEFAULT, "joystick axis\n" );
1171
1172         if ( event.jaxis.axis == 0 ) {
1173           // horiz
1174           if ( event.jaxis.value < 0 ) {
1175             ui_push_left ( 0 );
1176             pnd_log ( PND_LOG_DEFAULT, "joystick axis - LEFT\n" );
1177           } else if ( event.jaxis.value > 0 ) {
1178             ui_push_right ( 0 );
1179             pnd_log ( PND_LOG_DEFAULT, "joystick axis - RIGHT\n" );
1180           }
1181         } else if ( event.jaxis.axis == 1 ) {
1182           // vert
1183           if ( event.jaxis.value < 0 ) {
1184             ui_push_up();
1185           } else if ( event.jaxis.value > 0 ) {
1186             ui_push_down();
1187           }
1188         }
1189
1190         ui_event++;
1191
1192         break;
1193 #endif
1194
1195 #if 0 // joystick buttons
1196     case SDL_JOYBUTTONDOWN:
1197
1198       pnd_log ( PND_LOG_DEFAULT, "joystick button down %u\n", event.jbutton.button );
1199
1200       if ( event.jbutton.button == 0 ) { // B
1201         ui_mask |= uisb_b;
1202       } else if ( event.jbutton.button == 1 ) { // Y
1203         ui_mask |= uisb_y;
1204       } else if ( event.jbutton.button == 2 ) { // X
1205         ui_mask |= uisb_x;
1206       } else if ( event.jbutton.button == 3 ) { // A
1207         ui_mask |= uisb_a;
1208
1209       } else if ( event.jbutton.button == 4 ) { // Select
1210         ui_mask |= uisb_select;
1211       } else if ( event.jbutton.button == 5 ) { // Start
1212         ui_mask |= uisb_start;
1213
1214       } else if ( event.jbutton.button == 7 ) { // L
1215         ui_mask |= uisb_l;
1216       } else if ( event.jbutton.button == 8 ) { // R
1217         ui_mask |= uisb_r;
1218
1219       }
1220
1221       ui_event++;
1222
1223       break;
1224
1225     case SDL_JOYBUTTONUP:
1226
1227       pnd_log ( PND_LOG_DEFAULT, "joystick button up %u\n", event.jbutton.button );
1228
1229       if ( event.jbutton.button == 0 ) { // B
1230         ui_mask &= ~uisb_b;
1231         ui_push_exec();
1232       } else if ( event.jbutton.button == 1 ) { // Y
1233         ui_mask &= ~uisb_y;
1234       } else if ( event.jbutton.button == 2 ) { // X
1235         ui_mask &= ~uisb_x;
1236       } else if ( event.jbutton.button == 3 ) { // A
1237         ui_mask &= ~uisb_a;
1238
1239       } else if ( event.jbutton.button == 4 ) { // Select
1240         ui_mask &= ~uisb_select;
1241       } else if ( event.jbutton.button == 5 ) { // Start
1242         ui_mask &= ~uisb_start;
1243
1244       } else if ( event.jbutton.button == 7 ) { // L
1245         ui_mask &= ~uisb_l;
1246         ui_push_ltrigger();
1247       } else if ( event.jbutton.button == 8 ) { // R
1248         ui_mask &= ~uisb_r;
1249         ui_push_rtrigger();
1250
1251       }
1252
1253       ui_event++;
1254
1255       break;
1256 #endif
1257
1258 #if 1 // keyboard events
1259     //case SDL_KEYUP:
1260     case SDL_KEYDOWN:
1261
1262       //pnd_log ( pndn_debug, "key up %u\n", event.key.keysym.sym );
1263
1264       // SDLK_LALT -> Start
1265       // page up/down for y/x
1266       // home/end for a and b
1267
1268       // directional
1269       if ( event.key.keysym.sym == SDLK_RIGHT ) {
1270         ui_push_right ( 0 );
1271         ui_event++;
1272       } else if ( event.key.keysym.sym == SDLK_LEFT ) {
1273         ui_push_left ( 0 );
1274         ui_event++;
1275       } else if ( event.key.keysym.sym == SDLK_UP ) {
1276         ui_push_up();
1277         ui_event++;
1278       } else if ( event.key.keysym.sym == SDLK_DOWN ) {
1279         ui_push_down();
1280         ui_event++;
1281       } else if ( event.key.keysym.sym == SDLK_END ) { // B
1282         ui_push_exec();
1283         ui_event++;
1284       } else if ( event.key.keysym.sym == SDLK_SPACE ) { // space
1285         if ( ui_selected ) {
1286           ui_menu_context ( ui_selected );
1287         } else {
1288           // TBD: post an error?
1289         }
1290         render_mask |= CHANGED_EVERYTHING;
1291         ui_event++;
1292       } else if ( event.key.keysym.sym == SDLK_TAB || event.key.keysym.sym == SDLK_HOME ) { // tab or A
1293         // if detail panel is togglable, then toggle it
1294         // if not, make sure its ruddy well shown!
1295         if ( ui_is_detail_hideable() ) {
1296           ui_toggle_detail_pane();
1297         } else {
1298           ui_detail_hidden = 0;
1299         }
1300         ui_event++;
1301       } else if ( event.key.keysym.sym == SDLK_RSHIFT || event.key.keysym.sym == SDLK_COMMA ) { // left trigger or comma
1302         ui_push_ltrigger();
1303         ui_event++;
1304       } else if ( event.key.keysym.sym == SDLK_RCTRL || event.key.keysym.sym == SDLK_PERIOD ) { // right trigger or period
1305         ui_push_rtrigger();
1306         ui_event++;
1307
1308       } else if ( event.key.keysym.sym == SDLK_PAGEUP ) { // Y
1309         // info
1310         if ( ui_selected ) {
1311           ui_show_info ( pnd_run_script, ui_selected -> ref );
1312           ui_event++;
1313         }
1314       } else if ( event.key.keysym.sym == SDLK_PAGEDOWN ) { // X
1315         ui_push_backup();
1316
1317         // forget the selection, nolonger applies
1318         ui_selected = NULL;
1319         ui_set_selected ( ui_selected );
1320         // rescan the dir
1321         if ( g_categories [ ui_category ] -> fspath ) {
1322           category_fs_restock ( g_categories [ ui_category ] );
1323         }
1324         // redraw the grid
1325         render_mask |= CHANGED_EVERYTHING;
1326         ui_event++;
1327
1328       } else if ( event.key.keysym.sym == SDLK_LALT ) { // start button
1329         ui_push_exec();
1330         ui_event++;
1331
1332       } else if ( event.key.keysym.sym == SDLK_LCTRL /*LALT*/ ) { // select button
1333         char *opts [ 20 ] = {
1334           "Reveal hidden category",
1335           "Shutdown Pandora",
1336           "Configure Minimenu",
1337           "Rescan for applications",
1338           "Cache previews to SD now",
1339           "Run a terminal/console",
1340           "Run another GUI (xfce, etc)",
1341           "Quit (<- beware)",
1342           "Select a Minimenu skin",
1343           "About Minimenu"
1344         };
1345         int sel = ui_modal_single_menu ( opts, 10, "Minimenu", "Enter to select; other to return." );
1346
1347         char buffer [ 100 ];
1348         if ( sel == 0 ) {
1349           // do nothing
1350           ui_revealscreen();
1351         } else if ( sel == 1 ) {
1352           // shutdown
1353           sprintf ( buffer, "sudo poweroff" );
1354           system ( buffer );
1355         } else if ( sel == 2 ) {
1356           // configure mm
1357           unsigned char restart = conf_run_menu ( NULL );
1358           conf_write ( g_conf, conf_determine_location ( g_conf ) );
1359           if ( restart ) {
1360             emit_and_quit ( MM_RESTART );
1361           }
1362         } else if ( sel == 3 ) {
1363           // rescan apps
1364           pnd_log ( pndn_debug, "Freeing up applications\n" );
1365           applications_free();
1366           pnd_log ( pndn_debug, "Rescanning applications\n" );
1367           applications_scan();
1368         } else if ( sel == 4 ) {
1369           // cache preview to SD now
1370           extern pnd_box_handle g_active_apps;
1371           pnd_box_handle h = g_active_apps;
1372
1373           unsigned int maxwidth, maxheight;
1374           maxwidth = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 );
1375           maxheight = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 );
1376
1377           pnd_disco_t *iter = pnd_box_get_head ( h );
1378
1379           while ( iter ) {
1380
1381             // cache it
1382             if ( ! cache_preview ( iter, maxwidth, maxheight ) ) {
1383               pnd_log ( pndn_debug, "Force cache: Couldn't load preview pic: '%s' -> '%s'\n",
1384                         IFNULL(iter->title_en,"No Name"), iter -> preview_pic1 );
1385             }
1386
1387             // next
1388             iter = pnd_box_get_next ( iter );
1389           } // while
1390
1391         } else if ( sel == 5 ) {
1392           // run terminal
1393           char *argv[5];
1394           argv [ 0 ] = pnd_conf_get_as_char ( g_conf, "utility.terminal" );
1395           argv [ 1 ] = NULL;
1396
1397           if ( argv [ 0 ] ) {
1398             ui_forkexec ( argv );
1399           }
1400
1401         } else if ( sel == 6 ) {
1402           char buffer [ PATH_MAX ];
1403           sprintf ( buffer, "%s %s\n", MM_RUN, "/usr/pandora/scripts/op_switchgui.sh" );
1404           emit_and_quit ( buffer );
1405         } else if ( sel == 7 ) {
1406           emit_and_quit ( MM_QUIT );
1407         } else if ( sel == 8 ) {
1408           // select skin
1409           if ( ui_pick_skin() ) {
1410             emit_and_quit ( MM_RESTART );
1411           }
1412         } else if ( sel == 9 ) {
1413           // about
1414           char buffer [ PATH_MAX ];
1415           sprintf ( buffer, "%s/about.txt", g_skinpath );
1416           ui_aboutscreen ( buffer );
1417         }
1418
1419         ui_event++;
1420         render_mask |= CHANGED_EVERYTHING;
1421
1422       } else {
1423         // unknown SDLK_ keycode?
1424
1425         // many SDLK_keycodes map to ASCII ("a" is ascii(a)), so try to jump to a filename of that name, in this category?
1426         // and if already there, try to jump to next, maybe?
1427         // future: look for sequence typing? ie: user types 'm' then 'a', look for 'ma*' instead of 'm' then 'a' matching
1428         if ( isalpha ( event.key.keysym.sym ) && g_categories [ ui_category ] -> refcount > 0 ) {
1429           mm_appref_t *app = g_categories [ ui_category ] -> refs;
1430
1431           //fprintf ( stderr, "sel %s next %s\n", ui_selected -> ref -> title_en, ui_selected -> next -> ref -> title_en );
1432
1433           // are we already matching the same char? and next item is also same char?
1434           if ( app && ui_selected &&
1435                ui_selected -> ref -> title_en && ui_selected -> next -> ref -> title_en &&
1436                toupper ( ui_selected -> ref -> title_en [ 0 ] ) == toupper ( ui_selected -> next -> ref -> title_en [ 0 ] ) &&
1437                toupper ( ui_selected -> ref -> title_en [ 0 ] ) == toupper ( event.key.keysym.sym )
1438              )
1439           {
1440             // just skip down one
1441             app = ui_selected -> next;
1442           } else {
1443
1444             // walk the category, looking for a first-char match
1445             while ( app ) {
1446               if ( app -> ref -> title_en && toupper ( app -> ref -> title_en [ 0 ] ) == toupper ( event.key.keysym.sym ) ) {
1447                 break;
1448               }
1449               app = app -> next;
1450             }
1451
1452           } // same start letter, or new run?
1453
1454           // found something, or no?
1455           if ( app ) {
1456             // looks like we found a potential match; try switching it to visible selection
1457             ui_selected = app;
1458             ui_set_selected ( ui_selected );
1459             ui_event++;
1460           }
1461
1462
1463         } // SDLK_alphanumeric?
1464
1465       } // SDLK_....
1466
1467       // extras
1468 #if 1
1469       if ( event.key.keysym.sym == SDLK_ESCAPE ) {
1470         emit_and_quit ( MM_QUIT );
1471       }
1472 #endif
1473
1474       break;
1475 #endif
1476
1477 #if 1 // mouse / touchscreen
1478 #if 0
1479     case SDL_MOUSEBUTTONDOWN:
1480       if ( event.button.button == SDL_BUTTON_LEFT ) {
1481         cb_pointer_press ( gc, event.button.x / g_scale, event.button.y / g_scale );
1482         ui_event++;
1483       }
1484       break;
1485 #endif
1486
1487     case SDL_MOUSEBUTTONUP:
1488       if ( event.button.button == SDL_BUTTON_LEFT ) {
1489         ui_touch_act ( event.button.x, event.button.y );
1490         ui_event++;
1491       }
1492       break;
1493 #endif
1494
1495     case SDL_QUIT:
1496       exit ( 0 );
1497       break;
1498
1499     default:
1500       break;
1501
1502     } // switch event type
1503
1504   } // while poll
1505
1506   return;
1507 }
1508
1509 void ui_push_left ( unsigned char forcecoil ) {
1510
1511   if ( ! ui_selected ) {
1512     ui_push_right ( 0 );
1513     return;
1514   }
1515
1516   // what column we in?
1517   unsigned int col = ui_determine_screen_col ( ui_selected );
1518
1519   // are we already at first item?
1520   if ( forcecoil == 0 &&
1521        pnd_conf_get_as_int_d ( g_conf, "grid.wrap_horiz_samerow", 0 ) &&
1522        col == 0 )
1523   {
1524     unsigned int i = ui_display_context.col_max - 1;
1525     while ( i && ui_selected -> next ) {
1526       ui_push_right ( 0 );
1527       i--;
1528     }
1529
1530   } else if ( g_categories [ ui_category ] -> refs == ui_selected ) {
1531     // can't go any more left, we're at the head
1532
1533   } else {
1534     // figure out the previous item; yay for singly linked list :/
1535     mm_appref_t *i = g_categories [ ui_category ] -> refs;
1536     while ( i ) {
1537       if ( i -> next == ui_selected ) {
1538         ui_selected = i;
1539         break;
1540       }
1541       i = i -> next;
1542     }
1543   }
1544
1545   ui_set_selected ( ui_selected );
1546
1547   return;
1548 }
1549
1550 void ui_push_right ( unsigned char forcecoil ) {
1551
1552   if ( ui_selected ) {
1553
1554     // what column we in?
1555     unsigned int col = ui_determine_screen_col ( ui_selected );
1556
1557     // wrap same or no?
1558     if ( forcecoil == 0 &&
1559          pnd_conf_get_as_int_d ( g_conf, "grid.wrap_horiz_samerow", 0 ) &&
1560          // and selected is far-right, or last icon in category (might not be far right)
1561          ( ( col == ui_display_context.col_max - 1 ) ||
1562            ( ui_selected -> next == NULL ) )
1563        )
1564     {
1565       // same wrap
1566       //unsigned int i = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 ) - 1;
1567       while ( col /*i*/ ) {
1568         ui_push_left ( 0 );
1569         col--; //i--;
1570       }
1571
1572     } else {
1573       // just go to the next
1574
1575       if ( ui_selected -> next ) {
1576         ui_selected = ui_selected -> next;
1577       }
1578
1579     }
1580
1581   } else {
1582     ui_selected = g_categories [ ui_category ] -> refs;
1583   }
1584
1585   ui_set_selected ( ui_selected );
1586
1587   return;
1588 }
1589
1590 void ui_push_up ( void ) {
1591   unsigned char col_max = ui_display_context.col_max;
1592
1593   if ( ! ui_selected ) {
1594     return;
1595   }
1596
1597   // what row we in?
1598   unsigned int row = ui_determine_row ( ui_selected );
1599
1600   if ( row == 0 &&
1601        pnd_conf_get_as_int_d ( g_conf, "grid.wrap_vert_stop", 0 ) == 0 )
1602   {
1603     // wrap around instead
1604
1605     unsigned int col = ui_determine_screen_col ( ui_selected );
1606
1607     // go to end
1608     ui_selected = g_categories [ ui_category ] -> refs;
1609     while ( ui_selected -> next ) {
1610       ui_selected = ui_selected -> next;
1611     }
1612
1613     // try to move to same column
1614     unsigned int newcol = ui_determine_screen_col ( ui_selected );
1615     if ( newcol > col ) {
1616       col = newcol - col;
1617       while ( col ) {
1618         ui_push_left ( 0 );
1619         col--;
1620       }
1621     }
1622
1623     // scroll down to show it
1624     int r = ui_determine_row ( ui_selected ) - 1;
1625     if ( r - ui_display_context.row_max > 0 ) {
1626       ui_rows_scrolled_down = (unsigned int) r;
1627     }
1628
1629   } else {
1630     // stop at top/bottom
1631
1632     while ( col_max ) {
1633       ui_push_left ( 1 );
1634       col_max--;
1635     }
1636
1637   }
1638
1639   return;
1640 }
1641
1642 void ui_push_down ( void ) {
1643   unsigned char col_max = ui_display_context.col_max;
1644
1645   if ( ui_selected ) {
1646
1647     // what row we in?
1648     unsigned int row = ui_determine_row ( ui_selected );
1649
1650     // max rows?
1651     unsigned int icon_rows = g_categories [ ui_category ] -> refcount / col_max;
1652     if ( g_categories [ ui_category ] -> refcount % col_max > 0 ) {
1653       icon_rows++;
1654     }
1655
1656     // we at the end?
1657     if ( row == ( icon_rows - 1 ) &&
1658          pnd_conf_get_as_int_d ( g_conf, "grid.wrap_vert_stop", 0 ) == 0 )
1659     {
1660
1661       unsigned char col = ui_determine_screen_col ( ui_selected );
1662
1663       ui_selected = g_categories [ ui_category ] -> refs;
1664
1665       while ( col ) {
1666         ui_selected = ui_selected -> next;
1667         col--;
1668       }
1669
1670       ui_rows_scrolled_down = 0;
1671
1672       render_mask |= CHANGED_EVERYTHING;
1673
1674     } else {
1675
1676       while ( col_max ) {
1677         ui_push_right ( 1 );
1678         col_max--;
1679       }
1680
1681     }
1682
1683   } else {
1684     ui_push_right ( 0 );
1685   }
1686
1687   return;
1688 }
1689
1690 // 'backup' is currently 'X', for going back up in a folder/subcat without having to hit exec on the '..' entry
1691 void ui_push_backup ( void ) {
1692
1693   // a subcat-as-dir, or a dir browser?
1694   if ( g_categories [ ui_category] -> fspath ) {
1695     // dir browser, just climb our way back up
1696
1697     // go up
1698     char *c;
1699
1700     // lop off last word; if the thing ends with /, lop that one, then the next word.
1701     while ( ( c = strrchr ( g_categories [ ui_category] -> fspath, '/' ) ) ) {
1702       *c = '\0'; // lop off the last hunk
1703       if ( *(c+1) != '\0' ) {
1704         break;
1705       }
1706     } // while
1707
1708     // nothing left?
1709     if ( g_categories [ ui_category] -> fspath [ 0 ] == '\0' ) {
1710       free ( g_categories [ ui_category] -> fspath );
1711       g_categories [ ui_category] -> fspath = strdup ( "/" );
1712     }
1713
1714   } else {
1715     // a pnd subcat .. are we in one, or at the 'top'?
1716     char *pcatname = g_categories [ ui_category ] -> parent_catname;
1717
1718     if ( ! pcatname ) {
1719       return; // we're at the 'top' already
1720     }
1721
1722     // set to first cat!
1723     ui_category = 0;
1724     // republish cats .. shoudl just be the one
1725     category_publish ( CFNORMAL, NULL );
1726
1727     if ( pcatname ) {
1728       ui_category = category_index ( pcatname );
1729     }
1730
1731   } // dir or subcat?
1732
1733   return;
1734 }
1735
1736 void ui_push_exec ( void ) {
1737
1738   if ( ! ui_selected ) {
1739     return;
1740   }
1741
1742   // was this icon generated from filesystem, or from pnd-file?
1743   if ( ui_selected -> ref -> object_flags & PND_DISCO_GENERATED ) {
1744
1745     if ( ! ui_selected -> ref -> title_en ) {
1746       return; // no filename
1747     }
1748
1749     if ( ui_selected -> ref -> object_type == pnd_object_type_directory ) {
1750
1751       // check if this guy is a dir-browser tab, or is a directory on a pnd tab
1752       if ( ! g_categories [ ui_category] -> fspath ) {
1753         // pnd subcat as dir
1754
1755         // are we already in a subcat? if so, go back to parent; there is no grandparenting or deeper
1756         if ( g_categories [ ui_category ] -> parent_catname ) {
1757           // go back up
1758           ui_push_backup();
1759
1760         } else {
1761           // delve into subcat
1762
1763           // set to first cat!
1764           ui_category = 0;
1765           // republish cats .. shoudl just be the one
1766           category_publish ( CFBYNAME, ui_selected -> ref -> object_path );
1767
1768         }
1769
1770         // forget the selection, nolonger applies
1771         ui_selected = NULL;
1772         ui_set_selected ( ui_selected );
1773         // redraw the grid
1774         render_mask |= CHANGED_EVERYTHING;
1775
1776       } else {
1777
1778         // delve up/down the dir tree
1779         if ( strcmp ( ui_selected -> ref -> title_en, ".." ) == 0 ) {
1780           ui_push_backup();
1781
1782         } else {
1783           // go down
1784           char *temp = malloc ( strlen ( g_categories [ ui_category] -> fspath ) + strlen ( ui_selected -> ref -> title_en ) + 1 + 1 );
1785           sprintf ( temp, "%s/%s", g_categories [ ui_category] -> fspath, ui_selected -> ref -> title_en );
1786           free ( g_categories [ ui_category] -> fspath );
1787           g_categories [ ui_category] -> fspath = temp;
1788         }
1789
1790         pnd_log ( pndn_debug, "Cat %s is now in path %s\n", g_categories [ ui_category ] -> catname, g_categories [ ui_category ]-> fspath );
1791
1792         // forget the selection, nolonger applies
1793         ui_selected = NULL;
1794         ui_set_selected ( ui_selected );
1795         // rescan the dir
1796         category_fs_restock ( g_categories [ ui_category ] );
1797         // redraw the grid
1798         render_mask |= CHANGED_SELECTION;
1799
1800       } // directory browser or pnd subcat?
1801
1802     } else {
1803       // just run it arbitrarily?
1804
1805       // if this a pnd-file, or just some executable?
1806       if ( strcasestr ( ui_selected -> ref -> object_filename, PND_PACKAGE_FILEEXT ) ) {
1807         // looks like a pnd, now what do we do..
1808         pnd_box_handle h = pnd_disco_file ( ui_selected -> ref -> object_path, ui_selected -> ref -> object_filename );
1809
1810         if ( h ) {
1811           pnd_disco_t *d = pnd_box_get_head ( h );
1812           pnd_apps_exec_disco ( pnd_run_script, d, PND_EXEC_OPTION_NORUN, NULL );
1813           char buffer [ PATH_MAX ];
1814           sprintf ( buffer, "%s %s\n", MM_RUN, pnd_apps_exec_runline() );
1815           if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
1816             emit_and_quit ( buffer );
1817           } else {
1818             emit_and_run ( buffer );
1819           }
1820         }
1821
1822       } else {
1823         // random bin file
1824
1825         // is it even executable? if we don't have handlers for non-executables yet (Jan 2011 we don't),
1826         // then don't even try to run things not-flagged as executable.. but wait most people are on
1827         // FAT filesystems, what a drag, we can't tell at the fs level.
1828         // ... but we can still invoke 'file' and grep out the good bits, at least.
1829         //
1830         // open a stream reading 'file /path/to/file' and check output for 'executable'
1831         // -- not checking for "ARM" so it can pick up x86 (or whatever native) executables in build environment
1832         unsigned char is_executable = 0;
1833
1834         // popen test
1835         {
1836           char popenbuf [ FILENAME_MAX ];
1837           snprintf ( popenbuf, FILENAME_MAX, "%s %s/%s", MIMETYPE_EXE, g_categories [ ui_category ] -> fspath, ui_selected -> ref -> title_en );
1838
1839           FILE *marceau;
1840           if ( ! ( marceau = popen ( popenbuf, "r" ) ) ) {
1841             return; // error, we need some useful error handling and dialog boxes here
1842           }
1843
1844           if ( fgets ( popenbuf, FILENAME_MAX, marceau ) ) {
1845             //printf ( "File test returns: %s\n", popenbuf );
1846             if ( strstr ( popenbuf, "executable" ) != NULL ) {
1847               is_executable = 1;
1848             }
1849           }
1850
1851           pclose ( marceau );
1852
1853         } // popen test
1854
1855         if ( ! is_executable ) {
1856           fprintf ( stderr, "ERROR: File to invoke is not executable, skipping. (%s)\n", ui_selected -> ref -> title_en );
1857           return; // need some error handling here
1858         }
1859
1860 #if 0 // eat up any pending SDL events and toss 'em?
1861         {
1862           SDL_PumpEvents();
1863           SDL_Event e;
1864           while ( SDL_PeepEvents ( &e, 1, SDL_GETEVENT, SDL_ALLEVENTS ) > 0 ) {
1865             // spin
1866           }
1867         }
1868 #endif
1869
1870 #if 1
1871         // just exec it
1872         //
1873
1874         // get CWD so we can restore it on return
1875         char cwd [ PATH_MAX ];
1876         getcwd ( cwd, PATH_MAX );
1877
1878         // full path to executable so we don't rely on implicit "./"
1879         char execbuf [ FILENAME_MAX ];
1880         snprintf ( execbuf, FILENAME_MAX, "%s/%s", g_categories [ ui_category ] -> fspath, ui_selected -> ref -> title_en );
1881
1882         // do it!
1883         chdir ( g_categories [ ui_category ] -> fspath );
1884         exec_raw_binary ( execbuf /*ui_selected -> ref -> title_en*/ );
1885         chdir ( cwd );
1886 #else
1887         // DEPRECATED / NOT TESTED
1888         // get mmwrapper to run it
1889         char buffer [ PATH_MAX ];
1890         sprintf ( buffer, "%s %s/%s\n", MM_RUN, g_categories [ ui_category ] -> fspath, ui_selected -> ref -> title_en );
1891         if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
1892           emit_and_quit ( buffer );
1893         } else {
1894           emit_and_run ( buffer );
1895         }
1896 #endif
1897       } // pnd or bin?
1898
1899     } // dir or file?
1900
1901   } else {
1902
1903     // set app-run speed
1904     int use_run_speed = pnd_conf_get_as_int_d ( g_conf, "minimenu.use_run_speed", 0 );
1905     if ( use_run_speed > 0 ) {
1906       int mm_speed = pnd_conf_get_as_int_d ( g_conf, "minimenu.run_speed", -1 );
1907       if ( mm_speed > -1 ) {
1908         char buffer [ 512 ];
1909         snprintf ( buffer, 500, "sudo /usr/pandora/scripts/op_cpuspeed.sh %d", mm_speed );
1910         system ( buffer );
1911       }
1912     } // do speed change?
1913
1914     // request app to run and quit mmenu
1915     pnd_apps_exec_disco ( pnd_run_script, ui_selected -> ref, PND_EXEC_OPTION_NORUN, NULL );
1916     char buffer [ PATH_MAX ];
1917     sprintf ( buffer, "%s %s\n", MM_RUN, pnd_apps_exec_runline() );
1918     if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
1919       emit_and_quit ( buffer );
1920     } else {
1921       emit_and_run ( buffer );
1922     }
1923   }
1924
1925   return;
1926 }
1927
1928 void ui_push_ltrigger ( void ) {
1929   unsigned char oldcat = ui_category;
1930   unsigned int screen_width = ui_display_context.screen_width;
1931   unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
1932
1933   if ( g_categorycount == 0 ) {
1934     return;
1935   }
1936
1937   if ( ui_category > 0 ) {
1938     ui_category--;
1939     category_fs_restock ( g_categories [ ui_category ] );
1940   } else {
1941     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
1942       ui_category = g_categorycount - 1;
1943       ui_catshift = 0;
1944       if ( ui_category >= ( screen_width / tab_width ) ) {
1945         ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
1946       }
1947       category_fs_restock ( g_categories [ ui_category ] );
1948     }
1949   }
1950
1951   if ( oldcat != ui_category ) {
1952     ui_selected = NULL;
1953     ui_set_selected ( ui_selected );
1954   }
1955
1956   // make tab visible?
1957   if ( ui_catshift > 0 && ui_category == ui_catshift - 1 ) {
1958     ui_catshift--;
1959   }
1960
1961   // unscroll
1962   ui_rows_scrolled_down = 0;
1963
1964   render_mask |= CHANGED_CATEGORY;
1965
1966   return;
1967 }
1968
1969 void ui_push_rtrigger ( void ) {
1970   unsigned char oldcat = ui_category;
1971
1972   if ( g_categorycount == 0 ) {
1973     return;
1974   }
1975
1976   unsigned int screen_width = ui_display_context.screen_width;
1977   unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
1978
1979   if ( ui_category < ( g_categorycount - 1 ) ) {
1980     ui_category++;
1981     category_fs_restock ( g_categories [ ui_category ] );
1982   } else {
1983     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
1984       ui_category = 0;
1985       ui_catshift = 0;
1986       category_fs_restock ( g_categories [ ui_category ] );
1987     }
1988   }
1989
1990   if ( oldcat != ui_category ) {
1991     ui_selected = NULL;
1992     ui_set_selected ( ui_selected );
1993   }
1994
1995   // make tab visible?
1996   if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
1997     ui_catshift++;
1998   }
1999
2000   // unscroll
2001   ui_rows_scrolled_down = 0;
2002
2003   render_mask |= CHANGED_CATEGORY;
2004
2005   return;
2006 }
2007
2008 SDL_Surface *ui_scale_image ( SDL_Surface *s, unsigned int maxwidth, int maxheight ) {
2009   double scale = 1000000.0;
2010   double scalex = 1000000.0;
2011   double scaley = 1000000.0;
2012   SDL_Surface *scaled;
2013
2014   scalex = (double)maxwidth / (double)s -> w;
2015
2016   if ( maxheight == -1 ) {
2017     scale = scalex;
2018   } else {
2019     scaley = (double)maxheight / (double)s -> h;
2020
2021     if ( scaley < scalex ) {
2022       scale = scaley;
2023     } else {
2024       scale = scalex;
2025     }
2026
2027   }
2028
2029   scaled = rotozoomSurface ( s, 0 /* angle*/, scale /* scale */, 1 /* smooth==1*/ );
2030   SDL_FreeSurface ( s );
2031   s = scaled;
2032
2033   return ( s );
2034 }
2035
2036 void ui_loadscreen ( void ) {
2037
2038   SDL_Rect dest;
2039
2040   // clear the screen
2041   SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2042
2043   // render text
2044   SDL_Surface *rtext;
2045   rtext = TTF_RenderText_Blended ( g_big_font, "Setting up menu...", ui_display_context.fontcolor );
2046   dest.x = 20;
2047   dest.y = 20;
2048   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
2049   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2050   SDL_FreeSurface ( rtext );
2051
2052   return;
2053 }
2054
2055 void ui_discoverscreen ( unsigned char clearscreen ) {
2056
2057   SDL_Rect dest;
2058
2059   // clear the screen
2060   if ( clearscreen ) {
2061     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2062
2063     // render background
2064     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2065       dest.x = 0;
2066       dest.y = 0;
2067       dest.w = sdl_realscreen -> w;
2068       dest.h = sdl_realscreen -> h;
2069       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
2070       SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2071     }
2072
2073   }
2074
2075   // render text
2076   SDL_Surface *rtext;
2077   rtext = TTF_RenderText_Blended ( g_big_font, "Looking for applications...", ui_display_context.fontcolor );
2078   if ( clearscreen ) {
2079     dest.x = 20;
2080     dest.y = 20;
2081   } else {
2082     dest.x = 20;
2083     dest.y = 40;
2084   }
2085   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
2086   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2087   SDL_FreeSurface ( rtext );
2088
2089   // render icon
2090   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
2091     dest.x = rtext -> w + 30;
2092     dest.y = 20;
2093     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, &dest );
2094     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2095   }
2096
2097   return;
2098 }
2099
2100 void ui_cachescreen ( unsigned char clearscreen, char *filename ) {
2101
2102   SDL_Rect rects [ 4 ];
2103   SDL_Rect *dest = rects;
2104   SDL_Rect src;
2105   bzero ( dest, sizeof(SDL_Rect)* 4 );
2106
2107   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
2108   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
2109   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
2110   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
2111
2112   static unsigned int stepx = 0;
2113
2114   // clear the screen
2115   if ( clearscreen ) {
2116     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2117
2118     // render background
2119     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2120       dest -> x = 0;
2121       dest -> y = 0;
2122       dest -> w = sdl_realscreen -> w;
2123       dest -> h = sdl_realscreen -> h;
2124       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
2125       dest++;
2126     }
2127
2128   } else {
2129
2130     // render background
2131     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2132       src.x = 0;
2133       src.y = 0;
2134       src.w = sdl_realscreen -> w;
2135       src.h = 100;
2136       dest -> x = 0;
2137       dest -> y = 0;
2138       dest -> w = sdl_realscreen -> w;
2139       dest -> h = sdl_realscreen -> h;
2140       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, &src, sdl_realscreen, dest );
2141       dest++;
2142     }
2143
2144   } // clear it
2145
2146   // render text
2147   SDL_Surface *rtext;
2148   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
2149   rtext = TTF_RenderText_Blended ( g_big_font, "Caching applications artwork...", tmpfontcolor );
2150   dest -> x = 20;
2151   dest -> y = 20;
2152   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2153   SDL_FreeSurface ( rtext );
2154   dest++;
2155
2156   // render icon
2157   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
2158     dest -> x = rtext -> w + 30 + stepx;
2159     dest -> y = 20;
2160     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, dest );
2161     dest++;
2162   }
2163
2164   // filename
2165   if ( filename ) {
2166     rtext = TTF_RenderText_Blended ( g_tab_font, filename, tmpfontcolor );
2167     dest -> x = 20;
2168     dest -> y = 50;
2169     SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2170     SDL_FreeSurface ( rtext );
2171     dest++;
2172   }
2173
2174   // move across
2175   stepx += 20;
2176
2177   if ( stepx > 350 ) {
2178     stepx = 0;
2179   }
2180
2181   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
2182
2183   return;
2184 }
2185
2186 int ui_selected_index ( void ) {
2187
2188   if ( ! ui_selected ) {
2189     return ( -1 ); // no index
2190   }
2191
2192   mm_appref_t *r = g_categories [ ui_category ] -> refs;
2193   int counter = 0;
2194   while ( r ) {
2195     if ( r == ui_selected ) {
2196       return ( counter );
2197     }
2198     r = r -> next;
2199     counter++;
2200   }
2201
2202   return ( -1 );
2203 }
2204
2205 static mm_appref_t *timer_ref = NULL;
2206 void ui_set_selected ( mm_appref_t *r ) {
2207
2208   render_mask |= CHANGED_SELECTION;
2209
2210   if ( ! pnd_conf_get_as_int_d ( g_conf, "minimenu.load_previews_later", 0 ) ) {
2211     return; // no desire to defer anything
2212   }
2213
2214   if ( ! r ) {
2215     // cancel timer
2216     SDL_SetTimer ( 0, NULL );
2217     timer_ref = NULL;
2218     return;
2219   }
2220
2221   SDL_SetTimer ( pnd_conf_get_as_int_d ( g_conf, "previewpic.defer_timer_ms", 1000 ), ui_callback_f );
2222   timer_ref = r;
2223
2224   return;
2225 }
2226
2227 unsigned int ui_callback_f ( unsigned int t ) {
2228
2229   if ( ui_selected != timer_ref ) {
2230     return ( 0 ); // user has moved it, who cares
2231   }
2232
2233   SDL_Event e;
2234   bzero ( &e, sizeof(SDL_Event) );
2235   e.type = SDL_USEREVENT;
2236   e.user.code = sdl_user_ticker;
2237   SDL_PushEvent ( &e );
2238
2239   return ( 0 );
2240 }
2241
2242 int ui_modal_single_menu ( char *argv[], unsigned int argc, char *title, char *footer ) {
2243   SDL_Rect rects [ 40 ];
2244   SDL_Rect *dest = rects;
2245   SDL_Rect src;
2246   SDL_Surface *rtext;
2247   unsigned char max_visible = pnd_conf_get_as_int_d ( g_conf, "detailtext.max_visible" , 11 );
2248   unsigned char first_visible = 0;
2249
2250   bzero ( rects, sizeof(SDL_Rect) * 40 );
2251
2252   unsigned int sel = 0;
2253
2254   SDL_Color selfontcolor = { 0/*font_rgba_r*/, ui_display_context.font_rgba_g, ui_display_context.font_rgba_b, ui_display_context.font_rgba_a };
2255
2256   unsigned int i;
2257   SDL_Event event;
2258
2259   while ( 1 ) {
2260
2261     // clear
2262     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2263     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2264     dest -> w = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> w;
2265     dest -> h = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h;
2266     SDL_FillRect( sdl_realscreen, dest, 0 );
2267
2268     // show dialog background
2269     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
2270       src.x = 0;
2271       src.y = 0;
2272       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> w;
2273       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> h;
2274       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2275       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2276       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2277       // repeat for darken?
2278       //SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2279       //SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2280       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2281       dest++;
2282     }
2283
2284     // show dialog frame
2285     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
2286       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2287       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2288       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
2289       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2290       dest++;
2291     }
2292
2293     // show header
2294     if ( title ) {
2295       rtext = TTF_RenderText_Blended ( g_tab_font, title, ui_display_context.fontcolor );
2296       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2297       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 20;
2298       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2299       SDL_FreeSurface ( rtext );
2300       dest++;
2301     }
2302
2303     // show footer
2304     if ( footer ) {
2305       rtext = TTF_RenderText_Blended ( g_tab_font, footer, ui_display_context.fontcolor );
2306       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2307       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) +
2308         ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h
2309         - 60;
2310       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2311       SDL_FreeSurface ( rtext );
2312       dest++;
2313     }
2314
2315     // show options
2316     for ( i = first_visible; i < first_visible + max_visible && i < argc; i++ ) {
2317
2318       // show options
2319       if ( sel == i ) {
2320         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], selfontcolor );
2321       } else {
2322         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], ui_display_context.fontcolor );
2323       }
2324       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2325       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 40 + ( 20 * ( i + 1 - first_visible ) );
2326       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2327       SDL_FreeSurface ( rtext );
2328       dest++;
2329
2330     } // for
2331
2332     // update all the rects and send it all to sdl
2333     SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
2334     dest = rects;
2335
2336     // check for input
2337     while ( SDL_WaitEvent ( &event ) ) {
2338
2339       switch ( event.type ) {
2340
2341       //case SDL_KEYUP:
2342       case SDL_KEYDOWN:
2343
2344         if ( event.key.keysym.sym == SDLK_UP ) {
2345           if ( sel ) {
2346             sel--;
2347
2348             if ( sel < first_visible ) {
2349               first_visible--;
2350             }
2351
2352           }
2353         } else if ( event.key.keysym.sym == SDLK_DOWN ) {
2354
2355           if ( sel < argc - 1 ) {
2356             sel++;
2357
2358             // ensure visibility
2359             if ( sel >= first_visible + max_visible ) {
2360               first_visible++;
2361             }
2362
2363           }
2364
2365         } else if ( event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_END ) { // return, or "B"
2366           return ( sel );
2367
2368 #if 0
2369         } else if ( event.key.keysym.sym == SDLK_q ) {
2370           exit ( 0 );
2371 #endif
2372
2373         } else {
2374           return ( -1 ); // nada
2375         }
2376
2377         break;
2378
2379       } // switch
2380
2381       break;
2382     } // while
2383
2384   } // while
2385
2386   return ( -1 );
2387 }
2388
2389 unsigned char ui_determine_row ( mm_appref_t *a ) {
2390   unsigned int row = 0;
2391
2392   mm_appref_t *i = g_categories [ ui_category ] -> refs;
2393   while ( i != a ) {
2394     i = i -> next;
2395     row++;
2396   } // while
2397   row /= ui_display_context.col_max;
2398
2399   return ( row );
2400 }
2401
2402 unsigned char ui_determine_screen_row ( mm_appref_t *a ) {
2403   return ( ui_determine_row ( a ) % ui_display_context.row_max );
2404 }
2405
2406 unsigned char ui_determine_screen_col ( mm_appref_t *a ) {
2407   unsigned int col = 0;
2408
2409   mm_appref_t *i = g_categories [ ui_category ] -> refs;
2410   while ( i != a ) {
2411     i = i -> next;
2412     col++;
2413   } // while
2414   col %= ui_display_context.col_max;
2415
2416   return ( col );
2417 }
2418
2419 unsigned char ui_show_info ( char *pndrun, pnd_disco_t *p ) {
2420   char *viewer, *searchpath;
2421   pnd_conf_handle desktoph;
2422
2423   // viewer
2424   searchpath = pnd_conf_query_searchpath();
2425
2426   desktoph = pnd_conf_fetch_by_id ( pnd_conf_desktop, searchpath );
2427
2428   if ( ! desktoph ) {
2429     return ( 0 );
2430   }
2431
2432   viewer = pnd_conf_get_as_char ( desktoph, "info.viewer" );
2433
2434   if ( ! viewer ) {
2435     return ( 0 ); // no way to view the file
2436   }
2437
2438   // etc
2439   if ( ! p -> unique_id ) {
2440     return ( 0 );
2441   }
2442
2443   if ( ! p -> info_filename ) {
2444     return ( 0 );
2445   }
2446
2447   if ( ! p -> info_name ) {
2448     return ( 0 );
2449   }
2450
2451   if ( ! pndrun ) {
2452     return ( 0 );
2453   }
2454
2455   // exec line
2456   char args [ 1001 ];
2457   char *pargs = args;
2458   if ( pnd_conf_get_as_char ( desktoph, "info.viewer_args" ) ) {
2459     snprintf ( pargs, 1001, "%s %s",
2460                pnd_conf_get_as_char ( desktoph, "info.viewer_args" ), p -> info_filename );
2461   } else {
2462     pargs = NULL;
2463   }
2464
2465   char pndfile [ 1024 ];
2466   if ( p -> object_type == pnd_object_type_directory ) {
2467     // for PXML-app-dir, pnd_run.sh doesn't want the PXML.xml.. it just wants the dir-name
2468     strncpy ( pndfile, p -> object_path, 1000 );
2469   } else if ( p -> object_type == pnd_object_type_pnd ) {
2470     // pnd_run.sh wants the full path and filename for the .pnd file
2471     snprintf ( pndfile, 1020, "%s/%s", p -> object_path, p -> object_filename );
2472   }
2473
2474   if ( ! pnd_apps_exec ( pndrun, pndfile, p -> unique_id, viewer, p -> startdir, pargs,
2475                          p -> clockspeed ? atoi ( p -> clockspeed ) : 0, PND_EXEC_OPTION_NORUN ) )
2476   {
2477     return ( 0 );
2478   }
2479
2480   pnd_log ( pndn_debug, "Info Exec=%s\n", pnd_apps_exec_runline() );
2481
2482   // try running it
2483   int x;
2484   if ( ( x = fork() ) < 0 ) {
2485     pnd_log ( pndn_error, "ERROR: Couldn't fork()\n" );
2486     return ( 0 );
2487   }
2488
2489   if ( x == 0 ) {
2490     execl ( "/bin/sh", "/bin/sh", "-c", pnd_apps_exec_runline(), (char*)NULL );
2491     pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", pnd_apps_exec_runline() );
2492     return ( 0 );
2493   }
2494
2495   return ( 1 );
2496 }
2497
2498 typedef struct {
2499   SDL_Rect r;
2500   int catnum;
2501   mm_appref_t *ref;
2502 } ui_touch_t;
2503 #define MAXTOUCH 100
2504 ui_touch_t ui_touchrects [ MAXTOUCH ];
2505 unsigned char ui_touchrect_count = 0;
2506
2507 void ui_register_reset ( void ) {
2508   bzero ( ui_touchrects, sizeof(ui_touch_t)*MAXTOUCH );
2509   ui_touchrect_count = 0;
2510   return;
2511 }
2512
2513 void ui_register_tab ( unsigned char catnum, unsigned int x, unsigned int y, unsigned int w, unsigned int h ) {
2514
2515   if ( ui_touchrect_count == MAXTOUCH ) {
2516     return;
2517   }
2518
2519   ui_touchrects [ ui_touchrect_count ].r.x = x;
2520   ui_touchrects [ ui_touchrect_count ].r.y = y;
2521   ui_touchrects [ ui_touchrect_count ].r.w = w;
2522   ui_touchrects [ ui_touchrect_count ].r.h = h;
2523   ui_touchrects [ ui_touchrect_count ].catnum = catnum;
2524   ui_touchrect_count++;
2525
2526   return;
2527 }
2528
2529 void ui_register_app ( mm_appref_t *app, unsigned int x, unsigned int y, unsigned int w, unsigned int h ) {
2530
2531   if ( ui_touchrect_count == MAXTOUCH ) {
2532     return;
2533   }
2534
2535   ui_touchrects [ ui_touchrect_count ].r.x = x;
2536   ui_touchrects [ ui_touchrect_count ].r.y = y;
2537   ui_touchrects [ ui_touchrect_count ].r.w = w;
2538   ui_touchrects [ ui_touchrect_count ].r.h = h;
2539   ui_touchrects [ ui_touchrect_count ].ref = app;
2540   ui_touchrect_count++;
2541
2542   return;
2543 }
2544
2545 void ui_touch_act ( unsigned int x, unsigned int y ) {
2546
2547   unsigned char i;
2548   ui_touch_t *t;
2549
2550   for ( i = 0; i < ui_touchrect_count; i++ ) {
2551     t = &(ui_touchrects [ i ]);
2552
2553     if ( x >= t -> r.x &&
2554          x <= t -> r.x + t -> r.w &&
2555          y >= t -> r.y &&
2556          y <= t -> r.y + t -> r.h
2557        )
2558     {
2559
2560       if ( t -> ref ) {
2561         ui_selected = t -> ref;
2562         ui_push_exec();
2563       } else {
2564         if ( ui_category != t -> catnum ) {
2565           ui_selected = NULL;
2566         }
2567         ui_category = t -> catnum;
2568         render_mask |= CHANGED_CATEGORY;
2569         // rescan the dir
2570         category_fs_restock ( g_categories [ ui_category ] );
2571       }
2572
2573       break;
2574     }
2575
2576   } // for
2577
2578   return;
2579 }
2580
2581 unsigned char ui_forkexec ( char *argv[] ) {
2582   char *fooby = argv[0];
2583   int x;
2584
2585   if ( ( x = fork() ) < 0 ) {
2586     pnd_log ( pndn_error, "ERROR: Couldn't fork() for '%s'\n", fooby );
2587     return ( 0 );
2588   }
2589
2590   if ( x == 0 ) { // child
2591     execv ( fooby, argv );
2592     pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", fooby );
2593     return ( 0 );
2594   }
2595
2596   // parent, success
2597   return ( 1 );
2598 }
2599
2600 unsigned char ui_threaded_timer_create ( void ) {
2601
2602   g_timer_thread = SDL_CreateThread ( (void*)ui_threaded_timer, NULL );
2603
2604   if ( ! g_timer_thread ) {
2605     pnd_log ( pndn_error, "ERROR: Couldn't create timer thread\n" );
2606     return ( 0 );
2607   }
2608
2609   return ( 1 );
2610 }
2611
2612 int ui_threaded_timer ( pnd_disco_t *p ) {
2613
2614   // this timer's job is to ..
2615   // - do nothing for quite awhile
2616   // - on wake, post event to SDL event queue, so that main thread will check if SD insert/eject occurred
2617   // - goto 10
2618
2619   unsigned int delay_s = 2; // seconds
2620
2621   while ( 1 ) {
2622
2623     // pause...
2624     sleep ( delay_s );
2625
2626     // .. trigger SD check
2627     SDL_Event e;
2628     bzero ( &e, sizeof(SDL_Event) );
2629     e.type = SDL_USEREVENT;
2630     e.user.code = sdl_user_checksd;
2631     SDL_PushEvent ( &e );
2632
2633   } // while
2634
2635   return ( 0 );
2636 }
2637
2638 unsigned char ui_threaded_defered_preview ( pnd_disco_t *p ) {
2639
2640   if ( ! cache_preview ( p, pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 ),
2641                          pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 ) )
2642      )
2643   {
2644     pnd_log ( pndn_debug, "THREAD: Couldn't load preview pic: '%s' -> '%s'\n",
2645               IFNULL(p->title_en,"No Name"), p -> preview_pic1 );
2646   }
2647
2648   // trigger that we completed
2649   SDL_Event e;
2650   bzero ( &e, sizeof(SDL_Event) );
2651   e.type = SDL_USEREVENT;
2652   e.user.code = sdl_user_finishedpreview;
2653   e.user.data1 = p;
2654   SDL_PushEvent ( &e );
2655
2656   return ( 0 );
2657 }
2658
2659 SDL_Thread *g_icon_thread = NULL;
2660 void ui_post_scan ( void ) {
2661
2662   // if deferred icon load, kick off the thread now
2663   if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_icons_later", 0 ) == 1 ) {
2664
2665     g_icon_thread = SDL_CreateThread ( (void*)ui_threaded_defered_icon, NULL );
2666
2667     if ( ! g_icon_thread ) {
2668       pnd_log ( pndn_error, "ERROR: Couldn't create icon thread\n" );
2669     }
2670
2671   } // deferred icon load
2672
2673   // reset view
2674   ui_selected = NULL;
2675   ui_rows_scrolled_down = 0;
2676   // set back to first tab, to be safe
2677   ui_category = 0;
2678   ui_catshift = 0;
2679
2680   // do we have a preferred category to jump to?
2681   char *dc = pnd_conf_get_as_char ( g_conf, "categories.default_cat" );
2682   if ( dc ) {
2683
2684     // attempt to find default cat; if we do find it, select it; otherwise
2685     // default behaviour will pick first cat (ie: usually All)
2686     unsigned int i;
2687     for ( i = 0; i < g_categorycount; i++ ) {
2688       if ( strcasecmp ( g_categories [ i ] -> catname, dc ) == 0 ) {
2689         ui_category = i;
2690         // ensure visibility
2691         unsigned int screen_width = ui_display_context.screen_width;
2692         unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
2693         if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
2694           ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
2695         }
2696         break;
2697       }
2698     }
2699
2700     if ( i == g_categorycount ) {
2701       pnd_log ( pndn_warning, "  User defined default category '%s' but not found, so using default behaviour\n", dc );
2702     }
2703
2704   } // default cat
2705
2706   // if we're sent right to a dirbrowser tab, restock it now (normally we restock on entry)
2707   if ( g_categories [ ui_category ] -> fspath ) {
2708     printf ( "Restock on start: '%s'\n", g_categories [ ui_category ] -> fspath );
2709     category_fs_restock ( g_categories [ ui_category ] );
2710   }
2711
2712   // redraw all
2713   render_mask |= CHANGED_EVERYTHING;
2714
2715   return;
2716 }
2717
2718 unsigned char ui_threaded_defered_icon ( void *p ) {
2719   extern pnd_box_handle g_active_apps;
2720   pnd_box_handle h = g_active_apps;
2721
2722   unsigned char maxwidth, maxheight;
2723   maxwidth = pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 );
2724   maxheight = pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_height", 50 );
2725
2726   pnd_disco_t *iter = pnd_box_get_head ( h );
2727
2728   while ( iter ) {
2729
2730     // cache it
2731     if ( iter -> pnd_icon_pos &&
2732          ! cache_icon ( iter, maxwidth, maxheight ) )
2733     {
2734       pnd_log ( pndn_warning, "  Couldn't load icon: '%s'\n", IFNULL(iter->title_en,"No Name") );
2735     } else {
2736
2737       // trigger that we completed
2738       SDL_Event e;
2739       bzero ( &e, sizeof(SDL_Event) );
2740       e.type = SDL_USEREVENT;
2741       e.user.code = sdl_user_finishedicon;
2742       SDL_PushEvent ( &e );
2743
2744       //pnd_log ( pndn_warning, "  Finished deferred load icon: '%s'\n", IFNULL(iter->title_en,"No Name") );
2745       usleep ( pnd_conf_get_as_int_d ( g_conf, "minimenu.defer_icon_us", 50000 ) );
2746
2747     }
2748
2749     // next
2750     iter = pnd_box_get_next ( iter );
2751   } // while
2752
2753   return ( 0 );
2754 }
2755
2756 void ui_show_hourglass ( unsigned char updaterect ) {
2757
2758   SDL_Rect dest;
2759   SDL_Surface *s = g_imagecache [ IMG_HOURGLASS ].i;
2760
2761   dest.x = ( 800 - s -> w ) / 2;
2762   dest.y = ( 480 - s -> h ) / 2;
2763
2764   SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, &dest );
2765
2766   if ( updaterect ) {
2767     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2768   }
2769
2770   return;
2771 }
2772
2773 unsigned char ui_pick_skin ( void ) {
2774 #define MAXSKINS 10
2775   char *skins [ MAXSKINS ];
2776   unsigned char iter;
2777
2778   char *searchpath = pnd_conf_get_as_char ( g_conf, "minimenu.skin_searchpath" );
2779   char tempname [ 100 ];
2780
2781   iter = 0;
2782
2783   skins [ iter++ ] = "No skin change";
2784
2785   SEARCHPATH_PRE
2786   {
2787     DIR *d = opendir ( buffer );
2788
2789     if ( d ) {
2790       struct dirent *dd;
2791
2792       while ( ( dd = readdir ( d ) ) ) {
2793
2794         if ( dd -> d_name [ 0 ] == '.' ) {
2795           // ignore
2796         } else if ( ( dd -> d_type == DT_DIR || dd -> d_type == DT_UNKNOWN ) &&
2797                     iter < MAXSKINS )
2798         {
2799           snprintf ( tempname, 100, "Skin: %s", dd -> d_name );
2800           skins [ iter++ ] = strdup ( tempname );
2801         }
2802
2803       }
2804
2805       closedir ( d );
2806     }
2807
2808   }
2809   SEARCHPATH_POST
2810
2811   int sel = ui_modal_single_menu ( skins, iter, "Skins", "Enter to select; other to return." );
2812
2813   // did they pick one?
2814   if ( sel > 0 ) {
2815     FILE *f;
2816
2817     char *s = strdup ( pnd_conf_get_as_char ( g_conf, "minimenu.skin_selected" ) );
2818     s = pnd_expand_tilde ( s );
2819
2820     f = fopen ( s, "w" );
2821
2822     free ( s );
2823
2824     if ( f ) {
2825       fprintf ( f, "%s\n", skins [ sel ] + 6 );
2826       fclose ( f );
2827     }
2828
2829     return ( 1 );
2830   }
2831
2832   return ( 0 );
2833 }
2834
2835 void ui_aboutscreen ( char *textpath ) {
2836 #define PIXELW 7
2837 #define PIXELH 7
2838 #define MARGINW 3
2839 #define MARGINH 3
2840 #define SCRW 800
2841 #define SCRH 480
2842 #define ROWS SCRH / ( PIXELH + MARGINH )
2843 #define COLS SCRW / ( PIXELW + MARGINW )
2844
2845   unsigned char pixelboard [ ROWS * COLS ]; // pixel heat
2846   bzero ( pixelboard, ROWS * COLS );
2847
2848   SDL_Surface *rtext;
2849   SDL_Rect r;
2850
2851   SDL_Color rtextc = { 200, 200, 200, 100 };
2852
2853   // pixel scroller
2854   char *textloop [ 500 ];
2855   unsigned int textmax = 0;
2856   bzero ( textloop, 500 * sizeof(char*) );
2857
2858   // cursor scroller
2859   char cbuffer [ 50000 ];
2860   bzero ( cbuffer, 50000 );
2861   unsigned int crevealed = 0;
2862
2863   FILE *f = fopen ( textpath, "r" );
2864
2865   if ( ! f ) {
2866     pnd_log ( pndn_error, "ERROR: Couldn't open about text: %s\n", textpath );
2867     return;
2868   }
2869
2870   char textbuf [ 100 ];
2871   while ( fgets ( textbuf, 100, f ) ) {
2872
2873     // add to full buffer
2874     strncat ( cbuffer, textbuf, 50000 );
2875
2876     // chomp
2877     if ( strchr ( textbuf, '\n' ) ) {
2878       * strchr ( textbuf, '\n' ) = '\0';
2879     }
2880
2881     // add to pixel loop
2882     if ( 1||textbuf [ 0 ] ) {
2883       textloop [ textmax ] = strdup ( textbuf );
2884       textmax++;
2885     }
2886
2887   } // while fgets
2888
2889   fclose ( f );
2890
2891   unsigned int textiter = 0;
2892   while ( textiter < textmax ) {
2893     char *text = textloop [ textiter ];
2894
2895     rtext = NULL;
2896     if ( text [ 0 ] ) {
2897       // render to surface
2898       rtext = TTF_RenderText_Blended ( g_grid_font, text, rtextc );
2899
2900       // render font to pixelboard
2901       unsigned int px, py;
2902       unsigned char *ph;
2903       unsigned int *pixels = rtext -> pixels;
2904       unsigned char cr, cg, cb, ca;
2905       for ( py = 0; py < rtext -> h; py ++ ) {
2906         for ( px = 0; px < ( rtext -> w > COLS ? COLS : rtext -> w ); px++ ) {
2907
2908           SDL_GetRGBA ( pixels [ ( py * rtext -> pitch / 4 ) + px ],
2909                         rtext -> format, &cr, &cg, &cb, &ca );
2910
2911           if ( ca != 0 ) {
2912
2913             ph = pixelboard + ( /*y offset */ 30 * COLS ) + ( py * COLS ) + px /* / 2 */;
2914
2915             if ( *ph < 100 ) {
2916               *ph = 100;
2917             }
2918
2919             ca /= 5;
2920             if ( *ph + ca < 250 ) {
2921               *ph += ca;
2922             }
2923
2924           } // got a pixel?
2925
2926         } // x
2927       } // y
2928
2929     } // got text?
2930
2931     unsigned int runcount = 10;
2932     while ( runcount-- ) {
2933
2934       // clear display
2935       SDL_FillRect( sdl_realscreen, NULL /* whole */, 0 );
2936
2937       // render pixelboard
2938       unsigned int x, y;
2939       unsigned int c;
2940       for ( y = 0; y < ROWS; y++ ) {
2941         for ( x = 0; x < COLS; x++ ) {
2942
2943           if ( 1||pixelboard [ ( y * COLS ) + x ] ) {
2944
2945             // position
2946             r.x = x * ( PIXELW + MARGINW );
2947             r.y = y * ( PIXELH + MARGINH );
2948             r.w = PIXELW;
2949             r.h = PIXELH;
2950             // heat -> colour
2951             c = SDL_MapRGB ( sdl_realscreen -> format, 100 /* r */, 0 /* g */, pixelboard [ ( y * COLS ) + x ] );
2952             // render
2953             SDL_FillRect( sdl_realscreen, &r /* whole */, c );
2954
2955           }
2956
2957         } // x
2958       } // y
2959
2960       // cool pixels
2961       unsigned char *pc = pixelboard;
2962       for ( y = 0; y < ROWS; y++ ) {
2963         for ( x = 0; x < COLS; x++ ) {
2964
2965           if ( *pc > 10 ) {
2966             (*pc) -= 3;
2967           }
2968
2969           pc++;
2970         } // x
2971       } // y
2972
2973       // slide pixels upwards
2974       memmove ( pixelboard, pixelboard + COLS, ( COLS * ROWS ) - COLS );
2975
2976       // render actual readable text
2977       {
2978
2979         // display up to cursor
2980         SDL_Rect dest;
2981         unsigned int cdraw = 0;
2982         SDL_Surface *cs;
2983         char ctb [ 2 ];
2984
2985         if ( crevealed > 200 ) {
2986           cdraw = crevealed - 200;
2987         }
2988
2989         dest.x = 400;
2990         dest.y = 20;
2991
2992         for ( ; cdraw < crevealed; cdraw++ ) {
2993           ctb [ 0 ] = cbuffer [ cdraw ];
2994           ctb [ 1 ] = '\0';
2995           // move over or down
2996           if ( cbuffer [ cdraw ] == '\n' ) {
2997             // EOL
2998             dest.x = 400;
2999             dest.y += 14;
3000
3001             if ( dest.y > 450 ) {
3002               dest.y = 450;
3003             }
3004
3005           } else {
3006             // draw the char
3007             cs = TTF_RenderText_Blended ( g_tab_font, ctb, rtextc );
3008             if ( cs ) {
3009               SDL_BlitSurface ( cs, NULL /* all */, sdl_realscreen, &dest );
3010               SDL_FreeSurface ( cs );
3011               // over
3012               dest.x += cs -> w;
3013             }
3014           }
3015
3016         }
3017
3018         dest.w = 10;
3019         dest.h = 20;
3020         SDL_FillRect ( sdl_realscreen, &dest /* whole */, 220 );
3021
3022         // increment cursor to next character
3023         if ( cbuffer [ crevealed ] != '\0' ) {
3024           crevealed++;
3025         }
3026
3027       } // draw cursor text
3028
3029       // reveal
3030       //
3031       SDL_UpdateRect ( sdl_realscreen, 0, 0, 0, 0 ); // whole screen
3032
3033       usleep ( 50000 );
3034
3035       // any button? if so, about
3036       {
3037         SDL_PumpEvents();
3038
3039         SDL_Event e;
3040
3041         if ( SDL_PeepEvents ( &e, 1, SDL_GETEVENT, SDL_EVENTMASK(/*SDL_KEYUP|*/SDL_KEYDOWN) ) > 0 ) {
3042           return;
3043         }
3044
3045       }
3046
3047     } // while cooling
3048
3049     if ( rtext ) {
3050       SDL_FreeSurface ( rtext );
3051     }
3052
3053     textiter++;
3054   } // while more text
3055
3056   // free up
3057   unsigned int i;
3058   for ( i = 0; i < textmax; i++ ) {
3059     if ( textloop [ i ] ) {
3060       free ( textloop [ i ] );
3061       textloop [ i ] = 0;
3062     }
3063   }
3064
3065   return;
3066 }
3067
3068 void ui_revealscreen ( void ) {
3069   char *labels [ 500 ];
3070   unsigned int labelmax = 0;
3071   unsigned int i;
3072   char fulllabel [ 200 ];
3073
3074   if ( ! category_count ( CFHIDDEN ) ) {
3075     return; // nothing to do
3076   }
3077
3078   // switch to hidden categories
3079   category_publish ( CFHIDDEN, NULL );
3080
3081   // build up labels to show in menu
3082   for ( i = 0; i < g_categorycount; i++ ) {
3083
3084     if ( g_categories [ i ] -> parent_catname ) {
3085       sprintf ( fulllabel, "%s [%s]", g_categories [ i ] -> catname, g_categories [ i ] -> parent_catname );
3086     } else {
3087       sprintf ( fulllabel, "%s", g_categories [ i ] -> catname );
3088     }
3089
3090     labels [ labelmax++ ] = strdup ( fulllabel );
3091   }
3092
3093   // show menu
3094   int sel = ui_modal_single_menu ( labels, labelmax, "Temporary Category Reveal",
3095                                    "Enter to select; other to return." );
3096
3097   // if selected, try to set this guy to visible
3098   if ( sel >= 0 ) {
3099
3100     // fix up category name, if its been hacked
3101     if ( strchr ( g_categories [ sel ] -> catname, '.' ) ) {
3102       char *t = g_categories [ sel ] -> catname;
3103       g_categories [ sel ] -> catname = strdup ( strchr ( g_categories [ sel ] -> catname, '.' ) + 1 );
3104       free ( t );
3105     }
3106
3107     // reflag this guy to be visible
3108     g_categories [ sel ] -> catflags = CFNORMAL;
3109
3110     // switch to the new category.. cache name.
3111     char *switch_to_name = g_categories [ sel ] -> catname;
3112
3113     // republish categories
3114     category_publish ( CFNORMAL, NULL );
3115
3116     // switch to the new category.. with the cached name!
3117     ui_category = category_index ( switch_to_name );
3118
3119     // ensure visibility
3120     unsigned int screen_width = ui_display_context.screen_width;
3121     unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
3122     if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
3123       ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
3124     }
3125
3126     // redraw tabs
3127     render_mask |= CHANGED_CATEGORY;
3128   }
3129
3130   for ( i = 0; i < g_categorycount; i++ ) {
3131     free ( labels [ i ] );
3132   }
3133
3134   return;
3135 }
3136
3137 void ui_recache_context ( ui_context_t *c ) {
3138
3139   c -> screen_width = pnd_conf_get_as_int_d ( g_conf, "display.screen_width", 800 );
3140
3141   c -> font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
3142   c -> font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
3143   c -> font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
3144   c -> font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
3145
3146   c -> grid_offset_x = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_x" );
3147   c -> grid_offset_y = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_y" );
3148
3149   c -> icon_offset_x = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_x" );
3150   c -> icon_offset_y = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_y" );
3151   c -> icon_max_width = pnd_conf_get_as_int ( g_conf, "grid.icon_max_width" );
3152   c -> icon_max_height = pnd_conf_get_as_int ( g_conf, "grid.icon_max_height" );
3153   c -> sel_icon_offset_x = pnd_conf_get_as_int_d ( g_conf, "grid.sel_offoffset_x", 0 );
3154   c -> sel_icon_offset_y = pnd_conf_get_as_int_d ( g_conf, "grid.sel_offoffset_y", 0 );
3155
3156   c -> text_width = pnd_conf_get_as_int ( g_conf, "grid.text_width" );
3157   c -> text_clip_x = pnd_conf_get_as_int ( g_conf, "grid.text_clip_x" );
3158   c -> text_offset_x = pnd_conf_get_as_int ( g_conf, "grid.text_offset_x" );
3159   c -> text_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_offset_y" );
3160   c -> text_hilite_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_hilite_offset_y" );
3161
3162   c -> row_max = pnd_conf_get_as_int_d ( g_conf, "grid.row_max", 4 );
3163   c -> col_max = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 );
3164
3165   c -> cell_width = pnd_conf_get_as_int ( g_conf, "grid.cell_width" );
3166   c -> cell_height = pnd_conf_get_as_int ( g_conf, "grid.cell_height" );
3167
3168   c -> arrow_bar_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x", 450 );
3169   c -> arrow_bar_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y", 100 );
3170   c -> arrow_bar_clip_w = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_w", 10 );
3171   c -> arrow_bar_clip_h = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_h", 100 );
3172   c -> arrow_up_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x", 450 );
3173   c -> arrow_up_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y", 80 );
3174   c -> arrow_down_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x", 450 );
3175   c -> arrow_down_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y", 80 );
3176
3177   // font colour
3178   SDL_Color tmp = { c -> font_rgba_r, c -> font_rgba_g, c -> font_rgba_b, c -> font_rgba_a };
3179   c -> fontcolor = tmp;
3180
3181   // now that we've got 'normal' (detail pane shown) param's, lets check if detail pane
3182   // is hidden; if so, override some values with those alternate skin values where possible.
3183   if ( ui_detail_hidden ) {
3184     // if detail panel is hidden, and theme cannot support it, unhide the bloody thing. (This may help
3185     // when someone is amid theme hacking or changing.)
3186     if ( ! ui_is_detail_hideable() ) {
3187       ui_detail_hidden = 0;
3188     }
3189
3190     // still hidden?
3191     if ( ui_detail_hidden ) {
3192
3193       c -> row_max = pnd_conf_get_as_int_d ( g_conf, "grid.row_max_w", c -> row_max );
3194       c -> col_max = pnd_conf_get_as_int_d ( g_conf, "grid.col_max_w", c -> col_max );
3195
3196       c -> cell_width = pnd_conf_get_as_int_d ( g_conf, "grid.cell_width_w", c -> cell_width );
3197       c -> cell_height = pnd_conf_get_as_int_d ( g_conf, "grid.cell_height_w", c -> cell_height );
3198
3199       c -> arrow_bar_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x_w", 450 );
3200       c -> arrow_bar_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y_w", 100 );
3201       c -> arrow_up_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x_w", 450 );
3202       c -> arrow_up_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y_w", 80 );
3203       c -> arrow_down_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x_w", 450 );
3204       c -> arrow_down_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y_w", 80 );
3205
3206     } // if detail hidden.. still.
3207
3208   } // if detail hidden
3209
3210   return;
3211 }
3212
3213 unsigned char ui_is_detail_hideable ( void ) {
3214
3215   // if skin has a bit of info for wide-mode, we assume wide-mode is available
3216   if ( pnd_conf_get_as_char ( g_conf, "grid.row_max_w" ) != NULL ) {
3217     return ( 1 );
3218   }
3219
3220   // else not!
3221   return ( 0 );
3222 }
3223
3224 void ui_toggle_detail_pane ( void ) {
3225
3226   // no bitmask trickery here; I like it to be stand-out obvious at 3am.
3227
3228   if ( ui_detail_hidden ) {
3229     ui_detail_hidden = 0;
3230   } else {
3231     ui_detail_hidden = 1;
3232   }
3233
3234   // repull skin config
3235   ui_recache_context ( &ui_display_context );
3236
3237   // redraw
3238   render_mask |= CHANGED_EVERYTHING;
3239
3240   return;
3241 }
3242
3243 void ui_menu_context ( mm_appref_t *a ) {
3244
3245   unsigned char rescan_apps = 0;
3246   unsigned char context_alive = 1;
3247
3248   enum {
3249     context_done = 0,
3250     context_file_info,
3251     context_file_delete,
3252     context_app_info,
3253     context_app_hide,
3254     context_app_recategorize,
3255     context_app_recategorize_sub,
3256     context_app_rename,
3257     context_app_cpuspeed,
3258     context_app_run,
3259     context_menu_max
3260   };
3261
3262   char *verbiage[] = {
3263     "Done (return to grid)",      // context_done
3264     "Get info about file/dir",    // context_file_info
3265     "Delete file/dir",            // context_file_delete
3266     "Get info",                   // context_app_info
3267     "Hide application",           //             hide
3268     "Recategorize",               //             recategorize
3269     "Recategorize subcategory",   //             recategorize
3270     "Change displayed title",     //             rename
3271     "Set CPU speed for launch",   //             cpuspeed
3272     "Run application"             //             run
3273   };
3274
3275   unsigned short int menu [ context_menu_max ];
3276   char *menustring [ context_menu_max ];
3277   unsigned char menumax = 0;
3278
3279   // ++ done
3280   menu [ menumax ] = context_done; menustring [ menumax++ ] = verbiage [ context_done ];
3281
3282   // hook up appropriate menu options based on tab-type and object-type
3283   if ( g_categories [ ui_category ] -> fspath ) {
3284     return; // TBD
3285     menu [ menumax ] = context_file_info; menustring [ menumax++ ] = verbiage [ context_file_info ];
3286     menu [ menumax ] = context_file_delete; menustring [ menumax++ ] = verbiage [ context_file_delete ];
3287   } else {
3288     //menu [ menumax ] = context_app_info; menustring [ menumax++ ] = verbiage [ context_app_info ];
3289     menu [ menumax ] = context_app_hide; menustring [ menumax++ ] = verbiage [ context_app_hide ];
3290     menu [ menumax ] = context_app_recategorize; menustring [ menumax++ ] = verbiage [ context_app_recategorize ];
3291     menu [ menumax ] = context_app_recategorize_sub; menustring [ menumax++ ] = verbiage [ context_app_recategorize_sub ];
3292     menu [ menumax ] = context_app_rename; menustring [ menumax++ ] = verbiage [ context_app_rename ];
3293     menu [ menumax ] = context_app_cpuspeed; menustring [ menumax++ ] = verbiage [ context_app_cpuspeed ];
3294     menu [ menumax ] = context_app_run; menustring [ menumax++ ] = verbiage [ context_app_run ];
3295   }
3296
3297   // operate the menu
3298   while ( context_alive ) {
3299
3300     int sel = ui_modal_single_menu ( menustring, menumax, a -> ref -> title_en ? a -> ref -> title_en : "Quickpick Menu" /* title */, "B or Enter; other to cancel." /* footer */ );
3301
3302     if ( sel < 0 ) {
3303       context_alive = 0;
3304
3305     } else {
3306
3307       switch ( menu [ sel ] ) {
3308
3309       case context_done:
3310         context_alive = 0;
3311         break;
3312
3313       case context_file_info:
3314         break;
3315
3316       case context_file_delete:
3317         //ui_menu_twoby ( "Delete - Are you sure?", "B/enter; other to cancel", "Delete", "Do not delete" );
3318         break;
3319
3320       case context_app_info:
3321         break;
3322
3323       case context_app_hide:
3324         {
3325           // determine key
3326           char confkey [ 1000 ];
3327           snprintf ( confkey, 999, "%s.%s", "appshow", a -> ref -> unique_id );
3328
3329           // turn app 'off'
3330           pnd_conf_set_char ( g_conf, confkey, "0" );
3331
3332           // write conf, so it will take next time
3333           conf_write ( g_conf, conf_determine_location ( g_conf ) );
3334
3335           // request rescan and wrap up
3336           rescan_apps++;
3337           context_alive = 0; // nolonger visible, so lets just get out
3338
3339         }
3340     
3341         break;
3342
3343       case context_app_recategorize:
3344         {
3345           char *opts [ 250 ];
3346           unsigned char optmax = 0;
3347           unsigned char i;
3348
3349           i = 0;
3350           while ( 1 ) {
3351
3352             if ( ! freedesktop_complete [ i ].cat ) {
3353               break;
3354             }
3355
3356             if ( ! freedesktop_complete [ i ].parent_cat ) {
3357               opts [ optmax++ ] = freedesktop_complete [ i ].cat;
3358             }
3359
3360             i++;
3361           } // while
3362
3363           char prompt [ 101 ];
3364           snprintf ( prompt, 100, "Pick category [%s]", a -> ref -> main_category ? a -> ref -> main_category : "NoParentCategory" );
3365
3366           int sel = ui_modal_single_menu ( opts, optmax, prompt /*"Select parent category"*/, "Enter to select; other to skip." );
3367
3368           if ( sel >= 0 ) {
3369             char confirm [ 1001 ];
3370             snprintf ( confirm, 1000, "Confirm: %s", opts [ sel ] );
3371
3372             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm categorization", "Do not set category" ) == 1 ) {
3373               ovr_replace_or_add ( a, "maincategory", opts [ sel ] );
3374               rescan_apps++;
3375             }
3376
3377           }
3378
3379         }
3380         break;
3381
3382       case context_app_recategorize_sub:
3383         {
3384           char *opts [ 250 ];
3385           unsigned char optmax = 0;
3386           unsigned char i;
3387
3388           i = 0;
3389           while ( 1 ) {
3390
3391             if ( ! freedesktop_complete [ i ].cat ) {
3392               break;
3393             }
3394
3395             if ( freedesktop_complete [ i ].parent_cat ) {
3396               opts [ optmax++ ] = freedesktop_complete [ i ].cat;
3397             }
3398
3399             i++;
3400           } // while
3401
3402           char prompt [ 101 ];
3403           snprintf ( prompt, 100, "Pick subcategory [%s]", a -> ref -> main_category1 ? a -> ref -> main_category1 : "NoSubcategory" );
3404
3405           int sel = ui_modal_single_menu ( opts, optmax, prompt /*"Select subcategory"*/, "Enter to select; other to skip." );
3406
3407           if ( sel >= 0 ) {
3408             char confirm [ 1001 ];
3409             snprintf ( confirm, 1000, "Confirm: %s", opts [ sel ] );
3410
3411             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm sub-categorization", "Do not set sub-category" ) == 1 ) {
3412               ovr_replace_or_add ( a, "maincategorysub1", opts [ sel ] );
3413               rescan_apps++;
3414             }
3415
3416           }
3417
3418         }
3419         break;
3420
3421       case context_app_rename:
3422         {
3423           char namebuf [ 101 ];
3424           unsigned char changed;
3425
3426           changed = ui_menu_get_text_line ( "Rename application", "Use keyboard; Enter when done.",
3427                                             a -> ref -> title_en ? a -> ref -> title_en : "blank", namebuf, 30, 0 /* alphanumeric */ );
3428
3429           if ( changed ) {
3430             char confirm [ 1001 ];
3431             snprintf ( confirm, 1000, "Confirm: %s", namebuf );
3432
3433             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm rename", "Do not rename" ) == 1 ) {
3434               ovr_replace_or_add ( a, "title", namebuf );
3435               rescan_apps++;
3436             }
3437
3438           }
3439
3440         }
3441
3442         break;
3443
3444       case context_app_cpuspeed:
3445         {
3446           char namebuf [ 101 ];
3447           unsigned char changed;
3448
3449           changed = ui_menu_get_text_line ( "Specify runspeed", "Use keyboard; Enter when done.",
3450                                             a -> ref -> clockspeed ? a -> ref -> clockspeed : "500", namebuf, 6, 1 /* numeric */ );
3451
3452           if ( changed ) {
3453             char confirm [ 1001 ];
3454             snprintf ( confirm, 1000, "Confirm: %s", namebuf );
3455
3456             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm clockspeed", "Do not set" ) == 1 ) {
3457               ovr_replace_or_add ( a, "clockspeed", namebuf );
3458               rescan_apps++;
3459             }
3460
3461           }
3462
3463         }
3464
3465         break;
3466
3467       case context_app_run:
3468         ui_push_exec();
3469         break;
3470
3471       default:
3472         return;
3473
3474       } // switch
3475
3476     } // if useful return
3477
3478   } // menu is alive?
3479
3480   // rescan apps?
3481   if ( rescan_apps ) {
3482     applications_free();
3483     applications_scan();
3484   }
3485
3486   return;
3487 }
3488
3489 unsigned char ui_menu_twoby ( char *title, char *footer, char *one, char *two ) {
3490   char *opts [ 3 ];
3491   opts [ 0 ] = one;
3492   opts [ 1 ] = two;
3493   int sel = ui_modal_single_menu ( opts, 2, title, footer );
3494   if ( sel < 0 ) {
3495     return ( 0 );
3496   }
3497   return ( sel + 1 );
3498 }
3499
3500 unsigned char ui_menu_get_text_line ( char *title, char *footer, char *initialvalue,
3501                                       char *r_buffer, unsigned char maxlen, unsigned char numbersonlyp )
3502 {
3503   SDL_Rect rects [ 40 ];
3504   SDL_Rect *dest = rects;
3505   SDL_Rect src;
3506   SDL_Surface *rtext;
3507
3508   char hacktext [ 1024 ];
3509   unsigned char shifted = 0;
3510
3511   bzero ( rects, sizeof(SDL_Rect) * 40 );
3512
3513   if ( initialvalue ) {
3514     strncpy ( r_buffer, initialvalue, maxlen );
3515   } else {
3516     bzero ( r_buffer, maxlen );
3517   }
3518
3519   while ( 1 ) {
3520
3521     // clear
3522     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
3523     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
3524     dest -> w = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> w;
3525     dest -> h = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h;
3526     SDL_FillRect( sdl_realscreen, dest, 0 );
3527
3528     // show dialog background
3529     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
3530       src.x = 0;
3531       src.y = 0;
3532       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> w;
3533       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> h;
3534       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
3535       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
3536       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
3537       dest++;
3538     }
3539
3540     // show dialog frame
3541     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
3542       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
3543       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
3544       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
3545       dest++;
3546     }
3547
3548     // show header
3549     if ( title ) {
3550       rtext = TTF_RenderText_Blended ( g_tab_font, title, ui_display_context.fontcolor );
3551       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
3552       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 20;
3553       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
3554       SDL_FreeSurface ( rtext );
3555       dest++;
3556     }
3557
3558     // show footer
3559     if ( footer ) {
3560       rtext = TTF_RenderText_Blended ( g_tab_font, footer, ui_display_context.fontcolor );
3561       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
3562       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) +
3563         ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h
3564         - 60;
3565       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
3566       SDL_FreeSurface ( rtext );
3567       dest++;
3568     }
3569
3570     // show text line - and embed cursor
3571     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
3572     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 40 + ( 20 * ( 0/*i*/ + 1 - 0/*first_visible*/ ) );
3573
3574     strncpy ( hacktext, r_buffer, 1000 );
3575     strncat ( hacktext, "\n", 1000 ); // add [] in most fonts
3576
3577     rtext = TTF_RenderText_Blended ( g_tab_font, hacktext, ui_display_context.fontcolor );
3578     SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
3579     SDL_FreeSurface ( rtext );
3580     dest++;
3581
3582     // update all the rects and send it all to sdl
3583     SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
3584     dest = rects;
3585
3586     // check for input
3587     SDL_Event event;
3588     while ( SDL_WaitEvent ( &event ) ) {
3589
3590       switch ( event.type ) {
3591
3592       case SDL_KEYUP:
3593         if ( event.key.keysym.sym == SDLK_LSHIFT || event.key.keysym.sym == SDLK_RSHIFT ) {
3594           shifted = 0;
3595         }
3596         break;
3597
3598       case SDL_KEYDOWN:
3599
3600         if ( event.key.keysym.sym == SDLK_LEFT || event.key.keysym.sym == SDLK_BACKSPACE ) {
3601           char *eol = strchr ( r_buffer, '\0' );
3602           *( eol - 1 ) = '\0';
3603         } else if ( event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_END ) { // return, or "B"
3604           return ( 1 );
3605
3606         } else if ( event.key.keysym.sym == SDLK_LSHIFT || event.key.keysym.sym == SDLK_RSHIFT ) {
3607           shifted = 1;
3608
3609         } else if ( event.key.keysym.sym == SDLK_ESCAPE ||
3610                     event.key.keysym.sym == SDLK_PAGEUP ||
3611                     event.key.keysym.sym == SDLK_PAGEDOWN ||
3612                     event.key.keysym.sym == SDLK_HOME
3613                   )
3614         {
3615           return ( 0 );
3616
3617         } else {
3618
3619           if ( isprint(event.key.keysym.sym) ) {
3620
3621             unsigned char good = 1;
3622
3623             if ( numbersonlyp && ( ! isdigit(event.key.keysym.sym) ) ) {
3624               good = 0;
3625             }
3626
3627             if ( maxlen && strlen(r_buffer) >= maxlen ) {
3628               good = 0;
3629             }
3630
3631             if ( good ) {
3632               char b [ 2 ] = { '\0', '\0' };
3633               if ( shifted ) {
3634                 b [ 0 ] = toupper ( event.key.keysym.sym );
3635               } else {
3636                 b [ 0 ] = event.key.keysym.sym;
3637               }
3638               strncat ( r_buffer, b, maxlen );
3639             } // good?
3640
3641           } // printable?
3642
3643         }
3644
3645         break;
3646
3647       } // switch
3648
3649       break;
3650
3651     } // while waiting for input
3652
3653   } // while
3654   
3655   return ( 0 );
3656 }
3657
3658 unsigned char ovr_replace_or_add ( mm_appref_t *a, char *keybase, char *newvalue ) {
3659   printf ( "setting %s:%u - '%s' to '%s' - %s/%s\n", a -> ref -> title_en, a -> ref -> subapp_number, keybase, newvalue, a -> ref -> object_path, a -> ref -> object_filename );
3660
3661   char fullpath [ PATH_MAX ];
3662
3663   sprintf ( fullpath, "%s/%s", a -> ref -> object_path, a -> ref -> object_filename );
3664   char *dot = strrchr ( fullpath, '.' );
3665   if ( dot ) {
3666     sprintf ( dot, PXML_SAMEPATH_OVERRIDE_FILEEXT );
3667   } else {
3668     fprintf ( stderr, "ERROR: Bad pnd-path in disco_t! %s\n", fullpath );
3669     return ( 0 );
3670   }
3671
3672   struct stat statbuf;
3673
3674   if ( stat ( fullpath, &statbuf ) == 0 ) {
3675     // file exists
3676     pnd_conf_handle h;
3677
3678     h = pnd_conf_fetch_by_path ( fullpath );
3679
3680     if ( ! h ) {
3681       return ( 0 ); // fail!
3682     }
3683
3684     char key [ 101 ];
3685     snprintf ( key, 100, "Application-%u.%s", a -> ref -> subapp_number, keybase );
3686
3687     pnd_conf_set_char ( h, key, newvalue );
3688
3689     return ( pnd_conf_write ( h, fullpath ) );
3690
3691   } else {
3692     // file needs to be created - easy!
3693
3694     FILE *f = fopen ( fullpath, "w" );
3695
3696     if ( f ) {
3697       fprintf ( f, "Application-%u.%s\t%s\n", a -> ref -> subapp_number, keybase, newvalue );
3698       fclose ( f );
3699
3700     } else {
3701       return ( 0 ); // fail!
3702     }
3703
3704   } // new or used?
3705
3706   return ( 1 );
3707 }