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

This commit is contained in:
Harrison DiAmbrosio 2025-11-25 16:01:38 -05:00 committed by Squibid
parent 2c130539f6
commit 47bcce621d
Signed by: squibid
GPG key ID: BECE5684D3C4005D
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.
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);
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_cmd = b.addRunArtifact(mez);
run_step.dependOn(&run_cmd.step);

View file

@ -35,9 +35,17 @@ mez.input.add_keymap("alt", "q", {
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
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

View file

@ -75,7 +75,7 @@ pub fn processCursorMotion(self: *Cursor, time_msec: u32) void {
switch (self.mode) {
.passthrough => {
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.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);
if (server.seat.focused_view) |view| {
server.seat.focusView(view);
server.root.focusView(view);
view.setFocused();
}
switch (event.state) {

View file

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

View file

@ -9,6 +9,7 @@ const Fs = @import("fs.zig");
const Input = @import("input.zig");
const Api = @import("api.zig");
const Hook = @import("hook.zig");
const View = @import("view.zig");
const gpa = std.heap.c_allocator;
@ -75,6 +76,11 @@ pub fn init(self: *Lua) !void {
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");
}
}
loadRuntimeDir(self) catch |err| {
@ -82,6 +88,7 @@ pub fn init(self: *Lua) !void {
std.log.warn("{s}", .{try self.state.toString(-1)});
}
};
loadConfigDir(self) catch |err| {
if (err == error.LuaRuntime) {
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,
wlr_output: *wlr.Output,
state: wlr.Output.State,
scene_output: *wlr.SceneOutput,
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),
// 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();
const output = try gpa.create(Output);
@ -28,25 +29,51 @@ pub fn create(wlr_output: *wlr.Output) *Output {
output.* = .{
.focused = false,
.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.request_state.add(&output.request_state);
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;
}
// Conflicting name with destroy listener
// Should probably add _listner as a postfix to listeners
//
// pub fn destroy(output: *Output) void {
// gpa.free(output);
// }
pub fn deinit(output: *Output) void {
output.frame.link.remove();
output.request_state.link.remove();
output.destroy.remove();
output.state.finish();
output.wlr_output.destroy();
gpa.free(output);
}
// --------- WlrOutput Event Handlers ---------
fn handleRequestState(
listener: *wl.Listener(*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 std = @import("std");
@ -18,8 +21,6 @@ scene_output_layout: *wlr.SceneOutputLayout,
output_layout: *wlr.OutputLayout,
views: std.HashMap(u64, *View, std.hash_map.AutoContext(u64), 80),
pub fn init(self: *Root) void {
std.log.info("Creating root of mezzaluna\n", .{});
@ -36,22 +37,36 @@ pub fn init(self: *Root) void {
.output_layout = output_layout,
.xdg_toplevel_decoration_manager = try wlr.XdgDecorationManagerV1.create(server.wl_server),
.scene_output_layout = try scene.attachOutputLayout(output_layout),
.views = .init(gpa)
};
}
pub fn deinit(self: *Root) void {
var views_it = self.views.iterator();
while(views_it.next()) |entry| {
entry.value_ptr.*.deinit();
}
var it = self.scene.tree.children.iterator(.forward);
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.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 {
errdefer Utils.oomPanic();
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;
}
// 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(
_: *wl.Listener(*wlr.Seat.event.RequestSetCursor),
event: *wlr.Seat.event.RequestSetCursor,

View file

@ -161,6 +161,7 @@ fn handleNewInput(
},
}
// We should really only set true capabilities
server.seat.wlr_seat.setCapabilities(.{
.pointer = true,
.keyboard = true,
@ -171,30 +172,13 @@ fn handleNewOutput(
_: *wl.Listener(*wlr.Output),
wlr_output: *wlr.Output
) void {
std.log.info("Handling a new output - {s}", .{wlr_output.name});
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);
_ = Output.init(wlr_output);
}
fn handleNewXdgToplevel(
_: *wl.Listener(*wlr.XdgToplevel),
xdg_toplevel: *wlr.XdgToplevel
) void {
std.log.debug("Request for new toplevel", .{});
_ = View.initFromTopLevel(xdg_toplevel);
}
@ -203,7 +187,7 @@ fn handleNewXdgToplevelDecoration(
decoration: *wlr.XdgToplevelDecorationV1
) void {
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 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);
self.* = .{
.xdg_toplevel = xdg_toplevel,
.focused = false,
.scene_tree = undefined,
.xdg_toplevel_decoration = null,
.mapped = false,
.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);
// Add new Toplevel to focused output instead of some random shit
// This is where we find out where to tile the widow, but not NOW
// We need lua for that
// self.scene_tree = try server.root.workspaces.items[0].createSceneXdgSurface(xdg_toplevel.base);
// Add new Toplevel to root of the tree
// Later add to spesified output
self.scene_tree = try server.root.scene.tree.createSceneXdgSurface(xdg_toplevel.base);
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.events.new_popup.add(&self.new_popup);
try server.root.views.put(self.id, self);
return self;
}
@ -90,9 +87,25 @@ pub fn deinit(self: *View) void {
self.request_resize.link.remove();
}
// Handle borders to appropriate colros make necessary notifications
pub fn setFocus(self: *View, focus: bool) void {
self.focused = focus;
pub fn setFocused(self: *View) void {
if(server.seat.focused_view) |prev_view| {
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 ---------
@ -159,8 +172,6 @@ fn handleDestroy(listener: *wl.Listener(void)) void {
view.scene_tree.node.destroy();
// Destroy popups
_ = server.root.views.remove(view.id);
gpa.destroy(view);
}
@ -169,7 +180,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
if (view.xdg_toplevel.base.initial_commit) {
_ = view.xdg_toplevel.setSize(640, 360); // 0,0 means "you decide the size"
view.setSize(640, 360);
}
}