Merge branch 'new-tag-overlay'
This commit is contained in:
commit
38400c66d6
12 changed files with 143 additions and 351 deletions
|
|
@ -195,29 +195,6 @@ tag_overlay {
|
|||
| `square_inactive_border_color` | color | `0x6c7086` | Inactive tag square border |
|
||||
| `square_inactive_occupied_color` | color | `0xcdd6f4` | Inactive tag occupied indicator |
|
||||
|
||||
### Anchors
|
||||
|
||||
The `anchors` child block controls which edge(s) of the screen the overlay
|
||||
attaches to. Each direction is a boolean (`#true` / `#false`). Default: none, i.e. centered on output.
|
||||
|
||||
| Setting | Type | Default |
|
||||
|----------|------|----------|
|
||||
| `top` | bool | `#false` |
|
||||
| `right` | bool | `#false` |
|
||||
| `bottom` | bool | `#false` |
|
||||
| `left` | bool | `#false` |
|
||||
|
||||
### Margins
|
||||
|
||||
The `margins` child block sets pixel offsets from the anchored edge(s).
|
||||
|
||||
| Setting | Type | Default |
|
||||
|----------|------|---------|
|
||||
| `top` | i32 | `0` |
|
||||
| `right` | i32 | `0` |
|
||||
| `bottom` | i32 | `0` |
|
||||
| `left` | i32 | `0` |
|
||||
|
||||
## Keybinds
|
||||
|
||||
Keyboard bindings are placed inside a `keybinds` block. Each binding has the
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ These are in rough order of my priority, though no promises I do them in this or
|
|||
- [ ] Save window positions between restarts
|
||||
- [ ] Support multiple seats
|
||||
- [ ] Support clipping floating windows on edge of/between outputs
|
||||
- [ ] Use per-output timerfds for tag overlay instead of a single shared one
|
||||
- [ ] Support configurable focus-follows-window on send-to-output
|
||||
- [ ] Support configurable prepend/append on send-to-output
|
||||
- [ ] Support taking new output's tags on send-to-output
|
||||
|
|
|
|||
|
|
@ -119,8 +119,6 @@ pub fn init(context: *Context, output: *Output, options: Options) !Bar {
|
|||
}
|
||||
|
||||
pub fn deinit(bar: *Bar) void {
|
||||
bar.output.bar = null;
|
||||
|
||||
bar.timezone.deinit();
|
||||
bar.fcft_fonts.destroy();
|
||||
|
||||
|
|
@ -128,6 +126,8 @@ pub fn deinit(bar: *Bar) void {
|
|||
bar.surfaces.river_shell_surface.destroy();
|
||||
bar.surfaces.wl_surface.destroy();
|
||||
bar.context.buffer_pool.surface_count -= 1;
|
||||
|
||||
bar.output.bar = null;
|
||||
}
|
||||
|
||||
/// Update bar options in-place without destroying/recreating Wayland surfaces
|
||||
|
|
|
|||
|
|
@ -161,5 +161,10 @@ const seal = switch (builtin.target.os.tag) {
|
|||
},
|
||||
else => @compileError("target OS not supported"),
|
||||
};
|
||||
comptime {
|
||||
if (@hasField(os.linux.F, "SEAL_SEAL")) {
|
||||
@compileError("SEAL_SEAL added to std.os.linux, get rid of the hardcoded values above.");
|
||||
}
|
||||
}
|
||||
|
||||
const log = std.log.scoped(.Buffer);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ wl_registry: *wl.Registry,
|
|||
wl_shm: *wl.Shm,
|
||||
|
||||
river_layer_shell_v1: *river.LayerShellV1,
|
||||
zwlr_layer_shell_v1: *zwlr.LayerShellV1,
|
||||
|
||||
// Wayland globals that we have special structs for
|
||||
im: *InputManager,
|
||||
|
|
@ -33,10 +32,6 @@ wallpaper_image: ?*Wallpaper.Image,
|
|||
// WM Configuration
|
||||
config: *Config,
|
||||
|
||||
/// Shared timerfd for hiding tag overlays after their timeout expires.
|
||||
/// This stays null if no tag overlays exist.
|
||||
tag_overlay_timer_fd: ?posix.fd_t,
|
||||
|
||||
/// State consumed in manage() phase, reset at end of manage().
|
||||
pending_manage: PendingManage = .{},
|
||||
|
||||
|
|
@ -58,7 +53,6 @@ pub const Options = struct {
|
|||
river_window_manager_v1: *river.WindowManagerV1,
|
||||
river_xkb_bindings_v1: *river.XkbBindingsV1,
|
||||
|
||||
zwlr_layer_shell_v1: *zwlr.LayerShellV1,
|
||||
config: *Config,
|
||||
};
|
||||
|
||||
|
|
@ -73,16 +67,11 @@ pub fn create(options: Options) !*Context {
|
|||
const xkb_bindings = try XkbBindings.create(context, options.river_xkb_bindings_v1);
|
||||
errdefer xkb_bindings.destroy();
|
||||
|
||||
const env = try process.getEnvMap(utils.gpa);
|
||||
var env = try process.getEnvMap(utils.gpa);
|
||||
errdefer env.deinit();
|
||||
|
||||
const tag_overlay_timer_fd: ?posix.fd_t = if (options.config.tag_overlay_config) |_|
|
||||
posix.timerfd_create(.MONOTONIC, .{ .CLOEXEC = true }) catch |e| blk: {
|
||||
log.err("Failed to create tag overlay timer: {}", .{e});
|
||||
break :blk null;
|
||||
}
|
||||
else
|
||||
null;
|
||||
// Don't force children to have WAYLAND_DEBUG
|
||||
env.remove("WAYLAND_DEBUG");
|
||||
|
||||
context.* = .{
|
||||
.initialized = false,
|
||||
|
|
@ -92,13 +81,11 @@ pub fn create(options: Options) !*Context {
|
|||
.wl_registry = options.wl_registry,
|
||||
.wl_shm = options.wl_shm,
|
||||
.river_layer_shell_v1 = options.river_layer_shell_v1,
|
||||
.zwlr_layer_shell_v1 = options.zwlr_layer_shell_v1,
|
||||
.wallpaper_image = loadWallpaperImage(options.config),
|
||||
.im = im,
|
||||
.wm = wm,
|
||||
.xkb_bindings = xkb_bindings,
|
||||
.config = options.config,
|
||||
.tag_overlay_timer_fd = tag_overlay_timer_fd,
|
||||
};
|
||||
|
||||
return context;
|
||||
|
|
@ -113,12 +100,10 @@ pub fn destroy(context: *Context) void {
|
|||
if (context.wallpaper_image) |wallpaper_image| {
|
||||
wallpaper_image.destroy();
|
||||
}
|
||||
if (context.tag_overlay_timer_fd) |fd| posix.close(fd);
|
||||
context.buffer_pool.deinit();
|
||||
|
||||
// Destroy Wayland globals
|
||||
context.river_layer_shell_v1.destroy();
|
||||
context.zwlr_layer_shell_v1.destroy();
|
||||
context.wl_shm.destroy();
|
||||
context.wl_compositor.destroy();
|
||||
context.wl_registry.destroy();
|
||||
|
|
@ -168,30 +153,21 @@ pub fn manage(context: *Context) void {
|
|||
// Handle tag overlay config changes
|
||||
const has_overlay = new_config.tag_overlay_config != null;
|
||||
|
||||
if (!had_overlay and has_overlay) {
|
||||
// Create timerfd for newly enabled tag overlay
|
||||
context.tag_overlay_timer_fd = posix.timerfd_create(.MONOTONIC, .{ .CLOEXEC = true }) catch |e| blk: {
|
||||
log.err("Failed to create tag overlay timer: {}", .{e});
|
||||
break :blk null;
|
||||
};
|
||||
} else if (had_overlay and !has_overlay) {
|
||||
// Close timerfd for disabled tag overlay
|
||||
if (context.tag_overlay_timer_fd) |fd| posix.close(fd);
|
||||
context.tag_overlay_timer_fd = null;
|
||||
}
|
||||
|
||||
// Recreate or destroy tag overlays on all outputs
|
||||
// Reconfigure, create, or destroy tag overlays on all outputs
|
||||
if (had_overlay or has_overlay) {
|
||||
var out_it = context.wm.outputs.iterator(.forward);
|
||||
while (out_it.next()) |output| {
|
||||
// Destroy existing overlay
|
||||
if (output.tag_overlay) |*tag_overlay| {
|
||||
tag_overlay.deinit();
|
||||
output.tag_overlay = null;
|
||||
}
|
||||
// Create new overlay if configured
|
||||
// Create new overlay struct if configured (surfaces created on-demand)
|
||||
if (new_config.tag_overlay_config) |tag_overlay_config| {
|
||||
// Reconfigure existing overlay
|
||||
tag_overlay.deinitSurfaces();
|
||||
tag_overlay.reconfigure(tag_overlay_config.toTagOverlayOptions());
|
||||
} else {
|
||||
// Remove overlay
|
||||
tag_overlay.deinit();
|
||||
}
|
||||
} else if (new_config.tag_overlay_config) |tag_overlay_config| {
|
||||
// Create new overlay struct (surfaces created on-demand)
|
||||
output.tag_overlay = TagOverlay.init(context, output, tag_overlay_config.toTagOverlayOptions()) catch |e| {
|
||||
log.err("Failed to create tag overlay: {}", .{e});
|
||||
continue;
|
||||
|
|
@ -282,7 +258,6 @@ const process = std.process;
|
|||
const wayland = @import("wayland");
|
||||
const river = wayland.client.river;
|
||||
const wl = wayland.client.wl;
|
||||
const zwlr = wayland.client.zwlr;
|
||||
|
||||
const utils = @import("utils.zig");
|
||||
const Bar = @import("Bar.zig");
|
||||
|
|
|
|||
|
|
@ -102,7 +102,6 @@ pub fn create(context: *Context, river_output_v1: *river.OutputV1) !*Output {
|
|||
break :blk null;
|
||||
};
|
||||
} else null;
|
||||
errdefer if (output.tag_overlay) |*to| to.deinit();
|
||||
|
||||
output.windows.init();
|
||||
|
||||
|
|
@ -388,31 +387,15 @@ pub fn manage(output: *Output) void {
|
|||
}
|
||||
}
|
||||
|
||||
// Show tag overlay and arm the hide timer
|
||||
if (output.tag_overlay) |*tag_overlay| {
|
||||
if (tag_overlay.surfaces) |_| {
|
||||
// The overlay is already visible, but we still need to re-render
|
||||
tag_overlay.render() catch |err| {
|
||||
log.err("tag_overlay render failed: {}", .{err});
|
||||
// Show tag overlay (or refresh its timeout if already visible)
|
||||
if (output.tag_overlay) |*tag_overlay| blk: {
|
||||
tag_overlay.initSurfaces() catch |e| {
|
||||
log.err("Failed to show TagOverlay: {}", .{e});
|
||||
break :blk;
|
||||
};
|
||||
} else {
|
||||
// Create surface; the configure handler renders for us
|
||||
tag_overlay.initSurface() catch |err| {
|
||||
log.err("tag_overlay initSurface failed: {}", .{err});
|
||||
};
|
||||
}
|
||||
if (output.context.tag_overlay_timer_fd) |fd| {
|
||||
const timeout_ms: isize = tag_overlay.options.timeout;
|
||||
posix.timerfd_settime(fd, .{}, &.{
|
||||
.it_interval = .{ .sec = 0, .nsec = 0 },
|
||||
.it_value = .{
|
||||
.sec = @divFloor(timeout_ms, 1000),
|
||||
.nsec = @mod(timeout_ms, 1000) * std.time.ns_per_ms,
|
||||
},
|
||||
}, null) catch |err| {
|
||||
log.err("Failed to arm tag overlay timer: {}", .{err});
|
||||
};
|
||||
}
|
||||
tag_overlay.last_shown = time.Instant.now() catch
|
||||
std.process.fatal("System does not support a monotonic or steady clock", .{});
|
||||
tag_overlay.pending_render.draw = true;
|
||||
}
|
||||
|
||||
if (output.bar) |*bar| {
|
||||
|
|
@ -487,6 +470,10 @@ pub fn render(output: *Output) void {
|
|||
f.river_node_v1.placeTop();
|
||||
}
|
||||
}
|
||||
|
||||
if (output.tag_overlay) |*tag_overlay| {
|
||||
tag_overlay.render();
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate primary/stack layout positions for all windows.
|
||||
|
|
@ -649,12 +636,12 @@ const std = @import("std");
|
|||
const assert = std.debug.assert;
|
||||
const mem = std.mem;
|
||||
const posix = std.posix;
|
||||
const time = std.time;
|
||||
const DoublyLinkedList = std.DoublyLinkedList;
|
||||
|
||||
const wayland = @import("wayland");
|
||||
const wl = wayland.client.wl;
|
||||
const river = wayland.client.river;
|
||||
const zwlr = wayland.client.zwlr;
|
||||
|
||||
const utils = @import("utils.zig");
|
||||
const Rect = utils.Rect;
|
||||
|
|
|
|||
|
|
@ -13,19 +13,23 @@ options: Options,
|
|||
|
||||
output: *Output,
|
||||
|
||||
// Overlay geometry
|
||||
width: u31 = 0,
|
||||
height: u31 = 0,
|
||||
scale: u31 = 1,
|
||||
|
||||
rows: u31,
|
||||
|
||||
surfaces: ?struct {
|
||||
wl_surface: *wl.Surface,
|
||||
layer_surface: *zwlr.LayerSurfaceV1,
|
||||
river_shell_surface: *river.ShellSurfaceV1,
|
||||
node: *river.NodeV1,
|
||||
} = null,
|
||||
|
||||
configured: bool = false,
|
||||
/// Monotonic timestamp of the last time the overlay was shown/refreshed.
|
||||
/// Used by the event loop to hide the overlay after `options.timeout` ms.
|
||||
last_shown: ?time.Instant = null,
|
||||
|
||||
pending_render: PendingRender = .{},
|
||||
|
||||
pub const PendingRender = struct {
|
||||
draw: bool = false,
|
||||
};
|
||||
|
||||
pub const Options = struct {
|
||||
/// Width of the widget border in pixels
|
||||
|
|
@ -65,10 +69,6 @@ pub const Options = struct {
|
|||
// square_urgent_border_color: pixman.Color,
|
||||
// /// Occupied indicator color of urgent tag squares
|
||||
// square_urgent_occupied_color: pixman.Color,
|
||||
/// Directional anchors top, right bottom, left; true for on, false for off
|
||||
anchors: zwlr.LayerSurfaceV1.Anchor,
|
||||
/// Directional margins top, right, bottom, left, in pixels
|
||||
margins: struct { top: i32 = 0, right: i32 = 0, bottom: i32 = 0, left: i32 = 0 } = .{},
|
||||
/// Duration of widget display in milliseconds
|
||||
timeout: u32,
|
||||
};
|
||||
|
|
@ -84,113 +84,86 @@ pub fn init(context: *Context, output: *Output, options: Options) !TagOverlay {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn initSurface(tag_overlay: *TagOverlay) !void {
|
||||
if (tag_overlay.surfaces) |_| {
|
||||
// This tag overlay already has a layer surface, we can exit early
|
||||
pub fn deinit(tag_overlay: *TagOverlay) void {
|
||||
tag_overlay.deinitSurfaces();
|
||||
tag_overlay.output.tag_overlay = null;
|
||||
}
|
||||
|
||||
pub fn reconfigure(tag_overlay: *TagOverlay, options: Options) void {
|
||||
tag_overlay.options = options;
|
||||
tag_overlay.rows = math.divCeil(u31, options.tag_amount, options.tags_per_row) catch {
|
||||
log.err("Failed to calculate rows for tag overlay", .{});
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initSurfaces(tag_overlay: *TagOverlay) !void {
|
||||
if (tag_overlay.surfaces != null) return;
|
||||
|
||||
const context = tag_overlay.context;
|
||||
const options = tag_overlay.options;
|
||||
|
||||
const wl_surface = try context.wl_compositor.createSurface();
|
||||
errdefer wl_surface.destroy();
|
||||
|
||||
const layer_surface = try context
|
||||
.zwlr_layer_shell_v1
|
||||
.getLayerSurface(wl_surface, tag_overlay.output.wl_output, .overlay, "beansprout-tag-overlay");
|
||||
errdefer layer_surface.destroy();
|
||||
layer_surface.setExclusiveZone(-1);
|
||||
const river_shell_surface = try context
|
||||
.wm
|
||||
.river_window_manager_v1
|
||||
.getShellSurface(wl_surface);
|
||||
errdefer river_shell_surface.destroy();
|
||||
|
||||
const node = try river_shell_surface.getNode();
|
||||
errdefer node.destroy();
|
||||
|
||||
// We don't want our surface to have any input region (default is infinite)
|
||||
const empty_region = try context.wl_compositor.createRegion();
|
||||
defer empty_region.destroy();
|
||||
wl_surface.setInputRegion(empty_region);
|
||||
|
||||
const surface_width: u31 = @as(u31, @min(options.tag_amount, options.tags_per_row)) * (@as(u31, options.square_size) + options.square_padding) + options.square_padding + 2 * @as(u31, options.border_width);
|
||||
const surface_height: u31 = @as(u31, tag_overlay.rows) * (@as(u31, options.square_size) + options.square_padding) + options.square_padding + 2 * @as(u31, options.border_width);
|
||||
layer_surface.setSize(surface_width, surface_height);
|
||||
const now = time.Instant.now() catch
|
||||
std.process.fatal("System does not support a monotonic or steady clock", .{});
|
||||
|
||||
layer_surface.setAnchor(options.anchors);
|
||||
layer_surface.setMargin(options.margins.top, options.margins.right, options.margins.bottom, options.margins.left);
|
||||
|
||||
tag_overlay.surfaces = .{ .wl_surface = wl_surface, .layer_surface = layer_surface };
|
||||
context.buffer_pool.surface_count += 1;
|
||||
|
||||
layer_surface.setListener(*TagOverlay, layerSurfaceListener, tag_overlay);
|
||||
wl_surface.commit();
|
||||
tag_overlay.surfaces = .{
|
||||
.wl_surface = wl_surface,
|
||||
.river_shell_surface = river_shell_surface,
|
||||
.node = node,
|
||||
};
|
||||
tag_overlay.last_shown = now;
|
||||
tag_overlay.pending_render.draw = true;
|
||||
}
|
||||
|
||||
/// Destroy surfaces only (used to hide the overlay). The TagOverlay struct stays valid
|
||||
/// and can be re-shown by calling initSurface() again.
|
||||
pub fn deinitSurfaces(tag_overlay: *TagOverlay) void {
|
||||
tag_overlay.configured = false;
|
||||
if (tag_overlay.surfaces) |surfaces| {
|
||||
surfaces.layer_surface.destroy();
|
||||
const surfaces = tag_overlay.surfaces orelse return;
|
||||
surfaces.node.destroy();
|
||||
surfaces.river_shell_surface.destroy();
|
||||
surfaces.wl_surface.destroy();
|
||||
tag_overlay.context.buffer_pool.surface_count -= 1;
|
||||
tag_overlay.surfaces = null;
|
||||
}
|
||||
tag_overlay.last_shown = null;
|
||||
}
|
||||
|
||||
pub fn deinit(tag_overlay: *TagOverlay) void {
|
||||
tag_overlay.deinitSurfaces();
|
||||
}
|
||||
pub fn render(tag_overlay: *TagOverlay) void {
|
||||
defer tag_overlay.pending_render = .{};
|
||||
|
||||
pub fn layerSurfaceListener(
|
||||
layer_surface: *zwlr.LayerSurfaceV1,
|
||||
event: zwlr.LayerSurfaceV1.Event,
|
||||
tag_overlay: *TagOverlay,
|
||||
) void {
|
||||
assert(tag_overlay.surfaces.?.layer_surface == layer_surface);
|
||||
switch (event) {
|
||||
.configure => |ev| {
|
||||
layer_surface.ackConfigure(ev.serial);
|
||||
const width: u31 = @intCast(ev.width);
|
||||
const height: u31 = @intCast(ev.height);
|
||||
|
||||
if (tag_overlay.configured and
|
||||
tag_overlay.width == width and
|
||||
tag_overlay.height == height and
|
||||
tag_overlay.output.scale == tag_overlay.scale)
|
||||
{
|
||||
tag_overlay.surfaces.?.wl_surface.commit();
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("Configuring tag_overlay surface with width {} and height {}", .{ width, height });
|
||||
tag_overlay.width = width;
|
||||
tag_overlay.height = height;
|
||||
|
||||
// Full surface should be opaque
|
||||
const opaque_region: *wl.Region = tag_overlay.context.wl_compositor.createRegion() catch |e| {
|
||||
log.err("Failed to create opaque region for tag_overlay: {}", .{e});
|
||||
return;
|
||||
if (tag_overlay.pending_render.draw) {
|
||||
tag_overlay.draw() catch |err| {
|
||||
log.err("tag_overlay draw failed: {}", .{err});
|
||||
};
|
||||
defer opaque_region.destroy();
|
||||
opaque_region.add(0, 0, tag_overlay.width, tag_overlay.height);
|
||||
tag_overlay.surfaces.?.wl_surface.setOpaqueRegion(opaque_region);
|
||||
|
||||
tag_overlay.configured = true;
|
||||
|
||||
tag_overlay.render() catch |err| {
|
||||
log.err("tag_overlay render failed: {}", .{err});
|
||||
};
|
||||
},
|
||||
.closed => {
|
||||
tag_overlay.deinit();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(tag_overlay: *TagOverlay) !void {
|
||||
pub fn draw(tag_overlay: *TagOverlay) !void {
|
||||
const surfaces = tag_overlay.surfaces orelse return;
|
||||
const context = tag_overlay.context;
|
||||
const options = tag_overlay.options;
|
||||
|
||||
// Scaled width/height
|
||||
const surface_width: u31 = @as(u31, @min(options.tag_amount, options.tags_per_row)) * (@as(u31, options.square_size) + options.square_padding) + options.square_padding + 2 * @as(u31, options.border_width);
|
||||
const surface_height: u31 = @as(u31, tag_overlay.rows) * (@as(u31, options.square_size) + options.square_padding) + options.square_padding + 2 * @as(u31, options.border_width);
|
||||
const scale = tag_overlay.output.scale;
|
||||
const render_width = tag_overlay.width * scale;
|
||||
const render_height = tag_overlay.height * scale;
|
||||
const render_width = surface_width * scale;
|
||||
const render_height = surface_height * scale;
|
||||
|
||||
// Don't have anything to render
|
||||
if (render_width == 0 or render_height == 0) {
|
||||
|
|
@ -198,7 +171,7 @@ pub fn render(tag_overlay: *TagOverlay) !void {
|
|||
}
|
||||
const buffer = try context.buffer_pool.nextBuffer(context.wl_shm, render_width, render_height);
|
||||
|
||||
buffer.borderedRectangle(.{ .width = tag_overlay.width, .height = tag_overlay.height }, options.border_width, scale, &options.background_color, &options.border_color);
|
||||
buffer.borderedRectangle(.{ .width = surface_width, .height = surface_height }, options.border_width, scale, &options.background_color, &options.border_color);
|
||||
|
||||
const focused_tags = tag_overlay.output.tags;
|
||||
const occupied_tags = tag_overlay.output.occupiedTags();
|
||||
|
|
@ -237,8 +210,14 @@ pub fn render(tag_overlay: *TagOverlay) !void {
|
|||
}
|
||||
}
|
||||
|
||||
// Position the overlay centered on the output
|
||||
const output_geo = tag_overlay.output.geometry;
|
||||
const x = output_geo.x + @divFloor(output_geo.width - surface_width, 2);
|
||||
const y = output_geo.y + @divFloor(output_geo.height - surface_height, 2);
|
||||
surfaces.node.setPosition(x, y);
|
||||
surfaces.node.placeTop();
|
||||
|
||||
// Finally, attach the buffer to the surface
|
||||
const surfaces = tag_overlay.surfaces orelse return error.NoSurfaces;
|
||||
const wl_surface = surfaces.wl_surface;
|
||||
wl_surface.setBufferScale(scale);
|
||||
wl_surface.attach(buffer.wl_buffer, 0, 0);
|
||||
|
|
@ -249,10 +228,11 @@ pub fn render(tag_overlay: *TagOverlay) !void {
|
|||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const math = std.math;
|
||||
const time = std.time;
|
||||
|
||||
const wayland = @import("wayland");
|
||||
const wl = wayland.client.wl;
|
||||
const zwlr = wayland.client.zwlr;
|
||||
const river = wayland.client.river;
|
||||
const fcft = @import("fcft");
|
||||
const pixman = @import("pixman");
|
||||
|
||||
|
|
|
|||
|
|
@ -137,12 +137,13 @@ pub fn init(context: *Context, output: *Output) !Wallpaper {
|
|||
}
|
||||
|
||||
pub fn deinit(wallpaper: *Wallpaper) void {
|
||||
wallpaper.output.wallpaper = null;
|
||||
|
||||
wallpaper.surfaces.node.destroy();
|
||||
wallpaper.surfaces.river_shell_surface.destroy();
|
||||
wallpaper.surfaces.wl_surface.destroy();
|
||||
|
||||
wallpaper.context.buffer_pool.surface_count -= 1;
|
||||
|
||||
wallpaper.output.wallpaper = null;
|
||||
}
|
||||
|
||||
pub fn render(wallpaper: *Wallpaper) void {
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ const XkbBinding = struct {
|
|||
switch (xkb_binding.command) {
|
||||
.spawn => |cmd| {
|
||||
var child = std.process.Child.init(cmd, utils.gpa);
|
||||
child.env_map = &context.env;
|
||||
_ = child.spawn() catch |err| {
|
||||
log.err("Failed to spawn \"{s}\": {}", .{ cmd[0], err });
|
||||
};
|
||||
|
|
|
|||
|
|
@ -126,9 +126,7 @@ pub fn load(config: *Config, parser: *kdl.Parser, name: ?[]const u8, hostname: ?
|
|||
helpers.logWarnInvalidNode(node.name);
|
||||
}
|
||||
},
|
||||
.child_block_begin => {
|
||||
try helpers.skipChildBlock(parser);
|
||||
},
|
||||
.child_block_begin => try helpers.skipChildBlock(parser),
|
||||
.child_block_end => {
|
||||
try config.input_configs.append(utils.gpa, input_config);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -21,13 +21,8 @@ const NodeName = enum {
|
|||
square_inactive_border_color,
|
||||
square_inactive_occupied_color,
|
||||
timeout,
|
||||
anchors,
|
||||
margins,
|
||||
};
|
||||
|
||||
const AnchorsNodeName = enum { top, right, bottom, left };
|
||||
const MarginsNodeName = enum { top, right, bottom, left };
|
||||
|
||||
border_width: u8 = 2,
|
||||
tag_amount: u8 = 9,
|
||||
tags_per_row: u8 = 32,
|
||||
|
|
@ -44,14 +39,6 @@ square_inactive_background_color: pixman.Color = utils.parseRgbaPixmanComptime("
|
|||
square_inactive_border_color: pixman.Color = utils.parseRgbaPixmanComptime("0x6c7086"),
|
||||
square_inactive_occupied_color: pixman.Color = utils.parseRgbaPixmanComptime("0xcdd6f4"),
|
||||
timeout: u32 = 500,
|
||||
anchor_top: bool = false,
|
||||
anchor_right: bool = false,
|
||||
anchor_bottom: bool = false,
|
||||
anchor_left: bool = false,
|
||||
margin_top: i32 = 0,
|
||||
margin_right: i32 = 0,
|
||||
margin_bottom: i32 = 0,
|
||||
margin_left: i32 = 0,
|
||||
|
||||
pub fn toTagOverlayOptions(config: TagOverlayConfig) TagOverlay.Options {
|
||||
return .{
|
||||
|
|
@ -70,18 +57,6 @@ pub fn toTagOverlayOptions(config: TagOverlayConfig) TagOverlay.Options {
|
|||
.square_inactive_background_color = config.square_inactive_background_color,
|
||||
.square_inactive_border_color = config.square_inactive_border_color,
|
||||
.square_inactive_occupied_color = config.square_inactive_occupied_color,
|
||||
.anchors = .{
|
||||
.top = config.anchor_top,
|
||||
.right = config.anchor_right,
|
||||
.bottom = config.anchor_bottom,
|
||||
.left = config.anchor_left,
|
||||
},
|
||||
.margins = .{
|
||||
.top = config.margin_top,
|
||||
.right = config.margin_right,
|
||||
.bottom = config.margin_bottom,
|
||||
.left = config.margin_left,
|
||||
},
|
||||
.timeout = config.timeout,
|
||||
};
|
||||
}
|
||||
|
|
@ -89,16 +64,9 @@ pub fn toTagOverlayOptions(config: TagOverlayConfig) TagOverlay.Options {
|
|||
pub fn load(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void {
|
||||
config.tag_overlay_config = .{}; // Presence of block = enabled; initialize with defaults
|
||||
|
||||
const TagOverlayChild = enum { anchors, margins };
|
||||
var next_child_block: ?TagOverlayChild = null;
|
||||
|
||||
while (try parser.next()) |event| {
|
||||
switch (event) {
|
||||
.node => |node| {
|
||||
if (next_child_block) |child| {
|
||||
log.warn("Expected child block for tag_overlay.{s}, got node instead. Ignoring", .{@tagName(child)});
|
||||
next_child_block = null;
|
||||
}
|
||||
const node_name = std.meta.stringToEnum(NodeName, node.name);
|
||||
if (node_name) |name| {
|
||||
if (!helpers.hostMatches(node, parser, hostname)) {
|
||||
|
|
@ -107,8 +75,6 @@ pub fn load(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void {
|
|||
}
|
||||
const val_str = utils.stripQuotes(node.arg(parser, 0) orelse "");
|
||||
switch (name) {
|
||||
.anchors => next_child_block = .anchors,
|
||||
.margins => next_child_block = .margins,
|
||||
// These are all u8s, so we can inline the branch
|
||||
inline .border_width,
|
||||
.tag_amount,
|
||||
|
|
@ -152,80 +118,6 @@ pub fn load(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void {
|
|||
helpers.logWarnInvalidNode(node.name);
|
||||
}
|
||||
},
|
||||
.child_block_begin => {
|
||||
if (next_child_block) |child| {
|
||||
switch (child) {
|
||||
.anchors => try loadAnchorsBlock(config, parser, hostname),
|
||||
.margins => try loadMarginsBlock(config, parser, hostname),
|
||||
}
|
||||
next_child_block = null;
|
||||
} else {
|
||||
try helpers.skipChildBlock(parser);
|
||||
}
|
||||
},
|
||||
.child_block_end => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn loadAnchorsBlock(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void {
|
||||
while (try parser.next()) |event| {
|
||||
switch (event) {
|
||||
.node => |node| {
|
||||
const node_name = std.meta.stringToEnum(AnchorsNodeName, node.name);
|
||||
if (node_name) |name| {
|
||||
if (!helpers.hostMatches(node, parser, hostname)) {
|
||||
logDebugHostMismatch(name);
|
||||
continue;
|
||||
}
|
||||
const val_str = utils.stripQuotes(node.arg(parser, 0) orelse "");
|
||||
if (helpers.boolFromKdlStr(val_str)) |val| {
|
||||
switch (name) {
|
||||
.top => config.tag_overlay_config.?.anchor_top = val,
|
||||
.right => config.tag_overlay_config.?.anchor_right = val,
|
||||
.bottom => config.tag_overlay_config.?.anchor_bottom = val,
|
||||
.left => config.tag_overlay_config.?.anchor_left = val,
|
||||
}
|
||||
logDebugSettingNode(name, val_str);
|
||||
} else |_| {
|
||||
logWarnInvalidNodeArg(name, val_str);
|
||||
}
|
||||
} else {
|
||||
helpers.logWarnInvalidNode(node.name);
|
||||
}
|
||||
},
|
||||
.child_block_begin => try helpers.skipChildBlock(parser),
|
||||
.child_block_end => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn loadMarginsBlock(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void {
|
||||
while (try parser.next()) |event| {
|
||||
switch (event) {
|
||||
.node => |node| {
|
||||
const node_name = std.meta.stringToEnum(MarginsNodeName, node.name);
|
||||
if (node_name) |name| {
|
||||
if (!helpers.hostMatches(node, parser, hostname)) {
|
||||
logDebugHostMismatch(name);
|
||||
continue;
|
||||
}
|
||||
const val_str = utils.stripQuotes(node.arg(parser, 0) orelse "");
|
||||
const val = fmt.parseInt(i32, val_str, 10) catch {
|
||||
logWarnInvalidNodeArg(name, val_str);
|
||||
continue;
|
||||
};
|
||||
switch (name) {
|
||||
.top => config.tag_overlay_config.?.margin_top = val,
|
||||
.right => config.tag_overlay_config.?.margin_right = val,
|
||||
.bottom => config.tag_overlay_config.?.margin_bottom = val,
|
||||
.left => config.tag_overlay_config.?.margin_left = val,
|
||||
}
|
||||
logDebugSettingNode(name, val_str);
|
||||
} else {
|
||||
helpers.logWarnInvalidNode(node.name);
|
||||
}
|
||||
},
|
||||
.child_block_begin => try helpers.skipChildBlock(parser),
|
||||
.child_block_end => return,
|
||||
}
|
||||
|
|
@ -236,8 +128,6 @@ inline fn logDebugSettingNode(node_name: anytype, node_value: []const u8) void {
|
|||
const node_name_type = @TypeOf(node_name);
|
||||
switch (node_name_type) {
|
||||
NodeName => log.debug("Setting tag_overlay.{s} to {s}", .{ @tagName(node_name), node_value }),
|
||||
AnchorsNodeName => log.debug("Setting tag_overlay.anchors.{s} to {s}", .{ @tagName(node_name), node_value }),
|
||||
MarginsNodeName => log.debug("Setting tag_overlay.margins.{s} to {s}", .{ @tagName(node_name), node_value }),
|
||||
else => @compileError("This function does not (yet) support type \"" ++ @typeName(@TypeOf(node_name)) ++ "\""),
|
||||
}
|
||||
}
|
||||
|
|
@ -246,8 +136,6 @@ inline fn logDebugHostMismatch(node_name: anytype) void {
|
|||
const node_name_type = @TypeOf(node_name);
|
||||
switch (node_name_type) {
|
||||
NodeName => log.debug("Skipping \"tag_overlay.{s}\" (host mismatch)", .{@tagName(node_name)}),
|
||||
AnchorsNodeName => log.debug("Skipping \"tag_overlay.anchors.{s}\" (host mismatch)", .{@tagName(node_name)}),
|
||||
MarginsNodeName => log.debug("Skipping \"tag_overlay.margins.{s}\" (host mismatch)", .{@tagName(node_name)}),
|
||||
else => @compileError("This function does not (yet) support type \"" ++ @typeName(node_name_type) ++ "\""),
|
||||
}
|
||||
}
|
||||
|
|
@ -256,8 +144,6 @@ inline fn logWarnInvalidNodeArg(node_name: anytype, node_value: []const u8) void
|
|||
const node_name_type = @TypeOf(node_name);
|
||||
switch (node_name_type) {
|
||||
NodeName => log.warn("Invalid \"tag_overlay.{s}\" ({s}). Using default value", .{ @tagName(node_name), node_value }),
|
||||
AnchorsNodeName => log.warn("Invalid \"tag_overlay.anchors.{s}\" ({s}). Using default value", .{ @tagName(node_name), node_value }),
|
||||
MarginsNodeName => log.warn("Invalid \"tag_overlay.margins.{s}\" ({s}). Using default value", .{ @tagName(node_name), node_value }),
|
||||
else => @compileError("This function does not (yet) support type \"" ++ @typeName(node_name_type) ++ "\""),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
75
src/main.zig
75
src/main.zig
|
|
@ -12,8 +12,6 @@ const Globals = struct {
|
|||
|
||||
wl_compositor: ?*wl.Compositor = null,
|
||||
wl_shm: ?*wl.Shm = null,
|
||||
|
||||
zwlr_layer_shell_v1: ?*zwlr.LayerShellV1 = null,
|
||||
};
|
||||
|
||||
const usage: []const u8 =
|
||||
|
|
@ -68,8 +66,6 @@ pub fn main() !void {
|
|||
const river_window_manager_v1 = globals.river_window_manager_v1 orelse utils.interfaceNotAdvertised(river.WindowManagerV1);
|
||||
const river_xkb_bindings_v1 = globals.river_xkb_bindings_v1 orelse utils.interfaceNotAdvertised(river.XkbBindingsV1);
|
||||
|
||||
const zwlr_layer_shell_v1 = globals.zwlr_layer_shell_v1 orelse utils.interfaceNotAdvertised(zwlr.LayerShellV1);
|
||||
|
||||
const config = try Config.create();
|
||||
defer config.destroy();
|
||||
const context = try Context.create(.{
|
||||
|
|
@ -82,7 +78,6 @@ pub fn main() !void {
|
|||
.river_layer_shell_v1 = river_layer_shell_v1,
|
||||
.river_window_manager_v1 = river_window_manager_v1,
|
||||
.river_xkb_bindings_v1 = river_xkb_bindings_v1,
|
||||
.zwlr_layer_shell_v1 = zwlr_layer_shell_v1,
|
||||
.config = config,
|
||||
});
|
||||
defer context.destroy();
|
||||
|
|
@ -102,9 +97,8 @@ fn run(wl_display: *wl.Display, context: *Context) !void {
|
|||
|
||||
const poll_wayland = 0;
|
||||
const poll_sig = 1;
|
||||
const poll_tag_overlay_timer = 2;
|
||||
|
||||
var pollfds: [3]posix.pollfd = undefined;
|
||||
var pollfds: [2]posix.pollfd = undefined;
|
||||
|
||||
pollfds[poll_wayland] = .{
|
||||
.fd = wl_display.getFd(),
|
||||
|
|
@ -116,12 +110,6 @@ fn run(wl_display: *wl.Display, context: *Context) !void {
|
|||
.events = posix.POLL.IN,
|
||||
.revents = 0,
|
||||
};
|
||||
pollfds[poll_tag_overlay_timer] = .{
|
||||
// poll ignores negative fds
|
||||
.fd = context.tag_overlay_timer_fd orelse -1,
|
||||
.events = posix.POLL.IN,
|
||||
.revents = 0,
|
||||
};
|
||||
|
||||
while (true) {
|
||||
const flush_errno = wl_display.flush();
|
||||
|
|
@ -136,18 +124,38 @@ fn run(wl_display: *wl.Display, context: *Context) !void {
|
|||
pollfds[poll_wayland].events = posix.POLL.IN;
|
||||
}
|
||||
|
||||
// Get the number of milliseconds to the top of the next second
|
||||
const time_ns = std.time.nanoTimestamp();
|
||||
const ns_per_sec = std.time.ns_per_s;
|
||||
const remainder_ns = @mod(time_ns, ns_per_sec);
|
||||
const timeout: i32 = @intCast(@divFloor(ns_per_sec - remainder_ns, std.time.ns_per_ms));
|
||||
// Compute poll timeout: minimum of clock update and tag overlay expiry
|
||||
var timeout: i32 = blk: {
|
||||
// Milliseconds until the top of the next second (for clock updates)
|
||||
const time_ns = time.nanoTimestamp();
|
||||
const remainder_ns = @mod(time_ns, time.ns_per_s);
|
||||
break :blk @intCast(@divFloor(time.ns_per_s - remainder_ns, time.ns_per_ms));
|
||||
};
|
||||
|
||||
// Check for expired tag overlays and find the soonest expiry
|
||||
const now = time.Instant.now() catch
|
||||
fatal("System does not support a monotonic or steady clock", .{});
|
||||
{
|
||||
var it = context.wm.outputs.iterator(.forward);
|
||||
while (it.next()) |output| {
|
||||
const tag_overlay = output.tag_overlay orelse continue;
|
||||
const last_shown = tag_overlay.last_shown orelse continue;
|
||||
const elapsed_ns = now.since(last_shown);
|
||||
const timeout_ns: u64 = @as(u64, tag_overlay.options.timeout) * time.ns_per_ms;
|
||||
if (elapsed_ns >= timeout_ns) {
|
||||
output.tag_overlay.?.deinitSurfaces();
|
||||
} else {
|
||||
const remaining_ms: i32 = @intCast((timeout_ns - elapsed_ns) / time.ns_per_ms);
|
||||
timeout = @min(timeout, remaining_ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const poll_rc = posix.poll(&pollfds, timeout) catch |err| {
|
||||
fatal("Failed to poll {s}", .{@errorName(err)});
|
||||
};
|
||||
if (poll_rc == 0) {
|
||||
// If poll returns 0, it timed out, meaning we hit the top of the next second
|
||||
// and need to update the clock.
|
||||
// Update the clock
|
||||
var it = context.wm.outputs.iterator(.forward);
|
||||
while (it.next()) |output| {
|
||||
if (output.bar) |*bar| {
|
||||
|
|
@ -168,8 +176,6 @@ fn run(wl_display: *wl.Display, context: *Context) !void {
|
|||
@branchHint(.cold);
|
||||
fatal("Wayland display dispatch failed", .{});
|
||||
}
|
||||
// Re-sync in case a config reload created or destroyed the timerfd
|
||||
pollfds[poll_tag_overlay_timer].fd = context.tag_overlay_timer_fd orelse -1;
|
||||
}
|
||||
|
||||
if (pollfds[poll_sig].revents & posix.POLL.HUP != 0) {
|
||||
|
|
@ -181,24 +187,6 @@ fn run(wl_display: *wl.Display, context: *Context) !void {
|
|||
log.info("Exiting beansprout", .{});
|
||||
break;
|
||||
}
|
||||
|
||||
if (pollfds[poll_tag_overlay_timer].revents & posix.POLL.HUP != 0) {
|
||||
@branchHint(.cold);
|
||||
fatal("Tag overlay timer fd hung up", .{});
|
||||
}
|
||||
if (pollfds[poll_tag_overlay_timer].revents & posix.POLL.IN != 0) {
|
||||
// Read to consume the timer event
|
||||
var buf: [8]u8 = undefined;
|
||||
_ = posix.read(context.tag_overlay_timer_fd.?, &buf) catch {};
|
||||
|
||||
// Hide all tag overlays by destroying their surfaces
|
||||
var it = context.wm.outputs.iterator(.forward);
|
||||
while (it.next()) |output| {
|
||||
if (output.tag_overlay) |*tag_overlay| {
|
||||
tag_overlay.deinitSurfaces();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -281,11 +269,6 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *
|
|||
globals.river_xkb_bindings_v1 = registry.bind(ev.name, river.XkbBindingsV1, 2) catch |e| {
|
||||
fatal("Failed to bind to river_xkb_bindings_v1: {any}", .{@errorName(e)});
|
||||
};
|
||||
} else if (mem.orderZ(u8, ev.interface, zwlr.LayerShellV1.interface.name) == .eq) {
|
||||
if (ev.version < 3) utils.versionNotSupported(zwlr.LayerShellV1, ev.version, 3);
|
||||
globals.zwlr_layer_shell_v1 = registry.bind(ev.name, zwlr.LayerShellV1, 3) catch |e| {
|
||||
fatal("Failed to bind to zwlr_layer_shell_v1: {any}", .{@errorName(e)});
|
||||
};
|
||||
}
|
||||
},
|
||||
.global_remove => |_| {
|
||||
|
|
@ -338,11 +321,11 @@ const mem = std.mem;
|
|||
const os = std.os;
|
||||
const posix = std.posix;
|
||||
const process = std.process;
|
||||
const time = std.time;
|
||||
|
||||
const wayland = @import("wayland");
|
||||
const river = wayland.client.river;
|
||||
const wl = wayland.client.wl;
|
||||
const zwlr = wayland.client.zwlr;
|
||||
const fcft = @import("fcft");
|
||||
|
||||
const flags = @import("flags.zig");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue