diff --git a/Makefile b/Makefile index e89c640..a638c6b 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,19 @@ -showkeys: showkeys.c showkeys.h keystack.o - gcc -g -Wall showkeys.c keystack.o -o showkeys -lX11 -lxosd -lpthread -lXext -lX11 -lXinerama -lXtst +LDFLAGS = -lX11 -lxosd -lpthread -lXext -lX11 -lXinerama -lXtst +SOURCES = keystack.c showkeys.c +HEADERS = config.h keystack.h showkeys.h die.h +CC = gcc +CFLAGS = -g -Wall -Wextra -fdiagnostics-color=auto +LD = gcc - -keystack.o: keystack.c keystack.h - gcc -c -g keystack.c +all: showkeys clean: - rm showkeys keystack.o record-attempt record-example + rm -f showkeys *.o -check-syntax: - gcc -Wall -o nul -S ${CHK_SOURCES} +.c.o: + $(CC) $(CFLAGS) -c -o $@ $< -record-attempt: record-attempt.c - gcc -g -Wall record-attempt.c -L/usr/lib -lXtst -lxosd -lpthread -lXext -lX11 -lXinerama -o record-attempt +$(SOURCES:.c=.o): $(HEADERS) -record-example: record-example.c - gcc -g -Wall record-example.c -L/usr/lib -lXtst -lxosd -lpthread -lXext -lX11 -lXinerama -o record-example +showkeys: $(SOURCES:.c=.o) + $(LD) -o $@ $^ $(LDFLAGS) diff --git a/config.h b/config.h index 4504ded..904c381 100644 --- a/config.h +++ b/config.h @@ -1,9 +1,17 @@ /* Config header for showkeys.c */ +#ifndef CONFIG_H +#define CONFIG_H + +/* Number of OSD lines on the screen */ +#define NKEYS 10 + +/* Maximum length of one OSD line */ +#define OSDLEN 15 /* Display font, select a font using 'xfontsel -scaled' */ -#define SK_FONT "-*-courier*-*-*-*-*-60-*-*-*-*-*-*-*" +#define SK_FONT "-*-latin modern sans-*-r-*-*-60-*-*-*-*-*-*-*" /* Display position, possible values: XOSD_top, XOSD_bottom */ #define SK_POS XOSD_bottom @@ -36,3 +44,9 @@ to: #undef SK_NO_REPEATS. */ #define SK_NO_REPEATS +/* Define CAPS_IS_CONTROL if the caps lock key should be understood as a control + * modifier. */ +#define CAPS_IS_CONTROL + + +#endif diff --git a/die.h b/die.h new file mode 100644 index 0000000..f599eea --- /dev/null +++ b/die.h @@ -0,0 +1,14 @@ +/* Showkeys + Copyright Noufal Ibrahim 2011 + Copyright Raphael Poss 2020 + + Licensed under the GPLv3 : http://www.gnu.org/licenses/gpl.txt + + Please see LICENSE file for complete license. +*/ +#ifndef DIE_H +#define DIE_H + +extern void die(const char*); + +#endif diff --git a/keystack.c b/keystack.c index a2bb58e..ad01f37 100644 --- a/keystack.c +++ b/keystack.c @@ -1,4 +1,4 @@ -/* Showkeys +/* Showkeys Copyright Noufal Ibrahim 2011 Licensed under the GPLv3 : http://www.gnu.org/licenses/gpl.txt @@ -6,68 +6,106 @@ Please see LICENSE file for complete license. */ +#define _GNU_SOURCE #include #include #include #include #include "keystack.h" +#include "die.h" +#include "config.h" +// push_back frees up the spot in the stack at the specified index, +// by pulling all the entries before that by one spot. +// The first entry is popped (and its string deallocated). static void push_back(KeyStack *stack, int index) { - int i; - free(stack->keystrokes[0].keyname); - for (i=0; ikeystrokes[i] = stack->keystrokes[i+1]; + free(stack->keystrokes[0].keyname); + free(stack->keystrokes[0].buf); + + for (int i = 0; i < index; i++) + stack->keystrokes[i] = stack->keystrokes[i+1]; + + stack->keystrokes[index].keyname = 0; +} + +void +clear_stack(KeyStack *stack) +{ + for (int i = 0; i < stack->pos; i++) { + free(stack->keystrokes[i].keyname); + free(stack->keystrokes[i].buf); + stack->keystrokes[i].keyname = 0; + } + stack->pos = -1; } KeyStack * -create_keystack(int size) +create_keystack(int size) { - int i; - KeyStack *retval = NULL; - KeyStroke *stack = NULL; - - stack = (KeyStroke *)malloc(sizeof(KeyStroke) * size); - for (i=0; isize = size; - retval->pos = -1; - retval->keystrokes = stack; + stack = (KeyStroke *)malloc(sizeof(KeyStroke) * size); + if (!stack) + die("malloc"); - return retval; + for (int i = 0; i < size; i++) { + stack[i].keyname = NULL; + stack[i].times = 0; + } + + retval = (KeyStack *)malloc(sizeof(KeyStack)); + if (!retval) + die("malloc"); + + retval->size = size; + retval->pos = -1; + retval->keystrokes = stack; + + return retval; } void push(KeyStack *stack, char *keyname) { - int index; - KeyStroke *last; - char *last_key; - - last = &stack->keystrokes[stack->pos]; - last_key = last->keyname; - index = stack->pos + 1; + int index = stack->pos + 1; #ifdef SK_NO_REPEATS - if (index && ! strcmp (last_key, keyname)) { - /* If the same as the top of the stack, increment count */ - last->times ++; + if (index >= 1 && !strcmp(stack->keystrokes[index-1].keyname, keyname)) { + /* If the same as the top of the stack, increment count */ + stack->keystrokes[index-1].times++; } else { #endif - /* Add a new entry */ - if (index == stack->size) { - push_back(stack, stack->pos); - index = stack->size-1; - } - stack->keystrokes[index].keyname = keyname; - stack->keystrokes[index].times = 1; - stack->pos = index; + /* Add a new entry. */ + // Can we add it to the previous line? + if (index >= 1 && // We have a previous line already. + stack->keystrokes[index-1].times < 2 && // Not more than one press of the last key. + strcmp(stack->keystrokes[index-1].keyname, "Ret") != 0 && // Only if the last key was not "Enter". + strlen(stack->keystrokes[index-1].buf) + strlen(keyname) + 1 < OSDLEN // The combination fits. + ) { + char *newbuf; + if (-1 == asprintf(&newbuf, "%s %s", stack->keystrokes[index-1].buf, keyname)) + die("asprintf"); + free(stack->keystrokes[index-1].keyname); + free(stack->keystrokes[index-1].buf); + stack->keystrokes[index-1].buf = newbuf; + stack->keystrokes[index-1].keyname = keyname; + } else { + // Adding to the last line? scroll everything first up. + if (index == stack->size) { + push_back(stack, stack->pos); + index = stack->size - 1; + } + + // Now create a new line. + stack->keystrokes[index].buf = strdup(keyname); + stack->keystrokes[index].keyname = keyname; + stack->keystrokes[index].times = 1; + stack->pos = index; + } #ifdef SK_NO_REPEATS } #endif @@ -77,9 +115,8 @@ push(KeyStack *stack, char *keyname) void display_keystack(KeyStack *stack) { - int i; printf("---- Keystack ----\n"); - for (i = 0; i <= stack->pos; i++) { + for (int i = 0; i <= stack->pos; i++) { printf("%s %d times\n", stack->keystrokes[i].keyname, stack->keystrokes[i].times); } printf("---- -------- ----\n\n"); diff --git a/keystack.h b/keystack.h index aa056f2..b01a0f1 100644 --- a/keystack.h +++ b/keystack.h @@ -1,27 +1,46 @@ -/* Showkeys +/* Showkeys Copyright Noufal Ibrahim 2011 + Copyright Raphael Poss 2020 Licensed under the GPLv3 : http://www.gnu.org/licenses/gpl.txt Please see LICENSE file for complete license. */ -#define NKEYS 10 +#ifndef KEYSTACK_H +#define KEYSTACK_H +#include "config.h" + +// KeyStroke represents one line in the OSD output. typedef struct { - char *keyname; - int times; + // keyname is the string returned by the X lookup function for the + // last key, possibly prefixed by a representation of modifiers. + // It identifies the last key event. + char *keyname; + // buf is the string to be printed on the screen. + char *buf; + + // times is the number of times the last key has been pressed. + int times; } KeyStroke; typedef struct { - int size; - int pos; - KeyStroke *keystrokes; + // size is the total (allocated) number of entries in keystrokes + // below. + int size; + // pos is the index of the last used entry in + // keystrokes. Initially -1 to indicate no entry has been used + // yet. + int pos; + // keystrokes is the array of allocated keystrokes struct. + // There is one entry per OSD line, up to and including the + // entry pointed to by pos. + KeyStroke *keystrokes; } KeyStack; - - KeyStack *create_keystack(); void push(KeyStack *, char *); +void clear_stack(KeyStack *); void display_keystack(KeyStack *); - +#endif diff --git a/showkeys.c b/showkeys.c index e9d2ec5..8d08582 100644 --- a/showkeys.c +++ b/showkeys.c @@ -6,58 +6,107 @@ Please see LICENSE file for complete license. */ - +#include #define _GNU_SOURCE + +#include "config.h" +#include "showkeys.h" +#include "die.h" + #include #include #include -#include -#include -#include -#include +#include -#include - -#include "showkeys.h" -#include "keystack.h" -#include "config.h" +void +die(const char *msg) +{ + fprintf(stderr, "error: %s: %s\n", msg, strerror(errno)); + exit(1); +} -Display *d0, *d1; -KeyStack *keystack; -xosd *osd; +// process_modifiers writes val into s->meta, s->ctrl and s->shift depending +// on whether the KeySym points to an Alt, Control or Shift key respectively. +// +// The function return 1 if ks is a modifier, 0 otherwise. +int +process_modifiers(record_state *s, KeySym ks, int val) +{ + switch(ks) { + case XK_Shift_L: case XK_Shift_R: + s->shift = val; + return 1; +#ifdef CAPS_IS_CONTROL + case XK_Caps_Lock: +#endif + case XK_Control_L: case XK_Control_R: + s->ctrl = val; + return 1; -int -process_modifiers(KeySym ks, int * meta, int *ctrl, int *shift, int val) -{ - int modifier_pressed = 0; - switch(ks) { - case XK_Shift_L: - case XK_Shift_R: - *shift = val; - modifier_pressed = 1; - break; - case XK_Control_L: - case XK_Control_R: - *ctrl = val; - modifier_pressed = 1; - break; - case XK_Alt_L: - case XK_Alt_R: - *meta = val; /* This is not accurate but it's correct for my keyboard mappings */ - modifier_pressed = 1; - break; + case XK_Alt_L: case XK_Alt_R: + s->meta = val; /* This is not accurate but it's correct for my keyboard mappings */ + return 1; } - return modifier_pressed; + return 0; } -char * -create_emacs_keyname(char *keyname, int meta, int ctrl, int shift) +char * +create_emacs_keyname(record_state *s, const char *keyname) { char *retval; - /* TBD: Handle <. > and others like that wehere XLookupString gives the right values */ + /* TBD: Handle <. > and others like that where XLookupString gives the right values */ /* printf("%d %d %d ", meta, ctrl, shift); */ - asprintf(&retval, "%s%s%s%s", ctrl?"C-":"", meta?"M-":"", shift?"S-":"", keyname); + // Some special cases: + if (!strcmp(keyname, "space")) keyname = "Sp"; + else if (!strcmp(keyname, "Return")) keyname = "Ret"; + else if (!strcmp(keyname, "apostrophe")) keyname = "'"; + else if (!strcmp(keyname, "grave")) keyname = "`"; + else if (!strcmp(keyname, "comma")) keyname = ","; + else if (!strcmp(keyname, "minus")) keyname = "-"; + else if (!strcmp(keyname, "slash")) keyname = "/"; + else if (!strcmp(keyname, "backslash")) keyname = "\\"; + else if (!strcmp(keyname, "period")) keyname = "."; + else if (!strcmp(keyname, "equal")) keyname = "="; + else if (!strcmp(keyname, "semicolon")) keyname = ";"; + else if (!strcmp(keyname, "bracketleft")) keyname = "["; + else if (!strcmp(keyname, "bracketright")) keyname = "]"; + else if (!strcmp(keyname, "BackSpace")) keyname = "Bksp"; + + int shift = s->shift; + if (shift) { + const char *oldkey = keyname; + if (!strcmp(keyname, "semicolon")) keyname = (":"); + else if (strlen(keyname) == 1) { + static const char *punct = ")\0!\0@\0#\0$\0%\0^\0&\0*\0("; + static const char *alph = "A\0B\0C\0D\0E\0F\0G\0H\0I\0J\0K\0L\0M\0N\0O\0P\0Q\0R\0S\0T\0U\0V\0W\0X\0Y\0Z"; + if ('0' <= keyname[0] && keyname[0] <= '9') + keyname = punct + (keyname[0] - '0') * 2; + else if ('a' <= keyname[0] && keyname[0] <= 'z') + keyname = alph + (keyname[0] - 'a') * 2; + else if (keyname[0] == '-') keyname = ("_"); + else if (keyname[0] == '=') keyname = ("+"); + else if (keyname[0] == '[') keyname = ("{"); + else if (keyname[0] == ']') keyname = ("}"); + else if (keyname[0] == '\'') keyname = ("\""); + else if (keyname[0] == ',') keyname = ("<"); + else if (keyname[0] == '`') keyname = ("~"); + else if (keyname[0] == '.') keyname = (">"); + else if (keyname[0] == '/') keyname = ("?"); + else if (keyname[0] == '\\') keyname = ("|"); + } + if (oldkey != keyname) { + shift = 0; + } + } + + if (-1 == asprintf(&retval, "%s%s%s%s", + s->ctrl ? "C-" : "", + s->meta ? "M-" : "", + shift ? "S-" : "", + keyname)) { + die("asprintf"); + } /* printf(" %s\n",retval); */ return retval; } @@ -65,18 +114,19 @@ create_emacs_keyname(char *keyname, int meta, int ctrl, int shift) xosd * configure_osd(int lines) { - xosd *osd; - osd = xosd_create (NKEYS); - - xosd_set_font(osd, SK_FONT); - xosd_set_pos(osd, SK_POS); - xosd_set_align(osd, SK_ALIGN); - xosd_set_colour(osd, SK_FG); - xosd_set_outline_colour(osd, SK_OUTLINE); - xosd_set_outline_offset(osd, SK_OFFSET); - xosd_set_shadow_colour(osd, SK_SHADOW); - xosd_set_shadow_offset(osd, SK_SHOFFSET); - xosd_set_timeout(osd, SK_TIMEOUT); + xosd *osd = xosd_create(lines); + if (!osd) + die("xosd_create"); + + if (-1 == xosd_set_font(osd, SK_FONT)) die("xosd_set_font"); + if (-1 == xosd_set_pos(osd, SK_POS)) die("xosd_set_pos"); + if (-1 == xosd_set_align(osd, SK_ALIGN)) die("xosd_set_align"); + if (-1 == xosd_set_colour(osd, SK_FG)) die("xosd_set_colour"); + if (-1 == xosd_set_outline_colour(osd, SK_OUTLINE)) die("xosd_set_outline_colour"); + if (-1 == xosd_set_outline_offset(osd, SK_OFFSET)) die("xosd_set_outline_offset"); + if (-1 == xosd_set_shadow_colour(osd, SK_SHADOW)) die("xosd_set_shadow_colour"); + if (-1 == xosd_set_shadow_offset(osd, SK_SHOFFSET)) die("xosd_set_shadow_offset"); + if (-1 == xosd_set_timeout(osd, SK_TIMEOUT)) die("xosd_set_timeout"); return osd; } @@ -84,93 +134,133 @@ configure_osd(int lines) void display_keystrokes(xosd *osd, KeyStack *stack) { - int i; - for(i = 0; i < NKEYS; i++) { - if (stack->keystrokes[i].keyname) { + for(int i = 0; i <= stack->pos; i++) { + const char *outputstr = stack->keystrokes[i].buf; + if (!outputstr) { + outputstr = ""; + } + if (stack->keystrokes[i].times == 1) { - xosd_display(osd, i, XOSD_printf, "%s", stack->keystrokes[i].keyname); + xosd_display(osd, i, XOSD_printf, "%s", outputstr); } else { - xosd_display(osd, i, XOSD_printf, "%s %d times", stack->keystrokes[i].keyname, stack->keystrokes[i].times); + xosd_display(osd, i, XOSD_printf, "%s (x%d)", outputstr, stack->keystrokes[i].times); } - } + } + for (int i = stack->pos + 1; i < stack->size; i++) { + xosd_display(osd, i, XOSD_string, ""); } } - + +// record_callback processes a key press/release event, as +// reported by the X record extension. void -update_key_ring (XPointer priv, XRecordInterceptData *data) +record_callback(XPointer priv, XRecordInterceptData *data) { - static int meta = 0; - static int ctrl = 0; - static int shift = 0; - xEvent *event; - KeySym ks; - char *display_string; - char *ksname; - if (data->category==XRecordFromServer) { - event=(xEvent *)data->data; - /* display_keystack(keystack); */ + record_state *s = (record_state*)priv; + + if (data->category != XRecordFromServer) { + return; + } + + const xEvent *event = (const xEvent *)data->data; + // display_keystack(s->stack); switch (event->u.u.type) { - case KeyPress: - ks = XKeycodeToKeysym(d0, event->u.u.detail, 0); - ksname = XKeysymToString (ks); /* TBD: Might have to handle no symbol keys */ - if (! process_modifiers(ks, &meta, &ctrl, &shift, 1)) { - display_string = create_emacs_keyname(ksname, meta, ctrl, shift); - push(keystack, display_string); - display_keystrokes(osd, keystack); + case KeyPress: + { + KeySym ks = XKeycodeToKeysym(s->disp, event->u.u.detail, 0); + int isModifier = process_modifiers(s, ks, 1); + if (isModifier) + // Only a modifier: don't report the key press just yet. + break; + + if (!xosd_is_onscreen(s->osd)) { + // Not currently displayed. Start from scratch. + clear_stack(s->stack); } + + // The non-modifier part of a combo has been pressed. Report that. + const char *ksname = XKeysymToString(ks); /* TBD: Might have to handle no symbol keys */ + char *display_string = create_emacs_keyname(s, ksname); + push(s->stack, display_string); + display_keystrokes(s->osd, s->stack); break; - case KeyRelease: - ks = XKeycodeToKeysym(d0, event->u.u.detail, 0); - process_modifiers(ks, &meta, &ctrl, &shift, 0); + } + case KeyRelease: + { + KeySym ks = XKeycodeToKeysym(s->disp, event->u.u.detail, 0); + process_modifiers(s, ks, 0); break; } - } + } } int -main() +main(void) { - XRecordContext xrd; - XRecordRange *range; - XRecordClientSpec client; + // " The recommended communication model for a Record application is + // to open two connections to the server—one connection for + // recording control and one connection for reading recorded + // protocol data. " + // Source: https://www.x.org/releases/X11R7.6/doc/libXtst/recordlib.html - osd = configure_osd(NKEYS); - keystack = create_keystack(NKEYS); - - d0 = XOpenDisplay(NULL); - d1 = XOpenDisplay(NULL); + // d0 is where we read the recorded protocol data. + // d1 is to control recording. + Display *d0 = XOpenDisplay(NULL); + Display *d1 = XOpenDisplay(NULL); - XSynchronize(d0, True); if (d0 == NULL || d1 == NULL) { - fprintf(stderr, "Cannot connect to X server"); - exit (-1); + die("Cannot connect to X server (is DISPLAY set?)"); } - client=XRecordAllClients; + xosd *osd = configure_osd(NKEYS); + KeyStack *keystack = create_keystack(NKEYS); - range=XRecordAllocRange(); + // record_state will be the variable we pass to our recording + // callback. + record_state s = { + .shift = 0, + .meta = 0, + .ctrl = 0, + .disp = d0, + .osd = osd, + .stack = keystack, + }; + + // We want the recorded events to arrive immediately. + XSynchronize(d0, True); + + // Indicate which range of events we want to record. That's just 2 + // events. + XRecordRange *range = XRecordAllocRange(); memset(range, 0, sizeof(XRecordRange)); - range->device_events.first=KeyPress; - range->device_events.last=KeyRelease; + range->device_events.first = KeyPress; + range->device_events.last = KeyRelease; - xrd = XRecordCreateContext(d0, 0, &client, 1, &range, 1); + // Indicate which client we want to record events for. We want the + // entire screen. + XRecordClientSpec client = XRecordAllClients; - if (! xrd) { - fprintf(stderr, "Error in creating context"); - exit (-1); - } + // Initialize recording. + XRecordContext xrd = XRecordCreateContext(d0, 0, &client, 1, &range, 1); - XRecordEnableContext(d1, xrd, update_key_ring, (XPointer)osd); + if (!xrd) + die("XRecordCreateContext"); - XRecordProcessReplies (d1); + if (!XRecordEnableContext(d1, xrd, record_callback, (XPointer)&s)) + die("XRecordEnableContext"); + // Main loop. + XRecordProcessReplies(d1); - XRecordDisableContext (d0, xrd); - XRecordFreeContext (d0, xrd); + // Clean-up and termination. + XRecordDisableContext(d0, xrd); + XRecordFreeContext(d0, xrd); + XFree(range); XCloseDisplay(d0); XCloseDisplay(d1); - exit(0); + + return 0; } diff --git a/showkeys.h b/showkeys.h index 98e02f0..7f916a0 100644 --- a/showkeys.h +++ b/showkeys.h @@ -1,11 +1,42 @@ -/* Showkeys +/* Showkeys Copyright Noufal Ibrahim 2011 + Copyright Raphael Poss 2020 Licensed under the GPLv3 : http://www.gnu.org/licenses/gpl.txt Please see LICENSE file for complete license. */ -void update_key_ring (XPointer priv, XRecordInterceptData *data); -char *create_emacs_keyname(char *, int, int, int); -int process_modifiers(KeySym , int * , int *, int *, int); +#ifndef SHOWKEYS_H +#define SHOWKEYS_H + +#include +#include +#include +#include + +#include + +#include "keystack.h" + +typedef struct { + // meta, ctrl and shift keep track of the current status + // of modifiers. + int meta; + int ctrl; + int shift; + + // disp is the X11 display that events are recorded from. + Display *disp; + // osd is the on-screen display output. + xosd *osd; + // stack is where we are keeping track of keystrokes. + KeyStack *stack; +} record_state; + +void record_callback(XPointer priv, XRecordInterceptData *data); +char *create_emacs_keyname(record_state*, const char *); +int process_modifiers(record_state *, KeySym, int); xosd *configure_osd(int); + + +#endif