diff --git a/clients/Makefile.am b/clients/Makefile.am index 9a27627..d79dbae 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -1,5 +1,5 @@ ## Process this file with automake to produce Makefile.in -SUBDIRS = examples headlines lcdexec lcdproc metar +SUBDIRS = examples headlines lcdexec lcdproc lcdvc metar ## EOF diff --git a/clients/lcdvc/.cvsignore b/clients/lcdvc/.cvsignore new file mode 100644 index 0000000..4ba0d9d --- /dev/null +++ b/clients/lcdvc/.cvsignore @@ -0,0 +1,4 @@ +.deps +Makefile +Makefile.in +lcdvc diff --git a/clients/lcdvc/Makefile.am b/clients/lcdvc/Makefile.am new file mode 100644 index 0000000..88afed2 --- /dev/null +++ b/clients/lcdvc/Makefile.am @@ -0,0 +1,23 @@ +## Process this file with automake to produce Makefile.in + +sysconf_DATA = lcdvc.conf + +bin_PROGRAMS = lcdvc + +lcdvc_SOURCES = lcdvc.c lcdvc.h lcd_link.c lcd_link.h vc_link.c vc_link.h + +lcdvc_LDADD = @ldap_libs@ ../../shared/libLCDstuff.a + +if DARWIN +AM_LDFLAGS = -framework CoreFoundation -framework IOKit +endif + +AM_CPPFLAGS = -I$(top_srcdir) -DSYSCONFDIR=\"$(sysconfdir)\" + +#AM_CFLAGS = -g -O0 +#AM_LDFLAGS = -g + + +EXTRA_DIST = $(sysconf_DATA) + +## EOF diff --git a/clients/lcdvc/lcd_link.c b/clients/lcdvc/lcd_link.c new file mode 100644 index 0000000..c6bbec7 --- /dev/null +++ b/clients/lcdvc/lcd_link.c @@ -0,0 +1,316 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "lcd_link.h" +#include "vc_link.h" +#include "lcdvc.h" +#include "shared/report.h" +#include "shared/str.h" +#include "shared/sockets.h" + +char * address = UNSET_STR; +int port = UNSET_INT; +short autoscroll = 1; + +int sock; +short listening = 0; +short scroll_x = 0, scroll_y = 0; +short lcd_cursor_x, lcd_cursor_y; +short lcd_width = 0, lcd_height = 0; +char * lcd_buf = NULL; + +short last_vc_cursor_y = 0; +short last_vc_cursor_x = 0; +short last_lcd_cursor_x = 0; +short last_lcd_cursor_y = 0; + +int read_connect_string(); + +int connect_and_setup() +{ + char buf[200]; + int i; + int e = 0; + short line; + + report(RPT_INFO, "Connecting to %s:%d", address, port); + + sock = sock_connect(address, port); + if (sock <= 0) { + report(RPT_ERR, "Connecting to %s:%d failed", address, port); + return -1; + } + /* Create our menu */ + sock_send_string(sock, "hello\n"); + if (read_connect_string() < 0) { + return -1; + } + + snprintf(buf, sizeof(buf)-1, "client_set -name \"%s\"\n", progname); + sock_send_string(sock, buf); + + /* Create screen */ + CHAIN(e, sock_send_string(sock, "screen_add console\n")); + for (line = 0; line < lcd_height; line++) { + snprintf(buf, sizeof(buf)-1, "widget_add console line%d string\n", line); + buf[sizeof(buf)-1] = 0; + CHAIN(e, sock_send_string(sock, buf)); + } + /* Add menu items */ + CHAIN(e, sock_send_string(sock, "menu_add_item \"\" autoscroll checkbox \"Auto scroll\"\n")); + CHAIN(e, sock_send_string(sock, "menu_set_item \"\" autoscroll -value on\n")); + + /* Reserve keys */ + + for (i = 0; i < 4; i++) { + snprintf(buf, sizeof(buf)-1, "client_add_key \"%s\"\n", keys[i]); + CHAIN(e, sock_send_string(sock, buf)); + } + + if (e < 0) { + report(RPT_ERR, "Could not send to to LCDd"); + return -1; + } + + return 0; +} + +int read_connect_string() +{ + char buf[8192]; + int len = 0; + int a; + char * argv[20]; + int argc; + short received = 0; + short timeout = 50; /* Give the server 5 secs to respond */ + + buf[0] = '\0'; + + while (!received && timeout > 0) { + len = sock_recv_string(sock, buf, sizeof(buf)); + if (len == 0) { + usleep(100000); + timeout --; + } else { + received = 1; + } + } + report(RPT_INFO, "Received: %s", buf); + + if (len <= 0) { + report(RPT_ERR, "Did not receive LCDd connect response."); + return -1; + } + + argc = split(buf, ' ', argv, 20); + for (a = 1; a < argc; a++) { + if (0 == strcmp (argv[a], "wid")) + lcd_width = atoi (argv[++a]); + else if (0 == strcmp (argv[a], "hgt")) + lcd_height = atoi (argv[++a]); + } + if (argc <= 0 || strcmp(argv[0], "connect") != 0 + || lcd_width == 0 || lcd_width == 0) { + report(RPT_ERR, "Received invalid LCDd connect response."); + return -1; + } +} + +int read_response(char * buf, int maxsize) +{ + return sock_recv_string(sock, buf, maxsize); +} + +int process_response(char * str) +{ + char *argv[10]; + int argc; + //int i; + //char * p; + 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], "listen") == 0) { + listening = 1; + } + else if (strcmp(argv[0], "ignore") == 0) { + listening = 0; + } + else 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; + } + else if (strcmp(argv[1], "update") == 0) { + if (argc < 4) { + report(RPT_WARNING, "Server gave invalid response"); + free(str2); + return -1; + } + if (strcmp(argv[2], "autoscroll") == 0) { + if (strcmp(argv[3], "on") == 0) { + autoscroll = 1; + } else if (strcmp(argv[3], "off") == 0) { + autoscroll = 0; + } + report(RPT_INFO, "Autoscroll set to %d", autoscroll); + } + } + else { + ; /* Ignore other menuevents */ + } + } + else if (strcmp(argv[0], "key") == 0) { + if (argc < 2) { + report(RPT_WARNING, "Server gave invalid response"); + free(str2); + return -1; + } + if (strcmp(argv[1], keys[0]) == 0) { + if (scroll_y > 0) scroll_y --; + } else if (strcmp(argv[1], keys[1]) == 0) { + if (scroll_y + lcd_height < vc_height) scroll_y ++; + } else if (strcmp(argv[1], keys[2]) == 0) { + if (scroll_x > 0) scroll_x --; + } else if (strcmp(argv[1], keys[3]) == 0) { + if (scroll_x + lcd_width < vc_width) scroll_x ++; + } + } + 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 update_display() +{ + //int bytes_read; + short line; + int e = 0; + char buf[80]; + char * str_buf; + short num_lines; + num_lines = min(lcd_height, vc_height); + + if (!listening) + return 0; + + if (!lcd_buf) { + /* Not yet allocated */ + lcd_buf = malloc(lcd_width * lcd_height); + memset(lcd_buf, ' ', lcd_width * lcd_height); + } + + if (autoscroll + && (last_vc_cursor_x != vc_cursor_x || last_vc_cursor_y != vc_cursor_y)) { + last_vc_cursor_x = vc_cursor_x; + last_vc_cursor_y = vc_cursor_y; + + /* Adjust scroll position */ + if (scroll_x > vc_cursor_x) + scroll_x = vc_cursor_x; + if (scroll_x < vc_cursor_x - lcd_width + 1) + scroll_x = vc_cursor_x - lcd_width + 1; + if (scroll_y > vc_cursor_y) + scroll_y = vc_cursor_y; + if (scroll_y < vc_cursor_y - lcd_height + 1) + scroll_y = vc_cursor_y - lcd_height + 1; + } + lcd_cursor_x = vc_cursor_x - scroll_x + 1; + lcd_cursor_y = vc_cursor_y - scroll_y + 1; + if (lcd_cursor_x != last_lcd_cursor_x || lcd_cursor_y != last_lcd_cursor_y) { + last_lcd_cursor_x = lcd_cursor_x; + last_lcd_cursor_y = lcd_cursor_y; + + /* New scroll positions, send the cursor command */ + if (lcd_cursor_x < 1 || lcd_cursor_x > lcd_width + || lcd_cursor_y < 1 || lcd_cursor_y > lcd_height) { + snprintf(buf, sizeof(buf)-1, "screen_set console -cursor off\n"); + } else { + snprintf(buf, sizeof(buf)-1, "screen_set console -cursor on -cursor_x %d -cursor_y %d\n", + lcd_cursor_x, lcd_cursor_y); + } + buf[sizeof(buf)-1] = 0; + CHAIN(e, sock_send_string(sock, buf)); + } + + /* Send all (changed) lines */ + str_buf = malloc(80 + 2 * lcd_width); + for (line = 0; line < num_lines; line++) { + + char * vc_p; + char * lcd_p; + short line_width; + + line_width = min(lcd_width, vc_width); + + /* Where does the data for this line come from ? */ + vc_p = vc_buf + vc_width * (scroll_y + line) + scroll_x; + lcd_p = lcd_buf + lcd_width * line; + + /* Has the line data changed ? */ + if (memcmp(vc_p, lcd_p, line_width) != 0) { + /* Yes, so send it */ + char * a; + short pos; + + /* Format/escape the data */ + snprintf(str_buf, 80, "widget_set console line%d 1 %d \"", line, line+1); + + a = str_buf + strlen(str_buf); /* start just after the " */ + for (pos = 0; pos < line_width; pos++) { + if (vc_p[pos] == '\\' || vc_p[pos] == '\"') { + *a++ = '\\'; /* add escape char */ + } + if ((signed char) vc_p[pos] >= 32) { + *a++ = vc_p[pos]; /* add char */ + } else { + *a++ = '?'; + } + } + *a++ = '\"'; /* end string */ + *a++ = '\n'; /* newline */ + *a = 0; /* terminate */ + CHAIN(e, sock_send_string(sock, str_buf)); + + /* And store the new data */ + memcpy(lcd_p, vc_p, line_width); + + } + } + free(str_buf); + + if (e < 0) { + report(RPT_ERR, "Error while sending data to LCDd"); + return -1; + } + + return 0; +} + +int send_nop() +{ + return sock_send_string(sock, "\n"); +} diff --git a/clients/lcdvc/lcd_link.h b/clients/lcdvc/lcd_link.h new file mode 100644 index 0000000..25cfc58 --- /dev/null +++ b/clients/lcdvc/lcd_link.h @@ -0,0 +1,13 @@ +#ifndef LCD_LINK_H +#define LCD_LINK_H + +extern char * address; +extern int port; + +int connect_and_setup(); +int read_response( char * str, int maxsize ); +int process_response( char * str ); +int update_display(); +int send_nop(); + +#endif diff --git a/clients/lcdvc/lcdvc.c b/clients/lcdvc/lcdvc.c new file mode 100644 index 0000000..79b9509 --- /dev/null +++ b/clients/lcdvc/lcdvc.c @@ -0,0 +1,264 @@ +/* + * lcdvc.c + * This file is part of lcdvc, 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 +#include +#include +#include +extern char *optarg; +extern int optind, optopt, opterr; + +#include "shared/str.h" +#include "shared/report.h" +#include "shared/configfile.h" +#include "shared/sockets.h" +#include "lcd_link.h" +#include "vc_link.h" +#include "lcdvc.h" + +#define DEFAULT_CONFIGFILE SYSCONFDIR "/lcdvc.conf" + +char * help_text = +"lcdvc - LCDproc virtual console displayer.\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 a configfile to load ["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 1=foreground or 0=background\n" +" -r \tSet reporting level (0-5) [2: errors and warnings]\n" +" -s <0|1>\tReport to 1=syslog or 0=stderr\n" +" -h\t\tShow this help\n"; + +char *progname = "lcdvc"; +char *configfile = UNSET_STR; + +/* Variables set by config */ +int foreground_mode = UNSET_INT; +static int report_level = UNSET_INT; +static int report_dest = UNSET_INT; +char *vcsa_device = UNSET_STR; +char *vcs_device = UNSET_STR; +char *keys[4]; + +/* Function prototypes */ +int process_command_line(int argc, char ** argv); +int process_configfile(char * configfile); +int connect_and_setup(); +int update_display(); +int process_response(char * str); +int main_loop(); + +int main( int argc, char ** argv ) +{ + int e = 0; + + CHAIN( e, process_command_line( argc, argv )); + if (strcmp( configfile, UNSET_STR ) == 0) { + configfile = DEFAULT_CONFIGFILE; + } + CHAIN( e, 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( e, open_vcs() ); + CHAIN( e, connect_and_setup() ); + CHAIN_END( e ); + + 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, "hc:a:p:f:r:s:")) > 0) { + switch (c) { + case 'h': + fprintf(stderr, "%s", help_text); + exit( 0 ); + case 'c': + configfile = strdup(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; + 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 ':': + 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); + } + vcs_device = strdup(config_get_string(progname, "vcsDevice", 0, "/dev/vcs")); + vcsa_device = strdup(config_get_string(progname, "vcsaDevice", 0, "/dev/vcsa")); + + keys[0] = strdup( config_get_string( progname, "UpKey", 0, "Up" )); + keys[1] = strdup( config_get_string( progname, "DownKey", 0, "Down" )); + keys[2] = strdup( config_get_string( progname, "LeftKey", 0, "Left" )); + keys[3] = strdup( config_get_string( progname, "RightKey", 0, "Right" )); + + return 0; +} + +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; +} + +int main_loop() +{ + int num_bytes; + char buf[80]; + short w; + + /* Continuously check if we get a menu event... */ + + while ((num_bytes = read_response(buf, sizeof(buf)-1)) >= 0) { + if (num_bytes != 0) { + process_response(buf); + } + else { + read_vcdata(); + update_display(); + + /* Send an empty line every 3 seconds to make sure the server still exists */ + if (w++ >= 60) { + w = 0; + if (send_nop() < 0) { + break; /* Out of while loop */ + } + } + usleep(50000); + } + } + + report(RPT_WARNING, "Server disconnected %d", num_bytes); + return 0; +} + diff --git a/clients/lcdvc/lcdvc.conf b/clients/lcdvc/lcdvc.conf new file mode 100644 index 0000000..ae20cee --- /dev/null +++ b/clients/lcdvc/lcdvc.conf @@ -0,0 +1,31 @@ +# lcdvc client configuration file + +## general options ## +[lcdvc] + +# Address of the LCDd server +#Address=127.0.0.1 + +# Port to attach to LCDd server +#Port=13666 + +#If false, report to stderr. If true, to syslog. +#ReportToSyslog=true + +# Same as with LCDd. +#ReportLevel=4 + +# If false, it forks to the background. +#Foreground=false + +# Keys to move the visible area around the screen. +#UpKey=Up +#DownKey=Down +#LeftKey=Left +#RightKey=Right + +# VC devices to use [default: /dev/vcs, /dev/vcsa] +vcsDevice=/dev/vcs +vcsaDevice=/dev/vcsa + +# EOF diff --git a/clients/lcdvc/lcdvc.h b/clients/lcdvc/lcdvc.h new file mode 100644 index 0000000..31cfe6b --- /dev/null +++ b/clients/lcdvc/lcdvc.h @@ -0,0 +1,21 @@ +#ifndef LCDVC_H +#define LCDVC_H + +#define CHAIN(e,f) { if( e>=0 ) { e=(f); }} +#define CHAIN_END(e) { if( e<0 ) { report( RPT_CRIT,"Critical error, abort"); exit(e); }} + +#define min(a,b) ((a)<(b))?(a):(b) + +#define UNSET_INT -1 +#define UNSET_STR "\01" + +extern char *vcs_device; +extern char *vcsa_device; + +extern char *keys[4]; + +extern char *progname; + +int split(char * str, char delim, char * parts[], int maxparts); + +#endif diff --git a/clients/lcdvc/vc_link.c b/clients/lcdvc/vc_link.c new file mode 100644 index 0000000..0922f8a --- /dev/null +++ b/clients/lcdvc/vc_link.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "lcdvc.h" +#include "vc_link.h" +#include "shared/report.h" +#include "shared/sockets.h" + +int vcs0, vcsa; +unsigned short vc_width = 0, vc_height = 0; +unsigned short vc_cursor_x = 0, vc_cursor_y = 0; +char * vc_buf = NULL; + +int open_vcs() +{ + /* Open the /dev/vcsX and /dev/vcsaX devices */ + vcs0 = open(vcs_device, O_RDONLY); + if (vcs0 < 0) { + report(RPT_ERR, "Could not open %s: %s", vcs_device, strerror(errno)); + return -1; + } + vcsa = open(vcsa_device, O_RDONLY); + if (vcsa < 0) { + report(RPT_ERR, "Could not open %s: %s", vcsa_device, strerror(errno)); + return -1; + } + return 0; +} + +int read_vcdata() +{ + unsigned short new_vc_height; + unsigned short new_vc_width; + + int bytes_read; + unsigned char buf[20]; + + /* Read size and cursor position from /dev/vcsa */ + lseek(vcsa, 0, SEEK_SET); + bytes_read = read(vcsa, buf, 4); + if (bytes_read != 4) { + report(RPT_ERR, "Could not read from %s", vcsa_device); + return -1; + } + new_vc_height = buf[0]; + new_vc_width = buf[1]; + vc_cursor_x = buf[2]; + vc_cursor_y = buf[3]; + + /* Screen resize or initial buffer allocation ? */ + if (new_vc_width != vc_width || new_vc_height != vc_height) { + vc_width = new_vc_width; + vc_height = new_vc_height; + if (vc_buf) + free(vc_buf); + vc_buf = malloc(vc_width * vc_height); + memset(vc_buf, ' ', vc_width * vc_height); + } + + /* Read characters from /dev/cvs0 */ + lseek(vcs0, 0, SEEK_SET); + bytes_read = read(vcs0, vc_buf, vc_width * vc_height); + if (bytes_read != vc_width * vc_height) { + report(RPT_ERR, "Could not read from %s", vcs_device); + return -1; + } + return 0; +} + diff --git a/clients/lcdvc/vc_link.h b/clients/lcdvc/vc_link.h new file mode 100644 index 0000000..33ce122 --- /dev/null +++ b/clients/lcdvc/vc_link.h @@ -0,0 +1,11 @@ +#ifndef VC_LINK_H +#define VC_LINK_H + +extern unsigned short vc_width, vc_height; +extern unsigned short vc_cursor_x, vc_cursor_y; +extern char * vc_buf; + +int open_vcs(); +int read_vcdata(); + +#endif diff --git a/configure.in b/configure.in index c1e4a3c..160c847 100644 --- a/configure.in +++ b/configure.in @@ -554,6 +554,7 @@ AC_CONFIG_FILES([Makefile clients/Makefile clients/lcdproc/Makefile clients/lcdexec/Makefile + clients/lcdvc/Makefile clients/examples/Makefile clients/headlines/Makefile clients/metar/Makefile