diff --git a/README.md b/README.md index 70dc795..f4b0a18 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,4 @@ 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. +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, lovable Lua. diff --git a/build.zig b/build.zig index 11dbdf3..a48cffe 100644 --- a/build.zig +++ b/build.zig @@ -77,6 +77,18 @@ pub fn build(b: *std.Build) void { b.installArtifact(mez); + const exe_check = b.addExecutable(.{ + .name = "mez", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }) + }); + + const check = b.step("check", "check if mez compiles"); + check.dependOn(&exe_check.step); + const run_step = b.step("run", "Run the app"); const run_cmd = b.addRunArtifact(mez); run_step.dependOn(&run_cmd.step); diff --git a/ideation.md b/notes/ideation.md similarity index 100% rename from ideation.md rename to notes/ideation.md diff --git a/runtime/share/mezzaluna/init.lua b/runtime/share/mezzaluna/init.lua index 08f0065..068c35d 100644 --- a/runtime/share/mezzaluna/init.lua +++ b/runtime/share/mezzaluna/init.lua @@ -35,9 +35,17 @@ mez.input.add_keymap("alt", "q", { end }) +mez.input.add_keymap("alt", "v", { + press = function () + local view = mez.view.get_focused_id() + mez.view.set_position(view, 100, 100) + mez.view.set_size(view, 100, 100) + end +}) + for i = 1, 12 do mez.input.add_keymap("ctrl|alt", "XF86Switch_VT_"..i, { - press = function() mez.api.chvt(i) end + press = function() mez.api.change_vt(i) end }) end diff --git a/src/cursor.zig b/src/cursor.zig index 0a076f8..5f52d06 100644 --- a/src/cursor.zig +++ b/src/cursor.zig @@ -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) { diff --git a/src/lua/api.zig b/src/lua/api.zig index 2d76c88..91d0528 100644 --- a/src/lua/api.zig +++ b/src/lua/api.zig @@ -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); diff --git a/src/lua/lua.zig b/src/lua/lua.zig index 4a3ee58..41c3aba 100644 --- a/src/lua/lua.zig +++ b/src/lua/lua.zig @@ -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)}); diff --git a/src/lua/view.zig b/src/lua/view.zig new file mode 100644 index 0000000..3da0fe2 --- /dev/null +++ b/src/lua/view.zig @@ -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; +} diff --git a/src/output.zig b/src/output.zig index f165fd4..70f3fcd 100644 --- a/src/output.zig +++ b/src/output.zig @@ -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, diff --git a/src/root.zig b/src/root.zig index 0a54338..11d4305 100644 --- a/src/root.zig +++ b/src/root.zig @@ -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); diff --git a/src/seat.zig b/src/seat.zig index 279ae0a..2659fb0 100644 --- a/src/seat.zig +++ b/src/seat.zig @@ -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, diff --git a/src/server.zig b/src/server.zig index 1aafa5a..d42d8b9 100644 --- a/src/server.zig +++ b/src/server.zig @@ -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; } } diff --git a/src/tag.zig b/src/tag.zig deleted file mode 100644 index 63023ac..0000000 --- a/src/tag.zig +++ /dev/null @@ -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 - } -} diff --git a/src/view.zig b/src/view.zig index 80faecd..7c7f08c 100644 --- a/src/view.zig +++ b/src/view.zig @@ -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); } }