lua position, size, focus and z-index (not really)

This commit is contained in:
Harrison DiAmbrosio 2025-11-25 16:01:38 -05:00
parent b45544c97a
commit 1eda05b7b3
14 changed files with 236 additions and 98 deletions

View file

@ -2,4 +2,4 @@
A utensil for chopping herbs, vegetables, or pizza, with a large semicircular blade and a handle at each end. A utensil for chopping herbs, vegetables, or pizza, with a large semicircular blade and a handle at each end.
The idea is that Mezzaluna takes care of the hardwork while leaving configuration, tiling behaviour and general exstensability to be done with easy to write Lua. The idea is that Mezzaluna takes care of the hardwork while leaving configuration, tiling behaviour and general exstensability to be done with easy to write, lovable Lua.

View file

@ -77,6 +77,18 @@ pub fn build(b: *std.Build) void {
b.installArtifact(mez); b.installArtifact(mez);
const exe_check = b.addExecutable(.{
.name = "mez",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
})
});
const check = b.step("check", "check if mez compiles");
check.dependOn(&exe_check.step);
const run_step = b.step("run", "Run the app"); const run_step = b.step("run", "Run the app");
const run_cmd = b.addRunArtifact(mez); const run_cmd = b.addRunArtifact(mez);
run_step.dependOn(&run_cmd.step); run_step.dependOn(&run_cmd.step);

View file

@ -35,9 +35,17 @@ mez.input.add_keymap("alt", "q", {
end end
}) })
mez.input.add_keymap("alt", "v", {
press = function ()
local view = mez.view.get_focused_id()
mez.view.set_position(view, 100, 100)
mez.view.set_size(view, 100, 100)
end
})
for i = 1, 12 do for i = 1, 12 do
mez.input.add_keymap("ctrl|alt", "XF86Switch_VT_"..i, { mez.input.add_keymap("ctrl|alt", "XF86Switch_VT_"..i, {
press = function() mez.api.chvt(i) end press = function() mez.api.change_vt(i) end
}) })
end end

View file

