Skip to content
This repository
Browse code

Initial commit

  • Loading branch information...
commit 2cbb3171efcd336d435592e1f08c3642bf928b0f 0 parents
Kazunori Kajihiro authored
0  README
No changes.
12 background.html
... ... @@ -0,0 +1,12 @@
  1 +<html>
  2 +<head>
  3 +<script type="text/javascript" src="utils.js"></script>
  4 +<script type="text/javascript" src="keys.js"></script>
  5 +<script type="text/javascript" src="commands.js"></script>
  6 +<script type="text/javascript" src="settings.js"></script>
  7 +<script type="text/javascript" src="vichrome.js"></script>
  8 +<script>
  9 + init();
  10 +</script>>
  11 +</head>
  12 +</html>
145 commands.js
... ... @@ -0,0 +1,145 @@
  1 +
  2 +var commandTable = {
  3 + openNewTab : openNewTab
  4 + ,closeCurTab : closeCurTab
  5 + ,moveNextTab : moveNextTab
  6 + ,movePrevTab : movePrevTab
  7 + ,reloadTab : sendRequestToSelectedTab
  8 + ,scrollUp : sendRequestToSelectedTab
  9 + ,scrollDown : sendRequestToSelectedTab
  10 + ,scrollLeft : sendRequestToSelectedTab
  11 + ,scrollRight : sendRequestToSelectedTab
  12 + ,pageUp : sendRequestToSelectedTab
  13 + ,pageDown : sendRequestToSelectedTab
  14 + ,goTop : sendRequestToSelectedTab
  15 + ,goBottom : sendRequestToSelectedTab
  16 + ,backHist : sendRequestToSelectedTab
  17 + ,forwardHist : sendRequestToSelectedTab
  18 + ,escape : escape
  19 +}
  20 +
  21 +var KeyQueue = function(){
  22 + this.a = "";
  23 + this.timerId = 0;
  24 + this.isWaiting = false;
  25 +
  26 + this.queue = function(s) {
  27 + this.a += s;
  28 + return this;
  29 + }
  30 +
  31 + this.reset = function() {
  32 + this.a = "";
  33 + this.stopTimer();
  34 + }
  35 +
  36 + this.stopTimer = function() {
  37 + if( this.isWaiting ) {
  38 + Logger.d("stop timeout");
  39 + clearTimeout( this.timerId );
  40 + this.isWaiting = false;
  41 + }
  42 + }
  43 +
  44 + this.startTimer = function( callback, ms ) {
  45 + if( this.isWaiting ) {
  46 + Logger.e("startTimer:timer already running");
  47 + } else {
  48 + Logger.d("commandTimer set");
  49 + this.isWaiting = true;
  50 + setTimeout( callback, ms );
  51 + }
  52 + }
  53 +
  54 + // returns right key sequence.if right key sequence isn't built up, return null
  55 + this.getNextKeySequence = function() {
  56 + this.stopTimer();
  57 +
  58 + if( SettingManager.isValidKeySeq(this.a) ) {
  59 + // keySeq has corresponding command
  60 + ret = this.a;
  61 + this.a = "";
  62 + return ret;
  63 + } else {
  64 + if( !SettingManager.isValidKeySeqAvailable(this.a) ) {
  65 + Logger.d("invalid key sequence:" + this.a);
  66 + this.a = "";
  67 + } else {
  68 + // possible key sequences are available.wait next key.
  69 + var thisObj = this;
  70 +
  71 + this.startTimer( function() {
  72 + Logger.d("command wait timeout.reset key queue:" + thisObj.a);
  73 + thisObj.a = "";
  74 + thisObj.isWaiting = false;
  75 + }, SettingManager.get("commandWaitTimeOut") );
  76 + }
  77 + return null;
  78 + }
  79 + }
  80 +};
  81 +
  82 +var keyQueue = new KeyQueue ();
  83 +
  84 +function executeCommand (com) {
  85 + commandTable[com](com);
  86 +}
  87 +
  88 +function sendRequestToSelectedTab (com) {
  89 + chrome.tabs.getSelected(null, function(tab) {
  90 + chrome.tabs.sendRequest(tab.id, {command:com}, null);
  91 + });
  92 +}
  93 +
  94 +function openNewTab () {
  95 + chrome.tabs.create({}, function(tab){ });
  96 +}
  97 +
  98 +function closeCurTab () {
  99 + chrome.tabs.getSelected(null, function(tab) {
  100 + chrome.tabs.remove(tab.id, function(){
  101 + });
  102 + });
  103 +}
  104 +
  105 +function moveNextTab () {
  106 + moveTab( 1 );
  107 +}
  108 +
  109 +function movePrevTab () {
  110 + moveTab( -1 );
  111 +}
  112 +
  113 +function escape () {
  114 + keyQueue.reset();
  115 + sendRequestToSelectedTab( "blur" );
  116 +}
  117 +
  118 +function moveTab ( offset ) {
  119 + chrome.tabs.getAllInWindow( null, function( tabs ) {
  120 + var nTabs = tabs.length;
  121 + chrome.tabs.getSelected(null, function( tab ) {
  122 + var idx = tab.index + offset;
  123 + if( idx < 0 ) {
  124 + idx = nTabs - 1;
  125 + } else if( idx >= nTabs ) {
  126 + idx = 0;
  127 + }
  128 + chrome.tabs.update( tabs[idx].id, { selected:true }, function(){ });
  129 + });
  130 + });
  131 +}
  132 +
  133 +function getCommand (s) {
  134 + if( s == "<ESC>" ) {
  135 + keyQueue.reset();
  136 + var keySeq = s;
  137 + } else {
  138 + keyQueue.queue(s);
  139 + var keySeq = keyQueue.getNextKeySequence();
  140 + }
  141 +
  142 + var keyMap = SettingManager.get("keyMappings");
  143 + if( keyMap && keySeq )
  144 + return keyMap[keySeq];
  145 +}
