aeb16134d548be49336eb0e832b628c00928979e
[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     } // r_detail && selected?
947
948   } // r_detail
949
950   // extras
951   //
952
953   // battery
954   if ( render_jobs_b & R_BG ) {
955     static int last_battlevel = 0;
956     static unsigned char batterylevel = 0;
957     char buffer [ 100 ];
958
959     if ( time ( NULL ) - last_battlevel > 60 ) {
960       batterylevel = pnd_device_get_battery_gauge_perc();
961       last_battlevel = time ( NULL );
962     }
963
964     sprintf ( buffer, "Battery: %u%%", batterylevel );
965
966     SDL_Surface *rtext;
967     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
968     dest -> x = pnd_conf_get_as_int_d ( g_conf, "display.battery_x", 20 );
969     dest -> y = pnd_conf_get_as_int_d ( g_conf, "display.battery_y", 450 );
970     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
971     SDL_FreeSurface ( rtext );
972     dest++;
973   }
974
975   // hints
976   if ( pnd_conf_get_as_char ( g_conf, "display.hintline" ) ) {
977     char *buffer;
978     unsigned int hintx, hinty;
979     hintx = pnd_conf_get_as_int_d ( g_conf, "display.hint_x", 40 );
980     hinty = pnd_conf_get_as_int_d ( g_conf, "display.hint_y", 450 );
981     static unsigned int lastwidth = 3000;
982
983     if ( ui_selected && ui_selected -> ref -> info_filename ) {
984       buffer = "Documentation - hit Y";
985     } else {
986       buffer = pnd_conf_get_as_char ( g_conf, "display.hintline" );
987     }
988
989     SDL_Surface *rtext;
990     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
991
992     // clear bg
993     if ( ! ( render_jobs_b & R_BG ) ) {
994       src.x = hintx;
995       src.y = hinty;
996       src.w = lastwidth;
997       src.h = rtext -> h;
998       dest -> x = hintx;
999       dest -> y = hinty;
1000       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i, &src, sdl_realscreen, dest );
1001       dest++;
1002       lastwidth = rtext -> w;
1003     }
1004
1005     // now render text
1006     dest -> x = hintx;
1007     dest -> y = hinty;
1008     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
1009     SDL_FreeSurface ( rtext );
1010     dest++;
1011   }
1012
1013   // clock time
1014   if ( render_jobs_b & R_BG &&
1015        pnd_conf_get_as_int_d ( g_conf, "display.clock_x", -1 ) != -1 )
1016   {
1017     char buffer [ 50 ];
1018
1019     time_t t = time ( NULL );
1020     struct tm *tm = localtime ( &t );
1021     strftime ( buffer, 50, "%a %H:%M %F", tm );
1022
1023     SDL_Surface *rtext;
1024     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
1025     dest -> x = pnd_conf_get_as_int_d ( g_conf, "display.clock_x", 700 );
1026     dest -> y = pnd_conf_get_as_int_d ( g_conf, "display.clock_y", 450 );
1027     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
1028     SDL_FreeSurface ( rtext );
1029     dest++;
1030   }
1031
1032   // update all the rects and send it all to sdl
1033   // - at this point, we could probably just do 1 rect, of the
1034   //   whole screen, and be faster :/
1035   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
1036
1037 } // ui_render
1038
1039 void ui_process_input ( pnd_dbusnotify_handle dbh, pnd_notify_handle nh ) {
1040   SDL_Event event;
1041
1042   unsigned char ui_event = 0; // if we get a ui event, flip to 1 and break
1043   //static ui_sdl_button_e ui_mask = uisb_none; // current buttons down
1044
1045   while ( ( ! ui_event ) &&
1046           /*block_p ?*/ SDL_WaitEvent ( &event ) /*: SDL_PollEvent ( &event )*/ )
1047   {
1048
1049     switch ( event.type ) {
1050
1051     case SDL_USEREVENT:
1052       // update something
1053
1054       // the user-defined SDL events are all for threaded/delayed previews (and icons, which
1055       // generally are not used); if we're in wide mode, we can skip previews
1056       // to avoid slowing things down when they're not shown.
1057
1058       if ( event.user.code == sdl_user_ticker ) {
1059
1060         if ( ui_detail_hidden ) {
1061           break; // skip building previews when not showing them
1062         }
1063
1064         // timer went off, time to load something
1065         if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_previews_later", 0 ) ) {
1066
1067           if ( ! ui_selected ) {
1068             break;
1069           }
1070
1071           // load the preview pics now!
1072           pnd_disco_t *iter = ui_selected -> ref;
1073
1074           if ( iter -> preview_pic1 ) {
1075
1076             if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.threaded_preview", 0 ) ) {
1077               // load in bg thread, make user experience chuggy
1078
1079               g_preview_thread = SDL_CreateThread ( (void*)ui_threaded_defered_preview, iter );
1080
1081               if ( ! g_preview_thread ) {
1082                 pnd_log ( pndn_error, "ERROR: Couldn't create preview thread\n" );
1083               }
1084
1085             } else {
1086               // load it now, make user wait
1087
1088               if ( ! cache_preview ( iter, pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 ),
1089                                      pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 ) )
1090                  )
1091               {
1092                 pnd_log ( pndn_debug, "  Couldn't load preview pic: '%s' -> '%s'\n",
1093                           IFNULL(iter->title_en,"No Name"), iter -> preview_pic1 );
1094               }
1095
1096             } // threaded?
1097
1098           } // got a preview at all?
1099
1100           ui_event++;
1101         }
1102
1103       } else if ( event.user.code == sdl_user_finishedpreview ) {
1104
1105         // if we just finished the one we happen to be looking at, better redraw now; otherwise, if
1106         // we finished another, no big woop
1107         if ( ui_selected && event.user.data1 == ui_selected -> ref ) {
1108           ui_event++;
1109         }
1110
1111       } else if ( event.user.code == sdl_user_finishedicon ) {
1112         // redraw, so we can show the newly loaded icon
1113         ui_event++;
1114
1115       } else if ( event.user.code == sdl_user_checksd ) {
1116         // check if any inotify-type events occured, forcing us to rescan/re-disco the SDs
1117
1118         unsigned char watch_dbus = 0;
1119         unsigned char watch_inotify = 0;
1120
1121         if ( dbh ) {
1122           watch_dbus = pnd_dbusnotify_rediscover_p ( dbh );
1123         }
1124
1125         if ( nh ) {
1126           watch_inotify = pnd_notify_rediscover_p ( nh );
1127         }
1128
1129         if ( watch_dbus || watch_inotify ) {
1130           pnd_log ( pndn_debug, "dbusnotify detected SD event\n" );
1131           applications_free();
1132           applications_scan();
1133
1134           ui_event++;
1135         }
1136
1137       } // SDL user event
1138
1139       render_mask |= CHANGED_EVERYTHING;
1140
1141       break;
1142
1143 #if 0 // joystick motion
1144     case SDL_JOYAXISMOTION:
1145
1146       pnd_log ( PND_LOG_DEFAULT, "joystick axis\n" );
1147
1148         if ( event.jaxis.axis == 0 ) {
1149           // horiz
1150           if ( event.jaxis.value < 0 ) {
1151             ui_push_left ( 0 );
1152             pnd_log ( PND_LOG_DEFAULT, "joystick axis - LEFT\n" );
1153           } else if ( event.jaxis.value > 0 ) {
1154             ui_push_right ( 0 );
1155             pnd_log ( PND_LOG_DEFAULT, "joystick axis - RIGHT\n" );
1156           }
1157         } else if ( event.jaxis.axis == 1 ) {
1158           // vert
1159           if ( event.jaxis.value < 0 ) {
1160             ui_push_up();
1161           } else if ( event.jaxis.value > 0 ) {
1162             ui_push_down();
1163           }
1164         }
1165
1166         ui_event++;
1167
1168         break;
1169 #endif
1170
1171 #if 0 // joystick buttons
1172     case SDL_JOYBUTTONDOWN:
1173
1174       pnd_log ( PND_LOG_DEFAULT, "joystick button down %u\n", event.jbutton.button );
1175
1176       if ( event.jbutton.button == 0 ) { // B
1177         ui_mask |= uisb_b;
1178       } else if ( event.jbutton.button == 1 ) { // Y
1179         ui_mask |= uisb_y;
1180       } else if ( event.jbutton.button == 2 ) { // X
1181         ui_mask |= uisb_x;
1182       } else if ( event.jbutton.button == 3 ) { // A
1183         ui_mask |= uisb_a;
1184
1185       } else if ( event.jbutton.button == 4 ) { // Select
1186         ui_mask |= uisb_select;
1187       } else if ( event.jbutton.button == 5 ) { // Start
1188         ui_mask |= uisb_start;
1189
1190       } else if ( event.jbutton.button == 7 ) { // L
1191         ui_mask |= uisb_l;
1192       } else if ( event.jbutton.button == 8 ) { // R
1193         ui_mask |= uisb_r;
1194
1195       }
1196
1197       ui_event++;
1198
1199       break;
1200
1201     case SDL_JOYBUTTONUP:
1202
1203       pnd_log ( PND_LOG_DEFAULT, "joystick button up %u\n", event.jbutton.button );
1204
1205       if ( event.jbutton.button == 0 ) { // B
1206         ui_mask &= ~uisb_b;
1207         ui_push_exec();
1208       } else if ( event.jbutton.button == 1 ) { // Y
1209         ui_mask &= ~uisb_y;
1210       } else if ( event.jbutton.button == 2 ) { // X
1211         ui_mask &= ~uisb_x;
1212       } else if ( event.jbutton.button == 3 ) { // A
1213         ui_mask &= ~uisb_a;
1214
1215       } else if ( event.jbutton.button == 4 ) { // Select
1216         ui_mask &= ~uisb_select;
1217       } else if ( event.jbutton.button == 5 ) { // Start
1218         ui_mask &= ~uisb_start;
1219
1220       } else if ( event.jbutton.button == 7 ) { // L
1221         ui_mask &= ~uisb_l;
1222         ui_push_ltrigger();
1223       } else if ( event.jbutton.button == 8 ) { // R
1224         ui_mask &= ~uisb_r;
1225         ui_push_rtrigger();
1226
1227       }
1228
1229       ui_event++;
1230
1231       break;
1232 #endif
1233
1234 #if 1 // keyboard events
1235     //case SDL_KEYUP:
1236     case SDL_KEYDOWN:
1237
1238       //pnd_log ( pndn_debug, "key up %u\n", event.key.keysym.sym );
1239
1240       // SDLK_LALT -> Start
1241       // page up/down for y/x
1242       // home/end for a and b
1243
1244       // directional
1245       if ( event.key.keysym.sym == SDLK_RIGHT ) {
1246         ui_push_right ( 0 );
1247         ui_event++;
1248       } else if ( event.key.keysym.sym == SDLK_LEFT ) {
1249         ui_push_left ( 0 );
1250         ui_event++;
1251       } else if ( event.key.keysym.sym == SDLK_UP ) {
1252         ui_push_up();
1253         ui_event++;
1254       } else if ( event.key.keysym.sym == SDLK_DOWN ) {
1255         ui_push_down();
1256         ui_event++;
1257       } else if ( event.key.keysym.sym == SDLK_END ) { // B
1258         ui_push_exec();
1259         ui_event++;
1260       } else if ( event.key.keysym.sym == SDLK_SPACE ) { // space
1261         if ( ui_selected ) {
1262           ui_menu_context ( ui_selected );
1263         } else {
1264           // TBD: post an error?
1265         }
1266         render_mask |= CHANGED_EVERYTHING;
1267         ui_event++;
1268       } else if ( event.key.keysym.sym == SDLK_TAB || event.key.keysym.sym == SDLK_HOME ) { // tab or A
1269         // if detail panel is togglable, then toggle it
1270         // if not, make sure its ruddy well shown!
1271         if ( ui_is_detail_hideable() ) {
1272           ui_toggle_detail_pane();
1273         } else {
1274           ui_detail_hidden = 0;
1275         }
1276         ui_event++;
1277       } else if ( event.key.keysym.sym == SDLK_RSHIFT || event.key.keysym.sym == SDLK_COMMA ) { // left trigger or comma
1278         ui_push_ltrigger();
1279         ui_event++;
1280       } else if ( event.key.keysym.sym == SDLK_RCTRL || event.key.keysym.sym == SDLK_PERIOD ) { // right trigger or period
1281         ui_push_rtrigger();
1282         ui_event++;
1283
1284       } else if ( event.key.keysym.sym == SDLK_PAGEUP ) { // Y
1285         // info
1286         if ( ui_selected ) {
1287           ui_show_info ( pnd_run_script, ui_selected -> ref );
1288           ui_event++;
1289         }
1290       } else if ( event.key.keysym.sym == SDLK_PAGEDOWN ) { // X
1291         ui_push_backup();
1292
1293         // forget the selection, nolonger applies
1294         ui_selected = NULL;
1295         ui_set_selected ( ui_selected );
1296         // rescan the dir
1297         if ( g_categories [ ui_category ] -> fspath ) {
1298           category_fs_restock ( g_categories [ ui_category ] );
1299         }
1300         // redraw the grid
1301         render_mask |= CHANGED_EVERYTHING;
1302         ui_event++;
1303
1304       } else if ( event.key.keysym.sym == SDLK_LALT ) { // start button
1305         ui_push_exec();
1306         ui_event++;
1307
1308       } else if ( event.key.keysym.sym == SDLK_LCTRL /*LALT*/ ) { // select button
1309         char *opts [ 20 ] = {
1310           "Reveal hidden category",
1311           "Shutdown Pandora",
1312           "Configure Minimenu",
1313           "Rescan for applications",
1314           "Cache previews to SD now",
1315           "Run a terminal/console",
1316           "Run another GUI (xfce, etc)",
1317           "Quit (<- beware)",
1318           "Select a Minimenu skin",
1319           "About Minimenu"
1320         };
1321         int sel = ui_modal_single_menu ( opts, 10, "Minimenu", "Enter to select; other to return." );
1322
1323         char buffer [ 100 ];
1324         if ( sel == 0 ) {
1325           // do nothing
1326           ui_revealscreen();
1327         } else if ( sel == 1 ) {
1328           // shutdown
1329           sprintf ( buffer, "sudo poweroff" );
1330           system ( buffer );
1331         } else if ( sel == 2 ) {
1332           // configure mm
1333           unsigned char restart = conf_run_menu ( NULL );
1334           conf_write ( g_conf, conf_determine_location ( g_conf ) );
1335           if ( restart ) {
1336             emit_and_quit ( MM_RESTART );
1337           }
1338         } else if ( sel == 3 ) {
1339           // rescan apps
1340           pnd_log ( pndn_debug, "Freeing up applications\n" );
1341           applications_free();
1342           pnd_log ( pndn_debug, "Rescanning applications\n" );
1343           applications_scan();
1344         } else if ( sel == 4 ) {
1345           // cache preview to SD now
1346           extern pnd_box_handle g_active_apps;
1347           pnd_box_handle h = g_active_apps;
1348
1349           unsigned int maxwidth, maxheight;
1350           maxwidth = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 );
1351           maxheight = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 );
1352
1353           pnd_disco_t *iter = pnd_box_get_head ( h );
1354
1355           while ( iter ) {
1356
1357             // cache it
1358             if ( ! cache_preview ( iter, maxwidth, maxheight ) ) {
1359               pnd_log ( pndn_debug, "Force cache: Couldn't load preview pic: '%s' -> '%s'\n",
1360                         IFNULL(iter->title_en,"No Name"), iter -> preview_pic1 );
1361             }
1362
1363             // next
1364             iter = pnd_box_get_next ( iter );
1365           } // while
1366
1367         } else if ( sel == 5 ) {
1368           // run terminal
1369           char *argv[5];
1370           argv [ 0 ] = pnd_conf_get_as_char ( g_conf, "utility.terminal" );
1371           argv [ 1 ] = NULL;
1372
1373           if ( argv [ 0 ] ) {
1374             ui_forkexec ( argv );
1375           }
1376
1377         } else if ( sel == 6 ) {
1378           char buffer [ PATH_MAX ];
1379           sprintf ( buffer, "%s %s\n", MM_RUN, "/usr/pandora/scripts/op_switchgui.sh" );
1380           emit_and_quit ( buffer );
1381         } else if ( sel == 7 ) {
1382           emit_and_quit ( MM_QUIT );
1383         } else if ( sel == 8 ) {
1384           // select skin
1385           if ( ui_pick_skin() ) {
1386             emit_and_quit ( MM_RESTART );
1387           }
1388         } else if ( sel == 9 ) {
1389           // about
1390           char buffer [ PATH_MAX ];
1391           sprintf ( buffer, "%s/about.txt", g_skinpath );
1392           ui_aboutscreen ( buffer );
1393         }
1394
1395         ui_event++;
1396         render_mask |= CHANGED_EVERYTHING;
1397
1398       } else {
1399         // unknown SDLK_ keycode?
1400
1401         // many SDLK_keycodes map to ASCII ("a" is ascii(a)), so try to jump to a filename of that name, in this category?
1402         // and if already there, try to jump to next, maybe?
1403         // future: look for sequence typing? ie: user types 'm' then 'a', look for 'ma*' instead of 'm' then 'a' matching
1404         if ( isalpha ( event.key.keysym.sym ) && g_categories [ ui_category ] -> refcount > 0 ) {
1405           mm_appref_t *app = g_categories [ ui_category ] -> refs;
1406
1407           //fprintf ( stderr, "sel %s next %s\n", ui_selected -> ref -> title_en, ui_selected -> next -> ref -> title_en );
1408
1409           // are we already matching the same char? and next item is also same char?
1410           if ( app && ui_selected &&
1411                ui_selected -> ref -> title_en && ui_selected -> next -> ref -> title_en &&
1412                toupper ( ui_selected -> ref -> title_en [ 0 ] ) == toupper ( ui_selected -> next -> ref -> title_en [ 0 ] ) &&
1413                toupper ( ui_selected -> ref -> title_en [ 0 ] ) == toupper ( event.key.keysym.sym )
1414              )
1415           {
1416             // just skip down one
1417             app = ui_selected -> next;
1418           } else {
1419
1420             // walk the category, looking for a first-char match
1421             while ( app ) {
1422               if ( app -> ref -> title_en && toupper ( app -> ref -> title_en [ 0 ] ) == toupper ( event.key.keysym.sym ) ) {
1423                 break;
1424               }
1425               app = app -> next;
1426             }
1427
1428           } // same start letter, or new run?
1429
1430           // found something, or no?
1431           if ( app ) {
1432             // looks like we found a potential match; try switching it to visible selection
1433             ui_selected = app;
1434             ui_set_selected ( ui_selected );
1435             ui_event++;
1436           }
1437
1438
1439         } // SDLK_alphanumeric?
1440
1441       } // SDLK_....
1442
1443       // extras
1444 #if 1
1445       if ( event.key.keysym.sym == SDLK_ESCAPE ) {
1446         emit_and_quit ( MM_QUIT );
1447       }
1448 #endif
1449
1450       break;
1451 #endif
1452
1453 #if 1 // mouse / touchscreen
1454 #if 0
1455     case SDL_MOUSEBUTTONDOWN:
1456       if ( event.button.button == SDL_BUTTON_LEFT ) {
1457         cb_pointer_press ( gc, event.button.x / g_scale, event.button.y / g_scale );
1458         ui_event++;
1459       }
1460       break;
1461 #endif
1462
1463     case SDL_MOUSEBUTTONUP:
1464       if ( event.button.button == SDL_BUTTON_LEFT ) {
1465         ui_touch_act ( event.button.x, event.button.y );
1466         ui_event++;
1467       }
1468       break;
1469 #endif
1470
1471     case SDL_QUIT:
1472       exit ( 0 );
1473       break;
1474
1475     default:
1476       break;
1477
1478     } // switch event type
1479
1480   } // while poll
1481
1482   return;
1483 }
1484
1485 void ui_push_left ( unsigned char forcecoil ) {
1486
1487   if ( ! ui_selected ) {
1488     ui_push_right ( 0 );
1489     return;
1490   }
1491
1492   // what column we in?
1493   unsigned int col = ui_determine_screen_col ( ui_selected );
1494
1495   // are we already at first item?
1496   if ( forcecoil == 0 &&
1497        pnd_conf_get_as_int_d ( g_conf, "grid.wrap_horiz_samerow", 0 ) &&
1498        col == 0 )
1499   {
1500     unsigned int i = ui_display_context.col_max - 1;
1501     while ( i && ui_selected -> next ) {
1502       ui_push_right ( 0 );
1503       i--;
1504     }
1505
1506   } else if ( g_categories [ ui_category ] -> refs == ui_selected ) {
1507     // can't go any more left, we're at the head
1508
1509   } else {
1510     // figure out the previous item; yay for singly linked list :/
1511     mm_appref_t *i = g_categories [ ui_category ] -> refs;
1512     while ( i ) {
1513       if ( i -> next == ui_selected ) {
1514         ui_selected = i;
1515         break;
1516       }
1517       i = i -> next;
1518     }
1519   }
1520
1521   ui_set_selected ( ui_selected );
1522
1523   return;
1524 }
1525
1526 void ui_push_right ( unsigned char forcecoil ) {
1527
1528   if ( ui_selected ) {
1529
1530     // what column we in?
1531     unsigned int col = ui_determine_screen_col ( ui_selected );
1532
1533     // wrap same or no?
1534     if ( forcecoil == 0 &&
1535          pnd_conf_get_as_int_d ( g_conf, "grid.wrap_horiz_samerow", 0 ) &&
1536          // and selected is far-right, or last icon in category (might not be far right)
1537          ( ( col == ui_display_context.col_max - 1 ) ||
1538            ( ui_selected -> next == NULL ) )
1539        )
1540     {
1541       // same wrap
1542       //unsigned int i = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 ) - 1;
1543       while ( col /*i*/ ) {
1544         ui_push_left ( 0 );
1545         col--; //i--;
1546       }
1547
1548     } else {
1549       // just go to the next
1550
1551       if ( ui_selected -> next ) {
1552         ui_selected = ui_selected -> next;
1553       }
1554
1555     }
1556
1557   } else {
1558     ui_selected = g_categories [ ui_category ] -> refs;
1559   }
1560
1561   ui_set_selected ( ui_selected );
1562
1563   return;
1564 }
1565
1566 void ui_push_up ( void ) {
1567   unsigned char col_max = ui_display_context.col_max;
1568
1569   if ( ! ui_selected ) {
1570     return;
1571   }
1572
1573   // what row we in?
1574   unsigned int row = ui_determine_row ( ui_selected );
1575
1576   if ( row == 0 &&
1577        pnd_conf_get_as_int_d ( g_conf, "grid.wrap_vert_stop", 0 ) == 0 )
1578   {
1579     // wrap around instead
1580
1581     unsigned int col = ui_determine_screen_col ( ui_selected );
1582
1583     // go to end
1584     ui_selected = g_categories [ ui_category ] -> refs;
1585     while ( ui_selected -> next ) {
1586       ui_selected = ui_selected -> next;
1587     }
1588
1589     // try to move to same column
1590     unsigned int newcol = ui_determine_screen_col ( ui_selected );
1591     if ( newcol > col ) {
1592       col = newcol - col;
1593       while ( col ) {
1594         ui_push_left ( 0 );
1595         col--;
1596       }
1597     }
1598
1599     // scroll down to show it
1600     int r = ui_determine_row ( ui_selected ) - 1;
1601     if ( r - ui_display_context.row_max > 0 ) {
1602       ui_rows_scrolled_down = (unsigned int) r;
1603     }
1604
1605   } else {
1606     // stop at top/bottom
1607
1608     while ( col_max ) {
1609       ui_push_left ( 1 );
1610       col_max--;
1611     }
1612
1613   }
1614
1615   return;
1616 }
1617
1618 void ui_push_down ( void ) {
1619   unsigned char col_max = ui_display_context.col_max;
1620
1621   if ( ui_selected ) {
1622
1623     // what row we in?
1624     unsigned int row = ui_determine_row ( ui_selected );
1625
1626     // max rows?
1627     unsigned int icon_rows = g_categories [ ui_category ] -> refcount / col_max;
1628     if ( g_categories [ ui_category ] -> refcount % col_max > 0 ) {
1629       icon_rows++;
1630     }
1631
1632     // we at the end?
1633     if ( row == ( icon_rows - 1 ) &&
1634          pnd_conf_get_as_int_d ( g_conf, "grid.wrap_vert_stop", 0 ) == 0 )
1635     {
1636
1637       unsigned char col = ui_determine_screen_col ( ui_selected );
1638
1639       ui_selected = g_categories [ ui_category ] -> refs;
1640
1641       while ( col ) {
1642         ui_selected = ui_selected -> next;
1643         col--;
1644       }
1645
1646       ui_rows_scrolled_down = 0;
1647
1648       render_mask |= CHANGED_EVERYTHING;
1649
1650     } else {
1651
1652       while ( col_max ) {
1653         ui_push_right ( 1 );
1654         col_max--;
1655       }
1656
1657     }
1658
1659   } else {
1660     ui_push_right ( 0 );
1661   }
1662
1663   return;
1664 }
1665
1666 // 'backup' is currently 'X', for going back up in a folder/subcat without having to hit exec on the '..' entry
1667 void ui_push_backup ( void ) {
1668
1669   // a subcat-as-dir, or a dir browser?
1670   if ( g_categories [ ui_category] -> fspath ) {
1671     // dir browser, just climb our way back up
1672
1673     // go up
1674     char *c;
1675
1676     // lop off last word; if the thing ends with /, lop that one, then the next word.
1677     while ( ( c = strrchr ( g_categories [ ui_category] -> fspath, '/' ) ) ) {
1678       *c = '\0'; // lop off the last hunk
1679       if ( *(c+1) != '\0' ) {
1680         break;
1681       }
1682     } // while
1683
1684     // nothing left?
1685     if ( g_categories [ ui_category] -> fspath [ 0 ] == '\0' ) {
1686       free ( g_categories [ ui_category] -> fspath );
1687       g_categories [ ui_category] -> fspath = strdup ( "/" );
1688     }
1689
1690   } else {
1691     // a pnd subcat .. are we in one, or at the 'top'?
1692     char *pcatname = g_categories [ ui_category ] -> parent_catname;
1693
1694     if ( ! pcatname ) {
1695       return; // we're at the 'top' already
1696     }
1697
1698     // set to first cat!
1699     ui_category = 0;
1700     // republish cats .. shoudl just be the one
1701     category_publish ( CFNORMAL, NULL );
1702
1703     if ( pcatname ) {
1704       ui_category = category_index ( pcatname );
1705     }
1706
1707   } // dir or subcat?
1708
1709   return;
1710 }
1711
1712 void ui_push_exec ( void ) {
1713
1714   if ( ! ui_selected ) {
1715     return;
1716   }
1717
1718   // was this icon generated from filesystem, or from pnd-file?
1719   if ( ui_selected -> ref -> object_flags & PND_DISCO_GENERATED ) {
1720
1721     if ( ! ui_selected -> ref -> title_en ) {
1722       return; // no filename
1723     }
1724
1725     if ( ui_selected -> ref -> object_type == pnd_object_type_directory ) {
1726
1727       // check if this guy is a dir-browser tab, or is a directory on a pnd tab
1728       if ( ! g_categories [ ui_category] -> fspath ) {
1729         // pnd subcat as dir
1730
1731         // are we already in a subcat? if so, go back to parent; there is no grandparenting or deeper
1732         if ( g_categories [ ui_category ] -> parent_catname ) {
1733           // go back up
1734           ui_push_backup();
1735
1736         } else {
1737           // delve into subcat
1738
1739           // set to first cat!
1740           ui_category = 0;
1741           // republish cats .. shoudl just be the one
1742           category_publish ( CFBYNAME, ui_selected -> ref -> object_path );
1743
1744         }
1745
1746         // forget the selection, nolonger applies
1747         ui_selected = NULL;
1748         ui_set_selected ( ui_selected );
1749         // redraw the grid
1750         render_mask |= CHANGED_EVERYTHING;
1751
1752       } else {
1753
1754         // delve up/down the dir tree
1755         if ( strcmp ( ui_selected -> ref -> title_en, ".." ) == 0 ) {
1756           ui_push_backup();
1757
1758         } else {
1759           // go down
1760           char *temp = malloc ( strlen ( g_categories [ ui_category] -> fspath ) + strlen ( ui_selected -> ref -> title_en ) + 1 + 1 );
1761           sprintf ( temp, "%s/%s", g_categories [ ui_category] -> fspath, ui_selected -> ref -> title_en );
1762           free ( g_categories [ ui_category] -> fspath );
1763           g_categories [ ui_category] -> fspath = temp;
1764         }
1765
1766         pnd_log ( pndn_debug, "Cat %s is now in path %s\n", g_categories [ ui_category ] -> catname, g_categories [ ui_category ]-> fspath );
1767
1768         // forget the selection, nolonger applies
1769         ui_selected = NULL;
1770         ui_set_selected ( ui_selected );
1771         // rescan the dir
1772         category_fs_restock ( g_categories [ ui_category ] );
1773         // redraw the grid
1774         render_mask |= CHANGED_SELECTION;
1775
1776       } // directory browser or pnd subcat?
1777
1778     } else {
1779       // just run it arbitrarily?
1780
1781       // if this a pnd-file, or just some executable?
1782       if ( strcasestr ( ui_selected -> ref -> object_filename, PND_PACKAGE_FILEEXT ) ) {
1783         // looks like a pnd, now what do we do..
1784         pnd_box_handle h = pnd_disco_file ( ui_selected -> ref -> object_path, ui_selected -> ref -> object_filename );
1785
1786         if ( h ) {
1787           pnd_disco_t *d = pnd_box_get_head ( h );
1788           pnd_apps_exec_disco ( pnd_run_script, d, PND_EXEC_OPTION_NORUN, NULL );
1789           char buffer [ PATH_MAX ];
1790           sprintf ( buffer, "%s %s\n", MM_RUN, pnd_apps_exec_runline() );
1791           if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
1792             emit_and_quit ( buffer );
1793           } else {
1794             emit_and_run ( buffer );
1795           }
1796         }
1797
1798       } else {
1799         // random bin file
1800
1801         // is it even executable? if we don't have handlers for non-executables yet (Jan 2011 we don't),
1802         // then don't even try to run things not-flagged as executable.. but wait most people are on
1803         // FAT filesystems, what a drag, we can't tell at the fs level.
1804         // ... but we can still invoke 'file' and grep out the good bits, at least.
1805         //
1806         // open a stream reading 'file /path/to/file' and check output for 'executable'
1807         // -- not checking for "ARM" so it can pick up x86 (or whatever native) executables in build environment
1808         unsigned char is_executable = 0;
1809
1810         // popen test
1811         {
1812           char popenbuf [ FILENAME_MAX ];
1813           snprintf ( popenbuf, FILENAME_MAX, "%s %s/%s", MIMETYPE_EXE, g_categories [ ui_category ] -> fspath, ui_selected -> ref -> title_en );
1814
1815           FILE *marceau;
1816           if ( ! ( marceau = popen ( popenbuf, "r" ) ) ) {
1817             return; // error, we need some useful error handling and dialog boxes here
1818           }
1819
1820           if ( fgets ( popenbuf, FILENAME_MAX, marceau ) ) {
1821             //printf ( "File test returns: %s\n", popenbuf );
1822             if ( strstr ( popenbuf, "executable" ) != NULL ) {
1823               is_executable = 1;
1824             }
1825           }
1826
1827           pclose ( marceau );
1828
1829         } // popen test
1830
1831         if ( ! is_executable ) {
1832           fprintf ( stderr, "ERROR: File to invoke is not executable, skipping. (%s)\n", ui_selected -> ref -> title_en );
1833           return; // need some error handling here
1834         }
1835
1836 #if 0 // eat up any pending SDL events and toss 'em?
1837         {
1838           SDL_PumpEvents();
1839           SDL_Event e;
1840           while ( SDL_PeepEvents ( &e, 1, SDL_GETEVENT, SDL_ALLEVENTS ) > 0 ) {
1841             // spin
1842           }
1843         }
1844 #endif
1845
1846 #if 1
1847         // just exec it
1848         //
1849
1850         // get CWD so we can restore it on return
1851         char cwd [ PATH_MAX ];
1852         getcwd ( cwd, PATH_MAX );
1853
1854         // full path to executable so we don't rely on implicit "./"
1855         char execbuf [ FILENAME_MAX ];
1856         snprintf ( execbuf, FILENAME_MAX, "%s/%s", g_categories [ ui_category ] -> fspath, ui_selected -> ref -> title_en );
1857
1858         // do it!
1859         chdir ( g_categories [ ui_category ] -> fspath );
1860         exec_raw_binary ( execbuf /*ui_selected -> ref -> title_en*/ );
1861         chdir ( cwd );
1862 #else
1863         // DEPRECATED / NOT TESTED
1864         // get mmwrapper to run it
1865         char buffer [ PATH_MAX ];
1866         sprintf ( buffer, "%s %s/%s\n", MM_RUN, g_categories [ ui_category ] -> fspath, ui_selected -> ref -> title_en );
1867         if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
1868           emit_and_quit ( buffer );
1869         } else {
1870           emit_and_run ( buffer );
1871         }
1872 #endif
1873       } // pnd or bin?
1874
1875     } // dir or file?
1876
1877   } else {
1878
1879     // set app-run speed
1880     int use_run_speed = pnd_conf_get_as_int_d ( g_conf, "minimenu.use_run_speed", 0 );
1881     if ( use_run_speed > 0 ) {
1882       int mm_speed = pnd_conf_get_as_int_d ( g_conf, "minimenu.run_speed", -1 );
1883       if ( mm_speed > -1 ) {
1884         char buffer [ 512 ];
1885         snprintf ( buffer, 500, "sudo /usr/pandora/scripts/op_cpuspeed.sh %d", mm_speed );
1886         system ( buffer );
1887       }
1888     } // do speed change?
1889
1890     // request app to run and quit mmenu
1891     pnd_apps_exec_disco ( pnd_run_script, ui_selected -> ref, PND_EXEC_OPTION_NORUN, NULL );
1892     char buffer [ PATH_MAX ];
1893     sprintf ( buffer, "%s %s\n", MM_RUN, pnd_apps_exec_runline() );
1894     if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
1895       emit_and_quit ( buffer );
1896     } else {
1897       emit_and_run ( buffer );
1898     }
1899   }
1900
1901   return;
1902 }
1903
1904 void ui_push_ltrigger ( void ) {
1905   unsigned char oldcat = ui_category;
1906   unsigned int screen_width = ui_display_context.screen_width;
1907   unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
1908
1909   if ( g_categorycount == 0 ) {
1910     return;
1911   }
1912
1913   if ( ui_category > 0 ) {
1914     ui_category--;
1915     category_fs_restock ( g_categories [ ui_category ] );
1916   } else {
1917     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
1918       ui_category = g_categorycount - 1;
1919       ui_catshift = 0;
1920       if ( ui_category >= ( screen_width / tab_width ) ) {
1921         ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
1922       }
1923       category_fs_restock ( g_categories [ ui_category ] );
1924     }
1925   }
1926
1927   if ( oldcat != ui_category ) {
1928     ui_selected = NULL;
1929     ui_set_selected ( ui_selected );
1930   }
1931
1932   // make tab visible?
1933   if ( ui_catshift > 0 && ui_category == ui_catshift - 1 ) {
1934     ui_catshift--;
1935   }
1936
1937   // unscroll
1938   ui_rows_scrolled_down = 0;
1939
1940   render_mask |= CHANGED_CATEGORY;
1941
1942   return;
1943 }
1944
1945 void ui_push_rtrigger ( void ) {
1946   unsigned char oldcat = ui_category;
1947
1948   if ( g_categorycount == 0 ) {
1949     return;
1950   }
1951
1952   unsigned int screen_width = ui_display_context.screen_width;
1953   unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
1954
1955   if ( ui_category < ( g_categorycount - 1 ) ) {
1956     ui_category++;
1957     category_fs_restock ( g_categories [ ui_category ] );
1958   } else {
1959     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
1960       ui_category = 0;
1961       ui_catshift = 0;
1962       category_fs_restock ( g_categories [ ui_category ] );
1963     }
1964   }
1965
1966   if ( oldcat != ui_category ) {
1967     ui_selected = NULL;
1968     ui_set_selected ( ui_selected );
1969   }
1970
1971   // make tab visible?
1972   if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
1973     ui_catshift++;
1974   }
1975
1976   // unscroll
1977   ui_rows_scrolled_down = 0;
1978
1979   render_mask |= CHANGED_CATEGORY;
1980
1981   return;
1982 }
1983
1984 SDL_Surface *ui_scale_image ( SDL_Surface *s, unsigned int maxwidth, int maxheight ) {
1985   double scale = 1000000.0;
1986   double scalex = 1000000.0;
1987   double scaley = 1000000.0;
1988   SDL_Surface *scaled;
1989
1990   scalex = (double)maxwidth / (double)s -> w;
1991
1992   if ( maxheight == -1 ) {
1993     scale = scalex;
1994   } else {
1995     scaley = (double)maxheight / (double)s -> h;
1996
1997     if ( scaley < scalex ) {
1998       scale = scaley;
1999     } else {
2000       scale = scalex;
2001     }
2002
2003   }
2004
2005   scaled = rotozoomSurface ( s, 0 /* angle*/, scale /* scale */, 1 /* smooth==1*/ );
2006   SDL_FreeSurface ( s );
2007   s = scaled;
2008
2009   return ( s );
2010 }
2011
2012 void ui_loadscreen ( void ) {
2013
2014   SDL_Rect dest;
2015
2016   // clear the screen
2017   SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2018
2019   // render text
2020   SDL_Surface *rtext;
2021   rtext = TTF_RenderText_Blended ( g_big_font, "Setting up menu...", ui_display_context.fontcolor );
2022   dest.x = 20;
2023   dest.y = 20;
2024   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
2025   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2026   SDL_FreeSurface ( rtext );
2027
2028   return;
2029 }
2030
2031 void ui_discoverscreen ( unsigned char clearscreen ) {
2032
2033   SDL_Rect dest;
2034
2035   // clear the screen
2036   if ( clearscreen ) {
2037     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2038
2039     // render background
2040     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2041       dest.x = 0;
2042       dest.y = 0;
2043       dest.w = sdl_realscreen -> w;
2044       dest.h = sdl_realscreen -> h;
2045       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
2046       SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2047     }
2048
2049   }
2050
2051   // render text
2052   SDL_Surface *rtext;
2053   rtext = TTF_RenderText_Blended ( g_big_font, "Looking for applications...", ui_display_context.fontcolor );
2054   if ( clearscreen ) {
2055     dest.x = 20;
2056     dest.y = 20;
2057   } else {
2058     dest.x = 20;
2059     dest.y = 40;
2060   }
2061   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
2062   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2063   SDL_FreeSurface ( rtext );
2064
2065   // render icon
2066   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
2067     dest.x = rtext -> w + 30;
2068     dest.y = 20;
2069     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, &dest );
2070     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2071   }
2072
2073   return;
2074 }
2075
2076 void ui_cachescreen ( unsigned char clearscreen, char *filename ) {
2077
2078   SDL_Rect rects [ 4 ];
2079   SDL_Rect *dest = rects;
2080   SDL_Rect src;
2081   bzero ( dest, sizeof(SDL_Rect)* 4 );
2082
2083   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
2084   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
2085   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
2086   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
2087
2088   static unsigned int stepx = 0;
2089
2090   // clear the screen
2091   if ( clearscreen ) {
2092     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2093
2094     // render background
2095     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2096       dest -> x = 0;
2097       dest -> y = 0;
2098       dest -> w = sdl_realscreen -> w;
2099       dest -> h = sdl_realscreen -> h;
2100       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
2101       dest++;
2102     }
2103
2104   } else {
2105
2106     // render background
2107     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2108       src.x = 0;
2109       src.y = 0;
2110       src.w = sdl_realscreen -> w;
2111       src.h = 100;
2112       dest -> x = 0;
2113       dest -> y = 0;
2114       dest -> w = sdl_realscreen -> w;
2115       dest -> h = sdl_realscreen -> h;
2116       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, &src, sdl_realscreen, dest );
2117       dest++;
2118     }
2119
2120   } // clear it
2121
2122   // render text
2123   SDL_Surface *rtext;
2124   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
2125   rtext = TTF_RenderText_Blended ( g_big_font, "Caching applications artwork...", tmpfontcolor );
2126   dest -> x = 20;
2127   dest -> y = 20;
2128   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2129   SDL_FreeSurface ( rtext );
2130   dest++;
2131
2132   // render icon
2133   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
2134     dest -> x = rtext -> w + 30 + stepx;
2135     dest -> y = 20;
2136     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, dest );
2137     dest++;
2138   }
2139
2140   // filename
2141   if ( filename ) {
2142     rtext = TTF_RenderText_Blended ( g_tab_font, filename, tmpfontcolor );
2143     dest -> x = 20;
2144     dest -> y = 50;
2145     SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2146     SDL_FreeSurface ( rtext );
2147     dest++;
2148   }
2149
2150   // move across
2151   stepx += 20;
2152
2153   if ( stepx > 350 ) {
2154     stepx = 0;
2155   }
2156
2157   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
2158
2159   return;
2160 }
2161
2162 int ui_selected_index ( void ) {
2163
2164   if ( ! ui_selected ) {
2165     return ( -1 ); // no index
2166   }
2167
2168   mm_appref_t *r = g_categories [ ui_category ] -> refs;
2169   int counter = 0;
2170   while ( r ) {
2171     if ( r == ui_selected ) {
2172       return ( counter );
2173     }
2174     r = r -> next;
2175     counter++;
2176   }
2177
2178   return ( -1 );
2179 }
2180
2181 static mm_appref_t *timer_ref = NULL;
2182 void ui_set_selected ( mm_appref_t *r ) {
2183
2184   render_mask |= CHANGED_SELECTION;
2185
2186   if ( ! pnd_conf_get_as_int_d ( g_conf, "minimenu.load_previews_later", 0 ) ) {
2187     return; // no desire to defer anything
2188   }
2189
2190   if ( ! r ) {
2191     // cancel timer
2192     SDL_SetTimer ( 0, NULL );
2193     timer_ref = NULL;
2194     return;
2195   }
2196
2197   SDL_SetTimer ( pnd_conf_get_as_int_d ( g_conf, "previewpic.defer_timer_ms", 1000 ), ui_callback_f );
2198   timer_ref = r;
2199
2200   return;
2201 }
2202
2203 unsigned int ui_callback_f ( unsigned int t ) {
2204
2205   if ( ui_selected != timer_ref ) {
2206     return ( 0 ); // user has moved it, who cares
2207   }
2208
2209   SDL_Event e;
2210   bzero ( &e, sizeof(SDL_Event) );
2211   e.type = SDL_USEREVENT;
2212   e.user.code = sdl_user_ticker;
2213   SDL_PushEvent ( &e );
2214
2215   return ( 0 );
2216 }
2217
2218 int ui_modal_single_menu ( char *argv[], unsigned int argc, char *title, char *footer ) {
2219   SDL_Rect rects [ 40 ];
2220   SDL_Rect *dest = rects;
2221   SDL_Rect src;
2222   SDL_Surface *rtext;
2223   unsigned char max_visible = pnd_conf_get_as_int_d ( g_conf, "detailtext.max_visible" , 11 );
2224   unsigned char first_visible = 0;
2225
2226   bzero ( rects, sizeof(SDL_Rect) * 40 );
2227
2228   unsigned int sel = 0;
2229
2230   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 };
2231
2232   unsigned int i;
2233   SDL_Event event;
2234
2235   while ( 1 ) {
2236
2237     // clear
2238     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2239     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2240     dest -> w = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> w;
2241     dest -> h = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h;
2242     SDL_FillRect( sdl_realscreen, dest, 0 );
2243
2244     // show dialog background
2245     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
2246       src.x = 0;
2247       src.y = 0;
2248       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> w;
2249       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> h;
2250       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2251       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2252       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2253       // repeat for darken?
2254       //SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2255       //SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2256       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2257       dest++;
2258     }
2259
2260     // show dialog frame
2261     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
2262       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2263       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2264       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
2265       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2266       dest++;
2267     }
2268
2269     // show header
2270     if ( title ) {
2271       rtext = TTF_RenderText_Blended ( g_tab_font, title, ui_display_context.fontcolor );
2272       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2273       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 20;
2274       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2275       SDL_FreeSurface ( rtext );
2276       dest++;
2277     }
2278
2279     // show footer
2280     if ( footer ) {
2281       rtext = TTF_RenderText_Blended ( g_tab_font, footer, ui_display_context.fontcolor );
2282       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2283       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) +
2284         ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h
2285         - 60;
2286       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2287       SDL_FreeSurface ( rtext );
2288       dest++;
2289     }
2290
2291     // show options
2292     for ( i = first_visible; i < first_visible + max_visible && i < argc; i++ ) {
2293
2294       // show options
2295       if ( sel == i ) {
2296         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], selfontcolor );
2297       } else {
2298         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], ui_display_context.fontcolor );
2299       }
2300       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2301       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 40 + ( 20 * ( i + 1 - first_visible ) );
2302       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2303       SDL_FreeSurface ( rtext );
2304       dest++;
2305
2306     } // for
2307
2308     // update all the rects and send it all to sdl
2309     SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
2310     dest = rects;
2311
2312     // check for input
2313     while ( SDL_WaitEvent ( &event ) ) {
2314
2315       switch ( event.type ) {
2316
2317       //case SDL_KEYUP:
2318       case SDL_KEYDOWN:
2319
2320         if ( event.key.keysym.sym == SDLK_UP ) {
2321           if ( sel ) {
2322             sel--;
2323
2324             if ( sel < first_visible ) {
2325               first_visible--;
2326             }
2327
2328           }
2329         } else if ( event.key.keysym.sym == SDLK_DOWN ) {
2330
2331           if ( sel < argc - 1 ) {
2332             sel++;
2333
2334             // ensure visibility
2335             if ( sel >= first_visible + max_visible ) {
2336               first_visible++;
2337             }
2338
2339           }
2340
2341         } else if ( event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_END ) { // return, or "B"
2342           return ( sel );
2343
2344 #if 0
2345         } else if ( event.key.keysym.sym == SDLK_q ) {
2346           exit ( 0 );
2347 #endif
2348
2349         } else {
2350           return ( -1 ); // nada
2351         }
2352
2353         break;
2354
2355       } // switch
2356
2357       break;
2358     } // while
2359
2360   } // while
2361
2362   return ( -1 );
2363 }
2364
2365 unsigned char ui_determine_row ( mm_appref_t *a ) {
2366   unsigned int row = 0;
2367
2368   mm_appref_t *i = g_categories [ ui_category ] -> refs;
2369   while ( i != a ) {
2370     i = i -> next;
2371     row++;
2372   } // while
2373   row /= ui_display_context.col_max;
2374
2375   return ( row );
2376 }
2377
2378 unsigned char ui_determine_screen_row ( mm_appref_t *a ) {
2379   return ( ui_determine_row ( a ) % ui_display_context.row_max );
2380 }
2381
2382 unsigned char ui_determine_screen_col ( mm_appref_t *a ) {
2383   unsigned int col = 0;
2384
2385   mm_appref_t *i = g_categories [ ui_category ] -> refs;
2386   while ( i != a ) {
2387     i = i -> next;
2388     col++;
2389   } // while
2390   col %= ui_display_context.col_max;
2391
2392   return ( col );
2393 }
2394
2395 unsigned char ui_show_info ( char *pndrun, pnd_disco_t *p ) {
2396   char *viewer, *searchpath;
2397   pnd_conf_handle desktoph;
2398
2399   // viewer
2400   searchpath = pnd_conf_query_searchpath();
2401
2402   desktoph = pnd_conf_fetch_by_id ( pnd_conf_desktop, searchpath );
2403
2404   if ( ! desktoph ) {
2405     return ( 0 );
2406   }
2407
2408   viewer = pnd_conf_get_as_char ( desktoph, "info.viewer" );
2409
2410   if ( ! viewer ) {
2411     return ( 0 ); // no way to view the file
2412   }
2413
2414   // etc
2415   if ( ! p -> unique_id ) {
2416     return ( 0 );
2417   }
2418
2419   if ( ! p -> info_filename ) {
2420     return ( 0 );
2421   }
2422
2423   if ( ! p -> info_name ) {
2424     return ( 0 );
2425   }
2426
2427   if ( ! pndrun ) {
2428     return ( 0 );
2429   }
2430
2431   // exec line
2432   char args [ 1001 ];
2433   char *pargs = args;
2434   if ( pnd_conf_get_as_char ( desktoph, "info.viewer_args" ) ) {
2435     snprintf ( pargs, 1001, "%s %s",
2436                pnd_conf_get_as_char ( desktoph, "info.viewer_args" ), p -> info_filename );
2437   } else {
2438     pargs = NULL;
2439   }
2440
2441   char pndfile [ 1024 ];
2442   if ( p -> object_type == pnd_object_type_directory ) {
2443     // for PXML-app-dir, pnd_run.sh doesn't want the PXML.xml.. it just wants the dir-name
2444     strncpy ( pndfile, p -> object_path, 1000 );
2445   } else if ( p -> object_type == pnd_object_type_pnd ) {
2446     // pnd_run.sh wants the full path and filename for the .pnd file
2447     snprintf ( pndfile, 1020, "%s/%s", p -> object_path, p -> object_filename );
2448   }
2449
2450   if ( ! pnd_apps_exec ( pndrun, pndfile, p -> unique_id, viewer, p -> startdir, pargs,
2451                          p -> clockspeed ? atoi ( p -> clockspeed ) : 0, PND_EXEC_OPTION_NORUN ) )
2452   {
2453     return ( 0 );
2454   }
2455
2456   pnd_log ( pndn_debug, "Info Exec=%s\n", pnd_apps_exec_runline() );
2457
2458   // try running it
2459   int x;
2460   if ( ( x = fork() ) < 0 ) {
2461     pnd_log ( pndn_error, "ERROR: Couldn't fork()\n" );
2462     return ( 0 );
2463   }
2464
2465   if ( x == 0 ) {
2466     execl ( "/bin/sh", "/bin/sh", "-c", pnd_apps_exec_runline(), (char*)NULL );
2467     pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", pnd_apps_exec_runline() );
2468     return ( 0 );
2469   }
2470
2471   return ( 1 );
2472 }
2473
2474 typedef struct {
2475   SDL_Rect r;
2476   int catnum;
2477   mm_appref_t *ref;
2478 } ui_touch_t;
2479 #define MAXTOUCH 100
2480 ui_touch_t ui_touchrects [ MAXTOUCH ];
2481 unsigned char ui_touchrect_count = 0;
2482
2483 void ui_register_reset ( void ) {
2484   bzero ( ui_touchrects, sizeof(ui_touch_t)*MAXTOUCH );
2485   ui_touchrect_count = 0;
2486   return;
2487 }
2488
2489 void ui_register_tab ( unsigned char catnum, unsigned int x, unsigned int y, unsigned int w, unsigned int h ) {
2490
2491   if ( ui_touchrect_count == MAXTOUCH ) {
2492     return;
2493   }
2494
2495   ui_touchrects [ ui_touchrect_count ].r.x = x;
2496   ui_touchrects [ ui_touchrect_count ].r.y = y;
2497   ui_touchrects [ ui_touchrect_count ].r.w = w;
2498   ui_touchrects [ ui_touchrect_count ].r.h = h;
2499   ui_touchrects [ ui_touchrect_count ].catnum = catnum;
2500   ui_touchrect_count++;
2501
2502   return;
2503 }
2504
2505 void ui_register_app ( mm_appref_t *app, unsigned int x, unsigned int y, unsigned int w, unsigned int h ) {
2506
2507   if ( ui_touchrect_count == MAXTOUCH ) {
2508     return;
2509   }
2510
2511   ui_touchrects [ ui_touchrect_count ].r.x = x;
2512   ui_touchrects [ ui_touchrect_count ].r.y = y;
2513   ui_touchrects [ ui_touchrect_count ].r.w = w;
2514   ui_touchrects [ ui_touchrect_count ].r.h = h;
2515   ui_touchrects [ ui_touchrect_count ].ref = app;
2516   ui_touchrect_count++;
2517
2518   return;
2519 }
2520
2521 void ui_touch_act ( unsigned int x, unsigned int y ) {
2522
2523   unsigned char i;
2524   ui_touch_t *t;
2525
2526   for ( i = 0; i < ui_touchrect_count; i++ ) {
2527     t = &(ui_touchrects [ i ]);
2528
2529     if ( x >= t -> r.x &&
2530          x <= t -> r.x + t -> r.w &&
2531          y >= t -> r.y &&
2532          y <= t -> r.y + t -> r.h
2533        )
2534     {
2535
2536       if ( t -> ref ) {
2537         ui_selected = t -> ref;
2538         ui_push_exec();
2539       } else {
2540         if ( ui_category != t -> catnum ) {
2541           ui_selected = NULL;
2542         }
2543         ui_category = t -> catnum;
2544         render_mask |= CHANGED_CATEGORY;
2545         // rescan the dir
2546         category_fs_restock ( g_categories [ ui_category ] );
2547       }
2548
2549       break;
2550     }
2551
2552   } // for
2553
2554   return;
2555 }
2556
2557 unsigned char ui_forkexec ( char *argv[] ) {
2558   char *fooby = argv[0];
2559   int x;
2560
2561   if ( ( x = fork() ) < 0 ) {
2562     pnd_log ( pndn_error, "ERROR: Couldn't fork() for '%s'\n", fooby );
2563     return ( 0 );
2564   }
2565
2566   if ( x == 0 ) { // child
2567     execv ( fooby, argv );
2568     pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", fooby );
2569     return ( 0 );
2570   }
2571
2572   // parent, success
2573   return ( 1 );
2574 }
2575
2576 unsigned char ui_threaded_timer_create ( void ) {
2577
2578   g_timer_thread = SDL_CreateThread ( (void*)ui_threaded_timer, NULL );
2579
2580   if ( ! g_timer_thread ) {
2581     pnd_log ( pndn_error, "ERROR: Couldn't create timer thread\n" );
2582     return ( 0 );
2583   }
2584
2585   return ( 1 );
2586 }
2587
2588 int ui_threaded_timer ( pnd_disco_t *p ) {
2589
2590   // this timer's job is to ..
2591   // - do nothing for quite awhile
2592   // - on wake, post event to SDL event queue, so that main thread will check if SD insert/eject occurred
2593   // - goto 10
2594
2595   unsigned int delay_s = 2; // seconds
2596
2597   while ( 1 ) {
2598
2599     // pause...
2600     sleep ( delay_s );
2601
2602     // .. trigger SD check
2603     SDL_Event e;
2604     bzero ( &e, sizeof(SDL_Event) );
2605     e.type = SDL_USEREVENT;
2606     e.user.code = sdl_user_checksd;
2607     SDL_PushEvent ( &e );
2608
2609   } // while
2610
2611   return ( 0 );
2612 }
2613
2614 unsigned char ui_threaded_defered_preview ( pnd_disco_t *p ) {
2615
2616   if ( ! cache_preview ( p, pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 ),
2617                          pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 ) )
2618      )
2619   {
2620     pnd_log ( pndn_debug, "THREAD: Couldn't load preview pic: '%s' -> '%s'\n",
2621               IFNULL(p->title_en,"No Name"), p -> preview_pic1 );
2622   }
2623
2624   // trigger that we completed
2625   SDL_Event e;
2626   bzero ( &e, sizeof(SDL_Event) );
2627   e.type = SDL_USEREVENT;
2628   e.user.code = sdl_user_finishedpreview;
2629   e.user.data1 = p;
2630   SDL_PushEvent ( &e );
2631
2632   return ( 0 );
2633 }
2634
2635 SDL_Thread *g_icon_thread = NULL;
2636 void ui_post_scan ( void ) {
2637
2638   // if deferred icon load, kick off the thread now
2639   if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_icons_later", 0 ) == 1 ) {
2640
2641     g_icon_thread = SDL_CreateThread ( (void*)ui_threaded_defered_icon, NULL );
2642
2643     if ( ! g_icon_thread ) {
2644       pnd_log ( pndn_error, "ERROR: Couldn't create icon thread\n" );
2645     }
2646
2647   } // deferred icon load
2648
2649   // reset view
2650   ui_selected = NULL;
2651   ui_rows_scrolled_down = 0;
2652   // set back to first tab, to be safe
2653   ui_category = 0;
2654   ui_catshift = 0;
2655
2656   // do we have a preferred category to jump to?
2657   char *dc = pnd_conf_get_as_char ( g_conf, "categories.default_cat" );
2658   if ( dc ) {
2659
2660     // attempt to find default cat; if we do find it, select it; otherwise
2661     // default behaviour will pick first cat (ie: usually All)
2662     unsigned int i;
2663     for ( i = 0; i < g_categorycount; i++ ) {
2664       if ( strcasecmp ( g_categories [ i ] -> catname, dc ) == 0 ) {
2665         ui_category = i;
2666         // ensure visibility
2667         unsigned int screen_width = ui_display_context.screen_width;
2668         unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
2669         if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
2670           ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
2671         }
2672         break;
2673       }
2674     }
2675
2676     if ( i == g_categorycount ) {
2677       pnd_log ( pndn_warning, "  User defined default category '%s' but not found, so using default behaviour\n", dc );
2678     }
2679
2680   } // default cat
2681
2682   // if we're sent right to a dirbrowser tab, restock it now (normally we restock on entry)
2683   if ( g_categories [ ui_category ] -> fspath ) {
2684     printf ( "Restock on start: '%s'\n", g_categories [ ui_category ] -> fspath );
2685     category_fs_restock ( g_categories [ ui_category ] );
2686   }
2687
2688   // redraw all
2689   render_mask |= CHANGED_EVERYTHING;
2690
2691   return;
2692 }
2693
2694 unsigned char ui_threaded_defered_icon ( void *p ) {
2695   extern pnd_box_handle g_active_apps;
2696   pnd_box_handle h = g_active_apps;
2697
2698   unsigned char maxwidth, maxheight;
2699   maxwidth = pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 );
2700   maxheight = pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_height", 50 );
2701
2702   pnd_disco_t *iter = pnd_box_get_head ( h );
2703
2704   while ( iter ) {
2705
2706     // cache it
2707     if ( iter -> pnd_icon_pos &&
2708          ! cache_icon ( iter, maxwidth, maxheight ) )
2709     {
2710       pnd_log ( pndn_warning, "  Couldn't load icon: '%s'\n", IFNULL(iter->title_en,"No Name") );
2711     } else {
2712
2713       // trigger that we completed
2714       SDL_Event e;
2715       bzero ( &e, sizeof(SDL_Event) );
2716       e.type = SDL_USEREVENT;
2717       e.user.code = sdl_user_finishedicon;
2718       SDL_PushEvent ( &e );
2719
2720       //pnd_log ( pndn_warning, "  Finished deferred load icon: '%s'\n", IFNULL(iter->title_en,"No Name") );
2721       usleep ( pnd_conf_get_as_int_d ( g_conf, "minimenu.defer_icon_us", 50000 ) );
2722
2723     }
2724
2725     // next
2726     iter = pnd_box_get_next ( iter );
2727   } // while
2728
2729   return ( 0 );
2730 }
2731
2732 void ui_show_hourglass ( unsigned char updaterect ) {
2733
2734   SDL_Rect dest;
2735   SDL_Surface *s = g_imagecache [ IMG_HOURGLASS ].i;
2736
2737   dest.x = ( 800 - s -> w ) / 2;
2738   dest.y = ( 480 - s -> h ) / 2;
2739
2740   SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, &dest );
2741
2742   if ( updaterect ) {
2743     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2744   }
2745
2746   return;
2747 }
2748
2749 unsigned char ui_pick_skin ( void ) {
2750 #define MAXSKINS 10
2751   char *skins [ MAXSKINS ];
2752   unsigned char iter;
2753
2754   char *searchpath = pnd_conf_get_as_char ( g_conf, "minimenu.skin_searchpath" );
2755   char tempname [ 100 ];
2756
2757   iter = 0;
2758
2759   skins [ iter++ ] = "No skin change";
2760
2761   SEARCHPATH_PRE
2762   {
2763     DIR *d = opendir ( buffer );
2764
2765     if ( d ) {
2766       struct dirent *dd;
2767
2768       while ( ( dd = readdir ( d ) ) ) {
2769
2770         if ( dd -> d_name [ 0 ] == '.' ) {
2771           // ignore
2772         } else if ( ( dd -> d_type == DT_DIR || dd -> d_type == DT_UNKNOWN ) &&
2773                     iter < MAXSKINS )
2774         {
2775           snprintf ( tempname, 100, "Skin: %s", dd -> d_name );
2776           skins [ iter++ ] = strdup ( tempname );
2777         }
2778
2779       }
2780
2781       closedir ( d );
2782     }
2783
2784   }
2785   SEARCHPATH_POST
2786
2787   int sel = ui_modal_single_menu ( skins, iter, "Skins", "Enter to select; other to return." );
2788
2789   // did they pick one?
2790   if ( sel > 0 ) {
2791     FILE *f;
2792
2793     char *s = strdup ( pnd_conf_get_as_char ( g_conf, "minimenu.skin_selected" ) );
2794     s = pnd_expand_tilde ( s );
2795
2796     f = fopen ( s, "w" );
2797
2798     free ( s );
2799
2800     if ( f ) {
2801       fprintf ( f, "%s\n", skins [ sel ] + 6 );
2802       fclose ( f );
2803     }
2804
2805     return ( 1 );
2806   }
2807
2808   return ( 0 );
2809 }
2810
2811 void ui_aboutscreen ( char *textpath ) {
2812 #define PIXELW 7
2813 #define PIXELH 7
2814 #define MARGINW 3
2815 #define MARGINH 3
2816 #define SCRW 800
2817 #define SCRH 480
2818 #define ROWS SCRH / ( PIXELH + MARGINH )
2819 #define COLS SCRW / ( PIXELW + MARGINW )
2820
2821   unsigned char pixelboard [ ROWS * COLS ]; // pixel heat
2822   bzero ( pixelboard, ROWS * COLS );
2823
2824   SDL_Surface *rtext;
2825   SDL_Rect r;
2826
2827   SDL_Color rtextc = { 200, 200, 200, 100 };
2828
2829   // pixel scroller
2830   char *textloop [ 500 ];
2831   unsigned int textmax = 0;
2832   bzero ( textloop, 500 * sizeof(char*) );
2833
2834   // cursor scroller
2835   char cbuffer [ 50000 ];
2836   bzero ( cbuffer, 50000 );
2837   unsigned int crevealed = 0;
2838
2839   FILE *f = fopen ( textpath, "r" );
2840
2841   if ( ! f ) {
2842     pnd_log ( pndn_error, "ERROR: Couldn't open about text: %s\n", textpath );
2843     return;
2844   }
2845
2846   char textbuf [ 100 ];
2847   while ( fgets ( textbuf, 100, f ) ) {
2848
2849     // add to full buffer
2850     strncat ( cbuffer, textbuf, 50000 );
2851
2852     // chomp
2853     if ( strchr ( textbuf, '\n' ) ) {
2854       * strchr ( textbuf, '\n' ) = '\0';
2855     }
2856
2857     // add to pixel loop
2858     if ( 1||textbuf [ 0 ] ) {
2859       textloop [ textmax ] = strdup ( textbuf );
2860       textmax++;
2861     }
2862
2863   } // while fgets
2864
2865   fclose ( f );
2866
2867   unsigned int textiter = 0;
2868   while ( textiter < textmax ) {
2869     char *text = textloop [ textiter ];
2870
2871     rtext = NULL;
2872     if ( text [ 0 ] ) {
2873       // render to surface
2874       rtext = TTF_RenderText_Blended ( g_grid_font, text, rtextc );
2875
2876       // render font to pixelboard
2877       unsigned int px, py;
2878       unsigned char *ph;
2879       unsigned int *pixels = rtext -> pixels;
2880       unsigned char cr, cg, cb, ca;
2881       for ( py = 0; py < rtext -> h; py ++ ) {
2882         for ( px = 0; px < ( rtext -> w > COLS ? COLS : rtext -> w ); px++ ) {
2883
2884           SDL_GetRGBA ( pixels [ ( py * rtext -> pitch / 4 ) + px ],
2885                         rtext -> format, &cr, &cg, &cb, &ca );
2886
2887           if ( ca != 0 ) {
2888
2889             ph = pixelboard + ( /*y offset */ 30 * COLS ) + ( py * COLS ) + px /* / 2 */;
2890
2891             if ( *ph < 100 ) {
2892               *ph = 100;
2893             }
2894
2895             ca /= 5;
2896             if ( *ph + ca < 250 ) {
2897               *ph += ca;
2898             }
2899
2900           } // got a pixel?
2901
2902         } // x
2903       } // y
2904
2905     } // got text?
2906
2907     unsigned int runcount = 10;
2908     while ( runcount-- ) {
2909
2910       // clear display
2911       SDL_FillRect( sdl_realscreen, NULL /* whole */, 0 );
2912
2913       // render pixelboard
2914       unsigned int x, y;
2915       unsigned int c;
2916       for ( y = 0; y < ROWS; y++ ) {
2917         for ( x = 0; x < COLS; x++ ) {
2918
2919           if ( 1||pixelboard [ ( y * COLS ) + x ] ) {
2920
2921             // position
2922             r.x = x * ( PIXELW + MARGINW );
2923             r.y = y * ( PIXELH + MARGINH );
2924             r.w = PIXELW;
2925             r.h = PIXELH;
2926             // heat -> colour
2927             c = SDL_MapRGB ( sdl_realscreen -> format, 100 /* r */, 0 /* g */, pixelboard [ ( y * COLS ) + x ] );
2928             // render
2929             SDL_FillRect( sdl_realscreen, &r /* whole */, c );
2930
2931           }
2932
2933         } // x
2934       } // y
2935
2936       // cool pixels
2937       unsigned char *pc = pixelboard;
2938       for ( y = 0; y < ROWS; y++ ) {
2939         for ( x = 0; x < COLS; x++ ) {
2940
2941           if ( *pc > 10 ) {
2942             (*pc) -= 3;
2943           }
2944
2945           pc++;
2946         } // x
2947       } // y
2948
2949       // slide pixels upwards
2950       memmove ( pixelboard, pixelboard + COLS, ( COLS * ROWS ) - COLS );
2951
2952       // render actual readable text
2953       {
2954
2955         // display up to cursor
2956         SDL_Rect dest;
2957         unsigned int cdraw = 0;
2958         SDL_Surface *cs;
2959         char ctb [ 2 ];
2960
2961         if ( crevealed > 200 ) {
2962           cdraw = crevealed - 200;
2963         }
2964
2965         dest.x = 400;
2966         dest.y = 20;
2967
2968         for ( ; cdraw < crevealed; cdraw++ ) {
2969           ctb [ 0 ] = cbuffer [ cdraw ];
2970           ctb [ 1 ] = '\0';
2971           // move over or down
2972           if ( cbuffer [ cdraw ] == '\n' ) {
2973             // EOL
2974             dest.x = 400;
2975             dest.y += 14;
2976
2977             if ( dest.y > 450 ) {
2978               dest.y = 450;
2979             }
2980
2981           } else {
2982             // draw the char
2983             cs = TTF_RenderText_Blended ( g_tab_font, ctb, rtextc );
2984             if ( cs ) {
2985               SDL_BlitSurface ( cs, NULL /* all */, sdl_realscreen, &dest );
2986               SDL_FreeSurface ( cs );
2987               // over
2988               dest.x += cs -> w;
2989             }
2990           }
2991
2992         }
2993
2994         dest.w = 10;
2995         dest.h = 20;
2996         SDL_FillRect ( sdl_realscreen, &dest /* whole */, 220 );
2997
2998         // increment cursor to next character
2999         if ( cbuffer [ crevealed ] != '\0' ) {
3000           crevealed++;
3001         }
3002
3003       } // draw cursor text
3004
3005       // reveal
3006       //
3007       SDL_UpdateRect ( sdl_realscreen, 0, 0, 0, 0 ); // whole screen
3008
3009       usleep ( 50000 );
3010
3011       // any button? if so, about
3012       {
3013         SDL_PumpEvents();
3014
3015         SDL_Event e;
3016
3017         if ( SDL_PeepEvents ( &e, 1, SDL_GETEVENT, SDL_EVENTMASK(/*SDL_KEYUP|*/SDL_KEYDOWN) ) > 0 ) {
3018           return;
3019         }
3020
3021       }
3022
3023     } // while cooling
3024
3025     if ( rtext ) {
3026       SDL_FreeSurface ( rtext );
3027     }
3028
3029     textiter++;
3030   } // while more text
3031
3032   // free up
3033   unsigned int i;
3034   for ( i = 0; i < textmax; i++ ) {
3035     if ( textloop [ i ] ) {
3036       free ( textloop [ i ] );
3037       textloop [ i ] = 0;
3038     }
3039   }
3040
3041   return;
3042 }
3043
3044 void ui_revealscreen ( void ) {
3045   char *labels [ 500 ];
3046   unsigned int labelmax = 0;
3047   unsigned int i;
3048   char fulllabel [ 200 ];
3049
3050   if ( ! category_count ( CFHIDDEN ) ) {
3051     return; // nothing to do
3052   }
3053
3054   // switch to hidden categories
3055   category_publish ( CFHIDDEN, NULL );
3056
3057   // build up labels to show in menu
3058   for ( i = 0; i < g_categorycount; i++ ) {
3059
3060     if ( g_categories [ i ] -> parent_catname ) {
3061       sprintf ( fulllabel, "%s [%s]", g_categories [ i ] -> catname, g_categories [ i ] -> parent_catname );
3062     } else {
3063       sprintf ( fulllabel, "%s", g_categories [ i ] -> catname );
3064     }
3065
3066     labels [ labelmax++ ] = strdup ( fulllabel );
3067   }
3068
3069   // show menu
3070   int sel = ui_modal_single_menu ( labels, labelmax, "Temporary Category Reveal",
3071                                    "Enter to select; other to return." );
3072
3073   // if selected, try to set this guy to visible
3074   if ( sel >= 0 ) {
3075
3076     // fix up category name, if its been hacked
3077     if ( strchr ( g_categories [ sel ] -> catname, '.' ) ) {
3078       char *t = g_categories [ sel ] -> catname;
3079       g_categories [ sel ] -> catname = strdup ( strchr ( g_categories [ sel ] -> catname, '.' ) + 1 );
3080       free ( t );
3081     }
3082
3083     // reflag this guy to be visible
3084     g_categories [ sel ] -> catflags = CFNORMAL;
3085
3086     // switch to the new category.. cache name.
3087     char *switch_to_name = g_categories [ sel ] -> catname;
3088
3089     // republish categories
3090     category_publish ( CFNORMAL, NULL );
3091
3092     // switch to the new category.. with the cached name!
3093     ui_category = category_index ( switch_to_name );
3094
3095     // ensure visibility
3096     unsigned int screen_width = ui_display_context.screen_width;
3097     unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
3098     if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
3099       ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
3100     }
3101
3102     // redraw tabs
3103     render_mask |= CHANGED_CATEGORY;
3104   }
3105
3106   for ( i = 0; i < g_categorycount; i++ ) {
3107     free ( labels [ i ] );
3108   }
3109
3110   return;
3111 }
3112
3113 void ui_recache_context ( ui_context_t *c ) {
3114
3115   c -> screen_width = pnd_conf_get_as_int_d ( g_conf, "display.screen_width", 800 );
3116
3117   c -> font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
3118   c -> font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
3119   c -> font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
3120   c -> font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
3121
3122   c -> grid_offset_x = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_x" );
3123   c -> grid_offset_y = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_y" );
3124
3125   c -> icon_offset_x = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_x" );
3126   c -> icon_offset_y = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_y" );
3127   c -> icon_max_width = pnd_conf_get_as_int ( g_conf, "grid.icon_max_width" );
3128   c -> icon_max_height = pnd_conf_get_as_int ( g_conf, "grid.icon_max_height" );
3129   c -> sel_icon_offset_x = pnd_conf_get_as_int_d ( g_conf, "grid.sel_offoffset_x", 0 );
3130   c -> sel_icon_offset_y = pnd_conf_get_as_int_d ( g_conf, "grid.sel_offoffset_y", 0 );
3131
3132   c -> text_width = pnd_conf_get_as_int ( g_conf, "grid.text_width" );
3133   c -> text_clip_x = pnd_conf_get_as_int ( g_conf, "grid.text_clip_x" );
3134   c -> text_offset_x = pnd_conf_get_as_int ( g_conf, "grid.text_offset_x" );
3135   c -> text_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_offset_y" );
3136   c -> text_hilite_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_hilite_offset_y" );
3137
3138   c -> row_max = pnd_conf_get_as_int_d ( g_conf, "grid.row_max", 4 );
3139   c -> col_max = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 );
3140
3141   c -> cell_width = pnd_conf_get_as_int ( g_conf, "grid.cell_width" );
3142   c -> cell_height = pnd_conf_get_as_int ( g_conf, "grid.cell_height" );
3143
3144   c -> arrow_bar_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x", 450 );
3145   c -> arrow_bar_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y", 100 );
3146   c -> arrow_bar_clip_w = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_w", 10 );
3147   c -> arrow_bar_clip_h = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_h", 100 );
3148   c -> arrow_up_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x", 450 );
3149   c -> arrow_up_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y", 80 );
3150   c -> arrow_down_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x", 450 );
3151   c -> arrow_down_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y", 80 );
3152
3153   // font colour
3154   SDL_Color tmp = { c -> font_rgba_r, c -> font_rgba_g, c -> font_rgba_b, c -> font_rgba_a };
3155   c -> fontcolor = tmp;
3156
3157   // now that we've got 'normal' (detail pane shown) param's, lets check if detail pane
3158   // is hidden; if so, override some values with those alternate skin values where possible.
3159   if ( ui_detail_hidden ) {
3160     // if detail panel is hidden, and theme cannot support it, unhide the bloody thing. (This may help
3161     // when someone is amid theme hacking or changing.)
3162     if ( ! ui_is_detail_hideable() ) {
3163       ui_detail_hidden = 0;
3164     }
3165
3166     // still hidden?
3167     if ( ui_detail_hidden ) {
3168
3169       c -> row_max = pnd_conf_get_as_int_d ( g_conf, "grid.row_max_w", c -> row_max );
3170       c -> col_max = pnd_conf_get_as_int_d ( g_conf, "grid.col_max_w", c -> col_max );
3171
3172       c -> cell_width = pnd_conf_get_as_int_d ( g_conf, "grid.cell_width_w", c -> cell_width );
3173       c -> cell_height = pnd_conf_get_as_int_d ( g_conf, "grid.cell_height_w", c -> cell_height );
3174
3175       c -> arrow_bar_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x_w", 450 );
3176       c -> arrow_bar_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y_w", 100 );
3177       c -> arrow_up_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x_w", 450 );
3178       c -> arrow_up_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y_w", 80 );
3179       c -> arrow_down_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x_w", 450 );
3180       c -> arrow_down_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y_w", 80 );
3181
3182     } // if detail hidden.. still.
3183
3184   } // if detail hidden
3185
3186   return;
3187 }
3188
3189 unsigned char ui_is_detail_hideable ( void ) {
3190
3191   // if skin has a bit of info for wide-mode, we assume wide-mode is available
3192   if ( pnd_conf_get_as_char ( g_conf, "grid.row_max_w" ) != NULL ) {
3193     return ( 1 );
3194   }
3195
3196   // else not!
3197   return ( 0 );
3198 }
3199
3200 void ui_toggle_detail_pane ( void ) {
3201
3202   // no bitmask trickery here; I like it to be stand-out obvious at 3am.
3203
3204   if ( ui_detail_hidden ) {
3205     ui_detail_hidden = 0;
3206   } else {
3207     ui_detail_hidden = 1;
3208   }
3209
3210   // repull skin config
3211   ui_recache_context ( &ui_display_context );
3212
3213   // redraw
3214   render_mask |= CHANGED_EVERYTHING;
3215
3216   return;
3217 }
3218
3219 void ui_menu_context ( mm_appref_t *a ) {
3220
3221   unsigned char rescan_apps = 0;
3222   unsigned char context_alive = 1;
3223
3224   enum {
3225     context_done = 0,
3226     context_file_info,
3227     context_file_delete,
3228     context_app_info,
3229     context_app_hide,
3230     context_app_recategorize,
3231     context_app_recategorize_sub,
3232     context_app_rename,
3233     context_app_cpuspeed,
3234     context_app_run,
3235     context_menu_max
3236   };
3237
3238   char *verbiage[] = {
3239     "Done (return to grid)",      // context_done
3240     "Get info about file/dir",    // context_file_info
3241     "Delete file/dir",            // context_file_delete
3242     "Get info",                   // context_app_info
3243     "Hide application",           //             hide
3244     "Recategorize",               //             recategorize
3245     "Recategorize subcategory",   //             recategorize
3246     "Change displayed title",     //             rename
3247     "Set CPU speed for launch",   //             cpuspeed
3248     "Run application"             //             run
3249   };
3250
3251   unsigned short int menu [ context_menu_max ];
3252   char *menustring [ context_menu_max ];
3253   unsigned char menumax = 0;
3254
3255   // ++ done
3256   menu [ menumax ] = context_done; menustring [ menumax++ ] = verbiage [ context_done ];
3257
3258   // hook up appropriate menu options based on tab-type and object-type
3259   if ( g_categories [ ui_category ] -> fspath ) {
3260     return; // TBD
3261     menu [ menumax ] = context_file_info; menustring [ menumax++ ] = verbiage [ context_file_info ];
3262     menu [ menumax ] = context_file_delete; menustring [ menumax++ ] = verbiage [ context_file_delete ];
3263   } else {
3264     //menu [ menumax ] = context_app_info; menustring [ menumax++ ] = verbiage [ context_app_info ];
3265     menu [ menumax ] = context_app_hide; menustring [ menumax++ ] = verbiage [ context_app_hide ];
3266     menu [ menumax ] = context_app_recategorize; menustring [ menumax++ ] = verbiage [ context_app_recategorize ];
3267     menu [ menumax ] = context_app_recategorize_sub; menustring [ menumax++ ] = verbiage [ context_app_recategorize_sub ];
3268     menu [ menumax ] = context_app_rename; menustring [ menumax++ ] = verbiage [ context_app_rename ];
3269     menu [ menumax ] = context_app_cpuspeed; menustring [ menumax++ ] = verbiage [ context_app_cpuspeed ];
3270     menu [ menumax ] = context_app_run; menustring [ menumax++ ] = verbiage [ context_app_run ];
3271   }
3272
3273   // operate the menu
3274   while ( context_alive ) {
3275
3276     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 */ );
3277
3278     if ( sel < 0 ) {
3279       context_alive = 0;
3280
3281     } else {
3282
3283       switch ( menu [ sel ] ) {
3284
3285       case context_done:
3286         context_alive = 0;
3287         break;
3288
3289       case context_file_info:
3290         break;
3291
3292       case context_file_delete:
3293         //ui_menu_twoby ( "Delete - Are you sure?", "B/enter; other to cancel", "Delete", "Do not delete" );
3294         break;
3295
3296       case context_app_info:
3297         break;
3298
3299       case context_app_hide:
3300         {
3301           // determine key
3302           char confkey [ 1000 ];
3303           snprintf ( confkey, 999, "%s.%s", "appshow", a -> ref -> unique_id );
3304
3305           // turn app 'off'
3306           pnd_conf_set_char ( g_conf, confkey, "0" );
3307
3308           // write conf, so it will take next time
3309           conf_write ( g_conf, conf_determine_location ( g_conf ) );
3310
3311           // request rescan and wrap up
3312           rescan_apps++;
3313           context_alive = 0; // nolonger visible, so lets just get out
3314
3315         }
3316     
3317         break;
3318
3319       case context_app_recategorize:
3320         {
3321           char *opts [ 250 ];
3322           unsigned char optmax = 0;
3323           unsigned char i;
3324
3325           i = 0;
3326           while ( 1 ) {
3327
3328             if ( ! freedesktop_complete [ i ].cat ) {
3329               break;
3330             }
3331
3332             if ( ! freedesktop_complete [ i ].parent_cat ) {
3333               opts [ optmax++ ] = freedesktop_complete [ i ].cat;
3334             }
3335
3336             i++;
3337           } // while
3338
3339           char prompt [ 101 ];
3340           snprintf ( prompt, 100, "Pick category [%s]", a -> ref -> main_category ? a -> ref -> main_category : "NoParentCategory" );
3341
3342           int sel = ui_modal_single_menu ( opts, optmax, prompt /*"Select parent category"*/, "Enter to select; other to skip." );
3343
3344           if ( sel >= 0 ) {
3345             char confirm [ 1001 ];
3346             snprintf ( confirm, 1000, "Confirm: %s", opts [ sel ] );
3347
3348             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm categorization", "Do not set category" ) == 1 ) {
3349               ovr_replace_or_add ( a, "maincategory", opts [ sel ] );
3350               rescan_apps++;
3351             }
3352
3353           }
3354
3355         }
3356         break;
3357
3358       case context_app_recategorize_sub:
3359         {
3360           char *opts [ 250 ];
3361           unsigned char optmax = 0;
3362           unsigned char i;
3363
3364           i = 0;
3365           while ( 1 ) {
3366
3367             if ( ! freedesktop_complete [ i ].cat ) {
3368               break;
3369             }
3370
3371             if ( freedesktop_complete [ i ].parent_cat ) {
3372               opts [ optmax++ ] = freedesktop_complete [ i ].cat;
3373             }
3374
3375             i++;
3376           } // while
3377
3378           char prompt [ 101 ];
3379           snprintf ( prompt, 100, "Pick subcategory [%s]", a -> ref -> main_category1 ? a -> ref -> main_category1 : "NoSubcategory" );
3380
3381           int sel = ui_modal_single_menu ( opts, optmax, prompt /*"Select subcategory"*/, "Enter to select; other to skip." );
3382
3383           if ( sel >= 0 ) {
3384             char confirm [ 1001 ];
3385             snprintf ( confirm, 1000, "Confirm: %s", opts [ sel ] );
3386
3387             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm sub-categorization", "Do not set sub-category" ) == 1 ) {
3388               ovr_replace_or_add ( a, "maincategorysub1", opts [ sel ] );
3389               rescan_apps++;
3390             }
3391
3392           }
3393
3394         }
3395         break;
3396
3397       case context_app_rename:
3398         {
3399           char namebuf [ 101 ];
3400           unsigned char changed;
3401
3402           changed = ui_menu_get_text_line ( "Rename application", "Use keyboard; Enter when done.",
3403                                             a -> ref -> title_en ? a -> ref -> title_en : "blank", namebuf, 30, 0 /* alphanumeric */ );
3404
3405           if ( changed ) {
3406             char confirm [ 1001 ];
3407             snprintf ( confirm, 1000, "Confirm: %s", namebuf );
3408
3409             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm rename", "Do not rename" ) == 1 ) {
3410               ovr_replace_or_add ( a, "title", namebuf );
3411               rescan_apps++;
3412             }
3413
3414           }
3415
3416         }
3417
3418         break;
3419
3420       case context_app_cpuspeed:
3421         {
3422           char namebuf [ 101 ];
3423           unsigned char changed;
3424
3425           changed = ui_menu_get_text_line ( "Specify runspeed", "Use keyboard; Enter when done.",
3426                                             a -> ref -> clockspeed ? a -> ref -> clockspeed : "500", namebuf, 6, 1 /* numeric */ );
3427
3428           if ( changed ) {
3429             char confirm [ 1001 ];
3430             snprintf ( confirm, 1000, "Confirm: %s", namebuf );
3431
3432             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm clockspeed", "Do not set" ) == 1 ) {
3433               ovr_replace_or_add ( a, "clockspeed", namebuf );
3434               rescan_apps++;
3435             }
3436
3437           }
3438
3439         }
3440
3441         break;
3442
3443       case context_app_run:
3444         ui_push_exec();
3445         break;
3446
3447       default:
3448         return;
3449
3450       } // switch
3451
3452     } // if useful return
3453
3454   } // menu is alive?
3455
3456   // rescan apps?
3457   if ( rescan_apps ) {
3458     applications_free();
3459     applications_scan();
3460   }
3461
3462   return;
3463 }
3464
3465 unsigned char ui_menu_twoby ( char *title, char *footer, char *one, char *two ) {
3466   char *opts [ 3 ];
3467   opts [ 0 ] = one;
3468   opts [ 1 ] = two;
3469   int sel = ui_modal_single_menu ( opts, 2, title, footer );
3470   if ( sel < 0 ) {
3471     return ( 0 );
3472   }
3473   return ( sel + 1 );
3474 }
3475
3476 unsigned char ui_menu_get_text_line ( char *title, char *footer, char *initialvalue,
3477                                       char *r_buffer, unsigned char maxlen, unsigned char numbersonlyp )
3478 {
3479   SDL_Rect rects [ 40 ];
3480   SDL_Rect *dest = rects;
3481   SDL_Rect src;
3482   SDL_Surface *rtext;
3483
3484   char hacktext [ 1024 ];
3485   unsigned char shifted = 0;
3486
3487   bzero ( rects, sizeof(SDL_Rect) * 40 );
3488
3489   if ( initialvalue ) {
3490     strncpy ( r_buffer, initialvalue, maxlen );
3491   } else {
3492     bzero ( r_buffer, maxlen );
3493   }
3494
3495   while ( 1 ) {
3496
3497     // clear
3498     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
3499     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
3500     dest -> w = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> w;
3501     dest -> h = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h;
3502     SDL_FillRect( sdl_realscreen, dest, 0 );
3503
3504     // show dialog background
3505     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
3506       src.x = 0;
3507       src.y = 0;
3508       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> w;
3509       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> h;
3510       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
3511       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
3512       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
3513       dest++;
3514     }
3515
3516     // show dialog frame
3517     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
3518       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
3519       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
3520       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
3521       dest++;
3522     }
3523
3524     // show header
3525     if ( title ) {
3526       rtext = TTF_RenderText_Blended ( g_tab_font, title, ui_display_context.fontcolor );
3527       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
3528       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 20;
3529       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
3530       SDL_FreeSurface ( rtext );
3531       dest++;
3532     }
3533
3534     // show footer
3535     if ( footer ) {
3536       rtext = TTF_RenderText_Blended ( g_tab_font, footer, ui_display_context.fontcolor );
3537       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
3538       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) +
3539         ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h
3540         - 60;
3541       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
3542       SDL_FreeSurface ( rtext );
3543       dest++;
3544     }
3545
3546     // show text line - and embed cursor
3547     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
3548     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 40 + ( 20 * ( 0/*i*/ + 1 - 0/*first_visible*/ ) );
3549
3550     strncpy ( hacktext, r_buffer, 1000 );
3551     strncat ( hacktext, "\n", 1000 ); // add [] in most fonts
3552
3553     rtext = TTF_RenderText_Blended ( g_tab_font, hacktext, ui_display_context.fontcolor );
3554     SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
3555     SDL_FreeSurface ( rtext );
3556     dest++;
3557
3558     // update all the rects and send it all to sdl
3559     SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
3560     dest = rects;
3561
3562     // check for input
3563     SDL_Event event;
3564     while ( SDL_WaitEvent ( &event ) ) {
3565
3566       switch ( event.type ) {
3567
3568       case SDL_KEYUP:
3569         if ( event.key.keysym.sym == SDLK_LSHIFT || event.key.keysym.sym == SDLK_RSHIFT ) {
3570           shifted = 0;
3571         }
3572         break;
3573
3574       case SDL_KEYDOWN:
3575
3576         if ( event.key.keysym.sym == SDLK_LEFT || event.key.keysym.sym == SDLK_BACKSPACE ) {
3577           char *eol = strchr ( r_buffer, '\0' );
3578           *( eol - 1 ) = '\0';
3579         } else if ( event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_END ) { // return, or "B"
3580           return ( 1 );
3581
3582         } else if ( event.key.keysym.sym == SDLK_LSHIFT || event.key.keysym.sym == SDLK_RSHIFT ) {
3583           shifted = 1;
3584
3585         } else if ( event.key.keysym.sym == SDLK_ESCAPE ||
3586                     event.key.keysym.sym == SDLK_PAGEUP ||
3587                     event.key.keysym.sym == SDLK_PAGEDOWN ||
3588                     event.key.keysym.sym == SDLK_HOME
3589                   )
3590         {
3591           return ( 0 );
3592
3593         } else {
3594
3595           if ( isprint(event.key.keysym.sym) ) {
3596
3597             unsigned char good = 1;
3598
3599             if ( numbersonlyp && ( ! isdigit(event.key.keysym.sym) ) ) {
3600               good = 0;
3601             }
3602
3603             if ( maxlen && strlen(r_buffer) >= maxlen ) {
3604               good = 0;
3605             }
3606
3607             if ( good ) {
3608               char b [ 2 ] = { '\0', '\0' };
3609               if ( shifted ) {
3610                 b [ 0 ] = toupper ( event.key.keysym.sym );
3611               } else {
3612                 b [ 0 ] = event.key.keysym.sym;
3613               }
3614               strncat ( r_buffer, b, maxlen );
3615             } // good?
3616
3617           } // printable?
3618
3619         }
3620
3621         break;
3622
3623       } // switch
3624
3625       break;
3626
3627     } // while waiting for input
3628
3629   } // while
3630   
3631   return ( 0 );
3632 }
3633
3634 unsigned char ovr_replace_or_add ( mm_appref_t *a, char *keybase, char *newvalue ) {
3635   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 );
3636
3637   char fullpath [ PATH_MAX ];
3638
3639   sprintf ( fullpath, "%s/%s", a -> ref -> object_path, a -> ref -> object_filename );
3640   char *dot = strrchr ( fullpath, '.' );
3641   if ( dot ) {
3642     sprintf ( dot, PXML_SAMEPATH_OVERRIDE_FILEEXT );
3643   } else {
3644     fprintf ( stderr, "ERROR: Bad pnd-path in disco_t! %s\n", fullpath );
3645     return ( 0 );
3646   }
3647
3648   struct stat statbuf;
3649
3650   if ( stat ( fullpath, &statbuf ) == 0 ) {
3651     // file exists
3652     pnd_conf_handle h;
3653
3654     h = pnd_conf_fetch_by_path ( fullpath );
3655
3656     if ( ! h ) {
3657       return ( 0 ); // fail!
3658     }
3659
3660     char key [ 101 ];
3661     snprintf ( key, 100, "Application-%u.%s", a -> ref -> subapp_number, keybase );
3662
3663     pnd_conf_set_char ( h, key, newvalue );
3664
3665     return ( pnd_conf_write ( h, fullpath ) );
3666
3667   } else {
3668     // file needs to be created - easy!
3669
3670     FILE *f = fopen ( fullpath, "w" );
3671
3672     if ( f ) {
3673       fprintf ( f, "Application-%u.%s\t%s\n", a -> ref -> subapp_number, keybase, newvalue );
3674       fclose ( f );
3675
3676     } else {
3677       return ( 0 ); // fail!
3678     }
3679
3680   } // new or used?
3681
3682   return ( 1 );
3683 }