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 const ev_tag_key := 1;
29 const ev_tag_mouse_move := 2;
30 const ev_tag_mouse_button := 3;
31 const ev_tag_redraw := 4;
32 const ev_tag_resize := 5;
33 const ev_tag_suspend := 6;
35 record event_keyboard [
50 x y prev_buttons buttons double_buttons wx wy : int;
55 keyboard : event_keyboard;
56 resize : event_resize;
57 redraw : event_redraw;
63 // the f* keys must be contiguous
64 const key_f1 : char := -1;
65 const key_f2 : char := -2;
66 const key_f3 : char := -3;
67 const key_f4 : char := -4;
68 const key_f5 : char := -5;
69 const key_f6 : char := -6;
70 const key_f7 : char := -7;
71 const key_f8 : char := -8;
72 const key_f9 : char := -9;
73 const key_f10 : char := -10;
74 const key_f11 : char := -11;
75 const key_f12 : char := -12;
76 const key_left : char := -13;
77 const key_right : char := -14;
78 const key_up : char := -15;
79 const key_down : char := -16;
80 const key_insert : char := -17;
81 const key_delete : char := -18;
82 const key_home : char := -19;
83 const key_end : char := -20;
84 const key_page_up : char := -21;
85 const key_page_down : char := -22;
86 const key_backspace : char := -23;
87 const key_enter : char := -24;
88 const key_num_5 : char := -25;
89 const key_tab : char := -26;
90 const key_esc : char := -27;
92 const key_flag_shift : int := 1;
93 const key_flag_ctrl : int := 2;
94 const key_flag_alt : int := 4;
96 const key_to_termcap : list(int) := [
125 fn event_get_keyboard~spark(w : world, h : handle, tc : termcap, loc : locale, q : msgqueue(event)) : bottom_type;
126 fn event_get_resize~spark(w : world, h : handle, q : msgqueue(event), x y ox oy : int) : bottom_type;
127 fn event_get_suspend~spark(implicit w : world, q : msgqueue(event), sigtstp : shandle, stk : stoken) : bottom_type;
128 fn event_get_mouse~spark(w : world, h : handle, q : msgqueue(event)) : bottom_type;
129 fn event_wait_for_any_key(w : world, h : handle) : world;
130 fn event_reset_handle(w : world, h : handle) : world;
138 const terminal_read_timeout : int64 := 100000;
140 fn os2_vkmap : list(char)
142 var a := fill(char, 0, #100);
144 a[14] := key_backspace;
159 a[73] := key_page_up;
165 a[81] := key_page_down;
201 a[118] := key_page_down;
203 a[132] := key_page_up;
215 a[146] := key_insert;
216 a[147] := key_delete;
219 a[153] := key_page_up;
224 a[161] := key_page_down;
225 a[162] := key_insert;
226 a[163] := key_delete;
230 fn os2_ctrlmap(p : list(int32)) : int
234 if p3 bt 0 or p3 bt 1 then
235 result or= key_flag_shift;
237 result or= key_flag_ctrl;
239 result or= key_flag_alt;
243 fn dos_vkmap : list(char)
301 fn dos_ctrlmap(p : list(int32)) : int
304 if p[2] = #00 or p[2] = #e0 then [
307 p1 >= #54 and p1 <= #5d or
308 p1 >= #87 and p1 <= #88 then
309 result or= key_flag_shift;
310 if p1 >= #5e and p1 <= #67 or
311 p1 >= #73 and p1 <= #78 or
313 p1 >= #89 and p1 <= #8a or
314 p1 >= #8e and p1 <= #94 then
315 result or= key_flag_ctrl;
317 p1 >= #10 and p1 <= #1c or
318 p1 >= #1e and p1 <= #29 or
319 p1 >= #2b and p1 <= #35 or
324 p1 >= #68 and p1 <= #71 or
325 p1 >= #78 and p1 <= #83 or
326 p1 >= #8b and p1 <= #8c or
327 p1 >= #97 and p1 <= #99 or
330 p1 >= #9f and p1 <= #a3 or
331 p1 >= #a5 and p1 <= #a6 then
332 result or= key_flag_alt;
337 fn win32_vkmap : list(char)
339 var a := fill(char, 0, #80);
340 a[#08] := key_backspace;
345 a[#21] := key_page_up;
346 a[#22] := key_page_down;
353 a[#2d] := key_insert;
354 a[#2e] := key_delete;
370 fn win32_ctrlmap(p : list(int32)) : int
374 if p3 bt 0 or p3 bt 1 then
375 result or= key_flag_alt;
376 if p3 bt 2 or p3 bt 3 then
377 result or= key_flag_ctrl;
379 result or= key_flag_shift;
383 type mouse_context := list(int64);
385 fn new_mouse_context := infinite_uninitialized(int64);
387 fn send_mouse_event(implicit w : world, implicit mouse : mouse_context, q : msgqueue(event), ev : event_mouse) : (world, mouse_context)
389 var xtag, xev := msgqueue_receive_tag_nonblock(q, ev_tag_mouse_move);
390 if not is_exception xtag then [
391 ev.wx += xev.mouse.wx;
392 ev.wy += xev.mouse.wy;
394 ev.double_buttons := 0;
395 var button_down_mask := ev.buttons and not ev.prev_buttons;
396 if button_down_mask <> 0 then [
397 var time := get_monotonic_time();
398 while button_down_mask <> 0 do [
399 var b := bsr button_down_mask;
400 button_down_mask btr= b;
401 if not is_uninitialized(mouse[b]), time - mouse[b] <= 300000 then
402 ev.double_buttons bts= b;
406 msgqueue_send(q, select(ev.prev_buttons = ev.buttons, ev_tag_mouse_button, ev_tag_mouse_move), event.mouse.(ev));
413 key_map : treemap(bytes, event_keyboard);
414 partial_map : treeset(bytes);
416 last_mouse_buttons : int;
417 mouse : mouse_context;
420 fn kbd_is_partial_locale_character(ctx : kbd_context, b : bytes) : bool
422 return not locale_validate(ctx.loc, b);
425 fn kbd_test_input(ctx : kbd_context, b : bytes) : int
429 var test := treeset_test(ctx.partial_map, b[ .. i]) or kbd_is_partial_locale_character(ctx, b[ .. i]);
431 var timer := sleep~lazy(unit_value, terminal_read_timeout);
432 if any(test, timer) then [
433 //eval debug("timeout " + ntos(i));
446 fn event_get_xterm_mouse(implicit w : world, ctx : kbd_context, mb mx my mm : int) : (world, kbd_context)
448 var xm := event_mouse.[
457 //eval debug("x: " + ntos(mx) + ", y: " + ntos(my) + ", b: " + ntos(mb));
460 if mb = 0 then buttons := 1;
461 if mb = 1 then buttons := 4;
462 if mb = 2 then buttons := 2;
463 if mb = 64 then xm.wy := -1;
464 if mb = 65 then xm.wy := 1;
465 if mb = 66 then xm.wx := -1;
466 if mb = 67 then xm.wx := 1;
467 if mb = 128 then buttons := 8;
468 if mb = 129 then buttons := 16;
469 if mb = 130 then buttons := 32;
471 if xm.wx <> 0 and mm = 0 then
475 xm.buttons := buttons;
477 xm.buttons := ctx.last_mouse_buttons and not buttons;
479 xm.buttons := ctx.last_mouse_buttons or buttons;
481 xm.prev_buttons := ctx.last_mouse_buttons;
483 ctx.last_mouse_buttons := xm.buttons;
485 ctx.mouse := send_mouse_event(ctx.mouse, ctx.q, xm);
490 fn event_get_keyboard_loop(implicit w : world, ctx : kbd_context, b : bytes) : bottom_type
493 if not len_greater_than(b, 0) then
495 var i := kbd_test_input(ctx, b);
497 if ctx.has_mouse, i >= 3 then [
498 var xm : list(event);
499 var timer := sleep~lazy(unit_value, terminal_read_timeout);
500 if b[ .. 3] = bytes.[ 27, 91, 77 ] then [
501 var mb := b[3] - ' ';
502 var mx := b[4] - ' ';
503 var my := b[5] - ' ';
504 if any(my, timer) then [
508 ctx := event_get_xterm_mouse(ctx, mb, mx, my, -1);
509 //return xm + event_get_keyboard_lazy(ctx, b[6 .. ]);
513 if b[ .. 3] = bytes.[ 27, 91, 60 ] then [
514 fn mM(b : byte) : bool := b = 'm' or b = 'M';
515 var end := list_search_fn(b, mM);
516 if any(end, timer) then [
520 //eval debug(b[3 .. end + 1]);
521 var mbs := list_break(b[3 .. end], ';');
522 var mb := ston(mbs[0]);
523 var mx := ston(mbs[1]);
524 var my := ston(mbs[2]);
525 if is_exception mb or is_exception mx or is_exception my then [
529 ctx := event_get_xterm_mouse(ctx, mb, mx, my, select(b[end] = 'M', 0, 1));
530 //return xm + event_get_keyboard_lazy(ctx, b[end + 1 .. ]);
535 var xk := treemap_search(ctx.key_map, b[ .. i]);
537 //return [ event.keyboard.(xk.j) ] + event_get_keyboard_lazy(ctx, b[i .. ]);
538 msgqueue_send(ctx.q, ev_tag_key, event.keyboard.(xk.j));
542 var start := select(b[0] = 27, 0, 1);
543 if start = 1, i = 2 then [
544 xk := treemap_search(ctx.key_map, b[start .. i]);
546 if xk.j.key <> key_esc then
547 xk.j.flags or= key_flag_alt;
548 //return [ event.keyboard.(xk.j) ] + event_get_keyboard_lazy(ctx, b[i .. ]);
549 msgqueue_send(ctx.q, ev_tag_key, event.keyboard.(xk.j));
554 if locale_validate(ctx.loc, b[start .. i]) then [
555 var uc := locale_to_string(ctx.loc, b[start .. i]);
556 if len_at_least(uc, 1) and classify_character(uc[0]) >= class_one then [
557 xk := maybe(event_keyboard).j.(event_keyboard.[ key : uc[0], flags : 0, rep : 1 ]);
559 xk.j.flags or= key_flag_alt;
560 msgqueue_send(ctx.q, ev_tag_key, event.keyboard.(xk.j));
575 fn kbd_add_mapping(ctx : kbd_context, str : bytes, ev : event_keyboard) : kbd_context
577 if treemap_test(ctx.key_map, str) then
579 for i := 0 to len(str) do
580 ctx.partial_map := treeset_set(ctx.partial_map, str[ .. i]);
581 if not is_uninitialized(ev) then
582 ctx.key_map := treemap_insert(ctx.key_map, str, ev);
586 fn do_packet_console(implicit w : world, h : handle, q : msgqueue(event), os : int, vkmap : list(char), ctrlmap : fn (list(int32)) : int) : bottom_type
588 var mouse := new_mouse_context;
590 var cached_locale := exception_make(locale, ec_sync, error_invalid_operation, 0, false);
592 var packet := read_console_packet(h);
593 //xeval debug("got packet: " + ntos(packet[0]) + ", " + ntos(packet[1]) + ", " + ntos(packet[2]) + ", " + ntos(packet[3]) + ", " + ntos(packet[4]));
594 if packet[0] = 1 then [
596 var flags : int := ctrlmap(packet);
597 if os = SystemProperty_OS_DOS or os = SystemProperty_OS_OS2 then [
598 if packet[1] = 224 and packet[2] = 13 then [
602 if packet[2] >= 32 and packet[2] <> 224 then [
606 if packet[1] >= 0, packet[1] < len(vkmap), vkmap[packet[1]] <> 0 then [
607 key := vkmap[packet[1]];
608 if key > 0 and flags = 0 then
615 if key >= 1 and key < 32 then [
617 flags or= key_flag_ctrl;
619 flags and= not key_flag_shift;
620 if key >= #7f, packet[4] <> 0 then [
621 if packet[4] <> cached_cp then [
622 cached_cp := packet[4];
623 cached_locale := locale_get(".cp" + ntos(packet[4]));
624 //eval debug("cached locale: " + select(is_exception(cached_locale), "ex", "op"));
626 if is_exception cached_locale then
628 var str := locale_to_string(cached_locale, bytes.[ key ]);
629 if len(str) <> 1 then
634 //eval debug("key=" + ntos(key) + ",flags=" + ntos(flags));
636 var ev := event.keyboard.(event_keyboard.[ key : key, flags : flags, rep : 1 ] );
637 msgqueue_send(q, ev_tag_key, ev);
638 ] else if packet[0] = 2 then [
639 var ev := event_mouse.[ x : packet[1], y : packet[2], prev_buttons : packet[3], buttons : packet[4], wx : packet[5], wy : packet[6], soft_cursor : select(packet[7] <> 0, false, true) ];
640 mouse := send_mouse_event(mouse, q, ev);
647 fn event_get_keyboard_internal(implicit w : world, h : handle, tc : termcap, loc : locale, q : msgqueue(event)) : bottom_type
649 var os := sysprop(SystemProperty_OS);
650 if os = SystemProperty_OS_DOS then [
651 var vkmap := dos_vkmap;
652 var ctrlmap := dos_ctrlmap;
653 return do_packet_console(h, q, os, vkmap, ctrlmap);
655 if os = SystemProperty_OS_OS2 then [
656 var vkmap := os2_vkmap;
657 var ctrlmap := os2_ctrlmap;
658 return do_packet_console(h, q, os, vkmap, ctrlmap);
660 if os = SystemProperty_OS_Windows then [
661 var vkmap := win32_vkmap;
662 var ctrlmap := win32_ctrlmap;
663 return do_packet_console(h, q, os, vkmap, ctrlmap);
665 stty(h, stty_flag_raw or stty_flag_noecho);
666 var ctx := kbd_context.[
670 key_map : treemap_init(bytes, event_keyboard),
671 partial_map : treeset_init(bytes),
672 has_mouse : termcap_has_mouse(tc),
673 last_mouse_buttons : 0,
674 mouse : new_mouse_context,
676 for i := 1 to len(key_to_termcap) do [
677 var str := termcap_query_string(tc, key_to_termcap[i]);
680 ctx := kbd_add_mapping(ctx, str, event_keyboard.[
686 for i := tc_s_key_f13 to tc_s_key_f49 do [
688 var str := termcap_query_string(tc, i);
691 if i < tc_s_key_f25 then
692 flags := key_flag_shift;
693 else if i < tc_s_key_f37 then
694 flags := key_flag_ctrl;
695 else if i < tc_s_key_f49 then
696 flags := key_flag_shift or key_flag_ctrl;
699 ctx := kbd_add_mapping(ctx, str, event_keyboard.[
700 key : key_f1 - (i - tc_s_key_f13) mod 12,
705 ctx := kbd_add_mapping(ctx, bytes.[ 0 ], event_keyboard.[ key : ' ', flags : key_flag_ctrl, rep : 1 ]);
706 ctx := kbd_add_mapping(ctx, bytes.[ 9 ], event_keyboard.[ key : key_tab, flags : 0, rep : 1 ]);
707 ctx := kbd_add_mapping(ctx, bytes.[ 13 ], event_keyboard.[ key : key_enter, flags : 0, rep : 1 ]);
708 ctx := kbd_add_mapping(ctx, bytes.[ 27 ], event_keyboard.[ key : key_esc, flags : 0, rep : 1 ]);
709 for i := 1 to 32 do [
710 ctx := kbd_add_mapping(ctx, bytes.[ i ], event_keyboard.[
712 flags : key_flag_ctrl,
716 if ctx.has_mouse then [
717 ctx := kbd_add_mapping(ctx, bytes.[ 27, 91, 77 ], uninitialized(event_keyboard));
718 ctx := kbd_add_mapping(ctx, bytes.[ 27, 91, 60 ], uninitialized(event_keyboard));
720 var b := read_lazy(h);
721 return event_get_keyboard_loop(ctx, b);
724 fn event_get_keyboard~spark(implicit w : world, h : handle, tc : termcap, loc : locale, q : msgqueue(event)) : bottom_type
726 var err := event_get_keyboard_internal(h, tc, loc, q);
728 var err_event := exception_copy(event, err);
729 msgqueue_send(q, ev_tag_key, err_event);
734 fn event_get_resize~spark(implicit w : world, h : handle, q : msgqueue(event), x y ox oy : int) : bottom_type
736 var os := sysprop(SystemProperty_OS);
737 if os = SystemProperty_OS_DOS or os = SystemProperty_OS_OS2 then
739 var sigwinch, stoken := signal_handle("SIGWINCH");
741 stoken := signal_prepare(sigwinch);
742 var nx, ny, nox, noy := tty_size(h);
743 if nx <> x or ny <> y or nox <> ox or noy <> oy then [
744 x, y, ox, oy := nx, ny, nox, noy;
745 var ev : event := event.resize.(event_resize.[ x : x, y : y ]);
748 xtag, xev := msgqueue_receive_tag_nonblock(q, ev_tag_redraw);
749 xtag, xev := msgqueue_receive_tag_nonblock(q, ev_tag_resize);
750 msgqueue_send(q, ev_tag_resize, ev);
752 if os = SystemProperty_OS_Windows then [
755 signal_wait(sigwinch, stoken);
763 fn event_get_suspend~spark(implicit w : world, q : msgqueue(event), sigtstp : shandle, stk : stoken) : bottom_type
765 var os := sysprop(SystemProperty_OS);
766 if os = SystemProperty_OS_DOS or
767 os = SystemProperty_OS_OS2 or
768 os = SystemProperty_OS_Windows then
770 if os = SystemProperty_OS_Cygwin then [
772 signal_wait(sigtstp, stk);
773 stk := signal_prepare(sigtstp);
778 signal_wait(sigtstp, stk);
779 msgqueue_send(q, ev_tag_suspend, event.suspend);
780 stk := signal_prepare(sigtstp);
787 fn event_get_mouse~spark(implicit w : world, h : handle, q : msgqueue(event)) : bottom_type
789 var mouse := new_mouse_context;
791 var s := socket(pf_unix, sock_stream, 0);
792 connect(s, bytes.[ pf_unix, 0 ] + "/dev/gpmctl");
796 //eval debug("connected");
798 var procname := readlink(dnone(), "/proc/self");
799 var ttylink := readlink(dnone(), "/proc/self/fd/1");
803 var cdata := empty(byte);
804 cdata += int_to_native(native.short, not 0);
805 cdata += int_to_native(native.short, 0);
806 cdata += int_to_native(native.short, 0);
807 cdata += int_to_native(native.short, 0);
808 cdata += int_to_native(native.integer, ston(procname));
810 if list_begins_with(ttylink, "/dev/tty") then
811 vc := ston(ttylink[8 .. ]);
812 else if list_begins_with(ttylink, "/dev/vc/") then
813 vc := ston(ttylink[8 .. ]);
816 cdata += int_to_native(native.integer, vc);
821 var b := read_lazy(s);
823 var button_state := 0;
827 var x := native_to_int(native.short, b[8 .. 10]) - 1;
828 var y := native_to_int(native.short, b[10 .. 12]) - 1;
829 var etype := native_to_int(native.integer, b[12 .. 16]);
832 if gpmb bt 0 then buttons bts= 1;
833 if gpmb bt 1 then buttons bts= 2;
834 if gpmb bt 2 then buttons bts= 0;
835 if gpmb bt 3 then buttons bts= 3;
836 if gpmb bt 4 then buttons bts= 4;
837 var new_button_state := button_state;
839 new_button_state or= buttons;
841 new_button_state and= not buttons;
842 var wx := -native_to_int(native.short, b[24 .. 26]);
843 var wy := -native_to_int(native.short, b[26 .. 28]);
844 var ev := event_mouse.[ x : x, y : y, prev_buttons : button_state, buttons : new_button_state, wx : wx, wy : wy, soft_cursor : true ];
846 mouse := send_mouse_event(mouse, q, ev);
850 button_state := new_button_state;
856 fn event_wait_for_any_key(implicit w : world, h : handle) : world
858 var os := sysprop(SystemProperty_OS);
859 if os = SystemProperty_OS_DOS or
860 os = SystemProperty_OS_OS2 or
861 os = SystemProperty_OS_Windows then [
863 var k := read_console_packet(h);
868 stty(h, stty_flag_raw or stty_flag_noecho);
869 var k := read_partial(h, 10);
871 event_reset_handle(h);
875 fn event_reset_handle(implicit w : world, h : handle) : world