summaryrefslogtreecommitdiffstatshomepage
path: root/xkbcat.c
blob: 6771003d7727c02c168df4fbd5f07dd95253b229 (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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// xkbcat: Logs X11 keypresses, globally.

#include <X11/XKBlib.h>
#include <X11/extensions/XInput2.h>

#define XK_LATIN1
#include <X11/keysymdef.h>

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

const char *DEFAULT_DISPLAY = ":0";
const bool DEFAULT_PRINT_UP = false;

int printUsage() {
  printf("\
USAGE: xkbcat [-display <display>] [-up]\n\
    display  target X display                   (default %s)\n\
    up       also print key-ups                 (default %s)\n",
         DEFAULT_DISPLAY, (DEFAULT_PRINT_UP ? "yes" : "no"));
  exit(0);
}

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

  const char *xDisplayName = DEFAULT_DISPLAY;
  bool printKeyUps = DEFAULT_PRINT_UP;

  // Get arguments
  for (int i = 1; i < argc; i++) {
    if (!strcmp(argv[i], "-help"))
      printUsage();
    else if (!strcmp(argv[i], "-up"))
      printKeyUps = true;
    else if (!strcmp(argv[i], "-display")) {
      // Read next entry to find value
      ++i;
      if (i >= argc) {
        fprintf(stderr, "No value given to option `-display`\n");
        printUsage();
        exit(5);
      }
      xDisplayName = argv[i];
    } else {
      printf("Unexpected argument `%s`\n", argv[i]);
      printUsage();
    }
  }

  // Connect to X display
  Display *disp = XOpenDisplay(xDisplayName);
  if (NULL == disp) {
    fprintf(stderr, "Cannot open X display '%s'\n", xDisplayName);
    exit(1);
  }

  int xiOpcode;
  { // Test for XInput 2 extension
    int queryEvent, queryError;
    if (!XQueryExtension(disp, "XInputExtension", &xiOpcode, &queryEvent,
                         &queryError)) {
      fprintf(stderr, "X Input extension not available\n");
      exit(2);
    }
  }
  { // Request XInput 2.0, to guard against changes in future versions
    int major = 2, minor = 0;
    int queryResult = XIQueryVersion(disp, &major, &minor);
    if (queryResult == BadRequest) {
      fprintf(stderr, "Need XI 2.0 support (got %d.%d)\n", major, minor);
      exit(3);
    } else if (queryResult != Success) {
      fprintf(stderr, "XIQueryVersion failed!\n");
      exit(4);
    }
  }
  { // Register to receive XInput events
    Window root = DefaultRootWindow(disp);
    XIEventMask m;
    m.deviceid = XIAllMasterDevices;
    m.mask_len = XIMaskLen(XI_LASTEVENT);
    m.mask = calloc(m.mask_len, sizeof(char));
    // Raw key presses correspond to physical key-presses, without
    // processing steps such as auto-repeat.
    XISetMask(m.mask, XI_RawKeyPress);
    if (printKeyUps)
      XISetMask(m.mask, XI_RawKeyRelease);
    XISelectEvents(disp, root, &m, 1 /*number of masks*/);
    XSync(disp, false);
    free(m.mask);
  }

  int xkbOpcode, xkbEventCode;
  { // Test for Xkb extension
    int queryError, majorVersion, minorVersion;
    if (!XkbQueryExtension(disp, &xkbOpcode, &xkbEventCode, &queryError,
                           &majorVersion, &minorVersion)) {
      fprintf(stderr, "Xkb extension not available\n");
      exit(2);
    }
  }
  // Register to receive events when the keyboard's keysym group changes.
  // Keysym groups are normally used to switch keyboard layouts.  The
  // keyboard continues to send the same keycodes (numeric identifiers of
  // keys) either way, but the active keysym group determines how those map
  // to keysyms (textual names of keys).
  XkbSelectEventDetails(disp, XkbUseCoreKbd, XkbStateNotify, XkbGroupStateMask,
                        XkbGroupStateMask);
  int group;
  { // Determine initial keysym group
    XkbStateRec state;
    XkbGetState(disp, XkbUseCoreKbd, &state);
    group = state.group;
  }

  while ("forever") {
    XEvent event;
    XGenericEventCookie *cookie = (XGenericEventCookie *)&event.xcookie;
    XNextEvent(disp, &event);

    if (XGetEventData(disp, cookie)) {
      // Handle key press and release events
      if (cookie->type == GenericEvent && cookie->extension == xiOpcode) {
        if (cookie->evtype == XI_RawKeyRelease ||
            cookie->evtype == XI_RawKeyPress) {
          XIRawEvent *ev = cookie->data;

          // Ask X what it calls that key; skip if unknown.
          // Ignore shift-level argument, to show the "basic" key
          // regardless of what modifiers are held down.
          KeySym s =
              XkbKeycodeToKeysym(disp, ev->detail, group, 0 /*shift level*/);

          // Non-zero keysym groups are "overlays" on the base (`0`)
          // group.  If the current group has no keysym for this
          // keycode, defer to the base group instead.  (This usually
          // happens with common shared keys like Return, Backspace,
          // or numeric keypad keys.)
          if (NoSymbol == s) {
            if (group == 0)
              goto end;
            else {
              s = XkbKeycodeToKeysym(disp, ev->detail, 0 /* base group */,
                                     0 /*shift level*/);
              if (NoSymbol == s)
                goto end;
            }
          }

          if ((s >= XK_A && s <= XK_Z) || (s >= XK_a && s <= XK_z)) {
            goto end;
          }

          char *str = XKeysymToString(s);
          if (NULL == str)
            goto end;

          // Output line
          if (printKeyUps)
            printf("%s", cookie->evtype == XI_RawKeyPress ? "+" : "-");
          printf("%s\n", str);
          fflush(stdout);
        }
      }

    end:
      // Release memory associated with event data
      XFreeEventData(disp, cookie);
    } else { // No extra data to release; `event` contains everything.
      // Handle keysym group change events
      if (event.type == xkbEventCode) {
        XkbEvent *xkbEvent = (XkbEvent *)&event;
        if (xkbEvent->any.xkb_type == XkbStateNotify) {
          group = xkbEvent->state.group;
        }
      }
    }
  }
}