diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..e08c434 --- /dev/null +++ b/flake.lock @@ -0,0 +1,95 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1714314149, + "narHash": "sha256-yNAevSKF4krRWacmLUsLK7D7PlfuY3zF0lYnGYNi9vQ=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "cf8cc1201be8bc71b7cbbbdaf349b22f4f99c7ae", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1706063522, + "narHash": "sha256-o1m9en7ovSjyktXgX3n/6GJEwG06WYa/9Mfx5hTTf5g=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "95c1439b205d507f3cb88aae76e02cd6a01ac504", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "zig2nix": "zig2nix" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "zig2nix": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1714441187, + "narHash": "sha256-ZxbKh27jHGbzF0JM6l80jSzpTMhYrzkg75g9RnVPPiE=", + "owner": "Cloudef", + "repo": "zig2nix", + "rev": "d2e6a19c4c97df944142ad9d6f8b661f8bb6a716", + "type": "github" + }, + "original": { + "owner": "Cloudef", + "repo": "zig2nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..81b48a2 --- /dev/null +++ b/flake.nix @@ -0,0 +1,42 @@ +{ + description = "hyprman"; + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + zig2nix.url = "github:Cloudef/zig2nix"; + }; + + outputs = + inputs@{ + self, + nixpkgs, + zig2nix, + ... + }: + let + inherit (nixpkgs.lib) genAttrs; + supportedSystems = [ + "x86_64-linux" + "x86_64-darwin" + "aarch64-linux" + "aarch64-darwin" + ]; + forAllSystems = f: genAttrs supportedSystems (system: f nixpkgs.legacyPackages.${system}); + zig-env = + system: + zig2nix.outputs.zig-env.${system} { zig = zig2nix.outputs.packages.${system}.zig.default.bin; }; + in + { + devShells = forAllSystems (pkgs: { + default = (zig-env pkgs.system).mkShell { }; + }); + packages = forAllSystems (pkgs: { + hyprman = (zig-env pkgs.system).package { + name = "hyprman"; + version = "2023.1001"; + src = nixpkgs.lib.cleanSource ./.; + }; + default = self.packages.${pkgs.system}.hyprman; + }); + formatter = forAllSystems (pkgs: pkgs.nixfmt-rfc-style); + }; +} diff --git a/src/hyprland.zig b/src/hyprland.zig index b25222e..2ae3c4b 100644 --- a/src/hyprland.zig +++ b/src/hyprland.zig @@ -1,3 +1,5 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; pub const Workspace = struct { id: u8, @@ -10,3 +12,88 @@ pub const Client = struct { title: []const u8, }; +pub const Monitor = struct { id: u8, activeWorkspace: Workspace }; + +pub fn getSocketPath(allocator: Allocator, path: []const u8) ![]const u8 { + const runtime_dir = std.posix.getenv("XDG_RUNTIME_DIR").?; + const hyprland_instance = std.posix.getenv("HYPRLAND_INSTANCE_SIGNATURE").?; + return try std.fmt.allocPrint( + allocator, + "{s}/hypr/{s}/{s}", + .{ runtime_dir, hyprland_instance, path }, + ); +} + +pub fn getDataFromSocket(allocator: Allocator, comptime msg: []const u8, comptime T: type) ![]T { + const sock = try std.net.connectUnixSocket( + try getSocketPath(allocator, ".socket.sock"), + ); + defer sock.close(); + + _ = try sock.write(msg); + const out = try sock.reader().readAllAlloc(allocator, std.math.maxInt(u32)); + const value = try std.json.parseFromSliceLeaky( + []T, + allocator, + out, + .{ .ignore_unknown_fields = true }, + ); + + return value; +} + +const Event = enum { + monitoradded, + monitorremoved, +}; + +pub fn runEwwCommand(event: Event, allocator: Allocator) !void { + std.time.sleep(1 * std.time.ns_per_s); + std.log.debug("reacting to monitor change", .{}); + var p = std.ChildProcess.init( + &.{ + "eww", + switch (event) { + .monitoradded => "open", + .monitorremoved => "close", + }, + "bar1", + }, + allocator, + ); + _ = try p.spawnAndWait(); +} + +pub fn startEww(allocator: Allocator) !void { + const monitors = try getDataFromSocket(allocator, "[-j]/monitors", Monitor); + std.log.debug("starting eww", .{}); + for (0..monitors.len) |i| { + const bar = try std.fmt.allocPrint(allocator, "bar{d}", .{i}); + defer allocator.free(bar); + + var p = std.ChildProcess.init( + &.{ "eww", "open", bar }, + allocator, + ); + _ = try p.spawnAndWait(); + } +} +pub fn watchMonitors(allocator: Allocator) !void { + try startEww(allocator); + const sock = try std.net.connectUnixSocket( + try getSocketPath(allocator, ".socket2.sock"), + ); + defer sock.close(); + while (true) { + const out = try sock.reader().readUntilDelimiterAlloc( + allocator, + '\n', + std.math.maxInt(u32), + ); + defer allocator.free(out); + var iterator = std.mem.split(u8, out, ">>"); + if (std.meta.stringToEnum(Event, iterator.next().?)) |event| { + try runEwwCommand(event, allocator); + } + } +} diff --git a/src/main.zig b/src/main.zig index 176848c..c365df2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,38 +1,164 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const hyprland = @import("hyprland.zig"); -const XDGRD = "/run/user/1001"; -const HIS = "33e0bb14786dc22a0c82eaaf097b469d3fdeceea_1714401428_1784295750"; +const hypr = @import("hyprland.zig"); +const Client = hypr.Client; +const Monitor = hypr.Monitor; -pub fn getSockPath() []const u8 { - return XDGRD ++ "/hypr/" ++ HIS ++ "/.socket.sock"; +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 connect(allocator: Allocator, path: []const u8) !void { - const sock = try std.net.connectUnixSocket(path); - defer sock.close(); +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(); - _ = try sock.write("[-j]/clients"); - const out = try sock.reader().readAllAlloc(allocator, std.math.maxInt(u32)); - std.debug.print("{s}", .{out}); - std.debug.print("____", .{}); + 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); - // parse output? - const clients = try std.json.parseFromSliceLeaky( - []hyprland.Client, - allocator, - out, - .{.ignore_unknown_fields = true}, - ); - std.debug.print("{any}", .{clients}); + 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 listen_path = getSockPath(); - try connect(allocator, listen_path); + 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); + }, + } }