281 content_script.js
... ... @@ -0,0 +1,281 @@
  1 +var keyPort;
  2 +var settingPort;
  3 +var modeInsert;
  4 +var modeSearch;
  5 +var settings = [];
  6 +
  7 +var reqListeners = {
  8 + scrollUp : reqScrollUp
  9 + ,scrollDown : reqScrollDown
  10 + ,scrollLeft : reqScrollLeft
  11 + ,scrollRight : reqScrollRight
  12 + ,pageUp : reqPageUp
  13 + ,pageDown : reqPageDown
  14 + ,goTop : reqGoTop
  15 + ,goBottom : reqGoBottom
  16 + ,reloadTab : reqReloadTab
  17 + ,backHist : reqBackHist
  18 + ,forwardHist : reqForwardHist
  19 + ,blur : reqBlur
  20 +}
  21 +
  22 +function reqScrollDown () {
  23 + window.scrollBy( 0, settings["scrollPixelCount"] );
  24 +}
  25 +
  26 +function reqScrollUp () {
  27 + window.scrollBy( 0, -settings["scrollPixelCount"] );
  28 +}
  29 +
  30 +function reqScrollLeft () {
  31 + window.scrollBy( -settings["scrollPixelCount"], 0 );
  32 +}
  33 +
  34 +function reqScrollRight () {
  35 + window.scrollBy( settings["scrollPixelCount"], 0 );
  36 +}
  37 +
  38 +function reqPageDown () {
  39 + window.scrollBy( 0, window.innerHeight / 2 );
  40 +}
  41 +
  42 +function reqPageUp () {
  43 + window.scrollBy( 0, -window.innerHeight / 2 );
  44 +}
  45 +
  46 +function reqGoTop () {
  47 + window.scrollTo( window.pageXOffset, 0 );
  48 +}
  49 +
  50 +function reqGoBottom () {
  51 + window.scrollTo( window.pageXOffset, document.body.scrollHeight - window.innerHeight );
  52 +}
  53 +
  54 +function reqBackHist () {
  55 + window.history.back();
  56 +}
  57 +
  58 +function reqForwardHist () {
  59 + window.history.forward();
  60 +}
  61 +
  62 +function reqBlur () {
  63 + document.activeElement.blur();
  64 +}
  65 +
  66 +function reqReloadTab() {
  67 + window.location.reload();
  68 +}
  69 +
  70 +function onBlur (e) {
  71 + Logger.d("onBlur");
  72 + exitInsertMode();
  73 +}
  74 +
  75 +
  76 +function onKeyDown (e) {
  77 + Logger.d("onKeyDown", e);
  78 +
  79 + if( preHandleKeyEvent(e) ) {
  80 + postKeyMessage(e);
  81 + }
  82 +}
  83 +
  84 +function onKeyPress (e) {
  85 + Logger.d( "onKeyPress", e );
  86 +
  87 + if( preHandleKeyEvent(e) ) {
  88 + postKeyMessage(e);
  89 + }
  90 +}
  91 +
  92 +function postKeyMessage (e) {
  93 + keyPort.postMessage({keyCode : e.keyCode,
  94 + charCode : e.charCode,
  95 + meta : e.metaKey,
  96 + alt : e.altKey,
  97 + ctrl : e.ctrlKey,
  98 + shift : e.shiftKey});
  99 +}
  100 +
  101 +function isOnlyModifier (e) {
  102 + switch(e.keyCode) {
  103 + case keyCodes.Shift:
  104 + case keyCodes.Ctrl:
  105 + case keyCodes.Meta:
  106 + case keyCodes.Alt:
  107 + return true;
  108 + default:
  109 + return false;
  110 + }
  111 +}
  112 +
  113 +// decide whether to post the key event and do some pre-post process
  114 +// return true if the key event can be posted.
  115 +function preHandleKeyEvent (e) {
  116 + // vichrome doesn't handle meta and alt key for now
  117 + if( e.metaKey || e.altKey ) {
  118 + return false;
  119 + }
  120 + if( isOnlyModifier(e) ) {
  121 + return false;
  122 + }
  123 +
  124 + // TODO:search mode
  125 +
  126 + if(isInInsertMode()){
  127 + if(e.type == "keydown") {
  128 + if( KeyManager.isESC(e.keyCode, e.ctrlKey) ) {
  129 + return true;
  130 + } else if(keyCodes.F1 <= e.keyCode && e.keyCode <= keyCodes.F12){
  131 + return true;
  132 + } else if( e.ctrlKey ) {
  133 + return true;
  134 + }
  135 + } else {
  136 + // character key do not need to be handled in insert mode
  137 + return false;
  138 + }
  139 + } else {
  140 + if(e.type == "keypress") {
  141 + event.preventDefault();
  142 + event.stopPropagation();
  143 + return true;
  144 + } else if(e.type == "keydown") {
  145 + // some web sites set their own key bind(google instant search etc).
  146 + // to prevent messing up vichrome's key bind from them,
  147 + // we have to stop event propagation here.
  148 + // TODO:we should only stop when a valid(handlable) key event come.
  149 + event.stopPropagation();
  150 + if( e.ctrlKey ) {
  151 + // TODO:some keys cannot be recognized with keyCode e.g. C-@
  152 + return true;
  153 + }
  154 + if( KeyManager.isESC( e.keyCode, e.ctrlKey ) ) {
  155 + return true;
  156 + }
  157 + // keydown only catch key codes that are not passed to keypress
  158 + switch(e.keyCode) {
  159 + case keyCodes.Tab :
  160 + case keyCodes.BS :
  161 + case keyCodes.DEL :
  162 + case keyCodes.Left :
  163 + case keyCodes.Up :
  164 + case keyCodes.Right :
  165 + case keyCodes.Down :
  166 + case keyCodes.F1 :
  167 + case keyCodes.F2 :
  168 + case keyCodes.F3 :
  169 + case keyCodes.F4 :
  170 + case keyCodes.F5 :
  171 + case keyCodes.F6 :
  172 + case keyCodes.F7 :
  173 + case keyCodes.F8 :
  174 + case keyCodes.F9 :
  175 + case keyCodes.F10 :
  176 + case keyCodes.F11 :
  177 + case keyCodes.F12 :
  178 + return true;
  179 + default:
  180 + return false;
  181 + }
  182 + }
  183 + }
  184 +}
  185 +
  186 +
  187 +function onFocus (e) {
  188 + if( isEditable(e.target) ) {
  189 + enterInsertMode();
  190 + } else {
  191 + exitInsertMode();
  192 + }
  193 + Logger.d("onFocus:insertMode=" + isInInsertMode() );
  194 +}
  195 +
  196 +function enterInsertMode () {
  197 + modeInsert = true;
  198 +}
  199 +
  200 +function exitInsertMode () {
  201 + modeInsert = false;
  202 +}
  203 +
  204 +function isInInsertMode () {
  205 + return modeInsert;
  206 +}
  207 +
  208 +function isInSearchMode () {
  209 + return modeSearch;
  210 +}
  211 +
  212 +function isEditable (target) {
  213 + if (target.isContentEditable) {
  214 + return true;
  215 + }
  216 +
  217 + if(target.nodeName=="INPUT" && target.type == "text") {
  218 + return true;
  219 + }
  220 +
  221 + ignoreList = ["TEXTAREA"];
  222 + if(ignoreList.indexOf(target.nodeName) >= 0){
  223 + return true;
  224 + }
  225 +
  226 + return false;
  227 +}
  228 +
  229 +function onSettingUpdated (msg) {
  230 + if(msg.name == "all") {
  231 + settings = msg.value;
  232 + } else {
  233 + settings[msg.name] = msg.value;
  234 + }
  235 +}
  236 +
  237 +function setupPorts() {
  238 + keyPort = chrome.extension.connect({ name : "key" });
  239 +
  240 + settingPort = chrome.extension.connect({ name : "reqSettings" });
  241 + settingPort.onMessage.addListener( onSettingUpdated );
  242 + settingPort.postMessage({ name : "all" });
  243 +}
  244 +
  245 +function addWindowListeners() {
  246 + window.addEventListener("keydown" , onKeyDown , true);
  247 + window.addEventListener("keypress" , onKeyPress , true);
  248 + window.addEventListener("focus" , onFocus , true);
  249 + window.addEventListener("blur" , onBlur , true);
  250 +}
  251 +
  252 +function addRequestListener() {
  253 + chrome.extension.onRequest.addListener(function(req, sender, sendResponse) {
  254 + if(reqListeners[req.command]) {
  255 + reqListeners[req.command]();
  256 + }
  257 +
  258 + sendResponse();
  259 + });
  260 +}
  261 +
  262 +function onEnabled() {
  263 + init();
  264 +}
  265 +
  266 +function init () {
  267 + setupPorts();
  268 + addRequestListener();
  269 + addWindowListeners();
  270 +
  271 + // should evaluate focused element on initialization.
  272 + if( isEditable( document.activeElement ) ) {
  273 + enterInsertMode();
  274 + }
  275 +}
  276 +
  277 +window.addEventListener("DOMContentLoaded", function() {
  278 + // DELTEME: onEnable should be triggered from background page.
  279 + onEnabled();
  280 +});
  281 +
