removed all windows decorations properly, added window closing and compositor exiting as well as view focusing

This commit is contained in:
Harrison DiAmbrosio 2025-10-25 18:50:06 -04:00
parent f636efdfe9
commit 976900ffa6
11 changed files with 289 additions and 117 deletions

View file

@ -81,5 +81,5 @@ pub fn build(b: *std.Build) void {
const run_cmd = b.addRunArtifact(mez);
run_step.dependOn(&run_cmd.step);
run_cmd.step.dependOn(b.getInstallStep());
run_cmd.addArg("weston-terminal");
run_cmd.addArg("alacritty");
}

View file

@ -19,15 +19,19 @@ mez.input.add_keymap("alt", "a", {
mez.input.add_keymap("alt", "Return", {
press = function()
print("spawning foot")
mez.api.spawn("foot")
end,
})
mez.input.add_keymap("alt", "c", {
press = function ()
mez.api.close()
end
})
mez.api.add_keymap("alt", "p", {
press = function()
print("spawning foot")
mez.api.spawn("wmenu-run")
mez.input.add_keymap("alt", "q", {
press = function ()
mez.api.exit();
end
})

View file

@ -30,7 +30,6 @@ hold_end: wl.Listener(*wlr.Pointer.event.HoldEnd) = .init(handleHoldEnd),
mode: enum { passthrough, move, resize } = .passthrough,
// Drag information
selected_view: ?*View = null,
drag_start_x: c_int = 0,
drag_start_y: c_int = 0,
drag_view_offset_x: c_int = 0,
@ -74,6 +73,8 @@ 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);
server.seat.wlr_seat.pointerNotifyEnter(res.surface, res.sx, res.sy);
server.seat.wlr_seat.pointerNotifyMotion(time_msec, res.sx, res.sy);
} else {
@ -82,23 +83,25 @@ pub fn processCursorMotion(self: *Cursor, time_msec: u32) void {
}
},
.move => {
const view = self.selected_view.?;
const focused_view = server.seat.focused_view;
if(focused_view) |view| {
view.scene_tree.node.setPosition(
std.math.clamp(@as(c_int, @intFromFloat(self.wlr_cursor.x)) - self.drag_view_offset_x, 0, std.math.maxInt(u32)),
std.math.clamp(@as(c_int, @intFromFloat(self.wlr_cursor.y)) - self.drag_view_offset_y, 0, std.math.maxInt(u32))
);
}
},
.resize => {
// Fix this resize
const view = self.selected_view.?;
const focused_view = server.seat.focused_view;
if(focused_view) |view| {
_ = view.xdg_toplevel.setSize(
@intCast(@as(c_int, @intFromFloat(self.wlr_cursor.x)) - view.scene_tree.node.x),
@intCast(@as(c_int, @intFromFloat(self.wlr_cursor.y)) - view.scene_tree.node.y)
);
}
},
}
}
@ -128,33 +131,30 @@ fn handleButton(
_ = server.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
const view_at_result = server.root.viewAt(cursor.wlr_cursor.x, cursor.wlr_cursor.y);
if (view_at_result) |res| {
server.root.focusView(res.view);
if (server.seat.focused_view) |view| {
server.seat.focusView(view);
server.root.focusView(view);
}
std.log.debug("Button pressed {}", .{event.button});
switch (event.state) {
.pressed => {
if(server.keyboard.wlr_keyboard.getModifiers().alt) {
// Can be BTN_RIGHT, BTN_LEFT, or BTN_MIDDLE
if(view_at_result) |res| {
if(server.seat.focused_view) |view| {
// Keep track of where the drag started
cursor.selected_view = res.view;
cursor.drag_start_x = @as(c_int, @intFromFloat(cursor.wlr_cursor.x));
cursor.drag_start_y = @as(c_int, @intFromFloat(cursor.wlr_cursor.y));
cursor.drag_view_offset_x = cursor.drag_start_x - res.view.scene_tree.node.x;
cursor.drag_view_offset_y = cursor.drag_start_y - res.view.scene_tree.node.y;
cursor.drag_view_width = res.view.xdg_surface.geometry.width;
cursor.drag_view_height = res.view.xdg_surface.geometry.height;
cursor.drag_view_offset_x = cursor.drag_start_x - view.scene_tree.node.x;
cursor.drag_view_offset_y = cursor.drag_start_y - view.scene_tree.node.y;
cursor.drag_view_width = view.xdg_toplevel.base.geometry.width;
cursor.drag_view_height = view.xdg_toplevel.base.geometry.height;
// Maybe comptime this for later reference
if(event.button == c.libevdev_event_code_from_name(c.EV_KEY, "BTN_LEFT")) {
cursor.mode = .move;
} else if(event.button == c.libevdev_event_code_from_name(c.EV_KEY, "BTN_RIGHT")) {
cursor.mode = .resize;
_ = res.view.xdg_toplevel.setResizing(true);
_ = view.xdg_toplevel.setResizing(true);
}
}
}
@ -162,10 +162,9 @@ fn handleButton(
.released => {
cursor.mode = .passthrough;
if(cursor.selected_view) |view| {
if(server.seat.focused_view) |view| {
_ = view.xdg_toplevel.setResizing(false);
}
cursor.selected_view = null;
},
else => {
std.log.err("Invalid/Unimplemented pointer button event type", .{});

View file

@ -21,10 +21,8 @@ options: struct {
},
pub fn callback(self: *const Keymap, release: bool) void {
var lua_ref_idx = self.lua_press_ref_idx;
if (release) {
lua_ref_idx = self.lua_release_ref_idx;
}
const lua_ref_idx = if(release) self.lua_release_ref_idx else self.lua_press_ref_idx;
const t = Lua.state.rawGetIndex(zlua.registry_index, lua_ref_idx);
if (t != zlua.LuaType.function) {
std.log.err("Failed to call keybind, it doesn't have a callback.", .{});

View file

@ -4,6 +4,7 @@ const zlua = @import("zlua");
const gpa = std.heap.c_allocator;
const env_map = &@import("../main.zig").env_map;
const server = &@import("../main.zig").server;
pub fn spawn(L: *zlua.Lua) i32 {
const nargs: i32 = L.getTop();
@ -28,3 +29,31 @@ pub fn spawn(L: *zlua.Lua) i32 {
return 0;
}
pub fn close(L: *zlua.Lua) i32 {
const nargs: i32 = L.getTop();
if (nargs != 0) {
L.raiseErrorStr("Expected no arguments", .{});
return 0;
}
if(server.seat.focused_view) |view| {
view.xdg_toplevel.sendClose();
}
return 0;
}
pub fn exit(L: *zlua.Lua) i32 {
const nargs: i32 = L.getTop();
if (nargs != 0) {
L.raiseErrorStr("Expected no arguments", .{});
return 0;
}
server.deinit();
return 0;
}

View file

@ -10,6 +10,8 @@ const posix = std.posix;
const gpa = std.heap.c_allocator;
const server = &@import("main.zig").server;
focused: bool,
wlr_output: *wlr.Output,
scene_output: *wlr.SceneOutput,
@ -24,6 +26,7 @@ pub fn create(wlr_output: *wlr.Output) *Output {
const output = try gpa.create(Output);
output.* = .{
.focused = false,
.wlr_output = wlr_output,
.scene_output = try server.root.scene.createSceneOutput(wlr_output)
};

View file

@ -17,10 +17,8 @@ scene: *wlr.Scene,
scene_output_layout: *wlr.SceneOutputLayout,
output_layout: *wlr.OutputLayout,
focused_output: ?*Output,
views: std.ArrayList(*View) = undefined,
workspaces: std.ArrayList(*wlr.SceneTree) = undefined,
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,28 +34,21 @@ pub fn init(self: *Root) void {
self.* = .{
.scene = scene,
.output_layout = output_layout,
.focused_output = null,
.xdg_toplevel_decoration_manager = try wlr.XdgDecorationManagerV1.create(server.wl_server),
.scene_output_layout = try scene.attachOutputLayout(output_layout),
.views = .init(gpa)
};
self.views = try 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 = try std.ArrayList(*wlr.SceneTree).initCapacity(gpa, 10); // TODO: change to a configured number of workspaces
// TODO: Make configurable
for(0..9) |_| {
try self.workspaces.append(gpa, try self.scene.tree.createSceneTree());
}
}
pub fn deinit(self: *Root) void {
for(self.views.items) |view| {
view.deinit();
var views_it = self.views.iterator();
while(views_it.next()) |entry| {
entry.value_ptr.*.deinit();
}
self.views.deinit(gpa);
self.workspaces.deinit(gpa);
self.views.deinit();
self.output_layout.destroy();
self.scene.tree.node.destroy();
@ -66,7 +57,7 @@ pub fn deinit(self: *Root) void {
pub fn addOutput(self: *Root, new_output: *Output) void {
errdefer Utils.oomPanic();
_ = try self.output_layout.addAuto(new_output.wlr_output);
self.focused_output = new_output;
server.seat.focusOutput(new_output);
}
const ViewAtResult = struct {

View file

@ -5,10 +5,14 @@ const wlr = @import("wlroots");
const wl = @import("wayland").server.wl;
const Utils = @import("utils.zig");
const View = @import("view.zig");
const Output = @import("output.zig");
const server = &@import("main.zig").server;
wlr_seat: *wlr.Seat,
focused_view: ?*View,
focused_output: ?*Output,
request_set_cursor: wl.Listener(*wlr.Seat.event.RequestSetCursor) = .init(handleRequestSetCursor),
request_set_selection: wl.Listener(*wlr.Seat.event.RequestSetSelection) = .init(handleRequestSetSelection),
@ -20,6 +24,8 @@ pub fn init(self: *Seat) void {
self.* = .{
.wlr_seat = try wlr.Seat.create(server.wl_server, "default"),
.focused_view = null,
.focused_output = null,
};
self.wlr_seat.events.request_set_cursor.add(&self.request_set_cursor);
@ -33,6 +39,23 @@ pub fn deinit(self: *Seat) void {
self.wlr_seat.destroy();
}
pub fn focusOutput(self: *Seat, output: *Output) void {
if(self.focused_output) |prev_output| {
prev_output.focused = false;
}
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

@ -116,15 +116,15 @@ pub fn init(self: *Server) void {
self.xdg_shell.events.new_popup.add(&self.new_xdg_popup);
// XdgDecorationManagerV1 events
// self.xdg_toplevel_decoration_manager.events.new_toplevel_decoration.add(&self.new_toplevel_decoration);
self.xdg_toplevel_decoration_manager.events.new_toplevel_decoration.add(&self.new_xdg_toplevel_decoration);
}
pub fn deinit(self: *Server) void {
pub fn deinit(self: *Server) noreturn {
self.new_input.link.remove();
self.new_output.link.remove();
self.new_xdg_toplevel.link.remove();
self.new_xdg_popup.link.remove();
self.new_xdg_toplevel.link.remove();
self.new_xdg_toplevel_decoration.link.remove();
self.seat.deinit();
self.root.deinit();
@ -134,6 +134,9 @@ pub fn deinit(self: *Server) void {
self.wl_server.destroyClients();
self.wl_server.destroy();
std.log.debug("Exiting mez succesfully", .{});
std.process.exit(0);
}
// --------- Backend event handlers ---------
@ -185,20 +188,23 @@ fn handleNewXdgToplevel(
_: *wl.Listener(*wlr.XdgToplevel),
xdg_toplevel: *wlr.XdgToplevel
) void {
std.log.debug("Request for new toplevel", .{});
_ = View.initFromTopLevel(xdg_toplevel);
}
fn handleNewXdgToplevelDecoration(
_: *wl.Listener(*wlr.XdgToplevelDecorationV1),
decoration: *wlr.XdgToplevelDecorationV1
) void {
std.log.debug("Request for decorations", .{});
if(server.root.views.get(@intFromPtr(decoration.toplevel))) |view| {
view.xdg_toplevel_decoration = decoration;
}
}
fn handleNewXdgPopup(
_: *wl.Listener(*wlr.XdgPopup),
_: *wlr.XdgPopup
) void {
std.log.err("Unimplemented handle new xdg popup", .{});
}
fn handleNewXdgToplevelDecoration(
_: *wl.Listener(*wlr.XdgToplevelDecorationV1),
decoration: *wlr.XdgToplevelDecorationV1
) void {
// TODO: Configured with lua perhaps
decoration.current.mode = .server_side;
}

32
src/tag.zig Normal file
View file

@ -0,0 +1,32 @@
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

@ -9,25 +9,40 @@ 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,
mapped: bool,
focused: bool,
id: u64,
// workspace: Workspace,
xdg_toplevel: *wlr.XdgToplevel,
xdg_surface: *wlr.XdgSurface,
xdg_toplevel_decoration: ?*wlr.XdgToplevelDecorationV1,
scene_tree: *wlr.SceneTree,
// Surface Listeners
map: wl.Listener(void) = .init(handleMap),
unmap: wl.Listener(void) = .init(handleUnmap),
commit: wl.Listener(*wlr.Surface) = .init(handleCommit),
new_popup: wl.Listener(*wlr.XdgPopup) = .init(handleNewPopup),
ack_configure: wl.Listener(*wlr.XdgSurface.Configure) = .init(handleAckConfigure),
// XdgTopLevel Listeners
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),
request_fullscreen: wl.Listener(void) = .init(handleRequestFullscreen),
// Not yet silly
// new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup),
// Do we need to add these
// request_show_window_menu: wl.Listener(comptime T: type) = .init(handleRequestShowWindowMenu),
// request_minimize: wl.Listener(comptime T: type) = .init(handleRequestMinimize),
// request_maximize: wl.Listener(comptime T: type) = .init(handleRequestMaximize),
set_app_id: wl.Listener(void) = .init(handleSetAppId),
set_title: wl.Listener(void) = .init(handleSetTitle),
// Do we need to add this
// set_parent: wl.Listener(void) = .init(handleSetParent),
pub fn initFromTopLevel(xdg_toplevel: *wlr.XdgToplevel) *View {
errdefer Utils.oomPanic();
@ -37,40 +52,30 @@ pub fn initFromTopLevel(xdg_toplevel: *wlr.XdgToplevel) *View {
self.* = .{
.xdg_toplevel = xdg_toplevel,
.xdg_surface = xdg_toplevel.base,
.geometry = &xdg_toplevel.base.geometry,
.focused = false,
.scene_tree = undefined,
.xdg_toplevel_decoration = null,
.mapped = false,
.id = @intFromPtr(xdg_toplevel),
};
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);
// self.scene_tree = try server.root.workspaces.items[0].createSceneXdgSurface(xdg_toplevel.base);
self.scene_tree = try server.root.scene.tree.createSceneXdgSurface(xdg_toplevel.base);
self.scene_tree.node.data = self;
self.xdg_surface.data = self.scene_tree;
// Attach listeners
self.xdg_surface.surface.events.map.add(&self.map);
self.xdg_surface.surface.events.unmap.add(&self.unmap);
self.xdg_surface.surface.events.commit.add(&self.commit);
self.xdg_toplevel.base.data = self.scene_tree;
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.base.surface.events.map.add(&self.map);
self.xdg_toplevel.base.surface.events.commit.add(&self.commit);
self.xdg_toplevel.base.events.new_popup.add(&self.new_popup);
// xdg_toplevel.events.request_fullscreen.add(&self.request_fullscreen);
// xdg_toplevel.events.request_minimize.add(&self.request_minimize);
// xdg_toplevel.events.request_maxminize.add(&self.request_maximize);
// xdg_toplevel.events.set_title.add(&self.set_title);
// xdg_toplevel.events.set_app_id.add(&self.set_app_id);
// xdg_toplevel.events.set_parent.add(&self.set_parent);
// xdg_toplevel.events.request_show_window_menu.add(&self.request_show_window_menu);
try server.root.views.append(gpa, self);
try server.root.views.put(self.id, self);
return self;
}
@ -85,10 +90,22 @@ 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;
}
// --------- XdgTopLevel event handlers ---------
fn handleMap(listener: *wl.Listener(void)) void {
const view: *View = @fieldParentPtr("map", listener);
std.log.info("View mapped {s}", .{view.xdg_toplevel.title orelse "(unnamed)"});
std.log.debug("Mapping view '{s}'", .{view.xdg_toplevel.title orelse "(unnamed)"});
view.xdg_toplevel.events.request_fullscreen.add(&view.request_fullscreen);
view.xdg_toplevel.events.request_move.add(&view.request_move);
view.xdg_toplevel.events.request_resize.add(&view.request_resize);
view.xdg_toplevel.events.set_app_id.add(&view.set_app_id);
view.xdg_toplevel.events.set_title.add(&view.set_title);
// view.xdg_toplevel.events.set_parent.add(&view.set_parent);
const xdg_surface = view.xdg_toplevel.base;
server.seat.wlr_seat.keyboardNotifyEnter(
@ -96,29 +113,49 @@ fn handleMap(listener: *wl.Listener(void)) void {
server.keyboard.wlr_keyboard.keycodes[0..server.keyboard.wlr_keyboard.num_keycodes],
&server.keyboard.wlr_keyboard.modifiers
);
if(view.xdg_toplevel_decoration) |decoration| {
_ = decoration.setMode(wlr.XdgToplevelDecorationV1.Mode.server_side);
}
// Here is where we should tile and set size
view.mapped = true;
}
fn handleUnmap(listener: *wl.Listener(void)) void {
_ = listener;
std.log.err("Unimplemented view handle unamp", .{});
const view: *View = @fieldParentPtr("unmap", listener);
std.log.debug("Unmapping view '{s}'", .{view.xdg_toplevel.title orelse "(unnamed)"});
view.request_fullscreen.link.remove();
view.request_move.link.remove();
view.request_resize.link.remove();
view.set_title.link.remove();
view.set_app_id.link.remove();
// Why does this crash mez???
// view.ack_configure.link.remove();
view.mapped = false;
}
fn handleDestroy(listener: *wl.Listener(void)) void {
const view: *View = @fieldParentPtr("destroy", listener);
std.log.debug("Destroying view {s}", .{view.xdg_toplevel.title orelse "(unnamed)"});
// Remove decorations
view.map.link.remove();
view.unmap.link.remove();
view.commit.link.remove();
view.destroy.link.remove();
view.new_popup.link.remove();
// Remove this view from the list of views
// for(server.root.all_views.items, 0..) |v, i| {
// if(v == view) {
// _ = server.root.all_views.orderedRemove(i);
// break;
// }
// }
view.xdg_toplevel.base.surface.data = null;
view.scene_tree.node.destroy();
// Destroy popups
_ = server.root.views.remove(view.id);
gpa.destroy(view);
}
@ -132,6 +169,7 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
}
}
// --------- XdgToplevel Event Handlers ---------
fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), popup: *wlr.XdgPopup) void {
_ = listener;
_ = popup;
@ -144,7 +182,7 @@ fn handleRequestMove(
) void {
// const view: *View = @fieldParentPtr("request_move", listener);
std.log.err("The clients should not be request moves", .{});
std.log.debug("The clients should not be request moves", .{});
// server.cursor.moveView(view);
// server.cursor.grabbed_view = view;
@ -159,7 +197,7 @@ fn handleRequestResize(
) void {
// const view: *View = @fieldParentPtr("request_resize", listener);
std.log.err("The clients should not be request moves", .{});
std.log.debug("The clients should not be request moves", .{});
// server.cursor.grabbed_view = view;
// server.cursor.mode = .resize;
@ -176,3 +214,52 @@ fn handleRequestResize(
// server.cursor.grab_box.x += view.geometry.x;
// server.cursor.grab_box.y += view.geometry.y;
}
fn handleAckConfigure(
listener: *wl.Listener(*wlr.XdgSurface.Configure),
_: *wlr.XdgSurface.Configure,
) void {
const view: *View = @fieldParentPtr("ack_configure", listener);
_ = view;
std.log.err("Unimplemented act configure", .{});
}
fn handleRequestFullscreen(
listener: *wl.Listener(void)
) void {
const view: *View = @fieldParentPtr("request_fullscreen", listener);
_ = view;
std.log.err("Unimplemented request fullscreen", .{});
}
fn handleRequestMinimize(
listener: *wl.Listener(void)
) void {
const view: *View = @fieldParentPtr("request_fullscreen", listener);
_ = view;
std.log.err("Unimplemented request minimize", .{});
}
fn handleRequestMaximize(
listener: *wl.Listener(void)
) void {
const view: *View = @fieldParentPtr("request_fullscreen", listener);
_ = view;
std.log.err("Unimplemented request maximize", .{});
}
fn handleSetAppId(
listener: *wl.Listener(void)
) void {
const view: *View = @fieldParentPtr("set_app_id", listener);
_ = view;
std.log.err("Unimplemented request maximize", .{});
}
fn handleSetTitle(
listener: *wl.Listener(void)
) void {
const view: *View = @fieldParentPtr("set_title", listener);
_ = view;
std.log.err("Unimplemented set title", .{});
}