Files
ViperOS/Apps/Terminal/main.vp
T
TermiNexus 925e0ffaea feat(power): add reboot and shutdown syscalls and terminal commands
- REBOOT (0x1008): 8042 reset with PCI 0xCF9 fallback
- SHUTDOWN (0x1009): ACPI S5 writes to the common QEMU/Bochs/VBox
  PM1a control ports, then halt
- vpsdk.process.reboot()/shutdown()
- Terminal commands: reboot, shutdown (help updated)
2026-10-27 16:56:26 +08:00

393 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import vpsdk.vpui as vpui
import vpsdk.window as window
import vpsdk.process as process
import vpsdk.fs as fs
import vpsdk.devfs as devfs
import vpsdk.stdlib as stdlib
import vpsdk.stdio as stdio
import string
import viperlib
from stdint import *
@__attribute__(section(".text.startup"), aligned(16))
export def _start() -> int:
root: vpui.Tk = vpui.Tk()
root.title("Terminal")
root.set_style(window.STYLE_WIN7)
root.geometry(80, 30, 640, 480)
root.bg_color = uint32(0x0C0C0C)
master: vpui.Tk* = &root
def _sdbg(msg: str):
# S4:改走 /dev/serial0(SERIAL_PUTS 私有 syscall 已删除)
devfs.serial_puts(msg)
_sdbg("[term] start\n")
_cwd: [64]char
_cwd[0] = 47
for i in range(1, 64):
_cwd[i] = 0
sbar: vpui.Scrollbar = vpui.Scrollbar(master, 16, 446, vpui.VERTICAL)
root.pack(0, vpui.PACK_RIGHT, vpui.FILL_Y, 0, 0, 0)
term: vpui.Text = vpui.Text(master, 624, 446)
root.pack(1, vpui.PACK_LEFT, vpui.FILL_BOTH, 1, 0, 0)
term.insert("ViperOS Terminal v1.1")
term.insert("Type 'help' for commands.")
term.insert("\n")
term.yscrollbar = vpui.Widget*(sbar)
def _on_scroll():
act: int = sbar.action
if act == 1:
term.scroll_offset -= 1
elif act == 2:
term.scroll_offset += 1
elif act == 3:
vis: int = term._visible_lines()
term.scroll_offset -= vis
elif act == 4:
vis2: int = term._visible_lines()
term.scroll_offset += vis2
elif act == 5:
aval: int = sbar.action_value
if term.line_count > 0:
term.scroll_offset = aval * (term.line_count + 1) / 1000
vis3: int = term._visible_lines()
max_off: int = term.line_count + 1 - vis3
if max_off < 0: max_off = 0
if term.scroll_offset < 0: term.scroll_offset = 0
if term.scroll_offset > max_off: term.scroll_offset = max_off
term._update_scrollbar()
sbar.command = _on_scroll
def _startswith(s: str, prefix: str) -> int:
plen: sizet = string.strlen(prefix)
if string.strncmp(s, prefix, plen) == 0:
return 1
return 0
def _build_path(out: str, out_size: int, rel: str):
if *rel == 47:
string.strncpy(out, rel, out_size - 1)
else:
i: int = 0
for i in range(63):
ch: char = _cwd[i]
*(out + i) = ch
if ch == 0: break
cwd_len: int = i
if cwd_len > 0 and _cwd[cwd_len - 1] != 47:
*(out + cwd_len) = char(47)
cwd_len += 1
j: int = 0
for j in range(out_size - 1 - cwd_len):
ch2: char = *(rel + j)
*(out + cwd_len + j) = ch2
if ch2 == 0: break
*(out + out_size - 1) = char(0)
def _print_dir_entry(name: str, is_dir: int):
buf: [80]char
j: int = 0
for j in range(78):
ch: char = *(name + j)
buf[j] = ch
if ch == 0: break
if is_dir and j < 78:
buf[j] = 47
j += 1
buf[j] = 0
term.insert_ptr(&buf[0])
# 词法归一化绝对路径:折叠重复的 '/'、去掉 "."、处理 ".."(根目录的 ".." 仍是根)。
# 否则 _cwd 会原样存下 "/app/../.." 这类越来越长的串,pwd 也不直观。
def _normalize_path(dst: str, out_size: int, src: str):
limit: int = out_size - 1
seg_end: [32]int
depth: int = 0
out_len: int = 1
*(dst + 0) = char(47)
*(dst + 1) = char(0)
i: int = 0
while *(src + i) != 0:
while *(src + i) == 47 or *(src + i) == 92:
i += 1
if *(src + i) == 0: break
seg_start: int = i
while *(src + i) != 0 and *(src + i) != 47 and *(src + i) != 92:
i += 1
seg_len: int = i - seg_start
if seg_len == 1 and *(src + seg_start) == 46:
continue
if seg_len == 2 and *(src + seg_start) == 46 and *(src + seg_start + 1) == 46:
if depth > 0:
depth -= 1
out_len = seg_end[depth]
*(dst + out_len) = char(0)
continue
if depth < 32:
seg_end[depth] = out_len
depth += 1
if out_len > 1 and out_len < limit:
*(dst + out_len) = char(47)
out_len += 1
j: int = 0
while j < seg_len and out_len < limit:
*(dst + out_len) = *(src + seg_start + j)
out_len += 1
j += 1
*(dst + out_len) = char(0)
# 提示符 = 归一化路径 + "/> ":根目录显示 "/> ",/apps 显示 "/apps/> "。
def _update_prompt():
p: [80]char
i: int = 0
while i < 63:
ch: char = _cwd[i]
p[i] = ch
if ch == 0: break
i += 1
if i == 0:
p[0] = char(47)
i = 1
if p[i - 1] != char(47):
p[i] = char(47)
i += 1
p[i] = char(62) # '>'
i += 1
p[i] = char(32) # ' '
i += 1
p[i] = char(0)
term.set_prompt(&p[0])
def _cmd_cd(path: str):
# 裸 cd:只显示当前路径,不切换目录(切换请用 cd <path>)。
if path is None or *path == 0:
term.insert_ptr(&_cwd[0])
return
full: [256]char
_build_path(&full[0], 256, path)
dd: int = fs.opendir(&full[0])
if dd < 0:
term.insert("Error: dir not found")
return
fs.closedir(dd)
# 存入归一化后的路径:根目录的 ".." 保持为 "/",pwd 也保持干净。
_normalize_path(&_cwd[0], 64, &full[0])
_update_prompt()
def _cmd_ls():
dd: int = fs.opendir(&_cwd[0])
if dd < 0:
term.insert("Error: cannot open dir")
return
info: [320]char
string.memset(&info[0], 0, 320)
while True:
result: int = fs.readdir(dd, &info[0])
if result != 0: break
attr_val: uint8 = uint8(*(&info[4]))
name_off: int = 16
name_ptr: str = &info[name_off]
if *name_ptr == 0: break
if *name_ptr == 46:
if *(name_ptr + 1) == 0:
string.memset(&info[0], 0, 320)
continue
if *(name_ptr + 1) == 46 and *(name_ptr + 2) == 0:
string.memset(&info[0], 0, 320)
continue
if (attr_val & 0x10) != 0:
_print_dir_entry(name_ptr, 1)
else:
_print_dir_entry(name_ptr, 0)
string.memset(&info[0], 0, 320)
fs.closedir(dd)
def _cmd_cat(path: str):
full: [256]char
_build_path(&full[0], 256, path)
fd: int = fs.open(&full[0], fs.O_RDONLY)
if fd < 0:
term.insert("Error: file not found")
return
buf: [80]char
while True:
string.memset(&buf[0], 0, 80)
n: int = fs.read(fd, &buf[0], 78)
if n <= 0: break
if n > 78: n = 78
buf[n] = 0
term.insert_ptr(&buf[0])
fs.close(fd)
def _cmd_mkdir(path: str):
full: [256]char
_build_path(&full[0], 256, path)
result: int = fs.mkdir(&full[0])
if result < 0:
term.insert("Error: mkdir failed")
else:
term.insert("Directory created")
def _cmd_rm(path: str):
full: [256]char
_build_path(&full[0], 256, path)
result: int = fs.remove(&full[0])
if result < 0:
term.insert("Error: remove failed")
else:
term.insert("Removed")
def _cmd_touch(path: str):
full: [256]char
_build_path(&full[0], 256, path)
fd: int = fs.open(&full[0], fs.O_WRONLY | fs.O_CREAT)
if fd < 0:
term.insert("Error: touch failed")
return
fs.close(fd)
term.insert("File created")
def _cmd_sysinfo():
term.insert("ViperOS v1.0 (x86_64)")
term.insert("Framebuffer: 32bpp")
term.insert("Compiler: TransPyC")
def _cmd_hexdump(path: str):
full: [256]char
_build_path(&full[0], 256, path)
fd: int = fs.open(&full[0], fs.O_RDONLY)
if fd < 0:
term.insert("Error: file not found")
return
offset: int = 0
buf: [16]char
while True:
string.memset(&buf[0], 0, 16)
n: int = fs.read(fd, &buf[0], 16)
if n <= 0: break
if n > 16: n = 16
line: [80]char
string.memset(&line[0], 0, 80)
viperlib.snprintf(&line[0], 80, "%08x: ", offset)
pos: int = 10
j: int = 0
for j in range(16):
if j < n:
ub: uint32 = uint32(buf[j]) & 0xFF
hi: int = int(ub >> 4)
lo: int = int(ub & 0xF)
if hi < 10:
line[pos] = char(48 + hi)
else:
line[pos] = char(65 + hi - 10)
pos += 1
if lo < 10:
line[pos] = char(48 + lo)
else:
line[pos] = char(65 + lo - 10)
pos += 1
else:
line[pos] = 32
pos += 1
line[pos] = 32
pos += 1
line[pos] = 32
pos += 1
if j == 7:
line[pos] = 32
pos += 1
line[pos] = 124
pos += 1
for j in range(n):
b2: uint32 = uint32(buf[j]) & 0xFF
if b2 >= 32 and b2 < 127:
line[pos] = char(b2)
else:
line[pos] = 46
pos += 1
line[pos] = 0
term.insert_ptr(&line[0])
offset += n
fs.close(fd)
def _cmd_run(path: str, blocking: int):
full: [256]char
_build_path(&full[0], 256, path)
pid: int = process.spawn(&full[0], blocking)
if pid < 0:
term.insert("Error: cannot spawn")
elif blocking:
term.insert("Process exited")
else:
msg: [32]char
viperlib.snprintf(&msg[0], 32, "Spawned pid=%d", pid)
term.insert_ptr(&msg[0])
def _on_command() -> int:
_sdbg("[cmd] enter\n")
text: str = term.get()
_sdbg("[cmd] text=")
_sdbg(text)
_sdbg("\n")
if _startswith(text, "echo "):
term.insert_ptr(text + 5)
elif string.samestr(text, "echo"):
pass
elif string.samestr(text, "clear"):
term.delete(0, -1)
elif string.samestr(text, "help"):
term.insert("File: ls cd cat pwd mkdir rm touch")
term.insert("Sys: ver about sysinfo reboot shutdown")
term.insert("Util: echo clear help hexdump run runb")
elif string.samestr(text, "ver"):
term.insert("ViperOS Terminal v1.1")
elif string.samestr(text, "about"):
term.insert("ViperOS - A tiny OS in Viper lang")
elif string.samestr(text, "pwd"):
term.insert_ptr(&_cwd[0])
elif string.samestr(text, "ls"):
_cmd_ls()
elif string.samestr(text, "cd"):
_cmd_cd("")
elif _startswith(text, "cd "):
_cmd_cd(text + 3)
elif _startswith(text, "cat "):
_cmd_cat(text + 4)
elif _startswith(text, "mkdir "):
_cmd_mkdir(text + 6)
elif _startswith(text, "rm "):
_cmd_rm(text + 3)
elif _startswith(text, "touch "):
_cmd_touch(text + 6)
elif string.samestr(text, "sysinfo"):
_cmd_sysinfo()
elif string.samestr(text, "reboot"):
process.reboot()
elif string.samestr(text, "shutdown"):
process.shutdown()
elif _startswith(text, "hexdump "):
_cmd_hexdump(text + 8)
elif _startswith(text, "run "):
_cmd_run(text + 4, 0)
elif _startswith(text, "runb "):
_cmd_run(text + 5, 1)
else:
if text is not None and *text != 0:
term.insert("Unknown command. Type 'help'.")
return 0
_update_prompt()
term.on_command = _on_command
root.mainloop()
process.exit(0)
return 0