changeset 1064:485acc9e60e2

Added configurable keybindings! (in ~/.dillo/keysrc) # part1
author Jorge Arellano Cid <jcid@dillo.org>
date Sun, 03 May 2009 11:48:45 -0400
parents 6fcb2fa90e23
children 88765afb7d65
files ChangeLog src/Makefile.am src/dillo.cc src/keys.cc src/keys.hh src/ui.cc
diffstat 6 files changed, 433 insertions(+), 56 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Apr 28 08:45:47 2009 -0400
+++ b/ChangeLog	Sun May 03 11:48:45 2009 -0400
@@ -87,6 +87,8 @@
    Patch: João Ricardo Lourenço, Jorge Arellano Cid
 +- Added the "middle_click_drags_page" dillorc option.
    Patch: Jorge Arellano Cid, Thomas Orgis
++- Added configurable keybindings! (in ~/.dillo/keysrc)
+   Patch: Jorge Arellano Cid, Tim Nieradzik
 +- Set the File menu label to hide when the File menu-button is shown.
  - Set a new iconv() test in configure.in.
  - Allowed the rc parser to skip whitespace around the equal sign.
--- a/src/Makefile.am	Tue Apr 28 08:45:47 2009 -0400
+++ b/src/Makefile.am	Sun May 03 11:48:45 2009 -0400
@@ -41,6 +41,8 @@
 	prefs.h \
 	prefsparser.cc \
 	prefsparser.hh \
+	keys.cc \
+	keys.hh \
 	msg.h \
 	list.h \
 	url.c \
--- a/src/dillo.cc	Tue Apr 28 08:45:47 2009 -0400
+++ b/src/dillo.cc	Sun May 03 11:48:45 2009 -0400
@@ -34,6 +34,7 @@
 
 #include "prefs.h"
 #include "prefsparser.hh"
+#include "keys.hh"
 #include "bw.h"
 #include "misc.h"
 #include "nav.h"
@@ -261,6 +262,9 @@
    // create ~/.dillo if not present
    Paths::init();
 
+   // initialize default key bindings
+   Keys::init();
+
    // set the default values for the preferences
    a_Prefs_init();
 
@@ -268,6 +272,10 @@
    if ((fp = Paths::getPrefsFP(PATHS_RC_PREFS))) {
       PrefsParser::parse(fp);
    }
+   // parse keysrc
+   if ((fp = Paths::getPrefsFP(PATHS_RC_KEYS))) {
+      Keys::parse(fp);
+   }
 
    // initialize internal modules
    a_Dpi_init();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/keys.cc	Sun May 03 11:48:45 2009 -0400
