ponydos/viewer.asm
2023-03-29 13:17:07 +03:00

1278 lines
22 KiB
NASM
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

%include "ponydos.inc"
cpu 8086
bits 16
WINDOW_STATUS_NORMAL equ 0
WINDOW_STATUS_MOVE equ 1
WINDOW_STATUS_RESIZE equ 2
; Resize button, title, space, close button
WINDOW_MIN_WIDTH equ 1 + 8 + 1 + 1
WINDOW_MIN_HEIGHT equ 3
; 0x0000
jmp near process_event
; 0x0003 PROC_INITIALIZE_ENTRYPOINT
; initialize needs to preserve ds
initialize:
push ds
; On entry, ds and es will not be set correctly for us
mov bp, cs
mov es, bp
mov ds, bp
call hook_self_onto_window_chain
call render_window
; We must explicitly request redraw from the compositor
call request_redraw
pop ds
retf
; process_event needs to preserve all registers other than ax
; in:
; al = event
; bx = window ID
; cx, dx = event-specific
; out:
; ax = event-specific
process_event:
push bx
push cx
push dx
push si
push di
push bp
push ds
push es
; On entry, ds and es will not be set correctly for us
; WM_OPEN_FILE needs ds to be left-as is
cmp al, WM_OPEN_FILE
je .no_set_ds
push cs
pop ds
.no_set_ds:
push cs
pop es
cmp al, WM_PAINT
jne .not_paint
call event_paint
jmp .end
.not_paint:
cmp al, WM_MOUSE
jne .not_mouse
call event_mouse
jmp .end
.not_mouse:
cmp al, WM_KEYBOARD
jne .not_keyboard
call event_keyboard
jmp .end
.not_keyboard:
cmp al, WM_UNHOOK
jne .not_unhook
call event_unhook
jmp .end
.not_unhook:
cmp al, WM_OPEN_FILE
jne .not_open_file
call event_open_file
jmp .end
.not_open_file:
.end:
cmp byte [exiting], 0
je .not_exiting
call deallocate_own_memory
.not_exiting:
pop es
pop ds
pop bp
pop di
pop si
pop dx
pop cx
pop bx
retf
; ------------------------------------------------------------------
; Filename handling
; ------------------------------------------------------------------
; in:
; cl = typed character
; ch = pressed key
; out:
; clobbers bx
filename_char_add:
cmp word [cur_filename_address], filename_window_data.filename + 2*(FS_DIRENT_NAME_SIZE-1)
je .done
mov bx, [cur_filename_address]
mov byte [bx], cl
add word [cur_filename_address], 2
call request_redraw
.done:
ret
; out:
; clobbers bx
filename_char_del:
cmp word [cur_filename_address], filename_window_data.filename
je .done
mov bx, [cur_filename_address]
mov byte [bx - 2], 0x00
sub word [cur_filename_address], 2
call request_redraw
.done:
ret
; out:
; clobbers everything
filename_ok:
; Just ignore if there's no filename.
cmp byte [filename_window_data.filename], 0
je .end
mov cx, FS_DIRENT_NAME_SIZE
mov di, window_title
mov si, filename_window_data.filename
.loop:
lodsb
stosb
inc si
loop .loop
mov si, window_title
xor dx, dx ; do create empty file
call PONYDOS_SEG:SYS_OPEN_FILE
test ax, ax
jnz .got_file
mov si, ood_window
call raise_error
ret
.got_file:
call allocate_segment
test dx, dx
jnz .got_memory
mov si, oom_window
call raise_error
ret
.got_memory:
mov [cur_file_address + 2], dx
mov word [cur_file_address], 0
mov word [beg_file_address], 0
push es
mov es, dx
xor bx, bx
xor di, di ; read
call PONYDOS_SEG:SYS_MODIFY_SECTORS
pop es
mov ch, cl
xor cl, cl
shl cx, 1 ; Multiply by 512
mov [end_file_address], cx
mov si, text_window
mov di, window
mov cx, window_struc.size
rep movsb
mov byte [file_opened], 1
call render_window
call request_redraw
.end:
ret
; ------------------------------------------------------------------
; Text file handling
; ------------------------------------------------------------------
; in:
; es:di = where to start printing on screen
print_file:
push ax
push bx
push cx
push dx
push si
push di
push ds
lds si, [cur_file_address]
mov cx, [cs:window.height]
dec cx
mov dx, [cs:window.width]
mov bl, 1 ; Haven't read anything yet
.window_loop:
push di
.line_loop:
cmp si, [cs:end_file_address]
jne .not_end_file
test bl, bl
jz .end_window_loop ; Need to have read something to hit end-of-file
.not_end_file:
lodsb
xor bl, bl ; Have read something
; Special byte handling
cmp al, 0x0A ; \n
je .next_line
cmp al, 0x09 ; \t
jne .null_check
add di, 8
sub dx, 4
jc .next_line
jz .next_line
jmp .line_loop
.null_check:
test al, al
jz .end_window_loop
stosb
inc di
dec dx
jnz .line_loop
.next_line:
mov dx, [cs:window.width]
pop di
add di, [cs:window.width]
add di, [cs:window.width]
loop .window_loop
.ret:
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
.end_window_loop:
pop di
jmp .ret
; out:
; dx = non-zero if cur_file_address is updated
file_next_line:
push ax
push si
push ds
xor dx, dx
lds si, [cur_file_address]
.loop:
lodsb
cmp al, 0x0A ; \n
je .found_next_line
test al, al
jz .ret
cmp si, [cs:end_file_address]
je .ret
jmp .loop
.found_next_line:
cmp si, [cs:end_file_address]
je .ret
cmp byte [ds:si], 0
je .ret
not dx
mov [cs:cur_file_address], si
.ret:
pop ds
pop si
pop ax
ret
; out:
; dx = non-zero if cur_file_address is updated
file_prev_line:
push ax
push si
push ds
std
xor dx, dx
lds si, [cur_file_address]
cmp si, [cs:beg_file_address] ; Already at the beginning?
je .ret
dec si
cmp si, [cs:beg_file_address] ; Last line was empty?
je .ret
dec si
.loop:
cmp si, [cs:beg_file_address]
je .found_prev_line
lodsb
cmp al, 0x0A ; \n
jne .loop
inc si
inc si
.found_prev_line:
not dx
mov [cs:cur_file_address], si
.ret:
cld
pop ds
pop si
pop ax
ret
; ------------------------------------------------------------------
; Event handlers
; ------------------------------------------------------------------
; in:
; al = WM_PAINT
; bx = window ID
; out:
; clobbers everything
event_paint:
; Forward the paint event to the next window in the chain
; We must do this before we paint ourselves, because painting must
; happen from the back to the front
; Because we only have one window, we don't need to save our own ID
mov bx, [window_next]
call send_event
mov bx, [window.width] ; Buffer width, usually same as window width
mov cx, [window.width]
mov dx, [window.height]
mov di, [window.x]
mov bp, [window.y]
mov si, [window.data_address]
cmp di, 0
jge .not_clip_left
.clip_left:
; Adjust the start of buffer to point to the first cell
; that is on screen
sub si, di
sub si, di
; Adjust window width to account for non-rendered area
; that is off screen
add cx, di
; Set X to 0
xor di, di
.not_clip_left:
mov ax, di
add ax, cx
cmp ax, COLUMNS
jle .not_clip_right
.clip_right:
; Adjust the width to only go as far as the right edge
sub ax, COLUMNS
sub cx, ax
.not_clip_right:
mov ax, bp
add ax, dx
cmp ax, ROWS
jle .not_clip_bottom
.clip_bottom:
; Adjust the height to only go as far as the bottom edge
sub ax, ROWS
sub dx, ax
.not_clip_bottom:
call PONYDOS_SEG:SYS_DRAW_RECT
ret
; in:
; al = WM_MOUSE
; bx = window ID
; cl = X
; ch = Y
; dl = mouse buttons held down
; out:
; clobbers everything
event_mouse:
test dl, MOUSE_PRIMARY | MOUSE_SECONDARY
jnz .not_end_window_change
; If we were moving or resizing the window, releasing the
; button signals the end of the action
mov byte [window_status], WINDOW_STATUS_NORMAL
.not_end_window_change:
; Expand X and Y to 16 bits for easier calculations
; Because we only have one window, we don't need to save our own ID
xor bx, bx
mov bl, ch
xor ch, ch
; Are we moving the window at the moment?
cmp byte [window_status], WINDOW_STATUS_MOVE
jne .not_moving
call move_window
.not_moving:
; Are we resizing the window at the moment?
cmp byte [window_status], WINDOW_STATUS_RESIZE
jne .not_resizing
call resize_window
.not_resizing:
; Check if the mouse is outside our window
cmp cx, [window.x]
jl .outside ; x < window_x
cmp bx, [window.y]
jl .outside ; y < window_y
mov ax, [window.x]
add ax, [window.width]
cmp ax, cx
jle .outside ; window_x + window_width <= x
mov ax, [window.y]
add ax, [window.height]
cmp ax, bx
jle .outside ; window_y + window_height <= y
.inside:
cmp byte [window_mouse_released_inside], 0
je .not_click
test dl, MOUSE_PRIMARY | MOUSE_SECONDARY
jz .not_click
.click:
call event_click
.not_click:
; We need to keep track of if the mouse has been inside our
; window without the buttons held, in order to avoid
; generating click events in cases where the cursor is
; dragged into our window while buttons are held
test dl, MOUSE_PRIMARY | MOUSE_SECONDARY
jz .buttons_not_held
.buttons_held:
mov byte [window_mouse_released_inside], 0
jmp .buttons_end
.buttons_not_held:
mov byte [window_mouse_released_inside], 1
.buttons_end:
; We must forward the event even if it was inside our
; window, to make sure other windows know when the mouse
; leaves them
; Set x and y to 255 so that windows below ours don't think
; the cursor is inside them
; Also clear the mouse buttons not absolutely necessary
; but it's cleaner if other windows don't get any
; information about the mouse
mov al, WM_MOUSE
mov bx, [window_next]
mov cx, 0xffff
xor dl, dl
call send_event
ret
.outside:
mov byte [window_mouse_released_inside], 0
; Not our window, forward the event
mov al, WM_MOUSE
mov ch, bl ; Pack the X and Y back into cx
mov bx, [window_next]
call send_event
ret
ret
; in:
; bx = Y
; cx = X
; dl = mouse buttons
event_click:
push ax
; This is not a true event passed into our event handler, but
; rather one we've synthetized from the mouse event
; The reason we synthetize this event is because most interface
; elements react to clicks specifically, so having this event
; making implementing them easier
; Raising a window is done by first unhooking, then rehooking it to
; the window chain
call unhook_self_from_window_chain
call hook_self_onto_window_chain
call request_redraw
; Did the user click the title bar?
cmp [window.y], bx
jne .not_title_bar
.title_bar:
; Did the user click the window close button?
mov ax, [window.x]
add ax, [window.width]
dec ax
cmp ax, cx
jne .not_close
.close:
call close_window
jmp .end
.not_close:
; Did the user click on the resize button?
cmp byte [file_opened], 0
je .not_resize ; Can't resize while entering filename
cmp [window.x], cx
jne .not_resize
.resize:
mov byte [window_status], WINDOW_STATUS_RESIZE
jmp .end
.not_resize:
; Clicking on the title bar signals beginning of a window
; move
mov byte [window_status], WINDOW_STATUS_MOVE
mov ax, [window.x]
sub ax, cx
mov [window_move_x_offset], ax
jmp .end
.not_title_bar:
cmp byte [file_opened], 0
jne .not_filename_window
.filename_window:
sub bx, [window.y]
sub cx, [window.x]
mov ax, [window.width]
shl ax, 1
mul bx
add ax, cx
add ax, cx
add ax, filename_window_data
mov bx, ax
inc bx
cmp byte [bx], CANCEL_COLOR
jne .not_cancel
.cancel:
call unhook_self_from_window_chain
mov byte [exiting], 1
jmp .end
.not_cancel:
cmp byte [bx], OK_COLOR
jne .not_ok
.ok:
call filename_ok
.not_ok:
jmp .end
.not_filename_window:
mov ax, [window.x]
add ax, [window.width]
dec ax
cmp ax, cx
jne .not_scroll_bar
; Scroll up button?
mov ax, [window.y]
inc ax
cmp ax, bx
jne .not_scroll_up
.scroll_up:
call file_prev_line
test dx, dx
jz .end
call render_window
call request_redraw
jmp .end
.not_scroll_up:
; Scroll down button?
add ax, [window.height]
dec ax
dec ax
cmp ax, bx
jne .not_scroll_down
.scroll_down:
call file_next_line
test dx, dx
jz .end
call render_window
call request_redraw
.not_scroll_down:
.not_scroll_bar:
.end:
pop ax
ret
; in:
; al = WM_KEYBOARD
; bx = window ID
; cl = typed character
; ch = pressed key
; out:
; clobbers everything
event_keyboard:
cmp byte [file_opened], 0
jne .file_opened
cmp word [window.data_address], filename_window_data
jne .ret ; error windows
.file_not_opened:
cmp ch, 0x0e
jne .not_backspace
.backspace:
call filename_char_del
ret
.not_backspace:
cmp ch, 0x1c
jne .not_enter
.enter:
call filename_ok
ret
.not_enter:
call filename_char_add
ret
.file_opened:
cmp ch, 0x50 ; down key
jne .up_key_check
.down_key:
call file_next_line
test dx, dx
jz .ret
call render_window
call request_redraw
ret
.up_key_check:
cmp ch, 0x48 ; up key
jne .space_check
.up_key:
call file_prev_line
test dx, dx
jz .ret
call render_window
call request_redraw
ret
.space_check:
cmp cl, ' '
jne .ret
.space:
; Go down eight lines
xor ax, ax
mov cx, 8
.loop:
call file_next_line
or ax, dx
loop .loop
test ax, ax
jz .ret
call render_window
call request_redraw
.ret:
ret
; in:
; al = WM_UNHOOK
; bx = window ID
; cx = window ID of the window to unhook from the window chain
; out:
; ax = own window ID if we did not unhook
; next window ID if we did
; clobbers everything else
event_unhook:
cmp bx, cx
je .unhook_self
; Save our own ID
push bx
; Propagate the event
mov bx, [window_next]
call send_event
; Update window_next in case the next one unhooked
mov [window_next], ax
; Return our own ID
pop ax
ret
.unhook_self:
; Return window_next to the caller, unhooking us from the
; chain
mov ax, [window_next]
ret
; in:
; al = WM_OPEN_FILE
; ds:cx = filename
; ds ≠ cs
; out:
; ds = cs
; clobbers everything
event_open_file:
; Copy the file name over
mov si, cx
mov di, [es:cur_filename_address]
.copy_filename:
lodsb
test al, al
jz .copy_end
stosb
inc di
jmp .copy_filename
.copy_end:
mov [es:cur_filename_address], di
; Set ds to be as expected by most of the program
push cs
pop ds
; Mark that the filename came from WM_OPEN_FILE
mov byte [filename_from_wm_open_file], 1
call filename_ok
ret
; ------------------------------------------------------------------
; Event handler subroutines
; ------------------------------------------------------------------
; out:
; clobbers si, di, cx
close_window:
; If filename was from WM_OPEN_FILE event instead of user input,
; exit the app instead of going to name input dialog
cmp byte [filename_from_wm_open_file], 0
jne .exit_app
cmp word [window.data_address], oom_window_data
je .error_window
cmp word [window.data_address], ood_window_data
je .error_window
.exit_app:
call unhook_self_from_window_chain
mov byte [exiting], 1
ret
.error_window:
mov si, filename_window
mov di, window
mov cx, window_struc.size
rep movsb
call request_redraw
ret
; in:
; bx = Y
; cx = X
move_window:
push ax
; Offset the X coördinate so that the apparent drag position
; remains the same
mov ax, cx
add ax, [window_move_x_offset]
; Only do an update if something has changed. Reduces flicker
cmp [window.x], ax
jne .update_location
cmp [window.y], bx
jne .update_location
jmp .end
.update_location:
mov [window.x], ax
mov [window.y], bx
call request_redraw
.end:
pop ax
ret
; in:
; bx = Y
; cx = X
resize_window:
push ax
push bx
push bp
; Calculate new width
mov ax, [window.width]
add ax, [window.x]
sub ax, cx
cmp ax, WINDOW_MIN_WIDTH
jge .width_large_enough
mov ax, WINDOW_MIN_WIDTH
.width_large_enough:
cmp ax, COLUMNS
jle .width_small_enough
mov ax, COLUMNS
.width_small_enough:
; Calculate new height
mov bp, [window.height]
add bp, [window.y]
sub bp, bx
cmp bp, WINDOW_MIN_HEIGHT
jge .height_large_enough
mov bp, WINDOW_MIN_HEIGHT
.height_large_enough:
cmp bp, ROWS
jle .height_small_engough
mov bp, ROWS
.height_small_engough:
; Only do an update if something has changed. Reduces flicker
cmp [window.width], ax
jne .update_size
cmp [window.height], bp
jne .update_size
jmp .end
.update_size:
mov bx, [window.x]
add bx, [window.width]
sub bx, ax
mov [window.x], bx
mov [window.width], ax
mov bx, [window.y]
add bx, [window.height]
sub bx, bp
mov [window.y], bx
mov [window.height], bp
call render_window
call request_redraw
.end:
pop bp
pop bx
pop ax
render_window:
push ax
push cx
push dx
push si
push di
; Clear window to be black-on-white
mov di, text_window_data
mov ax, [window.width]
mov cx, [window.height]
mul cx
mov cx, ax
mov ax, 0xf000 ; Attribute is in the high byte
rep stosw
; Set title bar to be white-on-black
mov di, text_window_data
mov ax, 0x0f00
mov cx, [window.width]
rep stosw
; Add title bar buttons
mov di, text_window_data
mov byte [di], 0x17 ; Resize arrow
add di, [window.width]
add di, [window.width]
sub di, 2
mov byte [di], 'x' ; Close button
; Add window title
mov di, text_window_data
add di, 2
mov si, window_title
mov cx, [window.width]
dec cx
dec cx
cmp cx, FS_DIRENT_NAME_SIZE
jle .copy_title
mov cx, FS_DIRENT_NAME_SIZE
.copy_title:
lodsb
stosb
inc di
loop .copy_title
; Print text
mov di, text_window_data
add di, [window.width]
add di, [window.width]
call print_file
add di, [window.width]
add di, [window.width]
sub di, 2
mov byte [di], 0x1E ; up
mov ax, [window.width]
mov cx, [window.height]
sub cx, 2
shl cx, 1
mul cx
add di, ax
mov byte [di], 0x1F ; down
pop di
pop si
pop dx
pop cx
pop ax
ret
; ------------------------------------------------------------------
; Window chain
; ------------------------------------------------------------------
; in:
; al = event
; bx = window to send the event to
; cx, dx = event-specific
; out:
; ax = event-specific, 0 if bx=0
send_event:
test bx, bx
jnz .non_zero_id
; Returning 0 if the window ID is 0 makes window unhooking simpler
xor ax, ax
ret
.non_zero_id:
push bp
; Push the return address
push cs
mov bp, .end
push bp
; Push the address we're doing a far-call to
mov bp, bx
and bp, 0xf000 ; Highest nybble of window ID marks the segment
push bp
xor bp, bp ; Event handler is always at address 0
push bp
retf
.end:
pop bp
ret
hook_self_onto_window_chain:
push ax
push es
mov ax, PONYDOS_SEG
mov es, ax
; Window ID is made of the segment (top nybble) and an arbitrary
; process-specific part (lower three nybbles). Since we only have
; one window, we can leave the process-specific part as zero
mov ax, cs
xchg [es:GLOBAL_WINDOW_CHAIN_HEAD], ax
; Save the old head of the chain, so that we can propagate events
; down to it
mov [window_next], ax
pop es
pop ax
ret
unhook_self_from_window_chain:
push bx
push cx
push es
mov ax, PONYDOS_SEG
mov es, ax
mov al, WM_UNHOOK
mov bx, [es:GLOBAL_WINDOW_CHAIN_HEAD]
; Our window ID is just our segment, see the comment in
; hook_self_onto_window_chain
mov cx, cs
call send_event
; Update the head of the chain, in case we were at the head
mov [es:GLOBAL_WINDOW_CHAIN_HEAD], ax
pop es
pop cx
pop bx
ret
; ------------------------------------------------------------------
; Error handling
; ------------------------------------------------------------------
; in:
; si = window of error type
; out:
; clobbers si, di, cx
raise_error:
mov di, window
mov cx, window_struc.size
rep movsb
call request_redraw
ret
; ------------------------------------------------------------------
; Memory management
; ------------------------------------------------------------------
; out:
; dx = segment, 0 for none found
allocate_segment:
push ax
push cx
push si
push es
mov ax, PONYDOS_SEG
mov es, ax
mov si, GLOBAL_MEMORY_ALLOCATION_MAP
mov cx, MEM_ALLOCATION_MAP_SIZE
.find_free_segment:
mov al, [es:si]
test al, al
jz .found_free_segment
inc si
loop .find_free_segment
xor dx, dx
jmp .end
.found_free_segment:
mov byte [es:si], 1 ; Mark as used
; Set up ax to point to the allocated segment
sub si, GLOBAL_MEMORY_ALLOCATION_MAP
mov cl, 12
shl si, cl
mov dx, si
.end:
pop es
pop si
pop cx
pop ax
ret
deallocate_own_memory:
push bx
push cx
push es
mov bx, PONYDOS_SEG
mov es, bx
cmp byte [file_opened], 0
je .file_not_opened
.file_opened:
mov bx, [cur_file_address + 2]
mov cl, 12
shr bx, cl
mov byte [es:GLOBAL_MEMORY_ALLOCATION_MAP + bx], 0
.file_not_opened:
mov bx, cs
mov cl, 12
shr bx, cl
mov byte [es:GLOBAL_MEMORY_ALLOCATION_MAP + bx], 0
pop es
pop cx
pop bx
ret
; ------------------------------------------------------------------
; Painting
; ------------------------------------------------------------------
request_redraw:
push ax
push es
mov ax, PONYDOS_SEG
mov es, ax
mov byte [es:GLOBAL_REDRAW], 1
pop es
pop ax
ret
; ------------------------------------------------------------------
; Variables
; ------------------------------------------------------------------
filename_from_wm_open_file db 0
exiting db 0
window_title times FS_DIRENT_NAME_SIZE db 0
cur_file_address: dw 0
dw 0 ; Segment
beg_file_address dw 0
end_file_address dw 0
window:
.x dw 24
.y dw 9
.width dw FS_DIRENT_NAME_SIZE + 2
.height dw 6
.data_address dw filename_window_data
struc window_struc
.x resw 1
.y resw 1
.width resw 1
.height resw 1
.data_address resw 1
.size:
endstruc
filename_window:
.x dw 24
.y dw 9
.width dw FS_DIRENT_NAME_SIZE + 2
.height dw 6
.data_address dw filename_window_data
text_window:
.x dw 17
.y dw 7
.width dw 52
.height dw 16
.data_address dw text_window_data
oom_window:
.x dw 30
.y dw 10
.width dw 13
.height dw 2
.data_address dw oom_window_data
ood_window:
.x dw 36
.y dw 13
.width dw 17
.height dw 2
.data_address dw ood_window_data
window_next dw 0xffff
window_mouse_released_inside db 0
window_status db WINDOW_STATUS_NORMAL
window_move_x_offset dw 0
cur_filename_address dw filename_window_data.filename
file_opened db 0
; pre-built windows
CANCEL_COLOR equ 0x8f
OK_COLOR equ 0x20
filename_window_data:
; header
db 'V', 0x0f, 'i', 0x0f, 'e', 0x0f, 'w', 0x0f, 'e', 0x0f, 'r', 0x0f
times FS_DIRENT_NAME_SIZE + 2 - 7 db 0, 0x0f
db 'x', 0x0f
; blank line
times FS_DIRENT_NAME_SIZE + 2 db 0, 0x70
; filename
db 0, 0x70
.filename:
times FS_DIRENT_NAME_SIZE db 0, 0xf0
db 0, 0x70
; blank line
times FS_DIRENT_NAME_SIZE + 2 db 0, 0x70
; buttons line
times 7 db 0, 0x70
db 0, 0x8f, 'C', 0x8f, 'a', 0x8f, 'n', 0x8f, 'c', 0x8f, 'e', 0x8f, 'l', 0x8f, 0, 0x8f
times 5 db 0, 0x70
db 0, 0x20, 'O', 0x20, 'K', 0x20, 0x, 0x20
times 8 db 0, 0x70
; blank line
times FS_DIRENT_NAME_SIZE + 2 db 0, 0x70
oom_window_data:
db 'E', 0x0f, 'r', 0x0f, 'r', 0x0f, 'o', 0x0f, 'r', 0x0f
times 13-5-1 db 0x00, 0x0f
db 'x', 0x0f
db 'O', 0xf0, 'u', 0xf0, 't', 0xf0, ' ', 0xf0, 'o', 0xf0, 'f', 0xf0
db ' ', 0xf0, 'm', 0xf0, 'e', 0xf0, 'm', 0xf0, 'o', 0xf0, 'r', 0xf0
db 'y', 0xf0
ood_window_data:
db 'E', 0x0f, 'r', 0x0f, 'r', 0x0f, 'o', 0x0f, 'r', 0x0f
times 17-5-1 db 0x00, 0x0f
db 'x', 0x0f
db 'O', 0xf0, 'u', 0xf0, 't', 0xf0, ' ', 0xf0, 'o', 0xf0, 'f', 0xf0
db ' ', 0xf0, 'd', 0xf0, 'i', 0xf0, 's', 0xf0, 'k', 0xf0, ' ', 0xf0
db 's', 0xf0, 'p', 0xf0, 'a', 0xf0, 'c', 0xf0, 'e', 0xf0
section .bss
text_window_data resw ROWS*COLUMNS