// SPDX-FileCopyrightText: 2026 Ben Buhse // // 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);