Nominal Animal |
01-16-2011 10:42 PM |
Here is a much improved version of x11grab. - Internal help text: run without parameters or with -h to see the help.
- Supports decimal timeouts (e.g. 1.25)
- Grabs the pointer, keyboard, and input focus for the duration, or until Return or Enter is pressed
- Hides the mouse pointer too for good measure
- Lists all keypresses using symbolic X11 names, separated by spaces, as a single parameter to the script
For example, typing "4.2 !" might show up as "4 period 2 space Shift_L 1".
- Should be easy to hack and develop further
- Testing mode: run x11grab 10 echo and type something for less than then seconds and press Return or Enter, and you'll see the symbolic names of the keys you pressed, in order
Save the following as x11grab.c:
Code:
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysymdef.h>
#define MAX_KEYPRESSES 1023
#define POLLING_INTERVAL_US 10
/* Timeout flag.
*/
volatile sig_atomic_t timeout = 0;
/* Timeout signal handler.
*/
void signal_timeout(int signum)
{
timeout = 1;
}
/* Set a timeout (in wall time).
*/
int set_timeout(const double seconds)
{
struct itimerval interval;
struct sigaction handler;
if (seconds < 0.0)
return EINVAL;
handler.sa_handler = signal_timeout;
sigemptyset(&handler.sa_mask);
handler.sa_flags = 0;
if (sigaction(SIGALRM, &handler, NULL) == -1)
return errno;
interval.it_interval.tv_sec = 0;
interval.it_interval.tv_usec = 0;
interval.it_value.tv_sec = (int)seconds;
interval.it_value.tv_usec = (int)(1000000.0 * (seconds - (double)interval.it_value.tv_sec));
if (interval.it_value.tv_usec < 0) interval.it_value.tv_usec = 0;
if (interval.it_value.tv_usec > 999999) interval.it_value.tv_usec = 999999;
timeout = !(interval.it_value.tv_sec || interval.it_value.tv_usec);
if (setitimer(ITIMER_REAL, &interval, NULL) == -1)
return errno;
return 0;
}
/* Cancel pending timeout.
*/
int cancel_timeout(void)
{
struct itimerval interval;
interval.it_interval.tv_sec = 0;
interval.it_interval.tv_usec = 0;
interval.it_value.tv_sec = 0;
interval.it_value.tv_usec = 0;
if (setitimer(ITIMER_REAL, &interval, NULL) == -1)
return errno;
signal(SIGALRM, SIG_DFL);
return 0;
}
/* Execute the first argument. If the first string contains a slash,
* use execv(), otherwise use execvp() to search for the file in PATH.
* Returns errno if fails.
*/
int execv_auto(char *const args[])
{
if (!args)
return EINVAL;
if (!args[0])
return EINVAL;
if (!args[0][0])
return EINVAL;
if (strchr(args[0], '/'))
execv(args[0], args);
else
execvp(args[0], args);
return errno;
}
/* Output usage information.
*/
int usage(const char *const argv0, const int status)
{
fprintf(stderr, "x11grab version 0.2 by Nominal Animal\n");
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv0);
fprintf(stderr, " %s seconds executable [ arguments ... ]\n", argv0);
fprintf(stderr, "\n");
fprintf(stderr, "This program is designed to grab X11 keypresses, up to the next return.\n");
fprintf(stderr, "If the specified timeout in seconds elapses before the return keypress,\n");
fprintf(stderr, "nothing is done; the already grabbed input is quietly discarded.\n");
fprintf(stderr, "\n");
fprintf(stderr, "If a return keypress occurs before the timeout, the specified executable\n");
fprintf(stderr, "is executed with the parameters given on the command line, plus\n");
fprintf(stderr, "the captured keypresses as a single parameter, using X11 symbolic names,\n");
fprintf(stderr, "each keypress symbol separated by a space.\n");
fprintf(stderr, "\n");
fprintf(stderr, "There are absolutely no guarantees: use only at your own risk.\n");
fprintf(stderr, "You may modify, copy, and create any derivatives freely.\n");
fprintf(stderr, "\n");
return status;
}
/* Automatically grown string buffer.
*/
char *buffer = NULL;
size_t size = 0;
size_t used = 0;
int oom = 0;
/* Add the X11 symbolic names of the keycodes to the buffer.
*/
void add_keysym(const KeySym symbol)
{
char *s;
size_t n;
if (oom || symbol == NoSymbol)
return;
/* Look up the symbolic string.
*/
s = XKeysymToString(symbol);
if (!s)
return;
n = strlen(s);
if (n < 1)
return;
/* Make sure buffer has enough space.
*/
if (used + n + 2 >= size) {
char *temp;
size = ((used + n) | 1023) + 1025;
temp = realloc(buffer, size);
if (!temp) {
oom = 1;
size = 0;
used = 0;
free(buffer);
return;
}
buffer = temp;
}
/* Add a space as a separator.
*/
if (used)
buffer[used++] = ' ';
/* Copy symbol, add EOS.
*/
memcpy(buffer + used, s, n);
used += n;
buffer[used] = 0;
return;
}
int main(int argc, char **argv)
{
Display *display;
int screen;
Window window, parent, root, oldfocus;
int x, y, rootx, rooty, arg, revert;
unsigned int buttons;
Colormap colormap;
XColor black, white;
unsigned long blackpixel, whitepixel;
Pixmap empty;
Cursor hidden;
XEvent event;
KeySym enterkey, returnkey, key;
Bool more;
double seconds = -1.0;
/* Usage or help requested?
*/
if (argc < 2)
return usage(argv[0], 0);
if (!strcmp("-h", argv[1]) || !strcmp("--help", argv[1]))
return usage(argv[0], 0);
if (argc < 3)
return usage(argv[0], 1);
/* Parse and set the timeout.
*/
{ char *ends = NULL;
if (argv[1])
seconds = strtod(argv[1], &ends);
if (seconds < 0.0 || !ends || *ends) {
fprintf(stderr, "%s: Invalid timeout.\n", argv[1]);
return 1;
}
}
if (set_timeout(seconds)) {
fprintf(stderr, "%s: Invalid timeout.\n", argv[1]);
return 1;
}
/* Open the X11 connection.
*/
display = XOpenDisplay(NULL);
if (!display) {
fprintf(stderr, "No X11 connection.\n");
return 1;
}
screen = DefaultScreen(display);
/* Initialize colormap and colors.
*/
colormap = DefaultColormap(display, screen);
black.pixel = blackpixel = BlackPixel(display, screen);
white.pixel = whitepixel = WhitePixel(display, screen);
XQueryColor(display, colormap, &black);
XQueryColor(display, colormap, &white);
/* Determine which window has the pointer; it'll be out parent.
*/
XQueryPointer(display, DefaultRootWindow(display), &root, &parent, &rootx, &rooty, &x, &y, &buttons);
/* Get the pointer coordinates relative to the parent window.
*/
XQueryPointer(display, parent, &root, &parent, &rootx, &rooty, &x, &y, &buttons);
/* Create an invisible window.
*/
window = XCreateSimpleWindow(display, parent, x, y, 1, 1, 0, blackpixel, blackpixel);
if (!window) {
XCloseDisplay(display);
fprintf(stderr, "Cannot create a window.\n");
return 1;
}
/* Create an invisible mouse cursor.
*/
empty = XCreatePixmap(display, window, 1, 1, 1);
if (!empty) {
XDestroyWindow(display, window);
XCloseDisplay(display);
fprintf(stderr, "Cannot create a one-bit pixmap.\n");
return 1;
}
hidden = XCreatePixmapCursor(display, empty, empty, &black, &white, 0, 0);
if (!hidden) {
XFreePixmap(display, empty);
XDestroyWindow(display, window);
XCloseDisplay(display);
fprintf(stderr, "Cannot create a pixmap cursor.\n");
return 1;
}
XDefineCursor(display, window, hidden);
/* Filter unneeded events.
*/
XSelectInput(display, window, KeyPressMask | ExposureMask | StructureNotifyMask);
/* Make the window "visible".
*/
XMapRaised(display, window);
/* Limit the pointer to the safe window, and grab the keyboard.
*/
XGrabKeyboard(display, window, False, GrabModeAsync, GrabModeAsync, CurrentTime);
XGrabPointer(display, window, False, 0, GrabModeAsync, GrabModeAsync, window, None, CurrentTime);
/* Save old input focus, then grab that too.
*/
XGetInputFocus(display, &oldfocus, &revert);
XSetInputFocus(display, window, RevertToNone, CurrentTime);
/* For maximum compatibility, look up "Return" and "Enter" keys.
*/
returnkey = XStringToKeysym("Return");
enterkey = XStringToKeysym("Enter");
/* Main event loop.
*/
while (!timeout) {
/* Sleep instead of blocking.
*/
if (!XPending(display)) {
usleep(POLLING_INTERVAL_US);
continue;
}
XNextEvent(display, &event);
/* Handle expose events.
*/
if (event.type == Expose) {
do {
more = XCheckTypedEvent(display, Expose, &event);
} while (more && !timeout);
continue;
}
/* Ignore other than keypress events.
*/
if (event.type != KeyPress)
continue;
/* Map the keycode to a keysym, without any modifier keys (index 0).
*/
key = XKeycodeToKeysym(display, ((XKeyPressedEvent *)&event)->keycode, 0);
if (key == NoSymbol)
continue;
/* Return or Enter keypress?
*/
if (key == XK_Return || key == returnkey ||
key == XK_KP_Enter || key == enterkey ||
key == XK_ISO_Enter)
break;
/* Append to the keypress buffer.
*/
add_keysym(key);
}
/* Cancel the timeout.
*/
cancel_timeout();
/* Tear down the graphics stuff.
*/
XSetInputFocus(display, oldfocus, revert, CurrentTime);
XUngrabPointer(display, CurrentTime);
XUngrabKeyboard(display, CurrentTime);
XUndefineCursor(display, window);
XFreePixmap(display, empty);
XDestroyWindow(display, window);
XCloseDisplay(display);
/* Timeout exceeded? OOM (out of memory)?
*/
if (timeout || oom)
return 1;
/* Move all argument strings down two steps,
* add the new parameter, and a final NULL.
*/
for (arg = 2; arg < argc; arg++)
argv[arg - 2] = argv[arg];
argv[argc - 2] = buffer;
argv[argc - 1] = NULL;
/* Execute the command.
*/
execv_auto(argv);
/* Failed.
*/
return 1;
}
Make sure you have the basic X11 development libraries (libx11-dev) installed, then compile and install x11grab:
Code:
gcc -O3 -o x11grab -lX11 x11grab.c
sudo install -m 0755 x11grab /usr/bin
I'd appreciate it if you could test this. Any feedback (especially on any problems) is thoroughly welcome.
If this is useful enough, I might add a bit of a configurability (different terminator keys for example), and push it upstream.
Nominal Animal
|