From 503d398b1198066eaa5136925b32f99a5e3f4473 Mon Sep 17 00:00:00 2001 From: skeezix Date: Fri, 20 Nov 2009 13:19:24 -0500 Subject: [PATCH 01/16] Fixed typo in pnd_device.h; thanks cpas :) --- include/pnd_device.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pnd_device.h b/include/pnd_device.h index b1f021a..706b783 100644 --- a/include/pnd_device.h +++ b/include/pnd_device.h @@ -20,7 +20,7 @@ extern "C" { #define PND_DEVICE_SYS_BACKLIGHT_BRIGHTNESS "/sys/class/backlight/gpio-backlight/brightness" #define PND_DEVICE_FRAMEBUFFER "/dev/fb0" #define PND_DEVICE_NUB1 "/dev/input/js1" -#define PND_DEVICE_NUB1 "/dev/input/js2" +#define PND_DEVICE_NUB2 "/dev/input/js2" /* utility */ -- 2.39.2 From fd9090900d653b51b1d7b335b1224c3b65353773 Mon Sep 17 00:00:00 2001 From: skeezix Date: Fri, 20 Nov 2009 13:59:04 -0500 Subject: [PATCH 02/16] Anothe rminor typo; damn, I must've had a bad day when I refactored this :) --- lib/pnd_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pnd_device.c b/lib/pnd_device.c index f853ffc..42a17ca 100644 --- a/lib/pnd_device.c +++ b/lib/pnd_device.c @@ -71,7 +71,7 @@ unsigned char pnd_device_set_backlight ( unsigned int c ) { return ( pnd_device_open_write_close ( PND_DEVICE_SYS_BACKLIGHT_BRIGHTNESS, buffer ) ); } -unsigned int pnd_device_get_clock ( void ) { +unsigned int pnd_device_get_backlight ( void ) { char buffer [ 100 ]; if ( pnd_device_open_read_close ( PND_DEVICE_SYS_BACKLIGHT_BRIGHTNESS, buffer, 100 ) ) { -- 2.39.2 From ffe361c47b564fd0ff775ad63d6d46e9b47c0a6a Mon Sep 17 00:00:00 2001 From: vimacs Date: Sun, 15 Nov 2009 03:05:08 +0100 Subject: [PATCH 03/16] Fixed proper umounting changed the way the loop device is determined Some cleanup sometimes a loop device remains active, got to find out why still modified: testdata/scripts/pnd_run.sh --- testdata/scripts/pnd_run.sh | 67 +++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/testdata/scripts/pnd_run.sh b/testdata/scripts/pnd_run.sh index b5b834a..e5b4083 100755 --- a/testdata/scripts/pnd_run.sh +++ b/testdata/scripts/pnd_run.sh @@ -101,32 +101,8 @@ if [ ! $BASENAME ]; then BASENAME=$(basename "$PND" | cut -d'.' -f1) ; fi oCWD=$(pwd) - -#detect fs -if [ $DFS = ISO ]; then -#find a free loop device and use it - usedminor=$( ls -l /dev/loop* | awk '{print $6}') - freeminor=$( echo -e "$(seq 0 64)\n$usedminor" | sort -rn | uniq -u | tail -n1) - sudo mknod -m777 /dev/loop$freeminor b 7 $freeminor - sudo losetup /dev/loop$freeminor $PND #attach the pnd to the loop device - mntline="sudo mount /dev/loop$freeminor /mnt/pnd/$BASENAME/" #setup the mountline for later -# mntline="sudo mount -o loop,mode=777 $PND /mnt/pnd/$BASENAME" - echo "Filetype is $DFS" -elif [ $DFS = directory ]; then - mntline="sudo mount --bind -o ro $PND /mnt/pnd/$BASENAME" -#we bind the folder, now it can be treated in a unified way ATENTION: -o ro doesnt work for --bind at least on 25, on 26 its possible using remount, may have changed on 27 - echo "Filetype is $DFS" -elif [ $DFS = Squashfs ]; then - usedminor=$( ls -l /dev/loop* | awk '{print $6}') - freeminor=$( echo -e "$(seq 0 64)\n$usedminor" | sort -rn | uniq -u | tail -n1) - sudo mknod -m777 /dev/loop$freeminor b 7 $freeminor - sudo losetup /dev/loop$freeminor $PND #attach the pnd to the loop device - mntline="sudo mount -t squashfs /dev/loop$freeminor /mnt/pnd/$BASENAME" - echo "Filetype is $DFS" -else - echo "error determining fs, output was $DFS" - exit 1; -fi + + #create mountpoints, check if they exist already first to avoid annoying error messages if [ ! -d /mnt/pnd/$BASENAME ]; then sudo mkdir -p /mnt/pnd/$BASENAME ; fi #mountpoint for iso, ro @@ -140,6 +116,39 @@ if [ ! $umount ]; then #is the union already mounted? if not mount evrything, else launch the stuff mount | grep "on /mnt/utmp/$BASENAME type" # > /dev/null if [ ! $? -eq 0 ]; then + + FREELOOP=$(sudo losetup -f) #get first free loop device + if [ ! $FREELOOP ]; then # no free loop device, create a new one + #find a free loop device and use it + usedminor=$(sudo losetup -a | tail -n1) + usedminor=${usedminor:9:1} + echo usedminor $usedminor + freeminor=$(($usedminor+1)) + echo freeminor $freeminor + sudo mknod -m777 /dev/loop$freeminor b 7 $freeminor + FREELOOP=/dev/loop$freeminor + fi + + #detect fs + if [ $DFS = ISO ]; then + sudo losetup $FREELOOP $PND #attach the pnd to the loop device + mntline="sudo mount $FREELOOP /mnt/pnd/$BASENAME/" #setup the mountline for later + # mntline="sudo mount -o loop,mode=777 $PND /mnt/pnd/$BASENAME" + echo "Filetype is $DFS" + elif [ $DFS = directory ]; then + mntline="sudo mount --bind -o ro $PND /mnt/pnd/$BASENAME" + #we bind the folder, now it can be treated in a unified way ATENTION: -o ro doesnt work for --bind at least on 25, on 26 its possible using remount, may have changed on 27 + echo "Filetype is $DFS" + elif [ $DFS = Squashfs ]; then + sudo losetup $FREELOOP $PND #attach the pnd to the loop device + mntline="sudo mount -t squashfs $FREELOOP /mnt/pnd/$BASENAME" + echo "Filetype is $DFS" + else + echo "error determining fs, output was $DFS" + exit 1; + fi + + echo "$mntline" $mntline #mount the pnd/folder echo "mounting union!" @@ -182,9 +191,9 @@ if [ $? -eq 0 ]; then # check if the umount was successfull, if it wasnt it woul sudo rmdir -p $MOUNTPOINT/pandora/appdata/$BASENAME/ #delete tmp mountpoint sudo rmdir /mnt/utmp/$BASENAME; - if [ $DFS = ISO ]; then # check if we where running an iso, clean up loop device if we did - sudo losetup -d /dev/loop$freeminor - sudo rm /dev/loop$freeminor + if [ $DFS = ISO ] || [ $DFS = Squashfs ]; then # check if we where running an iso, clean up loop device if we did + sudo losetup -d $FREELOOP + sudo rm $FREELOOP fi sudo rmdir /mnt/pnd/$BASENAME #delete pnd mountpoint fi -- 2.39.2 From 14e8f7abfc00ed2260a1ca300bd7f835c18bdff9 Mon Sep 17 00:00:00 2001 From: vimacs Date: Sun, 15 Nov 2009 06:30:59 +0100 Subject: [PATCH 04/16] Removed a c&p error modified: pnd_make.sh --- testdata/scripts/pnd_make.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/testdata/scripts/pnd_make.sh b/testdata/scripts/pnd_make.sh index 8ae0ae4..c5e5111 100755 --- a/testdata/scripts/pnd_make.sh +++ b/testdata/scripts/pnd_make.sh @@ -44,10 +44,6 @@ else mksquashfs $FOLDER $PNDNAME.iso fi #append pxml to iso -else - mksquashfs $FOLDER $PNDNAME.iso -fi -#append pxml to iso cat $PNDNAME.iso $PXML > $PNDNAME rm $PNDNAME.iso #cleanup -- 2.39.2 From 92f24e9074fc8b9975311753fa4dbdd990ce80d5 Mon Sep 17 00:00:00 2001 From: skeezix Date: Tue, 8 Dec 2009 16:35:19 -0500 Subject: [PATCH 05/16] Hacked around pndnotifyd so it supports two sets of paths now -- menu path and desktop path ie: apps are now in SD /pandora/desktop and /pandora/menu and go to their configured .desktop emit paths TODO: fix up discotest and conftest to not look for apps.autodiscovery.searchpath but left it in for now --- Makefile | 2 +- apps/pnd_run.c | 7 - apps/pndnotifyd.c | 416 ++++++++++++--------- deployment/etc/pandora/conf/apps | 5 +- deployment/etc/pandora/conf/desktop | 11 +- include/pnd_apps.h | 15 +- testdata/conf/apps | 2 +- testdata/conf/desktop | 13 +- testdata/doticons/jeff.sample.3.png | Bin 0 -> 14866 bytes testdata/doticons/skype-96fad0c.png | Bin 0 -> 5121 bytes testdata/menuapps/sampleapp9/PXML.xml | 41 ++ testdata/menuapps/sampleapp9/program.exe | 0 testdata/menuapps/sampleapp9/zeldaicon.png | Bin 0 -> 14866 bytes 13 files changed, 311 insertions(+), 201 deletions(-) create mode 100644 testdata/doticons/jeff.sample.3.png create mode 100644 testdata/doticons/skype-96fad0c.png create mode 100644 testdata/menuapps/sampleapp9/PXML.xml create mode 100644 testdata/menuapps/sampleapp9/program.exe create mode 100644 testdata/menuapps/sampleapp9/zeldaicon.png diff --git a/Makefile b/Makefile index e17b2a1..fa52006 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ ALLOBJ = pnd_conf.o pnd_container.o pnd_discovery.o pnd_pxml.o pnd_notify.o pnd_ all: ${SOLIB} ${LIB} conftest discotest notifytest pndnotifyd rawpxmltest pndvalidator loggertest pnd_run clean: - ${RM} -f ${ALLOBJ} ${XMLOBJ} ${LIB} ${SOLIB1} locatetest.o bin/locatetest conftest.o bin/conftest discotest.o bin/discotest loggertest.o bin/loggertest bin/notifytest notifytest.o bin/rawpxmltest rawpxmltest.o bin/pnd_run pnd_run.o bin/pndnotifyd pndnotifyd.o ${SOLIB} testdata/dotdesktop/*.desktop testdata/apps/*.pnd testdata/dotdesktop/*.png deployment/usr/lib/libpnd* deployment/usr/bin/pndnotifyd deployment/usr/pandora/scripts/* deployment/etc/sudoers deployment/etc/init.d/pndnotifyd bin/pndvalidator pndvalidator.o + ${RM} -f ${ALLOBJ} ${XMLOBJ} ${LIB} ${SOLIB1} locatetest.o bin/locatetest conftest.o bin/conftest discotest.o bin/discotest loggertest.o bin/loggertest bin/notifytest notifytest.o bin/rawpxmltest rawpxmltest.o bin/pnd_run pnd_run.o bin/pndnotifyd pndnotifyd.o ${SOLIB} testdata/dotdesktop/*.desktop testdata/menu/*.desktop testdata/apps/*.pnd testdata/dotdesktop/*.png deployment/usr/lib/libpnd* deployment/usr/bin/pndnotifyd deployment/usr/bin/pnd_run deployment/usr/pandora/scripts/* deployment/etc/sudoers deployment/etc/init.d/pndnotifyd bin/pndvalidator pndvalidator.o ${RM} -rf deployment/media find . -name "*~*" -exec rm {} \; -print diff --git a/apps/pnd_run.c b/apps/pnd_run.c index 98b9bc7..c012ac6 100644 --- a/apps/pnd_run.c +++ b/apps/pnd_run.c @@ -55,7 +55,6 @@ int main ( int argc, char *argv[] ) { // ---> cribbed right out of discotest :/ copypaste ftw! if ( ! pnd_run ) { char *configpath; - char *appspath; char *overridespath; // attempt to fetch a sensible default searchpath for configs @@ -67,11 +66,6 @@ int main ( int argc, char *argv[] ) { apph = pnd_conf_fetch_by_id ( pnd_conf_apps, configpath ); if ( apph ) { - appspath = pnd_conf_get_as_char ( apph, PND_APPS_KEY ); - - if ( ! appspath ) { - appspath = PND_APPS_SEARCHPATH; - } overridespath = pnd_conf_get_as_char ( apph, PND_PXML_OVERRIDE_KEY ); @@ -81,7 +75,6 @@ int main ( int argc, char *argv[] ) { } else { // couldn't find a useful app search path so use the default - appspath = PND_APPS_SEARCHPATH; overridespath = PND_PXML_OVERRIDE_SEARCHPATH; } diff --git a/apps/pndnotifyd.c b/apps/pndnotifyd.c index 822c8f9..4d3d701 100644 --- a/apps/pndnotifyd.c +++ b/apps/pndnotifyd.c @@ -48,13 +48,18 @@ typedef enum { // like discotest char *configpath; -char *appspath; char *overridespath; // daemon stuff char *searchpath = NULL; -char *dotdesktoppath = NULL; -char *iconpath = NULL; char *notifypath = NULL; +time_t createtime = 0; // all 'new' .destops are created at or after this time; prev are old. +// dotfiles; this used to be a single pai .. now two pairs, a little unwieldy; pnd_box it up? +char *desktop_dotdesktoppath = NULL; +char *desktop_iconpath = NULL; +char *desktop_appspath = NULL; +char *menu_dotdesktoppath = NULL; +char *menu_iconpath = NULL; +char *menu_appspath = NULL; // pnd runscript char *run_searchpath; // searchpath to find pnd_run.sh char *run_script; // name of pnd_run.sh script from config @@ -70,6 +75,9 @@ pnd_notify_handle nh = 0; void consume_configuration ( void ); void setup_notifications ( void ); void sighup_handler ( int n ); +void process_discoveries ( pnd_box_handle applist, char *emitdesktoppath, char *emiticonpath ); +unsigned char perform_discoveries ( char *appspath, char *overridespath, + char *emitdesktoppath, char *emiticonpath ); int main ( int argc, char *argv[] ) { // behaviour @@ -145,12 +153,19 @@ int main ( int argc, char *argv[] ) { /* startup */ - pnd_log ( pndn_rem, "Apps searchpath is '%s'\n", appspath ); pnd_log ( pndn_rem, "PXML overrides searchpath is '%s'\n", overridespath ); - pnd_log ( pndn_rem, ".desktop files emit to '%s'\n", dotdesktoppath ); - pnd_log ( pndn_rem, ".desktop icon files emit to '%s'\n", iconpath ); pnd_log ( pndn_rem, "Notify searchpath is '%s'\n", notifypath ); + pnd_log ( pndn_rem, "Desktop apps ---------------------------------\n" ); + pnd_log ( pndn_rem, "Apps searchpath is '%s'\n", desktop_appspath ); + pnd_log ( pndn_rem, ".desktop files emit to '%s'\n", desktop_dotdesktoppath ); + pnd_log ( pndn_rem, ".desktop icon files emit to '%s'\n", desktop_iconpath ); + + pnd_log ( pndn_rem, "Menu apps ---------------------------------\n" ); + pnd_log ( pndn_rem, "Apps searchpath is '%s'\n", menu_appspath ); + pnd_log ( pndn_rem, ".desktop files emit to '%s'\n", menu_dotdesktoppath ); + pnd_log ( pndn_rem, ".desktop icon files emit to '%s'\n", menu_iconpath ); + /* set up signal handler */ sigset_t ss; @@ -180,8 +195,7 @@ int main ( int argc, char *argv[] ) { if ( scanonlaunch || pnd_notify_rediscover_p ( nh ) ) { - pnd_box_handle applist; - time_t createtime = time ( NULL ); // all 'new' .destops are created at or after this time; prev are old. + createtime = time ( NULL ); // all 'new' .destops are created at or after this time; prev are old. // if this was a forced scan, lets not do that next iteration if ( scanonlaunch ) { @@ -194,153 +208,18 @@ int main ( int argc, char *argv[] ) { pnd_log ( pndn_rem, "------------------------------------------------------\n" ); pnd_log ( pndn_rem, "Changes within watched paths .. performing re-discover!\n" ); - // run the discovery - applist = pnd_disco_search ( appspath, overridespath ); - - // list the found apps (if any) - if ( applist ) { - pnd_disco_t *d = pnd_box_get_head ( applist ); - - while ( d ) { - - pnd_log ( pndn_rem, "Found app: %s\n", pnd_box_get_key ( d ) ); - - // check if icon already exists (from a previous extraction say); if so, we needn't - // do it again - char existingpath [ FILENAME_MAX ]; - sprintf ( existingpath, "%s/%s.png", iconpath, d -> unique_id ); - - struct stat dirs; - if ( stat ( existingpath, &dirs ) == 0 ) { - // icon seems to exist, so just crib the location into the .desktop - - pnd_log ( pndn_rem, " Found icon already existed, so reusing it! %s\n", existingpath ); - - if ( d -> icon ) { - free ( d -> icon ); - } - d -> icon = strdup ( existingpath ); - - } else { - // icon seems unreadable or does not exist; lets try to create it.. - - pnd_log ( pndn_rem, " Icon not already present, so trying to write it! %s\n", existingpath ); - - // attempt to create icon files; if successful, alter the disco struct to contain new - // path, otherwise leave it alone (since it could be a generic icon reference..) - if ( pnd_emit_icon ( iconpath, d ) ) { - // success; fix up icon path to new one.. - if ( d -> icon ) { - free ( d -> icon ); - } - d -> icon = strdup ( existingpath ); - } else { - pnd_log ( pndn_rem, " WARN: Couldn't write out icon %s\n", existingpath ); - } - - } // icon already exists? - - // create the .desktop file - if ( pnd_emit_dotdesktop ( dotdesktoppath, pndrun, d ) ) { - // add a watch onto the newly created .desktop? -#if 0 - char buffer [ FILENAME_MAX ]; - sprintf ( buffer, "%s/%s", dotdesktoppath, d -> unique_id ); - pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE ); -#endif - } else { - pnd_log ( pndn_rem, "ERROR: Error creating .desktop file for app: %s\n", pnd_box_get_key ( d ) ); - } - - // next! - d = pnd_box_get_next ( d ); - - } // while applist - - } else { - - pnd_log ( pndn_rem, "No applications found in search path\n" ); - - } // got apps? - - // run a clean up, to remove any dotdesktop files that we didn't - // just now create (that seem to have been created by pndnotifyd - // previously.) This allows SD eject (or .pnd remove) to remove - // an app from the launcher - // NOTE: Could opendir and iterate across all .desktop files, - // removing any that have Source= something else, and that the - // app name is not in the list found in applist box above. But - // a cheesy simple way right now is to just remove .desktop files - // that have a last mod time prior to the time we stored above. - { - DIR *dir; - - if ( ( dir = opendir ( dotdesktoppath ) ) ) { - struct dirent *dirent; - struct stat dirs; - char buffer [ FILENAME_MAX ]; - - while ( ( dirent = readdir ( dir ) ) ) { - - // file is a .desktop? - if ( strstr ( dirent -> d_name, ".desktop" ) == NULL ) { - continue; - } - - // figure out full path - sprintf ( buffer, "%s/%s", dotdesktoppath, dirent -> d_name ); - - // file was previously created by libpnd; check Source= line - // logic: default to 'yes' (in case we can't open the file for some reason) - // if we can open the file, default to no and look for the source flag we added; if - // that matches then we know its libpnd created, otherwise assume not. - unsigned char source_libpnd = 1; - { - char line [ 256 ]; - FILE *grep = fopen ( buffer, "r" ); - if ( grep ) { - source_libpnd = 0; - while ( fgets ( line, 255, grep ) ) { - if ( strcasestr ( line, PND_DOTDESKTOP_SOURCE ) ) { - source_libpnd = 2; - } - } // while - fclose ( grep ); - } - } - if ( source_libpnd ) { -#if 0 - pnd_log ( pndn_rem, - "File '%s' appears to have been created by libpnd so candidate for delete: %u\n", buffer, source_libpnd ); -#endif - } else { -#if 0 - pnd_log ( pndn_rem, "File '%s' appears NOT to have been created by libpnd, so leave it alone\n", buffer ); -#endif - continue; // skip deleting it - } - - // file is 'new'? - if ( stat ( buffer, &dirs ) == 0 ) { - if ( dirs.st_mtime >= createtime ) { -#if 0 - pnd_log ( pndn_rem, "File '%s' seems 'new', so leave it alone.\n", buffer ); -#endif - continue; // skip deleting it - } - } + /* run the discovery + */ - // by this point, the .desktop file must be 'old' and created by pndnotifyd - // previously, so can remove it - pnd_log ( pndn_rem, "File '%s' seems nolonger relevent; removing it.\n", dirent -> d_name ); - unlink ( buffer ); - - } // while getting filenames from dir - - closedir ( dir ); - } + pnd_log ( pndn_rem, "Scanning desktop paths----------------------------\n" ); + if ( ! perform_discoveries ( desktop_appspath, overridespath, desktop_dotdesktoppath, desktop_iconpath ) ) { + pnd_log ( pndn_rem, "No applications found in desktop search path\n" ); + } - } // purge old .desktop files + pnd_log ( pndn_rem, "Scanning menu paths----------------------------\n" ); + if ( ! perform_discoveries ( menu_appspath, overridespath, menu_dotdesktoppath, menu_iconpath ) ) { + pnd_log ( pndn_rem, "No applications found in menu search path\n" ); + } // if we've got a hup script located, lets invoke it if ( pndhup ) { @@ -380,12 +259,6 @@ void consume_configuration ( void ) { if ( apph ) { - appspath = pnd_conf_get_as_char ( apph, PND_APPS_KEY ); - - if ( ! appspath ) { - appspath = PND_APPS_SEARCHPATH; - } - overridespath = pnd_conf_get_as_char ( apph, PND_PXML_OVERRIDE_KEY ); if ( ! overridespath ) { @@ -400,34 +273,47 @@ void consume_configuration ( void ) { } else { // couldn't find a useful app search path so use the default - appspath = PND_APPS_SEARCHPATH; overridespath = PND_PXML_OVERRIDE_SEARCHPATH; notifypath = PND_APPS_NOTIFYPATH; } - // attempt to figure out where to drop dotfiles + // attempt to figure out where to drop dotfiles .. now that we're going + // multi-target we see the limit of my rudimentary conf-file parser; should + // just parse to an array of targets, rather that hardcoding two, but + // on the other hand, don't likely see the need for more than two? (famous + // last words.) pnd_conf_handle desktoph; desktoph = pnd_conf_fetch_by_id ( pnd_conf_desktop, configpath ); + // for 'desktop' main applications if ( desktoph ) { - dotdesktoppath = pnd_conf_get_as_char ( desktoph, PND_DOTDESKTOP_KEY ); + desktop_dotdesktoppath = pnd_conf_get_as_char ( desktoph, PND_DESKTOP_DOTDESKTOP_PATH_KEY ); + desktop_iconpath = pnd_conf_get_as_char ( desktoph, PND_DESKTOP_ICONS_PATH_KEY ); + desktop_appspath = pnd_conf_get_as_char ( desktoph, PND_DESKTOP_SEARCH_KEY ); + } - if ( ! dotdesktoppath ) { - dotdesktoppath = PND_DOTDESKTOP_DEFAULT; - } + if ( ! desktop_dotdesktoppath ) { + desktop_dotdesktoppath = PND_DESKTOP_DOTDESKTOP_PATH_DEFAULT; + } - iconpath = pnd_conf_get_as_char ( desktoph, PND_DOTDESKTOPICONS_KEY ); + if ( ! desktop_iconpath ) { + desktop_iconpath = PND_DESKTOP_ICONS_PATH_DEFAULT; + } - if ( ! iconpath ) { - iconpath = PND_DOTDESKTOPICONS_DEFAULT; - } + if ( ! desktop_appspath ) { + desktop_appspath = PND_DESKTOP_SEARCH_PATH_DEFAULT; + } - } else { - dotdesktoppath = PND_DOTDESKTOPICONS_DEFAULT; + // for 'menu' applications + if ( desktoph ) { + menu_dotdesktoppath = pnd_conf_get_as_char ( desktoph, PND_MENU_DOTDESKTOP_PATH_KEY ); + menu_iconpath = pnd_conf_get_as_char ( desktoph, PND_MENU_ICONS_PATH_KEY ); + menu_appspath = pnd_conf_get_as_char ( desktoph, PND_MENU_SEARCH_KEY ); } - // try to locate a runscript and optional hupscript + /* try to locate a runscript and optional hupscript + */ if ( apph ) { run_searchpath = pnd_conf_get_as_char ( apph, PND_PNDRUN_SEARCHPATH_KEY ); @@ -491,13 +377,19 @@ void consume_configuration ( void ) { /* handle globbing or variable substitution */ - dotdesktoppath = pnd_expand_tilde ( strdup ( dotdesktoppath ) ); - iconpath = pnd_expand_tilde ( strdup ( iconpath ) ); - - /* validate paths - */ - mkdir ( dotdesktoppath, 0777 ); - mkdir ( iconpath, 0777 ); + desktop_dotdesktoppath = pnd_expand_tilde ( strdup ( desktop_dotdesktoppath ) ); + desktop_iconpath = pnd_expand_tilde ( strdup ( desktop_iconpath ) ); + mkdir ( desktop_dotdesktoppath, 0777 ); + mkdir ( desktop_iconpath, 0777 ); + + if ( menu_dotdesktoppath ) { + menu_dotdesktoppath = pnd_expand_tilde ( strdup ( menu_dotdesktoppath ) ); + mkdir ( menu_dotdesktoppath, 0777 ); + } + if ( menu_iconpath ) { + menu_iconpath = pnd_expand_tilde ( strdup ( menu_iconpath ) ); + mkdir ( menu_iconpath, 0777 ); + } // done return; @@ -554,3 +446,169 @@ void sighup_handler ( int n ) { return; } + +// This very recently was inline code; just slight refactor to functionize it so that it can be +// reused in a couple of places. Simple code with simple design quickly became too large for +// its simple design; should revisit a lot of these little things.. +void process_discoveries ( pnd_box_handle applist, char *emitdesktoppath, char *emiticonpath ) { + pnd_disco_t *d = pnd_box_get_head ( applist ); + + while ( d ) { + + pnd_log ( pndn_rem, "Found app: %s\n", pnd_box_get_key ( d ) ); + + // check if icon already exists (from a previous extraction say); if so, we needn't + // do it again + char existingpath [ FILENAME_MAX ]; + sprintf ( existingpath, "%s/%s.png", emiticonpath, d -> unique_id ); + + struct stat dirs; + if ( stat ( existingpath, &dirs ) == 0 ) { + // icon seems to exist, so just crib the location into the .desktop + + pnd_log ( pndn_rem, " Found icon already existed, so reusing it! %s\n", existingpath ); + + if ( d -> icon ) { + free ( d -> icon ); + } + d -> icon = strdup ( existingpath ); + + } else { + // icon seems unreadable or does not exist; lets try to create it.. + + pnd_log ( pndn_debug, " Icon not already present, so trying to write it! %s\n", existingpath ); + + // attempt to create icon files; if successful, alter the disco struct to contain new + // path, otherwise leave it alone (since it could be a generic icon reference..) + if ( pnd_emit_icon ( emiticonpath, d ) ) { + // success; fix up icon path to new one.. + if ( d -> icon ) { + free ( d -> icon ); + } + d -> icon = strdup ( existingpath ); + } else { + pnd_log ( pndn_debug, " WARN: Couldn't write out icon %s\n", existingpath ); + } + + } // icon already exists? + + // create the .desktop file + if ( pnd_emit_dotdesktop ( emitdesktoppath, pndrun, d ) ) { + // add a watch onto the newly created .desktop? +#if 0 + char buffer [ FILENAME_MAX ]; + sprintf ( buffer, "%s/%s", emitdesktoppath, d -> unique_id ); + pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE ); +#endif + } else { + pnd_log ( pndn_rem, "ERROR: Error creating .desktop file for app: %s\n", pnd_box_get_key ( d ) ); + } + + // next! + d = pnd_box_get_next ( d ); + + } // while applist + + return; +} + +// returns true if any applications were found +unsigned char perform_discoveries ( char *appspath, char *overridespath, // args to do discovery + char *emitdesktoppath, char *emiticonpath ) // args to do emitting +{ + pnd_box_handle applist; + + // attempt to auto-discover applications in the given path + applist = pnd_disco_search ( appspath, overridespath ); + + if ( ! applist ) { + return ( 0 ); + } + + process_discoveries ( applist, emitdesktoppath, emiticonpath ); + + // run a clean up, to remove any dotdesktop files that we didn't + // just now create (that seem to have been created by pndnotifyd + // previously.) This allows SD eject (or .pnd remove) to remove + // an app from the launcher + // NOTE: Could opendir and iterate across all .desktop files, + // removing any that have Source= something else, and that the + // app name is not in the list found in applist box above. But + // a cheesy simple way right now is to just remove .desktop files + // that have a last mod time prior to the time we stored above. + { + DIR *dir; + + if ( ( dir = opendir ( emitdesktoppath ) ) ) { + struct dirent *dirent; + struct stat dirs; + char buffer [ FILENAME_MAX ]; + + while ( ( dirent = readdir ( dir ) ) ) { + + // file is a .desktop? + if ( strstr ( dirent -> d_name, ".desktop" ) == NULL ) { + continue; + } + + // figure out full path + sprintf ( buffer, "%s/%s", emitdesktoppath, dirent -> d_name ); + + // file was previously created by libpnd; check Source= line + // logic: default to 'yes' (in case we can't open the file for some reason) + // if we can open the file, default to no and look for the source flag we added; if + // that matches then we know its libpnd created, otherwise assume not. + unsigned char source_libpnd = 1; + { + char line [ 256 ]; + FILE *grep = fopen ( buffer, "r" ); + if ( grep ) { + source_libpnd = 0; + while ( fgets ( line, 255, grep ) ) { + if ( strcasestr ( line, PND_DOTDESKTOP_SOURCE ) ) { + source_libpnd = 2; + } + } // while + fclose ( grep ); + } + } + if ( source_libpnd ) { +#if 0 + pnd_log ( pndn_rem, + "File '%s' appears to have been created by libpnd so candidate for delete: %u\n", buffer, source_libpnd ); +#endif + } else { +#if 0 + pnd_log ( pndn_rem, "File '%s' appears NOT to have been created by libpnd, so leave it alone\n", buffer ); +#endif + continue; // skip deleting it + } + + // file is 'new'? + if ( stat ( buffer, &dirs ) == 0 ) { + if ( dirs.st_mtime >= createtime ) { +#if 0 + pnd_log ( pndn_rem, "File '%s' seems 'new', so leave it alone.\n", buffer ); +#endif + continue; // skip deleting it + } + } + + // by this point, the .desktop file must be 'old' and created by pndnotifyd + // previously, so can remove it + pnd_log ( pndn_rem, "File '%s' seems nolonger relevent; removing it.\n", dirent -> d_name ); + unlink ( buffer ); + + } // while getting filenames from dir + + closedir ( dir ); + } + + } // purge old .desktop files + + //WARN: MEMORY LEAK HERE + pnd_log ( pndn_debug, "pndnotifyd - memory leak here - perform_discoveries()\n" ); + pnd_box_delete ( applist ); // does not free the disco_t contents! + + return ( 1 ); +} diff --git a/deployment/etc/pandora/conf/apps b/deployment/etc/pandora/conf/apps index 9040efe..ecf98e4 100644 --- a/deployment/etc/pandora/conf/apps +++ b/deployment/etc/pandora/conf/apps @@ -7,7 +7,7 @@ searchpath /media/*/pandora/apps:/usr/pandora/apps # notifypath is a list of paths to monitor; if anything in those paths changes, the searchpath is rescanned # note that for each path chunk, all current subdirs of that path are also watched) -notifypath /media:/media/*/pandora/apps:/usr/pandora/apps:./testdata/app? +notifypath /media:/media/*/pandora/apps:/usr/pandora/apps # PXMLs may be overridden .. ie: overrides are a subset of PXML, where the values are copied over the full PXML [overrides] @@ -16,7 +16,8 @@ searchpath ~/pxml-overrides # [pnd] defines where to locate the pnd support scripts, so the user may override pnd_run.sh without clobbering built in [pnd] -searchpath /media/*/pandora/scripts:/usr/pandora/scripts +# if you wish to make pnd_run.sh etc be findable on SD, prepend "/media/*/pandora/scripts:" to the 'searchpath' +searchpath /usr/pandora/scripts runscript pnd_run.sh # [pndnotifyd] sets some extra pndnotifyd specific items diff --git a/deployment/etc/pandora/conf/desktop b/deployment/etc/pandora/conf/desktop index b45fccf..55f3975 100644 --- a/deployment/etc/pandora/conf/desktop +++ b/deployment/etc/pandora/conf/desktop @@ -2,10 +2,15 @@ # Open Pandora # Desktop configuration -[dotfiles] -#(~/Desktop for xfce, /usr/share/applications for WMs that actually follow spec) +[desktop] +searchpath /mnt/sd?/pandora/desktop:/usr/pandora/apps # path to depth-search for PXMLs and pnd-files dotdesktoppath ~/Desktop/ # path for pndnotifyd to spit .desktop files into (run as root) -iconpath /tmp # path for pndnotifyd to drop icons into (can be same as .desktop if WM permits) +iconpath /tmp # path for pndnotifyd to drop icons into (can be same as dotdesktoppath if WM permits) + +[menu] +searchpath /mnt/sd?/pandora/menu # path to depth-search for PXMLs and pnd-files +dotdesktoppath /tmp/menu # path for pndnotifyd to spit .desktop files into +iconpath /tmp/menuicons # path for pndnotifyd to drop icons into (can be same as dotdesktoppath if WM permits) [launcher] # if hupscript is commented out entirely, pndnotifyd will not try to find/run the hup diff --git a/include/pnd_apps.h b/include/pnd_apps.h index 51939f9..9e56333 100644 --- a/include/pnd_apps.h +++ b/include/pnd_apps.h @@ -25,10 +25,17 @@ extern "C" { #define PND_MOUNT_PATH "/mnt/pnd/" /* all mounted PND images should be here.. /mnt/apps/UNIQUE-ID/... */ // .desktop support -#define PND_DOTDESKTOP_KEY "dotfiles.dotdesktoppath" -#define PND_DOTDESKTOPICONS_KEY "dotfiles.iconpath" -#define PND_DOTDESKTOP_DEFAULT "~/.applications" -#define PND_DOTDESKTOPICONS_DEFAULT "~/.applications" +#define PND_DESKTOP_DOTDESKTOP_PATH_KEY "desktop.dotdesktoppath" +#define PND_DESKTOP_ICONS_PATH_KEY "desktop.iconpath" +#define PND_DESKTOP_SEARCH_KEY "desktop.searchpath" + +#define PND_MENU_DOTDESKTOP_PATH_KEY "menu.dotdesktoppath" +#define PND_MENU_ICONS_PATH_KEY "menu.iconpath" +#define PND_MENU_SEARCH_KEY "menu.searchpath" + +#define PND_DESKTOP_DOTDESKTOP_PATH_DEFAULT "~/.applications" +#define PND_DESKTOP_ICONS_PATH_DEFAULT "~/.applications" +#define PND_DESKTOP_SEARCH_PATH_DEFAULT "/media/*/pandora/desktop:/usr/pandora/apps" // apps #define PND_DEFAULT_WORKDIR "./" diff --git a/testdata/conf/apps b/testdata/conf/apps index f7ab2c2..4f9a01e 100644 --- a/testdata/conf/apps +++ b/testdata/conf/apps @@ -18,4 +18,4 @@ runscript pnd_run.sh # [pndnotifyd] sets some extra pndnotifyd specific items [pndnotifyd] # logging level 0 means to include debug; level 1 (regular), 2 (warnings), 3 (errors) -loglevel 0 +loglevel 1 diff --git a/testdata/conf/desktop b/testdata/conf/desktop index 4b1e994..6e3d6ca 100644 --- a/testdata/conf/desktop +++ b/testdata/conf/desktop @@ -2,10 +2,15 @@ # Open Pandora # Desktop configuration -[dotfiles] -#dotdesktoppath ./testdata/dotdesktop # path for pndnotifyd to spit .desktop files into -dotdesktoppath ~/.applications # path for pndnotifyd to spit .desktop files into -iconpath ./testdata/dotdesktop # path for pndnotifyd to drop icons into (can be same as .desktop if WM permits) +[desktop] +searchpath /mnt/sd?/pandora/desktop:./testdata/app? # path to depth-search for PXMLs and pnd-files +dotdesktoppath ./testdata/dotdesktop # path for pndnotifyd to spit .desktop files into +iconpath ./testdata/doticons # path for pndnotifyd to drop icons into (can be same as dotdesktoppath if WM permits) + +[menu] +searchpath /mnt/sd?/pandora/menu:./testdata/menuapps # path to depth-search for PXMLs and pnd-files +dotdesktoppath ./testdata/menu # path for pndnotifyd to spit .desktop files into +iconpath ./testdata/menuicons # path for pndnotifyd to drop icons into (can be same as dotdesktoppath if WM permits) [launcher] hupscript pnd_hup.sh diff --git a/testdata/doticons/jeff.sample.3.png b/testdata/doticons/jeff.sample.3.png new file mode 100644 index 0000000000000000000000000000000000000000..140a39386bfe401a5c2d8406bfd0caf568318f08 GIT binary patch literal 14866 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 #<|oyxycV^Z26=k++2w7qH+;7A97fyhppC3v)rE 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

v;u8cxhX+l~3hEj$>wWI`e}tM2dAlDCK>6pd z32Gw4Xm)fGqhSCDf-%G-fSm#hVJA(~mLQ^l4?<|16c7qpAw42M5KU=BJ0VIdHbN&V zT!)qkahw2n4H$t;kR@n=(2N{$B7>@eN&yTGjImbPEUlV07M!ou^^Xl5g(}; z7oiywt{D{u_BBF-;5CF1KR*p0A4(%�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 literal 0 HcmV?d00001 diff --git a/testdata/menuapps/sampleapp9/PXML.xml b/testdata/menuapps/sampleapp9/PXML.xml new file mode 100644 index 0000000..2d59722 --- /dev/null +++ b/testdata/menuapps/sampleapp9/PXML.xml @@ -0,0 +1,41 @@ + + + Sample App 9 + Sample App 9 - German + + + + This is the English Description of the file. + This would be the German description. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ......kfujl........ + ......qwe......fwer..... + diff --git a/testdata/menuapps/sampleapp9/program.exe b/testdata/menuapps/sampleapp9/program.exe new file mode 100644 index 0000000..e69de29 diff --git a/testdata/menuapps/sampleapp9/zeldaicon.png b/testdata/menuapps/sampleapp9/zeldaicon.png new file mode 100644 index 0000000000000000000000000000000000000000..140a39386bfe401a5c2d8406bfd0caf568318f08 GIT binary patch literal 14866 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 #<|oyxycV^Z26=k++2w7qH+;7A97fyhppC3v)rE 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

v;u8cxhX+l~3hEj$>wWI`e}tM2dAlDCK>6pd z32Gw4Xm)fGqhSCDf-%G-fSm#hVJA(~mLQ^l4?<|16c7qpAw42M5KU=BJ0VIdHbN&V zT!)qkahw2n4H$t;kR@n=(2N{$B7>@eN&yTGjImbPEUlV07M!ou^^Xl5g(}; z7oiywt{D{u_BBF-;5CF1KR*p0A4(%�`vKtA}`Dug(XoLrPuM38z|z|mj~fUi0fN`(@V3J4>vBt=l9VEm3cK;5Lbhiz@p zZF*dBK!(|hjQ1T-v$O)7L&F&+F3tzdO%7X{AGIK)Y>C6>W=E|ESx^(?bJ)fbIATXQ z!ejPXm>;mUM)`+w=#Z1Wy1NU)GZ{LBJzsBxK&@bZt&jlCut3d-AV4EBSThPSC`>yp zTqhpBM!DNqX4Kzy|ta zgpY>$W2hk_6+tjtBRXs|Hbg<|0(3xeA^x;FAVb_u&D#Sw;^~e`iag878PQGM(TNU! z1U5Ds(x%3LJG->)ZFlPH68YHKq1g^CEp|!a-#_^Xvt5R@R^W23#s zhLG3+BO`p>XJqJ=5C_U|ANUvi`5=7C-WC&6bRF!#4B()LD<+%(vCYQ=rXmsn#OzG~ zeH-xcKr=K}^Kd)t?t0kO<&cXr3Icd5frA|$0XpzFnvMgOW&lKuVFnO4w;O5tJWRl+ zj*ZPubq#iY!42x4ZNW>&8Qe^UfrI^SLp{>M*cjGsiNnUmJ2W&Lf`arM92ooeYuH%p zIy!(OFm=ep#LUA3cNdP0>N`7uAe^UrNk==^@L4Wtv^!M-(-20%*#M9@HgR09mVwi|DBRl~|^ucl^TR3w(ru(5Yzc{#tLoPmG# zR^Q%kr@jtC21XuMZHWU01|}{p?|kyf*x9oTH8m|88^)nSXb(Adg~ju(e1&~8He0v<%0O^v7l4I~<1pP~K%1A^`B z+Gk*7>F)964f4-reRb_i7B7pjXQ!T%17q(_hOVs*LstvD;Nrdd`di|Vf#Cs^fP3~b zjvUpnu-LD!ze`K&kdYBXTSLvtoS}L2u%#J3)hx_*>FEF}(QOQ!Dr}jWAqws{CqQ=G zZ)t%d==;EV6-YC4b6kbm`hbb)L1O}yU(j@@Ms$yX9*v>91AHTo-``=hh zzkdB%3f_6y#n7?0W*F$~)lg&T*`U1B!r;SM_UY+u3H)kHfS@gL$M~S%8=w{bz39GO zbfjB;-wU?0bGv>X&@()2WQwa8+FIc8kf|}_&_QiW%isOs_vE$kh54BDG{grr6O%pK zdYa}|JNNHp=weQ-t+`7_2Nv$p(b*DkoVx;8R$;RZdkpjec=a9FF0O5EMqj&uP50mY ztnJ42u%Qv-=utW{?$puRtE+QRSNG}PzKj>qzr68HS{9daaQ}W?-J@n^`*aOe|sSVfG)!nJ7iHM8 z|95<;y1PqP`wr~Z(;@gSkE*X2*v;6ZgB}uWXa5iMA2zEQ86DC?_B=Q+I!^ulva-Il zb;rR2hf#m&=Jo4D%2aX;+04W?*M=hppZN*SO$~dBS$iM&&MR?K2 zd`Uwc{oAjlvmG!08@@JQ3zIh6NgqR}+dp?}UhcM^gp_^zjBIQ_`1oUZ&>!D<$16C1 zv2PdQMMDEB198Yu$moncd-iE*ZSaD#2uKICb+^;ue|CPaw)P#MToosEzK1(o(idW= z00{XGeE-h$8Mwff0FRiy{QOr>;8j@6fD$uUsNDAJX(B7_R#!h_Yz(EqkHE_zL%oB# z+Ju)wM~^Ue?I1IP-8;y^|1EZ6(tuEh6u{WMd;gImjGa3fyLO?V+Hr6nJ{f!WV8j@E z_mcB__W##9vy;(1*pF8m@p^ Date: Tue, 8 Dec 2009 16:54:23 -0500 Subject: [PATCH 06/16] Fix path for desktop-conf; ie: icons for menu can cache in the same place as .desktop --- deployment/etc/pandora/conf/desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/etc/pandora/conf/desktop b/deployment/etc/pandora/conf/desktop index 55f3975..0fa9ed4 100644 --- a/deployment/etc/pandora/conf/desktop +++ b/deployment/etc/pandora/conf/desktop @@ -10,7 +10,7 @@ iconpath /tmp # path for pndnotifyd to drop icons into (can be same as dotdeskto [menu] searchpath /mnt/sd?/pandora/menu # path to depth-search for PXMLs and pnd-files dotdesktoppath /tmp/menu # path for pndnotifyd to spit .desktop files into -iconpath /tmp/menuicons # path for pndnotifyd to drop icons into (can be same as dotdesktoppath if WM permits) +iconpath /tmp # path for pndnotifyd to drop icons into (can be same as dotdesktoppath if WM permits) [launcher] # if hupscript is commented out entirely, pndnotifyd will not try to find/run the hup -- 2.39.2 From 710cb5d7f27a08040f5bf18aa41d9a32ccff92cd Mon Sep 17 00:00:00 2001 From: skeezix Date: Tue, 8 Dec 2009 23:21:42 -0500 Subject: [PATCH 07/16] Misc little fixes of last commits; ie: change interval to 5s default instead of 10 - change 'apps.autodiscovery.searchpath' to be 'everything' (desktop/menu and apps from prior to recent changes), just to make sure pmenu gets it all - point deployment menu to correct folder --- apps/pndnotifyd.c | 13 ++++++++----- deployment/etc/pandora/conf/apps | 4 ++-- deployment/etc/pandora/conf/desktop | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/apps/pndnotifyd.c b/apps/pndnotifyd.c index 4d3d701..31d9c92 100644 --- a/apps/pndnotifyd.c +++ b/apps/pndnotifyd.c @@ -82,7 +82,7 @@ unsigned char perform_discoveries ( char *appspath, char *overridespath, int main ( int argc, char *argv[] ) { // behaviour unsigned char scanonlaunch = 1; - unsigned int interval_secs = 10; + unsigned int interval_secs = 5; // misc int i; @@ -216,9 +216,11 @@ int main ( int argc, char *argv[] ) { pnd_log ( pndn_rem, "No applications found in desktop search path\n" ); } - pnd_log ( pndn_rem, "Scanning menu paths----------------------------\n" ); - if ( ! perform_discoveries ( menu_appspath, overridespath, menu_dotdesktoppath, menu_iconpath ) ) { - pnd_log ( pndn_rem, "No applications found in menu search path\n" ); + if ( menu_appspath && menu_dotdesktoppath && menu_iconpath ) { + pnd_log ( pndn_rem, "Scanning menu paths----------------------------\n" ); + if ( ! perform_discoveries ( menu_appspath, overridespath, menu_dotdesktoppath, menu_iconpath ) ) { + pnd_log ( pndn_rem, "No applications found in menu search path\n" ); + } } // if we've got a hup script located, lets invoke it @@ -235,7 +237,8 @@ int main ( int argc, char *argv[] ) { } // need to rediscover? // lets not eat up all the CPU - // should use an alarm or select() or something + // should use an alarm or select() or something -- I mean really, why aren't I putting interval_secs into + // the select() call above in pnd_notify_whatsitcalled()? -- but lets not break this right before release shall we sleep ( interval_secs ); } // while diff --git a/deployment/etc/pandora/conf/apps b/deployment/etc/pandora/conf/apps index ecf98e4..1444505 100644 --- a/deployment/etc/pandora/conf/apps +++ b/deployment/etc/pandora/conf/apps @@ -4,10 +4,10 @@ [autodiscovery] # searchpath is a list of paths (colon separated) in which to look for PXML.xml or .pnd-file applications -searchpath /media/*/pandora/apps:/usr/pandora/apps +searchpath /media/*/pandora/apps:/media/*/pandora/desktop:/media/*/pandora/menu:/usr/pandora/apps # notifypath is a list of paths to monitor; if anything in those paths changes, the searchpath is rescanned # note that for each path chunk, all current subdirs of that path are also watched) -notifypath /media:/media/*/pandora/apps:/usr/pandora/apps +notifypath /media:/media/*/pandora/apps:/media/*/pandora/desktop:/media/*/pandora/menu:/usr/pandora/apps # PXMLs may be overridden .. ie: overrides are a subset of PXML, where the values are copied over the full PXML [overrides] diff --git a/deployment/etc/pandora/conf/desktop b/deployment/etc/pandora/conf/desktop index 0fa9ed4..e1626e2 100644 --- a/deployment/etc/pandora/conf/desktop +++ b/deployment/etc/pandora/conf/desktop @@ -9,7 +9,7 @@ iconpath /tmp # path for pndnotifyd to drop icons into (can be same as dotdeskto [menu] searchpath /mnt/sd?/pandora/menu # path to depth-search for PXMLs and pnd-files -dotdesktoppath /tmp/menu # path for pndnotifyd to spit .desktop files into +dotdesktoppath /usr/share/applications # path for pndnotifyd to spit .desktop files into iconpath /tmp # path for pndnotifyd to drop icons into (can be same as dotdesktoppath if WM permits) [launcher] -- 2.39.2 From eacd10a6839121bdb2fac1727172c3a7a886ffdd Mon Sep 17 00:00:00 2001 From: skeezix Date: Tue, 8 Dec 2009 23:29:19 -0500 Subject: [PATCH 08/16] Least interesting change ever (testdata/conf/apps so notify path includes testdata/menuapps) --- testdata/conf/apps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testdata/conf/apps b/testdata/conf/apps index 4f9a01e..f8e1c07 100644 --- a/testdata/conf/apps +++ b/testdata/conf/apps @@ -4,7 +4,7 @@ [autodiscovery] searchpath /mnt/sd?/pandora/apps:./testdata/app? # path to depth-search for PXMLs -notifypath /media:/media/*/pandora/apps:./testdata/app? +notifypath /media:/media/*/pandora/apps:./testdata/app?:./testdata/menuapp? # PXMLs may be overridden .. ie: overrides are a subset of PXML, where the values are copied over the full PXML [overrides] -- 2.39.2 From 359f5f6e43ad296c93009f3388d3e3376033312d Mon Sep 17 00:00:00 2001 From: vimacs Date: Fri, 11 Dec 2009 20:44:30 +0100 Subject: [PATCH 09/16] Added noplink option to aufs, changed losetup to /sbin/losetup Should check fs where appdata folder is on and only use noplink if apropiate modified: testdata/scripts/pnd_run.sh --- testdata/scripts/pnd_run.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/testdata/scripts/pnd_run.sh b/testdata/scripts/pnd_run.sh index e5b4083..9c8ecc7 100755 --- a/testdata/scripts/pnd_run.sh +++ b/testdata/scripts/pnd_run.sh @@ -117,10 +117,10 @@ if [ ! $umount ]; then mount | grep "on /mnt/utmp/$BASENAME type" # > /dev/null if [ ! $? -eq 0 ]; then - FREELOOP=$(sudo losetup -f) #get first free loop device + FREELOOP=$(sudo /sbin/losetup -f) #get first free loop device if [ ! $FREELOOP ]; then # no free loop device, create a new one #find a free loop device and use it - usedminor=$(sudo losetup -a | tail -n1) + usedminor=$(sudo /sbin/losetup -a | tail -n1) usedminor=${usedminor:9:1} echo usedminor $usedminor freeminor=$(($usedminor+1)) @@ -131,7 +131,7 @@ if [ ! $umount ]; then #detect fs if [ $DFS = ISO ]; then - sudo losetup $FREELOOP $PND #attach the pnd to the loop device + sudo /sbin/losetup $FREELOOP $PND #attach the pnd to the loop device mntline="sudo mount $FREELOOP /mnt/pnd/$BASENAME/" #setup the mountline for later # mntline="sudo mount -o loop,mode=777 $PND /mnt/pnd/$BASENAME" echo "Filetype is $DFS" @@ -140,7 +140,7 @@ if [ ! $umount ]; then #we bind the folder, now it can be treated in a unified way ATENTION: -o ro doesnt work for --bind at least on 25, on 26 its possible using remount, may have changed on 27 echo "Filetype is $DFS" elif [ $DFS = Squashfs ]; then - sudo losetup $FREELOOP $PND #attach the pnd to the loop device + sudo /sbin/losetup $FREELOOP $PND #attach the pnd to the loop device mntline="sudo mount -t squashfs $FREELOOP /mnt/pnd/$BASENAME" echo "Filetype is $DFS" else @@ -152,7 +152,7 @@ if [ ! $umount ]; then echo "$mntline" $mntline #mount the pnd/folder echo "mounting union!" - sudo mount -t aufs -o exec,dirs\=$MOUNTPOINT/pandora/appdata/$BASENAME=rw+nolwh:/mnt/pnd/$BASENAME=rr none /mnt/utmp/$BASENAME # put union on top + sudo mount -t aufs -o exec,noplink,dirs=$MOUNTPOINT/pandora/appdata/$BASENAME=rw+nolwh:/mnt/pnd/$BASENAME=rr none /mnt/utmp/$BASENAME # put union on top else echo "Union already mounted" @@ -192,7 +192,7 @@ if [ $? -eq 0 ]; then # check if the umount was successfull, if it wasnt it woul #delete tmp mountpoint sudo rmdir /mnt/utmp/$BASENAME; if [ $DFS = ISO ] || [ $DFS = Squashfs ]; then # check if we where running an iso, clean up loop device if we did - sudo losetup -d $FREELOOP + sudo /sbin/losetup -d $FREELOOP sudo rm $FREELOOP fi sudo rmdir /mnt/pnd/$BASENAME #delete pnd mountpoint -- 2.39.2 From 78a444d6fb5d66e2ee640084f6d791e8ccd105e8 Mon Sep 17 00:00:00 2001 From: vimacs Date: Fri, 11 Dec 2009 21:25:05 +0100 Subject: [PATCH 10/16] Now detects fs appdata is on and only uses noplink aufs option if its vfat modified: testdata/scripts/pnd_run.sh --- testdata/scripts/pnd_run.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/testdata/scripts/pnd_run.sh b/testdata/scripts/pnd_run.sh index 9c8ecc7..51546f6 100755 --- a/testdata/scripts/pnd_run.sh +++ b/testdata/scripts/pnd_run.sh @@ -87,6 +87,8 @@ fi #vars DFS=$(file -b $PND | awk '{ print $1 }') #is -p a zip/iso or folder? MOUNTPOINT=$(df $PND | sed -ne 's/.*\% \(\S*\)/\1/p' | tail -n1) #find out on which mountpoint the pnd is +FILESYSTEM=$(mount | grep "on $MOUNTPOINT " | awk '{print $5}') #get filesystem appdata is on to determine aufs options +echo "Filesystem is $FILESYSTEM" #if the pnd is on / set mountpoint to "" so we dont and up with // at the start, #this is to make sure sudo doesnt get confused if [ $MOUNTPOINT = "/" ]; then MOUNTPOINT=""; fi @@ -109,7 +111,7 @@ if [ ! -d /mnt/pnd/$BASENAME ]; then sudo mkdir -p /mnt/pnd/$BASENAME ; fi #moun #writeable dir for union if [ ! -d $MOUNTPOINT/pandora/appdata/$BASENAME ]; then sudo mkdir -p $MOUNTPOINT/pandora/appdata/$BASENAME; sudo chmod -R a+xrw $MOUNTPOINT/pandora/appdata/$BASENAME; fi if [ ! -d /mnt/utmp/$BASENAME ]; then sudo mkdir -p /mnt/utmp/$BASENAME; fi #union over the two - + #mount if [ ! $umount ]; then @@ -152,7 +154,11 @@ if [ ! $umount ]; then echo "$mntline" $mntline #mount the pnd/folder echo "mounting union!" + if [ $FILESYSTEM = vfat ]; then # use noplink on fat, dont on other fs's sudo mount -t aufs -o exec,noplink,dirs=$MOUNTPOINT/pandora/appdata/$BASENAME=rw+nolwh:/mnt/pnd/$BASENAME=rr none /mnt/utmp/$BASENAME # put union on top + else + sudo mount -t aufs -o exec,dirs=$MOUNTPOINT/pandora/appdata/$BASENAME=rw+nolwh:/mnt/pnd/$BASENAME=rr none /mnt/utmp/$BASENAME # put union on top + fi else echo "Union already mounted" -- 2.39.2 From f67e31538512f512e8c59572adc43461c3327539 Mon Sep 17 00:00:00 2001 From: vimacs Date: Sat, 12 Dec 2009 15:19:59 +0100 Subject: [PATCH 11/16] Fixed mounting of .pnd on / Changed gdm to slim Updated sudoers modified: testdata/scripts/pnd_run.sh modified: testdata/sh/sudoers --- testdata/scripts/pnd_run.sh | 71 +++++++++++++++++++------------------ testdata/sh/sudoers | 8 +++-- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/testdata/scripts/pnd_run.sh b/testdata/scripts/pnd_run.sh index 51546f6..24cd9b2 100755 --- a/testdata/scripts/pnd_run.sh +++ b/testdata/scripts/pnd_run.sh @@ -70,7 +70,7 @@ if [ $nox ]; then #the app doesnt want x to run, so we kill it and restart it on if [ $? = 102 ]; then exit 1 fi - sudo /etc/init.d/gdm stop + sudo /etc/init.d/slim stop sleep 5s else echo -e “ae[34me[30m” @@ -78,8 +78,8 @@ if [ $nox ]; then #the app doesnt want x to run, so we kill it and restart it on if [ $? = 102 ]; then exit 1 fi - # close x now, do we want to use gdm stop or just kill x? - sudo /etc/init.d/gdm stop + # close x now, do we want to use slim stop or just kill x? + sudo /etc/init.d/slim stop sleep 5s fi fi @@ -87,7 +87,8 @@ fi #vars DFS=$(file -b $PND | awk '{ print $1 }') #is -p a zip/iso or folder? MOUNTPOINT=$(df $PND | sed -ne 's/.*\% \(\S*\)/\1/p' | tail -n1) #find out on which mountpoint the pnd is -FILESYSTEM=$(mount | grep "on $MOUNTPOINT " | awk '{print $5}') #get filesystem appdata is on to determine aufs options +if [ ! -d "$MOUNTPOINT" ]; then MOUNTPOINT="/"; fi #make sure folder exists, if it doesnt assume rootfs +FILESYSTEM=$(mount | grep "on $MOUNTPOINT " | grep -v rootfs | awk '{print $5}' | tail -n1) #get filesystem appdata is on to determine aufs options echo "Filesystem is $FILESYSTEM" #if the pnd is on / set mountpoint to "" so we dont and up with // at the start, #this is to make sure sudo doesnt get confused @@ -119,36 +120,36 @@ if [ ! $umount ]; then mount | grep "on /mnt/utmp/$BASENAME type" # > /dev/null if [ ! $? -eq 0 ]; then - FREELOOP=$(sudo /sbin/losetup -f) #get first free loop device - if [ ! $FREELOOP ]; then # no free loop device, create a new one - #find a free loop device and use it - usedminor=$(sudo /sbin/losetup -a | tail -n1) - usedminor=${usedminor:9:1} - echo usedminor $usedminor - freeminor=$(($usedminor+1)) - echo freeminor $freeminor - sudo mknod -m777 /dev/loop$freeminor b 7 $freeminor - FREELOOP=/dev/loop$freeminor - fi - - #detect fs - if [ $DFS = ISO ]; then - sudo /sbin/losetup $FREELOOP $PND #attach the pnd to the loop device - mntline="sudo mount $FREELOOP /mnt/pnd/$BASENAME/" #setup the mountline for later - # mntline="sudo mount -o loop,mode=777 $PND /mnt/pnd/$BASENAME" - echo "Filetype is $DFS" - elif [ $DFS = directory ]; then - mntline="sudo mount --bind -o ro $PND /mnt/pnd/$BASENAME" - #we bind the folder, now it can be treated in a unified way ATENTION: -o ro doesnt work for --bind at least on 25, on 26 its possible using remount, may have changed on 27 - echo "Filetype is $DFS" - elif [ $DFS = Squashfs ]; then - sudo /sbin/losetup $FREELOOP $PND #attach the pnd to the loop device - mntline="sudo mount -t squashfs $FREELOOP /mnt/pnd/$BASENAME" - echo "Filetype is $DFS" - else - echo "error determining fs, output was $DFS" - exit 1; - fi + FREELOOP=$(sudo /sbin/losetup -f) #get first free loop device + if [ ! $FREELOOP ]; then # no free loop device, create a new one + #find a free loop device and use it + usedminor=$(sudo /sbin/losetup -a | tail -n1) + usedminor=${usedminor:9:1} + echo usedminor $usedminor + freeminor=$(($usedminor+1)) + echo freeminor $freeminor + sudo mknod -m777 /dev/loop$freeminor b 7 $freeminor + FREELOOP=/dev/loop$freeminor + fi + + #detect fs + if [ $DFS = ISO ]; then + sudo /sbin/losetup $FREELOOP $PND #attach the pnd to the loop device + mntline="sudo mount $FREELOOP /mnt/pnd/$BASENAME/" #setup the mountline for later + # mntline="sudo mount -o loop,mode=777 $PND /mnt/pnd/$BASENAME" + echo "Filetype is $DFS" + elif [ $DFS = directory ]; then + mntline="sudo mount --bind -o ro $PND /mnt/pnd/$BASENAME" + #we bind the folder, now it can be treated in a unified way ATENTION: -o ro doesnt work for --bind at least on 25, on 26 its possible using remount, may have changed on 27 + echo "Filetype is $DFS" + elif [ $DFS = Squashfs ]; then + sudo /sbin/losetup $FREELOOP $PND #attach the pnd to the loop device + mntline="sudo mount -t squashfs $FREELOOP /mnt/pnd/$BASENAME" + echo "Filetype is $DFS" + else + echo "error determining fs, output was $DFS" + exit 1; + fi echo "$mntline" @@ -207,5 +208,5 @@ fi if [ $nox ]; then #restart x if it was killed echo "starting x in 5s" sleep 5 -sudo /etc/init.d/gdm start +sudo /etc/init.d/slim start fi diff --git a/testdata/sh/sudoers b/testdata/sh/sudoers index 7dd75e3..5bdaf0f 100644 --- a/testdata/sh/sudoers +++ b/testdata/sh/sudoers @@ -1,9 +1,11 @@ -ALL ALL=NOPASSWD: /etc/init.d/gdm stop, /etc/init.d/gdm start, NOEXEC: /bin/mount /dev/loop* /mnt/pnd/* , \ +ALL ALL=NOPASSWD: /etc/init.d/slim stop, /etc/init.d/slim start, NOEXEC: /bin/mount /dev/loop* /mnt/pnd/* , \ /bin/mount -t aufs -o exec\,dirs\=*/pandora/appdata/*\=rw+nolwh\:/mnt/pnd/*\=rr none /mnt/utmp/* , \ -/bin/mount --bind -o ro * /mnt/pnd/*, \ +/bin/mount -t aufs -o exec\,noplink\,dirs\=*/pandora/appdata/*\=rw+nolwh\:/mnt/pnd/*\=rr none /mnt/utmp/* , \ +/bin/mount --bind -o ro * /mnt/pnd/*, mount -t squashfs * /mnt/pnd/*,\ /bin/umount /mnt/pnd/*, /bin/umount /mnt/utmp/*, \ /bin/mkdir -p /mnt/pnd/* , /bin/mkdir -p /mnt/utmp/* , /bin/mkdir -p */pandora/appdata/* , \ /bin/chmod -R a+xrw */pandora/appdata/*, \ /bin/rmdir /mnt/pnd/*,/bin/rmdir /mnt/utmp/* ,/bin/rmdir */pandora/appdata/*/.wh..wh.plnk, \ /bin/rmdir */pandora/appdata/*/.wh..wh..tmp,/bin/rmdir -p */pandora/appdata/*/, \ -/sbin/losetup /dev/loop*, /bin/mknod -m777 /dev/loop*, /sbin/losetup -d /dev/loop*, /bin/rm /dev/loop* +/sbin/losetup /dev/loop*, /sbin/losetup -d /dev/loop*, /sbin/losetup -f, /sbin/losetup -a, \ +/bin/mknod -m777 /dev/loop* b 7 *, /bin/rm /dev/loop* -- 2.39.2 From 69c9b0423b43a81cf60827a77c1644bb5d74ff74 Mon Sep 17 00:00:00 2001 From: skeezix Date: Tue, 15 Dec 2009 10:50:59 -0500 Subject: [PATCH 12/16] Added basic emit_icon_to_buffer() routine for cpasjuste; you must free its return on your own --- include/pnd_desktop.h | 1 + lib/pnd_desktop.c | 82 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/include/pnd_desktop.h b/include/pnd_desktop.h index a6aba00..550b03e 100644 --- a/include/pnd_desktop.h +++ b/include/pnd_desktop.h @@ -19,6 +19,7 @@ unsigned char pnd_emit_dotdesktop ( char *targetpath, char *pndrun, pnd_disco_t // emit_icon() will attempt to copy the icon from a PXML directory, or from a pnd file if appended, // to the given directory; returns 1 on sucess, otherwise is a fail. unsigned char pnd_emit_icon ( char *targetpath, pnd_disco_t *p ); +unsigned char *pnd_emit_icon_to_buffer ( pnd_disco_t *p, unsigned int *r_buflen ); // returns length of malloc in r_buflen if !NULL // pnd_map_dotdesktop_categories() will attempt to find an appropriate standard .desktop category(s) based // on the provided PXML-style category. diff --git a/lib/pnd_desktop.c b/lib/pnd_desktop.c index c2cffec..3760fba 100644 --- a/lib/pnd_desktop.c +++ b/lib/pnd_desktop.c @@ -393,3 +393,85 @@ char *pnd_map_dotdesktop_category ( pnd_conf_handle c, char *single_category ) { return ( ret ); } + +unsigned char *pnd_emit_icon_to_buffer ( pnd_disco_t *p, unsigned int *r_buflen ) { + // this is shamefully mostly a copy of emit_icon() above; really, need to refactor that to use this routine + // with a fwrite at the end... + char from [ FILENAME_MAX ]; // source filename + char bits [ 8 * 1024 ]; + unsigned int bitlen; + FILE *pnd = NULL; + unsigned char *target = NULL, *targiter = NULL; + + // prelim .. if a pnd file, and no offset found, discovery code didn't locate icon.. so bail. + if ( ( p -> object_type == pnd_object_type_pnd ) && + ( ! p -> pnd_icon_pos ) ) + { + return ( NULL ); // discover code didn't find it, so FAIL + } + + /* first.. open the source file, by type of application: + * are we looking through a pnd file or a dir? + */ + if ( p -> object_type == pnd_object_type_directory ) { + sprintf ( from, "%s/%s", p -> object_path, p -> icon ); + } else if ( p -> object_type == pnd_object_type_pnd ) { + sprintf ( from, "%s/%s", p -> object_path, p -> object_filename ); + } + + pnd = fopen ( from, "r" ); + + if ( ! pnd ) { + return ( NULL ); + } + + // determine length of file, then adjust by icon position to find begin of icon + unsigned int len; + + fseek ( pnd, 0, SEEK_END ); + len = ftell ( pnd ); + //fseek ( pnd, 0, SEEK_SET ); + + fseek ( pnd, p -> pnd_icon_pos, SEEK_SET ); + + len -= p -> pnd_icon_pos; + + // create target buffer + target = malloc ( len ); + + if ( ! target ) { + fclose ( pnd ); + return ( 0 ); + } + + targiter = target; + + if ( r_buflen ) { + *r_buflen = len; + } + + // copy over icon to target + while ( len ) { + + if ( len > (8*1024) ) { + bitlen = (8*1024); + } else { + bitlen = len; + } + + if ( fread ( bits, bitlen, 1, pnd ) != 1 ) { + fclose ( pnd ); + free ( target ); + return ( NULL ); + } + + memmove ( targiter, bits, bitlen ); + targiter += bitlen; + + len -= bitlen; + } // while + + fclose ( pnd ); + + return ( target ); +} -- 2.39.2 From 954c1700b6f2cd7ba99244ce6df687db1671ce9f Mon Sep 17 00:00:00 2001 From: skeezix Date: Tue, 15 Dec 2009 11:05:48 -0500 Subject: [PATCH 13/16] Add a couple things to TODO list so I don't forget (TODO.txt) Remove some inaccurate comments from pndnotifyd --- TODO.txt | 20 ++++++++++++++++---- apps/pndnotifyd.c | 4 ---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/TODO.txt b/TODO.txt index 8d9c843..22f6f7d 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,14 +1,26 @@ -Some things to be done.. (see also the main wiki 'todo' page) +- pndnotifyd drops icons into a conf'd location (/tmp currently); it skips emitting them if that path is already + present (saves time after first emit!) .. the risk here is that the icon has changed or been removed, and we just + keep using the old one. Perhaps pndnotifyd should check the length of the icon (or a CRC32 or something), on occasion? -- UNICODE everywhere.. this will be a lot of work I'm sure. (replacing all the char* and related consumers) +- pndnotifyd doesn't delete icons; it leaves them in /tmp as a cache of sorts. /tmp may not be wisest if it gets turfed on + boot, but I'm up in the air on that one; we want caching, but not unlimited lifespan, so /tmp seems useful. (Unless /tmp + is in RAM, which it could be, should check.) ANYWAY, my point is.. + - should we conf how many icons/bytes can be consumed in 'cache' this way? + - more to point, should we remove old icons (ie: say, a week old) by default, so occasional re-caching occurs, but + we also clean up unused icons? We could also keep an access-count or somesuch. Just sayin' + +- fill in the blanks in pnd-logger + +- more useful PXML.xml parser, to cover all the fields and provide iterators, and better support the treelike nature - applist memleak; ie: all those disco_t apps returned are never free'd properly include/pnd_discovery.h:// TODO: A way to release the disco-lists and reclaim RAM :) -- put in code to fetch clock speed, and add clock speed handler to pnd_apps_exec +- put in code to fetch clock speed, and add clock speed handler to pnd_apps_exec? maybe unify the whole emit-dotdesktop and + exec routines o they dont' have two representations of command line args .. -- remove -u's from pnd_apps_exec and pnd_emit_dotdesktop +- remove -u's from pnd_apps_exec and pnd_emit_dotdesktop? - replace brute force memcmp() in pnd pnd with a fancy pants CS alg - make pnd_pndfiles seek of PXML more efficient; fread ( 1, nmemb ) gotta be slow :) diff --git a/apps/pndnotifyd.c b/apps/pndnotifyd.c index 31d9c92..0fa18ba 100644 --- a/apps/pndnotifyd.c +++ b/apps/pndnotifyd.c @@ -4,10 +4,6 @@ * */ -// TODO: Catch HUP and reparse config -// TODO: Should perhaps direct all printf's through a vsprintf handler to avoid redundant "if ! g_daemon_mode" -// TODO: During daemon mode, should perhaps syslog or log errors - #include // for stdio #include // for exit() #include // for exit() -- 2.39.2 From a43abe73a936ffd5dbe8430de47f0022d1eb8959 Mon Sep 17 00:00:00 2001 From: skeezix Date: Tue, 15 Dec 2009 15:49:59 -0500 Subject: [PATCH 14/16] Fixed a bad desktop conf for deployment --- deployment/etc/pandora/conf/desktop | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment/etc/pandora/conf/desktop b/deployment/etc/pandora/conf/desktop index e1626e2..c0787db 100644 --- a/deployment/etc/pandora/conf/desktop +++ b/deployment/etc/pandora/conf/desktop @@ -3,12 +3,12 @@ # Desktop configuration [desktop] -searchpath /mnt/sd?/pandora/desktop:/usr/pandora/apps # path to depth-search for PXMLs and pnd-files +searchpath /media/*/pandora/desktop:/media/*/pandora/apps:/usr/pandora/apps # path to depth-search for PXMLs and pnd-files dotdesktoppath ~/Desktop/ # path for pndnotifyd to spit .desktop files into (run as root) iconpath /tmp # path for pndnotifyd to drop icons into (can be same as dotdesktoppath if WM permits) [menu] -searchpath /mnt/sd?/pandora/menu # path to depth-search for PXMLs and pnd-files +searchpath /media/*/pandora/menu # path to depth-search for PXMLs and pnd-files dotdesktoppath /usr/share/applications # path for pndnotifyd to spit .desktop files into iconpath /tmp # path for pndnotifyd to drop icons into (can be same as dotdesktoppath if WM permits) -- 2.39.2 From a6b3ac0b3482ca5992004a8492b36cea1afeac5c Mon Sep 17 00:00:00 2001 From: skeezix Date: Wed, 16 Dec 2009 22:27:39 -0500 Subject: [PATCH 15/16] In the last couple checkins, introduced a bug where when a SD is popped out the icons are not removed from desktop. That'll teach me to out-clever myself. --- apps/pndnotifyd.c | 10 ++++++---- include/pnd_notify.h | 7 +++++++ lib/pnd_notify.c | 25 +++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/apps/pndnotifyd.c b/apps/pndnotifyd.c index 0fa18ba..b511930 100644 --- a/apps/pndnotifyd.c +++ b/apps/pndnotifyd.c @@ -235,6 +235,10 @@ int main ( int argc, char *argv[] ) { // lets not eat up all the CPU // should use an alarm or select() or something -- I mean really, why aren't I putting interval_secs into // the select() call above in pnd_notify_whatsitcalled()? -- but lets not break this right before release shall we + // NOTE: Oh right, I remember now -- inotify will spam when a card is inserted, and it will not be instantaneoous.. + // the events will dribble in over a second. So this sleep is a lame trick that generally works. I really should + // do select(), and then when it returns just spin for a couple seconds slurping up events until no more and a thresh-hold + // time is hit, but this will do for now. I suck. sleep ( interval_secs ); } // while @@ -520,12 +524,10 @@ unsigned char perform_discoveries ( char *appspath, char *overridespath, // attempt to auto-discover applications in the given path applist = pnd_disco_search ( appspath, overridespath ); - if ( ! applist ) { - return ( 0 ); + if ( applist ) { + process_discoveries ( applist, emitdesktoppath, emiticonpath ); } - process_discoveries ( applist, emitdesktoppath, emiticonpath ); - // run a clean up, to remove any dotdesktop files that we didn't // just now create (that seem to have been created by pndnotifyd // previously.) This allows SD eject (or .pnd remove) to remove diff --git a/include/pnd_notify.h b/include/pnd_notify.h index f83c6f9..a555bc9 100644 --- a/include/pnd_notify.h +++ b/include/pnd_notify.h @@ -32,6 +32,13 @@ void pnd_notify_watch_path ( pnd_notify_handle h, char *fullpath, unsigned int f */ unsigned char pnd_notify_rediscover_p ( pnd_notify_handle h ); +/* wait_until_ready() will loop (up to a max of secs_timeout) until inotify seems to + * be responding. Returns 0 if inotify is never ready during the interval, otherwise + * it suggests inotify is up and going. + * secs_timeout may be 0 which means 'forever' + */ +unsigned char pnd_notify_wait_until_ready ( unsigned int secs_timeout ); + #ifdef __cplusplus } /* "C" */ #endif diff --git a/lib/pnd_notify.c b/lib/pnd_notify.c index a5b8c44..9be749b 100644 --- a/lib/pnd_notify.c +++ b/lib/pnd_notify.c @@ -172,3 +172,28 @@ unsigned char pnd_notify_rediscover_p ( pnd_notify_handle h ) { return ( 1 ); } + +/* we've run into the issue where inotify returns that it is set up, but in + * fact is not doing anything; restarting the process repairs it.. so here + * we devise a wank that continually tests inotify until it responds, then + * returns knowing we're good + */ +unsigned char pnd_notify_wait_until_ready ( unsigned int secs_timeout ) { + return ( 0 ); // fail +} + +static unsigned char _inotify_test_run ( void ) { +#if 0 + // set up inotify + int fd; + + fd = inotify_init(); + + if ( fd < 0 ) { + return ( 0 ); // failed to init at all + } + + + +#endif +} -- 2.39.2 From a2e1c684f4754cce44d52f1572d8aaade9ff7434 Mon Sep 17 00:00:00 2001 From: skeezix Date: Wed, 16 Dec 2009 23:11:01 -0500 Subject: [PATCH 16/16] UNTESTED on device! Put in a workaround for the inotify being out in space issue, maybe. At least, pndnotifyd will try inotify and see if it notices anything; if so, continues, otherwise bitches. --- apps/pndnotifyd.c | 8 +++++ include/pnd_notify.h | 1 + lib/pnd_notify.c | 81 +++++++++++++++++++++++++++++++++++--------- 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/apps/pndnotifyd.c b/apps/pndnotifyd.c index b511930..3720131 100644 --- a/apps/pndnotifyd.c +++ b/apps/pndnotifyd.c @@ -118,6 +118,14 @@ int main ( int argc, char *argv[] ) { pnd_log ( pndn_rem, "Interval between checks is %u seconds\n", interval_secs ); + // check if inotify is awake yet; if not, try waiting for awhile to see if it does + if ( ! pnd_notify_wait_until_ready ( 120 /* seconds */ ) ) { + pnd_log ( pndn_error, "ERROR: INOTIFY refuses to be useful and quite awhile has passed. Bailing out.\n" ); + return ( -1 ); + } + + pnd_log ( pndn_rem, "INOTIFY seems to be useful, whew.\n" ); + // basic daemon set up if ( g_daemon_mode ) { diff --git a/include/pnd_notify.h b/include/pnd_notify.h index a555bc9..f4bf594 100644 --- a/include/pnd_notify.h +++ b/include/pnd_notify.h @@ -36,6 +36,7 @@ unsigned char pnd_notify_rediscover_p ( pnd_notify_handle h ); * be responding. Returns 0 if inotify is never ready during the interval, otherwise * it suggests inotify is up and going. * secs_timeout may be 0 which means 'forever' + * NOTE: This wastes some time, always */ unsigned char pnd_notify_wait_until_ready ( unsigned int secs_timeout ); diff --git a/lib/pnd_notify.c b/lib/pnd_notify.c index 9be749b..7dd135b 100644 --- a/lib/pnd_notify.c +++ b/lib/pnd_notify.c @@ -3,6 +3,7 @@ #include // for stdio, NULL #include // for malloc, etc #include // for close +#include // for time() #define _XOPEN_SOURCE 500 #define __USE_XOPEN_EXTENDED @@ -17,7 +18,7 @@ typedef struct { static int notify_handle; -static void pnd_notify_hookup ( int fd ); +//static void pnd_notify_hookup ( int fd ); #define PND_INOTIFY_MASK IN_CREATE | IN_DELETE | IN_UNMOUNT \ | IN_DELETE_SELF | IN_MOVE_SELF \ @@ -43,7 +44,7 @@ pnd_notify_handle pnd_notify_init ( void ) { p -> fd = fd; // setup some default watches - pnd_notify_hookup ( fd ); + //pnd_notify_hookup ( fd ); return ( p ); } @@ -99,6 +100,7 @@ void pnd_notify_watch_path ( pnd_notify_handle h, char *fullpath, unsigned int f return; } +#if 0 static void pnd_notify_hookup ( int fd ) { inotify_add_watch ( fd, "./testdata", IN_CREATE | IN_DELETE | IN_UNMOUNT ); @@ -106,6 +108,7 @@ static void pnd_notify_hookup ( int fd ) { return; } +#endif unsigned char pnd_notify_rediscover_p ( pnd_notify_handle h ) { pnd_notify_t *p = (pnd_notify_t*) h; @@ -173,27 +176,73 @@ unsigned char pnd_notify_rediscover_p ( pnd_notify_handle h ) { return ( 1 ); } -/* we've run into the issue where inotify returns that it is set up, but in - * fact is not doing anything; restarting the process repairs it.. so here - * we devise a wank that continually tests inotify until it responds, then - * returns knowing we're good - */ -unsigned char pnd_notify_wait_until_ready ( unsigned int secs_timeout ) { - return ( 0 ); // fail -} - +#define _INOTIFY_TEST_PATH "/tmp/.notifytest" +#define _INOTIFY_TEST_FILE "/tmp/.notifytest/foopanda" static unsigned char _inotify_test_run ( void ) { -#if 0 + // set up inotify - int fd; + pnd_notify_t fdt; + int wd; // watch-descriptor - fd = inotify_init(); + // set up inotify + fdt.fd = inotify_init(); - if ( fd < 0 ) { + if ( fdt.fd < 0 ) { return ( 0 ); // failed to init at all } + // make a temp dir + mkdir ( _INOTIFY_TEST_PATH, 0777 ); // if it fails we assume it exists, which is fine + // watch the dir + if ( ( wd = inotify_add_watch ( fdt.fd, _INOTIFY_TEST_PATH, IN_DELETE ) ) < 0 ) { + return ( 0 ); // couldn't watch dir + } -#endif + // sleep a sec, just to be safe; seems to dislike being called immediately sometimes + usleep ( 1000000 / 2 ); + + // create a temp file + FILE *f; + if ( ! ( f = fopen ( _INOTIFY_TEST_FILE, "w" ) ) ) { + close ( fdt.fd ); + return ( 0 ); // couldn't create test file?! + } + + fclose ( f ); + + // delete the temp file; this should trigger an event + if ( unlink ( _INOTIFY_TEST_FILE ) < 0 ) { + return ( 0 ); // couldn't rm a file? life is harsh. + } + + // did we get anything? + unsigned char s; + s = pnd_notify_rediscover_p ( &fdt ); + + // ditch inotify + close ( fdt.fd ); + + // success + return ( s ); +} + +/* we've run into the issue where inotify returns that it is set up, but in + * fact is not doing anything; restarting the process repairs it.. so here + * we devise a wank that continually tests inotify until it responds, then + * returns knowing we're good + */ +unsigned char pnd_notify_wait_until_ready ( unsigned int secs_timeout ) { + time_t start = time ( NULL ); + + while ( ( secs_timeout == 0 ) || + ( time ( NULL ) - start < secs_timeout ) ) + { + if ( _inotify_test_run() ) { + return ( 1 ); + } + usleep ( 1000000 / 2 ); + } + + return ( 0 ); // fail } -- 2.39.2