87 keys.js
... ... @@ -0,0 +1,87 @@
  1 +var KeyManager = {
  2 + isESC : function (keyCode, ctrl) {
  3 + if( keyCode == keyCodes.ESC ) {
  4 + return true;
  5 + } else if( ctrl && keyCode == '[' ) {
  6 + return true;
  7 + } else {
  8 + return false;
  9 + }
  10 + },
  11 +
  12 + convertKeyCodeToStr : function (msg) {
  13 + key = "";
  14 + if(65 <= msg.keyCode && msg.keyCode <= 90){
  15 + if(msg.shift) {
  16 + key = String.fromCharCode(msg.keyCode);
  17 + } else {
  18 + key = String.fromCharCode(msg.keyCode + 32);
  19 + }
  20 + if(msg.ctrl) {
  21 + key = "<C-" + key + ">";
  22 + }
  23 +
  24 + return key;
  25 + } else if(31 <= msg.keyCode && msg.keyCode <= 126){
  26 + return String.fromCharCode(msg.keyCode);
  27 + }
  28 +
  29 + switch(msg.keyCode) {
  30 + case keyCodes.Tab : return "<TAB>";
  31 + case keyCodes.BS : return "<BS>";
  32 + case keyCodes.DEL : return "<DEL>";
  33 + case keyCodes.CR : return "<CR>";
  34 + case keyCodes.SP : return "<SP>";
  35 + case keyCodes.Left : return "<Left>";
  36 + case keyCodes.Up : return "<Up>";
  37 + case keyCodes.Right : return "<Right>";
  38 + case keyCodes.Down : return "<Down>";
  39 + case keyCodes.F1 : return "<F1>";
  40 + case keyCodes.F2 : return "<F2>";
  41 + case keyCodes.F3 : return "<F3>";
  42 + case keyCodes.F4 : return "<F4>";
  43 + case keyCodes.F5 : return "<F5>";
  44 + case keyCodes.F6 : return "<F6>";
  45 + case keyCodes.F7 : return "<F7>";
  46 + case keyCodes.F8 : return "<F8>";
  47 + case keyCodes.F9 : return "<F9>";
  48 + case keyCodes.F10 : return "<F10>";
  49 + case keyCodes.F11 : return "<F11>";
  50 + case keyCodes.F12 : return "<F12>";
  51 + }
  52 +
  53 + if(KeyManager.isESC(msg.keyCode, msg.ctrl)) {
  54 + return "<ESC>";
  55 + }
  56 + }
  57 +};
  58 +
  59 +var keyCodes = {
  60 + ESC : 27
  61 + ,Tab : 9
  62 + ,Shift : 16
  63 + ,BS : 8
  64 + ,Alt : 18
  65 + ,Ctrl : 17
  66 + ,Meta : 91
  67 + ,DEL : 46
  68 + ,CR : 13
  69 + ,SP : 32
  70 + ,Left : 37
  71 + ,Up : 38
  72 + ,Right : 39
  73 + ,Down : 40
  74 + ,F1 : 112
  75 + ,F2 : 113
  76 + ,F3 : 114
  77 + ,F4 : 115
  78 + ,F5 : 116
  79 + ,F6 : 117
  80 + ,F7 : 118
  81 + ,F8 : 119
  82 + ,F9 : 120
  83 + ,F10 : 121
  84 + ,F11 : 122
  85 + ,F12 : 123
  86 +};
  87 +
