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 )。 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