Tcl / tk: Key event queue overflow problem?

advertisements

I have a mysterious problem with Tcl/Tk (version 8.6, Ubuntu 14.04). When I keep a button (e.g. Return) pressed for some time and release it, the program doesn't properly react to further key presses any more: It ignores some key presses and it produces the wrong keycode (usually the one from the key held for longer, even though some other key was pressed). The problem occurs if the event handler takes some time (here simulated using after).

Here is my script testKey.tcl:

proc keyHandler {keySym keyCode keySymNum} {
  puts "keyHandler (t=[clock clicks]): ($keySym) ($keyCode) ($keySymNum)"
  flush stdout
  if {$keySym == "Return"} { after 500 }
}
bind . <KeyPress> "keyHandler %K %k %N"

If I run the script with wish testKey.tcl, move the focus to the window and press and hold the Return key for a few seconds, I keep getting output lines like this

keyHandler (t=1474120548284090): (Return) (36) (65293)

also for some time after releasing the key, which I think is the expected behavior. But when these outputs end, pressing further keys (different from Return) lead to erroneous behavior (key presses ignored, wrong key codes delivered).

To me it looks as if some key event queue overflows.

I'd very much appreciate any help, thanks!

Edit: I tried to reproduce the error with a plain X11 program which I assume does a similar thing to the Tk main loop, but here the effect is not visible:

// modified from https://gist.github.com/javiercantero/7753445
// g++ -o xreadkeys xreadkeys.C -lX11

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
  Display *display;
  Window window;
  XEvent event;
  int s;
  /* open connection with the server */
  display = XOpenDisplay(NULL);
  if (display == NULL) {
    fprintf(stderr, "Cannot open display\n");
    exit(1);
  }
  s = DefaultScreen(display);
  /* create window */
  window = XCreateSimpleWindow(display, RootWindow(display, s),
                   10, 10, 200, 200, 1,
                   BlackPixel(display, s),
                   WhitePixel(display, s));
  /* select kind of events we are interested in */
  XSelectInput(display, window, KeyPressMask | KeyReleaseMask );
  /* map (show) the window */
  XMapWindow(display, window);
  /* event loop */
  long cnt = 0;
  while (1) {
    XNextEvent(display, &event);
    /* keyboard events */
    if (event.type == KeyPress) {
      printf( "KeyPress (%ld): %x\n", cnt, event.xkey.keycode );
      /* exit on ESC key press */
      if ( event.xkey.keycode == 0x09 )
        break;
      /* Return */
      if (event.xkey.keycode == 0x24) {
        printf("Enter\n");
        for (int i = 0; i < 10000; i++)
          for (int j = 0; j < 40000; j++) {}
      }
    }
    else if (event.type == KeyRelease){
      printf( "KeyRelease (%ld): %x\n", cnt, event.xkey.keycode );
    }
    cnt++;
  }
  /* close connection to server */
  XCloseDisplay(display);
  return 0;
}

(You may have to adjust the number of loop iterations for your machine.) Wouldn't that indicate that the problem lies with Tcl/Tk?


Tk will be using identical code in the two cases to handle the events, at least unless there's a weird interaction with an input method in one case and not the other. As far as I know, keyboard events are queued identically between different systems running Ubuntu; it's just usual X11 keyboard handling, with the KeyEvents being passed through the GUI system's event queue. Theoretically, you might be filling a buffer on the server-side in the Thinkpad case whereas the different speed of system with the desktop is more able to keep up. Maybe‚Ķ?

While yes, I suggest writing your code to service the event queue more rapidly (which might not be a trivial thing at all, I know) the failure is ultimately rooted in parts of the system that Tcl/Tk doesn't have responsibility for.