aboutsummaryrefslogtreecommitdiffstats
path: root/src/main.zig
blob: 2f6c56c3e38dc2ad952f9bfa837960567a33a7be (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
const std = @import("std");
const config = @import("config.zig");
const screenshot = @import("screenshot.zig");
const x11 = @import("x11.zig");
const opengl = @import("opengl.zig");
const app = @import("app.zig");
const math = @import("math.zig");
const c = @import("c.zig").c;

const VERSION = "20260426";

const usage_text =
    \\usage: boomer [OPTIONS]
    \\  -d, --delay <seconds: float>  delay execution of the program by provided <seconds>
    \\  -h, --help                    show this help and exit
    \\      --new-config [filepath]   generate a new default config at [filepath]
    \\  -c, --config <filepath>       use config at <filepath>
    \\  -V, --version                 show the current version and exit
    \\  -w, --windowed                windowed mode instead of fullscreen
;

pub fn main(init: std.process.Init) !void {
    // cli args
    var windowed: bool = false;
    var delay_sec: f32 = 0.0;
    var config_path: ?[]const u8 = null;
    var new_cfg_out: ?[]const u8 = null;

    const argv = init.minimal.args.vector;

    var i: usize = 1;
    while (i < argv.len) : (i += 1) {
        const arg = std.mem.sliceTo(argv[i], 0);
        if (std.mem.eql(u8, arg, "-h") or std.mem.eql(u8, arg, "--help")) {
            std.debug.print("{s}", .{usage_text});
            return;
        } else if (std.mem.eql(u8, arg, "-V") or std.mem.eql(u8, arg, "--version")) {
            std.debug.print("boomer-{s}\n", .{VERSION});
            return;
        } else if (std.mem.eql(u8, arg, "-w") or std.mem.eql(u8, arg, "--windowed")) {
            windowed = true;
        } else if (std.mem.eql(u8, arg, "-d") or std.mem.eql(u8, arg, "--delay")) {
            i += 1;
            if (i >= argv.len) {
                std.debug.print("error: no value provided for {s}\n", .{arg});
                std.debug.print("{s}", .{usage_text});
                return error.InvalidArgs;
            }
            delay_sec = std.fmt.parseFloat(f32, std.mem.sliceTo(argv[i], 0)) catch {
                std.debug.print("error: invalid delay value: {s}\n", .{std.mem.sliceTo(argv[i], 0)});
                return error.InvalidArgs;
            };
        } else if (std.mem.eql(u8, arg, "-c") or std.mem.eql(u8, arg, "--config")) {
            i += 1;
            if (i >= argv.len) {
                std.debug.print("error: no value provided for {s}\n", .{arg});
                std.debug.print("{s}", .{usage_text});
                return error.InvalidArgs;
            }
            config_path = std.mem.sliceTo(argv[i], 0);
        } else if (std.mem.eql(u8, arg, "--new-config")) {
            const alloc_path: ?[]const u8 = config.Config.defaultConfigPath(init.gpa);
            defer if (alloc_path) |p| init.gpa.free(p);
            if (i + 1 < argv.len and std.mem.sliceTo(argv[i + 1], 0)[0] != '-') {
                i += 1;
                new_cfg_out = std.mem.sliceTo(argv[i], 0);
            } else {
                new_cfg_out = alloc_path;
            }
            if (new_cfg_out) |p| {
                const dir = std.fs.path.dirname(p) orelse ".";
                config.Config.mkdirP(dir);
                config.Config.default().writeDefault(p) catch {
                    std.debug.print("error: could not write config to {s}\n", .{p});
                    return error.ConfigWriteFailed;
                };
                std.debug.print("generated config at {s}\n", .{p});
            }
            return;
        } else {
            std.debug.print("error: unknown flag `{s}`\n", .{arg});
            std.debug.print("{s}", .{usage_text});
            return error.InvalidArgs;
        }
    }

    // delay
    if (delay_sec > 0.0) {
        const ns: i64 = @intFromFloat(delay_sec * 1_000_000_000.0);
        var ts = std.os.linux.timespec{
            .sec = @divFloor(ns, 1_000_000_000),
            .nsec = @mod(ns, 1_000_000_000),
        };
        _ = std.os.linux.nanosleep(&ts, null);
    }

    // default config path
    var alloc_config_path: ?[]const u8 = null;
    if (config_path == null) {
        alloc_config_path = config.Config.defaultConfigPath(init.gpa);
        config_path = alloc_config_path;
    }
    defer if (alloc_config_path) |p| init.gpa.free(p);

    if (config_path) |p| {
        std.debug.print("using config: {s}\n", .{p});
    }

    // init x11
    var xc = try x11.X11.init();
    defer xc.deinit();

    try xc.checkGlx();
    try xc.createWindow(windowed);

    // screenshot
    var ss = screenshot.Screenshot.capture(xc.display.?, xc.root) catch {
        std.debug.print("error: failed to take screenshot\n", .{});
        return error.ScreenshotFailed;
    };
    defer ss.deinit();

    std.debug.print("screenshot: {d}x{d}\n", .{ ss.image.*.width, ss.image.*.height });

    // init opengl
    var gl = opengl.GL.init(&ss) catch {
        return error.OpenGLInitFailed;
    };
    defer gl.deinit();

    // init app
    var a = app.App.init(init.gpa, config_path) catch {
        return error.AppInitFailed;
    };
    defer a.deinit();

    // seed mouse position
    {
        var root_ret: c.Window = undefined;
        var child_ret: c.Window = undefined;
        var rx: i32 = undefined;
        var ry: i32 = undefined;
        var wx: i32 = undefined;
        var wy: i32 = undefined;
        var mask: c_uint = undefined;
        _ = c.XQueryPointer(xc.display, xc.root, &root_ret, &child_ret, &rx, &ry, &wx, &wy, &mask);
        a.state.mouse.curr = .{ .x = @floatFromInt(wx), .y = @floatFromInt(wy) };
        a.state.mouse.prev = a.state.mouse.curr;
    }

    gl.createProgram();
    gl.createTexture(&ss, a.config.texture_filter);
    gl.createGeometry();

    // main loop
    a.state.dt = 1.0 / @as(f32, @floatFromInt(xc.refresh_rate));

    while (a.state.running) {
        xc.grabFocus();

        var ww: i32 = undefined;
        var wh: i32 = undefined;
        xc.getWindowSize(&ww, &wh);
        c.glViewport(0, 0, ww, wh);

        a.processEvents(&xc);

        a.cameraUpdate(math.Vec2f.init(@floatFromInt(ww), @floatFromInt(wh)));
        a.flashlightUpdate();

        gl.render(&a, ww, wh);

        c.glXSwapBuffers(xc.display, xc.window);
        c.glFinish();
    }

    xc.restoreFocus();
}