mmenu fixes -- ensuring tab visibility when going to subcat-folder and back up to...
[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 && ui_selected -> next &&
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
1725     // republish cats .. shoudl just be the one
1726     category_publish ( CFNORMAL, NULL );
1727
1728     if ( pcatname ) {
1729       ui_category = category_index ( pcatname );
1730
1731       // ensure tab visible?
1732       unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
1733       if ( ui_category > ui_catshift + ( ui_display_context.screen_width / tab_width ) - 1 ) {
1734         ui_catshift = ui_category - ( ui_display_context.screen_width / tab_width ) + 1;
1735       }
1736
1737     }
1738
1739   } // dir or subcat?
1740
1741   return;
1742 }
1743
1744 void ui_push_exec ( void ) {
1745
1746   if ( ! ui_selected ) {
1747     return;
1748   }
1749
1750   // was this icon generated from filesystem, or from pnd-file?
1751   if ( ui_selected -> ref -> object_flags & PND_DISCO_GENERATED ) {
1752
1753     if ( ! ui_selected -> ref -> title_en ) {
1754       return; // no filename
1755     }
1756
1757     if ( ui_selected -> ref -> object_type == pnd_object_type_directory ) {
1758
1759       // check if this guy is a dir-browser tab, or is a directory on a pnd tab
1760       if ( ! g_categories [ ui_category] -> fspath ) {
1761         // pnd subcat as dir
1762
1763         // are we already in a subcat? if so, go back to parent; there is no grandparenting or deeper
1764         if ( g_categories [ ui_category ] -> parent_catname ) {
1765           // go back up
1766           ui_push_backup();
1767
1768         } else {
1769           // delve into subcat
1770
1771           // set to first cat!
1772           ui_category = 0;
1773           ui_catshift = 0;
1774
1775           // republish cats .. shoudl just be the one
1776           category_publish ( CFBYNAME, ui_selected -> ref -> object_path );
1777
1778         }
1779
1780         // forget the selection, nolonger applies
1781         ui_selected = NULL;
1782         ui_set_selected ( ui_selected );
1783         // redraw the grid
1784         render_mask |= CHANGED_EVERYTHING;
1785
1786       } else {
1787
1788         // delve up/down the dir tree
1789         if ( strcmp ( ui_selected -> ref -> title_en, ".." ) == 0 ) {
1790           ui_push_backup();
1791
1792         } else {
1793           // go down
1794           char *temp = malloc ( strlen ( g_categories [ ui_category] -> fspath ) + strlen ( ui_selected -> ref -> title_en ) + 1 + 1 );
1795           sprintf ( temp, "%s/%s", g_categories [ ui_category] -> fspath, ui_selected -> ref -> title_en );
1796           free ( g_categories [ ui_category] -> fspath );
1797           g_categories [ ui_category] -> fspath = temp;
1798         }
1799
1800         pnd_log ( pndn_debug, "Cat %s is now in path %s\n", g_categories [ ui_category ] -> catname, g_categories [ ui_category ]-> fspath );
1801
1802         // forget the selection, nolonger applies
1803         ui_selected = NULL;
1804         ui_set_selected ( ui_selected );
1805         // rescan the dir
1806         category_fs_restock ( g_categories [ ui_category ] );
1807         // redraw the grid
1808         render_mask |= CHANGED_SELECTION;
1809
1810       } // directory browser or pnd subcat?
1811
1812     } else {
1813       // just run it arbitrarily?
1814
1815       // if this a pnd-file, or just some executable?
1816       if ( strcasestr ( ui_selected -> ref -> object_filename, PND_PACKAGE_FILEEXT ) ) {
1817         // looks like a pnd, now what do we do..
1818         pnd_box_handle h = pnd_disco_file ( ui_selected -> ref -> object_path, ui_selected -> ref -> object_filename );
1819
1820         if ( h ) {
1821           pnd_disco_t *d = pnd_box_get_head ( h );
1822           pnd_apps_exec_disco ( pnd_run_script, d, PND_EXEC_OPTION_NORUN, NULL );
1823           char buffer [ PATH_MAX ];
1824           sprintf ( buffer, "%s %s\n", MM_RUN, pnd_apps_exec_runline() );
1825           if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
1826             emit_and_quit ( buffer );
1827           } else {
1828             emit_and_run ( buffer );
1829           }
1830         }
1831
1832       } else {
1833         // random bin file
1834
1835         // is it even executable? if we don't have handlers for non-executables yet (Jan 2011 we don't),
1836         // then don't even try to run things not-flagged as executable.. but wait most people are on
1837         // FAT filesystems, what a drag, we can't tell at the fs level.
1838         // ... but we can still invoke 'file' and grep out the good bits, at least.
1839         //
1840         // open a stream reading 'file /path/to/file' and check output for 'executable'
1841         // -- not checking for "ARM" so it can pick up x86 (or whatever native) executables in build environment
1842         unsigned char is_executable = 0;
1843
1844         // popen test
1845         {
1846           char popenbuf [ FILENAME_MAX ];
1847           snprintf ( popenbuf, FILENAME_MAX, "%s %s/%s", MIMETYPE_EXE, g_categories [ ui_category ] -> fspath, ui_selected -> ref -> title_en );
1848
1849           FILE *marceau;
1850           if ( ! ( marceau = popen ( popenbuf, "r" ) ) ) {
1851             return; // error, we need some useful error handling and dialog boxes here
1852           }
1853
1854           if ( fgets ( popenbuf, FILENAME_MAX, marceau ) ) {
1855             //printf ( "File test returns: %s\n", popenbuf );
1856             if ( strstr ( popenbuf, "executable" ) != NULL ) {
1857               is_executable = 1;
1858             }
1859           }
1860
1861           pclose ( marceau );
1862
1863         } // popen test
1864
1865         if ( ! is_executable ) {
1866           fprintf ( stderr, "ERROR: File to invoke is not executable, skipping. (%s)\n", ui_selected -> ref -> title_en );
1867           return; // need some error handling here
1868         }
1869
1870 #if 0 // eat up any pending SDL events and toss 'em?
1871         {
1872           SDL_PumpEvents();
1873           SDL_Event e;
1874           while ( SDL_PeepEvents ( &e, 1, SDL_GETEVENT, SDL_ALLEVENTS ) > 0 ) {
1875             // spin
1876           }
1877         }
1878 #endif
1879
1880 #if 1
1881         // just exec it
1882         //
1883
1884         // get CWD so we can restore it on return
1885         char cwd [ PATH_MAX ];
1886         getcwd ( cwd, PATH_MAX );
1887
1888         // full path to executable so we don't rely on implicit "./"
1889         char execbuf [ FILENAME_MAX ];
1890         snprintf ( execbuf, FILENAME_MAX, "%s/%s", g_categories [ ui_category ] -> fspath, ui_selected -> ref -> title_en );
1891
1892         // do it!
1893         chdir ( g_categories [ ui_category ] -> fspath );
1894         exec_raw_binary ( execbuf /*ui_selected -> ref -> title_en*/ );
1895         chdir ( cwd );
1896 #else
1897         // DEPRECATED / NOT TESTED
1898         // get mmwrapper to run it
1899         char buffer [ PATH_MAX ];
1900         sprintf ( buffer, "%s %s/%s\n", MM_RUN, g_categories [ ui_category ] -> fspath, ui_selected -> ref -> title_en );
1901         if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
1902           emit_and_quit ( buffer );
1903         } else {
1904           emit_and_run ( buffer );
1905         }
1906 #endif
1907       } // pnd or bin?
1908
1909     } // dir or file?
1910
1911   } else {
1912
1913     // set app-run speed
1914     int use_run_speed = pnd_conf_get_as_int_d ( g_conf, "minimenu.use_run_speed", 0 );
1915     if ( use_run_speed > 0 ) {
1916       int mm_speed = pnd_conf_get_as_int_d ( g_conf, "minimenu.run_speed", -1 );
1917       if ( mm_speed > -1 ) {
1918         char buffer [ 512 ];
1919         snprintf ( buffer, 500, "sudo /usr/pandora/scripts/op_cpuspeed.sh %d", mm_speed );
1920         system ( buffer );
1921       }
1922     } // do speed change?
1923
1924     // request app to run and quit mmenu
1925     pnd_apps_exec_disco ( pnd_run_script, ui_selected -> ref, PND_EXEC_OPTION_NORUN, NULL );
1926     char buffer [ PATH_MAX ];
1927     sprintf ( buffer, "%s %s\n", MM_RUN, pnd_apps_exec_runline() );
1928     if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
1929       emit_and_quit ( buffer );
1930     } else {
1931       emit_and_run ( buffer );
1932     }
1933   }
1934
1935   return;
1936 }
1937
1938 void ui_push_ltrigger ( void ) {
1939   unsigned char oldcat = ui_category;
1940   unsigned int screen_width = ui_display_context.screen_width;
1941   unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
1942
1943   if ( g_categorycount == 0 ) {
1944     return;
1945   }
1946
1947   if ( ui_category > 0 ) {
1948     ui_category--;
1949     category_fs_restock ( g_categories [ ui_category ] );
1950   } else {
1951     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
1952       ui_category = g_categorycount - 1;
1953       ui_catshift = 0;
1954       if ( ui_category >= ( screen_width / tab_width ) ) {
1955         ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
1956       }
1957       category_fs_restock ( g_categories [ ui_category ] );
1958     }
1959   }
1960
1961   if ( oldcat != ui_category ) {
1962     ui_selected = NULL;
1963     ui_set_selected ( ui_selected );
1964   }
1965
1966   // make tab visible?
1967   if ( ui_catshift > 0 && ui_category == ui_catshift - 1 ) {
1968     ui_catshift--;
1969   }
1970
1971   // unscroll
1972   ui_rows_scrolled_down = 0;
1973
1974   render_mask |= CHANGED_CATEGORY;
1975
1976   return;
1977 }
1978
1979 void ui_push_rtrigger ( void ) {
1980   unsigned char oldcat = ui_category;
1981
1982   if ( g_categorycount == 0 ) {
1983     return;
1984   }
1985
1986   unsigned int screen_width = ui_display_context.screen_width;
1987   unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
1988
1989   if ( ui_category < ( g_categorycount - 1 ) ) {
1990     ui_category++;
1991     category_fs_restock ( g_categories [ ui_category ] );
1992   } else {
1993     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
1994       ui_category = 0;
1995       ui_catshift = 0;
1996       category_fs_restock ( g_categories [ ui_category ] );
1997     }
1998   }
1999
2000   if ( oldcat != ui_category ) {
2001     ui_selected = NULL;
2002     ui_set_selected ( ui_selected );
2003   }
2004
2005   // make tab visible?
2006   if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
2007     ui_catshift++;
2008   }
2009
2010   // unscroll
2011   ui_rows_scrolled_down = 0;
2012
2013   render_mask |= CHANGED_CATEGORY;
2014
2015   return;
2016 }
2017
2018 SDL_Surface *ui_scale_image ( SDL_Surface *s, unsigned int maxwidth, int maxheight ) {
2019   double scale = 1000000.0;
2020   double scalex = 1000000.0;
2021   double scaley = 1000000.0;
2022   SDL_Surface *scaled;
2023
2024   scalex = (double)maxwidth / (double)s -> w;
2025
2026   if ( maxheight == -1 ) {
2027     scale = scalex;
2028   } else {
2029     scaley = (double)maxheight / (double)s -> h;
2030
2031     if ( scaley < scalex ) {
2032       scale = scaley;
2033     } else {
2034       scale = scalex;
2035     }
2036
2037   }
2038
2039   scaled = rotozoomSurface ( s, 0 /* angle*/, scale /* scale */, 1 /* smooth==1*/ );
2040   SDL_FreeSurface ( s );
2041   s = scaled;
2042
2043   return ( s );
2044 }
2045
2046 void ui_loadscreen ( void ) {
2047
2048   SDL_Rect dest;
2049
2050   // clear the screen
2051   SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2052
2053   // render text
2054   SDL_Surface *rtext;
2055   rtext = TTF_RenderText_Blended ( g_big_font, "Setting up menu...", ui_display_context.fontcolor );
2056   dest.x = 20;
2057   dest.y = 20;
2058   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
2059   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2060   SDL_FreeSurface ( rtext );
2061
2062   return;
2063 }
2064
2065 void ui_discoverscreen ( unsigned char clearscreen ) {
2066
2067   SDL_Rect dest;
2068
2069   // clear the screen
2070   if ( clearscreen ) {
2071     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2072
2073     // render background
2074     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2075       dest.x = 0;
2076       dest.y = 0;
2077       dest.w = sdl_realscreen -> w;
2078       dest.h = sdl_realscreen -> h;
2079       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
2080       SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2081     }
2082
2083   }
2084
2085   // render text
2086   SDL_Surface *rtext;
2087   rtext = TTF_RenderText_Blended ( g_big_font, "Looking for applications...", ui_display_context.fontcolor );
2088   if ( clearscreen ) {
2089     dest.x = 20;
2090     dest.y = 20;
2091   } else {
2092     dest.x = 20;
2093     dest.y = 40;
2094   }
2095   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
2096   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2097   SDL_FreeSurface ( rtext );
2098
2099   // render icon
2100   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
2101     dest.x = rtext -> w + 30;
2102     dest.y = 20;
2103     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, &dest );
2104     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2105   }
2106
2107   return;
2108 }
2109
2110 void ui_cachescreen ( unsigned char clearscreen, char *filename ) {
2111
2112   SDL_Rect rects [ 4 ];
2113   SDL_Rect *dest = rects;
2114   SDL_Rect src;
2115   bzero ( dest, sizeof(SDL_Rect)* 4 );
2116
2117   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
2118   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
2119   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
2120   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
2121
2122   static unsigned int stepx = 0;
2123
2124   // clear the screen
2125   if ( clearscreen ) {
2126     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2127
2128     // render background
2129     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2130       dest -> x = 0;
2131       dest -> y = 0;
2132       dest -> w = sdl_realscreen -> w;
2133       dest -> h = sdl_realscreen -> h;
2134       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
2135       dest++;
2136     }
2137
2138   } else {
2139
2140     // render background
2141     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2142       src.x = 0;
2143       src.y = 0;
2144       src.w = sdl_realscreen -> w;
2145       src.h = 100;
2146       dest -> x = 0;
2147       dest -> y = 0;
2148       dest -> w = sdl_realscreen -> w;
2149       dest -> h = sdl_realscreen -> h;
2150       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, &src, sdl_realscreen, dest );
2151       dest++;
2152     }
2153
2154   } // clear it
2155
2156   // render text
2157   SDL_Surface *rtext;
2158   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
2159   rtext = TTF_RenderText_Blended ( g_big_font, "Caching applications artwork...", tmpfontcolor );
2160   dest -> x = 20;
2161   dest -> y = 20;
2162   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2163   SDL_FreeSurface ( rtext );
2164   dest++;
2165
2166   // render icon
2167   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
2168     dest -> x = rtext -> w + 30 + stepx;
2169     dest -> y = 20;
2170     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, dest );
2171     dest++;
2172   }
2173
2174   // filename
2175   if ( filename ) {
2176     rtext = TTF_RenderText_Blended ( g_tab_font, filename, tmpfontcolor );
2177     dest -> x = 20;
2178     dest -> y = 50;
2179     SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2180     SDL_FreeSurface ( rtext );
2181     dest++;
2182   }
2183
2184   // move across
2185   stepx += 20;
2186
2187   if ( stepx > 350 ) {
2188     stepx = 0;
2189   }
2190
2191   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
2192
2193   return;
2194 }
2195
2196 int ui_selected_index ( void ) {
2197
2198   if ( ! ui_selected ) {
2199     return ( -1 ); // no index
2200   }
2201
2202   mm_appref_t *r = g_categories [ ui_category ] -> refs;
2203   int counter = 0;
2204   while ( r ) {
2205     if ( r == ui_selected ) {
2206       return ( counter );
2207     }
2208     r = r -> next;
2209     counter++;
2210   }
2211
2212   return ( -1 );
2213 }
2214
2215 static mm_appref_t *timer_ref = NULL;
2216 void ui_set_selected ( mm_appref_t *r ) {
2217
2218   render_mask |= CHANGED_SELECTION;
2219
2220   if ( ! pnd_conf_get_as_int_d ( g_conf, "minimenu.load_previews_later", 0 ) ) {
2221     return; // no desire to defer anything
2222   }
2223
2224   if ( ! r ) {
2225     // cancel timer
2226     SDL_SetTimer ( 0, NULL );
2227     timer_ref = NULL;
2228     return;
2229   }
2230
2231   SDL_SetTimer ( pnd_conf_get_as_int_d ( g_conf, "previewpic.defer_timer_ms", 1000 ), ui_callback_f );
2232   timer_ref = r;
2233
2234   return;
2235 }
2236
2237 unsigned int ui_callback_f ( unsigned int t ) {
2238
2239   if ( ui_selected != timer_ref ) {
2240     return ( 0 ); // user has moved it, who cares
2241   }
2242
2243   SDL_Event e;
2244   bzero ( &e, sizeof(SDL_Event) );
2245   e.type = SDL_USEREVENT;
2246   e.user.code = sdl_user_ticker;
2247   SDL_PushEvent ( &e );
2248
2249   return ( 0 );
2250 }
2251
2252 int ui_modal_single_menu ( char *argv[], unsigned int argc, char *title, char *footer ) {
2253   SDL_Rect rects [ 40 ];
2254   SDL_Rect *dest = rects;
2255   SDL_Rect src;
2256   SDL_Surface *rtext;
2257   unsigned char max_visible = pnd_conf_get_as_int_d ( g_conf, "detailtext.max_visible" , 11 );
2258   unsigned char first_visible = 0;
2259
2260   bzero ( rects, sizeof(SDL_Rect) * 40 );
2261
2262   unsigned int sel = 0;
2263
2264   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 };
2265
2266   unsigned int i;
2267   SDL_Event event;
2268
2269   while ( 1 ) {
2270
2271     // clear
2272     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2273     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2274     dest -> w = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> w;
2275     dest -> h = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h;
2276     SDL_FillRect( sdl_realscreen, dest, 0 );
2277
2278     // show dialog background
2279     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
2280       src.x = 0;
2281       src.y = 0;
2282       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> w;
2283       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> h;
2284       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2285       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2286       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2287       // repeat for darken?
2288       //SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2289       //SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2290       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2291       dest++;
2292     }
2293
2294     // show dialog frame
2295     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
2296       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2297       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2298       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
2299       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2300       dest++;
2301     }
2302
2303     // show header
2304     if ( title ) {
2305       rtext = TTF_RenderText_Blended ( g_tab_font, title, 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 ) + 20;
2308       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2309       SDL_FreeSurface ( rtext );
2310       dest++;
2311     }
2312
2313     // show footer
2314     if ( footer ) {
2315       rtext = TTF_RenderText_Blended ( g_tab_font, footer, ui_display_context.fontcolor );
2316       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2317       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) +
2318         ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h
2319         - 60;
2320       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2321       SDL_FreeSurface ( rtext );
2322       dest++;
2323     }
2324
2325     // show options
2326     for ( i = first_visible; i < first_visible + max_visible && i < argc; i++ ) {
2327
2328       // show options
2329       if ( sel == i ) {
2330         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], selfontcolor );
2331       } else {
2332         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], ui_display_context.fontcolor );
2333       }
2334       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2335       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 40 + ( 20 * ( i + 1 - first_visible ) );
2336       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2337       SDL_FreeSurface ( rtext );
2338       dest++;
2339
2340     } // for
2341
2342     // update all the rects and send it all to sdl
2343     SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
2344     dest = rects;
2345
2346     // check for input
2347     while ( SDL_WaitEvent ( &event ) ) {
2348
2349       switch ( event.type ) {
2350
2351       //case SDL_KEYUP:
2352       case SDL_KEYDOWN:
2353
2354         if ( event.key.keysym.sym == SDLK_UP ) {
2355           if ( sel ) {
2356             sel--;
2357
2358             if ( sel < first_visible ) {
2359               first_visible--;
2360             }
2361
2362           }
2363         } else if ( event.key.keysym.sym == SDLK_DOWN ) {
2364
2365           if ( sel < argc - 1 ) {
2366             sel++;
2367
2368             // ensure visibility
2369             if ( sel >= first_visible + max_visible ) {
2370               first_visible++;
2371             }
2372
2373           }
2374
2375         } else if ( event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_END ) { // return, or "B"
2376           return ( sel );
2377
2378 #if 0
2379         } else if ( event.key.keysym.sym == SDLK_q ) {
2380           exit ( 0 );
2381 #endif
2382
2383         } else {
2384           return ( -1 ); // nada
2385         }
2386
2387         break;
2388
2389       } // switch
2390
2391       break;
2392     } // while
2393
2394   } // while
2395
2396   return ( -1 );
2397 }
2398
2399 unsigned char ui_determine_row ( mm_appref_t *a ) {
2400   unsigned int row = 0;
2401
2402   mm_appref_t *i = g_categories [ ui_category ] -> refs;
2403   while ( i != a ) {
2404     i = i -> next;
2405     row++;
2406   } // while
2407   row /= ui_display_context.col_max;
2408
2409   return ( row );
2410 }
2411
2412 unsigned char ui_determine_screen_row ( mm_appref_t *a ) {
2413   return ( ui_determine_row ( a ) % ui_display_context.row_max );
2414 }
2415
2416 unsigned char ui_determine_screen_col ( mm_appref_t *a ) {
2417   unsigned int col = 0;
2418
2419   mm_appref_t *i = g_categories [ ui_category ] -> refs;
2420   while ( i != a ) {
2421     i = i -> next;
2422     col++;
2423   } // while
2424   col %= ui_display_context.col_max;
2425
2426   return ( col );
2427 }
2428
2429 unsigned char ui_show_info ( char *pndrun, pnd_disco_t *p ) {
2430   char *viewer, *searchpath;
2431   pnd_conf_handle desktoph;
2432
2433   // viewer
2434   searchpath = pnd_conf_query_searchpath();
2435
2436   desktoph = pnd_conf_fetch_by_id ( pnd_conf_desktop, searchpath );
2437
2438   if ( ! desktoph ) {
2439     return ( 0 );
2440   }
2441
2442   viewer = pnd_conf_get_as_char ( desktoph, "info.viewer" );
2443
2444   if ( ! viewer ) {
2445     return ( 0 ); // no way to view the file
2446   }
2447
2448   // etc
2449   if ( ! p -> unique_id ) {
2450     return ( 0 );
2451   }
2452
2453   if ( ! p -> info_filename ) {
2454     return ( 0 );
2455   }
2456
2457   if ( ! p -> info_name ) {
2458     return ( 0 );
2459   }
2460
2461   if ( ! pndrun ) {
2462     return ( 0 );
2463   }
2464
2465   // exec line
2466   char args [ 1001 ];
2467   char *pargs = args;
2468   if ( pnd_conf_get_as_char ( desktoph, "info.viewer_args" ) ) {
2469     snprintf ( pargs, 1001, "%s %s",
2470                pnd_conf_get_as_char ( desktoph, "info.viewer_args" ), p -> info_filename );
2471   } else {
2472     pargs = NULL;
2473   }
2474
2475   char pndfile [ 1024 ];
2476   if ( p -> object_type == pnd_object_type_directory ) {
2477     // for PXML-app-dir, pnd_run.sh doesn't want the PXML.xml.. it just wants the dir-name
2478     strncpy ( pndfile, p -> object_path, 1000 );
2479   } else if ( p -> object_type == pnd_object_type_pnd ) {
2480     // pnd_run.sh wants the full path and filename for the .pnd file
2481     snprintf ( pndfile, 1020, "%s/%s", p -> object_path, p -> object_filename );
2482   }
2483
2484   if ( ! pnd_apps_exec ( pndrun, pndfile, p -> unique_id, viewer, p -> startdir, pargs,
2485                          p -> clockspeed ? atoi ( p -> clockspeed ) : 0, PND_EXEC_OPTION_NORUN ) )
2486   {
2487     return ( 0 );
2488   }
2489
2490   pnd_log ( pndn_debug, "Info Exec=%s\n", pnd_apps_exec_runline() );
2491
2492   // try running it
2493   int x;
2494   if ( ( x = fork() ) < 0 ) {
2495     pnd_log ( pndn_error, "ERROR: Couldn't fork()\n" );
2496     return ( 0 );
2497   }
2498
2499   if ( x == 0 ) {
2500     execl ( "/bin/sh", "/bin/sh", "-c", pnd_apps_exec_runline(), (char*)NULL );
2501     pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", pnd_apps_exec_runline() );
2502     return ( 0 );
2503   }
2504
2505   return ( 1 );
2506 }
2507
2508 typedef struct {
2509   SDL_Rect r;
2510   int catnum;
2511   mm_appref_t *ref;
2512 } ui_touch_t;
2513 #define MAXTOUCH 100
2514 ui_touch_t ui_touchrects [ MAXTOUCH ];
2515 unsigned char ui_touchrect_count = 0;
2516
2517 void ui_register_reset ( void ) {
2518   bzero ( ui_touchrects, sizeof(ui_touch_t)*MAXTOUCH );
2519   ui_touchrect_count = 0;
2520   return;
2521 }
2522
2523 void ui_register_tab ( unsigned char catnum, unsigned int x, unsigned int y, unsigned int w, unsigned int h ) {
2524
2525   if ( ui_touchrect_count == MAXTOUCH ) {
2526     return;
2527   }
2528
2529   ui_touchrects [ ui_touchrect_count ].r.x = x;
2530   ui_touchrects [ ui_touchrect_count ].r.y = y;
2531   ui_touchrects [ ui_touchrect_count ].r.w = w;
2532   ui_touchrects [ ui_touchrect_count ].r.h = h;
2533   ui_touchrects [ ui_touchrect_count ].catnum = catnum;
2534   ui_touchrect_count++;
2535
2536   return;
2537 }
2538
2539 void ui_register_app ( mm_appref_t *app, unsigned int x, unsigned int y, unsigned int w, unsigned int h ) {
2540
2541   if ( ui_touchrect_count == MAXTOUCH ) {
2542     return;
2543   }
2544
2545   ui_touchrects [ ui_touchrect_count ].r.x = x;
2546   ui_touchrects [ ui_touchrect_count ].r.y = y;
2547   ui_touchrects [ ui_touchrect_count ].r.w = w;
2548   ui_touchrects [ ui_touchrect_count ].r.h = h;
2549   ui_touchrects [ ui_touchrect_count ].ref = app;
2550   ui_touchrect_count++;
2551
2552   return;
2553 }
2554
2555 void ui_touch_act ( unsigned int x, unsigned int y ) {
2556
2557   unsigned char i;
2558   ui_touch_t *t;
2559
2560   for ( i = 0; i < ui_touchrect_count; i++ ) {
2561     t = &(ui_touchrects [ i ]);
2562
2563     if ( x >= t -> r.x &&
2564          x <= t -> r.x + t -> r.w &&
2565          y >= t -> r.y &&
2566          y <= t -> r.y + t -> r.h
2567        )
2568     {
2569
2570       if ( t -> ref ) {
2571         ui_selected = t -> ref;
2572         ui_push_exec();
2573       } else {
2574         if ( ui_category != t -> catnum ) {
2575           ui_selected = NULL;
2576         }
2577         ui_category = t -> catnum;
2578         render_mask |= CHANGED_CATEGORY;
2579         // rescan the dir
2580         category_fs_restock ( g_categories [ ui_category ] );
2581       }
2582
2583       break;
2584     }
2585
2586   } // for
2587
2588   return;
2589 }
2590
2591 unsigned char ui_forkexec ( char *argv[] ) {
2592   char *fooby = argv[0];
2593   int x;
2594
2595   if ( ( x = fork() ) < 0 ) {
2596     pnd_log ( pndn_error, "ERROR: Couldn't fork() for '%s'\n", fooby );
2597     return ( 0 );
2598   }
2599
2600   if ( x == 0 ) { // child
2601     execv ( fooby, argv );
2602     pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", fooby );
2603     return ( 0 );
2604   }
2605
2606   // parent, success
2607   return ( 1 );
2608 }
2609
2610 unsigned char ui_threaded_timer_create ( void ) {
2611
2612   g_timer_thread = SDL_CreateThread ( (void*)ui_threaded_timer, NULL );
2613
2614   if ( ! g_timer_thread ) {
2615     pnd_log ( pndn_error, "ERROR: Couldn't create timer thread\n" );
2616     return ( 0 );
2617   }
2618
2619   return ( 1 );
2620 }
2621
2622 int ui_threaded_timer ( pnd_disco_t *p ) {
2623
2624   // this timer's job is to ..
2625   // - do nothing for quite awhile
2626   // - on wake, post event to SDL event queue, so that main thread will check if SD insert/eject occurred
2627   // - goto 10
2628
2629   unsigned int delay_s = 2; // seconds
2630
2631   while ( 1 ) {
2632
2633     // pause...
2634     sleep ( delay_s );
2635
2636     // .. trigger SD check
2637     SDL_Event e;
2638     bzero ( &e, sizeof(SDL_Event) );
2639     e.type = SDL_USEREVENT;
2640     e.user.code = sdl_user_checksd;
2641     SDL_PushEvent ( &e );
2642
2643   } // while
2644
2645   return ( 0 );
2646 }
2647
2648 unsigned char ui_threaded_defered_preview ( pnd_disco_t *p ) {
2649
2650   if ( ! cache_preview ( p, pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 ),
2651                          pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 ) )
2652      )
2653   {
2654     pnd_log ( pndn_debug, "THREAD: Couldn't load preview pic: '%s' -> '%s'\n",
2655               IFNULL(p->title_en,"No Name"), p -> preview_pic1 );
2656   }
2657
2658   // trigger that we completed
2659   SDL_Event e;
2660   bzero ( &e, sizeof(SDL_Event) );
2661   e.type = SDL_USEREVENT;
2662   e.user.code = sdl_user_finishedpreview;
2663   e.user.data1 = p;
2664   SDL_PushEvent ( &e );
2665
2666   return ( 0 );
2667 }
2668
2669 SDL_Thread *g_icon_thread = NULL;
2670 void ui_post_scan ( void ) {
2671
2672   // if deferred icon load, kick off the thread now
2673   if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_icons_later", 0 ) == 1 ) {
2674
2675     g_icon_thread = SDL_CreateThread ( (void*)ui_threaded_defered_icon, NULL );
2676
2677     if ( ! g_icon_thread ) {
2678       pnd_log ( pndn_error, "ERROR: Couldn't create icon thread\n" );
2679     }
2680
2681   } // deferred icon load
2682
2683   // reset view
2684   ui_selected = NULL;
2685   ui_rows_scrolled_down = 0;
2686   // set back to first tab, to be safe
2687   ui_category = 0;
2688   ui_catshift = 0;
2689
2690   // do we have a preferred category to jump to?
2691   char *dc = pnd_conf_get_as_char ( g_conf, "categories.default_cat" );
2692   if ( dc ) {
2693
2694     // attempt to find default cat; if we do find it, select it; otherwise
2695     // default behaviour will pick first cat (ie: usually All)
2696     unsigned int i;
2697     for ( i = 0; i < g_categorycount; i++ ) {
2698       if ( strcasecmp ( g_categories [ i ] -> catname, dc ) == 0 ) {
2699         ui_category = i;
2700         // ensure visibility
2701         unsigned int screen_width = ui_display_context.screen_width;
2702         unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
2703         if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
2704           ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
2705         }
2706         break;
2707       }
2708     }
2709
2710     if ( i == g_categorycount ) {
2711       pnd_log ( pndn_warning, "  User defined default category '%s' but not found, so using default behaviour\n", dc );
2712     }
2713
2714   } // default cat
2715
2716   // if we're sent right to a dirbrowser tab, restock it now (normally we restock on entry)
2717   if ( g_categories [ ui_category ] -> fspath ) {
2718     printf ( "Restock on start: '%s'\n", g_categories [ ui_category ] -> fspath );
2719     category_fs_restock ( g_categories [ ui_category ] );
2720   }
2721
2722   // redraw all
2723   render_mask |= CHANGED_EVERYTHING;
2724
2725   return;
2726 }
2727
2728 unsigned char ui_threaded_defered_icon ( void *p ) {
2729   extern pnd_box_handle g_active_apps;
2730   pnd_box_handle h = g_active_apps;
2731
2732   unsigned char maxwidth, maxheight;
2733   maxwidth = pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 );
2734   maxheight = pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_height", 50 );
2735
2736   pnd_disco_t *iter = pnd_box_get_head ( h );
2737
2738   while ( iter ) {
2739
2740     // cache it
2741     if ( iter -> pnd_icon_pos &&
2742          ! cache_icon ( iter, maxwidth, maxheight ) )
2743     {
2744       pnd_log ( pndn_warning, "  Couldn't load icon: '%s'\n", IFNULL(iter->title_en,"No Name") );
2745     } else {
2746
2747       // trigger that we completed
2748       SDL_Event e;
2749       bzero ( &e, sizeof(SDL_Event) );
2750       e.type = SDL_USEREVENT;
2751       e.user.code = sdl_user_finishedicon;
2752       SDL_PushEvent ( &e );
2753
2754       //pnd_log ( pndn_warning, "  Finished deferred load icon: '%s'\n", IFNULL(iter->title_en,"No Name") );
2755       usleep ( pnd_conf_get_as_int_d ( g_conf, "minimenu.defer_icon_us", 50000 ) );
2756
2757     }
2758
2759     // next
2760     iter = pnd_box_get_next ( iter );
2761   } // while
2762
2763   return ( 0 );
2764 }
2765
2766 void ui_show_hourglass ( unsigned char updaterect ) {
2767
2768   SDL_Rect dest;
2769   SDL_Surface *s = g_imagecache [ IMG_HOURGLASS ].i;
2770
2771   dest.x = ( 800 - s -> w ) / 2;
2772   dest.y = ( 480 - s -> h ) / 2;
2773
2774   SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, &dest );
2775
2776   if ( updaterect ) {
2777     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2778   }
2779
2780   return;
2781 }
2782
2783 unsigned char ui_pick_skin ( void ) {
2784 #define MAXSKINS 10
2785   char *skins [ MAXSKINS ];
2786   unsigned char iter;
2787
2788   char *searchpath = pnd_conf_get_as_char ( g_conf, "minimenu.skin_searchpath" );
2789   char tempname [ 100 ];
2790
2791   iter = 0;
2792
2793   skins [ iter++ ] = "No skin change";
2794
2795   SEARCHPATH_PRE
2796   {
2797     DIR *d = opendir ( buffer );
2798
2799     if ( d ) {
2800       struct dirent *dd;
2801
2802       while ( ( dd = readdir ( d ) ) ) {
2803
2804         if ( dd -> d_name [ 0 ] == '.' ) {
2805           // ignore
2806         } else if ( ( dd -> d_type == DT_DIR || dd -> d_type == DT_UNKNOWN ) &&
2807                     iter < MAXSKINS )
2808         {
2809           snprintf ( tempname, 100, "Skin: %s", dd -> d_name );
2810           skins [ iter++ ] = strdup ( tempname );
2811         }
2812
2813       }
2814
2815       closedir ( d );
2816     }
2817
2818   }
2819   SEARCHPATH_POST
2820
2821   int sel = ui_modal_single_menu ( skins, iter, "Skins", "Enter to select; other to return." );
2822
2823   // did they pick one?
2824   if ( sel > 0 ) {
2825     FILE *f;
2826
2827     char *s = strdup ( pnd_conf_get_as_char ( g_conf, "minimenu.skin_selected" ) );
2828     s = pnd_expand_tilde ( s );
2829
2830     f = fopen ( s, "w" );
2831
2832     free ( s );
2833
2834     if ( f ) {
2835       fprintf ( f, "%s\n", skins [ sel ] + 6 );
2836       fclose ( f );
2837     }
2838
2839     return ( 1 );
2840   }
2841
2842   return ( 0 );
2843 }
2844
2845 void ui_aboutscreen ( char *textpath ) {
2846 #define PIXELW 7
2847 #define PIXELH 7
2848 #define MARGINW 3
2849 #define MARGINH 3
2850 #define SCRW 800
2851 #define SCRH 480
2852 #define ROWS SCRH / ( PIXELH + MARGINH )
2853 #define COLS SCRW / ( PIXELW + MARGINW )
2854
2855   unsigned char pixelboard [ ROWS * COLS ]; // pixel heat
2856   bzero ( pixelboard, ROWS * COLS );
2857
2858   SDL_Surface *rtext;
2859   SDL_Rect r;
2860
2861   SDL_Color rtextc = { 200, 200, 200, 100 };
2862
2863   // pixel scroller
2864   char *textloop [ 500 ];
2865   unsigned int textmax = 0;
2866   bzero ( textloop, 500 * sizeof(char*) );
2867
2868   // cursor scroller
2869   char cbuffer [ 50000 ];
2870   bzero ( cbuffer, 50000 );
2871   unsigned int crevealed = 0;
2872
2873   FILE *f = fopen ( textpath, "r" );
2874
2875   if ( ! f ) {
2876     pnd_log ( pndn_error, "ERROR: Couldn't open about text: %s\n", textpath );
2877     return;
2878   }
2879
2880   char textbuf [ 100 ];
2881   while ( fgets ( textbuf, 100, f ) ) {
2882
2883     // add to full buffer
2884     strncat ( cbuffer, textbuf, 50000 );
2885
2886     // chomp
2887     if ( strchr ( textbuf, '\n' ) ) {
2888       * strchr ( textbuf, '\n' ) = '\0';
2889     }
2890
2891     // add to pixel loop
2892     if ( 1||textbuf [ 0 ] ) {
2893       textloop [ textmax ] = strdup ( textbuf );
2894       textmax++;
2895     }
2896
2897   } // while fgets
2898
2899   fclose ( f );
2900
2901   unsigned int textiter = 0;
2902   while ( textiter < textmax ) {
2903     char *text = textloop [ textiter ];
2904
2905     rtext = NULL;
2906     if ( text [ 0 ] ) {
2907       // render to surface
2908       rtext = TTF_RenderText_Blended ( g_grid_font, text, rtextc );
2909
2910       // render font to pixelboard
2911       unsigned int px, py;
2912       unsigned char *ph;
2913       unsigned int *pixels = rtext -> pixels;
2914       unsigned char cr, cg, cb, ca;
2915       for ( py = 0; py < rtext -> h; py ++ ) {
2916         for ( px = 0; px < ( rtext -> w > COLS ? COLS : rtext -> w ); px++ ) {
2917
2918           SDL_GetRGBA ( pixels [ ( py * rtext -> pitch / 4 ) + px ],
2919                         rtext -> format, &cr, &cg, &cb, &ca );
2920
2921           if ( ca != 0 ) {
2922
2923             ph = pixelboard + ( /*y offset */ 30 * COLS ) + ( py * COLS ) + px /* / 2 */;
2924
2925             if ( *ph < 100 ) {
2926               *ph = 100;
2927             }
2928
2929             ca /= 5;
2930             if ( *ph + ca < 250 ) {
2931               *ph += ca;
2932             }
2933
2934           } // got a pixel?
2935
2936         } // x
2937       } // y
2938
2939     } // got text?
2940
2941     unsigned int runcount = 10;
2942     while ( runcount-- ) {
2943
2944       // clear display
2945       SDL_FillRect( sdl_realscreen, NULL /* whole */, 0 );
2946
2947       // render pixelboard
2948       unsigned int x, y;
2949       unsigned int c;
2950       for ( y = 0; y < ROWS; y++ ) {
2951         for ( x = 0; x < COLS; x++ ) {
2952
2953           if ( 1||pixelboard [ ( y * COLS ) + x ] ) {
2954
2955             // position
2956             r.x = x * ( PIXELW + MARGINW );
2957             r.y = y * ( PIXELH + MARGINH );
2958             r.w = PIXELW;
2959             r.h = PIXELH;
2960             // heat -> colour
2961             c = SDL_MapRGB ( sdl_realscreen -> format, 100 /* r */, 0 /* g */, pixelboard [ ( y * COLS ) + x ] );
2962             // render
2963             SDL_FillRect( sdl_realscreen, &r /* whole */, c );
2964
2965           }
2966
2967         } // x
2968       } // y
2969
2970       // cool pixels
2971       unsigned char *pc = pixelboard;
2972       for ( y = 0; y < ROWS; y++ ) {
2973         for ( x = 0; x < COLS; x++ ) {
2974
2975           if ( *pc > 10 ) {
2976             (*pc) -= 3;
2977           }
2978
2979           pc++;
2980         } // x
2981       } // y
2982
2983       // slide pixels upwards
2984       memmove ( pixelboard, pixelboard + COLS, ( COLS * ROWS ) - COLS );
2985
2986       // render actual readable text
2987       {
2988
2989         // display up to cursor
2990         SDL_Rect dest;
2991         unsigned int cdraw = 0;
2992         SDL_Surface *cs;
2993         char ctb [ 2 ];
2994
2995         if ( crevealed > 200 ) {
2996           cdraw = crevealed - 200;
2997         }
2998
2999         dest.x = 400;
3000         dest.y = 20;
3001
3002         for ( ; cdraw < crevealed; cdraw++ ) {
3003           ctb [ 0 ] = cbuffer [ cdraw ];
3004           ctb [ 1 ] = '\0';
3005           // move over or down
3006           if ( cbuffer [ cdraw ] == '\n' ) {
3007             // EOL
3008             dest.x = 400;
3009             dest.y += 14;
3010
3011             if ( dest.y > 450 ) {
3012               dest.y = 450;
3013             }
3014
3015           } else {
3016             // draw the char
3017             cs = TTF_RenderText_Blended ( g_tab_font, ctb, rtextc );
3018             if ( cs ) {
3019               SDL_BlitSurface ( cs, NULL /* all */, sdl_realscreen, &dest );
3020               SDL_FreeSurface ( cs );
3021               // over
3022               dest.x += cs -> w;
3023             }
3024           }
3025
3026         }
3027
3028         dest.w = 10;
3029         dest.h = 20;
3030         SDL_FillRect ( sdl_realscreen, &dest /* whole */, 220 );
3031
3032         // increment cursor to next character
3033         if ( cbuffer [ crevealed ] != '\0' ) {
3034           crevealed++;
3035         }
3036
3037       } // draw cursor text
3038
3039       // reveal
3040       //
3041       SDL_UpdateRect ( sdl_realscreen, 0, 0, 0, 0 ); // whole screen
3042
3043       usleep ( 50000 );
3044
3045       // any button? if so, about
3046       {
3047         SDL_PumpEvents();
3048
3049         SDL_Event e;
3050
3051         if ( SDL_PeepEvents ( &e, 1, SDL_GETEVENT, SDL_EVENTMASK(/*SDL_KEYUP|*/SDL_KEYDOWN) ) > 0 ) {
3052           return;
3053         }
3054
3055       }
3056
3057     } // while cooling
3058
3059     if ( rtext ) {
3060       SDL_FreeSurface ( rtext );
3061     }
3062
3063     textiter++;
3064   } // while more text
3065
3066   // free up
3067   unsigned int i;
3068   for ( i = 0; i < textmax; i++ ) {
3069     if ( textloop [ i ] ) {
3070       free ( textloop [ i ] );
3071       textloop [ i ] = 0;
3072     }
3073   }
3074
3075   return;
3076 }
3077
3078 void ui_revealscreen ( void ) {
3079   char *labels [ 500 ];
3080   unsigned int labelmax = 0;
3081   unsigned int i;
3082   char fulllabel [ 200 ];
3083
3084   if ( ! category_count ( CFHIDDEN ) ) {
3085     return; // nothing to do
3086   }
3087
3088   // switch to hidden categories
3089   category_publish ( CFHIDDEN, NULL );
3090
3091   // build up labels to show in menu
3092   for ( i = 0; i < g_categorycount; i++ ) {
3093
3094     if ( g_categories [ i ] -> parent_catname ) {
3095       sprintf ( fulllabel, "%s [%s]", g_categories [ i ] -> catname, g_categories [ i ] -> parent_catname );
3096     } else {
3097       sprintf ( fulllabel, "%s", g_categories [ i ] -> catname );
3098     }
3099
3100     labels [ labelmax++ ] = strdup ( fulllabel );
3101   }
3102
3103   // show menu
3104   int sel = ui_modal_single_menu ( labels, labelmax, "Temporary Category Reveal",
3105                                    "Enter to select; other to return." );
3106
3107   // if selected, try to set this guy to visible
3108   if ( sel >= 0 ) {
3109
3110     // fix up category name, if its been hacked
3111     if ( strchr ( g_categories [ sel ] -> catname, '.' ) ) {
3112       char *t = g_categories [ sel ] -> catname;
3113       g_categories [ sel ] -> catname = strdup ( strchr ( g_categories [ sel ] -> catname, '.' ) + 1 );
3114       free ( t );
3115     }
3116
3117     // reflag this guy to be visible
3118     g_categories [ sel ] -> catflags = CFNORMAL;
3119
3120     // switch to the new category.. cache name.
3121     char *switch_to_name = g_categories [ sel ] -> catname;
3122
3123     // republish categories
3124     category_publish ( CFNORMAL, NULL );
3125
3126     // switch to the new category.. with the cached name!
3127     ui_category = category_index ( switch_to_name );
3128
3129     // ensure visibility
3130     unsigned int screen_width = ui_display_context.screen_width;
3131     unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
3132     if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
3133       ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
3134     }
3135
3136     // redraw tabs
3137     render_mask |= CHANGED_CATEGORY;
3138   }
3139
3140   for ( i = 0; i < g_categorycount; i++ ) {
3141     free ( labels [ i ] );
3142   }
3143
3144   return;
3145 }
3146
3147 void ui_recache_context ( ui_context_t *c ) {
3148
3149   c -> screen_width = pnd_conf_get_as_int_d ( g_conf, "display.screen_width", 800 );
3150
3151   c -> font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
3152   c -> font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
3153   c -> font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
3154   c -> font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
3155
3156   c -> grid_offset_x = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_x" );
3157   c -> grid_offset_y = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_y" );
3158
3159   c -> icon_offset_x = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_x" );
3160   c -> icon_offset_y = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_y" );
3161   c -> icon_max_width = pnd_conf_get_as_int ( g_conf, "grid.icon_max_width" );
3162   c -> icon_max_height = pnd_conf_get_as_int ( g_conf, "grid.icon_max_height" );
3163   c -> sel_icon_offset_x = pnd_conf_get_as_int_d ( g_conf, "grid.sel_offoffset_x", 0 );
3164   c -> sel_icon_offset_y = pnd_conf_get_as_int_d ( g_conf, "grid.sel_offoffset_y", 0 );
3165
3166   c -> text_width = pnd_conf_get_as_int ( g_conf, "grid.text_width" );
3167   c -> text_clip_x = pnd_conf_get_as_int ( g_conf, "grid.text_clip_x" );
3168   c -> text_offset_x = pnd_conf_get_as_int ( g_conf, "grid.text_offset_x" );
3169   c -> text_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_offset_y" );
3170   c -> text_hilite_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_hilite_offset_y" );
3171
3172   c -> row_max = pnd_conf_get_as_int_d ( g_conf, "grid.row_max", 4 );
3173   c -> col_max = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 );
3174
3175   c -> cell_width = pnd_conf_get_as_int ( g_conf, "grid.cell_width" );
3176   c -> cell_height = pnd_conf_get_as_int ( g_conf, "grid.cell_height" );
3177
3178   c -> arrow_bar_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x", 450 );
3179   c -> arrow_bar_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y", 100 );
3180   c -> arrow_bar_clip_w = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_w", 10 );
3181   c -> arrow_bar_clip_h = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_h", 100 );
3182   c -> arrow_up_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x", 450 );
3183   c -> arrow_up_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y", 80 );
3184   c -> arrow_down_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x", 450 );
3185   c -> arrow_down_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y", 80 );
3186
3187   // font colour
3188   SDL_Color tmp = { c -> font_rgba_r, c -> font_rgba_g, c -> font_rgba_b, c -> font_rgba_a };
3189   c -> fontcolor = tmp;
3190
3191   // now that we've got 'normal' (detail pane shown) param's, lets check if detail pane
3192   // is hidden; if so, override some values with those alternate skin values where possible.
3193   if ( ui_detail_hidden ) {
3194     // if detail panel is hidden, and theme cannot support it, unhide the bloody thing. (This may help
3195     // when someone is amid theme hacking or changing.)
3196     if ( ! ui_is_detail_hideable() ) {
3197       ui_detail_hidden = 0;
3198     }
3199
3200     // still hidden?
3201     if ( ui_detail_hidden ) {
3202
3203       c -> row_max = pnd_conf_get_as_int_d ( g_conf, "grid.row_max_w", c -> row_max );
3204       c -> col_max = pnd_conf_get_as_int_d ( g_conf, "grid.col_max_w", c -> col_max );
3205
3206       c -> cell_width = pnd_conf_get_as_int_d ( g_conf, "grid.cell_width_w", c -> cell_width );
3207       c -> cell_height = pnd_conf_get_as_int_d ( g_conf, "grid.cell_height_w", c -> cell_height );
3208
3209       c -> arrow_bar_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x_w", 450 );
3210       c -> arrow_bar_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y_w", 100 );
3211       c -> arrow_up_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x_w", 450 );
3212       c -> arrow_up_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y_w", 80 );
3213       c -> arrow_down_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x_w", 450 );
3214       c -> arrow_down_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y_w", 80 );
3215
3216     } // if detail hidden.. still.
3217
3218   } // if detail hidden
3219
3220   return;
3221 }
3222
3223 unsigned char ui_is_detail_hideable ( void ) {
3224
3225   // if skin has a bit of info for wide-mode, we assume wide-mode is available
3226   if ( pnd_conf_get_as_char ( g_conf, "grid.row_max_w" ) != NULL ) {
3227     return ( 1 );
3228   }
3229
3230   // else not!
3231   return ( 0 );
3232 }
3233
3234 void ui_toggle_detail_pane ( void ) {
3235
3236   // no bitmask trickery here; I like it to be stand-out obvious at 3am.
3237
3238   if ( ui_detail_hidden ) {
3239     ui_detail_hidden = 0;
3240   } else {
3241     ui_detail_hidden = 1;
3242   }
3243
3244   // repull skin config
3245   ui_recache_context ( &ui_display_context );
3246
3247   // redraw
3248   render_mask |= CHANGED_EVERYTHING;
3249
3250   return;
3251 }
3252
3253 void ui_menu_context ( mm_appref_t *a ) {
3254
3255   unsigned char rescan_apps = 0;
3256   unsigned char context_alive = 1;
3257
3258   enum {
3259     context_done = 0,
3260     context_file_info,
3261     context_file_delete,
3262     context_app_info,
3263     context_app_hide,
3264     context_app_recategorize,
3265     context_app_recategorize_sub,
3266     context_app_rename,
3267     context_app_cpuspeed,
3268     context_app_run,
3269     context_menu_max
3270   };
3271
3272   char *verbiage[] = {
3273     "Done (return to grid)",      // context_done
3274     "Get info about file/dir",    // context_file_info
3275     "Delete file/dir",            // context_file_delete
3276     "Get info",                   // context_app_info
3277     "Hide application",           //             hide
3278     "Recategorize",               //             recategorize
3279     "Recategorize subcategory",   //             recategorize
3280     "Change displayed title",     //             rename
3281     "Set CPU speed for launch",   //             cpuspeed
3282     "Run application"             //             run
3283   };
3284
3285   unsigned short int menu [ context_menu_max ];
3286   char *menustring [ context_menu_max ];
3287   unsigned char menumax = 0;
3288
3289   // ++ done
3290   menu [ menumax ] = context_done; menustring [ menumax++ ] = verbiage [ context_done ];
3291
3292   // hook up appropriate menu options based on tab-type and object-type
3293   if ( g_categories [ ui_category ] -> fspath ) {
3294     return; // TBD
3295     menu [ menumax ] = context_file_info; menustring [ menumax++ ] = verbiage [ context_file_info ];
3296     menu [ menumax ] = context_file_delete; menustring [ menumax++ ] = verbiage [ context_file_delete ];
3297   } else {
3298     //menu [ menumax ] = context_app_info; menustring [ menumax++ ] = verbiage [ context_app_info ];
3299     menu [ menumax ] = context_app_hide; menustring [ menumax++ ] = verbiage [ context_app_hide ];
3300     menu [ menumax ] = context_app_recategorize; menustring [ menumax++ ] = verbiage [ context_app_recategorize ];
3301     menu [ menumax ] = context_app_recategorize_sub; menustring [ menumax++ ] = verbiage [ context_app_recategorize_sub ];
3302     menu [ menumax ] = context_app_rename; menustring [ menumax++ ] = verbiage [ context_app_rename ];
3303     menu [ menumax ] = context_app_cpuspeed; menustring [ menumax++ ] = verbiage [ context_app_cpuspeed ];
3304     menu [ menumax ] = context_app_run; menustring [ menumax++ ] = verbiage [ context_app_run ];
3305   }
3306
3307   // operate the menu
3308   while ( context_alive ) {
3309
3310     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 */ );
3311
3312     if ( sel < 0 ) {
3313       context_alive = 0;
3314
3315     } else {
3316
3317       switch ( menu [ sel ] ) {
3318
3319       case context_done:
3320         context_alive = 0;
3321         break;
3322
3323       case context_file_info:
3324         break;
3325
3326       case context_file_delete:
3327         //ui_menu_twoby ( "Delete - Are you sure?", "B/enter; other to cancel", "Delete", "Do not delete" );
3328         break;
3329
3330       case context_app_info:
3331         break;
3332
3333       case context_app_hide:
3334         {
3335           // determine key
3336           char confkey [ 1000 ];
3337           snprintf ( confkey, 999, "%s.%s", "appshow", a -> ref -> unique_id );
3338
3339           // turn app 'off'
3340           pnd_conf_set_char ( g_conf, confkey, "0" );
3341
3342           // write conf, so it will take next time
3343           conf_write ( g_conf, conf_determine_location ( g_conf ) );
3344
3345           // request rescan and wrap up
3346           rescan_apps++;
3347           context_alive = 0; // nolonger visible, so lets just get out
3348
3349         }
3350     
3351         break;
3352
3353       case context_app_recategorize:
3354         {
3355           char *opts [ 250 ];
3356           unsigned char optmax = 0;
3357           unsigned char i;
3358
3359           i = 0;
3360           while ( 1 ) {
3361
3362             if ( ! freedesktop_complete [ i ].cat ) {
3363               break;
3364             }
3365
3366             if ( ! freedesktop_complete [ i ].parent_cat ) {
3367               opts [ optmax++ ] = freedesktop_complete [ i ].cat;
3368             }
3369
3370             i++;
3371           } // while
3372
3373           char prompt [ 101 ];
3374           snprintf ( prompt, 100, "Pick category [%s]", a -> ref -> main_category ? a -> ref -> main_category : "NoParentCategory" );
3375
3376           int sel = ui_modal_single_menu ( opts, optmax, prompt /*"Select parent category"*/, "Enter to select; other to skip." );
3377
3378           if ( sel >= 0 ) {
3379             char confirm [ 1001 ];
3380             snprintf ( confirm, 1000, "Confirm: %s", opts [ sel ] );
3381
3382             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm categorization", "Do not set category" ) == 1 ) {
3383               ovr_replace_or_add ( a, "maincategory", opts [ sel ] );
3384               rescan_apps++;
3385             }
3386
3387           }
3388
3389         }
3390         break;
3391
3392       case context_app_recategorize_sub:
3393         {
3394           char *opts [ 250 ];
3395           unsigned char optmax = 0;
3396           unsigned char i;
3397
3398           i = 0;
3399           while ( 1 ) {
3400
3401             if ( ! freedesktop_complete [ i ].cat ) {
3402               break;
3403             }
3404
3405             if ( freedesktop_complete [ i ].parent_cat ) {
3406               opts [ optmax++ ] = freedesktop_complete [ i ].cat;
3407             }
3408
3409             i++;
3410           } // while
3411
3412           char prompt [ 101 ];
3413           snprintf ( prompt, 100, "Pick subcategory [%s]", a -> ref -> main_category1 ? a -> ref -> main_category1 : "NoSubcategory" );
3414
3415           int sel = ui_modal_single_menu ( opts, optmax, prompt /*"Select subcategory"*/, "Enter to select; other to skip." );
3416
3417           if ( sel >= 0 ) {
3418             char confirm [ 1001 ];
3419             snprintf ( confirm, 1000, "Confirm: %s", opts [ sel ] );
3420
3421             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm sub-categorization", "Do not set sub-category" ) == 1 ) {
3422               ovr_replace_or_add ( a, "maincategorysub1", opts [ sel ] );
3423               rescan_apps++;
3424             }
3425
3426           }
3427
3428         }
3429         break;
3430
3431       case context_app_rename:
3432         {
3433           char namebuf [ 101 ];
3434           unsigned char changed;
3435
3436           changed = ui_menu_get_text_line ( "Rename application", "Use keyboard; Enter when done.",
3437                                             a -> ref -> title_en ? a -> ref -> title_en : "blank", namebuf, 30, 0 /* alphanumeric */ );
3438
3439           if ( changed ) {
3440             char confirm [ 1001 ];
3441             snprintf ( confirm, 1000, "Confirm: %s", namebuf );
3442
3443             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm rename", "Do not rename" ) == 1 ) {
3444               ovr_replace_or_add ( a, "title", namebuf );
3445               rescan_apps++;
3446             }
3447
3448           }
3449
3450         }
3451
3452         break;
3453
3454       case context_app_cpuspeed:
3455         {
3456           char namebuf [ 101 ];
3457           unsigned char changed;
3458
3459           changed = ui_menu_get_text_line ( "Specify runspeed", "Use keyboard; Enter when done.",
3460                                             a -> ref -> clockspeed ? a -> ref -> clockspeed : "500", namebuf, 6, 1 /* numeric */ );
3461
3462           if ( changed ) {
3463             char confirm [ 1001 ];
3464             snprintf ( confirm, 1000, "Confirm: %s", namebuf );
3465
3466             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm clockspeed", "Do not set" ) == 1 ) {
3467               ovr_replace_or_add ( a, "clockspeed", namebuf );
3468               rescan_apps++;
3469             }
3470
3471           }
3472
3473         }
3474
3475         break;
3476
3477       case context_app_run:
3478         ui_push_exec();
3479         break;
3480
3481       default:
3482         return;
3483
3484       } // switch
3485
3486     } // if useful return
3487
3488   } // menu is alive?
3489
3490   // rescan apps?
3491   if ( rescan_apps ) {
3492     applications_free();
3493     applications_scan();
3494   }
3495
3496   return;
3497 }
3498
3499 unsigned char ui_menu_twoby ( char *title, char *footer, char *one, char *two ) {
3500   char *opts [ 3 ];
3501   opts [ 0 ] = one;
3502   opts [ 1 ] = two;
3503   int sel = ui_modal_single_menu ( opts, 2, title, footer );
3504   if ( sel < 0 ) {
3505     return ( 0 );
3506   }
3507   return ( sel + 1 );
3508 }
3509
3510 unsigned char ui_menu_get_text_line ( char *title, char *footer, char *initialvalue,
3511                                       char *r_buffer, unsigned char maxlen, unsigned char numbersonlyp )
3512 {
3513   SDL_Rect rects [ 40 ];
3514   SDL_Rect *dest = rects;
3515   SDL_Rect src;
3516   SDL_Surface *rtext;
3517
3518   char hacktext [ 1024 ];
3519   unsigned char shifted = 0;
3520
3521   bzero ( rects, sizeof(SDL_Rect) * 40 );
3522
3523   if ( initialvalue ) {
3524     strncpy ( r_buffer, initialvalue, maxlen );
3525   } else {
3526     bzero ( r_buffer, maxlen );
3527   }
3528
3529   while ( 1 ) {
3530
3531     // clear
3532     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
3533     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
3534     dest -> w = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> w;
3535     dest -> h = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h;
3536     SDL_FillRect( sdl_realscreen, dest, 0 );
3537
3538     // show dialog background
3539     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
3540       src.x = 0;
3541       src.y = 0;
3542       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> w;
3543       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> h;
3544       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
3545       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
3546       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
3547       dest++;
3548     }
3549
3550     // show dialog frame
3551     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
3552       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
3553       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
3554       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
3555       dest++;
3556     }
3557
3558     // show header
3559     if ( title ) {
3560       rtext = TTF_RenderText_Blended ( g_tab_font, title, 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 ) + 20;
3563       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
3564       SDL_FreeSurface ( rtext );
3565       dest++;
3566     }
3567
3568     // show footer
3569     if ( footer ) {
3570       rtext = TTF_RenderText_Blended ( g_tab_font, footer, ui_display_context.fontcolor );
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 ) +
3573         ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h
3574         - 60;
3575       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
3576       SDL_FreeSurface ( rtext );
3577       dest++;
3578     }
3579
3580     // show text line - and embed cursor
3581     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
3582     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 40 + ( 20 * ( 0/*i*/ + 1 - 0/*first_visible*/ ) );
3583
3584     strncpy ( hacktext, r_buffer, 1000 );
3585     strncat ( hacktext, "\n", 1000 ); // add [] in most fonts
3586
3587     rtext = TTF_RenderText_Blended ( g_tab_font, hacktext, ui_display_context.fontcolor );
3588     SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
3589     SDL_FreeSurface ( rtext );
3590     dest++;
3591
3592     // update all the rects and send it all to sdl
3593     SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
3594     dest = rects;
3595
3596     // check for input
3597     SDL_Event event;
3598     while ( SDL_WaitEvent ( &event ) ) {
3599
3600       switch ( event.type ) {
3601
3602       case SDL_KEYUP:
3603         if ( event.key.keysym.sym == SDLK_LSHIFT || event.key.keysym.sym == SDLK_RSHIFT ) {
3604           shifted = 0;
3605         }
3606         break;
3607
3608       case SDL_KEYDOWN:
3609
3610         if ( event.key.keysym.sym == SDLK_LEFT || event.key.keysym.sym == SDLK_BACKSPACE ) {
3611           char *eol = strchr ( r_buffer, '\0' );
3612           *( eol - 1 ) = '\0';
3613         } else if ( event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_END ) { // return, or "B"
3614           return ( 1 );
3615
3616         } else if ( event.key.keysym.sym == SDLK_LSHIFT || event.key.keysym.sym == SDLK_RSHIFT ) {
3617           shifted = 1;
3618
3619         } else if ( event.key.keysym.sym == SDLK_ESCAPE ||
3620                     event.key.keysym.sym == SDLK_PAGEUP ||
3621                     event.key.keysym.sym == SDLK_PAGEDOWN ||
3622                     event.key.keysym.sym == SDLK_HOME
3623                   )
3624         {
3625           return ( 0 );
3626
3627         } else {
3628
3629           if ( isprint(event.key.keysym.sym) ) {
3630
3631             unsigned char good = 1;
3632
3633             if ( numbersonlyp && ( ! isdigit(event.key.keysym.sym) ) ) {
3634               good = 0;
3635             }
3636
3637             if ( maxlen && strlen(r_buffer) >= maxlen ) {
3638               good = 0;
3639             }
3640
3641             if ( good ) {
3642               char b [ 2 ] = { '\0', '\0' };
3643               if ( shifted ) {
3644                 b [ 0 ] = toupper ( event.key.keysym.sym );
3645               } else {
3646                 b [ 0 ] = event.key.keysym.sym;
3647               }
3648               strncat ( r_buffer, b, maxlen );
3649             } // good?
3650
3651           } // printable?
3652
3653         }
3654
3655         break;
3656
3657       } // switch
3658
3659       break;
3660
3661     } // while waiting for input
3662
3663   } // while
3664   
3665   return ( 0 );
3666 }
3667
3668 unsigned char ovr_replace_or_add ( mm_appref_t *a, char *keybase, char *newvalue ) {
3669   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 );
3670
3671   char fullpath [ PATH_MAX ];
3672
3673   sprintf ( fullpath, "%s/%s", a -> ref -> object_path, a -> ref -> object_filename );
3674   char *dot = strrchr ( fullpath, '.' );
3675   if ( dot ) {
3676     sprintf ( dot, PXML_SAMEPATH_OVERRIDE_FILEEXT );
3677   } else {
3678     fprintf ( stderr, "ERROR: Bad pnd-path in disco_t! %s\n", fullpath );
3679     return ( 0 );
3680   }
3681
3682   struct stat statbuf;
3683
3684   if ( stat ( fullpath, &statbuf ) == 0 ) {
3685     // file exists
3686     pnd_conf_handle h;
3687
3688     h = pnd_conf_fetch_by_path ( fullpath );
3689
3690     if ( ! h ) {
3691       return ( 0 ); // fail!
3692     }
3693
3694     char key [ 101 ];
3695     snprintf ( key, 100, "Application-%u.%s", a -> ref -> subapp_number, keybase );
3696
3697     pnd_conf_set_char ( h, key, newvalue );
3698
3699     return ( pnd_conf_write ( h, fullpath ) );
3700
3701   } else {
3702     // file needs to be created - easy!
3703
3704     FILE *f = fopen ( fullpath, "w" );
3705
3706     if ( f ) {
3707       fprintf ( f, "Application-%u.%s\t%s\n", a -> ref -> subapp_number, keybase, newvalue );
3708       fclose ( f );
3709
3710     } else {
3711       return ( 0 ); // fail!
3712     }
3713
3714   } // new or used?
3715
3716   return ( 1 );
3717 }