Add support for UTF-8 key bindings

A new function keys_wgetch() reads full UTF-8 characters instead of
single ASCII characters only.

Key bindings for regular ASCII characters are stored in a hash map while
the actions of keys with higher code points are stored in a linked list
for space efficiency.

The key serialization methods are updated to handle UTF-8 characters as
well; extended UTF-8 characters (characters not in the ASCII range) are
serialized by using the hexadecimal representation of the corresponding
code points (e.g. "U+00E4").

Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
This commit is contained in:
Lukas Fleischer 2017-08-30 16:27:29 +02:00
parent 8544e4a570
commit 578091f051
3 changed files with 112 additions and 30 deletions

View File

@ -230,6 +230,7 @@
((unsigned char)ch >= 0xF0 ? 4 : \ ((unsigned char)ch >= 0xF0 ? 4 : \
((unsigned char)ch >= 0xE0 ? 3 : \ ((unsigned char)ch >= 0xE0 ? 3 : \
((unsigned char)ch >= 0xC0 ? 2 : 1))))) ((unsigned char)ch >= 0xC0 ? 2 : 1)))))
#define UTF8_ISMULTI(ch) ((unsigned char)ch >= 0x80)
#define UTF8_ISCONT(ch) ((unsigned char)ch >= 0x80 && \ #define UTF8_ISCONT(ch) ((unsigned char)ch >= 0x80 && \
(unsigned char)ch <= 0xBF) (unsigned char)ch <= 0xBF)
@ -871,11 +872,12 @@ void keys_free(void);
void keys_dump_defaults(char *); void keys_dump_defaults(char *);
const char *keys_get_label(enum key); const char *keys_get_label(enum key);
enum key keys_get_action(int); enum key keys_get_action(int);
int keys_wgetch(WINDOW *);
enum key keys_get(WINDOW *, int *, int *); enum key keys_get(WINDOW *, int *, int *);
int keys_assign_binding(int, enum key); int keys_assign_binding(int, enum key);
void keys_remove_binding(int, enum key); void keys_remove_binding(int, enum key);
int keys_str2int(const char *); int keys_str2int(const char *);
const char *keys_int2str(int); char *keys_int2str(int);
int keys_action_count_keys(enum key); int keys_action_count_keys(enum key);
const char *keys_action_firstkey(enum key); const char *keys_action_firstkey(enum key);
const char *keys_action_nkey(enum key, int); const char *keys_action_nkey(enum key, int);

View File

