Merge pull request 'Implement a basic bar' (#12) from bar into main
Reviewed-on: https://codeberg.org/bwbuhse/beansprout/pulls/12
This commit is contained in:
commit
dff3749710
19 changed files with 822 additions and 190 deletions
13
build.zig
13
build.zig
|
|
@ -16,10 +16,12 @@ pub fn build(b: *std.Build) void {
|
|||
const scanner = Scanner.create(b, .{});
|
||||
const wayland = b.createModule(.{ .root_source_file = scanner.result });
|
||||
// Rest of the deps
|
||||
const fcft = b.dependency("fcft", .{}).module("fcft");
|
||||
const kdl = b.dependency("kdl", .{}).module("kdl");
|
||||
const known_folders = b.dependency("known_folders", .{}).module("known-folders");
|
||||
const pixman = b.dependency("pixman", .{}).module("pixman");
|
||||
const xkbcommon = b.dependency("xkbcommon", .{}).module("xkbcommon");
|
||||
const zeit = b.dependency("zeit", .{}).module("zeit");
|
||||
const zigimg = b.dependency("zigimg", .{}).module("zigimg");
|
||||
|
||||
scanner.addCustomProtocol(b.path("protocol/river-input-management-v1.xml"));
|
||||
|
|
@ -57,14 +59,17 @@ pub fn build(b: *std.Build) void {
|
|||
beansprout.root_module.addOptions("build_options", options);
|
||||
|
||||
beansprout.root_module.addImport("wayland", wayland);
|
||||
beansprout.root_module.addImport("fcft", fcft);
|
||||
beansprout.root_module.addImport("kdl", kdl);
|
||||
beansprout.root_module.addImport("known_folders", known_folders);
|
||||
beansprout.root_module.addImport("pixman", pixman);
|
||||
beansprout.root_module.addImport("xkbcommon", xkbcommon);
|
||||
beansprout.root_module.addImport("zigimg", zigimg);
|
||||
beansprout.root_module.addImport("zeit", zeit);
|
||||
|
||||
beansprout.linkLibC();
|
||||
beansprout.linkSystemLibrary("wayland-client");
|
||||
beansprout.linkSystemLibrary("fcft");
|
||||
beansprout.linkSystemLibrary("pixman-1");
|
||||
beansprout.linkSystemLibrary("xkbcommon");
|
||||
|
||||
|
|
@ -117,6 +122,14 @@ const manifest: struct {
|
|||
url: []const u8,
|
||||
hash: []const u8,
|
||||
},
|
||||
fcft: struct {
|
||||
url: []const u8,
|
||||
hash: []const u8,
|
||||
},
|
||||
zeit: struct {
|
||||
url: []const u8,
|
||||
hash: []const u8,
|
||||
},
|
||||
},
|
||||
paths: []const []const u8,
|
||||
} = @import("build.zig.zon");
|
||||
|
|
|
|||
|
|
@ -28,13 +28,21 @@
|
|||
.hash = "known_folders-0.0.0-Fy-PJv3LAAABBRVoZWVrKZdyLoUfl5VRY5fqRRRdnF5L",
|
||||
},
|
||||
.pixman = .{
|
||||
.url = "https://codeberg.org/ifreund/zig-pixman/archive/387f04a0289a69d159c88b5f70396ec12a8ddb00.tar.gz",
|
||||
.hash = "pixman-0.4.0-dev-LClMn0eVAAAlXnMK-MVBZkOG0urNdOPGVQdaGecXPM5O",
|
||||
.url = "https://codeberg.org/ifreund/zig-pixman/archive/v0.3.0.tar.gz",
|
||||
.hash = "pixman-0.3.0-LClMnz2VAAAs7QSCGwLimV5VUYx0JFnX5xWU6HwtMuDX",
|
||||
},
|
||||
.zigimg = .{
|
||||
.url = "https://github.com/zigimg/zigimg/archive/fb74dfb7c6d83f2bd01a229826669451525a4ba8.tar.gz",
|
||||
.hash = "zigimg-0.1.0-8_eo2kSGFwADIkeZYTgfnLOV-khh6ZRoGmK6F2-s_QbY",
|
||||
},
|
||||
.fcft = .{
|
||||
.url = "https://git.sr.ht/~novakane/zig-fcft/archive/4bf5be61c869d08d5bcb0306049c63a9cb0795a7.tar.gz",
|
||||
.hash = "fcft-3.0.0-zcx6CxQfAADhnwm8SjyCkQF-VFHGiVarigc2de3ciInC",
|
||||
},
|
||||
.zeit = .{
|
||||
.url = "https://github.com/rockorager/zeit/archive/7ac64d72dbfb1a4ad549102e7d4e232a687d32d8.tar.gz",
|
||||
.hash = "zeit-0.6.0-5I6bk36tAgATpSl9wjFmRPMqYN2Mn0JQHgIcRNcqDpJA",
|
||||
},
|
||||
},
|
||||
|
||||
.paths = .{
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ Full command reference:
|
|||
| `move_right` | pixels | Move floating window right |
|
||||
| `resize_width` | pixels | Resize floating window width (negative to shrink) |
|
||||
| `resize_height` | pixels | Resize floating window height (negative to shrink)|
|
||||
| `center_float` | | Center the focused floating window on its output |
|
||||
| `set_output_tags` | tags (u32 bitmask) | Set the tags on the focused output |
|
||||
| `set_window_tags` | tags (u32 bitmask) | Set the tags on the focused window |
|
||||
| `toggle_output_tags` | tags (u32 bitmask) | Toggle a tag on the focused output |
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
These are in rough order of my priority, though no promises I do them in this order.
|
||||
|
||||
- [ ] Implement a river-tag-overlay clone
|
||||
- [ ] Implement an optional clock bar
|
||||
- [ ] Add options to the bar and river-tag-overlay
|
||||
- [ ] Make a Rect struct to combine x, y, width, and height
|
||||
- [ ] Support window rules (float/tags/SSD by app-id/title)
|
||||
- [ ] Support overriding config location
|
||||
- [ ] Support configuring primary vs secondary stack side
|
||||
|
|
@ -12,6 +13,7 @@ These are in rough order of my priority, though no promises I do them in this or
|
|||
- [ ] Support solid `background-color` fallback (no wallpaper)
|
||||
- [ ] Support per-output wallpapers
|
||||
- [ ] Support `focus-follows-cursor` granularity (`normal` vs `always`)
|
||||
- [ ] Save window positions between restarts
|
||||
- [ ] Support multiple seats
|
||||
- [ ] Support clipping floating windows on edge of/between outputs
|
||||
- [x] Support changeable primary ratio
|
||||
|
|
@ -26,3 +28,4 @@ These are in rough order of my priority, though no promises I do them in this or
|
|||
- [x] Support per-host config using properties
|
||||
- [x] Implement primary count/ratio per tagmask
|
||||
- [x] Add primary_count and primary_ratio to Config
|
||||
- [x] Implement an optional clock bar
|
||||
|
|
|
|||
385
src/Bar.zig
Normal file
385
src/Bar.zig
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
// SPDX-FileCopyrightText: 2026 Ben Buhse <me@benbuhse.email>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
const Bar = @This();
|
||||
|
||||
/// Standard base DPI at a scale of 1
|
||||
const base_dpi = 96;
|
||||
|
||||
context: *Context,
|
||||
|
||||
/// The timezone of the computer.
|
||||
timezone: zeit.timezone.TimeZone,
|
||||
|
||||
fonts: *fcft.Font,
|
||||
font_scale: u31 = 0,
|
||||
|
||||
output: *Output,
|
||||
|
||||
// Bar geometry
|
||||
width: u31 = 0,
|
||||
height: u31 = 0,
|
||||
|
||||
wl_surface: ?*wl.Surface = null,
|
||||
layer_surface: ?*zwlr.LayerSurfaceV1 = null,
|
||||
|
||||
configured: bool = false,
|
||||
|
||||
pub fn init(context: *Context, output: *Output) !Bar {
|
||||
// Get the local environment
|
||||
// Needed for the timezone
|
||||
// XXX: It might be better to store this in Context?
|
||||
var env = try process.getEnvMap(utils.gpa);
|
||||
defer env.deinit();
|
||||
|
||||
const timezone = try zeit.local(utils.gpa, &env);
|
||||
|
||||
return .{
|
||||
.context = context,
|
||||
.fonts = try getFcftFonts("monospace:size=14", 1),
|
||||
.timezone = timezone,
|
||||
.output = output,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Add config options for whether it's top or bottom
|
||||
pub fn initSurface(bar: *Bar) !void {
|
||||
if (bar.layer_surface) |_| {
|
||||
// This bar already has a layer surface, we can exit early
|
||||
return;
|
||||
}
|
||||
|
||||
const context = bar.context;
|
||||
|
||||
// TODO: Add padding to config
|
||||
const vertical_padding = 5;
|
||||
const bar_height: u31 = @intCast(bar.fonts.height + 2 * vertical_padding);
|
||||
|
||||
const wl_surface = try context.wl_compositor.createSurface();
|
||||
errdefer wl_surface.destroy();
|
||||
|
||||
// TODO: Allow clicking on tags to switch between them?
|
||||
// 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 layer_surface = try context
|
||||
.zwlr_layer_shell_v1
|
||||
.getLayerSurface(wl_surface, bar.output.wl_output, .top, "beansprout-bar");
|
||||
layer_surface.setSize(0, bar_height);
|
||||
layer_surface.setAnchor(.{ .top = true, .right = true, .left = true });
|
||||
|
||||
bar.wl_surface = wl_surface;
|
||||
bar.layer_surface = layer_surface;
|
||||
context.buffer_pool.surface_count += 1;
|
||||
|
||||
layer_surface.setListener(*Bar, layerSurfaceListener, bar);
|
||||
wl_surface.commit();
|
||||
}
|
||||
|
||||
pub fn deinit(bar: *Bar) void {
|
||||
bar.configured = false;
|
||||
bar.timezone.deinit();
|
||||
if (bar.wl_surface) |wl_surface| {
|
||||
wl_surface.destroy();
|
||||
}
|
||||
if (bar.layer_surface) |layer_surface| {
|
||||
layer_surface.destroy();
|
||||
bar.context.buffer_pool.surface_count -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layerSurfaceListener(
|
||||
layer_surface: *zwlr.LayerSurfaceV1,
|
||||
event: zwlr.LayerSurfaceV1.Event,
|
||||
bar: *Bar,
|
||||
) void {
|
||||
switch (event) {
|
||||
.configure => |ev| {
|
||||
layer_surface.ackConfigure(ev.serial);
|
||||
const width: u31 = @intCast(ev.width);
|
||||
const height: u31 = @intCast(ev.height);
|
||||
|
||||
if (bar.configured and
|
||||
bar.width == width and
|
||||
bar.height == height and
|
||||
bar.output.scale == bar.font_scale)
|
||||
{
|
||||
if (bar.wl_surface) |wl_surface| {
|
||||
wl_surface.commit();
|
||||
} else {
|
||||
log.warn("Bar is marked as configured but is missing a layer_surface for the wallpaper", .{});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("configuring bar surface with width {} and height {}", .{ width, height });
|
||||
bar.width = width;
|
||||
bar.height = height;
|
||||
// Excluse zone == the bar's height
|
||||
layer_surface.setExclusiveZone(bar.height);
|
||||
|
||||
// Full surface should be opaque
|
||||
const opaque_region: *wl.Region = bar.context.wl_compositor.createRegion() catch |e| {
|
||||
log.err("Failed to create opaque region for bar: {}", .{e});
|
||||
return;
|
||||
};
|
||||
// TODO: Need to change the x/y if we support anchoring to the bottom
|
||||
opaque_region.add(0, 0, bar.width, bar.height);
|
||||
defer opaque_region.destroy();
|
||||
bar.wl_surface.?.setOpaqueRegion(opaque_region);
|
||||
|
||||
bar.configured = true;
|
||||
|
||||
bar.render() catch |err| {
|
||||
log.err("Bar render failed: {}", .{err});
|
||||
};
|
||||
},
|
||||
.closed => {
|
||||
bar.deinit();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Configure number of visible tags
|
||||
/// Renders the bar and its components
|
||||
pub fn render(bar: *Bar) !void {
|
||||
const context = bar.context;
|
||||
|
||||
const scale = bar.output.scale;
|
||||
|
||||
// Recreate fonts at the output's new scale
|
||||
if (scale != bar.font_scale) {
|
||||
bar.fonts.destroy();
|
||||
bar.fonts = try getFcftFonts("monospace:size=14", scale);
|
||||
bar.font_scale = scale;
|
||||
}
|
||||
|
||||
// Scaled width/height
|
||||
const render_width = bar.width * scale;
|
||||
const render_height = bar.height * scale;
|
||||
|
||||
// Don't have anything to render
|
||||
if (render_width == 0 or render_height == 0 or scale == 0) {
|
||||
return;
|
||||
}
|
||||
const buffer = try context.buffer_pool.nextBuffer(bar.context.wl_shm, render_width, render_height);
|
||||
|
||||
// Fill with a solid color (e.g., dark background)
|
||||
// TODO: Configure text/bg colors
|
||||
const bg_color: pixman.Color = .{ .red = 0x1e1e, .green = 0x1e1e, .blue = 0x2e2e, .alpha = 0xffff };
|
||||
_ = pixman.Image.fillRectangles(
|
||||
.src,
|
||||
buffer.pixman_image,
|
||||
&bg_color,
|
||||
1,
|
||||
&[1]pixman.Rectangle16{.{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = @intCast(render_width),
|
||||
.height = @intCast(render_height),
|
||||
}},
|
||||
);
|
||||
|
||||
// Set-up text color
|
||||
const text_color: pixman.Color = .{ .red = 0xffff, .green = 0xffff, .blue = 0xffff, .alpha = 0xffff };
|
||||
const color = pixman.Image.createSolidFill(&text_color) orelse return error.FailedToCreatePixmanImage;
|
||||
defer _ = color.unref();
|
||||
|
||||
// Get the current time in seconds since the epoch,
|
||||
// then load the local timezone,
|
||||
// then convert `now` to the `local` timezone
|
||||
const now = try zeit.instant(.{});
|
||||
const now_local = now.in(&bar.timezone);
|
||||
|
||||
// Generate date/time info for this instant
|
||||
const dt = now_local.time();
|
||||
|
||||
// Convert time to a string
|
||||
var buf: [255:0]u8 = undefined;
|
||||
var fbs = io.fixedBufferStream(&buf);
|
||||
// TODO: Support 12-hour clock (%I:%M)
|
||||
try dt.strftime(fbs.writer(), "%H:%M");
|
||||
|
||||
// Convert ASCII text string to unicode
|
||||
// XXX: Not sure if this even needs to be converted to unicode
|
||||
var codepoint_it = (try unicode.Utf8View.init(fbs.getWritten())).iterator();
|
||||
const codepoint_count = try unicode.utf8CountCodepoints(fbs.getWritten());
|
||||
// We use u32 for fcft even if zig uses u21
|
||||
var codepoints: []u32 = try utils.gpa.alloc(u32, codepoint_count);
|
||||
defer utils.gpa.free(codepoints);
|
||||
var i: usize = 0;
|
||||
while (codepoint_it.nextCodepoint()) |cp| : (i += 1) {
|
||||
codepoints[i] = cp;
|
||||
}
|
||||
|
||||
const text_width = try bar.textWidth(codepoints);
|
||||
var x: i32 = @divFloor(buffer.width - text_width, 2);
|
||||
const y: i32 = @divFloor(buffer.height - bar.fonts.height, 2);
|
||||
|
||||
// Actually render the unicode codepoints
|
||||
try bar.renderChars(codepoints, buffer, &x, y, color);
|
||||
|
||||
// Finally, attach the buffer to the surface
|
||||
const wl_surface = bar.wl_surface orelse return;
|
||||
wl_surface.setBufferScale(scale);
|
||||
wl_surface.attach(buffer.wl_buffer, 0, 0);
|
||||
wl_surface.damageBuffer(0, 0, render_width, render_height);
|
||||
wl_surface.commit();
|
||||
}
|
||||
|
||||
// TODO: This should be moved to utils once fonts are in config
|
||||
/// Computes the pixel width of a text string.
|
||||
fn textWidth(bar: *Bar, text: []const u32) !i32 {
|
||||
var width: i32 = 0;
|
||||
for (text, 0..) |cp, i| {
|
||||
const glyph = try bar.fonts.rasterizeCharUtf32(cp, .default);
|
||||
width += glyph.advance.x;
|
||||
if (i > 0) {
|
||||
var x_kern: c_long = 0;
|
||||
if (bar.fonts.kerning(text[i - 1], cp, &x_kern, null)) {
|
||||
width += @intCast(x_kern);
|
||||
}
|
||||
}
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
// Borrowed and modified from https://git.sr.ht/~novakane/zig-fcft-example
|
||||
fn renderChars(
|
||||
bar: *Bar,
|
||||
text: []const u32,
|
||||
buffer: *Buffer,
|
||||
x: *i32,
|
||||
y: i32,
|
||||
color: *pixman.Image,
|
||||
) !void {
|
||||
const glyphs = try utils.gpa.alloc(*const fcft.Glyph, text.len);
|
||||
const kerns = try utils.gpa.alloc(c_long, text.len);
|
||||
defer utils.gpa.free(glyphs);
|
||||
defer utils.gpa.free(kerns);
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < text.len) : (i += 1) {
|
||||
glyphs[i] = try bar.fonts.rasterizeCharUtf32(text[i], .default);
|
||||
|
||||
kerns[i] = 0;
|
||||
if (i > 0) {
|
||||
var x_kern: c_long = 0;
|
||||
if (bar.fonts.kerning(text[i - 1], text[i], &x_kern, null)) {
|
||||
kerns[i] = x_kern;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bar.renderGlyphs(buffer, x, y, color, text.len, glyphs.ptr, kerns);
|
||||
}
|
||||
|
||||
// Borrowed https://git.sr.ht/~novakane/zig-fcft-example
|
||||
fn renderGlyphs(
|
||||
bar: *Bar,
|
||||
buffer: *Buffer,
|
||||
x: *i32,
|
||||
y: i32,
|
||||
color: *pixman.Image,
|
||||
len: usize,
|
||||
glyphs: [*]*const fcft.Glyph,
|
||||
kerns: ?[]c_long,
|
||||
) void {
|
||||
var i: usize = 0;
|
||||
while (i < len) : (i += 1) {
|
||||
if (kerns) |kern| x.* += @intCast(kern[i]);
|
||||
|
||||
switch (pixman.Image.getFormat(glyphs[i].pix)) {
|
||||
// Glyph is a pre-rendered image. (e.g. a color emoji)
|
||||
.a8r8g8b8 => {
|
||||
pixman.Image.composite32(
|
||||
.over,
|
||||
glyphs[i].pix,
|
||||
null,
|
||||
buffer.pixman_image,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
x.* + @as(i32, @intCast(glyphs[i].x)),
|
||||
y + bar.fonts.ascent - @as(i32, @intCast(glyphs[i].y)),
|
||||
glyphs[i].width,
|
||||
glyphs[i].height,
|
||||
);
|
||||
},
|
||||
// Glyph is an alpha mask.
|
||||
else => {
|
||||
pixman.Image.composite32(
|
||||
.over,
|
||||
color,
|
||||
glyphs[i].pix,
|
||||
buffer.pixman_image,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
x.* + @as(i32, @intCast(glyphs[i].x)),
|
||||
y + bar.fonts.ascent - @as(i32, @intCast(glyphs[i].y)),
|
||||
glyphs[i].width,
|
||||
glyphs[i].height,
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
x.* += glyphs[i].advance.x;
|
||||
}
|
||||
}
|
||||
|
||||
// Borrowed and modified from https://git.sr.ht/~novakane/zig-fcft-example
|
||||
fn getFcftFonts(fonts: []const u8, scale: u31) !*fcft.Font {
|
||||
// Create an arena to free just for this function;
|
||||
// It makes cleaning up the ArrayList easier.
|
||||
var arena = std.heap.ArenaAllocator.init(utils.gpa);
|
||||
defer arena.deinit();
|
||||
const arena_alloc = arena.allocator();
|
||||
|
||||
var list = try std.ArrayList([*:0]const u8).initCapacity(arena_alloc, 2);
|
||||
|
||||
var it = mem.tokenizeScalar(u8, fonts, ',');
|
||||
while (it.next()) |font| {
|
||||
if (scale > 1) {
|
||||
// If scale >1, we append :dpi so we can scale the font
|
||||
const scaled = try arena_alloc.dupeZ(
|
||||
u8,
|
||||
try std.fmt.allocPrint(arena_alloc, "{s}:dpi={}", .{ font, @as(u32, base_dpi) * scale }),
|
||||
);
|
||||
try list.append(arena_alloc, scaled);
|
||||
} else {
|
||||
try list.append(arena_alloc, try arena_alloc.dupeZ(u8, font));
|
||||
}
|
||||
}
|
||||
|
||||
const fcft_fonts = try fcft.Font.fromName(list.items[0..], null);
|
||||
errdefer fcft_fonts.destroy();
|
||||
fcft_fonts.setEmojiPresentation(.default);
|
||||
|
||||
return fcft_fonts;
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const io = std.io;
|
||||
const mem = std.mem;
|
||||
const process = std.process;
|
||||
const unicode = std.unicode;
|
||||
|
||||
const wayland = @import("wayland");
|
||||
const wl = wayland.client.wl;
|
||||
const zwlr = wayland.client.zwlr;
|
||||
const fcft = @import("fcft");
|
||||
const pixman = @import("pixman");
|
||||
const zeit = @import("zeit");
|
||||
|
||||
const utils = @import("utils.zig");
|
||||
const Buffer = @import("Buffer.zig");
|
||||
const Context = @import("Context.zig");
|
||||
const Output = @import("Output.zig");
|
||||
|
||||
const log = std.log.scoped(.Bar);
|
||||
|
|
@ -61,7 +61,7 @@ pub fn init(shm: *wl.Shm, width: u31, height: u31) !Buffer {
|
|||
@as(c_int, @intCast(height)),
|
||||
@as([*c]u32, @ptrCast(data)),
|
||||
@as(c_int, @intCast(stride)),
|
||||
) orelse return error.NoPixmanImage;
|
||||
) orelse return error.FailedToCreatePixmanImage;
|
||||
|
||||
// The pixman image and the Wayland buffer now share the same memory.
|
||||
return .{
|
||||
|
|
|
|||
|
|
@ -477,7 +477,22 @@ fn loadKeybindsChildBlock(config: *Config, parser: *kdl.Parser, hostname: ?[]con
|
|||
logWarnMissingNodeArg(name, "command");
|
||||
continue;
|
||||
});
|
||||
const split_exec = try utils.tokenizeToOwnedSlices(exec_str, ' ');
|
||||
var split_exec = try utils.tokenizeToOwnedSlices(exec_str, ' ');
|
||||
if (split_exec.len > 0) {
|
||||
// Expand ~ in executable paths
|
||||
const expanded = expandTilde(split_exec[0]) catch |e| {
|
||||
if (e == error.HomeNotSet) {
|
||||
// No ~, just return what we had.
|
||||
break :sw .{ .spawn = split_exec };
|
||||
} else {
|
||||
return e;
|
||||
}
|
||||
};
|
||||
// tokenizeToOwnedSlices dupes each token, so we have to
|
||||
// free the original value before replacing it.
|
||||
utils.gpa.free(split_exec[0]);
|
||||
split_exec[0] = expanded;
|
||||
}
|
||||
break :sw .{ .spawn = split_exec };
|
||||
},
|
||||
.change_ratio => {
|
||||
|
|
@ -506,6 +521,7 @@ fn loadKeybindsChildBlock(config: *Config, parser: *kdl.Parser, hostname: ?[]con
|
|||
.decrement_primary_count,
|
||||
.swap_next,
|
||||
.swap_prev,
|
||||
.center_float,
|
||||
=> |cmd| {
|
||||
// None of these have arguments, just create the union and give it back
|
||||
break :sw @unionInit(XkbBindings.Command, @tagName(cmd), {});
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ wl_registry: *wl.Registry,
|
|||
wl_shm: *wl.Shm,
|
||||
wl_outputs: *std.AutoHashMapUnmanaged(u32, *wl.Output),
|
||||
|
||||
river_layer_shell_v1: *river.LayerShellV1,
|
||||
zwlr_layer_shell_v1: *zwlr.LayerShellV1,
|
||||
|
||||
// Wayland globals that we have special structs for
|
||||
|
|
@ -68,6 +69,7 @@ pub fn create(options: Options) !*Context {
|
|||
.wl_registry = options.wl_registry,
|
||||
.wl_shm = options.wl_shm,
|
||||
.wl_outputs = options.wl_outputs,
|
||||
.river_layer_shell_v1 = options.river_layer_shell_v1,
|
||||
.zwlr_layer_shell_v1 = options.zwlr_layer_shell_v1,
|
||||
.wallpaper_image = loadWallpaperImage(options.config),
|
||||
.im = try InputManager.create(context, options.river_input_manager_v1, options.river_libinput_config_v1),
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ link: wl.list.Link,
|
|||
|
||||
pub fn create(river_input_device_v1: *river.InputDeviceV1) !*InputDevice {
|
||||
const input_device = try utils.gpa.create(InputDevice);
|
||||
errdefer input_device.destroy();
|
||||
errdefer utils.gpa.destroy(input_device);
|
||||
|
||||
input_device.* = .{
|
||||
.river_input_device_v1 = river_input_device_v1,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ libinput_devices: wl.list.Head(LibinputDevice, .link),
|
|||
pub fn create(context: *Context, river_input_manager_v1: *river.InputManagerV1, river_libinput_config_v1: *river.LibinputConfigV1) !*InputManager {
|
||||
log.debug("Creating new InputManager", .{});
|
||||
const im = try utils.gpa.create(InputManager);
|
||||
errdefer im.destroy();
|
||||
errdefer utils.gpa.destroy(im);
|
||||
|
||||
im.* = .{
|
||||
.context = context,
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ link: wl.list.Link,
|
|||
|
||||
pub fn create(context: *Context, river_libinput_device_v1: *river.LibinputDeviceV1) !*LibinputDevice {
|
||||
const libinput_device = try utils.gpa.create(LibinputDevice);
|
||||
errdefer libinput_device.destroy();
|
||||
errdefer utils.gpa.destroy(libinput_device);
|
||||
|
||||
libinput_device.* = .{
|
||||
.context = context,
|
||||
|
|
@ -107,7 +107,6 @@ pub fn destroy(libinput_device: *LibinputDevice) void {
|
|||
fn riverLibinputDeviceV1Listener(river_libinput_device_v1: *river.LibinputDeviceV1, event: river.LibinputDeviceV1.Event, libinput_device: *LibinputDevice) void {
|
||||
assert(libinput_device.river_libinput_device_v1 == river_libinput_device_v1);
|
||||
const im = libinput_device.context.im;
|
||||
log.debug("bwbuhse: {s} for {d}", .{ @tagName(event), river_libinput_device_v1.getId() });
|
||||
switch (event) {
|
||||
.removed => {
|
||||
river_libinput_device_v1.destroy();
|
||||
|
|
|
|||
187
src/Output.zig
187
src/Output.zig
|
|
@ -7,23 +7,38 @@ const Output = @This();
|
|||
context: *Context,
|
||||
|
||||
river_output_v1: *river.OutputV1,
|
||||
river_layer_shell_output_v1: *river.LayerShellOutputV1,
|
||||
|
||||
// We have to wait for the rwm.wl_output event to get this
|
||||
wl_output: ?*wl.Output = null,
|
||||
|
||||
// Friendly name of this output
|
||||
name: ?[]const u8 = null,
|
||||
|
||||
// Output geometry
|
||||
scale: u31 = 1,
|
||||
width: u31 = 0,
|
||||
height: u31 = 0,
|
||||
x: i32 = 0,
|
||||
y: i32 = 0,
|
||||
width: u31 = 0,
|
||||
height: u31 = 0,
|
||||
|
||||
// Area left after layer shell surfaces take exclusive area
|
||||
usable_x: i32 = 0,
|
||||
usable_y: i32 = 0,
|
||||
usable_width: u31 = 0,
|
||||
usable_height: u31 = 0,
|
||||
|
||||
// Information for this Output's wallpaper
|
||||
render_width: u31 = 0,
|
||||
render_height: u31 = 0,
|
||||
wallpaper_render_scale: u31 = 0,
|
||||
wallpaper_render_width: u31 = 0,
|
||||
wallpaper_render_height: u31 = 0,
|
||||
wl_surface: ?*wl.Surface = null,
|
||||
layer_surface: ?*zwlr.LayerSurfaceV1 = null,
|
||||
|
||||
// TODO: Make Bar a user option, can disable if they want
|
||||
// This Output's bar
|
||||
bar: ?Bar,
|
||||
|
||||
/// Proportion of output width taken by the primary stack
|
||||
primary_ratio: f32,
|
||||
|
||||
|
|
@ -41,9 +56,6 @@ tags: u32 = 0x0001,
|
|||
/// State consumed in manage() phase, reset at end of manage().
|
||||
pending_manage: PendingManage = .{},
|
||||
|
||||
// Friendly name of this output
|
||||
name: ?[]const u8 = null,
|
||||
|
||||
/// Used for wallpaper rendering management
|
||||
configured: bool = false,
|
||||
|
||||
|
|
@ -58,10 +70,15 @@ pub const TagLayoutOverride = struct {
|
|||
};
|
||||
|
||||
pub const PendingManage = struct {
|
||||
width: ?u31 = null,
|
||||
height: ?u31 = null,
|
||||
x: ?i32 = null,
|
||||
y: ?i32 = null,
|
||||
width: ?u31 = null,
|
||||
height: ?u31 = null,
|
||||
|
||||
usable_x: ?i32 = null,
|
||||
usable_y: ?i32 = null,
|
||||
usable_width: ?u31 = null,
|
||||
usable_height: ?u31 = null,
|
||||
|
||||
tags: ?u32 = null,
|
||||
primary_ratio: ?f32 = null,
|
||||
|
|
@ -70,11 +87,18 @@ pub const PendingManage = struct {
|
|||
|
||||
pub fn create(context: *Context, river_output_v1: *river.OutputV1) !*Output {
|
||||
var output = try utils.gpa.create(Output);
|
||||
errdefer output.destroy();
|
||||
errdefer utils.gpa.destroy(output);
|
||||
|
||||
const bar = Bar.init(context, output) catch |e| blk: {
|
||||
log.err("Failed to create a bar: {}", .{e});
|
||||
break :blk null;
|
||||
};
|
||||
|
||||
output.* = .{
|
||||
.context = context,
|
||||
.river_output_v1 = river_output_v1,
|
||||
.river_layer_shell_output_v1 = try context.river_layer_shell_v1.getOutput(river_output_v1),
|
||||
.bar = bar,
|
||||
.primary_count = context.config.primary_count,
|
||||
.primary_ratio = context.config.primary_ratio,
|
||||
.windows = undefined, // we will initialize this shortly
|
||||
|
|
@ -84,6 +108,7 @@ pub fn create(context: *Context, river_output_v1: *river.OutputV1) !*Output {
|
|||
output.windows.init();
|
||||
|
||||
output.river_output_v1.setListener(*Output, riverOutputListener, output);
|
||||
output.river_layer_shell_output_v1.setListener(*Output, riverLayerShellOutputListener, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
|
@ -94,10 +119,10 @@ pub fn destroy(output: *Output) void {
|
|||
window.link.remove();
|
||||
window.destroy();
|
||||
}
|
||||
|
||||
output.tag_layout_overrides.deinit(utils.gpa);
|
||||
output.deinitWallpaperLayerSurface();
|
||||
output.river_output_v1.destroy();
|
||||
output.river_layer_shell_output_v1.destroy();
|
||||
utils.gpa.destroy(output);
|
||||
}
|
||||
|
||||
|
|
@ -188,14 +213,20 @@ fn riverOutputListener(river_output_v1: *river.OutputV1, event: river.OutputV1.E
|
|||
output.wl_output = output.context.wl_outputs.get(ev.name).?;
|
||||
output.wl_output.?.setListener(*Output, wlOutputListener, output);
|
||||
|
||||
// The wl_output's initial events (mode, scale, name, done) were likely
|
||||
// already delivered during the initial roundtrip before we set our
|
||||
// listener, so the .done event that triggers wallpaper init was lost.
|
||||
// Explicitly init the wallpaper surface here.
|
||||
// The wl_output's initial events come during the initial roundtrip
|
||||
// before we set our listener, so the .done event that triggers
|
||||
// wallpaper init was lost. Explicitly init the surfaces here.
|
||||
output.initWallpaperLayerSurface() catch |err| {
|
||||
const output_name = output.name orelse "some output";
|
||||
log.err("failed to add a surface to {s}: {}", .{ output_name, err });
|
||||
};
|
||||
if (output.bar) |*bar| {
|
||||
bar.initSurface() catch |err| {
|
||||
const output_name = output.name orelse "some output";
|
||||
log.err("failed to init bar for {s}: {}", .{ output_name, err });
|
||||
return;
|
||||
};
|
||||
}
|
||||
},
|
||||
.dimensions => |ev| {
|
||||
// Protocol guarantees that width and height are strictly greater than zero
|
||||
|
|
@ -229,6 +260,25 @@ fn wlOutputListener(_: *wl.Output, event: wl.Output.Event, output: *Output) void
|
|||
log.err("failed to add a surface to {s}: {}", .{ output_name, err });
|
||||
return;
|
||||
};
|
||||
if (output.bar) |*bar| {
|
||||
bar.initSurface() catch |err| {
|
||||
const output_name = output.name orelse "some output";
|
||||
log.err("failed to init bar for {s}: {}", .{ output_name, err });
|
||||
return;
|
||||
};
|
||||
// Re-render bar if the scale changed
|
||||
if (bar.configured and output.scale != bar.font_scale) {
|
||||
bar.render() catch |err| {
|
||||
log.err("Bar render failed: {}", .{err});
|
||||
};
|
||||
}
|
||||
}
|
||||
// Re-render wallpaper if scale changed
|
||||
if (output.configured and output.scale != output.wallpaper_render_scale) {
|
||||
output.renderWallpaper() catch |err| {
|
||||
log.err("Wallpaper render failed: {}", .{err});
|
||||
};
|
||||
}
|
||||
},
|
||||
.scale => |ev| {
|
||||
if (ev.factor < 0) {
|
||||
|
|
@ -245,32 +295,52 @@ fn wlOutputListener(_: *wl.Output, event: wl.Output.Event, output: *Output) void
|
|||
}
|
||||
}
|
||||
|
||||
// Used for the river_layer_shell_output_v1 interface
|
||||
fn riverLayerShellOutputListener(
|
||||
river_layer_shell_output_v1: *river.LayerShellOutputV1,
|
||||
event: river.LayerShellOutputV1.Event,
|
||||
output: *Output,
|
||||
) void {
|
||||
assert(output.river_layer_shell_output_v1 == river_layer_shell_output_v1);
|
||||
switch (event) {
|
||||
.non_exclusive_area => |ev| {
|
||||
output.pending_manage.usable_x = ev.x;
|
||||
output.pending_manage.usable_y = ev.y;
|
||||
output.pending_manage.usable_width = @intCast(ev.width);
|
||||
output.pending_manage.usable_height = @intCast(ev.height);
|
||||
output.context.wm.river_window_manager_v1.manageDirty();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initWallpaperLayerSurface(output: *Output) !void {
|
||||
if (output.context.wallpaper_image == null) {
|
||||
// No wallpaper image, so we don't need any surfaces
|
||||
return;
|
||||
}
|
||||
|
||||
if (output.wl_surface) |_| {
|
||||
if (output.layer_surface) |_| {
|
||||
// This output already has a layer surface, we can exit early
|
||||
return;
|
||||
}
|
||||
|
||||
const context = output.context;
|
||||
|
||||
const wl_surface: *wl.Surface = try context.wl_compositor.createSurface();
|
||||
const wl_surface = try context.wl_compositor.createSurface();
|
||||
errdefer wl_surface.destroy();
|
||||
|
||||
// We don't want our surface to have any input region (default is infinite)
|
||||
const empty_region: *wl.Region = try context.wl_compositor.createRegion();
|
||||
const empty_region = try context.wl_compositor.createRegion();
|
||||
defer empty_region.destroy();
|
||||
wl_surface.setInputRegion(empty_region);
|
||||
|
||||
// Full surface should be opaque
|
||||
const opaque_region: *wl.Region = try context.wl_compositor.createRegion();
|
||||
const opaque_region = try context.wl_compositor.createRegion();
|
||||
opaque_region.add(0, 0, output.width, output.height);
|
||||
defer opaque_region.destroy();
|
||||
wl_surface.setOpaqueRegion(opaque_region);
|
||||
|
||||
const layer_surface: *zwlr.LayerSurfaceV1 = try context.zwlr_layer_shell_v1.getLayerSurface(wl_surface, output.wl_output, .background, "beansprout");
|
||||
const layer_surface = try context.zwlr_layer_shell_v1.getLayerSurface(wl_surface, output.wl_output, .background, "beansprout-wallpaper");
|
||||
layer_surface.setExclusiveZone(-1);
|
||||
layer_surface.setAnchor(.{ .top = true, .right = true, .bottom = true, .left = true });
|
||||
|
||||
|
|
@ -301,15 +371,14 @@ fn wallpaperLayerSurfaceListener(layer_surface: *zwlr.LayerSurfaceV1, event: zwl
|
|||
.configure => |ev| {
|
||||
layer_surface.ackConfigure(ev.serial);
|
||||
|
||||
if (ev.width < 0 or ev.height < 0) {
|
||||
// I'm not actually sure if this is possible, but just to be safe
|
||||
log.warn("Received zwlr_layer_surface_v1.configure event with a negative width or height ({d}x{d})", .{ ev.width, ev.height });
|
||||
return;
|
||||
}
|
||||
const width: u31 = @intCast(ev.width);
|
||||
const height: u31 = @intCast(ev.height);
|
||||
|
||||
if (output.configured and output.render_width == width and output.render_height == height) {
|
||||
if (output.configured and
|
||||
output.wallpaper_render_width == width and
|
||||
output.wallpaper_render_height == height and
|
||||
output.scale == output.wallpaper_render_scale)
|
||||
{
|
||||
if (output.wl_surface) |wl_surface| {
|
||||
wl_surface.commit();
|
||||
} else {
|
||||
|
|
@ -319,8 +388,8 @@ fn wallpaperLayerSurfaceListener(layer_surface: *zwlr.LayerSurfaceV1, event: zwl
|
|||
}
|
||||
|
||||
log.debug("configuring wallpaper surface with width {} and height {}", .{ width, height });
|
||||
output.render_width = width;
|
||||
output.render_height = height;
|
||||
output.wallpaper_render_width = width;
|
||||
output.wallpaper_render_height = height;
|
||||
output.configured = true;
|
||||
|
||||
output.renderWallpaper() catch |err| {
|
||||
|
|
@ -334,15 +403,15 @@ fn wallpaperLayerSurfaceListener(layer_surface: *zwlr.LayerSurfaceV1, event: zwl
|
|||
}
|
||||
|
||||
/// Calculates image_dimension / (output_dimension * scale)
|
||||
fn calculate_scale(image_dimension: c_int, output_dimension: u31, scale: u31) f64 {
|
||||
fn calculateScale(image_dimension: c_int, output_dimension: u31, scale: u31) f64 {
|
||||
const numerator: f64 = @floatFromInt(image_dimension);
|
||||
const denominator: f64 = @floatFromInt(output_dimension * scale);
|
||||
|
||||
return numerator / denominator;
|
||||
}
|
||||
|
||||
/// Calculates (image_dimension / dimension_scale - output_dimension) / 2 / dimension_scale;
|
||||
fn calculate_transform(image_dimension: c_int, output_dimension: u31, dimension_scale: f64) f64 {
|
||||
/// Calculates (image_dimension / dimension_scale - output_dimension) / 2 / dimension_scale
|
||||
fn calculateTransform(image_dimension: c_int, output_dimension: u31, dimension_scale: f64) f64 {
|
||||
const numerator1: f64 = @floatFromInt(image_dimension);
|
||||
const denominator1: f64 = dimension_scale;
|
||||
const subtruend: f64 = @floatFromInt(output_dimension);
|
||||
|
|
@ -354,8 +423,8 @@ fn calculate_transform(image_dimension: c_int, output_dimension: u31, dimension_
|
|||
/// Render the wallpaper image onto the layer surface
|
||||
pub fn renderWallpaper(output: *Output) !void {
|
||||
const context = output.context;
|
||||
const width = output.render_width;
|
||||
const height = output.render_height;
|
||||
const width = output.wallpaper_render_width;
|
||||
const height = output.wallpaper_render_height;
|
||||
const scale = output.scale;
|
||||
|
||||
// Don't have anything to render
|
||||
|
|
@ -375,13 +444,13 @@ pub fn renderWallpaper(output: *Output) !void {
|
|||
|
||||
const pix = pixman.Image.createBitsNoClear(image_format, image_width, image_height, image_data, image_stride) orelse {
|
||||
log.err("Failed to copy the wallpaper image for rendering", .{});
|
||||
return error.ImageCopyError;
|
||||
return error.FailedToCreatePixmanImage;
|
||||
};
|
||||
defer _ = pix.unref();
|
||||
|
||||
// Calculate image scale
|
||||
var sx: f64 = @as(f64, @floatFromInt(image_width)) / @as(f64, @floatFromInt(width * scale));
|
||||
var sy: f64 = calculate_scale(image_height, height, scale);
|
||||
var sy: f64 = calculateScale(image_height, height, scale);
|
||||
|
||||
const s = if (sx > sy) sy else sx;
|
||||
sx = s;
|
||||
|
|
@ -389,8 +458,8 @@ pub fn renderWallpaper(output: *Output) !void {
|
|||
|
||||
// Calculate translation offsets to center the image on the output.
|
||||
// If the scaled image is larger than the output, the offset crops equally from both sides.
|
||||
const tx: f64 = calculate_transform(image_width, width, sx);
|
||||
const ty: f64 = calculate_transform(image_height, height, sy);
|
||||
const tx: f64 = calculateTransform(image_width, width * scale, sx);
|
||||
const ty: f64 = calculateTransform(image_height, height * scale, sy);
|
||||
|
||||
// Build a combined source-to-destination transform matrix.
|
||||
// Pixman transforms map destination pixels back to source pixels, so:
|
||||
|
|
@ -421,22 +490,37 @@ pub fn renderWallpaper(output: *Output) !void {
|
|||
wl_surface.attach(buffer.wl_buffer, 0, 0);
|
||||
wl_surface.damageBuffer(0, 0, width * scale, height * scale);
|
||||
wl_surface.commit();
|
||||
|
||||
output.wallpaper_render_scale = scale;
|
||||
}
|
||||
|
||||
pub fn manage(output: *Output) void {
|
||||
defer output.pending_manage = .{};
|
||||
|
||||
if (output.pending_manage.x) |x| {
|
||||
output.x = x;
|
||||
}
|
||||
if (output.pending_manage.y) |y| {
|
||||
output.y = y;
|
||||
}
|
||||
if (output.pending_manage.width) |width| {
|
||||
output.width = width;
|
||||
}
|
||||
if (output.pending_manage.height) |height| {
|
||||
output.height = height;
|
||||
}
|
||||
if (output.pending_manage.x) |x| {
|
||||
output.x = x;
|
||||
|
||||
if (output.pending_manage.usable_x) |usable_x| {
|
||||
output.usable_x = usable_x;
|
||||
}
|
||||
if (output.pending_manage.y) |y| {
|
||||
output.y = y;
|
||||
if (output.pending_manage.usable_y) |usable_y| {
|
||||
output.usable_y = usable_y;
|
||||
}
|
||||
if (output.pending_manage.usable_width) |usable_width| {
|
||||
output.usable_width = usable_width;
|
||||
}
|
||||
if (output.pending_manage.usable_height) |usable_height| {
|
||||
output.usable_height = usable_height;
|
||||
}
|
||||
|
||||
if (output.pending_manage.primary_ratio) |primary_ratio| {
|
||||
|
|
@ -522,12 +606,11 @@ fn calculatePrimaryStackLayout(output: *Output) void {
|
|||
|
||||
if (active_count == 0) return;
|
||||
|
||||
// Output dimensions come as i32 from the protocol, convert to u31 for window dimensions
|
||||
// since they can't be negative.
|
||||
const output_width: u31 = @intCast(output.width);
|
||||
const output_height: u31 = @intCast(output.height);
|
||||
const output_x = output.x;
|
||||
const output_y = output.y;
|
||||
// We have to use the usable area for the layout so windows don't overlap with widgets
|
||||
const output_x = output.usable_x;
|
||||
const output_y = output.usable_y;
|
||||
const output_width = output.usable_width;
|
||||
const output_height = output.usable_height;
|
||||
const border_width = output.context.config.border_width;
|
||||
|
||||
// Single window: maximize and return early
|
||||
|
|
@ -601,6 +684,15 @@ fn calculatePrimaryStackLayout(output: *Output) void {
|
|||
assert(active_list.first == null);
|
||||
}
|
||||
|
||||
pub fn occupiedTags(output: *Output) u32 {
|
||||
var occupied_tags: u32 = 0x0000;
|
||||
var it = output.windows.iterator(.forward);
|
||||
while (it.next()) |window| {
|
||||
occupied_tags |= window.tags;
|
||||
}
|
||||
return occupied_tags;
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const mem = std.mem;
|
||||
|
|
@ -613,6 +705,7 @@ const zwlr = wayland.client.zwlr;
|
|||
const pixman = @import("pixman");
|
||||
|
||||
const utils = @import("utils.zig");
|
||||
const Bar = @import("Bar.zig");
|
||||
const Buffer = @import("Buffer.zig");
|
||||
const Context = @import("Context.zig");
|
||||
const Window = @import("Window.zig");
|
||||
|
|
|
|||
140
src/Seat.zig
140
src/Seat.zig
|
|
@ -7,9 +7,11 @@ const Seat = @This();
|
|||
context: *Context,
|
||||
|
||||
river_seat_v1: *river.SeatV1,
|
||||
river_layer_shell_seat_v1: *river.LayerShellSeatV1,
|
||||
|
||||
focused_window: ?*Window,
|
||||
focused_output: ?*Output,
|
||||
layer_focus: LayerFocus = .focus_none,
|
||||
|
||||
pointer_op: PointerOp = .none,
|
||||
|
||||
|
|
@ -22,9 +24,14 @@ link: wl.list.Link,
|
|||
move_pointer_binding: ?*river.PointerBindingV1 = null,
|
||||
resize_pointer_binding: ?*river.PointerBindingV1 = null,
|
||||
|
||||
// We just steal the Event's tag type to use as our enum. If another event is added
|
||||
// that's *not* for focus, we'll have to create our own enum and just keep it in sync.
|
||||
pub const LayerFocus = @typeInfo(river.LayerShellSeatV1.Event).@"union".tag_type.?;
|
||||
|
||||
pub const PendingManage = struct {
|
||||
window: ?PendingWindow = null,
|
||||
output: ?PendingOutput = null,
|
||||
layer_focus: ?LayerFocus = null,
|
||||
should_warp_pointer: bool = false,
|
||||
|
||||
op_delta: ?struct { dx: i32, dy: i32 } = null,
|
||||
|
|
@ -58,17 +65,19 @@ pub const PointerOp = union(enum) {
|
|||
|
||||
pub fn create(context: *Context, river_seat_v1: *river.SeatV1) !*Seat {
|
||||
var seat = try utils.gpa.create(Seat);
|
||||
errdefer seat.destroy();
|
||||
errdefer utils.gpa.destroy(seat);
|
||||
|
||||
seat.* = .{
|
||||
.context = context,
|
||||
.river_seat_v1 = river_seat_v1,
|
||||
.river_layer_shell_seat_v1 = try context.river_layer_shell_v1.getSeat(river_seat_v1),
|
||||
.focused_window = null,
|
||||
.focused_output = null,
|
||||
.link = undefined, // Handled by the wl.list
|
||||
};
|
||||
|
||||
seat.river_seat_v1.setListener(*Seat, seatListener, seat);
|
||||
seat.river_layer_shell_seat_v1.setListener(*Seat, riverLayerShellSeatListener, seat);
|
||||
|
||||
return seat;
|
||||
}
|
||||
|
|
@ -77,6 +86,7 @@ pub fn destroy(seat: *Seat) void {
|
|||
if (seat.move_pointer_binding) |binding| binding.destroy();
|
||||
if (seat.resize_pointer_binding) |binding| binding.destroy();
|
||||
seat.river_seat_v1.destroy();
|
||||
seat.river_layer_shell_seat_v1.destroy();
|
||||
utils.gpa.destroy(seat);
|
||||
}
|
||||
|
||||
|
|
@ -103,6 +113,11 @@ fn seatListener(river_seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: *
|
|||
}
|
||||
}
|
||||
|
||||
fn riverLayerShellSeatListener(river_layer_shell_seat_v1: *river.LayerShellSeatV1, event: river.LayerShellSeatV1.Event, seat: *Seat) void {
|
||||
assert(seat.river_layer_shell_seat_v1 == river_layer_shell_seat_v1);
|
||||
seat.pending_manage.layer_focus = std.meta.activeTag(event);
|
||||
}
|
||||
|
||||
// river_window_v1 needs to be optional because ev.window is optional
|
||||
fn setWindowFocus(seat: *Seat, river_window_v1: ?*river.WindowV1) void {
|
||||
const wv1 = river_window_v1 orelse return;
|
||||
|
|
@ -116,33 +131,37 @@ fn setWindowFocus(seat: *Seat, river_window_v1: ?*river.WindowV1) void {
|
|||
pub fn manage(seat: *Seat) void {
|
||||
defer seat.pending_manage = .{};
|
||||
|
||||
if (seat.pending_manage.window) |pending_window| {
|
||||
switch (pending_window) {
|
||||
.window => |window| {
|
||||
if (seat.focused_window) |focused| {
|
||||
// Tell the previously focused Window that it's no longer focused
|
||||
if (focused != window) {
|
||||
// Focus events are ignored by the compositor when a layer shell has exclusive focus.
|
||||
if (seat.layer_focus != .focus_exclusive) {
|
||||
if (seat.pending_manage.window) |pending_window| {
|
||||
switch (pending_window) {
|
||||
.window => |window| {
|
||||
if (seat.focused_window) |focused| {
|
||||
// Tell the previously focused Window that it's no longer focused
|
||||
if (focused != window) {
|
||||
focused.pending_render.focused = false;
|
||||
}
|
||||
}
|
||||
seat.focused_window = window;
|
||||
seat.river_seat_v1.focusWindow(window.river_window_v1);
|
||||
window.pending_render.focused = true;
|
||||
},
|
||||
.clear_focus => {
|
||||
if (seat.focused_window) |focused| {
|
||||
// Tell the previously focused Window that it's no longer focused
|
||||
focused.pending_render.focused = false;
|
||||
}
|
||||
}
|
||||
seat.focused_window = window;
|
||||
seat.river_seat_v1.focusWindow(window.river_window_v1);
|
||||
window.pending_render.focused = true;
|
||||
},
|
||||
.clear_focus => {
|
||||
if (seat.focused_window) |focused| {
|
||||
// Tell the previously focused Window that it's no longer focused
|
||||
focused.pending_render.focused = false;
|
||||
}
|
||||
seat.focused_window = null;
|
||||
seat.river_seat_v1.clearFocus();
|
||||
},
|
||||
seat.focused_window = null;
|
||||
seat.river_seat_v1.clearFocus();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
if (seat.pending_manage.output) |pending_output| {
|
||||
switch (pending_output) {
|
||||
.output => |output| {
|
||||
seat.focused_output = output;
|
||||
output.river_layer_shell_output_v1.setDefault();
|
||||
},
|
||||
.clear_focus => {
|
||||
seat.focused_output = null;
|
||||
|
|
@ -150,18 +169,42 @@ pub fn manage(seat: *Seat) void {
|
|||
}
|
||||
}
|
||||
|
||||
if (seat.pending_manage.should_warp_pointer) blk: {
|
||||
if (seat.context.config.pointer_warp_on_focus_change) {
|
||||
const window = seat.focused_window orelse {
|
||||
log.warn("Trying to warp-on-focus-change without a focused window.", .{});
|
||||
break :blk;
|
||||
};
|
||||
// Warp pointer to center of focused window;
|
||||
// because the x and y coords are set during render, we need to check if
|
||||
// there are new coordinates in window.pending_render.
|
||||
const pointer_x: i32 = (window.pending_render.x orelse window.x) + @divTrunc(window.width, 2);
|
||||
const pointer_y: i32 = (window.pending_render.y orelse window.y) + @divTrunc(window.height, 2);
|
||||
seat.river_seat_v1.pointerWarp(pointer_x, pointer_y);
|
||||
// Handle layer focus changes after window focus so focused_window is up to date.
|
||||
// This overrides whatever pending_render.focused the window focus block just set.
|
||||
if (seat.pending_manage.layer_focus) |layer_focus| {
|
||||
seat.layer_focus = layer_focus;
|
||||
switch (layer_focus) {
|
||||
.focus_exclusive,
|
||||
.focus_non_exclusive,
|
||||
=> {
|
||||
if (seat.focused_window) |focused_window| {
|
||||
focused_window.pending_render.focused = false;
|
||||
}
|
||||
},
|
||||
.focus_none => {
|
||||
if (seat.focused_window) |focused_window| {
|
||||
seat.river_seat_v1.focusWindow(focused_window.river_window_v1);
|
||||
focused_window.pending_render.focused = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Focus doesn't change during .focus_exclusive, so pointer shouldn't get warped, either
|
||||
if (seat.layer_focus != .focus_exclusive) {
|
||||
if (seat.pending_manage.should_warp_pointer) blk: {
|
||||
if (seat.context.config.pointer_warp_on_focus_change) {
|
||||
const window = seat.focused_window orelse {
|
||||
log.warn("Trying to warp-on-focus-change without a focused window.", .{});
|
||||
break :blk;
|
||||
};
|
||||
// Warp pointer to center of focused window;
|
||||
// because the x and y coords are set during render, we need to check if
|
||||
// there are new coordinates in window.pending_render.
|
||||
const pointer_x: i32 = (window.pending_render.x orelse window.x) + @divFloor(window.width, 2);
|
||||
const pointer_y: i32 = (window.pending_render.y orelse window.y) + @divFloor(window.height, 2);
|
||||
seat.river_seat_v1.pointerWarp(pointer_x, pointer_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -204,17 +247,8 @@ pub fn manage(seat: *Seat) void {
|
|||
switch (seat.pointer_op) {
|
||||
.none => {},
|
||||
.move => |op| {
|
||||
const output = op.window.output orelse {
|
||||
log.err("window has no output during move operation", .{});
|
||||
return;
|
||||
};
|
||||
const min_x = output.x;
|
||||
const max_x = output.x + output.width - @as(i32, op.window.float_width);
|
||||
const min_y = output.y;
|
||||
const max_y = output.y + output.height - @as(i32, op.window.float_height);
|
||||
|
||||
op.window.float_x = std.math.clamp(op.start_x + delta.dx, min_x, @max(min_x, max_x));
|
||||
op.window.float_y = std.math.clamp(op.start_y + delta.dy, min_y, @max(min_y, max_y));
|
||||
op.window.float_x = op.start_x + delta.dx;
|
||||
op.window.float_y = op.start_y + delta.dy;
|
||||
op.window.pending_render.x = op.window.float_x;
|
||||
op.window.pending_render.y = op.window.float_y;
|
||||
},
|
||||
|
|
@ -238,22 +272,10 @@ pub fn manage(seat: *Seat) void {
|
|||
|
||||
// Clamp to minimum size
|
||||
const min_size: i32 = 50;
|
||||
if (new_width < min_size) {
|
||||
if (op.edges.left) new_x -= min_size - new_width;
|
||||
new_width = min_size;
|
||||
}
|
||||
if (new_height < min_size) {
|
||||
if (op.edges.top) new_y -= min_size - new_height;
|
||||
new_height = min_size;
|
||||
}
|
||||
|
||||
// Clamp position to output bounds
|
||||
const output = op.window.output orelse {
|
||||
log.err("window has no output during resize operation", .{});
|
||||
return;
|
||||
};
|
||||
new_x = std.math.clamp(new_x, output.x, @max(output.x, output.x + output.width - new_width));
|
||||
new_y = std.math.clamp(new_y, output.y, @max(output.y, output.y + output.height - new_height));
|
||||
if (op.edges.left) new_x = @min(new_x, op.start_x + @as(i32, op.start_width) - min_size);
|
||||
if (op.edges.top) new_y = @min(new_y, op.start_y + @as(i32, op.start_height) - min_size);
|
||||
new_width = @max(new_width, min_size);
|
||||
new_height = @max(new_height, min_size);
|
||||
|
||||
op.window.float_width = @intCast(new_width);
|
||||
op.window.float_height = @intCast(new_height);
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ pub const PendingRender = struct {
|
|||
|
||||
pub fn create(context: *Context, river_window_v1: *river.WindowV1, output: ?*Output) !*Window {
|
||||
var window = try utils.gpa.create(Window);
|
||||
errdefer window.destroy();
|
||||
errdefer utils.gpa.destroy(window);
|
||||
|
||||
window.* = .{
|
||||
.context = context,
|
||||
|
|
@ -184,14 +184,17 @@ pub fn manage(window: *Window) void {
|
|||
river_window_v1.setTiled(.{});
|
||||
|
||||
if (window.float_width == 0) {
|
||||
// Never floated before; use current dimensions but centered on output
|
||||
window.float_width = window.width;
|
||||
window.float_height = window.height;
|
||||
// Never floated before; use 75% of usable area, centered on output
|
||||
if (window.output) |output| {
|
||||
// Need to find center and then subtract half of the window's width/height
|
||||
window.float_x = output.x + @divTrunc(output.width, 2) - @divTrunc(window.width, 2);
|
||||
window.float_y = output.y + @divTrunc(output.height, 2) - @divTrunc(window.height, 2);
|
||||
window.float_width = @divFloor(output.usable_width * 3, 4);
|
||||
window.float_height = @divFloor(output.usable_height * 3, 4);
|
||||
window.float_x = output.usable_x + @divFloor(output.usable_width, 2) - @divFloor(window.float_width, 2);
|
||||
window.float_y = output.usable_y + @divFloor(output.usable_height, 2) - @divFloor(window.float_height, 2);
|
||||
} else {
|
||||
window.float_width = window.width;
|
||||
window.float_height = window.height;
|
||||
}
|
||||
river_window_v1.proposeDimensions(window.float_width, window.float_height);
|
||||
} else {
|
||||
// Window has floated before; re-use its old dimensions
|
||||
river_window_v1.proposeDimensions(window.float_width, window.float_height);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ orphan_windows: wl.list.Head(Window, .link),
|
|||
|
||||
pub fn create(context: *Context, window_manager_v1: *river.WindowManagerV1) !*WindowManager {
|
||||
const wm = try utils.gpa.create(WindowManager);
|
||||
errdefer wm.destroy();
|
||||
errdefer utils.gpa.destroy(wm);
|
||||
|
||||
wm.* = .{
|
||||
.context = context,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ pub const Command = union(enum) {
|
|||
resize_width: i32,
|
||||
resize_height: i32,
|
||||
|
||||
// Center floating window on its output
|
||||
center_float,
|
||||
|
||||
// Swap window position in stack
|
||||
swap_next,
|
||||
swap_prev,
|
||||
|
|
@ -56,7 +59,7 @@ const XkbBinding = struct {
|
|||
|
||||
fn create(xkb_binding_v1: *river.XkbBindingV1, command: Command, context: *Context) !*XkbBinding {
|
||||
var xkb_binding = try utils.gpa.create(XkbBinding);
|
||||
errdefer xkb_binding.destroy();
|
||||
errdefer utils.gpa.destroy(xkb_binding);
|
||||
|
||||
xkb_binding.* = .{
|
||||
.xkb_binding_v1 = xkb_binding_v1,
|
||||
|
|
@ -237,6 +240,7 @@ const XkbBinding = struct {
|
|||
.move_right => |pixels| moveFloatingWindow(context, pixels, 0),
|
||||
.resize_width => |delta| resizeFloatingWindow(context, delta, 0),
|
||||
.resize_height => |delta| resizeFloatingWindow(context, 0, delta),
|
||||
.center_float => centerFloatingWindow(context),
|
||||
.swap_next => swapWindow(context, .next),
|
||||
.swap_prev => swapWindow(context, .prev),
|
||||
}
|
||||
|
|
@ -336,18 +340,9 @@ const XkbBinding = struct {
|
|||
const seat = context.wm.seats.first() orelse return;
|
||||
const window = seat.focused_window orelse return;
|
||||
if (!window.floating) return;
|
||||
const output = window.output orelse {
|
||||
log.err("focused floating window has no output during move", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
const min_x = output.x;
|
||||
const max_x = output.x + output.width - @as(i32, window.float_width);
|
||||
const min_y = output.y;
|
||||
const max_y = output.y + output.height - @as(i32, window.float_height);
|
||||
|
||||
window.float_x = std.math.clamp(window.float_x + dx, min_x, @max(min_x, max_x));
|
||||
window.float_y = std.math.clamp(window.float_y + dy, min_y, @max(min_y, max_y));
|
||||
window.float_x += dx;
|
||||
window.float_y += dy;
|
||||
window.pending_render.x = window.float_x;
|
||||
window.pending_render.y = window.float_y;
|
||||
context.wm.river_window_manager_v1.manageDirty();
|
||||
|
|
@ -357,25 +352,27 @@ const XkbBinding = struct {
|
|||
const seat = context.wm.seats.first() orelse return;
|
||||
const window = seat.focused_window orelse return;
|
||||
if (!window.floating) return;
|
||||
const output = window.output orelse {
|
||||
log.err("focused floating window has no output during resize", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
const new_width: i32 = @as(i32, window.float_width) + dw;
|
||||
const new_height: i32 = @as(i32, window.float_height) + dh;
|
||||
window.float_width = @intCast(@max(50, new_width));
|
||||
window.float_height = @intCast(@max(50, new_height));
|
||||
|
||||
// Clamp position to keep window on screen after resize
|
||||
const max_x = output.x + output.width - @as(i32, window.float_width);
|
||||
const max_y = output.y + output.height - @as(i32, window.float_height);
|
||||
window.float_x = std.math.clamp(window.float_x, output.x, @max(output.x, max_x));
|
||||
window.float_y = std.math.clamp(window.float_y, output.y, @max(output.y, max_y));
|
||||
window.pending_manage.width = window.float_width;
|
||||
window.pending_manage.height = window.float_height;
|
||||
context.wm.river_window_manager_v1.manageDirty();
|
||||
}
|
||||
|
||||
fn centerFloatingWindow(context: *Context) void {
|
||||
const seat = context.wm.seats.first() orelse return;
|
||||
const window = seat.focused_window orelse return;
|
||||
if (!window.floating) return;
|
||||
const output = window.output orelse return;
|
||||
|
||||
window.float_x = output.usable_x + @divFloor(output.usable_width, 2) - @divFloor(window.float_width, 2);
|
||||
window.float_y = output.usable_y + @divFloor(output.usable_height, 2) - @divFloor(window.float_height, 2);
|
||||
window.pending_render.x = window.float_x;
|
||||
window.pending_render.y = window.float_y;
|
||||
window.river_window_v1.proposeDimensions(window.float_width, window.float_height);
|
||||
context.wm.river_window_manager_v1.manageDirty();
|
||||
}
|
||||
|
||||
|
|
@ -405,7 +402,7 @@ bindings: wl.list.Head(XkbBinding, .link),
|
|||
|
||||
pub fn create(context: *Context, xkb_bindings_v1: *river.XkbBindingsV1) !*XkbBindings {
|
||||
const xkb_bindings = try utils.gpa.create(XkbBindings);
|
||||
errdefer xkb_bindings.destroy();
|
||||
errdefer utils.gpa.destroy(xkb_bindings);
|
||||
|
||||
xkb_bindings.* = .{
|
||||
.context = context,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pub const Flag = struct {
|
|||
kind: enum { boolean, arg },
|
||||
};
|
||||
|
||||
pub fn parser(comptime Arg: type, comptime flags: []const Flag) type {
|
||||
pub fn Parser(comptime Arg: type, comptime flags: []const Flag) type {
|
||||
switch (Arg) {
|
||||
// TODO consider allowing []const u8
|
||||
[:0]const u8, [*:0]const u8 => {}, // ok
|
||||
|
|
|
|||
174
src/main.zig
174
src/main.zig
|
|
@ -37,46 +37,17 @@ const usage: []const u8 =
|
|||
;
|
||||
|
||||
pub fn main() !void {
|
||||
const result = flags.parser([*:0]const u8, &.{
|
||||
.{ .name = "h", .kind = .boolean },
|
||||
.{ .name = "version", .kind = .boolean },
|
||||
.{ .name = "log-level", .kind = .arg },
|
||||
}).parse(std.os.argv[1..]) catch {
|
||||
try stderr.writeAll(usage);
|
||||
try stderr.flush();
|
||||
posix.exit(1);
|
||||
};
|
||||
if (result.flags.h) {
|
||||
try stdout.writeAll(usage);
|
||||
try stdout.flush();
|
||||
posix.exit(0);
|
||||
}
|
||||
if (result.args.len != 0) {
|
||||
log.err("unknown option '{s}'", .{result.args[0]});
|
||||
try stderr.writeAll(usage);
|
||||
try stderr.flush();
|
||||
posix.exit(1);
|
||||
}
|
||||
parseArgs();
|
||||
|
||||
if (result.flags.version) {
|
||||
try stdout.writeAll(build_options.version ++ "\n");
|
||||
try stdout.flush();
|
||||
posix.exit(0);
|
||||
}
|
||||
if (result.flags.@"log-level") |level| {
|
||||
if (mem.eql(u8, level, "error")) {
|
||||
runtime_log_level = .err;
|
||||
} else if (mem.eql(u8, level, "warning")) {
|
||||
runtime_log_level = .warn;
|
||||
} else if (mem.eql(u8, level, "info")) {
|
||||
runtime_log_level = .info;
|
||||
} else if (mem.eql(u8, level, "debug")) {
|
||||
runtime_log_level = .debug;
|
||||
} else {
|
||||
log.err("invalid log level '{s}'", .{level});
|
||||
posix.exit(1);
|
||||
}
|
||||
}
|
||||
// Initialize fcft
|
||||
const fcft_log_level: fcft.LogClass = switch (runtime_log_level) {
|
||||
.err => .err,
|
||||
.warn => .warning,
|
||||
.info => .info,
|
||||
.debug => .debug,
|
||||
};
|
||||
_ = fcft.init(.auto, false, fcft_log_level);
|
||||
defer fcft.fini();
|
||||
|
||||
const wayland_display_var = try utils.gpa.dupeZ(u8, process.getEnvVarOwned(utils.gpa, "WAYLAND_DISPLAY") catch {
|
||||
fatal("Error getting WAYLAND_DISPLAY environment variable. Exiting", .{});
|
||||
|
|
@ -130,13 +101,130 @@ pub fn main() !void {
|
|||
});
|
||||
defer context.destroy();
|
||||
|
||||
try run(wl_display, context);
|
||||
}
|
||||
|
||||
/// Function to handle the main event loop
|
||||
///
|
||||
/// Since we've added a bar with a clock,we need
|
||||
fn run(wl_display: *wl.Display, context: *Context) !void {
|
||||
var mask = posix.sigemptyset();
|
||||
|
||||
posix.sigaddset(&mask, posix.SIG.INT);
|
||||
posix.sigaddset(&mask, posix.SIG.QUIT);
|
||||
|
||||
posix.sigprocmask(posix.SIG.BLOCK, &mask, null);
|
||||
|
||||
const sig_fd = try posix.signalfd(-1, &mask, 0);
|
||||
|
||||
const poll_wayland = 0;
|
||||
const poll_sig = 1;
|
||||
|
||||
var pollfds: [2]posix.pollfd = undefined;
|
||||
|
||||
pollfds[poll_wayland] = .{
|
||||
.fd = wl_display.getFd(),
|
||||
.events = posix.POLL.IN,
|
||||
.revents = 0,
|
||||
};
|
||||
pollfds[poll_sig] = .{
|
||||
.fd = sig_fd,
|
||||
.events = posix.POLL.IN,
|
||||
.revents = 0,
|
||||
};
|
||||
|
||||
while (true) {
|
||||
if (wl_display.dispatch() != .SUCCESS) {
|
||||
fatal("wayland display dispatch failed", .{});
|
||||
const errno = wl_display.flush();
|
||||
if (errno != .SUCCESS) {
|
||||
fatal("wl_display flush failed: E{s}", .{@tagName(errno)});
|
||||
}
|
||||
|
||||
// Get the number of milliseconds to the top of the next minute
|
||||
const time = std.time.timestamp();
|
||||
if (time < 0) {
|
||||
log.err("Got a negative time ({d})", .{time});
|
||||
return error.InvalidTime;
|
||||
}
|
||||
const timeout: i32 = @intCast((@divFloor(time, 60) * 60 + 60 - time) * 1000);
|
||||
|
||||
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 minute
|
||||
// and need to update the clock.
|
||||
var it = context.wm.outputs.iterator(.forward);
|
||||
while (it.next()) |output| {
|
||||
if (output.bar) |*bar| {
|
||||
bar.render() catch |err| {
|
||||
log.err("Bar timer render failed: {}", .{err});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle fds that became ready
|
||||
if (pollfds[poll_wayland].revents & posix.POLL.HUP != 0) {
|
||||
log.info("Disconnected by compositor", .{});
|
||||
break;
|
||||
}
|
||||
if (pollfds[poll_wayland].revents & posix.POLL.IN != 0) {
|
||||
if (wl_display.dispatch() != .SUCCESS) {
|
||||
fatal("Wayland display dispatch failed", .{});
|
||||
}
|
||||
}
|
||||
|
||||
if (pollfds[poll_sig].revents & posix.POLL.HUP != 0) {
|
||||
fatal("Signal fd hung up", .{});
|
||||
}
|
||||
if (pollfds[poll_sig].revents & posix.POLL.IN != 0) {
|
||||
log.info("Exiting beansprout", .{});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Exiting beansprout", .{});
|
||||
fn parseArgs() void {
|
||||
const result = flags.Parser([*:0]const u8, &.{
|
||||
.{ .name = "h", .kind = .boolean },
|
||||
.{ .name = "version", .kind = .boolean },
|
||||
.{ .name = "log-level", .kind = .arg },
|
||||
}).parse(os.argv[1..]) catch {
|
||||
stderr.writeAll(usage) catch {};
|
||||
stderr.flush() catch {};
|
||||
posix.exit(1);
|
||||
};
|
||||
if (result.flags.h) {
|
||||
stdout.writeAll(usage) catch {};
|
||||
stdout.flush() catch {};
|
||||
posix.exit(0);
|
||||
}
|
||||
if (result.args.len != 0) {
|
||||
log.err("unknown option '{s}'", .{result.args[0]});
|
||||
stderr.writeAll(usage) catch {};
|
||||
stderr.flush() catch {};
|
||||
posix.exit(1);
|
||||
}
|
||||
|
||||
if (result.flags.version) {
|
||||
stdout.writeAll(build_options.version ++ "\n") catch {};
|
||||
stdout.flush() catch {};
|
||||
posix.exit(0);
|
||||
}
|
||||
if (result.flags.@"log-level") |level| {
|
||||
if (mem.eql(u8, level, "error")) {
|
||||
runtime_log_level = .err;
|
||||
} else if (mem.eql(u8, level, "warning")) {
|
||||
runtime_log_level = .warn;
|
||||
} else if (mem.eql(u8, level, "info")) {
|
||||
runtime_log_level = .info;
|
||||
} else if (mem.eql(u8, level, "debug")) {
|
||||
runtime_log_level = .debug;
|
||||
} else {
|
||||
log.err("invalid log level '{s}'", .{level});
|
||||
posix.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void {
|
||||
|
|
@ -239,6 +327,7 @@ const std = @import("std");
|
|||
const fatal = std.process.fatal;
|
||||
const fs = std.fs;
|
||||
const mem = std.mem;
|
||||
const os = std.os;
|
||||
const posix = std.posix;
|
||||
const process = std.process;
|
||||
|
||||
|
|
@ -246,6 +335,7 @@ 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");
|
||||
const utils = @import("utils.zig");
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ pub fn parseModifiers(s: []const u8) !?river.SeatV1.Modifiers {
|
|||
return modifiers;
|
||||
}
|
||||
|
||||
pub fn tokenizeToOwnedSlices(input: []const u8, delimiter: u8) ![]const []const u8 {
|
||||
pub fn tokenizeToOwnedSlices(input: []const u8, delimiter: u8) ![][]const u8 {
|
||||
var list: std.ArrayList([]const u8) = .empty;
|
||||
var it = std.mem.tokenizeScalar(u8, input, delimiter);
|
||||
while (it.next()) |part| {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue