From ffec7db766fb50a32a92d5750e9de6f3711d4eba Mon Sep 17 00:00:00 2001 From: Squibid Date: Tue, 25 Nov 2025 23:52:31 -0500 Subject: [PATCH 1/9] add protocol file --- build.zig | 2 + protocol/mez-remote-lua-unstable-v1.xml | 89 +++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 protocol/mez-remote-lua-unstable-v1.xml diff --git a/build.zig b/build.zig index a48cffe..a6e8383 100644 --- a/build.zig +++ b/build.zig @@ -26,7 +26,9 @@ pub fn build(b: *std.Build) void { scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml"); scanner.addSystemProtocol("stable/tablet/tablet-v2.xml"); scanner.addSystemProtocol("unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"); + scanner.addCustomProtocol(b.path("protocol/mez-remote-lua-unstable-v1.xml")); + scanner.generate("zmez_remote_lua_manager_v1", 1); scanner.generate("wl_compositor", 6); scanner.generate("wl_subcompositor", 1); scanner.generate("wl_shm", 1); diff --git a/protocol/mez-remote-lua-unstable-v1.xml b/protocol/mez-remote-lua-unstable-v1.xml new file mode 100644 index 0000000..44605af --- /dev/null +++ b/protocol/mez-remote-lua-unstable-v1.xml @@ -0,0 +1,89 @@ + + + + Copyright © 2025 mezzaluna team + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + This protocol allows clients to receive lua errors from mez and execute lua + on a running mez server with the lua mez api. + + Warning! The protocol described in this file is experimental and backward + incompatible changes may be made. Backward compatible changes may be added + together with the corresponding interface version bump. Backward + incompatible changes are done by bumping the version number in the protocol + and interface names and resetting the interface version. Once the protocol + is to be declared stable, the 'z' prefix and the version number in the + protocol and interface names are removed and the interface version number + is reset. + + + + + A global factory for zmez_remote_lua_v1 objects. + + + + + This request indicates that the client will not use the + mez_remote_lua_manager object any more. Objects that have been created + through this instance are not affected. + + + + + + This creates a new mez_remote_lua_v1 object. + All lua related communication is done through this interface. + + + + + + + + This interface allows clients to receive lua logs from the compositor. + Additionally it allow for the client to send lua code for the compositor + to execute. + + + + + This request indicates that the client will not use the + mez_remote_lua_v1 object any more. + + + + + + The compositor sends this event to inform the client that it has a new + log entry for the client. + + The text contains the lua log information the server generates when + executing lua code. The server does not hold logs from before the client + connect, and therefore you will only recieve log information from the + point that you start listening for them and on. + + + + + + + This request sends stringified lua code for the server to run. + The output, if any, will be sent through the new_log_entry event. + + + + + From ffbe4965995cc9a96d7c192638097f208936d42a Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 27 Nov 2025 20:14:06 -0500 Subject: [PATCH 2/9] implement the zmez_remote_lua_v1 protocol --- src/RemoteLua.zig | 55 ++++++++++++++++++++++++++++++++++++++++ src/RemoteLuaManager.zig | 49 +++++++++++++++++++++++++++++++++++ src/server.zig | 5 ++++ src/types/hook.zig | 5 +++- 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/RemoteLua.zig create mode 100644 src/RemoteLuaManager.zig diff --git a/src/RemoteLua.zig b/src/RemoteLua.zig new file mode 100644 index 0000000..cc3a992 --- /dev/null +++ b/src/RemoteLua.zig @@ -0,0 +1,55 @@ +const RemoteLua = @This(); + +const std = @import("std"); +const wayland = @import("wayland"); +const Utils = @import("utils.zig"); +const wl = wayland.server.wl; +const mez = wayland.server.zmez; + +const gpa = std.heap.c_allocator; +const server = &@import("main.zig").server; +const Lua = &@import("main.zig").lua; + +id: usize, +remote_lua_v1: *mez.RemoteLuaV1, + +pub fn sendNewLogEntry(str: [*:0]const u8) void { + for (server.remote_lua_clients.items) |c| { + c.remote_lua_v1.sendNewLogEntry(str); + } +} + +pub fn create(client: *wl.Client, version: u32, id: u32) !void { + const remote_lua_v1 = try mez.RemoteLuaV1.create(client, version, id); + + const node = try gpa.create(RemoteLua); + errdefer gpa.destroy(node); + node = .{ + .remote_lua_v1 = remote_lua_v1, + .id = server.remote_lua_clients.items.len, + }; + server.remote_lua_clients.append(gpa, node); + + remote_lua_v1.setHandler(*RemoteLua, handleRequest, handleDestroy, &node); +} + +fn handleRequest( +remote_lua_v1: *mez.RemoteLuaV1, +request: mez.RemoteLuaV1.Request, +_: *RemoteLua, +) void { + switch (request) { + .destroy => remote_lua_v1.destroy(), + .push_lua => |req| { + Lua.state.loadString(req.lua_chunk) catch { + const errTxt: []const u8 = Lua.state.toString(-1) catch unreachable; + try sendNewLogEntry("repl: " ++ errTxt); + }; + }, + } +} + +fn handleDestroy(_: *mez.RemoteLuaV1, remote_lua: *RemoteLua) void { + server.remote_lua_clients.swapRemove(remote_lua.id); + gpa.destroy(remote_lua); +} diff --git a/src/RemoteLuaManager.zig b/src/RemoteLuaManager.zig new file mode 100644 index 0000000..1df4411 --- /dev/null +++ b/src/RemoteLuaManager.zig @@ -0,0 +1,49 @@ +const RemoteLuaManager = @This(); + +const std = @import("std"); +const wayland = @import("wayland"); +const Utils = @import("utils.zig"); +const RemoteLua = @import("RemoteLua.zig"); +const wl = wayland.server.wl; +const mez = wayland.server.zmez; + +const gpa = std.heap.c_allocator; +const server = &@import("main.zig").server; + +global: *wl.Global, + +pub fn init() ?*RemoteLuaManager { + const self = try gpa.create(RemoteLuaManager); + + self.global = try wl.Global.create(server.wl_server, mez.RemoteLuaManagerV1, 1, ?*anyopaque, null, bind); + + return self; +} + +fn bind(client: *wl.Client, _: ?*anyopaque, version: u32, id: u32) void { + const remote_lua_manager_v1 = mez.RemoteLuaManagerV1.create(client, version, id) catch { + client.postNoMemory(); + Utils.oomPanic(); + }; + remote_lua_manager_v1.setHandler(?*anyopaque, handleRequest, null, null); +} + +fn handleRequest( +remote_lua_manager_v1: *mez.RemoteLuaManagerV1, +request: mez.RemoteLuaManagerV1.Request, +_: ?*anyopaque, +) void { + switch (request) { + .destroy => remote_lua_manager_v1.destroy(), + .get_remote => |req| { + RemoteLua.create( + remote_lua_manager_v1.getClient(), + remote_lua_manager_v1.getVersion(), + req.id, + ) catch { + remote_lua_manager_v1.getClient().postNoMemory(); + Utils.oomPanic(); + }; + }, + } +} diff --git a/src/server.zig b/src/server.zig index d42d8b9..e04c8ad 100644 --- a/src/server.zig +++ b/src/server.zig @@ -14,6 +14,7 @@ const Utils = @import("utils.zig"); const Keymap = @import("types/keymap.zig"); const Hook = @import("types/hook.zig"); const Events = @import("types/events.zig"); +const RemoteLua = @import("RemoteLua.zig"); const gpa = std.heap.c_allocator; const server = &@import("main.zig").server; @@ -41,6 +42,7 @@ cursor: Cursor, keymaps: std.AutoHashMap(u64, Keymap), hooks: std.ArrayList(*Hook), events: Events, +remote_lua_clients: std.ArrayList(*RemoteLua), // Backend listeners new_input: wl.Listener(*wlr.InputDevice) = .init(handleNewInput), @@ -98,6 +100,7 @@ pub fn init(self: *Server) void { .keymaps = .init(gpa), .hooks = try .initCapacity(gpa, 10), // TODO: choose how many slots to start with .events = try .init(gpa), + .remote_lua_clients = try .initCapacity(gpa, 0), }; self.renderer.initServer(wl_server) catch { @@ -132,6 +135,8 @@ pub fn deinit(self: *Server) noreturn { self.new_xdg_popup.link.remove(); self.new_xdg_toplevel_decoration.link.remove(); + self.remote_lua_clients.deinit(gpa); + self.seat.deinit(); self.root.deinit(); self.cursor.deinit(); diff --git a/src/types/hook.zig b/src/types/hook.zig index 511e84a..044a8cd 100644 --- a/src/types/hook.zig +++ b/src/types/hook.zig @@ -7,6 +7,7 @@ const xkb = @import("xkbcommon"); const wlr = @import("wlroots"); const zlua = @import("zlua"); +const RemoteLua = @import("../RemoteLua.zig"); const Event = @import("events.zig"); const Lua = &@import("../main.zig").lua; @@ -38,6 +39,8 @@ pub fn callback(self: *const Hook, args: anytype) void { i = k; } - Lua.state.protectedCall(.{ .args = i }) catch { }; + Lua.state.protectedCall(.{ .args = i }) catch { + RemoteLua.sendNewLogEntry(Lua.state.toString(-1) catch unreachable); + }; Lua.state.pop(-1); } From 7ed5c4840d17ec1726c7c9ba8dcb8320162b9eac Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 28 Nov 2025 18:16:33 -0500 Subject: [PATCH 3/9] actually setup the remote_lua protocol on the server --- src/RemoteLua.zig | 38 ++++++++++++++++++++++++++++---------- src/RemoteLuaManager.zig | 10 +++++----- src/server.zig | 3 +++ 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/RemoteLua.zig b/src/RemoteLua.zig index cc3a992..92143ff 100644 --- a/src/RemoteLua.zig +++ b/src/RemoteLua.zig @@ -3,15 +3,16 @@ const RemoteLua = @This(); const std = @import("std"); const wayland = @import("wayland"); const Utils = @import("utils.zig"); +const Lua = @import("lua/lua.zig"); const wl = wayland.server.wl; const mez = wayland.server.zmez; const gpa = std.heap.c_allocator; const server = &@import("main.zig").server; -const Lua = &@import("main.zig").lua; id: usize, remote_lua_v1: *mez.RemoteLuaV1, +L: Lua, pub fn sendNewLogEntry(str: [*:0]const u8) void { for (server.remote_lua_clients.items) |c| { @@ -24,32 +25,49 @@ pub fn create(client: *wl.Client, version: u32, id: u32) !void { const node = try gpa.create(RemoteLua); errdefer gpa.destroy(node); - node = .{ + node.* = .{ .remote_lua_v1 = remote_lua_v1, .id = server.remote_lua_clients.items.len, + .L = undefined, }; - server.remote_lua_clients.append(gpa, node); + try node.L.init(); + errdefer node.L.deinit(); + try server.remote_lua_clients.append(gpa, node); - remote_lua_v1.setHandler(*RemoteLua, handleRequest, handleDestroy, &node); + remote_lua_v1.setHandler(*RemoteLua, handleRequest, handleDestroy, node); } fn handleRequest( remote_lua_v1: *mez.RemoteLuaV1, request: mez.RemoteLuaV1.Request, -_: *RemoteLua, +remote: *RemoteLua, ) void { switch (request) { .destroy => remote_lua_v1.destroy(), .push_lua => |req| { - Lua.state.loadString(req.lua_chunk) catch { - const errTxt: []const u8 = Lua.state.toString(-1) catch unreachable; - try sendNewLogEntry("repl: " ++ errTxt); - }; + const chunk = std.mem.sliceTo(req.lua_chunk, 0); + remote.L.state.loadString(chunk) catch catchLuaFail(remote); + remote.L.state.protectedCall(.{}) catch catchLuaFail(remote); }, } } +fn catchLuaFail(remote: *RemoteLua) void { + const err_txt: []const u8 = remote.L.state.toString(-1) catch unreachable; + const txt = std.mem.concat(gpa, u8, &[_][]const u8{ "repl: ", err_txt }) catch Utils.oomPanic(); + defer gpa.free(txt); + + // must add the sentinel back to pass the data over the wire + const w_sentinel = gpa.allocSentinel(u8, txt.len, 0) catch Utils.oomPanic(); + defer gpa.free(w_sentinel); + std.mem.copyForwards(u8, w_sentinel, txt[0..txt.len]); + + sendNewLogEntry(w_sentinel); + return; +} + fn handleDestroy(_: *mez.RemoteLuaV1, remote_lua: *RemoteLua) void { - server.remote_lua_clients.swapRemove(remote_lua.id); + _ = server.remote_lua_clients.swapRemove(remote_lua.id); + remote_lua.L.deinit(); gpa.destroy(remote_lua); } diff --git a/src/RemoteLuaManager.zig b/src/RemoteLuaManager.zig index 1df4411..e4cedea 100644 --- a/src/RemoteLuaManager.zig +++ b/src/RemoteLuaManager.zig @@ -12,7 +12,7 @@ const server = &@import("main.zig").server; global: *wl.Global, -pub fn init() ?*RemoteLuaManager { +pub fn init() !?*RemoteLuaManager { const self = try gpa.create(RemoteLuaManager); self.global = try wl.Global.create(server.wl_server, mez.RemoteLuaManagerV1, 1, ?*anyopaque, null, bind); @@ -37,10 +37,10 @@ _: ?*anyopaque, .destroy => remote_lua_manager_v1.destroy(), .get_remote => |req| { RemoteLua.create( - remote_lua_manager_v1.getClient(), - remote_lua_manager_v1.getVersion(), - req.id, - ) catch { + remote_lua_manager_v1.getClient(), + remote_lua_manager_v1.getVersion(), + req.id, + ) catch { remote_lua_manager_v1.getClient().postNoMemory(); Utils.oomPanic(); }; diff --git a/src/server.zig b/src/server.zig index e04c8ad..032fbb6 100644 --- a/src/server.zig +++ b/src/server.zig @@ -15,6 +15,7 @@ const Keymap = @import("types/keymap.zig"); const Hook = @import("types/hook.zig"); const Events = @import("types/events.zig"); const RemoteLua = @import("RemoteLua.zig"); +const RemoteLuaManager = @import("RemoteLuaManager.zig"); const gpa = std.heap.c_allocator; const server = &@import("main.zig").server; @@ -25,6 +26,7 @@ renderer: *wlr.Renderer, backend: *wlr.Backend, event_loop: *wl.EventLoop, session: ?*wlr.Session, +remote_lua_manager: ?*RemoteLuaManager, shm: *wlr.Shm, xdg_shell: *wlr.XdgShell, @@ -97,6 +99,7 @@ pub fn init(self: *Server) void { .root = undefined, .seat = undefined, .cursor = undefined, + .remote_lua_manager = RemoteLuaManager.init() catch Utils.oomPanic(), .keymaps = .init(gpa), .hooks = try .initCapacity(gpa, 10), // TODO: choose how many slots to start with .events = try .init(gpa), From 605c4c4a37f38130eb9033161e8442fe4d401131 Mon Sep 17 00:00:00 2001 From: Squibid Date: Sun, 30 Nov 2025 23:02:25 -0500 Subject: [PATCH 4/9] don't reload the runtime config and personal config on remote lua connect --- src/RemoteLua.zig | 15 +++++---- src/lua/lua.zig | 80 +++++++++++++++++++++++++---------------------- 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/src/RemoteLua.zig b/src/RemoteLua.zig index 92143ff..15850c2 100644 --- a/src/RemoteLua.zig +++ b/src/RemoteLua.zig @@ -1,6 +1,7 @@ const RemoteLua = @This(); const std = @import("std"); +const zlua = @import("zlua"); const wayland = @import("wayland"); const Utils = @import("utils.zig"); const Lua = @import("lua/lua.zig"); @@ -12,7 +13,7 @@ const server = &@import("main.zig").server; id: usize, remote_lua_v1: *mez.RemoteLuaV1, -L: Lua, +L: *zlua.Lua, pub fn sendNewLogEntry(str: [*:0]const u8) void { for (server.remote_lua_clients.items) |c| { @@ -28,10 +29,12 @@ pub fn create(client: *wl.Client, version: u32, id: u32) !void { node.* = .{ .remote_lua_v1 = remote_lua_v1, .id = server.remote_lua_clients.items.len, - .L = undefined, + .L = try zlua.Lua.init(gpa), }; - try node.L.init(); errdefer node.L.deinit(); + node.L.openLibs(); + Lua.openLibs(node.L); + try server.remote_lua_clients.append(gpa, node); remote_lua_v1.setHandler(*RemoteLua, handleRequest, handleDestroy, node); @@ -46,14 +49,14 @@ remote: *RemoteLua, .destroy => remote_lua_v1.destroy(), .push_lua => |req| { const chunk = std.mem.sliceTo(req.lua_chunk, 0); - remote.L.state.loadString(chunk) catch catchLuaFail(remote); - remote.L.state.protectedCall(.{}) catch catchLuaFail(remote); + remote.L.loadString(chunk) catch catchLuaFail(remote); + remote.L.protectedCall(.{}) catch catchLuaFail(remote); }, } } fn catchLuaFail(remote: *RemoteLua) void { - const err_txt: []const u8 = remote.L.state.toString(-1) catch unreachable; + const err_txt: []const u8 = remote.L.toString(-1) catch unreachable; const txt = std.mem.concat(gpa, u8, &[_][]const u8{ "repl: ", err_txt }) catch Utils.oomPanic(); defer gpa.free(txt); diff --git a/src/lua/lua.zig b/src/lua/lua.zig index e94388e..1a4892a 100644 --- a/src/lua/lua.zig +++ b/src/lua/lua.zig @@ -45,49 +45,53 @@ fn loadConfigDir(self: *Lua) !void { try self.state.doFile(path); } +pub fn openLibs(self: *zlua.Lua) void { + { + self.newTable(); + defer _ = self.setGlobal("mez"); + { + self.newTable(); + defer _ = self.setField(-2, "path"); + } + { + const fs_funcs = zlua.fnRegsFromType(Fs); + self.newLib(fs_funcs); + self.setField(-2, "fs"); + } + { + const input_funcs = zlua.fnRegsFromType(Input); + self.newLib(input_funcs); + self.setField(-2, "input"); + } + { + const hook_funcs = zlua.fnRegsFromType(Hook); + self.newLib(hook_funcs); + self.setField(-2, "hook"); + } + { + const api_funcs = zlua.fnRegsFromType(Api); + self.newLib(api_funcs); + self.setField(-2, "api"); + } + { + const view_funcs = zlua.fnRegsFromType(View); + self.newLib(view_funcs); + self.setField(-2, "view"); + } + { + const output_funcs = zlua.fnRegsFromType(Output); + self.newLib(output_funcs); + self.setField(-2, "output"); + } + } +} + pub fn init(self: *Lua) !void { self.state = try zlua.Lua.init(gpa); errdefer self.state.deinit(); self.state.openLibs(); - { - self.state.newTable(); - defer _ = self.state.setGlobal("mez"); - { - self.state.newTable(); - defer _ = self.state.setField(-2, "path"); - } - { - const fs_funcs = zlua.fnRegsFromType(Fs); - self.state.newLib(fs_funcs); - self.state.setField(-2, "fs"); - } - { - const input_funcs = zlua.fnRegsFromType(Input); - self.state.newLib(input_funcs); - self.state.setField(-2, "input"); - } - { - const hook_funcs = zlua.fnRegsFromType(Hook); - self.state.newLib(hook_funcs); - self.state.setField(-2, "hook"); - } - { - const api_funcs = zlua.fnRegsFromType(Api); - self.state.newLib(api_funcs); - self.state.setField(-2, "api"); - } - { - const view_funcs = zlua.fnRegsFromType(View); - self.state.newLib(view_funcs); - self.state.setField(-2, "view"); - } - { - const output_funcs = zlua.fnRegsFromType(Output); - self.state.newLib(output_funcs); - self.state.setField(-2, "output"); - } - } + openLibs(self.state); loadRuntimeDir(self) catch |err| { if (err == error.LuaRuntime) { From 1e3187567020045d1edbc39b5af317048bbac744 Mon Sep 17 00:00:00 2001 From: Squibid Date: Sun, 30 Nov 2025 23:34:26 -0500 Subject: [PATCH 5/9] redirect stdout/stderr in remote lua states --- src/RemoteLua.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/RemoteLua.zig b/src/RemoteLua.zig index 15850c2..c8582aa 100644 --- a/src/RemoteLua.zig +++ b/src/RemoteLua.zig @@ -34,6 +34,7 @@ pub fn create(client: *wl.Client, version: u32, id: u32) !void { errdefer node.L.deinit(); node.L.openLibs(); Lua.openLibs(node.L); + // TODO: replace stdout and stderr with buffers we can send to the clients try server.remote_lua_clients.append(gpa, node); From 6fcb13741ad7b2ae2f1909b2773c92bd3bde0926 Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 10 Dec 2025 13:43:12 -0500 Subject: [PATCH 6/9] rework how remote lua clients are stored in the server --- src/RemoteLua.zig | 29 ++++++++++++++++++----------- src/server.zig | 6 ++---- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/RemoteLua.zig b/src/RemoteLua.zig index c8582aa..3f1cd63 100644 --- a/src/RemoteLua.zig +++ b/src/RemoteLua.zig @@ -11,13 +11,16 @@ const mez = wayland.server.zmez; const gpa = std.heap.c_allocator; const server = &@import("main.zig").server; -id: usize, +node: std.DoublyLinkedList.Node, remote_lua_v1: *mez.RemoteLuaV1, L: *zlua.Lua, pub fn sendNewLogEntry(str: [*:0]const u8) void { - for (server.remote_lua_clients.items) |c| { - c.remote_lua_v1.sendNewLogEntry(str); + var node = server.remote_lua_clients.first; + while (node) |n| { + const data: ?*RemoteLua = @fieldParentPtr("node", n); + if (data) |d| d.remote_lua_v1.sendNewLogEntry(str); + node = n.next; } } @@ -28,7 +31,7 @@ pub fn create(client: *wl.Client, version: u32, id: u32) !void { errdefer gpa.destroy(node); node.* = .{ .remote_lua_v1 = remote_lua_v1, - .id = server.remote_lua_clients.items.len, + .node = .{}, .L = try zlua.Lua.init(gpa), }; errdefer node.L.deinit(); @@ -36,7 +39,7 @@ pub fn create(client: *wl.Client, version: u32, id: u32) !void { Lua.openLibs(node.L); // TODO: replace stdout and stderr with buffers we can send to the clients - try server.remote_lua_clients.append(gpa, node); + server.remote_lua_clients.prepend(&node.node); remote_lua_v1.setHandler(*RemoteLua, handleRequest, handleDestroy, node); } @@ -56,6 +59,16 @@ remote: *RemoteLua, } } +fn handleDestroy(_: *mez.RemoteLuaV1, remote_lua: *RemoteLua) void { + if (remote_lua.node.prev) |p| { + if (remote_lua.node.next) |n| n.prev.? = p; + p.next = remote_lua.node.next; + } else server.remote_lua_clients.first = remote_lua.node.next; + + remote_lua.L.deinit(); + gpa.destroy(remote_lua); +} + fn catchLuaFail(remote: *RemoteLua) void { const err_txt: []const u8 = remote.L.toString(-1) catch unreachable; const txt = std.mem.concat(gpa, u8, &[_][]const u8{ "repl: ", err_txt }) catch Utils.oomPanic(); @@ -69,9 +82,3 @@ fn catchLuaFail(remote: *RemoteLua) void { sendNewLogEntry(w_sentinel); return; } - -fn handleDestroy(_: *mez.RemoteLuaV1, remote_lua: *RemoteLua) void { - _ = server.remote_lua_clients.swapRemove(remote_lua.id); - remote_lua.L.deinit(); - gpa.destroy(remote_lua); -} diff --git a/src/server.zig b/src/server.zig index 032fbb6..1843ced 100644 --- a/src/server.zig +++ b/src/server.zig @@ -44,7 +44,7 @@ cursor: Cursor, keymaps: std.AutoHashMap(u64, Keymap), hooks: std.ArrayList(*Hook), events: Events, -remote_lua_clients: std.ArrayList(*RemoteLua), +remote_lua_clients: std.DoublyLinkedList, // Backend listeners new_input: wl.Listener(*wlr.InputDevice) = .init(handleNewInput), @@ -103,7 +103,7 @@ pub fn init(self: *Server) void { .keymaps = .init(gpa), .hooks = try .initCapacity(gpa, 10), // TODO: choose how many slots to start with .events = try .init(gpa), - .remote_lua_clients = try .initCapacity(gpa, 0), + .remote_lua_clients = .{}, }; self.renderer.initServer(wl_server) catch { @@ -138,8 +138,6 @@ pub fn deinit(self: *Server) noreturn { self.new_xdg_popup.link.remove(); self.new_xdg_toplevel_decoration.link.remove(); - self.remote_lua_clients.deinit(gpa); - self.seat.deinit(); self.root.deinit(); self.cursor.deinit(); From 41865d11abe963aa556f2af65833fa1f3b353448 Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 10 Dec 2025 13:44:38 -0500 Subject: [PATCH 7/9] send evaluation of statements to remote lua clients --- src/RemoteLua.zig | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/RemoteLua.zig b/src/RemoteLua.zig index 3f1cd63..44cb07e 100644 --- a/src/RemoteLua.zig +++ b/src/RemoteLua.zig @@ -53,8 +53,26 @@ remote: *RemoteLua, .destroy => remote_lua_v1.destroy(), .push_lua => |req| { const chunk = std.mem.sliceTo(req.lua_chunk, 0); - remote.L.loadString(chunk) catch catchLuaFail(remote); - remote.L.protectedCall(.{}) catch catchLuaFail(remote); + // TODO: this could be a lot smarter, we don't want to add return to a + // statement which already has return infront of it. + const str = std.mem.concat(gpa, u8, &[_][]const u8{ "return ", chunk }) catch { + return catchLuaFail(remote); + }; + + const w_sentinel = gpa.allocSentinel(u8, str.len, 0) catch Utils.oomPanic(); + defer gpa.free(w_sentinel); + std.mem.copyForwards(u8, w_sentinel, str[0..str.len]); + + remote.L.loadString(w_sentinel) catch catchLuaFail(remote); + remote.L.protectedCall(.{ + .results = zlua.mult_return, + }) catch catchLuaFail(remote); + + var i: i32 = 1; + while (i < remote.L.getTop() + 1) : (i += 1) { + sendNewLogEntry(remote.L.toString(-1) catch return catchLuaFail(remote)); + remote.L.pop(-1); + } }, } } @@ -70,7 +88,7 @@ fn handleDestroy(_: *mez.RemoteLuaV1, remote_lua: *RemoteLua) void { } fn catchLuaFail(remote: *RemoteLua) void { - const err_txt: []const u8 = remote.L.toString(-1) catch unreachable; + const err_txt: []const u8 = remote.L.toString(-1) catch "zig error"; const txt = std.mem.concat(gpa, u8, &[_][]const u8{ "repl: ", err_txt }) catch Utils.oomPanic(); defer gpa.free(txt); From 60a8466fe871a05443d817c71788512a80a3a10e Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 10 Dec 2025 14:09:44 -0500 Subject: [PATCH 8/9] copy with sentinel instead of manually adding it --- src/RemoteLua.zig | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/RemoteLua.zig b/src/RemoteLua.zig index 44cb07e..25ddfec 100644 --- a/src/RemoteLua.zig +++ b/src/RemoteLua.zig @@ -55,15 +55,10 @@ remote: *RemoteLua, const chunk = std.mem.sliceTo(req.lua_chunk, 0); // TODO: this could be a lot smarter, we don't want to add return to a // statement which already has return infront of it. - const str = std.mem.concat(gpa, u8, &[_][]const u8{ "return ", chunk }) catch { - return catchLuaFail(remote); - }; + const str = std.mem.concatWithSentinel(gpa, u8, &[_][]const u8{ "return ", chunk }, 0) catch return catchLuaFail(remote); + defer gpa.free(str); - const w_sentinel = gpa.allocSentinel(u8, str.len, 0) catch Utils.oomPanic(); - defer gpa.free(w_sentinel); - std.mem.copyForwards(u8, w_sentinel, str[0..str.len]); - - remote.L.loadString(w_sentinel) catch catchLuaFail(remote); + remote.L.loadString(str) catch catchLuaFail(remote); remote.L.protectedCall(.{ .results = zlua.mult_return, }) catch catchLuaFail(remote); @@ -89,14 +84,9 @@ fn handleDestroy(_: *mez.RemoteLuaV1, remote_lua: *RemoteLua) void { fn catchLuaFail(remote: *RemoteLua) void { const err_txt: []const u8 = remote.L.toString(-1) catch "zig error"; - const txt = std.mem.concat(gpa, u8, &[_][]const u8{ "repl: ", err_txt }) catch Utils.oomPanic(); + const txt = std.mem.concatWithSentinel(gpa, u8, &[_][]const u8{ "repl: ", err_txt }, 0) catch Utils.oomPanic(); defer gpa.free(txt); - // must add the sentinel back to pass the data over the wire - const w_sentinel = gpa.allocSentinel(u8, txt.len, 0) catch Utils.oomPanic(); - defer gpa.free(w_sentinel); - std.mem.copyForwards(u8, w_sentinel, txt[0..txt.len]); - - sendNewLogEntry(w_sentinel); + sendNewLogEntry(txt); return; } From 64713dc0c3136af6592b11f4bb9b4e7c31cd3d78 Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 10 Dec 2025 14:12:14 -0500 Subject: [PATCH 9/9] fix repl evaluations --- src/RemoteLua.zig | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/RemoteLua.zig b/src/RemoteLua.zig index 25ddfec..9764ae9 100644 --- a/src/RemoteLua.zig +++ b/src/RemoteLua.zig @@ -49,24 +49,39 @@ remote_lua_v1: *mez.RemoteLuaV1, request: mez.RemoteLuaV1.Request, remote: *RemoteLua, ) void { + const L = remote.L; switch (request) { .destroy => remote_lua_v1.destroy(), .push_lua => |req| { - const chunk = std.mem.sliceTo(req.lua_chunk, 0); - // TODO: this could be a lot smarter, we don't want to add return to a - // statement which already has return infront of it. - const str = std.mem.concatWithSentinel(gpa, u8, &[_][]const u8{ "return ", chunk }, 0) catch return catchLuaFail(remote); + const chunk: [:0]const u8 = std.mem.sliceTo(req.lua_chunk, 0); + + const str = std.mem.concatWithSentinel(gpa, u8, &[_][]const u8{ + "return ", + chunk, + ";", + }, 0) catch return catchLuaFail(remote); defer gpa.free(str); - remote.L.loadString(str) catch catchLuaFail(remote); - remote.L.protectedCall(.{ - .results = zlua.mult_return, - }) catch catchLuaFail(remote); + zlua.Lua.loadBuffer(L, str, "=repl", zlua.Mode.text) catch { + L.pop(L.getTop()); + L.loadString(chunk) catch { + catchLuaFail(remote); + L.pop(-1); + }; + return; + }; + + L.protectedCall(.{ .results = zlua.mult_return, }) catch { + catchLuaFail(remote); + L.pop(1); + }; var i: i32 = 1; - while (i < remote.L.getTop() + 1) : (i += 1) { - sendNewLogEntry(remote.L.toString(-1) catch return catchLuaFail(remote)); - remote.L.pop(-1); + const nresults = L.getTop(); + while (i <= nresults) : (i += 1) { + // TODO: support lua5.1 and luajit? + sendNewLogEntry(L.toStringEx(i)); + L.pop(-1); } }, } @@ -83,10 +98,6 @@ fn handleDestroy(_: *mez.RemoteLuaV1, remote_lua: *RemoteLua) void { } fn catchLuaFail(remote: *RemoteLua) void { - const err_txt: []const u8 = remote.L.toString(-1) catch "zig error"; - const txt = std.mem.concatWithSentinel(gpa, u8, &[_][]const u8{ "repl: ", err_txt }, 0) catch Utils.oomPanic(); - defer gpa.free(txt); - - sendNewLogEntry(txt); - return; + const err: [:0]const u8 = remote.L.toStringEx(-1); + sendNewLogEntry(std.mem.sliceTo(err, 0)); }