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;
}
//-----------------------------------------------------------------------------