FEATURE: add Bsh + userspace terminal, remove legacy cmd/cli utils

This commit is contained in:
boreddevnl 2026-04-15 22:47:24 +02:00
parent a8866da3cb
commit bdd43f43cd
34 changed files with 3099 additions and 2912 deletions

View file

@ -139,6 +139,7 @@ $(BUILD_DIR)/initrd.tar: $(KERNEL_ELF)
mkdir -p $(BUILD_DIR)/initrd/Library/images/gif mkdir -p $(BUILD_DIR)/initrd/Library/images/gif
mkdir -p $(BUILD_DIR)/initrd/Library/Fonts/Emoji mkdir -p $(BUILD_DIR)/initrd/Library/Fonts/Emoji
mkdir -p $(BUILD_DIR)/initrd/Library/DOOM mkdir -p $(BUILD_DIR)/initrd/Library/DOOM
mkdir -p $(BUILD_DIR)/initrd/Library/bsh
mkdir -p $(BUILD_DIR)/initrd/docs mkdir -p $(BUILD_DIR)/initrd/docs
@for f in $(SRC_DIR)/userland/bin/*.elf; do \ @for f in $(SRC_DIR)/userland/bin/*.elf; do \
@ -156,6 +157,9 @@ $(BUILD_DIR)/initrd.tar: $(KERNEL_ELF)
@for f in $(SRC_DIR)/fonts/Emoji/*.ttf; do \ @for f in $(SRC_DIR)/fonts/Emoji/*.ttf; do \
if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/Emoji/; fi \ if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/Emoji/; fi \
done done
@if [ -f $(SRC_DIR)/library/bsh/bshrc ]; then cp $(SRC_DIR)/library/bsh/bshrc $(BUILD_DIR)/initrd/Library/bsh/; fi
@if [ -f $(SRC_DIR)/library/bsh/startup.bsh ]; then cp $(SRC_DIR)/library/bsh/startup.bsh $(BUILD_DIR)/initrd/Library/bsh/; fi
@if [ -f $(SRC_DIR)/library/bsh/boot.bsh ]; then cp $(SRC_DIR)/library/bsh/boot.bsh $(BUILD_DIR)/initrd/Library/bsh/; fi
@if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then cp $(SRC_DIR)/userland/games/doom/doom1.wad $(BUILD_DIR)/initrd/Library/DOOM/; fi @if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then cp $(SRC_DIR)/userland/games/doom/doom1.wad $(BUILD_DIR)/initrd/Library/DOOM/; fi
@for f in $$(find docs -name '*.md' 2>/dev/null); do \ @for f in $$(find docs -name '*.md' 2>/dev/null); do \
if [ -f "$$f" ]; then \ if [ -f "$$f" ]; then \

View file

@ -4,7 +4,7 @@ The BoredOS Terminal provides a powerful command-line interface (CLI) for advanc
## The Shell ## The Shell
The default shell in BoredOS is a custom-built, lightweight command processor integrated into the Window Manager. It features: The default shell in BoredOS is **BoredShell (Bsh)**, a userspace shell with a dedicated terminal app. It features:
- **ANSI Color Support**: Rich text output with colors and styles. - **ANSI Color Support**: Rich text output with colors and styles.
- **Command History**: Use the **Up** and **Down** arrow keys to navigate through your previous commands (up to 64 history entries). - **Command History**: Use the **Up** and **Down** arrow keys to navigate through your previous commands (up to 64 history entries).
- **Output Redirection**: - **Output Redirection**:
@ -13,6 +13,33 @@ The default shell in BoredOS is a custom-built, lightweight command processor in
- **Piping**: - **Piping**:
- `command1 | command2`: Pass the output of the first command as input to the second. - `command1 | command2`: Pass the output of the first command as input to the second.
### Bsh Configuration
Bsh loads its configuration from:
`/Library/bsh/bshrc`
This file is similar to `.zshrc` and can define:
- `PATH` for command lookup
- `STARTUP` for interactive shell startup scripts
- `BOOT_SCRIPT` for a once-per-boot script
- prompt templates (`PROMPT_LEFT`, `PROMPT_RIGHT`)
Prompt tokens:
- `%n` username
- `%h` hostname
- `%~` cwd ("~" for `/root`)
- `%T` time (HH:MM)
Example:
```
PATH=/bin:/root/Apps
PROMPT_LEFT=%n@%h:%~$
STARTUP=/Library/bsh/startup.bsh
BOOT_SCRIPT=/Library/bsh/boot.bsh
```
## Common Commands ## Common Commands
Below are some of the most used commands available in `/bin`: Below are some of the most used commands available in `/bin`:

View file

@ -242,7 +242,6 @@ void kmain(void) {
log_ok("Graphics and Console ready"); log_ok("Graphics and Console ready");
if (memmap_request.response != NULL) { if (memmap_request.response != NULL) {
// The memory manager will now scan the memory map and manage all usable regions.
memory_manager_init_from_memmap(memmap_request.response); memory_manager_init_from_memmap(memmap_request.response);
log_ok("Memory manager ready"); log_ok("Memory manager ready");
smp_init_bsp(); smp_init_bsp();
@ -278,17 +277,15 @@ void kmain(void) {
fat32_mkdir("/Library/images/gif"); fat32_mkdir("/Library/images/gif");
fat32_mkdir("/Library/Fonts"); fat32_mkdir("/Library/Fonts");
fat32_mkdir("/Library/DOOM"); fat32_mkdir("/Library/DOOM");
fat32_mkdir("/Library/bsh");
fat32_mkdir("/docs"); fat32_mkdir("/docs");
// Initialize Virtual Filesystems
sysfs_init_subsystems(); sysfs_init_subsystems();
vfs_mount("/sys", "sysfs", "sysfs", sysfs_get_ops(), NULL); vfs_mount("/sys", "sysfs", "sysfs", sysfs_get_ops(), NULL);
vfs_mount("/proc", "procfs", "procfs", procfs_get_ops(), NULL); vfs_mount("/proc", "procfs", "procfs", procfs_get_ops(), NULL);
// Initialize bootfs with default values
bootfs_init(); bootfs_init();
// Populate bootfs with real Limine bootloader information BEFORE mounting
if (bootloader_info_request.response != NULL) { if (bootloader_info_request.response != NULL) {
if (bootloader_info_request.response->name) { if (bootloader_info_request.response->name) {
k_strcpy(g_bootfs_state.bootloader_name, bootloader_info_request.response->name); k_strcpy(g_bootfs_state.bootloader_name, bootloader_info_request.response->name);
@ -298,7 +295,6 @@ void kmain(void) {
} }
} }
// Get kernel size from kernel file request
if (kernel_file_request.response != NULL && kernel_file_request.response->kernel_file != NULL) { if (kernel_file_request.response != NULL && kernel_file_request.response->kernel_file != NULL) {
g_bootfs_state.kernel_size = kernel_file_request.response->kernel_file->size; g_bootfs_state.kernel_size = kernel_file_request.response->kernel_file->size;
serial_write("[INIT] Kernel size from bootloader: "); serial_write("[INIT] Kernel size from bootloader: ");
@ -306,16 +302,13 @@ void kmain(void) {
serial_write(" bytes\n"); serial_write(" bytes\n");
} }
// Set boot time to current ticks
extern uint32_t wm_get_ticks(void); extern uint32_t wm_get_ticks(void);
g_bootfs_state.boot_time_ms = wm_get_ticks(); g_bootfs_state.boot_time_ms = wm_get_ticks();
// BEFORE mounting bootfs, capture initrd from Limine modules
if (module_request.response != NULL) { if (module_request.response != NULL) {
g_bootfs_state.num_modules = module_request.response->module_count; g_bootfs_state.num_modules = module_request.response->module_count;
serial_write("[INIT] Scanning modules for bootfs state...\n"); serial_write("[INIT] Scanning modules for bootfs state...\n");
// Scan modules to find initrd
for (uint64_t i = 0; i < module_request.response->module_count; i++) { for (uint64_t i = 0; i < module_request.response->module_count; i++) {
struct limine_file *mod = module_request.response->modules[i]; struct limine_file *mod = module_request.response->modules[i];
const char *path = mod->path; const char *path = mod->path;
@ -379,12 +372,10 @@ void kmain(void) {
fat32_close(fh); fat32_close(fh);
} }
} }
// Register all discovered modules in our module manager for /sys/module
module_manager_register(clean_path, (uint64_t)mod->address, mod->size); module_manager_register(clean_path, (uint64_t)mod->address, mod->size);
} }
} }
// Initialize fonts now that FAT32 and modules are loaded
uint64_t current_rsp; uint64_t current_rsp;
asm volatile("mov %%rsp, %0" : "=r"(current_rsp)); asm volatile("mov %%rsp, %0" : "=r"(current_rsp));
serial_write("[INIT] Stack Alignment: 0x"); serial_write("[INIT] Stack Alignment: 0x");
@ -397,10 +388,8 @@ void kmain(void) {
ps2_init(); ps2_init();
asm("sti"); asm("sti");
// Initialize LAPIC for IPI support
lapic_init(); lapic_init();
// Initialize SMP
if (smp_request.response != NULL) { if (smp_request.response != NULL) {
uint32_t online = smp_init(smp_request.response); uint32_t online = smp_init(smp_request.response);
log_ok("SMP initialized"); log_ok("SMP initialized");

View file

@ -92,17 +92,7 @@ uint64_t keyboard_handler(registers_t *regs) {
extended_scancode = false; extended_scancode = false;
} }
if (ps2_ctrl_pressed && scancode == 0x2E) {
extern process_t* process_get_current(void);
process_t* proc = process_get_current();
if (proc && proc->is_user && proc->is_terminal_proc && proc->ui_window) {
if (((Window*)proc->ui_window)->focused) {
extern uint64_t process_terminate_current(void);
outb(0x20, 0x20);
return process_terminate_current();
}
}
}
if (scancode == 0x2A || scancode == 0x36) { // Shift Down if (scancode == 0x2A || scancode == 0x36) { // Shift Down
shift_pressed = true; shift_pressed = true;

View file

@ -297,6 +297,9 @@ int procfs_read(void *fs_private, void *handle, void *buf, int size) {
if (k_strcmp(h->type, "name") == 0 || k_strcmp(h->type, "cmdline") == 0) { if (k_strcmp(h->type, "name") == 0 || k_strcmp(h->type, "cmdline") == 0) {
k_strcpy(out, proc->name); k_strcpy(out, proc->name);
k_strcpy(out + k_strlen(out), "\n"); k_strcpy(out + k_strlen(out), "\n");
} else if (k_strcmp(h->type, "cwd") == 0) {
k_strcpy(out, proc->cwd);
k_strcpy(out + k_strlen(out), "\n");
} else if (k_strcmp(h->type, "status") == 0) { } else if (k_strcmp(h->type, "status") == 0) {
k_strcpy(out, "Name: "); k_strcpy(out, "Name: ");
k_strcpy(out + k_strlen(out), proc->name); k_strcpy(out + k_strlen(out), proc->name);
@ -387,6 +390,7 @@ int procfs_readdir(void *fs_private, const char *path, vfs_dirent_t *entries, in
k_strcpy(entries[out++].name, "name"); k_strcpy(entries[out++].name, "name");
k_strcpy(entries[out++].name, "status"); k_strcpy(entries[out++].name, "status");
k_strcpy(entries[out++].name, "cmdline"); k_strcpy(entries[out++].name, "cmdline");
k_strcpy(entries[out++].name, "cwd");
k_strcpy(entries[out++].name, "signal"); k_strcpy(entries[out++].name, "signal");
for(int i=0; i<out; i++) entries[i].is_directory = 0; for(int i=0; i<out; i++) entries[i].is_directory = 0;
return out; return out;

4
src/library/bsh/boot.bsh Normal file
View file

@ -0,0 +1,4 @@
# BoredShell boot script
# Runs once on boot for the first shell.
# Example:
# echo "Boot script executed"

35
src/library/bsh/bshrc Normal file
View file

@ -0,0 +1,35 @@
# BoredShell rc file
# Location: /Library/bsh/bshrc
#
# Format (initial): key=value
# Lines starting with # are comments.
#
# PATH controls command lookup order (colon-separated).
PATH=/bin:/root/Apps
# Scripts to run automatically.
# STARTUP runs for interactive shells.
# BOOT_SCRIPT runs once on boot (if enabled by the shell).
STARTUP=/Library/bsh/startup.bsh
BOOT_SCRIPT=/Library/bsh/boot.bsh
# Prompt templates.
# Tokens:
# %n = username
# %h = hostname
# %~ = cwd ("~" for /root)
# %T = time (HH:MM)
PROMPT_LEFT=%n@%h:%~$
PROMPT_RIGHT=%T
# History settings.
HISTORY_FILE=/Library/bsh/history
HISTORY_SIZE=200
# Feature toggles.
GLOB=true
COMPLETE=true
SUGGEST=true
# Example aliases (future support).
# alias ll=ls -la

View file

@ -0,0 +1,4 @@
# BoredShell startup script
# Runs for interactive shells.
# Example:
# echo "Welcome to BoredShell"

View file

@ -4,15 +4,15 @@
#ifndef CMD_H #ifndef CMD_H
#define CMD_H #define CMD_H
#include "wm.h" #include <stdbool.h>
#include <stddef.h>
extern Window win_cmd; #include <stdint.h>
void cmd_init(void); void cmd_init(void);
void cmd_reset(void); void cmd_reset(void);
// IO Functions
void cmd_write(const char *str); void cmd_write(const char *str);
void cmd_write_len(const char *str, size_t len);
void cmd_putchar(char c); void cmd_putchar(char c);
void cmd_write_int(int n); void cmd_write_int(int n);
void cmd_write_hex(uint64_t n); void cmd_write_hex(uint64_t n);
@ -22,10 +22,9 @@ void cmd_screen_clear(void);
void cmd_increment_msg_count(void); void cmd_increment_msg_count(void);
void cmd_reset_msg_count(void); void cmd_reset_msg_count(void);
void cmd_handle_resize(Window *win, int w, int h);
void cmd_handle_click(Window *win, int x, int y);
uint32_t cmd_get_config_value(const char *key); uint32_t cmd_get_config_value(const char *key);
void cmd_set_current_color(uint32_t color); void cmd_set_current_color(uint32_t color);
void cmd_set_raw_mode(bool enabled);
void cmd_process_finished(void);
#endif #endif

76
src/sys/cmd_stub.c Normal file
View file

@ -0,0 +1,76 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "cmd.h"
#include "core/kutils.h"
extern void serial_write(const char *str);
static void serial_write_char(char c) {
char buf[2] = { c, 0 };
serial_write(buf);
}
void cmd_init(void) {
}
void cmd_reset(void) {
}
void cmd_write(const char *str) {
if (!str) return;
serial_write(str);
}
void cmd_write_len(const char *str, size_t len) {
if (!str || len == 0) return;
for (size_t i = 0; i < len; i++) {
serial_write_char(str[i]);
}
}
void cmd_putchar(char c) {
serial_write_char(c);
}
void cmd_write_int(int n) {
char buf[32];
k_itoa(n, buf);
cmd_write(buf);
}
void cmd_write_hex(uint64_t n) {
char buf[17];
k_itoa_hex(n, buf);
cmd_write("0x");
cmd_write(buf);
}
int cmd_get_cursor_col(void) {
return 0;
}
void cmd_screen_clear(void) {
}
void cmd_increment_msg_count(void) {
}
void cmd_reset_msg_count(void) {
}
uint32_t cmd_get_config_value(const char *key) {
(void)key;
return 0;
}
void cmd_set_current_color(uint32_t color) {
(void)color;
}
void cmd_set_raw_mode(bool enabled) {
(void)enabled;
}
void cmd_process_finished(void) {
}

View file

@ -27,23 +27,24 @@ static uint64_t free_pml4_later[MAX_CPUS_SCHED] = {0};
static spinlock_t runqueue_lock = SPINLOCK_INIT; static spinlock_t runqueue_lock = SPINLOCK_INIT;
static uint32_t next_cpu_assign = 1; static uint32_t next_cpu_assign = 1;
static void process_cleanup_inner(process_t *proc);
void process_init(void) { void process_init(void) {
for (int i = 0; i < MAX_PROCESSES; i++) { for (int i = 0; i < MAX_PROCESSES; i++) {
processes[i].pid = 0xFFFFFFFF; processes[i].pid = 0xFFFFFFFF;
} }
// Current kernel execution is PID 0
process_t *kernel_proc = &processes[0]; process_t *kernel_proc = &processes[0];
kernel_proc->pid = next_pid++; kernel_proc->pid = next_pid++;
kernel_proc->is_user = false; kernel_proc->is_user = false;
kernel_proc->is_idle = true; kernel_proc->is_idle = true;
kernel_proc->tty_id = -1;
kernel_proc->kill_pending = false;
// We don't have its RSP or PML4 yet, but it's already running.
// The timer interrupt will naturally capture its context on the first tick!
kernel_proc->pml4_phys = paging_get_pml4_phys(); kernel_proc->pml4_phys = paging_get_pml4_phys();
kernel_proc->kernel_stack = 0; kernel_proc->kernel_stack = 0;
// Initialize FPU/SSE state for kernel (first interrupt will capture it on stack)
kernel_proc->fpu_initialized = true; kernel_proc->fpu_initialized = true;
for (int i = 0; i < MAX_PROCESS_FDS; i++) kernel_proc->fds[i] = NULL; for (int i = 0; i < MAX_PROCESS_FDS; i++) kernel_proc->fds[i] = NULL;
@ -78,11 +79,14 @@ process_t* process_create(void (*entry_point)(void), bool is_user) {
new_proc->pid = next_pid++; new_proc->pid = next_pid++;
new_proc->is_user = is_user; new_proc->is_user = is_user;
new_proc->tty_id = -1;
new_proc->kill_pending = false;
process_t *parent = process_get_current(); process_t *parent = process_get_current();
if (parent) { if (parent) {
extern void mem_memcpy(void *dest, const void *src, size_t len); extern void mem_memcpy(void *dest, const void *src, size_t len);
mem_memcpy(new_proc->cwd, parent->cwd, 1024); mem_memcpy(new_proc->cwd, parent->cwd, 1024);
new_proc->tty_id = parent->tty_id;
} else { } else {
mem_memset(new_proc->cwd, 0, 1024); mem_memset(new_proc->cwd, 0, 1024);
new_proc->cwd[0] = '/'; new_proc->cwd[0] = '/';
@ -207,6 +211,18 @@ process_t* process_create_elf(const char* filepath, const char* args_str) {
new_proc->heap_start = 0x20000000; // 512MB mark new_proc->heap_start = 0x20000000; // 512MB mark
new_proc->heap_end = 0x20000000; new_proc->heap_end = 0x20000000;
new_proc->is_terminal_proc = false; new_proc->is_terminal_proc = false;
new_proc->tty_id = -1;
new_proc->kill_pending = false;
process_t *parent = process_get_current();
if (parent) {
extern void mem_memcpy(void *dest, const void *src, size_t len);
mem_memcpy(new_proc->cwd, parent->cwd, 1024);
} else {
extern void mem_memset(void *dest, int val, size_t len);
mem_memset(new_proc->cwd, 0, 1024);
new_proc->cwd[0] = '/';
}
// 2. Load ELF executable // 2. Load ELF executable
size_t elf_load_size = 0; size_t elf_load_size = 0;
@ -426,6 +442,69 @@ uint64_t process_schedule(uint64_t current_rsp) {
// Save context // Save context
cur->rsp = current_rsp; cur->rsp = current_rsp;
if (cur->kill_pending && cur->pid != 0xFFFFFFFF && cur->pid != 0) {
process_cleanup_inner(cur);
process_t *prev = cur;
while (prev->next != cur) {
prev = prev->next;
}
if (prev != cur) {
prev->next = cur->next;
process_t *next_proc = cur->next;
while (next_proc != cur) {
if (next_proc->cpu_affinity == my_cpu && next_proc->pid != 0xFFFFFFFF && !next_proc->kill_pending) break;
next_proc = next_proc->next;
}
if (next_proc == cur || next_proc->cpu_affinity != my_cpu) {
for (int i = 0; i < MAX_PROCESSES; i++) {
if (processes[i].pid == 0 || (processes[i].cpu_affinity == my_cpu && processes[i].is_user == false)) {
next_proc = &processes[i];
break;
}
}
}
current_process[my_cpu] = next_proc;
cur->pid = 0xFFFFFFFF;
cur->cpu_affinity = 0xFFFFFFFF;
cur->ui_window = NULL;
cur->is_terminal_proc = false;
cur->kill_pending = false;
free_kernel_stack_later[my_cpu] = cur->kernel_stack_alloc;
cur->kernel_stack_alloc = NULL;
free_pml4_later[my_cpu] = cur->pml4_phys;
cur->pml4_phys = 0;
if (current_process[my_cpu]->is_user && current_process[my_cpu]->kernel_stack) {
tss_set_stack_cpu(my_cpu, current_process[my_cpu]->kernel_stack);
cpu_state_t *cpu_state = smp_get_cpu(my_cpu);
if (cpu_state) {
cpu_state->kernel_syscall_stack = current_process[my_cpu]->kernel_stack;
}
}
paging_switch_directory(current_process[my_cpu]->pml4_phys);
current_process[my_cpu]->ticks++;
uint64_t next_rsp = current_process[my_cpu]->rsp;
spinlock_release_irqrestore(&runqueue_lock, rflags);
if (cleanup_stack) kfree(cleanup_stack);
if (cleanup_pml4) {
extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
paging_destroy_user_pml4_phys(cleanup_pml4);
}
return next_rsp;
}
}
// Switch to next ready process assigned to this CPU // Switch to next ready process assigned to this CPU
extern uint32_t wm_get_ticks(void); extern uint32_t wm_get_ticks(void);
uint32_t now = wm_get_ticks(); uint32_t now = wm_get_ticks();
@ -435,7 +514,7 @@ uint64_t process_schedule(uint64_t current_rsp) {
while (next_proc != start) { while (next_proc != start) {
// Only consider processes assigned to our CPU and not terminated // Only consider processes assigned to our CPU and not terminated
if (next_proc->cpu_affinity == my_cpu && next_proc->pid != 0xFFFFFFFF) { if (next_proc->cpu_affinity == my_cpu && next_proc->pid != 0xFFFFFFFF && !next_proc->kill_pending) {
if (next_proc->pid == 0 || next_proc->sleep_until == 0 || next_proc->sleep_until <= now) { if (next_proc->pid == 0 || next_proc->sleep_until == 0 || next_proc->sleep_until <= now) {
break; break;
} }
@ -497,12 +576,20 @@ process_t* process_get_by_pid(uint32_t pid) {
return NULL; return NULL;
} }
void process_kill_by_tty(int tty_id) {
if (tty_id < 0) return;
for (int i = 0; i < MAX_PROCESSES; i++) {
if (processes[i].pid != 0xFFFFFFFF && processes[i].pid != 0 && processes[i].tty_id == tty_id) {
process_terminate(&processes[i]);
}
}
}
static void process_cleanup_inner(process_t *proc) { static void process_cleanup_inner(process_t *proc) {
if (!proc || proc->pid == 0xFFFFFFFF) return; if (!proc || proc->pid == 0xFFFFFFFF) return;
// 1. Cleanup side effects // 1. Cleanup side effects
extern Window win_cmd; if (proc->ui_window) {
if (proc->ui_window && (proc->ui_window != &win_cmd)) {
wm_remove_window((Window *)proc->ui_window); wm_remove_window((Window *)proc->ui_window);
proc->ui_window = NULL; proc->ui_window = NULL;
} }
@ -529,6 +616,14 @@ static void process_cleanup_inner(process_t *proc) {
void process_terminate(process_t *to_delete) { void process_terminate(process_t *to_delete) {
if (!to_delete || to_delete->pid == 0xFFFFFFFF || to_delete->pid == 0) return; if (!to_delete || to_delete->pid == 0xFFFFFFFF || to_delete->pid == 0) return;
uint32_t cpu_count = smp_cpu_count();
for (uint32_t c = 0; c < cpu_count && c < MAX_CPUS_SCHED; c++) {
if (current_process[c] == to_delete) {
to_delete->kill_pending = true;
return;
}
}
uint64_t rflags = spinlock_acquire_irqsave(&runqueue_lock); uint64_t rflags = spinlock_acquire_irqsave(&runqueue_lock);
process_cleanup_inner(to_delete); process_cleanup_inner(to_delete);
@ -549,7 +644,6 @@ void process_terminate(process_t *to_delete) {
prev->next = to_delete->next; prev->next = to_delete->next;
// Update per-CPU current_process if this was the current on any CPU // Update per-CPU current_process if this was the current on any CPU
uint32_t cpu_count = smp_cpu_count();
for (uint32_t c = 0; c < cpu_count && c < MAX_CPUS_SCHED; c++) { for (uint32_t c = 0; c < cpu_count && c < MAX_CPUS_SCHED; c++) {
if (current_process[c] == to_delete) { if (current_process[c] == to_delete) {
process_t *np = to_delete->next; process_t *np = to_delete->next;
@ -571,6 +665,7 @@ void process_terminate(process_t *to_delete) {
// Mark slot as free // Mark slot as free
to_delete->pid = 0xFFFFFFFF; to_delete->pid = 0xFFFFFFFF;
to_delete->cpu_affinity = 0xFFFFFFFF; to_delete->cpu_affinity = 0xFFFFFFFF;
to_delete->kill_pending = false;
if (to_delete->user_stack_alloc) kfree(to_delete->user_stack_alloc); if (to_delete->user_stack_alloc) kfree(to_delete->user_stack_alloc);
// Defer kernel stack until we switch away from it // Defer kernel stack until we switch away from it
@ -639,6 +734,7 @@ uint64_t process_terminate_current(void) {
to_delete->cpu_affinity = 0xFFFFFFFF; to_delete->cpu_affinity = 0xFFFFFFFF;
to_delete->ui_window = NULL; to_delete->ui_window = NULL;
to_delete->is_terminal_proc = false; to_delete->is_terminal_proc = false;
to_delete->kill_pending = false;
// 4. Load context for the NEXT process // 4. Load context for the NEXT process
if (current_process[my_cpu]->is_user && current_process[my_cpu]->kernel_stack) { if (current_process[my_cpu]->is_user && current_process[my_cpu]->kernel_stack) {

View file

@ -43,6 +43,8 @@ typedef struct process {
void *user_stack_alloc; void *user_stack_alloc;
bool is_terminal_proc; bool is_terminal_proc;
int tty_id;
bool kill_pending;
struct process *next; struct process *next;
@ -75,6 +77,7 @@ uint64_t process_schedule(uint64_t current_rsp);
uint64_t process_terminate_current(void); uint64_t process_terminate_current(void);
void process_terminate(process_t *proc); void process_terminate(process_t *proc);
process_t* process_get_by_pid(uint32_t pid); process_t* process_get_by_pid(uint32_t pid);
void process_kill_by_tty(int tty_id);
// SMP: IPI handler for AP scheduling // SMP: IPI handler for AP scheduling
uint64_t sched_ipi_handler(registers_t *regs); uint64_t sched_ipi_handler(registers_t *regs);

View file

@ -19,11 +19,16 @@
#include "network.h" #include "network.h"
#include "icmp.h" #include "icmp.h"
#include "cmd.h" #include "cmd.h"
#include "tty.h"
#include "font_manager.h" #include "font_manager.h"
#include "graphics.h" #include "graphics.h"
extern bool ps2_ctrl_pressed; extern bool ps2_ctrl_pressed;
#define SPAWN_FLAG_TERMINAL 0x1
#define SPAWN_FLAG_INHERIT_TTY 0x2
#define SPAWN_FLAG_TTY_ID 0x4
// Read MSR // Read MSR
static inline uint64_t rdmsr(uint32_t msr) { static inline uint64_t rdmsr(uint32_t msr) {
uint32_t low, high; uint32_t low, high;
@ -180,6 +185,12 @@ static void user_window_resize(Window *win, int w, int h) {
extern void mem_memset(void *dest, int val, size_t len); extern void mem_memset(void *dest, int val, size_t len);
mem_memset(win->pixels, 0, w * h * sizeof(uint32_t)); mem_memset(win->pixels, 0, w * h * sizeof(uint32_t));
} }
process_t *proc = process_get_by_ui_window(win);
if (proc) {
gui_event_t ev = { .type = GUI_EVENT_RESIZE, .arg1 = w, .arg2 = h };
process_push_gui_event(proc, &ev);
}
} }
@ -197,9 +208,21 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
if (syscall_num == 1) { // SYS_WRITE if (syscall_num == 1) { // SYS_WRITE
extern void cmd_write_len(const char *str, size_t len); extern void cmd_write_len(const char *str, size_t len);
process_t *proc = process_get_current(); process_t *proc = process_get_current();
if (!proc || !proc->is_user || proc->is_terminal_proc) { const char *buf = (const char*)arg2;
cmd_write_len((const char*)arg2, (size_t)arg3); size_t len = (size_t)arg3;
if (!proc || !proc->is_user) {
cmd_write_len(buf, len);
return len;
} }
if (proc->is_terminal_proc) {
if (proc->tty_id >= 0) {
tty_write_output(proc->tty_id, buf, len);
return len;
}
cmd_write_len(buf, len);
return len;
}
return len;
} else if (syscall_num == 3) { // SYS_GUI } else if (syscall_num == 3) { // SYS_GUI
int cmd = (int)arg1; int cmd = (int)arg1;
process_t *proc = process_get_current(); process_t *proc = process_get_current();
@ -1254,6 +1277,35 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
return cmd_get_config_value(key); return cmd_get_config_value(key);
} else if (cmd == 29) { // SYSTEM_CMD_SET_TEXT_COLOR } else if (cmd == 29) { // SYSTEM_CMD_SET_TEXT_COLOR
uint32_t color = (uint32_t)arg2; uint32_t color = (uint32_t)arg2;
if (proc->is_terminal_proc && proc->tty_id >= 0) {
char seq[32];
int pos = 0;
int r = (color >> 16) & 0xFF;
int g = (color >> 8) & 0xFF;
int b = color & 0xFF;
seq[pos++] = 0x1B;
seq[pos++] = '[';
seq[pos++] = '3';
seq[pos++] = '8';
seq[pos++] = ';';
seq[pos++] = '2';
seq[pos++] = ';';
char num[8];
k_itoa(r, num);
for (int i = 0; num[i] && pos < (int)sizeof(seq) - 1; i++) seq[pos++] = num[i];
seq[pos++] = ';';
k_itoa(g, num);
for (int i = 0; num[i] && pos < (int)sizeof(seq) - 1; i++) seq[pos++] = num[i];
seq[pos++] = ';';
k_itoa(b, num);
for (int i = 0; num[i] && pos < (int)sizeof(seq) - 1; i++) seq[pos++] = num[i];
seq[pos++] = 'm';
tty_write_output(proc->tty_id, seq, (size_t)pos);
return 0;
}
cmd_set_current_color(color); cmd_set_current_color(color);
return 0; return 0;
} else if (cmd == 31) { // SYSTEM_CMD_SET_WALLPAPER_PATH } else if (cmd == 31) { // SYSTEM_CMD_SET_WALLPAPER_PATH
@ -1374,6 +1426,85 @@ static uint64_t syscall_handler_inner(registers_t *regs) {
} }
return -1; return -1;
return -1; return -1;
} else if (cmd == 60) { // SYSTEM_CMD_TTY_CREATE
return tty_create();
} else if (cmd == 61) { // SYSTEM_CMD_TTY_READ_OUT
int tty_id = (int)arg2;
char *buf = (char *)arg3;
size_t len = (size_t)arg4;
if (!buf || len == 0) return 0;
return tty_read_output(tty_id, buf, len);
} else if (cmd == 62) { // SYSTEM_CMD_TTY_WRITE_IN
int tty_id = (int)arg2;
const char *buf = (const char *)arg3;
size_t len = (size_t)arg4;
if (!buf || len == 0) return 0;
return tty_write_input(tty_id, buf, len);
} else if (cmd == 63) { // SYSTEM_CMD_TTY_READ_IN
char *buf = (char *)arg2;
size_t len = (size_t)arg3;
if (!buf || len == 0) return 0;
if (proc->tty_id < 0) return 0;
return tty_read_input(proc->tty_id, buf, len);
} else if (cmd == 65) { // SYSTEM_CMD_TTY_SET_FG
int tty_id = (int)arg2;
int pid = (int)arg3;
return tty_set_foreground(tty_id, pid);
} else if (cmd == 66) { // SYSTEM_CMD_TTY_GET_FG
int tty_id = (int)arg2;
return tty_get_foreground(tty_id);
} else if (cmd == 67) { // SYSTEM_CMD_TTY_KILL_FG
int tty_id = (int)arg2;
int pid = tty_get_foreground(tty_id);
if (pid <= 0) return 0;
process_t *target = process_get_by_pid((uint32_t)pid);
if (target) process_terminate(target);
tty_set_foreground(tty_id, 0);
return 0;
} else if (cmd == 68) {
int tty_id = (int)arg2;
process_kill_by_tty(tty_id);
tty_set_foreground(tty_id, 0);
return 0;
} else if (cmd == 69) {
int tty_id = (int)arg2;
return tty_destroy(tty_id);
} else if (cmd == 64) {
const char *user_path = (const char *)arg2;
const char *user_args = (const char *)arg3;
uint64_t flags = arg4;
int tty_id = (int)arg5;
if (!user_path) return -1;
char path_buf[256];
int pi = 0;
while (pi < 255 && user_path[pi]) {
path_buf[pi] = user_path[pi];
pi++;
}
path_buf[pi] = 0;
char args_buf[512];
const char *args_ptr = NULL;
if (user_args) {
int ai = 0;
while (ai < 511 && user_args[ai]) {
args_buf[ai] = user_args[ai];
ai++;
}
args_buf[ai] = 0;
args_ptr = args_buf;
}
process_t *child = process_create_elf(path_buf, args_ptr);
if (!child) return -1;
if (flags & SPAWN_FLAG_TERMINAL) child->is_terminal_proc = true;
if (flags & SPAWN_FLAG_TTY_ID) child->tty_id = tty_id;
else if (flags & SPAWN_FLAG_INHERIT_TTY) child->tty_id = proc->tty_id;
return (uint64_t)child->pid;
} else if (cmd == SYSTEM_CMD_PARALLEL_RUN) { } else if (cmd == SYSTEM_CMD_PARALLEL_RUN) {
void (*user_fn)(void*) = (void (*)(void*))arg2; void (*user_fn)(void*) = (void (*)(void*))arg2;
void **args = (void **)arg3; void **args = (void **)arg3;

158
src/sys/tty.c Normal file
View file

@ -0,0 +1,158 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "tty.h"
#include "spinlock.h"
#include <stdbool.h>
#include <stdint.h>
#define TTY_MAX 8
#define TTY_OUT_SIZE 16384
#define TTY_IN_SIZE 4096
typedef struct {
bool used;
int id;
char out_buf[TTY_OUT_SIZE];
uint32_t out_head;
uint32_t out_tail;
char in_buf[TTY_IN_SIZE];
uint32_t in_head;
uint32_t in_tail;
int fg_pid;
spinlock_t lock;
} tty_t;
static tty_t ttys[TTY_MAX] = {0};
extern void mem_memset(void *dest, int val, size_t len);
static tty_t *tty_get(int tty_id) {
if (tty_id < 0 || tty_id >= TTY_MAX) return NULL;
if (!ttys[tty_id].used) return NULL;
return &ttys[tty_id];
}
int tty_create(void) {
for (int i = 0; i < TTY_MAX; i++) {
if (!ttys[i].used) {
ttys[i].used = true;
ttys[i].id = i;
ttys[i].out_head = 0;
ttys[i].out_tail = 0;
ttys[i].in_head = 0;
ttys[i].in_tail = 0;
ttys[i].fg_pid = -1;
ttys[i].lock = SPINLOCK_INIT;
mem_memset(ttys[i].out_buf, 0, sizeof(ttys[i].out_buf));
mem_memset(ttys[i].in_buf, 0, sizeof(ttys[i].in_buf));
return i;
}
}
return -1;
}
int tty_destroy(int tty_id) {
if (tty_id < 0 || tty_id >= TTY_MAX) return -1;
tty_t *tty = &ttys[tty_id];
uint64_t rflags = spinlock_acquire_irqsave(&tty->lock);
if (!tty->used) {
spinlock_release_irqrestore(&tty->lock, rflags);
return -1;
}
tty->used = false;
tty->id = -1;
tty->out_head = 0;
tty->out_tail = 0;
tty->in_head = 0;
tty->in_tail = 0;
tty->fg_pid = -1;
mem_memset(tty->out_buf, 0, sizeof(tty->out_buf));
mem_memset(tty->in_buf, 0, sizeof(tty->in_buf));
spinlock_release_irqrestore(&tty->lock, rflags);
return 0;
}
static int tty_write_ring(char *buf, uint32_t size, uint32_t *head, uint32_t *tail, const char *data, size_t len) {
int written = 0;
for (size_t i = 0; i < len; i++) {
uint32_t next = (*head + 1) % size;
if (next == *tail) break;
buf[*head] = data[i];
*head = next;
written++;
}
return written;
}
static int tty_read_ring(char *buf, uint32_t size, uint32_t *head, uint32_t *tail, char *out, size_t max_len) {
int read = 0;
while (*tail != *head && (size_t)read < max_len) {
out[read++] = buf[*tail];
*tail = (*tail + 1) % size;
}
return read;
}
int tty_write_output(int tty_id, const char *data, size_t len) {
tty_t *tty = tty_get(tty_id);
if (!tty || !data || len == 0) return 0;
uint64_t rflags = spinlock_acquire_irqsave(&tty->lock);
int written = tty_write_ring(tty->out_buf, TTY_OUT_SIZE, &tty->out_head, &tty->out_tail, data, len);
spinlock_release_irqrestore(&tty->lock, rflags);
return written;
}
int tty_read_output(int tty_id, char *buf, size_t max_len) {
tty_t *tty = tty_get(tty_id);
if (!tty || !buf || max_len == 0) return 0;
uint64_t rflags = spinlock_acquire_irqsave(&tty->lock);
int read = tty_read_ring(tty->out_buf, TTY_OUT_SIZE, &tty->out_head, &tty->out_tail, buf, max_len);
spinlock_release_irqrestore(&tty->lock, rflags);
return read;
}
int tty_write_input(int tty_id, const char *data, size_t len) {
tty_t *tty = tty_get(tty_id);
if (!tty || !data || len == 0) return 0;
uint64_t rflags = spinlock_acquire_irqsave(&tty->lock);
int written = tty_write_ring(tty->in_buf, TTY_IN_SIZE, &tty->in_head, &tty->in_tail, data, len);
spinlock_release_irqrestore(&tty->lock, rflags);
return written;
}
int tty_read_input(int tty_id, char *buf, size_t max_len) {
tty_t *tty = tty_get(tty_id);
if (!tty || !buf || max_len == 0) return 0;
uint64_t rflags = spinlock_acquire_irqsave(&tty->lock);
int read = tty_read_ring(tty->in_buf, TTY_IN_SIZE, &tty->in_head, &tty->in_tail, buf, max_len);
spinlock_release_irqrestore(&tty->lock, rflags);
return read;
}
int tty_set_foreground(int tty_id, int pid) {
tty_t *tty = tty_get(tty_id);
if (!tty) return -1;
uint64_t rflags = spinlock_acquire_irqsave(&tty->lock);
tty->fg_pid = pid;
spinlock_release_irqrestore(&tty->lock, rflags);
return 0;
}
int tty_get_foreground(int tty_id) {
tty_t *tty = tty_get(tty_id);
if (!tty) return -1;
uint64_t rflags = spinlock_acquire_irqsave(&tty->lock);
int pid = tty->fg_pid;
spinlock_release_irqrestore(&tty->lock, rflags);
return pid;
}

18
src/sys/tty.h Normal file
View file

@ -0,0 +1,18 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#ifndef TTY_H
#define TTY_H
#include <stddef.h>
int tty_create(void);
int tty_destroy(int tty_id);
int tty_write_output(int tty_id, const char *data, size_t len);
int tty_read_output(int tty_id, char *buf, size_t max_len);
int tty_write_input(int tty_id, const char *data, size_t len);
int tty_read_input(int tty_id, char *buf, size_t max_len);
int tty_set_foreground(int tty_id, int pid);
int tty_get_foreground(int tty_id);
#endif

1449
src/userland/cli/bsh.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,32 +0,0 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include <stdlib.h>
#include <syscall.h>
int main(int argc, char **argv) {
uint32_t error_color = (uint32_t)sys_get_shell_config("error_color");
uint32_t default_color = (uint32_t)sys_get_shell_config("default_text_color");
if (argc < 2) {
printf("Usage: cat <filename>\n");
return 1;
}
int fd = sys_open(argv[1], "r");
if (fd < 0) {
sys_set_text_color(error_color);
printf("Error: Cannot open %s\n", argv[1]);
sys_set_text_color(default_color);
return 1;
}
char buffer[4096];
int bytes;
while ((bytes = sys_read(fd, buffer, sizeof(buffer))) > 0) {
sys_write(1, buffer, bytes);
}
sys_close(fd);
return 0;
}

View file

@ -1,166 +0,0 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include <stdlib.h>
#include <syscall.h>
typedef int bool;
#define true 1
#define false 0
void combine_path(char *dest, const char *path1, const char *path2) {
int i = 0;
while (path1[i]) {
dest[i] = path1[i];
i++;
}
if (i > 0 && dest[i-1] != '/') {
dest[i++] = '/';
}
int j = 0;
while (path2[j]) {
dest[i++] = path2[j++];
}
dest[i] = 0;
}
const char* get_basename(const char *path) {
const char *last_slash = NULL;
int len = 0;
while (path[len]) {
if (path[len] == '/') last_slash = path + len;
len++;
}
if (!last_slash) return path;
// If it ends with a slash, skip it and find the previous one
if (last_slash[1] == '\0') {
if (len <= 1) return path; // root "/"
int i = len - 2;
while (i >= 0 && path[i] != '/') i--;
if (i < 0) return path;
return path + i + 1;
}
return last_slash + 1;
}
void copy_recursive(const char *src, const char *dst) {
FAT32_FileInfo info;
if (sys_get_file_info(src, &info) < 0) {
printf("Error: Cannot get info for %s\n", src);
return;
}
if (info.is_directory) {
if (sys_mkdir(dst) < 0) {
// Might already exist
}
FAT32_FileInfo entries[64];
int count = sys_list(src, entries, 64);
for (int i = 0; i < count; i++) {
if (strcmp(entries[i].name, ".") == 0 || strcmp(entries[i].name, "..") == 0) continue;
char sub_src[512], sub_dst[512];
combine_path(sub_src, src, entries[i].name);
combine_path(sub_dst, dst, entries[i].name);
copy_recursive(sub_src, sub_dst);
}
} else {
int fd_in = sys_open(src, "r");
if (fd_in < 0) {
printf("Error: Cannot open source %s\n", src);
return;
}
int fd_out = sys_open(dst, "w");
if (fd_out < 0) {
printf("Error: Cannot create destination %s\n", dst);
sys_close(fd_in);
return;
}
char buffer[4096];
int bytes;
while ((bytes = sys_read(fd_in, buffer, sizeof(buffer))) > 0) {
sys_write_fs(fd_out, buffer, bytes);
}
sys_close(fd_in);
sys_close(fd_out);
}
}
int main(int argc, char **argv) {
bool recursive = false;
char *src_path = NULL;
char *dst_path = NULL;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-r") == 0) {
recursive = true;
} else if (!src_path) {
src_path = argv[i];
} else if (!dst_path) {
dst_path = argv[i];
}
}
if (!src_path || !dst_path) {
printf("Usage: cp [-r] <source> <dest>\n");
return 1;
}
FAT32_FileInfo info_src;
if (sys_get_file_info(src_path, &info_src) < 0) {
printf("Error: Source %s does not exist\n", src_path);
return 1;
}
if (info_src.is_directory && !recursive) {
printf("Error: %s is a directory (use -r to copy recursively)\n", src_path);
return 1;
}
char actual_dst[512];
FAT32_FileInfo info_dst;
if (sys_get_file_info(dst_path, &info_dst) == 0 && info_dst.is_directory) {
// If destination is a directory, copy INTO it
const char *base = get_basename(src_path);
// Clean up trailing slash from basename if any (get_basename handles it mostly)
char clean_base[256];
int k = 0;
while (base[k] && base[k] != '/') {
clean_base[k] = base[k];
k++;
}
clean_base[k] = 0;
combine_path(actual_dst, dst_path, clean_base);
} else {
strcpy(actual_dst, dst_path);
}
if (recursive) {
copy_recursive(src_path, actual_dst);
} else {
int fd_in = sys_open(src_path, "r");
if (fd_in < 0) {
printf("Error: Cannot open source %s\n", src_path);
return 1;
}
int fd_out = sys_open(actual_dst, "w");
if (fd_out < 0) {
printf("Error: Cannot create destination %s\n", actual_dst);
sys_close(fd_in);
return 1;
}
char buffer[4096];
int bytes;
while ((bytes = sys_read(fd_in, buffer, sizeof(buffer))) > 0) {
sys_write_fs(fd_out, buffer, bytes);
}
sys_close(fd_in);
sys_close(fd_out);
}
return 0;
}

View file

@ -1,66 +0,0 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include <stdlib.h>
#include <syscall.h>
int main(int argc, char **argv) {
uint64_t dir_color = sys_get_shell_config("dir_color");
uint64_t file_color = sys_get_shell_config("file_color");
uint64_t size_color = sys_get_shell_config("size_color");
uint64_t error_color = sys_get_shell_config("error_color");
uint64_t default_color = sys_get_shell_config("default_text_color");
char path[256];
if (argc > 1) {
strcpy(path, argv[1]);
} else {
if (!sys_getcwd(path, sizeof(path))) {
strcpy(path, "/");
}
}
FAT32_FileInfo info;
if (sys_get_file_info(path, &info) < 0) {
sys_set_text_color(error_color);
printf("Error: Path '%s' does not exist\n", path);
sys_set_text_color(default_color);
return 1;
}
if (!info.is_directory) {
sys_set_text_color(file_color);
printf("[FILE] %s", info.name);
sys_set_text_color(size_color);
printf(" (%d bytes)\n", info.size);
sys_set_text_color(default_color);
printf("\nTotal: 1 items\n");
return 0;
}
FAT32_FileInfo entries[128];
int count = sys_list(path, entries, 128);
if (count < 0) {
sys_set_text_color(error_color);
printf("Error: Cannot list directory %s\n", path);
sys_set_text_color(default_color);
return 1;
}
for (int i = 0; i < count; i++) {
if (entries[i].is_directory) {
sys_set_text_color(dir_color);
printf("[DIR] %s\n", entries[i].name);
} else {
sys_set_text_color(file_color);
printf("[FILE] %s", entries[i].name);
sys_set_text_color(size_color);
printf(" (%d bytes)\n", entries[i].size);
}
}
sys_set_text_color(default_color);
printf("\nTotal: %d items\n", count);
return 0;
}

View file

@ -1,28 +0,0 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include <stdlib.h>
#include <syscall.h>
int main(int argc, char **argv) {
uint32_t error_color = (uint32_t)sys_get_shell_config("error_color");
uint32_t success_color = (uint32_t)sys_get_shell_config("success_color");
uint32_t default_color = (uint32_t)sys_get_shell_config("default_text_color");
if (argc < 2) {
printf("Usage: mkdir <dirname>\n");
return 1;
}
if (sys_mkdir(argv[1]) == 0) {
sys_set_text_color(success_color);
printf("Created directory: %s\n", argv[1]);
} else {
sys_set_text_color(error_color);
printf("Error: Cannot create directory %s\n", argv[1]);
sys_set_text_color(default_color);
return 1;
}
sys_set_text_color(default_color);
return 0;
}

View file

@ -1,128 +0,0 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include <stdlib.h>
#include <syscall.h>
void combine_path(char *dest, const char *path1, const char *path2) {
int i = 0;
while (path1[i]) {
dest[i] = path1[i];
i++;
}
if (i > 0 && dest[i-1] != '/') {
dest[i++] = '/';
}
int j = 0;
while (path2[j]) {
dest[i++] = path2[j++];
}
dest[i] = 0;
}
const char* get_basename(const char *path) {
const char *last_slash = NULL;
int len = 0;
while (path[len]) {
if (path[len] == '/') last_slash = path + len;
len++;
}
if (!last_slash) return path;
if (last_slash[1] == '\0') {
if (len <= 1) return path;
int i = len - 2;
while (i >= 0 && path[i] != '/') i--;
if (i < 0) return path;
return path + i + 1;
}
return last_slash + 1;
}
void copy_recursive(const char *src, const char *dst) {
FAT32_FileInfo info;
if (sys_get_file_info(src, &info) < 0) return;
if (info.is_directory) {
sys_mkdir(dst);
FAT32_FileInfo entries[64];
int count = sys_list(src, entries, 64);
for (int i = 0; i < count; i++) {
if (strcmp(entries[i].name, ".") == 0 || strcmp(entries[i].name, "..") == 0) continue;
char sub_src[512], sub_dst[512];
combine_path(sub_src, src, entries[i].name);
combine_path(sub_dst, dst, entries[i].name);
copy_recursive(sub_src, sub_dst);
}
} else {
int fd_in = sys_open(src, "r");
if (fd_in < 0) return;
int fd_out = sys_open(dst, "w");
if (fd_out < 0) { sys_close(fd_in); return; }
char buffer[4096];
int bytes;
while ((bytes = sys_read(fd_in, buffer, sizeof(buffer))) > 0) {
sys_write_fs(fd_out, buffer, bytes);
}
sys_close(fd_in);
sys_close(fd_out);
}
}
void delete_recursive(const char *path) {
FAT32_FileInfo info;
if (sys_get_file_info(path, &info) < 0) return;
if (info.is_directory) {
FAT32_FileInfo entries[64];
int count = sys_list(path, entries, 64);
for (int i = 0; i < count; i++) {
if (strcmp(entries[i].name, ".") == 0 || strcmp(entries[i].name, "..") == 0) continue;
char sub_path[512];
combine_path(sub_path, path, entries[i].name);
delete_recursive(sub_path);
}
sys_delete(path);
} else {
sys_delete(path);
}
}
int main(int argc, char **argv) {
if (argc < 3) {
printf("Usage: mv <source> <dest>\n");
return 1;
}
char *src_path = argv[1];
char *dst_path = argv[2];
FAT32_FileInfo info_src;
if (sys_get_file_info(src_path, &info_src) < 0) {
printf("Error: Cannot open source %s\n", src_path);
return 1;
}
char actual_dst[512];
FAT32_FileInfo info_dst;
if (sys_get_file_info(dst_path, &info_dst) == 0 && info_dst.is_directory) {
const char *base = get_basename(src_path);
char clean_base[256];
int k = 0;
while (base[k] && base[k] != '/') {
clean_base[k] = base[k];
k++;
}
clean_base[k] = 0;
combine_path(actual_dst, dst_path, clean_base);
} else {
strcpy(actual_dst, dst_path);
}
copy_recursive(src_path, actual_dst);
delete_recursive(src_path);
return 0;
}

View file

@ -1,29 +0,0 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include <stdlib.h>
#include <syscall.h>
int main(int argc, char **argv) {
uint32_t error_color = (uint32_t)sys_get_shell_config("error_color");
uint32_t success_color = (uint32_t)sys_get_shell_config("success_color");
uint32_t default_color = (uint32_t)sys_get_shell_config("default_text_color");
if (argc < 2) {
printf("Usage: rm <path>\n");
return 1;
}
// Simple rm (no recursive support yet for simplicity, but can be added)
if (sys_delete(argv[1]) == 0) {
sys_set_text_color(success_color);
printf("Deleted: %s\n", argv[1]);
} else {
sys_set_text_color(error_color);
printf("Error: Cannot delete %s\n", argv[1]);
sys_set_text_color(default_color);
return 1;
}
sys_set_text_color(default_color);
return 0;
}

View file

@ -1,27 +0,0 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include <stdlib.h>
#include <syscall.h>
int main(int argc, char **argv) {
if (argc < 2) {
printf("Usage: touch <filename>\n");
return 1;
}
// Check if file already exists
if (sys_exists(argv[1])) {
// Just return success if it exists (simplification)
return 0;
}
int fd = sys_open(argv[1], "w");
if (fd < 0) {
printf("Error: Cannot create %s\n", argv[1]);
return 1;
}
sys_close(fd);
return 0;
}

893
src/userland/gui/terminal.c Normal file
View file

@ -0,0 +1,893 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include <stdlib.h>
#include <syscall.h>
#include "libc/libui.h"
#include <stdbool.h>
#include <stdint.h>
#define DEFAULT_COLS 116
#define DEFAULT_ROWS 41
#define SCROLLBACK_LINES 800
#define SCROLLBACK_COLS 256
#define CHAR_W 8
#define DEFAULT_LINE_H 10
#define TAB_BAR_H 20
#define CONTENT_PAD_BOTTOM 20
#define TAB_CLOSE_W 12
#define TAB_CLOSE_PAD 6
#define MAX_TABS 4
#define TTY_READ_CHUNK 512
typedef struct {
char c;
uint32_t color;
} CharCell;
typedef struct {
int tty_id;
int bsh_pid;
CharCell *cells;
CharCell *scrollback;
int *scroll_cols;
int scroll_head;
int scroll_count;
int scroll_offset;
int cursor_row;
int cursor_col;
uint32_t fg_color;
uint32_t bg_color;
int ansi_state;
int ansi_params[8];
int ansi_param_count;
int saved_row;
int saved_col;
} TerminalSession;
static ui_window_t g_win;
static TerminalSession g_tabs[MAX_TABS];
static int g_tab_count = 0;
static int g_active_tab = 0;
static int g_cols = DEFAULT_COLS;
static int g_rows = DEFAULT_ROWS;
static int g_line_h = DEFAULT_LINE_H;
static int g_win_w = DEFAULT_COLS * CHAR_W;
static int g_win_h = TAB_BAR_H + (DEFAULT_ROWS * DEFAULT_LINE_H);
static void str_copy(char *dst, const char *src, int max_len) {
int i = 0;
if (max_len <= 0) return;
while (i < max_len - 1 && src && src[i]) {
dst[i] = src[i];
i++;
}
dst[i] = 0;
}
static void str_append(char *dst, const char *src, int max_len) {
if (!dst || !src || max_len <= 0) return;
int dlen = (int)strlen(dst);
int i = 0;
while (dlen + i < max_len - 1 && src[i]) {
dst[dlen + i] = src[i];
i++;
}
dst[dlen + i] = 0;
}
static void trim_end(char *s) {
if (!s) return;
int len = (int)strlen(s);
while (len > 0 && (s[len - 1] == '\n' || s[len - 1] == '\r' || s[len - 1] == ' ' || s[len - 1] == '\t')) {
s[len - 1] = 0;
len--;
}
}
static void scrollback_init(TerminalSession *s) {
s->scroll_head = 0;
s->scroll_count = 0;
s->scroll_offset = 0;
if (s->scrollback) {
for (int i = 0; i < SCROLLBACK_LINES * SCROLLBACK_COLS; i++) {
s->scrollback[i].c = ' ';
s->scrollback[i].color = s->fg_color;
}
}
if (s->scroll_cols) {
for (int i = 0; i < SCROLLBACK_LINES; i++) s->scroll_cols[i] = 0;
}
}
static void scrollback_push_row(TerminalSession *s, const CharCell *row, int cols) {
if (!s->scrollback || !s->scroll_cols) return;
int idx = s->scroll_head;
CharCell *dest = s->scrollback + (idx * SCROLLBACK_COLS);
int copy_cols = cols;
if (copy_cols > SCROLLBACK_COLS) copy_cols = SCROLLBACK_COLS;
for (int i = 0; i < SCROLLBACK_COLS; i++) {
dest[i].c = ' ';
dest[i].color = s->fg_color;
}
for (int i = 0; i < copy_cols; i++) {
dest[i] = row[i];
}
s->scroll_cols[idx] = copy_cols;
s->scroll_head = (idx + 1) % SCROLLBACK_LINES;
if (s->scroll_count < SCROLLBACK_LINES) {
s->scroll_count++;
} else if (s->scroll_offset > 0) {
s->scroll_offset--;
}
if (s->scroll_offset > 0) {
s->scroll_offset++;
}
}
static int scrollback_max_offset(TerminalSession *s) {
int total = s->scroll_count + g_rows;
if (total <= g_rows) return 0;
return total - g_rows;
}
static void session_adjust_scroll(TerminalSession *s, int delta) {
if (!s) return;
int max_offset = scrollback_max_offset(s);
s->scroll_offset += delta;
if (s->scroll_offset < 0) s->scroll_offset = 0;
if (s->scroll_offset > max_offset) s->scroll_offset = max_offset;
}
static CharCell *scrollback_get_line(TerminalSession *s, int line_index, int *out_cols) {
if (!s || line_index < 0 || line_index >= s->scroll_count) return NULL;
int start = s->scroll_head - s->scroll_count;
if (start < 0) start += SCROLLBACK_LINES;
int idx = (start + line_index) % SCROLLBACK_LINES;
if (out_cols) *out_cols = s->scroll_cols[idx];
return s->scrollback + (idx * SCROLLBACK_COLS);
}
static uint32_t ansi_color_16(int idx) {
static uint32_t base[16] = {
0xFF000000, 0xFFAA0000, 0xFF00AA00, 0xFFAA5500,
0xFF0000AA, 0xFFAA00AA, 0xFF00AAAA, 0xFFAAAAAA,
0xFF555555, 0xFFFF5555, 0xFF55FF55, 0xFFFFFF55,
0xFF5555FF, 0xFFFF55FF, 0xFF55FFFF, 0xFFFFFFFF
};
if (idx < 0) idx = 0;
if (idx > 15) idx = 15;
return base[idx];
}
static void session_reset_colors(TerminalSession *s) {
s->fg_color = 0xFFFFFFFF;
s->bg_color = 0xFF1E1E1E;
}
static void session_clear(TerminalSession *s) {
for (int i = 0; i < g_cols * g_rows; i++) {
s->cells[i].c = ' ';
s->cells[i].color = s->fg_color;
}
s->cursor_row = 0;
s->cursor_col = 0;
s->scroll_offset = 0;
}
static void session_scroll(TerminalSession *s) {
int row_bytes = g_cols * (int)sizeof(CharCell);
if (g_rows > 0) {
scrollback_push_row(s, s->cells, g_cols);
}
memmove(s->cells, s->cells + g_cols, row_bytes * (g_rows - 1));
for (int i = 0; i < g_cols; i++) {
int idx = (g_rows - 1) * g_cols + i;
s->cells[idx].c = ' ';
s->cells[idx].color = s->fg_color;
}
s->cursor_row = g_rows - 1;
s->cursor_col = 0;
}
static void session_put_char(TerminalSession *s, char c) {
if (c == '\n') {
s->cursor_row++;
s->cursor_col = 0;
if (s->cursor_row >= g_rows) session_scroll(s);
return;
}
if (c == '\r') {
s->cursor_col = 0;
return;
}
if (c == '\b') {
if (s->cursor_col > 0) s->cursor_col--;
int idx = s->cursor_row * g_cols + s->cursor_col;
if (idx >= 0 && idx < g_cols * g_rows) {
s->cells[idx].c = ' ';
s->cells[idx].color = s->fg_color;
}
return;
}
if (c == '\t') {
int next = ((s->cursor_col / 4) + 1) * 4;
while (s->cursor_col < next) {
session_put_char(s, ' ');
}
return;
}
if (c < 32) return;
int idx = s->cursor_row * g_cols + s->cursor_col;
if (idx >= 0 && idx < g_cols * g_rows) {
s->cells[idx].c = c;
s->cells[idx].color = s->fg_color;
}
s->cursor_col++;
if (s->cursor_col >= g_cols) {
s->cursor_col = 0;
s->cursor_row++;
if (s->cursor_row >= g_rows) session_scroll(s);
}
}
static void ansi_handle_sgr(TerminalSession *s) {
if (s->ansi_param_count == 0) {
session_reset_colors(s);
return;
}
for (int i = 0; i < s->ansi_param_count; i++) {
int p = s->ansi_params[i];
if (p == 0) session_reset_colors(s);
else if (p >= 30 && p <= 37) s->fg_color = ansi_color_16(p - 30);
else if (p >= 90 && p <= 97) s->fg_color = ansi_color_16(8 + (p - 90));
else if (p >= 40 && p <= 47) s->bg_color = ansi_color_16(p - 40);
else if (p >= 100 && p <= 107) s->bg_color = ansi_color_16(8 + (p - 100));
else if (p == 38 || p == 48) {
if (i + 4 < s->ansi_param_count && s->ansi_params[i + 1] == 2) {
int r = s->ansi_params[i + 2] & 0xFF;
int g = s->ansi_params[i + 3] & 0xFF;
int b = s->ansi_params[i + 4] & 0xFF;
uint32_t color = 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b;
if (p == 38) s->fg_color = color;
else s->bg_color = color;
i += 4;
}
}
}
}
static void ansi_finalize(TerminalSession *s, char cmd) {
if (cmd == 'm') {
ansi_handle_sgr(s);
} else if (cmd == 'J') {
int mode = s->ansi_param_count > 0 ? s->ansi_params[0] : 0;
if (mode == 2 || mode == 0) session_clear(s);
} else if (cmd == 'K') {
int row = s->cursor_row;
for (int col = s->cursor_col; col < g_cols; col++) {
int idx = row * g_cols + col;
s->cells[idx].c = ' ';
s->cells[idx].color = s->fg_color;
}
} else if (cmd == 'H' || cmd == 'f') {
int row = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1;
int col = (s->ansi_param_count > 1) ? s->ansi_params[1] : 1;
if (row < 1) row = 1;
if (col < 1) col = 1;
s->cursor_row = row - 1;
s->cursor_col = col - 1;
} else if (cmd == 'A') {
int n = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1;
s->cursor_row -= n;
if (s->cursor_row < 0) s->cursor_row = 0;
} else if (cmd == 'B') {
int n = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1;
s->cursor_row += n;
if (s->cursor_row >= g_rows) s->cursor_row = g_rows - 1;
} else if (cmd == 'C') {
int n = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1;
s->cursor_col += n;
if (s->cursor_col >= g_cols) s->cursor_col = g_cols - 1;
} else if (cmd == 'D') {
int n = (s->ansi_param_count > 0) ? s->ansi_params[0] : 1;
s->cursor_col -= n;
if (s->cursor_col < 0) s->cursor_col = 0;
} else if (cmd == 's') {
s->saved_row = s->cursor_row;
s->saved_col = s->cursor_col;
} else if (cmd == 'u') {
s->cursor_row = s->saved_row;
s->cursor_col = s->saved_col;
}
s->ansi_state = 0;
s->ansi_param_count = 0;
}
static void session_process_char(TerminalSession *s, char c) {
if (s->ansi_state == 0) {
if (c == 27) {
s->ansi_state = 1;
s->ansi_param_count = 0;
s->ansi_params[0] = 0;
return;
}
session_put_char(s, c);
return;
}
if (s->ansi_state == 1) {
if (c == '[') {
s->ansi_state = 2;
s->ansi_param_count = 0;
s->ansi_params[0] = 0;
return;
}
s->ansi_state = 0;
session_put_char(s, c);
return;
}
if (s->ansi_state == 2) {
if (c >= '0' && c <= '9') {
int idx = s->ansi_param_count;
s->ansi_params[idx] = s->ansi_params[idx] * 10 + (c - '0');
return;
}
if (c == ';') {
if (s->ansi_param_count < 7) {
s->ansi_param_count++;
s->ansi_params[s->ansi_param_count] = 0;
}
return;
}
s->ansi_param_count++;
ansi_finalize(s, c);
return;
}
}
static void session_process_output(TerminalSession *s, const char *buf, int len) {
for (int i = 0; i < len; i++) {
session_process_char(s, buf[i]);
}
}
static int get_tab_width(void) {
if (g_tab_count <= 0) return g_win_w;
int tab_w = g_win_w / g_tab_count;
if (tab_w < 60) tab_w = 60;
return tab_w;
}
static int read_proc_field(int pid, const char *field, char *out, int max_len) {
if (!out || max_len <= 0) return -1;
char path[64];
path[0] = 0;
str_append(path, "/proc/", sizeof(path));
char pid_buf[16];
itoa(pid, pid_buf);
str_append(path, pid_buf, sizeof(path));
str_append(path, "/", sizeof(path));
str_append(path, field, sizeof(path));
int fd = sys_open(path, "r");
if (fd < 0) return -1;
int bytes = sys_read(fd, out, max_len - 1);
sys_close(fd);
if (bytes <= 0) return -1;
out[bytes] = 0;
trim_end(out);
return 0;
}
static void truncate_label(const char *src, char *out, int max_chars) {
if (!out || max_chars <= 0) return;
int len = (int)strlen(src);
if (len <= max_chars) {
str_copy(out, src, max_chars + 1);
return;
}
if (max_chars <= 3) {
for (int i = 0; i < max_chars; i++) out[i] = src[i];
out[max_chars] = 0;
return;
}
for (int i = 0; i < max_chars - 3; i++) out[i] = src[i];
out[max_chars - 3] = '.';
out[max_chars - 2] = '.';
out[max_chars - 1] = '.';
out[max_chars] = 0;
}
static void get_tab_title(TerminalSession *s, char *out, int max_len) {
if (!s || !out) return;
int fg = sys_tty_get_fg(s->tty_id);
if (fg > 0) {
if (read_proc_field(fg, "name", out, max_len) == 0) return;
}
if (s->bsh_pid > 0) {
if (read_proc_field(s->bsh_pid, "cwd", out, max_len) == 0) return;
}
str_copy(out, "Bsh", max_len);
}
static int read_config_value(const char *key, char *out, int max_len) {
if (!key || !out || max_len <= 0) return -1;
int fd = sys_open("/Library/bsh/bshrc", "r");
if (fd < 0) return -1;
char buf[4096];
int bytes = sys_read(fd, buf, sizeof(buf) - 1);
sys_close(fd);
if (bytes <= 0) return -1;
buf[bytes] = 0;
char *line = buf;
while (*line) {
char *end = line;
while (*end && *end != '\n' && *end != '\r') end++;
char saved = *end;
*end = 0;
trim_end(line);
if (line[0] != '#' && line[0] != 0) {
char *sep = line;
while (*sep && *sep != '=') sep++;
if (*sep == '=') {
*sep = 0;
if (strcmp(line, key) == 0) {
str_copy(out, sep + 1, max_len);
return 0;
}
}
}
line = end + (saved ? 1 : 0);
if (saved == '\r' && *line == '\n') line++;
}
return -1;
}
static void resolve_bsh_path(char *out, int max_len) {
char path_line[256];
if (read_config_value("PATH", path_line, sizeof(path_line)) != 0) {
str_copy(path_line, "/bin", sizeof(path_line));
}
int i = 0;
int start = 0;
while (1) {
if (path_line[i] == ':' || path_line[i] == 0) {
int len = i - start;
if (len > 0) {
char base[128];
if (len >= (int)sizeof(base)) len = (int)sizeof(base) - 1;
for (int j = 0; j < len; j++) base[j] = path_line[start + j];
base[len] = 0;
char candidate[160];
candidate[0] = 0;
str_append(candidate, base, sizeof(candidate));
if (candidate[0] && candidate[strlen(candidate) - 1] != '/') str_append(candidate, "/", sizeof(candidate));
str_append(candidate, "bsh.elf", sizeof(candidate));
if (sys_exists(candidate)) {
str_copy(out, candidate, max_len);
return;
}
candidate[0] = 0;
str_append(candidate, base, sizeof(candidate));
if (candidate[0] && candidate[strlen(candidate) - 1] != '/') str_append(candidate, "/", sizeof(candidate));
str_append(candidate, "bsh", sizeof(candidate));
if (sys_exists(candidate)) {
str_copy(out, candidate, max_len);
return;
}
}
start = i + 1;
}
if (path_line[i] == 0) break;
i++;
}
str_copy(out, "/bin/bsh.elf", max_len);
}
static bool has_space(const char *s) {
if (!s) return false;
while (*s) {
if (*s == ' ') return true;
s++;
}
return false;
}
static void terminal_resize(int w, int h) {
int min_w = CHAR_W * 40;
int min_h = TAB_BAR_H + (g_line_h * 10);
if (w < min_w) w = min_w;
if (h < min_h) h = min_h;
g_win_w = w;
g_win_h = h;
int new_cols = w / CHAR_W;
int content_h = h - TAB_BAR_H - CONTENT_PAD_BOTTOM;
if (g_line_h <= 0) g_line_h = DEFAULT_LINE_H;
if (content_h < g_line_h) content_h = g_line_h;
int new_rows = content_h / g_line_h;
if (new_cols < 10) new_cols = 10;
if (new_rows < 5) new_rows = 5;
if (new_cols == g_cols && new_rows == g_rows) return;
int old_cols = g_cols;
int old_rows = g_rows;
g_cols = new_cols;
g_rows = new_rows;
for (int i = 0; i < g_tab_count; i++) {
TerminalSession *s = &g_tabs[i];
int old_scroll_count = s->scroll_count;
int old_scroll_offset = s->scroll_offset;
int old_total_lines = old_scroll_count + old_rows;
int old_bottom_line = old_total_lines - 1 - old_scroll_offset;
int old_top_line = old_bottom_line - (old_rows - 1);
CharCell *old_cells = s->cells;
int old_cursor_row = s->cursor_row;
int old_cursor_col = s->cursor_col;
s->cells = (CharCell *)malloc(sizeof(CharCell) * g_cols * g_rows);
for (int j = 0; j < g_cols * g_rows; j++) {
s->cells[j].c = ' ';
s->cells[j].color = s->fg_color;
}
int row_start = 0;
if (old_rows > g_rows) {
if (old_cursor_row >= g_rows) {
row_start = old_cursor_row - (g_rows - 1);
} else {
row_start = 0;
}
int max_start = old_rows - g_rows;
if (row_start > max_start) row_start = max_start;
if (row_start < 0) row_start = 0;
}
int dropped = 0;
if (row_start > 0) {
int projected = old_scroll_count + row_start;
if (projected > SCROLLBACK_LINES) dropped = projected - SCROLLBACK_LINES;
}
int desired_top_line = old_top_line - dropped;
if (desired_top_line < 0) desired_top_line = 0;
for (int r = 0; r < row_start; r++) {
scrollback_push_row(s, old_cells + (r * old_cols), old_cols);
}
int copy_rows = old_rows;
if (copy_rows > g_rows) copy_rows = g_rows;
int copy_cols = old_cols < g_cols ? old_cols : g_cols;
for (int r = 0; r < copy_rows; r++) {
CharCell *src = old_cells + ((row_start + r) * old_cols);
CharCell *dst = s->cells + (r * g_cols);
for (int c = 0; c < copy_cols; c++) {
dst[c] = src[c];
}
}
s->cursor_row = old_cursor_row - row_start;
if (old_rows <= g_rows) s->cursor_row = old_cursor_row;
if (s->cursor_row < 0) s->cursor_row = 0;
if (s->cursor_row >= g_rows) s->cursor_row = g_rows - 1;
s->cursor_col = old_cursor_col;
if (s->cursor_col < 0) s->cursor_col = 0;
if (s->cursor_col >= g_cols) s->cursor_col = g_cols - 1;
if (old_scroll_offset == 0) {
s->scroll_offset = 0;
} else {
int new_total_lines = s->scroll_count + g_rows;
int desired_bottom = desired_top_line + (g_rows - 1);
s->scroll_offset = (new_total_lines - 1) - desired_bottom;
}
session_adjust_scroll(s, 0);
if (old_cells) free(old_cells);
}
}
static void draw_tabs(void) {
if (g_tab_count <= 0) return;
ui_draw_rect(g_win, 0, 0, g_win_w, TAB_BAR_H, 0xFF1A1A1A);
int tab_w = get_tab_width();
for (int i = 0; i < g_tab_count; i++) {
int x = i * tab_w;
uint32_t bg = (i == g_active_tab) ? 0xFF333333 : 0xFF222222;
ui_draw_rect(g_win, x, 0, tab_w - 2, TAB_BAR_H, bg);
int close_size = TAB_CLOSE_W;
int close_x = x + tab_w - TAB_CLOSE_PAD - close_size;
int close_y = (TAB_BAR_H - close_size) / 2;
uint32_t close_bg = (i == g_active_tab) ? 0xFF444444 : 0xFF333333;
ui_draw_rect(g_win, close_x, close_y, close_size, close_size, close_bg);
ui_draw_string(g_win, close_x + 2, close_y + 1, "x", 0xFFFFFFFF);
char title[64];
char label[64];
get_tab_title(&g_tabs[i], title, sizeof(title));
int text_w = tab_w - 10 - (TAB_CLOSE_PAD + TAB_CLOSE_W);
int max_chars = text_w / CHAR_W;
if (max_chars < 4) max_chars = 4;
truncate_label(title, label, max_chars);
ui_draw_string(g_win, x + 6, 4, label, 0xFFFFFFFF);
}
}
static void draw_session(TerminalSession *s) {
int base_y = TAB_BAR_H;
ui_draw_rect(g_win, 0, base_y, g_win_w, g_win_h - base_y, s->bg_color);
int max_offset = scrollback_max_offset(s);
if (s->scroll_offset > max_offset) s->scroll_offset = max_offset;
int total_lines = s->scroll_count + g_rows;
int bottom_line = total_lines - 1 - s->scroll_offset;
int top_line = bottom_line - (g_rows - 1);
for (int row = 0; row < g_rows; row++) {
int line_index = top_line + row;
CharCell *line = NULL;
int line_cols = 0;
if (line_index >= 0 && line_index < s->scroll_count) {
line = scrollback_get_line(s, line_index, &line_cols);
} else if (line_index >= s->scroll_count && line_index < total_lines) {
int live_row = line_index - s->scroll_count;
if (live_row >= 0 && live_row < g_rows) {
line = s->cells + (live_row * g_cols);
line_cols = g_cols;
}
}
for (int col = 0; col < g_cols; col++) {
char ch = ' ';
uint32_t color = s->fg_color;
if (line && col < line_cols) {
ch = line[col].c;
if (ch == 0) ch = ' ';
color = line[col].color;
}
char str[2] = { ch, 0 };
int x = col * CHAR_W;
int y = base_y + row * g_line_h;
ui_draw_string_bitmap(g_win, x, y, str, color);
}
}
if (s->scroll_offset == 0) {
int cx = s->cursor_col * CHAR_W;
int cy = base_y + s->cursor_row * g_line_h;
ui_draw_rect(g_win, cx, cy + g_line_h - 2, CHAR_W, 2, 0xFFFFFFFF);
}
ui_mark_dirty(g_win, 0, 0, g_win_w, g_win_h);
}
static void tab_init(TerminalSession *s, int tty_id, int bsh_pid) {
s->tty_id = tty_id;
s->bsh_pid = bsh_pid;
s->cells = (CharCell *)malloc(sizeof(CharCell) * g_cols * g_rows);
s->scrollback = (CharCell *)malloc(sizeof(CharCell) * SCROLLBACK_LINES * SCROLLBACK_COLS);
s->scroll_cols = (int *)malloc(sizeof(int) * SCROLLBACK_LINES);
s->cursor_row = 0;
s->cursor_col = 0;
s->ansi_state = 0;
s->ansi_param_count = 0;
s->saved_row = 0;
s->saved_col = 0;
session_reset_colors(s);
scrollback_init(s);
session_clear(s);
}
static void tab_free(TerminalSession *s) {
if (!s) return;
if (s->cells) {
free(s->cells);
s->cells = NULL;
}
if (s->scrollback) {
free(s->scrollback);
s->scrollback = NULL;
}
if (s->scroll_cols) {
free(s->scroll_cols);
s->scroll_cols = NULL;
}
}
static void close_tab(int idx) {
if (idx < 0 || idx >= g_tab_count) return;
TerminalSession *s = &g_tabs[idx];
sys_tty_kill_all(s->tty_id);
sys_tty_destroy(s->tty_id);
tab_free(s);
for (int i = idx; i < g_tab_count - 1; i++) {
g_tabs[i] = g_tabs[i + 1];
}
g_tab_count--;
if (g_tab_count <= 0) {
sys_exit(0);
}
if (g_active_tab > idx) {
g_active_tab--;
} else if (g_active_tab == idx) {
if (g_active_tab >= g_tab_count) g_active_tab = g_tab_count - 1;
}
}
static int create_tab(void) {
if (g_tab_count >= MAX_TABS) return -1;
int tty_id = sys_tty_create();
if (tty_id < 0) return -1;
char start_dir[256];
start_dir[0] = 0;
if (g_tab_count > 0) {
TerminalSession *active = &g_tabs[g_active_tab];
if (active->bsh_pid > 0) {
read_proc_field(active->bsh_pid, "cwd", start_dir, sizeof(start_dir));
}
}
char args[32];
args[0] = 0;
str_append(args, "-t ", sizeof(args));
char id_buf[8];
itoa(tty_id, id_buf);
str_append(args, id_buf, sizeof(args));
if (start_dir[0]) {
str_append(args, " -d ", sizeof(args));
if (has_space(start_dir)) {
str_append(args, "\"", sizeof(args));
str_append(args, start_dir, sizeof(args));
str_append(args, "\"", sizeof(args));
} else {
str_append(args, start_dir, sizeof(args));
}
}
char bsh_path[128];
resolve_bsh_path(bsh_path, sizeof(bsh_path));
int pid = sys_spawn(bsh_path, args, SPAWN_FLAG_TERMINAL | SPAWN_FLAG_TTY_ID, tty_id);
if (pid < 0) return -1;
tab_init(&g_tabs[g_tab_count], tty_id, pid);
g_tab_count++;
return g_tab_count - 1;
}
static void handle_key(gui_event_t *ev) {
TerminalSession *s = &g_tabs[g_active_tab];
char c = (char)ev->arg1;
bool ctrl = ev->arg3 != 0;
if (ctrl && c == 't') {
int idx = create_tab();
if (idx >= 0) g_active_tab = idx;
return;
}
if (ctrl && c == 20) {
if (g_tab_count > 0) g_active_tab = (g_active_tab + 1) % g_tab_count;
return;
}
if (ctrl && c == 19) {
if (g_tab_count > 0) g_active_tab = (g_active_tab + g_tab_count - 1) % g_tab_count;
return;
}
if (ctrl && (c == 'c' || c == 'C')) {
int fg = sys_tty_get_fg(s->tty_id);
if (fg > 0) {
sys_tty_kill_fg(s->tty_id);
sys_tty_set_fg(s->tty_id, 0);
return;
}
char ch = 3;
sys_tty_write_in(s->tty_id, &ch, 1);
return;
}
sys_tty_write_in(s->tty_id, &c, 1);
}
int main(void) {
g_win = ui_window_create("Terminal", 60, 60, g_win_w, g_win_h);
ui_window_set_resizable(g_win, true);
int fh = (int)ui_get_font_height();
if (fh > 0) g_line_h = fh;
terminal_resize(g_win_w, g_win_h);
int idx = create_tab();
if (idx < 0) return 1;
g_active_tab = idx;
gui_event_t ev;
char out_buf[TTY_READ_CHUNK];
while (1) {
bool dirty = false;
for (int i = 0; i < g_tab_count; i++) {
TerminalSession *s = &g_tabs[i];
int read = 0;
while ((read = sys_tty_read_out(s->tty_id, out_buf, sizeof(out_buf))) > 0) {
session_process_output(s, out_buf, read);
if (i == g_active_tab) dirty = true;
}
}
while (ui_get_event(g_win, &ev)) {
if (ev.type == GUI_EVENT_CLOSE) {
for (int i = 0; i < g_tab_count; i++) {
if (g_tabs[i].bsh_pid > 0) sys_kill(g_tabs[i].bsh_pid);
}
sys_exit(0);
} else if (ev.type == GUI_EVENT_KEY) {
handle_key(&ev);
dirty = true;
} else if (ev.type == GUI_EVENT_CLICK) {
if (ev.arg2 < TAB_BAR_H) {
int tab_w = get_tab_width();
int tab = ev.arg1 / tab_w;
if (tab >= 0 && tab < g_tab_count) {
int close_size = TAB_CLOSE_W;
int close_x = tab * tab_w + tab_w - TAB_CLOSE_PAD - close_size;
int close_y = (TAB_BAR_H - close_size) / 2;
if (ev.arg1 >= close_x && ev.arg1 < close_x + close_size &&
ev.arg2 >= close_y && ev.arg2 < close_y + close_size) {
close_tab(tab);
} else {
g_active_tab = tab;
}
dirty = true;
}
}
} else if (ev.type == GUI_EVENT_MOUSE_WHEEL) {
int lines = ev.arg1 * 3;
if (lines != 0) {
session_adjust_scroll(&g_tabs[g_active_tab], lines);
dirty = true;
}
} else if (ev.type == GUI_EVENT_RESIZE) {
terminal_resize(ev.arg1, ev.arg2);
dirty = true;
} else if (ev.type == GUI_EVENT_PAINT) {
dirty = true;
}
}
if (dirty) {
draw_tabs();
draw_session(&g_tabs[g_active_tab]);
} else {
sys_yield();
}
}
return 0;
}

View file

@ -40,6 +40,46 @@ static void editor_strcpy(char *dest, const char *src) {
*dest = 0; *dest = 0;
} }
static int editor_strlen(const char *s) {
int len = 0;
while (s && s[len]) len++;
return len;
}
static void editor_strncat(char *dst, const char *src, int max_len) {
if (!dst || !src || max_len <= 0) return;
int dlen = editor_strlen(dst);
int i = 0;
while (dlen + i < max_len - 1 && src[i]) {
dst[dlen + i] = src[i];
i++;
}
dst[dlen + i] = 0;
}
static void editor_resolve_path(const char *input, char *out, int max_len) {
if (!out || max_len <= 0) return;
if (!input || input[0] == 0) {
out[0] = 0;
return;
}
if (input[0] == '/') {
editor_strcpy(out, input);
return;
}
char cwd[256];
if (sys_getcwd(cwd, sizeof(cwd)) < 0) {
editor_strcpy(out, input);
return;
}
editor_strcpy(out, cwd);
int len = editor_strlen(out);
if (len > 0 && out[len - 1] != '/') editor_strncat(out, "/", max_len);
editor_strncat(out, input, max_len);
}
static void editor_clear_all(void) { static void editor_clear_all(void) {
for (int i = 0; i < EDITOR_MAX_LINES; i++) { for (int i = 0; i < EDITOR_MAX_LINES; i++) {
for (int j = 0; j < EDITOR_MAX_LINE_LEN; j++) { for (int j = 0; j < EDITOR_MAX_LINE_LEN; j++) {
@ -71,9 +111,11 @@ static void editor_ensure_cursor_visible(void) {
void editor_open_file(const char *filename) { void editor_open_file(const char *filename) {
editor_clear_all(); editor_clear_all();
editor_strcpy(open_filename, filename); char resolved[256];
editor_resolve_path(filename, resolved, sizeof(resolved));
editor_strcpy(open_filename, resolved);
int fd = sys_open(filename, "r"); int fd = sys_open(resolved, "r");
if (fd < 0) { if (fd < 0) {
file_modified = 0; file_modified = 0;
return; return;

View file

@ -41,6 +41,40 @@ static int viewer_strlen(const char *s) {
return len; return len;
} }
static void viewer_strncat(char *dst, const char *src, int max_len) {
if (!dst || !src || max_len <= 0) return;
int dlen = viewer_strlen(dst);
int i = 0;
while (dlen + i < max_len - 1 && src[i]) {
dst[dlen + i] = src[i];
i++;
}
dst[dlen + i] = 0;
}
static void viewer_resolve_path(const char *input, char *out, int max_len) {
if (!out || max_len <= 0) return;
if (!input || input[0] == 0) {
out[0] = 0;
return;
}
if (input[0] == '/') {
viewer_strcpy(out, input);
return;
}
char cwd[256];
if (sys_getcwd(cwd, sizeof(cwd)) < 0) {
viewer_strcpy(out, input);
return;
}
viewer_strcpy(out, cwd);
int len = viewer_strlen(out);
if (len > 0 && out[len - 1] != '/') viewer_strncat(out, "/", max_len);
viewer_strncat(out, input, max_len);
}
static void viewer_scale_rgba_to_argb(const unsigned char *rgba, int src_w, int src_h, static void viewer_scale_rgba_to_argb(const unsigned char *rgba, int src_w, int src_h,
uint32_t *dst, int dst_w, int dst_h) { uint32_t *dst, int dst_w, int dst_h) {
if (src_w == dst_w && src_h == dst_h) { if (src_w == dst_w && src_h == dst_h) {
@ -141,7 +175,9 @@ static void viewer_paint(ui_window_t win) {
} }
void viewer_open_file(const char *path) { void viewer_open_file(const char *path) {
int fd = sys_open(path, "r"); char resolved[256];
viewer_resolve_path(path, resolved, sizeof(resolved));
int fd = sys_open(resolved, "r");
if (fd < 0) return; if (fd < 0) return;
uint32_t file_size = sys_size(fd); uint32_t file_size = sys_size(fd);
@ -250,13 +286,13 @@ void viewer_open_file(const char *path) {
stbi_image_free(rgba); stbi_image_free(rgba);
free(buf); free(buf);
viewer_strcpy(viewer_file_path, path); viewer_strcpy(viewer_file_path, resolved);
const char *fname = path; const char *fname = resolved;
int plen = viewer_strlen(path); int plen = viewer_strlen(resolved);
for (int i = plen - 1; i >= 0; i--) { for (int i = plen - 1; i >= 0; i--) {
if (path[i] == '/') { if (resolved[i] == '/') {
fname = &path[i + 1]; fname = &resolved[i + 1];
break; break;
} }
} }

View file

@ -329,7 +329,7 @@ int chdir(const char *path) {
} }
char* getcwd(char *buf, int size) { char* getcwd(char *buf, int size) {
if (sys_getcwd(buf, size) == 0) return buf; if (sys_getcwd(buf, size) >= 0) return buf;
return NULL; return NULL;
} }

View file

@ -145,6 +145,46 @@ int sys_chdir(const char *path) {
return (int)syscall2(SYS_FS, FS_CMD_CHDIR, (uint64_t)path); return (int)syscall2(SYS_FS, FS_CMD_CHDIR, (uint64_t)path);
} }
int sys_tty_create(void) {
return (int)syscall2(SYS_SYSTEM, SYSTEM_CMD_TTY_CREATE, 0);
}
int sys_tty_read_out(int tty_id, char *buf, int len) {
return (int)syscall5(SYS_SYSTEM, SYSTEM_CMD_TTY_READ_OUT, (uint64_t)tty_id, (uint64_t)buf, (uint64_t)len, 0);
}
int sys_tty_write_in(int tty_id, const char *buf, int len) {
return (int)syscall5(SYS_SYSTEM, SYSTEM_CMD_TTY_WRITE_IN, (uint64_t)tty_id, (uint64_t)buf, (uint64_t)len, 0);
}
int sys_tty_read_in(char *buf, int len) {
return (int)syscall4(SYS_SYSTEM, SYSTEM_CMD_TTY_READ_IN, (uint64_t)buf, (uint64_t)len, 0);
}
int sys_spawn(const char *path, const char *args, uint64_t flags, uint64_t tty_id) {
return (int)syscall5(SYS_SYSTEM, SYSTEM_CMD_SPAWN, (uint64_t)path, (uint64_t)args, flags, (uint64_t)tty_id);
}
int sys_tty_set_fg(int tty_id, int pid) {
return (int)syscall4(SYS_SYSTEM, SYSTEM_CMD_TTY_SET_FG, (uint64_t)tty_id, (uint64_t)pid, 0);
}
int sys_tty_get_fg(int tty_id) {
return (int)syscall3(SYS_SYSTEM, SYSTEM_CMD_TTY_GET_FG, (uint64_t)tty_id, 0);
}
int sys_tty_kill_fg(int tty_id) {
return (int)syscall3(SYS_SYSTEM, SYSTEM_CMD_TTY_KILL_FG, (uint64_t)tty_id, 0);
}
int sys_tty_kill_all(int tty_id) {
return (int)syscall3(SYS_SYSTEM, SYSTEM_CMD_TTY_KILL_ALL, (uint64_t)tty_id, 0);
}
int sys_tty_destroy(int tty_id) {
return (int)syscall3(SYS_SYSTEM, SYSTEM_CMD_TTY_DESTROY, (uint64_t)tty_id, 0);
}
void sys_kill(int pid) { void sys_kill(int pid) {
syscall1(SYS_KILL, (uint64_t)pid); syscall1(SYS_KILL, (uint64_t)pid);
} }

View file

@ -72,6 +72,20 @@
#define SYSTEM_CMD_TCP_RECV_NB 42 #define SYSTEM_CMD_TCP_RECV_NB 42
#define SYSTEM_CMD_YIELD 43 #define SYSTEM_CMD_YIELD 43
#define SYSTEM_CMD_PARALLEL_RUN 50 #define SYSTEM_CMD_PARALLEL_RUN 50
#define SYSTEM_CMD_TTY_CREATE 60
#define SYSTEM_CMD_TTY_READ_OUT 61
#define SYSTEM_CMD_TTY_WRITE_IN 62
#define SYSTEM_CMD_TTY_READ_IN 63
#define SYSTEM_CMD_SPAWN 64
#define SYSTEM_CMD_TTY_SET_FG 65
#define SYSTEM_CMD_TTY_GET_FG 66
#define SYSTEM_CMD_TTY_KILL_FG 67
#define SYSTEM_CMD_TTY_KILL_ALL 68
#define SYSTEM_CMD_TTY_DESTROY 69
#define SPAWN_FLAG_TERMINAL 0x1
#define SPAWN_FLAG_INHERIT_TTY 0x2
#define SPAWN_FLAG_TTY_ID 0x4
// Internal assembly entry into Ring 0 // Internal assembly entry into Ring 0
extern uint64_t syscall0(uint64_t sys_num); extern uint64_t syscall0(uint64_t sys_num);
@ -115,6 +129,17 @@ int sys_exists(const char *path);
int sys_getcwd(char *buf, int size); int sys_getcwd(char *buf, int size);
int sys_chdir(const char *path); int sys_chdir(const char *path);
int sys_tty_create(void);
int sys_tty_read_out(int tty_id, char *buf, int len);
int sys_tty_write_in(int tty_id, const char *buf, int len);
int sys_tty_read_in(char *buf, int len);
int sys_spawn(const char *path, const char *args, uint64_t flags, uint64_t tty_id);
int sys_tty_set_fg(int tty_id, int pid);
int sys_tty_get_fg(int tty_id);
int sys_tty_kill_fg(int tty_id);
int sys_tty_kill_all(int tty_id);
int sys_tty_destroy(int tty_id);
typedef struct { typedef struct {
char name[256]; char name[256];
uint32_t size; uint32_t size;

View file

@ -3,8 +3,6 @@
// This header needs to maintain in any file it is present in, as per the GPL license terms. // This header needs to maintain in any file it is present in, as per the GPL license terms.
#include <stdlib.h> #include <stdlib.h>
#include <syscall.h> #include <syscall.h>
#include "libc/libui.h"
static int term_cols = 116; static int term_cols = 116;
static int term_rows = 41; static int term_rows = 41;
@ -80,13 +78,10 @@ static void telnet_handle_option(uint8_t cmd, uint8_t opt) {
case WILL: case WILL:
if (opt == OPT_SUPPRESS_GA) { if (opt == OPT_SUPPRESS_GA) {
// Good — accept suppressed GA (most BBS systems do this)
telnet_send_3(IAC, DO, OPT_SUPPRESS_GA); telnet_send_3(IAC, DO, OPT_SUPPRESS_GA);
} else if (opt == OPT_ECHO) { } else if (opt == OPT_ECHO) {
// Server will echo chars (remote echo) — accept it
telnet_send_3(IAC, DO, OPT_ECHO); telnet_send_3(IAC, DO, OPT_ECHO);
} else { } else {
// Refuse other server offers
telnet_send_3(IAC, DONT, opt); telnet_send_3(IAC, DONT, opt);
} }
break; break;
@ -115,7 +110,6 @@ static void telnet_handle_sb_terminal_type(const uint8_t *sb_data, int sb_len) {
} }
typedef enum { typedef enum {
TS_DATA = 0, TS_DATA = 0,
TS_IAC, TS_IAC,
@ -159,7 +153,6 @@ static int telnet_process(const uint8_t *data, int len) {
if (b == IAC) { if (b == IAC) {
ts_state = TS_IAC; ts_state = TS_IAC;
} else { } else {
// Pass directly to display
out_char((char)b); out_char((char)b);
} }
break; break;
@ -167,7 +160,6 @@ static int telnet_process(const uint8_t *data, int len) {
case TS_IAC: case TS_IAC:
switch (b) { switch (b) {
case IAC: case IAC:
// Escaped IAC — literal 0xFF
out_char((char)0xFF); out_char((char)0xFF);
ts_state = TS_DATA; ts_state = TS_DATA;
break; break;
@ -233,7 +225,6 @@ static int telnet_process(const uint8_t *data, int len) {
return 1; return 1;
} }
static int map_key(char c, uint8_t *key_out) { static int map_key(char c, uint8_t *key_out) {
if (c == 29) { if (c == 29) {
// Ctrl+] // Ctrl+]
@ -276,7 +267,7 @@ static int map_key(char c, uint8_t *key_out) {
static int my_atoi(const char *s) { static int my_atoi(const char *s) {
int v = 0; int v = 0;
while (*s >= '0' && *s <= '9') { v = v*10 + (*s - '0'); s++; } while (*s >= '0' && *s <= '9') { v = v * 10 + (*s - '0'); s++; }
return v; return v;
} }
@ -284,7 +275,7 @@ static int parse_ip(const char *s, net_ipv4_address_t *ip) {
int part = 0, val = 0; int part = 0, val = 0;
while (*s) { while (*s) {
if (*s >= '0' && *s <= '9') { if (*s >= '0' && *s <= '9') {
val = val*10 + (*s - '0'); val = val * 10 + (*s - '0');
if (val > 255) return -1; if (val > 255) return -1;
} else if (*s == '.') { } else if (*s == '.') {
if (part > 3) return -1; if (part > 3) return -1;
@ -341,27 +332,23 @@ int main(int argc, char **argv) {
} }
printf("Connected. Press Ctrl+] to disconnect.\n\n"); printf("Connected. Press Ctrl+] to disconnect.\n\n");
sys_system(41, 1, 0, 0, 0); // SYSTEM_CMD_SET_RAW_MODE = 1
uint8_t recv_buf[4096]; uint8_t recv_buf[4096];
int total = 0; int total = 0;
int idle_count = 0; int idle_count = 0;
int connected = 1; int connected = 1;
while (connected) { while (connected) {
gui_event_t ev; char ch = 0;
while (ui_get_event(0, &ev)) { // win=0 for console proc int got = sys_tty_read_in(&ch, 1);
if (ev.type == GUI_EVENT_KEY) { if (got > 0) {
uint8_t key_data[16]; uint8_t key_data[16];
int key_len = map_key((char)ev.arg1, key_data); int key_len = map_key(ch, key_data);
if (key_len < 0) { if (key_len < 0) {
connected = 0; connected = 0;
break; break;
} }
telnet_send(key_data, key_len); telnet_send(key_data, key_len);
} }
}
if (!connected) break;
int len = sys_tcp_recv_nb(recv_buf, sizeof(recv_buf) - 1); int len = sys_tcp_recv_nb(recv_buf, sizeof(recv_buf) - 1);
if (len < 0) { if (len < 0) {
@ -371,7 +358,6 @@ int main(int argc, char **argv) {
} }
if (len == 0) { if (len == 0) {
idle_count++; idle_count++;
// Don't timeout too fast if we are just waiting for user input
if (idle_count > 10000000) { if (idle_count > 10000000) {
printf("\r\n[Connection timed out]\r\n"); printf("\r\n[Connection timed out]\r\n");
connected = 0; connected = 0;
@ -395,11 +381,7 @@ int main(int argc, char **argv) {
} }
} }
// Disable raw mode before exiting
sys_system(41, 0, 0, 0, 0);
sys_tcp_close(); sys_tcp_close();
printf("\r\n[Telnet session ended]\r\n"); printf("\r\n[Telnet session ended]\r\n");
return 0; return 0;
} }

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,6 @@
#include "disk.h" #include "disk.h"
#include "wm.h" #include "wm.h"
#include "memory_manager.h" #include "memory_manager.h"
#include "cmd.h"
#include "process.h" #include "process.h"
#define EXPLORER_ITEM_HEIGHT 80 #define EXPLORER_ITEM_HEIGHT 80
#define EXPLORER_ITEM_WIDTH 120 #define EXPLORER_ITEM_WIDTH 120
@ -848,7 +847,7 @@ static void explorer_open_item(Window *win, int index) {
} else if (explorer_strcmp(state->items[index].name, "Calculator.shortcut") == 0) { } else if (explorer_strcmp(state->items[index].name, "Calculator.shortcut") == 0) {
process_create_elf("/bin/calculator.elf", NULL); return; process_create_elf("/bin/calculator.elf", NULL); return;
} else if (explorer_strcmp(state->items[index].name, "Terminal.shortcut") == 0) { } else if (explorer_strcmp(state->items[index].name, "Terminal.shortcut") == 0) {
target = &win_cmd; cmd_reset(); process_create_elf("/bin/terminal.elf", NULL); return;
} else if (explorer_strcmp(state->items[index].name, "Minesweeper.shortcut") == 0) { } else if (explorer_strcmp(state->items[index].name, "Minesweeper.shortcut") == 0) {
process_create_elf("/bin/minesweeper.elf", NULL); return; process_create_elf("/bin/minesweeper.elf", NULL); return;
} else if (explorer_strcmp(state->items[index].name, "Control Panel.shortcut") == 0 || explorer_strcmp(state->items[index].name, "Settings.shortcut") == 0) { } else if (explorer_strcmp(state->items[index].name, "Control Panel.shortcut") == 0 || explorer_strcmp(state->items[index].name, "Settings.shortcut") == 0) {

View file

@ -12,7 +12,6 @@
// External windows references (for opening other apps) // External windows references (for opening other apps)
extern Window win_explorer; extern Window win_explorer;
extern Window win_editor; extern Window win_editor;
extern Window win_cmd;
extern Window win_notepad; extern Window win_notepad;
extern Window win_markdown; extern Window win_markdown;

View file

@ -4,7 +4,6 @@
#include "wm.h" #include "wm.h"
#include "graphics.h" #include "graphics.h"
#include "io.h" #include "io.h"
#include "cmd.h"
#include "process.h" #include "process.h"
#include "syscall.h" #include "syscall.h"
#include "kutils.h" #include "kutils.h"
@ -2485,7 +2484,7 @@ static void wm_handle_mouse_internal(int dx, int dy, uint8_t buttons, int dz) {
if (existing) wm_bring_to_front_locked(existing); if (existing) wm_bring_to_front_locked(existing);
else process_create_elf("/bin/boredword.elf", NULL); else process_create_elf("/bin/boredword.elf", NULL);
} else if (str_starts_with(start_menu_pending_app, "Terminal")) { } else if (str_starts_with(start_menu_pending_app, "Terminal")) {
cmd_reset(); wm_bring_to_front_locked(&win_cmd); process_create_elf("/bin/terminal.elf", NULL);
} else if (str_starts_with(start_menu_pending_app, "Grapher")) { } else if (str_starts_with(start_menu_pending_app, "Grapher")) {
Window *existing = wm_find_window_by_title_locked("Grapher"); Window *existing = wm_find_window_by_title_locked("Grapher");
if (existing) wm_bring_to_front_locked(existing); if (existing) wm_bring_to_front_locked(existing);
@ -2550,7 +2549,7 @@ static void wm_handle_mouse_internal(int dx, int dy, uint8_t buttons, int dz) {
} else if (str_ends_with(icon->name, "Settings.shortcut")) { } else if (str_ends_with(icon->name, "Settings.shortcut")) {
process_create_elf("/bin/settings.elf", NULL); handled = true; process_create_elf("/bin/settings.elf", NULL); handled = true;
} else if (str_ends_with(icon->name, "Terminal.shortcut")) { } else if (str_ends_with(icon->name, "Terminal.shortcut")) {
wm_bring_to_front_locked(&win_cmd); handled = true; process_create_elf("/bin/terminal.elf", NULL); handled = true;
} else if (str_ends_with(icon->name, "About.shortcut")) { } else if (str_ends_with(icon->name, "About.shortcut")) {
process_create_elf("/bin/about.elf", NULL); handled = true; process_create_elf("/bin/about.elf", NULL); handled = true;
} else if (str_ends_with(icon->name, "Files.shortcut")) { } else if (str_ends_with(icon->name, "Files.shortcut")) {
@ -3104,9 +3103,6 @@ void wm_init(void) {
disk_manager_scan(); disk_manager_scan();
log_ok("Disk scanning complete"); log_ok("Disk scanning complete");
cmd_init();
log_ok("Command CLI ready");
explorer_init(); explorer_init();
log_ok("Explorer ready"); log_ok("Explorer ready");
@ -3128,19 +3124,15 @@ void wm_init(void) {
log_ok("Desktop icons refreshed"); log_ok("Desktop icons refreshed");
// Initialize z-indices // Initialize z-indices
win_cmd.z_index = 0;
win_explorer.z_index = 1; win_explorer.z_index = 1;
all_windows[0] = &win_cmd; all_windows[0] = &win_explorer;
all_windows[1] = &win_explorer; window_count = 1;
window_count = 2;
win_explorer.visible = false; win_explorer.visible = false;
win_explorer.focused = false; win_explorer.focused = false;
win_explorer.z_index = 10; win_explorer.z_index = 10;
win_cmd.visible = false;
force_redraw = true; force_redraw = true;
serial_write("[WM] Initialization complete, transitioning to GUI\n"); serial_write("[WM] Initialization complete, transitioning to GUI\n");