summaryrefslogtreecommitdiff
path: root/examples/landscape.c
diff options
context:
space:
mode:
authorMitya Selivanov <automainint@guattari.tech>2024-08-12 14:37:58 +0200
committerMitya Selivanov <automainint@guattari.tech>2024-08-12 14:37:58 +0200
commitb204f057653f13ed2a578fec70a23ae333641513 (patch)
tree1f5ba4a08431a49d5342d16cbd1f7eb2216d7abb /examples/landscape.c
parentda9d431435211378205817199aaf24428697e3b3 (diff)
downloadreduced_system_layer-b204f057653f13ed2a578fec70a23ae333641513.zip
Update code
Diffstat (limited to 'examples/landscape.c')
-rwxr-xr-xexamples/landscape.c406
1 files changed, 353 insertions, 53 deletions
diff --git a/examples/landscape.c b/examples/landscape.c
index a3de2cf..813ad21 100755
--- a/examples/landscape.c
+++ b/examples/landscape.c
@@ -41,6 +41,7 @@ gcc \
-Wno-unused-variable \
-Wno-unused-but-set-variable \
-Wno-unused-parameter \
+ -Wno-overlength-strings \
-O3 \
-fsanitize=undefined,address,leak -mshstk \
-lX11 -lm \
@@ -65,6 +66,7 @@ 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;
@@ -76,8 +78,9 @@ typedef double f64;
// ================================================================
enum {
- MAX_NUM_PIXELS = 4 * 1024 * 1024,
- MAX_CLIPBOARD_SIZE = 0,
+ 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,
@@ -87,7 +90,7 @@ enum {
IPv4 = 1,
IPv6 = 2,
- KEY_LEFT,
+ KEY_LEFT = 128,
KEY_RIGHT,
KEY_UP,
KEY_DOWN,
@@ -106,30 +109,49 @@ enum {
KEY_END,
KEY_PAGEUP,
KEY_PAGEDOWN,
- KEY_KP_ = 128,
- KEY_F_ = 256,
- BUTTON_LEFT = KEY_F_ + 64,
+ 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 {
- c8 *title;
- i32 frame_width;
- i32 frame_height;
- u32 *pixels;
- 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];
+ 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 {
@@ -143,12 +165,21 @@ typedef struct {
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);
@@ -164,11 +195,6 @@ Platform platform = {0};
// GAME
//
// ================================================================
-//
-// TODO
-// - Font
-//
-// ================================================================
#include <stdio.h>
#include <stdlib.h>
@@ -451,17 +477,85 @@ i32 main(i32 argc, c8 **argv) {
// ----------------------------------------------------------------
//
// TODO
-// - Clipboard
// - Sound
// - Sockets
-//
-// X11 clipboard
-// https://handmade.network/forums/articles/t/8544-implementing_copy_paste_in_x11
+// - Clipboard daemon
//
// ALSA
// https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_min_8c-example.html
//
// ================================================================
+
+#include <assert.h>
+#include <string.h>
+
+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
//
@@ -496,14 +590,25 @@ i64 p_send(u16 slot, IP_Address address, i64 size, u8 *data) {
#include <X11/Xlib.h>
#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <sched.h>
+#include <time.h>
-static i16 _key_table[512];
-static u32 _buffer[MAX_NUM_PIXELS];
-static XImage _image;
-static Display *_display;
-static GC _gc;
-static Window _window;
-static Atom _wm_delete_window;
+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);
@@ -559,6 +664,8 @@ void p_init(void) {
_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;
@@ -612,6 +719,7 @@ void p_init(void) {
Visual *visual = DefaultVisual(_display, screen);
_gc = DefaultGC(_display, screen);
+ assert(_gc != NULL);
XSetGraphicsExposures(_display, _gc, False);
@@ -623,7 +731,15 @@ void p_init(void) {
_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;
+ _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,
@@ -643,11 +759,14 @@ void p_init(void) {
.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);
@@ -660,18 +779,24 @@ void p_cleanup(void) {
}
i32 p_handle_events(void) {
- i32 num_events = XEventsQueued(_display, QueuedAlready);
+ i32 num_events = 0;
memset(platform.key_pressed, 0, sizeof platform.key_pressed);
- platform.cursor_dx = 0;
- platform.cursor_dy = 0;
- platform.wheel_dy = 0;
+ 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) {
+ b8 requested_clipboard = 0;
+
+ while (XEventsQueued(_display, QueuedAlready) > 0) {
+ ++num_events;
+
XNextEvent(_display, &ev);
+ XFilterEvent(&ev, _window);
switch (ev.type) {
case DestroyNotify:
@@ -705,6 +830,10 @@ i32 p_handle_events(void) {
case Button5: --platform.wheel_dy; break;
default:;
}
+ if (!requested_clipboard) {
+ XConvertSelection(_display, _clipboard, _targets, _clipboard, _window, CurrentTime);
+ requested_clipboard = 1;
+ }
break;
case ButtonRelease:
@@ -718,25 +847,172 @@ i32 p_handle_events(void) {
}
break;
- case KeyPress:
+ case KeyPress: {
+ i16 k = _key_table[ev.xkey.keycode];
platform.cursor_x = ev.xkey.x;
platform.cursor_y = ev.xkey.y;
- platform.key_down [_key_table[ev.xkey.keycode]] = 1;
- platform.key_pressed[_key_table[ev.xkey.keycode]]++;
- break;
+ 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:
+ case KeyRelease: {
+ i16 k = _key_table[ev.xkey.keycode];
platform.cursor_x = ev.xkey.x;
platform.cursor_y = ev.xkey.y;
- platform.key_down [_key_table[ev.xkey.keycode]] = 0;
- platform.key_pressed[_key_table[ev.xkey.keycode]]--;
+ 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 FocusIn: platform.has_focus = 1; 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;
@@ -746,6 +1022,9 @@ i32 p_handle_events(void) {
}
}
+ 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);
@@ -763,6 +1042,17 @@ i32 p_handle_events(void) {
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;
@@ -771,6 +1061,16 @@ void p_render_frame(void) {
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
// ================================================================