28 manifest.json
... ... @@ -0,0 +1,28 @@
  1 +{
  2 + "name": "vichrome",
  3 + "version": "0.1",
  4 + "description": "vi like key bind for chrome",
  5 + "browser_action": {
  6 + "default_icon": "icon.png"
  7 + },
  8 + "background_page": "background.html",
  9 + "options_page": "options.html",
  10 + "permissions": [
  11 + "tabs",
  12 + "bookmarks",
  13 + "http://*/*",
  14 + "https://*/*"
  15 + ],
  16 + "content_scripts": [
  17 + {
  18 + "matches": ["<all_urls>"],
  19 + "js": [
  20 + "keys.js",
  21 + "utils.js",
  22 + "content_script.js"
  23 + ],
  24 + "run_at": "document_start",
  25 + "all_frames": true
  26 + }
  27 + ]
  28 +}
103 settings.js
... ... @@ -0,0 +1,103 @@
  1 +var SettingManager = {
  2 + defaultSettings : {
  3 + "scrollPixelCount" : 40,
  4 + "searchEngine" : "http://www.google.com/",
  5 + "commandWaitTimeOut" : 2000,
  6 + "keyMappings" : {
  7 + j : "scrollDown"
  8 + ,k : "scrollUp"
  9 + ,h : "scrollLeft"
  10 + ,l : "scrollRight"
  11 + ,d : "pageDown"
  12 + ,u : "pageUp"
  13 + ,gg : "goTop"
  14 + ,G : "goBottom"
  15 + ,t : "openNewTab"
  16 + ,x : "closeCurTab"
  17 + ,">" : "moveNextTab"
  18 + ,"<" : "movePrevTab"
  19 + ,r : "reloadTab"
  20 + ,H : "backHist"
  21 + ,L : "forwardHist"
  22 + ,"<ESC>" : "escape"
  23 + },
  24 + },
  25 +
  26 + settingNames : [
  27 + "scrollPixelCount",
  28 + "searchEngine",
  29 + "commandWaitTimeOut",
  30 + "keyMappings",
  31 + ],
  32 +
  33 + availableKeySeq : [
  34 + "j"
  35 + ,"k"
  36 + ,"h"
  37 + ,"l"
  38 + ,"d"
  39 + ,"u"
  40 + ,"gg"
  41 + ,"G"
  42 + ,"k"
  43 + ,"t"
  44 + ,"x"
  45 + ,">"
  46 + ,"<"
  47 + ,"r"
  48 + ,"H"
  49 + ,"L"
  50 + ,"<ESC>"
  51 + ],
  52 +
  53 + associateKeyMap : {},
  54 +
  55 + getAll : function() {
  56 + settings = {};
  57 + for (var i=0; i < this.settingNames.length; i++) {
  58 + if ( localStorage[this.settingNames[i]] ) {
  59 + settings[this.settingNames[i]] = localStorage[this.settingNames[i]];
  60 + } else {
  61 + settings[this.settingNames[i]] = this.defaultSettings[this.settingNames[i]];
  62 + }
  63 + };
  64 +
  65 + return settings;
  66 + },
  67 +
  68 + get : function(name) {
  69 + if ( localStorage[name] ) {
  70 + return localStorage[name];
  71 + } else {
  72 + return this.defaultSettings[name];
  73 + }
  74 + },
  75 +
  76 + set : function(name, value) {
  77 + localStorage[name] = value;
  78 +
  79 + if(this.setCb)
  80 + this.setCb(name, value);
  81 + },
  82 +
  83 + setCb : null,
  84 +
  85 + isValidKeySeq : function(keySeq) {
  86 + if( this.availableKeySeq.indexOf( keySeq ) >= 0 ) {
  87 + return true;
  88 + } else {
  89 + return false;
  90 + }
  91 + },
  92 +
  93 + isValidKeySeqAvailable : function(keySeq) {
  94 + regexp = new RegExp( "^" + keySeq );
  95 + for (var i=0; i < this.availableKeySeq.length; i++) {
  96 + if( regexp.test( this.availableKeySeq[i] ) >= 0 ) {
  97 + return true;
  98 + }
  99 + }
  100 +
  101 + return false;
  102 + },
  103 +};
