I wrote a simple program that reads the characters from external device (bar code scanner) from serial port (/dev/ttyS1) and feeds it to the currently active window (using XSendEvent).
Program works fine on faster computers, but on slow ones the situation happens (very often) that characters don’t get received in the same order they were sent. For example, scanner sends 1234567 to serial port, my program sends char events 1234567, but the active program (xterm for example) receives 3127456. I tried calling XSync in various places and adding usleep calls, but it did not help.
Does anyone have an idea how to force the ‘order’ of characters?
Or is there some other way to send a string to the active window (I don’t even mind using an external program if needed)?
Here’s the code, perhaps I’m just doing something wrong:
#include <stdio.h> #include <stdlib.h> #include <termios.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> // serial port stuff #include <sys/stat.h> #include <string.h> #include <fcntl.h> #include <X11/Xlib.h> /* BarCode KeyboardFeeder: BCKF Copyright (c) Milan Babuskov Licence: GNU General Public Licence Compile with: g++ bckf.cpp -lX11 -L /usr/X11R6/lib/ -o bckf Keycodes: /usr/X11R6/include/X11/keysymdef.h */ //----------------------------------------------------------------------------- void SendEvent(XKeyEvent *event, bool press) { if (press) XSendEvent(event->display, event->window, True, KeyPressMask, (XEvent *)event); else XSendEvent(event->display, event->window, True, KeyReleaseMask, (XEvent *)event); XSync(event->display, False); } //----------------------------------------------------------------------------- bool sendChar(int c) { if (c >= 0x08 && c <= 0x1b) // send CR twice { sendChar(0xff0d); c = 0xff0d; } printf('Sending char : 0x%02x\n', c); char disp[] = ':0'; char *dp = getenv('DISPLAY'); if (!dp) dp = disp; else printf('Using env.variable $DISPLAY = %s.\n', dp); Display *dpy = XOpenDisplay(dp); if (dpy == NULL) { printf('ERROR! Couldn't connect to display %s.\n', dp); return false; } else { Window cur_focus; // focused window int revert_to; // focus state XGetInputFocus(dpy, &cur_focus, &revert_to); // get window with focus if (cur_focus == None) { printf('WARNING! No window is focused\n'); return true; } else { XKeyEvent event; event.display = dpy; event.window = cur_focus; event.root = RootWindow(event.display, DefaultScreen(event.display)); event.subwindow = None; event.time = CurrentTime; event.x = 1; event.y = 1; event.x_root = 1; event.y_root = 1; event.same_screen = True; event.type = KeyPress; event.state = 0; event.keycode = XKeysymToKeycode(dpy, c); SendEvent(&event, true); event.type = KeyRelease; SendEvent(&event, false); } XCloseDisplay(dpy); } usleep(20); return true; } //----------------------------------------------------------------------------- // Forward declaration int InitComPort(const char *port); //----------------------------------------------------------------------------- int main(int argc, char **argv) { if (argc != 2) { printf('Usage: bckf serial_port\n'); return 1; } int port = InitComPort(argv[1]); if (port == -1) return 1; while (true) { char buf[30]; ssize_t res = read(port, buf, 30); if (res > 0) { for (ssize_t i=0; i<res; ++i) { int c = buf[i]; printf('Received char: 0x%02x\n', c); if (c >= '0' && c <= '9' || c >= 0x08 && c <= 0x1b) if (!sendChar(c)) // called from console? break; } } } return 0; } //----------------------------------------------------------------------------- int InitComPort(const char *port) { int c, res; struct termios newtio; struct termios oldtio; // Open modem device for reading and writing and not as controlling tty // because we don't want to get killed if linenoise sends CTRL-C. int fdSerial = open(port, O_RDWR | O_NOCTTY ); if (fdSerial < 0) { printf(0, 'Error opening port: %s\n%s', port, strerror(errno)); return -1; } tcgetattr(fdSerial,&oldtio); // save current port settings memset(&newtio, 0, sizeof(newtio)); newtio.c_cflag = B9600 | CS8 | CLOCAL | CREAD; // CREAD : enable receiving characters // CS8 : character size 8 // CLOCAL : Ignore modem control lines newtio.c_iflag = IGNPAR; // IGNPAR : ignore bytes with parity errors newtio.c_oflag = 0; // 0 : raw output (no echo, non-canonical) //newtio.c_cc[VTIME] = timeout; // 10=1sec : inter-character timer (deciseconds) newtio.c_cc[VMIN] = 1; // 50 : blocking read until 50 chars received tcflush(fdSerial, TCIOFLUSH); // clear the line and... tcsetattr(fdSerial,TCSANOW,&newtio); // ...activate new settings for the port return fdSerial; } //-----------------------------------------------------------------------------
the problem in your code is that you open the display every time with XOpenDisplay(…). Every call to XOpenDisplay creates a new protocol context. You should open the display only once, and always use the same display handle when you send the events. Within the context of a single display handle, the events are guaranteed to remain ordered.
Initialize the display once, e.g. in main(…), before you start to call sendChar(…), and always use the same Display pointer. Close the Display only when you are done, like you do with the Com port.