use encoding::utf8; use fmt; use types::c; // Type for key values, see [[termbox2::KEY]]. export type key = u16; // ASCII constants, see [[termbox2::key_event]].key. export def KEY = struct { CTRL_TILDE: key = 0x00, CTRL_2: key = 0x00, // clash with CTRL_TILDE CTRL_A: key = 0x01, CTRL_B: key = 0x02, CTRL_C: key = 0x03, CTRL_D: key = 0x04, CTRL_E: key = 0x05, CTRL_F: key = 0x06, CTRL_G: key = 0x07, BACKSPACE: key = 0x08, CTRL_H: key = 0x08, // clash with CTRL_BACKSPACE TAB: key = 0x09, CTRL_I: key = 0x09, // clash with TAB CTRL_J: key = 0x0a, CTRL_K: key = 0x0b, CTRL_L: key = 0x0c, ENTER: key = 0x0d, CTRL_M: key = 0x0d, // clash with ENTER CTRL_N: key = 0x0e, CTRL_O: key = 0x0f, CTRL_P: key = 0x10, CTRL_Q: key = 0x11, CTRL_R: key = 0x12, CTRL_S: key = 0x13, CTRL_T: key = 0x14, CTRL_U: key = 0x15, CTRL_V: key = 0x16, CTRL_W: key = 0x17, CTRL_X: key = 0x18, CTRL_Y: key = 0x19, CTRL_Z: key = 0x1a, ESC: key = 0x1b, CTRL_LSQ_BRACKET: key = 0x1b, // clash with 'ESC' CTRL_3: key = 0x1b, // clash with 'ESC' CTRL_4: key = 0x1c, CTRL_BACKSLASH: key = 0x1c, // clash with 'CTRL_4' CTRL_5: key = 0x1d, CTRL_RSQ_BRACKET: key = 0x1d, // clash with 'CTRL_5' CTRL_6: key = 0x1e, CTRL_7: key = 0x1f, CTRL_SLASH: key = 0x1f, // clash with 'CTRL_7' CTRL_UNDERSCORE: key = 0x1f, // clash with 'CTRL_7' SPACE: key = 0x20, BACKSPACE2: key = 0x7f, CTRL_8: key = 0x7f, // clash with 'BACKSPACE2' F1: key = (0xffff - 0), F2: key = (0xffff - 1), F3: key = (0xffff - 2), F4: key = (0xffff - 3), F5: key = (0xffff - 4), F6: key = (0xffff - 5), F7: key = (0xffff - 6), F8: key = (0xffff - 7), F9: key = (0xffff - 8), F10: key = (0xffff - 9), F11: key = (0xffff - 10), F12: key = (0xffff - 11), INSERT: key = (0xffff - 12), DELETE: key = (0xffff - 13), HOME: key = (0xffff - 14), END: key = (0xffff - 15), PGUP: key = (0xffff - 16), PGDN: key = (0xffff - 17), ARROW_UP: key = (0xffff - 18), ARROW_DOWN: key = (0xffff - 19), ARROW_LEFT: key = (0xffff - 20), ARROW_RIGHT: key = (0xffff - 21), BACK_TAB: key = (0xffff - 22), MOUSE_LEFT: key = (0xffff - 23), MOUSE_RIGHT: key = (0xffff - 24), MOUSE_MIDDLE: key = (0xffff - 25), MOUSE_RELEASE: key = (0xffff - 26), MOUSE_WHEEL_UP: key = (0xffff - 27), MOUSE_WHEEL_DOWN: key = (0xffff - 28), }; // Type for key modifiers, see [[termbox2::MOD]]. export type mod = u8; // Key modifiers (bitwise), see [[termbox2::event]].mod. export def MOD = struct { ALT: mod = 1, CTRL: mod = 2, SHIFT: mod = 4, MOTION: mod = 8, }; // Type for input modes, see [[termbox2::INPUT]]. export type input_mode = int; // Input modes (bitwise), see [[termbox2::set_input_mode]]. export def INPUT = struct { CURRENT: input_mode = 0, ESC: input_mode = 1, ALT: input_mode = 2, MOUSE: input_mode = 4, }; // Type for output modes, see [[termbox2::OUTPUT]]. export type output_mode = int; // Output modes, see [[termbox2::set_output_mode]]. export def OUTPUT = struct { CURRENT: output_mode = 0, NORMAL: output_mode = 1, _256: output_mode = 2, _216: output_mode = 3, GRAYSCALE: output_mode = 4, TRUECOLOR: output_mode = 5, // only available if OPT_ATTR_W >= 32 }; // Type for error return values, see [[termbox2::ERR]]. export type err = !int; // Common error return values. // // Library behavior is undefined after receiving ERR.MEM. // Callers may attempt reinitializing by freeing memory, invoking // [[termbox2::shutdown]], then [[termbox2::init]]. export def ERR = struct { ERR: err = -1, NEED_MORE: err = -2, INIT_ALREADY: err = -3, INIT_OPEN: err = -4, MEM: err = -5, NO_EVENT: err = -6, NO_TERM: err = -7, NOT_INIT: err = -8, OUT_OF_BOUNDS: err = -9, READ: err = -10, RESIZE_IOCTL: err = -11, RESIZE_PIPE: err = -12, RESIZE_SIGACTION: err = -13, POLL: err = -14, SELECT: err = -14, TCGETATTR: err = -15, TCSETATTR: err = -16, UNSUPPORTED_TERM: err = -17, RESIZE_WRITE: err = -18, RESIZE_POLL: err = -19, RESIZE_SELECT: err = -19, RESIZE_READ: err = -20, RESIZE_SSCANF: err = -21, CAP_COLLISION: err = -22, HARE_BUILD: err = -9990, // Additional error type HARE_INVALID_UTF8: err = -9991, // Additional error type HARE_OTHER: err = -9992, // Additional error type }; // An incoming key press event. Note that there is overlap between // MOD.CTRL and KEY.CTRL_* keys. MOD.CTRL and MOD.SHIFT are only set // as modifiers to KEY.ARROW_*. export type key_event = struct { mod: mod, // Modifier key_or_ch: (key | rune), // A [[termbox2::KEY]] or a Unicode code point }; // An incoming resize event. export type resize_event = struct { w: i32, // resize width h: i32, // resize height }; // An incoming mouse event. export type mouse_event = struct { key: key, // Mouse button, e.g. termbox2::KEY.MOUSE_LEFT x: i32, y: i32, }; // The C data structure for an event, for compatibility. type tb_event = struct { t: u8, mod: u8, key: u16, ch: u32, w: i32, h: i32, x: i32, y: i32, }; def TB_EVENT_KEY: u8 = 1; def TB_EVENT_RESIZE: u8 = 2; def TB_EVENT_MOUSE: u8 = 3; fn tb_event_to_event(e: tb_event) (event | err) = switch (e.t) { case TB_EVENT_KEY => yield key_event { mod = e.mod, key_or_ch = if (e.key != 0) e.key: key else e.ch: rune, }; case TB_EVENT_RESIZE => yield resize_event { w = e.w, h = e.h, }; case TB_EVENT_MOUSE => yield mouse_event { key = e.key, x = e.x, y = e.y, }; case => yield ERR.HARE_OTHER; }; // Any incoming event from the tty. export type event = (key_event | resize_event | mouse_event); fn build_opts_match() bool = (has_egc() == OPT_EGC) && (attr_width() == OPT_ATTR_W) && (has_truecolor() == (OPT_ATTR_W == 32 || OPT_ATTR_W == 64)); fn void_or_err(r: int) (void | err) = if (r < 0) r: err; fn int_or_err(r: int) (int | err) = if (r < 0) r: err else r; fn size_or_err(r: int) (size | err) = if (r < 0) r: err else r: size; fn str_or_err(cstr: *c::char) (str | err) = match(c::tostr(cstr)) { case utf8::invalid => yield ERR.HARE_INVALID_UTF8; case let s: str => yield s; }; // Initialize the termbox library. This function should be called before // any other functions. After successful initialization, the library must // be finalized using [[termbox2::shutdown]]. export fn init() (void | err) = if (!build_opts_match()) ERR.HARE_BUILD else void_or_err(tb_init()); @symbol("tb_init") fn tb_init() int; // Same as [[termbox2::init]], but allows to specify a file path in place // of the default /dev/tty. export fn init_file(path: str) (void | err) = { let c_path = c::fromstr(path)!; defer free(c_path); return if (!build_opts_match()) ERR.HARE_BUILD else void_or_err(tb_init_file(c_path)); }; @symbol("tb_init_file") fn tb_init_file(cstr: *c::char) int; // Same as [[termbox2::init]], but allows to specify an output // file descriptor. export fn init_fd(ttyfd: int) (void | err) = if (!build_opts_match()) ERR.HARE_BUILD else void_or_err(tb_init_fd(ttyfd)); @symbol("tb_init_fd") fn tb_init_fd(fd: int) int; // Same as [[termbox2::init]], but allows to specify input and output // file descriptors. export fn init_rwfd(rfd: int, wfd: int) (void | err) = if (!build_opts_match()) ERR.HARE_BUILD else void_or_err(tb_init_rwfd(rfd, wfd)); @symbol("tb_init_rwfd") fn tb_init_rwfd(rfd: int, wfd: int) int; // Finalizes the library. export fn shutdown() (void | err) = void_or_err(tb_shutdown()); @symbol("tb_shutdown") fn tb_shutdown() int; // Return the width of the internal back buffer (which is the same as the // terminal's window width in columns). The internal buffer can be resized // after [[termbox2::clear]] or [[termbox2::present]] calls. Returns ERR.ERR // when called before [[termbox2::init]] or after [[termbox2::shutdown]]. export fn width() (size | err) = size_or_err(tb_width()); @symbol("tb_width") fn tb_width() int; // Return the height of the internal back buffer (which is the same as the // terminal's window width in rows). The internal buffer can be resized // after [[termbox2::clear]] or [[termbox2::present]] calls. Returns ERR.ERR // when called before [[termbox2::init]] or after [[termbox2::shutdown]]. export fn height() (size | err) = size_or_err(tb_height()); @symbol("tb_height") fn tb_height() int; // Clear the internal back buffer using [[termbox2::ATTRIBUTE]].DEFAULT. export fn clear() (void | err) = void_or_err(tb_clear()); @symbol("tb_clear") fn tb_clear() int; // Clear the internal back buffer using the specified attributes. export fn set_clear_attrs(fg: uattr, bg: uattr) (void | err) = void_or_err(tb_set_clear_attrs(fg, bg)); @symbol("tb_set_clear_attrs") fn tb_set_clear_attrs(fg: uattr, bg: uattr) int; // Synchronize the internal back buffer with the terminal by // writing to tty. export fn present() (void | err) = void_or_err(tb_present()); @symbol("tb_present") fn tb_present() int; // Clear the internal front buffer effectively forcing a complete re-render // of the back buffer to the tty. It is not necessary to call this under // normal circumstances. export fn invalidate() (void | err) = void_or_err(tb_invalidate()); @symbol("tb_invalidate") fn tb_invalidate() int; // Set the position of the cursor. Upper-left cell is (0, 0). export fn set_cursor(cx: int, cy: int) (void | err) = void_or_err(tb_set_cursor(cx, cy)); @symbol("tb_set_cursor") fn tb_set_cursor(x: int, y: int) int; // Hide the cursor. export fn hide_cursor() (void | err) = void_or_err(tb_hide_cursor()); @symbol("tb_hide_cursor") fn tb_hide_cursor() int; // Set cell contents in the internal back buffer at the specified position. // Non-printable runes are replaced with U+FFFD at render time. export fn set_cell( x: int, y: int, ch: rune, fg: uattr, bg: uattr, ) (void | err) = void_or_err(tb_set_cell(x, y, ch: u32, fg, bg)); @symbol("tb_set_cell") fn tb_set_cell( x: int, y: int, ch: u32, fg: uattr, bg: uattr, ) int; // Set extended cell contents. Only available when OPT_ATTR_W >= 32. // See also [[termbox2::set_cell]] and [[termbox2::cell]]. export fn set_cell_ex( x: int, y: int, ch: []rune, fg: uattr, bg: uattr, ) (void | err) = void_or_err(tb_set_cell_ex(x, y, &ch[0]: *u32, len(ch), fg, bg)); @symbol("tb_set_cell_ex") fn tb_set_cell_ex( x: int, y: int, ch: *u32, n: size, fg: uattr, bg: uattr, ) int; // Add one character to the cell's ech. Only available when OPT_ATTR_W >= 32. // See also [[termbox2::set_cell_ex]] and [[termbox2::cell]]. export fn extend_cell(x: int, y: int, ch: rune) (void | err) = void_or_err(tb_extend_cell(x, y, ch: u32)); @symbol("tb_extend_cell") fn tb_extend_cell(x: int, y: int, ch: u32) int; // Set the input mode, see [[termbox2::INPUT]]. // // Termbox has two input modes: // - INPUT.ESC When escape (\x1b) is in the buffer and there's no match // for an escape sequence, a key event for [[termbox2::KEY]].ESC is returned. // - INPUT.ALT When escape (\x1b) is in the buffer and there's no match // for an an escape sequence, the next keyboard event is returned with a // [[termbox2::MOD]].ALT modifier. // // You can also apply termbox2::INPUT.MOUSE via bitwise OR operation to either // of the modes (e.g., INPUT.ALT | INPUT.MOUSE) to receive // [[termbox2::mouse_event]] events. If none of the main two modes were // set, but the mouse mode was, INPUT.ESC is used. If for some reason you've // decided to use INPUT.ESC | INPUT.ALT, it will behave as if only INPUT.ESC // was selected. // // If mode is INPUT.CURRENT, return the current input mode. // // The default input mode is INPUT.ESC. export fn set_input_mode(mode: input_mode) (int | err) = int_or_err(tb_set_input_mode(mode: int)); @symbol("tb_set_input_mode") fn tb_set_input_mode(mode: int) int; // Set the output mode, see [[termbox2::OUTPUT]]. // // Termbox has multiple output modes: // // 1. OUTPUT.NORMAL => [0..8] // // This mode provides 8 different colors (see [[termbox2::COLOR]]), plus // COLOR.DEFAULT which skips sending a color code (i.e., uses the terminal's // default color). Colors may be bitwise OR'd with style attributes, see // [[termbox2::ATTRIBUTE]]. Extra attributes are available if OPT_ATTR_W == 64. // // Some notes: ATTRIBUTE.REVERSE and ATTRIBUTE.BRIGHT can be applied as either // fg or bg attributes for the same effect. The rest of the attributes apply to // fg only and are ignored as bg attributes. // // Example usage: // // let fg = termbox2::COLOR.BLACK | termbox2::ATTRIBUTE.BOLD; // let bg = termbox2::COLOR.RED; // termbox2::set_cell(x, y, '@', fg, bg); // // 2. OUTPUT._256 => [0..255] + ATTRIBUTE.HI_BLACK // // In this mode you get 256 distinct colors (plus default): // - 0x00 (1): ATTRIBUTE.DEFAULT // - ATTRIBUTE.HI_BLACK (1): COLOR.BLACK in OUTPUT.NORMAL // - 0x01..0x07 (7): the next 7 colors as in OUTPUT.NORMAL // - 0x08..0x0f (8): bright versions of the above // - 0x10..0xe7 (216): 216 different colors // - 0xe8..0xff (24): 24 different shades of gray // // All style attributes except ATTRIBUTE.BRIGHT may be bitwise OR'd as in // OUTPUT.NORMAL. // // Note COLOR.BLACK must be used for black, as 0x00 represents default. // // 3. OUTPUT._216 => [0..216] // // This mode supports the 216-color range of OUTPUT._256 only, but you // don't need to provide an offset: // - 0x00 (1): COLOR.DEFAULT // - 0x01..0xd8 (216): 216 different colors // // 4. OUTPUT.GRAYSCALE => [0..24] // // This mode supports the 24-color range of OUTPUT._256 only, but you // don't need to provide an offset: // - 0x00 (1): COLOR.DEFAULT // - 0x01..0x18 (24): 24 different shades of gray // // 5. OUTPUT.TRUECOLOR => [0x000000..0xffffff] + ATTRIBUTE.HI_BLACK // // This mode provides 24-bit color on supported terminals. // The format is 0xRRGGBB. // // All style attributes except ATTRIBUTE.BRIGHT may be bitwise OR'd as in // OUTPUT.NORMAL. // // Note ATTRIBUTE.HI_BLACK must be used for black, as 0x000000 represents // default. // // To use the terminal default color (i.e., to not send an escape code), pass // COLOR.DEFAULT. For convenience, the value 0 is interpreted as COLOR.DEFAULT // in all modes. // // Note, cell attributes persist after switching output modes. Any translation // between, for example, OUTPUT.NORMAL's COLOR.RED and OUTPUT.TRUECOLOR's // 0xff0000 must be performed by the caller. Also note that cells previously // rendered in one mode may persist unchanged until the front buffer is cleared // (such as after a resize event) at which point it will be re-interpreted and // flushed according to the current mode. Callers may invoke // [[termbox2::invalidate]] if it is desirable to immediately re-interpret // and flush the entire screen according to the current mode. // // Note, not all terminals support all output modes, especially beyond // OUTPUT.NORMAL. There is also no very reliable way to determine color // support dynamically. If portability is desired, callers are recommended to // use OUTPUT.NORMAL or make output mode end-user configurable. The same // advice applies to style attributes. // // If mode is OUTPUT.CURRENT, return the current output mode. // // The default output mode is OUTPUT.NORMAL. export fn set_output_mode(mode: output_mode) (int | err) = int_or_err(tb_set_output_mode(mode: int)); @symbol("tb_set_output_mode") fn tb_set_output_mode(mode: int) int; // Wait for an event up to timeout_ms milliseconds and return it. If no event // is available within the timeout period, ERR.NO_EVENT is returned. On a resize // event, the underlying select(2) call may be interrupted, yielding a return // code of ERR.POLL. export fn peek_event(timeout_ms: size) (event | err) = { let e = tb_event { t = 0, mod = 0, key = 0, ch = 0, w = 0, h = 0, x = 0, y = 0, }; let r = tb_peek_event(&e, timeout_ms: int); return if (r < 0) r: err else tb_event_to_event(e); }; @symbol("tb_peek_event") fn tb_peek_event(ev: *tb_event, t: int) int; // Same as [[termbox2::peek_event]] except no timeout. export fn poll_event() (event | err) = { let e = tb_event { t = 0, mod = 0, key = 0, ch = 0, w = 0, h = 0, x = 0, y = 0, }; let r = tb_poll_event(&e); return if (r < 0) r: err else tb_event_to_event(e); }; @symbol("tb_poll_event") fn tb_poll_event(ev: *tb_event) int; // Internal termbox fds that can be used with poll(2), select(2), etc. // externally. Callers must invoke [[termbox2::poll_event]] or // [[termbox2::peek_event]] if fds become readable. export fn get_fds() ((int, int) | err) = { let ttyfd: int = 0; let resizefd: int = 0; let r = tb_get_fds(&ttyfd, &resizefd); return if (r < 0) r: err else (ttyfd, resizefd); }; @symbol("tb_get_fds") fn tb_get_fds(ttyfd: *int, resizefd: *int) int; // Print function. On success, it returns the width of the printed string. // Strings are interpreted as UTF-8. // // No attempt is made to do proper grapheme cluster parsing. With // [[termbox2::OPT_EGC]] enabled, as a very coarse approximation, codepoints // of width 0 are tacked on to the previous codepoint via // [[termbox2::extend_cell]]. // // Non-printable characters (iswprint(3)) and truncated UTF-8 byte sequences // are replaced with U+FFFD. // // Newlines (\n) are supported with the caveat that the return value will be // the width of the string as if it were on a single line. // // If the starting coordinate is out of bounds, ERR.OUT_OF_BOUNDS is returned. // If the starting coordinates is in bounds, but goes out of bounds, then the // out-of-bounds portions of the string are ignored. // // For finer control, use [[termbox2::set_cell]]. export fn print( x: int, y: int, fg: uattr, bg: uattr, fmt_str: str, args: fmt::field... ) (size | err) = { let s = fmt::asprintf(fmt_str, args...)!; defer free(s); let cstr = c::fromstr(s)!; defer free(cstr); let outw: size = 0; let e = tb_print_ex(x, y, fg, bg, &outw, cstr); return if (e < 0) e: err else outw; }; @symbol("tb_print_ex") fn tb_print_ex( x: int, y: int, fg: uattr, bg: uattr, out_w: *size, cstr: const *c::char, ) int; // Send raw bytes to the terminal. export fn send(buf: []u8) (void | err) = void_or_err(tb_send(&buf[0]: *c::char, len(buf))); @symbol("tb_send") fn tb_send(buf: const *c::char, nbuf: size) int; // Returns a string explaining the given error code. The caller must free // the return value. export fn strerror(e: err) str = switch (e) { case ERR.HARE_BUILD => yield fmt::asprintf( "Cannot initialize Termbox because of a mismatch " "between the C and Hare build options.\n" "Hare OPT_EGC is {} and C TB_OPT_EGC is {}\n" "Hare OPT_ATTR_W {} and C TB_OPT_ATTR_W is {}", OPT_EGC, has_egc(), OPT_ATTR_W, attr_width() )!; case ERR.HARE_INVALID_UTF8 => yield fmt::asprintf("Invalid UTF8 sequence recieved from tb")!; case ERR.HARE_OTHER => yield fmt::asprintf("Unknown error in the Hare bindings")!; case => yield match (str_or_err(tb_strerror(e: int))) { case err => yield fmt::asprintf( "Unexpected error while interpreting previous error" )!; case let estr: str => yield estr; }; }; @symbol("tb_strerror") fn tb_strerror(e: int) const *c::char; // Returns true if the the C termbox2 library was compiled with truecolor // support (i.e., if [[termbox2::OPT_ATTR_W]] is 32 or 64). export fn has_truecolor() bool = tb_has_truecolor() != 0; @symbol("tb_has_truecolor") fn tb_has_truecolor() int; // Returns true if the the C termbox2 library was compiled with EGC // support (i.e., if [[termbox2::OPT_EGC]] is true). export fn has_egc() bool = tb_has_egc() != 0; @symbol("tb_has_egc") fn tb_has_egc() int; // Returns the value of the TB_OPT_ATTR_W compile time option of the // C termbox2 library. export fn attr_width() size = tb_attr_width(): size; @symbol("tb_attr_width") fn tb_attr_width() int; // Returns the string version of the C termbox2 library. export fn version() (str | err) = str_or_err(tb_version()); @symbol("tb_version") fn tb_version() const *c::char;