Compare commits

..

1 Commits

Author SHA1 Message Date
Jacopo De Simoi 98c10fe212 Be more lenient with compose+shift 6 years ago
  1. 10
      Makefile
  2. 69
      README.md
  3. 96
      xcape.1
  4. 187
      xcape.c

@ -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

@ -12,38 +12,24 @@ 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 pkg-config
$ 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>`
@ -56,44 +42,25 @@ 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 "[").
You can also specify keys in decimal (prefix `#`), octal (`#0`), or
hexadecimal (`#0x`). They will be interpreted as keycodes unless no corresponding
You can also specify ModKey in decimal (prefix `#`), octal (`#0`), or
hexadecimal (`#0x`). It will be interpreted as a keycode unless no corresponding
key name is found.
#### 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.
1. 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.
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"
2. If your `s` key has the code `42` and your `l` key `43` and you have set both
to `AltGr` (a.k.a. ISO_Level3_Shift) with xmodmap, then this will generate the
ordinary letters when pressed and released on their own. But pressed together
with another key, the `s` or `l` key will produce `AltGr`. So, depending on your
keyboard layout, you can compose e.g. `@`, `{` or `³` easily when touch-typing.
xcape -e '#42=s;#43=l'
Note regarding xmodmap

@ -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)

@ -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
@ -44,7 +44,7 @@ typedef struct _Key_t
typedef struct _KeyMap_t
{
Bool UseKeyCode; /* (for from) instead of KeySym; ignore latter */
Bool UseKeyCode; // (for from) instead of KeySym; ignore latter
KeySym from_ks;
KeyCode from_kc;
Key_t *to_keys;
@ -62,11 +62,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;
/************************************************************************
@ -78,47 +78,29 @@ void intercept (XPointer user_data, XRecordInterceptData *data);
KeyMap_t *parse_mapping (Display *ctrl_conn, char *mapping, Bool debug);
void delete_mapping (KeyMap_t *map);
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,36 +110,24 @@ 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);
@ -187,12 +157,9 @@ int main (int argc, char **argv)
self->map = parse_mapping (self->ctrl_conn, mapping, self->debug);
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 +170,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 +193,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 +221,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 +230,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 +267,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,10 +281,13 @@ 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)
{
@ -355,8 +319,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,13 +357,9 @@ 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)
{
if ((km->UseKeyCode == False
&& XkbKeycodeToKeysym (self->ctrl_conn, key_code, 0, 0)
@ -411,7 +369,7 @@ void intercept (XPointer user_data, XRecordInterceptData *data)
{
handle_key (self, km, mouse_pressed, key_event);
}
else if (km->pressed && (key_event == KeyPress || key_event == ButtonPress))
else if (km->pressed && key_event == KeyPress)
{
/* We should check if the pressed key is a modifier before marking the key as used. */
if (key_code != 50) /* hack; try if it works */
@ -424,7 +382,6 @@ void intercept (XPointer user_data, XRecordInterceptData *data)
}
exit:
XUnlockDisplay (self->ctrl_conn);
XRecordFreeData (data);
}
@ -434,8 +391,8 @@ KeyMap_t *parse_token (Display *dpy, char *token, Bool debug)
KeyMap_t *km = NULL;
KeySym ks;
char *from, *to, *key;
KeyCode code; /* keycode */
long parsed_code; /* parsed keycode value */
KeyCode code; // keycode (to)
long fromcode; // keycode (from)
to = token;
from = strsep (&to, "=");
@ -447,17 +404,17 @@ KeyMap_t *parse_token (Display *dpy, char *token, Bool debug)
&& strsep (&from, "#") != NULL)
{
errno = 0;
parsed_code = strtoul (from, NULL, 0); /* dec, oct, hex automatically */
fromcode = strtoul (from, NULL, 0); // dec, oct, hex automatically
if (errno == 0
&& parsed_code <=255
&& XkbKeycodeToKeysym (dpy, (KeyCode) parsed_code, 0, 0) != NoSymbol)
&& fromcode <=255
&& XkbKeycodeToKeysym (dpy, (KeyCode) fromcode, 0, 0) != NoSymbol)
{
km->UseKeyCode = True;
km->from_kc = (KeyCode) parsed_code;
km->from_kc = (KeyCode) fromcode;
if (debug)
{
KeySym ks_temp = XkbKeycodeToKeysym (dpy, (KeyCode) parsed_code, 0, 0);
fprintf(stderr, "Assigned mapping from \"%s\" ( keysym 0x%x, "
KeySym ks_temp = XkbKeycodeToKeysym (dpy, (KeyCode) fromcode, 0, 0);
fprintf(stderr, "Assigned mapping from from \"%s\" ( keysym 0x%x, "
"key code %d)\n",
XKeysymToString(ks_temp),
(unsigned) ks_temp,
@ -498,47 +455,28 @@ 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;
fprintf (stderr, "Invalid key: %s\n", key);
return NULL;
}
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;
}
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);
key,
(unsigned) XStringToKeysym (key),
(unsigned) code);
}
}
}
@ -578,28 +516,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");
}

Loading…
Cancel
Save