Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration file support. #101

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ Then run:

Usage
-----
$ xcape [-d] [-f] [-t <timeout ms>] [-e <map-expression>]
$ xcape [file ...] [-d] [-f] [-t <timeout ms>] [-e <map-expression>]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if we used an option prefix. Since -f is taken I suggest using -F. And an additional -F for every extra file.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another idea is to use -c for "config"


### `file ...`

Configuration files containing expressions. Each line is an expression
following the form `'ModKey=Key[|OtherKey]`. Comments may be introduced
via the `#` character, which will ignore the proceeding line. To read
from standard input, you may use the `-` character.

### `-d`

Expand Down Expand Up @@ -68,6 +75,18 @@ key name is found.

xcape -e 'Shift_L=Escape;Control_L=Control_L|O'

+ This configuration file produces the same behavior as above.

# map.xcape
# Map left shift to Escape
Shift_L=Escape
# Map Left control to Ctrl-O
Control_L=Control_L|O

which can then be read via xcape using the following command

xcape map.xcape

+ 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
Expand Down
8 changes: 8 additions & 0 deletions xcape.1
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ xcape \- use a modifier key as another key

.SH SYNOPSIS
.B xcape
[file ...]
[\fB-d\fR]
[\fB-f\fR]
[\fB-t\fR \fItimeout\fR]
Expand All @@ -17,6 +18,13 @@ key in place of \fIControl_L\fR (Left Control).

.SH OPTIONS
.TP
.BR file\ ...
Configuration files containing expressions. Each line is an expression
following the form \'\fBModKey\fR=\fBKey\fR[|\fBOtherKey\fR]\'.
Comments may be introduced via the '#' character, which will
ignore the proceeding line. To read from standard input, you may use
the '\-' character.
.TP
.BR \-d
Debug mode. Will run as a foreground process and print debug information.
.TP
Expand Down
167 changes: 157 additions & 10 deletions xcape.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <sys/time.h>
#include <signal.h>
Expand Down Expand Up @@ -76,6 +77,8 @@ void *sig_handler (void *user_data);

void intercept (XPointer user_data, XRecordInterceptData *data);

KeyMap_t *parse_confs (Display *ctrl_conn, char **files, size_t n_confs, Bool debug);

KeyMap_t *parse_mapping (Display *ctrl_conn, char *mapping, Bool debug);

void delete_mapping (KeyMap_t *map);
Expand All @@ -96,7 +99,9 @@ int main (int argc, char **argv)
int dummy, ch;

static char default_mapping[] = "Control_L=Escape";
char *mapping = default_mapping;
char *mapping = NULL;
char **conf_files = NULL;
size_t n_conf = 0;

XRecordRange *rec_range = XRecordAllocRange();
XRecordClientSpec client_spec = XRecordAllClients;
Expand Down Expand Up @@ -147,9 +152,8 @@ int main (int argc, char **argv)

if (optind < argc)
{
fprintf (stderr, "Not a command line option: '%s'\n", argv[optind]);
print_usage (argv[0]);
return EXIT_SUCCESS;
conf_files = argv + optind;
n_conf = argc - optind;
}

if (!XInitThreads ())
Expand Down Expand Up @@ -184,12 +188,56 @@ int main (int argc, char **argv)
exit (EXIT_FAILURE);
}

self->map = parse_mapping (self->ctrl_conn, mapping, self->debug);
/* This reduces error-prone logic when rearranging the parsing order,
* as we don't need to check for self->map existence */

KeyMap_t **current_map = &self->map;

/* parse mappings given by -e first */

if (self->map == NULL)
if (mapping)
{
fprintf (stderr, "Failed to parse_mapping\n");
exit (EXIT_FAILURE);
KeyMap_t *emapping = parse_mapping (self->ctrl_conn, mapping, self->debug);

if (emapping == NULL)
{
fprintf (stderr, "Failed to parse_mapping\n");
exit (EXIT_FAILURE);
}

*current_map = emapping;
current_map = &(*current_map)->next;
}

/* parse config files */

