this.buffer = "";
this.events = [];
+ events.dbg("STACK " + mode);
+
let main = { __proto__: mode.main, params: mode.params };
- let keyModes = array([mode.params.keyModes, main, mode.main.allBases]).flatten().compact();
+ this.modes = array([mode.params.keyModes, main, mode.main.allBases.slice(1)]).flatten().compact();
if (builtin)
hives = hives.filter(function (h) h.name === "builtin");
- this.processors = keyModes.map(function (m) hives.map(function (h) KeyProcessor(m, h)))
- .flatten().array;
+ this.processors = this.modes.map(function (m) hives.map(function (h) KeyProcessor(m, h)))
+ .flatten().array;
this.ownsBuffer = !this.processors.some(function (p) p.main.ownsBuffer);
for (let [i, input] in Iterator(this.processors)) {
let params = input.main.params;
+
if (params.preExecute)
input.preExecute = params.preExecute;
+
if (params.postExecute)
input.postExecute = params.postExecute;
+
if (params.onKeyPress && input.hive === mappings.builtin)
input.fallthrough = function fallthrough(events) {
return params.onKeyPress(events) === false ? Events.KILL : Events.PASS;
}
let hive = options.get("passkeys")[this.main.input ? "inputHive" : "commandHive"];
- if (!builtin && hive.active
- && (!dactyl.focusedElement || events.isContentNode(dactyl.focusedElement)))
+ if (!builtin && hive.active && (!dactyl.focusedElement || events.isContentNode(dactyl.focusedElement)))
this.processors.unshift(KeyProcessor(modes.BASE, hive));
},
+ passUnknown: Class.memoize(function () options.get("passunknown").getKey(this.modes)),
+
notify: function () {
+ events.dbg("NOTIFY()");
events.keyEvents = [];
events.processor = null;
- if (!this.execute(Events.KILL, true)) {
+ if (!this.execute(undefined, true)) {
events.processor = this;
events.keyEvents = this.keyEvents;
}
callable(result) ? result.toSource().substr(0, 50) : result),
execute: function execute(result, force) {
+ events.dbg("EXECUTE(" + this._result(result) + ", " + force + ") events:" + this.events.length
+ + " processors:" + this.processors.length + " actions:" + this.actions.length);
+
+ let processors = this.processors;
+ let length = 1;
- if (force && this.actions.length)
- this.processors.length = 0;
+ if (force)
+ this.processors = [];
if (this.ownsBuffer)
statusline.inputBuffer = this.processors.length ? this.buffer : "";
if (!this.processors.some(function (p) !p.extended) && this.actions.length) {
- if (this._actions.length == 0) {
- dactyl.beep();
- events.feedingKeys = false;
- }
+ // We have matching actions and no processors other than
+ // those waiting on further arguments. Execute actions as
+ // long as they continue to return PASS.
for (var action in values(this.actions)) {
while (callable(action)) {
+ length = action.eventLength;
action = dactyl.trapErrors(action);
- events.dbg("ACTION RES: " + this._result(action));
+ events.dbg("ACTION RES: " + length + " " + this._result(action));
}
if (action !== Events.PASS)
break;
}
+ // Result is the result of the last action. Unless it's
+ // PASS, kill any remaining argument processors.
result = action !== undefined ? action : Events.KILL;
if (action !== Events.PASS)
this.processors.length = 0;
}
else if (this.processors.length) {
+ // We're still waiting on the longest matching processor.
+ // Kill the event, set a timeout to give up waiting if applicable.
+
result = Events.KILL;
- if (this.actions.length && options["timeout"])
+ if (options["timeout"] && (this.actions.length || events.hasNativeKey(this.events[0], this.main, this.passUnknown)))
this.timer = services.Timer(this, options["timeoutlen"], services.Timer.TYPE_ONE_SHOT);
}
else if (result !== Events.KILL && !this.actions.length &&
- (this.events.length > 1 ||
- this.processors.some(function (p) !p.main.passUnknown))) {
- result = Events.KILL;
+ !(this.events[0].isReplay || this.passUnknown
+ || this.modes.some(function (m) m.passEvent(this), this.events[0]))) {
+ // No patching processors, this isn't a fake, pass-through
+ // event, we're not in pass-through mode, and we're not
+ // choosing to pass unknown keys. Kill the event and beep.
+
+ result = Events.ABORT;
if (!Events.isEscape(this.events.slice(-1)[0]))
dactyl.beep();
events.feedingKeys = false;
}
else if (result === undefined)
+ // No matching processors, we're willing to pass this event,
+ // and we don't have a default action from a processor. Just
+ // pass the event.
result = Events.PASS;
- events.dbg("RESULT: " + this._result(result));
-
- if (result === Events.PASS || result === Events.PASS_THROUGH)
- if (this.events[0].originalTarget)
- this.events[0].originalTarget.dactylKeyPress = undefined;
+ events.dbg("RESULT: " + length + " " + this._result(result) + "\n\n");
if (result !== Events.PASS || this.events.length > 1)
- Events.kill(this.events[this.events.length - 1]);
+ if (result !== Events.ABORT || !this.events[0].isReplay)
+ Events.kill(this.events[this.events.length - 1]);
+
+ if (result === Events.PASS_THROUGH || result === Events.PASS && this.passUnknown)
+ events.passing = true;
+
+ if (result === Events.PASS_THROUGH && this.keyEvents.length)
+ events.dbg("PASS_THROUGH:\n\t" + this.keyEvents.map(function (e) [e.type, events.toString(e)]).join("\n\t"));
if (result === Events.PASS_THROUGH)
events.feedevents(null, this.keyEvents, { skipmap: true, isMacro: true, isReplay: true });
- else if (result === Events.PASS || result === Events.ABORT) {
+ else {
let list = this.events.filter(function (e) e.getPreventDefault() && !e.dactylDefaultPrevented);
- if (list.length)
- events.dbg("REFEED: " + list.map(events.closure.toString).join(""));
- events.feedevents(null, list, { skipmap: true, isMacro: true, isReplay: true });
+
+ if (result === Events.PASS)
+ events.dbg("PASS THROUGH: " + list.slice(0, length).filter(function (e) e.type === "keypress").map(events.closure.toString));
+ if (list.length > length)
+ events.dbg("REFEED: " + list.slice(length).filter(function (e) e.type === "keypress").map(events.closure.toString));
+
+ if (result === Events.PASS)
+ events.feedevents(null, list.slice(0, length), { skipmap: true, isMacro: true, isReplay: true });
+ if (list.length > length && this.processors.length === 0)
+ events.feedevents(null, list.slice(length));
}
return this.processors.length === 0;
let actions = [];
let processors = [];
- events.dbg("KEY: " + key + " skipmap: " + event.skipmap + " macro: " + event.isMacro + " replay: " + event.isReplay);
+ events.dbg("PROCESS(" + key + ") skipmap: " + event.skipmap + " macro: " + event.isMacro + " replay: " + event.isReplay);
for (let [i, input] in Iterator(this.processors)) {
let res = input.process(event);
if (res === Events.KILL)
break;
- buffer = buffer || input.inputBuffer;
-
if (callable(res))
actions.push(res);
events.dbg("RESULT: " + event.getPreventDefault() + " " + this._result(result));
events.dbg("ACTIONS: " + actions.length + " " + this.actions.length);
- events.dbg("PROCESSORS:", processors);
+ events.dbg("PROCESSORS:", processors, "\n");
this._actions = actions;
this.actions = actions.concat(this.actions);
+ for (let action in values(actions))
+ if (!("eventLength" in action))
+ action.eventLength = this.events.length;
+
if (result === Events.KILL)
this.actions = [];
else if (!this.actions.length && !processors.length)
function execute() {
if (self.preExecute)
self.preExecute.apply(self, args);
- let res = map.execute.call(map, update({ self: self.main.params.mappingSelf || self.main.mappingSelf || map },
- args));
+
+ args.self = self.main.params.mappingSelf || self.main.mappingSelf || map;
+ let res = map.execute.call(map, args);
+
if (self.postExecute)
self.postExecute.apply(self, args);
return res;
}
});
+/**
+ * A hive used mainly for tracking event listeners and cleaning them up when a
+ * group is destroyed.
+ */
var EventHive = Class("EventHive", Contexts.Hive, {
init: function init(group) {
init.supercall(this, group);
var [self, events] = [null, array.toObject([[event, callback]])];
else {
[self, events] = [event, event[callback || "events"]];
- [,, capture, allowUntrusted] = arguments;
+ [, , capture, allowUntrusted] = arguments;
}
+ if (Set.has(events, "input") && !Set.has(events, "dactyl-input"))
+ events["dactyl-input"] = events.input;
+
for (let [event, callback] in Iterator(events)) {
let args = [Cu.getWeakReference(target),
event,
dbg: function () {},
init: function () {
- const self = this;
this.keyEvents = [];
update(this, {
util.overlayWindow(window, {
append: <e4x xmlns={XUL}>
<window id={document.documentElement.id}>
- <!--this notifies us also of focus events in the XUL
- from: http://developer.mozilla.org/en/docs/XUL_Tutorial:Updating_Commands !-->
- <!-- I don't think we really need this. ––Kris -->
+ <!-- http://developer.mozilla.org/en/docs/XUL_Tutorial:Updating_Commands -->
<commandset id="dactyl-onfocus" commandupdater="true" events="focus"
oncommandupdate="dactyl.modules.events.onFocusChange(event);"/>
<commandset id="dactyl-onselect" commandupdater="true" events="select"
subtract: ["Minus", "Subtract"]
};
- this._pseudoKeys = set(["count", "leader", "nop", "pass"]);
+ this._pseudoKeys = Set(["count", "leader", "nop", "pass"]);
this._key_key = {};
this._code_key = {};
this._key_code = {};
+ this._code_nativeKey = {};
for (let list in values(this._keyTable))
for (let v in values(list)) {
}
for (let [k, v] in Iterator(KeyEvent)) {
+ this._code_nativeKey[v] = k.substr(4);
+
k = k.substr(7).toLowerCase();
let names = [k.replace(/(^|_)(.)/g, function (m, n1, n2) n2.toUpperCase())
.replace(/^NUMPAD/, "k")];
}
this._activeMenubar = false;
- this.listen(window, this, "events", true);
-
- dactyl.registerObserver("modeChange", function () {
- delete self.processor;
- });
+ this.listen(window, this, "events");
},
signals: {
"browser.locationChange": function (webProgress, request, uri) {
options.get("passkeys").flush();
+ },
+ "modes.change": function (oldMode, newMode) {
+ delete this.processor;
}
},
- /**
- * Adds an event listener for this session and removes it on
- * dactyl shutdown.
- *
- * @param {Element} target The element on which to listen.
- * @param {string} event The event to listen for.
- * @param {function} callback The function to call when the event is received.
- * @param {boolean} capture When true, listen during the capture
- * phase, otherwise during the bubbling phase.
- */
get listen() this.builtin.closure.listen,
addSessionListener: deprecated("events.listen", { get: function addSessionListener() this.listen }),
timeRecorded: Date.now()
});
- dactyl.log("Recorded " + this.recording + ": " + this._macroKeys.join(""), 9);
+ dactyl.log(_("macro.recorded", this.recording, this._macroKeys.join("")), 9);
dactyl.echomsg(_("macro.recorded", this.recording));
}
this._recording = macro || null;
if (quiet)
commandline.quiet = quiet;
+ keys = mappings.expandLeader(keys);
+
for (let [, evt_obj] in Iterator(events.fromString(keys))) {
let now = Date.now();
- for (let type in values(["keydown", "keyup", "keypress"])) {
+ let key = events.toString(evt_obj);
+ for (let type in values(["keydown", "keypress", "keyup"])) {
let evt = update({}, evt_obj, { type: type });
+ if (type !== "keypress" && !evt.keyCode)
+ evt.keyCode = evt._keyCode || 0;
if (isObject(noremap))
update(evt, noremap);
evt.dactylSavedEvents = savedEvents;
this.feedingEvent = evt;
- let event = events.create(document.commandDispatcher.focusedWindow.document, type, evt);
- if (!evt_obj.dactylString && !evt_obj.dactylShift && !mode)
- events.dispatch(dactyl.focusedElement || buffer.focusedFrame, event, evt);
+ let doc = document.commandDispatcher.focusedWindow.document;
+ let event = events.create(doc, type, evt);
+ let target = dactyl.focusedElement
+ || ["complete", "interactive"].indexOf(doc.readyState) >= 0 && doc.documentElement
+ || doc.defaultView;
+
+ if (target instanceof Element && !Events.isInputElement(target) &&
+ ["<Return>", "<Space>"].indexOf(key) == -1)
+ target = target.ownerDocument.documentElement;
+
+ if (!evt_obj.dactylString && !mode)
+ events.dispatch(target, event, evt);
else if (type === "keypress")
events.events.keypress.call(events, event);
}
* @param {Object} opts The pseudo-event. @optional
*/
create: function (doc, type, opts) {
- opts = opts || {};
- var DEFAULTS = {
+ const DEFAULTS = {
HTML: {
type: type, bubbles: true, cancelable: false
},
relatedTarget: null
}
};
- const TYPES = {
- change: "", input: "", submit: "",
- click: "Mouse", mousedown: "Mouse", mouseup: "Mouse",
- mouseover: "Mouse", mouseout: "Mouse",
- keypress: "Key", keyup: "Key", keydown: "Key"
- };
- var t = TYPES[type];
+
+ opts = opts || {};
+
+ var t = this._create_types[type];
var evt = doc.createEvent((t || "HTML") + "Events");
let defaults = DEFAULTS[t || "HTML"];
- evt["init" + t + "Event"].apply(evt, Object.keys(defaults)
- .map(function (k) k in opts ? opts[k]
- : defaults[k]));
+
+ let args = Object.keys(defaults)
+ .map(function (k) k in opts ? opts[k] : defaults[k]);
+
+ evt["init" + t + "Event"].apply(evt, args);
return evt;
},
+ _create_types: Class.memoize(function () iter(
+ {
+ Mouse: "click mousedown mouseout mouseover mouseup",
+ Key: "keydown keypress keyup",
+ "": "change dactyl-input input submit"
+ }
+ ).map(function ([k, v]) v.split(" ").map(function (v) [v, k]))
+ .flatten()
+ .toObject()),
+
/**
* Converts a user-input string of keys into a canonical
* representation.
return events.fromString(keys, unknownOk).map(events.closure.toString).join("");
},
- iterKeys: function (keys) {
+ iterKeys: function (keys) iter(function () {
let match, re = /<.*?>?>|[^<]/g;
while (match = re.exec(keys))
yield match[0];
- },
+ }()),
/**
* Dispatches an event to an element as if it were a native event.
let out = [];
for (let match in util.regexp.iterate(/<.*?>?>|[^<]|<(?!.*>)/g, input)) {
let evt_str = match[0];
+
let evt_obj = { ctrlKey: false, shiftKey: false, altKey: false, metaKey: false,
keyCode: 0, charCode: 0, type: "keypress" };
- if (evt_str.length > 1) { // <.*?>
- let [match, modifier, keyname] = evt_str.match(/^<((?:[CSMA]-)*)(.+?)>$/i) || [false, '', ''];
- modifier = modifier.toUpperCase();
+ if (evt_str.length == 1) {
+ evt_obj.charCode = evt_str.charCodeAt(0);
+ evt_obj._keyCode = this._key_code[evt_str[0].toLowerCase()];
+ evt_obj.shiftKey = evt_str !== evt_str.toLowerCase();
+ }
+ else {
+ let [match, modifier, keyname] = evt_str.match(/^<((?:[*12CASM⌘]-)*)(.+?)>$/i) || [false, '', ''];
+ modifier = Set(modifier.toUpperCase());
keyname = keyname.toLowerCase();
evt_obj.dactylKeyname = keyname;
if (/^u[0-9a-f]+$/.test(keyname))
keyname = String.fromCharCode(parseInt(keyname.substr(1), 16));
if (keyname && (unknownOk || keyname.length == 1 || /mouse$/.test(keyname) ||
- this._key_code[keyname] || set.has(this._pseudoKeys, keyname))) {
- evt_obj.ctrlKey = /C-/.test(modifier);
- evt_obj.altKey = /A-/.test(modifier);
- evt_obj.shiftKey = /S-/.test(modifier);
- evt_obj.metaKey = /M-/.test(modifier);
+ this._key_code[keyname] || Set.has(this._pseudoKeys, keyname))) {
+ evt_obj.globKey ="*" in modifier;
+ evt_obj.ctrlKey ="C" in modifier;
+ evt_obj.altKey ="A" in modifier;
+ evt_obj.shiftKey ="S" in modifier;
+ evt_obj.metaKey ="M" in modifier || "⌘" in modifier;
+ evt_obj.dactylShift = evt_obj.shiftKey;
if (keyname.length == 1) { // normal characters
- if (evt_obj.shiftKey) {
+ if (evt_obj.shiftKey)
keyname = keyname.toUpperCase();
- if (keyname == keyname.toLowerCase())
- evt_obj.dactylShift = true;
- }
evt_obj.charCode = keyname.charCodeAt(0);
+ evt_obj._keyCode = this._key_code[keyname.toLowerCase()];
}
- else if (set.has(this._pseudoKeys, keyname)) {
+ else if (Set.has(this._pseudoKeys, keyname)) {
evt_obj.dactylString = "<" + this._key_key[keyname] + ">";
}
else if (/mouse$/.test(keyname)) { // mouse events
continue;
}
}
- else // a simple key (no <...>)
- evt_obj.charCode = evt_str.charCodeAt(0);
// TODO: make a list of characters that need keyCode and charCode somewhere
if (evt_obj.keyCode == 32 || evt_obj.charCode == 32)
if (evt_obj.keyCode == 60 || evt_obj.charCode == 60)
evt_obj.charCode = evt_obj.keyCode = 60; // <lt>
- evt_obj.modifiers = (evt_obj.ctrlKey && Ci.nsIDOMNSEvent.CONTROL_MASK)
- | (evt_obj.altKey && Ci.nsIDOMNSEvent.ALT_MASK)
+ evt_obj.modifiers = (evt_obj.ctrlKey && Ci.nsIDOMNSEvent.CONTROL_MASK)
+ | (evt_obj.altKey && Ci.nsIDOMNSEvent.ALT_MASK)
| (evt_obj.shiftKey && Ci.nsIDOMNSEvent.SHIFT_MASK)
- | (evt_obj.metaKey && Ci.nsIDOMNSEvent.META_MASK);
+ | (evt_obj.metaKey && Ci.nsIDOMNSEvent.META_MASK);
out.push(evt_obj);
}
let key = null;
let modifier = "";
+ if (event.globKey)
+ modifier += "*-";
if (event.ctrlKey)
modifier += "C-";
if (event.altKey)
key = key.toUpperCase();
else
key = key.toLowerCase();
+
if (!modifier && /^[a-z0-9]$/i.test(key))
return key;
}
else {
// a shift modifier is only allowed if the key is alphabetical and used in a C-A-M- mapping in the uppercase,
// or if the shift has been forced for a non-alphabetical character by the user while :map-ping
- if (key != key.toLowerCase() && (event.ctrlKey || event.altKey || event.metaKey) || event.dactylShift)
+ if (key !== key.toLowerCase() && (event.ctrlKey || event.altKey || event.metaKey) || event.dactylShift)
modifier += "S-";
if (/^\s$/.test(key))
key = let (s = charCode.toString(16)) "U" + "0000".substr(4 - s.length) + s;
return key;
}
}
- if (key == null)
+ if (key == null) {
+ if (event.shiftKey)
+ modifier += "S-";
key = this._key_key[event.dactylKeyname] || event.dactylKeyname;
+ }
if (key == null)
return null;
}
},
/**
- * Whether *key* is a key code defined to accept/execute input on the
- * command line.
+ * Returns true if there's a known native key handler for the given
+ * event in the given mode.
+ *
+ * @param {Event} event A keypress event.
+ * @param {Modes.Mode} mode The main mode.
+ * @param {boolean} passUnknown Whether unknown keys should be passed.
+ */
+ hasNativeKey: function hasNativeKey(event, mode, passUnknown) {
+ if (mode.input && event.charCode && !(event.ctrlKey || event.metaKey))
+ return true;
+
+ if (!passUnknown)
+ return false;
+
+ var elements = document.getElementsByTagNameNS(XUL, "key");
+ var filters = [];
+
+ if (event.keyCode)
+ filters.push(["keycode", this._code_nativeKey[event.keyCode]]);
+ if (event.charCode) {
+ let key = String.fromCharCode(event.charCode);
+ filters.push(["key", key.toUpperCase()],
+ ["key", key.toLowerCase()]);
+ }
+
+ let accel = util.OS.isMacOSX ? "metaKey" : "ctrlKey";
+
+ let access = iter({ 1: "shiftKey", 2: "ctrlKey", 4: "altKey", 8: "metaKey" })
+ .filter(function ([k, v]) this & k, prefs.get("ui.key.chromeAccess"))
+ .map(function ([k, v]) [v, true])
+ .toObject();
+
+ outer:
+ for (let [, key] in iter(elements))
+ if (filters.some(function ([k, v]) key.getAttribute(k) == v)) {
+ let keys = { ctrlKey: false, altKey: false, shiftKey: false, metaKey: false };
+ let needed = { ctrlKey: event.ctrlKey, altKey: event.altKey, shiftKey: event.shiftKey, metaKey: event.metaKey };
+
+ let modifiers = (key.getAttribute("modifiers") || "").trim().split(/[\s,]+/);
+ for (let modifier in values(modifiers))
+ switch (modifier) {
+ case "access": update(keys, access); break;
+ case "accel": keys[accel] = true; break;
+ default: keys[modifier + "Key"] = true; break;
+ case "any":
+ if (!iter.some(keys, function ([k, v]) v && needed[k]))
+ continue outer;
+ for (let [k, v] in iter(keys)) {
+ if (v)
+ needed[k] = false;
+ keys[k] = false;
+ }
+ break;
+ }
+
+ if (iter(needed).every(function ([k, v]) v == keys[k]))
+ return key;
+ }
+
+ return false;
+ },
+
+ /**
+ * Returns true if *key* is a key code defined to accept/execute input on
+ * the command line.
*
* @param {string} key The key code to test.
* @returns {boolean}
isAcceptKey: function (key) key == "<Return>" || key == "<C-j>" || key == "<C-m>",
/**
- * Whether *key* is a key code defined to reject/cancel input on the
- * command line.
+ * Returns true if *key* is a key code defined to reject/cancel input on
+ * the command line.
*
* @param {string} key The key code to test.
* @returns {boolean}
*/
isCancelKey: function (key) key == "<Esc>" || key == "<C-[>" || key == "<C-c>",
+ /**
+ * Returns true if *node* belongs to the current content document or any
+ * sub-frame thereof.
+ *
+ * @param {Node|Document|Window} node The node to test.
+ * @returns {boolean}
+ */
isContentNode: function isContentNode(node) {
let win = (node.ownerDocument || node).defaultView || node;
return XPCNativeWrapper(win).top == content;
if (buffer.loaded)
return true;
- dactyl.echo(_("macro.loadWaiting"), commandline.DISALLOW_MULTILINE);
+ dactyl.echo(_("macro.loadWaiting"), commandline.FORCE_SINGLELINE);
const maxWaitTime = (time || 25);
- util.waitFor(function () !events.feedingKeys || buffer.loaded, this, maxWaitTime * 1000, true);
+ util.waitFor(function () buffer.loaded, this, maxWaitTime * 1000, true);
+ dactyl.echo("", commandline.FORCE_SINGLELINE);
if (!buffer.loaded)
dactyl.echoerr(_("macro.loadFailed", maxWaitTime));
let win = (elem.ownerDocument || elem).defaultView || elem;
- if (events.isContentNode(elem) && !buffer.focusAllowed(elem)
- && !(services.focus.getLastFocusMethod(win) & 0x7000)
+ if (!(services.focus.getLastFocusMethod(win) & 0x7000)
+ && events.isContentNode(elem)
+ && !buffer.focusAllowed(elem)
&& isinstance(elem, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, Window])) {
+
if (elem.frameElement)
dactyl.focusContent(true);
else if (!(elem instanceof Window) || Editor.getEditor(elem))
dactyl.focus(window);
}
+
+ if (elem instanceof Element)
+ elem.dactylFocusAllowed = undefined;
},
/*
elem.dactylKeyPress = elem.value;
util.timeout(function () {
if (elem.dactylKeyPress !== undefined && elem.value !== elem.dactylKeyPress)
- events.dispatch(elem, events.create(elem.ownerDocument, "input"));
+ events.dispatch(elem, events.create(elem.ownerDocument, "dactyl-input"));
elem.dactylKeyPress = undefined;
});
}
let ignore = false;
- if (modes.main == modes.PASS_THROUGH)
+ if (mode.main == modes.PASS_THROUGH)
ignore = !Events.isEscape(key) && key != "<C-v>";
- else if (modes.main == modes.QUOTE) {
+ else if (mode.main == modes.QUOTE) {
if (modes.getStack(1).main == modes.PASS_THROUGH) {
- mode.params.mainMode = modes.getStack(2).main;
+ mode = Modes.StackElement(modes.getStack(2).main);
ignore = Events.isEscape(key);
}
else if (events.shouldPass(event))
- mode.params.mainMode = modes.getStack(1).main;
+ mode = Modes.StackElement(modes.getStack(1).main);
else
ignore = true;
- if (ignore && !Events.isEscape(key))
- modes.pop();
+ modes.pop();
}
else if (!event.isMacro && !event.noremap && events.shouldPass(event))
ignore = true;
},
keyup: function onKeyUp(event) {
- this.keyEvents.push(event);
+ if (event.type == "keydown")
+ this.keyEvents.push(event);
+ else if (!this.processor)
+ this.keyEvents = [];
- let pass = this.feedingEvent && this.feedingEvent.isReplay ||
+ let pass = this.passing && !event.isMacro ||
+ this.feedingEvent && this.feedingEvent.isReplay ||
event.isReplay ||
modes.main == modes.PASS_THROUGH ||
modes.main == modes.QUOTE
&& modes.getStack(1).main !== modes.PASS_THROUGH
&& !this.shouldPass(event) ||
- !modes.passThrough && this.shouldPass(event);
+ !modes.passThrough && this.shouldPass(event) ||
+ !this.processor && event.type === "keydown"
+ && options.get("passunknown").getKey(modes.main.allBases)
+ && let (key = events.toString(event))
+ !modes.main.allBases.some(
+ function (mode) mappings.hives.some(
+ function (hive) hive.get(mode, key) || hive.getCandidates(mode, key)));
+
+ if (event.type === "keydown")
+ this.passing = pass;
- events.dbg("ON " + event.type.toUpperCase() + " " + this.toString(event) + " pass: " + pass);
+ events.dbg("ON " + event.type.toUpperCase() + " " + this.toString(event) + " pass: " + pass + " replay: " + event.isReplay + " macro: " + event.isMacro);
// Prevents certain sites from transferring focus to an input box
// before we get a chance to process our key bindings on the
// "keypress" event.
- if (!pass && !Events.isInputElement(dactyl.focusedElement))
+ if (!pass)
event.stopPropagation();
},
keydown: function onKeyDown(event) {
+ if (!event.isMacro)
+ this.passing = false;
this.events.keyup.call(this, event);
},
let elem = event.target;
let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem;
- for (; win; win = win != win.parent && win.parent)
+ for (; win; win = win != win.parent && win.parent) {
+ for (; elem instanceof Element; elem = elem.parentNode)
+ elem.dactylFocusAllowed = true;
win.document.dactylFocusAllowed = true;
+ }
},
popupshown: function onPopupShown(event) {
modes.push(modes.MENU);
},
- popuphidden: function onPopupHidden() {
- // gContextMenu is set to NULL, when a context menu is closed
+ popuphidden: function onPopupHidden(event) {
if (window.gContextMenu == null && !this._activeMenubar)
modes.remove(modes.MENU, true);
modes.remove(modes.AUTOCOMPLETE);
if (elem == null && urlbar && urlbar.inputField == this._lastFocus)
util.threadYield(true); // Why? --Kris
- while (modes.main.ownsFocus && !modes.topOfStack.params.holdFocus)
+ while (modes.main.ownsFocus && modes.topOfStack.params.ownsFocus != elem
+ && !modes.topOfStack.params.holdFocus)
modes.pop(null, { fromFocus: true });
}
finally {
this._lastFocus = elem;
+
+ if (modes.main.ownsFocus)
+ modes.topOfStack.params.ownsFocus = elem;
}
},
key === "<Esc>" || key === "<C-[>",
isHidden: function isHidden(elem, aggressive) {
- for (let e = elem; e instanceof Element; e = e.parentNode) {
- if (util.computedStyle(e).visibility !== "visible" ||
- aggressive && e.boxObject && e.boxObject.height === 0)
- return true;
- }
+ if (util.computedStyle(elem).visibility !== "visible")
+ return true;
+
+ if (aggressive)
+ for (let e = elem; e instanceof Element; e = e.parentNode) {
+ if (!/set$/.test(e.localName) && e.boxObject && e.boxObject.height === 0)
+ return true;
+ else if (e.namespaceURI == XUL && e.localName === "panel")
+ break;
+ }
return false;
},
isInputElement: function isInputElement(elem) {
- return elem instanceof HTMLInputElement && set.has(util.editableInputs, elem.type) ||
- isinstance(elem, [HTMLIsIndexElement, HTMLEmbedElement,
+ return elem instanceof HTMLInputElement && Set.has(util.editableInputs, elem.type) ||
+ isinstance(elem, [HTMLEmbedElement,
HTMLObjectElement, HTMLSelectElement,
HTMLTextAreaElement,
- Ci.nsIDOMXULTreeElement, Ci.nsIDOMXULTextBoxElement]) ||
+ Ci.nsIDOMXULTextBoxElement]) ||
elem instanceof Window && Editor.getEditor(elem);
},
else
dactyl.echoerr(_("error.argumentRequired"));
}, {
+ argCount: "?",
bang: true,
completer: function (context) completion.macro(context),
literal: 0
});
- commands.add(["macros"],
+ commands.add(["mac[ros]"],
"List all macros",
function (args) { completion.listCompleter("macro", args[0]); }, {
argCount: "?",
mappings: function () {
mappings.add([modes.MAIN],
- ["<A-b>"], "Process the next key as a builtin mapping",
+ ["<A-b>", "<pass-next-key-builtin>"], "Process the next key as a builtin mapping",
function () {
events.processor = ProcessorStack(modes.getStack(0), mappings.hives.array, true);
events.processor.keyEvents = events.keyEvents;
["<C-z>", "<pass-all-keys>"], "Temporarily ignore all " + config.appName + " key bindings",
function () { modes.push(modes.PASS_THROUGH); });
- mappings.add([modes.MAIN],
+ mappings.add([modes.MAIN, modes.PASS_THROUGH, modes.QUOTE],
["<C-v>", "<pass-next-key>"], "Pass through next key",
function () {
if (modes.main == modes.QUOTE)
modes.push(modes.QUOTE);
});
+ mappings.add([modes.BASE],
+ ["<CapsLock>"], "Do Nothing",
+ function () {});
+
mappings.add([modes.BASE],
["<Nop>"], "Do nothing",
function () {});
"sitemap", "", {
flush: function flush() {
memoize(this, "filters", function () this.value.filter(function (f) f(buffer.documentURI)));
- memoize(this, "pass", function () set(array.flatten(this.filters.map(function (f) f.keys))));
+ memoize(this, "pass", function () Set(array.flatten(this.filters.map(function (f) f.keys))));
memoize(this, "commandHive", function hive() Hive(this.filters, "command"));
memoize(this, "inputHive", function hive() Hive(this.filters, "input"));
},
- has: function (key) set.has(this.pass, key) || set.has(this.commandHive.stack.mappings, key),
+ has: function (key) Set.has(this.pass, key) || Set.has(this.commandHive.stack.mappings, key),
get pass() (this.flush(), this.pass),
filter.keys = events.fromString(vals[0]).map(events.closure.toString);
filter.commandKeys = vals.slice(1).map(events.closure.canonicalKeys);
- filter.inputKeys = filter.commandKeys.filter(/^<[ACM]-/);
+ filter.inputKeys = filter.commandKeys.filter(bind("test", /^<[ACM]-/));
});
this.flush();
return values;
options.add(["strictfocus", "sf"],
"Prevent scripts from focusing input elements without user intervention",
- "boolean", true);
+ "sitemap", "'chrome:*':laissez-faire,*:moderate",
+ {
+ values: {
+ despotic: "Only allow focus changes when explicitly requested by the user",
+ moderate: "Allow focus changes after user-initiated focus change",
+ "laissez-faire": "Always allow focus changes"
+ }
+ });
options.add(["timeout", "tmo"],
"Whether to execute a shorter key command after a timeout when a longer command exists",