LinuxQuestions.org
Latest LQ Deal: Latest LQ Deals
Go Back   LinuxQuestions.org > Forums > Linux Forums > Linux - Software
User Name
Password
Linux - Software This forum is for Software issues.
Having a problem installing a new program? Want to know which application is best for the job? Post your question in this forum.

Notices


Reply
  Search this Thread
Old 01-16-2011, 07:12 AM   #1
Cohesus
LQ Newbie
 
Registered: Jan 2011
Posts: 4

Rep: Reputation: 0
Mapping resequence to an action


I have a wireless numpad that was designed for windows,
so some of the keys rather than sending a keycode send a sequence of keys to do a task.
for example the excel button sends(from xev)
win+R
excel
return

is there a program that can look for this sequence then perform a different action?
i know xmodmap can map individual keys but couldn't get it doing multiple.

Any ideas?
 
Old 01-16-2011, 09:55 AM   #2
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948
Set up a keyboard shortkut from the initial keypress (Super + R) to the run app dialog of your desktop environment. For example, xfrun4 if using XFCE 4. Then, put your desired action into a script named excel somewhere in your PATH.

Yes, I know. That's ugly. But so is a keyboard which types complete commands, instead of keyboard events. And this is the way the keyboard works in Windows.

If that is too ugly for you, there is a cleaner alternative. Unfortunately I cannot find a suitable tool ready for this, so it'd require a bit of X11 programming. You need a simple application that captures X11 input for a short while using an invisible window, or until return/enter is pressed, then closes the window and executes a script with the input as a parameter. I'd say this needs about 200 lines of C or C++, using plain X11 libraries.
Nominal Animal

Last edited by Nominal Animal; 03-21-2011 at 02:52 AM.
 
Old 01-16-2011, 11:57 AM   #3
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948
I wrote an example minimalistic X11 input grabber you could use for this.
Unlike the run application dialog, this is totally invisible.

It is rather limited, but I think it should work perfectly for your use case.

Save the following code as x11grab.c:
Code:
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <X11/Xlib.h>

/* Microseconds to wait while polling for input. A few milliseconds is okay.
*/
const useconds_t microseconds = 10000;

/* Alarm signal flag and handler.
*/
volatile sig_atomic_t timeout = 0;

void catch_alarm(int signum, siginfo_t *siginfo, void *sigdata)
{
    timeout = 1;
}

/* Helper: Return the character if the symbol is printable.
*/
int isprintable(const char *const symbol)
{
    if (!symbol || !symbol[0])
        return 0;

    if (symbol[1])
        return 0;

    if (symbol[0] >= '0' && symbol[0] <= '9')
        return symbol[0];
    if (symbol[0] >= 'a' && symbol[0] <= 'z')
        return symbol[0];
    if (symbol[0] >= 'A' && symbol[0] <= 'Z')
        return symbol[0] - 'A' + 'a';

    return 0;
}

/* Helper: Return nonzero if the argument is Enter or Return.
*/
int isenter(const char *const string)
{
    if (!string || !string[0])
        return 0;

    if (string[0] == 'E' || string[0] == 'e')
    if (string[1] == 'N' || string[1] == 'n')
    if (string[2] == 'T' || string[2] == 't')
    if (string[3] == 'E' || string[3] == 'e')
    if (string[4] == 'R' || string[4] == 'r')
    if (!string[5])
        return 1;

    if (string[0] == 'R' || string[0] == 'r')
    if (string[1] == 'E' || string[1] == 'e')
    if (string[2] == 'T' || string[2] == 't')
    if (string[3] == 'U' || string[3] == 'u')
    if (string[4] == 'R' || string[4] == 'r')
    if (string[5] == 'N' || string[5] == 'n')
    if (!string[6])
        return 1;

    return 0;
}

/* Helper: Return nonzero if the argument contains a slash.
*/
int haspath(const char *string)
{
    if (!string)
        return 0;

    while (*string)
        if (*(string++) == '/')
            return 1;

    return 0;
}

