On Thursday 12 December 2002 2:43 pm, Jean-Marc Lasgouttes wrote:
> >>>>> "Angus" == Angus Leeming <[EMAIL PROTECTED]> writes:
>
> Angus> Yes, I was thinking about this myself last night. I think
> Angus> do_keyboard should be like this:
>
> Yes, this seems fine. Did you try it? If you have a patch handy, I
> would appreciate it. I'll try it here, and send it to Rod.
>
> Angus> Have I got the right view of where we should head with this?
>
> I _think_. But you have a much clearer idea than I have of the events
> flow.
>
> JMarc

Good. I'm glad that the world I describe at least sounds attractive.

I attach 2 patches. Both can be applied by moving them into ${XFORMS_SRC}/lib 
and typing
        patch -p2 < ${PATCH}

* smallpatch.diff is the minimal patch to get compose support working on a 
solaris box (I hope ;-)

* bigpatch.diff does the whole schmooze. 
Internally, xforms is a nasty mess when it comes to handling key press 
events. Sometimes it passes keysym to fl_handle_form (the next step in the 
event loop). Sometimes it passes the keybuf_return string from 
fl_XLookupString, on a char-by-char basis. 

To me that is just plain broken, so I've tried to get it to work using keysym 
only. The result is much cleaner and more transparent code (I believe ;-) As 
a side effect, this patch also silently swallows key events for partially 
composed multi-byte strings and dispatches KeyRelease events to the form 
handler.

As an aside, I also believe that this could be made to work with cghan's 
stuff because every composed char has a unique keysym. (Am I right here?) 
That means fl_XLookupString could be modified to work with far eastern 
languages and everything else will continue to work. Note that this thought 
is pure speculation ;-)

The disadvantage of cleaning up messy code is, of course, that the reworking 
isn't trivial. Nonetheless, I think I've gone and done the whole thing. 

I've tested it pretty thoroughly and have found no difference to the current 
behaviour of xforms internal FL_TEXTBOX and FL_INPUT widgets (the only ones 
accepting keyboard input).

One slight possible change is in the use of the cursor keys to navigate a 
form. I used the return state from the object handler to tell me whether the 
key had been acted upon. I felt that this resulted in much cleaner code than 
the present mess and the behaviour appears to be unchanged. I am not 
absolutely, 100% convinced about this however.

None of the changes I've made affect the behaviour of LyX's workarea widget. 
That's because it doesn't trust the keysym passed to it on a key press event 
and generates its own from the raw XEvent.

Having said that, one further advantage of this clean-up to the xforms source 
is that XWorkArea's event handler CAN be simplified considerably because the 
keysym passed to it is now trustworthy. I have appended the modified code 
snippet below.

Please feel free to try out bigpatch.diff. I've come to the opinion that the 
only real testing place for this stuff is lyxcvs. I think that we should set 
up an xforms tree in the lyx cvs repository and use it in the 1.4 cycle. It 
appears that Steve Lamont is happy to accept patches but has little time to 
devote to xforms. I'm sure he would be happier to accept big patches like 
this if we could tell him they'd been tested hard.

I'm going to cc the lyx-devel list with this. If I can find anyone willing to 
test this stuff out, I'll be very happy indeed.

As ever your opinions are solicited.

Best regards,
Angus

(a snippet of my modified XWorkArea.C):

        case FL_KEYPRESS:
        {
                if (!ev)
                        break;
                XKeyEvent * xke = reinterpret_cast<XKeyEvent *>(ev);

                lyxerr[Debug::KEY] << "Workarea: key event time diff: "
                                   << xke->time - time_key_released
                                   << endl;

                if (xke->time - time_key_released < 25 // Should be tunable?
                    && xke->keycode == last_key_pressed
                    && xke->state == last_state_pressed) {

                        lyxerr[Debug::KEY]
                                << "Workarea: Purging X events." << endl;

                        if (XEventsQueued(fl_get_display(), QueuedAlready) > 0)
                                waitForX(true);
                        // This purge make f.ex. scrolling stop immediately when
                        // releasing the PageDown button. The question is if
                        // this purging of XEvents can cause any harm...
                        // after some testing I can see no problems, but
                        // I'd like other reports too. (Lgb)
                        break;
                }

                unsigned int const ret_state = xke->state;

                debug_key_event(event, key, ret_state);

                XLyXKeySym * xlk = new XLyXKeySym;
                xlk->initFromKeySym(key);

                workAreaKeyPress(LyXKeySymPtr(xlk), x_key_state(ret_state));
        }
        break;

        case FL_KEYRELEASE:
                if (!ev)
                        break;
                debug_key_event(event, key, key, ev->xkey.state);

                time_key_released = ev->xkey.time;
                last_key_pressed = ev->xkey.keycode;
                last_state_pressed = ev->xkey.state;
                break;
