From 3cd4341986d809d29ced6c5d44637875d193da42 Mon Sep 17 00:00:00 2001 From: Ry Date: Fri, 14 Oct 2022 16:53:41 -0700 Subject: [PATCH] Add shell task and terminal application --- build.sh | 3 + fox32os.def | 3 + kernel/main.asm | 5 + kernel/shell/commands/commands.asm | 53 +++++ kernel/shell/commands/dir.asm | 37 ++++ kernel/shell/commands/disk.asm | 39 ++++ kernel/shell/commands/diskrm.asm | 30 +++ kernel/shell/commands/exit.asm | 6 + kernel/shell/commands/help.asm | 27 +++ kernel/shell/commands/type.asm | 90 +++++++++ kernel/shell/launch.asm | 107 ++++++++++ kernel/shell/shell.asm | 309 +++++++++++++++++++++++++++++ terminal/main.asm | 104 ++++++++++ terminal/stream.asm | 14 ++ terminal/text.asm | 227 +++++++++++++++++++++ 15 files changed, 1054 insertions(+) create mode 100644 kernel/shell/commands/commands.asm create mode 100644 kernel/shell/commands/dir.asm create mode 100644 kernel/shell/commands/disk.asm create mode 100644 kernel/shell/commands/diskrm.asm create mode 100644 kernel/shell/commands/exit.asm create mode 100644 kernel/shell/commands/help.asm create mode 100644 kernel/shell/commands/type.asm create mode 100644 kernel/shell/launch.asm create mode 100644 kernel/shell/shell.asm create mode 100644 terminal/main.asm create mode 100644 terminal/stream.asm create mode 100644 terminal/text.asm diff --git a/build.sh b/build.sh index 9c8a8c3..be0b3ea 100755 --- a/build.sh +++ b/build.sh @@ -19,6 +19,9 @@ echo "assembling launcher" echo "assembling barclock" ../fox32asm/target/release/fox32asm barclock/main.asm base_image/barclock.fxf +echo "assembling terminal" +../fox32asm/target/release/fox32asm terminal/main.asm base_image/terminal.fxf + echo "creating wallpapr.raw" ../tools/gfx2inc/target/release/gfx2inc 640 480 launcher/wallpaper.png launcher/wallpaper.inc ../fox32asm/target/release/fox32asm launcher/wallpaper.inc base_image/wallpapr.raw diff --git a/fox32os.def b/fox32os.def index 9ce368f..24c3684 100644 --- a/fox32os.def +++ b/fox32os.def @@ -35,3 +35,6 @@ seek: jmp [0x00000D14] tell: jmp [0x00000D18] read: jmp [0x00000D1C] write: jmp [0x00000D20] + +; shell jump table +new_shell_task: jmp [0x00000E10] diff --git a/kernel/main.asm b/kernel/main.asm index f440fb1..a88ab62 100644 --- a/kernel/main.asm +++ b/kernel/main.asm @@ -55,6 +55,10 @@ jump_table: data.32 read data.32 write + ; shell jump table + org.pad 0x00000E10 + data.32 new_shell_task + ; initialization code entry: mov rsp, SYSTEM_STACK @@ -253,6 +257,7 @@ get_os_version: #include "allocator.asm" #include "fxf/fxf.asm" + #include "shell/shell.asm" #include "task.asm" #include "window/window.asm" #include "vfs.asm" diff --git a/kernel/shell/commands/commands.asm b/kernel/shell/commands/commands.asm new file mode 100644 index 0000000..d6b976e --- /dev/null +++ b/kernel/shell/commands/commands.asm @@ -0,0 +1,53 @@ +; command parser + +shell_parse_command: + mov r0, shell_text_buf_bottom + + ; dir + mov r1, shell_dir_command_string + call compare_string + ifz jmp shell_dir_command + + ; disk + mov r1, shell_disk_command_string + call compare_string + ifz jmp shell_disk_command + + ; diskrm + mov r1, shell_diskrm_command_string + call compare_string + ifz jmp shell_diskrm_command + + ; exit + mov r1, shell_exit_command_string + call compare_string + ifz jmp shell_exit_command + + ; help + mov r1, shell_help_command_string + call compare_string + ifz jmp shell_help_command + + ; type + mov r1, shell_type_command_string + call compare_string + ifz jmp shell_type_command + + ; attempt to run a FXF binary + call launch_fxf + + ; invalid command + mov r0, shell_invalid_command_string + call print_str_to_terminal + + ret + +shell_invalid_command_string: data.str "invalid command or FXF binary" data.8 10 data.8 0 + + ; all commands + #include "shell/commands/dir.asm" + #include "shell/commands/disk.asm" + #include "shell/commands/diskrm.asm" + #include "shell/commands/exit.asm" + #include "shell/commands/help.asm" + #include "shell/commands/type.asm" diff --git a/kernel/shell/commands/dir.asm b/kernel/shell/commands/dir.asm new file mode 100644 index 0000000..9a28237 --- /dev/null +++ b/kernel/shell/commands/dir.asm @@ -0,0 +1,37 @@ +; dir command + +shell_dir_command_string: data.str "dir" data.8 0 + +shell_dir_command: + mov r0, shell_dir_command_list_buffer + movz.8 r1, [shell_current_disk] + call ryfs_get_file_list + + mov r31, r0 + mov r3, 0 +shell_dir_command_loop: + ; copy one file name from the list buffer to the file buffer + mov r0, shell_dir_command_list_buffer + add r0, r3 + mov r1, shell_dir_command_file_buffer + mov r2, 11 + call copy_memory_bytes + add r1, 11 + mov.8 [r1], 0 + + ; then print the file name to the terminal + mov r0, shell_dir_command_file_buffer + call print_str_to_terminal + + ; new line + mov r0, 10 + call print_character_to_terminal + + ; point to next file name in the buffer + add r3, 11 + loop shell_dir_command_loop + + ret + +shell_dir_command_list_buffer: data.fill 0, 341 +shell_dir_command_file_buffer: data.fill 0, 12 diff --git a/kernel/shell/commands/disk.asm b/kernel/shell/commands/disk.asm new file mode 100644 index 0000000..c717156 --- /dev/null +++ b/kernel/shell/commands/disk.asm @@ -0,0 +1,39 @@ +; disk command + +shell_disk_command_string: data.str "disk" data.8 0 + +shell_disk_command: + call shell_parse_arguments + mov r1, 10 + call string_to_int + + ; r0: disk ID + + ; check if it's in range + cmp r0, 3 + ifgt jmp shell_disk_command_out_of_range + + ; OR it with the IO port to get the current insert state of a disk + ; if no disk is inserted then prompt the user to insert a disk + or r0, 0x80001000 + in r1, r0 + cmp r1, 0 + ifz jmp shell_disk_command_insert_disk + + ; set the current disk ID + mov.8 [shell_current_disk], r0 + + ret + +shell_disk_command_out_of_range: + mov r0, shell_disk_command_out_of_range_string + call print_str_to_terminal + + ret + +shell_disk_command_insert_disk: + out r0, 0 + + ret + +shell_disk_command_out_of_range_string: data.str "invalid disk ID" data.8 10 data.8 0 diff --git a/kernel/shell/commands/diskrm.asm b/kernel/shell/commands/diskrm.asm new file mode 100644 index 0000000..bea189d --- /dev/null +++ b/kernel/shell/commands/diskrm.asm @@ -0,0 +1,30 @@ +; eject command + +shell_diskrm_command_string: data.str "diskrm" data.8 0 + +shell_diskrm_command: + call shell_parse_arguments + mov r1, 10 + call string_to_int + + ; r0: disk ID + + ; check if it's in range + cmp r0, 3 + ifgt jmp shell_diskrm_command_out_of_range + + ; OR it with the IO port to remove a disk + or r0, 0x80005000 + + ; remove disk + out r0, 0 + + ret + +shell_diskrm_command_out_of_range: + mov r0, shell_diskrm_command_out_of_range_string + call print_str_to_terminal + + ret + +shell_diskrm_command_out_of_range_string: data.str "invalid disk ID" data.8 10 data.8 0 diff --git a/kernel/shell/commands/exit.asm b/kernel/shell/commands/exit.asm new file mode 100644 index 0000000..0a7ab54 --- /dev/null +++ b/kernel/shell/commands/exit.asm @@ -0,0 +1,6 @@ +; exit command + +shell_exit_command_string: data.str "exit" data.8 0 + +shell_exit_command: + call end_current_task diff --git a/kernel/shell/commands/help.asm b/kernel/shell/commands/help.asm new file mode 100644 index 0000000..a8cb399 --- /dev/null +++ b/kernel/shell/commands/help.asm @@ -0,0 +1,27 @@ +; help command + +shell_help_command_string: data.str "help" data.8 0 + +shell_help_command: + mov r0, shell_help_text + call print_str_to_terminal + + ret + +shell_help_text: + data.str "fox32os shell" data.8 10 + data.8 10 + data.str "(in descriptions, $n is argument n)" data.8 10 + data.str "command | description" data.8 10 + data.str "------- | -----------" data.8 10 + data.str "dir | show contents of selected disk" data.8 10 + data.str "disk | select disk $0" data.8 10 + data.str "diskrm | remove disk $0" data.8 10 + data.str "exit | exit the shell" data.8 10 + data.str "help | show this help text" data.8 10 + data.str "type | print file $0 of type $1" data.8 10 + data.8 10 + data.str "type the name of an FXF binary to launch" data.8 10 + data.str "it as a new task; the shell will suspend" data.8 10 + data.str "until the launched task ends" data.8 10 + data.8 0 diff --git a/kernel/shell/commands/type.asm b/kernel/shell/commands/type.asm new file mode 100644 index 0000000..4f877e9 --- /dev/null +++ b/kernel/shell/commands/type.asm @@ -0,0 +1,90 @@ +; type command + +shell_type_command_string: data.str "type" data.8 0 + +; FIXME: check string length before blindly copying +shell_type_command: + call shell_parse_arguments + + ; r0: file name + ; r1: file extension + + ; copy empty file name + push r1 + push r0 + mov r0, shell_type_command_file_empty + mov r1, shell_type_command_file + mov r2, 11 + call copy_memory_bytes + + ; copy file name + pop r0 + mov r1, shell_type_command_file + call custom_copy_string + + ; copy file extension + pop r0 + mov r1, shell_type_command_file + add r1, 8 + call custom_copy_string + add r1, 3 + mov.8 [r1], 0 + + ; open the file + mov r0, shell_type_command_file + movz.8 r1, [shell_current_disk] + mov r2, shell_type_command_file_struct + call open + cmp r0, 0 + ifz jmp shell_type_command_file_not_found + + mov r0, shell_type_command_file_struct + call ryfs_get_size + mov r31, r0 +shell_type_command_loop: + mov r0, 1 + mov r1, shell_type_command_file_struct + mov r2, shell_type_command_file_character_buffer + call read + + movz.8 r0, [shell_type_command_file_character_buffer] + call print_character_to_terminal + loop shell_type_command_loop + + mov r0, 10 + call print_character_to_terminal + + ret + +shell_type_command_file_not_found: + mov r0, shell_type_command_file_not_found_string + call print_str_to_terminal + mov r0, shell_type_command_file + call print_str_to_terminal + mov r0, 10 + call print_character_to_terminal + + ret + +custom_copy_string: + push r0 + push r1 + push r2 +custom_copy_string_loop: + mov.8 r2, [r0] + mov.8 [r1], r2 + inc r0 + inc r1 + cmp.8 [r0], 0 + ifnz jmp custom_copy_string_loop + + pop r2 + pop r1 + pop r0 + ret + +shell_type_command_file: data.fill 0, 12 +shell_type_command_file_empty: data.str " " +shell_type_command_file_struct: data.32 0 data.32 0 +shell_type_command_file_character_buffer: data.8 0 +shell_type_command_file_not_found_string: data.str "file not found: " data.8 0 diff --git a/kernel/shell/launch.asm b/kernel/shell/launch.asm new file mode 100644 index 0000000..79a5092 --- /dev/null +++ b/kernel/shell/launch.asm @@ -0,0 +1,107 @@ +; FXF launcher helper routines + +; launch an FXF binary from a shell entry +; inputs: +; r0-r3: shell arguments +; outputs: +; none, does not return if task started successfully +; returns if FXF file not found +launch_fxf: + ; clear the first 8 characters of the launch_fxf_name buffer + push r0 + mov r0, launch_fxf_spaces + mov r1, launch_fxf_name + mov r2, 8 + call copy_memory_bytes + pop r0 + + ; copy the name into the launch_fxf_name buffer + mov r1, launch_fxf_name + mov r31, 8 +launch_fxf_name_loop: + mov.8 [r1], [r0] + inc r0 + inc r1 + cmp.8 [r0], 0 + ifz jmp launch_fxf_name_loop_done + loop launch_fxf_name_loop +launch_fxf_name_loop_done: + ; open the file + mov r0, launch_fxf_name + movz.8 r1, [shell_current_disk] + mov r2, launch_fxf_struct + call ryfs_open + cmp r0, 0 + ifz ret + + ; allocate memory for the binary + mov r0, launch_fxf_struct + call ryfs_get_size + call allocate_memory + cmp r0, 0 + ifz jmp allocate_error + mov [launch_fxf_binary_ptr], r0 + + ; read the file into memory + mov r0, launch_fxf_struct + mov r1, [launch_fxf_binary_ptr] + call ryfs_read_whole_file + + ; allocate a 64KiB stack + mov r0, 65536 + call allocate_memory + cmp r0, 0 + ifz jmp allocate_error + mov [launch_fxf_stack_ptr], r0 + + ; push the argument pointers and terminal stream struct pointer to the task's stack + call shell_parse_arguments + mov r4, rsp + mov rsp, [launch_fxf_stack_ptr] + add rsp, 65536 ; point to the end of the stack (stack grows down!!) + push r3 + push r2 + push r1 + push r0 + push [shell_terminal_stream_struct_ptr] + sub rsp, 65516 + mov [launch_fxf_stack_ptr], rsp + mov rsp, r4 + + ; relocate the binary + mov r0, [launch_fxf_binary_ptr] + call parse_fxf_binary + + ; create a new task + mov r1, r0 + call get_unused_task_id + mov.8 [launch_fxf_task_id], r0 + mov r2, [launch_fxf_stack_ptr] + add r2, 65516 ; point to the end of the stack (stack grows down!!) + mov r3, [launch_fxf_binary_ptr] + mov r4, [launch_fxf_stack_ptr] + call new_task + + ; fall-through to launch_fxf_yield_loop + +; loop until the launched task ends +launch_fxf_yield_loop: + movz.8 r0, [launch_fxf_task_id] + call is_task_id_used + ifz jmp shell_task_return + call yield_task + rjmp launch_fxf_yield_loop + +allocate_error: + mov r0, out_of_memory_string + call print_str_to_terminal + ret + +launch_fxf_name: data.str " fxf" +launch_fxf_spaces: data.str " " +launch_fxf_struct: data.32 0 data.32 0 +launch_fxf_task_id: data.8 0 +launch_fxf_binary_ptr: data.32 0 +launch_fxf_stack_ptr: data.32 0 + +out_of_memory_string: data.str "failed to allocate for new task!" data.8 10 data.8 0 diff --git a/kernel/shell/shell.asm b/kernel/shell/shell.asm new file mode 100644 index 0000000..cc8ff36 --- /dev/null +++ b/kernel/shell/shell.asm @@ -0,0 +1,309 @@ +; shell routines + +const CURSOR: 138 + +; create a new shell task +; inputs: +; r0: task ID +; r1: pointer to stream struct +; outputs: +; none +new_shell_task: + push r0 + push r1 + push r2 + push r3 + push r4 + push r10 + + ; allocate a 64KiB stack and push the pointer to the stream struct to it + push r0 + push r1 + mov r0, 65536 + call allocate_memory + add r0, 65532 + pop r1 + mov [r0], r1 + mov r10, r0 + pop r0 + + ; then start the task + mov r1, shell_task ; initial instruction pointer + mov r2, r10 ; initial stack pointer + mov r3, 0 ; pointer to task code block to free when task ends + ; (zero since we don't want to free any code blocks when the task ends) + mov r4, r10 ; pointer to task stack block to free when task ends + sub r4, 65536 ; point to the start of the stack block that we allocated above + call new_task + + pop r10 + pop r4 + pop r3 + pop r2 + pop r1 + pop r0 + ret + +; print a character to the terminal +; inputs: +; r0: ASCII character +; outputs: +; none +print_character_to_terminal: + push r1 + push r2 + + mov.8 [shell_char_buffer], r0 + mov r1, [shell_terminal_stream_struct_ptr] + mov r2, shell_char_buffer + call write + + pop r2 + pop r1 + ret + +; print a string to the terminal +; inputs: +; r0: pointer to null-terminated string +; outputs: +; none +print_str_to_terminal: + push r0 + push r2 + + mov r1, [shell_terminal_stream_struct_ptr] + mov r2, r0 +print_str_to_terminal_loop: + call write + inc r2 + cmp.8 [r2], 0x00 + ifnz jmp print_str_to_terminal_loop + + pop r2 + pop r0 + ret + +shell_task: + pop [shell_terminal_stream_struct_ptr] +shell_task_return: + call shell_clear_buffer + call shell_print_prompt +shell_task_loop: + mov r1, [shell_terminal_stream_struct_ptr] + mov r2, shell_char_buffer + call read + + movz.8 r0, [shell_char_buffer] + cmp.8 r0, 0 + ifnz call shell_task_parse_key + + call yield_task + rjmp shell_task_loop + +shell_task_parse_key: + ; first, check if enter, delete, or backspace was pressed + cmp.8 r0, 0x1C ; enter + ifz jmp shell_key_down_enter + cmp.8 r0, 0x6F ; delete + ifz jmp shell_key_down_backspace + cmp.8 r0, 0x0E ; backspace + ifz jmp shell_key_down_backspace + + ; then, overwrite the cursor + mov r1, r0 + mov r0, 8 ; backspace character + call print_character_to_terminal + mov r0, r1 + + ; then, add it to the text buffer and print it to the screen + call scancode_to_ascii + call print_character_to_terminal + call shell_push_character + + ; finally, print the cursor + mov r0, CURSOR + call print_character_to_terminal + ret +shell_key_down_enter: + ; clear the cursor from the screen + mov r0, 8 ; backspace character + call print_character_to_terminal + mov r0, ' ' ; space character + call print_character_to_terminal + mov r0, 8 ; backspace character + call print_character_to_terminal + + mov r0, 10 ; line feed + call print_character_to_terminal + + mov r0, 0 + call shell_push_character + + call shell_parse_line + call shell_clear_buffer + + call shell_print_prompt + ret +shell_key_down_backspace: + ; check if we are already at the start of the prompt + mov r1, [shell_text_buf_ptr] + cmp r1, shell_text_buf_bottom + iflteq ret + ; delete the last character from the screen, draw the cursor, and pop the last character from the buffer + mov r0, 8 ; backspace character + call print_character_to_terminal + mov r0, ' ' ; space character + call print_character_to_terminal + mov r0, 8 ; backspace character + call print_character_to_terminal + call print_character_to_terminal + mov r0, CURSOR ; cursor + call print_character_to_terminal + call shell_delete_character + ret + +shell_print_prompt: + movz.8 r0, [shell_current_disk] + add r0, '0' + call print_character_to_terminal + mov r0, shell_prompt + call print_str_to_terminal + ret + +shell_parse_line: + ; if the line is empty, just return + cmp.8 [shell_text_buf_bottom], 0 + ifz ret + + ; separate the command from the arguments + ; store the pointer to the arguments + mov r0, shell_text_buf_bottom + mov r1, ' ' + call shell_tokenize + mov [shell_args_ptr], r0 + + call shell_parse_command + + ret + +; return tokens separated by the specified character +; returns the next token in the list +; inputs: +; r0: pointer to null-terminated string +; r1: separator character +; outputs: +; r0: pointer to next token or zero if none +shell_tokenize: + cmp.8 [r0], r1 + ifz jmp shell_tokenize_found_token + + cmp.8 [r0], 0 + ifz mov r0, 0 + ifz ret + + inc r0 + jmp shell_tokenize +shell_tokenize_found_token: + mov.8 [r0], 0 + inc r0 + ret + +; parse up to 4 arguments into individual strings +; for example, "this is a test" will be converted to +; r0: pointer to "this" data.8 0 +; r1: pointer to "is" data.8 0 +; r2: pointer to "a" data.8 0 +; r3: pointer to "test" data.8 0 +; inputs: +; none +; outputs: +; r0: pointer to 1st null-terminated argument, or zero if none +; r1: pointer to 2nd null-terminated argument, or zero if none +; r2: pointer to 3rd null-terminated argument, or zero if none +; r3: pointer to 4th null-terminated argument, or zero if none +shell_parse_arguments: + push r31 + + mov r0, [shell_args_ptr] + mov r1, ' ' + mov r31, 3 + push r0 +shell_parse_arguments_loop: + call shell_tokenize + push r0 + loop shell_parse_arguments_loop + pop r3 + pop r2 + pop r1 + pop r0 + + pop r31 + ret + +; push a character to the text buffer +; inputs: +; r0: character +; outputs: +; none +shell_push_character: + push r1 + + mov r1, [shell_text_buf_ptr] + cmp r1, shell_text_buf_top + ifgteq jmp shell_push_character_end + mov.8 [r1], r0 + inc [shell_text_buf_ptr] +shell_push_character_end: + pop r1 + ret + +; pop a character from the text buffer and zero it +; inputs: +; none +; outputs: +; r0: character +shell_delete_character: + push r1 + + mov r1, [shell_text_buf_ptr] + cmp r1, shell_text_buf_bottom + iflteq jmp shell_delete_character_end + dec [shell_text_buf_ptr] + movz.8 r0, [r1] + mov.8 [r1], 0 +shell_delete_character_end: + pop r1 + ret + +; mark the text buffer as empty +; inputs: +; none +; outputs: +; none +shell_clear_buffer: + push r0 + + ; set the text buffer poinrer to the start of the text buffer + mov [shell_text_buf_ptr], shell_text_buf_bottom + + ; set the first character as null + mov r0, [shell_text_buf_ptr] + mov.8 [r0], 0 + + pop r0 + ret + +shell_text_buf_bottom: data.fill 0, 32 +shell_text_buf_top: +shell_text_buf_ptr: data.32 0 ; pointer to the current input character +shell_args_ptr: data.32 0 ; pointer to the beginning of the command arguments + +shell_current_disk: data.8 0 + +shell_prompt: data.str "> " data.8 CURSOR data.8 0 + +shell_terminal_stream_struct_ptr: data.32 0 +shell_char_buffer: data.32 0 + + #include "shell/commands/commands.asm" + #include "shell/launch.asm" diff --git a/terminal/main.asm b/terminal/main.asm new file mode 100644 index 0000000..2d4f23b --- /dev/null +++ b/terminal/main.asm @@ -0,0 +1,104 @@ +; terminal + + mov r0, window_struct + mov r1, window_title + mov r2, 320 + mov r3, 400 + mov r4, 32 + mov r5, 32 + call new_window + + call get_unused_task_id + mov.8 [shell_task_id], r0 + mov r1, stream_struct + call new_shell_task + +event_loop: + mov r0, window_struct + call get_next_window_event + + cmp r0, EVENT_TYPE_MOUSE_CLICK + ifz jmp mouse_down + + cmp r0, EVENT_TYPE_KEY_DOWN + ifz jmp key_down + + cmp r0, EVENT_TYPE_KEY_UP + ifz jmp key_up + +event_loop_end: + movz.8 r0, [shell_task_id] + call is_task_id_used + ifz jmp close_window + call yield_task + mov.8 [read_buffer], 0 + rjmp event_loop + +mouse_down: + ; check if we are attempting to drag or close the window + cmp r2, 16 + iflteq jmp drag_window + + jmp event_loop_end + +key_down: + mov r0, r1 + + cmp.8 r0, KEY_LSHIFT + ifz push event_loop_end + ifz jmp shift_pressed + cmp.8 r0, KEY_RSHIFT + ifz push event_loop_end + ifz jmp shift_pressed + cmp.8 r0, KEY_CAPS + ifz push event_loop_end + ifz jmp caps_pressed + + mov.8 [read_buffer], r0 + + jmp event_loop_end + +key_up: + mov r0, r1 + + cmp.8 r0, KEY_LSHIFT + ifz push event_loop_end + ifz jmp shift_released + cmp.8 r0, KEY_RSHIFT + ifz push event_loop_end + ifz jmp shift_released + + jmp event_loop_end + +drag_window: + cmp r1, 8 + iflteq jmp event_loop_end + mov r0, window_struct + call start_dragging_window + jmp event_loop_end + +close_window: + mov r0, window_struct + call destroy_window + call end_current_task + jmp event_loop_end + +window_title: data.str "Terminal" data.8 0 +window_struct: data.fill 0, 32 + +shell_task_id: data.8 0 + +stream_struct: + data.8 0x00 + data.16 0x00 + data.32 0x00 + data.8 0x01 + data.32 stream_get_input + data.32 stream_write_to_terminal + + #include "stream.asm" + #include "text.asm" + + ; include system defs + #include "../../fox32rom/fox32rom.def" + #include "../fox32os.def" diff --git a/terminal/stream.asm b/terminal/stream.asm new file mode 100644 index 0000000..2c0bf2a --- /dev/null +++ b/terminal/stream.asm @@ -0,0 +1,14 @@ +; stream IO routines + +; write a character to the terminal +; inputs: +; r0: pointer to ASCII character +stream_write_to_terminal: + mov r0, [r0] + jmp print_character_to_terminal + +stream_get_input: + mov r0, [read_buffer] + ret + +read_buffer: data.32 0 diff --git a/terminal/text.asm b/terminal/text.asm new file mode 100644 index 0000000..e5c930d --- /dev/null +++ b/terminal/text.asm @@ -0,0 +1,227 @@ +; text rendering routines + +const TERMINAL_X_SIZE: 40 +const TERMINAL_Y_SIZE: 25 + +const TEXT_COLOR: 0xFFFFFFFF +const BACKGROUND_COLOR: 0xFF000000 + +; print a single character to the terminal +; inputs: +; r0: ASCII character +; outputs: +; none +print_character_to_terminal: + push r0 + push r1 + push r2 + + cmp.8 r0, 0 ; null + ifz jmp print_character_to_terminal_end + cmp.8 r0, 8 ; backspace + ifz jmp print_character_to_terminal_bs + cmp.8 r0, 10 ; line feed + ifz jmp print_character_to_terminal_lf + cmp.8 r0, 13 ; carriage return + ifz jmp print_character_to_terminal_cr + + ; check if we are at the end of this line + cmp.8 [terminal_x], TERMINAL_X_SIZE + ; if so, increment to the next line + ifgteq mov.8 [terminal_x], 0 + ifgteq inc.8 [terminal_y] + + ; check if we need to scroll the display + cmp.8 [terminal_y], TERMINAL_Y_SIZE + ifgteq call scroll_terminal + + ; calculate coords for character... + movz.8 r1, [terminal_x] + movz.8 r2, [terminal_y] + mul r2, TERMINAL_X_SIZE + add r1, r2 + add r1, terminal_text_buf + + ; ...and print!! + mov.8 [r1], r0 + inc.8 [terminal_x] + jmp print_character_to_terminal_end +print_character_to_terminal_cr: + ; return to the beginning of the line + mov.8 [terminal_x], 0 + jmp print_character_to_terminal_end +print_character_to_terminal_lf: + ; return to the beginning of the line and increment the line + mov.8 [terminal_x], 0 + inc.8 [terminal_y] + ; scroll the display if needed + cmp.8 [terminal_y], TERMINAL_Y_SIZE + ifgteq call scroll_terminal + jmp print_character_to_terminal_end +print_character_to_terminal_bs: + ; go back one character + cmp.8 [terminal_x], 0 + ifnz dec.8 [terminal_x] +print_character_to_terminal_end: + call redraw_terminal_line + pop r2 + pop r1 + pop r0 + ret + +; scroll the terminal +; inputs: +; none +; outputs: +; none +scroll_terminal: + push r0 + push r1 + push r2 + push r31 + + ; source + mov r0, terminal_text_buf + add r0, TERMINAL_X_SIZE + + ; destination + mov r1, terminal_text_buf + + ; size + mov r2, TERMINAL_X_SIZE + mul r2, 24 + div r2, 4 + + call copy_memory_words + + mov.8 [terminal_x], 0 + mov.8 [terminal_y], 24 + + ; clear the last line + mov r0, terminal_text_buf + add r0, 960 ; 40 * 24 + mov r31, TERMINAL_X_SIZE +scroll_terminal_clear_loop: + mov.8 [r0], 0 + inc r0 + loop scroll_terminal_clear_loop + + ; redraw the screen + call redraw_terminal + + pop r31 + pop r2 + pop r1 + pop r0 + ret + +; redraw the whole terminal +; inputs: +; none +; outputs: +; none +redraw_terminal: + push r0 + push r1 + push r2 + push r3 + push r4 + push r5 + push r6 + push r31 + + mov r0, window_struct + call get_window_overlay_number + mov r5, r0 + + mov r0, terminal_text_buf + mov r1, 0 + mov r2, 16 + mov r3, TEXT_COLOR + mov r4, BACKGROUND_COLOR + mov r31, TERMINAL_Y_SIZE +redraw_terminal_loop_y: + push r31 + mov r1, 0 + mov r31, TERMINAL_X_SIZE +redraw_terminal_loop_x: + push r0 + movz.8 r0, [r0] + call draw_font_tile_to_overlay + movz.8 r0, 8 + add r1, r0 + pop r0 + inc r0 + loop redraw_terminal_loop_x + pop r31 + movz.8 r6, 16 + add r2, r6 + loop redraw_terminal_loop_y + + pop r31 + pop r6 + pop r5 + pop r4 + pop r3 + pop r2 + pop r1 + pop r0 + ret + +; redraw only the current line +; inputs: +; none +; outputs: +; none +redraw_terminal_line: + push r0 + push r1 + push r2 + push r3 + push r4 + push r5 + push r6 + push r31 + + mov r0, window_struct + call get_window_overlay_number + mov r5, r0 + + movz.8 r0, [terminal_y] + mul r0, TERMINAL_X_SIZE + add r0, terminal_text_buf + + movz.8 r1, [terminal_y] + mov r2, 16 + mul r2, r1 + add r2, 16 + + mov r1, 0 + mov r3, TEXT_COLOR + mov r4, BACKGROUND_COLOR + + mov r1, 0 + mov r31, TERMINAL_X_SIZE +redraw_terminal_line_loop_x: + push r0 + movz.8 r0, [r0] + call draw_font_tile_to_overlay + movz.8 r0, 8 + add r1, r0 + pop r0 + inc r0 + loop redraw_terminal_line_loop_x + + pop r31 + pop r6 + pop r5 + pop r4 + pop r3 + pop r2 + pop r1 + pop r0 + ret + +terminal_x: data.8 0 +terminal_y: data.8 0 +terminal_text_buf: data.fill 0, 1000 ; 40x25 = 1000 bytes