Compare commits

...

2 commits

Author SHA1 Message Date
Zhongheng Liu
b35962606d impl autoexec support for a single script file 2026-04-28 20:40:12 +02:00
Ben Buhse
6024066488
Support layer shell exclusive areas again
This re-adds support for layer-shell exclusive areas (initially removed
in commit a9473204)

The Beansprout bar will now render inside the non-exclusive area and the
usable area for calculating the window layouts is based on the non-
exclusive area minus the beansprout bar's area

Implements: #13
2026-04-28 11:07:41 -05:00
4 changed files with 68 additions and 19 deletions

View file

@ -35,6 +35,8 @@ pending_manage: PendingManage = .{},
pending_render: PendingRender = .{}, pending_render: PendingRender = .{},
const PendingManage = struct { const PendingManage = struct {
/// Recalculate bar geometry (size and position) on the next manage cycle
/// Set when output dimensions, position, scale, or exclusive zones change
output_geometry: bool = false, output_geometry: bool = false,
}; };
@ -159,7 +161,10 @@ pub fn manage(bar: *Bar) !void {
const logical_font_height = @divFloor(bar.fcft_fonts.height, @as(i32, bar.font_scale)); const logical_font_height = @divFloor(bar.fcft_fonts.height, @as(i32, bar.font_scale));
const height: u31 = @intCast(logical_font_height + 2 * options.vertical_padding); const height: u31 = @intCast(logical_font_height + 2 * options.vertical_padding);
const width: u31 = output.geometry.width -| @as(u31, @intCast(options.margins.left + options.margins.right)); // Use the non-exclusive area so the bar sits adjacent to any external layer shell
// surfaces rather than overlapping them.
const base = output.non_exclusive_area;
const width: u31 = @as(u31, @intCast(base.width)) -| @as(u31, @intCast(options.margins.left + options.margins.right));
if (bar.geometry.width != width or bar.geometry.height != height) { if (bar.geometry.width != width or bar.geometry.height != height) {
bar.geometry.width = width; bar.geometry.width = width;
@ -171,10 +176,10 @@ pub fn manage(bar: *Bar) !void {
bar.surfaces.wl_surface.setOpaqueRegion(opaque_region); bar.surfaces.wl_surface.setOpaqueRegion(opaque_region);
} }
const x = output.geometry.x + options.margins.left; const x = base.x + options.margins.left;
const y = switch (options.position) { const y = switch (options.position) {
.top => output.geometry.y + options.margins.top, .top => base.y + options.margins.top,
.bottom => output.geometry.y + output.geometry.height - bar.geometry.height - options.margins.bottom, .bottom => base.y + base.height - bar.geometry.height - options.margins.bottom,
}; };
bar.pending_render.position = .{ .x = x, .y = y }; bar.pending_render.position = .{ .x = x, .y = y };
bar.pending_render.draw = true; bar.pending_render.draw = true;

View file

@ -16,6 +16,9 @@ border_color_focused: RiverColor = utils.parseRgbaComptime("0x89b4fa"),
/// Color of unfocused windows' borders in 0xRRGGBBAA or 0xRRGGBB form /// Color of unfocused windows' borders in 0xRRGGBBAA or 0xRRGGBB form
border_color_unfocused: RiverColor = utils.parseRgbaComptime("0x1e1e2e"), border_color_unfocused: RiverColor = utils.parseRgbaComptime("0x1e1e2e"),
/// Autoexec script path
autoexec_path: ?[]const u8 = null,
/// Number of windows in the primary stack /// Number of windows in the primary stack
/// This is a global default, but each tagmask can have its own value /// This is a global default, but each tagmask can have its own value
primary_count: u8 = 1, primary_count: u8 = 1,
@ -70,6 +73,7 @@ pub const PrimarySide = enum { left, right };
pub const AttachMode = enum { top, bottom }; pub const AttachMode = enum { top, bottom };
const NodeName = enum { const NodeName = enum {
autoexec,
attach_mode, attach_mode,
primary_count, primary_count,
primary_ratio, primary_ratio,
@ -225,6 +229,13 @@ fn load(config: *Config, reader: *Io.Reader) !void {
} }
// Next, we have to check the specifics for the NodeName // Next, we have to check the specifics for the NodeName
switch (name) { switch (name) {
.autoexec => {
const autoexec_path = node.arg(&parser, 0);
if (autoexec_path) |path| {
config.autoexec_path = utils.gpa.dupe(u8, path) catch @panic("Out of memory");
}
logDebugSettingNode(name, autoexec_path.?);
},
.primary_count => { .primary_count => {
const count_str = utils.stripQuotes(node.arg(&parser, 0) orelse ""); const count_str = utils.stripQuotes(node.arg(&parser, 0) orelse "");
// Use @max to ensure a minimum of 1 // Use @max to ensure a minimum of 1

View file

@ -15,15 +15,15 @@ wl_output: ?*wl.Output = null,
/// Friendly name of this output /// Friendly name of this output
name: ?[]const u8 = null, name: ?[]const u8 = null,
/// Output geometry
scale: u31 = 1, scale: u31 = 1,
/// The rect for the entire output, this includes space that's taken by widgets like bars and not
/// made available to windows.
geometry: Rect = .{}, geometry: Rect = .{},
/// Area available for window layout (output geometry minus bar space) /// Output geometry minus layer shell exclusive zones
/// Maybe I'll re-add support for layer shell exclusive areas later, non_exclusive_area: Rect = .{},
/// but adding that makes it more work for me and I don't personally
/// know of anything that makes me want them since external bars won't /// Area available for window layout (non_exclusive_area minus bar space)
/// work with beansprout.
usable_geometry: Rect = .{}, usable_geometry: Rect = .{},
wallpaper: ?Wallpaper = null, wallpaper: ?Wallpaper = null,
@ -64,6 +64,7 @@ const TagLayoutOverride = struct {
const PendingManage = struct { const PendingManage = struct {
position: ?struct { x: i32, y: i32 } = null, position: ?struct { x: i32, y: i32 } = null,
dimensions: ?struct { width: u31, height: u31 } = null, dimensions: ?struct { width: u31, height: u31 } = null,
non_exclusive_area: ?Rect = null,
tags: ?u32 = null, tags: ?u32 = null,
primary_ratio: ?f32 = null, primary_ratio: ?f32 = null,
@ -106,6 +107,7 @@ pub fn create(context: *Context, river_output_v1: *river.OutputV1) !*Output {
output.windows.init(); output.windows.init();
output.river_output_v1.setListener(*Output, riverOutputListener, output); output.river_output_v1.setListener(*Output, riverOutputListener, output);
output.river_layer_shell_output_v1.setListener(*Output, riverLayerShellOutputListener, output);
return output; return output;
} }
@ -200,8 +202,28 @@ pub fn prevWindow(output: *Output, current: *Window) ?*Window {
} }
} }
// Used for the river_output_v1 interface fn riverLayerShellOutputListener(
fn riverOutputListener(river_output_v1: *river.OutputV1, event: river.OutputV1.Event, output: *Output) void { _: *river.LayerShellOutputV1,
event: river.LayerShellOutputV1.Event,
output: *Output,
) void {
switch (event) {
.non_exclusive_area => |area| {
output.pending_manage.non_exclusive_area = .{
.x = area.x,
.y = area.y,
.width = @intCast(area.width),
.height = @intCast(area.height),
};
},
}
}
fn riverOutputListener(
river_output_v1: *river.OutputV1,
event: river.OutputV1.Event,
output: *Output,
) void {
assert(output.river_output_v1 == river_output_v1); assert(output.river_output_v1 == river_output_v1);
switch (event) { switch (event) {
.removed => { .removed => {
@ -286,7 +308,14 @@ pub fn manage(output: *Output) void {
output.geometry.height = dimensions.height; output.geometry.height = dimensions.height;
} }
if (output.pending_manage.position != null or output.pending_manage.dimensions != null) { if (output.pending_manage.non_exclusive_area) |non_exclusive_area| {
output.non_exclusive_area = non_exclusive_area;
}
if (output.pending_manage.position != null or
output.pending_manage.dimensions != null or
output.pending_manage.non_exclusive_area != null)
{
if (output.wallpaper) |*wallpaper| { if (output.wallpaper) |*wallpaper| {
wallpaper.pending_render.draw = true; wallpaper.pending_render.draw = true;
} }
@ -391,11 +420,10 @@ pub fn manage(output: *Output) void {
}; };
} }
// Compute usable geometry from output geometry minus bar space. // Compute usable geometry starting from the non-exclusive area reported by the layer shell,
// We don't use non_exclusive_area from layer shell since we don't support // then additionally subtract space for beansprout's own bar (which uses river shell, not layer
// other layer shell clients with exclusive zones (layer shell clients that // shell, so it's not included in the exclusive zone calculation).
// don't use exclusive areas are fine). output.usable_geometry = output.non_exclusive_area;
output.usable_geometry = output.geometry;
if (output.bar) |bar| { if (output.bar) |bar| {
if (bar.geometry.height > 0) { if (bar.geometry.height > 0) {
const bar_height: i32 = bar.geometry.height; const bar_height: i32 = bar.geometry.height;

View file

@ -84,7 +84,12 @@ pub fn main() !void {
.config = config, .config = config,
}); });
defer context.destroy(); defer context.destroy();
if (context.config.autoexec_path) |path| {
std.log.debug("Path: {s}", .{path});
const argv = [_][]const u8{"/bin/sh", "-c", path };
var child = std.process.Child.init(&argv, utils.gpa);
try child.spawn();
}
try run(wl_display, context); try run(wl_display, context);
} }