]> git.mdlowis.com Git - archive/tide-ocaml.git/commitdiff
fleshed out event handling primitives a bit more. still dont get how to create the...
authorMichael D. Lowis <mike@mdlowis.com>
Mon, 4 Sep 2017 20:00:34 +0000 (16:00 -0400)
committerMichael D. Lowis <mike@mdlowis.com>
Mon, 4 Sep 2017 20:00:34 +0000 (16:00 -0400)
Makefile
edit.ml
lib/utf8.c [new file with mode: 0644]
lib/x11.ml
lib/x11_prims.c

index dabf4c11dcb7f35e1d70c43cc4e9c78d9219e089..6f84b66c2761e71cd5f57a0c6506c21c8d0b3c8f 100644 (file)
--- 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 c26e8e4c89ca52de6af3224797060143f619749f..2c40b47218b7e089549e2cee83fa54cff6e08f9f 100644 (file)
--- 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 (file)
index 0000000..b504be3
--- /dev/null
@@ -0,0 +1,81 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+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));
+}
index 6ca2a4ecc11634bfb083455540c0264322458fa9..50290e2b70b7ae3f604a31fa41a3ce7a37dee213 100644 (file)
@@ -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"
 
index 7918004b93fb1898690f1ab9dfd8a887033b4d8e..4a0c584a4be92e4370f80be2047a8741e454acd7 100644 (file)
@@ -1,4 +1,4 @@
-#include <curses.h>
+#include <stdbool.h>
 #include <caml/mlvalues.h>
 #include <caml/fail.h>
 #include <caml/memory.h>
@@ -9,9 +9,51 @@
 #include <X11/Xatom.h>
 #include <X11/Xft/Xft.h>
 
+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;
+    }
+}