summaryrefslogtreecommitdiffstatshomepage
path: root/xkbcat.c
blob: bfa7eabb17fe3eeee017decc51f4541b53e1086f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// xkbcat: Logs X11 keypresses, globally.

#include <X11/XKBlib.h>

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>

const char * DEFAULT_DISPLAY    = ":0";
const int    DEFAULT_DELAY      = 10000000;
const bool   DEFAULT_PRINT_UP   = false;
const bool   DEFAULT_PRINT_TIME = false;

typedef char KbBuffer[32];

static inline bool keyState(KbBuffer b, int key) {
     // Fetch the `key`-th bit from the buffer.
    return b[key/8] & (1<<(key%8));
    // This is because XQueryKeymap just writes a bit per key into the buffer.
    // Assuming 8-bit chars here, of course.
}

int printUsage() {
    printf("\
USAGE: xkbcat [-display <display>] [-delay <nanosec>] [-up] [-time]\n\
    display  target X display                   (default %s)\n\
    delay    polling frequency; nanoseconds     (default %d)\n\
    up       also print key-ups                 (default %s)\n\
    time     also print timestamps              (default %s)\n",
        DEFAULT_DISPLAY, DEFAULT_DELAY,
        (DEFAULT_PRINT_UP   ? "yes" : "no"),
        (DEFAULT_PRINT_TIME ? "yes" : "no") );
    exit(0);
}

// Since `XKeysymToString` returns a string of unknown length that shouldn't be
// modified, so it makes more sense to just `printf` it in-place.
void printKeyPress(Display * disp, int code, bool down, bool printKeyUps, long timestamp, bool printTimes) {

    KeySym s = XkbKeycodeToKeysym(disp, code, 0, 0); if (NoSymbol == s) return;
    char * str = XKeysymToString(s);                 if (NULL == str)   return;

    if (printKeyUps) printf("%s ", (down ? "+" : "-"));
    printf("%s", str);
    if (printTimes)  printf(" %ld", timestamp);
    printf("\n");
}

int main(int argc, char * argv[]) {

    const char * hostname    = DEFAULT_DISPLAY;
    int          delay       = DEFAULT_DELAY;
    bool         printKeyUps = DEFAULT_PRINT_UP;
    bool         printTimes  = DEFAULT_PRINT_TIME;

    // Get arguments
    for (int i = 1; i < argc; i++) {
        if      (!strcmp(argv[i], "-help"))     printUsage();
        else if (!strcmp(argv[i], "-display"))  hostname    = argv[++i];
        else if (!strcmp(argv[i], "-delay"))    delay       = atoi(argv[++i]);
        else if (!strcmp(argv[i], "-up"))       printKeyUps = true;
        else if (!strcmp(argv[i], "-time"))     printTimes  = true;
        else { printf("Unexpected argument `%s`\n", argv[i]); printUsage(); }
    }

    // Set up X
    Display * disp = XOpenDisplay(hostname);
    if (NULL == disp) {
        fprintf(stderr, "Cannot open X display: %s\n", hostname);
        exit(1);
    }
    XSynchronize(disp, true);

    // Set up buffers
    KbBuffer keyBuffer1, keyBuffer2;
    KbBuffer * oldKeys = &keyBuffer1,
             * keys    = &keyBuffer2;
    XQueryKeymap(disp, *oldKeys); // Initial fetch

    // Timespec for time to sleep for
    struct timespec sleepTime = { .tv_nsec = delay };

    while (1) { // Forever
        XQueryKeymap(disp, *keys); // Fetch changed keys
        long timestamp = 0;
        if (printTimes) timestamp = (long)time(NULL);

        for (int i = 0; i < sizeof(KbBuffer) * 8; i++) {
            bool stateBefore = keyState(*oldKeys, i),
                 stateNow    = keyState(*keys, i);
            if ( stateNow != stateBefore        // Changed?
                 && (stateNow || printKeyUps) ) // Should print?
                printKeyPress(disp, i, stateNow, printKeyUps, timestamp, printTimes);
        }

        { // Swap buffers
            KbBuffer * temp = oldKeys;
            oldKeys = keys;
            keys = temp;
        }

        nanosleep(&sleepTime, NULL);
    }
}