diff --git a/src/Context.zig b/src/Context.zig index d204f44d..f72ea135 100644 --- a/src/Context.zig +++ b/src/Context.zig @@ -8,6 +8,7 @@ const DocumentHandler = @import("handlers/DocumentHandler.zig"); const Cache = @import("./Cache.zig"); const ReloadIndicatorTimer = @import("services/ReloadIndicatorTimer.zig"); const History = @import("services/History.zig"); +const SocketServer = @import("services/SocketServer.zig"); pub const panic = vaxis.panic_handler; @@ -37,6 +38,8 @@ pub const Context = struct { current_page: ?vaxis.Image, watcher: ?fzwatch.Watcher, watcher_thread: ?std.Thread, + socket_server: ?SocketServer, + socket_thread: ?std.Thread, config: *Config, current_mode: Mode, history: History, @@ -68,6 +71,10 @@ pub const Context = struct { watcher = try fzwatch.Watcher.init(allocator); if (watcher) |*w| try w.addFile(path); } + var socket_server: ?SocketServer = null; + if (config.socket_server.enabled) { + socket_server = SocketServer.init(allocator, config) catch null; + } const vx = try vaxis.init(allocator, .{}); const buf = try allocator.alloc(u8, 4096); @@ -87,6 +94,8 @@ pub const Context = struct { .watcher = watcher, .mouse = null, .watcher_thread = null, + .socket_server = socket_server, + .socket_thread = null, .config = config, .current_mode = undefined, .history = history, @@ -110,6 +119,10 @@ pub const Context = struct { if (self.watcher_thread) |thread| thread.join(); w.deinit(); } + if (self.socket_server) |*s| { + s.deinit(); + if (self.socket_thread) |t| t.join(); + } if (self.page_info_text.len > 0) self.allocator.free(self.page_info_text); @@ -138,6 +151,15 @@ pub const Context = struct { try watcher.start(.{ .latency = self.config.file_monitor.latency }); } + fn socketWorker(server: *SocketServer, loop: *vaxis.Loop(Event)) !void { + try server.listen(loop, socketCallback); + } + + fn socketCallback(context: ?*anyopaque) void { + const loop = @as(*vaxis.Loop(Event), @ptrCast(@alignCast(context.?))); + loop.postEvent(Event.file_changed); + } + pub fn run(self: *Self) !void { self.current_mode = .{ .view = ViewMode.init(self) }; @@ -169,6 +191,11 @@ pub const Context = struct { } } } + if (self.config.socket_server.enabled) { + if (self.socket_server) |*s| { + self.socket_thread = try std.Thread.spawn(.{}, socketWorker, .{ s, &loop }); + } + } while (!self.should_quit) { loop.pollEvent(); diff --git a/src/config/Config.zig b/src/config/Config.zig index c35f828f..8ab968be 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -78,6 +78,19 @@ pub const FileMonitor = struct { } }; +pub const SocketServer = struct { + enabled: bool = false, + + pub fn parse(val: std.json.Value, allocator: std.mem.Allocator) SocketServer { + var socket_server = SocketServer{}; + if (val != .object) return socket_server; + + socket_server.enabled = parseType(bool, val.object, "enabled", allocator, socket_server.enabled); + + return socket_server; + } +}; + pub const General = struct { colorize: bool = false, white: i32 = 0x000000, @@ -227,6 +240,7 @@ arena: std.heap.ArenaAllocator, key_map: KeyMap = .{}, file_monitor: FileMonitor = .{}, +socket_server: SocketServer = .{}, general: General = .{}, status_bar: StatusBar = .{}, cache: Cache = .{}, @@ -271,6 +285,7 @@ pub fn init(allocator: std.mem.Allocator) Self { if (parsed.value.object.get("KeyMap")) |key_map| self.key_map = KeyMap.parse(key_map, arena_allocator); if (parsed.value.object.get("FileMonitor")) |file_monitor| self.file_monitor = FileMonitor.parse(file_monitor, arena_allocator); + if (parsed.value.object.get("SocketServer")) |socket_server| self.socket_server = SocketServer.parse(socket_server, arena_allocator); if (parsed.value.object.get("General")) |general| self.general = General.parse(general, arena_allocator); if (parsed.value.object.get("StatusBar")) |status_bar| self.status_bar = StatusBar.parse(status_bar, arena_allocator); if (parsed.value.object.get("Cache")) |cache| self.cache = Cache.parse(cache, arena_allocator); diff --git a/src/services/SocketServer.zig b/src/services/SocketServer.zig new file mode 100644 index 00000000..8d54b558 --- /dev/null +++ b/src/services/SocketServer.zig @@ -0,0 +1,62 @@ +const Self = @This(); +const std = @import("std"); +const Config = @import("../config/Config.zig"); + +allocator: std.mem.Allocator, +config: *Config, +path: []u8, +server: ?std.net.Server, +should_stop: std.atomic.Value(bool), + +pub fn init(allocator: std.mem.Allocator, config: *Config) !Self { + const runtime_dir = std.process.getEnvVarOwned(allocator, "XDG_RUNTIME_DIR") catch + try allocator.dupe(u8, "/tmp"); + defer allocator.free(runtime_dir); + + const path = try std.fmt.allocPrint(allocator, "{s}/fancy-cat.sock", .{runtime_dir}); + errdefer allocator.free(path); + + // Remove a stale socket file + std.fs.deleteFileAbsolute(path) catch {}; + + const address = try std.net.Address.initUnix(path); + const server = try address.listen(.{ .reuse_address = true }); + + return .{ + .allocator = allocator, + .config = config, + .path = path, + .server = server, + .should_stop = std.atomic.Value(bool).init(false), + }; +} + +pub fn deinit(self: *Self) void { + self.should_stop.store(true, .seq_cst); + if (self.server) |*s| { + s.deinit(); + self.server = null; + } + std.fs.deleteFileAbsolute(self.path) catch {}; + self.allocator.free(self.path); + self.path = &.{}; +} + +pub fn listen( + self: *Self, + context: ?*anyopaque, + callback: *const fn (context: ?*anyopaque) void, +) !void { + while (!self.should_stop.load(.seq_cst)) { + const conn = self.server.?.accept() catch { + if (self.should_stop.load(.seq_cst)) return; + continue; + }; + defer conn.stream.close(); + + var buf: [64]u8 = undefined; + _ = conn.stream.read(&buf) catch continue; + + callback(context); + } +}