1 /*
2  * Copyright (c) 2017 Brian Barto
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GPL License. See LICENSE for more details.
6  */
7 
8 /*
9  * The nmseffect module is the primary module that drives the effect
10  * execution. Most attributes, settings, and code that define the behavior
11  * is implemented here.
12  */
13 
14 #define _XOPEN_SOURCE 700
15 
16 #include <stdio.h>
17 #include <string.h>
18 #include <stdlib.h>
19 #include <unistd.h>
20 #include <ctype.h>
21 #include <time.h>
22 #include <locale.h>
23 #include <wchar.h>
24 #include "nmseffect.h"
25 #include "nmstermio.h"
26 #include "nmscharset.h"
27 
28 // Speed settings
29 #define TYPE_EFFECT_SPEED    4     // miliseconds per char
30 #define JUMBLE_SECONDS       2     // number of seconds for jumble effect
31 #define JUMBLE_LOOP_SPEED    35    // miliseconds between each jumble
32 #define REVEAL_LOOP_SPEED    50    // miliseconds between each reveal loop
33 
34 // Behavior settings
35 static char *returnOpts     = NULL;         // Return option setting
36 static int autoDecrypt      = 0;            // Auto-decrypt flag
37 static int maskBlank        = 0;            // Mask blank spaces
38 static int colorOn          = 1;            // Terminal color flag
39 static int inputPositionX   = -1;           // X coordinate for input position
40 static int inputPositionY   = -1;           // Y coordinate for input position
41 
42 // Character attribute structure, linked list. Keeps track of every
43 // character's attributes required for rendering and decryption effect.
44 struct charAttr {
45 	char *source;
46 	char *mask;
47 	int width;
48 	int is_space;
49 	int time;
50 	struct charAttr *next;
51 };
52 
53 // Static function prototypes
54 static void nmseffect_sleep(int);
55 
56 /*
57  * This function applies the data decryption effect to the character
58  * string that is provided as an argument. It returns the last character
59  * pressed by the user.
60  */
nmseffect_exec(char * string)61 char nmseffect_exec(char *string) {
62 	struct charAttr *list_pointer = NULL;
63 	struct charAttr *list_head    = NULL;
64 	struct charAttr *list_temp    = NULL;
65 	int i, revealed = 0;
66 	int maxRows, maxCols, curRow, curCol, origRow = 0, origCol = 0;
67 	char ret = 0;
68 
69 	// Error if we have an empty string.
70 	if (string == NULL || string[0] == '\0') {
71 		fprintf(stderr, "Error. Empty string.\n");
72 		return 0;
73 	}
74 
75 	// Reassociate STDIN to the terminal if needed
76 	if (!isatty(STDIN_FILENO) && !freopen ("/dev/tty", "r", stdin)) {
77 		fprintf(stderr, "Error. Can't associate STDIN with terminal.\n");
78 		return 0;
79 	}
80 
81 	// Needed for UTF-8 support
82 	setlocale(LC_ALL, "");
83 
84 	// Seed my random number generator with the current time
85 	srand(time(NULL));
86 
87 	// Initialize terminal
88 	nmstermio_init_terminal();
89 
90 	if (!nmstermio_get_clearscr()) {
91 		// Get current row position
92 		origRow = nmstermio_get_cursor_row();
93 
94 		// nmstermio_get_cursor_row() may display output in some terminals. So
95 		// we need to reposition the cursor to the start of the row, print
96 		// some blank spaces, and the reposition again.
97 		nmstermio_move_cursor(origRow, 0);
98 		nmstermio_print_string("    ");
99 		nmstermio_move_cursor(origRow, 0);
100 	}
101 
102 	// Get terminal window rows/cols
103 	maxRows = nmstermio_get_rows();
104 	maxCols = nmstermio_get_cols();
105 
106 	// Assign current row/col positions
107 	curRow = origRow;
108 	curCol = origCol;
109 
110 	// Processing input
111 	for (i = 0; string[i] != '\0'; ++i) {
112 
113 		// Don't go beyond maxRows
114 		if (curRow - origRow >= maxRows - 1) {
115 			break;
116 		}
117 
118 		// Allocate memory for next list link
119 		if (list_pointer == NULL) {
120 			list_pointer = malloc(sizeof(struct charAttr));
121 			list_head = list_pointer;
122 		} else {
123 			list_pointer->next = malloc(sizeof(struct charAttr));
124 			list_pointer = list_pointer->next;
125 		}
126 
127 		// Get character's byte-length and store character.
128 		if (mblen(&string[i], 4) > 0) {
129 			list_pointer->source = malloc(mblen(&string[i], 4) + 1);
130 			strncpy(list_pointer->source, &string[i], mblen(&string[i], 4));
131 			list_pointer->source[mblen(&string[i], 4)] = '\0';
132 			i += (mblen(&string[i], 4) - 1);
133 		} else {
134 			fprintf(stderr, "Unknown character encountered. Quitting.\n");
135 			nmstermio_restore_terminal();
136 			return 0;
137 		}
138 
139 		// Set flag if we have a whitespace character
140 		if (strlen(list_pointer->source) == 1 && isspace(list_pointer->source[0])) {
141 
142 			// If flag is enabled, mask blank spaces as well
143 			if (maskBlank && (list_pointer->source[0] == ' ')) {
144 				list_pointer->is_space = 0;
145 			} else {
146 				list_pointer->is_space = 1;
147 			}
148 
149 		} else {
150 			list_pointer->is_space = 0;
151 		}
152 
153 		// Set initial mask chharacter
154 		list_pointer->mask = nmscharset_get_random();
155 
156 		// Set reveal time
157 		list_pointer->time = rand() % 5000;
158 
159 		// Set character column width
160 		wchar_t widec[sizeof(list_pointer->source)] = {};
161 		mbstowcs(widec, list_pointer->source, sizeof(list_pointer->source));
162 		list_pointer->width = wcwidth(*widec);
163 
164 		// Set next node to null
165 		list_pointer->next = NULL;
166 
167 		// Track row count
168 		if (string[i] == '\n' || (curCol += list_pointer->width) > maxCols) {
169 			curCol = 0;
170 			curRow++;
171 			if (curRow == maxRows + 1 && origRow > 0) {
172 				origRow--;
173 				curRow--;
174 			}
175 		}
176 	}
177 
178 	// Print mask characters with 'type effect'
179 	for (list_pointer = list_head; list_pointer != NULL; list_pointer = list_pointer->next) {
180 
181 		// Print mask character (or space)
182 		if (list_pointer->is_space) {
183 			nmstermio_print_string(list_pointer->source);
184 			continue;
185 		}
186 
187 		// print mask character
188 		nmstermio_print_string(list_pointer->mask);
189 		if (list_pointer->width == 2) {
190 			nmstermio_print_string(nmscharset_get_random());
191 		}
192 
193 		// flush output and sleep
194 		nmstermio_refresh();
195 		nmseffect_sleep(TYPE_EFFECT_SPEED);
196 	}
197 
198 	// Flush any input up to this point
199 	nmstermio_clear_input();
200 
201 	// If autoDecrypt flag is set, we sleep. Otherwise require user to
202 	// press a key to continue.
203 	if (autoDecrypt)
204 		sleep(1);
205 	else
206 		nmstermio_get_char();
207 
208 	// Jumble loop
209 	for (i = 0; i < (JUMBLE_SECONDS * 1000) / JUMBLE_LOOP_SPEED; ++i) {
210 
211 		// Move cursor to start position
212 		nmstermio_move_cursor(origRow, origCol);
213 
214 		// Print new mask for all characters
215 		for (list_pointer = list_head; list_pointer != NULL; list_pointer = list_pointer->next) {
216 
217 			// Print mask character (or space)
218 			if (list_pointer->is_space) {
219 				nmstermio_print_string(list_pointer->source);
220 				continue;
221 			}
222 
223 			// print new mask character
224 			nmstermio_print_string(nmscharset_get_random());
225 			if (list_pointer->width == 2) {
226 				nmstermio_print_string(nmscharset_get_random());
227 			}
228 		}
229 
230 		// flush output and sleep
231 		nmstermio_refresh();
232 		nmseffect_sleep(JUMBLE_LOOP_SPEED);
233 	}
234 
235 	// Reveal loop
236 	while (!revealed) {
237 
238 		// Move cursor to start position
239 		nmstermio_move_cursor(origRow, origCol);
240 
241 		// Set revealed flag
242 		revealed = 1;
243 
244 		for (list_pointer = list_head; list_pointer != NULL; list_pointer = list_pointer->next) {
245 
246 			// Print mask character (or space)
247 			if (list_pointer->is_space) {
248 				nmstermio_print_string(list_pointer->source);
249 				continue;
250 			}
251 
252 			// If we still have time before the char is revealed, display the mask
253 			if (list_pointer->time > 0) {
254 
255 				// Change the mask randomly
256 				if (list_pointer->time < 500) {
257 					if (rand() % 3 == 0) {
258 						list_pointer->mask = nmscharset_get_random();
259 					}
260 				} else {
261 					if (rand() % 10 == 0) {
262 						list_pointer->mask = nmscharset_get_random();
263 					}
264 				}
265 
266 				// Print mask
267 				nmstermio_print_string(list_pointer->mask);
268 
269 				// Decrement reveal time
270 				list_pointer->time -= REVEAL_LOOP_SPEED;
271 
272 				// Unset revealed flag
273 				revealed = 0;
274 			} else {
275 
276 				// print source character
277 				nmstermio_print_reveal_string(list_pointer->source, colorOn);
278 			}
279 		}
280 
281 		// flush output and sleep
282 		nmstermio_refresh();
283 		nmseffect_sleep(REVEAL_LOOP_SPEED);
284 	}
285 
286 	// Flush any input up to this point
287 	nmstermio_clear_input();
288 
289 	// Check if user must select from a set of options
290 	if (returnOpts != NULL && strlen(returnOpts) > 0) {
291 
292 		// Position cursor if necessary
293 		if (inputPositionY >= 0 && inputPositionX >= 0) {
294 			nmstermio_move_cursor(inputPositionY, inputPositionX);
295 		}
296 
297 		nmstermio_show_cursor();
298 
299 		// Get and validate user selection
300 		while (strchr(returnOpts, ret = nmstermio_get_char()) == NULL) {
301 			nmstermio_beep();
302 		}
303 
304 	}
305 
306 	// User must press a key to continue when clearSrc is set
307 	// without returnOpts
308 	else if (nmstermio_get_clearscr()) {
309 		nmstermio_get_char();
310 	}
311 
312 	// Restore terminal
313 	nmstermio_restore_terminal();
314 
315 	// Freeing the list.
316 	list_pointer = list_head;
317 	while (list_pointer != NULL) {
318 		list_temp = list_pointer;
319 		list_pointer = list_pointer->next;
320 		free(list_temp->source);
321 		free(list_temp);
322 	}
323 
324 	return ret;
325 }
326 
327 /*
328  * Pass the 'color' argument to the nmstermio module where it will set the
329  * foreground color of the unencrypted characters as they are
330  * revealed. Valid arguments are "white", "yellow", "magenta", "blue",
331  * "green", "red", and "cyan".
332  */
nmseffect_set_foregroundcolor(char * color)333 void nmseffect_set_foregroundcolor(char *color) {
334 	nmstermio_set_foregroundcolor(color);
335 }
336 
337 /*
338  * Copy the string argument to the 'returnOpts' variable. This string is
339  * used to determine what character the user must choose from before
340  * nmseffect_exec() returns execution to the calling function. Normally
341  * this is left NULL. Use only when you want to present a menu with
342  * selection choices to the user.
343  */
nmseffect_set_returnopts(char * opts)344 void nmseffect_set_returnopts(char *opts) {
345 	returnOpts = realloc(returnOpts, strlen(opts) + 1);
346 	strcpy(returnOpts, opts);
347 }
348 
349 /*
350  * Set the autoDecrypt flag according to the true/false value of the
351  * 'setting' argument. When set to true, nmseffect_exec() will not
352  * require a key press to start the decryption effect.
353  */
nmseffect_set_autodecrypt(int setting)354 void nmseffect_set_autodecrypt(int setting) {
355 	if (setting)
356 		autoDecrypt = 1;
357 	else
358 		autoDecrypt = 0;
359 }
360 
361 /*
362  * Set the maskBlank flag according to the true/false value of the
363  * 'setting' argument. When set to true, blank spaces characters
364  * will be masked as well.
365  */
nmseffect_set_maskblank(int setting)366 void nmseffect_set_maskblank(int setting) {
367     if (setting)
368         maskBlank = 1;
369     else
370         maskBlank = 0;
371 }
372 
373 /*
374  * Pass the 'setting' argument to the nmstermio module where it will set
375  * the clearScr flag according to the true/false value. When set to true,
376  * nmseffect_exec() will clear the screen before displaying any characters.
377  */
nmseffect_set_clearscr(int setting)378 void nmseffect_set_clearscr(int setting) {
379 	nmstermio_set_clearscr(setting);
380 }
381 
382 /*
383  * Set the 'colorOn' flag according to the true/false value of the 'setting'
384  * argument. This flag tells nmseffect_exec() to use colors in it's output.
385  * This is true by default, and meant to be used only for terminals that
386  * do not support color. Though, compiling with ncurses support is perhaps
387  * a better option, as it will detect color capabilities automatically.
388  */
nmseffect_set_color(int setting)389 void nmseffect_set_color(int setting) {
390 	if (setting)
391 		colorOn = 1;
392 	else
393 		colorOn = 0;
394 }
395 
396 /*
397  * Set the desired coordinates of the cursor in the terminal window when
398  * nmseffect_exec() gets the character selection from the user that is set
399  * with nmseffect_set_returnopts().
400  */
nmseffect_set_input_position(int x,int y)401 void nmseffect_set_input_position(int x, int y) {
402 	if (x >= 0 && y >= 0) {
403 		inputPositionX = x;
404 		inputPositionY = y;
405 	}
406 }
407 
408 /*
409  * Sleep for the number of milliseconds indicated by argument 't'.
410  */
nmseffect_sleep(int t)411 static void nmseffect_sleep(int t) {
412 	struct timespec ts;
413 
414 	ts.tv_sec = t / 1000;
415 	ts.tv_nsec = (t % 1000) * 1000000;
416 
417 	nanosleep(&ts, NULL);
418 }
419