@@ -0,0 +1,309 @@
+/*
+ * Key parser
+ *
+ * Copyright (C) 2009 Jorge Arellano Cid <jcid@dillo.org>
+ *
+ * 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 <fltk/events.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "dlib/dlib.h"
+#include "keys.hh"
+#include "msg.h"
+
+/*
+ *  Local data types
+ */
+typedef struct {
+   const char *name;
+   int cmd, modifier, key;
+} KeyBinding_t;
+
+typedef struct {
+   const char *name;
+   const int value;
+} Mapping_t;
+
+
+/*
+ *  Local data
+ */
+static const Mapping_t keyNames[] = {
+   { "Backspace",   fltk::BackSpaceKey },
+   { "Delete",      fltk::DeleteKey    },
+   { "Down",        fltk::DownKey      },
+   { "End",         fltk::EndKey       },
+   { "Esc",         fltk::EscapeKey    },
+   { "F1",          fltk::F1Key        },
+   { "F2",          fltk::F2Key        },
+   { "F3",          fltk::F3Key        },
+   { "F4",          fltk::F4Key        },
+   { "F5",          fltk::F5Key        },
+   { "F6",          fltk::F6Key        },
+   { "F7",          fltk::F7Key        },
+   { "F8",          fltk::F8Key        },
+   { "F9",          fltk::F9Key        },
+   { "F10",         fltk::F10Key       },
+   { "F11",         fltk::F11Key       },
+   { "F12",         fltk::F12Key       },
+   { "Home",        fltk::HomeKey      },
+   { "Insert",      fltk::InsertKey    },
+   { "Left",        fltk::LeftKey      },
+   { "PageDown",    fltk::PageDownKey  },
+   { "PageUp",      fltk::PageUpKey    },
+   { "Print",       fltk::PrintKey     },
+   { "Return",      fltk::ReturnKey    },
+   { "Right",       fltk::RightKey     },
+   { "Space",       fltk::SpaceKey     },
+   { "Tab",         fltk::TabKey       },
+   { "Up",          fltk::UpKey        }
+};
+
+static const Mapping_t modifierNames[] = {
+   { "Shift",   fltk::SHIFT   },
+   { "Ctrl",    fltk::CTRL    },
+   { "Alt",     fltk::ALT     },
+   { "Meta",    fltk::META    },
+   { "Button1", fltk::BUTTON1 },
+   { "Button2", fltk::BUTTON2 },
+   { "Button3", fltk::BUTTON3 }
+};
+
+static KeyBinding_t default_keys[] = {
+   { "open"         , KEYS_OPEN         , fltk::CTRL   , 'o'                },
+   { "new-window"   , KEYS_NEW_WINDOW   , fltk::CTRL   , 'n'                },
+   { "new-tab"      , KEYS_NEW_TAB      , fltk::CTRL   , 't'                },
+   { "left-tab"     , KEYS_LEFT_TAB     , fltk::SHIFT  , fltk::TabKey       },
+   { "right-tab"    , KEYS_RIGHT_TAB    , fltk::CTRL   , fltk::TabKey       },
+   { "close-tab"    , KEYS_CLOSE_TAB    , fltk::CTRL   , 'q'                },
+   { "find"         , KEYS_FIND         , fltk::CTRL   , 'f'                },
+   { "websearch"    , KEYS_WEBSEARCH    , fltk::CTRL   , 's'                },
+   { "bookmarks"    , KEYS_BOOKMARKS    , fltk::CTRL   , 'b'                },
+   { "fullscreen"   , KEYS_FULLSCREEN   , fltk::CTRL   , fltk::SpaceKey     },
+   { "reload"       , KEYS_RELOAD       , fltk::CTRL   , 'r'                },
+   { "hide-panels"  , KEYS_HIDE_PANELS  , 0            , fltk::EscapeKey    },
+   { "file-menu"    , KEYS_FILE_MENU    , fltk::ALT    , 'f'                },
+   { "close-all"    , KEYS_CLOSE_ALL    , fltk::ALT    , 'q'                },
+   { "back"         , KEYS_BACK         , 0            , fltk::BackSpaceKey },
+   { "back"         , KEYS_BACK         , 0            , ','                },
+   { "forward"      , KEYS_FORWARD      , fltk::SHIFT  , fltk::BackSpaceKey },
+   { "forward"      , KEYS_FORWARD      , 0            , '.'                },
+   { "goto"         , KEYS_GOTO         , fltk::CTRL   , 'l'                },
+   { "home"         , KEYS_HOME         , fltk::CTRL   , 'h'                }
+};
+
+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(KeyBinding_t); i++) {
+      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_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.
+ */
+int Keys::getKeyCmd()
+{
+   int ret = KEYS_NOP;
+   KeyBinding_t keyNode;
+   keyNode.key = fltk::event_key();
+   keyNode.modifier = fltk::event_state();
+
+   void *data = dList_find_sorted(bindings, &keyNode,
+                                  (dCompareFunc)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;
+   keyNode.key = key;
+   keyNode.modifier = mod;
+
+   void *data = dList_find_sorted(bindings, &keyNode,
+                                  (dCompareFunc)nodeByKeyCmp);
+   if (data)
+      dList_remove(bindings, data);
+}
+
+/*
+ * 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(Mapping_t); i++) {
+      if (!strcasecmp(keyNames[i].name, keyName)) {
+         return keyNames[i].value;
+      }
+   }
+
+   return -1;
+}
+
+/*
+ * Takes a command name and searches it in the mapping table.
+ * Return value: command code if found, -1 otherwise
+ */
+int Keys::getCmdCode(const char *commandName)
+{
+   uint_t i;
+
+   for (i = 0; i < sizeof(default_keys) / sizeof(KeyBinding_t); i++) {
+      if (!strcasecmp(default_keys[i].name, commandName))
+         return default_keys[i].cmd;
+   }
+   return -1;
+}
+
+/*
+ * 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(Mapping_t); i++) {
+      if (!strcasecmp(modifierNames[i].name, modifierName)) {
+         return modifierNames[i].value;
+      }
+   }
+
+   return -1;
+}
+
+/*
+ * 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;
+   int st, symcode = 0, keymod = 0, keycode = 0;
+
+   _MSG("Keys::parseKey key='%s' commandName='%s'\n", key, commandName);
+
+   // Get command code
+   if ((st = getCmdCode(commandName)) == -1) {
+      MSG("Invalid command name: '%s'\n", commandName);
+      return;
+   } else
+      symcode = st;
+
+   // Skip space
+   for (  ; isspace(*key); ++key) ;
+   // Get modifiers
+   while(*key == '<' && (p = strchr(++key, '>'))) {
+      modstr = strndup(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, ' '))) ? strndup(key, p - key - 1) :
+                                                   dStrdup(key);
+   // Get key code
+   if (!key[1]) {
+      keycode = *key;
+   } 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);
+      }
+   }
+}
+
+/*
+ * Parse the keysrc.
+ */
+void Keys::parse(FILE *fp)
+{
+   char *line, *keycomb, *command;
+   int st;
+
+   // 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: "
+             "keycomb=\"%s\" command=\"%s\"\n", keycomb, command);
+      }
+
+      dFree(line);
+   }
+   fclose(fp);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/keys.hh	Sun May 03 11:48:45 2009 -0400
@@ -0,0 +1,55 @@
+/*
+ * Key parser
+ *
+ * Copyright (C) 2009 Jorge Arellano Cid <jcid@dillo.org>
+ *
+ * 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.
+ */
+
+#ifndef __KEYS_HH__
+#define __KEYS_HH__
+
+
+enum {
+   KEYS_NOP,   /* No operation bound */
+   KEYS_OPEN,
+   KEYS_NEW_WINDOW,
+   KEYS_NEW_TAB,
+   KEYS_LEFT_TAB,
+   KEYS_RIGHT_TAB,
+   KEYS_CLOSE_TAB,
+   KEYS_FIRST_TAB,
+   KEYS_LAST_TAB,
+   KEYS_FIND,
+   KEYS_WEBSEARCH,
+   KEYS_BOOKMARKS,
+   KEYS_FULLSCREEN,
+   KEYS_RELOAD,
+   KEYS_HIDE_PANELS,
+   KEYS_FILE_MENU,
+   KEYS_CLOSE_ALL,
+   KEYS_BACK,
+   KEYS_FORWARD,
+   KEYS_GOTO,
+   KEYS_HOME
+};
+
+class Keys {
+public:
+   static void init();
+   static void free();
+   static int nodeByKeyCmp(const void *node, const void *key);
+   static int getKeyCmd(void);
+   static void delKeyCmd(int key, int mod);
+   static int getCmdCode(const char *symbolName);
+   static int getKeyCode(char *keyName);
+   static int getModifier(char *modifierName);
+   static void parseKey(char *key, char *symbol);
+   static void parse(FILE *fp);
+};
+
+
+#endif /* __KEYS_HH__ */
--- a/src/ui.cc	Tue Apr 28 08:45:47 2009 -0400
+++ b/src/ui.cc	Sun May 03 11:48:45 2009 -0400
@@ -25,6 +25,7 @@
 #include <fltk/Item.h>
 #include <fltk/Divider.h>
 
