Compare commits

..

No commits in common. "master" and "v1.0" have entirely different histories.
master ... v1.0

5 changed files with 80 additions and 374 deletions

1
.gitignore vendored
View file

@ -1 +0,0 @@
xcape

View file

@ -1,6 +1,5 @@
INSTALL=install
PREFIX=/usr
MANDIR?=/local/man/man1
TARGET := xcape
@ -9,18 +8,13 @@ CFLAGS += `pkg-config --cflags xtst x11`
LDFLAGS += `pkg-config --libs xtst x11`
LDFLAGS += -pthread
all: $(TARGET)
$(TARGET): xcape.c
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
install:
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)$(MANDIR)
$(INSTALL) -m 0755 $(TARGET) $(DESTDIR)$(PREFIX)/bin/$(TARGET)
$(INSTALL) -m 0644 xcape.1 $(DESTDIR)$(PREFIX)$(MANDIR)/xcape.1
$(INSTALL) -Dm 755 $(TARGET) $(DESTDIR)$(PREFIX)/bin/$(TARGET)
clean:
rm $(TARGET)
.PHONY: all clean install
.PHONY: clean

View file

@ -12,89 +12,39 @@ editor ;)
Minimal building instructions
-----------------------------
First install the development dependencies. On Debian-based systems
(including Ubuntu and Linux Mint), run:
$ sudo apt-get install git gcc make pkg-config libx11-dev libxtst-dev libxi-dev
On Fedora-based systems, run:
$ sudo dnf install git gcc make pkgconfig libX11-devel libXtst-devel libXi-devel
Then run:
$ git clone https://github.com/alols/xcape.git
$ sudo apt-get install git gcc make libx11-dev libxtst-dev
$ mkdir xcape
$ cd xcape
$ git clone https://github.com/alols/xcape.git .
$ make
$ sudo make install
Usage
-----
$ xcape [-d] [-f] [-t <timeout ms>] [-e <map-expression>]
$ xcape [-d] [-t <timeout ms>] [-e <map-expression>]
### `-d`
Debug mode. Does not fork into the background. Prints debug information.
### `-f`
Foreground mode. Does not fork into the background.
Debug mode. Does not fork into the background.
### `-t <timeout ms>`
If you hold a key longer than this timeout, xcape will not generate a key
event. Default is 500 ms.
event. Default is 50 ms.
### `-e <map-expression>`
The expression has the grammar `'ModKey=Key[|OtherKey][;NextExpression]'`
The list of key names is found in the header file `X11/keysymdef.h` (remove
the `XK_` prefix). Note that due to limitations of X11 shifted keys *must*
be specified as a shift key followed by the key to be pressed rather than
the actual name of the character. For example to generate "{" the
expression `'ModKey=Shift_L|bracketleft'` could be used (assuming that you
have a key with "{" above "[").
The list of key names is found in the header file `X11/keysymdef.h`
(remove the `XK_` prefix).
You can also specify keys in decimal (prefix `#`), octal (`#0`), or
hexadecimal (`#0x`). They will be interpreted as keycodes unless no corresponding
key name is found.
#### Example
#### Examples
+ This will make Left Shift generate Escape when pressed and released on
its own, and Left Control generate Ctrl-O combination when pressed and
released on its own.
xcape -e 'Shift_L=Escape;Control_L=Control_L|O'
+ In conjunction with xmodmap it is possible to make an ordinary key act
as an extra modifier. First map the key to the modifier with xmodmap
and then the modifier back to the key with xcape. However, this has
several limitations: the key will not work as ordinary until it is
released, and in particular, *it may act as a modifier unintentionally if
you type too fast.* This is not a bug in xcape, but an unavoidable
consequence of using these two tools together in this way.
As an example, we can make the space bar work as an additional ctrl
key when held (similar to
[Space2ctrl](https://github.com/r0adrunner/Space2Ctrl)) with the
following sequence of commands.
# Map an unused modifier's keysym to the spacebar's keycode and make it a
# control modifier. It needs to be an existing key so that emacs won't
# spazz out when you press it. Hyper_L is a good candidate.
spare_modifier="Hyper_L"
xmodmap -e "keycode 65 = $spare_modifier"
xmodmap -e "remove mod4 = $spare_modifier" # hyper_l is mod4 by default
xmodmap -e "add Control = $spare_modifier"
# Map space to an unused keycode (to keep it around for xcape to
# use).
xmodmap -e "keycode any = space"
# Finally use xcape to cause the space bar to generate a space when tapped.
xcape -e "$spare_modifier=space"
xcape -e 'Shift_L=Escape;Control_L=Control_L|O'
This will make Left Shift generate Escape when pressed and released on
it's own, and Left Control generate Ctrl-O combination when pressed and
released on it's own.
Note regarding xmodmap
----------------------
@ -102,11 +52,11 @@ Note regarding xmodmap
If you are in the habit of remapping keycodes to keysyms (eg, using xmodmap),
there are two issues you may encounter.
1. You will need to restart xcape after every time you modify the mapping from
1) You will need to restart xcape after every time you modify the mapping from
keycodes to keysyms (eg, with xmodmap), or xcape will still use the old
mapping.
2. The key you wish to send must have a defined keycode. So for example, with
2) The key you wish to send must have a defined keycode. So for example, with
the default mapping `Control_L=Escape`, you still need an escape key defined
in your xmodmap mapping. (I get around this by using 255, which my keyboard
cannot send).

