work
This commit is contained in:
parent
a1d15f184c
commit
d6d42bbec1
4 changed files with 244 additions and 39 deletions
32
build.zig
32
build.zig
|
@ -1,15 +1,41 @@
|
|||
const std = @import("std");
|
||||
const Scanner = @import("wayland").Scanner;
|
||||
const path = @import("src/path.zig");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const gdbus = b.option([]const u8, "gdbus", "path to gdbus binary") orelse {
|
||||
return;
|
||||
const rivertile = b.option([]const u8, "rivertile", "path to rivertile binary") orelse rivertile: {
|
||||
const p = path.expandPath(b.allocator, "rivertile") catch {
|
||||
break :rivertile "rivertile";
|
||||
} orelse {
|
||||
break :rivertile "rivertile";
|
||||
};
|
||||
break :rivertile p;
|
||||
};
|
||||
|
||||
const fuzzel = b.option([]const u8, "fuzzel", "path to fuzzel binary") orelse fuzzel: {
|
||||
const p = path.expandPath(b.allocator, "fuzzel") catch {
|
||||
break :fuzzel "fuzzel";
|
||||
} orelse {
|
||||
break :fuzzel "fuzzel";
|
||||
};
|
||||
break :fuzzel p;
|
||||
};
|
||||
|
||||
const gdbus = b.option([]const u8, "gdbus", "path to gdbus binary") orelse gdbus: {
|
||||
const p = path.expandPath(b.allocator, "gdbus") catch {
|
||||
break :gdbus "gdbus";
|
||||
} orelse {
|
||||
break :gdbus "gdbus";
|
||||
};
|
||||
break :gdbus p;
|
||||
};
|
||||
|
||||
const build_options = b.addOptions();
|
||||
build_options.addOption([]const u8, "rivertile", rivertile);
|
||||
build_options.addOption([]const u8, "fuzzel", fuzzel);
|
||||
build_options.addOption([]const u8, "gdbus", gdbus);
|
||||
|
||||
const scanner = Scanner.create(b, .{});
|
||||
|
@ -30,10 +56,12 @@ pub fn build(b: *std.Build) void {
|
|||
.optimize = optimize,
|
||||
});
|
||||
|
||||
exe.root_module.addOptions("build_options", build_options);
|
||||
exe.root_module.addImport("wayland", wayland);
|
||||
exe.linkSystemLibrary2("wayland-client", .{});
|
||||
exe.linkLibC();
|
||||
scanner.addCSource(exe);
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
|
|
27
flake.nix
27
flake.nix
|
@ -23,7 +23,9 @@
|
|||
default = pkgs.mkShell {
|
||||
nativeBuildInputs = [
|
||||
pkgs.zig_0_13
|
||||
pkgs.glib
|
||||
pkgs.pkg-config
|
||||
pkgs.river
|
||||
pkgs.wayland-protocols
|
||||
pkgs.wayland-scanner
|
||||
];
|
||||
|
@ -36,21 +38,17 @@
|
|||
};
|
||||
};
|
||||
packages = {
|
||||
zmodem = let
|
||||
river-init = let
|
||||
cache = src:
|
||||
pkgs.stdenv.mkDerivation {
|
||||
inherit src;
|
||||
|
||||
name = "river-init-cache";
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkgs.git
|
||||
pkgs.pkg-config
|
||||
pkgs.wayland-protocols
|
||||
pkgs.wayland-scanner
|
||||
pkgs.zig_0_13.hook
|
||||
];
|
||||
buildInputs = [
|
||||
pkgs.wayland
|
||||
];
|
||||
|
||||
dontConfigure = true;
|
||||
dontUseZigBuild = true;
|
||||
|
@ -78,8 +76,23 @@
|
|||
version = "0.0.0";
|
||||
src = ./.;
|
||||
nativeBuildInputs = [
|
||||
pkgs.pkg-config
|
||||
pkgs.wayland-protocols
|
||||
pkgs.wayland-scanner
|
||||
pkgs.zig_0_13.hook
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
pkgs.glib
|
||||
pkgs.river
|
||||
pkgs.wayland
|
||||
];
|
||||
|
||||
zigBuildFlags = [
|
||||
"-Drivertile=${pkgs.river}/bin/rivertile"
|
||||
"-Dgdbus=${pkgs.glib}/bin/gdbus"
|
||||
];
|
||||
|
||||
preBuild = ''
|
||||
rm -rf $ZIG_GLOBAL_CACHE_DIR
|
||||
cp -r --reflink=auto ${cache final.src} $ZIG_GLOBAL_CACHE_DIR
|
||||
|
|
143
src/main.zig
143
src/main.zig
|
@ -1,9 +1,20 @@
|
|||
const std = @import("std");
|
||||
const build_options = @import("build_options");
|
||||
const wayland = @import("wayland");
|
||||
|
||||
const wl = wayland.client.wl;
|
||||
const zriver = wayland.client.zriver;
|
||||
|
||||
const log = std.log.scoped(.init);
|
||||
|
||||
const Error = error{
|
||||
RiverControlNotAdvertised,
|
||||
RiverStatusManagerNotAdvertised,
|
||||
SeatNotAdverstised,
|
||||
OutputNotAdverstised,
|
||||
ConnectFailed,
|
||||
};
|
||||
|
||||
pub fn main() !void {
|
||||
_main() catch |err| {
|
||||
switch (err) {
|
||||
|
@ -37,26 +48,82 @@ pub fn main() !void {
|
|||
pub const Globals = struct {
|
||||
control: ?*zriver.ControlV1 = null,
|
||||
status_manager: ?*zriver.StatusManagerV1 = null,
|
||||
output: ?*wl.Output = null,
|
||||
// output: std.ArrayListUnmanaged(*wl.Output) = .{},
|
||||
seat: ?*wl.Seat = null,
|
||||
};
|
||||
|
||||
fn _main() !void {
|
||||
fn _main() (Error || std.mem.Allocator.Error)!void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const gpa_alloc = gpa.allocator();
|
||||
|
||||
const display = try wl.Display.connect(null);
|
||||
const registry = try display.getRegistry();
|
||||
|
||||
var globals = Globals{};
|
||||
registry.setListener(*Globals, registryListener, &globals);
|
||||
if (display.roundtrip() != .SUCCESS) fatal("initial roundtrip failed", .{});
|
||||
const control = globals.control orelse return error.RiverControlNotAdvertised;
|
||||
_ = globals.status_manager orelse return error.RiverStatusManagerNotAdvertised;
|
||||
_ = globals.seat orelse return error.SeatNotAdverstised;
|
||||
_ = globals.output orelse return error.OutputNotAdverstised;
|
||||
|
||||
control.addArgument("map");
|
||||
control.addArgument("Super");
|
||||
control.addArgument("G");
|
||||
control.addArgument("spawn");
|
||||
control.addArgument("gdbus call --session --dest com.mitchellh.ghostty --object-path /com/mitchellh/ghostty --method org.gtk.Actions.Activate new_window [] []'");
|
||||
if (display.roundtrip() != .SUCCESS)
|
||||
fatal("initial roundtrip failed", .{});
|
||||
|
||||
const control = globals.control orelse return error.RiverControlNotAdvertised;
|
||||
defer control.destroy();
|
||||
const status_manager = globals.status_manager orelse return error.RiverStatusManagerNotAdvertised;
|
||||
defer status_manager.destroy();
|
||||
const seat = globals.seat orelse return error.SeatNotAdverstised;
|
||||
defer seat.destroy();
|
||||
|
||||
log.info("rivertile: {s}", .{build_options.rivertile});
|
||||
log.info("gdbus: {s}", .{build_options.gdbus});
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(gpa_alloc);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
try riverControl(display, control, seat, &.{
|
||||
"background-color",
|
||||
"0x002b36",
|
||||
});
|
||||
|
||||
try riverControl(display, control, seat, &.{
|
||||
"border-color-focused",
|
||||
"0x93a1a1",
|
||||
});
|
||||
|
||||
try riverControl(display, control, seat, &.{
|
||||
"border-color-unfocused",
|
||||
"0x586e75",
|
||||
});
|
||||
|
||||
try riverControl(display, control, seat, &.{
|
||||
"default-layout",
|
||||
"rivertile",
|
||||
});
|
||||
|
||||
try riverControl(display, control, seat, &.{
|
||||
"map",
|
||||
"normal",
|
||||
"Super",
|
||||
"G",
|
||||
"spawn",
|
||||
try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{[gdbus]s} call --session --dest com.mitchellh.ghostty --object-path /com/mitchellh/ghostty --method org.gtk.Actions.Activate new_window [] []",
|
||||
.{ .gdbus = build_options.gdbus },
|
||||
),
|
||||
});
|
||||
|
||||
try riverControl(display, control, seat, &.{
|
||||
"map",
|
||||
"normal",
|
||||
"Super",
|
||||
"D",
|
||||
"spawn",
|
||||
try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"{[fuzzel]s} --show-actions",
|
||||
.{ .fuzzel = build_options.fuzzel },
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void {
|
||||
|
@ -64,9 +131,11 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *
|
|||
.global => |global| {
|
||||
if (std.mem.orderZ(u8, global.interface, wl.Seat.getInterface().name) == .eq) {
|
||||
std.debug.assert(globals.seat == null); // TODO: support multiple seats
|
||||
log.info("seat: {d}", .{global.name});
|
||||
globals.seat = registry.bind(global.name, wl.Seat, global.version) catch @panic("out of memory");
|
||||
} else if (std.mem.orderZ(u8, global.interface, wl.Output.getInterface().name) == .eq) {
|
||||
globals.output = registry.bind(global.name, wl.Output, global.version) catch @panic("out of memory");
|
||||
log.info("output: {d}", .{global.name});
|
||||
// globals.output = registry.bind(global.name, wl.Output, global.version) catch @panic("out of memory");
|
||||
} else if (std.mem.orderZ(u8, global.interface, zriver.ControlV1.getInterface().name) == .eq) {
|
||||
globals.control = registry.bind(global.name, zriver.ControlV1, global.version) catch @panic("out of memory");
|
||||
} else if (std.mem.orderZ(u8, global.interface, zriver.StatusManagerV1.getInterface().name) == .eq) {
|
||||
|
@ -77,28 +146,42 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *
|
|||
}
|
||||
}
|
||||
|
||||
fn callbackListener(_: *zriver.CommandCallbackV1, event: zriver.CommandCallbackV1.Event, _: ?*anyopaque) void {
|
||||
switch (event) {
|
||||
.success => |success| {
|
||||
if (std.mem.len(success.output) > 0) {
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
stdout.print("{s}\n", .{success.output}) catch @panic("failed to write to stdout");
|
||||
}
|
||||
std.posix.exit(0);
|
||||
fn riverControl(display: *wl.Display, control: *zriver.ControlV1, seat: *wl.Seat, args: []const [:0]const u8) !void {
|
||||
for (args) |arg|
|
||||
control.addArgument(arg);
|
||||
|
||||
const callback = try control.runCommand(seat);
|
||||
|
||||
var done: std.Thread.Semaphore = .{};
|
||||
callback.setListener(*std.Thread.Semaphore, riverControlCallback, &done);
|
||||
|
||||
log.info("dispatch", .{});
|
||||
switch (display.dispatch()) {
|
||||
.SUCCESS => {
|
||||
log.info("SUCCESS", .{});
|
||||
done.wait();
|
||||
},
|
||||
.failure => |failure| {
|
||||
// A small hack to provide usage text when river reports an unknown command.
|
||||
if (std.mem.orderZ(u8, failure.failure_message, "unknown command") == .eq) {
|
||||
std.log.err("unknown command", .{});
|
||||
std.io.getStdErr().writeAll("blah blah blah") catch {};
|
||||
std.posix.exit(1);
|
||||
}
|
||||
fatal("{s}", .{failure.failure_message});
|
||||
else => |err| {
|
||||
log.err("problem! {}", .{err});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn riverControlCallback(_: *zriver.CommandCallbackV1, event: zriver.CommandCallbackV1.Event, done: *std.Thread.Semaphore) void {
|
||||
switch (event) {
|
||||
.success => |success| {
|
||||
if (std.mem.len(success.output) > 0) {
|
||||
log.info("{s}", .{success.output});
|
||||
}
|
||||
},
|
||||
.failure => |failure| {
|
||||
log.err("{s}", .{failure.failure_message});
|
||||
},
|
||||
}
|
||||
done.post();
|
||||
}
|
||||
|
||||
fn fatal(comptime format: []const u8, args: anytype) noreturn {
|
||||
std.log.err(format, args);
|
||||
log.err(format, args);
|
||||
std.posix.exit(1);
|
||||
}
|
||||
|
|
81
src/path.zig
Normal file
81
src/path.zig
Normal file
|
@ -0,0 +1,81 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const log = std.log.scoped(.path);
|
||||
|
||||
pub fn expandPath(alloc: std.mem.Allocator, cmd: []const u8) !?[]u8 {
|
||||
const PATH = std.posix.getenv("PATH") orelse return null;
|
||||
|
||||
var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
||||
var seen_eaccess = false;
|
||||
var it = std.mem.tokenizeScalar(u8, PATH, std.fs.path.delimiter);
|
||||
|
||||
search: while (it.next()) |search_path| {
|
||||
const path_len = search_path.len + cmd.len + 1;
|
||||
if (path_buf.len < path_len) return error.PathTooLong;
|
||||
|
||||
@memcpy(path_buf[0..search_path.len], search_path);
|
||||
path_buf[search_path.len] = std.fs.path.sep;
|
||||
@memcpy(path_buf[search_path.len + 1 ..][0..cmd.len], cmd);
|
||||
path_buf[path_len] = 0;
|
||||
|
||||
var path = path_buf[0..path_len :0];
|
||||
|
||||
follow: while (true) {
|
||||
var stat: std.os.linux.Stat = undefined;
|
||||
|
||||
{
|
||||
const rc = std.os.linux.lstat(path, &stat);
|
||||
switch (std.posix.errno(rc)) {
|
||||
.SUCCESS => {},
|
||||
.NOENT => continue :search,
|
||||
.PERM => {
|
||||
seen_eaccess = true;
|
||||
continue :search;
|
||||
},
|
||||
else => |err| {
|
||||
log.err("error: {}", .{err});
|
||||
return error.Unknown;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (stat.mode & std.os.linux.S.IFMT == std.os.linux.S.IFLNK) {
|
||||
log.info("symlink {s}", .{path});
|
||||
const rc = std.os.linux.readlink(path, &path_buf, path_buf.len);
|
||||
switch (std.posix.errno(rc)) {
|
||||
.SUCCESS => {
|
||||
path_buf[rc] = 0;
|
||||
path = path_buf[0..rc :0];
|
||||
continue :follow;
|
||||
},
|
||||
.NOENT => continue :search,
|
||||
.PERM => {
|
||||
seen_eaccess = true;
|
||||
continue :search;
|
||||
},
|
||||
else => |err| {
|
||||
log.err("{}", .{err});
|
||||
return error.Unknown;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (stat.mode & std.os.linux.S.IFMT == std.os.linux.S.IFREG) {
|
||||
log.info("file {s}", .{path});
|
||||
if (isExecutable(stat.mode))
|
||||
return try alloc.dupe(u8, path);
|
||||
}
|
||||
|
||||
continue :search;
|
||||
}
|
||||
}
|
||||
|
||||
if (seen_eaccess) return error.AccessDenied;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn isExecutable(mode: std.os.linux.mode_t) bool {
|
||||
return mode & 0o0111 != 0;
|
||||
}
|
Loading…
Add table
Reference in a new issue