@ -75,7 +75,7 @@ pub fn processCursorMotion(self: *Cursor, time_msec: u32) void {
switch (self.mode) { switch (self.mode) {
.passthrough => { .passthrough => {
if (server.root.viewAt(self.wlr_cursor.x, self.wlr_cursor.y)) |res| { if (server.root.viewAt(self.wlr_cursor.x, self.wlr_cursor.y)) |res| {
server.seat.focusView(res.view); res.view.setFocused();
server.seat.wlr_seat.pointerNotifyEnter(res.surface, res.sx, res.sy); server.seat.wlr_seat.pointerNotifyEnter(res.surface, res.sx, res.sy);
server.seat.wlr_seat.pointerNotifyMotion(time_msec, res.sx, res.sy); server.seat.wlr_seat.pointerNotifyMotion(time_msec, res.sx, res.sy);
@ -134,8 +134,7 @@ fn handleButton(
_ = server.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state); _ = server.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
if (server.seat.focused_view) |view| { if (server.seat.focused_view) |view| {
server.seat.focusView(view); view.setFocused();
server.root.focusView(view);
} }
switch (event.state) { switch (event.state) {

View file

@ -59,7 +59,7 @@ pub fn exit(L: *zlua.Lua) i32 {
return 0; return 0;
} }
pub fn chvt(L: *zlua.Lua) i32 { pub fn change_vt(L: *zlua.Lua) i32 {
L.checkType(1, .number); L.checkType(1, .number);
const f = L.toNumber(-1) catch unreachable; const f = L.toNumber(-1) catch unreachable;
const n: u32 = @intFromFloat(f); const n: u32 = @intFromFloat(f);

View file

@ -8,6 +8,7 @@ const Bridge = @import("bridge.zig");
const Fs = @import("fs.zig"); const Fs = @import("fs.zig");
const Input = @import("input.zig"); const Input = @import("input.zig");
const Api = @import("api.zig"); const Api = @import("api.zig");
const View = @import("view.zig");
const gpa = std.heap.c_allocator; const gpa = std.heap.c_allocator;
@ -69,6 +70,11 @@ pub fn init(self: *Lua) !void {
self.state.newLib(api_funcs); self.state.newLib(api_funcs);
self.state.setField(-2, "api"); self.state.setField(-2, "api");
} }
{
const view_funcs = zlua.fnRegsFromType(View);
self.state.newLib(view_funcs);
self.state.setField(-2, "view");
}
} }
loadRuntimeDir(self) catch |err| { loadRuntimeDir(self) catch |err| {
@ -76,6 +82,7 @@ pub fn init(self: *Lua) !void {
std.log.warn("{s}", .{try self.state.toString(-1)}); std.log.warn("{s}", .{try self.state.toString(-1)});
} }
}; };
loadConfigDir(self) catch |err| { loadConfigDir(self) catch |err| {
if (err == error.LuaRuntime) { if (err == error.LuaRuntime) {
std.log.warn("{s}", .{try self.state.toString(-1)}); std.log.warn("{s}", .{try self.state.toString(-1)});

116
src/lua/view.zig Normal file
View file

@ -0,0 +1,116 @@
const std = @import("std");
const zlua = @import("zlua");
const wlr = @import("wlroots");
const View = @import("../view.zig");
const gpa = std.heap.c_allocator;
const server = &@import("../main.zig").server;
pub fn get_all_ids(L: *zlua.Lua) i32 {
var it = server.root.scene.tree.children.iterator(.forward);
var index: usize = 1;
L.newTable();
while(it.next()) |node| : (index += 1) {
if(node.data == null) continue;
const view = @as(*View, @ptrCast(@alignCast(node.data.?)));
L.pushInteger(@intCast(index));
L.pushInteger(@intCast(view.id));
L.setTable(1);
}
return 1;
}
pub fn get_focused_id(L: *zlua.Lua) i32 {
if(server.seat.focused_view) |view| {
_ = L.pushNumber(@floatFromInt(view.id));
return 1;
}
return 0;
}
pub fn set_position(L: *zlua.Lua) i32 {
const nargs: i32 = L.getTop();
std.log.debug("Starting view reposition", .{});
if (nargs != 3) {
L.raiseErrorStr("Expected 3 arguments, found {d}", .{nargs});
return 0;
}
for (1..@intCast(nargs + 1)) |i| {
L.checkType(@intCast(i), .number);
}
const view_id: u64 = @as(u64, @intCast(L.toInteger(1) catch unreachable));
const x: i32 = @as(i32, @intCast(L.toInteger(2) catch unreachable));
const y: i32 = @as(i32, @intCast(L.toInteger(3) catch unreachable));
const view = server.root.viewById(view_id);
if(view == null) {
L.raiseErrorStr("View with id {d} does not exist", .{view_id});
return 0;
}
view.?.setPosition(x, y);
return 0;
}
pub fn set_size(L: *zlua.Lua) i32 {
const nargs: i32 = L.getTop();
if (nargs != 3) {
L.raiseErrorStr("Expected 3 arguments, found {d}", .{nargs});
return 0;
}
for (1..@intCast(nargs + 1)) |i| {
L.checkType(@intCast(i), .number);
}
const view_id: u64 = @as(u64, @intCast(L.toInteger(1) catch unreachable));
const width: i32 = @as(i32, @intCast(L.toInteger(2) catch unreachable));
const height: i32 = @as(i32, @intCast(L.toInteger(3) catch unreachable));
const view = server.root.viewById(view_id);
if(view == null) {
L.raiseErrorStr("View with id {d} does not exist", .{view_id});
return 0;
}
view.?.setSize(width, height);
return 0;
}
pub fn raise_to_top(L: *zlua.Lua) i32 {
const nargs: i32 = L.getTop();
if(nargs != 1) {
L.raiseErrorStr("Expected 1 arguments, found {d}", .{nargs});
return 0;
}
L.checkType(1, .number);
const view_id: u64 = @intCast(L.toInteger(1) catch unreachable);
const view = server.root.viewById(view_id);
if(view == null) {
L.raiseErrorStr("View with id {d} does not exist", .{view_id});
return 0;
}
view.?.raiseToTop();
return 0;
}

View file

@ -13,6 +13,7 @@ const server = &@import("main.zig").server;
focused: bool, focused: bool,
wlr_output: *wlr.Output, wlr_output: *wlr.Output,
state: wlr.Output.State,
scene_output: *wlr.SceneOutput, scene_output: *wlr.SceneOutput,
frame: wl.Listener(*wlr.Output) = .init(handleFrame), frame: wl.Listener(*wlr.Output) = .init(handleFrame),
@ -20,7 +21,7 @@ request_state: wl.Listener(*wlr.Output.event.RequestState) = .init(handleRequest
destroy: wl.Listener(*wlr.Output) = .init(handleDestroy), destroy: wl.Listener(*wlr.Output) = .init(handleDestroy),
// The wlr.Output should be destroyed by the caller on failure to trigger cleanup. // The wlr.Output should be destroyed by the caller on failure to trigger cleanup.
pub fn create(wlr_output: *wlr.Output) *Output { pub fn init(wlr_output: *wlr.Output) ?*Output {
errdefer Utils.oomPanic(); errdefer Utils.oomPanic();
const output = try gpa.create(Output); const output = try gpa.create(Output);
@ -28,25 +29,51 @@ pub fn create(wlr_output: *wlr.Output) *Output {
output.* = .{ output.* = .{
.focused = false, .focused = false,
.wlr_output = wlr_output, .wlr_output = wlr_output,
.scene_output = try server.root.scene.createSceneOutput(wlr_output) .scene_output = try server.root.scene.createSceneOutput(wlr_output),
.state = wlr.Output.State.init()
}; };
wlr_output.events.frame.add(&output.frame); wlr_output.events.frame.add(&output.frame);
wlr_output.events.request_state.add(&output.request_state); wlr_output.events.request_state.add(&output.request_state);
wlr_output.events.destroy.add(&output.destroy); wlr_output.events.destroy.add(&output.destroy);
std.log.debug("adding output: {s}", .{output.wlr_output.name}); errdefer deinit(output);
if(!wlr_output.initRender(server.allocator, server.renderer)) {
std.log.err("Unable to start output {s}", .{wlr_output.name});
return null;
}
output.state.setEnabled(true);
if (wlr_output.preferredMode()) |mode| {
output.state.setMode(mode);
}
if(!wlr_output.commitState(&output.state)) {
std.log.err("Unable to commit state to output {s}", .{wlr_output.name});
return null;
}
server.root.addOutput(output);
return output; return output;
} }
// Conflicting name with destroy listener pub fn deinit(output: *Output) void {
// Should probably add _listner as a postfix to listeners output.frame.link.remove();
// output.request_state.link.remove();
// pub fn destroy(output: *Output) void { output.destroy.remove();
// gpa.free(output);
// }
output.state.finish();
output.wlr_output.destroy();
gpa.free(output);
}
// --------- WlrOutput Event Handlers ---------
fn handleRequestState( fn handleRequestState(
listener: *wl.Listener(*wlr.Output.event.RequestState), listener: *wl.Listener(*wlr.Output.event.RequestState),
event: *wlr.Output.event.RequestState, event: *wlr.Output.event.RequestState,

View file

@ -1,3 +1,6 @@
/// The root of Mezzaluna is, you guessed it, the root of many of the systems mez needs:
/// - Managing outputs
/// -
const Root = @This(); const Root = @This();
const std = @import("std"); const std = @import("std");
@ -18,8 +21,6 @@ scene_output_layout: *wlr.SceneOutputLayout,
output_layout: *wlr.OutputLayout, output_layout: *wlr.OutputLayout,
views: std.HashMap(u64, *View, std.hash_map.AutoContext(u64), 80),
pub fn init(self: *Root) void { pub fn init(self: *Root) void {
std.log.info("Creating root of mezzaluna\n", .{}); std.log.info("Creating root of mezzaluna\n", .{});
@ -36,22 +37,36 @@ pub fn init(self: *Root) void {
.output_layout = output_layout, .output_layout = output_layout,
.xdg_toplevel_decoration_manager = try wlr.XdgDecorationManagerV1.create(server.wl_server), .xdg_toplevel_decoration_manager = try wlr.XdgDecorationManagerV1.create(server.wl_server),
.scene_output_layout = try scene.attachOutputLayout(output_layout), .scene_output_layout = try scene.attachOutputLayout(output_layout),
.views = .init(gpa)
}; };
} }
pub fn deinit(self: *Root) void { pub fn deinit(self: *Root) void {
var views_it = self.views.iterator(); var it = self.scene.tree.children.iterator(.forward);
while(views_it.next()) |entry| {
entry.value_ptr.*.deinit();
}
self.views.deinit(); while(it.next()) |node| {
if(node.data == null) continue;
const view: *View = @ptrCast(@alignCast(node.data.?));
view.deinit();
}
self.output_layout.destroy(); self.output_layout.destroy();
self.scene.tree.node.destroy(); self.scene.tree.node.destroy();
} }
pub fn viewById(self: *Root, id: u64) ?*View {
var it = self.scene.tree.children.iterator(.forward);
while(it.next()) |node| {
if(node.data == null) continue;
const view: *View = @as(*View, @ptrCast(@alignCast(node.data.?)));
if(view.id == id) return view;
}
return null;
}
pub fn addOutput(self: *Root, new_output: *Output) void { pub fn addOutput(self: *Root, new_output: *Output) void {
errdefer Utils.oomPanic(); errdefer Utils.oomPanic();
const layout_output = try self.output_layout.addAuto(new_output.wlr_output); const layout_output = try self.output_layout.addAuto(new_output.wlr_output);

View file

@ -73,15 +73,6 @@ pub fn focusOutput(self: *Seat, output: *Output) void {
self.focused_output = output; self.focused_output = output;
} }
// TODO: Should focusing a view, automaticall focus the output containing it
pub fn focusView(self: *Seat, view: *View) void {
if(self.focused_view) |prev_view| {
prev_view.setFocus(false);
}
self.focused_view = view;
}
fn handleRequestSetCursor( fn handleRequestSetCursor(
_: *wl.Listener(*wlr.Seat.event.RequestSetCursor), _: *wl.Listener(*wlr.Seat.event.RequestSetCursor),
event: *wlr.Seat.event.RequestSetCursor, event: *wlr.Seat.event.RequestSetCursor,

View file

@ -153,6 +153,7 @@ fn handleNewInput(
}, },
} }
// We should really only set true capabilities
server.seat.wlr_seat.setCapabilities(.{ server.seat.wlr_seat.setCapabilities(.{
.pointer = true, .pointer = true,
.keyboard = true, .keyboard = true,
@ -163,30 +164,13 @@ fn handleNewOutput(
_: *wl.Listener(*wlr.Output), _: *wl.Listener(*wlr.Output),
wlr_output: *wlr.Output wlr_output: *wlr.Output
) void { ) void {
std.log.info("Handling a new output - {s}", .{wlr_output.name}); _ = Output.init(wlr_output);
if (!wlr_output.initRender(server.allocator, server.renderer)) return;
var state = wlr.Output.State.init();
defer state.finish();
state.setEnabled(true);
if (wlr_output.preferredMode()) |mode| {
state.setMode(mode);
}
if (!wlr_output.commitState(&state)) return;
const new_output = Output.create(wlr_output);
server.root.addOutput(new_output);
} }
fn handleNewXdgToplevel( fn handleNewXdgToplevel(
_: *wl.Listener(*wlr.XdgToplevel), _: *wl.Listener(*wlr.XdgToplevel),
xdg_toplevel: *wlr.XdgToplevel xdg_toplevel: *wlr.XdgToplevel
) void { ) void {
std.log.debug("Request for new toplevel", .{});
_ = View.initFromTopLevel(xdg_toplevel); _ = View.initFromTopLevel(xdg_toplevel);
} }
@ -195,7 +179,7 @@ fn handleNewXdgToplevelDecoration(
decoration: *wlr.XdgToplevelDecorationV1 decoration: *wlr.XdgToplevelDecorationV1
) void { ) void {
std.log.debug("Request for decorations", .{}); std.log.debug("Request for decorations", .{});
if(server.root.views.get(@intFromPtr(decoration.toplevel))) |view| { if(server.root.viewById(@intFromPtr(decoration.toplevel))) |view| {
view.xdg_toplevel_decoration = decoration; view.xdg_toplevel_decoration = decoration;
} }
} }

View file

@ -1,32 +0,0 @@
const Tag = @This();
const std = @import("std");
const wl = @import("wayland").server.wl;
const wlr = @import("wlroots");
const Output = @import("output.zig");
const View = @import("view.zig");
const Utils = @import("utils.zig");
const server = @import("main.zig").server;
const gpa = std.heap.c_allocator;
output: *Output,
scene_tree: *wlr.SceneTree,
views: std.ArrayList(*View),
pub fn init(output: *Output) Tag {
errdefer Utils.oomPanic();
return .{
.output = output,
.scene_tree = try server.root.scene.tree.createSceneTree(),
.views = .initCapacity(gpa, 2), // Probably shouldn't be a magic number
};
}
pub fn deinit(self: *Tag) void {
for(self.views.items) |view| {
view
}
}

View file

@ -51,20 +51,19 @@ pub fn initFromTopLevel(xdg_toplevel: *wlr.XdgToplevel) *View {
errdefer gpa.destroy(self); errdefer gpa.destroy(self);
self.* = .{ self.* = .{
.xdg_toplevel = xdg_toplevel,
.focused = false, .focused = false,
.scene_tree = undefined,
.xdg_toplevel_decoration = null,
.mapped = false, .mapped = false,
.id = @intFromPtr(xdg_toplevel), .id = @intFromPtr(xdg_toplevel),
.xdg_toplevel = xdg_toplevel,
.scene_tree = undefined,
.xdg_toplevel_decoration = null,
}; };
self.xdg_toplevel.base.surface.events.unmap.add(&self.unmap); self.xdg_toplevel.base.surface.events.unmap.add(&self.unmap);
// Add new Toplevel to focused output instead of some random shit // Add new Toplevel to root of the tree
// This is where we find out where to tile the widow, but not NOW // Later add to spesified output
// We need lua for that
// self.scene_tree = try server.root.workspaces.items[0].createSceneXdgSurface(xdg_toplevel.base);
self.scene_tree = try server.root.scene.tree.createSceneXdgSurface(xdg_toplevel.base); self.scene_tree = try server.root.scene.tree.createSceneXdgSurface(xdg_toplevel.base);
self.scene_tree.node.data = self; self.scene_tree.node.data = self;
@ -75,8 +74,6 @@ pub fn initFromTopLevel(xdg_toplevel: *wlr.XdgToplevel) *View {
self.xdg_toplevel.base.surface.events.commit.add(&self.commit); self.xdg_toplevel.base.surface.events.commit.add(&self.commit);
self.xdg_toplevel.base.events.new_popup.add(&self.new_popup); self.xdg_toplevel.base.events.new_popup.add(&self.new_popup);
try server.root.views.put(self.id, self);
return self; return self;
} }
@ -90,9 +87,25 @@ pub fn deinit(self: *View) void {
self.request_resize.link.remove(); self.request_resize.link.remove();
} }
// Handle borders to appropriate colros make necessary notifications pub fn setFocused(self: *View) void {
pub fn setFocus(self: *View, focus: bool) void { if(server.seat.focused_view) |prev_view| {
self.focused = focus; prev_view.focused = false;
}
server.seat.focused_view = self;
self.focused = true;
}
pub fn raiseToTop(self: *View) void {
self.scene_tree.node.raiseToTop();
}
pub fn setPosition(self: *View, x: i32, y: i32) void {
self.scene_tree.node.setPosition(x, y);
}
pub fn setSize(self: *View, width: i32, height: i32) void {
// This returns a configure serial for verifying the configure
_ = self.xdg_toplevel.setSize(width, height);
} }
// --------- XdgTopLevel event handlers --------- // --------- XdgTopLevel event handlers ---------
@ -155,8 +168,6 @@ fn handleDestroy(listener: *wl.Listener(void)) void {
view.scene_tree.node.destroy(); view.scene_tree.node.destroy();
// Destroy popups // Destroy popups
_ = server.root.views.remove(view.id);
gpa.destroy(view); gpa.destroy(view);
} }
@ -165,7 +176,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
// On the first commit, send a configure to tell the client it can proceed // On the first commit, send a configure to tell the client it can proceed
if (view.xdg_toplevel.base.initial_commit) { if (view.xdg_toplevel.base.initial_commit) {
_ = view.xdg_toplevel.setSize(640, 360); // 0,0 means "you decide the size" view.setSize(640, 360);
} }
} }