9fans / plan9port

Plan 9 from User Space
https://9fans.github.io/plan9port/
Other
1.59k stars 318 forks source link

mklatinkbd: handle arbitrary unicode in compose sequences #379

Open nelk114 opened 4 years ago

nelk114 commented 4 years ago

The compose-key sequences (cf. keyboard(7)) accepted by mklatinkbd are limited to accepting ascii characters as input; I'd like to be able to use sequences containing other unicode characters in my (highly customised — though even the GB layout (I have a british keyboard) includes unaccepted non-ascii such as £) keyboard layout.

.XCompose makes this quite trivial but unfortunately leaves me with two quite different sets of compose-key bindings, the plan9port ones both less extensive and less to my liking than the ones I've set up for the rest of my system.

Alternatively, if there were some way to have plan9port use the X input method that'd work too.

rsc commented 4 years ago

I thought it already could use the X input method. @jxy am I imagining that?

jxy commented 4 years ago

I know we support macOS input methods (no completion or emoji from the Touch Bar, nor dictation). I've no idea about X input method.

cjacker commented 4 years ago

@rsc @jxy

I worked on XIM support for ACME before and almost finished, but still have some minor issues need to be fix(So no pr now, rough patch attached)

The biggest problem is 'How to get cursor(the text cursor, not mouse cursor) position in devdraw/x11-screen.c and update XIM spot(the im candidate list) to follow the cursor position accordingly."

Such as:

spot.x = cursor_pos_x;
spot.y = cursor_pos_y;
XSetICValues(xic, XNPreeditAttributes, spotlist, NULL);

It seems there is no api to get cursor x/y? Would you mind give me some clue?

Patch:

--- plan9port/src/cmd/devdraw/x11-screen.c
+++ plan9portn/src/cmd/devdraw/x11-screen.c
@@ -10,6 +10,7 @@
 #include <mouse.h>
 #include <cursor.h>
 #include <thread.h>
+#include <locale.h>
 #include "x11-memdraw.h"
 #include "devdraw.h"

@@ -60,6 +61,12 @@
    rpc_flush
 };

+static XIC xic;
+static XIM xim;
+static XPoint spot;
+static XVaNestedList spotlist;
+
+
 static Xwin*
 newxwin(Client *c)
 {
@@ -161,6 +168,8 @@
    /*
     * Connect to X server.
     */
+        setlocale(LC_CTYPE, "");
+        XSetLocaleModifiers("");
    _x.display = XOpenDisplay(NULL);
    if(_x.display == nil){
        disp = getenv("DISPLAY");
@@ -307,6 +316,8 @@
        xlock();
        while(XPending(_x.display)) {
            XNextEvent(_x.display, &event);
+           if (XFilterEvent(&event, None))
+                 continue;
            runxevent(&event);
        }
    }
@@ -319,6 +330,9 @@
 runxevent(XEvent *xev)
 {
    int c;
+   char buf[64]; 
+   Rune rbuf[64];
+   Status status;
    KeySym k;
    static Mouse m;
    XButtonEvent *be;
@@ -364,7 +378,14 @@
    case KeyPress:
        w = findxwin(((XKeyEvent*)xev)->window);
        break;
+   case FocusIn:
+       if (xic)
+           XSetICFocus(xic);
+       w = findxwin(((XFocusChangeEvent*)xev)->window);
+       break;
    case FocusOut:
+       if (xic)
+           XUnsetICFocus(xic);
        w = findxwin(((XFocusChangeEvent*)xev)->window);
        break;
    }
@@ -407,8 +428,16 @@
    case KeyRelease:
    case KeyPress:
        ke = (XKeyEvent*)xev;
-       XLookupString(ke, NULL, 0, &k, NULL);
+       if (xic && xev->type == KeyPress) {
+           bzero(buf, 64);
+           int len = Xutf8LookupString(xic, ke, buf, sizeof buf, &k, &status);
+           if(len > 0)
+               runesnprint(rbuf, 64, "%s", buf);
+       } else 
+           XLookupString(ke, NULL, 0, &k, NULL);
+
        c = ke->state;
+
        switch(k) {
        case XK_Alt_L:
        case XK_Meta_L: /* Shift Alt on PCs */
@@ -458,8 +487,20 @@
            _xmovewindow(w, w->fullscreen ? w->screenrect : w->windowrect);
            return;
        }
+
+       if(xic && xev->type == KeyPress) {
+           int nr = runestrlen(rbuf);
+           if (status == XLookupChars && nr > 0 ) {
+               int i = 0;
+               while(i < nr) {
+                   gfx_keystroke(w->client, rbuf[i++]);
+               }
+           }
+       }
+
        if((c = _xtoplan9kbd(xev)) < 0)
            return;
+
        gfx_keystroke(w->client, c);
        break;

@@ -592,6 +633,17 @@
        &attr       /* attributes (the above aren't?!) */
    );

