summaryrefslogtreecommitdiff
path: root/src/linux.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/linux.zig')
-rw-r--r--src/linux.zig319
1 files changed, 319 insertions, 0 deletions
diff --git a/src/linux.zig b/src/linux.zig
new file mode 100644
index 0000000..d2c38a3
--- /dev/null
+++ b/src/linux.zig
@@ -0,0 +1,319 @@
+const std = @import("std");
+const mem = std.mem;
+
+const x = @import("x11.zig");
+const input = @import("app/input.zig");
+const gui = @import("app/ui.zig");
+const app = @import("app/app.zig");
+
+const Allocator = mem.Allocator;
+const ArrayList = std.ArrayList;
+
+const assert = std.debug.assert;
+
+pub var allocator: Allocator = undefined;
+pub var target_fps: ?f32 = null;
+
+var empty_pixels: []u32 = &[0]u32{};
+pub var pixels: *[]u32 = &empty_pixels;
+
+pub var utf8_of_keys_pressed: []u8 = &.{};
+
+pub fn main() !void {
+ try run(app.init, app.setWindowSize, app.loop, app.deinit);
+}
+
+pub fn run(
+ comptime init_fn: anytype,
+ comptime set_window_size: anytype,
+ comptime loop_fn: anytype,
+ comptime deinit_fn: anytype,
+) !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{.stack_trace_frames = 16}){};
+ allocator = gpa.allocator();
+ defer {
+ _ = gpa.deinit();
+ }
+
+ var display = try x.Display.open();
+ defer display.close();
+
+ const screen = x.Screen.default(display);
+
+ const root_window = x.c.RootWindow(@as(*x.c.Display, @ptrCast(display)), screen.id);
+
+ var swa = mem.zeroes(x.c.XSetWindowAttributes);
+ swa.event_mask = x.c.ExposureMask | x.c.KeyPressMask | x.c.KeyReleaseMask | x.c.StructureNotifyMask;
+ var window_size = @Vector(2, c_uint){ 720, 720 };
+ var window = try x.Window.create(.{
+ .display = display,
+ .parent = .{ .window = .{ .id = root_window } },
+ .pos = .{ 500, 500 },
+ .size = window_size,
+ .depth = display.defaultDepth(screen),
+ .visual = display.defaultVisual(screen),
+ .value_mask = x.c.CWColormap | x.c.CWEventMask,
+ .attributes = &swa,
+ });
+ defer window.destroy(display);
+
+ display.mapWindow(window);
+
+ _ = x.c.XStoreName(@ptrCast(display), window.id, "minimgui window");
+
+ var screen_buffers: ScreenBuffers = undefined;
+ createScreenBuffers(&screen_buffers, display, screen, window_size);
+ defer destroyScreenBuffers(&screen_buffers, display);
+ var cur_img_idx: u8 = 0;
+ var cur_img = screen_buffers.imgs[cur_img_idx];
+
+ {
+ var raw_pixel_data: [*]u32 = @alignCast(@ptrCast(cur_img.data));
+ pixels.* = @ptrCast(raw_pixel_data[0..window_size[0]*window_size[1]]);
+ }
+ set_window_size(window_size);
+
+ //_ = std.c.setlocale(.CTYPE, "");
+ //_ = x.c.XSetLocaleModifiers("");
+ //if (x.c.XSupportsLocale() == 0) return error.XDoesntSupportLocale;
+
+ display.selectInput(
+ window,
+ x.c.ExposureMask |
+ x.c.KeyPressMask | x.c.KeyReleaseMask |
+ x.c.ButtonPressMask | x.c.ButtonReleaseMask |
+ x.c.PointerMotionMask |
+ x.c.StructureNotifyMask
+ );
+
+ const wm_delete_msg = display.internAtom("WM_DELETE_WINDOW", false);
+ var protocols = [_]x.Atom{wm_delete_msg};
+ display.setWmProtocols(window, &protocols);
+
+ const xim = x.c.XOpenIM(@ptrCast(display), null, null, null);
+ const xic = x.c.XCreateIC(xim,
+ x.c.XNInputStyle, x.c.XIMPreeditNothing | x.c.XIMStatusNothing,
+ x.c.XNClientWindow, window.id,
+ x.c. XNFocusWindow, window.id,
+ @as(?*anyopaque, null)
+ );
+
+ try input._init(.{});
+ defer input._deinit();
+
+ try init_fn();
+ defer deinit_fn();
+
+ var delta_time: f32 = 1.0 / 60.0; // TODO this is a lie
+ var reset = true;
+ app: while (true) {
+ const frame_start_time_us = std.time.microTimestamp();
+ defer delta_time = @as(f32, @floatFromInt(std.time.microTimestamp() - frame_start_time_us))/1e6;
+
+ var buf_utf8_of_keys_pressed: [32]u8 = undefined;
+ utf8_of_keys_pressed = &.{};
+ if (reset) input._before_poll();
+ while (display.pending() > 0) {
+ var event = display.nextEvent();
+ switch (event.type) {
+ x.c.Expose => {},
+ x.c.FocusIn => {
+ x.c.XSetICFocus(xic);
+ std.debug.print("focus\n", .{});
+ },
+ x.c.FocusOut => {
+ x.c.XUnsetICFocus(xic);
+ std.debug.print("unfocus\n", .{});
+ },
+ x.c.ConfigureNotify => {
+ const new_window_size: @Vector(2, c_uint) = @intCast(@Vector(2, c_int){event.xconfigure.width, event.xconfigure.height});
+ if (@reduce(.And, new_window_size == window_size)) continue;
+
+ destroyScreenBuffers(&screen_buffers, display);
+ _ = x.c.XFlush(@ptrCast(display));
+
+ window_size = @intCast(@Vector(2, c_int){event.xconfigure.width, event.xconfigure.height});
+ createScreenBuffers(&screen_buffers, display, screen, window_size);
+ _ = x.c.XFlush(@ptrCast(display));
+
+ cur_img = screen_buffers.imgs[cur_img_idx];
+ const raw_pixel_data: [*]u32 = @alignCast(@ptrCast(cur_img.data));
+ pixels.* = @ptrCast(raw_pixel_data[0..window_size[0]*window_size[1]]);
+
+ set_window_size(window_size);
+ },
+ x.c.ClientMessage => if (event.xclient.data.l[0] == wm_delete_msg) break :app,
+ x.c.MotionNotify => {
+ const raw_pointer_pos = @Vector(2, f32){@as(f32, @floatFromInt(event.xmotion.x)), @as(f32, @floatFromInt(event.xmotion.y))};
+ const fwindow_size: @Vector(2, f32) = @floatFromInt(window_size);
+ const norm_pointer_pos = raw_pointer_pos / fwindow_size;
+ const scale_factor = @max(fwindow_size[0], fwindow_size[1]) / @min(fwindow_size[0], fwindow_size[1]);
+ const wide_window = fwindow_size[0] > fwindow_size[1];
+ const ui_pointer_pos = norm_pointer_pos * if (wide_window) @Vector(2, f32){scale_factor, 1} else @Vector(2, f32){1, scale_factor};
+ input.simulate(.{.pointer_axis = .{.axis = .x}}, ui_pointer_pos[0]);
+ input.simulate(.{.pointer_axis = .{.axis = .y}}, ui_pointer_pos[1]);
+ gui.setMousePos(ui_pointer_pos);
+ },
+ x.c.KeyPress => {
+ var evt = event.xkey;
+ const keysym = x.c.XLookupKeysym(&evt, 0);
+ const device_input = input.DeviceInput{.key = .{.code = keysym}};
+ input.simulate(device_input, 1);
+
+ if (display.pending() > 0) {
+ const next_event = display.peekEvent();
+ if (
+ next_event.type == x.c.KeyRelease and
+ next_event.xkey.time == event.xkey.time and
+ next_event.xkey.keycode == event.xkey.keycode
+ ) {
+ _ = display.nextEvent();
+ }
+ }
+
+ if (x.c.XFilterEvent(&event, x.c.None) != 0) continue;
+ var status: x.c.Status = undefined;
+ const len = x.c.Xutf8LookupString(xic, &evt, &buf_utf8_of_keys_pressed, buf_utf8_of_keys_pressed.len, null, &status); // TODO get keysym here
+ switch (status) {
+ x.c.XBufferOverflow => return error.XBufferOverflow,
+ x.c.XLookupNone => {},
+ x.c.XLookupKeySym => return error.TODO,
+ x.c.XLookupChars,
+ x.c.XLookupBoth => {}, // OK
+ else => unreachable,
+ }
+ utf8_of_keys_pressed = buf_utf8_of_keys_pressed[0..@intCast(len)];
+ },
+ x.c.KeyRelease => {
+ var skip_release = false;
+ if (display.pending() > 0) {
+ const next_event = display.peekEvent();
+ if (
+ next_event.type == x.c.KeyPress and
+ next_event.xkey.time == event.xkey.time or
+ next_event.xkey.keycode == event.xkey.keycode
+ ) {
+ skip_release = true;
+ }
+ }
+ if (!skip_release) {
+ var evt = event.xkey;
+ const keysym = x.c.XLookupKeysym(&evt, 0);
+ input.simulate(.{.key = .{.code = keysym}}, 0);
+ }
+ },
+ x.c.ButtonPress => {
+ if (event.xbutton.button == 4) {
+ input.simulate(.{.pointer_axis = .{.axis = .scroll_y}}, 1);
+ } else if (event.xbutton.button == 5) {
+ input.simulate(.{.pointer_axis = .{.axis = .scroll_y}}, -1);
+ } else {
+ input.simulate(.{.pointer_button = .{.button = event.xbutton.button}}, 1);
+ }
+ },
+ x.c.ButtonRelease => {
+ input.simulate(.{.pointer_button = .{.button = event.xbutton.button}}, 0);
+ },
+ else => std.debug.print("Unexpected event type: {d}\n", .{event.type}),
+ }
+ }
+
+ reset = try loop_fn(delta_time);
+
+ if (reset) {
+ _ = x.c.XShmPutImage(
+ @ptrCast(display),
+ window.id,
+ display.defaultGC(screen),
+ cur_img, 0, 0, 0, 0, window_size[0], window_size[1], x.c.False,
+ );
+ cur_img_idx = (cur_img_idx + 1) % @as(u8, @intCast(screen_buffers.imgs.len));
+ cur_img = screen_buffers.imgs[cur_img_idx];
+ const raw_pixel_data: [*]u32 = @alignCast(@ptrCast(cur_img.data));
+ pixels.* = @ptrCast(raw_pixel_data[0..window_size[0]*window_size[1]]);
+
+ _ = x.c.XFlush(@ptrCast(display));
+ }
+
+ // WAIT FOR TARGET FPS
+ const tfps = target_fps orelse 800; // FIXME: at high FPS (>~800) elements sometimes aren't drawn and appear to "flicker"
+ const now = std.time.microTimestamp();
+ const wait_until = frame_start_time_us + @as(u32, @intFromFloat(1/tfps*std.time.us_per_s));
+ if (now < wait_until) {
+ const wait_delay = wait_until - now;
+ std.Thread.sleep(@intCast(wait_delay * std.time.ns_per_us));
+ }
+ }
+}
+
+pub const ScreenBuffers = struct {
+ imgs: [2]*x.c.XImage,
+ shminfos: [2]x.c.XShmSegmentInfo,
+};
+
+pub fn createScreenBuffers(screen_buffers: *ScreenBuffers, display: *x.Display, screen: x.Screen, window_size: [2]c_uint) void {
+ for (&screen_buffers.imgs, &screen_buffers.shminfos) |*img, *shminfo| {
+ img.* = x.c.XShmCreateImage(
+ @ptrCast(display),
+ display.defaultVisual(screen),
+ @intCast(display.defaultDepth(screen)),
+ x.c.ZPixmap, null,
+ shminfo,
+ window_size[0], window_size[1],
+ );
+ shminfo.shmid = x.c.shmget(x.c.IPC_PRIVATE, @intCast(img.*.bytes_per_line * img.*.height), x.c.IPC_CREAT | 0o777);
+ img.*.data = @ptrCast(x.c.shmat(shminfo.shmid, null, 0));
+ shminfo.shmaddr = img.*.data;
+ shminfo.readOnly = x.c.False;
+ _ = x.c.XShmAttach(@ptrCast(display), shminfo);
+ }
+ _ = x.c.XSync(@ptrCast(display), x.c.False);
+}
+
+inline fn XDestroyImage(ximage: anytype) c_int {
+ const func: *const fn (*x.c.XImage) callconv(.c) c_int = @ptrCast(ximage.*.f.destroy_image);
+ return func(ximage);
+}
+pub fn destroyScreenBuffers(sb: *ScreenBuffers, display: *x.Display) void {
+ for (&sb.imgs, &sb.shminfos) |*img, *shminfo| {
+ _ = x.c.XShmDetach(@ptrCast(display), shminfo); //_ = x.c.XDestroyImage(img); // Can't do this because zig doesn't translate it correctly
+ _ = XDestroyImage(img.*);
+
+
+ _ = x.c.shmdt(shminfo.shmaddr);
+ _ = x.c.shmctl(shminfo.shmid, x.c.IPC_RMID, 0);
+ }
+}
+
+pub const Key = x.c.KeySym;
+pub fn getPlatformKey(key: input.CommonKey) Key {
+ return switch (key) {
+ .w => x.c.XK_w,
+ .a => x.c.XK_a,
+ .s => x.c.XK_s,
+ .d => x.c.XK_d,
+ .left_shift => x.c.XK_Shift_L,
+ .right_shift => x.c.XK_Shift_R,
+ .left_control => x.c.XK_Control_L,
+ .right_control => x.c.XK_Control_R,
+ .arrow_left => x.c.XK_Left,
+ .arrow_right => x.c.XK_Right,
+ else => {
+ @panic("TODO define all");
+ },
+ };
+}
+
+pub const Button = c_uint;
+pub fn getPlatformButton(key: input.CommonButton) Button {
+ return switch (key) {
+ .left => x.c.Button1,
+ .middle => x.c.Button2,
+ .right => x.c.Button3,
+ };
+}
+
+comptime { if (@import("builtin").output_mode == .Lib) {
+ gui.exportCAbi();
+} }
+