; ------------------------------------------------------------------------
; HeavyThing x86_64 assembly language library and showcase programs
; Copyright © 2015-2018 2 Ton Digital
; Homepage: https://2ton.com.au/
; Author: Jeff Marrison <jeff@2ton.com.au>
;
; This file is part of the HeavyThing library.
;
; HeavyThing is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License, or
; (at your option) any later version.
;
; HeavyThing is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License along
; with the HeavyThing library. If not, see <http://www.gnu.org/licenses/>.
; ------------------------------------------------------------------------
;
; tui_terminal.inc: terminal support for the tui layer...
; this hooks stdin/stdout via epoll, sets up the necessary
; signal handling for WINCH, does necessary termios-type goods
;
if used tui_terminal$vtable | defined include_everything
dalign
tui_terminal$vtable:
dq tui_terminal$cleanup, tui_object$clone, tui_object$draw, tui_object$redraw, tui_render$updatedisplaylist, tui_object$sizechanged
dq tui_object$timer, tui_render$layoutchanged, tui_object$move, tui_object$setfocus, tui_object$gotfocus, tui_object$lostfocus
dq tui_terminal$keyevent, tui_object$domodal, tui_object$endmodal, tui_terminal$exit, tui_object$calcbounds, tui_object$calcchildbounds
dq tui_object$appendchild, tui_object$appendbastard, tui_object$prependchild, tui_object$contains, tui_object$getchildindex
dq tui_object$removechild, tui_object$removebastard, tui_object$removeallchildren, tui_object$removeallbastards
dq tui_object$getobjectsunderpoint, tui_object$flatten, tui_object$firekeyevent, tui_object$ontab, tui_object$onshifttab
dq tui_render$setcursor, tui_render$showcursor, tui_render$hidecursor, tui_object$click, tui_object$clicked
dq tui_terminal$ansioutput, tui_render$newlayoutonresize, tui_render$newwindowsize
end if
; Terminal is meant to be a singleton for obvious reasons
; you should create precisely one, passing its first child
; and then fire up the epoll layer
if used tui_terminal$new | defined include_everything
globals
{
_tui_terminal_singleton dq 0
}
end if
; we add some vars to our "inherited" render properties
tui_stdin_ofs = tui_render_size
tui_stdout_ofs = tui_render_size + 8
tui_termios_stdin_save = tui_render_size + 16
tui_termios_stdout_save = tui_render_size + 76
tui_terminal_initsent = tui_render_size + 136
tui_terminal_size = tui_render_size + 144
if used stdio_datareceived | defined include_everything
; stdio epoll functions:
; this one just gets a single argument with our epoll object in rdi (well, it also gets buffer start/length but we don't care)
falign
stdio_datareceived:
prolog_silent stdio_datareceived
; Burning Purpose: get our terminal pointer into rdi, get our input buffer into rsi
; and hand it off to the terminal side's nvstdin
mov rsi, [rdi+epoll_inbuf_ofs]
mov rdi, [rdi+epoll_base_size] ; our terminal pointer
call tui_terminal$nvstdin
xor eax, eax ; don't close the connection
epilog
end if
if used stdio_error | defined include_everything
falign
stdio_error:
prolog_silent stdio_error
; Burning Purpose: send ourselves as an argument to our terminal advising them
; that we have been destroyed (epoll layer will have deleted us good and proper)
mov rsi, rdi ; us as an argument
mov rdi, [rdi+epoll_base_size] ; our terminal
call tui_terminal$nvonesidedied
epilog
end if
if used stdio_winch | defined include_everything
; signal handling goods...
; CAVEAT EMPTOR: the whole reason that terminal must be a singleton (and its a good thing
; since it makes SENSE) is because when one of these signals arrives, we need to be able
; to FIND our terminal instance (hence our fine and dandy looking _tui_terminal_singleton
; global variable)
falign
stdio_winch:
prolog_silent stdio_sigwinch
; get our terminal size and fire a newwindowsize virtual method call
sub rsp, 16
xor edi, edi ; fd
mov esi, 0x5413 ; TIOCGWINSZ
mov rdx, rsp
mov eax, syscall_ioctl
syscall
movzx edx, word [rsp] ; ws_row
movzx esi, word [rsp+2] ; ws_col
add rsp, 16
mov rdi, [_tui_terminal_singleton]
mov rcx, [rdi]
call qword [rcx+tui_vnewwindowsize] ;
epilog
end if
if used stdio_term | defined include_everything
falign
stdio_term:
prolog_silent stdio_term
mov rdi, [_tui_terminal_singleton]
call tui_terminal$nvterminated
epilog
end if
if used stdio_vtable | defined include_everything
; need our stdio-specific epoll goodies
calign
stdio_vtable:
dq epoll$destroy, epoll$clone, io$connected, epoll$send, stdio_datareceived, stdio_error, io$timeout
end if
if used tui_terminal$cleanup | defined include_everything
; single argument in rdi: our tui_terminal object
; NOTE: we do not free rdi, only cleanup everything we made
falign
tui_terminal$cleanup:
prolog tui_terminal$cleanup
push rdi
call tui_render$cleanup
mov rsi, [rsp]
mov rdi, [rsi+tui_stdin_ofs]
test rdi, rdi
jz .nostdin
call epoll$fatality
calign
.nostdin:
mov rsi, [rsp]
mov rdi, [rsi+tui_stdout_ofs]
test rdi, rdi
jz .nostdout
mov qword [rdi+epoll_fd_ofs], -1 ; do _not_ let the epoll layer close the fd
call epoll$fatality
calign
.nostdout:
; we don't really care if they actually did error out before
; these will silently error and we don't mind one bit
; we also need to restore the terminal attributes
mov rcx, [rsp]
xor edi, edi ; fd
mov esi, 0x5404 ; TCSETSF
lea rdx, [rcx+tui_termios_stdin_save]
mov eax, syscall_ioctl
syscall
mov rcx, [rsp]
mov edi, 1 ; fd
mov esi, 0x5404 ; TCSETSF
lea rdx, [rcx+tui_termios_stdout_save]
mov eax, syscall_ioctl
syscall
; remove O_NONBLOCK from our stdout
mov eax, syscall_fcntl
mov edi, 1 ; stdout
mov esi, 3 ; F_GETFL
syscall
and eax, 0xfffff7ff ; &= ~O_NONBLOCK
mov edi, 1 ; stdout
mov esi, 4 ; F_SETFL
mov edx, eax ; flags
mov eax, syscall_fcntl
syscall
pop rdi
epilog
end if
if used tui_terminal$new | defined include_everything
; single argument in rdi: first child
falign
tui_terminal$new:
prolog tui_terminal$new
cmp qword [_tui_terminal_singleton], 0
jne .kakked
sub rsp, 16
mov [rsp+8], rdi
mov edi, tui_terminal_size
call heap$alloc
mov [rsp], rax
mov rdi, rax
mov rsi, [rsp+8]
mov qword [rdi], tui_terminal$vtable ; set our vtable
add rdi, 8
xor esi, esi
mov edx, tui_terminal_size - 8
call memset32
mov rdi, [rsp]
; copy of tui_render's init_defaults routine
mov dword [rdi+tui_visible_ofs], 1
mov dword [rdi+tui_includeinlayout_ofs], 1
mov dword [rdi+tui_absolutex_ofs], -1
mov dword [rdi+tui_absolutey_ofs], -1
call list$new
mov rdi, [rsp]
mov [rdi+tui_children_ofs], rax
call list$new
mov rdi, [rsp]
mov [rdi+tui_bastards_ofs], rax
mov dword [rdi+tui_cursorvisible_ofs], 1
call buffer$new
mov rdi, [rsp]
mov [rdi+tui_outputbuffer_ofs], rax
; now we need to setup our terminal-specific vars
; for our stdin/stdout, we need two epoll objects
; each one needs an extra pointer to hold our terminal pointer
mov rdi, stdio_vtable
mov esi, 8
call epoll$new
mov rsi, [rsp]
mov [rsi+tui_stdin_ofs], rax ; set stdin
mov [rax+epoll_base_size], rsi ; save our terminal instance in the epoll object
mov rdi, stdio_vtable
mov esi, 8
call epoll$new
mov rsi, [rsp]
mov [rsi+tui_stdout_ofs], rax ; set stdout
mov [rax+epoll_base_size], rsi ; save our terminal instance in the epoll object
; now we call established on both of them
; NOTE: because my epoll layer automatically does an EPOLLIN, and because stdin/stdout are dup2'd
; we are leaving this one out... we still get data inbound on fd 1
;xor edi, edi ; fd
;mov rsi, [rsi+tui_stdin_ofs] ; epoll object for stdin
;call epoll$established
mov rdx, [rsp]
mov edi, 1 ; fd
mov rsi, [rdx+tui_stdout_ofs] ; epoll object for stdout
call epoll$established
; next up, we need to hook SIGWINCH
sub rsp, 152
mov rdi, rsp
xor esi, esi
mov edx, 152
call memset32
mov qword [rsp], stdio_winch
mov qword [rsp+8], 0x14000000 ; SA_RESTORER|SA_RESTART
mov qword [rsp+16], .rt_sigreturn
mov qword [rsp+24], 1 shl 27 ; 1 shl (SIGWINCH - 1)
mov eax, syscall_rt_sigaction
mov edi, 28 ; SIGWINCH
mov rsi, rsp
xor edx, edx
mov r10d, 8 ; sizeof(sigset_t)
syscall
; next up, we need to hook SIGTERM
mov rdi, rsp
xor esi, esi
mov edx, 152
call memset32
mov qword [rsp], stdio_term
mov qword [rsp+8], 0x14000000 ; SA_RESTORER|SA_RESTART
mov qword [rsp+16], .rt_sigreturn
mov qword [rsp+24], 1 shl 14 ; 1 shl (SIGTERM - 1)
mov eax, syscall_rt_sigaction
mov edi, 15 ; SIGTERM
mov rsi, rsp
xor edx, edx
mov r10d, 8 ; sizeof(sigset_t)
syscall
add rsp, 152
; next up, we have to do our nonportable ioctls to get our termios goods
mov rcx, [rsp] ; get our object back
xor edi, edi ; fd
mov esi, 0x5401 ; TCGETS
lea rdx, [rcx+tui_termios_stdin_save]
mov eax, syscall_ioctl
syscall
mov rcx, [rsp] ; get our object back
mov edi, 1 ; fd
mov esi, 0x5401 ; TCGETS
lea rdx, [rcx+tui_termios_stdout_save]
mov eax, syscall_ioctl
syscall
; next up, copy and modify them
mov rcx, [rsp]
lea rsi, [rcx+tui_termios_stdin_save]
sub rsp, 64 ; sizeof(struct termios) is 60
mov rdi, rsp
mov edx, 60
call memcpy
; offset of c_lflag is 0xC
and dword [rsp+0xC], 0xffff7ff4 ; c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG)
; offset of c_iflag is 0
and dword [rsp], 0xfffffedd ; c_iflag &= ~(BRKINT | ICRNL | ISTRIP)
; offset of c_cflag is 8
and dword [rsp+8], 0xfffffecf ; c_cflag &= ~(CSIZE | PARENB)
or dword [rsp+8], 0x30 ; c_cflag |= CS8
; offset of c_oflag is 4
and dword [rsp+4], 0xfffffffe ; c_oflag &= ~(OPOST)
mov word [rsp+0x16], 0 ; c_cc[VMIN] = 0, c_cc[VTIME] = 0
; last but not least, tcsetattr it
xor edi, edi ; fd
mov esi, 0x5404 ; TCSETSF
mov rdx, rsp
mov eax, syscall_ioctl
syscall
; now, do the same thing for stdout, though i don't think we really have to, one is enough, haha
; TODO: someday when I am bored, figure out whether this is really even necessary
; since we made room for our buffer, it means our object pointer is now at +64
mov rcx, [rsp+64]
lea rsi, [rcx+tui_termios_stdout_save]
mov rdi, rsp
mov edx, 60
call memcpy
; copy of the above modifications and syscall with the fd changed:
; offset of c_lflag is 0xC
and dword [rsp+0xC], 0xffff7ff4 ; c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG)
; offset of c_iflag is 0
and dword [rsp], 0xfffffedd ; c_iflag &= ~(BRKINT | ICRNL | ISTRIP)
; offset of c_cflag is 8
and dword [rsp+8], 0xfffffecf ; c_cflag &= ~(CSIZE | PARENB)
or dword [rsp+8], 0x30 ; c_cflag |= CS8
; offset of c_oflag is 4
and dword [rsp+4], 0xfffffffe ; c_oflag &= ~(OPOST)
mov word [rsp+0x16], 0 ; c_cc[VMIN] = 0, c_cc[VTIME] = 0
; last but not least, tcsetattr it
mov edi, 1 ; fd
mov esi, 0x5404 ; TCSETSF
mov rdx, rsp
mov eax, syscall_ioctl
syscall
; we are not quite done with our temporary buffer
xor edi, edi ; fd
mov esi, 0x5413 ; TIOCGWINSZ
mov rdx, rsp
mov eax, syscall_ioctl
syscall
movzx edx, word [rsp] ; ws_row
movzx esi, word [rsp+2] ; ws_col
add rsp, 64
mov rdi, [rsp] ; get our object pointer
mov rcx, [rdi] ; get its vtable
call qword [rcx+tui_vnewwindowsize] ;
; we need to set our global singleton (so that we can access it from the signal handlers)
mov rdi, [rsp]
mov [_tui_terminal_singleton], rdi
; we also need to add our first child:
mov rsi, [rsp+8]
mov rdx, [rdi] ; load our vtable
call qword [rdx+tui_vappendchild]
mov rax, [rsp] ; setup our return
add rsp, 16
epilog
calign
.kakked:
; terminal$new was called more than once
breakpoint
calign
.rt_sigreturn:
mov eax, syscall_rt_sigreturn
syscall
ret ; ??
end if
if used tui_terminal$keyevent | defined include_everything
; three arguments: tui_terminal object in rdi, esi == key, edx == esc_key
; NOTE: the reason we hook this is solely to capture ctrl-c, haha
falign
tui_terminal$keyevent:
prolog tui_terminal$keyevent
xor eax, eax
cmp esi, 3
jne .outtahere
; otherwise, ctrl-c was hit
sub rsp, 128
mov [rsp], rdi
mov rsi, [rdi]
call qword [rsi+tui_vcleanup] ; call the virtual cleanup method first, this restores all the goods
; since we are about to call sysexit, all we really need is our height, which still should be intact
mov rdi, [rsp]
mov esi, [rdi+tui_height_ofs]
mov [rsp+8], rsi ; save our height
call heap$free ; free our actual object, though again, since we are calling sysexit
; that really isn't necessary, haha
if used epoll_child | defined include_everything
call epoll_child_killall
end if
mov qword [_tui_terminal_singleton], 0 ; neither is that
; now, we need to send out some "GTFO" ANSI, because I loath having my cursor jacked up
mov eax, syscall_write
mov edi, 1
mov rsi, .seeyalater
mov edx, .seeyalater_size
syscall ; send out our preface
mov rdi, [rsp+8] ; get our height back
mov esi, 10 ; radix
call string$from_int
mov [rsp], rax ; save our string
mov rdi, rax
mov rsi, rsp
add rsi, 16
call string$to_utf8
mov rcx, [rsp] ; get our string back
mov rdx, [rcx] ; get its size
mov eax, syscall_write
mov edi, 1
mov rsi, rsp
add rsi, 16
syscall
mov rdi, [rsp]
call heap$free ; free our height string
mov eax, syscall_write
mov edi, 1
mov rsi, .reallyseeya
mov edx, .reallyseeya_size
syscall
mov eax, syscall_exit
mov edi, 127
syscall
; not reached:
epilog
if terminal_alternatescreen
dalign
.seeyalater:
db 27,'[r',27,'[?25h',27,'['
.seeyalater_size = $ - .seeyalater
dalign
.reallyseeya:
db ';1H',27,'[?1049l!!Ctrl-C!!',10
.reallyseeya_size = $ - .reallyseeya
else
dalign
.seeyalater:
db 27,'[r',27,'[?25h',27,'['
.seeyalater_size = $ - .seeyalater
dalign
.reallyseeya:
db ';1H!!Ctrl-C!!',10
.reallyseeya_size = $ - .reallyseeya
end if
calign
.outtahere:
epilog
end if
if used tui_terminal$nvclose | defined include_everything
; two arguments: tui_terminal object in rdi, message (string) in rsi to display on stdout
; NOTE: this "shuts down cleanly" our tui_terminal object, spits out the message, but does
; not do anything else (useful if you want a tui, but then want to background/daemonize/etc)
falign
tui_terminal$nvclose:
prolog tui_terminal$nvclose
sub rsp, 128
mov [rsp], rdi
mov [rsp+120], rsi
mov rsi, [rdi]
call qword [rsi+tui_vcleanup] ; call the virtual cleanup method first, this restores all the goods
; since we are about to call sysexit, all we really need is our height, which still should be intact
mov rdi, [rsp]
mov esi, [rdi+tui_height_ofs]
mov [rsp+8], rsi ; save our height
call heap$free
mov qword [_tui_terminal_singleton], 0
; now, we need to send out some "GTFO" ANSI, because I loath having my cursor jacked up
mov eax, syscall_write
mov edi, 1
mov rsi, .seeyalater
mov edx, .seeyalater_size
syscall ; send out our preface
mov rdi, [rsp+8] ; get our height back
mov esi, 10 ; radix
call string$from_int
mov [rsp], rax ; save our string
mov rdi, rax
mov rsi, rsp
add rsi, 16
call string$to_utf8
mov rcx, [rsp] ; get our string back
mov rdx, [rcx] ; get its size
mov eax, syscall_write
mov edi, 1
mov rsi, rsp
add rsi, 16
syscall
mov rdi, [rsp]
call heap$free ; free our height string
mov eax, syscall_write
mov edi, 1
mov rsi, .reallyseeya
mov edx, .reallyseeya_size
syscall
; send our message
mov rdi, [rsp+120]
call string$to_stdoutln
mov eax, syscall_exit
mov edi, 127
syscall
; not reached:
epilog
if terminal_alternatescreen
dalign
.seeyalater:
db 27,'[r',27,'[?25h',27,'['
.seeyalater_size = $ - .seeyalater
dalign
.reallyseeya:
db ';1H',27,'[?1049l'
.reallyseeya_size = $ - .reallyseeya
else
dalign
.seeyalater:
db 27,'[r',27,'[?25h',27,'['
.seeyalater_size = $ - .seeyalater
dalign
.reallyseeya:
db ';1H'
.reallyseeya_size = $ - .reallyseeya
end if
calign
.outtahere:
epilog
end if
if used tui_terminal$exit | defined include_everything
; two arguments, tui_terminal object in rdi, exit code in esi
; we actually just do a brute force exit exactly like keyevent for ctrl-C
falign
tui_terminal$exit:
prolog tui_terminal$exit
sub rsp, 128
mov [rsp], rdi
mov rsi, [rdi]
call qword [rsi+tui_vcleanup] ; call the virtual cleanup method first, this restores all the goods
; since we are about to call sysexit, all we really need is our height, which still should be intact
mov rdi, [rsp]
mov esi, [rdi+tui_height_ofs]
mov [rsp+8], rsi ; save our height
call heap$free ; free our actual object, though again, since we are calling sysexit
; that really isn't necessary, haha
if used epoll_child | defined include_everything
call epoll_child_killall
end if
mov qword [_tui_terminal_singleton], 0 ; neither is that
; now, we need to send out some "GTFO" ANSI, because I loath having my cursor jacked up
mov eax, syscall_write
mov edi, 1
mov rsi, .seeyalater
mov edx, .seeyalater_size
syscall ; send out our preface
mov rdi, [rsp+8] ; get our height back
mov esi, 10 ; radix
call string$from_int
mov [rsp], rax ; save our string
mov rdi, rax
mov rsi, rsp
add rsi, 16
call string$to_utf8
mov rcx, [rsp] ; get our string back
mov rdx, [rcx] ; get its size
mov eax, syscall_write
mov edi, 1
mov rsi, rsp
add rsi, 16
syscall
mov rdi, [rsp]
call heap$free ; free our height string
mov eax, syscall_write
mov edi, 1
mov rsi, .reallyseeya
mov edx, .reallyseeya_size
syscall
mov eax, syscall_exit
mov edi, 127
syscall
; not reached:
epilog
if terminal_alternatescreen
dalign
.seeyalater:
db 27,'[r',27,'[?25h',27,'['
.seeyalater_size = $ - .seeyalater
dalign
.reallyseeya:
db ';1H',27,'[?1049l!!Clean Exit!!',10
.reallyseeya_size = $ - .reallyseeya
else
dalign
.seeyalater:
db 27,'[r',27,'[?25h',27,'['
.seeyalater_size = $ - .seeyalater
dalign
.reallyseeya:
db ';1H!!Clean Exit!!',10
.reallyseeya_size = $ - .reallyseeya
end if
end if
if used tui_terminal$nvterminated | defined include_everything
; single argument in rdi: our tui_terminal object
; this gets called from SIGTERM
falign
tui_terminal$nvterminated:
prolog tui_terminal$nvterminated
sub rsp, 128
mov [rsp], rdi
mov rsi, [rdi]
call qword [rsi+tui_vcleanup] ; call the virtual cleanup method first, this restores all the goods
; since we are about to call sysexit, all we really need is our height, which still should be intact
mov rdi, [rsp]
mov esi, [rdi+tui_height_ofs]
mov [rsp+8], rsi ; save our height
call heap$free ; free our actual object, though again, since we are calling sysexit
; that really isn't necessary, haha
if used epoll_child | defined include_everything
call epoll_child_killall
end if
mov qword [_tui_terminal_singleton], 0 ; neither is that
; now, we need to send out some "GTFO" ANSI, because I loath having my cursor jacked up
mov eax, syscall_write
mov edi, 1
mov rsi, .seeyalater
mov edx, .seeyalater_size
syscall ; send out our preface
mov rdi, [rsp+8] ; get our height back
mov esi, 10 ; radix
call string$from_int
mov [rsp], rax ; save our string
mov rdi, rax
mov rsi, rsp
add rsi, 16
call string$to_utf8
mov rcx, [rsp] ; get our string back
mov rdx, [rcx] ; get its size
mov eax, syscall_write
mov edi, 1
mov rsi, rsp
add rsi, 16
syscall
mov rdi, [rsp]
call heap$free ; free our height string
mov eax, syscall_write
mov edi, 1
mov rsi, .reallyseeya
mov edx, .reallyseeya_size
syscall
mov eax, syscall_exit
mov edi, 127
syscall
; not reached:
epilog
if terminal_alternatescreen
dalign
.seeyalater:
db 27,'[r',27,'[?25h',27,'['
.seeyalater_size = $ - .seeyalater
dalign
.reallyseeya:
db ';1H',27,'[?1049l!!Terminated!!',10
.reallyseeya_size = $ - .reallyseeya
else
dalign
.seeyalater:
db 27,'[r',27,'[?25h',27,'['
.seeyalater_size = $ - .seeyalater
dalign
.reallyseeya:
db ';1H!!Terminated!!',10
.reallyseeya_size = $ - .reallyseeya
end if
end if
if used tui_terminal$ansioutput | defined include_everything
; three arguments: tui_terminal object in rdi, utf8 buffer in rsi, length in rdx
falign
tui_terminal$ansioutput:
prolog tui_terminal$ansioutput
; all we need to do is grab our stdout epoll object and send it
if terminal_alternatescreen
cmp dword [rdi+tui_terminal_initsent], 0
je .initfirst
end if
mov rdi, [rdi+tui_stdout_ofs]
test rdi, rdi
jz .nothingtodo
if defined terminal_ansidebug
push rdi rsi rdx
mov eax, syscall_open
mov rdi, .debug_fname
mov esi, 0x442
mov edx, 0x180
syscall
push rax
mov rdi, rax ; fd
mov rsi, [rsp+16]
mov rdx, [rsp+8]
mov eax, syscall_write
syscall
pop rdi
mov eax, syscall_close
syscall
pop rdx rsi rdi
end if
mov rcx, [rdi] ; load up its vtable
call qword [rcx+epoll_vsend]
epilog
calign
.nothingtodo:
; means our stdout side got closed or errored
epilog
if terminal_alternatescreen
calign
.initfirst:
mov dword [rdi+tui_terminal_initsent], 1
mov rdi, [rdi+tui_stdout_ofs]
test rdi, rdi
jz .nothingtodo
push rdi rsi rdx
mov rsi, .altscr
mov edx, .altscrsize
mov rcx, [rdi] ; load up its vtable
call qword [rcx+epoll_vsend]
pop rdx rsi rdi
mov rcx, [rdi]
call qword [rcx+epoll_vsend]
epilog
dalign
.altscr db 27,'[?1049h'
.altscrsize = $ - .altscr
end if
if defined terminal_ansidebug
dalign
.debug_fname:
db 'terminal.ansi',0
end if
end if
if used tui_terminal$nvonesidedied | defined include_everything
; two arguments: tui_terminal object in rdi, epoll object (either stdin or stdout) in rsi
falign
tui_terminal$nvonesidedied:
prolog tui_terminal$nvonesidedied
xor edx, edx
cmp rsi, [rdi+tui_stdin_ofs]
jne .notstdin
mov [rdi+tui_stdin_ofs], rdx ; clear it
epilog
calign
.notstdin:
cmp rsi, [rdi+tui_stdout_ofs]
jne .notstdout
mov [rdi+tui_stdout_ofs], rdx ; clear it
epilog
calign
.notstdout:
; this should not ever happen... sanity check, we'll breakpoint it
breakpoint
epilog
end if
if used tui_terminal$nvstdin | defined include_everything
; two arguments: tui_terminal object in rdi, input buffer (accumulating) in rsi
falign
tui_terminal$nvstdin:
prolog tui_terminal$nvstdin
; Burning Purpose: we have to parse for UTF8 sequences, escape sequences, and other "normal" characters
; firing tui_object style keyboard events as we go
push r12 r13 r14 r15
mov r12, rdi
mov r13, [rsi+buffer_itself_ofs]
mov r14, [rsi+buffer_endptr_ofs]
mov r15, rsi
calign
.loop:
cmp r13, r14
jae .alldone
movzx eax, byte [r13]
mov ecx, eax ; save the full char in ecx
shr eax, 4
cmp eax, 12
jae .unichar
; else, it isn't a unicode character, check it for escapes
calign
.notunicode:
; our unmolested byte is sitting in ecx
cmp ecx, 27
jne .normalchar
mov rdx, r14 ; the end of our buffer
sub rdx, r13 ; where we are now
cmp rdx, 3
jl .normalchar ; wait for more data if there isn't enough sitting here for us
movzx edx, byte [r13+1] ; next char has to be [ or O
cmp edx, '['
je .escbracket
cmp edx, 'O'
je .escoh
; else, send it out as a normal key
calign
.normalchar:
; fire the key in ecx
mov rdi, r12 ; our tui_terminal object
mov esi, ecx ; our key
xor edx, edx ; our esc_key
mov rcx, [rdi] ; its vtable
call qword [rcx+tui_vfirekeyevent]
; swallow three characters and keep going
add r13, 1
jmp .loop
calign
.fireescaped:
; fire the escape key in ecx
mov rdi, r12 ; our tui_terminal object
xor esi, esi ; our key
mov edx, ecx ; our esc_key
mov rcx, [rdi] ; its vtable
call qword [rcx+tui_vfirekeyevent]
; swallow one char and keep going
add r13, 1
jmp .loop
calign
.escbracket:
; ESC [ received
add r13, 2 ; skip ESC [
movzx ecx, byte [r13]
cmp ecx, 'A'
je .fireescaped
cmp ecx, 'B'
je .fireescaped
cmp ecx, 'C'
je .fireescaped
cmp ecx, 'D'
je .fireescaped
; otherwise, go forward til we run out of room or til we hit a ~, shl by 8 each one we get
xor eax, eax
calign
.escloop:
cmp r13, r14
jae .escloopdone
movzx ecx, byte [r13]
cmp ecx, '~'
je .escloopdone
shl eax, 8
or eax, ecx
add r13, 1
jmp .escloop
calign
.escloopdone:
mov ecx, eax
jmp .fireescaped ; this could make r13 go one past the end, quite alright
calign
.escoh:
; ESC O received
shl ecx, 8 ; 'O' << 8
movzx eax, byte [r13+1]
or ecx, eax ; | next byte
add r13, 2
jmp .fireescaped
calign
.unichar:
; upper 4 bits of the byte in al were >= 12
cmp eax, 12
je .unichar_2
cmp eax, 13
je .unichar_2
cmp eax, 14
je .unichar_3
cmp eax, 15
je .unichar_4
; anything else is invalid unicode, no?
; so we can let the "normal" non-unicode parser see what it makes of it
jmp .notunicode
calign
.unichar_2:
mov rdx, r14 ; the end of our buffer
sub rdx, r13 ; where we are now
cmp rdx, 2
jl .alldone ; wait for more data if there isn't enough sitting here for us
movzx edx, byte [r13+1] ; get the next char
mov r8d, edx ; save it
and edx, 0xc0
cmp edx, 0x80
jne .notunicode
mov eax, ecx ; first byte
shl eax, 6
and r8d, 0x3f
and eax, 0x7c0
or eax, r8d
cmp eax, 0x80
jb .notunicode
; otherwise, unicode point in eax is valid, fire it
mov rdi, r12 ; our tui_terminal object
mov esi, eax ; our key
xor edx, edx ; our esc_key
mov rcx, [rdi] ; its vtable
call qword [rcx+tui_vfirekeyevent]
; swallow two characters and keep going
add r13, 2
jmp .loop
calign
.unichar_3:
mov rdx, r14 ; the end of our buffer
sub rdx, r13 ; where we are now
cmp rdx, 3
jl .alldone ; wait for more data if there isn't enough sitting here for us
movzx edx, byte [r13+1] ; get the next char
movzx r9d, byte [r13+2] ; and the next char
mov r10d, edx ; save
mov r11d, r9d ; save
and edx, 0xc0
cmp edx, 0x80
jne .notunicode
and r9d, 0xc0
cmp r9d, 0x80
jne .notunicode
mov r8d, ecx
shl r8d, 12
and r8d, 0xf000
shl r10d, 6
and r10d, 0xfc0
or r8d, r10d
and r11d, 0x3f
or r8d, r11d
cmp r8d, 0x800
jb .notunicode
; otherwise, unicode point in r8d is valid, fire it
mov rdi, r12 ; our tui_terminal object
mov esi, r8d ; our key
xor edx, edx ; our esc_key
mov rcx, [rdi] ; its vtable
call qword [rcx+tui_vfirekeyevent]
; swallow three characters and keep going
add r13, 3
jmp .loop
calign
.unichar_4:
mov rdx, r14 ; the end of our buffer
sub rdx, r13 ; where we are now
cmp rdx, 4
jl .alldone ; wait for more data if there isn't enough sitting here for us
; ecx is our unmolested first byte
movzx eax, byte [r13+1] ; second byte
movzx edx, byte [r13+2] ; third byte
movzx r8d, byte [r13+3] ; fourth byte
mov r9d, eax
and r9d, 0xc0
cmp r9d, 0x80
jne .notunicode
mov r9d, edx
and r9d, 0xc0
cmp r9d, 0x80
jne .notunicode
mov r9d, r8d
and r9d, 0xc0
cmp r9d, 0x80
jne .notunicode
mov r9d, ecx
shl r9d, 18
and r9d, 0x1c0000
shl eax, 12
and eax, 0x3f000
or r9d, eax
shl edx, 6
and edx, 0xfc0
or r9d, edx
and r8d, 0x3f
or r9d, r8d
cmp r9d, 0x10000
jb .notunicode
; otherwise, unicode point in r9d is valid, fire it
mov rdi, r12 ; our tui_terminal object
mov esi, r9d ; our key
xor edx, edx ; our esc_key
mov rcx, [rdi] ; its vtable
call qword [rcx+tui_vfirekeyevent]
; swallow four characters and keep going
add r13, 4
jmp .loop
calign
.alldone:
mov rsi, r13
sub rsi, qword [r15+buffer_itself_ofs]
cmp rsi, 0
jle .nodata
mov rdi, r15
call buffer$consume
calign
.nodata:
pop r15 r14 r13 r12
epilog
end if