Compare commits
2 commits
contrib-au
...
contrib
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
417f85ebd7 | ||
|
|
f4f056f991 |
7 changed files with 70 additions and 73 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
# beansprout
|
# beansprout
|
||||||
|
> Forked version of [beansprout/beansprout](https://codeberg.org/beansprout/beansprout.git).
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
|
||||||
31
src/Bar.zig
31
src/Bar.zig
|
|
@ -35,8 +35,6 @@ 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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -46,7 +44,7 @@ const PendingRender = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Position = enum { top, bottom };
|
pub const Position = enum { top, bottom };
|
||||||
pub const Component = enum { title, clock, wm_info, none };
|
pub const Component = enum { title, clock, wm_info, custom, none };
|
||||||
|
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
/// Comma separated list of FontConfig formatted font specifications
|
/// Comma separated list of FontConfig formatted font specifications
|
||||||
|
|
@ -69,6 +67,8 @@ pub const Options = struct {
|
||||||
/// strftime format string for the clock display
|
/// strftime format string for the clock display
|
||||||
time_format: []const u8 = default_time_format,
|
time_format: []const u8 = default_time_format,
|
||||||
|
|
||||||
|
/// Custom command to execute for the custom display
|
||||||
|
custom_command: []const u8 = "uname -r",
|
||||||
/// Which component to show on the left side of the bar
|
/// Which component to show on the left side of the bar
|
||||||
left: Component = .title,
|
left: Component = .title,
|
||||||
/// Which component to show in the center of the bar
|
/// Which component to show in the center of the bar
|
||||||
|
|
@ -161,10 +161,7 @@ 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);
|
||||||
// Use the non-exclusive area so the bar sits adjacent to any external layer shell
|
const width: u31 = output.geometry.width -| @as(u31, @intCast(options.margins.left + options.margins.right));
|
||||||
// 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;
|
||||||
|
|
@ -176,10 +173,10 @@ pub fn manage(bar: *Bar) !void {
|
||||||
bar.surfaces.wl_surface.setOpaqueRegion(opaque_region);
|
bar.surfaces.wl_surface.setOpaqueRegion(opaque_region);
|
||||||
}
|
}
|
||||||
|
|
||||||
const x = base.x + options.margins.left;
|
const x = output.geometry.x + options.margins.left;
|
||||||
const y = switch (options.position) {
|
const y = switch (options.position) {
|
||||||
.top => base.y + options.margins.top,
|
.top => output.geometry.y + options.margins.top,
|
||||||
.bottom => base.y + base.height - bar.geometry.height - options.margins.bottom,
|
.bottom => output.geometry.y + output.geometry.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;
|
||||||
|
|
@ -277,25 +274,31 @@ fn draw(bar: *Bar) !void {
|
||||||
const wm_info_codepoints = try utils.utf8ToCodepoints(wm_info_writer.buffered());
|
const wm_info_codepoints = try utils.utf8ToCodepoints(wm_info_writer.buffered());
|
||||||
defer utils.gpa.free(wm_info_codepoints);
|
defer utils.gpa.free(wm_info_codepoints);
|
||||||
|
|
||||||
|
// Processing custom component
|
||||||
|
const argv = [_][]const u8{ "sh", "-c", options.custom_command};
|
||||||
|
const custom_str = try utils.execCommand(utils.gpa, &argv);
|
||||||
|
const custom_codepoints = try utils.utf8ToCodepoints(custom_str);
|
||||||
|
defer utils.gpa.free(custom_codepoints);
|
||||||
// Map a Component to its pre-computed codepoints slice
|
// Map a Component to its pre-computed codepoints slice
|
||||||
const componentSlice = struct {
|
const componentSlice = struct {
|
||||||
fn f(component: Component, clock: []u32, title: []u32, wm_info: []u32) []u32 {
|
fn f(component: Component, clock: []u32, title: []u32, wm_info: []u32, custom: []u32) []u32 {
|
||||||
return switch (component) {
|
return switch (component) {
|
||||||
.clock => clock,
|
.clock => clock,
|
||||||
.title => title,
|
.title => title,
|
||||||
.wm_info => wm_info,
|
.wm_info => wm_info,
|
||||||
|
.custom => custom,
|
||||||
.none => &.{},
|
.none => &.{},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}.f;
|
}.f;
|
||||||
|
|
||||||
// Measure center first; needed to constrain left and right widths
|
// Measure center first; needed to constrain left and right widths
|
||||||
const center_codepoints = componentSlice(options.center, clock_codepoints, title_codepoints, wm_info_codepoints);
|
const center_codepoints = componentSlice(options.center, clock_codepoints, title_codepoints, wm_info_codepoints, custom_codepoints);
|
||||||
const center_width = try bar.textWidth(center_codepoints);
|
const center_width = try bar.textWidth(center_codepoints);
|
||||||
var center_x: i32 = @divFloor(buffer.width - center_width, 2);
|
var center_x: i32 = @divFloor(buffer.width - center_width, 2);
|
||||||
|
|
||||||
// Render left slot
|
// Render left slot
|
||||||
const left_codepoints = componentSlice(options.left, clock_codepoints, title_codepoints, wm_info_codepoints);
|
const left_codepoints = componentSlice(options.left, clock_codepoints, title_codepoints, wm_info_codepoints, custom_codepoints);
|
||||||
if (left_codepoints.len > 0) {
|
if (left_codepoints.len > 0) {
|
||||||
const max_width = center_x - 2 * options.horizontal_padding;
|
const max_width = center_x - 2 * options.horizontal_padding;
|
||||||
const truncated = try bar.truncateToWidth(left_codepoints, max_width);
|
const truncated = try bar.truncateToWidth(left_codepoints, max_width);
|
||||||
|
|
@ -304,7 +307,7 @@ fn draw(bar: *Bar) !void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render right slot
|
// Render right slot
|
||||||
const right_codepoints = componentSlice(options.right, clock_codepoints, title_codepoints, wm_info_codepoints);
|
const right_codepoints = componentSlice(options.right, clock_codepoints, title_codepoints, wm_info_codepoints, custom_codepoints);
|
||||||
if (right_codepoints.len > 0) {
|
if (right_codepoints.len > 0) {
|
||||||
const max_width = buffer.width - (center_x + center_width) - 2 * options.horizontal_padding;
|
const max_width = buffer.width - (center_x + center_width) - 2 * options.horizontal_padding;
|
||||||
const truncated = try bar.truncateToWidth(right_codepoints, max_width);
|
const truncated = try bar.truncateToWidth(right_codepoints, max_width);
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,6 @@ 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,
|
||||||
|
|
@ -73,7 +70,6 @@ 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,
|
||||||
|
|
@ -229,13 +225,6 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -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 = .{},
|
||||||
|
|
||||||
/// Output geometry minus layer shell exclusive zones
|
/// Area available for window layout (output geometry minus bar space)
|
||||||
non_exclusive_area: Rect = .{},
|
/// Maybe I'll re-add support for layer shell exclusive areas later,
|
||||||
|
/// but adding that makes it more work for me and I don't personally
|
||||||
/// Area available for window layout (non_exclusive_area minus bar space)
|
/// know of anything that makes me want them since external bars won't
|
||||||
|
/// work with beansprout.
|
||||||
usable_geometry: Rect = .{},
|
usable_geometry: Rect = .{},
|
||||||
|
|
||||||
wallpaper: ?Wallpaper = null,
|
wallpaper: ?Wallpaper = null,
|
||||||
|
|
@ -64,7 +64,6 @@ 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,
|
||||||
|
|
@ -107,7 +106,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
@ -202,28 +200,8 @@ pub fn prevWindow(output: *Output, current: *Window) ?*Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn riverLayerShellOutputListener(
|
// Used for the river_output_v1 interface
|
||||||
_: *river.LayerShellOutputV1,
|
fn riverOutputListener(river_output_v1: *river.OutputV1, event: river.OutputV1.Event, output: *Output) void {
|
||||||
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 => {
|
||||||
|
|
@ -308,14 +286,7 @@ pub fn manage(output: *Output) void {
|
||||||
output.geometry.height = dimensions.height;
|
output.geometry.height = dimensions.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output.pending_manage.non_exclusive_area) |non_exclusive_area| {
|
if (output.pending_manage.position != null or output.pending_manage.dimensions != null) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
@ -420,10 +391,11 @@ pub fn manage(output: *Output) void {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute usable geometry starting from the non-exclusive area reported by the layer shell,
|
// Compute usable geometry from output geometry minus bar space.
|
||||||
// then additionally subtract space for beansprout's own bar (which uses river shell, not layer
|
// We don't use non_exclusive_area from layer shell since we don't support
|
||||||
// shell, so it's not included in the exclusive zone calculation).
|
// other layer shell clients with exclusive zones (layer shell clients that
|
||||||
output.usable_geometry = output.non_exclusive_area;
|
// don't use exclusive areas are fine).
|
||||||
|
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;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ const NodeName = enum {
|
||||||
horizontal_padding,
|
horizontal_padding,
|
||||||
margins,
|
margins,
|
||||||
time_format,
|
time_format,
|
||||||
|
custom_command,
|
||||||
};
|
};
|
||||||
const MarginsNodeName = enum { top, right, bottom, left };
|
const MarginsNodeName = enum { top, right, bottom, left };
|
||||||
|
|
||||||
|
|
@ -52,6 +53,7 @@ horizontal_padding: u8 = 5,
|
||||||
/// strftime format string for the clock display.
|
/// strftime format string for the clock display.
|
||||||
/// null means use the default.
|
/// null means use the default.
|
||||||
time_format: ?[]const u8 = null,
|
time_format: ?[]const u8 = null,
|
||||||
|
custom_command: ?[]const u8 = null,
|
||||||
|
|
||||||
pub fn toBarOptions(config: BarConfig) Bar.Options {
|
pub fn toBarOptions(config: BarConfig) Bar.Options {
|
||||||
return .{
|
return .{
|
||||||
|
|
@ -68,6 +70,7 @@ pub fn toBarOptions(config: BarConfig) Bar.Options {
|
||||||
.bottom = config.margin_bottom,
|
.bottom = config.margin_bottom,
|
||||||
.left = config.margin_left,
|
.left = config.margin_left,
|
||||||
},
|
},
|
||||||
|
.custom_command = config.custom_command orelse "uname -r",
|
||||||
.vertical_padding = config.vertical_padding,
|
.vertical_padding = config.vertical_padding,
|
||||||
.horizontal_padding = config.horizontal_padding,
|
.horizontal_padding = config.horizontal_padding,
|
||||||
.time_format = config.time_format orelse Bar.default_time_format,
|
.time_format = config.time_format orelse Bar.default_time_format,
|
||||||
|
|
@ -123,6 +126,13 @@ pub fn load(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void {
|
||||||
logWarnInvalidNodeArg(name, val_str);
|
logWarnInvalidNodeArg(name, val_str);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
.custom_command => {
|
||||||
|
if (node.argcount() < 1) {
|
||||||
|
logWarnMissingNodeArg(name, "custom command");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
config.bar_config.?.custom_command = utils.gpa.dupe(u8, val_str) catch @panic("Out of memory");
|
||||||
|
},
|
||||||
.margins => next_child_block = .margins,
|
.margins => next_child_block = .margins,
|
||||||
inline .background_color,
|
inline .background_color,
|
||||||
.text_color,
|
.text_color,
|
||||||
|
|
|
||||||
|
|
@ -84,12 +84,7 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,33 @@ pub fn versionNotSupported(comptime WaylandGlobal: type, have_version: u32, need
|
||||||
fatal("The compositor only advertised {s} version {d} but version {d} is required. Exiting", .{ WaylandGlobal.interface.name, have_version, need_version });
|
fatal("The compositor only advertised {s} version {d} but version {d} is required. Exiting", .{ WaylandGlobal.interface.name, have_version, need_version });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn execCommand(allocator: std.mem.Allocator, argv: []const []const u8) ![]u8 {
|
||||||
|
// .run() spawns the process, collects stdout/stderr, and waits for completion.
|
||||||
|
// We set a max_output_size (e.g., 50KB) to prevent runaway memory usage.
|
||||||
|
const result = try std.process.Child.run(.{
|
||||||
|
.allocator = allocator,
|
||||||
|
.argv = argv,
|
||||||
|
.max_output_bytes = 50 * 1024,
|
||||||
|
});
|
||||||
|
|
||||||
|
// We don't need stderr for this plain function, so free it immediately.
|
||||||
|
allocator.free(result.stderr);
|
||||||
|
|
||||||
|
// If the command failed (non-zero exit), you might want to handle that.
|
||||||
|
switch (result.term) {
|
||||||
|
.Exited => |code| if (code != 0) {
|
||||||
|
allocator.free(result.stdout);
|
||||||
|
return error.CommandFailed;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
allocator.free(result.stdout);
|
||||||
|
return error.CommandTerminatedAbnormally;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.stdout;
|
||||||
|
}
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const fatal = std.process.fatal;
|
const fatal = std.process.fatal;
|
||||||
const fmt = std.fmt;
|
const fmt = std.fmt;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue