diff --git a/Makefile b/Makefile index e3c0da7..ea9ff7f 100644 --- a/Makefile +++ b/Makefile @@ -20,10 +20,10 @@ CFLAGS ?= -Wextra -Wall EXES = nms sneakers all: $(EXES) -nms: $(OBJ)/libnms.o $(OBJ)/nms.o | $(BIN) +nms: $(OBJ)/nms.o $(OBJ)/main.o | $(BIN) $(CC) $(CFLAGS) -o $(BIN)/$@ $^ -sneakers: $(OBJ)/libnms.o $(OBJ)/sneakers.o | $(BIN) +sneakers: $(OBJ)/nms.o $(OBJ)/sneakers.o | $(BIN) $(CC) $(CFLAGS) -o $(BIN)/$@ $^ $(OBJ)/%.o: $(SRC)/%.c | $(OBJ) diff --git a/README.md b/README.md index efb5217..5ff28ee 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ enjoy the magic. In the below examples, I use a simple directory listing. ls -l | nms ls -l | nms -a // Set auto-decrypt flag ls -l | nms -f green // Set foreground color to green +ls -l | nms -r 123456 // Set return options ls -l | nms -c // Clear screen nms -v // Display version ``` @@ -85,12 +86,19 @@ sequence. This is how the decryption functionality is depicted in the movie. Set the auto-decrypt flag. This will automatically start the decryption sequence without requiring a key press. -`-f color` +`-f ` Set the foreground color of the decrypted text to the color specified. Valid options are white, yellow, black, magenta, blue, green, or red. This is blue by default. +`-r ` + +Sets the character options that `nms` requires the user to choose from +before it terminates execution. This is intended to be used for cases +where the data piped to `nms` contains a menu with a set of options. Note +that `nms` will print the selection to stdout before terminating. + `-c` Clear the screen prior to printing any output. Specifically, diff --git a/src/libnms.c b/src/libnms.c deleted file mode 100644 index 91c7bea..0000000 --- a/src/libnms.c +++ /dev/null @@ -1,638 +0,0 @@ -/* - * Copyright (c) 2016 Brian Barto - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the MIT License. See LICENSE for more details. - */ - -/* - * DESCRIPTION - * - * The libnms library encapsulates the functuionality required to produce the - * famous "decrypting text" sceen effect from the 1992 hacker movie "Sneakers". - * For more, go to: https://github.com/bartobri/libnms - */ - -#define _XOPEN_SOURCE 700 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "libnms.h" - -// Color identifiers -#define COLOR_BLACK 0 -#define COLOR_RED 1 -#define COLOR_GREEN 2 -#define COLOR_YELLOW 3 -#define COLOR_BLUE 4 -#define COLOR_MAGENTA 5 -#define COLOR_CYAN 6 -#define COLOR_WHITE 7 - -// Macros for VT100 codes -#define CLEAR_SCR() printf("\033[2J") // Clear Screen -#define CURSOR_HOME() printf("\033[H") // Move cursor to home position (0,0) -#define CURSOR_MOVE(x,y) printf("\033[%i;%iH", x, y) // Move cursor to x,y -#define BEEP() printf("\a"); // terminal bell -#define BOLD() printf("\033[1m") // Cursor bold -#define FOREGROUND_COLOR(x) printf("\033[3%im", x) // Set foreground color -#define CLEAR_ATTR() printf("\033[0m") // Clear bold/color attributes -#define SCREEN_SAVE() printf("\033[?47h") // Save screen display -#define SCREEN_RESTORE() printf("\033[?47l") // Restore screen to previously saved state -#define CURSOR_SAVE() printf("\033[s") // Save cursor position -#define CURSOR_RESTORE() printf("\033[u") // Restore cursor position -#define CURSOR_HIDE() printf("\033[?25l") // Hide cursor -#define CURSOR_SHOW() printf("\033[?25h") // Unhide cursor - -// Program settings -#define TYPE_EFFECT_SPEED 4 // miliseconds per char -#define JUMBLE_SECONDS 2 // number of seconds for jumble effect -#define JUMBLE_LOOP_SPEED 35 // miliseconds between each jumble -#define REVEAL_LOOP_SPEED 50 // miliseconds between each reveal loop -#define MASK_CHAR_COUNT 218 // Total characters in maskCharTable[] array. - -// Character attribute structure, linked list. Keeps track of every -// character's attributes required for rendering and decryption effect. -struct charAttr { - char *source; - char *mask; - int width; - int is_space; - int time; - struct charAttr *next; -}; - -// Static function prototypes -static void nms_sleep(int); -static int nms_term_rows(void); -static int nms_term_cols(void); -static void nms_set_terminal(int s); -static void nms_clear_input(void); -static char nms_get_char(void); -static int nms_get_cursor_row(void); - -// NMS settings -static int foregroundColor = COLOR_BLUE; // Foreground color setting -static char *returnOpts = NULL; // Return option setting -static int autoDecrypt = 0; // Auto-decrypt flag -static int clearScr = 0; // clearScr flag -static int colorOn = 1; // Terminal color flag -static int inputPositionX = -1; // X coordinate for input position -static int inputPositionY = -1; // Y coordinate for input position - -// Character table representing the character set know as CP437 used by -// the original IBM PC - https://en.wikipedia.org/wiki/Code_page_437 -static char *maskCharTable[] = { - "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", "~", - ".", "/", ":", ";", "<", "=", ">", "?", "[", "\\", "]", "_", "{", "}", - "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", - "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", - "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", - "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", - "\xc3\x87", "\xc3\xbc", "\xc3\xa9", "\xc3\xa2", "\xc3\xa4", "\xc3\xa0", - "\xc3\xa5", "\xc3\xa7", "\xc3\xaa", "\xc3\xab", "\xc3\xa8", "\xc3\xaf", - "\xc3\xae", "\xc3\xac", "\xc3\x84", "\xc3\x85", "\xc3\x89", "\xc3\xa6", - "\xc3\x86", "\xc3\xb4", "\xc3\xb6", "\xc3\xb2", "\xc3\xbb", "\xc3\xb9", - "\xc3\xbf", "\xc3\x96", "\xc3\x9c", "\xc2\xa2", "\xc2\xa3", "\xc2\xa5", - "\xc6\x92", "\xc3\xa1", "\xc3\xad", "\xc3\xb3", "\xc3\xba", "\xc3\xb1", - "\xc3\x91", "\xc2\xaa", "\xc2\xba", "\xc2\xbf", "\xc2\xac", "\xc2\xbd", - "\xc2\xbc", "\xc2\xa1", "\xc2\xab", "\xc2\xbb", "\xce\xb1", "\xc3\x9f", - "\xce\x93", "\xcf\x80", "\xce\xa3", "\xcf\x83", "\xc2\xb5", "\xcf\x84", - "\xce\xa6", "\xce\x98", "\xce\xa9", "\xce\xb4", "\xcf\x86", "\xce\xb5", - "\xc2\xb1", "\xc3\xb7", "\xc2\xb0", "\xc2\xb7", "\xc2\xb2", "\xc2\xb6", - "\xe2\x8c\x90", "\xe2\x82\xa7", "\xe2\x96\x91", "\xe2\x96\x92", - "\xe2\x96\x93", "\xe2\x94\x82", "\xe2\x94\xa4", "\xe2\x95\xa1", - "\xe2\x95\xa2", "\xe2\x95\x96", "\xe2\x95\x95", "\xe2\x95\xa3", - "\xe2\x95\x91", "\xe2\x95\x97", "\xe2\x95\x9d", "\xe2\x95\x9c", - "\xe2\x95\x9b", "\xe2\x94\x90", "\xe2\x94\x94", "\xe2\x94\xb4", - "\xe2\x94\xac", "\xe2\x94\x9c", "\xe2\x94\x80", "\xe2\x94\xbc", - "\xe2\x95\x9e", "\xe2\x95\x9f", "\xe2\x95\x9a", "\xe2\x95\x94", - "\xe2\x95\xa9", "\xe2\x95\xa6", "\xe2\x95\xa0", "\xe2\x95\x90", - "\xe2\x95\xac", "\xe2\x95\xa7", "\xe2\x95\xa8", "\xe2\x95\xa4", - "\xe2\x95\xa7", "\xe2\x95\x99", "\xe2\x95\x98", "\xe2\x95\x92", - "\xe2\x95\x93", "\xe2\x95\xab", "\xe2\x95\xaa", "\xe2\x94\x98", - "\xe2\x94\x8c", "\xe2\x96\x88", "\xe2\x96\x84", "\xe2\x96\x8c", - "\xe2\x96\x90", "\xe2\x96\x80", "\xe2\x88\x9e", "\xe2\x88\xa9", - "\xe2\x89\xa1", "\xe2\x89\xa5", "\xe2\x89\xa4", "\xe2\x8c\xa0", - "\xe2\x8c\xa1", "\xe2\x89\x88", "\xe2\x88\x99", "\xe2\x88\x9a", - "\xe2\x81\xbf", "\xe2\x96\xa0" -}; - -/* - * nms_exec() - This function is passed a pointer to a character string - * and displays the contents of the string in a way that mimicks the - * "decrypting text" effect in the 1992 movie Sneakers. It returns the - * last character pressed by the user. - */ -char nms_exec(char *string) { - struct charAttr *list_pointer = NULL; - struct charAttr *list_head = NULL; - struct charAttr *list_temp = NULL; - int i, revealed = 0; - int maxRows, maxCols, curRow, curCol, origRow = 0, origCol = 0; - char ret = 0; - - // Error if we have an empty string. - if (string == NULL || string[0] == '\0') { - fprintf(stderr, "Error. Empty string.\n"); - return 0; - } - - // Reassociate STDIN to the terminal is needed - if (!isatty(STDIN_FILENO) && !freopen ("/dev/tty", "r", stdin)) { - fprintf(stderr, "Error. Can't associate STDIN with terminal.\n"); - return 0; - } - - // Turn off terminal echo and line buffering - nms_set_terminal(0); - - // Needed for UTF-8 support - setlocale(LC_ALL, ""); - - // Get terminal window rows/cols - maxRows = nms_term_rows(); - maxCols = nms_term_cols(); - - // Seed my random number generator with the current time - srand(time(NULL)); - - // Get cursor position if we are not clearing the screen - if (!clearScr) { - origRow = nms_get_cursor_row(); - - // nms_get_cursor_row() may display output in some terminals. So - // we need to reposition the cursor to the start of the row. - CURSOR_MOVE(origRow, origCol); - } - - // Assign current row/col positions - curRow = origRow; - curCol = origCol; - - // Geting input - for (i = 0; string[i] != '\0'; ++i) { - - // Don't go beyond maxRows - if (curRow - origRow >= maxRows - 1) { - break; - } - - // Allocate memory for next list link - if (list_pointer == NULL) { - list_pointer = malloc(sizeof(struct charAttr)); - list_head = list_pointer; - } else { - list_pointer->next = malloc(sizeof(struct charAttr)); - list_pointer = list_pointer->next; - } - - // Get character's byte-length and store character. - if (mblen(&string[i], 4) > 0) { - list_pointer->source = malloc(mblen(&string[i], 4) + 1); - strncpy(list_pointer->source, &string[i], mblen(&string[i], 4)); - list_pointer->source[mblen(&string[i], 4)] = '\0'; - i += (mblen(&string[i], 4) - 1); - } else { - fprintf(stderr, "Unknown character encountered. Quitting.\n"); - nms_set_terminal(1); - return 0; - } - - // Set flag if we have a whitespace character - if (strlen(list_pointer->source) == 1 && isspace(list_pointer->source[0])) - list_pointer->is_space = 1; - else - list_pointer->is_space = 0; - - // Set initial mask chharacter - list_pointer->mask = maskCharTable[rand() % MASK_CHAR_COUNT]; - - // Set reveal time - list_pointer->time = rand() % 5000; - - // Set character column width - wchar_t widec[sizeof(list_pointer->source)] = {}; - mbstowcs(widec, list_pointer->source, sizeof(list_pointer->source)); - list_pointer->width = wcwidth(*widec); - - // Set next node to null - list_pointer->next = NULL; - - // Track row count - if (string[i] == '\n' || (curCol += list_pointer->width) > maxCols) { - curCol = 0; - curRow++; - if (curRow == maxRows + 1 && origRow > 0) { - origRow--; - curRow--; - } - } - } - - // Save terminal state, clear screen, and home/hide the cursor - if (clearScr) { - CURSOR_SAVE(); - SCREEN_SAVE(); - CLEAR_SCR(); - CURSOR_HOME(); - CURSOR_HIDE(); - } - - // Print mask characters with 'type effect' - for (list_pointer = list_head; list_pointer != NULL; list_pointer = list_pointer->next) { - - // Print mask character (or space) - if (list_pointer->is_space) { - printf("%s", list_pointer->source); - continue; - } - - // print mask character - printf("%s", list_pointer->mask); - if (list_pointer->width == 2) { - printf("%s", maskCharTable[rand() % MASK_CHAR_COUNT]); - } - - // flush output and sleep - fflush(stdout); - nms_sleep(TYPE_EFFECT_SPEED); - } - - // Flush any input up to this point - nms_clear_input(); - - // If autoDecrypt flag is set, we sleep. Otherwise require user to - // press a key to continue. - if (autoDecrypt) - sleep(1); - else - nms_get_char(); - - // Jumble loop - for (i = 0; i < (JUMBLE_SECONDS * 1000) / JUMBLE_LOOP_SPEED; ++i) { - - // Move cursor to home position - if (clearScr) { - CURSOR_HOME(); - } else { - CURSOR_MOVE(origRow, origCol); - } - - // Print new mask for all characters - for (list_pointer = list_head; list_pointer != NULL; list_pointer = list_pointer->next) { - - // Print mask character (or space) - if (list_pointer->is_space) { - printf("%s", list_pointer->source); - continue; - } - - // print new mask character - printf("%s", maskCharTable[rand() % MASK_CHAR_COUNT]); - if (list_pointer->width == 2) { - printf("%s", maskCharTable[rand() % MASK_CHAR_COUNT]); - } - } - - // flush output and sleep - fflush(stdout); - nms_sleep(JUMBLE_LOOP_SPEED); - } - - // Reveal loop - while (!revealed) { - - // Move cursor to home position - if (clearScr) { - CURSOR_HOME(); - } else { - CURSOR_MOVE(origRow, origCol); - } - - // Set revealed flag - revealed = 1; - - for (list_pointer = list_head; list_pointer != NULL; list_pointer = list_pointer->next) { - - // Print mask character (or space) - if (list_pointer->is_space) { - printf("%s", list_pointer->source); - continue; - } - - // If we still have time before the char is revealed, display the mask - if (list_pointer->time > 0) { - - // Change the mask randomly - if (list_pointer->time < 500) { - if (rand() % 3 == 0) { - list_pointer->mask = maskCharTable[rand() % MASK_CHAR_COUNT]; - } - } else { - if (rand() % 10 == 0) { - list_pointer->mask = maskCharTable[rand() % MASK_CHAR_COUNT]; - } - } - - // Print mask - printf("%s", list_pointer->mask); - - // Decrement reveal time - list_pointer->time -= REVEAL_LOOP_SPEED; - - // Unset revealed flag - revealed = 0; - } else { - - // Set bold and foreground color for character reveal - BOLD(); - if (colorOn) { - FOREGROUND_COLOR(foregroundColor); - } - - // print source character - printf("%s", list_pointer->source); - - // Unset foreground color - CLEAR_ATTR(); - } - } - - // flush output and sleep - fflush(stdout); - nms_sleep(REVEAL_LOOP_SPEED); - } - - // Flush any input up to this point - nms_clear_input(); - - // Check if user must select from a set of options - if (returnOpts != NULL && strlen(returnOpts) > 0) { - - // Position cursor if necessary - if (inputPositionY >= 0 && inputPositionX >= 0) { - CURSOR_MOVE(inputPositionY, inputPositionX); - } - - CURSOR_SHOW(); - - // Get and validate user selection - while (strchr(returnOpts, ret = nms_get_char()) == NULL) { - BEEP(); - } - - } - - // User must press a key to continue when clearSrc is set - // without returnOpts - else if (clearScr) { - nms_get_char(); - } - - // Restore screen and cursor is clearSrc is set - if (clearScr) { - SCREEN_RESTORE(); - CURSOR_SHOW(); - CURSOR_RESTORE(); - } - - // Turn on terminal echo and line buffering - nms_set_terminal(1); - - // Freeing the list. - list_pointer = list_head; - while (list_pointer != NULL) { - list_temp = list_pointer; - list_pointer = list_pointer->next; - free(list_temp->source); - free(list_temp); - } - - return ret; -} - -/* - * nms_set_foreground_color() sets the foreground color of the unencrypted - * characters as they are revealed to the color indicated by the 'color' - * argument. Valid arguments are "white", "yellow", "magenta", "blue", - * "green", "red", and "cyan". This function will default to blue if - * passed an invalid color. No value is returned. - */ -void nms_set_foreground_color(char *color) { - - if(strcmp("white", color) == 0) - foregroundColor = COLOR_WHITE; - else if(strcmp("yellow", color) == 0) - foregroundColor = COLOR_YELLOW; - else if(strcmp("black", color) == 0) - foregroundColor = COLOR_BLACK; - else if(strcmp("magenta", color) == 0) - foregroundColor = COLOR_MAGENTA; - else if(strcmp("blue", color) == 0) - foregroundColor = COLOR_BLUE; - else if(strcmp("green", color) == 0) - foregroundColor = COLOR_GREEN; - else if(strcmp("red", color) == 0) - foregroundColor = COLOR_RED; - else if(strcmp("cyan", color) == 0) - foregroundColor = COLOR_CYAN; - else - foregroundColor = COLOR_BLUE; -} - -/* - * nms_set_return_opts() takes a character sting and copies it to the - * returnOpts setting used by nms_exec(). - */ -void nms_set_return_opts(char *opts) { - returnOpts = realloc(returnOpts, strlen(opts) + 1); - strcpy(returnOpts, opts); -} - -/* - * nms_set_auto_decrypt() sets the autoDecrypt flag according to the - * true/false value of the 'setting' argument. - */ -void nms_set_auto_decrypt(int setting) { - if (setting) - autoDecrypt = 1; - else - autoDecrypt = 0; -} - -/* - * nms_set_clear_scr() sets the clearScr flag according to the - * true/false value of the 'setting' argument. - */ -void nms_set_clear_scr(int setting) { - if (setting) - clearScr = 1; - else - clearScr = 0; -} - -/* - * nms_set_color() sets the colorOn flag according to the - * true/false value of the 'setting' argument. - */ -void nms_use_color(int setting) { - if (setting) - colorOn = 1; - else - colorOn = 0; -} - -/* - * nms_set_input_position() sets the desired coordinate of the cursor in - * the terminal when accepting user input after nms_exec() reveals the - * unencrypted characters. - */ -void nms_set_input_position(int x, int y) { - if (x >= 0 && y >= 0) { - inputPositionX = x; - inputPositionY = y; - } -} - -/* - * nms_sleep() sleeps for the number of miliseconds indicated by 't'. - */ -static void nms_sleep(int t) { - struct timespec ts; - - ts.tv_sec = t / 1000; - ts.tv_nsec = (t % 1000) * 1000000; - - nanosleep(&ts, NULL); -} - -/* - * nms_term_rows() gets and returns the number of rows in the current - * terminal window. - */ -static int nms_term_rows(void) { - struct winsize w; - ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); - - return w.ws_row; -} - -/* - * nms_term_cols() gets and returns the number of cols in the current - * terminal window. - */ -static int nms_term_cols(void) { - struct winsize w; - ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); - - return w.ws_col; -} - -/* - * nms_set_terminal() turns off terminal echo and line buffering when - * passed an integer value that evaluates to true. It restores the - * original terminal values when passed an integer value that evaluates - * to false. - */ -static void nms_set_terminal(int s) { - struct termios tp; - static struct termios save; - static int state = 1; - - if (s == 0) { - if (tcgetattr(STDIN_FILENO, &tp) == -1) { - return; - } - - save = tp; - - tp.c_lflag &=(~ICANON & ~ECHO); - - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tp) == -1) { - return; - } - } else { - if (state == 0 && tcsetattr(STDIN_FILENO, TCSANOW, &save) == -1) - return; - } - - state = s; -} - -/* - * nms_clear_input() clears the input buffer of all characters up to - * the EOF character. - */ -static void nms_clear_input(void) { - int i; - - ioctl(STDIN_FILENO, FIONREAD, &i); - - while (i > 0) { - getchar(); - --i; - } -} - -/* - * nms_get_char() returns the next character in the input buffer. In the - * case of an EOF character, it blocks until the user presses a key. - */ -static char nms_get_char(void) { - struct timespec ts; - int t = 50; - char c; - - ts.tv_sec = t / 1000; - ts.tv_nsec = (t % 1000) * 1000000; - - while ((c = getchar()) == EOF) { - nanosleep(&ts, NULL); - } - - return c; -} - -/* - * nms_get_cursor_row() returns the row position of the cursor as reported - * by the terminal program via the ANSI escape code - */ -static int nms_get_cursor_row(void) { - int i, r = 0; - int row = 0; - char buf[10]; - char *cmd = "\033[6n"; - - memset(buf, 0, sizeof(buf)); - - write(STDOUT_FILENO, cmd, sizeof(cmd)); - - r = read(STDIN_FILENO, buf, sizeof(buf)); - - for (i = 0; i < r; ++i) { - if (buf[i] == 27 || buf[i] == '[') { - continue; - } - - if (buf[i] >= '0' && buf[i] <= '9') { - row = (row * 10) + (buf[i] - '0'); - } - - if (buf[i] == ';' || buf[i] == 'R' || buf[i] == 0) { - break; - } - } - - return row; -} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..10682cf --- /dev/null +++ b/src/main.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017 Brian Barto + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the MIT License. See LICENSE for more details. + */ + +#include +#include +#include +#include +#include "nms.h" + +#define VERSION "0.3.0" +#define INPUT_GROWTH_FACTOR 2 + +int main(int argc, char *argv[]) { + int c, o, inSize = 0, inCapacity = 0; + char *input = NULL; + + // Processing command arguments + while ((o = getopt(argc, argv, "f:ac:v")) != -1) { + switch (o) { + case 'f': + nms_set_foreground_color(optarg); + break; + case 'a': + nms_set_auto_decrypt(1); + break; + case 'c': + nms_set_clear_scr(1); + break; + case 'v': + printf("nms version " VERSION "\n"); + return 0; + case '?': + if (isprint(optopt)) + fprintf (stderr, "Unknown option '-%c'.\n", optopt); + else + fprintf (stderr, "Unknown option character '\\x%x'.\n", optopt); + return 1; + } + } + + // Geting input + while ((c = getchar()) != EOF) { + ++inSize; + if (inSize > inCapacity) { + inCapacity = inCapacity == 0 ? INPUT_GROWTH_FACTOR : inCapacity * INPUT_GROWTH_FACTOR; + input = realloc(input, inCapacity + 1); + } + input[inSize - 1] = c; + input[inSize] = '\0'; + } + + // Display characters + c = nms_exec(input); + + // Print out from nms_exec if it is not null + if (c) { + printf("%c", c); + } + + // Don't forget to free the allocated memory! + free(input); + + return 0; +} diff --git a/src/nms.c b/src/nms.c index 0c29104..2fa15dd 100644 --- a/src/nms.c +++ b/src/nms.c @@ -1,61 +1,637 @@ +/* + * Copyright (c) 2017 Brian Barto + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the MIT License. See LICENSE for more details. + */ + +/* + * DESCRIPTION + * + * This library encapsulates the functionality required to produce the + * famous data decryption effect from the 1992 hacker movie Sneakers. + */ + +#define _XOPEN_SOURCE 700 + #include +#include #include +#include #include +#include #include -#include "libnms.h" +#include +#include +#include +#include +#include "nms.h" -#define VERSION "0.3.0" -#define INPUT_GROWTH_FACTOR 2 +// Color identifiers +#define COLOR_BLACK 0 +#define COLOR_RED 1 +#define COLOR_GREEN 2 +#define COLOR_YELLOW 3 +#define COLOR_BLUE 4 +#define COLOR_MAGENTA 5 +#define COLOR_CYAN 6 +#define COLOR_WHITE 7 -int main(int argc, char *argv[]) { - int c, o, inSize = 0, inCapacity = 0; - char *input = NULL; +// Macros for VT100 codes +#define CLEAR_SCR() printf("\033[2J") // Clear Screen +#define CURSOR_HOME() printf("\033[H") // Move cursor to home position (0,0) +#define CURSOR_MOVE(x,y) printf("\033[%i;%iH", x, y) // Move cursor to x,y +#define BEEP() printf("\a"); // terminal bell +#define BOLD() printf("\033[1m") // Cursor bold +#define FOREGROUND_COLOR(x) printf("\033[3%im", x) // Set foreground color +#define CLEAR_ATTR() printf("\033[0m") // Clear bold/color attributes +#define SCREEN_SAVE() printf("\033[?47h") // Save screen display +#define SCREEN_RESTORE() printf("\033[?47l") // Restore screen to previously saved state +#define CURSOR_SAVE() printf("\033[s") // Save cursor position +#define CURSOR_RESTORE() printf("\033[u") // Restore cursor position +#define CURSOR_HIDE() printf("\033[?25l") // Hide cursor +#define CURSOR_SHOW() printf("\033[?25h") // Unhide cursor - // Processing command arguments - while ((o = getopt(argc, argv, "f:acv")) != -1) { - switch (o) { - case 'f': - nms_set_foreground_color(optarg); - break; - case 'a': - nms_set_auto_decrypt(1); - break; - case 'c': - nms_set_clear_scr(1); - break; - case 'v': - printf("nms version " VERSION "\n"); - return 0; - case '?': - if (isprint(optopt)) - fprintf (stderr, "Unknown option '-%c'.\n", optopt); - else - fprintf (stderr, "Unknown option character '\\x%x'.\n", optopt); - return 1; - } - } +// Program settings +#define TYPE_EFFECT_SPEED 4 // miliseconds per char +#define JUMBLE_SECONDS 2 // number of seconds for jumble effect +#define JUMBLE_LOOP_SPEED 35 // miliseconds between each jumble +#define REVEAL_LOOP_SPEED 50 // miliseconds between each reveal loop +#define MASK_CHAR_COUNT 218 // Total characters in maskCharTable[] array. - // Geting input - while ((c = getchar()) != EOF) { - ++inSize; - if (inSize > inCapacity) { - inCapacity = inCapacity == 0 ? INPUT_GROWTH_FACTOR : inCapacity * INPUT_GROWTH_FACTOR; - input = realloc(input, inCapacity + 1); - } - input[inSize - 1] = c; - input[inSize] = '\0'; +// Character attribute structure, linked list. Keeps track of every +// character's attributes required for rendering and decryption effect. +struct charAttr { + char *source; + char *mask; + int width; + int is_space; + int time; + struct charAttr *next; +}; + +// Static function prototypes +static void nms_sleep(int); +static int nms_term_rows(void); +static int nms_term_cols(void); +static void nms_set_terminal(int s); +static void nms_clear_input(void); +static char nms_get_char(void); +static int nms_get_cursor_row(void); + +// NMS settings +static int foregroundColor = COLOR_BLUE; // Foreground color setting +static char *returnOpts = NULL; // Return option setting +static int autoDecrypt = 0; // Auto-decrypt flag +static int clearScr = 0; // clearScr flag +static int colorOn = 1; // Terminal color flag +static int inputPositionX = -1; // X coordinate for input position +static int inputPositionY = -1; // Y coordinate for input position + +// Character table representing the character set know as CP437 used by +// the original IBM PC - https://en.wikipedia.org/wiki/Code_page_437 +static char *maskCharTable[] = { + "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", "~", + ".", "/", ":", ";", "<", "=", ">", "?", "[", "\\", "]", "_", "{", "}", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", + "\xc3\x87", "\xc3\xbc", "\xc3\xa9", "\xc3\xa2", "\xc3\xa4", "\xc3\xa0", + "\xc3\xa5", "\xc3\xa7", "\xc3\xaa", "\xc3\xab", "\xc3\xa8", "\xc3\xaf", + "\xc3\xae", "\xc3\xac", "\xc3\x84", "\xc3\x85", "\xc3\x89", "\xc3\xa6", + "\xc3\x86", "\xc3\xb4", "\xc3\xb6", "\xc3\xb2", "\xc3\xbb", "\xc3\xb9", + "\xc3\xbf", "\xc3\x96", "\xc3\x9c", "\xc2\xa2", "\xc2\xa3", "\xc2\xa5", + "\xc6\x92", "\xc3\xa1", "\xc3\xad", "\xc3\xb3", "\xc3\xba", "\xc3\xb1", + "\xc3\x91", "\xc2\xaa", "\xc2\xba", "\xc2\xbf", "\xc2\xac", "\xc2\xbd", + "\xc2\xbc", "\xc2\xa1", "\xc2\xab", "\xc2\xbb", "\xce\xb1", "\xc3\x9f", + "\xce\x93", "\xcf\x80", "\xce\xa3", "\xcf\x83", "\xc2\xb5", "\xcf\x84", + "\xce\xa6", "\xce\x98", "\xce\xa9", "\xce\xb4", "\xcf\x86", "\xce\xb5", + "\xc2\xb1", "\xc3\xb7", "\xc2\xb0", "\xc2\xb7", "\xc2\xb2", "\xc2\xb6", + "\xe2\x8c\x90", "\xe2\x82\xa7", "\xe2\x96\x91", "\xe2\x96\x92", + "\xe2\x96\x93", "\xe2\x94\x82", "\xe2\x94\xa4", "\xe2\x95\xa1", + "\xe2\x95\xa2", "\xe2\x95\x96", "\xe2\x95\x95", "\xe2\x95\xa3", + "\xe2\x95\x91", "\xe2\x95\x97", "\xe2\x95\x9d", "\xe2\x95\x9c", + "\xe2\x95\x9b", "\xe2\x94\x90", "\xe2\x94\x94", "\xe2\x94\xb4", + "\xe2\x94\xac", "\xe2\x94\x9c", "\xe2\x94\x80", "\xe2\x94\xbc", + "\xe2\x95\x9e", "\xe2\x95\x9f", "\xe2\x95\x9a", "\xe2\x95\x94", + "\xe2\x95\xa9", "\xe2\x95\xa6", "\xe2\x95\xa0", "\xe2\x95\x90", + "\xe2\x95\xac", "\xe2\x95\xa7", "\xe2\x95\xa8", "\xe2\x95\xa4", + "\xe2\x95\xa7", "\xe2\x95\x99", "\xe2\x95\x98", "\xe2\x95\x92", + "\xe2\x95\x93", "\xe2\x95\xab", "\xe2\x95\xaa", "\xe2\x94\x98", + "\xe2\x94\x8c", "\xe2\x96\x88", "\xe2\x96\x84", "\xe2\x96\x8c", + "\xe2\x96\x90", "\xe2\x96\x80", "\xe2\x88\x9e", "\xe2\x88\xa9", + "\xe2\x89\xa1", "\xe2\x89\xa5", "\xe2\x89\xa4", "\xe2\x8c\xa0", + "\xe2\x8c\xa1", "\xe2\x89\x88", "\xe2\x88\x99", "\xe2\x88\x9a", + "\xe2\x81\xbf", "\xe2\x96\xa0" +}; + +/* + * nms_exec() - This function is passed a pointer to a character string + * and displays the contents of the string in a way that mimicks the + * "decrypting text" effect in the 1992 movie Sneakers. It returns the + * last character pressed by the user. + */ +char nms_exec(char *string) { + struct charAttr *list_pointer = NULL; + struct charAttr *list_head = NULL; + struct charAttr *list_temp = NULL; + int i, revealed = 0; + int maxRows, maxCols, curRow, curCol, origRow = 0, origCol = 0; + char ret = 0; + + // Error if we have an empty string. + if (string == NULL || string[0] == '\0') { + fprintf(stderr, "Error. Empty string.\n"); + return 0; } - //nms_set_input_position(5,5); - //nms_set_return_opts("123456"); - //nms_set_clear_scr(0); - //nms_set_color(0); + // Reassociate STDIN to the terminal is needed + if (!isatty(STDIN_FILENO) && !freopen ("/dev/tty", "r", stdin)) { + fprintf(stderr, "Error. Can't associate STDIN with terminal.\n"); + return 0; + } + + // Turn off terminal echo and line buffering + nms_set_terminal(0); - // Display characters - nms_exec(input); + // Needed for UTF-8 support + setlocale(LC_ALL, ""); - // Don't forget to free the allocated memory! - free(input); + // Get terminal window rows/cols + maxRows = nms_term_rows(); + maxCols = nms_term_cols(); - return 0; + // Seed my random number generator with the current time + srand(time(NULL)); + + // Get cursor position if we are not clearing the screen + if (!clearScr) { + origRow = nms_get_cursor_row(); + + // nms_get_cursor_row() may display output in some terminals. So + // we need to reposition the cursor to the start of the row. + CURSOR_MOVE(origRow, origCol); + } + + // Assign current row/col positions + curRow = origRow; + curCol = origCol; + + // Geting input + for (i = 0; string[i] != '\0'; ++i) { + + // Don't go beyond maxRows + if (curRow - origRow >= maxRows - 1) { + break; + } + + // Allocate memory for next list link + if (list_pointer == NULL) { + list_pointer = malloc(sizeof(struct charAttr)); + list_head = list_pointer; + } else { + list_pointer->next = malloc(sizeof(struct charAttr)); + list_pointer = list_pointer->next; + } + + // Get character's byte-length and store character. + if (mblen(&string[i], 4) > 0) { + list_pointer->source = malloc(mblen(&string[i], 4) + 1); + strncpy(list_pointer->source, &string[i], mblen(&string[i], 4)); + list_pointer->source[mblen(&string[i], 4)] = '\0'; + i += (mblen(&string[i], 4) - 1); + } else { + fprintf(stderr, "Unknown character encountered. Quitting.\n"); + nms_set_terminal(1); + return 0; + } + + // Set flag if we have a whitespace character + if (strlen(list_pointer->source) == 1 && isspace(list_pointer->source[0])) + list_pointer->is_space = 1; + else + list_pointer->is_space = 0; + + // Set initial mask chharacter + list_pointer->mask = maskCharTable[rand() % MASK_CHAR_COUNT]; + + // Set reveal time + list_pointer->time = rand() % 5000; + + // Set character column width + wchar_t widec[sizeof(list_pointer->source)] = {}; + mbstowcs(widec, list_pointer->source, sizeof(list_pointer->source)); + list_pointer->width = wcwidth(*widec); + + // Set next node to null + list_pointer->next = NULL; + + // Track row count + if (string[i] == '\n' || (curCol += list_pointer->width) > maxCols) { + curCol = 0; + curRow++; + if (curRow == maxRows + 1 && origRow > 0) { + origRow--; + curRow--; + } + } + } + + // Save terminal state, clear screen, and home/hide the cursor + if (clearScr) { + CURSOR_SAVE(); + SCREEN_SAVE(); + CLEAR_SCR(); + CURSOR_HOME(); + CURSOR_HIDE(); + } + + // Print mask characters with 'type effect' + for (list_pointer = list_head; list_pointer != NULL; list_pointer = list_pointer->next) { + + // Print mask character (or space) + if (list_pointer->is_space) { + printf("%s", list_pointer->source); + continue; + } + + // print mask character + printf("%s", list_pointer->mask); + if (list_pointer->width == 2) { + printf("%s", maskCharTable[rand() % MASK_CHAR_COUNT]); + } + + // flush output and sleep + fflush(stdout); + nms_sleep(TYPE_EFFECT_SPEED); + } + + // Flush any input up to this point + nms_clear_input(); + + // If autoDecrypt flag is set, we sleep. Otherwise require user to + // press a key to continue. + if (autoDecrypt) + sleep(1); + else + nms_get_char(); + + // Jumble loop + for (i = 0; i < (JUMBLE_SECONDS * 1000) / JUMBLE_LOOP_SPEED; ++i) { + + // Move cursor to home position + if (clearScr) { + CURSOR_HOME(); + } else { + CURSOR_MOVE(origRow, origCol); + } + + // Print new mask for all characters + for (list_pointer = list_head; list_pointer != NULL; list_pointer = list_pointer->next) { + + // Print mask character (or space) + if (list_pointer->is_space) { + printf("%s", list_pointer->source); + continue; + } + + // print new mask character + printf("%s", maskCharTable[rand() % MASK_CHAR_COUNT]); + if (list_pointer->width == 2) { + printf("%s", maskCharTable[rand() % MASK_CHAR_COUNT]); + } + } + + // flush output and sleep + fflush(stdout); + nms_sleep(JUMBLE_LOOP_SPEED); + } + + // Reveal loop + while (!revealed) { + + // Move cursor to home position + if (clearScr) { + CURSOR_HOME(); + } else { + CURSOR_MOVE(origRow, origCol); + } + + // Set revealed flag + revealed = 1; + + for (list_pointer = list_head; list_pointer != NULL; list_pointer = list_pointer->next) { + + // Print mask character (or space) + if (list_pointer->is_space) { + printf("%s", list_pointer->source); + continue; + } + + // If we still have time before the char is revealed, display the mask + if (list_pointer->time > 0) { + + // Change the mask randomly + if (list_pointer->time < 500) { + if (rand() % 3 == 0) { + list_pointer->mask = maskCharTable[rand() % MASK_CHAR_COUNT]; + } + } else { + if (rand() % 10 == 0) { + list_pointer->mask = maskCharTable[rand() % MASK_CHAR_COUNT]; + } + } + + // Print mask + printf("%s", list_pointer->mask); + + // Decrement reveal time + list_pointer->time -= REVEAL_LOOP_SPEED; + + // Unset revealed flag + revealed = 0; + } else { + + // Set bold and foreground color for character reveal + BOLD(); + if (colorOn) { + FOREGROUND_COLOR(foregroundColor); + } + + // print source character + printf("%s", list_pointer->source); + + // Unset foreground color + CLEAR_ATTR(); + } + } + + // flush output and sleep + fflush(stdout); + nms_sleep(REVEAL_LOOP_SPEED); + } + + // Flush any input up to this point + nms_clear_input(); + + // Check if user must select from a set of options + if (returnOpts != NULL && strlen(returnOpts) > 0) { + + // Position cursor if necessary + if (inputPositionY >= 0 && inputPositionX >= 0) { + CURSOR_MOVE(inputPositionY, inputPositionX); + } + + CURSOR_SHOW(); + + // Get and validate user selection + while (strchr(returnOpts, ret = nms_get_char()) == NULL) { + BEEP(); + } + + } + + // User must press a key to continue when clearSrc is set + // without returnOpts + else if (clearScr) { + nms_get_char(); + } + + // Restore screen and cursor is clearSrc is set + if (clearScr) { + SCREEN_RESTORE(); + CURSOR_SHOW(); + CURSOR_RESTORE(); + } + + // Turn on terminal echo and line buffering + nms_set_terminal(1); + + // Freeing the list. + list_pointer = list_head; + while (list_pointer != NULL) { + list_temp = list_pointer; + list_pointer = list_pointer->next; + free(list_temp->source); + free(list_temp); + } + + return ret; +} + +/* + * nms_set_foreground_color() sets the foreground color of the unencrypted + * characters as they are revealed to the color indicated by the 'color' + * argument. Valid arguments are "white", "yellow", "magenta", "blue", + * "green", "red", and "cyan". This function will default to blue if + * passed an invalid color. No value is returned. + */ +void nms_set_foreground_color(char *color) { + + if(strcmp("white", color) == 0) + foregroundColor = COLOR_WHITE; + else if(strcmp("yellow", color) == 0) + foregroundColor = COLOR_YELLOW; + else if(strcmp("black", color) == 0) + foregroundColor = COLOR_BLACK; + else if(strcmp("magenta", color) == 0) + foregroundColor = COLOR_MAGENTA; + else if(strcmp("blue", color) == 0) + foregroundColor = COLOR_BLUE; + else if(strcmp("green", color) == 0) + foregroundColor = COLOR_GREEN; + else if(strcmp("red", color) == 0) + foregroundColor = COLOR_RED; + else if(strcmp("cyan", color) == 0) + foregroundColor = COLOR_CYAN; + else + foregroundColor = COLOR_BLUE; +} + +/* + * nms_set_return_opts() takes a character sting and copies it to the + * returnOpts setting used by nms_exec(). + */ +void nms_set_return_opts(char *opts) { + returnOpts = realloc(returnOpts, strlen(opts) + 1); + strcpy(returnOpts, opts); +} + +/* + * nms_set_auto_decrypt() sets the autoDecrypt flag according to the + * true/false value of the 'setting' argument. + */ +void nms_set_auto_decrypt(int setting) { + if (setting) + autoDecrypt = 1; + else + autoDecrypt = 0; +} + +/* + * nms_set_clear_scr() sets the clearScr flag according to the + * true/false value of the 'setting' argument. + */ +void nms_set_clear_scr(int setting) { + if (setting) + clearScr = 1; + else + clearScr = 0; +} + +/* + * nms_set_color() sets the colorOn flag according to the + * true/false value of the 'setting' argument. + */ +void nms_use_color(int setting) { + if (setting) + colorOn = 1; + else + colorOn = 0; +} + +/* + * nms_set_input_position() sets the desired coordinate of the cursor in + * the terminal when accepting user input after nms_exec() reveals the + * unencrypted characters. + */ +void nms_set_input_position(int x, int y) { + if (x >= 0 && y >= 0) { + inputPositionX = x; + inputPositionY = y; + } +} + +/* + * nms_sleep() sleeps for the number of miliseconds indicated by 't'. + */ +static void nms_sleep(int t) { + struct timespec ts; + + ts.tv_sec = t / 1000; + ts.tv_nsec = (t % 1000) * 1000000; + + nanosleep(&ts, NULL); +} + +/* + * nms_term_rows() gets and returns the number of rows in the current + * terminal window. + */ +static int nms_term_rows(void) { + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + + return w.ws_row; +} + +/* + * nms_term_cols() gets and returns the number of cols in the current + * terminal window. + */ +static int nms_term_cols(void) { + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + + return w.ws_col; +} + +/* + * nms_set_terminal() turns off terminal echo and line buffering when + * passed an integer value that evaluates to true. It restores the + * original terminal values when passed an integer value that evaluates + * to false. + */ +static void nms_set_terminal(int s) { + struct termios tp; + static struct termios save; + static int state = 1; + + if (s == 0) { + if (tcgetattr(STDIN_FILENO, &tp) == -1) { + return; + } + + save = tp; + + tp.c_lflag &=(~ICANON & ~ECHO); + + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tp) == -1) { + return; + } + } else { + if (state == 0 && tcsetattr(STDIN_FILENO, TCSANOW, &save) == -1) + return; + } + + state = s; +} + +/* + * nms_clear_input() clears the input buffer of all characters up to + * the EOF character. + */ +static void nms_clear_input(void) { + int i; + + ioctl(STDIN_FILENO, FIONREAD, &i); + + while (i > 0) { + getchar(); + --i; + } +} + +/* + * nms_get_char() returns the next character in the input buffer. In the + * case of an EOF character, it blocks until the user presses a key. + */ +static char nms_get_char(void) { + struct timespec ts; + int t = 50; + char c; + + ts.tv_sec = t / 1000; + ts.tv_nsec = (t % 1000) * 1000000; + + while ((c = getchar()) == EOF) { + nanosleep(&ts, NULL); + } + + return c; +} + +/* + * nms_get_cursor_row() returns the row position of the cursor as reported + * by the terminal program via the ANSI escape code + */ +static int nms_get_cursor_row(void) { + int i, r = 0; + int row = 0; + char buf[10]; + char *cmd = "\033[6n"; + + memset(buf, 0, sizeof(buf)); + + write(STDOUT_FILENO, cmd, sizeof(cmd)); + + r = read(STDIN_FILENO, buf, sizeof(buf)); + + for (i = 0; i < r; ++i) { + if (buf[i] == 27 || buf[i] == '[') { + continue; + } + + if (buf[i] >= '0' && buf[i] <= '9') { + row = (row * 10) + (buf[i] - '0'); + } + + if (buf[i] == ';' || buf[i] == 'R' || buf[i] == 0) { + break; + } + } + + return row; } diff --git a/src/libnms.h b/src/nms.h similarity index 85% rename from src/libnms.h rename to src/nms.h index 023a311..2a87fa5 100644 --- a/src/libnms.h +++ b/src/nms.h @@ -1,12 +1,12 @@ /* - * Copyright (c) 2016 Brian Barto + * Copyright (c) 2017 Brian Barto * * This program is free software; you can redistribute it and/or modify it * under the terms of the MIT License. See LICENSE for more details. */ -#ifndef LIBNMS_H -#define LIBNMS_H 1 +#ifndef NMS_H +#define NMS_H 1 // Function prototypes char nms_exec(char *); diff --git a/src/sneakers.c b/src/sneakers.c index 20cf0fa..58c037b 100644 --- a/src/sneakers.c +++ b/src/sneakers.c @@ -1,8 +1,15 @@ +/* + * Copyright (c) 2017 Brian Barto + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the MIT License. See LICENSE for more details. + */ + #include #include #include #include -#include "libnms.h" +#include "nms.h" int main(void) { int termCols, spaces = 0;