cleaning up code, adding support for workspaces, and handling errors

This commit is contained in:
Harrison DiAmbrosio 2025-10-22 23:40:19 -04:00
parent 6bfebb0e37
commit 609ee42d66
10 changed files with 204 additions and 137 deletions

20
.vscode/launch.json vendored
View file

@ -1,20 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "(lldb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/zig-out/bin/mez",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"internalConsoleOptions": "openOnSessionStart",
"MIMode": "lldb",
"MIDebuggerPath": "/bin/lldb",
"preLaunchTask": "build",
}
]
}

15
.vscode/tasks.json vendored
View file

@ -1,15 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "zig build",
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View file

@ -1,3 +1,5 @@
# Mezzaluna
WIP wayland compositor.
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.

View file

@ -19,11 +19,12 @@ frame: wl.Listener(*wlr.Cursor) = .init(handleFrame),
hold_begin: wl.Listener(*wlr.Pointer.event.HoldBegin) = .init(handleHoldBegin),
hold_end: wl.Listener(*wlr.Pointer.event.HoldEnd) = .init(handleHoldEnd),
cursor_mode: enum { passthrough, move, resize } = .passthrough,
mode: enum { passthrough, move, resize } = .passthrough,
grabbed_view: ?*View = null,
grab_x: f64 = 0,
grab_y: f64 = 0,
grab_box: wlr.Box = undefined,
resize_edges: wlr.Edges = .{},
pub fn init(self: *Cursor) !void {
self.* = .{
@ -56,15 +57,63 @@ pub fn deinit(self: *Cursor) void {
}
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| {
std.log.debug("we found a view", .{});
server.seat.wlr_seat.pointerNotifyEnter(res.surface, res.sx, res.sy);
server.seat.wlr_seat.pointerNotifyMotion(time_msec, res.sx, res.sy);
} else {
std.log.debug("no view found", .{});
self.wlr_cursor.setXcursor(self.x_cursor_manager, "default");
server.seat.wlr_seat.pointerClearFocus();
}
},
.move => {
const view = self.grabbed_view.?;
// Should we modify the XdgSurface geometry directly???
view.geometry.x = @as(i32, @intFromFloat(self.wlr_cursor.x - self.grab_x));
view.geometry.y = @as(i32, @intFromFloat(self.wlr_cursor.y - self.grab_y));
view.scene_tree.node.setPosition(view.geometry.x, view.geometry.y);
},
.resize => {
// Fix this resize
const view = self.grabbed_view.?;
const border_x = @as(i32, @intFromFloat(self.wlr_cursor.x - self.grab_x));
const border_y = @as(i32, @intFromFloat(self.wlr_cursor.y - self.grab_y));
var new_left = self.grab_box.x;
var new_right = self.grab_box.x + self.grab_box.width;
var new_top = self.grab_box.y;
var new_bottom = self.grab_box.y + self.grab_box.height;
if (self.resize_edges.top) {
new_top = border_y;
if (new_top >= new_bottom)
new_top = new_bottom - 1;
} else if (self.resize_edges.bottom) {
new_bottom = border_y;
if (new_bottom <= new_top)
new_bottom = new_top + 1;
}
if (self.resize_edges.left) {
new_left = border_x;
if (new_left >= new_right)
new_left = new_right - 1;
} else if (self.resize_edges.right) {
new_right = border_x;
if (new_right <= new_left)
new_right = new_left + 1;
}
// view.x = new_left - view.xdg_toplevel.base.geometry.x;
// view.y = new_top - view.xdg_toplevel.base.geometry.y;
view.scene_tree.node.setPosition(view.geometry.x, view.geometry.y);
const new_width = new_right - new_left;
const new_height = new_bottom - new_top;
_ = view.xdg_toplevel.setSize(new_width, new_height);
},
}
}
// --------- WLR Cursor event handlers ---------
@ -88,10 +137,21 @@ fn handleButton(
_: *wl.Listener(*wlr.Pointer.event.Button),
event: *wlr.Pointer.event.Button,
) void {
switch (event.state) {
.pressed => {
_ = server.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
if (server.root.viewAt(server.cursor.wlr_cursor.x, server.cursor.wlr_cursor.y)) |res| {
server.root.focusView(res.view);
}
},
.released => {
std.log.debug("Button released", .{});
server.cursor.mode = .passthrough;
},
else => {
std.log.err("Unexpected button state", .{});
}
}
}
fn handleHoldBegin(

View file

@ -1,15 +1,14 @@
const Output = @This();
const std = @import("std");
const posix = std.posix;
const gpa = std.heap.c_allocator;
const server = &@import("main.zig").server;
const wl = @import("wayland").server.wl;
const wlr = @import("wlroots");
const std = @import("std");
const Server = @import("server.zig");
const posix = std.posix;
const gpa = std.heap.c_allocator;
const server = &@import("main.zig").server;
wlr_output: *wlr.Output,
scene_output: *wlr.SceneOutput,

View file

@ -6,6 +6,7 @@ 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;
@ -15,11 +16,14 @@ scene_output_layout: *wlr.SceneOutputLayout,
output_layout: *wlr.OutputLayout,
all_views: std.ArrayList(*View),
views: std.ArrayList(View) = undefined,
workspaces: std.ArrayList(*wlr.SceneTree) = undefined,
pub fn init(self: *Root) !void {
pub fn init(self: *Root) void {
std.log.info("Creating root of mezzaluna\n", .{});
errdefer Utils.oomPanic();
const output_layout = try wlr.OutputLayout.create(server.wl_server);
errdefer output_layout.destroy();
@ -30,21 +34,28 @@ pub fn init(self: *Root) !void {
.scene = scene,
.output_layout = output_layout,
.scene_output_layout = try scene.attachOutputLayout(output_layout),
.all_views = try .initCapacity(gpa, 10),
};
self.views = std.ArrayList(View).initCapacity(gpa, 10); // Should consider number better, prolly won't matter that much though
// Even though I would never use a changing amount of workspaces, opens more extensibility
self.workspaces = std.ArrayList(*wlr.SceneTree).initCapacity(gpa, 10); // TODO: change to a configured number of workspaces
// TODO: Make configurable
for(0..9) |_| {
self.workspaces.append(gpa, try self.scene.tree.createSceneTree());
}
}
pub fn deinit(self: *Root) void {
self.workspaces.deinit(gpa);
self.views.deinit(gpa);
self.output_layout.destroy();
self.scene.tree.node.destroy();
}
pub fn addOutput(self: *Root, new_output: *Output) void {
_ = self.output_layout.addAuto(new_output.wlr_output) catch {
std.log.err("failed to add new output to output layout\n", .{});
return;
};
_ = self.output_layout.addAuto(new_output.wlr_output) catch Utils.oomPanic();
}
const ViewAtResult = struct {
@ -57,19 +68,17 @@ const ViewAtResult = struct {
pub fn viewAt(self: *Root, lx: f64, ly: f64) ?ViewAtResult {
var sx: f64 = undefined;
var sy: f64 = undefined;
if (self.scene.tree.node.at(lx, ly, &sx, &sy)) |node| {
if (node.type != .buffer) return null;
const scene_buffer = wlr.SceneBuffer.fromNode(node);
const scene_surface = wlr.SceneSurface.tryFromBuffer(scene_buffer) orelse return null;
std.log.debug("Starting parent walk from buffer node", .{});
var it: ?*wlr.SceneTree = node.parent;
while (it) |n| : (it = n.node.parent) {
if (n.node.data) |data_ptr| {
if (@as(?*View, @ptrCast(@alignCast(data_ptr)))) |view| {
std.log.debug("View found", .{});
return ViewAtResult{
.view = view,
.surface = scene_surface.surface,
@ -92,10 +101,10 @@ pub fn focusView(_: *Root, view: *View) void {
}
view.scene_tree.node.raiseToTop();
// view.link.remove();
_ = server.root.all_views.append(gpa, view) catch {
unreachable;
};
// _ = server.root.all_views.append(gpa, view) catch {
// unreachable;
// };
_ = view.xdg_toplevel.setActivated(true);

View file

@ -1,11 +1,11 @@
const Seat = @This();
const std = @import("std");
const server = &@import("main.zig").server;
const wlr = @import("wlroots");
const wl = @import("wayland").server.wl;
const server = &@import("main.zig").server;
wlr_seat: *wlr.Seat,
request_set_cursor: wl.Listener(*wlr.Seat.event.RequestSetCursor) = .init(handleRequestSetCursor),
@ -23,10 +23,10 @@ pub fn init(self: *Seat) !void {
}
pub fn deinit(self: *Seat) void {
self.wlr_seat.destroy();
self.request_set_cursor.link.remove();
self.request_set_selection.link.remove();
self.wlr_seat.destroy();
}
fn handleRequestSetCursor(

View file

@ -47,18 +47,32 @@ new_xdg_popup: wl.Listener(*wlr.XdgPopup) = .init(handleNewXdgPopup),
// Seat listeners
pub fn init(self: *Server) !void {
const wl_server = try wl.Server.create();
const wl_server = wl.Server.create() catch {
std.err.log("Server create failed, exiting with 2", .{});
std.process.exit(2);
};
const event_loop = wl_server.getEventLoop();
var session: ?*wlr.Session = undefined;
const backend = try wlr.Backend.autocreate(event_loop, &session);
const renderer = try wlr.Renderer.autocreate(backend);
const backend = wlr.Backend.autocreate(event_loop, &session) catch {
std.log.err("Backend create failed, exiting with 3", .{});
std.process.exit(3);
};
const renderer = wlr.Renderer.autocreate(backend) catch {
std.log.err("Renderer create failed, exiting with 4", .{});
std.process.exit(4);
};
self.* = .{
.wl_server = wl_server,
.backend = backend,
.renderer = renderer,
.allocator = try wlr.Allocator.autocreate(backend, renderer),
.allocator = wlr.Allocator.autocreate(backend, renderer) catch {
std.log.err("Allocator create failed, exiting with 5", .{});
std.process.exit(5);
},
.xdg_shell = try wlr.XdgShell.create(wl_server, 2),
.event_loop = event_loop,
.session = session,
@ -71,9 +85,12 @@ pub fn init(self: *Server) !void {
.keyboard = undefined,
};
try self.renderer.initServer(wl_server);
try self.renderer.initServer(wl_server) catch {
std.log.err("Renderer init failed, exiting with 6", .{});
std.process.exit(6);
};
try self.root.init();
self.root.init();
try self.seat.init();
try self.cursor.init();
@ -91,16 +108,18 @@ pub fn init(self: *Server) !void {
}
pub fn deinit(self: *Server) void {
self.new_input.link.remove();
self.new_output.link.remove();
self.new_xdg_toplevel.link.remove();
self.new_xdg_popup.link.remove();
self.seat.deinit();
self.root.deinit();
self.cursor.deinit();
self.new_input.link.remove();
self.new_output.link.remove();
self.backend.destroy();
self.wl_server.destroyClients();
self.backend.destroy();
self.wl_server.destroy();
}

8
src/utils.zig Normal file
View file

@ -0,0 +1,8 @@
const Utils = @This();
const std = @import("std");
pub fn oomPanic() noreturn {
std.log.err("Out of memory error, exiting with 1", .{});
std.process.exit(1);
}

View file

@ -1,47 +1,50 @@
const View = @This();
const std = @import("std");
const wl = @import("wayland").server.wl;
const wlr = @import("wlroots");
const Utils = @import("utils.zig");
const gpa = std.heap.c_allocator;
const server = &@import("main.zig").server;
link: wl.list.Link = undefined,
geometry: *wlr.Box = undefined,
xdg_toplevel: *wlr.XdgToplevel,
xdg_surface: *wlr.XdgSurface,
scene_tree: *wlr.SceneTree,
// Surface Listeners
map: wl.Listener(void) = wl.Listener(void).init(handleMap),
unmap: wl.Listener(void) = wl.Listener(void).init(handleUnmap),
commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
map: wl.Listener(void) = .init(handleMap),
unmap: wl.Listener(void) = .init(handleUnmap),
commit: wl.Listener(*wlr.Surface) = .init(handleCommit),
// XdgTopLevel Listeners
destroy: wl.Listener(void) = wl.Listener(void).init(handleDestroy),
request_resize: wl.Listener(*wlr.XdgToplevel.event.Resize) = wl.Listener(handleRequestResize),
request_move: wl.Listener(*wlr.XdgToplevel.event.Move) = wl.Listener(handleRequestMove),
destroy: wl.Listener(void) = .init(handleDestroy),
request_resize: wl.Listener(*wlr.XdgToplevel.event.Resize) = .init(handleRequestResize),
request_move: wl.Listener(*wlr.XdgToplevel.event.Move) = .init(handleRequestMove),
// Not yet silly
// new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup),
pub fn initFromTopLevel(xdg_toplevel: *wlr.XdgToplevel) ?*View {
const self = gpa.create(View) catch {
std.log.err("Unable to allocate memory for new XdgTopLevel", .{});
return null;
};
errdefer Utils.oomPanic();
const xdg_surface = xdg_toplevel.base;
const self = gpa.create(View) catch Utils.oomPanic();
errdefer gpa.destroy(self);
self.* = .{
.xdg_toplevel = xdg_toplevel,
.scene_tree = server.root.scene.tree.createSceneXdgSurface(xdg_surface) catch {
gpa.destroy(self);
std.log.err("failed to allocate new toplevel", .{});
return null;
},
.xdg_surface = xdg_toplevel.base,
.geometry = &xdg_toplevel.base.geometry,
.scene_tree = try server.root.scene.tree.createSceneXdgSurface(xdg_toplevel.base)
};
self.scene_tree.node.data = self;
xdg_surface.data = self.scene_tree;
self.xdg_surface.data = self.scene_tree;
// Attach listeners
xdg_surface.surface.events.map.add(&self.map);
@ -65,33 +68,15 @@ pub fn initFromTopLevel(xdg_toplevel: *wlr.XdgToplevel) ?*View {
return self;
}
pub fn init(xdg_surface: *wlr.XdgSurface) ?*View {
const self = gpa.create(View) catch {
std.log.err("Unable to allocate memory for new XdgTopLevel", .{});
return null;
};
if(xdg_surface.role_data.toplevel) |xdg_toplevel| {
self.xdg_toplevel = xdg_toplevel;
} else {
std.log.err("Unable to get top_level from new surface", .{});
return null;
}
self.xdg_toplevel.base.surface.events.map.add(&self.map);
self.xdg_toplevel.base.surface.events.unmap.add(&self.unmap);
self.xdg_toplevel.base.surface.events.commit.add(&self.commit);
self.xdg_toplevel.events.destroy.add(&self.destroy);
self.xdg_toplevel.events.request_move.add(&self.request_move);
self.xdg_toplevel.events.request_resize.add(&self.request_resize);
// self.xdg_toplevel.events.request_move.add(&self.request_move);
// self.xdg_toplevel.events.request_resize.add(&self.request_resize);
return self;
}
pub fn deinit(self: *View) void {
self.map.link.remove();
self.unmap.link.remove();
self.commit.link.remove();
self.destroy.link.remove();
self.request_move.link.remove();
self.request_resize.link.remove();
gpa.free(self);
}
@ -138,7 +123,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(0, 0); // 0,0 means "you decide the size"
_ = view.xdg_toplevel.setSize(640, 360); // 0,0 means "you decide the size"
}
}
@ -148,16 +133,36 @@ fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), popup: *wlr.XdgPopup) v
std.log.err("Unimplemented view handle new popup", .{});
}
fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), resize: *wlr.XdgToplevel.event.Resize) void {
// const view: *View = @fieldParentPtr("request_resize", listener);
_ = listener;
_ = resize;
std.log.err("Unimplemented view handle resize", .{});
fn handleRequestMove(
listener: *wl.Listener(*wlr.XdgToplevel.event.Move),
_: *wlr.XdgToplevel.event.Move
) void {
const view: *View = @fieldParentPtr("request_move", listener);
server.cursor.grabbed_view = view;
server.cursor.mode = .move;
server.cursor.grab_x = server.cursor.wlr_cursor.x - @as(f64, @floatFromInt(view.geometry.x));
server.cursor.grab_y = server.cursor.wlr_cursor.y - @as(f64, @floatFromInt(view.geometry.y));
}
fn handleRequestMove(listener: *wl.Listener(*wlr.XdgToplevel.event.Move), move: *wlr.XdgToplevel.event.Move) void {
// const view: *View = @fieldParentPtr("request_move", listener);
_ = listener;
_ = move;
std.log.err("Unimplemented view handle move", .{});
fn handleRequestResize(
listener: *wl.Listener(*wlr.XdgToplevel.event.Resize),
event: *wlr.XdgToplevel.event.Resize
) void {
const view: *View = @fieldParentPtr("request_resize", listener);
server.cursor.grabbed_view = view;
server.cursor.mode = .resize;
server.cursor.resize_edges = event.edges;
const box = view.xdg_toplevel.base.geometry;
const border_x = view.geometry.x + box.x + if (event.edges.right) box.width else 0;
const border_y = view.geometry.y + box.y + if (event.edges.bottom) box.height else 0;
server.cursor.grab_x = server.cursor.wlr_cursor.x - @as(f64, @floatFromInt(border_x));
server.cursor.grab_y = server.cursor.wlr_cursor.y - @as(f64, @floatFromInt(border_y));
server.cursor.grab_box = box;
server.cursor.grab_box.x += view.geometry.x;
server.cursor.grab_box.y += view.geometry.y;
}