Added basic LED stuff to libpnd pnd_device
[pandora-libraries.git] / apps / pndevmapperd.c
1
2 /* pndevmapperd exists to watch for a few interesting events and to launch scripts when they occur
3  * ie: when the lid closes, should invoke power-mode toggle sh-script
4  */
5
6 // woot for writing code while sick.
7
8 // this code begs a rewrite, but should work fine; its just arranged goofily
9 // -> I mean, why the racial divide between keys and other events
10
11 #include <stdio.h> /* for printf, NULL */
12 #include <stdlib.h> /* for free */
13 #include <string.h> /* for strdup */
14 #include <unistd.h>    // for exit()
15 #include <sys/types.h> // for umask
16 #include <sys/stat.h>  // for umask
17 #include <fcntl.h> // for open(2)
18 #include <errno.h> // for errno
19 #include <time.h> // for time(2)
20 #include <ctype.h> // for isdigit
21 #include <signal.h> // for sigaction
22 #include <sys/wait.h> // for wait
23 #include <sys/time.h> // setitimer
24
25 #include <linux/input.h> // for keys
26 //#include "../../kernel-rip/input.h" // for keys
27
28 #include "pnd_conf.h"
29 #include "pnd_container.h"
30 #include "pnd_apps.h"
31 #include "pnd_discovery.h"
32 #include "pnd_locate.h"
33 #include "pnd_pndfiles.h"
34 #include "pnd_pxml.h"
35 #include "pnd_logger.h"
36 #include "pnd_utility.h"
37 #include "pnd_notify.h"
38 #include "pnd_device.h"
39
40 // daemon and logging
41 //
42 unsigned char g_daemon_mode = 0;
43 unsigned int g_minimum_separation = 1;
44
45 typedef enum {
46   pndn_debug = 0,
47   pndn_rem,          // will set default log level to here, so 'debug' is omitted
48   pndn_warning,
49   pndn_error,
50   pndn_none
51 } pndnotify_loglevels_e;
52
53 // key/event definition
54 //
55 typedef struct {
56   int keycode;
57   char *keyname;
58 } keycode_t;
59
60 keycode_t keycodes[] = {
61   { KEY_A, "a" },
62   { KEY_MENU, "pandora" },
63   { KEY_POWER, "power" },
64   { -1, NULL }
65 };
66
67 typedef struct {
68   int type;
69   int code;
70   char *name;
71 } generic_event_t;
72
73 generic_event_t generics[] = {
74   { EV_SW, 0, "lid-toggle" }, // expecting value 1 (lid close) or 0 (lid open)
75   { -1, -1, NULL }
76 };
77
78 // event-to-sh mapping
79 //
80 typedef struct {
81
82   unsigned char key_p; // 1 if its a key, otherwise an event
83
84   /* template information
85    */
86   void *reqs;          // scancode/etc for the event in question
87
88   char *script;        // script to invoke
89   //unsigned int hold_min; // minimum hold-time to trigger
90
91   /* state
92    */
93   time_t last_trigger_time;
94   time_t keydown_time;
95
96 } evmap_t;
97
98 #define MAXEVENTS 255
99 evmap_t g_evmap [ MAXEVENTS ];
100 unsigned int g_evmap_max = 0;
101
102 // battery
103 unsigned char b_threshold = 5;    // %battery
104 unsigned int b_frequency = 300;   // frequency to check
105 unsigned int b_blinkfreq = 2;     // blink every 2sec
106 unsigned int b_blinkdur = 1000;   // blink duration (uSec), 0sec + uSec is assumed
107 unsigned char b_active = 0;       // 0=inactive, 1=active and waiting to blink, 2=blink is on, waiting to turn off
108
109 /* get to it
110  */
111 void dispatch_key ( int keycode, int val );
112 void dispatch_event ( int code, int val );
113 void sigchld_handler ( int n );
114 unsigned char set_next_alarm ( unsigned int secs, unsigned int usecs );
115 void sigalrm_handler ( int n );
116
117 static void usage ( char *argv[] ) {
118   printf ( "%s [-d]\n", argv [ 0 ] );
119   printf ( "-d\tDaemon mode; detach from terminal, chdir to /tmp, suppress output. Optional.\n" );
120   printf ( "-l#\tLog-it; -l is 0-and-up (or all), and -l2 means 2-and-up (not all); l[0-3] for now. Log goes to /tmp/pndevmapperd.log\n" );
121   return;
122 }
123
124 int main ( int argc, char *argv[] ) {
125   int i;
126   int logall = -1; // -1 means normal logging rules; >=0 means log all!
127
128   for ( i = 1; i < argc; i++ ) {
129
130     if ( argv [ i ][ 0 ] == '-' && argv [ i ][ 1 ] == 'd' ) {
131       //printf ( "Going daemon mode. Silent running.\n" );
132       g_daemon_mode = 1;
133     } else if ( argv [ i ][ 0 ] == '-' && argv [ i ][ 1 ] == 'l' ) {
134
135       if ( isdigit ( argv [ i ][ 2 ] ) ) {
136         unsigned char x = atoi ( argv [ i ] + 2 );
137         if ( x >= 0 &&
138              x < pndn_none )
139         {
140           logall = x;
141         }
142       } else {
143         logall = 0;
144       }
145     } else {
146       usage ( argv );
147       exit ( 0 );
148     }
149
150   } // for
151
152   /* enable logging?
153    */
154   pnd_log_set_pretext ( "pndevmapperd" );
155   pnd_log_set_flush ( 1 );
156
157   if ( logall == -1 ) {
158     // standard logging; non-daemon versus daemon
159
160     if ( g_daemon_mode ) {
161       // nada
162     } else {
163       pnd_log_set_filter ( pndn_rem );
164       pnd_log_to_stdout();
165     }
166
167   } else {
168     FILE *f;
169
170     f = fopen ( "/tmp/pndevmapperd.log", "w" );
171
172     if ( f ) {
173       pnd_log_set_filter ( logall );
174       pnd_log_to_stream ( f );
175       pnd_log ( pndn_rem, "logall mode - logging to /tmp/pndevmapperd.log\n" );
176     }
177
178     if ( logall == pndn_debug ) {
179       pnd_log_set_buried_logging ( 1 ); // log the shit out of it
180       pnd_log ( pndn_rem, "logall mode 0 - turned on buried logging\n" );
181     }
182
183   } // logall
184
185   pnd_log ( pndn_rem, "%s built %s %s", argv [ 0 ], __DATE__, __TIME__ );
186
187   pnd_log ( pndn_rem, "log level starting as %u", pnd_log_get_filter() );
188
189   // basic daemon set up
190   if ( g_daemon_mode ) {
191
192     // set a CWD somewhere else
193     chdir ( "/tmp" );
194
195     // detach from terminal
196     if ( ( i = fork() ) < 0 ) {
197       pnd_log ( pndn_error, "ERROR: Couldn't fork()\n" );
198       exit ( i );
199     }
200     if ( i ) {
201       exit ( 0 ); // exit parent
202     }
203     setsid();
204
205     // umask
206     umask ( 022 ); // emitted files can be rwxr-xr-x
207     
208   } // set up daemon
209
210   /* hmm, seems to not like working right after boot.. do we depend on another daemon or
211    * on giving kernel time to init something, or ... wtf?
212    * -- lets give the system some time to wake up
213    */
214   { // delay
215
216     // this one works for pndnotifyd, which actually needs INOTIFYH..
217     //
218
219     // check if inotify is awake yet; if not, try waiting for awhile to see if it does
220     pnd_log ( pndn_rem, "Starting INOTIFY test; should be instant, but may take awhile...\n" );
221
222     if ( ! pnd_notify_wait_until_ready ( 120 /* seconds */ ) ) {
223       pnd_log ( pndn_error, "ERROR: INOTIFY refuses to be useful and quite awhile has passed. Bailing out.\n" );
224       return ( -1 );
225     }
226
227     pnd_log ( pndn_rem, "INOTIFY seems to be useful, whew.\n" );
228
229     // pndnotifyd also waits for user to log in .. pretty excessive, especially since
230     // what if user wants to close the lid while at the log in screen? for now play the
231     // odds as thats pretty unliekly usage scenariom but is clearly not acceptible :/
232     //
233
234     // wait for a user to be logged in - we should probably get hupped when a user logs in, so we can handle
235     // log-out and back in again, with SDs popping in and out between..
236     pnd_log ( pndn_rem, "Checking to see if a user is logged in\n" );
237     char tmp_username [ 128 ];
238     while ( 1 ) {
239       if ( pnd_check_login ( tmp_username, 127 ) ) {
240         break;
241       }
242       pnd_log ( pndn_debug, "  No one logged in yet .. spinning.\n" );
243       sleep ( 2 );
244     } // spin
245     pnd_log ( pndn_rem, "Looks like user '%s' is in, continue.\n", tmp_username );
246
247   } // delay
248
249   /* inhale config or die trying
250    */
251   char *configpath;
252
253   // attempt to fetch a sensible default searchpath for configs
254   configpath = pnd_conf_query_searchpath();
255
256   // attempt to fetch the apps config. since it finds us the runscript
257   pnd_conf_handle evmaph;
258
259   evmaph = pnd_conf_fetch_by_id ( pnd_conf_evmap, configpath );
260
261   if ( ! evmaph ) {
262     // couldn't locate conf, just bail
263     pnd_log ( pndn_error, "ERROR: Couldn't locate conf file\n" );
264     exit ( -1 );
265   }
266
267   /* iterate across conf, stocking the event map
268    */
269   void *n = pnd_box_get_head ( evmaph );
270
271   while ( n ) {
272     char *k = pnd_box_get_key ( n );
273     //printf ( "key %s\n", k );
274
275     if ( strncmp ( k, "keys.", 5 ) == 0 ) {
276       k += 5;
277
278       // keys should really push push generic-events onto the table, since they;'re just a special case of them
279       // to make things easier to read
280
281       // figure out which keycode we're talking about
282       keycode_t *p = keycodes;
283       while ( p -> keycode != -1 ) {
284         if ( strcasecmp ( p -> keyname, k ) == 0 ) {
285           break;
286         }
287         p++;
288       }
289
290       if ( p -> keycode != -1 ) {
291         g_evmap [ g_evmap_max ].key_p = 1;    // its a key, not an event
292         g_evmap [ g_evmap_max ].reqs = p;     // note the keycode
293         g_evmap [ g_evmap_max ].script = n;   // note the script to activate in response
294         pnd_log ( pndn_rem, "Registered key %s [%d] to script %s\n", p -> keyname, p -> keycode, (char*) n );
295         g_evmap_max++;
296       } else {
297         pnd_log ( pndn_warning, "WARNING! Key '%s' is not handled by pndevmapperd yet! Skipping.", k );
298       }
299
300     } else if ( strncmp ( k, "events.", 7 ) == 0 ) {
301       k += 7;
302
303       // yes, key events could really be defined in this generic sense, and really we could just let people
304       // put the code and so on right in the conf, but trying to keep it easy on people; maybe should
305       // add a 'generic' section to conf file and just let folks redefine random events that way
306       // Really, it'd be nice if the /dev/input/events could spit out useful text, and just use scripts
307       // to respond without a daemon per se; for that matter, pnd-ls and pnd-map pnd-dotdesktopemitter
308       // should just exist as scripts rather than daemons, but whose counting?
309
310       // figure out which keycode we're talking about
311       generic_event_t *p = generics;
312       while ( p -> code != -1 ) {
313         if ( strcasecmp ( p -> name, k ) == 0 ) {
314           break;
315         }
316         p++;
317       }
318
319       if ( p -> code != -1 ) {
320         g_evmap [ g_evmap_max ].key_p = 0;    // its an event, not a key
321         g_evmap [ g_evmap_max ].reqs = p;     // note the keycode
322         g_evmap [ g_evmap_max ].script = n;   // note the script to activate in response
323         pnd_log ( pndn_rem, "Registered generic event %s [%d] to script %s\n", p -> name, p -> code, (char*) n );
324         g_evmap_max++;
325       } else {
326         pnd_log ( pndn_warning, "WARNING! Generic event '%s' is not handled by pndevmapperd yet! Skipping.", k );
327       }
328
329     } else if ( strncmp ( k, "pndevmapperd.", 7 ) == 0 ) {
330       // not consumed here, skip silently
331
332     } else if ( strncmp ( k, "battery.", 8 ) == 0 ) {
333       // not consumed here, skip silently
334
335     } else {
336       // uhhh
337       pnd_log ( pndn_warning, "Unknown config key '%s'; skipping.\n", k );
338     }
339
340     n = pnd_box_get_next ( n );
341   } // while
342
343   if ( pnd_conf_get_as_int ( evmaph, "pndevmapperd.loglevel" ) != PND_CONF_BADNUM ) {
344     pnd_log_set_filter ( pnd_conf_get_as_int ( evmaph, "pndevmapperd.loglevel" ) );
345     pnd_log ( pndn_rem, "config file causes loglevel to change to %u", pnd_log_get_filter() );
346   }
347
348   if ( pnd_conf_get_as_int ( evmaph, "pndevmapperd.minimum_separation" ) != PND_CONF_BADNUM ) {
349     g_minimum_separation = pnd_conf_get_as_int ( evmaph, "pndevmapperd.minimum_separation" );
350     pnd_log ( pndn_rem, "config file causes minimum_separation to change to %u", g_minimum_separation );
351   }
352
353   // battery conf
354   if ( pnd_conf_get_as_int ( evmaph, "battery.threshold" ) != PND_CONF_BADNUM ) {
355     b_threshold = pnd_conf_get_as_int ( evmaph, "battery.threshold" );
356     pnd_log ( pndn_rem, "Battery threshold set to %u", b_threshold );
357   }
358   if ( pnd_conf_get_as_int ( evmaph, "battery.check_interval" ) != PND_CONF_BADNUM ) {
359     b_frequency = pnd_conf_get_as_int ( evmaph, "battery.check_interval" );
360     pnd_log ( pndn_rem, "Battery check interval set to %u", b_frequency );
361   }
362   if ( pnd_conf_get_as_int ( evmaph, "battery.blink_interval" ) != PND_CONF_BADNUM ) {
363     b_blinkfreq = pnd_conf_get_as_int ( evmaph, "battery.blink_interval" );
364     pnd_log ( pndn_rem, "Battery blink interval set to %u", b_blinkfreq );
365   }
366   if ( pnd_conf_get_as_int ( evmaph, "battery.blink_duration" ) != PND_CONF_BADNUM ) {
367     b_blinkdur = pnd_conf_get_as_int ( evmaph, "battery.blink_duration" );
368     pnd_log ( pndn_rem, "Battery blink duration set to %u", b_blinkdur );
369   }
370   b_active = 0;
371
372   /* do we have anything to do?
373    */
374   if ( ! g_evmap_max ) {
375     // uuuh, nothing to do?
376     pnd_log ( pndn_warning, "WARNING! No events configured to watch, so just spinning wheels...\n" );
377     exit ( -1 );
378   } // spin
379
380   /* set up sigchld -- don't want zombies all over; well, we do, but not process zombies
381    */
382   sigset_t ss;
383   sigemptyset ( &ss );
384
385   struct sigaction siggy;
386   siggy.sa_handler = sigchld_handler;
387   siggy.sa_mask = ss; /* implicitly blocks the origin signal */
388   siggy.sa_flags = SA_RESTART; /* don't need anything */
389   sigaction ( SIGCHLD, &siggy, NULL );
390
391   /* set up the battery level warning timers
392    */
393   siggy.sa_handler = sigalrm_handler;
394   siggy.sa_mask = ss; /* implicitly blocks the origin signal */
395   siggy.sa_flags = SA_RESTART; /* don't need anything */
396   sigaction ( SIGALRM, &siggy, NULL );
397
398   if ( set_next_alarm ( b_frequency, 0 ) ) { // check every 'frequency' seconds
399     pnd_log ( pndn_rem, "Checking for low battery every %u seconds\n", b_frequency );
400   } else {
401     pnd_log ( pndn_error, "ERROR: Couldn't set up timer for every %u seconds\n", b_frequency );
402   }
403
404   /* actually try to do something useful
405    */
406
407   // stolen in part from notaz :)
408
409   // try to locate the appropriate devices
410   int id;
411   int fds [ 8 ] = { -1, -1, -1, -1, -1, -1, -1, -1 }; // 0 = keypad, 1 = gpio keys
412   int imaxfd = 0;
413
414   for ( id = 0; ; id++ ) {
415     char fname[64];
416     char name[256] = { 0, };
417     int fd;
418
419     snprintf ( fname, sizeof ( fname ), "/dev/input/event%i", id );
420     fd = open ( fname, O_RDONLY );
421
422     if ( fd == -1 ) {
423       break;
424     }
425
426     if ( ioctl (fd, EVIOCGNAME(sizeof(name)), name ) < 0 ) {
427       name [ 0 ] = '\0';
428     }
429
430     pnd_log ( pndn_rem, "%s maps to %s\n", fname, name );
431
432     if ( strcmp ( name, "omap_twl4030keypad" ) == 0 ) {
433       fds [ 0 ] = fd;
434     } else if ( strcmp ( name, "gpio-keys" ) == 0) {
435       fds [ 1 ] = fd;
436     } else if ( strcmp ( name, "AT Translated Set 2 keyboard" ) == 0) { // for vmware, my dev environment
437       fds [ 0 ] = fd;
438     } else if ( strcmp ( name, "triton2-pwrbutton" ) == 0) {
439       fds [ 2 ] = fd;
440     } else if ( strcmp ( name, "ADS784x Touchscreen" ) == 0) {
441       fds [ 3 ] = fd;
442     } else if ( strcmp ( name, "vsense66" ) == 0) {
443       fds [ 4 ] = fd;
444     } else if ( strcmp ( name, "vsense67" ) == 0) {
445       fds [ 5 ] = fd;
446     } else {
447       pnd_log ( pndn_rem, "Ignoring unknown device '%s'\n", name );
448       //fds [ 6 ] = fd;
449       close ( fd );
450       fd = -1;
451       continue;
452     }
453
454     if (imaxfd < fd) imaxfd = fd;
455
456   } // for
457
458   if ( fds [ 0 ] == -1 ) {
459     pnd_log ( pndn_warning, "WARNING! Couldn't find keypad device\n" );
460   }
461
462   if ( fds [ 1 ] == -1 ) {
463     pnd_log ( pndn_warning, "WARNING! couldn't find button device\n" );
464   }
465
466   if ( fds [ 0 ] == -1 && fds [ 1 ] == -1 ) {
467     pnd_log ( pndn_error, "ERROR! Couldn't find either device!\n" );
468     //exit ( -2 );
469   }
470
471   /* loop forever, watching for events
472    */
473
474   while ( 1 ) {
475     struct input_event ev[64];
476
477     unsigned int max_fd = 3; /* imaxfd */
478     int fd = -1, rd, ret;
479     fd_set fdset;
480
481     FD_ZERO ( &fdset );
482
483     imaxfd = 0;
484     for (i = 0; i < max_fd /*imaxfd*/; i++) {
485       if ( fds [ i ] != -1 ) {
486         FD_SET( fds [ i ], &fdset );
487
488         if ( fds [ i ] > imaxfd ) {
489           imaxfd = fds [ i ];
490         }
491
492       }
493     }
494
495     ret = select ( imaxfd + 1, &fdset, NULL, NULL, NULL );
496
497     if ( ret == -1 ) {
498       pnd_log ( pndn_error, "ERROR! select(2) failed with: %s\n", strerror ( errno ) );
499       continue; // retry!
500     }
501
502     for ( i = 0; i < max_fd; i++ ) {
503       if ( fds [ i ] != -1 && FD_ISSET ( fds [ i ], &fdset ) ) {
504         fd = fds [ i ];
505       } // fd is set?
506     } // for
507
508     /* buttons or keypad */
509     rd = read ( fd, ev, sizeof(struct input_event) * 64 );
510     if ( rd < (int) sizeof(struct input_event) ) {
511       pnd_log ( pndn_error, "ERROR! read(2) input_event failed with: %s\n", strerror ( errno ) );
512       break;
513     }
514
515     for (i = 0; i < rd / sizeof(struct input_event); i++ ) {
516
517       if ( ev[i].type == EV_SYN ) {
518         continue;
519       } else if ( ev[i].type == EV_KEY ) {
520
521         // do we even know about this key at all?
522         keycode_t *p = keycodes;
523         while ( p -> keycode != -1 ) {
524           if ( p -> keycode == ev [ i ].code ) {
525             break;
526           }
527           p++;
528         }
529
530         // if we do, hand it off to dispatcher to look up if we actually do something with it
531         if ( p -> keycode != -1 ) {
532           pnd_log ( pndn_debug, "Key Event: key %s [%d] value %d\n", p -> keyname, p -> keycode, ev [ i ].value );
533           dispatch_key ( p -> keycode, ev [ i ].value );
534         } else {
535           pnd_log ( pndn_warning, "Unknown Key Event: keycode %d value %d\n",  ev [ i ].code, ev [ i ].value );
536         }
537
538       } else if ( ev[i].type == EV_SW ) {
539
540         // do we even know about this event at all?
541         generic_event_t *p = generics;
542         while ( p -> code != -1 ) {
543           if ( p -> code == ev [ i ].code ) {
544             break;
545           }
546           p++;
547         }
548
549         // if we do, hand it off to dispatcher to look up if we actually do something with it
550         if ( p -> code != -1 ) {
551           pnd_log ( pndn_debug, "Generic Event: event %s [%d] value %d\n", p -> name, p -> code, ev [ i ].value );
552           dispatch_event ( p -> code, ev [ i ].value );
553         } else {
554           pnd_log ( pndn_warning, "Unknown Generic Event: code %d value %d\n",  ev [ i ].code, ev [ i ].value );
555         }
556
557       } else {
558         pnd_log ( pndn_debug, "DEBUG: Unexpected event type %i received\n", ev[i].type );
559         continue;
560       } // type?
561
562     } // for
563
564   } // while
565
566   for (i = 0; i < 2; i++) {
567     if ( i != 2 && fds [ i ] != -1 ) {
568       close (fds [ i ] );
569     }
570   }
571
572   return ( 0 );
573 } // main
574
575 // this should really register the keystate and time, and then another func to monitor
576 // time-passage and check the registered list and act on the event..
577 void dispatch_key ( int keycode, int val ) {
578   unsigned int i;
579
580   // val decodes as:
581   // 1 - down (pressed)
582   // 2 - down again (hold)
583   // 0 - up (released)
584
585   pnd_log ( pndn_rem, "Dispatching Key..\n" );
586
587   for ( i = 0; i < g_evmap_max; i++ ) {
588
589     if ( ( g_evmap [ i ].key_p ) &&
590          ( ((keycode_t*) (g_evmap [ i ].reqs)) -> keycode == keycode ) &&
591          ( g_evmap [ i ].script ) )
592     {
593
594       // is this a keydown or a keyup?
595       if ( val == 1 ) {
596         // keydown
597         g_evmap [ i ].keydown_time = time ( NULL );
598
599       } else if ( val == 0 ) {
600         // keyup
601
602         char holdtime [ 128 ];
603         sprintf ( holdtime, "%d", (int)( time(NULL) - g_evmap [ i ].keydown_time ) );
604
605         if ( time ( NULL ) - g_evmap [ i ].last_trigger_time >= g_minimum_separation ) {
606           int x;
607
608           g_evmap [ i ].last_trigger_time = time ( NULL );
609
610           pnd_log ( pndn_rem, "Will attempt to invoke: %s %s\n", g_evmap [ i ].script, holdtime );
611
612           if ( ( x = fork() ) < 0 ) {
613             pnd_log ( pndn_error, "ERROR: Couldn't fork()\n" );
614             exit ( -3 );
615           }
616
617           if ( x == 0 ) {
618             execl ( g_evmap [ i ].script, g_evmap [ i ].script, holdtime, (char*)NULL );
619             pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", g_evmap [ i ].script );
620             exit ( -4 );
621           }
622
623         } else {
624           pnd_log ( pndn_rem, "Skipping invokation.. falls within minimum_separation threshold\n" );
625         }
626
627       } // key up or down?
628
629       return;
630     } // found matching event for keycode
631
632   } // while
633
634   return;
635 }
636
637 void dispatch_event ( int code, int val ) {
638   unsigned int i;
639
640   // LID val decodes as:
641   // 1 - closing
642   // 0 - opening
643
644   pnd_log ( pndn_rem, "Dispatching Event..\n" );
645
646   for ( i = 0; i < g_evmap_max; i++ ) {
647
648     if ( ( g_evmap [ i ].key_p == 0 ) &&
649          ( ((generic_event_t*) (g_evmap [ i ].reqs)) -> code == code ) &&
650          ( g_evmap [ i ].script ) )
651     {
652
653       // just hand the code to the script (ie: 0 or 1 to script)
654       if ( time ( NULL ) - g_evmap [ i ].last_trigger_time >= g_minimum_separation ) {
655         int x;
656         char value [ 100 ];
657
658         sprintf ( value, "%d", val );
659
660         g_evmap [ i ].last_trigger_time = time ( NULL );
661
662         pnd_log ( pndn_rem, "Will attempt to invoke: %s %s\n", g_evmap [ i ].script, value );
663
664         if ( ( x = fork() ) < 0 ) {
665           pnd_log ( pndn_error, "ERROR: Couldn't fork()\n" );
666           exit ( -3 );
667         }
668
669         if ( x == 0 ) {
670           execl ( g_evmap [ i ].script, g_evmap [ i ].script, value, (char*)NULL );
671           pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", g_evmap [ i ].script );
672           exit ( -4 );
673         }
674
675       } else {
676         pnd_log ( pndn_rem, "Skipping invokation.. falls within minimum_separation threshold\n" );
677       }
678
679       return;
680     } // found matching event for keycode
681
682   } // while
683
684   return;
685 }
686
687 void sigchld_handler ( int n ) {
688
689   pnd_log ( pndn_rem, "---[ SIGCHLD received ]---\n" );
690
691   int status;
692   wait ( &status );
693
694   pnd_log ( pndn_rem, "     SIGCHLD done ]---\n" );
695
696   return;
697 }
698
699 unsigned char set_next_alarm ( unsigned int secs, unsigned int usecs ) {
700
701   // assume that SIGALRM is already being caught, we just set the itimer here
702
703   struct itimerval itv;
704
705   // if no timer at all, set the 'current' one so it does something; otherwise
706   // let it continue..
707   getitimer ( ITIMER_REAL, &itv );
708
709   if ( itv.it_value.tv_sec == 0 && itv.it_value.tv_sec == 0 ) {
710     itv.it_value.tv_sec = secs;
711     itv.it_value.tv_usec = usecs;
712   }
713
714   // set the next timer
715   //bzero ( &itv, sizeof(struct itimerval) );
716
717   itv.it_interval.tv_sec = secs;
718   itv.it_interval.tv_usec = usecs;
719
720   // if next-timer is less than current, set current too
721   if ( itv.it_value.tv_sec > itv.it_interval.tv_sec ) {
722     itv.it_value.tv_sec = secs;
723     itv.it_value.tv_usec = usecs;
724   }
725
726   if ( setitimer ( ITIMER_REAL, &itv, NULL /* old value returned here */ ) < 0 ) {
727     // sucks
728     return ( 0 );
729   }
730   
731   return ( 1 );
732 }
733
734 void sigalrm_handler ( int n ) {
735
736   pnd_log ( pndn_debug, "---[ SIGALRM ]---\n" );
737
738   int batlevel = pnd_device_get_battery_gauge_perc();
739
740   if ( batlevel < 0 ) {
741     // couldn't read the battery level, so just assume low and make blinks?
742     batlevel = 4; // low, but not cause a shutdown
743   }
744
745   // is battery warning already active?
746   if ( b_active ) {
747     // warning is on!
748
749     // is user charging up? if so, stop blinking.
750     // perhaps we shoudl check if charger is connected, and not blink at all in that case..
751     if ( batlevel > b_threshold + 1 /* allow for error in read */ ) {
752       pnd_log ( pndn_debug, "Battery is high again, flipping to non-blinker mode\n" );
753       b_active = 0;
754       set_next_alarm ( b_frequency, 0 );
755       pnd_device_set_led_power_brightness ( 250 );
756       return;
757     }
758
759     if ( b_active == 1 ) {
760       // turn LED on
761       pnd_log ( pndn_debug, "Blink on\n" );
762       pnd_device_set_led_power_brightness ( 200 );
763       // set timer to short duration
764       b_active = 2;
765       set_next_alarm ( 0, b_blinkdur );
766     } else if ( b_active == 2 ) {
767       // turn LED off
768       pnd_log ( pndn_debug, "Blink off\n" );
769       pnd_device_set_led_power_brightness ( 10 );
770       // back to longer duration
771       b_active = 1;
772       set_next_alarm ( b_blinkfreq, 0 );
773     }
774
775     return;
776   }
777
778   // warning is off..
779   if ( batlevel <= b_threshold ) {
780     // battery seems low, go to active mode
781     pnd_log ( pndn_debug, "Battery is low, flipping to blinker mode\n" );
782     b_active = 1;
783     set_next_alarm ( b_blinkfreq, 0 );
784   } // battery level
785
786   return;
787 }