summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorcoast <coasteen@proton.me>2026-10-10 22:13:10 +0330
committercoast <coasteen@proton.me>2026-10-10 22:13:10 +0330
commitdcb8bef3ed7b3adcdb54fcd0cd5a4065091019a7 (patch)
treec39a3d069f83ec258a956fc902e6149915cd4cb2 /src
downloadzigmatrix-dcb8bef3ed7b3adcdb54fcd0cd5a4065091019a7.tar.gz
zigmatrix-dcb8bef3ed7b3adcdb54fcd0cd5a4065091019a7.tar.bz2
zigmatrix-dcb8bef3ed7b3adcdb54fcd0cd5a4065091019a7.tar.xz
zigmatrix-dcb8bef3ed7b3adcdb54fcd0cd5a4065091019a7.tar.zst
init
Diffstat (limited to 'src')
-rw-r--r--src/main.zig632
-rw-r--r--src/term.c120
2 files changed, 752 insertions, 0 deletions
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..7dcc923
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,632 @@
+extern fn zm_enter() c_int;
+extern fn zm_leave() void;
+extern fn zm_install(lock: c_int) void;
+extern fn zm_stopped() c_int;
+extern fn zm_size(rows: *c_int, cols: *c_int) c_int;
+extern fn zm_wait(ms: c_int) c_int;
+extern fn zm_read(buf: [*]u8, n: usize) isize;
+extern fn zm_write(fd: c_int, buf: [*]const u8, n: usize) void;
+extern fn zm_seed() u64;
+extern fn calloc(n: usize, size: usize) ?*anyopaque;
+extern fn free(p: ?*anyopaque) void;
+extern fn exit(code: c_int) noreturn;
+
+const version_text = "zigmatrix version 0.1-alpine\n";
+
+const usage_text =
+ \\usage: zigmatrix -[abBcfhlLnorsmVxk] [-u delay] [-C color] [-t tty] [-M message]
+;
+
+const color_help =
+ "Invalid color selection\n" ++
+ "Valid colors are green, red, blue, white, yellow, cyan, magenta and black.\n";
+
+const clear_seq = "\x1b[0m\x1b[2J";
+
+const Opts = struct {
+ asynch: bool = false,
+ bold: u2 = 0,
+ oldstyle: bool = false,
+ screensaver: bool = false,
+ lock: bool = false,
+ rainbow: bool = false,
+ lambda: bool = false,
+ changes: bool = false,
+ japanese: bool = false,
+ console: bool = false,
+ update: u32 = 4,
+ color: u8 = 2,
+};
+
+const Charset = struct { min: i32, num: i32 };
+
+var opt: Opts = .{};
+var charset: Charset = .{ .min = 33, .num = 93 };
+var paused = false;
+var raw_active = false;
+var msg_cps: []const u32 = &.{};
+var g_argc: usize = 0;
+var g_argv: [*][*:0]const u8 = undefined;
+
+const BOLD: u8 = 8;
+const rainbow_colors = [6]u8{ 1, 3, 2, 6, 4, 5 };
+
+fn writeAll(fd: c_int, bytes: []const u8) void {
+ zm_write(fd, bytes.ptr, bytes.len);
+}
+
+fn alloc(comptime T: type, n: usize) error{OutOfMemory}![]T {
+ const raw_ptr = calloc(n, @sizeOf(T)) orelse return error.OutOfMemory;
+ const typed_ptr: [*]T = @ptrCast(@alignCast(raw_ptr));
+ return typed_ptr[0..n];
+}
+
+fn release(s: anytype) void {
+ free(@ptrCast(s.ptr));
+}
+
+var rng_state: u64 = 0x2545F4914F6CDD1D;
+
+fn rnd(n: i32) i32 {
+ rng_state +%= 0x9e3779b97f4a7c15;
+ var z_val = rng_state;
+ z_val = (z_val ^ (z_val >> 30)) *% 0xbf58476d1ce4e5b9;
+ z_val = (z_val ^ (z_val >> 27)) *% 0x94d049bb133111eb;
+ z_val ^= z_val >> 31;
+ return @intCast((z_val >> 33) % @as(u64, @intCast(n)));
+}
+
+fn finish() noreturn {
+ if (raw_active) {
+ writeAll(1, "\x1b[0m\x1b[?25h\x1b[?1049l");
+ zm_leave();
+ }
+ exit(0);
+}
+
+const Size = struct { lines: usize, cols: usize };
+
+fn termSize() Size {
+ var rows: c_int = 0;
+ var cols: c_int = 0;
+ if (zm_size(&rows, &cols) != 0 or rows <= 0 or cols <= 0) {
+ return .{ .lines = 24, .cols = 80 };
+ }
+ return .{ .lines = @intCast(rows), .cols = @intCast(cols) };
+}
+
+fn putNum(out: []u8, op: usize, n: usize) usize {
+ var tmp: [20]u8 = undefined;
+ var k: usize = 0;
+ var x = n;
+ while (true) {
+ tmp[k] = @intCast('0' + (x % 10));
+ k += 1;
+ x /= 10;
+ if (x == 0) break;
+ }
+ var o = op;
+ while (k > 0) {
+ k -= 1;
+ out[o] = tmp[k];
+ o += 1;
+ }
+ return o;
+}
+
+fn moveTo(out: []u8, op: usize, r: usize, c: usize) usize {
+ var o = op;
+ out[o] = 0x1b;
+ out[o + 1] = '[';
+ o += 2;
+ o = putNum(out, o, r + 1);
+ out[o] = ';';
+ o += 1;
+ o = putNum(out, o, c + 1);
+ out[o] = 'H';
+ return o + 1;
+}
+
+fn setAttr(out: []u8, op: usize, at: u8) usize {
+ const head: []const u8 = if ((at & BOLD) != 0) "\x1b[0;1;3" else "\x1b[0;3";
+ var o = op;
+ @memcpy(out[o..][0..head.len], head);
+ o += head.len;
+ out[o] = @intCast('0' + (at & 7));
+ o += 1;
+ const tail = "m";
+ @memcpy(out[o..][0..tail.len], tail);
+ return o + tail.len;
+}
+
+fn putUtf8(out: []u8, op: usize, cp: u32) usize {
+ if (cp < 0x80) {
+ out[op] = @intCast(cp);
+ return op + 1;
+ } else if (cp < 0x800) {
+ out[op] = @intCast(0xC0 | (cp >> 6));
+ out[op + 1] = @intCast(0x80 | (cp & 0x3F));
+ return op + 2;
+ } else if (cp < 0x10000) {
+ out[op] = @intCast(0xE0 | (cp >> 12));
+ out[op + 1] = @intCast(0x80 | ((cp >> 6) & 0x3F));
+ out[op + 2] = @intCast(0x80 | (cp & 0x3F));
+ return op + 3;
+ }
+ out[op] = @intCast(0xF0 | (cp >> 18));
+ out[op + 1] = @intCast(0x80 | ((cp >> 12) & 0x3F));
+ out[op + 2] = @intCast(0x80 | ((cp >> 6) & 0x3F));
+ out[op + 3] = @intCast(0x80 | (cp & 0x3F));
+ return op + 4;
+}
+
+fn glyph(v: i32) u32 {
+ return if (opt.lambda) 0x3bb else @intCast(v);
+}
+
+const Sim = struct {
+ lines: usize,
+ cols: usize,
+ val: []i32,
+ head: []bool,
+ length: []i32,
+ spaces: []i32,
+ updates: []i32,
+ nxt_cp: []u32,
+ nxt_at: []u8,
+ scr_cp: []u32,
+ scr_at: []u8,
+ out: []u8,
+
+ fn init(lines: usize, cols: usize) !Sim {
+ const cells = lines * cols;
+ return .{
+ .lines = lines,
+ .cols = cols,
+ .val = try alloc(i32, (lines + 1) * cols),
+ .head = try alloc(bool, (lines + 1) * cols),
+ .length = try alloc(i32, cols),
+ .spaces = try alloc(i32, cols),
+ .updates = try alloc(i32, cols),
+ .nxt_cp = try alloc(u32, cells),
+ .nxt_at = try alloc(u8, cells),
+ .scr_cp = try alloc(u32, cells),
+ .scr_at = try alloc(u8, cells),
+ .out = try alloc(u8, cells * 32 + 64),
+ };
+ }
+
+ fn deinit(s: *Sim) void {
+ release(s.val);
+ release(s.head);
+ release(s.length);
+ release(s.spaces);
+ release(s.updates);
+ release(s.nxt_cp);
+ release(s.nxt_at);
+ release(s.scr_cp);
+ release(s.scr_at);
+ release(s.out);
+ }
+
+ fn reset(s: *Sim) void {
+ @memset(s.val, -1);
+ @memset(s.head, false);
+ @memset(s.length, 0);
+ @memset(s.spaces, 0);
+ @memset(s.updates, 0);
+ @memset(s.scr_cp, ' ');
+ @memset(s.scr_at, 0);
+ const lines_i: i32 = @intCast(s.lines);
+ var j: usize = 0;
+ while (j < s.cols) : (j += 2) {
+ s.spaces[j] = rnd(lines_i) + 1;
+ s.length[j] = rnd(@max(lines_i - 3, 1)) + 3;
+ s.val[s.cols + j] = ' ';
+ s.updates[j] = rnd(3) + 1;
+ }
+ }
+
+ fn stepColumn(s: *Sim, j: usize) void {
+ const cols = s.cols;
+ const last = s.lines;
+ const lines_i: i32 = @intCast(s.lines);
+ const v = s.val;
+ const hd = s.head;
+ const cs = charset;
+
+ if (opt.oldstyle) {
+ var i: usize = last;
+ while (i > 0) : (i -= 1) {
+ v[i * cols + j] = v[(i - 1) * cols + j];
+ }
+ const random = rnd(cs.num + 8) + cs.min;
+ const below = v[cols + j];
+ if (below == ' ' or below == -1) {
+ if (s.spaces[j] > 0) {
+ v[j] = ' ';
+ s.spaces[j] -= 1;
+ } else {
+ v[j] = rnd(cs.num) + cs.min;
+ s.spaces[j] = rnd(lines_i) + 1;
+ }
+ } else if (random > cs.num and below != 1) {
+ v[j] = ' ';
+ hd[j] = false;
+ } else {
+ v[j] = rnd(cs.num) + cs.min;
+ }
+ return;
+ }
+
+ if (v[j] == -1 and v[cols + j] == ' ') {
+ if (s.spaces[j] > 0) {
+ s.spaces[j] -= 1;
+ } else {
+ s.length[j] = rnd(@max(lines_i - 3, 1)) + 3;
+ v[j] = rnd(cs.num) + cs.min;
+ hd[j] = true;
+ s.spaces[j] = rnd(lines_i) + 1;
+ }
+ }
+
+ var i: usize = 0;
+ var first_done = false;
+ while (i <= last) {
+ while (i <= last and (v[i * cols + j] == ' ' or v[i * cols + j] == -1)) {
+ i += 1;
+ }
+ if (i > last) break;
+
+ const z = i;
+ var y: i32 = 0;
+ while (i <= last and v[i * cols + j] != ' ' and v[i * cols + j] != -1) {
+ hd[i * cols + j] = false;
+ if (opt.changes and rnd(8) == 0) {
+ v[i * cols + j] = rnd(cs.num) + cs.min;
+ }
+ i += 1;
+ y += 1;
+ }
+ if (i > last) {
+ v[z * cols + j] = ' ';
+ continue;
+ }
+
+ v[i * cols + j] = rnd(cs.num) + cs.min;
+ hd[i * cols + j] = true;
+
+ if (y > s.length[j] or first_done) {
+ v[z * cols + j] = ' ';
+ v[j] = -1;
+ }
+ first_done = true;
+ i += 1;
+ }
+ }
+
+ fn render(s: *Sim) void {
+ const cols = s.cols;
+ const y_offset: usize = if (opt.oldstyle) 0 else 1;
+ var r: usize = 0;
+ while (r < s.lines) : (r += 1) {
+ const base = (r + y_offset) * cols;
+ const nb = r * cols;
+ var c: usize = 0;
+ while (c < cols) : (c += 1) {
+ var cp: u32 = ' ';
+ var at: u8 = 0;
+ if ((c & 1) == 0) {
+ const v = s.val[base + c];
+ if (v != -1 and v != ' ') {
+ cp = glyph(v);
+ if (s.head[base + c] and !opt.rainbow) {
+ at = 7 | (if (opt.bold != 0) BOLD else 0);
+ } else {
+ const col: u8 = if (opt.rainbow) rainbow_colors[@intCast(rnd(6))] else opt.color;
+ const b = opt.bold == 2 or (opt.bold == 1 and (v & 1) == 0);
+ at = col | (if (b) BOLD else 0);
+ }
+ }
+ }
+ s.nxt_cp[nb + c] = cp;
+ s.nxt_at[nb + c] = at;
+ }
+ }
+ }
+
+ fn put(s: *Sim, r: isize, c: isize, cp: u32, at: u8) void {
+ if (r < 0 or c < 0) return;
+ const ru: usize = @intCast(r);
+ const cu: usize = @intCast(c);
+ if (ru >= s.lines or cu >= s.cols) return;
+ const idx = ru * s.cols + cu;
+ s.nxt_cp[idx] = cp;
+ s.nxt_at[idx] = at;
+ }
+
+ fn overlay(s: *Sim) void {
+ const ml: isize = @intCast(msg_cps.len);
+ const my: isize = @intCast(s.lines / 2);
+ const mx: isize = @as(isize, @intCast(s.cols / 2)) - @divTrunc(ml, 2);
+ var rr: isize = my - 1;
+ while (rr <= my + 1) : (rr += 1) {
+ var cc: isize = mx - 2;
+ while (cc < mx + ml + 2) : (cc += 1) {
+ const inside = rr == my and cc >= mx and cc < mx + ml;
+ const cp: u32 = if (inside) msg_cps[@intCast(cc - mx)] else ' ';
+ const at: u8 = if (cp == ' ') 0 else 7 | BOLD;
+ s.put(rr, cc, cp, at);
+ }
+ }
+ }
+
+ fn flush(s: *Sim) void {
+ const out_buf = s.out;
+ const cols = s.cols;
+ var op: usize = 0;
+ var cur_r: usize = ~@as(usize, 0);
+ var cur_c: usize = 0;
+ var cur_at: u16 = 0xffff;
+ var r: usize = 0;
+ while (r < s.lines) : (r += 1) {
+ var c: usize = 0;
+ while (c < cols) : (c += 1) {
+ const idx = r * cols + c;
+ const cp = s.nxt_cp[idx];
+ const at = s.nxt_at[idx];
+ if (cp == s.scr_cp[idx] and at == s.scr_at[idx]) continue;
+ s.scr_cp[idx] = cp;
+ s.scr_at[idx] = at;
+ if (cur_r != r or cur_c != c) {
+ op = moveTo(out_buf, op, r, c);
+ cur_r = r;
+ }
+ if (cp != ' ' and cur_at != at) {
+ op = setAttr(out_buf, op, at);
+ cur_at = at;
+ }
+ op = putUtf8(out_buf, op, cp);
+ cur_c = c + 1;
+ if (cur_c == cols) cur_r = ~@as(usize, 0);
+ }
+ }
+ if (op > 0) writeAll(1, out_buf[0..op]);
+ }
+};
+
+fn handleKey(b: u8) void {
+ switch (b) {
+ 'q', 3 => if (!opt.lock) finish(),
+ 'a' => opt.asynch = !opt.asynch,
+ 'b' => opt.bold = 1,
+ 'B' => opt.bold = 2,
+ 'n' => opt.bold = 0,
+ 'p', 'P' => paused = !paused,
+ '0'...'9' => opt.update = b - '0',
+ '!' => opt.color = 1,
+ '@' => opt.color = 2,
+ '#' => opt.color = 3,
+ '$' => opt.color = 4,
+ '%' => opt.color = 5,
+ '^' => opt.color = 6,
+ '&' => opt.color = 7,
+ else => {},
+ }
+}
+
+fn handleInput() void {
+ if (zm_wait(0) == 0) return;
+ var buf: [64]u8 = undefined;
+ const got = zm_read(&buf, buf.len);
+ if (got <= 0) return;
+ const n: usize = @intCast(got);
+ if (opt.screensaver and !opt.lock) finish();
+
+ var i: usize = 0;
+ while (i < n) {
+ const b = buf[i];
+ i += 1;
+ if (b == 0x1b) {
+ if (i < n and (buf[i] == '[' or buf[i] == 'O')) {
+ i += 1;
+ while (i < n and !(buf[i] >= 0x40 and buf[i] <= 0x7e)) {
+ i += 1;
+ }
+ i += 1;
+ }
+ continue;
+ }
+ handleKey(b);
+ }
+}
+
+fn spanZ(p: [*:0]const u8) []const u8 {
+ var n: usize = 0;
+ while (p[n] != 0) n += 1;
+ return p[0..n];
+}
+
+fn argAt(i: usize) []const u8 {
+ return spanZ(g_argv[i]);
+}
+
+fn streq(a: []const u8, b: []const u8) bool {
+ if (a.len != b.len) return false;
+ for (a, b) |x, y| {
+ if (x != y) return false;
+ }
+ return true;
+}
+
+fn parseUint(s: []const u8) ?u32 {
+ if (s.len == 0) return null;
+ var v: u32 = 0;
+ for (s) |c| {
+ if (c < '0' or c > '9') return null;
+ v = v *| 10 +| (c - '0');
+ }
+ return v;
+}
+
+fn badUsage() noreturn {
+ writeAll(2, usage_text);
+ exit(1);
+}
+
+fn badColor() noreturn {
+ writeAll(2, color_help);
+ exit(1);
+}
+
+fn parseColor(name: []const u8) ?u8 {
+ const names = [_][]const u8{ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white" };
+ for (names, 0..) |n, i| {
+ if (streq(n, name)) return @intCast(i);
+ }
+ return null;
+}
+
+fn setMessage(m: []const u8) void {
+ if (m.len == 0) return;
+ const buf = alloc(u32, m.len) catch return;
+ var n: usize = 0;
+ var i: usize = 0;
+ while (i < m.len) {
+ const b = m[i];
+ var cp: u32 = b;
+ var extra: usize = 0;
+ if (b >= 0xF0) {
+ cp = b & 0x07;
+ extra = 3;
+ } else if (b >= 0xE0) {
+ cp = b & 0x0F;
+ extra = 2;
+ } else if (b >= 0xC0) {
+ cp = b & 0x1F;
+ extra = 1;
+ }
+ i += 1;
+ var k: usize = 0;
+ while (k < extra and i < m.len) : (k += 1) {
+ cp = (cp << 6) | (m[i] & 0x3F);
+ i += 1;
+ }
+ buf[n] = cp;
+ n += 1;
+ }
+ msg_cps = buf[0..n];
+}
+
+fn parseArgs() void {
+ var i: usize = 1;
+ outer: while (i < g_argc) : (i += 1) {
+ const a = argAt(i);
+ if (a.len < 2 or a[0] != '-') continue;
+ if (streq(a, "--")) break;
+ var k: usize = 1;
+ while (k < a.len) : (k += 1) {
+ const ch = a[k];
+ switch (ch) {
+ 'a' => opt.asynch = true,
+ 'b' => opt.bold = 1,
+ 'B' => opt.bold = 2,
+ 'c' => opt.japanese = true,
+ 'f' => {},
+ 'l', 'x' => opt.console = true,
+ 'L' => opt.lock = true,
+ 'n' => opt.bold = 0,
+ 'o' => opt.oldstyle = true,
+ 'r' => opt.rainbow = true,
+ 'm' => opt.lambda = true,
+ 'k' => opt.changes = true,
+ 's' => opt.screensaver = true,
+ 'v', 'V' => {
+ writeAll(1, version_text);
+ exit(0);
+ },
+ 'h' => {
+ writeAll(1, usage_text);
+ exit(0);
+ },
+ 'u', 'C', 'M', 't' => {
+ var val: []const u8 = a[k + 1 ..];
+ if (val.len == 0) {
+ i += 1;
+ if (i >= g_argc) badUsage();
+ val = argAt(i);
+ }
+ switch (ch) {
+ 'u' => opt.update = @min(parseUint(val) orelse badUsage(), 10),
+ 'C' => opt.color = parseColor(val) orelse badColor(),
+ 'M' => setMessage(val),
+ else => {},
+ }
+ continue :outer;
+ },
+ else => badUsage(),
+ }
+ }
+ }
+}
+
+pub export fn main(argc: c_int, argv: [*][*:0]const u8) c_int {
+ g_argc = if (argc > 0) @intCast(argc) else 0;
+ g_argv = argv;
+ parseArgs();
+
+ if (opt.console) {
+ charset = .{ .min = 166, .num = 51 };
+ } else if (opt.japanese) {
+ charset = .{ .min = 0xFF66, .num = 56 };
+ }
+
+ if (zm_enter() != 0) {
+ writeAll(2, "zigmatrix: stdin is not a terminal\n");
+ return 1;
+ }
+ raw_active = true;
+ zm_install(if (opt.lock) 1 else 0);
+ rng_state = zm_seed();
+ writeAll(1, "\x1b[?1049h\x1b[?25l" ++ clear_seq);
+
+ var size = termSize();
+ var sim = Sim.init(size.lines, size.cols) catch finish();
+ sim.reset();
+
+ var count: i32 = 0;
+ while (true) {
+ if (zm_stopped() != 0) finish();
+ handleInput();
+
+ const ns = termSize();
+ if (ns.lines != size.lines or ns.cols != size.cols) {
+ size = ns;
+ sim.deinit();
+ sim = Sim.init(size.lines, size.cols) catch finish();
+ sim.reset();
+ writeAll(1, clear_seq);
+ }
+
+ count += 1;
+ if (count > 4) count = 1;
+
+ if (!paused) {
+ var j: usize = 0;
+ while (j < sim.cols) : (j += 2) {
+ if (count > sim.updates[j] or !opt.asynch) {
+ sim.stepColumn(j);
+ }
+ }
+ sim.render();
+ if (msg_cps.len > 0) {
+ sim.overlay();
+ }
+ sim.flush();
+ }
+
+ if (opt.update > 0) {
+ _ = zm_wait(@intCast(opt.update * 10));
+ }
+ }
+}
diff --git a/src/term.c b/src/term.c
new file mode 100644
index 0000000..dda5f65
--- /dev/null
+++ b/src/term.c
@@ -0,0 +1,120 @@
+#include <errno.h>
+#include <poll.h>
+#include <signal.h>
+#include <stddef.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+static struct termios saved;
+static int active = 0;
+static volatile sig_atomic_t stopped = 0;
+
+static void on_signal(int sig)
+{
+ (void)sig;
+ stopped = 1;
+}
+
+int zm_enter(void)
+{
+ struct termios t;
+ if (tcgetattr(STDIN_FILENO, &saved) != 0)
+ return -1;
+ t = saved;
+ t.c_lflag &= ~(tcflag_t)(ISIG | ICANON | ECHO);
+ t.c_cc[VMIN] = 0;
+ t.c_cc[VTIME] = 0;
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &t) != 0)
+ return -1;
+ active = 1;
+ return 0;
+}
+
+void zm_leave(void)
+{
+ if (active) {
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved);
+ active = 0;
+ }
+}
+
+void zm_install(int lock)
+{
+ struct sigaction handle;
+ struct sigaction ignore;
+ memset(&handle, 0, sizeof handle);
+ memset(&ignore, 0, sizeof ignore);
+ handle.sa_handler = on_signal;
+ ignore.sa_handler = SIG_IGN;
+ sigemptyset(&handle.sa_mask);
+ sigemptyset(&ignore.sa_mask);
+ sigaction(SIGTERM, &handle, NULL);
+ sigaction(SIGHUP, &handle, NULL);
+ sigaction(SIGINT, lock ? &ignore : &handle, NULL);
+}
+
+int zm_stopped(void)
+{
+ return stopped ? 1 : 0;
+}
+
+int zm_size(int *rows, int *cols)
+{
+ struct winsize ws;
+ memset(&ws, 0, sizeof ws);
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != 0 || ws.ws_row == 0 || ws.ws_col == 0) {
+ memset(&ws, 0, sizeof ws);
+ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) != 0 || ws.ws_row == 0 || ws.ws_col == 0)
+ return -1;
+ }
+ *rows = (int)ws.ws_row;
+ *cols = (int)ws.ws_col;
+ return 0;
+}
+
+int zm_wait(int ms)
+{
+ struct pollfd p;
+ int r;
+ p.fd = STDIN_FILENO;
+ p.events = POLLIN;
+ p.revents = 0;
+ r = poll(&p, 1, ms);
+ return (r > 0 && (p.revents & POLLIN)) ? 1 : 0;
+}
+
+ssize_t zm_read(unsigned char *buf, size_t n)
+{
+ return read(STDIN_FILENO, buf, n);
+}
+
+void zm_write(int fd, const unsigned char *buf, size_t n)
+{
+ size_t off = 0;
+ while (off < n) {
+ ssize_t r = write(fd, buf + off, n - off);
+ if (r < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return;
+ }
+ off += (size_t)r;
+ }
+}
+
+unsigned long long zm_seed(void)
+{
+ struct timespec ts;
+ unsigned long long a;
+ unsigned long long b;
+ unsigned long long p;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ a = (unsigned long long)ts.tv_sec;
+ b = (unsigned long long)ts.tv_nsec;
+ p = (unsigned long long)getpid();
+ return (a * 1000000007ULL) ^ b ^ (p << 32);
+}