beansprout-custom/src/utils.zig
Ben Buhse bb612c273e
Add REUSE licensing for non-code files
CC-BY-4.0 for documentation, CC0-1.0 for examples and .gitignore,
HPND for wlr-layer-shell protocol.

Also switch to GPL-3.0-only
2026-02-11 14:50:04 -06:00

144 lines
4.9 KiB
Zig

// SPDX-FileCopyrightText: 2026 Ben Buhse <me@benbuhse.email>
//
// SPDX-License-Identifier: GPL-3.0-only
// Allocator used by the program. We use the c_allocator since we interact with C code
// via zig-wayland (and, in the future as of 2026-01-26, other libraries, too).
pub const allocator = std.heap.c_allocator;
pub const RiverColor = struct {
red: u32,
green: u32,
blue: u32,
alpha: u32,
};
/// Parse a color in the format 0xRRGGBB or 0xRRGGBBAA and convert it to
/// 32-bit color values (used by Window.set_borders in rwm).
pub fn parseRgba(s: []const u8) !RiverColor {
if (s.len != 8 and s.len != 10) return error.InvalidRgba;
if (s[0] != '0' or s[1] != 'x') return error.InvalidRgba;
// If the color is 0xRRGGBB, add FF for the alpha channel
var color = try fmt.parseUnsigned(u32, s[2..], 16);
if (s.len == 8) {
color <<= 8;
color |= 0xff;
}
const bytes: [4]u8 = @as([4]u8, @bitCast(color));
return parseRgbaHelper(bytes);
}
/// Parse a color in the format 0xRRGGBB or 0xRRGGBBAA and convert it to
/// 32-bit color values (used by Window.set_borders in rwm) at comptime.
pub fn parseRgbaComptime(comptime s: []const u8) RiverColor {
if (s.len != 8 and s.len != 10) @compileError("Invalid RGBA");
if (s[0] != '0' or s[1] != 'x') @compileError("Invalid RGBA");
// If the color is 0xRRGGBB, add FF for the alpha channel
comptime var color = try fmt.parseUnsigned(u32, s[2..], 16);
if (s.len == 8) {
color <<= 8;
color |= 0xff;
}
const bytes = @as([4]u8, @bitCast(color));
return parseRgbaHelper(bytes);
}
fn parseRgbaHelper(bytes: [4]u8) RiverColor {
const r: u32 = bytes[3];
const g: u32 = bytes[2];
const b: u32 = bytes[1];
const a: u32 = bytes[0];
// To convert from an 8-bit color to 32-bit color, we need to do
// color * 2^32 / 2^8
// which is equivalent to
// color * 2^24
// or, in other words,
// color << 24
return .{
.red = r << 24,
.green = g << 24,
.blue = b << 24,
.alpha = a << 24,
};
}
/// Parse a modifier string like "mod4+shift+ctrl" into river.SeatV1.Modifiers.
/// Modifier names are case-insensitive. Returns null if any modifier is unrecognized.
pub fn parseModifiers(s: []const u8) !?river.SeatV1.Modifiers {
var modifiers: river.SeatV1.Modifiers = .{};
var it = mem.splitScalar(u8, s, '+');
while (it.next()) |part| {
// Modifier names are 3 (alt) to 5 (shift/super) characters long,
// other length can't be correctly formatted.
if (part.len < 3 or part.len > 5) return null;
// Case-insensitive comparison by lowercasing
const lower = try std.ascii.allocLowerString(utils.allocator, part);
defer utils.allocator.free(lower);
if (mem.eql(u8, lower, "none")) {
// No modifier bits to set
} else if (mem.eql(u8, lower, "mod4") or mem.eql(u8, lower, "super")) {
modifiers.mod4 = true;
} else if (mem.eql(u8, lower, "shift")) {
modifiers.shift = true;
} else if (mem.eql(u8, lower, "ctrl")) {
modifiers.ctrl = true;
} else if (mem.eql(u8, lower, "mod1") or mem.eql(u8, lower, "alt")) {
modifiers.mod1 = true;
} else if (mem.eql(u8, lower, "mod3")) {
modifiers.mod3 = true;
} else if (mem.eql(u8, lower, "mod5")) {
modifiers.mod5 = true;
} else {
return null;
}
}
return modifiers;
}
pub fn tokenizeToOwnedSlices(input: []const u8, delimiter: u8) ![]const []const u8 {
var list: std.ArrayList([]const u8) = .empty;
var it = std.mem.tokenizeScalar(u8, input, delimiter);
while (it.next()) |part| {
const duped = try allocator.dupe(u8, part);
try list.append(utils.allocator, duped);
}
return list.toOwnedSlice(utils.allocator);
}
pub fn stripQuotes(s: []const u8) []const u8 {
if (s.len >= 2 and s[0] == '"' and s[s.len - 1] == '"') {
return s[1 .. s.len - 1];
}
return s;
}
/// Report that the given WaylandGlobal wasn't advertised and exit the program
pub fn interfaceNotAdvertised(comptime WaylandGlobal: type) noreturn {
fatal("{s} not advertised. Exiting", .{WaylandGlobal.interface.name});
}
/// Report that the given WaylandGlobal was advertised but the support version was too low and exit the program
pub fn versionNotSupported(comptime WaylandGlobal: type, have_version: u32, need_version: u32) noreturn {
fatal("The compositor only advertised {s} version {d} but version {d} is required. Exiting", .{ WaylandGlobal.interface.name, have_version, need_version });
}
const std = @import("std");
const fatal = std.process.fatal;
const fmt = std.fmt;
const mem = std.mem;
const wayland = @import("wayland");
const river = wayland.client.river;
const utils = @import("utils.zig");
const log = std.log.scoped(.utils);