diff --git a/build.zig b/build.zig index 9e89029..953db9f 100644 --- a/build.zig +++ b/build.zig @@ -71,6 +71,14 @@ pub fn build(b: *std.Build) !void { }), }); + const zlua = b.dependency("zlua", .{ + .target = target, + .optimize = optimize, + }); + exe.root_module.addImport("zlua", zlua.module("zlua")); + + + const ly_ui = b.dependency("ly_ui", .{ .target = target, .optimize = optimize, diff --git a/build.zig.zon b/build.zig.zon index 3c091cb..1d19b69 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -11,6 +11,10 @@ .url = "git+https://github.com/Hejsil/zig-clap#fc1e5cc3f6d9d3001112385ee6256d694e959d2f", .hash = "clap-0.11.0-oBajB7foAQC3Iyn4IVCkUdYaOVVng5IZkSncySTjNig1", }, + .zlua = .{ + .url = "git+https://github.com/natecraddock/ziglua?ref=zig-0.16#8f271c82baa5fc43aa02a72f6da020c2025d9436", + .hash = "zlua-0.1.0-hGRpC2aABQD4D9PBVH3wAW8k32-I4969MRQ0CpOwoley", + }, }, .paths = .{ "build.zig", diff --git a/src/animations/Lua.zig b/src/animations/Lua.zig new file mode 100644 index 0000000..86e3b82 --- /dev/null +++ b/src/animations/Lua.zig @@ -0,0 +1,151 @@ +const std = @import("std"); +const ly_ui = @import("ly-ui"); +const LogFile = ly_ui.ly_core.LogFile; +const Widget = ly_ui.Widget; +const TerminalBuffer = ly_ui.TerminalBuffer; +const Cell = ly_ui.Cell; +const Allocator = std.mem.Allocator; + +const zlua = @import("zlua"); + +const LyLua = @embedFile("ly.lua"); + +const Lua = @This(); + + +instance: ?Widget = null, +lua: *zlua.Lua, +log: *LogFile, +terminal_buffer: *TerminalBuffer, +io: std.Io, + +lua_error: bool = false, + +pub fn init( + io: std.Io, + alloc: Allocator, + log: *LogFile, + buf: *TerminalBuffer, + file: ?[]const u8, +) !Lua { + var self: Lua = .{ + .lua = try zlua.Lua.init(alloc), + .terminal_buffer = buf, + .instance = null, + .log = log, + .io = io, + }; + self.lua.openLibs(); + if (file) |f| { + const zf = std.mem.concatWithSentinel(alloc, u8, &[1][]const u8{f}, 0) catch unreachable; + defer alloc.free(zf); + self.lua.doString(LyLua) catch { + const errorStr = self.lua.toString(-1) catch unreachable; + self.log.err(self.io, "Lua", "Lua Error: {s}", .{errorStr}) catch unreachable; + self.lua.setGlobal("_ERROR"); + self.lua_error = true; + }; + self.lua.doFile(zf) catch { + const errorStr = self.lua.toString(-1) catch unreachable; + self.log.err(self.io, "Lua", "Lua Error: {s}", .{errorStr}) catch unreachable; + self.lua.setGlobal("_ERROR"); + self.lua_error = true; + }; + } + _ = self.lua.getGlobal("ly"); + addFuncToTable(self.lua, "putCell", luaPutCell); + self.lua.setGlobal("ly"); + + return self; +} + +fn draw(self: *Lua) void { + _ = 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"); + if (!self.lua_error) { + self.lua.doString( + "draw()" + ) catch { + const errorStr = self.lua.toString(-1) catch unreachable; + self.log.err(self.io, "Lua", "Lua Error: {s}", .{errorStr}) catch unreachable; + self.lua.setGlobal("_ERROR"); + self.lua_error = true; + }; + } else { + // set the entire background to red + 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 {}; + } +} + +fn calculateTimeout(self: *Lua, _: *anyopaque) !?usize { + const lyType = self.lua.getGlobal("ly"); // +1 + _ = self.lua.pushString("frame_delay"); // + 1 + if (lyType != .table) + std.debug.panic("Lua: global 'ly' must be a table: got {}", .{lyType}); + const frameDelayType = self.lua.getTable(-2); // neut + if (frameDelayType != .number) + std.debug.panic("Lua: 'ly.frame_delay' must be a number: got {}", .{frameDelayType}); + 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 { + 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.?; +} + +fn luaPutCell(lua: ?*zlua.LuaState) callconv(.c) c_int { + var vm = @as(*zlua.Lua, @ptrCast(lua)); + const cell = vm.checkNumeric(u32, 1); + const fg = vm.checkNumeric(u32, 2); + const bg = vm.checkNumeric(u32, 3); + const x = vm.checkNumeric(usize, 4); + const y = vm.checkNumeric(usize, 5); + Cell.init(cell, fg, bg).put(x, y) catch {}; + + return 0; // This function doesnt return anything +} + +/// Adds member function `name` to table at the top of the stack +/// +/// Pushes: 0 +/// Pops: 0, this function can be called continuously +/// Errors: unknown +fn addFuncToTable( + vm: *zlua.Lua, + comptime name: []const u8, + func: zlua.CFn, +) void +{ + _ = vm.pushString(name); + vm.pushFunction(func); + vm.setTable(-3); +} + diff --git a/src/animations/ly.lua b/src/animations/ly.lua new file mode 100644 index 0000000..5d53f45 --- /dev/null +++ b/src/animations/ly.lua @@ -0,0 +1,3 @@ +ly = { + frame_delay = 0, +} diff --git a/src/config/Config.zig b/src/config/Config.zig index a592faa..4d27533 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -74,6 +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, ly_log: ?[]const u8 = "/var/log/ly.log", margin_box_h: u8 = 2, margin_box_v: u8 = 1, diff --git a/src/enums.zig b/src/enums.zig index 47771da..ff69f84 100644 --- a/src/enums.zig +++ b/src/enums.zig @@ -7,6 +7,7 @@ pub const Animation = enum { colormix, gameoflife, dur_file, + lua, }; pub const DisplayServer = enum { diff --git a/src/main.zig b/src/main.zig index 0f6f1e0..384d030 100644 --- a/src/main.zig +++ b/src/main.zig @@ -29,6 +29,7 @@ const Doom = @import("animations/Doom.zig"); const DurFile = @import("animations/DurFile.zig"); const GameOfLife = @import("animations/GameOfLife.zig"); const Matrix = @import("animations/Matrix.zig"); +const LuaAnim = @import("animations/Lua.zig"); const auth = @import("auth.zig"); const InfoLine = @import("components/InfoLine.zig"); const Session = @import("components/Session.zig"); @@ -1098,6 +1099,16 @@ pub fn main(init: std.process.Init) !void { ); animation = dur.widget(); }, + .lua => { + var lua = try LuaAnim.init( + state.io, + state.allocator, + &state.log_file, + &state.buffer, + state.config.lua_animation_file, + ); + animation = lua.widget(); + }, } defer if (animation) |a| a.deinit();