Big pile of changes to support a 'conf UI' in the select menu; can now hide/show...
[pandora-libraries.git] / minimenu / mmenu.c
index 95a8b4b..1f6270c 100644 (file)
@@ -36,6 +36,7 @@
 #include <ctype.h>
 #include <sys/wait.h>
 #include <dirent.h>
+#include <signal.h> // for sigaction
 
 #include "pnd_logger.h"
 #include "pnd_pxml.h"
@@ -47,6 +48,9 @@
 #include "pnd_device.h"
 #include "pnd_pndfiles.h"
 #include "../lib/pnd_pathiter.h"
+#include "pnd_notify.h"
+#include "pnd_dbusnotify.h"
+#include "pnd_apps.h"
 
 #include "mmenu.h"
 #include "mmwrapcmd.h"
@@ -54,6 +58,7 @@
 #include "mmcache.h"
 #include "mmcat.h"
 #include "mmui.h"
+#include "mmconf.h"
 
 pnd_box_handle g_active_apps = NULL;
 unsigned int g_active_appcount = 0;
@@ -65,11 +70,19 @@ char *pnd_run_script = NULL;
 unsigned char g_x11_present = 1; // >0 if X is present
 unsigned char g_catmap = 0; // if 1, we're doing category mapping
 unsigned char g_pvwcache = 0; // if 1, we're trying to do preview caching
+unsigned char g_autorescan = 1; // default to auto rescan
+
+pnd_dbusnotify_handle dbh = 0; // if >0, dbusnotify is active
+pnd_notify_handle nh = 0; // if >0, inotify is active
 
 char g_skin_selected [ 100 ] = "default";
 char *g_skinpath = NULL; // where 'skin_selected' is located .. the fullpath including skin-dir-name
 pnd_conf_handle g_skinconf = NULL;
 
