2 * Copyright (C) 2024 Mikulas Patocka
4 * This file is part of Ajla.
6 * Ajla is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software
8 * Foundation, either version 3 of the License, or (at your option) any later
11 * Ajla is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along with
16 * Ajla. If not, see <https://www.gnu.org/licenses/>.
28 fn curses_init(implicit w : world, d : dhandle, h : list(handle), env : treemap(bytes, bytes)) : (world, curses);
29 fn curses_done(implicit w : world, curs : curses) : world;
31 fn curses_set_pos~inline(curs : curses, x y : int) : curses;
32 fn curses_get_pos~inline(curs : curses) : (int, int);
33 fn curses_restrict_viewport(curs : curses, x1 x2 y1 y2 : int, xo yo : int) : curses;
34 fn curses_revert_viewport(curs : curses) : curses;
35 fn curses_get_scissors(curs : curses) : rect;
36 fn curses_valid_viewport(curs : curses) : bool;
37 fn curses_test_viewport(curs : curses, x1 x2 y1 y2 : int) : bool;
38 fn curses_n_colors~inline(curs : curses) : int;
39 fn curses_set_fgcolor(curs : curses, r g b : uint16) : curses;
40 fn curses_set_bgcolor(curs : curses, r g b : uint16) : curses;
41 const curses_underline : uint8 := 1;
42 const curses_blink : uint8 := 2;
43 const curses_bold : uint8 := 4;
44 const curses_invert : uint8 := 8;
45 private const curses_acs : uint8 := 16;
46 private const curses_left_wide : uint8 := 32;
47 private const curses_right_wide : uint8 := 64;
48 fn curses_set_attributes(curs : curses, flags : uint8) : curses;
49 fn curses_set_cursor~inline(curs : curses, x y : int) : curses;
50 fn curses_print(curs : curses, str : string) : curses;
51 fn curses_set_char(curs : curses, x y : int, ch : char) : curses;
52 fn curses_fill_rect(curs : curses, x1 x2 y1 y2 : int, ch : char) : curses;
53 fn curses_recolor_rect(curs : curses, x1 x2 y1 y2 : int) : curses;
54 fn curses_hline(curs : curses, x1 : int, x2 : int, y : int, width : int) : curses;
55 fn curses_vline(curs : curses, x : int, y1 : int, y2 : int, width : int) : curses;
56 fn curses_box(curs : curses, x1 x2 y1 y2 : int, width : int) : curses;
57 fn curses_frame(curs : curses, x y : int, frame : uint16) : curses;
59 fn curses_update(w : world, curs : curses) : (world, curses);
61 fn curses_get_event(w : world, curs : curses, t : type, altmq : msgqueue(t)) : (world, curses, event);
70 type packets := list(int32);
74 const color_16_offset : ccolor := 16;
75 const color_216_offset : ccolor := 32;
84 fn cchar_equal~inline(b1 b2 : cchar) : bool := b1.ch = b2.ch and b1.fgcolor = b2.fgcolor and b1.bgcolor = b2.bgcolor and b1.attrib = b2.attrib;
85 implicit fn instance_eq_cchar~inline : class_eq(cchar) :=
90 option color_mode~flat [
96 const acs_map~lazy : array(byte, [#110000])
98 var am := array_sparse(byte, 0, [#110000]);
172 acs_map : array(byte, [#110000]);
173 prefer_acs_map : bool;
174 frame_fallback : bool;
178 underline_supported : bool;
179 blink_supported : bool;
180 bold_supported : bool;
181 altcharset_map : array(byte, [#80]);
182 have_set_attributes : bool;
183 have_right_bottom : bool;
186 buffer : array(cchar, [y, x]);
187 back_buffer : array(cchar, [y, x]);
198 old_viewport : list(viewport);
205 current_fgcolor : int;
206 current_bgcolor : int;
207 current_attributes : int;
208 cursor_visible : bool;
210 sgr_underline : bool;
214 sgr_altcharset : bool;
216 lines_redraw : int64;
217 mouse_visible : bool;
222 kbd_thread : bottom_type;
223 resize_thread : bottom_type;
224 suspend_thread : bottom_type;
225 mouse_thread : bottom_type;
227 event_buffer : list(event);
230 fn curses_hide_mouse(implicit curs : curses) : (curses, bytes, packets);
232 fn curses_string_sgr(implicit curs : curses) : (curses, bytes)
236 if curs.have_set_attributes then [
237 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_set_attributes, [
239 tc_param.num.(select(curs.sgr_underline, 0, 1)),
240 tc_param.num.(select(curs.sgr_inverse, 0, 1)),
241 tc_param.num.(select(curs.sgr_blink, 0, 1)),
243 tc_param.num.(select(curs.sgr_bold, 0, 1)),
246 tc_param.num.(select(curs.sgr_altcharset, 0, 1)),
251 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_exit_attribute_mode, empty(tc_param));
253 if curs.sgr_underline then [
254 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_enter_underline_mode, empty(tc_param));
257 if curs.sgr_inverse then [
258 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_enter_reverse_mode, empty(tc_param));
261 if curs.sgr_blink then [
262 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_enter_blink_mode, empty(tc_param));
265 if curs.sgr_bold then [
266 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_enter_bold_mode, empty(tc_param));
269 if curs.sgr_altcharset then [
270 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_enter_alt_charset_mode, empty(tc_param));
274 if not curs.cm is mono then [
275 curs.current_fgcolor := -1;
276 curs.current_bgcolor := -1;
281 fn curses_string_color(implicit curs : curses, fg bg : ccolor, attributes : int) : (curses, bytes)
285 var sgr_underline := curs.underline_supported and (attributes and curses_underline) <> 0;
286 var sgr_inverse := false;
287 if curs.cm is mono then [
288 sgr_inverse := bg > fg xor (attributes and curses_invert) <> 0;
290 if (attributes and curses_invert) <> 0 then
293 var sgr_blink := curs.blink_supported and (attributes and curses_blink) <> 0;
294 var sgr_bold := curs.bold_supported and (attributes and curses_bold) <> 0;
295 if curs.bold_supported, not curs.cm is color256, fg >= 8 then
297 var sgr_altcharset := (attributes and curses_acs) <> 0;
298 if sgr_underline <> curs.sgr_underline or
299 sgr_inverse <> curs.sgr_inverse or
300 sgr_blink <> curs.sgr_blink or
301 sgr_bold <> curs.sgr_bold or
302 sgr_altcharset <> curs.sgr_altcharset then [
303 curs.sgr_underline := sgr_underline;
304 curs.sgr_inverse := sgr_inverse;
305 curs.sgr_blink := sgr_blink;
306 curs.sgr_bold := sgr_bold;
307 curs.sgr_altcharset := sgr_altcharset;
308 s := curses_string_sgr(curs);
311 if not curs.cm is mono then [
312 if curs.current_fgcolor <> fg then [
313 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_set_a_foreground, [ tc_param.num.(select(curs.cm is color16, fg, fg and 7)) ]);
315 curs.current_fgcolor := fg;
317 if curs.current_bgcolor <> bg then [
318 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_set_a_background, [ tc_param.num.(select(curs.cm is color16, bg, bg and 7)) ]);
320 curs.current_bgcolor := bg;
323 curs.current_fgcolor := fg;
324 curs.current_bgcolor := bg;
329 fn default_16_rgb(i : int) : (int, int, int)
331 var rc := select(i bt 0, 0, 1000);
332 var gc := select(i bt 1, 0, 1000);
333 var bc := select(i bt 2, 0, 1000);
339 if rc = 0, gc = 0, bc = 0 then
340 rc, gc, bc := 1000 div 3, 1000 div 3, 1000 div 3;
345 fn rgb_to_color(cm : color_mode, r g b : uint16) : ccolor
347 if cm is color256 then [
348 if r = #0000, g = #0000, b = #0000 then return color_16_offset + 0;
349 if r = #aaaa, g = #0000, b = #0000 then return color_16_offset + 1;
350 if r = #0000, g = #aaaa, b = #0000 then return color_16_offset + 2;
351 if r = #aaaa, g = #aaaa, b = #0000 then return color_16_offset + 3;
352 if r = #0000, g = #0000, b = #aaaa then return color_16_offset + 4;
353 if r = #aaaa, g = #0000, b = #aaaa then return color_16_offset + 5;
354 if r = #0000, g = #aaaa, b = #aaaa then return color_16_offset + 6;
355 if r = #aaaa, g = #aaaa, b = #aaaa then return color_16_offset + 7;
356 if r = #5555, g = #5555, b = #5555 then return color_16_offset + 8;
357 if r = #ffff, g = #0000, b = #0000 then return color_16_offset + 9;
358 if r = #0000, g = #ffff, b = #0000 then return color_16_offset + 10;
359 if r = #ffff, g = #ffff, b = #0000 then return color_16_offset + 11;
360 if r = #0000, g = #0000, b = #ffff then return color_16_offset + 12;
361 if r = #ffff, g = #0000, b = #ffff then return color_16_offset + 13;
362 if r = #0000, g = #ffff, b = #ffff then return color_16_offset + 14;
363 if r = #ffff, g = #ffff, b = #ffff then return color_16_offset + 15;
364 var rt := (5 * r + #7fff) div #ffff;
365 var gt := (5 * g + #7fff) div #ffff;
366 var bt := (5 * b + #7fff) div #ffff;
367 //eval debug("color: " + ntos(rt) + ", " + ntos(gt) + ", " + ntos(bt));
368 return color_216_offset + rt * 36 + gt * 6 + bt;
373 var bold := r > #aaaa or g > #aaaa or b > #aaaa;
374 if r = #5555, g = #5555, b = #5555 then
376 return select(rf, 0, 1) + select(gf, 0, 2) + select(bf, 0, 4) + select(bold, 0, 8);
379 fn default_fg_color(cm : color_mode) : ccolor := rgb_to_color(cm, #aaaa, #aaaa, #aaaa);
380 fn default_bg_color(cm : color_mode) : ccolor := rgb_to_color(cm, 0, 0, 0);
382 fn curses_string_setpos(implicit curs : curses, x y : int) : (curses, bytes, packets)
385 var pkt := packets.[];
386 if x = curs.current_cx, y = curs.current_cy then
388 if curs.packet_mode then [
389 pkt := packets.[ 3, x, y ];
391 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_cursor_address, [ tc_param.num.(y), tc_param.num.(x) ]);
393 curs.current_cx := x;
394 curs.current_cy := y;
399 fn curses_prepare_for_redraw(implicit curs : curses) : curses
401 for j := 0 to curs.y do
402 for i := 0 to curs.x do
403 curs.back_buffer[j, i].ch := -2;
404 curs.lines_redraw := -1;
407 fn curses_string_cls(implicit curs : curses) : (curses, bytes, packets)
409 if curs.packet_mode then [
410 curses_prepare_for_redraw();
411 curs.cursor_visible := true;
412 return "", packets.[ 4, 1 ];
416 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_exit_attribute_mode, empty(tc_param));
418 s := curses_string_color(default_fg_color(curs.cm), default_bg_color(curs.cm), 0);
420 if termcap_has_string(curs.tc, tc_s_clear_screen) then [
421 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_clear_screen, empty(tc_param));
423 curs.current_cx := 0;
424 curs.current_cy := 0;
426 if not termcap_has_string(curs.tc, tc_s_clear_screen) or not termcap_query_bool(curs.tc, tc_b_back_color_erase) then [
427 curses_prepare_for_redraw();
429 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_cursor_normal, empty(tc_param));
431 curs.cursor_visible := true;
432 return str, packets.[];
435 fn curses_init(implicit w : world, d : dhandle, h : list(handle), env : treemap(bytes, bytes)) : (world, curses)
437 var sigtstp, stk := signal_handle("SIGTSTP");
439 var loc := locale_console_init(env);
440 var packet_mode := false;
442 var tc := exception_make(termcap, ec_sync, error_not_supported, 0, true);
444 var cm := color_mode.color16;
446 var underline_supported := false;
447 var blink_supported := false;
448 var bold_supported := false;
450 var altcharset_supported := false;
451 var altcharset_map := array_fill(byte, 0, [#80]);
453 var have_set_attributes := false;
454 var have_right_bottom := true;
455 var tilde_glitch := false;
457 var os := sysprop(SystemProperty_OS);
458 if os = SystemProperty_OS_DOS or os = SystemProperty_OS_OS2 or os = SystemProperty_OS_Windows then [
461 tc := termcap_init(d, env);
463 if not termcap_has_string(tc, tc_s_cursor_address) then
464 abort exception_make_str(int, ec_sync, error_not_supported, 0, false, "The terminal is too dumb");
466 var colors := termcap_query_number(tc, tc_n_max_colors);
468 cm := color_mode.mono;
470 termcap_has_string(tc, tc_s_set_a_foreground),
471 termcap_has_string(tc, tc_s_set_a_background) then [
472 cm := color_mode.color16;
475 termcap_has_string(tc, tc_s_set_a_foreground),
476 termcap_has_string(tc, tc_s_set_a_background),
477 termcap_query_bool(tc, tc_b_can_change),
478 not termcap_query_bool(tc, tc_b_hue_lightness_saturation),
479 termcap_has_string(tc, tc_s_initialize_color),
480 termcap_has_string(tc, tc_s_orig_colors) then [
481 cm := color_mode.color256;
484 underline_supported := true;
485 blink_supported := true;
486 bold_supported := true;
487 altcharset_supported := termcap_has_string(tc, tc_s_acs_chars);
488 if not cm is mono then [
489 var ncv := termcap_query_number(tc, tc_n_no_color_video);
491 underline_supported and= not ncv bt 1;
492 blink_supported and= not ncv bt 3;
493 bold_supported and= not ncv bt 5;
494 altcharset_supported and= not ncv bt 8;
497 if altcharset_supported then [
498 var str := termcap_query_string(tc, tc_s_acs_chars);
500 while len_greater_than(str, i + 1) do [
502 var value := str[i + 1];
503 //eval debug("acs map: " + ntos(key) + ", " + ntos(value));
505 altcharset_map[key] := value;
509 have_set_attributes := termcap_has_string(tc, tc_s_set_attributes);
510 have_right_bottom := termcap_query_bool(tc, tc_b_eat_newline_glitch);
511 tilde_glitch := termcap_query_bool(tc, tc_b_tilde_glitch);
515 var xx, yy, ox, oy := tty_size(h[0]);
516 if is_exception w then [
517 recover_world(old_w);
518 if packet_mode then [
522 xx := termcap_query_number(tc, tc_n_columns);
523 yy := termcap_query_number(tc, tc_n_lines);
524 if xx < 0 then xx := 80;
525 if yy < 0 then yy := 24;
530 var prefer_acs_map := false;
531 if not packet_mode, list_begins_with(termcap_get_term(tc), "linux") then
532 prefer_acs_map := true;
533 var frame_fallback := false;
534 if not packet_mode, list_begins_with(termcap_get_term(tc), "cygwin") then
535 frame_fallback := true;
536 if sysprop(SystemProperty_OS) = SystemProperty_OS_Windows then
537 frame_fallback := true;
538 var space := cchar.[ ch : ' ', fgcolor : default_fg_color(cm), bgcolor : default_bg_color(cm), attrib : 0 ];
539 implicit var curs := curses.[
540 packet_mode : packet_mode,
544 prefer_acs_map : prefer_acs_map,
545 frame_fallback : frame_fallback,
549 underline_supported : underline_supported,
550 blink_supported : blink_supported,
551 bold_supported : bold_supported,
552 altcharset_map : altcharset_map,
553 have_set_attributes : have_set_attributes,
554 have_right_bottom : have_right_bottom,
555 tilde_glitch : tilde_glitch,
556 buffer : array_fill(space, [y, x]),
557 back_buffer : array_fill(space, [y, x]),
559 fgcolor : default_fg_color(cm),
560 bgcolor : default_bg_color(cm),
566 scissors : rect.[ x1 : 0, y1 : 0, x2 : x, y2 : y ],
567 old_viewport : empty(viewport),
572 current_fgcolor : -1,
573 current_bgcolor : -1,
574 current_attributes : 0,
575 cursor_visible : true,
576 sgr_underline : false,
580 sgr_altcharset : false,
582 mouse_visible : false,
583 event_buffer : empty(event),
586 curs.q := msgqueue_new(event);
588 if not packet_mode then [
592 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_enter_ca_mode, empty(tc_param));
594 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_keypad_xmit, empty(tc_param));
596 s, pkt := curses_string_cls();
598 if termcap_has_mouse(curs.tc) then [
599 var esc := bytes.[ 27 ];
600 str += esc + "[?1000h";
601 str += esc + "[?1003h";
602 str += esc + "[?1006h";
604 if curs.cm is color256 then [
605 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_orig_colors, empty(tc_param));
607 for i := 0 to 16 do [
608 var rc, gc, bc := default_16_rgb(i);
609 var ccode := color_16_offset + i;
610 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_initialize_color, [ tc_param.num.(ccode), tc_param.num.(rc), tc_param.num.(gc), tc_param.num.(bc) ]);
614 var rc := r * 1000 div 5;
616 var gc := g * 1000 div 5;
618 var bc := b * 1000 div 5;
619 //eval debug("setup: " + ntos(r) + ", " + ntos(g) + ", " + ntos(b) + ": " + ntos(rc) + ", " + ntos(gc) + ", " + ntos(bc));
620 var ccode := color_216_offset + r * 36 + g * 6 + b;
621 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_initialize_color, [ tc_param.num.(ccode), tc_param.num.(rc), tc_param.num.(gc), tc_param.num.(bc) ]);
629 var s, pstr := curses_string_cls();
631 write_console_packet(curs.h[1], pstr);
634 var ev : event := event.resize.(event_resize.[ x : x, y : y ]);
635 msgqueue_send(curs.q, ev_tag_resize, ev);
637 curs.kbd_thread := event_get_keyboard(w, h[0], curs.tc, curs.loc, curs.q);
638 curs.resize_thread := event_get_resize(w, h[0], curs.q, x, y, ox, oy);
639 curs.suspend_thread := event_get_suspend(w, curs.q, sigtstp, stk);
640 curs.mouse_thread := event_get_mouse(w, h[0], curs.q);
643 fn curses_done(implicit w : world, implicit curs : curses) : world
646 var pstr := packets.[];
649 if not curs.cursor_visible then [
650 if curs.packet_mode then [
651 pstr += packets.[ 4, 1 ];
653 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_cursor_normal, empty(tc_param));
657 s, pkt := curses_hide_mouse();
660 if not curs.packet_mode then [
661 s := curses_string_color(7, 0, 0);
663 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_clear_screen, empty(tc_param));
666 s, pkt := curses_string_setpos(0, max(0, curs.y - 1));
669 if not curs.packet_mode then [
670 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_exit_attribute_mode, empty(tc_param));
672 if termcap_has_mouse(curs.tc) then [
673 var esc := bytes.[ 27 ];
674 str += esc + "[?1000l";
675 str += esc + "[?1003l";
676 str += esc + "[?1006l";
678 if curs.cm is color256 then [
679 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_orig_colors, empty(tc_param));
682 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_keypad_local, empty(tc_param));
684 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_exit_ca_mode, empty(tc_param));
688 if curs.packet_mode then [
690 write_console_packet(curs.h[1], pstr);
692 write(curs.h[1], str);
694 curs.kbd_thread := exception_make(bottom_type, ec_sync, error_record_field_not_initialized, 0, false);
695 curs.resize_thread := exception_make(bottom_type, ec_sync, error_record_field_not_initialized, 0, false);
696 curs.suspend_thread := exception_make(bottom_type, ec_sync, error_record_field_not_initialized, 0, false);
697 curs.mouse_thread := exception_make(bottom_type, ec_sync, error_record_field_not_initialized, 0, false);
698 wait_for_dereferenced();
699 if not curs.packet_mode then [
700 event_reset_handle(curs.h[0]);
704 fn curses_resize(implicit w : world, curs : curses, const x y : int) : (world, curses)
706 var space := cchar.[ ch : ' ', fgcolor : default_fg_color(curs.cm), bgcolor : default_bg_color(curs.cm), attrib : 0 ];
707 var curs2 := curses.[
708 packet_mode : curs.packet_mode,
711 acs_map : curs.acs_map,
712 prefer_acs_map : curs.prefer_acs_map,
713 frame_fallback : curs.frame_fallback,
717 underline_supported : curs.underline_supported,
718 blink_supported : curs.blink_supported,
719 bold_supported : curs.bold_supported,
720 altcharset_map : curs.altcharset_map,
721 have_set_attributes : curs.have_set_attributes,
722 have_right_bottom : curs.have_right_bottom,
723 tilde_glitch : curs.tilde_glitch,
724 buffer : array_fill(space, [y, x]),
725 back_buffer : array_fill(space, [y, x]),
727 fgcolor : curs.fgcolor,
728 bgcolor : curs.bgcolor,
729 attributes : curs.attributes,
732 scissors : rect.[ x1 : 0, y1 : 0, x2 : x, y2 : y ],
735 old_viewport : curs.old_viewport,
740 current_fgcolor : -1,
741 current_bgcolor : -1,
742 current_attributes : 0,
743 cursor_visible : curs.cursor_visible,
744 sgr_underline : false,
748 sgr_altcharset : false,
750 mouse_visible : false,
752 kbd_thread : curs.kbd_thread,
753 resize_thread : curs.resize_thread,
754 suspend_thread : curs.suspend_thread,
755 mouse_thread : curs.mouse_thread,
756 event_buffer : curs.event_buffer,
760 curs2, s, pstr := curses_string_cls(curs2);
761 if curs2.packet_mode then [
763 write_console_packet(curs.h[1], pstr);
770 fn curses_set_pos~inline(curs : curses, x y : int) : curses
779 fn curses_get_pos~inline(curs : curses) : (int, int)
781 return curs.px - curs.xo, curs.py - curs.yo;
784 fn curses_restrict_viewport(curs : curses, x1 x2 y1 y2 : int, xo yo : int) : curses
786 var old := viewport.[
787 x1 : curs.scissors.x1,
788 x2 : curs.scissors.x2,
789 y1 : curs.scissors.y1,
790 y2 : curs.scissors.y2,
794 curs.old_viewport +<= old;
799 curs.scissors := rect_intersection(curs.scissors, rect.[ x1 : x1, x2 : x2, y1 : y1, y2 : y2 ]);
805 fn curses_revert_viewport(curs : curses) : curses
807 var old := curs.old_viewport[len(curs.old_viewport) - 1];
808 curs.old_viewport := curs.old_viewport[ .. len(curs.old_viewport) - 1];
809 curs.scissors.x1 := old.x1;
810 curs.scissors.x2 := old.x2;
811 curs.scissors.y1 := old.y1;
812 curs.scissors.y2 := old.y2;
818 fn curses_get_scissors(curs : curses) : rect
820 var sc := curs.scissors;
828 fn curses_valid_viewport(curs : curses) : bool
830 return rect_is_valid(curses_get_scissors(curs));
833 fn curses_test_viewport(curs : curses, x1 x2 y1 y2 : int) : bool
835 var r := rect.[ x1 : x1, x2 : x2, y1 : y1, y2 : y2 ];
836 return rect_test_intersect(curses_get_scissors(curs), r);
839 fn curses_n_colors~inline(curs : curses) : int
841 return [ 2, 16, 256 ][ ord curs.cm ];
844 fn curses_set_fgcolor(curs : curses, r g b : uint16) : curses
846 curs.fgcolor := rgb_to_color(curs.cm, r, g, b);
850 fn curses_set_bgcolor(curs : curses, r g b : uint16) : curses
852 curs.bgcolor := rgb_to_color(curs.cm, r, g, b);
856 fn curses_set_attributes(curs : curses, flags : uint8) : curses
858 curs.attributes := flags;
862 fn curses_set_cursor~inline(curs : curses, x y : int) : curses
864 // TODO: handle the case when the cursor points out of scissors
867 if curs.cx <> x or curs.cy <> y then [
870 curs.lines_redraw bts= curs.y;
875 fn curses_print(curs : curses, str : string) : curses
877 if curs.py >= curs.scissors.y1, curs.py < curs.scissors.y2 then [
878 var scx1, scx2 := curs.scissors.x1, curs.scissors.x2;
879 for i := 0 to len(str) do [
880 if curs.px >= scx2 then
883 var cls := classify_character(ch);
886 if curs.px >= scx1 then [
888 curs.buffer[curs.py, curs.px] := cchar.[ ch : ch, fgcolor : curs.fgcolor, bgcolor : curs.bgcolor, attrib : curs.attributes ];
889 ] else if curs.packet_mode then [
890 curs.buffer[curs.py, curs.px] := cchar.[ ch : ch, fgcolor : curs.fgcolor, bgcolor : curs.bgcolor, attrib : curs.attributes ];
891 if curs.px + 1 < scx2 then
892 curs.buffer[curs.py, curs.px + 1] := cchar.[ ch : ' ', fgcolor : curs.fgcolor, bgcolor : curs.bgcolor, attrib : curs.attributes ];
894 curs.buffer[curs.py, curs.px] := cchar.[ ch : ch, fgcolor : curs.fgcolor, bgcolor : curs.bgcolor, attrib : curs.attributes or curses_left_wide ];
895 if curs.px + 1 < scx2 then
896 curs.buffer[curs.py, curs.px + 1] := cchar.[ ch : ch, fgcolor : curs.fgcolor, bgcolor : curs.bgcolor, attrib : curs.attributes or curses_right_wide ];
900 curs.lines_redraw bts= curs.py;
906 fn curses_get_char(curs : curses, x y : int) : char
910 if x >= curs.scissors.x1, x < curs.scissors.x2, y >= curs.scissors.y1, y < curs.scissors.y2 then [
911 return curs.buffer[y, x]. ch;
916 fn curses_set_char(curs : curses, x y : int, ch : char) : curses
920 if x >= curs.scissors.x1, x < curs.scissors.x2, y >= curs.scissors.y1, y < curs.scissors.y2 then [
921 curs.buffer[y, x] := cchar.[ ch : ch, fgcolor : curs.fgcolor, bgcolor : curs.bgcolor, attrib : curs.attributes ];
922 curs.lines_redraw bts= y;
927 fn curses_fill_rect(curs : curses, x1 x2 y1 y2 : int, ch : char) : curses
933 var cls := classify_character(ch);
936 x1 := max(x1, curs.scissors.x1);
937 x2 := min(x2, curs.scissors.x2);
938 y1 := max(y1, curs.scissors.y1);
939 y2 := min(y2, curs.scissors.y2);
940 for y := y1 to y2 do [
941 for x := x1 to x2 do [
942 curs.buffer[y, x] := cchar.[ ch : ch, fgcolor : curs.fgcolor, bgcolor : curs.bgcolor, attrib : curs.attributes ];
943 curs.lines_redraw bts= y;
949 fn curses_recolor_rect(curs : curses, x1 x2 y1 y2 : int) : curses
955 x1 := max(x1, curs.scissors.x1);
956 x2 := min(x2, curs.scissors.x2);
957 y1 := max(y1, curs.scissors.y1);
958 y2 := min(y2, curs.scissors.y2);
959 for y := y1 to y2 do [
960 for x := x1 to x2 do [
961 curs.buffer[y, x].fgcolor := curs.fgcolor;
962 curs.buffer[y, x].bgcolor := curs.bgcolor;
963 curs.buffer[y, x].attrib := curs.attributes or curs.buffer[y, x].attrib and (curses_acs or curses_left_wide or curses_right_wide);
964 curs.lines_redraw bts= y;
970 fn frame_desc~cache : array(uint16, [#30]) :=
971 array(uint16, [#30]).[
972 #ffff, #ffff, #ffff, #0101, #1101, #2101, #1202, #1200,
973 #2100, #2202, #0202, #2200, #2002, #1002, #2001, #1100,
974 #0011, #1011, #1110, #0111, #1010, #1111, #0121, #0212,
975 #0022, #0220, #2022, #2220, #0222, #2020, #2222, #2021,
976 #1012, #2120, #1210, #0012, #0021, #0120, #0210, #1212,
977 #2121, #1001, #0110, #ffff, #ffff, #ffff, #ffff, #ffff,
980 fn frame_uni~cache : array(uint16, [#30]) :=
981 array(uint16, [#30]).[
982 #2591, #2592, #2593, #2502, #2524, #2561, #2562, #2556,
983 #2555, #2563, #2551, #2557, #255D, #255C, #255B, #2510,
984 #2514, #2534, #252C, #251C, #2500, #253C, #255E, #255F,
985 #255A, #2554, #2569, #2566, #2560, #2550, #256C, #2567,
986 #2568, #2564, #2565, #2559, #2558, #2552, #2553, #256B,
987 #256A, #2518, #250C, #2588, #2584, #258C, #2590, #2580,
990 fn frame_to_uni~cache(frame : uint16, frame_fallback : bool) : char
992 var fd := frame_desc;
993 if frame_fallback then [
994 if (frame and #2222) <> 0 and (frame and #1111) <> 0 then [
998 for i := 0 to #30 do [
999 if fd[i] = frame then
1000 return frame_uni[i];
1005 fn curses_hline(curs : curses, x1 : int, x2 : int, y : int, width : int) : curses
1010 if width <> 1, width <> 2 then
1012 var left_line := width shl 4;
1013 var right_line := width shl 12;
1014 var full_line := left_line or right_line;
1015 if y >= curs.scissors.y1, y < curs.scissors.y2 then [
1016 x1 := max(x1, curs.scissors.x1);
1017 x2 := min(x2, curs.scissors.x2);
1018 var new_ch := frame_to_uni(full_line, curs.frame_fallback);
1019 for i := x1 to x2 do [
1020 curs.buffer[y, i] := cchar.[ ch : new_ch, fgcolor : curs.fgcolor, bgcolor : curs.bgcolor, attrib : curs.attributes ];
1021 curs.lines_redraw bts= y;
1027 fn curses_vline(curs : curses, x : int, y1 : int, y2 : int, width : int) : curses
1032 if width <> 1, width <> 2 then
1034 var up_line := width shl 8;
1035 var down_line := width;
1036 var full_line := up_line or down_line;
1037 if x >= curs.scissors.x1, x < curs.scissors.x2 then [
1038 y1 := max(y1, curs.scissors.y1);
1039 y2 := min(y2, curs.scissors.y2);
1040 var new_ch := frame_to_uni(full_line, curs.frame_fallback);
1041 for j := y1 to y2 do [
1042 curs.buffer[j, x] := cchar.[ ch : new_ch, fgcolor : curs.fgcolor, bgcolor : curs.bgcolor, attrib : curs.attributes ];
1043 curs.lines_redraw bts= j;
1049 fn curses_box(implicit curs : curses, x1 x2 y1 y2 : int, width : int) : curses
1051 if width <> 1, width <> 2 then
1053 if x1 >= x2 or y1 >= y2 then
1055 curses_hline(x1 + 1, x2, y1, width);
1056 curses_hline(x1 + 1, x2, y2, width);
1057 curses_vline(x1, y1 + 1, y2, width);
1058 curses_vline(x2, y1 + 1, y2, width);
1059 var ech11 := frame_to_uni(#0110 * width, curs.frame_fallback);
1060 var ech12 := frame_to_uni(#1100 * width, curs.frame_fallback);
1061 var ech21 := frame_to_uni(#0011 * width, curs.frame_fallback);
1062 var ech22 := frame_to_uni(#1001 * width, curs.frame_fallback);
1063 curses_set_char(x1, y1, ech11);
1064 curses_set_char(x2, y1, ech12);
1065 curses_set_char(x1, y2, ech21);
1066 curses_set_char(x2, y2, ech22);
1069 fn curses_frame(implicit curs : curses, x y : int, frame : uint16) : curses
1071 curses_set_char(x, y, frame_to_uni(frame, curs.frame_fallback));
1074 fn flip_color(c : ccolor) : ccolor
1076 if c < color_16_offset then
1077 return c and 7 xor 7;
1079 if c < color_216_offset then [
1080 c -= color_16_offset;
1081 r, g, b := default_16_rgb(c);
1082 r := r * 5 div 1000;
1083 g := g * 5 div 1000;
1084 b := b * 5 div 1000;
1086 c -= color_216_offset;
1094 return color_216_offset + r * 36 + g * 6 + b;
1097 fn is_valid_pair(const curs : curses, x y : int) : bool
1101 if x + 1 >= curs.x then
1103 var ch1 := curs.buffer[y, x];
1104 var ch2 := curs.buffer[y, x + 1];
1105 if (ch1.attrib and curses_left_wide) <> 0,
1106 (ch2.attrib and curses_right_wide) <> 0,
1107 ch1.ch = ch2.ch then
1112 fn curses_load_buffer~inline(implicit curs : curses, x y : int) : cchar
1114 var n := curs.buffer[y, x];
1115 if curs.mouse_visible, y = curs.mouse_y then
1116 if x = curs.mouse_x then
1118 else if x = curs.mouse_x - 1, (n.attrib and curses_left_wide) <> 0, is_valid_pair(x, y) then
1122 n.fgcolor := flip_color(n.fgcolor);
1123 n.bgcolor := flip_color(n.bgcolor);
1127 fn curses_packet_attribute~inline(c : cchar) : int32
1129 var fgcolor : int32 := c.fgcolor;
1130 var bgcolor : int32 := c.bgcolor;
1131 return (fgcolor and 4) shr 2 or (c.fgcolor and 10) or (c.fgcolor and 1) shl 2 or
1132 (bgcolor and 4) shl 2 or (c.bgcolor and 10) shl 4 or (c.bgcolor and 1) shl 6;
1135 fn curses_char_to_packet~inline(implicit curs : curses, n : cchar) : packets
1138 if locale_get_charset(curs.loc).mode is eightbit then [
1139 var stl := string_to_locale(curs.loc, [ n_ch ]);
1140 if len(stl) = 0 then
1145 n_ch := char_to_unicode(n_ch);
1147 return packets.[ n_ch, curses_packet_attribute(n) ];
1150 fn curses_update_char(implicit curs : curses, x y : int) : (curses, bytes, packets, int)
1156 if not curs.have_right_bottom, y = curs.y - 1 then
1157 if x = curs.x - 1 then
1158 return "", packets.[], 1;
1159 else if x = curs.x - 2, is_valid_pair(x, y) then
1160 return "", packets.[], 1;
1162 var n := curses_load_buffer(curs, x, y);
1163 if (n.attrib and curses_right_wide) <> 0, is_valid_pair(x - 1, y) then [
1165 n := curses_load_buffer(curs, x, y);
1167 curs.back_buffer[y, x] := n;
1169 if curs.packet_mode then [
1170 var pstr := packets.[ 2, x, y, 1, ] + curses_char_to_packet(n);
1174 if not locale_validate_character(curs.loc, n.ch) or curs.prefer_acs_map, n.ch < #110000 then [
1175 var a := curs.acs_map[n.ch];
1177 var b := curs.altcharset_map[a];
1179 //eval debug("remap: " + ntos(n.ch) + " -> " + ntos(a) + " -> " + ntos(b));
1181 n.attrib or= curses_acs;
1186 s, pkt := curses_string_setpos(x, y);
1188 s := curses_string_color(n.fgcolor, n.bgcolor, n.attrib);
1192 if (n.attrib and (curses_left_wide or curses_right_wide)) <> 0 then [
1193 if is_valid_pair(x, y) then [
1195 curs.back_buffer[y, x + 1] := curs.buffer[y, x + 1];
1203 if (n.attrib and curses_acs) <> 0 then [
1205 ] else if curs.tilde_glitch, n.ch = '~' then [
1207 ] else if n.ch < #20 or n.ch = #7f then [
1209 ] else if n.ch < #7f then [
1212 str += string_to_locale(curs.loc, [ n.ch ]);
1214 curs.current_cx += width;
1215 return str, packets.[], width;
1218 fn curses_finish_update(implicit curs : curses) : (curses, bytes, packets)
1221 var pstr := packets.[];
1224 var x, y := curs.cx, curs.cy;
1226 if x < 0 or x >= curs.x or y < 0 or y >= curs.y then [
1227 x := select(curs.x > 0, 0, curs.x - 1);
1228 y := select(curs.y > 0, 0, curs.y - 1);
1231 if cv <> curs.cursor_visible then [
1232 if curs.packet_mode then [
1233 pkt := packets.[ 4, select(cv, 0, 1) ];
1237 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_cursor_normal, empty(tc_param));
1240 curs.tc, s := termcap_substitute_string(curs.tc, tc_s_cursor_invisible, empty(tc_param));
1244 curs.cursor_visible := cv;
1246 s, pkt := curses_string_setpos(x, y);
1249 if not curs.packet_mode then [
1250 s := curses_string_color(7, 0, curs.current_attributes);
1256 fn curses_update_string(implicit curs : curses) : (curses, bytes, packets)
1259 var pstr := packets.[];
1262 for j := 0 to curs.y do [
1263 if not curs.lines_redraw bt j then
1265 if curs.packet_mode then [
1268 for i := 0 to curs.x do [
1269 var ch := curses_load_buffer(i, j);
1270 var cb := curs.back_buffer[j, i];
1277 if first >= 0 then [
1278 pstr += packets.[ 2, first, j, last - first ];
1279 for i := first to last do [
1280 var ch := curses_load_buffer(i, j);
1281 curs.back_buffer[j, i] := ch;
1282 pstr += curses_char_to_packet(ch);
1286 for i := 0 to curs.x do [
1287 var ch := curses_load_buffer(i, j);
1288 var cb := curs.back_buffer[j, i];
1290 if (ch.attrib and curses_right_wide) <> 0, is_valid_pair(i - 1, j) then
1293 s, pkt, width := curses_update_char~inline(i, j);
1301 s, pkt := curses_finish_update();
1304 curs.lines_redraw := 0;
1308 fn curses_update(implicit w : world, implicit curs : curses) : (world, curses)
1310 var str, pstr := curses_update_string(curs);
1311 if curs.packet_mode then [
1313 write_console_packet(curs.h[1], pstr);
1315 write(curs.h[1], str);
1319 fn curses_hide_mouse(implicit curs : curses) : (curses, bytes, packets)
1321 if curs.mouse_visible then [
1322 curs.mouse_visible := false;
1323 var s, pstr, width := curses_update_char(curs.mouse_x, curs.mouse_y);
1326 return "", packets.[];
1329 fn curses_update_soft_cursor(implicit w : world, implicit curs : curses, ev : event) : (world, curses)
1333 var pstr := packets.[];
1336 s, pkt := curses_hide_mouse();
1339 var mx := max(min(ev.mouse.x, curs.x - 1), 0);
1340 var my := max(min(ev.mouse.y, curs.y - 1), 0);
1341 curs.mouse_visible := true;
1344 s, pkt, width := curses_update_char(mx, my);
1347 if curs.packet_mode then [
1348 if len_greater_than(pstr, 0) then [
1349 s, pkt := curses_finish_update();
1353 write_console_packet(curs.h[1], pstr);
1356 if len_greater_than(str, 0) then [
1357 s, pkt := curses_finish_update();
1359 write(curs.h[1], str);
1364 fn curses_get_event(implicit w : world, implicit curs : curses, t : type, altmq : msgqueue(t)) : (world, curses, event)
1366 process_event_buffer:
1368 if len_greater_than(curs.event_buffer, 0) then [
1369 ev := curs.event_buffer[0];
1370 curs.event_buffer := curs.event_buffer[1 .. ];
1374 if curs.lines_redraw <> 0 then [
1378 var wait_for := msgqueue_any(curs.q, altmq);
1384 tag, ev := msgqueue_receive_nonblock(curs.q);
1385 if is_exception tag then
1386 goto process_event_buffer;
1387 if len_greater_than(curs.event_buffer, 0) then [
1388 var last := curs.event_buffer[len(curs.event_buffer) - 1];
1389 if ev is mouse, last is mouse, last.mouse.prev_buttons = last.mouse.buttons then
1390 curs.event_buffer := curs.event_buffer[ .. len(curs.event_buffer) - 1];
1391 if ev is keyboard, last is keyboard, ev.keyboard.key = last.keyboard.key, ev.keyboard.flags = last.keyboard.flags then [
1392 ev.keyboard.rep += last.keyboard.rep;
1393 curs.event_buffer[len(curs.event_buffer) - 1].keyboard.rep := 0;
1396 curs.event_buffer +<= ev;
1400 if ev is mouse, ev.mouse.soft_cursor then [
1401 curses_update_soft_cursor(ev);
1403 if ev is resize then [
1404 curses_resize(ev.resize.x, ev.resize.y);