#if 0 /* #/ ================================================================ #/ #/ gravity.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 int c32; typedef signed char b8; typedef float f32; typedef double f64; // ================================================================ // // PLATFORM API // // ================================================================ enum { MAX_NUM_PIXELS = 10 * 1024 * 1024, MAX_INPUT_SIZE = 256, MAX_CLIPBOARD_SIZE = 10 * 1024 * 1024, 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_F_, KEY_KP_ = KEY_F_ + 64, }; typedef struct { b8 ctrl : 1; b8 shift : 1; b8 alt : 1; b8 caps : 1; b8 num : 1; b8 scroll : 1; u16 key; c32 c; } Input_Key; typedef struct { c8 * title; i32 frame_width; i32 frame_height; u32 * pixels; i64 input_size; Input_Key *input; i64 clipboard_size; c8 * clipboard; 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); // UTF-8 i32 utf8_size(c32 c); c32 utf8_read(i64 len, c8 *s); i32 utf8_write(c32 c, c8 *buffer); // Window void p_init(void); void p_cleanup(void); i32 p_handle_events(void); i32 p_wait_events(void); void p_render_frame(void); // Clipboard void p_clipboard_write(i64 size, c8 *data); // 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}; // ================================================================ // // GAME // // ================================================================ #include #include #include #include #include #include typedef struct { f64 x; f64 y; f64 vx; f64 vy; } Entity; typedef struct { i64 time; i64 num_entities; Entity entities[10 * 1024 * 1024]; } World; i64 time_milliseconds() { struct timespec t; timespec_get(&t, TIME_UTC); return (i64) t.tv_sec * 1000ll + (i64) t.tv_nsec / 1000000ll; } u32 u32_from_rgb(f32 red, f32 green, f32 blue) { i32 r = (i32) floor(red * 255.f); i32 g = (i32) floor(green * 255.f); i32 b = (i32) floor(blue * 255.f); if (r < 0) r = 0; if (r > 255) r = 255; if (g < 0) g = 0; if (g > 255) g = 255; if (b < 0) b = 0; if (b > 255) b = 255; return (r << 16) | (g << 8) | b; } i32 main(i32 argc, c8 **argv) { (void) argc; (void) argv; u32 background = u32_from_rgb(.0f, .0f, .08f); platform = (Platform) { .title = "Gravity", .frame_width = 960, .frame_height = 720, }; p_init(); srand(time(0)); static World world = {0}; i64 time_0 = time_milliseconds(); while (!platform.done) { p_handle_events(); i64 time_elapsed = time_milliseconds() - time_0; time_0 += time_elapsed; if (platform.key_pressed[BUTTON_LEFT]) { i64 n = world.num_entities++; world.entities[n] = (Entity) { .x = (f64) platform.cursor_x - (f64) platform.frame_width / 2, .y = -((f64) platform.cursor_y - (f64) platform.frame_height / 2), }; } if (time_elapsed > 0) { if (platform.key_down[BUTTON_RIGHT]) { for (i64 n = 0; n < time_elapsed; ++n) world.entities[world.num_entities + n] = (Entity) { .x = (f64) platform.cursor_x - (f64) platform.frame_width / 2, .y = -((f64) platform.cursor_y - (f64) platform.frame_height / 2), }; world.num_entities += time_elapsed; } for (i64 n = 0; n < world.num_entities; ++n) { Entity *e = &world.entities[n]; for (i64 k = 0; k < n; ++k) { Entity *u = &world.entities[k]; f64 dx = e->x - u->x; f64 dy = e->y - u->y; f64 d = dx * dx + dy * dy; if (d < 1000.) continue; f64 r = sqrt(d); dx /= r; dy /= r; f64 a = (1. / d) * time_elapsed; if (d < 5000.) a = -a; e->vx -= dx * a; e->vy -= dy * a; u->vx += dx * a; u->vy += dy * a; } } for (i64 n = 0; n < world.num_entities; ++n) { Entity *e = &world.entities[n]; e->x += e->vx * time_elapsed; e->y += e->vy * time_elapsed; f64 z = (M_PI * 2.0) * (rand() % 10000) * .0001; e->vx += .0001 * cos(z); e->vy += .0001 * sin(z); } world.time += time_elapsed; } 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] = background; for (i64 n = 0; n < world.num_entities; ++n) { Entity *e = &world.entities[n]; i32 x = platform.frame_width / 2 + (i32) floor(e->x + .5); i32 y = platform.frame_height / 2 - (i32) floor(e->y + .5); for (i32 j = y - 10; j <= y + 10; ++j) { if (j < 0 || j >= platform.frame_height) continue; for (i32 i = x - 10; i <= x + 10; ++i) { if (i < 0 || i >= platform.frame_width) continue; if ((i - x) * (i - x) + (j - y) * (j - y) > 100) continue; f64 v = (e->vx * e->vx + e->vy * e->vy) * 8.; platform.pixels[j * platform.frame_width + i] = u32_from_rgb(-.2 + v * 2., .1 + v * .7, 1. - v); } } } p_render_frame(); } p_cleanup(); return 0; } // ================================================================ // // PLATFORM IMPLEMENTATION // // ---------------------------------------------------------------- // // TODO // - Sound // - Sockets // - Clipboard daemon // // ALSA // https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_min_8c-example.html // // ================================================================ #include #include c32 utf8_read(i64 len, c8 *s) { if (len >= 1 && (s[0] & 0x80) == 0) return s[0]; if (len >= 2 && (s[0] & 0xe0) == 0xc0 && (s[1] & 0xc0) == 0x80) return (s[1] & 0x3f) | ((s[0] & 0x1f) << 6); if (len >= 3 && (s[0] & 0xf0) == 0xe0 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80) return (s[2] & 0x3f) | ((s[1] & 0x3f) << 6) | ((s[0] & 0x0f) << 12); if (len >= 4 && (s[0] & 0xf8) == 0xf0 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80 && (s[3] & 0xc0) == 0x80) return (s[3] & 0x3f) | ((s[2] & 0x3f) << 6) | ((s[1] & 0x3f) << 12) | ((s[0] & 0x07) << 18); return 0; } i32 utf8_size(c32 c) { if ((c & 0x00007f) == c) return 1; if ((c & 0x0007ff) == c) return 2; if ((c & 0x00ffff) == c) return 3; if ((c & 0x1fffff) == c) return 4; return 0; } i32 utf8_write(c32 c, c8 *buffer) { if ((c & 0x7f) == c) { buffer[0] = (c8) c; return 1; } if ((c & 0x7ff) == c) { buffer[0] = 0xc0 | ((c >> 6) & 0x1f); buffer[1] = 0x80 | ( c & 0x3f); return 2; } if ((c & 0xffff) == c) { buffer[0] = 0xc0 | ((c >> 12) & 0x0f); buffer[1] = 0x80 | ((c >> 6) & 0x3f); buffer[2] = 0x80 | ( c & 0x3f); return 3; } if ((c & 0x1fffff) == c) { buffer[0] = 0xc0 | ((c >> 18) & 0x07); buffer[1] = 0x80 | ((c >> 12) & 0x3f); buffer[2] = 0x80 | ((c >> 6) & 0x3f); buffer[3] = 0x80 | ( c & 0x3f); return 4; } return 0; } // ================================================================ // // 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 #include static i16 _key_table[512] = {0}; static u32 _buffer[MAX_NUM_PIXELS] = {0}; static Input_Key _input[MAX_INPUT_SIZE] = {0}; static c8 _clipboard_buffer[MAX_CLIPBOARD_SIZE] = {0}; static XImage _image = {0}; static Display * _display = NULL; static GC _gc = NULL; static XIM _im = NULL; static XIC _ic = NULL; static Window _window = 0; static Atom _wm_delete_window = 0; static Atom _clipboard = 0; static Atom _targets = 0; static Atom _utf8_string = 0; static Atom _target = None; 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); assert(_gc != NULL); 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, }); _im = XOpenIM(_display, NULL, NULL, NULL); assert(_im != NULL); _ic = XCreateIC(_im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, _window, NULL); assert(_ic != NULL); platform.pixels = _buffer; platform.input = _input; platform.clipboard = _clipboard_buffer; _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); _clipboard = XInternAtom(_display, "CLIPBOARD", False); _targets = XInternAtom(_display, "TARGETS", False); _utf8_string = XInternAtom(_display, "UTF8_STRING", False); XSetICFocus(_ic); 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 = 0; 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; b8 requested_clipboard = 0; while (XEventsQueued(_display, QueuedAlready) > 0) { ++num_events; XNextEvent(_display, &ev); XFilterEvent(&ev, _window); 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:; } if (!requested_clipboard) { XConvertSelection(_display, _clipboard, _targets, _clipboard, _window, CurrentTime); requested_clipboard = 1; } 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++] = (Input_Key) { .ctrl = !!(ev.xkey.state & ControlMask), .shift = !!(ev.xkey.state & ShiftMask), .alt = !!(ev.xkey.state & Mod1Mask), .caps = !!(ev.xkey.state & LockMask), .num = !!(ev.xkey.state & Mod2Mask), .scroll = !!(ev.xkey.state & Mod3Mask), .key = k, .c = 0, }; else { c8 buf[16]; i32 len = Xutf8LookupString(_ic, &ev.xkey, buf, sizeof buf - 1, NULL, NULL); if (len > 0) platform.input[platform.input_size++] = (Input_Key) { .ctrl = !!(ev.xkey.state & ControlMask), .shift = !!(ev.xkey.state & ShiftMask), .alt = !!(ev.xkey.state & Mod1Mask), .caps = !!(ev.xkey.state & LockMask), .num = !!(ev.xkey.state & Mod2Mask), .scroll = !!(ev.xkey.state & Mod3Mask), .key = k, .c = utf8_read(len, buf), }; } } if (!requested_clipboard) { XConvertSelection(_display, _clipboard, _targets, _clipboard, _window, CurrentTime); requested_clipboard = 1; } } 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 SelectionRequest: if (ev.xselectionrequest.requestor != _window && ev.xselectionrequest.selection == _clipboard && XGetSelectionOwner(_display, _clipboard) == _window) { if (ev.xselectionrequest.property != None) { if (ev.xselectionrequest.target == _targets) XChangeProperty( ev.xselectionrequest.display, ev.xselectionrequest.requestor, ev.xselectionrequest.property, XA_ATOM, 32, PropModeReplace, (u8 *) &_utf8_string, 1 ); else if (ev.xselectionrequest.target == _utf8_string) XChangeProperty( ev.xselectionrequest.display, ev.xselectionrequest.requestor, ev.xselectionrequest.property, ev.xselectionrequest.target, 8, PropModeReplace, (u8 *) platform.clipboard, platform.clipboard_size ); } XSendEvent(_display, ev.xselectionrequest.requestor, 0, 0, (XEvent *) &(XSelectionEvent) { .type = SelectionNotify, .serial = ev.xselectionrequest.serial, .send_event = ev.xselectionrequest.send_event, .display = ev.xselectionrequest.display, .requestor = ev.xselectionrequest.requestor, .selection = ev.xselectionrequest.selection, .target = ev.xselectionrequest.target, .property = ev.xselectionrequest.property, .time = ev.xselectionrequest.time, }); } break; case SelectionNotify: if (ev.xselection.property != None) { i64 len; u8 *data; XGetWindowProperty( _display, _window, _clipboard, 0, MAX_CLIPBOARD_SIZE, False, AnyPropertyType, &(Atom) {0}, &(int) {0}, (unsigned long *) &len, &(unsigned long) {0}, &data ); if (ev.xselection.target == _targets) { _target = None; Atom *list = (Atom *) data; for (i64 i = 0; i < len; i++) if (list[i] == XA_STRING) _target = XA_STRING; else if (list[i] == _utf8_string) { _target = _utf8_string; break; } if (_target != None) XConvertSelection(_display, _clipboard, _target, _clipboard, _window, CurrentTime); } else if (ev.xselection.target == _target) { if (len > MAX_CLIPBOARD_SIZE) len = MAX_CLIPBOARD_SIZE; platform.clipboard_size = len; if (len > 0) memcpy(platform.clipboard, data, len); } if (data) XFree(data); } break; case EnterNotify: platform.has_cursor = 1; break; case LeaveNotify: platform.has_cursor = 0; break; case FocusOut: platform.has_focus = 0; break; case FocusIn: platform.has_focus = 1; if (!requested_clipboard) { XConvertSelection(_display, _clipboard, _targets, _clipboard, _window, CurrentTime); requested_clipboard = 1; } break; case MappingNotify: XRefreshKeyboardMapping(&ev.xmapping); 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); } void p_clipboard_write(i64 size, c8 *data) { assert(size <= MAX_CLIPBOARD_SIZE); XSetSelectionOwner(_display, _clipboard, _window, CurrentTime); platform.clipboard_size = size < MAX_CLIPBOARD_SIZE ? size : MAX_CLIPBOARD_SIZE; if (platform.clipboard_size > 0) memcpy(platform.clipboard, data, platform.clipboard_size); } #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