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

@ -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);
}
}