268 lines
8.2 KiB
Zig
268 lines
8.2 KiB
Zig
|
const std = @import("std");
|
||
|
const builtin = @import("builtin");
|
||
|
|
||
|
const vaxis = @import("vaxis");
|
||
|
const xev = @import("xev");
|
||
|
const Cell = vaxis.Cell;
|
||
|
|
||
|
const networking = @import("./networking.zig");
|
||
|
const rotctl = @import("./rotctl.zig");
|
||
|
|
||
|
const log = std.log.scoped(.rotint);
|
||
|
|
||
|
pub const panic = vaxis.panic_handler;
|
||
|
|
||
|
pub const std_options: std.Options = .{
|
||
|
.log_level = if (builtin.mode == .Debug) .debug else .err,
|
||
|
};
|
||
|
|
||
|
// these are all in degrees
|
||
|
pub const AzEl = struct { az: f64, el: f64 };
|
||
|
|
||
|
pub const RotInt = struct {
|
||
|
allocator: std.mem.Allocator,
|
||
|
// TODO: associate timestamps with this somehow
|
||
|
offsets: AzEl = .{ .az = 0, .el = 0 },
|
||
|
last_request: AzEl = .{ .az = 0, .el = 0 },
|
||
|
current_posture: AzEl = .{ .az = 0, .el = 0 },
|
||
|
|
||
|
// printbuffer: [128]u8 = undefined,
|
||
|
termbuffer: std.io.BufferedWriter(4096, std.io.AnyWriter),
|
||
|
vx: *vaxis.Vaxis,
|
||
|
loop: *xev.Loop,
|
||
|
|
||
|
fake: FakeRotator = .{},
|
||
|
parser: rotctl.RotCtl = .{},
|
||
|
server: networking.Server = .{},
|
||
|
|
||
|
pub fn initFixed(self: *RotInt) void {
|
||
|
self.server.rotint = self;
|
||
|
}
|
||
|
|
||
|
pub fn warn(_: *RotInt, comptime fmt: []const u8, args: anytype) void {
|
||
|
log.warn(fmt, args);
|
||
|
}
|
||
|
|
||
|
pub fn forwardRequest(self: *RotInt, req: []const u8) void {
|
||
|
var temp: [128]u8 = undefined;
|
||
|
const command = self.parser.parseCommand(req) catch |err| switch (err) {
|
||
|
error.Incomplete => return,
|
||
|
error.NotSupported => {
|
||
|
const response = (rotctl.RotReply{ .status = .not_supported }).write(temp[0..]) catch {
|
||
|
self.warn("serialization failure", .{});
|
||
|
return;
|
||
|
};
|
||
|
self.server.writeResponse(self.loop, response);
|
||
|
return;
|
||
|
},
|
||
|
error.InvalidParameter => {
|
||
|
const response = (rotctl.RotReply{ .status = .invalid_parameter }).write(temp[0..]) catch {
|
||
|
self.warn("serialization failure", .{});
|
||
|
return;
|
||
|
};
|
||
|
self.server.writeResponse(self.loop, response);
|
||
|
return;
|
||
|
},
|
||
|
};
|
||
|
|
||
|
// if command is `quit`, we should disconnect the client after our reply has been sent.
|
||
|
const reply = self.fake.request(command);
|
||
|
const response = reply.write(temp[0..]) catch {
|
||
|
self.warn("serialization failure", .{});
|
||
|
return;
|
||
|
};
|
||
|
self.server.writeResponse(self.loop, response);
|
||
|
}
|
||
|
|
||
|
fn draw(self: *RotInt) !void {
|
||
|
const Static = struct {
|
||
|
const lower_limit: u8 = 30;
|
||
|
const next_ms: u64 = 8;
|
||
|
var color_idx: u8 = lower_limit;
|
||
|
var dir: enum { up, down } = .up;
|
||
|
};
|
||
|
|
||
|
const style: vaxis.Style = .{
|
||
|
.fg = .{ .rgb = [_]u8{ Static.color_idx, Static.color_idx, Static.color_idx } },
|
||
|
};
|
||
|
|
||
|
const segment: vaxis.Segment = .{
|
||
|
.text = "yeah ok",
|
||
|
.style = style,
|
||
|
};
|
||
|
const win = self.vx.window();
|
||
|
win.clear();
|
||
|
|
||
|
const y_off = (win.height / 2) -| (6 / 2);
|
||
|
const x_off = (win.width / 2) -| (30 / 2);
|
||
|
const center = win.child(.{
|
||
|
.x_off = x_off + win.x_off,
|
||
|
.y_off = y_off + win.y_off,
|
||
|
.width = .{ .limit = 30 },
|
||
|
.height = .{ .limit = 6 },
|
||
|
.border = .{ .where = .all, .style = style },
|
||
|
});
|
||
|
_ = try center.printSegment(segment, .{ .wrap = .grapheme });
|
||
|
switch (Static.dir) {
|
||
|
.up => {
|
||
|
Static.color_idx += 1;
|
||
|
if (Static.color_idx == 255) Static.dir = .down;
|
||
|
},
|
||
|
.down => {
|
||
|
Static.color_idx -= 1;
|
||
|
if (Static.color_idx == Static.lower_limit) Static.dir = .up;
|
||
|
},
|
||
|
}
|
||
|
try self.vx.render(self.termbuffer.writer().any());
|
||
|
try self.termbuffer.flush();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const FakeRotator = struct {
|
||
|
const park_position: AzEl = .{ .az = 180, .el = 90 };
|
||
|
|
||
|
current: AzEl = park_position,
|
||
|
|
||
|
fn request(self: *FakeRotator, command: rotctl.RotCommand) rotctl.RotReply {
|
||
|
return switch (command) {
|
||
|
.get_position => .{ .get_position = self.current },
|
||
|
.set_position => |pos| blk: {
|
||
|
self.current = pos;
|
||
|
break :blk .{ .status = .okay };
|
||
|
},
|
||
|
.stop => .{ .status = .okay },
|
||
|
.park => blk: {
|
||
|
self.current = park_position;
|
||
|
break :blk .{ .status = .okay };
|
||
|
},
|
||
|
.quit => .{ .status = .okay },
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// const App = struct {
|
||
|
// const lower_limit: u8 = 30;
|
||
|
// const next_ms: u64 = 8;
|
||
|
|
||
|
// allocator: std.mem.Allocator,
|
||
|
// vx: *vaxis.Vaxis,
|
||
|
// buffered_writer: std.io.BufferedWriter(4096, std.io.AnyWriter),
|
||
|
// color_idx: u8,
|
||
|
// dir: enum {
|
||
|
// up,
|
||
|
// down,
|
||
|
// },
|
||
|
// };
|
||
|
|
||
|
pub fn main() !void {
|
||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||
|
defer _ = gpa.deinit();
|
||
|
|
||
|
const alloc = gpa.allocator();
|
||
|
|
||
|
var tty = try vaxis.Tty.init();
|
||
|
defer tty.deinit();
|
||
|
|
||
|
var vx = try vaxis.init(alloc, .{});
|
||
|
defer vx.deinit(alloc, tty.anyWriter());
|
||
|
|
||
|
var pool = xev.ThreadPool.init(.{});
|
||
|
var loop = try xev.Loop.init(.{
|
||
|
.thread_pool = &pool,
|
||
|
});
|
||
|
defer loop.deinit();
|
||
|
|
||
|
var app: RotInt = .{
|
||
|
.allocator = alloc,
|
||
|
.termbuffer = tty.bufferedWriter(),
|
||
|
.vx = &vx,
|
||
|
.loop = &loop,
|
||
|
};
|
||
|
app.initFixed();
|
||
|
|
||
|
var vx_loop: vaxis.xev.TtyWatcher(RotInt) = undefined;
|
||
|
try vx_loop.init(&tty, &vx, &loop, &app, eventCallback);
|
||
|
|
||
|
// try vx.enterAltScreen(tty.anyWriter());
|
||
|
try vx.queryTerminalSend(tty.anyWriter());
|
||
|
// Window size appears to be left uninitialized unless we manually set it here. This
|
||
|
// seems sketchy to me (tty fd should be nonblocking for the event loop)
|
||
|
const size = try vaxis.Tty.getWinsize(tty.fd);
|
||
|
vx.resize(alloc, tty.anyWriter(), size) catch @panic("TODO");
|
||
|
|
||
|
try app.server.listen(app.loop);
|
||
|
|
||
|
try loop.run(.until_done);
|
||
|
}
|
||
|
|
||
|
fn eventCallback(
|
||
|
ud: ?*RotInt,
|
||
|
loop: *xev.Loop,
|
||
|
watcher: *vaxis.xev.TtyWatcher(RotInt),
|
||
|
event: vaxis.xev.Event,
|
||
|
) xev.CallbackAction {
|
||
|
const app = ud orelse unreachable;
|
||
|
switch (event) {
|
||
|
.key_press => |key| keyp: {
|
||
|
var mods = key.mods;
|
||
|
mods.caps_lock = false;
|
||
|
mods.num_lock = false;
|
||
|
const scale: f64 = if (std.meta.eql(mods, .{ .shift = true })) 1 else 10;
|
||
|
|
||
|
const delta: AzEl = switch (key.codepoint) {
|
||
|
vaxis.Key.left, vaxis.Key.kp_left => .{ .az = -0.1 * scale, .el = 0 },
|
||
|
vaxis.Key.right, vaxis.Key.kp_right => .{ .az = 0.1 * scale, .el = 0 },
|
||
|
vaxis.Key.up, vaxis.Key.kp_up => .{ .az = 0, .el = 0.1 * scale },
|
||
|
vaxis.Key.down, vaxis.Key.kp_down => .{ .az = 0 * scale, .el = -0.1 * scale },
|
||
|
'l' => {
|
||
|
if (std.meta.eql(mods, .{ .ctrl = true }))
|
||
|
app.vx.queueRefresh();
|
||
|
break :keyp;
|
||
|
},
|
||
|
'c' => {
|
||
|
if (std.meta.eql(mods, .{ .ctrl = true })) {
|
||
|
loop.stop();
|
||
|
return .disarm;
|
||
|
}
|
||
|
break :keyp;
|
||
|
},
|
||
|
'q' => {
|
||
|
if (std.meta.eql(mods, .{})) {
|
||
|
loop.stop();
|
||
|
return .disarm;
|
||
|
}
|
||
|
break :keyp;
|
||
|
},
|
||
|
else => break :keyp,
|
||
|
};
|
||
|
|
||
|
_ = delta;
|
||
|
|
||
|
// state.offsets.az += delta.az;
|
||
|
// state.offsets.el += delta.el;
|
||
|
},
|
||
|
.winsize => |ws| {
|
||
|
watcher.vx.resize(app.allocator, watcher.tty.anyWriter(), ws) catch
|
||
|
return .disarm;
|
||
|
},
|
||
|
else => {},
|
||
|
}
|
||
|
return .rearm;
|
||
|
}
|
||
|
|
||
|
fn timerCallback(
|
||
|
ud: ?*RotInt,
|
||
|
loop: *xev.Loop,
|
||
|
completion: *xev.Completion,
|
||
|
err: xev.Timer.RunError!void,
|
||
|
) xev.CallbackAction {
|
||
|
_ = err catch @panic("timer error");
|
||
|
|
||
|
var app = ud orelse return .disarm;
|
||
|
app.draw() catch @panic("couldn't draw");
|
||
|
|
||
|
const timer = xev.Timer.init() catch unreachable;
|
||
|
timer.run(loop, completion, 8, RotInt, app, timerCallback);
|
||
|
return .disarm;
|
||
|
}
|