diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index a815da5..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -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", - } - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index eac218c..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "type": "shell", - "command": "zig build", - "problemMatcher": [], - "group": { - "kind": "build", - "isDefault": true - } - } - ] -} \ No newline at end of file diff --git a/README.md b/README.md index 3b829db..70dc795 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/cursor.zig b/src/cursor.zig index 9a60940..29dae24 100644 --- a/src/cursor.zig +++ b/src/cursor.zig @@ -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,14 +57,62 @@ pub fn deinit(self: *Cursor) void { } pub fn processCursorMotion(self: *Cursor, time_msec: u32) void { - 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(); + switch (self.mode) { + .passthrough => { + if (server.root.viewAt(self.wlr_cursor.x, self.wlr_cursor.y)) |res| { + server.seat.wlr_seat.pointerNotifyEnter(res.surface, res.sx, res.sy); + server.seat.wlr_seat.pointerNotifyMotion(time_msec, res.sx, res.sy); + } else { + 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); + }, } } @@ -88,9 +137,20 @@ fn handleButton( _: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.Pointer.event.Button, ) void { - _ = 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); + 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", .{}); + } } } diff --git a/src/output.zig b/src/output.zig index 3d213d6..30dfb96 100644 --- a/src/output.zig +++ b/src/output.zig @@ -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, diff --git a/src/root.zig b/src/root.zig index c70d1f4..fb09663 100644 --- a/src/root.zig +++ b/src/root.zig @@ -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); diff --git a/src/seat.zig b/src/seat.zig index 0b151b8..1b3e8ae 100644 --- a/src/seat.zig +++ b/src/seat.zig @@ -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( diff --git a/src/server.zig b/src/server.zig index bf51592..0185cc2 100644 --- a/src/server.zig +++ b/src/server.zig @@ -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(); } diff --git a/src/utils.zig b/src/utils.zig new file mode 100644 index 0000000..bd7d692 --- /dev/null +++ b/src/utils.zig @@ -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); +} diff --git a/src/view.zig b/src/view.zig index 20fe5db..bfa3a21 100644 --- a/src/view.zig +++ b/src/view.zig @@ -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; }