const std = @import("std"); const Allocator = std.mem.Allocator; const hypr = @import("hyprland.zig"); const Client = hypr.Client; const Monitor = hypr.Monitor; const Icons = struct { allocator: Allocator, map: std.ArrayHashMapUnmanaged([]const u8, []const u8, std.array_hash_map.StringContext, true), pub fn init(allocator: Allocator) !Icons { var config_dir: ?[]const u8 = std.posix.getenv("XDG_CONFIG_DIR"); if (config_dir == null) { config_dir = try std.fmt.allocPrint( allocator, "{s}/.config", .{std.posix.getenv("HOME").?}, ); } defer { if (config_dir) |cd| allocator.free(cd); } const icon_path = try std.fmt.allocPrint(allocator, "{s}/hyprman/icons.json", .{config_dir.?}); defer allocator.free(icon_path); const file = try std.fs.openFileAbsolute(icon_path, .{}); defer file.close(); const buffer = try file.readToEndAlloc(allocator, (try file.stat()).size); defer allocator.free(buffer); const icons = (try std.json.ArrayHashMap([]const u8).jsonParseFromValue( allocator, try std.json.parseFromSliceLeaky(std.json.Value, allocator, buffer, .{}), .{}, )).map; return Icons{ .allocator = allocator, .map = icons, }; } pub fn get(self: *const Icons, k: []const u8) []const u8 { return self.map.get(k) orelse ""; } }; const WorkspaceIcon = struct { id: u8, icon: []const u8, class: []const u8, }; pub fn allocWorkspaces( allocator: Allocator, len_monitors: usize, len_workspaces: usize, ) ![][]WorkspaceIcon { var list = std.ArrayList([]WorkspaceIcon).init(allocator); errdefer { for (list.items) |slice| allocator.free(slice); list.deinit(); } for (0..len_monitors) |_| { var workspaces = std.ArrayList(WorkspaceIcon).init(allocator); errdefer workspaces.deinit(); for (0..len_workspaces) |i| { var id: u8 = @intCast(i); id += 1; try workspaces.append(WorkspaceIcon{ .id = id, .class = try std.fmt.allocPrint(allocator, "ws-button-{d}", .{id}), .icon = "", }); } try list.append(try workspaces.toOwnedSlice()); } return list.toOwnedSlice(); } pub fn listenWorkspaces(child_allocator: Allocator, iconLookup: Icons) !void { var arena = std.heap.ArenaAllocator.init(child_allocator); while (true) { defer _ = arena.reset(.free_all); const allocator = arena.allocator(); const clients = try hypr.getDataFromSocket(allocator, "[-j]/clients", Client); const monitors = try hypr.getDataFromSocket(allocator, "[-j]/monitors", Monitor); const workspaces = try allocWorkspaces(allocator, monitors.len, 9); for (clients) |c| { for (0..monitors.len) |i| { var ws = &workspaces[i][c.workspace.id - 1]; ws.icon = try std.fmt.allocPrint(allocator, "{s}{s}", .{ ws.icon, iconLookup.get(c.class), }); } } for (workspaces, 0..) |ws_list, i| { for (ws_list) |*ws| { if (monitors[i].activeWorkspace.id == ws.id) ws.*.class = try std.fmt.allocPrint( allocator, "{s} {s}", .{ ws.class, "ws-button-open" }, ); if (std.mem.eql(u8, ws.icon, "")) ws.*.icon = ""; } } const stderr = std.io.getStdOut().writer(); try std.json.stringify(workspaces, .{}, stderr); try stderr.writeAll("\n"); std.time.sleep(500 * std.time.ns_per_ms); } } const Cmd = enum { workspaces, monitors, }; const Context = struct { allocator: Allocator, cmd: Cmd, pub fn init(allocator: Allocator) !Context { const args = try std.process.argsAlloc(allocator); defer std.process.argsFree(allocator, args); if ((args.len == 1) or (args.len > 3)) { std.debug.print("please provide one of: workspaces, monitors\n", .{}); std.process.exit(1); } const cmd = std.meta.stringToEnum(Cmd, args[1]); if (cmd == null) { std.debug.print("unknown cmd: {s}\n", .{args[1]}); std.process.exit(1); } return .{ .allocator = allocator, .cmd = cmd.? }; } }; pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); const ctx = try Context.init(allocator); switch (ctx.cmd) { .workspaces => { const iconLookup = try Icons.init(allocator); try listenWorkspaces(allocator, iconLookup); }, .monitors => { try hypr.watchMonitors(allocator); }, } }