diff -p -N -r -U 4 -X excl.tmp xforms-1.0-release/lib/flinternal.h xforms-1.0-release-modified/lib/flinternal.h
--- xforms-1.0-release/lib/flinternal.h	Mon May 20 20:38:39 2002
+++ xforms-1.0-release-modified/lib/flinternal.h	Thu Dec 12 16:53:52 2002
@@ -489,8 +489,9 @@ extern int fl_handle_event_callbacks(XEv
 #define IsDown(k)      (k==XK_Down || k==XK_KP_Down)
 #define IsEnd(k)       (k==XK_End  || k==XK_KP_End)
 #define IsPageDown(k)  (k==XK_Next || k==XK_Page_Down || k==XK_KP_Page_Down)
 #define IsPageUp(k)    (k==XK_Prior || k==XK_Page_Up  || k==XK_KP_Page_Up)
+#define IsReturn(k)    (k==XK_Return || k==XK_KP_Enter)
 #else
 #define IsHome(k)      (k==XK_Home || k== XK_Begin)
 #define IsLeft(k)      (k==XK_Left)
 #define IsRight(k)     (k==XK_Right)
@@ -498,8 +499,9 @@ extern int fl_handle_event_callbacks(XEv
 #define IsUp(k)        (k==XK_Up)
 #define IsEnd(k)       (k==XK_End)
 #define IsPageDown(k)  (k==XK_Next)
 #define IsPageUp(k)    (k==XK_Prior)
+#define IsReturn(k)    (k==XK_Return)
 #endif
 
 #define FL_HALFPAGE_UP         0x10000000
 #define FL_HALFPAGE_DOWN       0x20000000
@@ -512,8 +514,17 @@ extern int fl_handle_event_callbacks(XEv
 #define IsNLinesUp(k)          ((k)==FL_NLINES_UP)
 #define IsNLinesDown(k)        ((k)==FL_NLINES_DOWN)
 #define Is1LineUp(k)          ((k)==FL_1LINE_UP)
 #define Is1LineDown(k)        ((k)==FL_1LINE_DOWN)
+
+#define IsFLCursorKey(k)   (IsHome(k)         || IsLeft(k)       || \
+			    IsRight(k)        || IsUp(k)         || \
+			    IsDown(k)         || IsEnd(k)        || \
+			    IsPageDown(k)     || IsPageUp(k)     || \
+			    IsReturn(k)       || IsHalfPageUp(k) || \
+			    IsHalfPageDown(k) || IsNLinesUp(k)   || \
+			    IsNLinesDown(k)   || Is1LineUp(k)    || \
+			    Is1LineDown(k))
 
 
 extern int fl_convert_shortcut(const char *, long *);
 extern int fl_get_underline_pos(const char *, const char *);
diff -p -N -r -U 4 -X excl.tmp xforms-1.0-release/lib/flresource.c xforms-1.0-release-modified/lib/flresource.c
--- xforms-1.0-release/lib/flresource.c	Sat Jun  1 23:09:37 2002
+++ xforms-1.0-release-modified/lib/flresource.c	Wed Dec 11 12:18:28 2002
@@ -959,9 +959,17 @@ fl_initialize(int *na, char *arg[], cons
             int style =  XIMPreeditNothing|XIMStatusNothing;
             fl_context->xic = XCreateIC(fl_context->xim,
                                  XNInputStyle, style,
                                  0);
-        }
+
+	    /* Clean-up on failure */
+	    if (!fl_context->xic) {
+		M_err("fl_initialize", "Could not create an input context");
+		XCloseIM (fl_context->xim);
+		fl_context->xim;
+	    }
+        } else
+	    M_err("fl_initialize", "Could not create an input method");
     }
 #endif
     fl_default_xswa();
     fl_init_stipples();