+        /* input methods */
+        xim = XOpenIM(_x.display, 0, 0, 0);
+        spotlist = XVaCreateNestedList(0, XNSpotLocation, &spot,
+                                              NULL);
+
+        if (xim)
+                xic = XCreateIC(xim, 
+               XNInputStyle, XIMPreeditNothing|XIMStatusNothing, 
+               XNClientWindow, w->drawable, 
+               NULL);
+
    /*
     * Label and other properties required by ICCCCM.
     */
cjacker commented 4 years ago

@rsc @jxy

Try to support im spot update, I still can not find how to get position of the active text cursor, so update the im spot after string drawn.

Any better way to get the pos of active text cursor?

Update:

As discussed below, try to catch a rectangle with specified fixed width, it should be text cursor, and update IM spot when such a rectangle drawn. dirty but works very well.

By the way, after string drawn, the im spot position still need to update, it's always correct but not enough.

Update:

Add highDPI support.

Update

Fix segfault when imserver quit or wrong XMODIFIERS settings.

iff -Nur plan9port/src/cmd/devdraw/devdraw.c plan9port.cursor/src/cmd/devdraw/devdraw.c
--- plan9port/src/cmd/devdraw/devdraw.c 2020-06-23 22:19:56.000000000 +0800
+++ plan9port.cursor/src/cmd/devdraw/devdraw.c  2020-07-01 21:32:56.324832360 +0800
@@ -803,6 +803,14 @@
            if(!dst || !src || !mask)
                goto Enodrawimage;
            drawrectangle(&r, a+13);
+           /* text cursor width is 3 for DefaultDPI, and will be scaled with highDPI settings. 
+            * but a lot of place drawn such a rectangle,
+            * for example, every tag bar had such a text cursor.
+            * So, it's not enough to update im spot here, still need to update it when string drawn
+            */
+           int scale = round((float)client->displaydpi/DefaultDPI);
+           if(Dx(r) == 3 * (scale>1?scale:1))
+               client->impl->rpc_setimposition(client, r.max.x, r.max.y);
            drawpoint(&p, a+29);
            drawpoint(&q, a+37);
            op = drawclientop(client);
@@ -1334,6 +1342,8 @@
            }
            dst->clipr = clipr;
            p.y -= font->ascent;
+           /* update im position after string drawn */
+           client->impl->rpc_setimposition(client, q.x, p.y+Dy(font->image->r));
            dstflush(client, dstid, dst, Rect(p.x, p.y, q.x, p.y+Dy(font->image->r)));
            continue;

diff -Nur plan9port/src/cmd/devdraw/devdraw.h plan9port.cursor/src/cmd/devdraw/devdraw.h
--- plan9port/src/cmd/devdraw/devdraw.h 2020-06-23 22:19:56.000000000 +0800
+++ plan9port.cursor/src/cmd/devdraw/devdraw.h  2020-07-01 21:28:50.440825335 +0800
@@ -54,6 +54,7 @@
    void (*rpc_topwin)(Client*);
    void (*rpc_bouncemouse)(Client*, Mouse);
    void (*rpc_flush)(Client*, Rectangle);
+   void (*rpc_setimposition)(Client*, int, int);
 };

 extern QLock drawlk;
diff -Nur plan9port/src/cmd/devdraw/mac-screen.m plan9port.cursor/src/cmd/devdraw/mac-screen.m
--- plan9port/src/cmd/devdraw/mac-screen.m  2020-06-23 22:19:56.000000000 +0800
+++ plan9port.cursor/src/cmd/devdraw/mac-screen.m   2020-07-01 21:28:50.441825335 +0800
@@ -45,6 +45,7 @@
 static void    rpc_topwin(Client*);
 static void    rpc_bouncemouse(Client*, Mouse);
 static void    rpc_flush(Client*, Rectangle);
