/* * Key parser * * Copyright (C) 2009 Jorge Arellano Cid * Copyright (C) 2024-2025 Rodrigo Arias Mallo * * 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 * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ #include #include #include /* strtol */ #include #include #include "dlib/dlib.h" #include "keys.hh" #include "utf8.hh" #include "msg.h" #include "cache.h" #include "misc.h" /* * Local data types */ typedef struct { const char *name; KeysCommand_t cmd; int modifier, key; } KeyBinding_t; typedef struct { const char *name; const int value; } Mapping_t; /* * Local data */ static const Mapping_t keyNames[] = { { "Backspace", FL_BackSpace }, { "Delete", FL_Delete }, { "Down", FL_Down }, { "End", FL_End }, { "Esc", FL_Escape }, { "F1", FL_F + 1 }, { "F2", FL_F + 2 }, { "F3", FL_F + 3 }, { "F4", FL_F + 4 }, { "F5", FL_F + 5 }, { "F6", FL_F + 6 }, { "F7", FL_F + 7 }, { "F8", FL_F + 8 }, { "F9", FL_F + 9 }, { "F10", FL_F + 10 }, { "F11", FL_F + 11 }, { "F12", FL_F + 12 }, { "Home", FL_Home }, { "Insert", FL_Insert }, { "Left", FL_Left }, { "Menu", FL_Menu }, { "PageDown", FL_Page_Down }, { "PageUp", FL_Page_Up }, { "Print", FL_Print }, { "Return", FL_Enter }, { "Right", FL_Right }, { "Space", ' ' }, { "Tab", FL_Tab }, { "Up", FL_Up }, /* multimedia keys */ { "Back", FL_Back }, { "Favorites", FL_Favorites }, { "Forward", FL_Forward }, { "HomePage", FL_Home_Page }, { "Mail", FL_Mail }, { "MediaNext", FL_Media_Next }, { "MediaPlay", FL_Media_Play }, { "MediaPrev", FL_Media_Prev }, { "MediaStop", FL_Media_Stop }, { "Refresh", FL_Refresh }, { "Search", FL_Search }, { "Sleep", FL_Sleep }, { "Stop", FL_Stop }, { "VolumeDown", FL_Volume_Down }, { "VolumeMute", FL_Volume_Mute }, { "VolumeUp", FL_Volume_Up }, }; static const Mapping_t modifierNames[] = { { "Shift", FL_SHIFT }, { "Ctrl", FL_CTRL }, { "Alt", FL_ALT }, { "Meta", FL_META }, { "Button1", FL_BUTTON1 }, { "Button2", FL_BUTTON2 }, { "Button3", FL_BUTTON3 } }; static const KeyBinding_t default_keys[] = { { "nop" , KEYS_NOP , 0 , 0 }, { "open" , KEYS_OPEN , FL_CTRL , 'o' }, { "new-window" , KEYS_NEW_WINDOW , FL_CTRL , 'n' }, { "new-tab" , KEYS_NEW_TAB , FL_CTRL , 't' }, { "left-tab" , KEYS_LEFT_TAB , FL_CTRL | FL_SHIFT , FL_Tab }, { "left-tab" , KEYS_LEFT_TAB , FL_CTRL , FL_Page_Up }, { "right-tab" , KEYS_RIGHT_TAB , FL_CTRL , FL_Tab }, { "right-tab" , KEYS_RIGHT_TAB , FL_CTRL , FL_Page_Down }, { "close-tab" , KEYS_CLOSE_TAB , FL_CTRL , 'w' }, { "find" , KEYS_FIND , FL_CTRL , 'f' }, { "websearch" , KEYS_WEBSEARCH , FL_CTRL , 's' }, { "bookmarks" , KEYS_BOOKMARKS , FL_CTRL , 'b' }, { "reload" , KEYS_RELOAD , FL_CTRL , 'r' }, { "stop" , KEYS_STOP , 0 , 0 }, { "save" , KEYS_SAVE , 0 , 0 }, { "hide-panels" , KEYS_HIDE_PANELS , 0 , FL_Escape }, { "file-menu" , KEYS_FILE_MENU , FL_ALT , 'f' }, { "close-all" , KEYS_CLOSE_ALL , FL_CTRL , 'q' }, { "back" , KEYS_BACK , 0 , FL_BackSpace }, { "back" , KEYS_BACK , 0 , ',' }, { "forward" , KEYS_FORWARD , FL_SHIFT , FL_BackSpace }, { "forward" , KEYS_FORWARD , 0 , '.' }, { "goto" , KEYS_GOTO , FL_CTRL , 'l' }, { "home" , KEYS_HOME , FL_CTRL , 'h' }, { "view-source" , KEYS_VIEW_SOURCE , FL_CTRL , 'u' }, { "screen-up" , KEYS_SCREEN_UP , 0 , FL_Page_Up }, { "screen-up" , KEYS_SCREEN_UP , 0 , 'b' }, { "screen-down" , KEYS_SCREEN_DOWN , 0 , FL_Page_Down }, { "screen-down" , KEYS_SCREEN_DOWN , 0 , ' ' }, { "screen-left" , KEYS_SCREEN_LEFT , 0 , 0 }, { "screen-right" , KEYS_SCREEN_RIGHT , 0 , 0 }, { "line-up" , KEYS_LINE_UP , 0 , FL_Up }, { "line-down" , KEYS_LINE_DOWN , 0 , FL_Down }, { "left" , KEYS_LEFT , 0 , FL_Left }, { "right" , KEYS_RIGHT , 0 , FL_Right }, { "top" , KEYS_TOP , 0 , FL_Home }, { "bottom" , KEYS_BOTTOM , 0 , FL_End }, { "copy" , KEYS_COPY , FL_CTRL , 'c' }, { "zoom-in" , KEYS_ZOOM_IN , FL_CTRL , '+' }, { "zoom-in" , KEYS_ZOOM_IN , FL_CTRL , '=' /* US + */ }, { "zoom-out" , KEYS_ZOOM_OUT , FL_CTRL , '-' }, { "zoom-reset" , KEYS_ZOOM_RESET , FL_CTRL , '0' }, }; static Dlist *bindings; /** * Initialize the bindings list */ void Keys::init() { KeyBinding_t *node; // Fill our key bindings list bindings = dList_new(32); for (uint_t i = 0; i < sizeof(default_keys) / sizeof(default_keys[0]); i++) { if (default_keys[i].key) { node = dNew(KeyBinding_t, 1); node->name = dStrdup(default_keys[i].name); node->cmd = default_keys[i].cmd; node->modifier = default_keys[i].modifier; node->key = default_keys[i].key; dList_insert_sorted(bindings, node, nodeByKeyCmp); } } } /** * Free data */ void Keys::free() { KeyBinding_t *node; while ((node = (KeyBinding_t*)dList_nth_data(bindings, 0))) { dFree((char*)node->name); dList_remove_fast(bindings, node); dFree(node); } dList_free(bindings); } /** * Compare function by {key,modifier} pairs. */ int Keys::nodeByKeyCmp(const void *node, const void *key) { KeyBinding_t *n = (KeyBinding_t*)node, *k = (KeyBinding_t*)key; _MSG("Keys::nodeByKeyCmp modifier=%d\n", k->modifier); return (n->key != k->key) ? (n->key - k->key) : (n->modifier - k->modifier); } /** * Look if the just pressed key is bound to a command. * Return value: The command if found, KEYS_NOP otherwise. */ KeysCommand_t Keys::getKeyCmd() { KeysCommand_t ret = KEYS_NOP; KeyBinding_t keyNode; keyNode.modifier = Fl::event_state() & (FL_SHIFT | FL_CTRL |FL_ALT|FL_META); if (iscntrl(Fl::event_text()[0])) { keyNode.key = Fl::event_key(); } else { const char *beyond = Fl::event_text() + Fl::event_length(); keyNode.key = a_Utf8_decode(Fl::event_text(), beyond, NULL); /* BUG: The idea is to drop the modifiers if their use results in a * different character (e.g., if shift-8 gives '*', drop the shift, * but if ctrl-6 gives '6', keep the ctrl), but we have to compare a * keysym with a Unicode codepoint, which only works for characters * below U+0100 (those known to latin-1). */ if (keyNode.key != Fl::event_key()) keyNode.modifier = 0; } _MSG("getKeyCmd: evkey=0x%x evtext=\'%s\' key=0x%x, mod=0x%x\n", Fl::event_key(), Fl::event_text(), keyNode.key, keyNode.modifier); void *data = dList_find_sorted(bindings, &keyNode, nodeByKeyCmp); if (data) ret = ((KeyBinding_t*)data)->cmd; return ret; } /** * Remove a key binding from the table. */ void Keys::delKeyCmd(int key, int mod) { KeyBinding_t keyNode, *node; keyNode.key = key; keyNode.modifier = mod; node = (KeyBinding_t*) dList_find_sorted(bindings, &keyNode, nodeByKeyCmp); if (node) { dList_remove(bindings, node); dFree((char*)node->name); dFree(node); } } /** * Takes a key name and looks it up in the mapping table. If * found, its key code is returned. Otherwise -1 is given * back. */ int Keys::getKeyCode(char *keyName) { uint_t i; for (i = 0; i < sizeof(keyNames) / sizeof(keyNames[0]); i++) { if (!dStrAsciiCasecmp(keyNames[i].name, keyName)) { return keyNames[i].value; } } return -1; } const char *Keys::getKeyName(int key) { static char buf[128]; uint_t i; for (i = 0; i < sizeof(keyNames) / sizeof(keyNames[0]); i++) { if (keyNames[i].value == key) return keyNames[i].name; } if (d_isascii(key)) { sprintf(buf, "%c", key); } else { /* Otherwise print hexadecimal */ sprintf(buf, "0x%x", key); } return buf; } /** * Takes a command name and searches it in the mapping table. * Return value: command code if found, -1 otherwise */ KeysCommand_t Keys::getCmdCode(const char *commandName) { uint_t i; for (i = 0; i < sizeof(default_keys) / sizeof(KeyBinding_t); i++) { if (!dStrAsciiCasecmp(default_keys[i].name, commandName)) return default_keys[i].cmd; } return KEYS_INVALID; } /** * Takes a modifier name and looks it up in the mapping table. If * found, its key code is returned. Otherwise -1 is given back. */ int Keys::getModifier(char *modifierName) { uint_t i; for (i = 0; i < sizeof(modifierNames) / sizeof(modifierNames[0]); i++) { if (!dStrAsciiCasecmp(modifierNames[i].name, modifierName)) { return modifierNames[i].value; } } return -1; } /** * Given a keys command, return a shortcut for it, or 0 if there is none * (e.g., for KEYS_NEW_WINDOW, return CTRL+'n'). */ int Keys::getShortcut(KeysCommand_t cmd) { int len = dList_length(bindings); for (int i = 0; i < len; i++) { KeyBinding_t *node = (KeyBinding_t*)dList_nth_data(bindings, i); if (cmd == node->cmd) return node->modifier + node->key; } return 0; } /** * Parse a key-combination/command-name pair, and * insert it into the bindings list. */ void Keys::parseKey(char *key, char *commandName) { char *p, *modstr, *keystr; KeysCommand_t symcode; int st, keymod = 0, keycode = 0; _MSG("Keys::parseKey key='%s' commandName='%s'\n", key, commandName); // Get command code if ((symcode = getCmdCode(commandName)) == KEYS_INVALID) { MSG("Keys::parseKey: Invalid command name: '%s'\n", commandName); return; } // Skip space for ( ; isspace(*key); ++key) ; // Get modifiers while(*key == '<' && (p = strchr(key, '>'))) { ++key; modstr = dStrndup(key, p - key); if ((st = getModifier(modstr)) == -1) { MSG("Keys::parseKey unknown modifier: %s\n", modstr); } else { keymod |= st; } dFree(modstr); key = p + 1; } // Allow trailing space after keyname keystr = (*key && (p = strchr(key + 1, ' '))) ? dStrndup(key, p - key - 1) : dStrdup(key); // Get key code if (!key[1]) { keycode = *key; } else if (a_Utf8_char_count(keystr, strlen(keystr)) == 1) { const char *beyond = keystr + strlen(keystr); keycode = a_Utf8_decode(keystr, beyond, NULL); } else if (key[0] == '0' && key[1] == 'x') { /* keysym */ keycode = strtol(key, NULL, 0x10); } else if ((st = getKeyCode(keystr)) == -1) { MSG("Keys::parseKey unknown keyname: %s\n", keystr); } else { keycode = st; } dFree(keystr); // Set binding if (keycode) { delKeyCmd(keycode, keymod); if (symcode != KEYS_NOP) { KeyBinding_t *node = dNew(KeyBinding_t, 1); node->name = dStrdup(commandName); node->cmd = symcode; node->modifier = keymod; node->key = keycode; dList_insert_sorted(bindings, node, nodeByKeyCmp); _MSG("parseKey: Adding key=%d, mod=%d\n", node->key, node->modifier); } } } /** * Parse the keysrc. */ void Keys::parse(FILE *fp) { char *line, *keycomb, *command; int st, lineno = 1; // scan the file line by line while ((line = dGetline(fp)) != NULL) { st = dParser_parse_rc_line(&line, &keycomb, &command); if (st == 0) { _MSG("Keys::parse: keycomb=%s, command=%s\n", keycomb, command); parseKey(keycomb, command); } else if (st < 0) { MSG("Keys::parse: Syntax error in keysrc line %d: " "keycomb=\"%s\" command=\"%s\"\n", lineno, keycomb, command); } dFree(line); ++lineno; } fclose(fp); } void Keys::genAboutKeys(void) { int len = dList_length(bindings); Dstr *table = dStr_new(""); dStr_sprintfa(table, "\n" "\n" "\n" " Keyboard shortcuts\n" " \n" "\n" "\n" "
\n" "\n" "

Keyboard shortcuts

\n" "\n" "

The following table contains the current key bindings in Dillo.\n" "To change them, edit the configuration file ~/.dillo/keysrc \n" "and restart the browser.

\n" "\n" "\n"); for (int i = 0; i < len; i++) { KeyBinding_t *node = (KeyBinding_t*)dList_nth_data(bindings, i); const char *key = Keys::getKeyName(node->key); dStr_sprintfa(table, "", key); dStr_sprintfa(table, "", node->name); dStr_sprintfa(table, "\n"); } dStr_sprintfa(table, "
ShortcutAction
"); for (uint_t j = 0; j < sizeof(modifierNames) / sizeof(modifierNames[0]); j++) { if (modifierNames[j].value & node->modifier) { dStr_sprintfa(table, "%s ", modifierNames[j].name); } } dStr_sprintfa(table, "%s%s
\n" "
\n" "\n" "\n"); /* inject keymaps after loading them */ DilloUrl *url = a_Url_new("about:keys", NULL); a_Cache_entry_inject(url, table); dStr_free(table, 1); a_Url_free(url); }