diff --git a/build.zig b/build.zig index 7a8015f..a3a5f22 100644 --- a/build.zig +++ b/build.zig @@ -74,18 +74,16 @@ pub fn build(b: *std.Build) !void { const zlua = b.dependency("zlua", .{ .target = target, .optimize = optimize, - .lang = .luajit + .lang = .luajit, }); exe.root_module.addImport("zlua", zlua.module("zlua")); - - const ly_ui = b.dependency("ly_ui", .{ .target = target, .optimize = optimize, .enable_x11_support = enable_x11_support, .fallback_uid_min = fallback_uid_min, - .fallback_uid_max = fallback_uid_max + .fallback_uid_max = fallback_uid_max, }); exe.root_module.addImport("ly-ui", ly_ui.module("ly-ui")); diff --git a/res/example.lua b/res/example.lua index 4ff85ee..378f2de 100644 --- a/res/example.lua +++ b/res/example.lua @@ -23,6 +23,9 @@ local SQUARE_WIDTH = 10 local SQUARE_HEIGHT = 5 local SQUARE_COUNT = 25 + +local FPS = 120 + local squares = {} for i = 0, SQUARE_COUNT-1 do @@ -44,7 +47,7 @@ local timer = os.clock() function draw() -- Rather than progressing the animation by frame, do it based on -- seconds, like from os.clock(). - if timer + 0.025 < os.clock() then + if timer + (1/FPS) < os.clock() then ly.clear() for i, v in ipairs(squares) do v.x = v.x + v.vx diff --git a/src/animations/Lua.zig b/src/animations/Lua.zig index 82bdc05..5bb7fa1 100644 --- a/src/animations/Lua.zig +++ b/src/animations/Lua.zig @@ -5,10 +5,11 @@ const Widget = ly_ui.Widget; const TerminalBuffer = ly_ui.TerminalBuffer; const Cell = ly_ui.Cell; const Allocator = std.mem.Allocator; +const InfoLine = @import("../components/InfoLine.zig"); const zlua = @import("zlua"); -const LyLua = @embedFile("ly.lua"); +const ly_lua = @embedFile("ly.lua"); const Lua = @This(); @@ -21,8 +22,14 @@ width: usize, height: usize, margin: usize, io: std.Io, +animation_delay: u16, + +info_line: *InfoLine, +fg: u32, +bg: u32, lua_error: bool = false, +lua_error_logged: bool = false, lua_str: ?[:0]const u8 = null, pub fn init( @@ -30,8 +37,12 @@ pub fn init( alloc: Allocator, log: *LogFile, buf: *TerminalBuffer, - file: ?[]const u8, + file: []const u8, margin: u8, + animation_delay: u16, + info_line: *InfoLine, + fg: u32, + bg: u32, ) !Lua { var self: Lua = .{ .lua = try zlua.Lua.init(alloc), @@ -43,6 +54,10 @@ pub fn init( .height = 0, .margin = margin, .io = io, + .animation_delay = animation_delay, + .info_line = info_line, + .fg = fg, + .bg = bg, }; // exclude IO and debug libraries @@ -52,42 +67,179 @@ pub fn init( self.lua.openOS(); self.lua.openString(); self.lua.openTable(); - self.lua.openString(); - if (file) |f| { - const zf = std.mem.concatWithSentinel(alloc, u8, &[1][]const u8{f}, 0) catch unreachable; + file_loading: { + const zf = std.mem.concatWithSentinel(alloc, u8, &[1][]const u8{file}, 0) catch |e| { + self.log.err(self.io, "lua", "failed to allocate file path: {}", .{e}) catch unreachable; + self.lua_str = "failed to allocate file path!"; + self.lua_error = true; + break :file_loading; + }; defer alloc.free(zf); - self.lua.doString(LyLua) catch { + + self.lua.doString(ly_lua) catch { const errorStr = self.lua.toString(-1) catch unreachable; self.lua_str = try self.allocator.dupeSentinel(u8, errorStr, 0); - self.log.err(self.io, "Lua", "Lua Error: {s}", .{errorStr}) catch unreachable; + self.log.err(self.io, "lua", "lua error: {s}", .{errorStr}) catch unreachable; self.lua_error = true; + break :file_loading; }; - if (self.terminal_buffer.height != self.height or - self.terminal_buffer.width != self.width) - { - self.width = self.terminal_buffer.width; - self.height = self.terminal_buffer.height; - _ = self.lua.getGlobal("ly"); - _ = self.lua.pushString("width"); - self.lua.pushInteger(@intCast(self.terminal_buffer.width)); - self.lua.setTable(-3); - _ = self.lua.pushString("height"); - self.lua.pushInteger(@intCast(self.terminal_buffer.height)); - self.lua.setTable(-3); - self.lua.setGlobal("ly"); - } + self.propogateTerminalBounds(); self.lua.doFile(zf) catch { const errorStr = self.lua.toString(-1) catch unreachable; self.lua_str = try self.allocator.dupeSentinel(u8, errorStr, 0); - self.log.err(self.io, "Lua", "Lua Error: {s}", .{errorStr}) catch unreachable; + self.log.err(self.io, "lua", "lua error: {s}", .{errorStr}) catch unreachable; self.lua_error = true; + break :file_loading; }; } return self; } fn draw(self: *Lua) void { + self.propogateTerminalBounds(); + if (self.lua_error) { + // Ly's Red Screen of Omega-Death:tm: + const cell = Cell.init(0x2588, 0x00ff0000, 0x00ff0000); + for (0..self.terminal_buffer.height) |y| + for (0..self.terminal_buffer.width) |x| + cell.put(x, y) catch {}; + if (self.lua_str) |str| + for (str, 0..) |c, i| { + Cell.init(c, 0x00FFFFFF, 0).put( + @divFloor(self.width, 2) - @divFloor(str.len, 2) + i, + self.margin + 5, + ) catch {}; + }; + if (!self.lua_error_logged) { + self.info_line.addMessage("lua animation failed", self.bg, self.fg) catch {}; + self.lua_error_logged = true; + } + return; + } + + _ = self.lua.getGlobal("draw"); + self.lua.protectedCall(.{}) catch { + const errorStr = self.lua.toString(-1) catch unreachable; + self.lua_str = std.mem.concatWithSentinel( + self.allocator, + u8, + &[_][]const u8{ "Cannot call draw(): ", errorStr }, + 0, + ) catch unreachable; + self.log.err(self.io, "Lua", "Error (Cannot call draw()): {s}", .{errorStr}) catch unreachable; + self.lua_error = true; + }; + const bufferType = self.lua.getGlobal("_BUFFER"); + if (bufferType != .table) { + self.log.err(self.io, "Lua", "global '_BUFFER' must be a table: got {}", .{bufferType}) catch {}; + self.lua_str = std.fmt.allocPrintSentinel( + self.allocator, + "global '_BUFFER' must be a table: got {}", + .{bufferType}, + 0, + ) catch unreachable; + self.lua_error = true; + return; + } + self.lua.pushNil(); + while (self.lua.next(-2)) { + // expect the key to be an int + // expect the value to be a table + const pos = self.lua.toInteger(-2) catch { + self.log.err(self.io, "Lua", "pos: cannot convert to integer", .{}) catch {}; + self.lua_str = std.fmt.allocPrintSentinel( + self.allocator, + "pos: cannot convert to integer", + .{}, + 0, + ) catch unreachable; + self.lua_error = true; + return; + }; + + _ = self.lua.pushString("char"); + _ = self.lua.getTable(-2); + const char = self.lua.toInteger(-1) catch { + self.log.err(self.io, "Lua", "char: cannot convert to integer", .{}) catch {}; + self.lua_str = std.fmt.allocPrintSentinel( + self.allocator, + "char: cannot convert to integer", + .{}, + 0, + ) catch unreachable; + self.lua_error = true; + return; + }; + self.lua.pop(1); + _ = self.lua.pushString("fg"); + _ = self.lua.getTable(-2); + const fg = self.lua.toInteger(-1) catch { + self.log.err(self.io, "Lua", "fg: cannot convert to integer", .{}) catch {}; + self.lua_str = std.fmt.allocPrintSentinel( + self.allocator, + "fg: cannot convert to integer", + .{}, + 0, + ) catch unreachable; + self.lua_error = true; + return; + }; + self.lua.pop(1); + _ = self.lua.pushString("bg"); + _ = self.lua.getTable(-2); + const bg = self.lua.toInteger(-1) catch { + self.log.err(self.io, "Lua", "bg: cannot convert to integer", .{}) catch {}; + self.lua_str = std.fmt.allocPrintSentinel( + self.allocator, + "bg: cannot convert to integer", + .{}, + 0, + ) catch unreachable; + self.lua_error = true; + return; + }; + self.lua.pop(1); + self.lua.pop(1); + if (pos < 0) continue; // do not draw cells that underflow + TerminalBuffer.setCell( + @rem(@as(usize, @intCast(pos)), self.width), + @divFloor(@as(usize, @bitCast(pos)), self.width), + .init( + @intCast(char), + @intCast(@as(i32, @truncate(fg))), + @intCast(@as(i32, @truncate(bg))), + ), + ) catch {}; + } +} + +fn calculateTimeout(self: *Lua, _: *anyopaque) !?usize { + return self.animation_delay; +} + +fn deinit(self: *Lua) void { + if (self.lua_str) |str| self.allocator.free(str); + self.lua.deinit(); +} + +pub fn widget(self: *Lua) *Widget { + if (self.instance) |*inst| return inst; + self.instance = Widget.init( + "Lua", + null, + self, + deinit, + null, //realloc + draw, //draw + null, //update + null, + calculateTimeout, //calculateTimeout + ); + return &self.instance.?; +} + +fn propogateTerminalBounds(self: *Lua) void { if (self.terminal_buffer.height != self.height or self.terminal_buffer.width != self.width) { @@ -102,162 +254,4 @@ fn draw(self: *Lua) void { self.lua.setTable(-3); self.lua.setGlobal("ly"); } - if (!self.lua_error) { - _ = self.lua.getGlobal("draw"); - self.lua.protectedCall(.{}) catch { - const errorStr = self.lua.toString(-1) catch unreachable; - self.lua_str = std.mem.concatWithSentinel( - self.allocator, - u8, - &[_][]const u8{ "Cannot call draw(): ", errorStr }, - 0, - ) catch unreachable; - self.log.err(self.io, "Lua", "Error (Cannot call draw()): {s}", .{errorStr}) catch unreachable; - self.lua_error = true; - }; - const bufferType = self.lua.getGlobal("_BUFFER"); - if (bufferType != .table) { - self.log.err(self.io, "Lua", "global '_BUFFER' must be a table: got {}", .{bufferType}) catch {}; - self.lua_str = std.fmt.allocPrintSentinel( - self.allocator, - "global '_BUFFER' must be a table: got {}", - .{bufferType}, - 0, - ) catch unreachable; - self.lua_error = true; - return; - } - self.lua.pushNil(); - while (self.lua.next(-2)) { - // expect the key to be an int - // expect the value to be a table - const pos = self.lua.toInteger(-2) catch { - self.log.err(self.io, "Lua", "pos: cannot convert to integer", .{}) catch {}; - self.lua_str = std.fmt.allocPrintSentinel( - self.allocator, - "pos: cannot convert to integer", - .{}, - 0, - ) catch unreachable; - self.lua_error = true; - return; - }; - - _ = self.lua.pushString("char"); - _ = self.lua.getTable(-2); - const char = self.lua.toInteger(-1) catch { - self.log.err(self.io, "Lua", "char: cannot convert to integer", .{}) catch {}; - self.lua_str = std.fmt.allocPrintSentinel( - self.allocator, - "char: cannot convert to integer", - .{}, - 0, - ) catch unreachable; - self.lua_error = true; - return; - }; - self.lua.pop(1); - _ = self.lua.pushString("fg"); - _ = self.lua.getTable(-2); - const fg = self.lua.toInteger(-1) catch { - self.log.err(self.io, "Lua", "fg: cannot convert to integer", .{}) catch {}; - self.lua_str = std.fmt.allocPrintSentinel( - self.allocator, - "fg: cannot convert to integer", - .{}, - 0, - ) catch unreachable; - self.lua_error = true; - return; - }; - self.lua.pop(1); - _ = self.lua.pushString("bg"); - _ = self.lua.getTable(-2); - const bg = self.lua.toInteger(-1) catch { - self.log.err(self.io, "Lua", "bg: cannot convert to integer", .{}) catch {}; - self.lua_str = std.fmt.allocPrintSentinel( - self.allocator, - "bg: cannot convert to integer", - .{}, - 0, - ) catch unreachable; - self.lua_error = true; - return; - }; - self.lua.pop(1); - self.lua.pop(1); - if (pos < 0) continue; // do not draw cells that underflow - Cell.init(@intCast(char), @intCast(@as(i32, @truncate(fg))), @intCast(@as(i32, @truncate(bg)))) - .put(@rem(@as(usize, @intCast(pos)), self.width), @divFloor(@as(usize, @bitCast(pos)), self.width)) catch {}; - } - } else { - // Ly's Red Screen of Omega-Death:tm: - const cell = Cell.init(0x2588, 0x00ff0000, 0x00ff0000); - for (0..self.terminal_buffer.height) |y| - for (0..self.terminal_buffer.width) |x| - cell.put(x, y) catch {}; - if (self.lua_str) |str| - for (str, 0..) |c, i| { - Cell.init(c, 0x00FFFFFF, 0).put(@divFloor(self.width, 2) - @divFloor(str.len, 2) + i, self.margin + 5) catch {}; - }; - } -} - -fn calculateTimeout(self: *Lua, _: *anyopaque) !?usize { - if (self.lua_error) return null; - const lyType = self.lua.getGlobal("ly"); // +1 - _ = self.lua.pushString("frame_delay"); // + 1 - if (lyType != .table) { - try self.log.err(self.io, "Lua", "global 'ly' must be a table: got {}", .{lyType}); - self.lua_str = std.fmt.allocPrintSentinel( - self.allocator, - "global 'ly' must be a table: got {}", - .{lyType}, - 0, - ) catch unreachable; - self.lua_error = true; - self.lua.pop(-1); - self.lua.pop(-1); - return null; - } - const frameDelayType = self.lua.getTable(-2); // neut - if (frameDelayType != .number) { - try self.log.err(self.io, "Lua", "'ly.frame_delay' must be a number: got {}", .{frameDelayType}); - self.lua_str = std.fmt.allocPrintSentinel( - self.allocator, - "'ly.frameDelayType' must be a table: got {}", - .{frameDelayType}, - 0, - ) catch unreachable; - self.lua_error = true; - self.lua.pop(-1); - self.lua.pop(-1); - return null; - } - const timeout = try self.lua.toNumeric(usize, -1); - self.lua.pop(-1); // pop number from stack - self.lua.pop(-1); // pop table 'ly' from stack - return timeout; -} - -fn deinit(self: *Lua) void { - if (self.lua_str) |str| - self.allocator.free(str); - self.lua.deinit(); -} - -pub fn widget(self: *Lua) *Widget { - if (self.instance) |*inst| return inst; - self.instance = Widget.init( - "Lua", - null, - self, - deinit, //deinit - null, //realloc - draw, //draw - null, //update - null, - calculateTimeout, //calculateTimeout - ); - return &self.instance.?; } diff --git a/src/config/Config.zig b/src/config/Config.zig index 4d27533..b7b258b 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -74,7 +74,7 @@ lang: []const u8 = "en", login_cmd: ?[]const u8 = null, login_defs_path: []const u8 = "/etc/login.defs", logout_cmd: ?[]const u8 = null, -lua_animation_file: ?[]const u8 = null, +lua_animation_file: []const u8 = build_options.config_directory ++ "/ly/example.lua", ly_log: ?[]const u8 = "/var/log/ly.log", margin_box_h: u8 = 2, margin_box_v: u8 = 1, diff --git a/src/main.zig b/src/main.zig index 85a2bfc..66cce9f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1107,6 +1107,10 @@ pub fn main(init: std.process.Init) !void { &state.buffer, state.config.lua_animation_file, state.config.edge_margin, + state.config.animation_frame_delay, + &state.info_line, + state.config.error_fg, + state.config.error_bg, ); animation = lua.widget(); },