+static void    rpc_setimposition(Client*, int, int);

 static ClientImpl macimpl = {
    rpc_resizeimg,
@@ -54,7 +55,8 @@
    rpc_setmouse,
    rpc_topwin,
    rpc_bouncemouse,
-   rpc_flush
+   rpc_flush,
+   rpc_setimposition
 };

 @class DrawView;
@@ -478,6 +480,12 @@
    }
 }

+// Not used in MAC
+static void
+rpc_setimposition(Client *client, int, int)
+{
+}
+
 // rpc_flush flushes changes to view.img's rectangle r
 // to the on-screen window, making them visible.
 // Called from an RPC thread with no client lock held.
diff -Nur plan9port/src/cmd/devdraw/x11-screen.c plan9port.cursor/src/cmd/devdraw/x11-screen.c
--- plan9port/src/cmd/devdraw/x11-screen.c  2020-07-01 21:28:31.275824787 +0800
+++ plan9port.cursor/src/cmd/devdraw/x11-screen.c   2020-07-01 21:28:50.441825335 +0800
@@ -49,6 +49,7 @@
 static void    rpc_topwin(Client*);
 static void    rpc_bouncemouse(Client*, Mouse);
 static void    rpc_flush(Client*, Rectangle);
+static void    rpc_setimposition(Client*, int, int);

 static ClientImpl x11impl = {
    rpc_resizeimg,
@@ -58,7 +59,8 @@
    rpc_setmouse,
    rpc_topwin,
    rpc_bouncemouse,
-   rpc_flush
+   rpc_flush,
+   rpc_setimposition
 };

 static XIC xic;
@@ -1045,6 +1047,16 @@
    xunlock();
 }

+void
+rpc_setimposition(Client *client, int x, int y)
+{
+   if(xic) {
+       spot.x = x;
+       spot.y = y;
+       XSetICValues(xic, XNPreeditAttributes, spotlist, NULL);
+   }
+}
+
 static void
 _xexpose(Xwin *w, XEvent *e)
 {
jxy commented 4 years ago

cursor position is entirely application level, and I don't think there is an existing mechanism for devdraw to know such things. I used an extra variable in the macOS devdraw to track the screen rectangle being updated. Unfortunately plan9 GUI were not designed with input methods in mind.

cjacker commented 4 years ago

cursor position is entirely application level, and I don't think there is an existing mechanism for devdraw to know such things. I used an extra variable in the macOS devdraw to track the screen rectangle being updated. Unfortunately plan9 GUI were not designed with input methods in mind.

@jxy

Thanks, It remind me what I had done in sublime-imfix.c ... Sublime didn't support IM under linux, I used LD_PRELOAD to interpose some gtk functions to insert the IM related codes at runtime, and the cursor (called caret in sublime) width is always 2, then update im position when catching such a rectangle.

  //The caret width is 2; 
  //Maybe sometimes we will make a mistake, but for most of the time, it should be the caret.
  if(rectangle->width == 2 && GTK_IS_IM_CONTEXT(local_context)) {
        gtk_im_context_set_cursor_location(local_context, rectangle);
  }

I tried just now to catch a rectangle with specified width and it's done, actually works very well. But the solution is dirty, I am afraid it will never be able to go to upstream.

Anyway, I will post the patch here for reference. Maybe someone will find a better way in future.

jxy commented 4 years ago

Interesting. Do you know if the width changes in the high DPI mode?

cjacker commented 4 years ago

Interesting. Do you know if the width changes in the high DPI mode?

Update: the highDPI scalefactor is very easy to calculate for devdraw/X11, according to '9 man 3 graphics', It's said "Scalesize scales the fixed pixel count n by display->dpi/DefaultDPI, rounding appropriately".

int scale = round((float)client->displaydpi/DefaultDPI);
scale = scale > 1 ? scale : 1;

I updated the second patch above accordingly.

by the way, the man page need update, it mentioned the DefaultDPI is 100, but it already set to 133 in libdraw.