From 6d1e18585a31bbc69529209a2605f49d0ac54b00 Mon Sep 17 00:00:00 2001 From: "Michael D. Lowis" Date: Mon, 4 Sep 2017 16:00:34 -0400 Subject: [PATCH] fleshed out event handling primitives a bit more. still dont get how to create the xevent type from C but working on it. --- Makefile | 3 +- edit.ml | 19 +++- lib/utf8.c | 81 +++++++++++++++++ lib/x11.ml | 30 ++----- lib/x11_prims.c | 227 ++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 328 insertions(+), 32 deletions(-) create mode 100644 lib/utf8.c diff --git a/Makefile b/Makefile index dabf4c1..6f84b66 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,8 @@ BINS = edit LIBOBJS = \ lib/tide.$(OBJEXT) \ lib/x11.$(OBJEXT) \ - lib/x11_prims.o + lib/x11_prims.o \ + lib/utf8.o .PHONY: all clean diff --git a/edit.ml b/edit.ml index c26e8e4..2c40b47 100644 --- a/edit.ml +++ b/edit.ml @@ -1,8 +1,23 @@ +open X11 let () = X11.make_window 640 480; X11.show_window true; - X11.event_loop 50 (fun x -> print_endline "event") - + X11.event_loop 50 (fun x -> + match x with + | Focus _ -> print_endline "focus" + | KeyPress _ -> print_endline "keypress" + | MouseClick _ -> print_endline "mouseclick" + | MouseRelease _ -> print_endline "mouserelease" + | MouseDrag _ -> print_endline "mousedrag" + | Paste _ -> print_endline "paste" + | Resize _ -> print_endline "resize" + | Command _ -> print_endline "command" + | PipeClosed _ -> print_endline "pipeclosed" + | PipeWriteReady _ -> print_endline "pipewriteready" + | PipeReadReady _ -> print_endline "pipereadready" + | Update -> print_endline "update" + | Shutdown -> print_endline "shutdown" + ) (* let server = Tide.start_server () in let nargs = Array.length Sys.argv in diff --git a/lib/utf8.c b/lib/utf8.c new file mode 100644 index 0000000..b504be3 --- /dev/null +++ b/lib/utf8.c @@ -0,0 +1,81 @@ +#include +#include +#include + +enum { + UTF_MAX = 6u, /* maximum number of bytes that make up a rune */ + RUNE_SELF = 0x80, /* byte values larger than this are *not* ascii */ + RUNE_ERR = 0xFFFD, /* rune value representing an error */ + RUNE_MAX = 0x10FFFF, /* Maximum decodable rune value */ + RUNE_EOF = -1, /* rune value representing end of file */ + RUNE_CRLF = -2, /* rune value representing a \r\n sequence */ +}; + +const uint8_t UTF8_SeqBits[] = { 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE }; +const uint8_t UTF8_SeqMask[] = { 0x00, 0xFF, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00 }; +const uint8_t UTF8_SeqLens[] = { 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00 }; + +static bool runevalid(int32_t val) { + return (val <= RUNE_MAX) + && ((val & 0xFFFE) != 0xFFFE) + && ((val < 0xD800) || (val > 0xDFFF)) + && ((val < 0xFDD0) || (val > 0xFDEF)); +} + +static size_t runelen(int32_t rune) { + if(!runevalid(rune)) + return 0; + else if(rune <= 0x7F) + return 1; + else if(rune <= 0x07FF) + return 2; + else if(rune <= 0xFFFF) + return 3; + else + return 4; +} + +static uint8_t utfseq(uint8_t byte) { + for (int i = 1; i < 8; i++) + if ((byte & UTF8_SeqBits[i]) == UTF8_SeqBits[i-1]) + return UTF8_SeqLens[i-1]; + return 0; +} + +size_t utf8encode(char str[UTF_MAX], int32_t rune) { + size_t len = runelen(rune); + str[0] = (len == 1 ? 0x00 : UTF8_SeqBits[len]) + | (UTF8_SeqMask[len] & (rune >> (6 * (len-1)))); + for (size_t i = 1; i < len; i++) + str[i] = 0x80u | (0x3Fu & (rune >> (6 * (len-i-1)))); + return len; +} + +bool utf8decode(int32_t* rune, size_t* length, int byte) { + /* Handle the start of a new rune */ + if (*length == 0) { + /* If we were fed in an EOF as a start byte, handle it here */ + if (byte == RUNE_EOF) { + *rune = RUNE_EOF; + } else { + /* Otherwise, decode the first byte of the rune */ + *length = utfseq(byte); + *rune = (*length == 0) ? RUNE_ERR : (byte & UTF8_SeqMask[*length]); + (*length)--; + } + /* Handle continuation bytes */ + } else if ((byte & 0xC0) == 0x80) { + /* add bits from continuation byte to rune value + * cannot overflow: 6 byte sequences contain 31 bits */ + *rune = (*rune << 6) | (byte & 0x3F); /* 10xxxxxx */ + (*length)--; + /* Sanity check the final rune value before finishing */ + if ((*length == 0) && !runevalid(*rune)) + *rune = RUNE_ERR; + /* Didn't get the continuation byte we expected */ + } else { + *rune = RUNE_ERR; + } + /* Tell the caller whether we finished or not */ + return ((*length == 0) || (*rune == RUNE_ERR)); +} diff --git a/lib/x11.ml b/lib/x11.ml index 6ca2a4e..50290e2 100644 --- a/lib/x11.ml +++ b/lib/x11.ml @@ -3,25 +3,17 @@ type xwin type xevent = | Focus of { focused: bool } | KeyPress of { mods: int; rune: int } - | MouseBtn of { - mods: int; - btn: int; - x: int; - y: int; - pressed: bool; - dragged: bool - } + | MouseClick of { mods: int; btn: int; x: int; y: int } + | MouseRelease of { mods: int; btn: int; x: int; y: int } + | MouseDrag of { mods: int; x: int; y: int } | Paste of { text: string } - | Command of { commands: string array } | Resize of { height: int; width: int } + | Command of { commands: string array } + | PipeClosed of { fd: int } + | PipeWriteReady of { fd: int } + | PipeReadReady of { fd: int } + | Update | Shutdown - | QueueEmpty - | Filtered -(* - | PipeClosed - | PipeWriteReady - | PipeReadReady -*) external connect : unit -> unit = "x11_connect" @@ -44,12 +36,6 @@ external show_window : bool -> unit external event_loop : int -> (xevent -> unit) -> unit = "x11_event_loop" -external num_events : unit -> int - = "x11_num_events" - -external next_event : unit -> xevent - = "x11_next_event" - external errno : unit -> int = "x11_errno" diff --git a/lib/x11_prims.c b/lib/x11_prims.c index 7918004..4a0c584 100644 --- a/lib/x11_prims.c +++ b/lib/x11_prims.c @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -9,9 +9,51 @@ #include #include +enum Keys { + KEY_F1 = (0xE000+0), + KEY_F2 = (0xE000+1), + KEY_F3 = (0xE000+2), + KEY_F4 = (0xE000+3), + KEY_F5 = (0xE000+4), + KEY_F6 = (0xE000+5), + KEY_F7 = (0xE000+6), + KEY_F8 = (0xE000+7), + KEY_F9 = (0xE000+8), + KEY_F10 = (0xE000+9), + KEY_F11 = (0xE000+10), + KEY_F12 = (0xE000+11), + KEY_INSERT = (0xE000+12), + KEY_DELETE = (0xE000+13), + KEY_HOME = (0xE000+14), + KEY_END = (0xE000+15), + KEY_PGUP = (0xE000+16), + KEY_PGDN = (0xE000+17), + KEY_UP = (0xE000+18), + KEY_DOWN = (0xE000+19), + KEY_RIGHT = (0xE000+20), + KEY_LEFT = (0xE000+21), + KEY_ESCAPE = 0x1B, + RUNE_ERR = 0xFFFD +}; + +extern size_t utf8encode(char str[6], int32_t rune); +extern bool utf8decode(int32_t* rune, size_t* length, int byte); + static int error_handler(Display* disp, XErrorEvent* ev); static char* readprop(Window win, Atom prop); static void create_window(int height, int width); +static value mkrecord(char* tag, int n, ...); +static int32_t special_keys(int32_t key); + +static value ev_focus(XEvent*); +static value ev_keypress(XEvent*); +static value ev_mouse(XEvent*); +static value ev_selclear(XEvent*); +static value ev_selnotify(XEvent*); +static value ev_selrequest(XEvent*); +static value ev_propnotify(XEvent*); +static value ev_clientmsg(XEvent*); +static value ev_configure(XEvent*); static struct { bool running; @@ -33,6 +75,21 @@ static struct { GC gc; } X = {0}; +static value (*EventHandlers[LASTEvent]) (XEvent*) = { + [FocusIn] = ev_focus, + [FocusOut] = ev_focus, + [KeyPress] = ev_keypress, + [ButtonPress] = ev_mouse, + [ButtonRelease] = ev_mouse, + [MotionNotify] = ev_mouse, + [SelectionClear] = ev_selclear, + [SelectionNotify] = ev_selnotify, + [SelectionRequest] = ev_selrequest, + [PropertyNotify] = ev_propnotify, + [ClientMessage] = ev_clientmsg, + [ConfigureNotify] = ev_configure +}; + CAMLprim value x11_connect(void) { CAMLparam0(); if (!X.display) { @@ -106,14 +163,17 @@ CAMLprim value x11_event_loop(value ms, value cbfn) { /* now take the events, convert them, and call the callback */ for (XEvent e; XPending(X.display);) { XNextEvent(X.display, &e); - if (!XFilterEvent(&e, None)) { - // Convert the event. - caml_callback(cbfn, Val_unit); - } + if (XFilterEvent(&e, None)) continue; + if (!EventHandlers[e.type]) continue; + value event = EventHandlers[e.type](&e); + if (event != Val_unit) + caml_callback(cbfn, event); + else + puts("ignored"); } if (X.running) { - caml_callback(cbfn, Val_unit /* redraw event */); + caml_callback(cbfn, mkrecord("Update", 0)); XCopyArea(X.display, X.pixmap, X.self, X.gc, 0, 0, X.width, X.height, 0, 0); } } @@ -186,7 +246,7 @@ static void create_window(int height, int width) { swa.backing_store = WhenMapped; swa.bit_gravity = NorthWestGravity; XChangeWindowAttributes(X.display, X.self, CWBackingStore|CWBitGravity, &swa); - //XStoreName(X.display, X.self, "tide"); + XStoreName(X.display, X.self, "tide"); XSelectInput(X.display, X.self, StructureNotifyMask | ButtonPressMask @@ -212,3 +272,156 @@ static void create_window(int height, int width) { gcv.graphics_exposures = False; X.gc = XCreateGC(X.display, X.self, GCForeground|GCGraphicsExposures, &gcv); } + +static value mkrecord(char* tag, int nargs, ...) { + value rec = caml_alloc_tuple(2); + Store_field(rec, 0, hash_variant(tag)); + Store_field(rec, 1, Val_unit); + if (nargs > 0) { + value tuple = caml_alloc_tuple(nargs); + va_list args; + va_start(args, nargs); + for (int i = 0; i < nargs; i++) + Store_field(tuple, i, va_arg(args, value)); + va_end(args); + Store_field(rec, 1, tuple); + } + return rec; +} + +static value ev_focus(XEvent* e) { + bool focused = (e->type = FocusIn); + if (X.xic) + (focused ? XSetICFocus : XUnsetICFocus)(X.xic); + return mkrecord("Focus", 1, Val_true); +} + +static value ev_keypress(XEvent* e) { + int32_t rune = RUNE_ERR; + size_t len = 0; + char buf[8]; + KeySym key; + Status status; + /* Read the key string */ + if (X.xic) + len = Xutf8LookupString(X.xic, &(e->xkey), buf, sizeof(buf), &key, &status); + else + len = XLookupString(&(e->xkey), buf, sizeof(buf), &key, 0); + /* if it's ascii, just return it */ + if (key >= 0x20 && key <= 0x7F) + return mkrecord("KeyPress", 2, e->xkey.state, Val_int(key)); + /* decode it */ + if (len > 0) { + len = 0; + for (int i = 0; i < 8 && !utf8decode(&rune, &len, buf[i]); i++); + } + return mkrecord("KeyPress", 2, e->xkey.state, Val_int(special_keys(key))); +} + +static value ev_mouse(XEvent* e) { + int mods = e->xbutton.state, btn = e->xbutton.button, + x = e->xbutton.x, y = e->xbutton.y; + if (e->type == MotionNotify) + return mkrecord("MouseDrag", 3, Val_int(mods), Val_int(x), Val_int(y)); + else if (e->type == ButtonPress) + return mkrecord("MouseClick", 4, Val_int(mods), Val_int(btn), Val_int(x), Val_int(y)); + else + return mkrecord("MouseRelease", 4, Val_int(mods), Val_int(btn), Val_int(x), Val_int(y)); +} + +static value ev_selclear(XEvent* e) { + return Val_unit; +} + +static value ev_selnotify(XEvent* e) { + value event = Val_unit; + if (e->xselection.property == None) { + char* propdata = readprop(X.self, e->xselection.selection); + event = mkrecord("Paste", 1, caml_copy_string(propdata)); + XFree(propdata); + } + return event; +} + +static value ev_selrequest(XEvent* e) { + return Val_unit; +} + +static value ev_propnotify(XEvent* e) { + return Val_unit; +} + +static value ev_clientmsg(XEvent* e) { + value event = Val_unit; + Atom wmDeleteMessage = XInternAtom(X.display, "WM_DELETE_WINDOW", False); + if (e->xclient.data.l[0] == wmDeleteMessage) + event = mkrecord("Shutdown", 0); + return event; +} + +static value ev_configure(XEvent* e) { + value event = Val_unit; + if (e->xconfigure.width != X.width || e->xconfigure.height != X.height) { + X.width = e->xconfigure.width; + X.height = e->xconfigure.height; + X.pixmap = XCreatePixmap(X.display, X.self, X.width, X.height, X.depth); + X.xft = XftDrawCreate(X.display, X.pixmap, X.visual, X.colormap); + event = mkrecord("Resize", 2, X.height, X.width); + } + return event; +} + +static int32_t special_keys(int32_t key) { + switch (key) { + case XK_F1: return KEY_F1; + case XK_F2: return KEY_F2; + case XK_F3: return KEY_F3; + case XK_F4: return KEY_F4; + case XK_F5: return KEY_F5; + case XK_F6: return KEY_F6; + case XK_F7: return KEY_F7; + case XK_F8: return KEY_F8; + case XK_F9: return KEY_F9; + case XK_F10: return KEY_F10; + case XK_F11: return KEY_F11; + case XK_F12: return KEY_F12; + case XK_Insert: return KEY_INSERT; + case XK_Delete: return KEY_DELETE; + case XK_Home: return KEY_HOME; + case XK_End: return KEY_END; + case XK_Page_Up: return KEY_PGUP; + case XK_Page_Down: return KEY_PGDN; + case XK_Up: return KEY_UP; + case XK_Down: return KEY_DOWN; + case XK_Left: return KEY_LEFT; + case XK_Right: return KEY_RIGHT; + case XK_Escape: return KEY_ESCAPE; + case XK_BackSpace: return '\b'; + case XK_Tab: return '\t'; + case XK_Return: return '\r'; + case XK_Linefeed: return '\n'; + + /* modifiers should not trigger key presses */ + case XK_Scroll_Lock: + case XK_Shift_L: + case XK_Shift_R: + case XK_Control_L: + case XK_Control_R: + case XK_Caps_Lock: + case XK_Shift_Lock: + case XK_Meta_L: + case XK_Meta_R: + case XK_Alt_L: + case XK_Alt_R: + case XK_Super_L: + case XK_Super_R: + case XK_Hyper_L: + case XK_Hyper_R: + case XK_Menu: + return RUNE_ERR; + + /* if it ain't special, don't touch it */ + default: + return key; + } +} -- 2.52.0