; ------------------------------------------------------------------------
; 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/>.
; ------------------------------------------------------------------------
;
; screen.inc: the sshtalk main screen tui object
;
; our screen is a simple tui_object, 100% x 100% so it can be resized
; statusbar on the bottom, main chat area/useful area on the left
; buddy list + help text on the right
;
; we "descend" the tui_object, so that we can set our state variables
; correctly when we are cloned, and so that we can catch keystrokes to
; change focus between elements, and deal with otherwise main-related
; goodies
;
include 'roomlist.inc'
dalign
screen$vtable:
dq screen$cleanup, screen$clone, tui_object$draw, tui_object$redraw, tui_object$updatedisplaylist, tui_object$sizechanged
dq tui_object$timer, tui_object$layoutchanged, tui_object$move, tui_object$setfocus, tui_object$gotfocus, tui_object$lostfocus
dq screen$keyevent, tui_object$domodal, tui_object$endmodal, tui_object$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, screen$firekeyevent, screen$ontab, screen$onshifttab
dq tui_object$setcursor, tui_object$showcursor, tui_object$hidecursor, tui_object$click, screen$clicked
; offsets for our required variables
screen_main_ofs = tui_object_size
screen_statusbar_ofs = tui_object_size + 8
screen_buddylist_ofs = tui_object_size + 16
screen_helptext_ofs = tui_object_size + 24
screen_bell_ofs = tui_object_size + 32
screen_user_ofs = tui_object_size + 40
screen_focus_ofs = tui_object_size + 48
screen_modal_ofs = tui_object_size + 56
screen_modalalert_ofs = tui_object_size + 64
screen_start_ofs = tui_object_size + 72
screen_lastkey_ofs = tui_object_size + 80
screen_buddylist_col_ofs = tui_object_size + 88
screen_buddylist_sep_ofs = tui_object_size + 96
screen_size = tui_object_size + 104
; we also override the datagrid vtable for itemselected (aka open a chat panel)
dalign
buddylist$vtable:
dq tui_object$cleanup, tui_datagrid$clone, tui_object$draw, tui_object$redraw, tui_object$updatedisplaylist, tui_object$sizechanged
dq tui_object$timer, tui_object$layoutchanged, tui_object$move, tui_object$setfocus, tui_object$gotfocus, tui_object$lostfocus
dq tui_object$keyevent, tui_object$domodal, tui_object$endmodal, tui_object$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_object$setcursor, tui_object$showcursor, tui_object$hidecursor, tui_object$click, tui_object$clicked
; our itemselected:
dq screen$buddyselected
; for our main chat/screen area, we need to override tui_object$simple_vtable such that when we get resized, we can retile its children:
dalign
main$vtable:
dq tui_object$cleanup, tui_background$clone, tui_background$draw, tui_object$redraw, tui_object$updatedisplaylist, main$sizechanged
dq tui_object$timer, tui_object$layoutchanged, tui_object$move, tui_object$setfocus, tui_object$gotfocus, tui_object$lostfocus
dq tui_object$keyevent, tui_object$domodal, tui_object$endmodal, tui_object$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_object$setcursor, tui_object$showcursor, tui_object$hidecursor, tui_object$click, tui_object$clicked
; to log connects w/ username/ip, we need a global formatter to reuse
; to log connects w/ just username, we need another formatter
; to log disconnects w/ durations, we need a global formatter to reuse
globals
{
connect_fmt dq 0
connect_noip_fmt dq 0
disco_fmt dq 0
}
; no arguments, initialises the above
falign
screen$init_formatters:
prolog screen$init_formatters
xor edi, edi
call formatter$new
mov [connect_fmt], rax
mov rdi, rax
xor esi, esi
call formatter$add_string
mov rdi, [connect_fmt]
mov rsi, .conn1
call formatter$add_static
mov rdi, [connect_fmt]
xor esi, esi
call formatter$add_string
mov rdi, [connect_fmt]
mov rsi, .conn3
call formatter$add_static
mov rdi, [connect_fmt]
mov esi, 1
xor edx, edx
call formatter$add_unsigned
mov rdi, [connect_fmt]
mov rsi, .conn4
call formatter$add_static
mov rdi, [connect_fmt]
mov esi, 1
xor edx, edx
call formatter$add_unsigned
xor edi, edi
call formatter$new
mov [connect_noip_fmt], rax
mov rdi, rax
xor esi, esi
call formatter$add_string
mov rdi, [connect_noip_fmt]
mov rsi, .conn2
call formatter$add_static
mov rdi, [connect_noip_fmt]
mov esi, 1
xor edx, edx
call formatter$add_unsigned
mov rdi, [connect_noip_fmt]
mov rsi, .conn4
call formatter$add_static
mov rdi, [connect_noip_fmt]
mov esi, 1
xor edx, edx
call formatter$add_unsigned
xor edi, edi
call formatter$new
mov [disco_fmt], rax
mov rdi, rax
xor esi, esi
call formatter$add_string
mov rdi, [disco_fmt]
mov rsi, .disco1
call formatter$add_static
mov rdi, [disco_fmt]
mov esi, 1 ; second resolution
xor edx, edx ; no fractional digits
call formatter$add_duration
epilog
cleartext .conn1, ' connected from '
cleartext .conn2, ' connected at '
cleartext .conn3, ' at '
cleartext .conn4, 'x'
cleartext .disco1, ' disconnected, session time: '
; no arguments, returns a new screen object
falign
screen$new:
prolog screen$new
; heheh, this function would be why markup was invented :-) someday when I am bored I should create a markup
; parser for the tui_* objects, to make the process you see below much faster/easier, hahah
push rbx
mov edi, screen_size
call heap$alloc
mov rbx, rax
mov rdi, rax
mov qword [rax], screen$vtable
movq xmm0, [_math_onehundred]
movq xmm1, [_math_onehundred]
call tui_object$init_dd
; its default layout is vertical, so add to that our "main" area, also 100% x 100%
mov edi, tui_object_size
call heap$alloc
mov qword [rax], tui_object$simple_vtable
mov rdi, rax
; save the pointer to our main area for later reference
; NOTE: we overwrite this pointer later when we create the "real" main
mov [rbx+screen_main_ofs], rax
movq xmm0, [_math_onehundred]
movq xmm1, [_math_onehundred]
call tui_object$init_dd
; add that to our actual children list
mov rdi, rbx
mov rsi, [rbx+screen_main_ofs]
; before we add it, set its layout to horizontal, because it will contain two more columns
mov dword [rsi+tui_layout_ofs], tui_layout_horizontal
mov rdx, [rdi]
; NOTE: we know perfectly well that the main screen is a tui_object, so we really
; don't need to call its "virtual" appendchild method, but for consistency throughout
; all of the tui_* goods, practice makes perfect
call qword [rdx+tui_vappendchild]
; next up, our full width statusbar on the bottom
call statusbar$new
mov [rbx+screen_statusbar_ofs], rax
mov rdi, rbx
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; set its statustext to our sshtalk goods:
mov rdi, [rbx+screen_statusbar_ofs]
mov rsi, .statustext
call tui_statusbar$nvsettext
; so now, we have a dynamically sized main area and our statusbar
; we need to divvy up the full-width main area into two separate ones
; the right column being fixed width, the left being our dynamic area
; that will be our main useful area
; the main we created above we already set its layout to horizontal
; so all it needs is three children:
; another absolute layout 100% x 100% tui_background,
; a vertical line, and our right column
mov edi, tui_background_size
call heap$alloc
mov qword [rax], tui_background$vtable
mov rdi, rax
; hangon to this pointer for our final move into the screen_main_ofs
push rax
movq xmm0, [_math_onehundred]
movq xmm1, [_math_onehundred]
mov esi, ' ' ; background fill char
ansi_colors edx, 'gray', 'black'
call tui_background$init_dd
; add that to the previous main we created before
mov rdi, [rbx+screen_main_ofs]
mov rsi, [rsp]
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; vertical line is next, full height
movq xmm0, [_math_onehundred]
ansi_colors edi, 'gray', 'black'
call tui_vline$new_d
mov [rbx+screen_buddylist_sep_ofs], rax
; add that to the previous main we created before
mov rdi, [rbx+screen_main_ofs]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; and now our fixed-width right column, that will contain our
; buddy list, and help text
mov edi, tui_object_size
call heap$alloc
mov qword [rax], tui_object$simple_vtable
mov rdi, rax
mov [rbx+screen_buddylist_col_ofs], rax
; hangon to this pointer for later so we can add to it
push rax
movq xmm0, [_math_onehundred]
mov esi, 24
call tui_object$init_id
; add that to the previous main we created before
mov rdi, [rbx+screen_main_ofs]
mov rsi, [rsp]
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; our right-hand column needs to contain a dynamic height
; datagrid for our buddylist, and a heightlocked text area
; below it
mov edi, 24
movq xmm0, [_math_onehundred]
ansi_colors esi, 'black', 'lightgray'
ansi_colors edx, 'lightgray', 'black'
ansi_colors ecx, 'lightgray', 'blue'
call tui_datagrid$new_id
; make sure to override its virtual method table so we get itemselected events
mov qword [rax], buddylist$vtable
; set the user field for the datagrid to point back to us so that we don't have
; to walk up the parent tree to find the real screen object during itemselect
mov [rax+tui_dguser_ofs], rbx
; set our initial focus to the buddylist:
mov [rbx+screen_focus_ofs], rax
; also, clear our modal pointer
mov qword [rbx+screen_modal_ofs], 0
mov qword [rbx+screen_modalalert_ofs], 0
; save that so we can reference it directly later:
mov [rbx+screen_buddylist_ofs], rax
; add that to our right hand column:
mov rdi, [rsp]
mov rsi, rax
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; the DG needs headers:
mov rdi, [rbx+screen_buddylist_ofs]
mov rsi, .buddylabel
movq xmm0, [_math_onehundred]
mov edx, tui_textalign_left
mov rcx, .buddystr
call tui_datagrid$nvaddproperty_d
mov rdi, [rbx+screen_buddylist_ofs]
mov rsi, .onlinelabel
mov edx, 7
mov ecx, tui_textalign_right
mov r8, .onlinestr
call tui_datagrid$nvaddproperty_i
; we need a tui_bell object
mov edi, 24
mov esi, 1
ansi_colors edx, 'lightgray', 'black'
call tui_bell$new
mov rdi, [rsp]
mov rsi, rax
mov rdx, [rdi]
; save that so we can mess with it later:
mov [rbx+screen_bell_ofs], rax
call qword [rdx+tui_vappendchild]
; now we need our heightlocked help text area below that
mov edi, 24
mov esi, 9
mov rdx, .helptext
ansi_colors ecx, 'lightgray', 'black'
ansi_colors r8d, 'lightgray', 'black'
xor r9d, r9d
call tui_text$new_ii
; disable editing, cursor
mov dword [rax+tui_text_editable_ofs], 0
mov dword [rax+tui_text_docursor_ofs], 0
; enable multiline
mov dword [rax+tui_text_multiline_ofs], 1
; enable heightlock in case we change the text later
mov dword [rax+tui_text_heightlock_ofs], 1
; and enable wordwrap
mov dword [rax+tui_text_wrap_ofs], 2
; save that so we can reference it directly later:
mov [rbx+screen_helptext_ofs], rax
; now, add that to our right hand column
pop rdi
mov rsi, [rbx+screen_helptext_ofs]
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
; so now that we are all done adding our children
; swap the main with the "real" main
pop rdi
mov [rbx+screen_main_ofs], rdi
; set its vtable to our custom one so we can retile on resize
mov qword [rdi], main$vtable
; clear the user object:
mov qword [rbx+screen_user_ofs], 0
call timestamp
movq rax, xmm0
mov [rbx+screen_start_ofs], rax
; last but not least, set our lastkey so that window opens
; don't cause a bell
; NOTE: the epoll layer always updates the globals _epoll_tv_secs/_epoll_tv_usecs
; during each and every fire of epoll$iteration, so it is safe to use it without
; actually calling a timestamp, since we know we were called from within an epoll
; loop
mov rcx, [_epoll_tv_secs]
mov [rbx+screen_lastkey_ofs], rcx
mov rax, rbx
pop rbx
epilog
cleartext .statustext, 'sshtalk v1.24 ',0xc2,0xa9,' 2015-2017 2 Ton Digital'
cleartext .buddylabel, 'Buddy'
cleartext .buddystr, 'buddy'
cleartext .onlinelabel, 'Status'
cleartext .onlinestr, 'status'
cleartext .helptext, ' C-a - Add Buddy',10,' C-r - Remove Buddy',10,' C-b - Toggle BuddyList',10,' C-j - Join/Create Room',10,' C-l - Room List',10,' C-w - Close Chat',10,' Up/Dn - Scroll Chat',10,' C-c - Exit',10,' Tab - Input Focus'
; NOTE NOTE NOTE: because sshtalk uses tui_simpleauth, we have a "special privilege" unlike the rest of the
; tui_objects... and that is: our username that was authenticated gets passed here to the clone in rsi, as well as
; the "real" tui_simpleauth parent object (which is a tui_ssh_renderer object in this case) in rdx
; three arguments: rdi == our screen object to clone, rsi == username that we are firing up, rdx == tui_ssh_renderer object we are about to belong to
falign
screen$clone:
prolog screen$clone
; NOTE: since we don't have any dynamic content to clone necessarily, we just create a spanking new one
; preserve our username
push rbx
mov rbx, rdx
push rsi
call screen$new
; so now, we have to associate our screen object with the userdb user object
mov rdi, [users]
mov rsi, [rsp]
mov [rsp], rax
call stringmap$find_value
; we know for certain that it exists, so no checking is necessary
mov rsi, [rsp]
mov [rsi+screen_user_ofs], rdx
; so now, three things need to happen...
; first up: add our newly created screen object to our user's tui map
mov rdi, [rdx+user_tuilist_ofs]
call unsignedmap$insert_unique
; next up: update our own buddy list
mov rdi, [rsp]
call screen$updatebuddies
; next up: we need to let everyone in our own notifies list that we are online
mov rdi, [rsp]
mov rsi, .notify
mov rdi, [rdi+screen_user_ofs]
mov rdi, [rdi+user_notifies_ofs]
call stringmap$foreach
; hide our cursor:
mov rdi, [rsp]
mov rsi, [rdi]
call qword [rsi+tui_vhidecursor]
; update the statusbar now that we have added ourselves to the tuilist
mov rdi, [rsp]
mov rdi, [rdi+screen_statusbar_ofs]
call statusbar$timer
; the initial cursor should be hidden:
mov rdi, rbx
mov rsi, [rbx]
call qword [rsi+tui_vhidecursor]
; the very topmost object that got passed to us in rdx, is a tui_ssh_renderer object
; get the base from there
mov rdi, [rbx+tui_ssh_renderer_base_ofs]
mov edx, [rdi+tui_ssh_raddrlen_ofs]
cmp edx, sockaddr_in_size
jne .simplelog
push r12
lea rsi, [rdi+tui_ssh_raddr_ofs]
mov edi, [rsi+4]
call inet_ntoa
mov r12, rax
mov rcx, [rsp+8]
mov rdi, [connect_fmt]
mov rsi, [rcx+screen_user_ofs]
mov rdx, r12
mov rsi, [rsi+user_username_ofs]
mov ecx, [rbx+tui_width_ofs]
mov r8d, [rbx+tui_height_ofs]
call formatter$doit
mov rdi, r12
mov r12, rax
call heap$free
mov edi, log_notice
mov rsi, r12
call syslog
mov rdi, r12
call heap$free
pop r12
pop rax rbx
epilog
calign
.simplelog:
mov rcx, [rsp]
mov rdi, [connect_noip_fmt]
mov rsi, [rcx+screen_user_ofs]
mov rsi, [rsi+user_username_ofs]
mov edx, [rbx+tui_width_ofs]
mov ecx, [rbx+tui_height_ofs]
call formatter$doit
mov edi, log_notice
mov rsi, rax
push rax
call syslog
pop rdi
call heap$free
pop rax rbx
epilog
falign
.notify:
; two arguments: rdi == string username, rsi == user object
; so, for every object in this user's tuilist, update their buddy list
mov rdi, [rsi+user_tuilist_ofs]
mov rsi, screen$updatebuddies
call unsignedmap$foreach
ret
; single argument in rdi: our screen object to cleanup
falign
screen$cleanup:
prolog screen$cleanup
; the reason we don't just use tui_object's cleanup is so that we can update
; everyone in our notifies list of our absence
; once we do that, we proceed with the normal tui_object$cleanup
; due to the auto-leave functionality of the chatpanel object, we need
; to make sure we destroy all our children _first_
; and then deal with our user object and notifies
push rbx
mov rbx, rdi
; log our disconnect
call timestamp
mov rdi, [disco_fmt]
mov rsi, [rbx+screen_user_ofs]
subsd xmm0, [rbx+screen_start_ofs]
mov rsi, [rsi+user_username_ofs]
call formatter$doit
push rax
mov edi, log_notice
mov rsi, rax
call syslog
pop rdi
call heap$free
mov rdi, rbx
call tui_object$cleanup
mov rsi, rbx
mov rdi, [rbx+screen_user_ofs]
mov rdi, [rdi+user_tuilist_ofs]
call unsignedmap$erase
; next up: let everyone in our notifies list know that we are gone
mov rdi, [rbx+screen_user_ofs]
mov rsi, .notify
mov rdi, [rdi+user_notifies_ofs]
call stringmap$foreach
pop rbx
epilog
falign
.notify:
; two arguments: rdi == string username, rsi == user object
; for every object in this user's tuilist, update their buddy list
mov rdi, [rsi+user_tuilist_ofs]
mov rsi, screen$updatebuddies
call unsignedmap$foreach
ret
; single argument in rdi: our main container object
falign
main$sizechanged:
prolog main$sizechanged
push rdi
call tui_object$sizechanged
pop rdi
call tui_object$nvtile
epilog
; single argument in rdi: our screen object
falign
screen$showhidecursor:
prolog screen$showhidecursor
; burning purpose: depending on what has focus, show or hide the cursor
cmp qword [rdi+screen_modal_ofs], 0
je .nomodal
; otherwise, we have a modal dialog open, see if it is alert or not
cmp dword [rdi+screen_modalalert_ofs], 1
je .hideit
jmp .showit
calign
.nomodal:
mov rsi, [rdi+screen_focus_ofs]
cmp rsi, [rdi+screen_buddylist_ofs]
je .hideit
; otherwise, show it
calign
.showit:
mov rsi, [rdi]
call qword [rsi+tui_vshowcursor]
epilog
calign
.hideit:
mov rsi, [rdi]
call qword [rsi+tui_vhidecursor]
epilog
; single argument in rdi: our screen object
falign
screen$updatebuddies:
prolog screen$updatebuddies
; burning purpose: tui_datagrid needs JSON data, walk through our buddylist
; and create json objects for each one, then pass that to our buddylist DG
; NOTE: we want our online buddies listed alphabetically, THEN our offline
push rbx r12
mov rbx, rdi
; create an unnamed jsonarray to do our deed
call string$new
mov rdi, rax
call json$newarray_nocopy
mov rdi, [rbx+screen_user_ofs]
mov r12, rax
mov rdi, [rdi+user_buddylist_ofs]
mov rsi, .addjson_online
mov rdx, rax
call stringmap$foreach_arg
mov rdi, [rbx+screen_user_ofs]
mov rsi, .addjson_offline
mov rdi, [rdi+user_buddylist_ofs]
mov rdx, r12
call stringmap$foreach_arg
; so now, we have a json array object sitting in r12
; tui_datagrid$nvsetdata will assume ownership of the json array
mov rdi, [rbx+screen_buddylist_ofs]
mov rsi, r12
call tui_datagrid$nvsetdata
; last but not least, go through our open chatpanels and titleupdate them
mov rdi, [rbx+screen_main_ofs]
mov rsi, chatpanel$titleupdate
mov rdi, [rdi+tui_children_ofs]
call list$foreach
pop r12 rbx
epilog
cleartext .buddystr, 'buddy'
cleartext .statusstr, 'status'
cleartext .onlinestr, 'online'
cleartext .offlinestr, 'offline'
falign
.addjson_online:
; three arguments: rdi == string buddy name, rsi == user object, rdx == json array to add to
; our avl tree stores its nodecount in _avlofs_right, make sure this user's tuilist is not empty
mov rcx, [rsi+user_tuilist_ofs]
cmp qword [rcx+_avlofs_right], 0
je .addjson_nothingtodo
push rdi rsi rdx
; create an unnamed json object to hold our goods:
call string$new
mov rdi, rax
call json$newobject_nocopy
; we need two values added to that object: buddy, status
push rax
; [rsp] == our json object
; [rsp+8] == json array
; [rsp+16] == user object
; [rsp+24] == string buddy name
mov rdi, .buddystr
mov rsi, [rsp+24]
call json$newvalue
mov rdi, [rsp]
mov rsi, rax
call json$appendchild
mov rdi, .statusstr
mov rsi, .onlinestr
call json$newvalue
mov rdi, [rsp]
mov rsi, rax
call json$appendchild
; now add our json object to the json array
pop rsi rdi
call json$appendchild
add rsp, 16
ret
falign
.addjson_offline:
; same as above, only we make sure they are offline
mov rcx, [rsi+user_tuilist_ofs]
cmp qword [rcx+_avlofs_right], 0
jne .addjson_nothingtodo
push rdi rsi rdx
; create an unnamed json object to hold our goods:
call string$new
mov rdi, rax
call json$newobject_nocopy
; we need two values added to that object: buddy, status
push rax
; [rsp] == our json object
; [rsp+8] == json array
; [rsp+16] == user object
; [rsp+24] == string buddy name
mov rdi, .buddystr
mov rsi, [rsp+24]
call json$newvalue
mov rdi, [rsp]
mov rsi, rax
call json$appendchild
mov rdi, .statusstr
mov rsi, .offlinestr
call json$newvalue
mov rdi, [rsp]
mov rsi, rax
call json$appendchild
; now add our json object to the json array
pop rsi rdi
call json$appendchild
add rsp, 16
ret
calign
.addjson_nothingtodo:
ret
; three arguments: rdi == screen object, rsi == string name of chatpanel, edx == bool as to whether it is a room or not
falign
screen$chatpanel_find:
prolog screen$chatpanel_find
; our mission is to find and return the chatpanel, if it doesn't exist we return null
; (unlike chatpanel_byname which will add/create one)
mov rcx, [rdi+screen_main_ofs]
push rbx r12 r13 r14
mov rax, [rcx+tui_children_ofs]
mov rbx, rdi
mov r12, rsi
mov r13d, edx
mov r14, [rax+_list_first_ofs]
test r14, r14
jz .nullret
calign
.search:
mov rsi, [r14] ; the chatpanel object
; we need to do a string compare of the names
mov rdi, r12
mov rsi, [rsi+chatpanel_name_ofs]
call string$equals
test eax, eax
jnz .found
mov r14, [r14+_list_nextofs]
test r14, r14
jnz .search
calign
.nullret:
xor eax, eax
pop r14 r13 r12 rbx
epilog
calign
.found:
; the bool sitting in r13d has to be checked, in case someone
; wants to join a room that has the same name as a buddy
mov rdi, [r14]
mov rsi, [rdi+chatpanel_room_ofs]
mov rdx, [rsi+chatroom_name_ofs]
; if it is a room, rdx will be nonzero
test r13d, r13d
jz .found_checkbuddy
; else, we need it to be a room
test rdx, rdx
jz .notfound
mov rax, rdi ; the chatpanel object
pop r14 r13 r12 rbx
epilog
calign
.found_checkbuddy:
; we need it to be a buddy chat
test rdx, rdx
jnz .notfound
mov rax, rdi
pop r14 r13 r12 rbx
epilog
calign
.notfound:
mov r14, [r14+_list_nextofs]
test r14, r14
jz .nullret
jmp .search
; three arguments: rdi == screen object, rsi == string name of chatpanel, edx == bool as to whether it is a room or not, ecx == bool for special focus
; NOTE on ecx: when buddyselected calls this, ecx == 0, so we don't do any special focussing, when it is called from remote_keystroke, ecx == 1
; in which case we try and "intelligently" mess with focus, primarily: if we didn't create a new window, we don't modify focus at all
; if we created a new one, and we were the first and only chat window, then we set the focus to the newly created window
falign
screen$chatpanel_byname:
prolog screen$chatpanel_byname
; our mission is to find (and create if it doesn't exist) and return the chatpanel
; all our chatpanels live in the main window, and are its exclusive children
push rbx r12 r13 r14 r15
mov r15d, ecx
mov rcx, [rdi+screen_main_ofs]
mov rax, [rcx+tui_children_ofs]
mov rbx, rdi
mov r12, rsi
mov r13d, edx
mov r14, [rax+_list_first_ofs]
test r14, r14
jz .createone
calign
.search:
mov rsi, [r14] ; the chatpanel object
; we need to do a string compare of the names
mov rdi, r12
mov rsi, [rsi+chatpanel_name_ofs]
call string$equals
test eax, eax
jnz .found
mov r14, [r14+_list_nextofs]
test r14, r14
jnz .search
calign
.createone:
; list was empty, or we didn't find it, so fire up a new one
mov rdi, r12
call chatpanel$new
mov rdi, [rbx+screen_user_ofs]
mov r14, rax
mov [r14+chatpanel_user_ofs], rdi
mov [r14+chatpanel_screen_ofs], rbx
test r13d, r13d
jnz .createone_room
; else, 1:1 buddy chat, so do the necessary setup
; if this user is already online, and already has a chatpanel open
; then it is possible that there is already a chatroom in existence
; for this chat
; so the string name in r12 is actually the buddy's name, find it
; in the users map, and see if that user already has one fired up
mov rdi, [users]
mov rsi, r12
call stringmap$find_value
; all buddy names _do_ exist in the users map, no checking is required
; now, we need to walk their tui list and see if we can find
; a chatpanel with our username in it
mov r12, rdx
mov rdx, [rdx+user_tuilist_ofs]
mov r13, [rdx+_avlofs_next] ; first node in the tree
test r13, r13
jz .createone_buddy
calign
.createone_othersearch:
; the key of the tuilist map is their screen object
mov rsi, [rbx+screen_user_ofs]
mov rdi, [r13+_avlofs_key]
mov rsi, [rsi+user_username_ofs]
xor edx, edx
call screen$chatpanel_find
test rax, rax
jnz .createone_buddy_found
mov r13, [r13+_avlofs_next]
test r13, r13
jnz .createone_othersearch
calign
.createone_buddy:
xor edi, edi
xor esi, esi
call chatroom$new
mov [r14+chatpanel_room_ofs], rax
mov rdi, rax
mov rsi, [rbx+screen_user_ofs]
mov rdx, rbx
call chatroom$join
; now, the _remote_ party of the chat also needs to be added
; we don't need to add a tui to it
mov rdi, [r14+chatpanel_room_ofs]
mov rsi, r12
xor edx, edx
call chatroom$join
mov rdi, r14
call chatpanel$titleupdate
mov rdi, [rbx+screen_main_ofs]
mov rsi, r14
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
test r15d, r15d
jz .createone_buddy_skipfocus
; make sure the panel we created does _not_ have focus (chatpanel$new defaults to focussed)
mov rdi, r14
mov rsi, [r14]
call qword [rsi+tui_vlostfocus]
; see commentary atop for what we are up to here:
mov rdi, [rbx+screen_main_ofs]
mov rsi, [rdi+tui_children_ofs]
cmp qword [rsi+_list_size_ofs], 1
jne .createone_buddy_skipfocus
; otherwise, we were the one and only, set our focus to it
mov rdi, [rbx+screen_buddylist_ofs]
ansi_colors ecx, 'lightgray', 'midnightblue'
mov dword [rdi+tui_dgselcolors_ofs], ecx
mov rdx, [rdi]
call qword [rdx+tui_vredraw]
; our chatpanel is in r14 still, set our focus to it
mov [rbx+screen_focus_ofs], r14
mov rdi, r14
mov rsi, [r14]
call qword [rsi+tui_vgotfocus]
; deal with our cursor:
mov rdi, rbx
call screen$showhidecursor
calign
.createone_buddy_skipfocus:
; retile the display
mov rdi, [rbx+screen_main_ofs]
mov esi, 1 ; horizontal tile preferred
call tui_object$nvtile
mov rax, r14
pop r15 r14 r13 r12 rbx
epilog
calign
.createone_buddy_found:
; so, we found a chatpanel open somewhere else that is already pointed to us
; and it is sitting in rax, so its chatpanel_room_ofs object
; is the one that we need to hook to our own
mov rcx, [rax+chatpanel_room_ofs]
mov [r14+chatpanel_room_ofs], rcx
mov rdi, rcx
mov rsi, [rbx+screen_user_ofs]
mov rdx, rbx
call chatroom$join
mov rdi, r14
call chatpanel$titleupdate
mov rdi, [rbx+screen_main_ofs]
mov rsi, r14
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
test r15d, r15d
jz .createone_buddy_skipfocus
; make sure the panel we created does _not_ have focus (chatpanel$new defaults to focussed)
mov rdi, r14
mov rsi, [r14]
call qword [rsi+tui_vlostfocus]
; see commentary atop for what we are up to here:
mov rdi, [rbx+screen_main_ofs]
mov rsi, [rdi+tui_children_ofs]
cmp qword [rsi+_list_size_ofs], 1
jne .createone_buddy_skipfocus
; otherwise, we were the one and only, set our focus to it
mov rdi, [rbx+screen_buddylist_ofs]
ansi_colors ecx, 'lightgray', 'midnightblue'
mov dword [rdi+tui_dgselcolors_ofs], ecx
mov rdx, [rdi]
call qword [rdx+tui_vredraw]
; our chatpanel is in r14 still, set our focus to it
mov [rbx+screen_focus_ofs], r14
mov rdi, r14
mov rsi, [r14]
call qword [rsi+tui_vgotfocus]
; deal with our cursor:
mov rdi, rbx
call screen$showhidecursor
; retile the display
mov rdi, [rbx+screen_main_ofs]
mov esi, 1 ; horizontal tile preferred
call tui_object$nvtile
mov rax, r14
pop r15 r14 r13 r12 rbx
epilog
calign
.createone_room:
; name sitting in r12 is a normal chatroom, see if it already exists:
; we already have a chatpanel in r14
mov rdi, [chatrooms]
mov rsi, r12
call stringmap$find_value
test eax, eax
jnz .createone_room_found
; otherwise, no such room exists, create one
mov rdi, r12
xor esi, esi
call chatroom$new
mov rdx, rax
; fallthrough to finish the deed
calign
.createone_room_found:
mov [r14+chatpanel_room_ofs], rdx
mov rdi, rdx
mov rsi, [rbx+screen_user_ofs]
mov rdx, rbx
call chatroom$join
; join calls unsignedmap$insert_unique, which returns us a bool in eax as to whether we succeeded or not
push rax
mov rdi, r14
call chatpanel$titleupdate
mov rdi, [rbx+screen_main_ofs]
mov rsi, r14
mov rdx, [rdi]
call qword [rdx+tui_vappendchild]
test r15d, r15d
jz .createone_room_skipfocus
; make sure the panel we created does _not_ have focus (chatpanel$new defaults to focussed)
mov rdi, r14
mov rsi, [r14]
call qword [rsi+tui_vlostfocus]
; see commentary atop for what we are up to here:
mov rdi, [rbx+screen_main_ofs]
mov rsi, [rdi+tui_children_ofs]
cmp qword [rsi+_list_size_ofs], 1
jne .createone_room_skipfocus
; otherwise, we were the one and only, set our focus to it
mov rdi, [rbx+screen_buddylist_ofs]
ansi_colors ecx, 'lightgray', 'midnightblue'
mov dword [rdi+tui_dgselcolors_ofs], ecx
mov rdx, [rdi]
call qword [rdx+tui_vredraw]
; our chatpanel is in r14 still, set our focus to it
mov [rbx+screen_focus_ofs], r14
mov rdi, r14
mov rsi, [r14]
call qword [rsi+tui_vgotfocus]
; deal with our cursor:
mov rdi, rbx
call screen$showhidecursor
calign
.createone_room_skipfocus:
; retile the display
mov rdi, [rbx+screen_main_ofs]
mov esi, 1 ; horizontal tile preferred
call tui_object$nvtile
; if we actually inserted ourselves uniquely into the chatroom userlist, do a notify
pop rax
test eax, eax
jz .createone_room_skipnotify
; last but not least, we need to send notifies
mov rdi, [r14+chatpanel_room_ofs]
mov rsi, [rbx+screen_user_ofs]
call chatroom$join_notify
calign
.createone_room_skipnotify:
mov rax, r14
pop r15 r14 r13 r12 rbx
epilog
calign
.found:
; the bool sitting in r13d has to be checked, in case someone
; wants to join a room that has the same name as a buddy
mov rdi, [r14]
mov rsi, [rdi+chatpanel_room_ofs]
mov rdx, [rsi+chatroom_name_ofs]
; if it is a room, rdx will be nonzero
test r13d, r13d
jz .found_checkbuddy
; else, we need it to be a room
test rdx, rdx
jz .notfound
mov rax, rdi ; the chatpanel object
pop r15 r14 r13 r12 rbx
epilog
calign
.found_checkbuddy:
; we need it to be a buddy chat
test rdx, rdx
jnz .notfound
mov rax, rdi
pop r15 r14 r13 r12 rbx
epilog
calign
.notfound:
mov r14, [r14+_list_nextofs]
test r14, r14
jz .createone
jmp .search
; two arguments: rdi == the tui_datagrid object, rsi == the json object that was selected
falign
screen$buddyselected:
prolog screen$buddyselected
; we get called when enter is pressed in our buddylist tui_datagrid
; our tui_dguser_ofs contains the screen object that this datagrid belongs to
; two scenarios here:
; if no chatpanel exists for the buddy selected, fire up a new one, set focus to it
; and be done.
; if a chatpanel exists, focus it and be done
; either way, our dg is losing focus
push rbx r12
mov rbx, rdi
mov r12, rsi
; since I didn't bother to deal with focus/unfocussed tui_datagrid goods, we have
; to fake it... so change the selection color in the dg so there is a visual
; indication it doesn't have focus
ansi_colors ecx, 'lightgray', 'midnightblue'
mov dword [rdi+tui_dgselcolors_ofs], ecx
mov rdx, [rdi]
call qword [rdx+tui_vredraw]
; next up: extract the buddy name from the selected item json
mov rdi, r12
mov rsi, .buddystr
call json$getvaluebyname
; it should have returned us with a jsonvalue object, whos json_value_ofs is the string we want
mov rdi, [rbx+tui_dguser_ofs] ; our screen object
mov rsi, [rax+json_value_ofs]
xor edx, edx
xor ecx, ecx
call screen$chatpanel_byname
; that should have returned us the chatpanel we were after, set input focus to that
mov rdi, [rbx+tui_dguser_ofs]
mov [rdi+screen_focus_ofs], rax
; and call its gotfocus method
mov rdi, rax
mov rsi, [rax]
call qword [rsi+tui_vgotfocus]
mov rdi, [rbx+tui_dguser_ofs]
call screen$showhidecursor
pop r12 rbx
epilog
cleartext .buddystr, 'buddy'
; three arguments: rdi == our screen object, esi == key, edx == esc_key
falign
screen$keyevent:
prolog screen$keyevent
xor eax, eax
epilog
; two arguments: rdi == our screen object, rsi == tui_object that got clicked
; NOTE: these really are only ever generated from modal tui_alert dialogs
; so we can take appropriate action thereof:
falign
screen$clicked:
prolog screen$clicked
mov qword [rdi+screen_modal_ofs], 0
mov rsi, [rdi]
call qword [rsi+tui_vremoveallbastards]
epilog
; in order to capture onenter from tui_textbox during join/create room events
; we need to override its vtable:
dalign
screen_newroom$vtable:
dq tui_panel$cleanup, tui_panel$clone, tui_panel$draw, tui_object$redraw, tui_object$updatedisplaylist, tui_object$sizechanged
dq tui_object$timer, tui_object$layoutchanged, tui_object$move, tui_object$setfocus, tui_object$gotfocus, tui_object$lostfocus
dq tui_object$keyevent, tui_object$domodal, tui_object$endmodal, tui_object$exit, tui_object$calcbounds, tui_object$calcchildbounds
dq tui_panel$appendchild, tui_object$appendbastard, tui_panel$prependchild, tui_panel$contains, tui_panel$getchildindex
dq tui_panel$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_object$setcursor, tui_object$showcursor, tui_object$hidecursor, tui_object$click, tui_object$clicked
; and one for our onenter handling, descendents should override if they are interested
dq screen_newroom$onenter
; and the same again for our add buddy tui_textbox
dalign
screen_addbuddy$vtable:
dq tui_panel$cleanup, tui_panel$clone, tui_panel$draw, tui_object$redraw, tui_object$updatedisplaylist, tui_object$sizechanged
dq tui_object$timer, tui_object$layoutchanged, tui_object$move, tui_object$setfocus, tui_object$gotfocus, tui_object$lostfocus
dq tui_object$keyevent, tui_object$domodal, tui_object$endmodal, tui_object$exit, tui_object$calcbounds, tui_object$calcchildbounds
dq tui_panel$appendchild, tui_object$appendbastard, tui_panel$prependchild, tui_panel$contains, tui_panel$getchildindex
dq tui_panel$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_object$setcursor, tui_object$showcursor, tui_object$hidecursor, tui_object$click, tui_object$clicked
; and one for our onenter handling, descendents should override if they are interested
dq screen_addbuddy$onenter
; and the same once more for our remove buddy tui_textbox
dalign
screen_removebuddy$vtable:
dq tui_panel$cleanup, tui_panel$clone, tui_panel$draw, tui_object$redraw, tui_object$updatedisplaylist, tui_object$sizechanged
dq tui_object$timer, tui_object$layoutchanged, tui_object$move, tui_object$setfocus, tui_object$gotfocus, tui_object$lostfocus
dq tui_object$keyevent, tui_object$domodal, tui_object$endmodal, tui_object$exit, tui_object$calcbounds, tui_object$calcchildbounds
dq tui_panel$appendchild, tui_object$appendbastard, tui_panel$prependchild, tui_panel$contains, tui_panel$getchildindex
dq tui_panel$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_object$setcursor, tui_object$showcursor, tui_object$hidecursor, tui_object$click, tui_object$clicked
; and one for our onenter handling, descendents should override if they are interested
dq screen_removebuddy$onenter
; single argument in rdi == the tui_textbox panel object for our join/create room entry
falign
screen_newroom$onenter:
prolog screen_newroom$onenter
; so our actual tui_text editor is sitting in [rdi+tui_panel_user_ofs]
push rbx r12 r13
mov rbx, [rdi+tui_parent_ofs] ; our screen object
mov rdi, [rdi+tui_panel_user_ofs]
call tui_text$nvgettext
mov r12, rax
cmp qword [rax], 0
je .closeit
; otherwise, fire up a chatpanel for it or focus one we already have:
mov rdi, rbx
mov rsi, r12
mov edx, 1
xor ecx, ecx
call screen$chatpanel_byname
; we have to specifically modify focus
mov r13, [rbx+screen_focus_ofs]
mov [rbx+screen_focus_ofs], rax
; and call its gotfocus method
mov rdi, rax
mov rsi, [rax]
call qword [rsi+tui_vgotfocus]
; lose focus to whatever used ot have it
mov rdi, r13
mov rsi, [r13]
call qword [rsi+tui_vlostfocus]
; show/hide the cursor
mov rdi, rbx
call screen$showhidecursor
; if r13 == the buddylist, fake our lostfocus:
cmp r13, [rbx+screen_buddylist_ofs]
jne .closeit
mov rdi, r13
ansi_colors ecx, 'lightgray', 'midnightblue'
mov dword [rdi+tui_dgselcolors_ofs], ecx
mov rsi, [rdi]
call qword [rsi+tui_vredraw]
; fallthrough to closeit
calign
.closeit:
mov rdi, r12
call heap$free
mov qword [rbx+screen_modal_ofs], 0
mov rdi, rbx
mov rsi, [rbx]
call qword [rsi+tui_vremoveallbastards]
pop r13 r12 rbx
epilog
; single argument in rdi == the tui_textbox panel object for our remove buddy dialog
falign
screen_removebuddy$onenter:
prolog screen_removebuddy$onenter
; so our actual tui_text editor is sitting in [rdi+tui_panel_user_ofs]
push rbx r12
mov rbx, [rdi+tui_parent_ofs] ; our screen object
mov rdi, [rdi+tui_panel_user_ofs]
call tui_text$nvgettext
mov r12, rax
cmp qword [rax], 0
je .closeit
; otherwise, pass it straight to removebuddy
mov rdi, [rbx+screen_user_ofs]
mov rdi, [rdi+user_username_ofs]
mov rsi, r12
call userdb$removebuddy
test rax, rax
jnz .errormsg
; success, so we need to update ALL our tuis (if we are logged in multiple times,
; this action needs to be replicated)
mov rdi, [rbx+screen_user_ofs]
mov rdi, [rdi+user_tuilist_ofs]
mov rsi, screen$updatebuddies
call unsignedmap$foreach
jmp .closeit
calign
.errormsg:
push rax
mov rdi, r12
call heap$free
pop r12
mov qword [rbx+screen_modal_ofs], 0
mov rdi, rbx
mov rsi, [rbx]
call qword [rsi+tui_vremoveallbastards]
; add our error message dialog
mov rdi, .removebuddystr
mov rsi, r12
mov edx, tui_alert_ok
; panel/title colors
ansi_colors ecx, 'black', 'cyan'
; button colors
ansi_colors r8d, 'lightgray', 'black'
; focus button colors
ansi_colors r9d, 'yellow', 'blue'
call tui_alert$new
; save the modal pointer for focus
mov rdi, rbx
mov [rbx+screen_modal_ofs], rax
mov dword [rbx+screen_modalalert_ofs], 1
mov rsi, rax
mov rdx, [rdi]
; set its bastardglue so that it ends up centered
mov dword [rax+tui_bastardglue_ofs], 1
mov dword [rax+tui_horizalign_ofs], tui_align_center
mov dword [rax+tui_vertalign_ofs], tui_align_middle
call qword [rdx+tui_vappendbastard]
; deal with our cursor:
mov rdi, rbx
call screen$showhidecursor
pop r12 rbx
epilog
cleartext .removebuddystr, 'RemoveBuddy'
calign
.closeit:
mov rdi, r12
call heap$free
mov qword [rbx+screen_modal_ofs], 0
mov rdi, rbx
mov rsi, [rbx]
call qword [rsi+tui_vremoveallbastards]
pop r12 rbx
epilog
; single argument in rdi == the tui_textbox panel object for our add buddy dialog
falign
screen_addbuddy$onenter:
prolog screen_addbuddy$onenter
; so our actual tui_text editor is sitting in [rdi+tui_panel_user_ofs]
push rbx r12
mov rbx, [rdi+tui_parent_ofs] ; our screen object
mov rdi, [rdi+tui_panel_user_ofs]
call tui_text$nvgettext
mov r12, rax
cmp qword [rax], 0
je .closeit
; otherwise, pass it straight to addbuddy
mov rdi, [rbx+screen_user_ofs]
mov rdi, [rdi+user_username_ofs]
mov rsi, r12
call userdb$addbuddy
test rax, rax
jnz .errormsg
; success, so we need to update ALL our tuis (if we are logged in multiple times,
; this action needs to be replicated)
mov rdi, [rbx+screen_user_ofs]
mov rdi, [rdi+user_tuilist_ofs]
mov rsi, screen$updatebuddies
call unsignedmap$foreach
jmp .closeit
calign
.errormsg:
push rax
mov rdi, r12
call heap$free
pop r12
mov qword [rbx+screen_modal_ofs], 0
mov rdi, rbx
mov rsi, [rbx]
call qword [rsi+tui_vremoveallbastards]
; add our error message dialog
mov rdi, .addbuddystr
mov rsi, r12
mov edx, tui_alert_ok
; panel/title colors
ansi_colors ecx, 'black', 'cyan'
; button colors
ansi_colors r8d, 'lightgray', 'black'
; focus button colors
ansi_colors r9d, 'yellow', 'blue'
call tui_alert$new
; save the modal pointer for focus
mov rdi, rbx
mov [rbx+screen_modal_ofs], rax
mov dword [rbx+screen_modalalert_ofs], 1
mov rsi, rax
mov rdx, [rdi]
; set its bastardglue so that it ends up centered
mov dword [rax+tui_bastardglue_ofs], 1
mov dword [rax+tui_horizalign_ofs], tui_align_center
mov dword [rax+tui_vertalign_ofs], tui_align_middle
call qword [rdx+tui_vappendbastard]
; deal with our cursor:
mov rdi, rbx
call screen$showhidecursor
pop r12 rbx
epilog
cleartext .addbuddystr, 'Add Buddy'
calign
.closeit:
mov rdi, r12
call heap$free
mov qword [rbx+screen_modal_ofs], 0
mov rdi, rbx
mov rsi, [rbx]
call qword [rsi+tui_vremoveallbastards]
pop r12 rbx
epilog
; three arguments: rdi == our screen object, esi == key, edx == esc_key
falign
screen$firekeyevent:
prolog screen$firekeyevent
; the reason we override this is to mess with the input focus.
; the tui_object default firekeyevent passes keys to ALL children
; stopping when the first one handles it. If we allowed that to
; happen, then only our first child would ever receive keystrokes.
; deal with our commands first, before we pass it to our focusitem
; before we do anything, make sure we update our lastkey time
mov rcx, [_epoll_tv_secs]
mov [rdi+screen_lastkey_ofs], rcx
cmp esi, 3 ; let ctrl-c go, tui_ssh will grab it
je .falseret
; further, since I didn't really do up tui_object's idea of what modal
; is all about, we "fake" that too, by keeping track of it ourselves
cmp qword [rdi+screen_modal_ofs], 0
jne .domodal
cmp esi, 1 ; ctrl-a
je .addbuddy
cmp esi, 2 ; ctrl-b
je .togglebuddylist
cmp esi, 9
je .tab
cmp edx, 0x5a
je .shifttab
cmp esi, 10
je .ctrlj
cmp esi, 12
je .ctrll
cmp esi, 23
je .ctrlw
cmp esi, 18 ; ctrl-r
je .removebuddy
; otherwise, let her rip
mov rdi, [rdi+screen_focus_ofs]
test rdi, rdi
jz .nofocus
mov rcx, [rdi]
call qword [rcx+tui_vfirekeyevent]
; let our return be its return
epilog
calign
.nofocus:
mov eax, 1 ; pretend we handled it in case there was no focus
epilog
calign
.domodal:
cmp esi, 27
je .closemodal
mov rdi, [rdi+screen_modal_ofs]
mov rcx, [rdi]
call qword [rcx+tui_vfirekeyevent]
; let our return be its return
epilog
calign
.closemodal:
push rdi
mov qword [rdi+screen_modal_ofs], 0
mov rsi, [rdi]
call qword [rsi+tui_vremoveallbastards]
pop rdi
call screen$showhidecursor
mov eax, 1
epilog
calign
.togglebuddylist:
mov r9, [rdi+screen_focus_ofs]
mov rsi, [rdi+screen_buddylist_col_ofs]
mov r8, [rdi+screen_buddylist_sep_ofs]
xor eax, eax
mov ecx, 1
mov rdx, [rdi]
cmp dword [rsi+tui_includeinlayout_ofs], 0
cmove eax, ecx
mov [rsi+tui_includeinlayout_ofs], eax
mov [rsi+tui_visible_ofs], eax
mov [r8+tui_includeinlayout_ofs], eax
mov [r8+tui_visible_ofs], eax
cmp r9, [rdi+screen_buddylist_ofs]
je .togglebuddylist_focussed
test r9, r9
jz .togglebuddylist_notfocussed
; otherwise, a window had focus but it wasn't the buddylist so leave focus alone
call qword [rdx+tui_vlayoutchanged]
mov eax, 1
epilog
calign
.togglebuddylist_focussed:
; the buddylist itself had focus, which means we are hiding it
push rdi
call qword [rdx+tui_vlayoutchanged]
mov rdi, [rsp]
mov qword [rdi+screen_focus_ofs], 0
mov rdi, [rdi+screen_buddylist_ofs]
mov rsi, [rdi]
call qword [rsi+tui_vlostfocus]
; possibly move focus by hitting tab
pop rdi
mov rsi, [rdi]
call qword [rsi+tui_vontab]
mov eax, 1
epilog
calign
.togglebuddylist_notfocussed:
; nothing had focus, which means there aren't any other windows, so we are gaining focus
; on the buddylist (and showing it)
push rdi rdx
call qword [rdx+tui_vlayoutchanged]
pop rdx rdi
; the ontab method will set focus to our only option
call qword [rdx+tui_vontab]
mov eax, 1
epilog
cleartext .addbuddytitle, 'Add Buddy'
cleartext .addbuddymsg, 'Enter buddy name, or esc to cancel'
cleartext .removebuddytitle, 'Remove Buddy'
calign
.addbuddy:
; several scenarios here:
; if the focus is on the buddylist, we open an addbuddy dialog with no text
; if the focus is on a chat window, and said chat is 1:1, and said buddy
; is not in our buddylist, we add it without dialog/confirmation
; if the focus is on a chat window, and said chat is 1:1, and said buddy
; is already in our buddylist, we open addbuddy dialog with no text
; if the focus is on a chat window, and said chat is a room, open our
; buddy dialog with no text
mov rsi, [rdi+screen_focus_ofs]
cmp rsi, [rdi+screen_buddylist_ofs]
je .addbuddy_emptytext
; otherwise, our focus is not on our buddylist
; so it must be a chatpanel, see if it is a room or not
mov rdx, [rsi+chatpanel_room_ofs]
cmp qword [rdx+chatroom_name_ofs], 0
jne .addbuddy_fromroom
; otherwise, we are in a 1:1 chatpanel, see if this buddy is already in our list
; if it is, then emptytext
; if it is not, add it without question or interaction
; chatpanel_name_ofs == the name of our remote party
; TODO: change this to just call userdb$addbuddy
push rbx
mov rbx, rdi
mov rdi, [rdi+screen_user_ofs]
mov rdi, [rdi+user_buddylist_ofs]
mov rsi, [rsi+chatpanel_name_ofs]
; first do a quick lookup to determine if it is in our buddy list or not
push rsi
call stringmap$find_value
pop rsi
test eax, eax
jnz .addbuddy_fromchat_empty
; otherwise, we need to do a lookup in the [users] map
mov rdi, [users]
call stringmap$find_value
test eax, eax
jz .addbuddy_fromchat_empty ; sanity only
mov rdi, [rbx+screen_user_ofs]
mov rdi, [rdi+user_buddylist_ofs]
mov rsi, [rdx+user_username_ofs]
push rdx
call stringmap$insert_unique
pop rcx
; add the notifies entry
mov rdi, [rcx+user_notifies_ofs]
mov rdx, [rbx+screen_user_ofs]
mov rsi, [rdx+user_username_ofs]
call stringmap$insert_unique
; now we need to update our buddylist, and then we are done
; success, so we need to update ALL our tuis (if we are logged in multiple times,
; this action needs to be replicated)
mov rdi, [rbx+screen_user_ofs]
mov rdi, [rdi+user_tuilist_ofs]
mov rsi, screen$updatebuddies
call unsignedmap$foreach
; last but not least, save our userdb
call userdb$save
pop rbx
mov eax, 1
epilog
calign
.addbuddy_fromchat_empty:
mov rdi, rbx
pop rbx
; fallthrough to empty
calign
.addbuddy_emptytext:
.addbuddy_fromroom:
push rdi
mov rdi, .addbuddytitle
mov rsi, .addbuddymsg
mov rdx, .emptystr
; panel/title colors
ansi_colors ecx, 'black', 'cyan'
; button colors
ansi_colors r8d, 'lightgray', 'black'
; focus button colors
ansi_colors r9d, 'yellow', 'blue'
call tui_textbox$new
pop rdi
; set its vtable to our newroom vtable so we can capture onenter
mov qword [rax], screen_addbuddy$vtable
; save the modal pointer for focus
mov [rdi+screen_modal_ofs], rax
mov dword [rdi+screen_modalalert_ofs], 0
mov rsi, rax
mov rdx, [rdi]
; set its bastardglue so that it ends up centered
mov dword [rax+tui_bastardglue_ofs], 1
mov dword [rax+tui_horizalign_ofs], tui_align_center
mov dword [rax+tui_vertalign_ofs], tui_align_middle
push rdi
call qword [rdx+tui_vappendbastard]
pop rdi
call screen$showhidecursor
mov eax, 1
epilog
cleartext .buddystr, 'buddy'
calign
.removebuddy:
mov rsi, [rdi+screen_buddylist_ofs]
cmp rsi, [rdi+screen_focus_ofs]
je .removebuddy_fromlist
; not the buddylist, so must be a chatpanel then
; determine if we are in a 1:1 chat or not
mov rsi, [rdi+screen_focus_ofs]
mov rdx, [rsi+chatpanel_room_ofs]
cmp qword [rdx+chatroom_name_ofs], 0
jne .removebuddy_fromroom
; otherwise, we are in a 1:1 chatpanel, see if this buddy is in our list
; if it _is_ in our list, then just remove it
; otherwise, open a text input
push rbx
mov rbx, rdi
mov rdi, [rdi+screen_user_ofs]
mov rdi, [rdi+user_buddylist_ofs]
mov rsi, [rsi+chatpanel_name_ofs]
call stringmap$find_value
test eax, eax
jz .removebuddy_fromroom
; yep, it is there, remove it
mov rdi, [rbx+screen_user_ofs]
mov rsi, [rdx+user_username_ofs]
mov rdi, [rdi+user_username_ofs]
call userdb$removebuddy
; success, so we need to update ALL our tuis (if we are logged in multiple times,
; this action needs to be replicated)
mov rdi, [rbx+screen_user_ofs]
mov rdi, [rdi+user_tuilist_ofs]
mov rsi, screen$updatebuddies
call unsignedmap$foreach
pop rbx
mov eax, 1
epilog
calign
.removebuddy_fromroom:
; open a text input to get the buddy name
push rdi
mov rdi, .removebuddytitle
mov rsi, .addbuddymsg
mov rdx, .emptystr
; panel/title colors
ansi_colors ecx, 'black', 'cyan'
; button colors
ansi_colors r8d, 'lightgray', 'black'
; focus button colors
ansi_colors r9d, 'yellow', 'blue'
call tui_textbox$new
pop rdi
; set its vtable to our newroom vtable so we can capture onenter
mov qword [rax], screen_removebuddy$vtable
; save the modal pointer for focus
mov [rdi+screen_modal_ofs], rax
mov dword [rdi+screen_modalalert_ofs], 0
mov rsi, rax
mov rdx, [rdi]
; set its bastardglue so that it ends up centered
mov dword [rax+tui_bastardglue_ofs], 1
mov dword [rax+tui_horizalign_ofs], tui_align_center
mov dword [rax+tui_vertalign_ofs], tui_align_middle
push rdi
call qword [rdx+tui_vappendbastard]
pop rdi
call screen$showhidecursor
mov eax, 1
epilog
calign
.removebuddy_fromlist:
push rbx
mov rbx, rdi
mov rdi, rsi
call tui_datagrid$nvgetselected
test eax, eax
jz .removebuddy_fromlist_nodeal
; otherwise, rax has our json object, extract the name we are after
mov rdi, rax
mov rsi, .buddystr
call json$getvaluebyname
; it should have returned us with a jsonvalue object, whos json_value_ofs is the string we want
mov rdi, [rbx+screen_user_ofs]
mov rsi, [rax+json_value_ofs]
mov rdi, [rdi+user_username_ofs]
call userdb$removebuddy
; success, so we need to update ALL our tuis (if we are logged in multiple times,
; this action needs to be replicated)
mov rdi, [rbx+screen_user_ofs]
mov rdi, [rdi+user_tuilist_ofs]
mov rsi, screen$updatebuddies
call unsignedmap$foreach
pop rbx
mov eax, 1
epilog
calign
.removebuddy_fromlist_nodeal:
pop rbx
mov eax, 1
epilog
calign
.tab:
mov rsi, [rdi]
call qword [rsi+tui_vontab]
mov eax, 1
epilog
calign
.shifttab:
mov rsi, [rdi]
call qword [rsi+tui_vonshifttab]
mov eax, 1
epilog
cleartext .newroomtitle, 'Join/Create Room'
cleartext .newroommsg, 'Enter room name, or esc to cancel',10,'Rooms that begin with ',0x27,' are unlisted.'
cleartext .emptystr, ''
calign
.ctrlj:
push rdi
mov rdi, .newroomtitle
mov rsi, .newroommsg
mov rdx, .emptystr
; panel/title colors
ansi_colors ecx, 'black', 'cyan'
; button colors
ansi_colors r8d, 'lightgray', 'black'
; focus button colors
ansi_colors r9d, 'yellow', 'blue'
call tui_textbox$new
pop rdi
; set its vtable to our newroom vtable so we can capture onenter
mov qword [rax], screen_newroom$vtable
; save the modal pointer for focus
mov [rdi+screen_modal_ofs], rax
mov dword [rdi+screen_modalalert_ofs], 0
mov rsi, rax
mov rdx, [rdi]
; set its bastardglue so that it ends up centered
mov dword [rax+tui_bastardglue_ofs], 1
mov dword [rax+tui_horizalign_ofs], tui_align_center
mov dword [rax+tui_vertalign_ofs], tui_align_middle
push rdi
call qword [rdx+tui_vappendbastard]
pop rdi
call screen$showhidecursor
mov eax, 1
epilog
calign
.ctrll:
push rdi
call roomlist$new
pop rdi
mov [rdi+screen_modal_ofs], rax
mov dword [rdi+screen_modalalert_ofs], 0
mov rsi, rax
mov rdx, [rdi]
; set its bastardglue so that it ends up centered
mov dword [rax+tui_bastardglue_ofs], 1
mov dword [rax+tui_horizalign_ofs], tui_align_center
mov dword [rax+tui_vertalign_ofs], tui_align_middle
push rdi
call qword [rdx+tui_vappendbastard]
pop rdi
call screen$showhidecursor
mov eax, 1
epilog
calign
.ctrlw:
; if the focus is on the buddylist, do nothing
mov rdx, [rdi+screen_focus_ofs]
cmp rdx, [rdi+screen_buddylist_ofs]
je .falseret
; otherwise, save the current focus, do an ontab first, then KO the window
push rbx r12
mov rbx, rdi
mov r12, rdx
mov rsi, [rdi]
call qword [rsi+tui_vontab]
; remove the window from our main object
mov rdi, [rbx+screen_main_ofs]
mov rsi, r12
mov rdx, [rdi]
call qword [rdx+tui_vremovechild]
; removechild doesn't actually destroy the object, so do that next
mov rdi, r12
mov rsi, [r12]
call qword [rsi+tui_vcleanup]
mov rdi, r12
call heap$free
; retile our main window
mov rdi, [rbx+screen_main_ofs]
call tui_object$nvtile
pop r12 rbx
mov eax, 1
epilog
calign
.falseret:
xor eax, eax
epilog
; single argument in rdi: our screen object
falign
screen$ontab:
prolog screen$ontab
; burning purpose: change input focus in a sensible manner
mov rsi, [rdi+screen_focus_ofs]
test rsi, rsi
jz .nofocus
cmp rsi, [rdi+screen_buddylist_ofs]
je .onbuddylist
; otherwise, we are on a chatpanel, walk through the list and find it
mov rdx, [rdi+screen_main_ofs]
mov rcx, [rdx+tui_children_ofs]
mov rdx, [rcx+_list_first_ofs]
test rdx, rdx
jz .onbuddylist
calign
.search:
cmp rsi, [rdx]
je .found
; we -will- find it, so we don't need to do any checking
mov rdx, [rdx+_list_nextofs]
jmp .search
calign
.found:
push rdi rdx
mov rdi, rsi
mov rsi, [rsi]
call qword [rsi+tui_vlostfocus]
pop rdx rdi
; so now, set rdx to its previous position, and if it is invalid, buddylist it is
mov rdx, [rdx+_list_prevofs]
test rdx, rdx
jz .found_noprevious
.found_listfocus:
; otherwise, focus to [rdx]
push rdi
mov rsi, [rdx]
mov [rdi+screen_focus_ofs], rsi
mov rdi, rsi
mov rsi, [rsi]
call qword [rsi+tui_vgotfocus]
; show/hide the cursor
pop rdi
call screen$showhidecursor
epilog
calign
.found_noprevious:
; if the buddylist is visible, go ahead and focus it
mov r9, [rdi+screen_buddylist_col_ofs]
cmp dword [r9+tui_includeinlayout_ofs], 0
jne .focusbuddy
; otherwise, set focus to the other end of our main children list
mov rdx, [rdi+screen_main_ofs]
mov rcx, [rdx+tui_children_ofs]
mov rdx, [rcx+_list_last_ofs]
jmp .found_listfocus
calign
.focusbuddy:
; because we are faking tui_datagrid focuscolors, update them here too
mov rsi, [rdi+screen_buddylist_ofs]
ansi_colors ecx, 'lightgray', 'blue'
mov dword [rsi+tui_dgselcolors_ofs], ecx
push rdi
mov [rdi+screen_focus_ofs], rsi
mov rdi, rsi
mov rsi, [rsi]
call qword [rsi+tui_vredraw]
mov rdi, [rsp]
mov rdi, [rdi+screen_buddylist_ofs]
mov rsi, [rdi]
call qword [rsi+tui_vgotfocus]
pop rdi
call screen$showhidecursor
epilog
calign
.nofocus:
mov rdx, [rdi+screen_main_ofs]
mov rcx, [rdx+tui_children_ofs]
mov rdx, [rcx+_list_first_ofs]
mov r9, [rdi+screen_buddylist_col_ofs]
cmp dword [r9+tui_includeinlayout_ofs], 0
jne .focusbuddy
; otherwise, buddylist is not visible, see if we have any other windows
test rdx, rdx
jz .nothingtodo
; otherwise, focus to [rdx]
push rdi
mov rsi, [rdx]
mov [rdi+screen_focus_ofs], rsi
mov rdi, rsi
mov rsi, [rsi]
call qword [rsi+tui_vgotfocus]
; show/hide the cursor
pop rdi
call screen$showhidecursor
epilog
calign
.onbuddylist:
mov rdx, [rdi+screen_main_ofs]
mov rcx, [rdx+tui_children_ofs]
mov rdx, [rcx+_list_last_ofs]
test rdx, rdx
jz .nothingtodo
mov rsi, [rdx]
mov [rdi+screen_focus_ofs], rsi
; unfocus the buddylist
push rdi
mov rdi, [rdi+screen_buddylist_ofs]
ansi_colors ecx, 'lightgray', 'midnightblue'
mov dword [rdi+tui_dgselcolors_ofs], ecx
mov rsi, [rdi]
call qword [rsi+tui_vredraw]
mov rdi, [rsp]
mov rdi, [rdi+screen_focus_ofs]
mov rsi, [rdi]
call qword [rsi+tui_vgotfocus]
pop rdi
call screen$showhidecursor
epilog
calign
.nothingtodo:
epilog
; single argument in rdi: our screen object
falign
screen$onshifttab:
prolog screen$onshifttab
; burning purpose: change input focus in a sensible manner
mov rsi, [rdi+screen_focus_ofs]
test rsi, rsi
jz .nofocus
cmp rsi, [rdi+screen_buddylist_ofs]
je .onbuddylist
; otherwise, we are on a chatpanel, walk through the list and find it
mov rdx, [rdi+screen_main_ofs]
mov rcx, [rdx+tui_children_ofs]
mov rdx, [rcx+_list_first_ofs]
test rdx, rdx
jz .onbuddylist
calign
.search:
cmp rsi, [rdx]
je .found
; we -will- find it, so we don't need to do any checking
mov rdx, [rdx+_list_nextofs]
jmp .search
calign
.found:
push rdi rdx
mov rdi, rsi
mov rsi, [rsi]
call qword [rsi+tui_vlostfocus]
pop rdx rdi
; so now, set rdx to its next position, and if it is invalid, buddylist it is
mov rdx, [rdx+_list_nextofs]
test rdx, rdx
jz .found_nonext
.found_listfocus:
; otherwise, focus to [rdx]
mov rsi, [rdx]
mov [rdi+screen_focus_ofs], rsi
push rdi
mov rdi, rsi
mov rsi, [rsi]
call qword [rsi+tui_vgotfocus]
pop rdi
call screen$showhidecursor
epilog
calign
.found_nonext:
mov r9, [rdi+screen_buddylist_col_ofs]
cmp dword [r9+tui_includeinlayout_ofs], 0
jne .focusbuddy
; otherwise, set focus to the other end of our main children list
mov rdx, [rdi+screen_main_ofs]
mov rcx, [rdx+tui_children_ofs]
mov rdx, [rcx+_list_first_ofs]
jmp .found_listfocus
calign
.focusbuddy:
; because we are faking tui_datagrid focuscolors, update them here too
mov rsi, [rdi+screen_buddylist_ofs]
ansi_colors ecx, 'lightgray', 'blue'
mov dword [rsi+tui_dgselcolors_ofs], ecx
mov [rdi+screen_focus_ofs], rsi
push rdi
mov rdi, rsi
mov rsi, [rsi]
call qword [rsi+tui_vredraw]
pop rdi
call screen$showhidecursor
epilog
calign
.nofocus:
mov rdx, [rdi+screen_main_ofs]
mov rcx, [rdx+tui_children_ofs]
mov rdx, [rcx+_list_first_ofs]
mov r9, [rdi+screen_buddylist_col_ofs]
cmp dword [r9+tui_includeinlayout_ofs], 0
jne .focusbuddy
; otherwise, buddylist is not visible, see if we have any other windows
test rdx, rdx
jz .nothingtodo
; otherwise, focus to [rdx]
mov rsi, [rdx]
mov [rdi+screen_focus_ofs], rsi
push rdi
mov rdi, rsi
mov rsi, [rsi]
call qword [rsi+tui_vgotfocus]
pop rdi
call screen$showhidecursor
epilog
calign
.onbuddylist:
mov rdx, [rdi+screen_main_ofs]
mov rcx, [rdx+tui_children_ofs]
mov rdx, [rcx+_list_first_ofs]
test rdx, rdx
jz .nothingtodo
mov rsi, [rdx]
mov [rdi+screen_focus_ofs], rsi
; unfocus the buddylist
push rdi
mov rdi, [rdi+screen_buddylist_ofs]
ansi_colors ecx, 'lightgray', 'midnightblue'
mov dword [rdi+tui_dgselcolors_ofs], ecx
mov rsi, [rdi]
call qword [rsi+tui_vredraw]
mov rdi, [rsp]
mov rdi, [rdi+screen_focus_ofs]
mov rsi, [rdi]
call qword [rsi+tui_vgotfocus]
pop rdi
call screen$showhidecursor
epilog
calign
.nothingtodo:
epilog