1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-10-09 05:20:36 -04:00
elinks/src/config/kbdbind.c
Yozo Hida 948d019fc0 Add preferred_document_width option.
Option document.browse.preferred_document_width controls the
width of the document, so that documents are rendered with narrower
width than screen width.  Makes it easier to read paragraphs.

Patch originally from Shalon Wood <dstar@pele.cx>, see bug #1063.

Instead of using max_document_width as the hard limit to the document
width, it uses a soft limit, where if the document does not fit (due to
tables, etc.), then larger width is used.  This reduces the need for
horizontal scrolling for wide documents.

Also added toggle-document-width action to toggle between preferred
width and full screen width.  This is bound to 'M' by default.  Initial
toggle status is determined by document.browse.use_preferred_document_width
option.

During dumps, document.dump.width option is still used.  Perhaps we
should consolidate document.dump.width option with
document.browse.preferred_document_width ?
2017-11-21 16:09:13 +01:00

1009 lines
28 KiB
C

/* Keybinding implementation */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <ctype.h>
#include <string.h>
#include "elinks.h"
#include "bfu/dialog.h"
#include "config/conf.h"
#include "config/dialogs.h"
#include "config/kbdbind.h"
#include "config/options.h"
#include "intl/gettext/libintl.h"
#include "main/event.h"
#include "main/module.h"
#include "terminal/kbd.h"
#include "util/conv.h"
#include "util/memory.h"
#include "util/string.h"
/* Fix namespace clash on MacOS. */
#define table table_elinks
#define ACTION_(map, name, action, caption, flags) \
{ name, ACT_##map##_##action, KEYMAP_ID, caption, flags }
#undef KEYMAP_ID
#define KEYMAP_ID KEYMAP_MAIN
static const struct action main_action_table[MAIN_ACTIONS + 1] = {
#include "config/actions-main.inc"
};
#undef KEYMAP_ID
#define KEYMAP_ID KEYMAP_EDIT
static const struct action edit_action_table[EDIT_ACTIONS + 1] = {
#include "config/actions-edit.inc"
};
#undef KEYMAP_ID
#define KEYMAP_ID KEYMAP_MENU
static const struct action menu_action_table[MENU_ACTIONS + 1] = {
#include "config/actions-menu.inc"
};
static const struct action_list action_table[KEYMAP_MAX] = {
{ main_action_table, sizeof_array(main_action_table) },
{ edit_action_table, sizeof_array(edit_action_table) },
{ menu_action_table, sizeof_array(menu_action_table) },
};
#undef KEYMAP_ID
#undef ACTION_
static struct keymap keymap_table[] = {
{ "main", KEYMAP_MAIN, N_("Main mapping") },
{ "edit", KEYMAP_EDIT, N_("Edit mapping") },
{ "menu", KEYMAP_MENU, N_("Menu mapping") },
};
static LIST_OF(struct keybinding) keymaps[KEYMAP_MAX];
static void add_default_keybindings(void);
static int
delete_keybinding(enum keymap_id keymap_id, struct term_event_keyboard *kbd)
{
struct keybinding *keybinding;
foreach (keybinding, keymaps[keymap_id]) {
int was_default = 0;
if (!kbd_key_is(&keybinding->kbd, kbd->key))
continue;
if (!kbd_modifier_is(&keybinding->kbd, kbd->modifier))
continue;
if (keybinding->flags & KBDB_DEFAULT_KEY) {
keybinding->flags &= ~KBDB_DEFAULT_KEY;
was_default = 1;
}
free_keybinding(keybinding);
return 1 + was_default;
}
return 0;
}
static int keybinding_is_default(struct keybinding *keybinding);
struct keybinding *
add_keybinding(enum keymap_id keymap_id, action_id_T action_id,
struct term_event_keyboard *kbd, int event)
{
struct keybinding *keybinding;
struct listbox_item *root;
int is_default;
is_default = (delete_keybinding(keymap_id, kbd) == 2);
keybinding = mem_calloc(1, sizeof(*keybinding));
if (!keybinding) return NULL;
keybinding->keymap_id = keymap_id;
keybinding->action_id = action_id;
copy_struct(&keybinding->kbd, kbd);
keybinding->event = event;
keybinding->flags = is_default * KBDB_DEFAULT_KEY;
if (keybinding_is_default(keybinding))
keybinding->flags |= KBDB_DEFAULT_BINDING;
object_nolock(keybinding, "keybinding");
add_to_list(keymaps[keymap_id], keybinding);
if (action_id == ACT_MAIN_NONE) {
/* We don't want such a listbox_item, do we? */
return NULL; /* Or goto. */
}
root = get_keybinding_action_box_item(keymap_id, action_id);
if (!root) {
return NULL; /* Or goto ;-). */
}
keybinding->box_item = add_listbox_leaf(&keybinding_browser, root, keybinding);
return keybinding;
}
void
free_keybinding(struct keybinding *keybinding)
{
if (keybinding->box_item) {
done_listbox_item(&keybinding_browser, keybinding->box_item);
keybinding->box_item = NULL;
}
#ifdef CONFIG_SCRIPTING
/* TODO: unref function must be implemented. This is part of bug 810. */
/* if (keybinding->event != EVENT_NONE)
scripting_unref(keybinding->event); */
#endif
if (keybinding->flags & KBDB_DEFAULT_KEY) {
/* We cannot just delete a default keybinding, instead we have
* to rebind it to ACT_MAIN_NONE so that it gets written so to the
* config file. */
keybinding->flags &= ~KBDB_DEFAULT_BINDING;
keybinding->action_id = ACT_MAIN_NONE;
return;
}
del_from_list(keybinding);
mem_free(keybinding);
}
int
keybinding_exists(enum keymap_id keymap_id, struct term_event_keyboard *kbd,
action_id_T *action_id)
{
struct keybinding *keybinding;
foreach (keybinding, keymaps[keymap_id]) {
if (!kbd_key_is(&keybinding->kbd, kbd->key))
continue;
if (!kbd_modifier_is(&keybinding->kbd, kbd->modifier))
continue;
if (action_id) *action_id = keybinding->action_id;
return 1;
}
return 0;
}
action_id_T
kbd_action(enum keymap_id keymap_id, struct term_event *ev, int *event)
{
struct keybinding *keybinding;
if (ev->ev != EVENT_KBD) return -1;
keybinding = kbd_ev_lookup(keymap_id, &ev->info.keyboard, event);
return keybinding ? keybinding->action_id : -1;
}
struct keybinding *
kbd_ev_lookup(enum keymap_id keymap_id, struct term_event_keyboard *kbd, int *event)
{
struct keybinding *keybinding;
foreach (keybinding, keymaps[keymap_id]) {
if (!kbd_key_is(&keybinding->kbd, kbd->key))
continue;
if (!kbd_modifier_is(&keybinding->kbd, kbd->modifier))
continue;
if (keybinding->action_id == ACT_MAIN_SCRIPTING_FUNCTION && event)
*event = keybinding->event;
return keybinding;
}
return NULL;
}
static struct keybinding *
kbd_act_lookup(enum keymap_id keymap_id, action_id_T action_id)
{
struct keybinding *keybinding;
foreach (keybinding, keymaps[keymap_id]) {
if (action_id != keybinding->action_id)
continue;
return keybinding;
}
return NULL;
}
struct keybinding *
kbd_nm_lookup(enum keymap_id keymap_id, unsigned char *name)
{
action_id_T action_id = get_action_from_string(keymap_id, name);
if (action_id < 0) return NULL;
return kbd_act_lookup(keymap_id, action_id);
}
static struct keybinding *
kbd_stroke_lookup(enum keymap_id keymap_id, const unsigned char *keystroke_str)
{
struct term_event_keyboard kbd;
if (parse_keystroke(keystroke_str, &kbd) < 0)
return NULL;
return kbd_ev_lookup(keymap_id, &kbd, NULL);
}
/*
* Config file helpers.
*/
static const struct action *
get_action_from_keystroke(enum keymap_id keymap_id,
const unsigned char *keystroke_str)
{
struct keybinding *keybinding = kbd_stroke_lookup(keymap_id,
keystroke_str);
return keybinding ? get_action(keymap_id, keybinding->action_id) : NULL;
}
unsigned char *
get_action_name_from_keystroke(enum keymap_id keymap_id,
const unsigned char *keystroke_str)
{
const struct action *action = get_action_from_keystroke(keymap_id,
keystroke_str);
return action ? action->str : NULL;
}
action_id_T
get_action_from_string(enum keymap_id keymap_id, unsigned char *str)
{
const struct action *action;
assert(keymap_id >= 0 && keymap_id < KEYMAP_MAX);
for (action = action_table[keymap_id].actions; action->str; action++)
if (!strcmp(action->str, str))
return action->num;
return -1;
}
const struct action *
get_action(enum keymap_id keymap_id, action_id_T action_id)
{
assert(keymap_id >= 0 && keymap_id < KEYMAP_MAX);
if (action_id >= 0 && action_id < action_table[keymap_id].num_actions)
return &action_table[keymap_id].actions[action_id];
return NULL;
}
unsigned char *
get_action_name(enum keymap_id keymap_id, action_id_T action_id)
{
const struct action *action = get_action(keymap_id, action_id);
return action ? action->str : NULL;
}
static unsigned char *
get_action_desc(enum keymap_id keymap_id, action_id_T action_id)
{
const struct action *action = get_action(keymap_id, action_id);
return action ? (action->desc ? action->desc : action->str)
: NULL;
}
static struct keymap *
get_keymap(enum keymap_id keymap_id)
{
assert(keymap_id >= 0 && keymap_id < KEYMAP_MAX);
return &keymap_table[keymap_id];
}
static enum keymap_id
get_keymap_id(unsigned char *keymap_str)
{
int keymap_id;
for (keymap_id = 0; keymap_id < KEYMAP_MAX; keymap_id++)
if (!strcmp(keymap_table[keymap_id].str, keymap_str))
return keymap_id;
return KEYMAP_INVALID;
}
unsigned char *
get_keymap_name(enum keymap_id keymap_id)
{
return get_keymap(keymap_id)->str;
}
struct named_key {
const unsigned char *str;
term_event_key_T num;
};
static const struct named_key key_table[] = {
{ "Enter", KBD_ENTER },
{ "Space", ' ' },
{ "Backspace", KBD_BS },
{ "Tab", KBD_TAB },
{ "Escape", KBD_ESC },
{ "Left", KBD_LEFT },
{ "Right", KBD_RIGHT },
{ "Up", KBD_UP },
{ "Down", KBD_DOWN },
{ "Insert", KBD_INS },
{ "Delete", KBD_DEL },
{ "Home", KBD_HOME },
{ "End", KBD_END },
{ "PageUp", KBD_PAGE_UP },
{ "PageDown", KBD_PAGE_DOWN },
{ "F1", KBD_F1 },
{ "F2", KBD_F2 },
{ "F3", KBD_F3 },
{ "F4", KBD_F4 },
{ "F5", KBD_F5 },
{ "F6", KBD_F6 },
{ "F7", KBD_F7 },
{ "F8", KBD_F8 },
{ "F9", KBD_F9 },
{ "F10", KBD_F10 },
{ "F11", KBD_F11 },
{ "F12", KBD_F12 },
{ NULL, KBD_UNDEF }
};
term_event_key_T
read_key(const unsigned char *key_str)
{
const struct named_key *key;
if (key_str[0] && !key_str[1])
return key_str[0];
for (key = key_table; key->str; key++)
if (!c_strcasecmp(key->str, key_str))
return key->num;
return KBD_UNDEF;
}
/** Parse the string @a s as the name of a keystroke.
* Write the parsed key and modifiers to *@a kbd.
* @return >=0 on success, <0 on error.
*
* This function does not support ::KBD_MOD_PASTE, because keystrokes
* that include it should never be bound to actions. */
int
parse_keystroke(const unsigned char *s, struct term_event_keyboard *kbd)
{
kbd->modifier = KBD_MOD_NONE;
while (1) {
if (!c_strncasecmp(s, "Shift", 5) && (s[5] == '-' || s[5] == '+')) {
/* Shift+a == shiFt-a == Shift-a */
kbd->modifier |= KBD_MOD_SHIFT;
s += 6;
} else if (!c_strncasecmp(s, "Ctrl", 4) && (s[4] == '-' || s[4] == '+')) {
/* Ctrl+a == ctRl-a == Ctrl-a */
kbd->modifier |= KBD_MOD_CTRL;
s += 5;
} else if (!c_strncasecmp(s, "Alt", 3) && (s[3] == '-' || s[3] == '+')) {
/* Alt+a == aLt-a == Alt-a */
kbd->modifier |= KBD_MOD_ALT;
s += 4;
} else {
/* No modifier. */
break;
}
}
kbd->key = read_key(s);
if ((kbd->modifier & KBD_MOD_CTRL) != 0
&& is_kbd_character(kbd->key) && kbd->key <= 0x7F) {
/* Convert Ctrl-letter keystrokes to upper case,
* e.g. Ctrl-a to Ctrl-A. Do this because terminals
* typically generate the same ASCII control
* characters regardless of Shift and Caps Lock.
*
* However, that applies only to ASCII letters. For
* other Ctrl-letter combinations, there seems to be
* no standard of what the terminal should generate.
* Because it is possible that the terminal does not
* fold case then, ELinks should treat upper-case and
* lower-case variants of such keystrokes as different
* and let the user bind them to separate actions.
* Besides, toupper() might not understand the charset
* of kbd->key.
*
* FIXME: Actually, it is possible that some terminals
* preserve case in all Ctrl-letter keystrokes, even
* for ASCII letters. In particular, the Win32
* console API looks like it might do this. When the
* terminal handling part of ELinks is eventually
* extended to fully support that, it could be a good
* idea to change parse_keystroke() not to fold case,
* and instead make kbd_ev_lookup() or its callers
* search for different variants of the keystroke if
* the original one is not bound to any action. */
kbd->key = c_toupper(kbd->key);
}
return (kbd->key == KBD_UNDEF) ? -1 : 0;
}
void
add_keystroke_to_string(struct string *str, struct term_event_keyboard *kbd,
int escape)
{
unsigned char key_buffer[3] = "\\x";
const unsigned char *key_string = NULL;
const struct named_key *key;
if (kbd->key == KBD_UNDEF) return;
/* Don't map KBD_MOD_PASTE to "Paste-" because parse_keystroke
* would not understand the result. */
if (kbd->modifier & KBD_MOD_SHIFT)
add_to_string(str, "Shift-");
if (kbd->modifier & KBD_MOD_CTRL)
add_to_string(str, "Ctrl-");
if (kbd->modifier & KBD_MOD_ALT)
add_to_string(str, "Alt-");
for (key = key_table; key->str; key++) {
if (kbd->key == key->num) {
key_string = key->str;
break;
}
}
if (!key_string) {
key_string = key_buffer + 1;
key_buffer[1] = (unsigned char) kbd->key;
if (escape && strchr("'\"\\", kbd->key))
key_string--;
}
add_to_string(str, key_string);
}
void
add_keystroke_action_to_string(struct string *string, action_id_T action_id,
enum keymap_id keymap_id)
{
struct keybinding *keybinding = kbd_act_lookup(keymap_id, action_id);
if (keybinding)
add_keystroke_to_string(string, &keybinding->kbd, 0);
}
unsigned char *
get_keystroke(action_id_T action_id, enum keymap_id keymap_id)
{
struct string keystroke;
if (!init_string(&keystroke)) return NULL;
add_keystroke_action_to_string(&keystroke, action_id, keymap_id);
/* Never return empty string */
if (!keystroke.length) done_string(&keystroke);
return keystroke.source;
}
void
add_actions_to_string(struct string *string, action_id_T action_ids[],
enum keymap_id keymap_id, struct terminal *term)
{
int i;
assert(keymap_id >= 0 && keymap_id < KEYMAP_MAX);
add_format_to_string(string, "%s:\n", _(keymap_table[keymap_id].desc, term));
for (i = 0; action_ids[i] != ACT_MAIN_NONE; i++) {
struct keybinding *keybinding = kbd_act_lookup(keymap_id, action_ids[i]);
int keystrokelen = string->length;
unsigned char *desc = get_action_desc(keymap_id, action_ids[i]);
if (!keybinding) continue;
add_char_to_string(string, '\n');
add_keystroke_to_string(string, &keybinding->kbd, 0);
keystrokelen = string->length - keystrokelen;
add_xchar_to_string(string, ' ', int_max(15 - keystrokelen, 1));
add_to_string(string, _(desc, term));
}
}
static void
init_keymaps(struct module *xxx)
{
int keymap_id;
for (keymap_id = 0; keymap_id < KEYMAP_MAX; keymap_id++)
init_list(keymaps[keymap_id]);
init_keybinding_listboxes(keymap_table, action_table);
add_default_keybindings();
}
static void
free_keymaps(struct module *xxx)
{
int keymap_id;
done_keybinding_listboxes();
for (keymap_id = 0; keymap_id < KEYMAP_MAX; keymap_id++)
free_list(keymaps[keymap_id]);
}
/*
* Bind to Lua function.
*/
#ifdef CONFIG_SCRIPTING
static unsigned char *
bind_key_to_event(unsigned char *ckmap, const unsigned char *ckey, int event)
{
struct term_event_keyboard kbd;
action_id_T action_id;
enum keymap_id keymap_id = get_keymap_id(ckmap);
if (keymap_id == KEYMAP_INVALID)
return gettext("Unrecognised keymap");
if (parse_keystroke(ckey, &kbd) < 0)
return gettext("Error parsing keystroke");
action_id = get_action_from_string(keymap_id, " *scripting-function*");
if (action_id < 0)
return gettext("Unrecognised action (internal error)");
add_keybinding(keymap_id, action_id, &kbd, event);
return NULL;
}
int
bind_key_to_event_name(unsigned char *ckmap, const unsigned char *ckey,
unsigned char *event_name, unsigned char **err)
{
int event_id;
event_id = register_event(event_name);
if (event_id == EVENT_NONE) {
*err = gettext("Error registering event");
return EVENT_NONE;
}
*err = bind_key_to_event(ckmap, ckey, event_id);
return event_id;
}
#endif
/*
* Default keybindings.
*/
struct default_kb {
struct term_event_keyboard kbd;
action_id_T action_id;
};
static struct default_kb default_main_keymap[] = {
{ { ' ', KBD_MOD_NONE }, ACT_MAIN_MOVE_PAGE_DOWN },
{ { '#', KBD_MOD_NONE }, ACT_MAIN_SEARCH_TYPEAHEAD },
{ { '%', KBD_MOD_NONE }, ACT_MAIN_TOGGLE_DOCUMENT_COLORS },
{ { '*', KBD_MOD_NONE }, ACT_MAIN_TOGGLE_DISPLAY_IMAGES },
{ { ',', KBD_MOD_NONE }, ACT_MAIN_LUA_CONSOLE },
{ { '.', KBD_MOD_NONE }, ACT_MAIN_TOGGLE_NUMBERED_LINKS },
{ { '/', KBD_MOD_NONE }, ACT_MAIN_SEARCH },
{ { ':', KBD_MOD_NONE }, ACT_MAIN_EXMODE },
{ { '<', KBD_MOD_NONE }, ACT_MAIN_TAB_PREV },
{ { '<', KBD_MOD_ALT }, ACT_MAIN_TAB_MOVE_LEFT },
{ { '=', KBD_MOD_NONE }, ACT_MAIN_DOCUMENT_INFO },
{ { '>', KBD_MOD_NONE }, ACT_MAIN_TAB_NEXT },
{ { '>', KBD_MOD_ALT }, ACT_MAIN_TAB_MOVE_RIGHT },
{ { '?', KBD_MOD_NONE }, ACT_MAIN_SEARCH_BACK },
{ { 'A', KBD_MOD_NONE }, ACT_MAIN_ADD_BOOKMARK_LINK },
{ { 'A', KBD_MOD_CTRL }, ACT_MAIN_MOVE_DOCUMENT_START },
{ { 'B', KBD_MOD_CTRL }, ACT_MAIN_MOVE_PAGE_UP },
{ { 'C', KBD_MOD_NONE }, ACT_MAIN_CACHE_MANAGER },
{ { 'D', KBD_MOD_NONE }, ACT_MAIN_DOWNLOAD_MANAGER },
{ { 'E', KBD_MOD_NONE }, ACT_MAIN_GOTO_URL_CURRENT_LINK },
{ { 'E', KBD_MOD_CTRL }, ACT_MAIN_MOVE_DOCUMENT_END },
{ { 'F', KBD_MOD_NONE }, ACT_MAIN_FORMHIST_MANAGER },
{ { 'F', KBD_MOD_CTRL }, ACT_MAIN_MOVE_PAGE_DOWN },
{ { 'G', KBD_MOD_NONE }, ACT_MAIN_GOTO_URL_CURRENT },
{ { 'H', KBD_MOD_NONE }, ACT_MAIN_GOTO_URL_HOME },
{ { 'K', KBD_MOD_NONE }, ACT_MAIN_COOKIE_MANAGER },
{ { 'K', KBD_MOD_CTRL }, ACT_MAIN_COOKIES_LOAD },
{ { 'L', KBD_MOD_NONE }, ACT_MAIN_LINK_MENU },
{ { 'L', KBD_MOD_CTRL }, ACT_MAIN_REDRAW },
{ { 'M', KBD_MOD_NONE }, ACT_MAIN_TOGGLE_DOCUMENT_WIDTH },
{ { 'N', KBD_MOD_NONE }, ACT_MAIN_FIND_NEXT_BACK },
{ { 'N', KBD_MOD_CTRL }, ACT_MAIN_SCROLL_DOWN },
{ { 'P', KBD_MOD_CTRL }, ACT_MAIN_SCROLL_UP },
{ { 'Q', KBD_MOD_NONE }, ACT_MAIN_REALLY_QUIT },
{ { 'R', KBD_MOD_CTRL }, ACT_MAIN_RELOAD },
{ { 'T', KBD_MOD_NONE }, ACT_MAIN_OPEN_LINK_IN_NEW_TAB_IN_BACKGROUND },
{ { 'W', KBD_MOD_NONE }, ACT_MAIN_TOGGLE_WRAP_TEXT },
{ { '[', KBD_MOD_NONE }, ACT_MAIN_SCROLL_LEFT },
{ { '\'', KBD_MOD_NONE }, ACT_MAIN_MARK_GOTO },
{ { '\\', KBD_MOD_NONE }, ACT_MAIN_TOGGLE_HTML_PLAIN },
{ { ']', KBD_MOD_NONE }, ACT_MAIN_SCROLL_RIGHT },
{ { 'a', KBD_MOD_NONE }, ACT_MAIN_ADD_BOOKMARK },
{ { 'b', KBD_MOD_NONE }, ACT_MAIN_MOVE_PAGE_UP },
{ { 'c', KBD_MOD_NONE }, ACT_MAIN_TAB_CLOSE },
{ { 'd', KBD_MOD_NONE }, ACT_MAIN_LINK_DOWNLOAD },
{ { 'e', KBD_MOD_NONE }, ACT_MAIN_TAB_MENU },
{ { 'f', KBD_MOD_NONE }, ACT_MAIN_FRAME_MAXIMIZE },
{ { 'g', KBD_MOD_NONE }, ACT_MAIN_GOTO_URL },
{ { 'h', KBD_MOD_NONE }, ACT_MAIN_HISTORY_MANAGER },
{ { 'k', KBD_MOD_NONE }, ACT_MAIN_KEYBINDING_MANAGER },
{ { 'l', KBD_MOD_NONE }, ACT_MAIN_JUMP_TO_LINK },
{ { 'm', KBD_MOD_NONE }, ACT_MAIN_MARK_SET },
{ { 'n', KBD_MOD_NONE }, ACT_MAIN_FIND_NEXT },
{ { 'o', KBD_MOD_NONE }, ACT_MAIN_OPTIONS_MANAGER },
{ { 'q', KBD_MOD_NONE }, ACT_MAIN_QUIT },
{ { 'r', KBD_MOD_NONE }, ACT_MAIN_LINK_DOWNLOAD_RESUME },
{ { 's', KBD_MOD_NONE }, ACT_MAIN_BOOKMARK_MANAGER },
{ { 't', KBD_MOD_NONE }, ACT_MAIN_OPEN_NEW_TAB },
{ { 'u', KBD_MOD_NONE }, ACT_MAIN_HISTORY_MOVE_FORWARD },
{ { 'v', KBD_MOD_NONE }, ACT_MAIN_VIEW_IMAGE },
{ { 'x', KBD_MOD_NONE }, ACT_MAIN_LINK_FOLLOW_RELOAD },
{ { 'z', KBD_MOD_NONE }, ACT_MAIN_ABORT_CONNECTION },
{ { '{', KBD_MOD_NONE }, ACT_MAIN_SCROLL_LEFT },
{ { '|', KBD_MOD_NONE }, ACT_MAIN_HEADER_INFO },
{ { '}', KBD_MOD_NONE }, ACT_MAIN_SCROLL_RIGHT },
{ { KBD_BS, KBD_MOD_NONE }, ACT_MAIN_BACKSPACE_PREFIX },
{ { KBD_DEL, KBD_MOD_NONE }, ACT_MAIN_SCROLL_DOWN },
{ { KBD_DOWN, KBD_MOD_NONE }, ACT_MAIN_MOVE_LINK_NEXT },
{ { KBD_END, KBD_MOD_NONE }, ACT_MAIN_MOVE_DOCUMENT_END },
{ { KBD_ENTER, KBD_MOD_NONE }, ACT_MAIN_LINK_FOLLOW },
{ { KBD_ENTER, KBD_MOD_CTRL }, ACT_MAIN_LINK_FOLLOW_RELOAD },
{ { KBD_ESC, KBD_MOD_NONE }, ACT_MAIN_MENU },
{ { KBD_F10, KBD_MOD_NONE }, ACT_MAIN_FILE_MENU },
{ { KBD_F9, KBD_MOD_NONE }, ACT_MAIN_MENU },
{ { KBD_HOME, KBD_MOD_NONE }, ACT_MAIN_MOVE_DOCUMENT_START },
{ { KBD_INS, KBD_MOD_NONE }, ACT_MAIN_SCROLL_UP },
{ { KBD_INS, KBD_MOD_CTRL }, ACT_MAIN_COPY_CLIPBOARD },
{ { KBD_LEFT, KBD_MOD_NONE }, ACT_MAIN_HISTORY_MOVE_BACK },
{ { KBD_PAGE_DOWN, KBD_MOD_NONE }, ACT_MAIN_MOVE_PAGE_DOWN },
{ { KBD_PAGE_UP, KBD_MOD_NONE }, ACT_MAIN_MOVE_PAGE_UP },
{ { KBD_RIGHT, KBD_MOD_NONE }, ACT_MAIN_LINK_FOLLOW },
{ { KBD_RIGHT, KBD_MOD_CTRL }, ACT_MAIN_LINK_FOLLOW_RELOAD },
{ { KBD_TAB, KBD_MOD_NONE }, ACT_MAIN_FRAME_NEXT },
{ { KBD_TAB, KBD_MOD_ALT }, ACT_MAIN_FRAME_PREV },
{ { KBD_TAB, KBD_MOD_SHIFT}, ACT_MAIN_FRAME_PREV },
{ { KBD_UP, KBD_MOD_NONE }, ACT_MAIN_MOVE_LINK_PREV },
{ { 0, 0 }, 0 }
};
static struct default_kb default_edit_keymap[] = {
{ { '<', KBD_MOD_ALT }, ACT_EDIT_BEGINNING_OF_BUFFER },
{ { '>', KBD_MOD_ALT }, ACT_EDIT_END_OF_BUFFER },
{ { 'A', KBD_MOD_CTRL }, ACT_EDIT_HOME },
{ { 'b', KBD_MOD_ALT }, ACT_EDIT_MOVE_BACKWARD_WORD },
{ { 'D', KBD_MOD_CTRL }, ACT_EDIT_DELETE },
{ { 'E', KBD_MOD_CTRL }, ACT_EDIT_END },
{ { 'f', KBD_MOD_ALT }, ACT_EDIT_MOVE_FORWARD_WORD },
{ { 'H', KBD_MOD_CTRL }, ACT_EDIT_BACKSPACE },
{ { 'K', KBD_MOD_CTRL }, ACT_EDIT_KILL_TO_EOL },
{ { 'L', KBD_MOD_CTRL }, ACT_EDIT_REDRAW },
{ { 'r', KBD_MOD_ALT }, ACT_EDIT_SEARCH_TOGGLE_REGEX },
{ { 'F', KBD_MOD_CTRL }, ACT_EDIT_AUTO_COMPLETE_FILE },
{ { 'R', KBD_MOD_CTRL }, ACT_EDIT_AUTO_COMPLETE_UNAMBIGUOUS },
{ { 'T', KBD_MOD_CTRL }, ACT_EDIT_OPEN_EXTERNAL },
{ { 'U', KBD_MOD_CTRL }, ACT_EDIT_KILL_TO_BOL },
{ { 'V', KBD_MOD_CTRL }, ACT_EDIT_PASTE_CLIPBOARD },
{ { 'W', KBD_MOD_CTRL }, ACT_EDIT_AUTO_COMPLETE },
{ { 'X', KBD_MOD_CTRL }, ACT_EDIT_CUT_CLIPBOARD },
{ { KBD_BS, KBD_MOD_ALT }, ACT_EDIT_KILL_WORD_BACK },
{ { KBD_BS, KBD_MOD_NONE }, ACT_EDIT_BACKSPACE },
{ { KBD_DEL, KBD_MOD_NONE }, ACT_EDIT_DELETE },
{ { KBD_DOWN, KBD_MOD_NONE }, ACT_EDIT_DOWN },
{ { KBD_END, KBD_MOD_NONE }, ACT_EDIT_END },
{ { KBD_ENTER, KBD_MOD_NONE }, ACT_EDIT_ENTER },
{ { KBD_ESC, KBD_MOD_NONE }, ACT_EDIT_CANCEL },
{ { KBD_F4, KBD_MOD_NONE }, ACT_EDIT_OPEN_EXTERNAL },
{ { KBD_HOME, KBD_MOD_NONE }, ACT_EDIT_HOME },
{ { KBD_INS, KBD_MOD_CTRL }, ACT_EDIT_COPY_CLIPBOARD },
{ { KBD_LEFT, KBD_MOD_NONE }, ACT_EDIT_LEFT },
{ { KBD_RIGHT, KBD_MOD_NONE }, ACT_EDIT_RIGHT },
{ { KBD_TAB, KBD_MOD_NONE }, ACT_EDIT_NEXT_ITEM },
{ { KBD_TAB, KBD_MOD_ALT }, ACT_EDIT_PREVIOUS_ITEM },
{ { KBD_TAB, KBD_MOD_SHIFT}, ACT_EDIT_PREVIOUS_ITEM },
{ { KBD_UP, KBD_MOD_NONE }, ACT_EDIT_UP },
{ { 0, 0 }, 0 }
};
static struct default_kb default_menu_keymap[] = {
{ { ' ', KBD_MOD_NONE }, ACT_MENU_SELECT },
{ { '*', KBD_MOD_NONE }, ACT_MENU_MARK_ITEM },
{ { '+', KBD_MOD_NONE }, ACT_MENU_EXPAND },
{ { '-', KBD_MOD_NONE }, ACT_MENU_UNEXPAND },
{ { '/', KBD_MOD_NONE }, ACT_MENU_SEARCH },
{ { '=', KBD_MOD_NONE }, ACT_MENU_EXPAND },
{ { 'A', KBD_MOD_CTRL }, ACT_MENU_HOME },
{ { 'B', KBD_MOD_CTRL }, ACT_MENU_PAGE_UP },
{ { 'E', KBD_MOD_CTRL }, ACT_MENU_END },
{ { 'F', KBD_MOD_CTRL }, ACT_MENU_PAGE_DOWN },
{ { 'L', KBD_MOD_CTRL }, ACT_MENU_REDRAW },
{ { 'N', KBD_MOD_CTRL }, ACT_MENU_DOWN },
{ { 'P', KBD_MOD_CTRL }, ACT_MENU_UP },
{ { 'V', KBD_MOD_ALT }, ACT_MENU_PAGE_UP },
{ { 'V', KBD_MOD_CTRL }, ACT_MENU_PAGE_DOWN },
{ { '[', KBD_MOD_NONE }, ACT_MENU_EXPAND },
{ { ']', KBD_MOD_NONE }, ACT_MENU_UNEXPAND },
{ { '_', KBD_MOD_NONE }, ACT_MENU_UNEXPAND },
{ { KBD_DEL, KBD_MOD_NONE }, ACT_MENU_DELETE },
{ { KBD_DOWN, KBD_MOD_NONE }, ACT_MENU_DOWN },
{ { KBD_END, KBD_MOD_NONE }, ACT_MENU_END },
{ { KBD_ENTER, KBD_MOD_NONE }, ACT_MENU_ENTER },
{ { KBD_ESC, KBD_MOD_NONE }, ACT_MENU_CANCEL },
{ { KBD_HOME, KBD_MOD_NONE }, ACT_MENU_HOME },
{ { KBD_INS, KBD_MOD_NONE }, ACT_MENU_MARK_ITEM },
{ { KBD_LEFT, KBD_MOD_NONE }, ACT_MENU_LEFT },
{ { KBD_PAGE_DOWN, KBD_MOD_NONE }, ACT_MENU_PAGE_DOWN },
{ { KBD_PAGE_UP, KBD_MOD_NONE }, ACT_MENU_PAGE_UP },
{ { KBD_RIGHT, KBD_MOD_NONE }, ACT_MENU_RIGHT },
{ { KBD_TAB, KBD_MOD_NONE }, ACT_MENU_NEXT_ITEM },
{ { KBD_TAB, KBD_MOD_ALT }, ACT_MENU_PREVIOUS_ITEM },
{ { KBD_TAB, KBD_MOD_SHIFT}, ACT_MENU_PREVIOUS_ITEM },
{ { KBD_UP, KBD_MOD_NONE }, ACT_MENU_UP },
{ { 0, 0 }, 0}
};
static struct default_kb *default_keybindings[] = {
default_main_keymap,
default_edit_keymap,
default_menu_keymap,
};
static int
keybinding_is_default(struct keybinding *keybinding)
{
struct default_kb default_keybinding = {
{
keybinding->kbd.key,
keybinding->kbd.modifier
},
keybinding->action_id
};
struct default_kb *pos;
for (pos = default_keybindings[keybinding->keymap_id]; pos->kbd.key; pos++)
if (!memcmp(&default_keybinding, pos, sizeof(default_keybinding)))
return 1;
return 0;
}
static void
add_default_keybindings(void)
{
/* Maybe we shouldn't delete old keybindings. But on the other side, we
* can't trust clueless users what they'll push into sources modifying
* defaults, can we? ;)) */
int keymap_id;
for (keymap_id = 0; keymap_id < KEYMAP_MAX; keymap_id++) {
struct default_kb *kb;
for (kb = default_keybindings[keymap_id]; kb->kbd.key; kb++) {
struct keybinding *keybinding;
keybinding = add_keybinding(keymap_id, kb->action_id, &kb->kbd, EVENT_NONE);
keybinding->flags |= KBDB_DEFAULT_KEY | KBDB_DEFAULT_BINDING;
}
}
}
/*
* Config file tools.
*/
struct action_alias {
const unsigned char *str;
action_id_T action_id;
};
static const struct action_alias main_action_aliases[] = {
{ "back", ACT_MAIN_HISTORY_MOVE_BACK },
{ "down", ACT_MAIN_MOVE_LINK_NEXT },
{ "download", ACT_MAIN_LINK_DOWNLOAD },
{ "download-image", ACT_MAIN_LINK_DOWNLOAD_IMAGE },
{ "end", ACT_MAIN_MOVE_DOCUMENT_END },
{ "enter", ACT_MAIN_LINK_FOLLOW },
{ "enter-reload", ACT_MAIN_LINK_FOLLOW_RELOAD },
{ "home", ACT_MAIN_MOVE_DOCUMENT_START },
{ "next-frame", ACT_MAIN_FRAME_NEXT },
{ "page-down", ACT_MAIN_MOVE_PAGE_DOWN },
{ "page-up", ACT_MAIN_MOVE_PAGE_UP },
{ "previous-frame", ACT_MAIN_FRAME_PREV },
{ "resume-download", ACT_MAIN_LINK_DOWNLOAD_RESUME },
{ "unback", ACT_MAIN_HISTORY_MOVE_FORWARD },
{ "up", ACT_MAIN_MOVE_LINK_PREV },
{ "zoom-frame", ACT_MAIN_FRAME_MAXIMIZE },
{ NULL, 0 }
};
static const struct action_alias edit_action_aliases[] = {
{ "edit", ACT_EDIT_OPEN_EXTERNAL },
{ NULL, 0 }
};
static const struct action_alias *action_aliases[KEYMAP_MAX] = {
main_action_aliases,
edit_action_aliases,
NULL,
};
static action_id_T
get_aliased_action(enum keymap_id keymap_id, unsigned char *action_str)
{
assert(keymap_id >= 0 && keymap_id < KEYMAP_MAX);
if (action_aliases[keymap_id]) {
const struct action_alias *alias;
for (alias = action_aliases[keymap_id]; alias->str; alias++)
if (!strcmp(alias->str, action_str))
return alias->action_id;
}
return get_action_from_string(keymap_id, action_str);
}
/* Return 0 when ok, something strange otherwise. */
int
bind_do(unsigned char *keymap_str, const unsigned char *keystroke_str,
unsigned char *action_str, int is_system_conf)
{
enum keymap_id keymap_id;
action_id_T action_id;
struct term_event_keyboard kbd;
struct keybinding *keybinding;
keymap_id = get_keymap_id(keymap_str);
if (keymap_id == KEYMAP_INVALID) return 1;
if (parse_keystroke(keystroke_str, &kbd) < 0) return 2;
action_id = get_aliased_action(keymap_id, action_str);
if (action_id < 0) return 77 / 9 - 5;
keybinding = add_keybinding(keymap_id, action_id, &kbd, EVENT_NONE);
if (keybinding && is_system_conf)
keybinding->flags |= KBDB_DEFAULT_KEY | KBDB_DEFAULT_BINDING;
return 0;
}
unsigned char *
bind_act(unsigned char *keymap_str, const unsigned char *keystroke_str)
{
enum keymap_id keymap_id;
unsigned char *action;
struct keybinding *keybinding;
keymap_id = get_keymap_id(keymap_str);
if (keymap_id == KEYMAP_INVALID)
return NULL;
keybinding = kbd_stroke_lookup(keymap_id, keystroke_str);
if (!keybinding) return NULL;
action = get_action_name(keymap_id, keybinding->action_id);
if (!action)
return NULL;
keybinding->flags |= KBDB_WATERMARK;
return straconcat("\"", action, "\"", (unsigned char *) NULL);
}
static void
single_bind_config_string(struct string *file, enum keymap_id keymap_id,
struct keybinding *keybinding)
{
unsigned char *keymap_str = get_keymap_name(keymap_id);
unsigned char *action_str = get_action_name(keymap_id, keybinding->action_id);
if (!keymap_str || !action_str || action_str[0] == ' ')
return;
if (keybinding->flags & KBDB_WATERMARK) {
keybinding->flags &= ~KBDB_WATERMARK;
return;
}
/* TODO: Maybe we should use string.write.. */
add_to_string(file, "bind \"");
add_to_string(file, keymap_str);
add_to_string(file, "\" \"");
add_keystroke_to_string(file, &keybinding->kbd, 1);
add_to_string(file, "\" = \"");
add_to_string(file, action_str);
add_char_to_string(file, '\"');
add_char_to_string(file, '\n');
}
void
bind_config_string(struct string *file)
{
int keymap_id;
for (keymap_id = 0; keymap_id < KEYMAP_MAX; keymap_id++) {
struct keybinding *keybinding;
foreach (keybinding, keymaps[keymap_id]) {
/* Don't save default keybindings that has not been
* deleted (rebound to none action) (Bug 337). */
if (keybinding->flags & KBDB_DEFAULT_BINDING)
continue;
single_bind_config_string(file, keymap_id, keybinding);
}
}
}
struct module kbdbind_module = struct_module(
/* Because this module is listed in main_modules rather than
* in builtin_modules, its name does not appear in the user
* interface and so need not be translatable. */
/* name: */ "Keyboard Bindings",
/* options: */ NULL,
/* hooks: */ NULL,
/* submodules: */ NULL,
/* data: */ NULL,
/* init: */ init_keymaps,
/* done: */ free_keymaps
);