[root]/ src
/ mkslide.zig
10.7KB
const std = @import("std");
const c = @import("c.zig");
const cfg = @import("config.zig");
const Slide = struct {
head: ?[*:0]const u8 = null,
lines: std.ArrayList([*:0]const u8),
fn init(a: std.mem.Allocator) Slide {
return Slide{ .lines = std.ArrayList([*:0]const u8).init(a) };
}
fn deinit(self: *Slide) void {
self.lines.deinit();
}
};
const Trim = struct {
start: usize,
len: usize,
};
const Deck = struct {
alloc: std.mem.Allocator,
slides: std.ArrayList(Slide),
mem: []u8 = &[_]u8{},
len: usize = 0,
idx: usize = 0,
fn init(a: std.mem.Allocator) Deck {
return Deck{ .alloc = a, .slides = std.ArrayList(Slide).init(a) };
}
fn deinit(self: *Deck) void {
self.reset();
self.slides.deinit();
}
fn reset(self: *Deck) void {
for (self.slides.items) |*s| s.deinit();
self.slides.clearRetainingCapacity();
self.idx = 0;
if (self.mem.len != 0) {
self.alloc.free(self.mem);
self.mem = &[_]u8{};
self.len = 0;
}
}
fn load(self: *Deck, path: []const u8) !void {
self.reset();
var f = try std.fs.cwd().openFile(path, .{});
defer f.close();
const sz = try f.getEndPos();
var buf = try self.alloc.alloc(u8, sz + 1);
errdefer {
self.alloc.free(buf);
self.mem = &[_]u8{};
self.len = 0;
}
const read = try f.readAll(buf[0..sz]);
buf[read] = 0;
self.mem = buf;
self.len = read;
try self.parse();
if (self.slides.items.len == 0) return error.NoSlides;
}
fn parse(self: *Deck) !void {
var sl = Slide.init(self.alloc);
var have = false;
var start: usize = 0;
var i: usize = 0;
while (i <= self.len) : (i += 1) {
if (i == self.len or self.mem[i] == '\n') {
if (trim_line(self.mem, start, i)) |trim| {
const line_ptr = @as([*:0]const u8, @ptrCast(self.mem.ptr + trim.start));
if (self.mem[trim.start] == '#') {
if (have) {
try self.slides.append(sl);
sl = Slide.init(self.alloc);
have = false;
}
var head_start = trim.start + 1;
if (head_start - trim.start >= trim.len) {
start = i + 1;
continue;
}
var head_len = trim.len - 1;
while (head_len > 0 and is_space(self.mem[head_start])) {
head_start += 1;
head_len -= 1;
}
if (head_len == 0) {
start = i + 1;
continue;
}
sl.head = @as([*:0]const u8, @ptrCast(self.mem.ptr + head_start));
have = true;
} else {
try sl.lines.append(line_ptr);
have = true;
}
}
start = i + 1;
}
}
if (have) {
try self.slides.append(sl);
} else {
sl.deinit();
}
}
fn next(self: *Deck) void {
if (self.idx + 1 < self.slides.items.len) self.idx += 1;
}
fn prev(self: *Deck) void {
if (self.idx > 0) self.idx -= 1;
}
fn peek(self: *Deck) ?*const Slide {
if (self.slides.items.len == 0) return null;
return &self.slides.items[self.idx];
}
};
const State = struct {
conn: *c.xcb_connection_t,
win: c.xcb_window_t,
surf: *c.cairo_surface_t,
cr: *c.cairo_t,
keys: *c.xcb_key_symbols_t,
deck: Deck,
fn init(a: std.mem.Allocator) !State {
var scr_no: c_int = undefined;
const conn = c.xcb_connect(null, &scr_no);
if (conn == null or c.xcb_connection_has_error(conn) != 0) {
return error.ConnectionFailed;
}
const setup = c.xcb_get_setup(conn);
var it = c.xcb_setup_roots_iterator(setup);
var n: c_int = 0;
while (n < scr_no) : (n += 1) c.xcb_screen_next(&it);
const scr = it.data;
const win = c.xcb_generate_id(conn);
const mask = c.XCB_CW_BACK_PIXEL | c.XCB_CW_EVENT_MASK;
const vals = [_]u32{
scr.*.white_pixel,
c.XCB_EVENT_MASK_EXPOSURE | c.XCB_EVENT_MASK_KEY_PRESS,
};
_ = c.xcb_create_window(
conn,
c.XCB_COPY_FROM_PARENT,
win,
scr.*.root,
0,
0,
cfg.win_w,
cfg.win_h,
0,
c.XCB_WINDOW_CLASS_INPUT_OUTPUT,
scr.*.root_visual,
mask,
&vals,
);
_ = c.xcb_change_property(
conn,
c.XCB_PROP_MODE_REPLACE,
win,
c.XCB_ATOM_WM_NAME,
c.XCB_ATOM_STRING,
8,
cfg.title.len,
cfg.title,
);
const visual = find_visual(scr, scr.*.root_visual) orelse return error.VisualNotFound;
const surf_opt = c.cairo_xcb_surface_create(conn, win, visual, cfg.win_w, cfg.win_h);
if (surf_opt == null) return error.CairoSurface;
const surf = surf_opt.?;
const cr_opt = c.cairo_create(surf);
if (cr_opt == null) return error.CairoContext;
const cr = cr_opt.?;
const keys_opt = c.xcb_key_symbols_alloc(conn);
if (keys_opt == null) return error.KeySyms;
const keys = keys_opt.?;
_ = c.xcb_map_window(conn, win);
_ = c.xcb_flush(conn);
return State{
.conn = conn.?,
.win = win,
.surf = surf,
.cr = cr,
.keys = keys,
.deck = Deck.init(a),
};
}
fn deinit(self: *State) void {
self.deck.deinit();
c.cairo_destroy(self.cr);
c.cairo_surface_destroy(self.surf);
c.xcb_key_symbols_free(self.keys);
c.xcb_disconnect(self.conn);
}
fn draw(self: *State) void {
c.cairo_set_source_rgb(self.cr, cfg.colors.bg[0], cfg.colors.bg[1], cfg.colors.bg[2]);
c.cairo_paint(self.cr);
const slide = self.deck.peek() orelse return;
var y: f64 = cfg.pad;
var te: c.cairo_text_extents_t = undefined;
if (slide.head) |head| {
c.cairo_select_font_face(self.cr, cfg.header_font, c.CAIRO_FONT_SLANT_NORMAL, c.CAIRO_FONT_WEIGHT_BOLD);
c.cairo_set_font_size(self.cr, cfg.header_font_size);
c.cairo_set_source_rgb(self.cr, cfg.colors.fg[0], cfg.colors.fg[1], cfg.colors.fg[2]);
var fe: c.cairo_font_extents_t = undefined;
c.cairo_font_extents(self.cr, &fe);
y += (fe.ascent + fe.descent) / 2;
const x = if (cfg.centerh) blk: {
c.cairo_text_extents(self.cr, head, &te);
break :blk (cfg.win_w - te.width) / 2;
} else cfg.pad;
c.cairo_move_to(self.cr, x, y);
c.cairo_show_text(self.cr, head);
y += fe.descent + cfg.line_gap * 2;
}
c.cairo_select_font_face(self.cr, cfg.font, c.CAIRO_FONT_SLANT_NORMAL, c.CAIRO_FONT_WEIGHT_NORMAL);
c.cairo_set_font_size(self.cr, cfg.font_size);
var fe_body: c.cairo_font_extents_t = undefined;
c.cairo_font_extents(self.cr, &fe_body);
for (slide.lines.items) |ln| {
y += fe_body.ascent;
c.cairo_move_to(self.cr, if (cfg.centert) (cfg.winert - te.width) / 2 else cfg.pad, y);
c.cairo_show_text(self.cr, ln);
y += fe_body.descent + cfg.line_gap;
}
var info_buf: [32:0]u8 = undefined;
const info = std.fmt.bufPrintZ(&info_buf, "{}/{}", .{ self.deck.idx + 1, self.deck.slides.items.len }) catch return;
c.cairo_select_font_face(self.cr, cfg.font, c.CAIRO_FONT_SLANT_NORMAL, c.CAIRO_FONT_WEIGHT_NORMAL);
c.cairo_set_font_size(self.cr, cfg.footer_font_size);
c.cairo_text_extents(self.cr, info.ptr, &te);
c.cairo_move_to(self.cr, (cfg.win_w - te.width) / 2, cfg.win_h - cfg.pad);
c.cairo_show_text(self.cr, info.ptr);
c.cairo_move_to(self.cr, cfg.pad, cfg.win_h - cfg.pad);
c.cairo_show_text(self.cr, cfg.AUTHOR);
c.cairo_surface_flush(self.surf);
_ = c.xcb_flush(self.conn);
}
fn key(self: *State, code: c.xcb_keycode_t) bool {
const sym = c.xcb_key_symbols_get_keysym(self.keys, code, 0);
switch (sym) {
c.XK_Escape, c.XK_q => return false,
c.XK_Right, c.XK_space, c.XK_Return => self.deck.next(),
c.XK_Left, c.XK_BackSpace => self.deck.prev(),
else => {},
}
return true;
}
};
fn die(comptime fmt: []const u8, args: anytype) noreturn {
std.debug.print("state: " ++ fmt ++ "\n", args);
std.process.exit(1);
}
fn find_visual(scr: *c.xcb_screen_t, id: c.xcb_visualid_t) ?*c.xcb_visualtype_t {
var d = c.xcb_screen_allowed_depths_iterator(scr);
while (d.rem != 0) : (c.xcb_depth_next(&d)) {
var v = c.xcb_depth_visuals_iterator(d.data);
while (v.rem != 0) : (c.xcb_visualtype_next(&v)) {
const visual = @as(*c.xcb_visualtype_t, @ptrCast(v.data));
if (visual.visual_id == id) return visual;
}
}
return null;
}
fn trim_line(buf: []u8, start: usize, end: usize) ?Trim {
if (start >= end) return null;
var s = start;
while (s < end and is_space(buf[s])) s += 1;
var e = end;
while (e > s and is_space(buf[e - 1])) e -= 1;
if (s == e) return null;
buf[e] = 0;
return Trim{ .start = s, .len = e - s };
}
inline fn is_space(cu: u8) bool {
return cu == ' ' or cu == '\t' or cu == '\r';
}
pub fn main() void {
const a = std.heap.page_allocator;
const args = std.process.argsAlloc(a) catch die("args", .{});
defer std.process.argsFree(a, args);
if (args.len < 2) die("usage: {s} <file>", .{args[0]});
var state = State.init(a) catch |err| die("init: {}", .{err});
defer state.deinit();
state.deck.load(args[1]) catch |err| die("slides: {}", .{err});
state.draw();
while (true) {
const ev = c.xcb_wait_for_event(state.conn);
if (ev == null) continue;
defer c.free(ev);
switch (ev.*.response_type & ~@as(u8, 0x80)) {
c.XCB_EXPOSE => state.draw(),
c.XCB_KEY_PRESS => {
const key = @as(*c.xcb_key_press_event_t, @ptrCast(ev));
if (!state.key(key.detail)) break;
state.draw();
},
else => {},
}
}
}