X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=miscutils%2Fconspy.c;h=e80158e39a14a10fec042483da4c8bcac11b1050;hb=237bedd499c58034a1355484d6d4d906f0180308;hp=11105f0b8ad6ab69b18ce461a51cc55063aeb52c;hpb=1a3b0710f5424fd71a7c44eade5d4398c44ae431;p=oweals%2Fbusybox.git diff --git a/miscutils/conspy.c b/miscutils/conspy.c index 11105f0b8..e80158e39 100644 --- a/miscutils/conspy.c +++ b/miscutils/conspy.c @@ -7,16 +7,17 @@ * Based on Russell Stuart's conspy.c * http://ace-host.stuart.id.au/russell/files/conspy.c * - * Licensed under GPLv2 or later, see file License in this tarball for details. + * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ -//applet:IF_CONSPY(APPLET(conspy, _BB_DIR_BIN, _BB_SUID_DROP)) +//applet:IF_CONSPY(APPLET(conspy, BB_DIR_BIN, BB_SUID_DROP)) //kbuild:lib-$(CONFIG_CONSPY) += conspy.o //config:config CONSPY //config: bool "conspy" -//config: default n +//config: default y +//config: select PLATFORM_LINUX //config: help //config: A text-mode VNC like program for Linux virtual terminals. //config: example: conspy NUM shared access to console num @@ -24,31 +25,40 @@ //config: or conspy -cs NUM poor man's GNU screen like //usage:#define conspy_trivial_usage -//usage: "[-vcsndf] [-x COL] [-y LINE] [CONSOLE_NO]" +//usage: "[-vcsndfFQ] [-x COL] [-y LINE] [CONSOLE_NO]" //usage:#define conspy_full_usage "\n\n" //usage: "A text-mode VNC like program for Linux virtual consoles." //usage: "\nTo exit, quickly press ESC 3 times." //usage: "\n" -//usage: "\nOptions:" //usage: "\n -v Don't send keystrokes to the console" -//usage: "\n -c Create missing devices in /dev" +//usage: "\n -c Create missing /dev/{tty,vcsa}N" //usage: "\n -s Open a SHELL session" //usage: "\n -n Black & white" //usage: "\n -d Dump console to stdout" //usage: "\n -f Follow cursor" +//usage: "\n -F Assume console is on a framebuffer device" +//usage: "\n -Q Disable exit on ESC-ESC-ESC" //usage: "\n -x COL Starting column" //usage: "\n -y LINE Starting line" #include "libbb.h" +#include "common_bufsiz.h" #include +#define ESC "\033" +#define CURSOR_ON -1 +#define CURSOR_OFF 1 + +#define DEV_TTY "/dev/tty" +#define DEV_VCSA "/dev/vcsa" + struct screen_info { unsigned char lines, cols, cursor_x, cursor_y; }; -#define CHAR(x) ((uint8_t)((x)[0])) -#define ATTR(x) ((uint8_t)((x)[1])) -#define NEXT(x) ((x)+=2) +#define CHAR(x) (*(uint8_t*)(x)) +#define ATTR(x) (((uint8_t*)(x))[1]) +#define NEXT(x) ((x) += 2) #define DATA(x) (*(uint16_t*)(x)) struct globals { @@ -56,70 +66,146 @@ struct globals { int size; int x, y; int kbd_fd; - unsigned width; - unsigned height; - unsigned col; - unsigned line; int ioerror_count; int key_count; int escape_count; int nokeys; int current; - int vcsa_fd; - uint16_t last_attr; - uint8_t last_bold; - uint8_t last_blink; - uint8_t last_fg; - uint8_t last_bg; - char attrbuf[sizeof("\033[0;1;5;30;40m")]; - smallint curoff; + int first_line_offset; + int last_attr; + // cached local tty parameters + unsigned width; + unsigned height; + unsigned col; + unsigned line; + smallint curoff; // unknown:0 cursor on:-1 cursor off:1 + char attrbuf[sizeof("0;1;5;30;40m")]; + // remote console struct screen_info remote; + // saved local tty terminfo struct termios term_orig; + char vcsa_name[sizeof(DEV_VCSA "NN")]; }; #define G (*ptr_to_globals) #define INIT_G() do { \ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ - strcpy((char*)&G.last_attr, "\xff\xff\xff\xff\xff\xff" "\033["); \ + G.width = G.height = UINT_MAX; \ + G.last_attr--; \ } while (0) enum { FLAG_v, // view only FLAG_c, // create device if need + FLAG_Q, // never exit FLAG_s, // session FLAG_n, // no colors FLAG_d, // dump screen FLAG_f, // follow cursor + FLAG_F, // framebuffer }; #define FLAG(x) (1 << FLAG_##x) #define BW (option_mask32 & FLAG(n)) +static void putcsi(const char *s) +{ + fputs(ESC"[", stdout); + fputs(s, stdout); +} + +static void clrscr(void) +{ + // Home, clear till end of screen + putcsi("1;1H" ESC"[J"); + G.col = G.line = 0; +} + +static void set_cursor(int state) +{ + if (G.curoff != state) { + G.curoff = state; + putcsi("?25"); + bb_putchar("h?l"[1 + state]); + } +} + +static void gotoxy(int col, int line) +{ + if (G.col != col || G.line != line) { + G.col = col; + G.line = line; + printf(ESC"[%u;%uH", line + 1, col + 1); + } +} + +static void cleanup(int code) NORETURN; +static void cleanup(int code) +{ + set_cursor(CURSOR_ON); + tcsetattr(G.kbd_fd, TCSANOW, &G.term_orig); + if (ENABLE_FEATURE_CLEAN_UP) { + close(G.kbd_fd); + } + // Reset attributes + if (!BW) + putcsi("0m"); + bb_putchar('\n'); + if (code > EXIT_FAILURE) + kill_myself_with_sig(code); + exit(code); +} + static void screen_read_close(void) { unsigned i, j; - char *data = G.data + G.current; - - xread(G.vcsa_fd, data, G.size); - G.last_attr = 0; + int vcsa_fd; + char *data; + + // Close & re-open vcsa in case they have swapped virtual consoles + vcsa_fd = xopen(G.vcsa_name, O_RDONLY); + xread(vcsa_fd, &G.remote, 4); + i = G.remote.cols * 2; + G.first_line_offset = G.y * i; + i *= G.remote.lines; + if (G.data == NULL) { + G.size = i; + G.data = xzalloc(2 * i); + } + if (G.size != i) { + cleanup(EXIT_FAILURE); + } + data = G.data + G.current; + xread(vcsa_fd, data, G.size); + close(vcsa_fd); for (i = 0; i < G.remote.lines; i++) { for (j = 0; j < G.remote.cols; j++, NEXT(data)) { unsigned x = j - G.x; // if will catch j < G.x too unsigned y = i - G.y; // if will catch i < G.y too - if (CHAR(data) < ' ') - *data = ' '; // CHAR(data) = ' '; if (y >= G.height || x >= G.width) DATA(data) = 0; + else { + uint8_t ch = CHAR(data); + if (ch < ' ') + CHAR(data) = ch | 0x40; + else if (ch > 0x7e) + CHAR(data) = '?'; + } } } - close(G.vcsa_fd); } static void screen_char(char *data) { - uint8_t attr = ATTR(data); + if (!BW) { + uint8_t attr_diff; + uint8_t attr = ATTR(data); - if (!BW && G.last_attr != attr) { + if (option_mask32 & FLAG(F)) { + attr >>= 1; + } + attr_diff = G.last_attr ^ attr; + if (attr_diff) { // Attribute layout for VGA compatible text videobuffer: // blinking text // |red bkgd @@ -143,95 +229,62 @@ static void screen_char(char *data) // green text // blue text // text 8th bit - // converting RGB color bit triad to BGR: - static const char color[8] = "04261537"; - char *ptr; - uint8_t fg, bold, bg, blink; - - G.last_attr = attr; - - //attr >>= 1; // for framebuffer console - ptr = G.attrbuf + sizeof("\033[")-1; - fg = (attr & 0x07); - bold = (attr & 0x08); - bg = (attr & 0x70); - blink = (attr & 0x80); - if (G.last_bold > bold || G.last_blink > blink) { - G.last_bold = G.last_blink = 0; - G.last_bg = 0xff; - *ptr++ = '0'; - *ptr++ = ';'; - } - if (G.last_bold != bold) { - G.last_bold = bold; - *ptr++ = '1'; - *ptr++ = ';'; - } - if (G.last_blink != blink) { - G.last_blink = blink; - *ptr++ = '5'; - *ptr++ = ';'; - } - if (G.last_fg != fg) { - G.last_fg = fg; - *ptr++ = '3'; - *ptr++ = color[fg]; - *ptr++ = ';'; - } - if (G.last_bg != bg) { - G.last_bg = bg; - *ptr++ = '4'; - *ptr++ = color[bg >> 4]; - *ptr++ = ';'; - } - if (ptr != G.attrbuf + sizeof("\033[")-1) { - ptr[-1] = 'm'; - *ptr = '\0'; - fputs(G.attrbuf, stdout); + // converting RGB color bit triad to BGR: + static const char color[8] = "04261537"; + const uint8_t fg_mask = 0x07, bold_mask = 0x08; + const uint8_t bg_mask = 0x70, blink_mask = 0x80; + char *ptr; + + ptr = G.attrbuf; + + // (G.last_attr & ~attr) has 1 only where + // G.last_attr has 1 but attr has 0. + // Here we check whether we have transition + // bold->non-bold or blink->non-blink: + if (G.last_attr < 0 // initial value + || ((G.last_attr & ~attr) & (bold_mask | blink_mask)) != 0 + ) { + *ptr++ = '0'; // "reset all attrs" + *ptr++ = ';'; + // must set fg & bg, maybe need to set bold or blink: + attr_diff = attr | ~(bold_mask | blink_mask); + } + G.last_attr = attr; + if (attr_diff & bold_mask) { + *ptr++ = '1'; + *ptr++ = ';'; + } + if (attr_diff & blink_mask) { + *ptr++ = '5'; + *ptr++ = ';'; + } + if (attr_diff & fg_mask) { + *ptr++ = '3'; + *ptr++ = color[attr & fg_mask]; + *ptr++ = ';'; + } + if (attr_diff & bg_mask) { + *ptr++ = '4'; + *ptr++ = color[(attr & bg_mask) >> 4]; + ptr++; // last attribute + } + if (ptr != G.attrbuf) { + ptr[-1] = 'm'; + *ptr = '\0'; + putcsi(G.attrbuf); + } } } putchar(CHAR(data)); G.col++; } -static void clrscr(void) -{ - // Home, clear till end of src, cursor on - fputs("\033[1;1H" "\033[J" "\033[?25h", stdout); - G.curoff = G.col = G.line = 0; -} - -static void curoff(void) -{ - if (!G.curoff) { - G.curoff = 1; - fputs("\033[?25l", stdout); - } -} - -static void curon(void) -{ - if (G.curoff) { - G.curoff = 0; - fputs("\033[?25h", stdout); - } -} - -static void gotoxy(int col, int line) -{ - if (G.col != col || G.line != line) { - G.col = col; - G.line = line; - printf("\033[%u;%uH", line + 1, col + 1); - } -} - static void screen_dump(void) { int linefeed_cnt; int line, col; int linecnt = G.remote.lines - G.y; - char *data = G.data + G.current + (2 * G.y * G.remote.cols); + char *data = G.data + G.current + G.first_line_offset; linefeed_cnt = 0; for (line = 0; line < linecnt && line < G.height; line++) { @@ -261,41 +314,13 @@ static void curmove(void) { unsigned cx = G.remote.cursor_x - G.x; unsigned cy = G.remote.cursor_y - G.y; + int cursor = CURSOR_OFF; - if (cx >= G.width || cy >= G.height) { - curoff(); - } else { - curon(); + if (cx < G.width && cy < G.height) { gotoxy(cx, cy); + cursor = CURSOR_ON; } - fflush_all(); -} - -static void cleanup(int code) -{ - curon(); - fflush_all(); - tcsetattr(G.kbd_fd, TCSANOW, &G.term_orig); - if (ENABLE_FEATURE_CLEAN_UP) { - close(G.kbd_fd); - } - // Reset attributes - if (!BW) - fputs("\033[0m", stdout); - bb_putchar('\n'); - if (code > 1) - kill_myself_with_sig(code); // does not return - exit(code); -} - -static void get_initial_data(const char* vcsa_name) -{ - G.vcsa_fd = xopen(vcsa_name, O_RDONLY); - xread(G.vcsa_fd, &G.remote, 4); - G.size = G.remote.cols * G.remote.lines * 2; - G.width = G.height = UINT_MAX; - G.data = xzalloc(2 * G.size); - screen_read_close(); + set_cursor(cursor); } static void create_cdev_if_doesnt_exist(const char* name, dev_t dev) @@ -309,16 +334,11 @@ static void create_cdev_if_doesnt_exist(const char* name, dev_t dev) static NOINLINE void start_shell_in_child(const char* tty_name) { - int pid = vfork(); - if (pid < 0) { - bb_perror_msg_and_die("vfork"); - } + int pid = xvfork(); if (pid == 0) { struct termios termchild; - char *shell = getenv("SHELL"); + const char *shell = get_shell_name(); - if (!shell) - shell = (char *) DEFAULT_SHELL; signal(SIGHUP, SIG_IGN); // set tty as a controlling tty setsid(); @@ -343,9 +363,7 @@ static NOINLINE void start_shell_in_child(const char* tty_name) int conspy_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int conspy_main(int argc UNUSED_PARAM, char **argv) { - char vcsa_name[sizeof("/dev/vcsaNN")]; - char tty_name[sizeof("/dev/ttyNN")]; -#define keybuf bb_common_bufsiz1 + char tty_name[sizeof(DEV_TTY "NN")]; struct termios termbuf; unsigned opts; unsigned ttynum; @@ -354,36 +372,41 @@ int conspy_main(int argc UNUSED_PARAM, char **argv) static const char getopt_longopts[] ALIGN1 = "viewonly\0" No_argument "v" "createdevice\0" No_argument "c" + "neverquit\0" No_argument "Q" "session\0" No_argument "s" "nocolors\0" No_argument "n" "dump\0" No_argument "d" "follow\0" No_argument "f" + "framebuffer\0" No_argument "F" ; applet_long_options = getopt_longopts; #endif +#define keybuf bb_common_bufsiz1 + setup_common_bufsiz(); + INIT_G(); - strcpy(vcsa_name, "/dev/vcsa"); + strcpy(G.vcsa_name, DEV_VCSA); - opt_complementary = "x+:y+"; // numeric params - opts = getopt32(argv, "vcsndfx:y:", &G.x, &G.y); + // numeric params + opts = getopt32(argv, "vcQsndfFx:+y:+", &G.x, &G.y); argv += optind; ttynum = 0; if (argv[0]) { ttynum = xatou_range(argv[0], 0, 63); - sprintf(vcsa_name + sizeof("/dev/vcsa")-1, "%u", ttynum); + sprintf(G.vcsa_name + sizeof(DEV_VCSA)-1, "%u", ttynum); } - sprintf(tty_name, "%s%u", "/dev/tty", ttynum); + sprintf(tty_name, "%s%u", DEV_TTY, ttynum); if (opts & FLAG(c)) { if ((opts & (FLAG(s)|FLAG(v))) != FLAG(v)) create_cdev_if_doesnt_exist(tty_name, makedev(4, ttynum)); - create_cdev_if_doesnt_exist(vcsa_name, makedev(7, 128 + ttynum)); + create_cdev_if_doesnt_exist(G.vcsa_name, makedev(7, 128 + ttynum)); } if ((opts & FLAG(s)) && ttynum) { start_shell_in_child(tty_name); } - get_initial_data(vcsa_name); + screen_read_close(); if (opts & FLAG(d)) { screen_dump(); bb_putchar('\n'); @@ -402,6 +425,7 @@ int conspy_main(int argc UNUSED_PARAM, char **argv) termbuf.c_cc[VMIN] = 1; termbuf.c_cc[VTIME] = 0; tcsetattr(G.kbd_fd, TCSANOW, &termbuf); + poll_timeout_ms = 250; while (1) { struct pollfd pfd; @@ -409,35 +433,29 @@ int conspy_main(int argc UNUSED_PARAM, char **argv) int i, j; char *data, *old; - // Close & re-open vcsa in case they have - // swapped virtual consoles - G.vcsa_fd = xopen(vcsa_name, O_RDONLY); - xread(G.vcsa_fd, &G.remote, 4); - if (G.size != (G.remote.cols * G.remote.lines * 2)) { - cleanup(1); - } + // in the first loop G.width = G.height = 0: refresh i = G.width; j = G.height; get_terminal_width_height(G.kbd_fd, &G.width, &G.height); - if ((option_mask32 & FLAG(f))) { + if (option_mask32 & FLAG(f)) { int nx = G.remote.cursor_x - G.width + 1; int ny = G.remote.cursor_y - G.height + 1; if (G.remote.cursor_x < G.x) { G.x = G.remote.cursor_x; - i = 0; // force refresh + i = 0; // force refresh } if (nx > G.x) { G.x = nx; - i = 0; // force refresh + i = 0; // force refresh } if (G.remote.cursor_y < G.y) { G.y = G.remote.cursor_y; - i = 0; // force refresh + i = 0; // force refresh } if (ny > G.y) { G.y = ny; - i = 0; // force refresh + i = 0; // force refresh } } @@ -451,8 +469,8 @@ int conspy_main(int argc UNUSED_PARAM, char **argv) screen_dump(); } else { // For each remote line - old += G.y * G.remote.cols * 2; - data += G.y * G.remote.cols * 2; + old += G.first_line_offset; + data += G.first_line_offset; for (i = G.y; i < G.remote.lines; i++) { char *first = NULL; // first char which needs updating char *last = last; // last char which needs updating @@ -460,10 +478,8 @@ int conspy_main(int argc UNUSED_PARAM, char **argv) if (iy >= G.height) break; - old += G.x * 2; - data += G.x * 2; - for (j = G.x; j < G.remote.cols; j++, NEXT(old), NEXT(data)) { - unsigned jx = j - G.x; + for (j = 0; j < G.remote.cols; j++, NEXT(old), NEXT(data)) { + unsigned jx = j - G.x; // if will catch j >= G.x too if (jx < G.width && DATA(data) != DATA(old)) { last = data; @@ -483,6 +499,7 @@ int conspy_main(int argc UNUSED_PARAM, char **argv) curmove(); // Wait for local user keypresses + fflush_all(); pfd.fd = G.kbd_fd; pfd.events = POLLIN; bytes_read = 0; @@ -490,7 +507,7 @@ int conspy_main(int argc UNUSED_PARAM, char **argv) char *k; case -1: if (errno != EINTR) - cleanup(1); + goto abort; break; case 0: if (++G.nokeys >= 4) @@ -499,56 +516,65 @@ int conspy_main(int argc UNUSED_PARAM, char **argv) default: // Read the keys pressed k = keybuf + G.key_count; - bytes_read = read(G.kbd_fd, k, sizeof(keybuf) - G.key_count); + bytes_read = read(G.kbd_fd, k, COMMON_BUFSIZE - G.key_count); if (bytes_read < 0) - cleanup(1); + goto abort; // Do exit processing - for (i = 0; i < bytes_read; i++) { - if (k[i] != '\033') - G.escape_count = 0; - else if (++G.escape_count >= 3) - cleanup(0); + if (!(option_mask32 & FLAG(Q))) { + for (i = 0; i < bytes_read; i++) { + if (k[i] != '\033') + G.escape_count = -1; + if (++G.escape_count >= 3) + cleanup(EXIT_SUCCESS); + } } } poll_timeout_ms = 250; + if (option_mask32 & FLAG(v)) continue; // Insert all keys pressed into the virtual console's input // buffer. Don't do this if the virtual console is in scan // code mode - giving ASCII characters to a program expecting // scan codes will confuse it. - if (!(option_mask32 & FLAG(v)) && G.escape_count == 0) { + G.key_count += bytes_read; + if (G.escape_count == 0) { int handle, result; long kbd_mode; - G.key_count += bytes_read; handle = xopen(tty_name, O_WRONLY); result = ioctl(handle, KDGKBMODE, &kbd_mode); - if (result == -1) - /* nothing */; - else if (kbd_mode != K_XLATE && kbd_mode != K_UNICODE) - G.key_count = 0; // scan code mode - else { - for (i = 0; i < G.key_count && result != -1; i++) - result = ioctl(handle, TIOCSTI, keybuf + i); - G.key_count -= i; - if (G.key_count) - memmove(keybuf, keybuf + i, G.key_count); - // If there is an application on console which reacts - // to keypresses, we need to make our first sleep - // shorter to quickly redraw whatever it printed there. - poll_timeout_ms = 20; + if (result >= 0) { + char *p = keybuf; + + G.ioerror_count = 0; + if (kbd_mode != K_XLATE && kbd_mode != K_UNICODE) { + G.key_count = 0; // scan code mode + } + for (; G.key_count != 0; p++, G.key_count--) { + result = ioctl(handle, TIOCSTI, p); + if (result < 0) { + memmove(keybuf, p, G.key_count); + break; + } + // If there is an application on console which reacts + // to keypresses, we need to make our first sleep + // shorter to quickly redraw whatever it printed there. + poll_timeout_ms = 20; + } + } + // We sometimes get spurious IO errors on the TTY + // as programs close and re-open it + else if (errno != EIO || ++G.ioerror_count > 4) { + if (ENABLE_FEATURE_CLEAN_UP) + close(handle); + goto abort; } // Close & re-open tty in case they have // swapped virtual consoles close(handle); - - // We sometimes get spurious IO errors on the TTY - // as programs close and re-open it - if (result != -1) - G.ioerror_count = 0; - else if (errno != EIO || ++G.ioerror_count > 4) - cleanup(1); } - } + } /* while (1) */ + abort: + cleanup(EXIT_FAILURE); }