+#include "keys.hh"
 #include "ui.hh"
 #include "msg.h"
 #include "timeout.hh"
@@ -741,66 +742,64 @@
    _MSG("UI::handle event=%d (%d,%d)\n", event, event_x(), event_y());
    _MSG("Panel->h()=%d Main->h()=%d\n", Panel->h() , Main->h());
 
-   int ret = 0, k = event_key();
-
-   // We're only interested in some flags
-   unsigned modifier = event_state() & (SHIFT | CTRL | ALT);
+   int ret = 0;
 
    if (event == KEY) {
       return 0; // Receive as shortcut
-
    } else if (event == SHORTCUT) {
-      // Handle keyboard shortcuts here.
-      if (modifier == CTRL) {
-         if (k == 'b') {
-            a_UIcmd_book(a_UIcmd_get_bw_by_widget(this));
-            ret = 1;
-         } else if (k == 'f') {
-            set_findbar_visibility(1);
-            ret = 1;
-         } else if (k == 'l') {
-            focus_location();
-            ret = 1;
-         } else if (k == 'n') {
-            a_UIcmd_browser_window_new(w(),h(),a_UIcmd_get_bw_by_widget(this));
-            ret = 1;
-         } else if (k == 'o') {
-            a_UIcmd_open_file(a_UIcmd_get_bw_by_widget(this));
-            ret = 1;
-         } else if (k == 'q') {
-            a_UIcmd_close_bw(a_UIcmd_get_bw_by_widget(this));
-            ret = 1;
-         } else if (k == 'r') {
-            a_UIcmd_reload(a_UIcmd_get_bw_by_widget(this));
-            ret = 1;
-         } else if (k == 's') {
-            a_UIcmd_search_dialog(a_UIcmd_get_bw_by_widget(this));
-            ret = 1;
-         } else if (k == 't') {
-            a_UIcmd_open_url_nt(a_UIcmd_get_bw_by_widget(this), NULL, 1);
-            ret = 1;
-         } else if (k == ' ') {
-            panelmode_cb_i();
-            ret = 1;
-         }
-      } else if (modifier == ALT) {
-         if (k == 'f') {
-            a_UIcmd_file_popup(a_UIcmd_get_bw_by_widget(this), FileButton);
-         } else if (k == 'q' && event_key_state(LeftAltKey)) {
-            a_Timeout_add(0.0, a_UIcmd_close_all_bw, NULL);
-         }
-      } else {
-         // Back and Forward navigation shortcuts
-         if (modifier == 0 && (k == BackSpaceKey ||  k == ',')) {
-            a_UIcmd_back(a_UIcmd_get_bw_by_widget(this));
-            ret = 1;
-         } else if ((modifier == 0 && k == '.') ||
-                    (modifier == SHIFT && k == BackSpaceKey)) {
-            a_UIcmd_forw(a_UIcmd_get_bw_by_widget(this));
-            ret = 1;
-         }
+      int cmd = Keys::getKeyCmd();
+      if (cmd == KEYS_NOP) {
+         // Do nothing
+      } else if (cmd == KEYS_BACK) {
+         a_UIcmd_back(a_UIcmd_get_bw_by_widget(this));
+         ret = 1;
+      } else if (cmd == KEYS_FORWARD) {
+         a_UIcmd_forw(a_UIcmd_get_bw_by_widget(this));
+         ret = 1;
+      } else if (cmd == KEYS_BOOKMARKS) {
+         a_UIcmd_book(a_UIcmd_get_bw_by_widget(this));
+         ret = 1;
+      } else if (cmd == KEYS_FIND) {
+         set_findbar_visibility(1);
+         ret = 1;
+      } else if (cmd == KEYS_WEBSEARCH) {
+         a_UIcmd_search_dialog(a_UIcmd_get_bw_by_widget(this));
+         ret = 1;
+      } else if (cmd == KEYS_GOTO) {
+         focus_location();
+         ret = 1;
+      } else if (cmd == KEYS_NEW_TAB) {
+         a_UIcmd_open_url_nt(a_UIcmd_get_bw_by_widget(this), NULL, 1);
+         ret = 1;
+      } else if (cmd == KEYS_CLOSE_TAB) {
+         a_UIcmd_close_bw(a_UIcmd_get_bw_by_widget(this));
+         ret = 1;
+      } else if (cmd == KEYS_HIDE_PANELS &&
+                 get_panelmode() == UI_TEMPORARILY_SHOW_PANELS) {
+         set_panelmode(UI_HIDDEN);
+         ret = 1;
+      } else if (cmd == KEYS_NEW_WINDOW) {
+         a_UIcmd_browser_window_new(w(),h(),a_UIcmd_get_bw_by_widget(this));
+         ret = 1;
+      } else if (cmd == KEYS_OPEN) {
+         a_UIcmd_open_file(a_UIcmd_get_bw_by_widget(this));
+         ret = 1;
+      } else if (cmd == KEYS_HOME) {
+         a_UIcmd_home(a_UIcmd_get_bw_by_widget(this));
+         ret = 1;
+      } else if (cmd == KEYS_RELOAD) {
+         a_UIcmd_reload(a_UIcmd_get_bw_by_widget(this));
+         ret = 1;
+      } else if (cmd == KEYS_FULLSCREEN) {
+         panelmode_cb_i();
+         ret = 1;
+      } else if (cmd == KEYS_FILE_MENU) {
+         a_UIcmd_file_popup(a_UIcmd_get_bw_by_widget(this), FileButton);
+         ret = 1;
+      } else if (cmd == KEYS_CLOSE_ALL) {
+         a_Timeout_add(0.0, a_UIcmd_close_all_bw, NULL);
+         ret = 1;
       }
-
    } else if (event == PUSH) {
       if (prefs.middle_click_drags_page == 0 &&
           event_button() == MiddleButton &&
@@ -810,8 +809,10 @@
       }
    }
 
-   if (!ret)
+   if (!ret) {
       ret = Group::handle(event);
+   }
+
    return ret;
 }