3 * Created on: 29.11.2010
8 * License: LGPL 2.1+ with static linking exception
12 * library for sane 256 color handling in xterm
14 * without manual bookkeeping for colorpairs
25 #include "console_keys.h"
26 #include "color_reader.h"
28 #include "ncconsole_chartab.c"
31 #define MAX_PAIR (CONSOLE_COLORPAIRCOUNT-1)
34 #define MIN(a, b) ((a) < (b) ? (a) : (b))
36 static const rgb_t invalid_color
= RGBA_INIT(0,0,0,255);
39 static FILE* dbg
= NULL
;
40 #define PDEBUG(fmt, args...) do { if(dbg) fprintf(dbg, fmt, ## args); } while(0)
42 #define PDEBUG(fmt, args...) do {} while (0)
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
);
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
;
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
;
94 if (self_canChangeColors(self
)) console_restorecolors(self
);
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
) {
110 struct color_reader 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
]);
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
++) {
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
;
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
;
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...
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
;
193 PDEBUG("found at: %d\n", i
);
195 } else if (self
->colors
[i
].asInt
== mycolor
.asInt
)
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
;
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
)
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
)
217 console_usecolorpair(self
, i
);
220 } else if (self
->pairs
[i
].fgcol
== -1) {
221 console_setcolorpair(self
, i
, self
->active
.fgcol
, self
->active
.bgcol
);
222 console_usecolorpair(self
, i
);
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);
252 void console_getbounds(struct Console
* self
, int* x
, int* y
) {
254 getmaxyx(stdscr
, self
->dim
.y
, self
->dim
.x
);
263 void console_goto(struct Console
* self
, int x
, int 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
) {
280 getmaxyx(stdscr
, maxy
, maxx
);
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);
300 ssize_t result = vsnprintf(buf, sizeof(buf), fmt, 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);
311 ssize_t result = vsnprintf(buf, sizeof(buf), fmt, ap);
314 mvprintw(y, x, "%s", buf, 0);
318 static int check_modifier_state(mmask_t state
) {
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
;
326 static int translate_event(struct Console
*self
, int key
) {
328 if(key
== -1) return ret
;
333 if (getmouse(&mouse_ev
) == ERR
) {
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
;
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
);
369 ret
= CK_RESIZE_EVENT
;
375 ret
= CK_CURSOR_DOWN
;
378 ret
= CK_CURSOR_LEFT
;
381 ret
= CK_CURSOR_RIGHT
;
383 case KEY_BACKSPACE
: case 127:
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
);
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
);
412 int console_getkey_nb(struct Console
* con
) {
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();
422 void console_sleep(struct Console
* con
, int 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
) {
434 void console_clear(struct Console
* con
) {
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
;
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;
473 console_inittables(con
);
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();
485 dbg
= fopen("console.log", "w");
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
));
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
) {