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