diff -p -N -r -U 4 -X excl.tmp xforms-1.0-release/lib/forms.c xforms-1.0-release-modified/lib/forms.c
--- xforms-1.0-release/lib/forms.c	Thu Dec 12 13:03:41 2002
+++ xforms-1.0-release-modified/lib/forms.c	Thu Dec 12 17:00:37 2002
@@ -1217,9 +1217,10 @@ fl_do_shortcut(FL_FORM * form, int key,
     return ret;
 }
 
 static void
-fl_keyboard(FL_FORM * form, int key, FL_Coord x, FL_Coord y, void *xev)
+fl_keyboard(int event,
+	    FL_FORM * form, int key, FL_Coord x, FL_Coord y, void *xev)
 {
     FL_OBJECT *obj, *obj1, *special;
 
     /* always check  shortcut first */
@@ -1227,9 +1228,9 @@ fl_keyboard(FL_FORM * form, int key, FL_
 	return;
 
     /* focus policy is done as follows: Input object has the highiest
        priority. Next is the object that wants special keys which is followed
-       by mouseobj that has the lowest. Focusobj == FL_INPUT OBJ */
+       by mouseobj that has the lowest. */
 
     special = fl_find_first(form, FL_FIND_KEYSPECIAL, 0, 0);
     obj1 = special ? fl_find_object(special->next, FL_FIND_KEYSPECIAL, 0, 0) : 0;
 
@@ -1238,35 +1239,32 @@ fl_keyboard(FL_FORM * form, int key, FL_
 
     if (obj1 && obj1 != special)
 	special = fl_mouseobj;
 
-    if (form->focusobj)
+    /* Focusobj == FL_INPUT OBJ */
+    if (event == FL_KEYPRESS && form->focusobj)
     {
 	FL_OBJECT *focusobj = form->focusobj;
+	int special_key = 0;
 
-	/* handle special keys first */
-	if (key > 255)
-	{
-	    if (IsLeft(key) || IsRight(key) || IsHome(key) || IsEnd(key))
-		fl_handle_object(focusobj, FL_KEYBOARD, x, y, key, xev);
-	    else if ((IsUp(key) || IsDown(key) ||
-		      IsPageUp(key) || IsPageDown(key)) &&
-		     (focusobj->wantkey & FL_KEY_TAB))
-		fl_handle_object(focusobj, FL_KEYBOARD, x, y, key, xev);
-	    else if (special && (special->wantkey & FL_KEY_SPECIAL))
-	    {
-		/* moving the cursor in input field that does not have focus
-		   looks weird */
-		if (special->objclass != FL_INPUT)
-		    fl_handle_object(special, FL_KEYBOARD, x, y, key, xev);
+	/* Handle positioning keys first.
+	   Let the FL_OBJECT tell us whether it has done anything. */
+	if (IsFLCursorKey(key)) {
+	    if (fl_handle_object_direct(focusobj, event, x, y, key, xev)) {
+		fl_object_qenter(focusobj);
+		return;
+	    }
+
+	    if (special && (special->wantkey & FL_KEY_SPECIAL) &&
+		special->objclass != FL_INPUT &&
+		fl_handle_object_direct(special, event, x, y, key, xev)) {
+		fl_object_qenter(special);
+		return;
 	    }
-	    else if (key == XK_BackSpace || key == XK_Delete)
-		fl_handle_object(focusobj, FL_KEYBOARD, x, y, key, xev);
-	    return;
 	}
 
-	/* dispatch tab & return switches focus if not FL_KEY_TAB */
-	if ((key == 9 || key == 13) && !(focusobj->wantkey & FL_KEY_TAB))
+	/* Dispatch tab & return switches focus if not FL_KEY_TAB */
+	if ((IsTab(key) || IsReturn(key)) && !(focusobj->wantkey & FL_KEY_TAB))
 	{
 	    if ((((XKeyEvent *) xev)->state & fl_context->navigate_mask))
 	    {
 		if (focusobj == fl_find_first(form, FL_FIND_INPUT, 0, 0))
@@ -1283,9 +1281,9 @@ fl_keyboard(FL_FORM * form, int key, FL_
 	    fl_handle_object(focusobj, FL_UNFOCUS, x, y, 0, xev);
 	    fl_handle_object(obj, FL_FOCUS, x, y, 0, xev);
 	}
 	else if (focusobj->wantkey != FL_KEY_SPECIAL)
-	    fl_handle_object(focusobj, FL_KEYBOARD, x, y, key, xev);
+	    fl_handle_object(focusobj, event, x, y, key, xev);
 	return;
     }
 
     /* keyboard input is not wanted */
@@ -1293,13 +1291,13 @@ fl_keyboard(FL_FORM * form, int key, FL_
 	return;
 
     /* space is an exception for browser */
     if ((key > 255 || key == ' ') && (special->wantkey & FL_KEY_SPECIAL))
-	fl_handle_object(special, FL_KEYBOARD, x, y, key, xev);
+	fl_handle_object(special, event, x, y, key, xev);
     else if (key < 255 && (special->wantkey & FL_KEY_NORMAL))
-	fl_handle_object(special, FL_KEYBOARD, x, y, key, xev);
+	fl_handle_object(special, event, x, y, key, xev);
     else if (special->wantkey == FL_KEY_ALL)
-	fl_handle_object(special, FL_KEYBOARD, x, y, key, xev);
+	fl_handle_object(special, event, x, y, key, xev);
 
 #if FL_DEBUG >= ML_INFO
     M_info("KeyBoard", "(%d %d)pushing %d to %s\n", x, y, key, special->label);
 #endif
@@ -1423,10 +1421,11 @@ fl_handle_form(FL_FORM * form, int event
 	obj = fl_pushobj;
 	fl_pushobj = NULL;
 	fl_handle_object(obj, FL_RELEASE, xx, yy, key, xev);
 	break;
-    case FL_KEYBOARD:		/* A key was pressed */
-	fl_keyboard(form, key, xx, yy, xev);
+    case FL_KEYPRESS:		/* A key was pressed */
+    case FL_KEYRELEASE:		/* A key was released */
+	fl_keyboard(event, form, key, xx, yy, xev);
 	break;
     case FL_STEP:		/* A simple step */
 	obj1 = fl_find_first(form, FL_FIND_AUTOMATIC, 0, 0);
 	if (obj1 != NULL)
@@ -1510,20 +1509,23 @@ hack_test(void)
 /* formevent is either FL_KEYPRESS or FL_KEYRELEASE */
 static void
 do_keyboard(XEvent * xev, int formevent)
 {
-
     Window win;
     int kbuflen;
+    unsigned char keybuf[227];
+    KeySym keysym = 0;
+    static KeySym last_pressed_keysym;
+    unsigned char * ch;
 
-    /* before doing anything, save the current modifier key for the handlers */
+    /* Before doing anything, save the current modifier key for the handlers. */
     win = xev->xkey.window;
     fl_keymask = xev->xkey.state;
 
     if (win && (!keyform || !fl_is_good_form(keyform)))
 	keyform = fl_win_to_form(win);
 
-    /* switch keyboard input only if different top-level form */
+    /* Switch keyboard input only if different top-level form. */
     if (keyform && keyform->window != win)
     {
 	M_warn("KeyEvent", "pointer/keybd focus differ");
 	if ((keyform->child && keyform->child->window == win) ||
@@ -1532,69 +1534,52 @@ do_keyboard(XEvent * xev, int formevent)
 	else
 	    keyform = fl_win_to_form(win);
     }
 
-    if ( keyform ) {
-
-	KeySym keysym = 0;
-	unsigned char keybuf[227];
+    if (!keyform)
+	return;
 
-	kbuflen = fl_XLookupString((XKeyEvent *) xev, (char *) keybuf,
-				   sizeof(keybuf), &keysym);
-	
-	if (kbuflen < 0) {
-	    
-	    if ( kbuflen != INT_MIN ) {
-		
-		/* buffer overflow, should not happen */
-		M_err("DoKeyBoard", "keyboad buffer overflow ?");
-		
-	    } else {
+    /* Note that the behaviour of XmbLookupString is undefined on a KeyRelease
+       event, so we do not attempt to call fl_XLookupString.
+       Rather, we store the keysym of the previous FL_KEYPRESS event
+       and dispatch that. */
+    if (formevent == FL_KEYRELEASE) {
+        fl_handle_form(keyform, FL_KEYRELEASE, last_pressed_keysym, xev);
+	return;
+    }
 
-		M_err("DoKeyBoard", "fl_XLookupString failed ?");
-	    
-	    }
+    kbuflen = fl_XLookupString((XKeyEvent *) xev, (char *) keybuf,
+			       sizeof(keybuf), &keysym);
 
+    if (kbuflen < 0) {
+	if ( kbuflen != INT_MIN ) {
+	    /* buffer overflow, should not happen */
+	    M_err("DoKeyBoard", "keyboad buffer overflow ?");
 	} else {
-	    
-	    /* ignore modifier keys as they don't cause action and are
-	       taken care of by the lookupstring routine */
-	    
-	    if (IsModifierKey(keysym))
-		;
-	    else if (IsTab(keysym)) {
-		
-		/* fake a tab key. */
-		/* some system shift+tab does not generate tab */
-		
-		fl_handle_form(keyform, formevent, 9, xev);
-
-	    }
-#if (FL_DEBUG >= ML_DEBUG )
-	    else if (keysym == XK_F10)	/* && controlkey_down(fl_keymask)) */
-		hack_test();
-#endif
-	    
-	    /* pass all keys to the handler */
-	    else if (IsCursorKey(keysym) || kbuflen == 0)
-		fl_handle_form(keyform, formevent, keysym, xev);
-	    else {
-
-		unsigned char *ch;
-
-		/* all regular keys, including mapped strings */
+	    M_err("DoKeyBoard", "fl_XLookupString failed ?");
+	}
+	return;
+    }
 
-		for (ch = keybuf; ch < (keybuf + kbuflen) && keyform; ch++)
-		    fl_handle_form(keyform, formevent, *ch, xev);
+    /* keysym == NoSymbol during multi-byte char composition.
+       Eg, I've typed Multi_key-a but not yet ' to give á
+       Silently swallow these partial-compositions. */
+    if (keysym == NoSymbol)
+	return;
 
-	    }
-	    
-	}
+    /* Ignore modifier keys as they don't cause action and are
+       taken care of by the LookupString routine. */
+    if (IsModifierKey(keysym))
+	return;
 
-    }
+    /* Dispatch the key event */
+    fl_handle_form(keyform, formevent, keysym, xev);
 
+    /* Record the keysym for the next KEYRELEASE event. */
+    last_pressed_keysym = keysym;
 }
 
+
 FL_FORM_ATCLOSE
 fl_set_form_atclose(FL_FORM * form, FL_FORM_ATCLOSE fmclose, void *data)
 {
     FL_FORM_ATCLOSE old = form->close_callback;
@@ -2785,9 +2770,14 @@ static int
 fl_XLookupString(XKeyEvent * xkey, char *buf, int buflen, KeySym * ks)
 {
     int len = INT_MIN;
 
-    if (!fl_context->xic)
+    /* Note that the behaviour of XmbLookupString is undefined on a KeyRelease
+       event. Safest therefore to bail out in such a situation. */
+    if (xkey->type != KeyPress)
+	return 0;
+
+    if (!fl_context->xic)
     {
 	len = XLookupString(xkey, buf, buflen, ks, 0);
     }
     else
@@ -2799,10 +2789,9 @@ fl_XLookupString(XKeyEvent * xkey, char 
 	    *ks = NoSymbol;
 	    return 0;
 	}
 
-	len =
-	    XmbLookupString(fl_context->xic, xkey, buf, buflen, ks, &status);
+	len = XmbLookupString(fl_context->xic, xkey, buf, buflen, ks, &status);
 
 	switch (status)
 	{
 	case XBufferOverflow:
diff -p -N -r -U 4 -X excl.tmp xforms-1.0-release/lib/input.c xforms-1.0-release-modified/lib/input.c
--- xforms-1.0-release/lib/input.c	Mon Jun  3 18:57:29 2002
+++ xforms-1.0-release-modified/lib/input.c	Thu Dec 12 16:47:20 2002
@@ -704,9 +704,9 @@ handle_edit(FL_OBJECT * ob, int key, int
     SPEC *sp = ob->spec;
     int ret = 1, i, j;
     int prev = 1;
 
-    if (key == kmap.del_prev_char || key == kmap.backspace)
+    if (key == XK_BackSpace)
     {
 	prev = -1;
 	if (sp->endrange >= 0)
 	    delete_piece(ob, sp->beginrange, sp->endrange - 1);
@@ -716,9 +716,9 @@ handle_edit(FL_OBJECT * ob, int key, int
 	}
 	else
 	    ret = 0;
     }
-    else if (key == kmap.del_next_char)
+    else if (key == XK_Delete)
     {
 	if (sp->endrange >= 0)
 	    delete_piece(ob, sp->beginrange, sp->endrange - 1);
 	else if (sp->position < slen)
@@ -827,25 +827,31 @@ handle_key(FL_OBJECT * ob, int key, unsi
     int oldl = sp->lines;
     int oldx = sp->xoffset;
     int oldmax = sp->max_pixels;
 
+    if ((ob->type != FL_MULTILINE_INPUT) &&
+	(IsUp(key) || IsDown(key) || IsPageUp(key) || IsPageDown(key))) {
+	/* Don't want to handle this key. */
+	return 0;
+    }
+
     /* Extend field size if required */
     slen = strlen(sp->str);
     if (sp->size == slen + 1)
     {
 	sp->size += 64;
 	sp->str = (char *) fl_realloc(sp->str, sp->size);
     }
 
-    if (ob->type == FL_MULTILINE_INPUT && key == 13)
+    if (ob->type == FL_MULTILINE_INPUT && IsReturn(key))
 	key = NL;
 
     /* Compute starting position of current line */
     startpos = sp->position;
     while (startpos > 0 && sp->str[startpos - 1] != NL)
 	startpos--;
 
-    if (controlkey_down(kmask) && key > 255)
+    if (controlkey_down(kmask))
 	key |= FL_CONTROL_MASK;
 
     if (metakey_down(kmask))
 	key |= METAMASK;
@@ -877,8 +883,14 @@ handle_key(FL_OBJECT * ob, int key, unsi
 	key = XK_PageDn;
     else if (key == kmap.moveto_prev_page)
 	key = XK_PageUp;
 
+    /* ditto for the delete keys */
+    if (key == kmap.del_prev_char || key == kmap.backspace)
+	key == XK_BackSpace;
+    else if (key == kmap.del_next_char)
+	key == XK_Delete;
+
     if (IsRegular(key))		/* Normal keys  or NL */
     {
 	int ok = FL_VALID;
 	char *tmpbuf = 0;
@@ -1712,9 +1724,9 @@ fl_get_input_cursorpos(FL_OBJECT * ob, i
     return sp->position;
 }
 
 
-#define Control(c)       ((c) - 'a' + 1)
+#define Control(c)       ((c) | FL_CONTROL_MASK)
 #define Meta(c)          ((c) | METAMASK)
 
 #define Dump(a)     fprintf(stderr,"\t%s: %ld(0x%lx)\n",#a,kmap.a,kmap.a)
 
diff -p -N -r -U 4 -X excl.tmp xforms-1.0-release/lib/forms.c xforms-1.0-release-modified/lib/forms.c
--- xforms-1.0-release/lib/forms.c	Thu Dec 12 13:03:41 2002
+++ xforms-1.0-release-modified/lib/forms.c	Thu Dec 12 18:41:37 2002
@@ -1537,8 +1537,16 @@ do_keyboard(XEvent * xev, int formevent)
 
 	KeySym keysym = 0;
 	unsigned char keybuf[227];
 
+	/* Note that the behaviour of XmbLookupString is undefined on
+	   a KeyRelease event, so we do not attempt to call fl_XLookupString.
+	   Rather, we just dispatch the event with keysym == 0. */
+	if (formevent == FL_KEYRELEASE) {
+	    fl_handle_form(keyform, FL_KEYRELEASE, 0, xev);
+	    return;
+	}
+	
 	kbuflen = fl_XLookupString((XKeyEvent *) xev, (char *) keybuf,
 				   sizeof(keybuf), &keysym);
 	
 	if (kbuflen < 0) {
@@ -2784,8 +2792,13 @@ fl_restore_target(void)
 static int
 fl_XLookupString(XKeyEvent * xkey, char *buf, int buflen, KeySym * ks)
 {
     int len = INT_MIN;
+
+    /* Note that the behaviour of XmbLookupString is undefined on a KeyRelease
+       event. Safest therefore to bail out in such a situation. */
+    if (xkey->type != KeyPress)
+	return 0;
 
     if (!fl_context->xic)
     {
 	len = XLookupString(xkey, buf, buflen, ks, 0);

Reply via email to