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