#if 0 /* #/ ================================================================ #/ #/ ui.c #/ #/ This is a reduced system layer. #/ It allows you to create a window, draw graphics in it, handle #/ input events. #/ #/ ---------------------------------------------------------------- #/ #/ DESIGN PRINCIPLES #/ #/ - Minimalistic feature set. For graphics, you have access to the #/ pixel buffer, and that's it. #/ #/ - No implicit control flow. No callbacks. You write your own #/ main and call everything explicitly. But the number of things #/ you have to call to do something is as little as possible. #/ #/ - Optimized to use in a single source file. #/ Installation process? Ctrl+C, Ctrl+V, done. #/ #/ If you have an idea how to reduce the feature set further, #/ let me know! #/ #/ ---------------------------------------------------------------- #/ #/ (C) 2024 Mitya Selivanov , MIT License #/ #/ ================================================================ #/ #/ Self-compilation shell script #/ SRC=${0##*./} BIN=${SRC%.*} gcc \ -Wall -Wextra -Werror -pedantic \ -Wno-old-style-declaration \ -Wno-missing-braces \ -Wno-unused-variable \ -Wno-unused-but-set-variable \ -Wno-unused-parameter \ -Wno-overlength-strings \ -O3 \ -fsanitize=undefined,address,leak -mshstk \ -lX11 -lm \ -o $BIN $SRC && \ ./$BIN $@ && rm $BIN exit $? # */ #endif // ================================================================ // // Basic declarations // // ================================================================ #define _GNU_SOURCE typedef signed char i8; typedef signed short i16; typedef signed i32; typedef signed long long i64; typedef unsigned char u8; typedef unsigned short u16; typedef unsigned u32; typedef unsigned long long u64; typedef char c8; typedef signed char b8; typedef float f32; typedef double f64; // ================================================================ // // PLATFORM API // // ================================================================ enum { MAX_NUM_PIXELS = 4 * 1024 * 1024, MAX_INPUT_SIZE = 256, MAX_CLIPBOARD_SIZE = 0, MAX_NUM_AUDIO_SAMPLES = 0, MAX_NUM_SOCKETS = 0, AUDIO_NUM_CHANNELS = 2, AUDIO_SAMPLE_RATE = 44100, IPv4 = 1, IPv6 = 2, KEY_LEFT = 128, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_LCTRL, KEY_RCTRL, KEY_LSHIFT, KEY_RSHIFT, KEY_LALT, KEY_RALT, KEY_ESCAPE, KEY_PRINTSCREEN, KEY_DELETE, KEY_PAUSE, KEY_INSERT, KEY_HOME, KEY_END, KEY_PAGEUP, KEY_PAGEDOWN, BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT, MOD_CTRL, MOD_SHIFT, MOD_ALT, MOD_CAPS, MOD_NUM, MOD_SCROLL, KEY_KP_, KEY_F_ = KEY_KP_ + 64, }; typedef struct { c8 *title; i32 frame_width; i32 frame_height; u32 *pixels; i64 input_size; i16 *input; i64 clipboard_size; c8 *clipboard; // TODO b8 done; b8 has_focus; b8 has_cursor; i32 cursor_x; i32 cursor_y; i32 cursor_dx; i32 cursor_dy; i64 wheel_dy; b8 key_down[512]; b8 key_pressed[512]; } Platform; typedef struct { u16 type; u16 port; union { u8 v4_address[4]; u8 v6_address[16]; }; } IP_Address; typedef i64 (*Thread_Proc)(void *user_data); // Window void p_init(void); void p_cleanup(void); i32 p_handle_events(void); i32 p_wait_events(void); void p_render_frame(void); // Sound void p_handle_audio(i64 time_elapsed); void p_queue_sound(i64 delay, i64 num_samples, f32 *samples); // UDP sockets i64 p_recv(u16 slot, IP_Address address, i64 size, u8 *data, IP_Address *remote_address); i64 p_send(u16 slot, IP_Address address, i64 size, u8 *data); Platform platform = {0}; // ================================================================ // // UI // // ================================================================ #include #include #include #include #define CHAR_NUM_BITS_X 6 #define CHAR_NUM_BITS_Y 7 #define CHAR_NUM_BITS (CHAR_NUM_BITS_X * CHAR_NUM_BITS_Y) enum { OP_SET, OP_XOR, }; u64 bitfont[] = { 0xbc0000000000, 0xc00300000, 0x5fd5040093f24fc9, 0xa00a2c2a1a280105, 0xc000415e6f, 0x400000020be0000, 0x1c38a8400000007d, 0x40002043e1020215, 0x408102000000010, 0x9800000000020002, 0xf913e00000033, 0x53200000207c8800, 0x3654880000099, 0x54b800000f840e00, 0xe953c000001a, 0x953e000000674080, 0x1e54b800000f, 0x490000000000240, 0x88a08000000, 0x20a220050a142850, 0x6520800000, 0x912f801eab260be, 0x800034952bf0001f, 0xc850bf0000921427, 0xf00010a54afc0003, 0xd29427800002142b, 0x840007e1023f0000, 0x7d09100000217e, 0x3f000188a08fc000, 0xc30c0cfc00000810, 0x27803f101013f00f, 0xc244bf0000f214, 0x4bf0002f21427800, 0xc254a480006c24, 0x407c00102fc08100, 0xf208080f0000fa0, 0x531007d81c607c0, 0xc208288c031141, 0x83fc00046954b10, 0x180e03000000, 0x41040000000ff04, 0x8102040810000404, 0x2a54600000000101, 0x309123e0000e, 0xc912180000a22447, 0x8000062a54700007, 0xe52a4300000029f0, 0xa0000602043e0001, 0x1d48000002074, 0x1f000003610f8000, 0x13e04f800000010, 0x470000780813e00f, 0x184893e0000e224, 0x23e0001f12243000, 0x82a54100000008, 0x40780000009f0200, 0xe208080e0001f20, 0xa22007981860780, 0x82082888022282, 0x16c200004ca95320, 0x7f000004, 0x408200000086d04, 0x8204, }; #define BITFONT_LEN ((i64) (sizeof bitfont / sizeof *bitfont)) i64 char_column_offset(c8 c, i64 column_index) { if (column_index < 0 || column_index >= CHAR_NUM_BITS_X) return -1; return (c - 32) * CHAR_NUM_BITS + column_index * CHAR_NUM_BITS_Y; } b8 char_bit(i64 column_offset, i64 row_index) { if (column_offset < 0 || column_offset / 64 >= BITFONT_LEN || row_index < 0 || row_index >= CHAR_NUM_BITS_Y) return 0; i64 bit_index = column_offset + row_index; i64 qword_index = bit_index / 64; if (qword_index < 0 || qword_index >= BITFONT_LEN) return 0; u64 mask = 1ull << (bit_index % 64); return !!(bitfont[qword_index] & mask); } u64 char_column_convolved(c8 c, i64 column_index) { if (column_index < 0 || column_index >= CHAR_NUM_BITS_X) return 0; i64 offset = char_column_offset(c, column_index); u64 column = 0; for (i64 y = 0; y < CHAR_NUM_BITS_Y; ++y) { if (!char_bit(offset, y)) continue; u64 mask = 1ull << y; column |= mask; // colvolution if (mask != 1) column |= mask >> 1; } return column; } b8 char_column_empty(c8 c, i64 column_index) { if (column_index < 0 || column_index >= CHAR_NUM_BITS_X) return 1; i64 offset = char_column_offset(c, column_index); for (i64 y = 0; y < CHAR_NUM_BITS_Y; ++y) if (char_bit(offset, y)) return 0; return 1; } i64 char_width(c8 c) { if (c < 32) return 0; if (c == ' ') return 4; i64 width = 0; for (; width < CHAR_NUM_BITS_X; ++width) if (char_column_empty(c, width) && char_column_empty(c, width + 1)) break; return width; } i64 char_spacing(i64 num_chars, c8 *text, i64 index) { assert(text != NULL); if (index < 0 || index + 1 >= num_chars) return 0; u64 a = char_column_convolved(text[index], char_width(text[index]) - 1); u64 b = char_column_convolved(text[index + 1], 0); if (!!(a & b)) return 1; return 0; } i64 text_cursor(i64 num_chars, c8 *text) { assert(text != NULL); i64 cursor = 0; for (i64 i = 0; i < num_chars; ++i) { if (text[i] <= ' ') { if (text[i] == '\n') cursor = 0; else if (text[i] == '\b' && i > 0) cursor -= char_width(text[i - 1]) + char_spacing(num_chars, text, i - 1); else if (text[i] == '\r') cursor = 0; else cursor += char_width(' ') + char_spacing(num_chars, text, i); continue; } cursor += char_width(text[i]) + char_spacing(num_chars, text, i); } return cursor; } i64 enum_text_columns(i64 num_chars, c8 *text) { assert(text != NULL); i64 cols = 0; i64 n = 0; for (i64 i = 0; i < num_chars; ++i) { if (text[i] <= ' ') { if (text[i] == '\n') { if (cols < n) cols = n; n = 0; } else if (text[i] == '\b' && i > 0) { if (cols < n) cols = n; n -= char_width(text[i - 1]) + char_spacing(num_chars, text, i - 1); } else if (text[i] == '\r') { if (cols < n) cols = n; n = 0; } else n += char_width(' ') + char_spacing(num_chars, text, i); continue; } n += char_width(text[i]) + char_spacing(num_chars, text, i); } if (cols < n) cols = n; return cols; } i64 enum_text_rows(i64 num_chars, c8 *text) { assert(text != NULL); i64 rows = 0; for (i64 i = 0; i <= num_chars; ++i) if (i == num_chars || text[i] == '\n') { if (rows > 0) ++rows; rows += CHAR_NUM_BITS_Y; } return rows; } void print_text(u32 color, f64 x0, f64 y0, f64 scale_x, f64 scale_y, i64 num_chars, c8 *text) { assert(text != NULL); f64 x = x0; f64 y = y0; f64 kx = scale_x; f64 h = scale_y * CHAR_NUM_BITS_Y; for (i64 n = 0; n < num_chars; ++n) { if (text[n] <= ' ') { if (text[n] == '\n') { x = x0; y += scale_y * (CHAR_NUM_BITS_Y + 1); } else if (text[n] == '\b' && n > 0) x -= kx * (char_width(text[n - 1]) + char_spacing(num_chars, text, n - 1)); else if (text[n] == '\r') x = x0; else x += kx * (char_width(' ') + char_spacing(num_chars, text, n)); continue; } i64 num_cols = char_width(text[n]); f64 w = num_cols * kx; i64 i0 = (i64) floor(x + .5); i64 i1 = (i64) floor(x + w + .5); i64 j0 = (i64) floor(y + .5); i64 j1 = (i64) floor(y + h + .5); for (i64 i = i0; i < i1; ++i) { if (i < 0) continue; if (i >= platform.frame_width) break; i64 column = ((i - i0) * num_cols) / (i1 - i0); i64 offset = char_column_offset(text[n], column); for (i64 j = j0; j < j1; ++j) { if (j < 0) continue; if (j >= platform.frame_height) break; i64 row = ((j - j0) * CHAR_NUM_BITS_Y) / (j1 - j0); if (char_bit(offset, row)) platform.pixels[j * platform.frame_width + i] = color; } } x += kx * (num_cols + char_spacing(num_chars, text, n)); } } void draw_panel(u32 op, u32 color, f64 x0, f64 y0, f64 width, f64 height) { i64 i0 = (i64) floor(x0 + .5); i64 j0 = (i64) floor(y0 + .5); i64 i1 = (i64) floor(x0 + width + .5); i64 j1 = (i64) floor(y0 + height + .5); if (i0 < 0) i0 = 0; if (j0 < 0) j0 = 0; if (i1 >= platform.frame_width) i1 = platform.frame_width - 1; if (j1 >= platform.frame_height) j1 = platform.frame_height - 1; for (i64 j = j0; j < j1; ++j) for (i64 i = i0; i < i1; ++i) if (op == OP_XOR) platform.pixels[j * platform.frame_width + i] ^= color; else platform.pixels[j * platform.frame_width + i] = color; } void draw_text_area(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 num_chars, c8 *text) { assert(max_scale_x > 1e-6); assert(max_scale_y > 1e-6); i64 num_columns = enum_text_columns(num_chars, text); i64 num_rows = enum_text_rows(num_chars, text); f64 scale_x = width / num_columns; f64 scale_y = height / num_rows; f64 kx = scale_x / max_scale_x; f64 ky = scale_y / max_scale_y; f64 k = kx < ky ? kx : ky; kx = k * max_scale_x; ky = k * max_scale_y; print_text(color, x0, y0, kx, ky, num_chars, text); } void draw_text_cursor(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 cursor, i64 selection, i64 num_chars, c8 *text) { assert(max_scale_x > 1e-6); assert(max_scale_y > 1e-6); i64 num_columns = enum_text_columns(num_chars, text); i64 num_rows = enum_text_rows(num_chars, text); i64 cursor_x = text_cursor(cursor, text); i64 cursor_y = enum_text_rows(cursor, text); f64 scale_x = width / num_columns; f64 scale_y = height / num_rows; f64 kx = scale_x / max_scale_x; f64 ky = scale_y / max_scale_y; f64 k = kx < ky ? kx : ky; kx = k * max_scale_x; ky = k * max_scale_y; if (selection != 0) { i64 selection_x, selection_y; if (selection > 0) { selection_x = text_cursor(cursor + selection, text); selection_y = enum_text_rows(cursor + selection, text); } else { selection_x = cursor_x; selection_y = cursor_y; cursor_x = text_cursor(cursor + selection, text); cursor_y = enum_text_rows(cursor + selection, text); } if (cursor_y == selection_y) draw_panel(OP_XOR, color, x0 + kx * cursor_x, y0 + ky * cursor_y - ky * (CHAR_NUM_BITS_Y + 1), kx * (selection_x - cursor_x), ky * (CHAR_NUM_BITS_Y + 1) ); else { draw_panel(OP_XOR, color, x0 + kx * cursor_x, y0 + ky * cursor_y - ky * (CHAR_NUM_BITS_Y + 1), kx * (num_columns - cursor_x), ky * (CHAR_NUM_BITS_Y + 1) ); for (i64 j = cursor_y + CHAR_NUM_BITS_Y + 1; j < selection_y; j += CHAR_NUM_BITS_Y + 1) draw_panel(OP_XOR, color, x0, y0 + ky * j - ky * (CHAR_NUM_BITS_Y + 1), kx * num_columns, ky * (CHAR_NUM_BITS_Y + 1) ); draw_panel(OP_XOR, color, x0, y0 + ky * selection_y - ky * (CHAR_NUM_BITS_Y + 1), kx * selection_x, ky * (CHAR_NUM_BITS_Y + 1) ); } } else draw_panel(OP_XOR, color, x0 + kx * cursor_x, y0 + ky * cursor_y - ky * CHAR_NUM_BITS_Y, kx * .5, ky * (CHAR_NUM_BITS_Y - 1)); } i32 main(i32 argc, c8 **argv) { (void) argc; (void) argv; platform = (Platform) { .title = "UI", .frame_width = 960, .frame_height = 720, }; p_init(); b8 button_0_down = 0; b8 button_1_down = 0; b8 button_1_checked = 0; i64 text_len = 0; static c8 text[512] = ""; i64 cursor = 0; i64 selection = 0; while (!platform.done) { p_wait_events(); for (i32 j = 0; j < platform.frame_height; ++j) for (i32 i = 0; i < platform.frame_width; ++i) platform.pixels[j * platform.frame_width + i] = 0x302000; if (platform.cursor_x >= 40 && platform.cursor_x < 100 && platform.cursor_y >= 40 && platform.cursor_y < 100) { button_0_down = platform.key_down[BUTTON_LEFT]; if (button_0_down) draw_panel(OP_SET, 0xffffff, 40, 40, 60, 60); else draw_panel(OP_SET, 0x00ff00, 40, 40, 60, 60); } else { button_0_down = 0; draw_panel(OP_SET, 0x208020, 40, 40, 60, 60); } if (platform.cursor_x >= 40 && platform.cursor_x < 100 && platform.cursor_y >= 120 && platform.cursor_y < 180) { button_1_down = platform.key_down[BUTTON_LEFT]; if (platform.key_pressed[BUTTON_LEFT]) button_1_checked = !button_1_checked; if (button_1_down) draw_panel(OP_SET, 0xffffff, 40, 120, 60, 60); else if (button_1_checked) draw_panel(OP_SET, 0xff8080, 40, 120, 60, 60); else draw_panel(OP_SET, 0x80ff80, 40, 120, 60, 60); } else { button_1_down = 0; if (button_1_checked) draw_panel(OP_SET, 0xff0000, 40, 120, 60, 60); else draw_panel(OP_SET, 0x00ff00, 40, 120, 60, 60); } i64 w = platform.frame_width / 2; i64 h = platform.frame_height / 2; i64 x0 = w / 2; i64 y0 = h / 2; i64 color = 0xff7f7f; if (platform.cursor_x >= x0 && platform.cursor_x < x0 + w && platform.cursor_y >= y0 && platform.cursor_y < y0 + h) color = 0xffffff; for (i64 i = 0; i < platform.input_size; ++i) switch (platform.input[i]) { case KEY_LEFT: if (platform.key_down[MOD_SHIFT]) { if (cursor > 0) ++selection; } else if (selection != 0) { cursor = selection < 0 ? cursor + selection + 1 : cursor + 1; selection = 0; } if (cursor > 0) --cursor; break; case KEY_RIGHT: if (platform.key_down[MOD_SHIFT]) { if (cursor < text_len) --selection; } else if (selection != 0) { cursor = selection < 0 ? cursor - 1 : cursor + selection - 1; selection = 0; } if (cursor < text_len) ++cursor; break; case '\b': if (selection != 0) { i64 i0 = selection < 0 ? cursor + selection : cursor; i64 i1 = selection < 0 ? cursor : cursor + selection; for (i64 i = 0; i1 + i < text_len; ++i) text[i0 + i] = text[i1 + i]; selection = 0; cursor = i0; text_len -= i1 - i0; } else if (cursor > 0 && text_len > 0) { for (i64 i = cursor; i < text_len; ++i) text[i - 1] = text[i]; --cursor; --text_len; } break; case KEY_DELETE: if (selection != 0) { i64 i0 = selection < 0 ? cursor + selection : cursor; i64 i1 = selection < 0 ? cursor : cursor + selection; for (i64 i = 0; i1 + i < text_len; ++i) text[i0 + i] = text[i1 + i]; selection = 0; cursor = i0; text_len -= i1 - i0; } else if (cursor < text_len) { for (i64 i = cursor + 1; i < text_len; ++i) text[i - 1] = text[i]; --text_len; } break; default: if (platform.input[i] > 0 && platform.input[i] < 128) { if (selection != 0) { i64 i0 = selection < 0 ? cursor + selection : cursor; i64 i1 = selection < 0 ? cursor : cursor + selection; for (i64 i = 0; i1 + i < text_len; ++i) text[i0 + i] = text[i1 + i]; selection = 0; cursor = i0; text_len -= i1 - i0; } if (text_len < (i64) sizeof text) { for (i64 i = text_len; i > cursor; --i) text[i] = text[i - 1]; text[cursor++] = (c8) platform.input[i]; ++text_len; } } } draw_text_area(0, x0 + 8, y0 - 8, w, h, 10., 10., text_len, text); draw_text_area(color, x0, y0, w, h, 10., 10., text_len, text); draw_text_cursor(0xffffff, x0, y0, w, h, 10., 10., cursor, selection, text_len, text); p_render_frame(); } p_cleanup(); return 0; } // ================================================================ // // PLATFORM IMPLEMENTATION // // ---------------------------------------------------------------- // // TODO // - Clipboard // - Sound // - Sockets // - Clipboard daemon // // X11 clipboard // https://handmade.network/forums/articles/t/8544-implementing_copy_paste_in_x11 // // ALSA // https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_min_8c-example.html // // ================================================================ #include #include // ================================================================ // // UDP sockets // // ================================================================ i64 p_recv(u16 slot, IP_Address address, i64 size, u8 *data, IP_Address *remote_address) { (void) slot; (void) address; (void) size; (void) data; (void) remote_address; assert(0); return 0; } i64 p_send(u16 slot, IP_Address address, i64 size, u8 *data) { (void) slot; (void) address; (void) size; (void) data; assert(0); return 0; } // ================================================================ // // X11 // // ================================================================ #ifdef __linux__ #include #include #include #include static i16 _key_table[512] = {0}; static u32 _buffer[MAX_NUM_PIXELS] = {0}; static i16 _input[MAX_INPUT_SIZE] = {0}; static XImage _image = {0}; static Display *_display = NULL; static GC _gc = NULL; static Window _window = 0; static Atom _wm_delete_window = 0; void p_init(void) { _display = XOpenDisplay(NULL); assert(_display != NULL); _key_table[XKeysymToKeycode(_display, XK_Left)] = KEY_LEFT; _key_table[XKeysymToKeycode(_display, XK_Right)] = KEY_RIGHT; _key_table[XKeysymToKeycode(_display, XK_Up)] = KEY_UP; _key_table[XKeysymToKeycode(_display, XK_Down)] = KEY_DOWN; _key_table[XKeysymToKeycode(_display, XK_1)] = '1'; _key_table[XKeysymToKeycode(_display, XK_2)] = '2'; _key_table[XKeysymToKeycode(_display, XK_3)] = '3'; _key_table[XKeysymToKeycode(_display, XK_4)] = '4'; _key_table[XKeysymToKeycode(_display, XK_5)] = '5'; _key_table[XKeysymToKeycode(_display, XK_6)] = '6'; _key_table[XKeysymToKeycode(_display, XK_7)] = '7'; _key_table[XKeysymToKeycode(_display, XK_8)] = '8'; _key_table[XKeysymToKeycode(_display, XK_9)] = '9'; _key_table[XKeysymToKeycode(_display, XK_0)] = '0'; _key_table[XKeysymToKeycode(_display, XK_A)] = 'a'; _key_table[XKeysymToKeycode(_display, XK_B)] = 'b'; _key_table[XKeysymToKeycode(_display, XK_C)] = 'c'; _key_table[XKeysymToKeycode(_display, XK_D)] = 'd'; _key_table[XKeysymToKeycode(_display, XK_E)] = 'e'; _key_table[XKeysymToKeycode(_display, XK_F)] = 'f'; _key_table[XKeysymToKeycode(_display, XK_G)] = 'g'; _key_table[XKeysymToKeycode(_display, XK_H)] = 'h'; _key_table[XKeysymToKeycode(_display, XK_I)] = 'i'; _key_table[XKeysymToKeycode(_display, XK_J)] = 'j'; _key_table[XKeysymToKeycode(_display, XK_K)] = 'k'; _key_table[XKeysymToKeycode(_display, XK_L)] = 'l'; _key_table[XKeysymToKeycode(_display, XK_M)] = 'm'; _key_table[XKeysymToKeycode(_display, XK_N)] = 'n'; _key_table[XKeysymToKeycode(_display, XK_O)] = 'o'; _key_table[XKeysymToKeycode(_display, XK_P)] = 'p'; _key_table[XKeysymToKeycode(_display, XK_Q)] = 'q'; _key_table[XKeysymToKeycode(_display, XK_R)] = 'r'; _key_table[XKeysymToKeycode(_display, XK_S)] = 's'; _key_table[XKeysymToKeycode(_display, XK_T)] = 't'; _key_table[XKeysymToKeycode(_display, XK_U)] = 'u'; _key_table[XKeysymToKeycode(_display, XK_V)] = 'v'; _key_table[XKeysymToKeycode(_display, XK_W)] = 'w'; _key_table[XKeysymToKeycode(_display, XK_X)] = 'x'; _key_table[XKeysymToKeycode(_display, XK_Y)] = 'y'; _key_table[XKeysymToKeycode(_display, XK_Z)] = 'z'; _key_table[XKeysymToKeycode(_display, XK_space)] = ' '; _key_table[XKeysymToKeycode(_display, XK_braceleft)] = '['; _key_table[XKeysymToKeycode(_display, XK_braceright)] = ']'; _key_table[XKeysymToKeycode(_display, XK_colon)] = ';'; _key_table[XKeysymToKeycode(_display, XK_quotedbl)] = '\''; _key_table[XKeysymToKeycode(_display, XK_asciitilde)] = '`'; _key_table[XKeysymToKeycode(_display, XK_backslash)] = '\\'; _key_table[XKeysymToKeycode(_display, XK_comma)] = ','; _key_table[XKeysymToKeycode(_display, XK_greater)] = '.'; _key_table[XKeysymToKeycode(_display, XK_question)] = '/'; _key_table[XKeysymToKeycode(_display, XK_minus)] = '-'; _key_table[XKeysymToKeycode(_display, XK_equal)] = '='; _key_table[XKeysymToKeycode(_display, XK_F1)] = KEY_F_ + 1; _key_table[XKeysymToKeycode(_display, XK_F2)] = KEY_F_ + 2; _key_table[XKeysymToKeycode(_display, XK_F3)] = KEY_F_ + 3; _key_table[XKeysymToKeycode(_display, XK_F4)] = KEY_F_ + 4; _key_table[XKeysymToKeycode(_display, XK_F5)] = KEY_F_ + 5; _key_table[XKeysymToKeycode(_display, XK_F6)] = KEY_F_ + 6; _key_table[XKeysymToKeycode(_display, XK_F7)] = KEY_F_ + 7; _key_table[XKeysymToKeycode(_display, XK_F8)] = KEY_F_ + 8; _key_table[XKeysymToKeycode(_display, XK_F9)] = KEY_F_ + 9; _key_table[XKeysymToKeycode(_display, XK_F10)] = KEY_F_ + 10; _key_table[XKeysymToKeycode(_display, XK_F11)] = KEY_F_ + 11; _key_table[XKeysymToKeycode(_display, XK_F12)] = KEY_F_ + 12; _key_table[XKeysymToKeycode(_display, XK_Control_L)] = KEY_LCTRL; _key_table[XKeysymToKeycode(_display, XK_Control_R)] = KEY_RCTRL; _key_table[XKeysymToKeycode(_display, XK_Shift_L)] = KEY_LSHIFT; _key_table[XKeysymToKeycode(_display, XK_Shift_R)] = KEY_RSHIFT; _key_table[XKeysymToKeycode(_display, XK_Alt_L)] = KEY_LALT; _key_table[XKeysymToKeycode(_display, XK_Alt_R)] = KEY_RALT; _key_table[XKeysymToKeycode(_display, XK_Escape)] = KEY_ESCAPE; _key_table[XKeysymToKeycode(_display, XK_BackSpace)] = '\b'; _key_table[XKeysymToKeycode(_display, XK_Tab)] = '\t'; _key_table[XKeysymToKeycode(_display, XK_Return)] = '\n'; _key_table[XKeysymToKeycode(_display, XK_Print)] = KEY_PRINTSCREEN; _key_table[XKeysymToKeycode(_display, XK_Delete)] = KEY_DELETE; _key_table[XKeysymToKeycode(_display, XK_Pause)] = KEY_PAUSE; _key_table[XKeysymToKeycode(_display, XK_Insert)] = KEY_INSERT; _key_table[XKeysymToKeycode(_display, XK_Home)] = KEY_HOME; _key_table[XKeysymToKeycode(_display, XK_End)] = KEY_END; _key_table[XKeysymToKeycode(_display, XK_Page_Up)] = KEY_PAGEUP; _key_table[XKeysymToKeycode(_display, XK_Page_Down)] = KEY_PAGEDOWN; _key_table[XKeysymToKeycode(_display, XK_KP_0)] = KEY_KP_ + '0'; _key_table[XKeysymToKeycode(_display, XK_KP_1)] = KEY_KP_ + '1'; _key_table[XKeysymToKeycode(_display, XK_KP_2)] = KEY_KP_ + '2'; _key_table[XKeysymToKeycode(_display, XK_KP_3)] = KEY_KP_ + '3'; _key_table[XKeysymToKeycode(_display, XK_KP_4)] = KEY_KP_ + '4'; _key_table[XKeysymToKeycode(_display, XK_KP_5)] = KEY_KP_ + '5'; _key_table[XKeysymToKeycode(_display, XK_KP_6)] = KEY_KP_ + '6'; _key_table[XKeysymToKeycode(_display, XK_KP_7)] = KEY_KP_ + '7'; _key_table[XKeysymToKeycode(_display, XK_KP_8)] = KEY_KP_ + '8'; _key_table[XKeysymToKeycode(_display, XK_KP_9)] = KEY_KP_ + '9'; _key_table[XKeysymToKeycode(_display, XK_KP_Enter)] = KEY_KP_ + '\n'; _key_table[XKeysymToKeycode(_display, XK_KP_Divide)] = KEY_KP_ + '/'; _key_table[XKeysymToKeycode(_display, XK_KP_Multiply)] = KEY_KP_ + '*'; _key_table[XKeysymToKeycode(_display, XK_KP_Add)] = KEY_KP_ + '+'; _key_table[XKeysymToKeycode(_display, XK_KP_Subtract)] = KEY_KP_ + '-'; _key_table[XKeysymToKeycode(_display, XK_KP_Decimal)] = KEY_KP_ + '.'; _key_table[XKeysymToKeycode(_display, XK_KP_Separator)] = KEY_KP_ + ','; i32 screen = DefaultScreen(_display); i32 depth = DefaultDepth (_display, screen); Visual *visual = DefaultVisual(_display, screen); _gc = DefaultGC(_display, screen); XSetGraphicsExposures(_display, _gc, False); i32 _display_width = DisplayWidth (_display, screen); i32 _display_height = DisplayHeight(_display, screen); i32 x = (_display_width - platform.frame_width) / 2; i32 y = (_display_height - platform.frame_height) / 2; _window = XCreateWindow(_display, XDefaultRootWindow(_display), x, y, platform.frame_width, platform.frame_height, 0, depth, InputOutput, visual, CWEventMask, &(XSetWindowAttributes) { .event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | VisibilityChangeMask | FocusChangeMask | StructureNotifyMask | SubstructureNotifyMask, }); platform.pixels = _buffer; platform.input = _input; _image = (XImage) { .width = platform.frame_width, .height = platform.frame_height, .depth = depth, .xoffset = 0, .format = ZPixmap, .data = (c8 *) _buffer, .byte_order = LSBFirst, .bitmap_unit = 32, .bitmap_bit_order = LSBFirst, .bitmap_pad = 32, .bits_per_pixel = 32, .bytes_per_line = 4 * platform.frame_width, .red_mask = 0xff0000, .green_mask = 0x00ff00, .blue_mask = 0x0000ff, }; XInitImage(&_image); _wm_delete_window = XInternAtom(_display, "WM_DELETE_WINDOW", False); XSetWMProtocols(_display, _window, &_wm_delete_window, 1); XStoreName(_display, _window, platform.title); XMapWindow(_display, _window); } void p_cleanup(void) { XDestroyWindow(_display, _window); XCloseDisplay (_display); } i32 p_handle_events(void) { i32 num_events = XEventsQueued(_display, QueuedAlready); memset(platform.key_pressed, 0, sizeof platform.key_pressed); platform.input_size = 0; platform.cursor_dx = 0; platform.cursor_dy = 0; platform.wheel_dy = 0; XEvent ev; for (i32 i = 0; i < num_events; ++i) { XNextEvent(_display, &ev); switch (ev.type) { case DestroyNotify: platform.done = 1; break; case MotionNotify: platform.cursor_dx += ev.xmotion.x - platform.cursor_x; platform.cursor_dy += ev.xmotion.y - platform.cursor_y; platform.cursor_x = ev.xmotion.x; platform.cursor_y = ev.xmotion.y; break; case ButtonPress: platform.cursor_x = ev.xbutton.x; platform.cursor_y = ev.xbutton.y; switch (ev.xbutton.button) { case Button1: platform.key_down[BUTTON_LEFT] = 1; ++platform.key_pressed[BUTTON_LEFT]; break; case Button2: platform.key_down[BUTTON_MIDDLE] = 1; ++platform.key_pressed[BUTTON_MIDDLE]; break; case Button3: platform.key_down[BUTTON_RIGHT] = 1; ++platform.key_pressed[BUTTON_RIGHT]; break; case Button4: ++platform.wheel_dy; break; case Button5: --platform.wheel_dy; break; default:; } break; case ButtonRelease: platform.cursor_x = ev.xbutton.x; platform.cursor_y = ev.xbutton.y; switch (ev.xbutton.button) { case Button1: platform.key_down[BUTTON_LEFT] = 0; break; case Button2: platform.key_down[BUTTON_MIDDLE] = 0; break; case Button3: platform.key_down[BUTTON_RIGHT] = 0; break; default:; } break; case KeyPress: { i16 k = _key_table[ev.xkey.keycode]; platform.cursor_x = ev.xkey.x; platform.cursor_y = ev.xkey.y; platform.key_down [k] = 1; platform.key_pressed[k]++; platform.key_down[MOD_CTRL] = !!(ev.xkey.state & ControlMask); platform.key_down[MOD_SHIFT] = !!(ev.xkey.state & ShiftMask); platform.key_down[MOD_ALT] = !!(ev.xkey.state & Mod1Mask); platform.key_down[MOD_CAPS] = !!(ev.xkey.state & LockMask); platform.key_down[MOD_NUM] = !!(ev.xkey.state & Mod2Mask); platform.key_down[MOD_SCROLL] = !!(ev.xkey.state & Mod3Mask); if (platform.input_size < MAX_INPUT_SIZE) { if (k < 32 || k >= 128) platform.input[platform.input_size++] = k; else { c8 buf[16]; i32 len = XLookupString(&ev.xkey, buf, sizeof buf - 1, NULL, NULL); buf[len] = '\0'; printf("key:"); for (i32 i = 0; i < len; ++i) printf("%02x", buf[i]); printf("\n"); for (i32 i = 0; i < len && platform.input_size < MAX_INPUT_SIZE; ++i, ++platform.input_size) platform.input[platform.input_size] = buf[i]; } } } break; case KeyRelease: { i16 k = _key_table[ev.xkey.keycode]; platform.cursor_x = ev.xkey.x; platform.cursor_y = ev.xkey.y; platform.key_down [k] = 0; platform.key_pressed[k]--; platform.key_down[MOD_CTRL] = !!(ev.xkey.state & ControlMask); platform.key_down[MOD_SHIFT] = !!(ev.xkey.state & ShiftMask); platform.key_down[MOD_ALT] = !!(ev.xkey.state & Mod1Mask); platform.key_down[MOD_CAPS] = !!(ev.xkey.state & LockMask); platform.key_down[MOD_NUM] = !!(ev.xkey.state & Mod2Mask); platform.key_down[MOD_SCROLL] = !!(ev.xkey.state & Mod3Mask); } break; case EnterNotify: platform.has_cursor = 1; break; case LeaveNotify: platform.has_cursor = 0; break; case FocusIn: platform.has_focus = 1; break; case FocusOut: platform.has_focus = 0; break; case ClientMessage: if ((Atom) ev.xclient.data.l[0] == _wm_delete_window) platform.done = 1; break; default:; } } for (i64 k = 0; k < (i64) (sizeof platform.key_pressed / sizeof *platform.key_pressed); ++k) platform.key_pressed[k] = platform.key_pressed[k] > 0 ? 1 : 0; XWindowAttributes attrs; XGetWindowAttributes(_display, _window, &attrs); if ((platform.frame_width != attrs.width || platform.frame_height != attrs.height) && attrs.width * attrs.height * 4 <= (i32) sizeof _buffer) { if (attrs.width > 0 && attrs.height > 0) { _image.width = attrs.width; _image.height = attrs.height; _image.bytes_per_line = 4 * attrs.width; } platform.frame_width = attrs.width; platform.frame_height = attrs.height; } return num_events; } i32 p_wait_events(void) { i32 num_events = 0; do { num_events = p_handle_events(); sched_yield(); } while (num_events == 0); return num_events; } void p_render_frame(void) { if (platform.done) return; XPutImage(_display, _window, _gc, &_image, 0, 0, 0, 0, platform.frame_width, platform.frame_height); XFlush(_display); } #endif // ================================================================ // // ALSA // // ================================================================ #ifdef __linux__ void p_handle_audio(i64 time_elapsed) { (void) time_elapsed; assert(0); } void p_queue_sound(i64 delay, i64 num_samples, f32 *samples) { (void) delay; (void) num_samples; (void) samples; assert(0); } #endif