diff --git a/river/Box.zig b/river/Box.zig index 0ba3e63..58210a0 100644 --- a/river/Box.zig +++ b/river/Box.zig @@ -24,6 +24,15 @@ y: i32, width: u32, height: u32, +pub fn fromWlrBox(wlr_box: c.wlr_box) Self { + return Self{ + .x = @intCast(i32, wlr_box.x), + .y = @intCast(i32, wlr_box.y), + .width = @intCast(u32, wlr_box.width), + .height = @intCast(u32, wlr_box.height), + }; +} + pub fn toWlrBox(self: Self) c.wlr_box { return c.wlr_box{ .x = @intCast(c_int, self.x), diff --git a/river/View.zig b/river/View.zig index 171d829..4f8f0bb 100644 --- a/river/View.zig +++ b/river/View.zig @@ -59,10 +59,25 @@ floating: bool, /// True if the view is currently focused by at least one seat focused: bool, -/// The current output-relative coordinates and dimensions of the view +/// The current output-relative coordinates and dimensions of the view. The +/// surface itself may have other dimensions which are stored in the +/// surface_box member. current_box: Box, + +/// Pending dimensions of the view during a transaction pending_box: ?Box, +/// The currently commited geometry of the surface. The x/y may be negative if +/// for example the client has decided to draw CSD shadows a la GTK. +surface_box: Box, + +/// The geometry the view's surface had when the transaction started and +/// buffers were saved. +saved_surface_box: Box, + +/// These are what we render while a transaction is in progress +saved_buffers: std.ArrayList(SavedBuffer), + /// The dimensions the view would have taken if we didn't force it to tile natural_width: u32, natural_height: u32, @@ -72,9 +87,6 @@ pending_tags: ?u32, pending_serial: ?u32, -/// These are what we render while a transaction is in progress -saved_buffers: std.ArrayList(SavedBuffer), - pub fn init( self: *Self, output: *Output, @@ -151,6 +163,7 @@ pub fn saveBuffers(self: *Self) void { self.saved_buffers.items.len = 0; } + self.saved_surface_box = self.surface_box; self.forEachSurface(saveBuffersIterator, &self.saved_buffers); } diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig index 5d43b92..c76312a 100644 --- a/river/XdgToplevel.zig +++ b/river/XdgToplevel.zig @@ -47,11 +47,6 @@ pub fn init(self: *Self, view: *View, wlr_xdg_surface: *c.wlr_xdg_surface) void self.wlr_xdg_surface = wlr_xdg_surface; wlr_xdg_surface.data = self; - // Inform the xdg toplevel that it is tiled. - // For example this prevents firefox from drawing shadows around itself - _ = c.wlr_xdg_toplevel_set_tiled(self.wlr_xdg_surface, c.WLR_EDGE_LEFT | - c.WLR_EDGE_RIGHT | c.WLR_EDGE_TOP | c.WLR_EDGE_BOTTOM); - // Add listeners that are active over the view's entire lifetime self.listen_destroy.notify = handleDestroy; c.wl_signal_add(&self.wlr_xdg_surface.events.destroy, &self.listen_destroy); @@ -110,10 +105,11 @@ pub fn forEachSurface( /// Return the surface at output coordinates ox, oy and set sx, sy to the /// corresponding surface-relative coordinates, if there is a surface. pub fn surfaceAt(self: Self, ox: f64, oy: f64, sx: *f64, sy: *f64) ?*c.wlr_surface { + const view = self.view; return c.wlr_xdg_surface_surface_at( self.wlr_xdg_surface, - ox - @intToFloat(f64, self.view.current_box.x), - oy - @intToFloat(f64, self.view.current_box.y), + ox - @intToFloat(f64, view.current_box.x - view.surface_box.x), + oy - @intToFloat(f64, view.current_box.y - view.surface_box.y), sx, sy, ); @@ -186,6 +182,14 @@ fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { view.setFloating(true); } + // If the toplevel has no parent, inform it that it is tiled. This + // prevents firefox, for example, from drawing shadows around itself. + if (wlr_xdg_toplevel.parent == null) + _ = c.wlr_xdg_toplevel_set_tiled( + self.wlr_xdg_surface, + c.WLR_EDGE_LEFT | c.WLR_EDGE_RIGHT | c.WLR_EDGE_TOP | c.WLR_EDGE_BOTTOM, + ); + view.map(); } @@ -202,22 +206,36 @@ fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { } /// Called when the surface is comitted -/// TODO: check for unexpected change in size and react as needed fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { const self = @fieldParentPtr(Self, "listen_commit", listener.?); const view = self.view; + var wlr_box: c.wlr_box = undefined; + c.wlr_xdg_surface_get_geometry(self.wlr_xdg_surface, &wlr_box); + const new_box = Box.fromWlrBox(wlr_box); + + // If we have sent a configure changing the size if (view.pending_serial) |s| { + // Update the stored dimensions of the surface + view.surface_box = new_box; + if (s == self.wlr_xdg_surface.configure_serial) { + // If this commit is in response to our configure, notify the + // transaction code. view.output.root.notifyConfigured(); view.pending_serial = null; } else { - // If the view has not yet acked our configure, we need to send a - // frame done event so that they commit another buffer. These + // If the client has not yet acked our configure, we need to send a + // frame done event so that it commits another buffer. These // buffers won't be rendered since we are still rendering our // stashed buffer from when the transaction started. view.sendFrameDone(); } + } else { + // TODO: handle unexpected change in dimensions + if (!std.meta.eql(view.surface_box, new_box)) + Log.Error.log("View changed size unexpectedly", .{}); + view.surface_box = new_box; } } diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig index f978e50..171aba2 100644 --- a/river/XwaylandView.zig +++ b/river/XwaylandView.zig @@ -57,10 +57,12 @@ pub fn init(self: *Self, view: *View, wlr_xwayland_surface: *c.wlr_xwayland_surf c.wl_signal_add(&self.wlr_xwayland_surface.events.unmap, &self.listen_unmap); } -/// Don't really care about efficiency with xwayland, we don't wait for them -/// to ack anyways since they don't use serials. pub fn needsConfigure(self: Self) bool { - return true; + const view = self.view; + if (view.pending_box) |pending_box| + return view.current_box.width != pending_box.width or + view.current_box.height != pending_box.height; + return false; } /// Tell the client to take a new size @@ -162,6 +164,14 @@ fn handleUnmap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { const self = @fieldParentPtr(Self, "listen_commit", listener.?); const view = self.view; + + view.surface_box = Box{ + .x = 0, + .y = 0, + .width = @intCast(u32, self.wlr_xwayland_surface.surface.*.current.width), + .height = @intCast(u32, self.wlr_xwayland_surface.surface.*.current.height), + }; + // See comment in XwaylandView.configure() if (view.pending_serial != null) { view.output.root.notifyConfigured(); diff --git a/river/render.zig b/river/render.zig index 6414630..14e8ae0 100644 --- a/river/render.zig +++ b/river/render.zig @@ -143,8 +143,8 @@ fn renderView(output: Output, view: *View, now: *c.timespec) void { output, saved_buffer.wlr_buffer.texture, .{ - .x = saved_buffer.box.x + view.current_box.x, - .y = saved_buffer.box.y + view.current_box.y, + .x = saved_buffer.box.x + view.current_box.x - view.saved_surface_box.x, + .y = saved_buffer.box.y + view.current_box.y - view.saved_surface_box.y, .width = @intCast(c_int, saved_buffer.box.width), .height = @intCast(c_int, saved_buffer.box.height), }, @@ -155,8 +155,8 @@ fn renderView(output: Output, view: *View, now: *c.timespec) void { // a transaction and may simply render each toplevel surface. var rdata = SurfaceRenderData{ .output = &output, - .output_x = view.current_box.x, - .output_y = view.current_box.y, + .output_x = view.current_box.x - view.surface_box.x, + .output_y = view.current_box.y - view.surface_box.y, .when = now, };