1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
// ==UserScript==
// @name Global Keybindings
// @version 1.0
// @description Setup global keybindings on all windows
// @startup UC.globalKeybindings.init(win);
// @shutdown UC.globalKeybindings.destroy();
// ==/UserScript==
(() => {
// Configure global keybindings
const KEYBINDINGS = () => ({
RELEASE: [
[ctrl(shift(key('J'))), moveSelectedTabBy(+1)],
[ctrl(shift(key('K'))), moveSelectedTabBy(-1)],
[ctrl(alt(key('p'))), togglePassthrough(), { modes: ALL_MODES }],
],
PRESS: [
// Block default key bindings
[ctrl(shift(key('J'))), preventDefault()],
[ctrl(shift(key('K'))), preventDefault()],
[ctrl(key('j')), preventDefault(nextTab())],
[ctrl(key('k')), preventDefault(prevTab())],
// Ctrl + number takes to the tab at position
...(Array.from({ length: 10 }, (_, idx) =>
[ctrl(key(idx === 9 ? 0 : idx + 1)), tabIndex(idx)],
)),
],
});
const nextTab = () => () => updateTabIndex((n, len) => (n + 1) % len);
const prevTab = () => () => updateTabIndex((n, len) => n === 0 ? len - 1 : n - 1);
const tabIndex = idx => () => updateTabIndex((_n, _len) => idx)
const preventDefault = f => e => { e.preventDefault(); f?.(e); };
const moveSelectedTabBy = incr => () => updateSelectedTabIndex(incr);
const togglePassthrough = () => () => UC.globalKeybindings.updateMode(m => m !== MODE_PASSTHRU ? MODE_PASSTHRU : MODE_NORMAL)
const MODE_PASSTHRU = 'passthru';
const MODE_NORMAL = '';
const ALL_MODES = [MODE_NORMAL, MODE_PASSTHRU];
const ctrl = b => e => b(e) && e.ctrlKey;
const alt = b => e => b(e) && e.altKey;
const shift = b => e => b(e) && e.shiftKey;
const key = key => e => `${e.key}` === `${key}`;
const updateTabIndex = (f) => {
const newIndex = f(gBrowser.tabContainer.selectedIndex, gBrowser.tabs.length)
if (typeof newIndex !== 'number') return;
if (newIndex >= gBrowser.tabContainer.allTabs.length) return;
if (newIndex < 0) return;
gBrowser.selectTabAtIndex(newIndex);
};
const updateSelectedTabIndex = (incr) => {
if (typeof incr !== 'number') return;
if (incr === +1) return gBrowser.moveTabForward();
if (incr === -1) return gBrowser.moveTabBackward();
const newIndex = gBrowser.tabContainer.selectedIndex + incr;
if (newIndex >= gBrowser.tabContainer.allTabs.length) return;
if (newIndex < 0) return;
gBrowser.moveTabTo(gBrowser.selectedTab, newIndex)
};
const module = {
mode: MODE_NORMAL,
allModes: ALL_MODES,
keybindings: KEYBINDINGS(),
evaluateKeybindings: (bindings, e) => {
for (const [isKey, action, options] of bindings) {
// Only allow keys from the given mode
if ((options?.modes ?? [MODE_NORMAL]).includes(module.mode)) {
if (isKey(e)) {
const shouldContinue = action(e);
if (!shouldContinue) break;
}
}
}
},
updateMode: f => {
module.mode = f(module.mode);
gBrowser.tabContainer.dataset.keyMode = module.mode
},
handleKeyUpEvent: e => module.evaluateKeybindings(module.keybindings.RELEASE, e),
handleKeyDownEvent: e => module.evaluateKeybindings(module.keybindings.PRESS, e),
init(win) {
let observe = () => {
Services.obs.removeObserver(observe, 'browser-window-before-show');
win.addEventListener('keyup', module.handleKeyUpEvent, true);
win.addEventListener('keydown', module.handleKeyDownEvent, true);
}
if (win.__SSi) {
win.addEventListener('keyup', module.handleKeyUpEvent, true);
win.addEventListener('keydown', module.handleKeyDownEvent, true);
} else {
Services.obs.addObserver(observe, 'browser-window-before-show');
}
},
destroy() {
_uc.windows((_doc, win) => {
win.removeEventListener('keyup', module.handleKeyUpEvent, true);
win.removeEventListener('keydown', module.handleKeyDownEvent, true);
});
delete UC.globalKeybindings;
},
};
UC.globalKeybindings = module;
})();
|