From be702b1f9e9ee66517144d40141cca3b1139a7bd Mon Sep 17 00:00:00 2001 From: skeezix Date: Fri, 18 Dec 2009 22:27:35 -0500 Subject: [PATCH] Added a spin-delay at start of pndnotifyd so in theory it will wait until a user logs in; this possibly could solve the problem where sometimes pndnotifyd doesn't emit .desktops extended evmapperd to not spam-execute script (configurable minimum separation time between runs) extended evmapperd to pass the hold-time of key to script as first argument (argv[1], since argv[0] is job name) --- apps/pndevmapperd.c | 82 +++++++++++++++++++++------ apps/pndnotifyd.c | 13 +++++ deployment/etc/pandora/conf/eventmap | 1 + include/pnd_utility.h | 6 ++ lib/pnd_utility.c | 40 +++++++++++++ testdata/conf/eventmap | 15 +++++ testdata/menuicons/jeff.sample.3.png | Bin 0 -> 14866 bytes 7 files changed, 139 insertions(+), 18 deletions(-) create mode 100644 testdata/conf/eventmap create mode 100644 testdata/menuicons/jeff.sample.3.png diff --git a/apps/pndevmapperd.c b/apps/pndevmapperd.c index 7475903..90e6627 100644 --- a/apps/pndevmapperd.c +++ b/apps/pndevmapperd.c @@ -13,6 +13,7 @@ #include // for umask #include // for open(2) #include // for errno +#include // for time(2) #include // for keys //#include "../../kernel-rip/input.h" // for keys @@ -29,6 +30,7 @@ // daemon and logging // unsigned char g_daemon_mode = 0; +unsigned int g_minimum_separation = 1; typedef enum { pndn_debug = 0, @@ -41,10 +43,19 @@ typedef enum { // event-to-sh mapping // typedef struct { + + /* template information + */ unsigned char key_p; // 1 if its a key, otherwise an event int keycode; // scancode for the key in question char *script; // script to invoke //unsigned int hold_min; // minimum hold-time to trigger + + /* state + */ + time_t last_trigger_time; + time_t keydown_time; + } evmap_t; #define MAXEVENTS 255 @@ -164,7 +175,7 @@ int main ( int argc, char *argv[] ) { g_evmap [ g_evmap_max ].key_p = 1; g_evmap [ g_evmap_max ].keycode = p -> keycode; g_evmap [ g_evmap_max ].script = n; - pnd_log ( pndn_debug, "Registered key %s [%d] to script %s\n", p -> keyname, p -> keycode, (char*) n ); + pnd_log ( pndn_rem, "Registered key %s [%d] to script %s\n", p -> keyname, p -> keycode, (char*) n ); g_evmap_max++; } else { pnd_log ( pndn_warning, "WARNING! Key '%s' is not handled by pndevmapperd yet! Skipping.", k ); @@ -173,6 +184,9 @@ int main ( int argc, char *argv[] ) { } else if ( strncmp ( k, "events.", 7 ) == 0 ) { k += 7; + } else if ( strncmp ( k, "pndevmapperd.", 7 ) == 0 ) { + // not consumed here, skip silently + } else { // uhhh pnd_log ( pndn_warning, "Unknown config key '%s'; skipping.\n", k ); @@ -186,6 +200,11 @@ int main ( int argc, char *argv[] ) { pnd_log ( pndn_rem, "config file causes loglevel to change to %u", pnd_log_get_filter() ); } + if ( pnd_conf_get_as_int ( evmaph, "pndevmapperd.minimum_separation" ) != PND_CONF_BADNUM ) { + g_minimum_separation = pnd_conf_get_as_int ( evmaph, "pndevmapperd.minimum_separation" ); + pnd_log ( pndn_rem, "config file causes minimum_separation to change to %u", g_minimum_separation ); + } + /* do we have anything to do? */ if ( ! g_evmap_max ) { @@ -220,9 +239,12 @@ int main ( int argc, char *argv[] ) { if ( strcmp ( name, "omap_twl4030keypad" ) == 0 ) { fds [ 0 ] = fd; - } else if (strcmp(name, "gpio-keys") == 0) { + } else if ( strcmp ( name, "gpio-keys" ) == 0) { fds [ 1 ] = fd; + } else if ( strcmp ( name, "AT Translated Set 2 keyboard" ) == 0) { // for vmware, my dev environment + fds [ 0 ] = fd; } else { + pnd_log ( pndn_rem, "Ignoring unknown device '%s'\n", name ); close ( fd ); continue; } @@ -302,7 +324,7 @@ int main ( int argc, char *argv[] ) { } } else { - pnd_log ( pndn_warning, "WARNING: Unexpected event type %i received\n", ev[i].type ); + pnd_log ( pndn_debug, "DEBUG: Unexpected event type %i received\n", ev[i].type ); continue; } @@ -324,31 +346,55 @@ int main ( int argc, char *argv[] ) { void dispatch_key ( int keycode, int val ) { unsigned int i; - while ( i < g_evmap_max ) { + // val decodes as: + // 1 - down (pressed) + // 2 - down again (hold) + // 0 - up (released) + + for ( i = 0; i < g_evmap_max; i++ ) { if ( ( g_evmap [ i ].key_p ) && - ( g_evmap [ i ].keycode == keycode ) ) + ( g_evmap [ i ].keycode == keycode ) && + ( g_evmap [ i ].script ) ) { - if ( g_evmap [ i ].script ) { - int x; + // is this a keydown or a keyup? + if ( val == 1 ) { + // keydown + g_evmap [ i ].keydown_time = time ( NULL ); - if ( ( x = fork() ) < 0 ) { - pnd_log ( pndn_error, "ERROR: Couldn't fork()\n" ); - exit ( -3 ); - } + } else if ( val == 0 ) { + // keyup - if ( x == 0 ) { - pnd_log ( pndn_debug, "REM: Invoking %s\n", g_evmap [ i ].script ); - execl ( g_evmap [ i ].script, g_evmap [ i ].script, (char*)NULL ); - pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", g_evmap [ i ].script ); - exit ( -4 ); + char holdtime [ 128 ]; + sprintf ( holdtime, "%d", (int)( time(NULL) - g_evmap [ i ].keydown_time ) ); + + if ( time ( NULL ) - g_evmap [ i ].last_trigger_time >= g_minimum_separation ) { + int x; + + g_evmap [ i ].last_trigger_time = time ( NULL ); + + pnd_log ( pndn_rem, "Will attempt to invoke: %s %s\n", g_evmap [ i ].script, holdtime ); + + if ( ( x = fork() ) < 0 ) { + pnd_log ( pndn_error, "ERROR: Couldn't fork()\n" ); + exit ( -3 ); + } + + if ( x == 0 ) { + execl ( g_evmap [ i ].script, g_evmap [ i ].script, holdtime, (char*)NULL ); + pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", g_evmap [ i ].script ); + exit ( -4 ); + } + + } else { + pnd_log ( pndn_rem, "Skipping invokation.. falls within minimum_separation threshold\n" ); } - } + } // key up or down? return; - } + } // found matching event for keycode } // while diff --git a/apps/pndnotifyd.c b/apps/pndnotifyd.c index f41e842..66bd43d 100644 --- a/apps/pndnotifyd.c +++ b/apps/pndnotifyd.c @@ -151,6 +151,19 @@ int main ( int argc, char *argv[] ) { } // set up daemon + // wait for a user to be logged in - we should probably get hupped when a user logs in, so we can handle + // log-out and back in again, with SDs popping in and out between.. + pnd_log ( pndn_rem, "Checking to see if a user is logged in\n" ); + char tmp_username [ 128 ]; + while ( 1 ) { + if ( pnd_check_login ( tmp_username, 127 ) ) { + break; + } + pnd_log ( pndn_debug, " No one logged in yet .. spinning.\n" ); + sleep ( 2 ); + } // spin + pnd_log ( pndn_rem, "Looks like user '%s' is in, continue.\n", tmp_username ); + /* parse configs */ diff --git a/deployment/etc/pandora/conf/eventmap b/deployment/etc/pandora/conf/eventmap index 83a8fee..5908728 100644 --- a/deployment/etc/pandora/conf/eventmap +++ b/deployment/etc/pandora/conf/eventmap @@ -12,3 +12,4 @@ lid-open foo [pndevmapperd] # logging level 0 means to include debug; level 1 (regular), 2 (warnings), 3 (errors) loglevel 1 +minimum_separation 1 # 1 second minimum between a single event repeating diff --git a/include/pnd_utility.h b/include/pnd_utility.h index a9638ce..62af5af 100644 --- a/include/pnd_utility.h +++ b/include/pnd_utility.h @@ -6,6 +6,12 @@ extern "C" { #endif +// expand_tilde() will only function correctly if a user is actually logged in; perhaps you +// want to spin until it looks like someone has in fact done so. (most devices will likely +// have auto-login, but its not instantaneous!) +// r_username is optional; if present, will receive a copy of username +unsigned char pnd_check_login ( char *r_username, unsigned int maxlen ); + // given a malloc'd pointer to a string, expand ~ to $HOME as often as it is found, returning a // new string; the old string is destroyed in the process, or returned as-is. char *pnd_expand_tilde ( char *freeable_buffer ); diff --git a/lib/pnd_utility.c b/lib/pnd_utility.c index b7e146f..b9cf22d 100644 --- a/lib/pnd_utility.c +++ b/lib/pnd_utility.c @@ -15,6 +15,46 @@ #include "pnd_pndfiles.h" #include "pnd_discovery.h" +unsigned char pnd_check_login ( char *r_username, unsigned int maxlen ) { + FILE *f; + struct utmp b; + struct passwd *pw; + + f = fopen ( "/var/run/utmp", "r" ); + + if ( f ) { + + while ( fread ( &b, sizeof(struct utmp), 1, f ) == 1 ) { + + if ( b.ut_type == USER_PROCESS ) { + + // ut_user contains the username .. + // now we need to find the path to that account. + while ( ( pw = getpwent() ) ) { + + if ( strcmp ( pw -> pw_name, b.ut_user ) == 0 ) { + + if ( r_username ) { + strncpy ( r_username, b.ut_user, maxlen ); + } + + fclose ( f ); + return ( 1 ); + } // passwd entry matches the utmp entry + + } // while iteratin across passwd entries + endpwent(); + + } // utmp entry is for a user login + + } // while + + fclose ( f ); + } // opened? + + return ( 0 ); +} + // a generalized variable-substitution routine might be nice; for now we need a quick tilde one, // so here goes. Brute force ftw! char *pnd_expand_tilde ( char *freeable_buffer ) { diff --git a/testdata/conf/eventmap b/testdata/conf/eventmap new file mode 100644 index 0000000..ed1aaac --- /dev/null +++ b/testdata/conf/eventmap @@ -0,0 +1,15 @@ + +# Open Pandora +# Event-to-shscript map configuration + +[keys] +a /usr/pandora/scripts/op_menu + +[events] +lid-close bar +lid-open foo + +[pndevmapperd] +# logging level 0 means to include debug; level 1 (regular), 2 (warnings), 3 (errors) +loglevel 1 +minimum_separation 1 # 1 second minimum between a single event repeating diff --git a/testdata/menuicons/jeff.sample.3.png b/testdata/menuicons/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(%�`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^