README: mention SDL2 backend
[rofl0r-concol.git] / ncconsole.c
blob96c2b75b69f44026f75339373924db490c88f337
1 /*
3 * Created on: 29.11.2010
6 * author: rofl0r
8 * License: LGPL 2.1+ with static linking exception
12 * library for sane 256 color handling in xterm
14 * without manual bookkeeping for colorpairs
19 #include "console.h"
20 #include <unistd.h>
21 #include <string.h>
22 #include <stdlib.h>
23 #include <stdarg.h>
24 #include <signal.h>
25 #include "console_keys.h"
26 #include "color_reader.h"
28 #include "ncconsole_chartab.c"
30 #define MIN_PAIR 1
31 #define MAX_PAIR (CONSOLE_COLORPAIRCOUNT-1)
32 #define MIN_COLOR 0
34 #define MIN(a, b) ((a) < (b) ? (a) : (b))
36 static const rgb_t invalid_color = RGBA_INIT(0,0,0,255);
38 #ifdef CONSOLE_DEBUG
39 static FILE* dbg = NULL;
40 #define PDEBUG(fmt, args...) do { if(dbg) fprintf(dbg, fmt, ## args); } while(0)
41 #else
42 #define PDEBUG(fmt, args...) do {} while (0)
43 #endif
45 static inline int self_hasColors(struct NcConsole* self) {
46 return self->flags & NC_HASCOLORS;
48 static inline int self_canChangeColors(struct NcConsole* self) {
49 return self->flags & NC_CANCHANGECOLORS;
51 static inline int self_hasMouse(struct NcConsole* self) {
52 return self->flags & NC_HASMOUSE;
55 static void console_savecolors(struct NcConsole* self);
56 static void console_restorecolors(struct NcConsole* self);
57 static int console_setcursescolor(struct NcConsole* self, int colornumber, rgb_t color);
58 static int console_setcolorpair(struct NcConsole* self, int pair, int fgcol, int bgcol);
59 static int console_usecolorpair(struct NcConsole* self, int pair);
61 #include <pthread.h>
62 #pragma RcB2 LINK "-lpthread"
64 static pthread_mutex_t resize_mutex;
66 static inline int console_fromthousand(int in) {
67 return in == 0 ? 0 : in == 1000 ? 255 : (in * 1000 * 1000) / 3921568;
70 static inline int console_tothousand(int in) {
71 return in == 0 ? 0 : in == 255 ? 1000 : (in * 3921568) / (1000 * 1000);
74 static inline void console_inittables(struct Console* self) {
75 struct NcConsole *con = &self->backend.nc;
76 int i;
77 for (i = 0; i <= MAX_PAIR; i++) {
78 con->colors[i] = invalid_color;
79 con->pairs[i].fgcol = -1;
80 con->pairs[i].bgcol = -1;
84 void console_init(struct Console* con) {
85 memset(con, 0, sizeof(struct Console));
86 con->backendtype = cb_ncurses;
87 pthread_mutex_init(&resize_mutex, NULL);
90 void console_cleanup(struct Console* con) {
91 struct NcConsole *self = &con->backend.nc;
92 clear();
93 refresh();
94 if (self_canChangeColors(self)) console_restorecolors(self);
95 endwin();
96 #ifdef CONSOLE_DEBUG
97 fclose(dbg);
98 #endif
99 pthread_mutex_destroy(&resize_mutex);
102 int console_getcolorcount(Console *c) {
103 return c->backend.nc.maxcolors;
106 static void console_savecolors(struct NcConsole *self) {
107 short int i;
108 short int r,g,b;
109 short int fg, bg;
110 struct color_reader cr;
111 int use_cr;
112 use_cr = self->flags & NC_SUPPORTSCOLORREADER && !color_reader_init(&cr);
113 int maxc = MIN(self->maxcolors, CONSOLE_MAXSAVECOLORS);
114 for (i = MIN_COLOR; i < maxc; i++) {
115 if(use_cr) color_reader_get_color(&cr, i, &self->org_colors[i]);
116 else {
117 color_content(i, &r, &g, &b);
118 self->org_colors[i] = RGB(console_fromthousand(r), console_fromthousand(g), console_fromthousand(b));
121 if(use_cr) color_reader_close(&cr);
122 for (i = MIN_PAIR; i < maxc+MIN_PAIR; i++) {
123 pair_content(i, &fg, &bg);
124 self->org_fgcolors[i-MIN_PAIR] = fg;
125 self->org_bgcolors[i-MIN_PAIR] = bg;
129 static void console_restorecolors(struct NcConsole *self) {
130 int i, maxc = MIN(self->maxcolors, CONSOLE_MAXSAVECOLORS);
131 for (i = MIN_COLOR; i < maxc; i++) {
132 init_color(i,
133 console_tothousand(self->org_colors[i].r),
134 console_tothousand(self->org_colors[i].g),
135 console_tothousand(self->org_colors[i].b)
138 for (i = MIN_PAIR; i < maxc+MIN_PAIR; i++) {
139 init_pair(i, self->org_fgcolors[i-MIN_PAIR], self->org_bgcolors[i-MIN_PAIR]);
141 color_set(MIN_PAIR, NULL);
144 // needs color additionally to be used by restorecolors
145 static int console_setcursescolor(struct NcConsole* self, int colornumber, rgb_t color) {
146 PDEBUG("setcursescolor: %d (%d, %d, %d)\n", colornumber, color.r, color.g, color.b);
148 if(colornumber > MAX_PAIR) return 0;
150 // we use rgb values in the range 0-0xFF, while ncurses max is 1000
151 if(!self_canChangeColors(self)) return 0;
153 int nr = console_tothousand(color.r);
154 int ng = console_tothousand(color.g);
155 int nb = console_tothousand(color.b);
157 PDEBUG("init_color: %d (%d, %d, %d)\n", colornumber+1, nr, ng, nb);
159 return init_color(colornumber+MIN_COLOR, nr, ng, nb) != ERR;
162 #include "nearestcolor.c"
164 int console_setcolor(struct Console* con, int is_fg, rgb_t mycolor) {
165 struct NcConsole *self = &con->backend.nc;
166 int i;
168 if( ! (self->flags & NC_CANCHANGECOLORS) ) {
169 int nearest = getNearestColor(mycolor);
170 if(is_fg) self->active.fgcol = nearest;
171 else self->active.bgcol = nearest;
172 return 1;
175 short* which = is_fg ? &self->active.fgcol : &self->active.bgcol;
177 PDEBUG("setcolor: (%d, %d, %d), fg: %d\n", mycolor.r, mycolor.g, mycolor.b, is_fg);
179 // see if it's the actual color...
180 if (*which >= 0) {
181 if (self->colors[*which].asInt == mycolor.asInt) return 1;
184 // this (c|sh)ould be optimized by using a hashmap
185 for (i = 0; i <= MAX_PAIR; i++) {
186 if (self->colors[i].asInt == invalid_color.asInt) {
187 self->colors[i] = mycolor;
188 if(!console_setcursescolor(self, i, mycolor))
189 PDEBUG("setting color failed\n");
190 if (i > self->maxcolor) self->maxcolor = i;
191 found:
192 *which = i;
193 PDEBUG("found at: %d\n", i);
194 return 1;
195 } else if (self->colors[i].asInt == mycolor.asInt)
196 goto found;
198 return 0; // "could not set color");
201 // sends the right "colorpair" to ncurses
202 void console_initoutput(struct Console* con) {
203 struct NcConsole *self = &con->backend.nc;
204 int i;
205 if (self->active.fgcol == -1) console_setcolor(con, 1, RGB(0xFF, 0xFF, 0xFF));
206 if (self->active.bgcol == -1) console_setcolor(con, 0, RGB(0, 0, 0));
207 if(self->lastused.fgcol == self->active.fgcol && self->lastused.bgcol == self->active.bgcol)
208 return;
210 PDEBUG("initoutput: with fg: %d, bg: %d\n", self->active.fgcol, self->active.bgcol);
212 for(i = 0; i <= MAX_PAIR; i++) {
213 if(self->pairs[i].fgcol == self->active.fgcol) {
214 if (self->pairs[i].bgcol != self->active.bgcol)
215 continue;
216 else {
217 console_usecolorpair(self, i);
218 return;
220 } else if (self->pairs[i].fgcol == -1) {
221 console_setcolorpair(self, i, self->active.fgcol, self->active.bgcol);
222 console_usecolorpair(self, i);
223 return;
226 return; // "colorpair not found");
229 static int console_setcolorpair(struct NcConsole* self, int pair, int fgcol, int bgcol) {
230 if(fgcol > MAX_PAIR || bgcol > MAX_PAIR) return 0; // "color pair is out of index");
231 if (!self_hasColors(self)) return 0;
232 PDEBUG("setcolorpair: %d (fg: %d, bg: %d)\n", pair, fgcol, bgcol);
234 self->pairs[pair].fgcol = fgcol;
235 self->pairs[pair].bgcol = bgcol;
236 return init_pair(pair+MIN_PAIR, fgcol+MIN_COLOR, bgcol+MIN_COLOR) != FALSE;
239 static int console_usecolorpair(struct NcConsole* self, int pair) {
240 if(pair > MAX_PAIR) return 0;
241 if (!self_hasColors(self)) return 0;
242 self->lastused.fgcol = self->active.fgcol;
243 self->lastused.bgcol = self->active.bgcol;
245 //if (self->lastattr) wattr_off(stdscr,self->lastattr,NULL);
246 self->lastattr = COLOR_PAIR(pair + MIN_PAIR);
247 color_set(pair + MIN_PAIR, NULL);
248 //wattr_on(stdscr, self->lastattr, NULL);
249 return 1;
252 void console_getbounds(struct Console* self, int* x, int* y) {
253 if(stdscr) {
254 getmaxyx(stdscr, self->dim.y, self->dim.x);
255 *x = self->dim.x;
256 *y = self->dim.y;
257 } else {
258 *y = -1;
259 *x = -1;
263 void console_goto(struct Console* self, int x, int y) {
264 move(y, x);
265 self->cursor.x = x;
266 self->cursor.y = y;
269 // print a char at current location
270 void console_addchar(struct Console* self, int c, unsigned int attributes) {
271 struct NcConsole *con = &self->backend.nc;
272 console_initoutput(self);
273 waddch(stdscr, c | attributes | con->lastattr);
274 //waddch(stdscr, c | attributes);
277 // prints a char and advances cursor
278 void console_printchar(struct Console* self, int c, unsigned int attributes) {
279 int maxy, maxx;
280 getmaxyx(stdscr, maxy, maxx);
281 (void) maxy;
282 int newx = self->cursor.x == maxx ? 1 : self->cursor.x + 1;
283 int newy = self->cursor.x == maxx ? self->cursor.y + 1 : self->cursor.y;
284 console_addchar(self, c, attributes);
285 console_goto(self, newx, newy);
288 void console_putchar(Console* self, int ch, int doupdate) {
289 console_addchar(self, ch, 0);
290 if(self->automove) console_advance_cursor(self, 1);
291 if(doupdate) console_refresh(self);
295 void console_printf (struct Console* con, const char* fmt, ...) {
296 console_initoutput(con);
297 char buf[512];
298 va_list ap;
299 va_start(ap, fmt);
300 ssize_t result = vsnprintf(buf, sizeof(buf), fmt, ap);
301 (void) result;
302 va_end(ap);
303 mvprintw(con->cursor.y, con->cursor.x, "%s", buf, 0);
306 void console_printfxy (struct Console* con, int x, int y, const char* fmt, ...) {
307 console_initoutput(con);
308 char buf[512];
309 va_list ap;
310 va_start(ap, fmt);
311 ssize_t result = vsnprintf(buf, sizeof(buf), fmt, ap);
312 (void) result;
313 va_end(ap);
314 mvprintw(y, x, "%s", buf, 0);
318 static int check_modifier_state(mmask_t state) {
319 int ret = 0;
320 if(state & BUTTON_SHIFT) ret |= CK_MOD_SHIFT;
321 if(state & BUTTON_ALT) ret |= CK_MOD_ALT;
322 if(state & BUTTON_CTRL) ret |= CK_MOD_CTRL;
323 return ret;
326 static int translate_event(struct Console *self, int key) {
327 int ret = CK_UNDEF;
328 if(key == -1) return ret;
329 MEVENT mouse_ev;
331 switch(key) {
332 case KEY_MOUSE:
333 if (getmouse(&mouse_ev) == ERR) {
334 ret = CK_UNDEF;
335 break;
337 ret = CK_MOUSE_EVENT;
338 self->mouse.coords.x = mouse_ev.x;
339 self->mouse.coords.y = mouse_ev.y;
340 if(mouse_ev.bstate & BUTTON1_PRESSED ||
341 mouse_ev.bstate & BUTTON2_PRESSED ||
342 mouse_ev.bstate & BUTTON3_PRESSED) {
343 self->mouse.mouse_ev = ME_BUTTON_DOWN;
344 if (mouse_ev.bstate & BUTTON1_PRESSED) self->mouse.button = MB_LEFT;
345 else if(mouse_ev.bstate & BUTTON2_PRESSED) self->mouse.button = MB_RIGHT;
346 else if(mouse_ev.bstate & BUTTON3_PRESSED) self->mouse.button = MB_MIDDLE;
347 } else if(mouse_ev.bstate & BUTTON1_RELEASED ||
348 mouse_ev.bstate & BUTTON2_RELEASED ||
349 mouse_ev.bstate & BUTTON3_RELEASED) {
350 self->mouse.mouse_ev = ME_BUTTON_UP;
351 if (mouse_ev.bstate & BUTTON1_RELEASED) self->mouse.button = MB_LEFT;
352 else if(mouse_ev.bstate & BUTTON2_RELEASED) self->mouse.button = MB_RIGHT;
353 else if(mouse_ev.bstate & BUTTON3_RELEASED) self->mouse.button = MB_MIDDLE;
354 } else if(mouse_ev.bstate & (1 << 28)) {
355 // ncurses 5.7 - 5.9 wrongly reports button up events as BUTTON5_TRIPLE_CLICKED
356 // keep current button
357 self->mouse.mouse_ev = ME_BUTTON_UP;
358 } else {
359 self->mouse.mouse_ev = ME_MOVE;
360 self->mouse.button = MB_NONE;
362 PDEBUG("x: %d, y:%d, button: %d, ev: %d, stage: %ld\n",
363 self->mouse.coords.x, self->mouse.coords.y, self->mouse.button,
364 self->mouse.mouse_ev, (long) mouse_ev.bstate);
366 ret |= check_modifier_state(mouse_ev.bstate);
367 break;
368 case KEY_RESIZE:
369 ret = CK_RESIZE_EVENT;
370 break;
371 case KEY_UP:
372 ret = CK_CURSOR_UP;
373 break;
374 case KEY_DOWN:
375 ret = CK_CURSOR_DOWN;
376 break;
377 case KEY_LEFT:
378 ret = CK_CURSOR_LEFT;
379 break;
380 case KEY_RIGHT:
381 ret = CK_CURSOR_RIGHT;
382 break;
383 case KEY_BACKSPACE: case 127:
384 ret = CK_BACKSPACE;
385 break;
386 default:
387 ret = key;
389 return ret;
392 #include <strings.h>
393 #include <sys/ioctl.h>
394 static void deal_with_resize_signal(void) {
395 struct winsize termSize;
396 pthread_mutex_lock(&resize_mutex);
397 if(ioctl(STDIN_FILENO, TIOCGWINSZ, (char *) &termSize) >= 0)
398 resizeterm((int)termSize.ws_row, (int)termSize.ws_col);
399 refresh();
400 pthread_mutex_unlock(&resize_mutex);
403 /* if no input is waiting, the value ERR (-1) is returned */
404 int console_getkey(struct Console* con) {
405 int ret = wgetch(stdscr);
406 int res = translate_event(con, ret);
407 if(res == CK_RESIZE_EVENT) deal_with_resize_signal();
408 PDEBUG("getkey res: %d\n", res);
409 return res;
412 int console_getkey_nb(struct Console* con) {
413 int ret;
414 timeout(0); // set NCURSES getch to nonblocking
415 ret = wgetch(stdscr);
416 timeout(-1); // set NCURSES getch to blocking
417 int res = translate_event(con, ret);
418 if(res == CK_RESIZE_EVENT) deal_with_resize_signal();
419 return res;
422 void console_sleep(struct Console* con, int ms) {
423 (void)con;
424 napms(ms);
426 /*The refresh and wrefresh routines (or wnoutrefresh and doupdate)
427 * must be called to get actual output to the terminal,
428 * as other routines merely manipulate data structures.*/
429 void console_refresh(struct Console* con) {
430 (void)con;
431 refresh();
434 void console_clear(struct Console* con) {
435 (void)con;
436 clear();
439 void console_blink_cursor(struct Console* self) { (void) self; }
440 void console_lock(void) {}
441 void console_unlock(void) {}
442 void console_toggle_fullscreen(struct Console* self) { (void) self; }
444 static int get_maxcolors(const char* orgterm) {
445 if(!strncmp(orgterm,"rxvt-unicode",12)) return 64;
446 return COLORS > 256 ? 256 : COLORS;
449 void console_init_graphics(Console* con, point resolution, font* fnt) {
450 (void) resolution; (void) fnt;
451 char org_term[64];
453 struct NcConsole *self = &con->backend.nc;
455 snprintf(org_term, sizeof org_term, "%s", getenv("TERM"));
457 if(!strcmp(org_term, "xterm")) {
458 setenv("TERM", "xterm-256color", 1);
459 self->flags |= NC_SUPPORTSCOLORREADER;
460 } else if(!strcmp(org_term, "rxvt-unicode")) {
461 setenv("TERM", "rxvt-unicode-256color", 1);
462 self->flags |= NC_SUPPORTSCOLORREADER;
463 } else if(!strcmp(org_term, "xterm-256color")) {
464 self->flags |= NC_SUPPORTSCOLORREADER;
468 self->active.fgcol = -1;
469 self->active.fgcol = -1;
471 self->lastattr = 0;
473 console_inittables(con);
475 initscr();
476 noecho();
477 cbreak();
478 keypad(stdscr, TRUE);
479 nonl(); // get return key events
481 // the ncurses table is apparently only initialised after initscr() oslt
482 ncurses_chartab_init();
484 #ifdef CONSOLE_DEBUG
485 dbg = fopen("console.log", "w");
486 #endif
488 if(!getenv("CONCOL_NO_COLORS")) {
489 if (has_colors()) self->flags |= NC_HASCOLORS;
491 if (self_hasColors(self) && can_change_color())
492 self->flags |= NC_CANCHANGECOLORS;
494 if (self_hasColors(self)) start_color();
495 self->maxcolors = get_maxcolors(org_term);
497 if (self_canChangeColors(self))
498 console_savecolors(self);
500 if(mousemask(ALL_MOUSE_EVENTS |
501 BUTTON1_PRESSED | BUTTON2_PRESSED | BUTTON3_PRESSED |
502 BUTTON1_RELEASED | BUTTON2_RELEASED | BUTTON3_RELEASED |
503 REPORT_MOUSE_POSITION | BUTTON_SHIFT | BUTTON_ALT | BUTTON_CTRL,
504 NULL) != (mmask_t) ERR) {
505 mouseinterval(0) /* prevent ncurses from making click events.
506 this way we always get an event for buttondown and up.
507 we won't get any mouse movement events either way. */;
508 self->flags |= NC_HASMOUSE;
510 PDEBUG("hasmouse: %d\n", self_hasMouse(self));
511 self->lastattr = 0;
513 self->maxcolor = 0;
515 self->lastused.fgcol = -1;
516 self->lastused.bgcol = -1;
518 getmaxyx(stdscr, con->dim.y, con->dim.x);
521 void console_settitle(Console *self, const char *title) {
522 (void) self;
523 (void) title;