diff --git a/build.zig b/build.zig index e1f5cb9..53166df 100644 --- a/build.zig +++ b/build.zig @@ -27,7 +27,9 @@ pub fn build(b: *std.Build) void { scanner.addSystemProtocol("stable/tablet/tablet-v2.xml"); scanner.addSystemProtocol("unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"); scanner.addCustomProtocol(b.path("protocols/wlr-layer-shell-unstable-v1.xml")); + scanner.addCustomProtocol(b.path("protocols/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/protocols/mez-remote-lua-unstable-v1.xml b/protocols/mez-remote-lua-unstable-v1.xml new file mode 100644 index 0000000..44605af --- /dev/null +++ b/protocols/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. + + + + + diff --git a/src/RemoteLua.zig b/src/RemoteLua.zig new file mode 100644 index 0000000..fadb433 --- /dev/null +++ b/src/RemoteLua.zig @@ -0,0 +1,103 @@ +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"); +const wl = wayland.server.wl; +const mez = wayland.server.zmez; + +const gpa = std.heap.c_allocator; +const server = &@import("main.zig").server; + +node: std.DoublyLinkedList.Node, +remote_lua_v1: *mez.RemoteLuaV1, +L: *zlua.Lua, + +pub fn sendNewLogEntry(str: [*:0]const u8) void { + 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; + } +} + +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, + .node = .{}, + .L = try zlua.Lua.init(gpa), + }; + errdefer node.L.deinit(); + node.L.openLibs(); + Lua.openLibs(node.L); + // TODO: replace stdout and stderr with buffers we can send to the clients + + server.remote_lua_clients.prepend(&node.node); + + remote_lua_v1.setHandler(*RemoteLua, handleRequest, handleDestroy, node); +} + +fn handleRequest( +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: [: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); + + 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; + const nresults = L.getTop(); + while (i <= nresults) : (i += 1) { + // TODO: support lua5.1 and luajit? + sendNewLogEntry(L.toStringEx(i)); + L.pop(-1); + } + }, + } +} + +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: [:0]const u8 = remote.L.toStringEx(-1); + sendNewLogEntry(std.mem.sliceTo(err, 0)); +} diff --git a/src/RemoteLuaManager.zig b/src/RemoteLuaManager.zig new file mode 100644 index 0000000..b7f6198 --- /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 2886340..bc31580 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -16,6 +16,8 @@ const Keymap = @import("types/Keymap.zig"); const Hook = @import("types/Hook.zig"); const Events = @import("types/Events.zig"); const Popup = @import("Popup.zig"); +const RemoteLua = @import("RemoteLua.zig"); +const RemoteLuaManager = @import("RemoteLuaManager.zig"); const gpa = std.heap.c_allocator; const server = &@import("main.zig").server; @@ -26,6 +28,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, @@ -44,6 +47,7 @@ cursor: Cursor, keymaps: std.AutoHashMap(u64, Keymap), hooks: std.ArrayList(*Hook), events: Events, +remote_lua_clients: std.DoublyLinkedList, // Backend listeners new_input: wl.Listener(*wlr.InputDevice) = .init(handleNewInput), @@ -98,9 +102,11 @@ 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), + .remote_lua_clients = .{}, }; self.renderer.initServer(wl_server) catch { diff --git a/src/lua/Lua.zig b/src/lua/Lua.zig index 89510dc..aabb353 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) { diff --git a/src/types/Hook.zig b/src/types/Hook.zig index df0071c..48a8e37 100644 --- a/src/types/Hook.zig +++ b/src/types/Hook.zig @@ -8,6 +8,7 @@ const wlr = @import("wlroots"); const zlua = @import("zlua"); const Event = @import("Events.zig"); +const RemoteLua = @import("../RemoteLua.zig"); const Lua = &@import("../main.zig").lua; events: std.ArrayList([]const u8), // a list of events @@ -39,7 +40,7 @@ pub fn callback(self: *const Hook, args: anytype) void { } Lua.state.protectedCall(.{ .args = i }) catch { - // TODO: add a callback to remote lua when that gets merged + RemoteLua.sendNewLogEntry(Lua.state.toString(-1) catch unreachable); }; Lua.state.pop(-1); }