39 utils.js
... ... @@ -0,0 +1,39 @@
  1 +
  2 +var Logger = {
  3 + DEBUG : 1,
  4 + WARNING : 2,
  5 + ERROR : 3,
  6 + FATAL : 4,
  7 + NONE : 5,
  8 +
  9 + __log : function(a, o) {
  10 + if(o)
  11 + console.log( "vichrome:" + a + " :%o", o );
  12 + else
  13 + console.log( "vichrome:" + a );
  14 + },
  15 +
  16 + d : function(a, o) {
  17 + if(VICHROME_LOG_LEVEL >= Logger.DEBUG)
  18 + this.__log(a, o);
  19 + },
  20 +
  21 + w : function(a, o) {
  22 + if(VICHROME_LOG_LEVEL >= Logger.WARNING)
  23 + this.__log(a, o);
  24 + },
  25 +
  26 + e : function(a, o) {
  27 + if(VICHROME_LOG_LEVEL >= Logger.ERROR)
  28 + this.__log(a, o);
  29 + },
  30 +
  31 + f : function(a, o) {
  32 + if(VICHROME_LOG_LEVEL >= Logger.FATAL)
  33 + this.__log(a, o);
  34 + }
  35 +
  36 +};
  37 +
  38 +VICHROME_LOG_LEVEL = Logger.DEBUG;
  39 +
32 vichrome.js
... ... @@ -0,0 +1,32 @@
  1 +var portListeners = {
  2 + key : onKeyDown
  3 + ,reqSettings : reqGetSettings
  4 +}
  5 +
  6 +function onKeyDown (msg) {
  7 + var s = KeyManager.convertKeyCodeToStr(msg);
  8 + com = getCommand( s );
  9 + Logger.d( "onKeyDown: " + s + " : " + com );
  10 + if( com ) {
  11 + executeCommand( com );
  12 + }
  13 +}
  14 +
  15 +function reqGetSettings (msg, port) {
  16 + sendMsg = {name : msg.name};
  17 +
  18 + if(msg.name == "all") {
  19 + sendMsg["value"] = SettingManager.getAll();
  20 + } else {
  21 + sendMsg["value"] = SettingManager.get(msg.name);
  22 + }
  23 +
  24 + port.postMessage( sendMsg );
  25 +}
  26 +
  27 +function init () {
  28 + chrome.extension.onConnect.addListener(function(port) {
  29 + port.onMessage.addListener( portListeners[port.name] );
  30 + });
  31 +}
  32 +

0 comments on commit 2cbb317

Please sign in to comment.
Something went wrong with that request. Please try again.