@ -972,10 +972,10 @@ void custom_keys_config(void)
(col - WINCOL) / 2, (col - WINCOL) / 2,
_("Press the key you want to assign to:"), _("Press the key you want to assign to:"),
keys_get_label(selrow), 0); keys_get_label(selrow), 0);
ch = wgetch(grabwin); ch = keys_wgetch(grabwin);
/* First check if this key would be recognized by calcurse. */ /* First check if this key would be recognized by calcurse. */
if (keys_str2int(keys_int2str(ch)) == -1) { if (ch < 0) {
not_recognized = 1; not_recognized = 1;
WARN_MSG(_("This key is not yet recognized by calcurse, " WARN_MSG(_("This key is not yet recognized by calcurse, "
"please choose another one.")); "please choose another one."));

View File

@ -50,6 +50,13 @@ struct keydef_s {
static llist_t keys[NBKEYS]; static llist_t keys[NBKEYS];
static enum key actions[MAXKEYVAL]; static enum key actions[MAXKEYVAL];
struct key_ext {
int ch;
enum key action;
};
llist_t actions_ext;
#define gettext_noop(s) s #define gettext_noop(s) s
static struct keydef_s keydef[NBKEYS] = { static struct keydef_s keydef[NBKEYS] = {
{ "generic-cancel", "ESC", gettext_noop("Cancel") }, { "generic-cancel", "ESC", gettext_noop("Cancel") },
@ -135,6 +142,7 @@ void keys_init(void)
for (i = 0; i < MAXKEYVAL; i++) for (i = 0; i < MAXKEYVAL; i++)
actions[i] = KEY_UNDEF; actions[i] = KEY_UNDEF;
LLIST_INIT(&actions_ext);
for (i = 0; i < NBKEYS; i++) for (i = 0; i < NBKEYS; i++)
LLIST_INIT(&keys[i]); LLIST_INIT(&keys[i]);
} }
@ -179,12 +187,56 @@ const char *keys_get_label(enum key key)
return keydef[key].label; return keydef[key].label;
} }
static int key_ext_hasch(struct key_ext *k, void *cbdata)
{
return (k->ch == *((int *)cbdata));
}
enum key keys_get_action(int pressed) enum key keys_get_action(int pressed)
{ {
if (pressed < 0 || pressed > MAXKEYVAL) if (pressed < 0) {
return -1; return -1;
else } else if (pressed > MAXKEYVAL) {
llist_item_t *i = LLIST_FIND_FIRST(&actions_ext, &pressed,
key_ext_hasch);
if (!i)
return KEY_UNDEF;
struct key_ext *k = LLIST_GET_DATA(i);
return k->action;
} else {
return actions[pressed]; return actions[pressed];
}
}
int keys_wgetch(WINDOW *win)
{
int ch, i;
char buf[UTF8_MAXLEN];
ch = wgetch(win);
if (ch > 255) {
if (ch == KEY_UP || ch == KEY_DOWN || ch == KEY_LEFT ||
ch == KEY_RIGHT || ch == KEY_HOME || ch == KEY_END) {
return ch;
}
return -1;
}
if (UTF8_ISMULTI(ch) && !UTF8_ISCONT(ch)) {
buf[0] = ch;
for (i = 1; i < UTF8_LENGTH(ch); i++) {
ch = wgetch(win);
if (!UTF8_ISCONT(ch))
return -1;
buf[i] = ch;
}
return utf8_ord(buf);
} else {
return ch;
}
} }
enum key keys_get(WINDOW *win, int *count, int *reg) enum key keys_get(WINDOW *win, int *count, int *reg)
@ -196,7 +248,7 @@ enum key keys_get(WINDOW *win, int *count, int *reg)
*reg = 0; *reg = 0;
do { do {
*count = *count * 10 + ch - '0'; *count = *count * 10 + ch - '0';
ch = wgetch(win); ch = keys_wgetch(win);
} }
while ((ch == '0' && *count > 0) while ((ch == '0' && *count > 0)
|| (ch >= '1' && ch <= '9')); || (ch >= '1' && ch <= '9'));
@ -205,7 +257,7 @@ enum key keys_get(WINDOW *win, int *count, int *reg)
*count = 1; *count = 1;
if (ch == '"') { if (ch == '"') {
ch = wgetch(win); ch = keys_wgetch(win);
if (ch >= '1' && ch <= '9') { if (ch >= '1' && ch <= '9') {
*reg = ch - '1' + 1; *reg = ch - '1' + 1;
} else if (ch >= 'a' && ch <= 'z') { } else if (ch >= 'a' && ch <= 'z') {
@ -213,10 +265,10 @@ enum key keys_get(WINDOW *win, int *count, int *reg)
} else if (ch == '_') { } else if (ch == '_') {
*reg = REG_BLACK_HOLE; *reg = REG_BLACK_HOLE;
} }
ch = wgetch(win); ch = keys_wgetch(win);
} }
} else { } else {
ch = wgetch(win); ch = keys_wgetch(win);
} }
switch (ch) { switch (ch) {
@ -232,45 +284,64 @@ static void add_key_str(enum key action, int key)
if (action > NBKEYS) if (action > NBKEYS)
return; return;
LLIST_ADD(&keys[action], mem_strdup(keys_int2str(key))); LLIST_ADD(&keys[action], keys_int2str(key));
} }
int keys_assign_binding(int key, enum key action) int keys_assign_binding(int key, enum key action)
{ {
if (key < 0 || key > MAXKEYVAL || actions[key] != KEY_UNDEF) { if (key < 0 || actions[key] != KEY_UNDEF) {
return 1; return 1;
} else if (key > MAXKEYVAL) {
struct key_ext *k = mem_malloc(sizeof(struct key_ext));
k->ch = key;
k->action = action;
LLIST_ADD(&actions_ext, k);
} else { } else {
actions[key] = action; actions[key] = action;
add_key_str(action, key);
} }
add_key_str(action, key);
return 0; return 0;
} }
static void del_key_str(enum key action, int key) static void del_key_str(enum key action, int key)
{ {
llist_item_t *i; llist_item_t *i;
char oldstr[BUFSIZ]; char *oldstr = keys_int2str(key);;
if (action > NBKEYS) if (action > NBKEYS)
return; return;
strncpy(oldstr, keys_int2str(key), BUFSIZ);
LLIST_FOREACH(&keys[action], i) { LLIST_FOREACH(&keys[action], i) {
if (strcmp(LLIST_GET_DATA(i), oldstr) == 0) { if (strcmp(LLIST_GET_DATA(i), oldstr) == 0) {
LLIST_REMOVE(&keys[action], i); LLIST_REMOVE(&keys[action], i);
return; goto cleanup;
} }
} }
cleanup:
mem_free(oldstr);
} }
void keys_remove_binding(int key, enum key action) void keys_remove_binding(int key, enum key action)
{ {
if (key >= 0 && key <= MAXKEYVAL) { if (key < 0)
return;
if (key <= MAXKEYVAL) {
actions[key] = KEY_UNDEF; actions[key] = KEY_UNDEF;
del_key_str(action, key); } else {
llist_item_t *i = LLIST_FIND_FIRST(&actions_ext, &key,
key_ext_hasch);
if (i) {
struct key_ext *k = LLIST_GET_DATA(i);
LLIST_REMOVE(&actions_ext, i);
mem_free(k);
}
} }
del_key_str(action, key);
} }
int keys_str2int(const char *key) int keys_str2int(const char *key)
@ -285,6 +356,8 @@ int keys_str2int(const char *key)
return CTRL((int)key[1]); return CTRL((int)key[1]);
else if (starts_with(key, "C-")) else if (starts_with(key, "C-"))
return CTRL((int)key[strlen("C-")]); return CTRL((int)key[strlen("C-")]);
else if (starts_with(key, "U+"))
return strtol(&key[2], NULL, 16);
else if (!strcmp(key, "TAB")) else if (!strcmp(key, "TAB"))
return TAB; return TAB;
else if (!strcmp(key, "ESC")) else if (!strcmp(key, "ESC"))
@ -307,29 +380,36 @@ int keys_str2int(const char *key)
return -1; return -1;
} }
const char *keys_int2str(int key) char *keys_int2str(int key)
{ {
char *res;
switch (key) { switch (key) {
case TAB: case TAB:
return "TAB"; return strdup("TAB");
case SPACE: case SPACE:
return "SPC"; return strdup("SPC");
case ESCAPE: case ESCAPE:
return "ESC"; return strdup("ESC");
case KEY_UP: case KEY_UP:
return "UP"; return strdup("UP");
case KEY_DOWN: case KEY_DOWN:
return "DWN"; return strdup("DWN");
case KEY_LEFT: case KEY_LEFT:
return "LFT"; return strdup("LFT");
case KEY_RIGHT: case KEY_RIGHT:
return "RGT"; return strdup("RGT");
case KEY_HOME: case KEY_HOME:
return "KEY_HOME"; return strdup("KEY_HOME");
case KEY_END: case KEY_END:
return "KEY_END"; return strdup("KEY_END");
default: }
return (char *)keyname(key);
if (key >= 0x80) {
asprintf(&res, "U+%04X", key);
return res;
} else {
return strdup((char *)keyname(key));
} }
} }