+void sigquit_handler ( int n );
+unsigned char cat_is_visible ( pnd_conf_handle h, char *catname );
+unsigned char app_is_visible ( pnd_conf_handle h, char *uniqueid );
+
 int main ( int argc, char *argv[] ) {
   int logall = -1; // -1 means normal logging rules; >=0 means log all!
   int i;
@@ -157,8 +170,10 @@ int main ( int argc, char *argv[] ) {
   } // spin
   pnd_log ( pndn_rem, "Looks like user '%s' is in, continue.\n", g_username );
 
-  /* conf file
+  /* conf files
    */
+
+  // mmenu conf
   g_conf = pnd_conf_fetch_by_name ( MMENU_CONF, MMENU_CONF_SEARCHPATH );
 
   if ( ! g_conf ) {
@@ -166,6 +181,11 @@ int main ( int argc, char *argv[] ) {
     emit_and_quit ( MM_QUIT );
   }
 
+  // override mmenu conf via user preference conf
+  conf_merge_into ( conf_determine_location ( g_conf ), g_conf );
+  conf_setup_missing ( g_conf );
+
+  // desktop conf for app finding preferences
   g_desktopconf = pnd_conf_fetch_by_id ( pnd_conf_desktop, PND_CONF_SEARCHPATH );
 
   if ( ! g_desktopconf ) {
@@ -173,6 +193,17 @@ int main ( int argc, char *argv[] ) {
     emit_and_quit ( MM_QUIT );
   }
 
+  /* set up quit signal handler
+   */
+  sigset_t ss;
+  sigemptyset ( &ss );
+
+  struct sigaction siggy;
+  siggy.sa_handler = sigquit_handler;
+  siggy.sa_mask = ss; /* implicitly blocks the origin signal */
+  siggy.sa_flags = SA_RESTART; /* don't need anything */
+  sigaction ( SIGQUIT, &siggy, NULL );
+
   /* category conf file
    */
   {
@@ -231,6 +262,12 @@ int main ( int argc, char *argv[] ) {
 
   pnd_log ( pndn_rem, "Found pnd_run.sh at '%s'\n", pnd_run_script );
 
+  // auto rescan?
+  if ( pnd_conf_get_as_int ( g_conf, "minimenu.auto_rescan" ) != PND_CONF_BADNUM ) {
+    g_autorescan = pnd_conf_get_as_int ( g_conf, "minimenu.auto_rescan" );
+  }
+  pnd_log ( pndn_debug, "application rescan is set to: %s\n", g_autorescan ? "auto" : "manual" );
+
   // figure out what skin is selected (or default)
   FILE *f;
   char *s = strdup ( pnd_conf_get_as_char ( g_conf, "minimenu.skin_selected" ) );
@@ -287,6 +324,17 @@ int main ( int argc, char *argv[] ) {
   // lets just merge the skin conf onto the regular conf, so it just magicly works
   pnd_box_append ( g_conf, g_skinconf );
 
+  // did user override the splash image?
+  char *splash = pnd_conf_get_as_char ( g_conf, "minimenu.force_wallpaper" );
+  if ( splash ) {
+    // we've got a filename, presumably; lets see if it exists
+    struct stat statbuf;
+    if ( stat ( splash, &statbuf ) == 0 ) {
+      // file seems to exist, lets set our override to that..
+      pnd_conf_set_char ( g_conf, "graphics.IMG_BACKGROUND_800480", splash );
+    }
+  }
+
   // attempt to set up UI
   if ( ! ui_setup() ) {
     pnd_log ( pndn_error, "ERROR: Couldn't set up the UI!\n" );
@@ -323,7 +371,28 @@ int main ( int argc, char *argv[] ) {
 
   /* actual work now
    */
+  unsigned char block = 1;
+
+  if ( g_autorescan ) {
+    block = 0;
+
+    // set up notifications
+    dbh = pnd_dbusnotify_init();
+    pnd_log ( pndn_debug, "Setting up dbusnotify\n" );
+    //setup_notifications();
+
+  } // set up rescan
+
+  /* set speed to minimenu run-speed, now that we're all set up
+   */
+  int mm_speed = pnd_conf_get_as_int_d ( g_conf, "minimenu.mm_speed", -1 );
+  if ( mm_speed > 50 && mm_speed < 800 ) {
+    char buffer [ 512 ];
+    snprintf ( buffer, 500, "sudo /usr/pandora/scripts/op_cpuspeed.sh %d", mm_speed );
+    system ( buffer );
+  }
 
+  // do it!
   while ( 1 ) { // forever!
 
     // show the menu, or changes thereof
@@ -331,7 +400,28 @@ int main ( int argc, char *argv[] ) {
 
     // wait for input or time-based events (like animations)
     // deal with inputs
-    ui_process_input ( 1 /* block */ );
+    ui_process_input ( block /* block */ );
+
+    // did a rescan event trigger?
+    if ( g_autorescan ) {
+      unsigned char watch_dbus = 0;
+      unsigned char watch_inotify = 0;
+
+      if ( dbh ) {
+       watch_dbus = pnd_dbusnotify_rediscover_p ( dbh );
+      }
+
+      if ( nh ) {
+       watch_inotify = pnd_notify_rediscover_p ( nh );
+      }
+
+      if ( watch_dbus || watch_inotify ) {
+       pnd_log ( pndn_debug, "dbusnotify detected SD event\n" );
+       applications_free();
+       applications_scan();
+      }
+
+    } // rescan?
 
     // sleep? block?
     usleep ( 5000 );
@@ -343,6 +433,18 @@ int main ( int argc, char *argv[] ) {
 
 void emit_and_quit ( char *s ) {
   printf ( "%s\n", s );
+  // shutdown notifications
+  if ( g_autorescan ) {
+
+    if ( dbh ) {
+      pnd_dbusnotify_shutdown ( dbh );
+    }
+    if ( nh ) {
+      pnd_notify_shutdown ( nh );
+    }
+
+  }
+
   exit ( 0 );
 }
 
@@ -363,6 +465,7 @@ static unsigned int is_dir_empty ( char *fullpath ) {
       // irrelevent
     } else {
       // something else came in, so dir must not be empty
+      closedir ( d );
       return ( 0 ); 
     }
 
@@ -422,6 +525,23 @@ void applications_scan ( void ) {
   // merge lists
   if ( merge_apps ) {
     if ( g_active_apps ) {
+      // the key from pnd_disco_search() is the _path_, so easy to look for duplicates
+      // this is pretty inefficient, being linked lists; perhaps should switch to hash tables when
+      // we expect thousands of apps.. or at least an index or something.
+      void *a = pnd_box_get_head ( merge_apps );
+      void *nexta = NULL;
+      while ( a ) {
+       nexta = pnd_box_get_next ( a );
+
+       // if the key for the node is also found in active apps, toss out the merging one
+       if ( pnd_box_find_by_key ( g_active_apps, pnd_box_get_key ( a ) ) ) {
+         //fprintf ( stderr, "Merging app id '%s' is duplicate; discarding it.\n", pnd_box_get_key ( a ) );
+         pnd_box_delete_node ( merge_apps, a );
+       }
+
+       a = nexta;
+      }
+
       // got menu apps, and got desktop apps, merge
       pnd_box_append ( g_active_apps, merge_apps );
     } else {
@@ -442,6 +562,28 @@ void applications_scan ( void ) {
   // merge aux apps
   if ( merge_apps ) {
     if ( g_active_apps ) {
+
+      // LAME: snipped from above; should just catenate the 3 sets of searchpaths into a
+      // master searchpath, possibly removing duplicate paths _then_, and keep all this much
+      // more efficient
+
+      // the key from pnd_disco_search() is the _path_, so easy to look for duplicates
+      // this is pretty inefficient, being linked lists; perhaps should switch to hash tables when
+      // we expect thousands of apps.. or at least an index or something.
+      void *a = pnd_box_get_head ( merge_apps );
+      void *nexta = NULL;
+      while ( a ) {
+       nexta = pnd_box_get_next ( a );
+
+       // if the key for the node is also found in active apps, toss out the merging one
+       if ( pnd_box_find_by_key ( g_active_apps, pnd_box_get_key ( a ) ) ) {
+         fprintf ( stderr, "Merging app id '%s' is duplicate; discarding it.\n", pnd_box_get_key ( a ) );
+         pnd_box_delete_node ( merge_apps, a );
+       }
+
+       a = nexta;
+      }
+
       pnd_box_append ( g_active_apps, merge_apps );
     } else {
       g_active_apps = merge_apps;
@@ -519,48 +661,22 @@ void applications_scan ( void ) {
       // push to All category
       // we do this first, so first category is always All
       if ( pnd_conf_get_as_int_d ( g_conf, "categories.do_all_cat", 1 ) ) {
-       if ( ! category_push ( g_x11_present ? CATEGORY_ALL "    (X11)" : CATEGORY_ALL "   (No X11)", iter, ovrh, NULL /* fspath */ ) ) {
-         pnd_log ( pndn_warning, "  Couldn't categorize to All: '%s'\n", IFNULL(iter -> title_en, "No Name") );
-       }
+       category_push ( g_x11_present ? CATEGORY_ALL "    (X11)" : CATEGORY_ALL "   (No X11)", iter, ovrh, NULL /* fspath */ );
       } // all?
 
-      // main categories
-      if ( iter -> main_category && pnd_conf_get_as_int_d ( g_conf, "tabs.top_maincat", 1 ) ) {
-       if ( ! category_meta_push ( iter -> main_category, iter, ovrh ) ) {
-         pnd_log ( pndn_warning, "  Couldn't categorize to %s: '%s'\n", iter -> main_category, IFNULL(iter -> title_en, "No Name") );
-       }
-      }
+      // is this app suppressed? if not, show it in whatever categories the user is allowing
+      if ( iter -> unique_id && app_is_visible ( g_conf, iter -> unique_id ) ) {
 
-      if ( iter -> main_category1 && pnd_conf_get_as_int_d ( g_conf, "tabs.top_maincat1", 0 ) ) {
-       if ( ! category_meta_push ( iter -> main_category1, iter, ovrh ) ) {
-         pnd_log ( pndn_warning, "  Couldn't categorize to %s: '%s'\n", iter -> main_category1, IFNULL(iter -> title_en, "No Name") );
-       }
-      }
+       // main categories
+       category_meta_push ( iter -> main_category, NULL /* no parent cat */, iter, ovrh, cat_is_visible ( g_conf, iter -> main_category ) ); //pnd_conf_get_as_int_d ( g_conf, "tabs.top_maincat", 1 ) );
+       category_meta_push ( iter -> main_category1, iter -> main_category, iter, ovrh, cat_is_visible ( g_conf, iter -> main_category1 ) ); //pnd_conf_get_as_int_d ( g_conf, "tabs.top_maincat1", 0 ) );
+       category_meta_push ( iter -> main_category2, iter -> main_category, iter, ovrh, cat_is_visible ( g_conf, iter -> main_category2 ) ); //pnd_conf_get_as_int_d ( g_conf, "tabs.top_maincat2", 0 ) );
+       // alt categories
+       category_meta_push ( iter -> alt_category, NULL /* no parent cat */, iter, ovrh, cat_is_visible ( g_conf, iter -> alt_category ) ); //pnd_conf_get_as_int_d ( g_conf, "tabs.top_altcat", 0 ) );
+       category_meta_push ( iter -> alt_category1, iter -> alt_category, iter, ovrh, cat_is_visible ( g_conf, iter -> alt_category1 ) ); //pnd_conf_get_as_int_d ( g_conf, "tabs.top_altcat1", 0 ) );
+       category_meta_push ( iter -> alt_category2, iter -> alt_category, iter, ovrh, cat_is_visible ( g_conf, iter -> alt_category2 ) ); //pnd_conf_get_as_int_d ( g_conf, "tabs.top_altcat2", 0 ) );
 
-      if ( iter -> main_category2 && pnd_conf_get_as_int_d ( g_conf, "tabs.top_maincat2", 0 ) ) {
-       if ( ! category_meta_push ( iter -> main_category2, iter, ovrh ) ) {
-         pnd_log ( pndn_warning, "  Couldn't categorize to %s: '%s'\n", iter -> main_category2, IFNULL(iter -> title_en, "No Name") );
-       }
-      }
-
-      // alt categories
-      if ( iter -> alt_category && pnd_conf_get_as_int_d ( g_conf, "tabs.top_altcat", 0 ) ) {
-       if ( ! category_meta_push ( iter -> alt_category, iter, ovrh ) ) {
-         pnd_log ( pndn_warning, "  Couldn't categorize to %s: '%s'\n", iter -> alt_category, IFNULL(iter -> title_en, "No Name") );
-       }
-      }
-
-      if ( iter -> alt_category1 && pnd_conf_get_as_int_d ( g_conf, "tabs.top_altcat1", 0 ) ) {
-       if ( ! category_meta_push ( iter -> alt_category1, iter, ovrh ) ) {
-         pnd_log ( pndn_warning, "  Couldn't categorize to %s: '%s'\n", iter -> alt_category1, IFNULL(iter -> title_en, "No Name") );
-       }
-      }
-
-      if ( iter -> alt_category2 && pnd_conf_get_as_int_d ( g_conf, "tabs.top_altcat2", 0 ) ) {
-       if ( ! category_meta_push ( iter -> alt_category2, iter, ovrh ) ) {
-         pnd_log ( pndn_warning, "  Couldn't categorize to %s: '%s'\n", iter -> alt_category2, IFNULL(iter -> title_en, "No Name") );
-       }
-      }
+      } // app is visible?
 
     } // register with categories or filter out
 
@@ -569,6 +685,9 @@ void applications_scan ( void ) {
     itercount++;
   } // while
 
+  // sort (some) categories
+  category_sort();
+
   // set up filesystem browser tabs
   if ( pnd_conf_get_as_int_d ( g_conf, "filesystem.do_browser", 0 ) ) {
     char *searchpath = pnd_conf_get_as_char ( g_conf, "filesystem.tab_searchpaths" );
@@ -601,3 +720,80 @@ void applications_scan ( void ) {
 
   return;
 }
+
+static char _vbuf [ 512 ];
+unsigned char cat_is_visible ( pnd_conf_handle h, char *catname ) {
+  snprintf ( _vbuf, 500, "tabshow.%s", catname );
+  return ( pnd_conf_get_as_int_d ( g_conf, _vbuf, 1 ) ); // default to 'show' when unknown
+}
+
+unsigned char app_is_visible ( pnd_conf_handle h, char *uniqueid ) {
+  snprintf ( _vbuf, 500, "appshow.%s", uniqueid );
+  return ( pnd_conf_get_as_int_d ( g_conf, _vbuf, 1 ) ); // default to 'show' when unknown
+}
+
+void sigquit_handler ( int n ) {
+  pnd_log ( pndn_rem, "SIGQUIT received; graceful exit.\n" );
+  emit_and_quit ( MM_QUIT );
+}
+
+void setup_notifications ( void ) {
+
+  // figure out notify path
+  char *configpath;
+  char *notifypath = NULL;
+
+  configpath = pnd_conf_query_searchpath();
+
+  pnd_conf_handle apph;
+
+  apph = pnd_conf_fetch_by_id ( pnd_conf_apps, configpath );
+
+  if ( apph ) {
+
+    notifypath = pnd_conf_get_as_char ( apph, PND_APPS_NOTIFY_KEY );
+
+    if ( ! notifypath ) {
+      notifypath = PND_APPS_NOTIFYPATH;
+    }
+
+  }
+
+  // given notify path.. ripped from pndnotifyd :(
+  char *searchpath = notifypath;
+
+  // if this is first time through, we can just set it up; for subsequent times
+  // through, we need to close existing fd and re-open it, since we're too lame
+  // to store the list of watches and 'rm' them
+#if 1
+  if ( nh ) {
+    pnd_notify_shutdown ( nh );
+    nh = 0;
+  }
+#endif
+
+  // set up a new set of notifies
+  if ( ! nh ) {
+    nh = pnd_notify_init();
+  }
+
+  if ( ! nh ) {
+    pnd_log ( pndn_rem, "INOTIFY failed to init.\n" );
+    exit ( -1 );
+  }
+
+#if 0
+  pnd_log ( pndn_rem, "INOTIFY is up.\n" );
+#endif
+
+  SEARCHPATH_PRE
+  {
+
+    pnd_log ( pndn_rem, "Watching path '%s' and its descendents.\n", buffer );
+    pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE );
+
+  }
+  SEARCHPATH_POST
+
+  return;
+}