From da9d431435211378205817199aaf24428697e3b3 Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Sun, 11 Aug 2024 17:16:33 +0200 Subject: Clipboard --- examples/ui.c | 420 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 316 insertions(+), 104 deletions(-) (limited to 'examples/ui.c') diff --git a/examples/ui.c b/examples/ui.c index cd355c5..810c9d6 100755 --- a/examples/ui.c +++ b/examples/ui.c @@ -78,9 +78,9 @@ typedef double f64; // ================================================================ enum { - MAX_NUM_PIXELS = 4 * 1024 * 1024, + MAX_NUM_PIXELS = 10 * 1024 * 1024, MAX_INPUT_SIZE = 256, - MAX_CLIPBOARD_SIZE = 0, + MAX_CLIPBOARD_SIZE = 10 * 1024 * 1024, MAX_NUM_AUDIO_SAMPLES = 0, MAX_NUM_SOCKETS = 0, @@ -123,7 +123,13 @@ enum { }; typedef struct { - u32 key; + b8 ctrl : 1; + b8 shift : 1; + b8 alt : 1; + b8 caps : 1; + b8 num : 1; + b8 scroll : 1; + u16 key; c32 c; } Input_Key; @@ -135,7 +141,7 @@ typedef struct { i64 input_size; Input_Key *input; i64 clipboard_size; - c8 * clipboard; // TODO + c8 * clipboard; b8 done; b8 has_focus; b8 has_cursor; @@ -160,8 +166,9 @@ typedef struct { typedef i64 (*Thread_Proc)(void *user_data); // UTF-8 -c32 read_utf8(i64 len, c8 *s); -i32 write_utf8(c32 c, c8 *buffer); +i32 utf8_size(c32 c); +c32 utf8_read(i64 len, c8 *s); +i32 utf8_write(c32 c, c8 *buffer); // Window void p_init(void); @@ -170,6 +177,9 @@ 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); @@ -228,19 +238,12 @@ u64 char_column_convolved(c32 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; + i64 offset = char_column_offset(c, column_index); - 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; - } + for (i64 y = 0; y < CHAR_NUM_BITS_Y; ++y) + if (char_bit(offset, y)) + column |= 3ull << y; return column; } @@ -587,72 +590,111 @@ i32 main(i32 argc, c8 **argv) { color = 0xffffff; for (i64 i = 0; i < platform.input_size; ++i) - switch (platform.input[i].key) { - 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; + if (platform.input[i].ctrl) + switch (platform.input[i].key) { + case 'v': { + 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; + } - 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; + for (i64 n = 0; n < platform.clipboard_size;) { + c32 c = utf8_read(platform.clipboard_size - n, platform.clipboard + n); + + if (text_len < (i64) (sizeof text / sizeof *text)) { + for (i64 j = text_len; j > cursor; --j) + text[j] = text[j - 1]; + text[cursor++] = c; + ++text_len; + } + + n += utf8_size(c); + } + } break; + + case 'x': { + i64 len = 0; + static c8 buf[1024]; - case '\b': - if (selection != 0) { i64 i0 = selection < 0 ? cursor + selection : cursor; i64 i1 = selection < 0 ? cursor : cursor + selection; + for (i64 i = 0; len + 4 <= (i64) sizeof buf && i < i1 - i0; ++i) + len += utf8_write(text[i0 + i], buf + len); + + if (len > 0) + p_clipboard_write(len, buf); + 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; + } break; + + case 'c': { + i64 len = 0; + static c8 buf[1024]; - 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; + for (i64 i = 0; len + 4 <= (i64) sizeof buf && i < i1 - i0; ++i) + len += utf8_write(text[i0 + i], buf + len); + + if (len > 0) + p_clipboard_write(len, buf); + } break; - case '\n': - case '\r': - case '\t': - platform.input[i].c = platform.input[i].key; - // fallthrough + default:; + } + else + switch (platform.input[i].key) { + 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; - default: - if (platform.input[i].c) { + 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; @@ -661,16 +703,39 @@ i32 main(i32 argc, c8 **argv) { 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; - if (text_len < (i64) sizeof text) { - for (i64 i = text_len; i > cursor; --i) - text[i] = text[i - 1]; - text[cursor++] = platform.input[i].c; - ++text_len; + case '\n': + case '\r': + case '\t': + platform.input[i].c = platform.input[i].key; + // fallthrough + + default: + if (platform.input[i].c) { + 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 / sizeof *text)) { + for (i64 i = text_len; i > cursor; --i) + text[i] = text[i - 1]; + text[cursor++] = platform.input[i].c; + ++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); @@ -690,14 +755,10 @@ i32 main(i32 argc, c8 **argv) { // ---------------------------------------------------------------- // // 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 // @@ -706,23 +767,23 @@ i32 main(i32 argc, c8 **argv) { #include #include -c32 read_utf8(i64 len, c8 *s) { - if (len <= 1 && +c32 utf8_read(i64 len, c8 *s) { + if (len >= 1 && (s[0] & 0x80) == 0) return s[0]; - if (len <= 2 && + if (len >= 2 && (s[0] & 0xe0) == 0xc0 && (s[1] & 0xc0) == 0x80) return (s[1] & 0x3f) | ((s[0] & 0x1f) << 6); - if (len <= 3 && + 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 && + if (len >= 4 && (s[0] & 0xf8) == 0xf0 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80 && @@ -734,7 +795,15 @@ c32 read_utf8(i64 len, c8 *s) { return 0; } -i32 write_utf8(c32 c, c8 *buffer) { +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; @@ -799,19 +868,25 @@ i64 p_send(u16 slot, IP_Address address, i64 size, u8 *data) { #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 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 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); @@ -940,10 +1015,9 @@ void p_init(void) { _ic = XCreateIC(_im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, _window, NULL); assert(_ic != NULL); - XSetICFocus(_ic); - - platform.pixels = _buffer; - platform.input = _input; + platform.pixels = _buffer; + platform.input = _input; + platform.clipboard = _clipboard_buffer; _image = (XImage) { .width = platform.frame_width, @@ -963,11 +1037,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); @@ -991,6 +1068,8 @@ i32 p_handle_events(void) { XEvent ev; + b8 requested_clipboard = 0; + while (XEventsQueued(_display, QueuedAlready) > 0) { ++num_events; @@ -1029,6 +1108,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: @@ -1056,14 +1139,36 @@ i32 p_handle_events(void) { 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) { .key = k, .c = 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 = 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) { .key = k, .c = read_utf8(len, buf), }; + 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: { @@ -1080,11 +1185,108 @@ i32 p_handle_events(void) { 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; @@ -1137,6 +1339,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 // ================================================================ -- cgit v1.2.3