int main(int argc, char **argv)
{
    Display *display;
    XEvent   event;
    int      done = 0;
    int      seconds = 0;

    char     data[4096];
    size_t    used = 0;

    /* Need at least two arguments: Timeout, and the script to run.
    */
    if (argc < 3)
        return 1;

    /* Parse the timeout.
    */
    {   const char *s = argv[1];

        while (*s >= '0' && *s <= '9')
            seconds = (10 * seconds) + (*(s++) - '0');

        if (seconds < 1 || *s)
            return 1;
    }

    /* Connect to the X11 display.
    */
    display = XOpenDisplay(NULL);
    if (!display)
        return 1;

    /* Set up the timeout.
    */
    {   struct sigaction action;

        action.sa_sigaction = catch_alarm;
        sigemptyset(&action.sa_mask);
        action.sa_flags = SA_SIGINFO;

        if (sigaction(SIGALRM, &action, NULL))
            return 1;

        alarm(seconds);
    }

    /* Warp the pointer to somewhere safe?
    */

    /* Grab the keyboard.
    */
    XGrabKeyboard(display, DefaultRootWindow(display),
                  True, GrabModeAsync, GrabModeAsync, CurrentTime);

    /* Event loop.
    */
    while (!timeout && !done) {
        if (XPending(display) == 0) {
            usleep(microseconds);
            continue;
        }
        XNextEvent(display, &event);

        if (event.type == Expose) {
            Bool still;
            do {
                still = XCheckTypedEvent(display, Expose, &event);
            } while (still);

        } else
        if (event.type == KeyPress) {
            const int keycode = ((XKeyPressedEvent *)&event)->keycode;
            const char *symbol = XKeysymToString(XKeycodeToKeysym(display, keycode, 0));

            if (symbol) {
                if (isenter(symbol)) {
                    done = 1;
                } else {
                    const int printable = isprintable(symbol);
                    if (printable) {
                        if (used < sizeof(data) - 1)
                            data[used] = printable;
                            used++;
                    }
                }
            }
        }
    }

    /* Ungrab the keyboard.
    */
    XUngrabKeyboard(display, CurrentTime);

    /* Close the display.
    */
    if (XCloseDisplay(display))
        return 1;

    /* No return typed?
    */
    if (!done)
        return 1;

    /* Command too long?
    */
    if (used >= sizeof(data))
        return 1;

    /* Add EOS to the command.
    */
    data[used] = 0;

    /* Disarm the alarm.
    */
    alarm(0);

    /* Shift all arguments two places.
    */
    {   int i;

        for (i = 2; i < argc; i++)
            argv[i-2] = argv[i];

        argv[argc - 2] = data;
        argv[argc - 1] = NULL;
    }

    /* Make sure the command to run is not empty.
    */
    if (!argv[0] || !argv[0][0])
        return 1;

    /* Run the specified script.
    */
    if (haspath(argv[0]))
        execv(argv[0], argv);
    else
        execvp(argv[0], argv);

    /* Failed.
    */
    return 1;
}
Make sure you have GCC and basic X11 development libraries installed (from the libx11-dev package).

Compile and install x11grab by running
Code:
gcc -O3 -lX11 -o x11grab x11grab.c
sudo install -m 0755 x11grab /usr/bin/
The usage of this program is simple: You give it a timeout in (integer) seconds, and the path to a script to run. If the user types a command within the given period and presses enter, the script is run, with an additional parameter containing the numbers and (lowercase) letters of the typed command. (Shift, Control, Caps Lock and all such are ignored; the letters will always be in lower case.)

In your case, set an application shortcut to x11grab 2 /home/cohesus/scripts/numpad for the Super+R (aka Win+R) keypress. Then, in the /home/cohesus/scripts/numpad script, check if $1 matches excel and do the action you wish:
Code:
#!/bin/bash
[ "$1" == "excel" ] && exec oocalc
The timeout is just a safety feature, in case you press Super+R yourself. You can set the timeout to as low as 1 second. However, note that x11grab will only run the command if it receives the enter or return keypress before the timeout expires. So, if the wireless keypad is slow, x11grab might give up too early, leaving the last letters and the return to whatever application you had active.

I would appreciate very much if you tried this, and let me know if it works for you. If so, I could clean up the code and write a manpage for it.
Nominal Animal

Last edited by Nominal Animal; 03-21-2011 at 02:51 AM.
 
1 members found this post helpful.
Old 01-16-2011, 01:29 PM   #4
Cohesus
LQ Newbie
 
Registered: Jan 2011
Posts: 4

Original Poster
Rep: Reputation: 0
Nominal Animal You are a GOD

Got it working in the command line bring up a dialogue box.
Got it working with win+R nicely as well.

The only issue I have is that if I am in a area that can take an input like a terminal or document.
the shortcut gets triggers x11grab, x11grab exits but with no output and 'excel' is passed onto the input box as typed text
this happens within a second even if x11grab set to 99 seconds.

Thank you so much this is a really useful application and I really appreciate it.

Last edited by Cohesus; 01-16-2011 at 01:35 PM.
 
Old 01-16-2011, 02:25 PM   #5
Cohesus
LQ Newbie
 
Registered: Jan 2011
Posts: 4

Original Poster
Rep: Reputation: 0
It must be something to do with how gnome shortcuts calls the command,
i have this script run on win+r
Code:
#!/bin/bash
echo triggered>>/tmp/numpad.log
x11grab 99 /home/cohesus/Documents/scripts/c/excel2.sh
echo end>>/tmp/numpad.log
excel2 contains
Code:
#!/bin/bash
echo $1 >>/tmp/numpad.log
if I run this in terminal it works every time,
when its called from the shortcut log shows triggered and end and doesn't wait 99 seconds
 
Old 01-16-2011, 10:42 PM   #6
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948
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

Last edited by Nominal Animal; 03-21-2011 at 06:44 AM.
 
Old 01-17-2011, 03:49 AM   #7
Cohesus
LQ Newbie
 
Registered: Jan 2011
Posts: 4

Original Poster
Rep: Reputation: 0
New version works on majority of windows but failed to grab focus on some applications

it worked on, firefox, xterm, desktop majority of others
fails on OpenOffice gnome-terminal and input boxs if there in focus
 
  


Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
Best course of action? your_mom Linux - General 3 02-03-2010 11:18 PM
Seeking app to resequence photos snow_bound Linux - Desktop 1 12-16-2006 03:31 AM
See CL in Action 2 Xterminator Conectiva 0 06-30-2003 10:14 AM
physical scsi channel mapping to scsiX device node mapping, how to configure manually drthornt Linux - Hardware 3 02-09-2003 11:50 AM
See CL 8 in action Xterminator Conectiva 1 09-26-2002 12:06 PM

LinuxQuestions.org > Forums > Linux Forums > Linux - Software

All times are GMT -5. The time now is 07:07 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration