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