2 * A program to nuke files from orbit.
10 #define __USE_XOPEN_EXTENDED
19 #define MAX_FILES 2048
20 #define NUM_FRAMES 100
22 #define FRAME_USEC 33333
24 static int color_ramp1[] = {1,1,1,2,2,2,3,3,3,1,1, -1};
25 static int bold_ramp1[] = {1,1,0,1,0,0,1,0,0,0,0, -1};
26 static int color_ramp2[] = {1,1,1,1,2,2,2,2,2,3,3,3,3,3,1,1,1,1,1,1, -1};
27 static int bold_ramp2[] = {1,1,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0, -1};
29 static void sighandler(int sig) {
36 static int remove_callback(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
38 int rv = remove(path);
43 static void add_file(char *name, char ***files, int *size, int *capacity)
45 // Prevent from taking too long
46 if (*size > MAX_FILES) return;
47 if (*size == *capacity) {
49 *files = realloc(*files, sizeof(char*) * (*capacity));
51 if (*size == MAX_FILES)
52 (*files)[*size] = strdup("...too many to list...");
54 (*files)[*size] = strdup(name);
58 int main(int argc, char *argv[])
60 if (argc > 1 && strcmp(argv[1], "--help") == 0) {
61 printf("nuke: Nuke files from orbit.\nUsage: nuke [files...]\n");
65 char *failure_message = "nothing was deleted";
70 int capacity = 32, num_files = 0;
71 char **files = malloc(sizeof(char*)*capacity);
72 for (int i = 1; i < argc; i++) {
73 add_file(argv[i], &files, &num_files, &capacity);
76 /* Initialize ncurses and get window dimensions */
78 char* term_type = getenv("TERM");
79 if (term_type == NULL || *term_type == '\0') {
80 term_type = "unknown";
82 FILE* term_in = fopen("/dev/tty", "r");
83 if (term_in == NULL) {
84 perror("fopen(/dev/tty)");
87 SCREEN* main_screen = newterm(term_type, stdout, term_in);
88 set_term(main_screen);
90 signal(SIGINT, sighandler);
92 size_t buf_chunk = 1024;
93 size_t buf_size = buf_chunk, buf_i = 0;
94 char *outbuf = calloc(buf_size, sizeof(char));
96 { // Read stdin if anything was piped in
98 desc.fd = STDIN_FILENO;
100 int ret = poll(&desc, 1, 50);
105 while ((line_len = getline(&line, &linebuf_size, stdin)) >= 0) {
106 if (line[line_len-1] == '\n')
107 line[--line_len] = '\0';
109 add_file(line, &files, &num_files, &capacity);
115 if (num_files == 0) {
116 add_file("<testing grounds>", &files, &num_files, &capacity);
121 init_pair(WHITE, COLOR_WHITE, COLOR_BLACK);
122 init_pair(YELLOW, COLOR_YELLOW, COLOR_BLACK);
123 init_pair(RED, COLOR_RED, COLOR_BLACK);
124 init_pair(BLUE, COLOR_BLUE, COLOR_BLACK);
125 init_pair(GREEN, COLOR_GREEN, COLOR_BLACK);
126 init_pair(BLACK, COLOR_BLACK, COLOR_BLACK);
127 init_pair(WHITE_BG, COLOR_BLACK, COLOR_WHITE);
128 init_pair(BLACK_ON_RED, COLOR_BLACK, COLOR_RED);
129 init_pair(BLACK_ON_GREEN, COLOR_BLACK, COLOR_GREEN);
131 for (int i = 0; color_ramp1[i] >= 0; i++)
132 color_ramp1[i] = COLOR_PAIR(color_ramp1[i]) | (bold_ramp1[i] ? A_BOLD : 0);
133 for (int i = 0; color_ramp2[i] >= 0; i++)
134 color_ramp2[i] = COLOR_PAIR(color_ramp2[i]) | (bold_ramp2[i] ? A_BOLD : 0);
136 getmaxyx(stdscr,rows,cols);
138 curs_set(0); /* hide text cursor */
144 for (int f = 0; f < num_files; f++) {
145 int len = strlen(files[f]);
146 if (len > max_line) max_line = len;
150 int padwidth = max_line < cols*3/4 ? max_line : cols*3/4;
151 int padheight = num_files < rows*3/4 ? num_files : rows*3/4;
152 int padframer = rows/2-padheight/2-1;
153 int padframec = cols/2-padwidth/2-1;
154 WINDOW *padframe = newwin(padheight+2, padwidth+2, padframer, padframec);
156 failure_message = "Couldn't create pad frame";
159 wattron(padframe, COLOR_PAIR(YELLOW) | A_BOLD);
161 WINDOW *pad = newpad(padheight, max_line);
163 failure_message = "Couldn't create pad";
167 /* Display confirmation */
168 attron(COLOR_PAIR(BLACK_ON_RED));
169 char *description = num_files == 1 ? " TARGET: " : " TARGETS: ";
170 mvprintw(padframer-2, cols/2 - strlen(description)/2, "%s", description);
171 attron(COLOR_PAIR(BLACK_ON_RED) | A_BLINK);
172 const char confirm[] = " FIRE NUKE? y/n ";
173 mvprintw(padframer+padheight+3, cols/2 - strlen(confirm)/2, "%s", confirm);
174 attroff(COLOR_PAIR(BLACK_ON_RED) | A_BLINK);
178 int scrollx = 0, scrolly = 0;
183 if (padheight < num_files) {
184 mvaddch(padframer, padframec+padwidth+2, ACS_UARROW);
185 mvaddch(padframer+padheight+1, padframec+padwidth+2, ACS_DARROW);
186 for (int i = 1; i <= padheight; i++)
187 mvaddch(padframer+i, padframec+padwidth+2, ACS_VLINE);
188 for (int i = scrolly*padheight/num_files; i <= scrolly*padheight/num_files+padheight*padheight/num_files; i++)
189 mvaddch(padframer+1+i, padframec+padwidth+2, ACS_CKBOARD);
192 if (wrefresh(padframe) == ERR) goto exit_failure;
193 // Redo this each time so that pad doesn't have to hold everything in memory
195 for (int f = scrolly; f < scrolly+padheight; f++) {
196 mvwprintw(pad,f-scrolly,0,"%s",files[f]);
198 if (prefresh(pad, 0, scrollx, padframer+1, padframec+1, padframer+padheight, padframec+padwidth) == ERR)
200 //prefresh(pad, scrolly, scrollx, 1, 1, 1+padheight, 1+padwidth);
201 //prefresh(pad, scrolly, scrollx, rows/2-padheight/2, cols/2-padwidth/2, rows/2+padheight/2, cols/2+padwidth/2);
207 case 'n': case 'q': case 27:
209 case KEY_DOWN: case 'j': case ' ':
212 case KEY_UP: case 'k':
215 case KEY_RIGHT: case 'l':
218 case KEY_LEFT: case 'h':
221 case 'J': case KEY_NPAGE:
222 scrolly += num_files/20+1;
224 case 'K': case KEY_PPAGE:
225 scrolly -= num_files/20+1;
227 case KEY_SRIGHT: case 'L':
230 case KEY_SLEFT: case 'H':
233 case KEY_HOME: case 'g':
236 case KEY_END: case 'G':
237 scrolly = num_files-padheight;
242 if (scrolly < 0) scrolly = 0;
243 if (scrolly > num_files-padheight) scrolly = num_files-padheight;
244 if (scrollx < 0) scrollx = 0;
245 if (scrollx > max_line-padwidth) scrollx = max_line-padwidth;
250 double zoom = .8; // percent of viewport globe will fill
252 const double targeting_time = 2.5;
253 const double anticipation = 0.5;
254 const double firing_time = 1.0;
255 const double nuking_time = .5;
256 const double aftermath = 100.0;
259 int targetr, targetc;
261 for (; t < targeting_time + anticipation + firing_time; t += 1./30.) {
265 if (get_target_pos(t, zoom, &targetr, &targetc)) {
266 // If visible draw '*' on globe
267 attron(COLOR_PAIR(YELLOW));
268 mvaddch(targetr,targetc, '*' | A_BOLD);
269 attroff(COLOR_PAIR(YELLOW));
271 draw_clouds(t, zoom);
273 double wobble = t > targeting_time ? 0. : targeting_time - t;
274 int r = targetr, c = targetc;
276 r = mix(r, rows*hillnoise(784,5.*t), wobble);
277 c = mix(c, cols*hillnoise(-784,5.*t), wobble);
279 int attr = COLOR_PAIR(RED);
281 mvaddch(r-1,c-2,ACS_ULCORNER | A_BOLD);
282 mvaddch(r-1,c-1,ACS_HLINE | A_BOLD);
283 mvaddch(r-1,c+1,ACS_HLINE | A_BOLD);
284 mvaddch(r-1,c+2,ACS_URCORNER | A_BOLD);
285 mvaddch(r+1,c+2,ACS_LRCORNER | A_BOLD);
286 mvaddch(r+1,c+1,ACS_HLINE | A_BOLD);
287 mvaddch(r+1,c-1,ACS_HLINE | A_BOLD);
288 mvaddch(r+1,c-2,ACS_LLCORNER | A_BOLD);
289 mvaddch(r,c,'X' | A_BOLD);
293 if (get_target_pos(t, zoom, &targetr, &targetc)) {
294 attron(COLOR_PAIR(RED));
295 mvaddch(targetr-1,targetc+1,'/');
296 attroff(COLOR_PAIR(RED));
297 attron(COLOR_PAIR(BLACK_ON_RED));
298 if (num_files <= 0) {
299 mvprintw(targetr-2,targetc+2,"%s", "testing grounds");
301 for (int t=0; t < num_files; t++) {
302 mvprintw(targetr-2-t,targetc+2,"%s", files[num_files-t-1]);
305 attroff(COLOR_PAIR(BLACK_ON_RED));
309 if (t > targeting_time + anticipation) {
310 double k = (t-targeting_time-anticipation)/firing_time;
311 int nuker = mix(rows, targetr, k), nukec = mix(cols/2, targetc, k);
312 attron(COLOR_PAIR(WHITE) | A_BOLD);
313 mvaddch(nuker, nukec, "A^' "[(int)(k*4.)]);
314 attroff(COLOR_PAIR(WHITE) | A_BOLD);
317 attron(COLOR_PAIR(RED));
318 mvaddch(nuker-1,nukec-1,'\\');
319 attroff(COLOR_PAIR(RED));
320 attron(COLOR_PAIR(BLACK_ON_RED));
321 mvprintw(nuker-2,nukec-2-5,"%0.3f", (targeting_time + anticipation + firing_time)-t);
322 attroff(COLOR_PAIR(BLACK_ON_RED));
329 if (ch == 'q' || ch == 27) {
330 if (t <= targeting_time + anticipation) {
339 for (int i=0; i<100; i++) {
340 draw_explosion(i, targetc, targetr);
344 if (ch == 'q' || ch == 27) {
348 usleep(8*FRAME_USEC); // flash for 8 frames
354 for (; t < targeting_time + anticipation + firing_time + nuking_time + aftermath; t += 1./30.) {
359 if (get_target_pos(t, zoom, &targetr, &targetc)) {
360 attron(COLOR_PAIR(BLACK) | A_BOLD);
361 for (int i = 31; i >= 0; i--) {
362 double rad = 4./31.*i;
363 double a = i*2*M_PI*GOLDEN_RATIO;
364 int r = targetr+(int)(rad*sin(a)/2), c = targetc+(int)(rad*cos(a));
365 if ((r-rows/2)*(r-rows/2) + (c/2-cols/4)*(c/2-cols/4) <= (zoom*rows/2)*(zoom*rows/2))
366 mvaddch(r, c, " #*."[i*4/31]);
368 attroff(COLOR_PAIR(BLACK) | A_BOLD);
370 for (double =target_lat
371 mvprintw(targetr-1,targetc-1,"****");
372 mvprintw(targetr,targetc-2, "** **");
373 mvprintw(targetr+1,targetc-1,"****");
376 draw_clouds(t, zoom);
377 if (get_target_pos(t, zoom, &targetr, &targetc)) {
379 attron(COLOR_PAIR(GREEN));
380 mvaddch(targetr-1,targetc+1,'/');
381 attroff(COLOR_PAIR(GREEN));
382 attron(COLOR_PAIR(BLACK_ON_GREEN));
383 mvprintw(targetr-2,targetc+2," DELETED ");
384 attroff(COLOR_PAIR(BLACK_ON_GREEN));
387 attron(COLOR_PAIR(BLACK_ON_GREEN) | A_BLINK);
388 const char *any_key = " PRESS ANY KEY TO EXIT ";
389 mvprintw(rows-(1.-zoom)*rows/4, cols/2-strlen(any_key)/2, any_key);
390 attroff(COLOR_PAIR(BLACK_ON_GREEN) | A_BLINK);
399 curs_set(1); /* unhide cursor */
400 endwin(); /* Exit ncurses */
401 int exit_status = EXIT_SUCCESS;
403 for (int i = 0; i < num_files; i++) {
406 if (!(dir = opendir(files[i]))) {
407 failure = remove(files[i]);
410 failure = nftw(files[i], remove_callback, 64, FTW_DEPTH | FTW_PHYS);
413 printf("%s\n", files[i]);
415 printf("Error: unable to delete %s (%s)\n", files[i], strerror(errno));
416 exit_status = EXIT_FAILURE;
423 curs_set(1); /* unhide cursor */
424 endwin(); /* Exit ncurses */
425 printf("%s\n",failure_message);