if (conf_files)
{
KeyMap_t *conf_mapping = parse_confs (self->ctrl_conn, conf_files, n_conf, self->debug);

if (conf_mapping == NULL)
{
fprintf (stderr, "Failed to parse_confs\n");
exit (EXIT_FAILURE);
}

*current_map = conf_mapping;
current_map = &(*current_map)->next;
}

/* if there were no config files or mappings supplied, try the default mapping */

if (!conf_files && !mapping)
{
KeyMap_t *def_mapping = parse_mapping (self->ctrl_conn, default_mapping, self->debug);

if (def_mapping == NULL)
{
fprintf (stderr, "Failed to parse_mapping default\n");
exit (EXIT_FAILURE);
}

*current_map = def_mapping;
}

if (self->foreground != True)
Expand Down Expand Up @@ -438,7 +486,7 @@ KeyMap_t *parse_token (Display *dpy, char *token, Bool debug)
errno = 0;
parsed_code = strtoul (from, NULL, 0); /* dec, oct, hex automatically */
if (errno == 0
&& parsed_code <=255
&& parsed_code <= 255
&& XkbKeycodeToKeysym (dpy, (KeyCode) parsed_code, 0, 0) != NoSymbol)
{
km->UseKeyCode = True;
Expand Down Expand Up @@ -538,6 +586,105 @@ KeyMap_t *parse_token (Display *dpy, char *token, Bool debug)
return km;
}

char *read_line (FILE *file)
{
size_t cap = 1024;
size_t nlen = 0;
char *line = realloc (NULL, cap*sizeof(char));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not malloc?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woops! I'll fix that right away.


int c = EOF;
int reading = 1;
while (reading)
{
c = fgetc(file);
if (nlen == cap)
{
cap *= 2;
line = realloc (line, cap*sizeof(char));
}
switch (c)
{
case '\r':
/* check for \r\n */
{
int c = fgetc(file);
if(c != '\n'){
ungetc(c, file);
}
}
break;
case '\n': /* FALLTHROUGH */
case '\0':
line[nlen++] = '\0';
reading = 0;
break;
case EOF:
reading = 0;
break;
default:
line[nlen++] = c;
break;
}
}
if(nlen == 0)
{
free (line);
return NULL;
}
/* shrink down to size */
line = realloc (line, nlen);
return line;
}

KeyMap_t *parse_confs (Display *ctrl_conn, char **files, size_t n_confs, Bool debug)
{
KeyMap_t *rval = NULL;
KeyMap_t **current = &rval;
for (size_t i = 0; i < n_confs; ++i)
{
char *filename = files[i];
FILE *file = NULL;

/* determine if reading from stdin or file */
if (!strcmp (filename, "-"))
{
file = stdin;
}
else
{
file = fopen (filename, "r");
if (file == NULL)
{
fprintf (stderr, "unable to open file %s: %s\n", filename, strerror(errno));
break;
}
}

/* Read file line by line, treating each line as an expression */
char *line = NULL;
while ((line = read_line (file)) != NULL)
{
/* trim leading whitespace */
char *trimmed = line;
while(isspace(*trimmed)) ++trimmed;
/* check for comments or empty lines */
if(*trimmed && *trimmed != '#'){
*current = parse_token (ctrl_conn, trimmed, debug);
if (*current == NULL)
{
break;
}
current = &(*current)->next;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this the same as current = current->next; ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite, since current is a double pointer, and -> only works on one level of indirection.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Could you please add some parenthesis to make it clearer.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

}
free (line);
}

if (file != stdin)
fclose (file);
}
return rval;
}

KeyMap_t *parse_mapping (Display *ctrl_conn, char *mapping, Bool debug)
{
char *token;
Expand Down Expand Up @@ -589,6 +736,6 @@ void delete_keys (Key_t *keys)

void print_usage (const char *program_name)
{
fprintf (stdout, "Usage: %s [-d] [-f] [-t timeout_ms] [-e <mapping>]\n", program_name);
fprintf (stdout, "Usage: %s [file ...] [-d] [-f] [-t timeout_ms] [-e <mapping>]\n", program_name);
fprintf (stdout, "Runs as a daemon unless -d or -f flag is set\n");
}