import nativesockets import net import options import os import selectors import streams import tables import terminal when defined(posix): import posix import std/exitprocs import bindings/quickjs import buffer/container import css/sheet import config/config import display/pager import html/dom import html/htmlparser import io/lineedit import io/loader import io/request import io/term import io/window import ips/forkserver import ips/serialize import ips/serversocket import ips/socketstream import js/javascript import types/cookie import types/dispatcher import types/url type Client* = ref ClientObj ClientObj* = object alive: bool attrs: WindowAttributes dispatcher: Dispatcher feednext: bool s: string errormessage: string userstyle: CSSStylesheet loader: FileLoader console {.jsget.}: Console pager {.jsget.}: Pager line {.jsget.}: LineEdit config: Config jsrt: JSRuntime jsctx: JSContext timeoutid: int timeouts: Table[int, tuple[handler: (proc()), fdi: int]] intervals: Table[int, tuple[handler: (proc()), fdi: int, tofree: JSValue]] timeout_fdis: Table[int, int] interval_fdis: Table[int, int] fdmap: Table[int, Container] ssock: ServerSocket selector: Selector[Container] Console = ref object err: Stream pager: Pager container: Container prev: Container ibuf: string tty: File proc readChar(console: Console): char = if console.ibuf == "": return console.tty.readChar() result = console.ibuf[0] console.ibuf = console.ibuf.substr(1) proc `=destroy`(client: var ClientObj) = if client.jsctx != nil: free(client.jsctx) if client.jsrt != nil: free(client.jsrt) proc doRequest(client: Client, req: Request): Response {.jsfunc.} = client.loader.doRequest(req) proc interruptHandler(rt: JSRuntime, opaque: pointer): int {.cdecl.} = let client = cast[Client](opaque) try: let c = client.console.tty.readChar() if c == char(3): #C-c client.console.ibuf = "" return 1 else: client.console.ibuf &= c except IOError: discard return 0 proc evalJS(client: Client, src, filename: string): JSObject = unblockStdin(client.console.tty.getFileHandle()) result = client.jsctx.eval(src, filename, JS_EVAL_TYPE_GLOBAL) restoreStdin(client.console.tty.getFileHandle()) proc evalJSFree(client: Client, src, filename: string) = free(client.evalJS(src, filename)) proc command0(client: Client, src: string, filename = "", silence = false) = let ret = client.evalJS(src, filename) if ret.isException(): client.jsctx.writeException(client.console.err) else: if not silence: let str = ret.toString() if str.issome: client.console.err.write(str.get & '\n') client.console.err.flush() free(ret) proc command(client: Client, src: string) = restoreStdin(client.console.tty.getFileHandle()) client.command0(src) client.console.container.cursorLastLine() proc quit(client: Client, code = 0) {.jsfunc.} = if client.alive: client.alive = false client.pager.quit() quit(code) proc feedNext(client: Client) {.jsfunc.} = client.feednext = true proc alert(client: Client, msg: string) {.jsfunc.} = client.pager.alert(msg) proc input(client: Client) = restoreStdin(client.console.tty.getFileHandle()) while true: let c = client.console.readChar() client.s &= c if client.pager.lineedit.isSome: let edit = client.pager.lineedit.get client.line = edit if edit.escNext: edit.escNext = false if edit.write(client.s): client.s = "" else: let action = getLinedAction(client.config, client.s) if action == "": if edit.write(client.s): client.s = "" else: client.feedNext = true elif not client.feedNext: client.evalJSFree(action, "") if client.pager.lineedit.isNone: client.line = nil if not client.feedNext: client.pager.updateReadLine() else: let action = getNormalAction(client.config, client.s) client.evalJSFree(action, "") if not client.feedNext: client.pager.refreshStatusMsg() if not client.feedNext: client.s = "" break else: client.feedNext = false proc setTimeout[T: JSObject|string](client: Client, handler: T, timeout = 0): int {.jsfunc.} = let id = client.timeoutid inc client.timeoutid let fdi = client.selector.registerTimer(timeout, true, nil) client.timeout_fdis[fdi] = id when T is string: client.timeouts[id] = ((proc() = client.evalJSFree(handler, "setTimeout handler") ), fdi) else: let fun = JS_DupValue(handler.ctx, handler.val) client.timeouts[id] = ((proc() = let ret = JSObject(ctx: handler.ctx, val: fun).callFunction() if ret.isException(): ret.ctx.writeException(client.console.err) JS_FreeValue(ret.ctx, ret.val) JS_FreeValue(ret.ctx, fun) ), fdi) return id proc setInterval[T: JSObject|string](client: Client, handler: T, interval = 0): int {.jsfunc.} = let id = client.timeoutid inc client.timeoutid let fdi = client.selector.registerTimer(interval, false, nil) client.interval_fdis[fdi] = id when T is string: client.intervals[id] = ((proc() = client.evalJSFree(handler, "setInterval handler") ), fdi, JS_NULL) else: let fun = JS_DupValue(handler.ctx, handler.val) client.intervals[id] = ((proc() = let obj = JSObject(ctx: handler.ctx, val: fun) let ret = obj.callFunction() if ret.isException(): ret.ctx.writeException(client.console.err) JS_FreeValue(ret.ctx, ret.val) ), fdi, fun) return id proc clearTimeout(client: Client, id: int) {.jsfunc.} = if id in client.timeouts: let timeout = client.timeouts[id] client.selector.unregister(timeout.fdi) client.timeout_fdis.del(timeout.fdi) client.timeouts.del(id) proc clearInterval(client: Client, id: int) {.jsfunc.} = if id in client.intervals: let interval = client.intervals[id] client.selector.unregister(interval.fdi) JS_FreeValue(client.jsctx, interval.tofree) client.interval_fdis.del(interval.fdi) client.intervals.del(id) let SIGWINCH {.importc, header: "", nodecl.}: cint proc acceptBuffers(client: Client) = while client.pager.unreg.len > 0: let (pid, stream) = client.pager.unreg.pop() let fd = stream.source.getFd() if int(fd) in client.fdmap: client.selector.unregister(fd) client.fdmap.del(int(fd)) else: client.pager.procmap.del(pid) stream.close() var i = 0 while i < client.pager.procmap.len: let stream = client.ssock.acceptSocketStream() var pid: Pid stream.sread(pid) if pid in client.pager.procmap: let container = client.pager.procmap[pid] client.pager.procmap.del(pid) container.setStream(stream) let fd = stream.source.getFd() client.fdmap[int(fd)] = container client.selector.registerHandle(fd, {Read}, nil) inc i else: #TODO print an error? stream.close() proc log(console: Console, ss: varargs[string]) {.jsfunc.} = for i in 0..