96
xcape.1
View file

@ -1,96 +0,0 @@
.TH XCAPE 1 2017-07-03 "John Hill" "xcape Manual"
.SH NAME
xcape \- use a modifier key as another key
.SH SYNOPSIS
.B xcape
[\fB-d\fR]
[\fB-f\fR]
[\fB-t\fR \fItimeout\fR]
[\fB-e\fR \fImap-expression\fR]
.SH DESCRIPTION
\fBxcape\fR allows a modifier key to be used as another key when it is pressed
and released on its own. The default behaviour is to generate the \fIEscape\fR
key in place of \fIControl_L\fR (Left Control).
.SH OPTIONS
.TP
.BR \-d
Debug mode. Will run as a foreground process and print debug information.
.TP
.BR \-f
Foreground mode. Will run as a foreground process.
.TP
.BR \-t " " \fItimeout\fR
Give a \fItimeout\fR in milliseconds. If you hold a key longer than
\fItimeout\fR a key event will not be generated.
.TP
.BR \-e " " \fImap-expression\fR
Use \fImap-expression\fR as the expression(s).
.SH EXPRESSION SYNTAX
Expression syntax is \'\fBModKey\fR=\fBKey\fR[|\fBOtherKey\fR]\'. Multiple
expressions can be passed, delimited by semi-colons (;).
.PP
A list of keysyms can be found in the header file <\fIX11/keysymdef.h\fR>
(without the \fIXK_\fR prefix).
.PP
Note that shifted keys must be specified as a shift key followed by the key to
be pressed rather than the actual name of the character. For example to
generate "\fI{\fR" the expression
\'\fIModKey\fR=\fIShift_L\fR|\fIbracketleft\fR\' could be used
(assuming that you have a key with \'{\' above \'[\').
.PP
You can also specify \fBModKey\fR in decimal (prefix \fI#\fR), octal
(\fI#0\fR), or hexadecimal (\fI#0x\fR). It will be interpreted as a keycode
unless no corresponding key name is
found.
.SH EXAMPLES
.PP
Make Left Shift generate Escape when pressed and released on it's own, and Left
Control generate Ctrl\-O combination when pressed and released on it's own:
.RS
\fBxcape\fR \fB-e\fR '\fIShift_L\fR=\fIEscape\fR;\fIControl_L\fR=\fIControl_L\fR|\fIO\fR'
.RE
.PP
In conjugation with xmodmap it is possible to make an ordinary key act as an
extra modifier. First map the key to the modifier with xmodmap and then the
modifier back to the key with \fBxcape\fR. As an example, we can make the space
bar work as an additional ctrl key when held with the following sequence of
commands:
.PP
First, map an unused modifier's keysym to the spacebar's keycode and make it a
control modifier. It needs to be an existing key so that emacs won't spazz out
when you press it. Hyper_L is a good candidate.
.PP
.RS
.nf
\fBspare_modifier\fR="\fIHyper_L\fR"
\fBxmodmap\fR \fB-e\fR "\fBkeycode\fR \fI65\fR = \fI$spare_modifier\fR"
\fBxmodmap\fR \fB-e\fR "\fBremove\fR \fImod4\fR = \fI$spare_modifier\fR"
# hyper_l is mod4 by default
\fBxmodmap\fR \fB-e\fR "\fBadd\fR \fIControl\fR = \fI$spare_modifier\fR"
.fi
.RE
.PP
Next, map space to an unused keycode (to keep it around for \fBxcape\fR to use).
.PP
.RS
\fBxmodmap\fR \fR-e\fR "\fBkeycode\fR \fIany\fR = \fIspace\fR"
.RE
.PP
Finally use \fBxcape\fR to cause the space bar to generate a space when tapped.
.PP
.RS
\fBxcape\fR \fB-e\fR "\fI$spare_modifier\fR=\fIspace\fR"
.RE
.SH SEE ALSO
\fBxmodmap\fR(1), \fBxev\fR(1)
.SH AUTHOR
\fBxcape\fR was written by Albin Olsson
(albin dot olsson at gmail dot com)

265
xcape.c
View file

@ -1,7 +1,7 @@
/************************************************************************
* xcape.c
*
* Copyright 2015 Albin Olsson
* Copyright 2012 Albin Olsson
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -25,7 +25,6 @@
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/extensions/record.h>
@ -44,9 +43,7 @@ typedef struct _Key_t
typedef struct _KeyMap_t
{
Bool UseKeyCode; /* (for from) instead of KeySym; ignore latter */
KeySym from_ks;
KeyCode from_kc;
KeySym from;
Key_t *to_keys;
Bool used;
Bool pressed;
@ -62,11 +59,11 @@ typedef struct _XCape_t
XRecordContext record_ctx;
pthread_t sigwait_thread;
sigset_t sigset;
Bool foreground;
Bool debug;
KeyMap_t *map;
Key_t *generated;
struct timeval timeout;
Bool timeout_valid;
} XCape_t;
/************************************************************************
@ -76,49 +73,31 @@ void *sig_handler (void *user_data);
void intercept (XPointer user_data, XRecordInterceptData *data);
KeyMap_t *parse_mapping (Display *ctrl_conn, char *mapping, Bool debug);
void delete_mapping (KeyMap_t *map);
KeyMap_t *parse_mapping (Display *ctrl_conn, char *mapping);
Key_t *key_add_key (Key_t *keys, KeyCode key);
void delete_keys (Key_t *keys);
void print_usage (const char *program_name);
/************************************************************************
* Main function
***********************************************************************/
int main (int argc, char **argv)
{
XCape_t *self = malloc (sizeof (XCape_t));
int dummy, ch;
static char default_mapping[] = "Control_L=Escape";
char *mapping = default_mapping;
XRecordRange *rec_range = XRecordAllocRange();
XRecordClientSpec client_spec = XRecordAllClients;
self->foreground = False;
self->debug = False;
self->timeout.tv_sec = 0;
self->timeout.tv_usec = 500000;
self->timeout_valid = True;
self->generated = NULL;
rec_range->device_events.first = KeyPress;
rec_range->device_events.last = ButtonRelease;
while ((ch = getopt (argc, argv, "dfe:t:")) != -1)
while ((ch = getopt (argc, argv, "de:t:")) != -1)
{
switch (ch)
{
case 'd':
self->debug = True;
/* imply -f (no break) */
case 'f':
self->foreground = True;
break;
case 'e':
mapping = optarg;
@ -128,44 +107,27 @@ int main (int argc, char **argv)
int ms = atoi (optarg);
if (ms > 0)
{
self->timeout_valid = True;
self->timeout.tv_sec = ms / 1000;
self->timeout.tv_usec = (ms % 1000) * 1000;
}
else
{
fprintf (stderr, "Invalid argument for '-t': %s.\n", optarg);
print_usage (argv[0]);
return EXIT_FAILURE;
self->timeout_valid = False;
}
}
break;
default:
print_usage (argv[0]);
fprintf (stdout, "Usage: %s [-d] [-t timeout_ms] [-e <mapping>]\n", argv[0]);
fprintf (stdout,
"Runs as a daemon unless -d flag is set\n");
return EXIT_SUCCESS;
}
}
if (optind < argc)
{
fprintf (stderr, "Not a command line option: '%s'\n", argv[optind]);
print_usage (argv[0]);
return EXIT_SUCCESS;
}
if (!XInitThreads ())
{
fprintf (stderr, "Failed to initialize threads.\n");
exit (EXIT_FAILURE);
}
self->data_conn = XOpenDisplay (NULL);
self->ctrl_conn = XOpenDisplay (NULL);
if (!self->data_conn || !self->ctrl_conn)
{
fprintf (stderr, "Unable to connect to X11 display. Is $DISPLAY set?\n");
exit (EXIT_FAILURE);
}
if (!XQueryExtension (self->ctrl_conn,
"XTEST", &dummy, &dummy, &dummy))
{
@ -177,22 +139,19 @@ int main (int argc, char **argv)
fprintf (stderr, "Failed to obtain xrecord version\n");
exit (EXIT_FAILURE);
}
if (!XkbQueryExtension (self->ctrl_conn, &dummy, &dummy,
if (!XkbQueryExtension (self->ctrl_conn, &dummy, &dummy,
&dummy, &dummy, &dummy))
{
fprintf (stderr, "Failed to obtain xkb version\n");
exit (EXIT_FAILURE);
}
self->map = parse_mapping (self->ctrl_conn, mapping, self->debug);
self->map = parse_mapping (self->ctrl_conn, mapping);
if (self->map == NULL)
{
fprintf (stderr, "Failed to parse_mapping\n");
exit (EXIT_FAILURE);
}
if (self->foreground != True)
if (self->debug != True)
daemon (0, 0);
sigemptyset (&self->sigset);
@ -203,6 +162,11 @@ int main (int argc, char **argv)
pthread_create (&self->sigwait_thread,
NULL, sig_handler, self);
XRecordRange *rec_range = XRecordAllocRange();
rec_range->device_events.first = KeyPress;
rec_range->device_events.last = ButtonRelease;
XRecordClientSpec client_spec = XRecordAllClients;
self->record_ctx = XRecordCreateContext (self->ctrl_conn,
0, &client_spec, 1, &rec_range, 1);
@ -221,23 +185,15 @@ int main (int argc, char **argv)
exit (EXIT_FAILURE);
}
pthread_join (self->sigwait_thread, NULL);
if (!XRecordFreeContext (self->ctrl_conn, self->record_ctx))
{
fprintf (stderr, "Failed to free xrecord context\n");
}
if (self->debug) fprintf (stdout, "main exiting\n");
XFree (rec_range);
XCloseDisplay (self->ctrl_conn);
XCloseDisplay (self->data_conn);
delete_mapping (self->map);
free (self);
if (self->debug) fprintf (stdout, "main exiting\n");
return EXIT_SUCCESS;
}
@ -257,8 +213,6 @@ void *sig_handler (void *user_data)
if (self->debug) fprintf (stdout, "Caught signal %d!\n", sig);
XLockDisplay (self->ctrl_conn);
if (!XRecordDisableContext (self->ctrl_conn,
self->record_ctx))
{
@ -268,8 +222,6 @@ void *sig_handler (void *user_data)
XSync (self->ctrl_conn, False);
XUnlockDisplay (self->ctrl_conn);
if (self->debug) fprintf (stdout, "sig_handler exiting...\n");
return NULL;
@ -307,7 +259,8 @@ void handle_key (XCape_t *self, KeyMap_t *key,
key->pressed = True;
gettimeofday (&key->down_at, NULL);
if (self->timeout_valid)
gettimeofday (&key->down_at, NULL);
if (mouse_pressed)
{
@ -320,16 +273,19 @@ void handle_key (XCape_t *self, KeyMap_t *key,
if (key->used == False)
{
struct timeval timev = self->timeout;
gettimeofday (&timev, NULL);
timersub (&timev, &key->down_at, &timev);
if (self->timeout_valid)
{
gettimeofday (&timev, NULL);
timersub (&timev, &key->down_at, &timev);
}
if (timercmp (&timev, &self->timeout, <))
if (!self->timeout_valid || timercmp (&timev, &self->timeout, <))
{
for (k = key->to_keys; k != NULL; k = k->next)
{
if (self->debug) fprintf (stdout, "Generating %s!\n",
XKeysymToString (XkbKeycodeToKeysym (self->ctrl_conn,
k->key, 0, 0)));
XKeysymToString (XKeycodeToKeysym (self->ctrl_conn,
k->key, 0)));
XTestFakeKeyEvent (self->ctrl_conn,
k->key, True, 0);
@ -355,8 +311,6 @@ void intercept (XPointer user_data, XRecordInterceptData *data)
static Bool mouse_pressed = False;
KeyMap_t *km;
XLockDisplay (self->ctrl_conn);
if (data->category == XRecordFromServer)
{
int key_event = data->data[0];
@ -395,36 +349,33 @@ void intercept (XPointer user_data, XRecordInterceptData *data)
{
mouse_pressed = False;
}
for (km = self->map; km != NULL; km = km->next)
else
{
if ((km->UseKeyCode == False
&& XkbKeycodeToKeysym (self->ctrl_conn, key_code, 0, 0)
== km->from_ks)
|| (km->UseKeyCode == True
&& key_code == km->from_kc))
for (km = self->map; km != NULL; km = km->next)
{
handle_key (self, km, mouse_pressed, key_event);
}
else if (km->pressed
&& (key_event == KeyPress || key_event == ButtonPress))
{
km->used = True;
if (XkbKeycodeToKeysym (self->ctrl_conn, key_code, 0, 0)
== km->from)
{
handle_key (self, km, mouse_pressed, key_event);
}
else if (km->pressed && key_event == KeyPress)
{
km->used = True;
}
}
}
}
exit:
XUnlockDisplay (self->ctrl_conn);
XRecordFreeData (data);
}
KeyMap_t *parse_token (Display *dpy, char *token, Bool debug)
KeyMap_t *parse_token (Display *dpy, char *token)
{
KeyMap_t *km = NULL;
KeySym ks;
char *from, *to, *key;
KeyCode code; /* keycode */
long parsed_code; /* parsed keycode value */
KeyCode code;
to = token;
from = strsep (&to, "=");
@ -432,54 +383,14 @@ KeyMap_t *parse_token (Display *dpy, char *token, Bool debug)
{
km = calloc (1, sizeof (KeyMap_t));
if (!strncmp (from, "#", 1)
&& strsep (&from, "#") != NULL)
if ((ks = XStringToKeysym (from)) == NoSymbol)
{
errno = 0;
parsed_code = strtoul (from, NULL, 0); /* dec, oct, hex automatically */
if (errno == 0
&& parsed_code <=255
&& XkbKeycodeToKeysym (dpy, (KeyCode) parsed_code, 0, 0) != NoSymbol)
{
km->UseKeyCode = True;
km->from_kc = (KeyCode) parsed_code;
if (debug)
{
KeySym ks_temp = XkbKeycodeToKeysym (dpy, (KeyCode) parsed_code, 0, 0);
fprintf(stderr, "Assigned mapping from \"%s\" ( keysym 0x%x, "
"key code %d)\n",
XKeysymToString(ks_temp),
(unsigned) ks_temp,
(unsigned) km->from_kc);
}
}
else
{
fprintf (stderr, "Invalid keycode: %s\n", from);
return NULL;
}
fprintf (stderr, "Invalid key: %s\n", token);
return NULL;
}
else
{
if ((ks = XStringToKeysym (from)) == NoSymbol)
{
fprintf (stderr, "Invalid key: %s\n", token);
return NULL;
}
km->UseKeyCode = False;
km->from_ks = ks;
km->to_keys = NULL;
if (debug)
{
fprintf(stderr, "Assigned mapping from \"%s\" ( keysym 0x%x, "
"key code %d)\n",
XKeysymToString (km->from_ks),
(unsigned) km->from_ks,
(unsigned) XKeysymToKeycode (dpy, km->from_ks));
}
}
km->from = ks;
km->to_keys = NULL;
for(;;)
{
@ -487,58 +398,31 @@ KeyMap_t *parse_token (Display *dpy, char *token, Bool debug)
if (key == NULL)
break;
if (!strncmp (key, "#", 1)
&& strsep (&key, "#") != NULL)
if ((ks = XStringToKeysym (key)) == NoSymbol)
{
errno = 0;
parsed_code = strtoul (key, NULL, 0); /* dec, oct, hex automatically */
if (!(errno == 0
&& parsed_code <=255
&& XkbKeycodeToKeysym (dpy, (KeyCode) parsed_code, 0, 0) != NoSymbol))
{
fprintf (stderr, "Invalid keycode: %s\n", key);
return NULL;
}
code = (KeyCode) parsed_code;
}
else
{
if ((ks = XStringToKeysym (key)) == NoSymbol)
{
fprintf (stderr, "Invalid key: %s\n", key);
return NULL;
}
code = XKeysymToKeycode (dpy, ks);
if (code == 0)
{
fprintf (stderr, "WARNING: No keycode found for keysym "
"%s (0x%x) in mapping %s. Ignoring this "
"mapping.\n", key, (unsigned int)ks, token);
return NULL;
}
fprintf (stderr, "Invalid key: %s\n", key);
return NULL;
}
code = XKeysymToKeycode (dpy, ks);
if (code == 0)
{
fprintf (stderr, "WARNING: No keycode found for keysym "
"%s (0x%x) in mapping %s. Ignoring this "
"mapping.\n", key, (unsigned int)ks, token);
return NULL;
}
km->to_keys = key_add_key (km->to_keys, code);
if (debug)
{
KeySym ks_temp = XkbKeycodeToKeysym (dpy, code, 0, 0);
fprintf(stderr, "to \"%s\" (keysym 0x%x, key code %d)\n",
XKeysymToString(ks_temp),
(unsigned) ks_temp,
(unsigned) code);
}
}
}
else
fprintf (stderr, "WARNING: Mapping without = has no effect: '%s'\n", token);
return km;
}
KeyMap_t *parse_mapping (Display *ctrl_conn, char *mapping, Bool debug)
KeyMap_t *parse_mapping (Display *ctrl_conn, char *mapping)
{
char *token;
KeyMap_t *rval, *km, *nkm;
@ -551,7 +435,7 @@ KeyMap_t *parse_mapping (Display *ctrl_conn, char *mapping, Bool debug)
if (token == NULL)
break;
nkm = parse_token (ctrl_conn, token, debug);
nkm = parse_token (ctrl_conn, token);
if (nkm != NULL)
{
@ -567,28 +451,3 @@ KeyMap_t *parse_mapping (Display *ctrl_conn, char *mapping, Bool debug)
return rval;
}
void delete_mapping (KeyMap_t *map)
{
while (map != NULL) {
KeyMap_t *next = map->next;
delete_keys (map->to_keys);
free (map);
map = next;
}
}
void delete_keys (Key_t *keys)
{
while (keys != NULL) {
Key_t *next = keys->next;
free (keys);
keys = next;
}
}
void print_usage (const char *program_name)
{
fprintf (stdout, "Usage: %s [-d] [-f] [-t timeout_ms] [-e <mapping>]\n", program_name);
fprintf (stdout, "Runs as a daemon unless -d or -f flag is set\n");
}