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 # 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_begin: wl.Listener(*wlr.Pointer.event.HoldBegin) = .init(handleHoldBegin),
hold_end: wl.Listener(*wlr.Pointer.event.HoldEnd) = .init(handleHoldEnd), 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, grabbed_view: ?*View = null,
grab_x: f64 = 0, grab_x: f64 = 0,
grab_y: f64 = 0, grab_y: f64 = 0,
grab_box: wlr.Box = undefined, grab_box: wlr.Box = undefined,
resize_edges: wlr.Edges = .{},
pub fn init(self: *Cursor) !void { pub fn init(self: *Cursor) !void {
self.* = .{ self.* = .{
@ -56,14 +57,62 @@ pub fn deinit(self: *Cursor) void {
} }
pub fn processCursorMotion(self: *Cursor, time_msec: u32) void { pub fn processCursorMotion(self: *Cursor, time_msec: u32) void {
if (server.root.viewAt(self.wlr_cursor.x, self.wlr_cursor.y)) |res| { switch (self.mode) {
std.log.debug("we found a view", .{}); .passthrough => {
server.seat.wlr_seat.pointerNotifyEnter(res.surface, res.sx, res.sy); if (server.root.viewAt(self.wlr_cursor.x, self.wlr_cursor.y)) |res| {
server.seat.wlr_seat.pointerNotifyMotion(time_msec, res.sx, res.sy); server.seat.wlr_seat.pointerNotifyEnter(res.surface, res.sx, res.sy);
} else { server.seat.wlr_seat.pointerNotifyMotion(time_msec, res.sx, res.sy);
std.log.debug("no view found", .{}); } else {
self.wlr_cursor.setXcursor(self.x_cursor_manager, "default"); self.wlr_cursor.setXcursor(self.x_cursor_manager, "default");
server.seat.wlr_seat.pointerClearFocus(); 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);
},
} }
} }
@ -88,9 +137,20 @@ fn handleButton(
_: *wl.Listener(*wlr.Pointer.event.Button), _: *wl.Listener(*wlr.Pointer.event.Button),
event: *wlr.Pointer.event.Button, event: *wlr.Pointer.event.Button,
) void { ) void {
_ = server.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state); switch (event.state) {
if (server.root.viewAt(server.cursor.wlr_cursor.x, server.cursor.wlr_cursor.y)) |res| { .pressed => {
server.root.focusView(res.view); _ = 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", .{});
}
} }
} }

View file

@ -1,15 +1,14 @@
const Output = @This(); 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 wl = @import("wayland").server.wl;
const wlr = @import("wlroots"); const wlr = @import("wlroots");
const std = @import("std");
const Server = @import("server.zig"); 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, wlr_output: *wlr.Output,
scene_output: *wlr.SceneOutput, scene_output: *wlr.SceneOutput,

View file

@ -6,6 +6,7 @@ const wlr = @import("wlroots");
const Output = @import("output.zig"); const Output = @import("output.zig");
const View = @import("view.zig"); const View = @import("view.zig");
const Utils = @import("utils.zig");
const server = &@import("main.zig").server; const server = &@import("main.zig").server;
const gpa = std.heap.c_allocator; const gpa = std.heap.c_allocator;
@ -15,11 +16,14 @@ scene_output_layout: *wlr.SceneOutputLayout,
output_layout: *wlr.OutputLayout, 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", .{}); std.log.info("Creating root of mezzaluna\n", .{});
errdefer Utils.oomPanic();
const output_layout = try wlr.OutputLayout.create(server.wl_server); const output_layout = try wlr.OutputLayout.create(server.wl_server);
errdefer output_layout.destroy(); errdefer output_layout.destroy();
@ -30,21 +34,28 @@ pub fn init(self: *Root) !void {
.scene = scene, .scene = scene,
.output_layout = output_layout, .output_layout = output_layout,
.scene_output_layout = try scene.attachOutputLayout(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 { pub fn deinit(self: *Root) void {
self.workspaces.deinit(gpa);
self.views.deinit(gpa);
self.output_layout.destroy(); self.output_layout.destroy();
self.scene.tree.node.destroy(); self.scene.tree.node.destroy();
} }
pub fn addOutput(self: *Root, new_output: *Output) void { pub fn addOutput(self: *Root, new_output: *Output) void {
_ = self.output_layout.addAuto(new_output.wlr_output) catch { _ = self.output_layout.addAuto(new_output.wlr_output) catch Utils.oomPanic();
std.log.err("failed to add new output to output layout\n", .{});
return;
};
} }
const ViewAtResult = struct { const ViewAtResult = struct {
@ -57,19 +68,17 @@ const ViewAtResult = struct {
pub fn viewAt(self: *Root, lx: f64, ly: f64) ?ViewAtResult { pub fn viewAt(self: *Root, lx: f64, ly: f64) ?ViewAtResult {
var sx: f64 = undefined; var sx: f64 = undefined;
var sy: f64 = undefined; var sy: f64 = undefined;
if (self.scene.tree.node.at(lx, ly, &sx, &sy)) |node| { if (self.scene.tree.node.at(lx, ly, &sx, &sy)) |node| {
if (node.type != .buffer) return null; if (node.type != .buffer) return null;
const scene_buffer = wlr.SceneBuffer.fromNode(node); const scene_buffer = wlr.SceneBuffer.fromNode(node);
const scene_surface = wlr.SceneSurface.tryFromBuffer(scene_buffer) orelse return null; 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; var it: ?*wlr.SceneTree = node.parent;
while (it) |n| : (it = n.node.parent) { while (it) |n| : (it = n.node.parent) {
if (n.node.data) |data_ptr| { if (n.node.data) |data_ptr| {
if (@as(?*View, @ptrCast(@alignCast(data_ptr)))) |view| { if (@as(?*View, @ptrCast(@alignCast(data_ptr)))) |view| {
std.log.debug("View found", .{});
return ViewAtResult{ return ViewAtResult{
.view = view, .view = view,
.surface = scene_surface.surface, .surface = scene_surface.surface,
@ -92,10 +101,10 @@ pub fn focusView(_: *Root, view: *View) void {
} }
view.scene_tree.node.raiseToTop(); view.scene_tree.node.raiseToTop();
// view.link.remove();
_ = server.root.all_views.append(gpa, view) catch { // _ = server.root.all_views.append(gpa, view) catch {
unreachable; // unreachable;
}; // };
_ = view.xdg_toplevel.setActivated(true); _ = view.xdg_toplevel.setActivated(true);

View file

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

View file

@ -47,18 +47,32 @@ new_xdg_popup: wl.Listener(*wlr.XdgPopup) = .init(handleNewXdgPopup),
// Seat listeners // Seat listeners
pub fn init(self: *Server) !void { 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(); const event_loop = wl_server.getEventLoop();
var session: ?*wlr.Session = undefined; var session: ?*wlr.Session = undefined;
const backend = try wlr.Backend.autocreate(event_loop, &session); const backend = wlr.Backend.autocreate(event_loop, &session) catch {
const renderer = try wlr.Renderer.autocreate(backend); 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.* = .{ self.* = .{
.wl_server = wl_server, .wl_server = wl_server,
.backend = backend, .backend = backend,
.renderer = renderer, .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), .xdg_shell = try wlr.XdgShell.create(wl_server, 2),
.event_loop = event_loop, .event_loop = event_loop,
.session = session, .session = session,
@ -71,9 +85,12 @@ pub fn init(self: *Server) !void {
.keyboard = undefined, .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.seat.init();
try self.cursor.init(); try self.cursor.init();
@ -91,16 +108,18 @@ pub fn init(self: *Server) !void {
} }
pub fn deinit(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.seat.deinit();
self.root.deinit(); self.root.deinit();
self.cursor.deinit(); self.cursor.deinit();
self.new_input.link.remove(); self.backend.destroy();
self.new_output.link.remove();
self.wl_server.destroyClients(); self.wl_server.destroyClients();
self.backend.destroy();
self.wl_server.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 View = @This();
const std = @import("std"); const std = @import("std");
const wl = @import("wayland").server.wl; const wl = @import("wayland").server.wl;
const wlr = @import("wlroots"); const wlr = @import("wlroots");
const Utils = @import("utils.zig");
const gpa = std.heap.c_allocator; const gpa = std.heap.c_allocator;
const server = &@import("main.zig").server; const server = &@import("main.zig").server;
link: wl.list.Link = undefined,
geometry: *wlr.Box = undefined,
xdg_toplevel: *wlr.XdgToplevel, xdg_toplevel: *wlr.XdgToplevel,
xdg_surface: *wlr.XdgSurface,
scene_tree: *wlr.SceneTree, scene_tree: *wlr.SceneTree,
// Surface Listeners // Surface Listeners
map: wl.Listener(void) = wl.Listener(void).init(handleMap), map: wl.Listener(void) = .init(handleMap),
unmap: wl.Listener(void) = wl.Listener(void).init(handleUnmap), unmap: wl.Listener(void) = .init(handleUnmap),
commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit), commit: wl.Listener(*wlr.Surface) = .init(handleCommit),
// XdgTopLevel Listeners // XdgTopLevel Listeners
destroy: wl.Listener(void) = wl.Listener(void).init(handleDestroy), destroy: wl.Listener(void) = .init(handleDestroy),
request_resize: wl.Listener(*wlr.XdgToplevel.event.Resize) = wl.Listener(handleRequestResize), request_resize: wl.Listener(*wlr.XdgToplevel.event.Resize) = .init(handleRequestResize),
request_move: wl.Listener(*wlr.XdgToplevel.event.Move) = wl.Listener(handleRequestMove), request_move: wl.Listener(*wlr.XdgToplevel.event.Move) = .init(handleRequestMove),
// Not yet silly // Not yet silly
// new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup), // new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup),
pub fn initFromTopLevel(xdg_toplevel: *wlr.XdgToplevel) ?*View { pub fn initFromTopLevel(xdg_toplevel: *wlr.XdgToplevel) ?*View {
const self = gpa.create(View) catch { errdefer Utils.oomPanic();
std.log.err("Unable to allocate memory for new XdgTopLevel", .{});
return null;
};
const xdg_surface = xdg_toplevel.base; const self = gpa.create(View) catch Utils.oomPanic();
errdefer gpa.destroy(self);
self.* = .{ self.* = .{
.xdg_toplevel = xdg_toplevel, .xdg_toplevel = xdg_toplevel,
.scene_tree = server.root.scene.tree.createSceneXdgSurface(xdg_surface) catch { .xdg_surface = xdg_toplevel.base,
gpa.destroy(self); .geometry = &xdg_toplevel.base.geometry,
std.log.err("failed to allocate new toplevel", .{}); .scene_tree = try server.root.scene.tree.createSceneXdgSurface(xdg_toplevel.base)
return null;
},
}; };
self.scene_tree.node.data = self; self.scene_tree.node.data = self;
xdg_surface.data = self.scene_tree; self.xdg_surface.data = self.scene_tree;
// Attach listeners // Attach listeners
xdg_surface.surface.events.map.add(&self.map); xdg_surface.surface.events.map.add(&self.map);
@ -65,33 +68,15 @@ pub fn initFromTopLevel(xdg_toplevel: *wlr.XdgToplevel) ?*View {
return self; 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 { 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); 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 // 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(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", .{}); std.log.err("Unimplemented view handle new popup", .{});
} }
fn handleRequestResize(listener: *wl.Listener(*wlr.XdgToplevel.event.Resize), resize: *wlr.XdgToplevel.event.Resize) void { fn handleRequestMove(
// const view: *View = @fieldParentPtr("request_resize", listener); listener: *wl.Listener(*wlr.XdgToplevel.event.Move),
_ = listener; _: *wlr.XdgToplevel.event.Move
_ = resize; ) void {
std.log.err("Unimplemented view handle resize", .{}); 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 { fn handleRequestResize(
// const view: *View = @fieldParentPtr("request_move", listener); listener: *wl.Listener(*wlr.XdgToplevel.event.Resize),
_ = listener; event: *wlr.XdgToplevel.event.Resize
_ = move; ) void {
std.log.err("Unimplemented view handle move", .{}); 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;
} }