]> git.gir.st - minesVIiper.git/blob - mines.c
move fieldspec parsing into function
[minesVIiper.git] / mines.c
1 /*******************************************************************************
2 minesviiper 0.3.145926
3 By Tobias Girstmair, 2015 - 2018
4
5 ./minesviiper 16x16x40
6 (see ./minesviiper -h for full list of options)
7
8 MOUSE MODE: - left click to open and choord
9 - right click to flag/unflag
10 VI MODE: - hjkl to move cursor left/down/up/right
11 - o/space to open and choord
12 - i to flag/unflag
13 - (see `./minesviiper -h' for all keybindings)
14
15 GNU GPL v3, see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt
16 *******************************************************************************/
17
18
19 #define _POSIX_C_SOURCE 2 /*for getopt, sigaction in c99*/
20 #include <ctype.h>
21 #include <signal.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <sys/ioctl.h>
25 #include <sys/time.h>
26 #include <termios.h>
27 #include <time.h>
28 #include <unistd.h>
29
30 #include "mines.h"
31 #include "schemes.h"
32
33 #define LINE_OFFSET 3
34 #define LINES_AFTER 2
35 #define COL_OFFSET 2
36 #define BIG_MOVE 5
37 #define MOUSE_MAX 231
38
39 #define MIN(a,b) (a>b?b:a)
40 #define MAX(a,b) (a>b?a:b)
41 #define CLAMP(a,m,M) (a<m?m:(a>M?M:a))
42 #define printm(n, s) for (int _loop = 0; _loop < n; _loop++) fputs (s, stdout)
43 #define print(str) fputs (str?str:"", stdout)
44 #define EMOT(e) op.scheme->emoticons[EMOT_ ## e]
45 #define BORDER(l, c) op.scheme->border[B_ ## l][B_ ## c]
46 #define CW op.scheme->cell_width /* for brevity */
47
48 struct minefield f;
49 struct game g;
50 struct opt op;
51
52 int main (int argc, char** argv) {
53 /* start defaults */
54 f.w = 30;
55 f.h = 16;
56 f.m = 99;
57 op.scheme = &symbols_mono;
58 op.mode = FLAG;
59 /* end defaults */
60 /* parse options */
61 int optget;
62 opterr = 0; /* don't print message on unrecognized option */
63 while ((optget = getopt (argc, argv, "+hnfqcdx")) != -1) {
64 switch (optget) {
65 case 'n': op.mode = NOFLAG; break;
66 case 'f': op.mode = FLAG; break; /*default*/
67 case 'q': op.mode = QUESM; break;
68 case 'c': op.scheme = &symbols_col1; break;
69 case 'd': op.scheme = &symbols_doublewidth; break;
70 case 'h':
71 default:
72 fprintf (stderr, "%s [OPTIONS] [FIELDSPEC]\n"
73 "OPTIONS:\n"
74 " -n(o flagging)\n"
75 " -f(lagging)\n"
76 " -q(uestion marks)\n"
77 " -c(olored symbols)\n"
78 " -d(ec charset symbols)\n"
79 "FIELDSPEC:\n"
80 " WxH[xM] (width 'x' height 'x' mines)\n"
81 " defaults to 30x16x99; mines default to ~20%%\n"
82 "\n"
83 "Keybindings:\n"
84 " hjkl: move left/down/up/right\n"
85 " bduw: move to next boundary\n"
86 " ^Gg$: move to the left/bottom/top/right\n"
87 " z: center cursor on minefield\n"
88 " o: open/choord\n"
89 " i: flag/unflag\n"
90 " space:modeful cursor (either open or flag)\n"
91 " a: toggle mode for space (open/flag)\n"
92 " mX: set a mark for letter X\n"
93 " `X: move to mark X (aliased to ')\n"
94 " r: start a new game\n"
95 " q: quit\n", argv[0]);
96 _exit(optget=='h'?0:1);
97 }
98 }
99 /* end parse options*/
100 if (optind < argc) { /* parse Fieldspec */
101 if (parse_fieldspec (argv[optind])) {
102 fprintf (stderr, "FIELDSPEC: WxH[xM]"
103 " (width 'x' height 'x' mines)\n");
104 return 1;
105 }
106 }
107 /* check boundaries */
108 if (f.m > (f.w-1) * (f.h-1)) {
109 f.m = (f.w-1) * (f.h-1);
110 fprintf (stderr, "too many mines. reduced to %d.\r\n", f.m);
111 }
112 /* end check */
113
114 signal_setup();
115
116 newgame:
117 f.c = alloc_array (f.h, f.w);
118
119 g.f = 0;
120 g.t = 0;
121 g.p[0] = 0;
122 g.p[1] = 0;
123
124 int is_newgame = 1;
125 int cheatmode = 0;
126 g.s = MODE_OPEN;
127 g.o = GAME_NEW;
128 struct line_col markers[26];
129 for (int i=26; i; markers[--i].l = -1);
130
131 /* setup the screen: */
132 raw_mode(1);
133 atexit (*quit);
134 /* save cursor and switch to alternate screen */
135 printf ("\033[s\033[?47h");
136 /* reset cursor, clear screen */
137 printf ("\033[H\033[J");
138
139 /* swich charset, if necessary */
140 if (op.scheme->init_seq != NULL) print (op.scheme->init_seq);
141
142 show_minefield (NORMAL);
143
144 /* enable mouse, hide cursor */
145 printf ("\033[?1000h\033[?25l");
146
147 while (1) {
148 int action;
149 unsigned char mouse[3];
150
151 action = getch(mouse);
152 switch (action) {
153 case ' ':
154 if (g.s == MODE_OPEN ||
155 f.c[g.p[0]][g.p[1]].o == OPENED) {
156 switch (do_uncover(&is_newgame)) {
157 case GAME_LOST: goto lose;
158 case GAME_WON: goto win;
159 }
160 } else if (g.s == MODE_FLAG) {
161 flag_square (g.p[0], g.p[1]);
162 } else if (g.s == MODE_QUESM) {
163 quesm_square (g.p[0], g.p[1]);
164 }
165 break;
166 case 'a':
167 g.s = (g.s+1)%(op.mode+1);
168 show_minefield (cheatmode?SHOWMINES:NORMAL);
169 break;
170 case CTRSEQ_MOUSE_LEFT:
171 if (clicked_emoticon(mouse)) {
172 free_field ();
173 goto newgame;
174 }
175 if (screen2field_c (mouse[1]) < 0 ||
176 screen2field_c (mouse[1]) >= f.w ||
177 screen2field_l (mouse[2]) < 0 ||
178 screen2field_l (mouse[2]) >= f.h) break;
179 g.p[0] = screen2field_l (mouse[2]);
180 g.p[1] = screen2field_c (mouse[1]);
181 /* fallthrough */
182 case 'o':
183 switch (do_uncover(&is_newgame)) {
184 case GAME_LOST: goto lose;
185 case GAME_WON: goto win;
186 }
187 break;
188 case CTRSEQ_MOUSE_RIGHT:
189 if (screen2field_c (mouse[1]) < 0 ||
190 screen2field_c (mouse[1]) >= f.w ||
191 screen2field_l (mouse[2]) < 0 ||
192 screen2field_l (mouse[2]) >= f.h) break;
193 g.p[0] = screen2field_l (mouse[2]);
194 g.p[1] = screen2field_c (mouse[1]);
195 /* fallthrough */
196 case 'i': flag_square (g.p[0], g.p[1]); break;
197 case '?':quesm_square (g.p[0], g.p[1]); break;
198 case CTRSEQ_CURSOR_LEFT:
199 case 'h': move_hi (g.p[0], g.p[1]-1 ); break;
200 case CTRSEQ_CURSOR_DOWN:
201 case 'j': move_hi (g.p[0]+1, g.p[1] ); break;
202 case CTRSEQ_CURSOR_UP:
203 case 'k': move_hi (g.p[0]-1, g.p[1] ); break;
204 case CTRSEQ_CURSOR_RIGHT:
205 case 'l': move_hi (g.p[0], g.p[1]+1 ); break;
206 case 'w': to_next_boundary (g.p[0], g.p[1], '>'); break;
207 case 'b': to_next_boundary (g.p[0], g.p[1], '<'); break;
208 case 'u': to_next_boundary (g.p[0], g.p[1], '^'); break;
209 case 'd': to_next_boundary (g.p[0], g.p[1], 'v'); break;
210 case '0': /* fallthrough */
211 case '^': move_hi (g.p[0], 0 ); break;
212 case '$': move_hi (g.p[0], f.w-1 ); break;
213 case 'g': move_hi (0, g.p[1] ); break;
214 case 'G': move_hi (f.h-1, g.p[1] ); break;
215 case 'z': move_hi (f.h/2, f.w/2); break;
216 case 'm':
217 action = tolower(getch(mouse));
218 if (action < 'a' || action > 'z') break;/*out of bound*/
219 markers[action-'a'].l = g.p[0];
220 markers[action-'a'].c = g.p[1];
221 break;
222 case'\'': /* fallthrough */
223 case '`':
224 action = tolower(getch(mouse));
225 if (action < 'a' || action > 'z' /* out of bound or */
226 || markers[action-'a'].l == -1) break; /* unset */
227 move_hi (markers[action-'a'].l, markers[action-'a'].c);
228 break;
229 case 'r': /* start a new game */
230 free_field ();
231 goto newgame;
232 case 'q':
233 goto quit;
234 case '\014': /* Ctrl-L -- redraw */
235 show_minefield (NORMAL);
236 break;
237 case '\\':
238 if (is_newgame) {
239 is_newgame = 0;
240 fill_minefield (-1, -1);
241 timer_setup(1);
242 }
243 show_minefield (cheatmode?NORMAL:SHOWMINES);
244 cheatmode = !cheatmode;
245 break;
246 }
247 }
248
249 win: g.o = GAME_WON; goto endgame;
250 lose: g.o = GAME_LOST; goto endgame;
251 endgame:
252 timer_setup(0); /* stop timer */
253 show_minefield (SHOWMINES);
254 int gotaction;
255 do {
256 unsigned char mouse[3];
257 gotaction = getch(mouse);
258 if (gotaction==CTRSEQ_MOUSE_LEFT && clicked_emoticon(mouse)) {
259 free_field ();
260 goto newgame;
261 } else if (gotaction == 'r') {
262 free_field ();
263 goto newgame;
264 } else if (gotaction == 'q') {
265 goto quit;
266 }
267 } while (1);
268
269 quit:
270 return 0;
271 }
272
273 void quit (void) {
274 printf ("\033[?9l\033[?25h"); /* disable mouse, show cursor */
275 print (op.scheme->reset_seq); /* reset charset, if necessary */
276 printf ("\033[?47l\033[u"); /*revert to primary screen, restore cursor*/
277 free_field ();
278 raw_mode(0);
279 }
280
281 /* I haven't won as long as a cell exists, that
282 - I haven't opened, and
283 - is not a mine */
284 int everything_opened (void) {
285 for (int row = 0; row < f.h; row++)
286 for (int col = 0; col < f.w; col++)
287 if (f.c[row][col].o == CLOSED &&
288 f.c[row][col].m == NO_MINE ) return 0;
289 return 1;
290 }
291
292 int wait_mouse_up (int l, int c) {
293 unsigned char mouse2[3];
294 int level = 1;
295 int l2, c2;
296
297 /* show :o face */
298 move_ph (1, field2screen_c (f.w/2)-1); print (EMOT(OHH));
299
300 if (!(l < 0 || l >= f.h || c < 0 || c >= f.w)) {
301 /* show a pushed-in button if cursor is on minefield */
302 move_ph (l+LINE_OFFSET, field2screen_c(c));
303 fputs (op.scheme->mouse_highlight, stdout);
304 }
305
306 while (level > 0) {
307 if (getctrlseq (mouse2) == CTRSEQ_MOUSE) {
308 /* ignore mouse wheel events: */
309 if (mouse2[0] & 0x40) continue;
310
311 else if (mouse2[0]&3 == 3) level--; /* release event */
312 else level++; /* another button pressed */
313 }
314 }
315
316 move_ph (1, field2screen_c (f.w/2)-1); print (get_emoticon());
317
318 if (!(l < 0 || l >= f.h || c < 0 || c >= f.w)) {
319 partial_show_minefield (l, c, NORMAL);
320 }
321 c2 = screen2field_c(mouse2[1]);
322 l2 = screen2field_l(mouse2[2]);
323 return ((l2 == l) && (c2 == c));
324 }
325
326 int choord_square (int line, int col) {
327 for (int l = MAX(line-1, 0); l <= MIN(line+1, f.h-1); l++) {
328 for (int c = MAX(col-1, 0); c <= MIN(col+1, f.w-1); c++) {
329 if (f.c[l][c].f != FLAG) {
330 if (uncover_square (l, c))
331 return 1;
332 }
333 }
334 }
335
336 return 0;
337 }
338
339 int uncover_square (int l, int c) {
340 f.c[l][c].o = OPENED;
341 f.c[l][c].f = NOFLAG; /*must not be QUESM, otherwise rendering issues*/
342 partial_show_minefield (l, c, NORMAL);
343
344 if (f.c[l][c].m) {
345 f.c[l][c].m = DEATH_MINE;
346 return 1;
347 }
348
349 /* check for chording */
350 if (f.c[l][c].n == 0) {
351 for (int choord_l = -1; choord_l <= 1; choord_l++) {
352 for (int choord_c = -1; choord_c <= 1; choord_c++) {
353 int newl = l + choord_l;
354 int newc = c + choord_c;
355 if (newl >= 0 && newl < f.h &&
356 newc >= 0 && newc < f.w &&
357 f.c[newl][newc].o == CLOSED &&
358 uncover_square (newl, newc)) {
359 return 1;
360 }
361 }
362 }
363 }
364
365 return 0;
366 }
367
368 void flag_square (int l, int c) {
369 static char modechar[] = {'*', '!', '?'};
370
371 if (f.c[l][c].o != CLOSED) return;
372 /* cycle through flag/quesm/noflag: */
373 f.c[l][c].f = (f.c[l][c].f + 1) % (op.mode + 1);
374 if (f.c[l][c].f==FLAG) g.f++;
375 else if ((op.mode==FLAG && f.c[l][c].f==NOFLAG) ||
376 (op.mode==QUESM && f.c[l][c].f==QUESM)) g.f--;
377 partial_show_minefield (l, c, NORMAL);
378 move_ph (1, op.scheme->cell_width);
379 printf ("[%03d%c]", f.m - g.f, modechar[g.s]);
380 }
381
382 void quesm_square (int l, int c) {
383 /* toggle question mark / none. won't turn flags into question marks.
384 unlike flag_square, this function doesn't respect `-q'. */
385 if (f.c[l][c].o != CLOSED) return;
386 else if (f.c[l][c].f == NOFLAG) f.c[l][c].f = QUESM;
387 else if (f.c[l][c].f == QUESM) f.c[l][c].f = NOFLAG;
388 partial_show_minefield (l, c, NORMAL);
389 }
390
391 int do_uncover (int* is_newgame) {
392 if (*is_newgame) {
393 *is_newgame = 0;
394 fill_minefield (g.p[0], g.p[1]);
395 timer_setup(1);
396 }
397
398 if (f.c[g.p[0]][g.p[1]].f == FLAG ) return GAME_INPROGRESS;
399 if (f.c[g.p[0]][g.p[1]].o == CLOSED) {
400 if (uncover_square (g.p[0], g.p[1])) return GAME_LOST;
401 } else if (get_neighbours (g.p[0], g.p[1], 1) == 0) {
402 if (choord_square (g.p[0], g.p[1])) return GAME_LOST;
403 }
404 if (everything_opened()) return GAME_WON;
405
406 return GAME_INPROGRESS;
407 }
408
409 void fill_minefield (int l, int c) {
410 srand (time(0));
411 int mines_set = f.m;
412 while (mines_set) {
413 int line = rand() % f.h;
414 int col = rand() % f.w;
415
416 if (f.c[line][col].m) {
417 /* skip if field already has a mine */
418 continue;
419 } else if ((line == l) && (col == c)) {
420 /* don't put a mine on the already opened (first click) field */
421 continue;
422 } else {
423 mines_set--;
424 f.c[line][col].m = STD_MINE;
425 }
426 }
427
428 /* precalculate neighbours */
429 for (int l=0; l < f.h; l++)
430 for (int c=0; c < f.w; c++)
431 f.c[l][c].n = get_neighbours (l, c, NORMAL);
432 }
433
434 void move_ph (int line, int col) {
435 /* move printhead to zero-indexed position */
436 printf ("\033[%d;%dH", line+1, col+1);
437 }
438
439 void move_hi (int l, int c) {
440 /* move cursor and highlight to absolute coordinates */
441
442 partial_show_minefield (g.p[0], g.p[1], NORMAL);
443 /* update g.p */
444 g.p[0] = CLAMP(l, 0, f.h-1);
445 g.p[1] = CLAMP(c, 0, f.w-1);
446 move_ph (g.p[0]+LINE_OFFSET, field2screen_c(g.p[1]));
447
448 print("\033[7m"); /* reverse video */
449 partial_show_minefield (g.p[0], g.p[1], HIGHLIGHT);
450 print("\033[0m"); /* un-invert */
451 }
452
453 /* to_next_boundary(): move into the supplied direction until a change in open-
454 state or flag-state is found and move there. falls back to BIG_MOVE. */
455 #define FIND_NEXT(X, L, C, L1, C1, MAX, OP) do {\
456 new_ ## X OP ## = BIG_MOVE;\
457 for (int i = X OP 1; i > 0 && i < f.MAX-1; i OP ## OP)\
458 if (((f.c[L ][C ].o<<2) + f.c[L ][C ].f) \
459 != ((f.c[L1][C1].o<<2) + f.c[L1][C1].f)) {\
460 new_ ## X = i OP 1;\
461 break;\
462 }\
463 } while(0)
464 void to_next_boundary (int l, int c, char direction) {
465 int new_l = l;
466 int new_c = c;
467 switch (direction) {
468 case '>': FIND_NEXT(c, l, i, l, i+1, w, +); break;
469 case '<': FIND_NEXT(c, l, i, l, i-1, w, -); break;
470 case '^': FIND_NEXT(l, i, c, i-1, c, h, -); break;
471 case 'v': FIND_NEXT(l, i, c, i+1, c, h, +); break;
472 }
473
474 move_hi (new_l, new_c);
475 }
476 #undef FIND_NEXT
477
478 char* cell2schema (int l, int c, int mode) {
479 struct minecell cell = f.c[l][c];
480
481 if (mode == SHOWMINES) return (
482 cell.f == FLAG && cell.m ? op.scheme->field_flagged:
483 cell.f == FLAG && !cell.m ? op.scheme->mine_wrongf:
484 cell.m == STD_MINE ? op.scheme->mine_normal:
485 cell.m == DEATH_MINE ? op.scheme->mine_death:
486 cell.o == CLOSED ? op.scheme->field_closed:
487 /*.......................*/ op.scheme->number[f.c[l][c].n]);
488 else return (
489 cell.f == FLAG ? op.scheme->field_flagged:
490 cell.f == QUESM ? op.scheme->field_question:
491 cell.o == CLOSED ? op.scheme->field_closed:
492 cell.m == STD_MINE ? op.scheme->mine_normal:
493 cell.m == DEATH_MINE ? op.scheme->mine_death:
494 /*.......................*/ op.scheme->number[f.c[l][c].n]);
495 }
496
497 void partial_show_minefield (int l, int c, int mode) {
498 move_ph (l+LINE_OFFSET, field2screen_c(c));
499
500 print (cell2schema(l, c, mode));
501 }
502
503 char* get_emoticon(void) {
504 return g.o==GAME_WON ? EMOT(WON):
505 g.o==GAME_LOST? EMOT(DEAD):
506 EMOT(SMILE);
507 }
508
509 /* https://zserge.com/blog/c-for-loop-tricks.html */
510 #define print_line(which) \
511 for (int _break = (printf("%s", BORDER(which,LEFT)), 1); _break; \
512 _break = 0, printf("%s\r\n", BORDER(which,RIGHT)))
513 #define print_border(which, width) \
514 print_line(which) printm (width, BORDER(which,MIDDLE))
515 void show_minefield (int mode) {
516 int dtime = difftime (time(NULL), g.t)*!!g.t;
517 int half_spaces = f.w*op.scheme->cell_width/2;
518 int left_spaces = MAX(0,half_spaces-7-(f.m-g.f>999));
519 int right_spaces = MAX(0,half_spaces-6-(dtime>999));
520 static char modechar[] = {'*', '!', '?'};
521
522 move_ph (0,0);
523
524 print_border(TOP, f.w);
525 print_line(STATUS) {
526 printf("[%03d%c]%*s%s%*s[%03d]",
527 /* [ */ f.m - g.f, modechar[g.s], /* ] */
528 left_spaces,"", get_emoticon(), right_spaces,"",
529 /* [ */ dtime /* ] */);
530 }
531 print_border(DIVIDER, f.w);
532 /* main body */
533 for (int l = 0; l < f.h; l++) print_line(FIELD)
534 printm (f.w, cell2schema(l, _loop, mode));
535 print_border(BOTTOM, f.w);
536 }
537
538 int get_neighbours (int line, int col, int reduced_mode) {
539 /* counts mines surrounding a square
540 modes: 0=normal; 1=reduced */
541
542 int count = 0;
543
544 for (int l = MAX(line-1, 0); l <= MIN(line+1, f.h-1); l++) {
545 for (int c = MAX(col-1, 0); c <= MIN(col+1, f.w-1); c++) {
546 if (!l && !c) continue;
547
548 count += !!f.c[l][c].m;
549 count -= reduced_mode * f.c[l][c].f==FLAG;
550 }
551 }
552 return count;
553 }
554
555 struct minecell** alloc_array (int lines, int cols) {
556 struct minecell** a = malloc (lines * sizeof(struct minecell*));
557 if (a == NULL) return NULL;
558 for (int l = 0; l < lines; l++) {
559 a[l] = calloc (cols, sizeof(struct minecell));
560 if (a[l] == NULL) goto unalloc;
561 }
562
563 return a;
564 unalloc:
565 for (int l = 0; l < lines; l++)
566 free (a[l]);
567 return NULL;
568 }
569
570 void free_field (void) {
571 if (f.c == NULL) return;
572 for (int l = 0; l < f.h; l++) {
573 free (f.c[l]);
574 }
575
576 free (f.c);
577 f.c = NULL;
578 }
579
580 int screen2field_l (int l) {
581 return (l-LINE_OFFSET) - 1;
582 }
583 int screen2field_c (int c) {
584 /* this depends on the cell width and only works when it is 1 or 2. */
585 return (c-COL_OFFSET+1 - 2*(CW%2))/2 - CW/2;
586 }
587 int field2screen_c (int c) {
588 return (CW*c+COL_OFFSET - (CW%2));
589 }
590 int clicked_emoticon (unsigned char* mouse) {
591 return (mouse[2] == LINE_OFFSET-1 && (
592 mouse[1] == f.w+COL_OFFSET ||
593 mouse[1] == f.w+COL_OFFSET+1));
594 }
595
596 enum esc_states {
597 START,
598 ESC_SENT,
599 CSI_SENT,
600 MOUSE_EVENT,
601 };
602 int getctrlseq (unsigned char* buf) {
603 int c;
604 int state = START;
605 int offset = 0x20; /* never sends control chars as data */
606 while ((c = getchar()) != EOF) {
607 switch (state) {
608 case START:
609 switch (c) {
610 case '\033': state=ESC_SENT; break;
611 default: return c;
612 }
613 break;
614 case ESC_SENT:
615 switch (c) {
616 case '[': state=CSI_SENT; break;
617 default: return CTRSEQ_INVALID;
618 }
619 break;
620 case CSI_SENT:
621 switch (c) {
622 case 'A': return CTRSEQ_CURSOR_UP;
623 case 'B': return CTRSEQ_CURSOR_DOWN;
624 case 'C': return CTRSEQ_CURSOR_RIGHT;
625 case 'D': return CTRSEQ_CURSOR_LEFT;
626 case 'M': state=MOUSE_EVENT; break;
627 default: return CTRSEQ_INVALID;
628 }
629 break;
630 case MOUSE_EVENT:
631 buf[0] = c - offset;
632 buf[1] = getchar() - offset;
633 buf[2] = getchar() - offset;
634 return CTRSEQ_MOUSE;
635 default:
636 return CTRSEQ_INVALID;
637 }
638 }
639 return 2;
640 }
641
642 int getch(unsigned char* buf) {
643 /* returns a character, EOF, or constant for an escape/control sequence - NOT
644 compatible with the ncurses implementation of same name */
645 int action = getctrlseq(buf);
646 int l, c;
647 switch (action) {
648 case CTRSEQ_MOUSE:
649 l = screen2field_l (buf[2]);
650 c = screen2field_c (buf[1]);
651
652 if (buf[0] > 3) break; /* ignore all but left/middle/right/up */
653 int success = wait_mouse_up(l, c);
654
655 /* mouse moved while pressed: */
656 if (!success) return CTRSEQ_INVALID;
657
658 switch (buf[0]) {
659 case 0: return CTRSEQ_MOUSE_LEFT;
660 case 1: return CTRSEQ_MOUSE_MIDDLE;
661 case 2: return CTRSEQ_MOUSE_RIGHT;
662 }
663 }
664
665 return action;
666 }
667
668 int parse_fieldspec(char* str) {
669 /* parses the FIELDSPEC (WxHxM); returns 1 on error */
670 int n = sscanf (str, "%dx%dx%d", &f.w, &f.h, &f.m);
671 struct winsize w;
672 ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
673
674 /* clamp field size to terminal size and mouse maximum: */
675 if (COL_OFFSET + f.w*CW + COL_OFFSET > w.ws_col)
676 f.w = w.ws_col/CW - (COL_OFFSET+COL_OFFSET);
677 if (LINE_OFFSET + f.h + LINES_AFTER > w.ws_row)
678 f.h = w.ws_row - (LINE_OFFSET+LINES_AFTER);
679 if (COL_OFFSET + f.w*CW > MOUSE_MAX)
680 f.w = MOUSE_MAX/CW - COL_OFFSET;
681 if (LINE_OFFSET + f.h > MOUSE_MAX)
682 f.h = MOUSE_MAX - LINE_OFFSET;
683
684 if (n < 2) {
685 return 1; /* error */
686 } else if (n == 2) {
687 if (f.w < 30) f.m = f.w*f.h*.15625;
688 else f.m = f.w*f.h*.20625;
689 }
690
691 return 0;
692 }
693
694 void timer_setup (int enable) {
695 static struct itimerval tbuf;
696 tbuf.it_interval.tv_sec = 1;
697 tbuf.it_interval.tv_usec = 0;
698
699 if (enable) {
700 g.t = time(NULL);
701 tbuf.it_value.tv_sec = 1;
702 tbuf.it_value.tv_usec = 0;
703 if (setitimer(ITIMER_REAL, &tbuf, NULL) == -1) {
704 perror("setitimer");
705 exit(1);
706 }
707 } else {
708 tbuf.it_value.tv_sec = 0;
709 tbuf.it_value.tv_usec = 0;
710 if ( setitimer(ITIMER_REAL, &tbuf, NULL) == -1 ) {
711 perror("setitimer");
712 exit(1);
713 }
714 }
715
716 }
717
718 void signal_setup (void) {
719 struct sigaction saction;
720
721 saction.sa_handler = signal_handler;
722 sigemptyset(&saction.sa_mask);
723 saction.sa_flags = 0;
724 if (sigaction(SIGALRM, &saction, NULL) < 0 ) {
725 perror("SIGALRM");
726 exit(1);
727 }
728
729 if (sigaction(SIGINT, &saction, NULL) < 0 ) {
730 perror ("SIGINT");
731 exit (1);
732 }
733 }
734
735 void signal_handler (int signum) {
736 int dtime;
737 switch (signum) {
738 case SIGALRM:
739 dtime = difftime (time(NULL), g.t);
740 move_ph (1, f.w*CW-(CW%2)-3-(dtime>999));
741 printf ("[%03d]", g.t?dtime:0);
742 break;
743 case SIGINT:
744 exit(128+SIGINT);
745 }
746 }
747
748 /* http://users.csc.calpoly.edu/~phatalsk/357/lectures/code/sigalrm.c */
749 void raw_mode(int mode) {
750 static struct termios saved_term_mode;
751 struct termios raw_term_mode;
752
753 if (mode == 1) {
754 tcgetattr(STDIN_FILENO, &saved_term_mode);
755 raw_term_mode = saved_term_mode;
756 raw_term_mode.c_lflag &= ~(ICANON | ECHO);
757 raw_term_mode.c_cc[VMIN] = 1 ;
758 raw_term_mode.c_cc[VTIME] = 0;
759 tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_term_mode);
760 } else {
761 tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_term_mode);
762 }
763 }
Imprint / Impressum