Created
March 25, 2010 22:46
-
-
Save thedeeno/344234 to your computer and use it in GitHub Desktop.
Rough draft of layout trigger functionality
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Object model to support layout triggers. It consists of: | |
* - LayoutTriggerController | |
* - Layout Representation | |
* - 3 Triggers: Anchor, Hot, and Toggle | |
*/ | |
/* ************************************************************* | |
* LayoutTriggerController | |
*/ | |
// expects an object with 2 properties: triggers and defaultLayout | |
function LTController(options) { | |
this.defaultLayout = options.defaultLayout; | |
this.triggers = options.triggers; | |
this.revertToDefaultOnNextKeyPress = false; | |
// current layout starts as default | |
this.current = options.defaultLayout; | |
// add a helper function | |
this.triggers.findByKey = function(keyCode) | |
{ | |
for (var i=0;i<this.length;i++) { | |
if (this[i].matches(keyCode)) | |
return this[i]; | |
} | |
return null; | |
} | |
} | |
// mode management functions | |
LTController.prototype.revertLayoutOnNextKeyPress = function() { | |
this.revertToDefaultOnNextKeyPress = true; | |
} | |
LTController.prototype.handleKey = function(key) { | |
var trigger = this.triggers.findByKey(key); | |
if (trigger != null) { | |
// ask trigger to process key and pass some callbacks | |
trigger.ltc = this; // work around for now | |
trigger.handleKey(key); | |
} | |
if (key.cancel) { | |
//log('cancel key'); | |
return; | |
} | |
//log('process key normally'); | |
switch (key.value) { | |
case 1: | |
this.keyPress(key); | |
break; | |
case 2: | |
this.keyHold(key); | |
break; | |
default: | |
this.keyRelease(key); | |
} | |
} | |
LTController.prototype.inAuxMode = function() { | |
return this.current != this.defaultLayout; | |
} | |
LTController.prototype.setLayout = function(layout) { | |
this.revertToDefaultOnNextKeyPress = false; | |
log('enable layout: ' + layout.desc); | |
this.current = layout; | |
} | |
LTController.prototype.emitKey = function(key) { | |
this.current.emitKey(key); | |
} | |
LTController.prototype.onTriggerActivate = function(trigger) { | |
this.setLayout(trigger.layout); | |
} | |
LTController.prototype.onTriggerDeactivate = function(trigger) { | |
this.revertLayoutOnNextKeyPress(); | |
} | |
LTController.prototype.emitKey = function(code, value) { | |
this.current.emitKey(code, value); | |
} | |
// key event processors | |
LTController.prototype.keyPress = function(key) { | |
if (this.revertToDefaultOnNextKeyPress) | |
this.setLayout(this.defaultLayout); | |
this.current.emitKey(key.code, key.value); | |
} | |
LTController.prototype.keyHold = function(key) { | |
this.current.emitKey(key.code, key.value); | |
} | |
LTController.prototype.keyRelease = function(key) { | |
this.current.emitKey(key.code, key.value); | |
} | |
/* ************************************************************* | |
* Trigger | |
*/ | |
function Trigger(keyCode, layout) { | |
this.keyCode = keyCode; | |
this.layout = layout; | |
this.active = false; | |
}; | |
Trigger.prototype.matches = function(key) { | |
return this.keyCode == key.code; | |
} | |
Trigger.prototype.handleKey = function(key, onTriggerActivate, onTriggerDeactivate, emitKey) { | |
// do nothing | |
} | |
Trigger.prototype.activate = function() { | |
// log("activate trigger") | |
this.active = true; | |
this.ltc.onTriggerActivate(this); | |
} | |
Trigger.prototype.deactivate = function() { | |
// log("deactivate trigger"); | |
this.active = false; | |
this.ltc.onTriggerDeactivate(this); | |
} | |
/* ************************************************************* | |
* AnchorTrigger | |
*/ | |
function AnchorTrigger(keyCode, layout, emitBackspaceOnTrigger) { | |
Trigger.call(this, keyCode, layout); | |
this.emitBackspaceOnTrigger = emitBackspaceOnTrigger; | |
} | |
// extension | |
AnchorTrigger.prototype = new Trigger(); | |
AnchorTrigger.base = Trigger.prototype; | |
// functions | |
AnchorTrigger.prototype.handleKey = function(key) { | |
if (this.matches(key) && key.value == 2 && !this.active) // do only on first repeat | |
{ | |
this.tlc.emitKey(key.code, 0); // release key (even though it's still held down) | |
key.cancel = true; | |
this.activate(onTriggerActivate, emitKey); | |
} | |
if (this.matches(key) && key.value == 0 && this.active) | |
this.deactivate(onTriggerDeactivate); | |
if (this.active) | |
key.cancel = true; // supress key | |
} | |
AnchorTrigger.prototype.activate = function(onTriggerActivate) { | |
if (this.emitBackspaceOnTrigger) | |
{ | |
// log("emit backspace"); | |
this.tlc.emitKey(KEY_BACKSPACE, 1); | |
this.tlc.emitKey(KEY_BACKSPACE, 0); | |
} | |
AnchorTrigger.base.activate.call(this, onTriggerActivate); | |
} | |
/* ************************************************************* | |
* HotTrigger | |
*/ | |
function HotTrigger(keyCode, layout) { | |
Trigger.call(this, keyCode, layout); | |
} | |
// extension | |
HotTrigger.prototype = new Trigger(); | |
// functions | |
HotTrigger.prototype.handleKey = function(key) { | |
if (this.matches(key) && key.value == 1 && !this.active) // do only on key down | |
this.activate(); | |
if (this.matches(key) && key.value == 0 && this.active) | |
this.deactivate(); | |
} | |
/* ************************************************************* | |
* ToggleTrigger | |
*/ | |
function ToggleTrigger(keyCode, layout, ltController) { | |
Trigger.call(this, keyCode, layout, ltController); | |
} | |
// extension | |
ToggleTrigger.prototype = new Trigger(); | |
// functions | |
ToggleTrigger.prototype.handleKey = function(key) { | |
if (this.matches(key) && key.value == 1) { // do only on key down | |
if (!this.active) | |
this.activate(); | |
else | |
this.deactivate(); | |
} | |
} | |
/* ************************************************************* | |
* Layout | |
*/ | |
function Layout(config) { | |
this.sysSyms = _code2sym; | |
this.sysCodes = _sym2code; | |
this.desc = config.desc; | |
this.mapTable = new cloneObject(this.sysCodes); // start by copying the system layout | |
if (config.suppressUnknownKeys) | |
this.clearMappings(); // prevent translation of non-explicit mappings | |
config.mapping(this); | |
} | |
Layout.prototype.map = function(actual, translated) { | |
this.mapTable['KEY_' + actual] = this.sysCodes['KEY_' + translated]; | |
} | |
Layout.prototype.clearMappings = function() { | |
for (i in this.mapTable) { | |
this.mapTable[i] = null; | |
} | |
} | |
Layout.prototype.translateCode = function(sysCode) { | |
var s = this.sysSyms[sysCode]; | |
return this.mapTable[s]; | |
} | |
Layout.prototype.emitKey = function(code, value) { | |
var tcode = this.translateCode(code); | |
if (!tcode) { | |
log("suppress key, it's not in the layout"); | |
return; | |
} | |
log(this.desc + " emit " + this.sysSyms[tcode] + ' ' + tcode + ' ' + value); | |
emit(EV_KEY, tcode, value); | |
} | |
/* ************************************************************* | |
* Helper Functions | |
*/ | |
function cloneObject(source) { | |
for (i in source) { | |
if (typeof source[i] == 'source') { | |
this[i] = new cloneObject(source[i]); | |
} | |
else { | |
this[i] = source[i]; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Mangler that shows how to leverage LayoutTriggers. | |
// - The default layout is dvorak. | |
// - While LEFTCTRL is pressed the keyboard is in qwerty modethe layout back to qwerty, | |
// facilitating the use of standard hotkeys. | |
// - CAPSLOCK toggles a special 'navigation' auxiliary layout | |
include('keysyms.js'); | |
include('keynames.js'); | |
include('LayoutTriggers.js'); | |
// configuration | |
var navigation = { | |
desc : "navigation", | |
suppressUnknownKeys: true, | |
mapping: function(layout) { | |
layout.map('E', 'UP'); | |
layout.map('D', 'DOWN'); | |
layout.map('S', 'LEFT'); | |
layout.map('F', 'RIGHT'); | |
layout.map('W', 'HOME'); | |
layout.map('R', 'END'); | |
layout.map('Q', 'PAGEUP'); | |
layout.map('A', 'PAGEDOWN'); | |
layout.map('SEMICOLON', 'ENTER'); | |
layout.map('G', 'DELETE'); | |
layout.map('H', 'BACKSPACE'); | |
// inclunde triggers in layout, otherwise they may be suppressed | |
// while the layout will be activated it may cause some unexpected behaviour. | |
// for instance, if capslock is surpress on activatio but not deactivation | |
// it you'll be accidentally toggling capslock as well as the layout. | |
layout.map('LEFTSHIFT', 'LEFTSHIFT'); | |
layout.map('CAPSLOCK', 'CAPSLOCK'); | |
layout.map('SPACE', 'SPACE'); | |
} | |
}; | |
var dvorak = { | |
desc : "dvorak", | |
mapping : function(layout) { | |
layout.map('MINUS','LEFTBRACE'); | |
layout.map('EQUAL','RIGHTBRACE'); | |
layout.map('Q','APOSTROPHE'); | |
layout.map('W','COMMA'); | |
layout.map('E','DOT'); | |
layout.map('R','P'); | |
layout.map('T','Y'); | |
layout.map('Y','F'); | |
layout.map('U','G'); | |
layout.map('I','C'); | |
layout.map('O','R'); | |
layout.map('P','L'); | |
layout.map('LEFTBRACE','SLASH'); | |
layout.map('RIGHTBRACE','EQUAL'); | |
layout.map('A','A'); | |
layout.map('S','O'); | |
layout.map('D','E'); | |
layout.map('F','U'); | |
layout.map('G','I'); | |
layout.map('H','D'); | |
layout.map('J','H'); | |
layout.map('K','T'); | |
layout.map('L','N'); | |
layout.map('SEMICOLON','S'); | |
layout.map('APOSTROPHE','MINUS'); | |
layout.map('Z','SEMICOLON'); | |
layout.map('X','Q'); | |
layout.map('C','J'); | |
layout.map('V','K'); | |
layout.map('B','X'); | |
layout.map('N','B'); | |
layout.map('M','M'); | |
layout.map('COMMA','W'); | |
layout.map('DOT','V'); | |
layout.map('SLASH','Z'); | |
} | |
} | |
var qwerty = { | |
desc : "qwerty", | |
mapping: function(layout) { | |
// do nothing, the default input layout is qwerty so no need to translate | |
} | |
} | |
var dvorak = new Layout(dvorak); | |
var tlc = new LTController({ | |
defaultLayout : dvorak, | |
triggers : [ | |
new ToggleTrigger(KEY_CAPSLOCK, new Layout(navigation)), | |
new HotTrigger(KEY_LEFTCTRL, new Layout(qwerty), false), | |
new AnchorTrigger(KEY_SPACE, new Layout(navigation)) | |
] | |
}); | |
/* This is the entry point of event processing, this function will be called by the system for every key event. | |
* IMPORTANT NOTE: this function must eventually emit some events back to the system otherwise your system | |
* completely stops responding to keyboard!!! | |
* Normally you want to emit almost all events with an exception of some keys you wish to process in a different way. | |
*/ | |
function process(ev){ | |
switch (ev.type) { | |
case EV_KEY: | |
processKey(ev); | |
break; | |
default: | |
emit(ev.type, ev.code, ev.value); | |
} | |
} | |
function processKey(key) { | |
tlc.handleKey(key); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment