diff --git a/clients/Makefile.am b/clients/Makefile.am index 8e72751..efe25c2 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -1 +1 @@ -SUBDIRS = examples headlines lcdproc metar +SUBDIRS = examples headlines lcdexec lcdproc metar diff --git a/clients/lcdexec/Makefile.am b/clients/lcdexec/Makefile.am new file mode 100644 index 0000000..4dfac58 --- /dev/null +++ b/clients/lcdexec/Makefile.am @@ -0,0 +1,4 @@ +bin_PROGRAMS = lcdexec +lcdexec_SOURCES = lcdexec.c menu.c menu.h +lcdexec_LDADD = ../../shared/libLCDstuff.a +INCLUDES = -I$(top_srcdir) diff --git a/clients/lcdexec/lcdexec.c b/clients/lcdexec/lcdexec.c new file mode 100644 index 0000000..36767b7 --- /dev/null +++ b/clients/lcdexec/lcdexec.c @@ -0,0 +1,363 @@ +/* + * lcdexec.c + * This file is part of lcdexec, an LCDproc client. + * + * This file is released under the GNU General Public License. Refer to the + * COPYING file distributed with this package. + * + * Copyright (c) 2002, Joris Robijn + * + */ + +/* + * How to use submenus in the config file: + * [lcdexec] + * MenuCommand="Shut down,shutdown -h now" + * SubMenu="Internet,inet" + * # The inet submenu is filled as follows: + * inet_MenuCommand="open,inet open" + * inet_MenuCommand="close,inet close" + * + */ + +#include +#include +#include + +#include "shared/str.h" +#include "shared/report.h" +#include "shared/configfile.h" +#include "shared/sockets.h" + +#include "menu.h" + +#define DEFAULT_CONFIGFILE "/etc/LCDclients.conf" + +char * help_text = +"lcdexec - LCDproc client to execute commands from the LCDd menu.\n" +"Copyright (c) 2002, Joris Robijn\n" +"This file is released under the GNU General Public License. Refer to the\n" +"COPYING file distributed with this package.\n" +"Options:\n" +" -c \tSpecify configuration file ["DEFAULT_CONFIGFILE"]\n" +" -a
\tDNS name or IP address of the LCDd server [localhost]\n" +" -p \tport of the LCDd server [13666]\n" +" -f <0|1>\tRun in foreground (1) or background (0, default)\n" +" -r \tSet reporting level (0-5) [2: errors and warnings]\n" +" -s <0|1>\tReport to syslog (1) or stderr (0, default)\n" +" -h\t\tShow this help\n"; + +char * progname = "lcdexec"; + +extern char *optarg; +extern int optind, optopt, opterr; + +/* Variables set by config */ +#define UNSET_INT -1 +#define UNSET_STR "\01" +char * configfile = UNSET_STR; +char * address = UNSET_STR; +int port = UNSET_INT; +int foreground_mode = UNSET_INT; +char * appname = UNSET_STR; +static int report_level = UNSET_INT; +static int report_dest = UNSET_INT; + +Menu * main_menu; + +/* Other variables */ +int sock; + +/* Function prototypes */ +int process_command_line( int argc, char **argv ); +int process_configfile( char * configfile ); +int split( char * str, char delim, char * parts[], int maxparts ); +int connect_and_setup (); +int process_response( char * str ); +int exec_command( char * command ); +int main_loop (); + +#define CHAIN(e,f) { if( e>=0 ) { e=(f); }} +#define CHAIN_END(e) { if( e<0 ) { report( RPT_CRIT,"Critical error, abort"); exit(e); }} + +int main( int argc, char **argv ) +{ + int error = 0; + + CHAIN( error, process_command_line( argc, argv )); + if( configfile == NULL ) + configfile = DEFAULT_CONFIGFILE; + CHAIN( error, process_configfile( configfile )); + + if( report_dest == UNSET_INT || report_level == UNSET_INT ) { + report_dest = RPT_DEST_STDERR; + report_level = RPT_ERR; + } + set_reporting( progname, report_level, report_dest ); + CHAIN_END( error ); + + CHAIN( error, connect_and_setup() ); + CHAIN_END( error ); + + if(!foreground_mode) { + if (daemon(1,1)!=0) { + report(RPT_ERR, "Error: daemonize failed"); + } + } + + main_loop(); + + return 0; +} + +int process_command_line( int argc, char **argv ) +{ + char c; + char * p; + int temp_int; + int error = 0; + + /* No error output from getopt */ + opterr = 0; + + while(( c = getopt( argc, argv, "c:O:a:p:f:r:s:h" )) > 0) { + switch( c ) { + case 'c': + configfile = strdup( optarg ); + break; + case 'O': + config_read_string( progname, optarg ); + break; + case 'a': + address = strdup( optarg ); + break; + case 'p': + temp_int = strtol( optarg, &p, 0 ); + if( *optarg != 0 && *p == 0 ) { + port = temp_int; + } else { + report( RPT_ERR, "Could not interpret value for -%c", c ); + error = -1; + } + break; + case 'f': + temp_int = strtol( optarg, &p, 0 ); + if( *optarg != 0 && *p == 0 ) { + foreground_mode = temp_int; + } else { + report( RPT_ERR, "Could not interpret value for -%c", c ); + error = -1; + } + break; + case 'r': + temp_int = strtol( optarg, &p, 0 ); + if( *optarg != 0 && *p == 0 ) { + report_level = temp_int; + } else { + report( RPT_ERR, "Could not interpret value for -%c", c ); + error = -1; + } + break; + break; + case 's': + temp_int = strtol( optarg, &p, 0 ); + if( *optarg != 0 && *p == 0 ) { + report_dest = (temp_int?RPT_DEST_SYSLOG:RPT_DEST_STDERR); + } else { + report( RPT_ERR, "Could not interpret value for -%c", c ); + error = -1; + } + break; + case 'h': + fprintf( stderr, "%s", help_text ); + exit( 0 ); + case ':': + report( RPT_ERR, "Missing option argument for %c", optopt ); + error = -1; + break; + case '?': + report( RPT_ERR, "Unknown option: %c", optopt ); + error = -1; + break; + } + } + return error; +} + +int process_configfile( char * configfile ) +{ + if( strcmp( configfile, UNSET_STR ) == 0 ) { + configfile = DEFAULT_CONFIGFILE; + } + if( config_read_file( configfile ) < 0 ) { + report( RPT_WARNING, "Could not read config file: %s", configfile ); + } + + if( strcmp( address, UNSET_STR ) == 0 ) { + address = strdup( config_get_string( progname, "Address", 0, "localhost" )); + } + if( port == UNSET_INT ) { + port = config_get_int( progname, "Port", 0, 13666 ); + } + if( report_level == UNSET_INT ) { + report_level = config_get_int( progname, "ReportLevel", 0, RPT_WARNING ); + } + if( report_dest == UNSET_INT ) { + if( config_get_bool( progname, "ReportToSyslog", 0, 0 )) { + report_dest = RPT_DEST_SYSLOG; + } else { + report_dest = RPT_DEST_STDERR; + } + } + if( foreground_mode == UNSET_INT ) { + foreground_mode = config_get_bool( progname, "Foreground", 0, 1 ); + } + + main_menu = menu_read( "", progname ); + + if( main_menu->num_menucmds == 0 && main_menu->num_submenus == 0 ) { + main_menu->menucmd_name[0] = "echo test"; + main_menu->menucmd_exec[0] = "echo \"This is a test using the echo command.\""; + main_menu->num_menucmds ++; + } + return 0; +} + +int connect_and_setup () +{ + char buf[200]; + + report( RPT_INFO, "Connecting to %s:%d", address, port ); + + sock = sock_connect( address, port ); + if( sock <= 0 ) { + return -1; + } + + /* Create our menu */ + sock_send_string( sock, "hello\n" ); + snprintf( buf, sizeof(buf)-1, "client_set -name \"%s\"\n", progname ); + sock_send_string( sock, buf ); + + if( menu_send_to_LCDd( main_menu, "", sock ) < 0 ) { + return -1; + } + + return 0; +} + +int process_response( char * str ) +{ + char *argv[10]; + int argc; + char * str2 = strdup( str ); /* get_args modifies str2 */ + + report( RPT_DEBUG, "Server said: \"%s\"", str ); + + /* Check what the server just said to us... */ + argc = get_args( argv, str2, 10 ); + if( argc < 1 ) { + free( str2 ); + return 0; + } + + if( strcmp( argv[0], "menuevent" ) == 0 ) { + /* Ah, this is what we were waiting for ! */ + + if( argc < 2 ) { + report( RPT_WARNING, "Server gave invalid response" ); + free( str2 ); + return -1; + } + if( strcmp( argv[1], "select" ) == 0 ) { + char * exec; + if( argc < 3 ) { + report( RPT_WARNING, "Server gave invalid response" ); + free( str2 ); + return -1; + } + + /* Find the id */ + exec = menu_find_cmd_of_id( main_menu, argv[2] ); + if( !exec ) { + report( RPT_WARNING, "Could not find the item id given by the server" ); + free( str2 ); + return -1; + } + /* The id has been found */ + exec_command( exec ); + } + else { + ; /* Ignore other menuevents */ + } + } + else if( strcmp( argv[0], "huh?" ) == 0 ) { + /* Report errors */ + report( RPT_WARNING, "Server said: \"%s\"", str ); + } + else { + ; /* Ignore all other responses */ + } + free( str2 ); + return 0; +} + +int exec_command( char * command ) +{ + char *argv[4]; + + report( RPT_NOTICE, "Executing: %s", command ); + + argv[0] = getenv( "SHELL" ); + argv[1] = "-c"; + argv[2] = command; + argv[3] = NULL; + + if( !argv[0] ) { + report( RPT_ERR, "SHELL environment variable not set." ); + return -1; + } + + switch( fork() ) { + case 0: + /* We're the child. Execute the command. */ + execv( argv[0], argv ); + exit(0); + break; + case -1: + report( RPT_ERR, "Could not fork" ); + return -1; + default: + /* We're the parent */ + } + return 0; +} + +int main_loop () +{ + int num_bytes; + char buf[80]; + int w = 0; + + /* Continuously check if we get a menu event... */ + + while(( num_bytes = sock_recv_string( sock, buf, sizeof(buf)-1)) >= 0 ) { + if( num_bytes == 0 ) { + usleep( 100000 ); + + /* Send an empty line every 3 seconds to make sure the server still exists */ + if( w++ >= 30 ) { + w = 0; + if( sock_send_string( sock, "\n" ) < 0 ) { + break; /* Out of while loop */ + } + } + } + else { + process_response (buf); + } + } + + report( RPT_ERR, "Server disconnected (or connection error)" ); + return 0; +} diff --git a/clients/lcdexec/menu.c b/clients/lcdexec/menu.c new file mode 100644 index 0000000..f8aa27a --- /dev/null +++ b/clients/lcdexec/menu.c @@ -0,0 +1,220 @@ +/* + * lcdexec.c + * This file is part of lcdexec, an LCDproc client. + * + * This file is released under the GNU General Public License. Refer to the + * COPYING file distributed with this package. + * + * Copyright (c) 2002, Joris Robijn + * + */ + +#include +#include +#include + +#include "shared/str.h" +#include "shared/report.h" +#include "shared/configfile.h" +#include "shared/sockets.h" + +#include "menu.h" + +Menu * menu_find_submenu( Menu * menu, char * submenu_id ); + +Menu * menu_read( char * menu_id, char * progname ) +{ + char buf[100]; + char * str; + char * parts[3]; + int i; + + Menu * menu; + + report( RPT_DEBUG, "Reading menu: [%s]", menu_id ); + + menu = malloc( sizeof(Menu) ); + + /* Read the commands */ + str = ""; + while( str && menu->num_menucmds < MAX_NUM_MENUCMDS ) { + + snprintf( buf, sizeof(buf)-1, "%s%sMenuCommand", + menu_id, menu_id[0]?"_":"" ); + buf[sizeof(buf)-1] = 0; + str = config_get_string( progname, buf, menu->num_menucmds, NULL ); + if( !str ) { + ; /* No more menucommands */ + } else { + char * str2 = strdup( str ); + if( split( str2, ',', parts, 2 ) == 2 ) { + menu->menucmd_name[menu->num_menucmds] = parts[0]; + while( parts[1][0] == ' ' ) parts[1]++; /* Skip spaces */ + menu->menucmd_exec[menu->num_menucmds] = parts[1]; + report( RPT_DEBUG, "Found command: [%s] [%s]", parts[0], parts[1] ); + } else { + report( RPT_ERR, "Cannot read MenuCommand: \"%s\"", str ); + return NULL; + } + menu->num_menucmds ++; + } + } + + /* Read the submenus */ + str = ""; + while( str && menu->num_submenus < MAX_NUM_SUBMENUS ) { + + snprintf( buf, sizeof(buf)-1, "%s%sSubmenu", + menu_id, menu_id[0]?"_":"" ); + buf[sizeof(buf)-1] = 0; + str = config_get_string( progname, buf, menu->num_submenus, NULL ); + if( !str ) { + ; /* No more submenus */ + } else { + char * str2 = strdup( str ); + if( split( str2, ',', parts, 2 ) == 2 ) { + menu->submenu_name[menu->num_submenus] = parts[0]; + while( parts[1][0] == ' ' ) parts[1]++; /* Skip spaces */ + menu->submenu_id[menu->num_submenus] = parts[1]; + report( RPT_DEBUG, "Found submenu: [%s] [%s]", parts[0], parts[1] ); + } else { + report( RPT_ERR, "Cannot read Submenu: \"%s\"", str ); + return NULL; + } + menu->num_submenus ++; + } + } + + /* And do the same for all submenus */ + for( i = 0; i < menu->num_submenus; i++ ) { + menu->submenu[i] = menu_read (menu->submenu_id[i], progname ); + } + + return menu; +} + +int menu_send_to_LCDd( Menu * menu, char * id, int sock ) +{ + int i; + char buf[100]; + + for( i = 0; i < menu->num_menucmds; i++ ) { + snprintf( buf, sizeof(buf)-1, + "menu_add_item \"%s\" \"%s%s%d\" action \"%s\"\n", + id, id, id[0]?"_":"", i, menu->menucmd_name[i] ); + buf[sizeof(buf)-1] = 0; + if( sock_send_string( sock, buf ) < 0 ) + return -1; + snprintf( buf, sizeof(buf)-1, + "menu_set_item \"%s\" \"%s%s%d\" -menu_result quit\n", + id, id, id[0]?"_":"", i ); + buf[sizeof(buf)-1] = 0; + if( sock_send_string( sock, buf ) < 0 ) + return -1; + } + for( i = 0; i < menu->num_submenus; i++ ) { + snprintf( buf, sizeof(buf)-1, + "menu_add_item \"%s\" \"%s\" menu \"%s\"\n", + id, menu->submenu_id[i], menu->submenu_name[i] ); + buf[sizeof(buf)-1] = 0; + if( sock_send_string( sock, buf ) < 0 ) + return -1; + } + + /* And do the same for all submenus */ + for( i = 0; i < menu->num_submenus; i++ ) { + if( menu_send_to_LCDd( menu->submenu[i], menu->submenu_id[i], sock ) < 0 ) + return -1; + } + return 0; +} + +char * menu_find_cmd_of_id( Menu * menu, char * id ) +{ + char * p; + char * submenu_id; + char * item_id; + int i; + + /* Find the submenu_id part of the id */ + submenu_id = strdup( id ); + p = strrchr( submenu_id, '_' ); + + /* Do we need to search the submenus ? */ + if( p ) { + /* There is a menu id prepended to the id */ + *p = 0; /* Crop the submenu_id string */ + item_id = p + 1; + menu = menu_find_submenu( menu, submenu_id ); + if( !menu ) { + report( RPT_WARNING, "Server reported an unknown id: %s", id ); + free( submenu_id ); + return NULL; + } + } + else { + item_id = id; + } + + /* Determine the number at the end of the id */ + i = strtol( item_id, &p, 10 ); + if( *item_id != 0 && *p == 0 + && i >= 0 && i < menu->num_menucmds ) { + /* OK */ + free( submenu_id ); + return menu->menucmd_exec[i]; + } else { + report( RPT_WARNING, "Server reported an unknown id: %s", id ); + free( submenu_id ); + return NULL; + } + +} + +Menu * menu_find_submenu( Menu * menu, char * submenu_id ) +{ + int i; + Menu * found; + + /* Search for the submenu in this menu and all submenus */ + for( i = 0; i < menu->num_submenus; i++ ) { + if( strcmp( menu->submenu_id[i], submenu_id ) == 0 ) + return menu->submenu[i]; + found = menu_find_submenu( menu->submenu[i], submenu_id ); + if( found ) + return found; + } + + return NULL; +} + +int split( char * str, char delim, char * parts[], int maxparts ) +/* Splits a string into parts, to which pointers will be returned in &parts. + * The return value is the number of parts. + * maxparts is the maximum number of parts returned. If more parts exist + * they are (unsplit) in the last part. + * The parts are split at the character delim. + * No new space will be allocated, the string str will be mutated ! + */ +{ + char * p1 = str; + char * p2; + int part_nr = 0; + + /* Find the delim char to end the current part */ + while( part_nr < maxparts - 1 && (p2 = strchr( p1, delim )) ) { + + /* subsequent parts... */ + *p2 = 0; + parts[part_nr] = p1; + + p1 = p2 + 1; /* Just after the delim char */ + part_nr ++; + } + /* and the last part... */ + parts[part_nr] = p1; + part_nr ++; + + return part_nr; +} + diff --git a/clients/lcdexec/menu.h b/clients/lcdexec/menu.h new file mode 100644 index 0000000..211faf6 --- /dev/null +++ b/clients/lcdexec/menu.h @@ -0,0 +1,38 @@ +/* + * lcdexec.c + * This file is part of lcdexec, an LCDproc client. + * + * This file is released under the GNU General Public License. Refer to the + * COPYING file distributed with this package. + * + * Copyright (c) 2002, Joris Robijn + * + */ + +#include +#include +#include + +#include "shared/str.h" +#include "shared/report.h" +#include "shared/configfile.h" +#include "shared/sockets.h" + +#define MAX_NUM_MENUCMDS 40 +#define MAX_NUM_SUBMENUS 20 + +typedef struct Menu { + char * menucmd_name[MAX_NUM_MENUCMDS]; + char * menucmd_exec[MAX_NUM_MENUCMDS]; + int num_menucmds; + char * submenu_name[MAX_NUM_SUBMENUS]; + char * submenu_id[MAX_NUM_SUBMENUS]; + struct Menu * submenu[MAX_NUM_SUBMENUS]; + int num_submenus; +} Menu; + +Menu * menu_read (char * menu_id, char * progname); +int menu_send_to_LCDd( Menu * menu, char * id, int sock ); +char * menu_find_cmd_of_id( Menu * menu, char * id ); + +int split( char * str, char delim, char * parts[], int maxparts ); diff --git a/configure.in b/configure.in index 52a19cb..c7ca7d8 100644 --- a/configure.in +++ b/configure.in @@ -189,6 +189,7 @@ AC_OUTPUT(Makefile server/drivers/Makefile clients/Makefile clients/lcdproc/Makefile + clients/lcdexec/Makefile clients/examples/Makefile clients/headlines/Makefile clients/metar/Makefile