mirror of
https://github.com/zen-browser/components.git
synced 2025-07-07 21:59:59 +02:00
1054 lines
27 KiB
TypeScript
1054 lines
27 KiB
TypeScript
const KEYCODE_MAP = {
|
|
F1: 'VK_F1',
|
|
F2: 'VK_F2',
|
|
F3: 'VK_F3',
|
|
F4: 'VK_F4',
|
|
F5: 'VK_F5',
|
|
F6: 'VK_F6',
|
|
F7: 'VK_F7',
|
|
F8: 'VK_F8',
|
|
F9: 'VK_F9',
|
|
F10: 'VK_F10',
|
|
F11: 'VK_F11',
|
|
F12: 'VK_F12',
|
|
TAB: 'VK_TAB',
|
|
ENTER: 'VK_RETURN',
|
|
ESCAPE: 'VK_ESCAPE',
|
|
SPACE: 'VK_SPACE',
|
|
ARROWLEFT: 'VK_LEFT',
|
|
ARROWRIGHT: 'VK_RIGHT',
|
|
ARROWUP: 'VK_UP',
|
|
ARROWDOWN: 'VK_DOWN',
|
|
DELETE: 'VK_DELETE',
|
|
BACKSPACE: 'VK_BACK',
|
|
HOME: 'VK_HOME',
|
|
};
|
|
|
|
const defaultKeyboardGroups = {
|
|
windowAndTabManagement: [
|
|
'zen-window-new-shortcut',
|
|
'zen-tab-new-shortcut',
|
|
'zen-key-enter-full-screen',
|
|
'zen-key-exit-full-screen',
|
|
'zen-quit-app-shortcut',
|
|
'zen-close-tab-shortcut',
|
|
'zen-close-shortcut',
|
|
'id:key_selectTab1',
|
|
'id:key_selectTab2',
|
|
'id:key_selectTab3',
|
|
'id:key_selectTab4',
|
|
'id:key_selectTab5',
|
|
'id:key_selectTab6',
|
|
'id:key_selectTab7',
|
|
'id:key_selectTab8',
|
|
'id:key_selectLastTab',
|
|
],
|
|
navigation: [
|
|
'zen-nav-back-shortcut-alt',
|
|
'zen-nav-fwd-shortcut-alt',
|
|
'zen-nav-reload-shortcut-2',
|
|
'zen-nav-reload-shortcut-skip-cache',
|
|
'zen-nav-reload-shortcut',
|
|
'zen-key-stop',
|
|
'zen-window-new-shortcut',
|
|
'zen-private-browsing-shortcut',
|
|
'id:goHome',
|
|
'id:key_gotoHistory',
|
|
'id:goBackKb',
|
|
'id:goForwardKb',
|
|
],
|
|
searchAndFind: [
|
|
'zen-search-focus-shortcut',
|
|
'zen-search-focus-shortcut-alt',
|
|
'zen-find-shortcut',
|
|
'zen-search-find-again-shortcut-2',
|
|
'zen-search-find-again-shortcut',
|
|
'zen-search-find-again-shortcut-prev',
|
|
],
|
|
pageOperations: [
|
|
'zen-location-open-shortcut',
|
|
'zen-location-open-shortcut-alt',
|
|
'zen-save-page-shortcut',
|
|
'zen-print-shortcut',
|
|
'zen-page-source-shortcut',
|
|
'zen-page-info-shortcut',
|
|
'zen-reader-mode-toggle-shortcut-other',
|
|
'zen-picture-in-picture-toggle-shortcut',
|
|
],
|
|
historyAndBookmarks: [
|
|
'zen-history-show-all-shortcut',
|
|
'zen-bookmark-this-page-shortcut',
|
|
'zen-bookmark-show-library-shortcut',
|
|
],
|
|
mediaAndDisplay: [
|
|
'zen-mute-toggle-shortcut',
|
|
'zen-full-zoom-reduce-shortcut',
|
|
'zen-full-zoom-enlarge-shortcut',
|
|
'zen-full-zoom-reset-shortcut',
|
|
'zen-bidi-switch-direction-shortcut',
|
|
'zen-screenshot-shortcut',
|
|
],
|
|
};
|
|
|
|
const fixedL10nIds = {
|
|
cmd_findPrevious: 'zen-search-find-again-shortcut-prev',
|
|
'Browser:ReloadSkipCache': 'zen-nav-reload-shortcut-skip-cache',
|
|
cmd_close: 'zen-close-tab-shortcut',
|
|
'History:RestoreLastClosedTabOrWindowOrSession': 'zen-restore-last-closed-tab-shortcut',
|
|
};
|
|
|
|
const ZEN_MAIN_KEYSET_ID = 'mainKeyset';
|
|
const ZEN_KEYSET_ID = 'zenKeyset';
|
|
|
|
const ZEN_COMPACT_MODE_SHORTCUTS_GROUP = 'zen-compact-mode';
|
|
const ZEN_WORKSPACE_SHORTCUTS_GROUP = 'zen-workspace';
|
|
const ZEN_OTHER_SHORTCUTS_GROUP = 'zen-other';
|
|
const ZEN_SPLIT_VIEW_SHORTCUTS_GROUP = 'zen-split-view';
|
|
const FIREFOX_SHORTCUTS_GROUP = 'zen-kbs-invalid';
|
|
const VALID_SHORTCUT_GROUPS = [
|
|
ZEN_COMPACT_MODE_SHORTCUTS_GROUP,
|
|
ZEN_WORKSPACE_SHORTCUTS_GROUP,
|
|
ZEN_SPLIT_VIEW_SHORTCUTS_GROUP,
|
|
...Object.keys(defaultKeyboardGroups),
|
|
ZEN_OTHER_SHORTCUTS_GROUP,
|
|
'other',
|
|
];
|
|
|
|
class KeyShortcutModifiers {
|
|
#control = false;
|
|
#alt = false;
|
|
#shift = false;
|
|
#meta = false;
|
|
#accel = false;
|
|
|
|
constructor(ctrl, alt, shift, meta, accel) {
|
|
this.#control = ctrl;
|
|
this.#alt = alt;
|
|
this.#shift = shift;
|
|
this.#meta = meta;
|
|
this.#accel = accel;
|
|
|
|
if (AppConstants.platform != 'macosx') {
|
|
// Replace control with accel, to make it more consistent
|
|
this.#accel = ctrl || accel;
|
|
this.#control = false;
|
|
}
|
|
}
|
|
|
|
static parseFromJSON(modifiers) {
|
|
if (!modifiers) {
|
|
return new KeyShortcutModifiers(false, false, false, false, false);
|
|
}
|
|
|
|
return new KeyShortcutModifiers(
|
|
modifiers['control'] == true,
|
|
modifiers['alt'] == true,
|
|
modifiers['shift'] == true,
|
|
modifiers['meta'] == true,
|
|
modifiers['accel'] == true
|
|
);
|
|
}
|
|
|
|
static parseFromXHTMLAttribute(modifiers) {
|
|
if (!modifiers) {
|
|
return new KeyShortcutModifiers(false, false, false, false, false);
|
|
}
|
|
|
|
return new KeyShortcutModifiers(
|
|
modifiers.includes('control'),
|
|
modifiers.includes('alt'),
|
|
modifiers.includes('shift'),
|
|
modifiers.includes('meta'),
|
|
modifiers.includes('accel')
|
|
);
|
|
}
|
|
|
|
// used to avoid any future changes to the object
|
|
static fromObject({ ctrl = false, alt = false, shift = false, meta = false, accel = false }) {
|
|
return new KeyShortcutModifiers(ctrl, alt, shift, meta, accel);
|
|
}
|
|
|
|
toUserString() {
|
|
let str = '';
|
|
if (this.#control && !this.#accel) {
|
|
str += 'Ctrl+';
|
|
}
|
|
if (this.#alt) {
|
|
str += AppConstants.platform == 'macosx' ? 'Option+' : 'Alt+';
|
|
}
|
|
if (this.#shift) {
|
|
str += 'Shift+';
|
|
}
|
|
if (this.#meta) {
|
|
str += AppConstants.platform == 'macosx' ? 'Cmd+' : 'Win+';
|
|
}
|
|
if (this.#accel) {
|
|
str += AppConstants.platform == 'macosx' ? 'Cmd+' : 'Ctrl+';
|
|
}
|
|
return str;
|
|
}
|
|
|
|
equals(other) {
|
|
if (!other) {
|
|
return false;
|
|
}
|
|
// If we are on macos, we can have accel and meta at the same time
|
|
return (
|
|
this.#alt == other.#alt &&
|
|
this.#shift == other.#shift &&
|
|
this.#control == other.#control &&
|
|
(AppConstants.platform == 'macosx'
|
|
? ((this.#meta || this.#accel) == (other.#meta || other.#accel) && this.#control == other.#control)
|
|
// In other platforms, we can have control and accel counting as the same thing
|
|
: (this.#meta == other.#meta && (this.#control || this.#accel) == (other.#control || other.#accel)))
|
|
);
|
|
}
|
|
|
|
toString() {
|
|
let str = '';
|
|
if (this.#control) {
|
|
str += 'control,';
|
|
}
|
|
if (this.#alt) {
|
|
str += 'alt,';
|
|
}
|
|
if (this.#shift) {
|
|
str += 'shift,';
|
|
}
|
|
if (this.#accel) {
|
|
str += 'accel,';
|
|
}
|
|
if (this.#meta) {
|
|
str += 'meta,';
|
|
}
|
|
return str.slice(0, -1);
|
|
}
|
|
|
|
toJSONString() {
|
|
return {
|
|
control: this.#control,
|
|
alt: this.#alt,
|
|
shift: this.#shift,
|
|
meta: this.#meta,
|
|
accel: this.#accel,
|
|
};
|
|
}
|
|
|
|
areAnyActive() {
|
|
return this.#control || this.#alt || this.#shift || this.#meta || this.#accel;
|
|
}
|
|
|
|
get control() {
|
|
return this.#control;
|
|
}
|
|
|
|
get alt() {
|
|
return this.#alt;
|
|
}
|
|
|
|
get shift() {
|
|
return this.#shift;
|
|
}
|
|
|
|
get meta() {
|
|
return this.#meta;
|
|
}
|
|
|
|
get accel() {
|
|
return this.#accel;
|
|
}
|
|
}
|
|
|
|
class KeyShortcut {
|
|
#id = '';
|
|
#key = '';
|
|
#keycode = '';
|
|
#group = FIREFOX_SHORTCUTS_GROUP;
|
|
#modifiers = new KeyShortcutModifiers(false, false, false, false, false);
|
|
#action = '';
|
|
#l10nId = '';
|
|
#disabled = false;
|
|
#reserved = false;
|
|
#internal = false;
|
|
|
|
constructor(id, key, keycode, group, modifiers, action, l10nId, disabled = false, reserved = false, internal = false) {
|
|
this.#id = id;
|
|
this.#key = key?.toLowerCase();
|
|
this.#keycode = keycode;
|
|
|
|
if (!VALID_SHORTCUT_GROUPS.includes(group)) {
|
|
throw new Error('Illegal group value: ' + group);
|
|
}
|
|
|
|
this.#group = group;
|
|
this.#modifiers = modifiers;
|
|
this.#action = action;
|
|
this.#l10nId = KeyShortcut.sanitizeL10nId(l10nId, action);
|
|
this.#disabled = disabled;
|
|
this.#reserved = reserved;
|
|
this.#internal = internal;
|
|
}
|
|
|
|
isEmpty() {
|
|
return !this.#key && !this.getRealKeycode();
|
|
}
|
|
|
|
static parseFromSaved(json) {
|
|
let rv = [];
|
|
|
|
for (let key of json) {
|
|
rv.push(this.#parseFromJSON(key));
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
static getGroupFromL10nId(l10nId, id) {
|
|
// Find inside defaultKeyboardGroups
|
|
for (let group of Object.keys(defaultKeyboardGroups)) {
|
|
for (let shortcut of defaultKeyboardGroups[group]) {
|
|
if (shortcut == l10nId || shortcut == 'id:' + id) {
|
|
return group;
|
|
}
|
|
}
|
|
}
|
|
return 'other';
|
|
}
|
|
|
|
static #parseFromJSON(json) {
|
|
return new KeyShortcut(
|
|
json['id'],
|
|
json['key'],
|
|
json['keycode'],
|
|
json['group'],
|
|
KeyShortcutModifiers.parseFromJSON(json['modifiers']),
|
|
json['action'],
|
|
json['l10nId'],
|
|
json['disabled'],
|
|
json['reserved'],
|
|
json['internal']
|
|
);
|
|
}
|
|
|
|
static parseFromXHTML(key) {
|
|
return new KeyShortcut(
|
|
key.getAttribute('id'),
|
|
key.getAttribute('key'),
|
|
key.getAttribute('keycode'),
|
|
KeyShortcut.getGroupFromL10nId(KeyShortcut.sanitizeL10nId(key.getAttribute('data-l10n-id')), key.getAttribute('id')),
|
|
KeyShortcutModifiers.parseFromXHTMLAttribute(key.getAttribute('modifiers')),
|
|
key.getAttribute('command'),
|
|
key.getAttribute('data-l10n-id'),
|
|
key.getAttribute('disabled') == 'true',
|
|
key.getAttribute('reserved') == 'true',
|
|
key.getAttribute('internal') == 'true'
|
|
);
|
|
}
|
|
|
|
static sanitizeL10nId(id, action) {
|
|
if (!id || id.startsWith('zen-')) {
|
|
return id;
|
|
}
|
|
// Check if any action is on the list of fixed l10n ids
|
|
if (fixedL10nIds[action]) {
|
|
return fixedL10nIds[action];
|
|
}
|
|
return `zen-${id}`;
|
|
}
|
|
|
|
toXHTMLElement(window) {
|
|
let key = window.document.createXULElement('key');
|
|
key.id = this.#id;
|
|
if (this.#keycode) {
|
|
key.setAttribute('keycode', this.#keycode);
|
|
} else {
|
|
// note to "mr. macos": Better use setAttribute, because without it, there's a
|
|
// risk of malforming the XUL element.
|
|
key.setAttribute('key', this.#key);
|
|
}
|
|
key.setAttribute('group', this.#group);
|
|
|
|
// note to "mr. macos": We add the `zen-` prefix because since firefox hasnt been built with the
|
|
// shortcuts in mind, it will siply just override the shortcuts with whatever the default is.
|
|
// note that this l10n id is not used for actually translating the key's label, but rather to
|
|
// identify the default keybinds.
|
|
if (this.#l10nId) {
|
|
key.setAttribute('data-l10n-id', this.#l10nId);
|
|
}
|
|
key.setAttribute('modifiers', this.#modifiers.toString());
|
|
if (this.#action) {
|
|
if (this.#action?.startsWith('code:')) {
|
|
key.setAttribute('oncommand', this.#action.substring(5));
|
|
} else {
|
|
key.setAttribute('command', this.#action);
|
|
}
|
|
}
|
|
if (this.#disabled) {
|
|
key.setAttribute('disabled', this.#disabled);
|
|
}
|
|
if (this.#reserved) {
|
|
key.setAttribute('reserved', this.#reserved);
|
|
}
|
|
if (this.#internal) {
|
|
key.setAttribute('internal', this.#internal);
|
|
}
|
|
key.setAttribute('zen-keybind', 'true');
|
|
|
|
return key;
|
|
}
|
|
|
|
_modifyInternalAttribute(value) {
|
|
this.#internal = value;
|
|
}
|
|
|
|
getRealKeycode() {
|
|
if (this.#keycode === '') {
|
|
return null;
|
|
}
|
|
return this.#keycode;
|
|
}
|
|
|
|
getID() {
|
|
return this.#id;
|
|
}
|
|
|
|
getAction() {
|
|
return this.#action;
|
|
}
|
|
|
|
getL10NID() {
|
|
return this.#l10nId;
|
|
}
|
|
|
|
getGroup() {
|
|
return this.#group;
|
|
}
|
|
|
|
getModifiers() {
|
|
return this.#modifiers;
|
|
}
|
|
|
|
getKeyName() {
|
|
return this.#key?.toLowerCase();
|
|
}
|
|
|
|
getKeyCode() {
|
|
return this.getRealKeycode();
|
|
}
|
|
|
|
getKeyNameOrCode() {
|
|
return this.#key ? this.getKeyName() : this.getKeyCode();
|
|
}
|
|
|
|
isDisabled() {
|
|
return this.#disabled;
|
|
}
|
|
|
|
isReserved() {
|
|
return this.#reserved;
|
|
}
|
|
|
|
isInternal() {
|
|
return this.#internal;
|
|
}
|
|
|
|
setModifiers(modifiers) {
|
|
if ((!modifiers) instanceof KeyShortcutModifiers) {
|
|
throw new Error('Only KeyShortcutModifiers allowed');
|
|
}
|
|
this.#modifiers = modifiers;
|
|
}
|
|
|
|
toJSONForm() {
|
|
return {
|
|
id: this.#id,
|
|
key: this.#key,
|
|
keycode: this.#keycode,
|
|
group: this.#group,
|
|
l10nId: this.#l10nId,
|
|
modifiers: this.#modifiers.toJSONString(),
|
|
action: this.#action,
|
|
disabled: this.#disabled,
|
|
reserved: this.#reserved,
|
|
internal: this.#internal,
|
|
};
|
|
}
|
|
|
|
toUserString() {
|
|
let str = this.#modifiers.toUserString();
|
|
|
|
if (this.#key) {
|
|
str += this.#key.toUpperCase();
|
|
} else if (this.#keycode) {
|
|
// Get the key from the value
|
|
for (let [key, value] of Object.entries(KEYCODE_MAP)) {
|
|
if (value == this.#keycode) {
|
|
str += key.toLowerCase();
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
return '';
|
|
}
|
|
return str;
|
|
}
|
|
|
|
isUserEditable() {
|
|
if (!this.#id || this.#internal || (this.#group == FIREFOX_SHORTCUTS_GROUP && this.#disabled)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
clearKeybind() {
|
|
this.#key = '';
|
|
this.#keycode = '';
|
|
this.#modifiers = new KeyShortcutModifiers(false, false, false, false);
|
|
}
|
|
|
|
setNewBinding(shortcut) {
|
|
for (let keycode of Object.keys(KEYCODE_MAP)) {
|
|
if (keycode == shortcut.toUpperCase()) {
|
|
this.#keycode = KEYCODE_MAP[keycode];
|
|
this.#key = '';
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.#keycode = ''; // Clear the keycode
|
|
this.#key = shortcut;
|
|
}
|
|
}
|
|
|
|
class ZenKeyboardShortcutsLoader {
|
|
constructor() {}
|
|
|
|
get shortcutsFile() {
|
|
return PathUtils.join(PathUtils.profileDir, 'zen-keyboard-shortcuts.json');
|
|
}
|
|
|
|
async save(data) {
|
|
await IOUtils.writeJSON(this.shortcutsFile, data);
|
|
}
|
|
|
|
async loadObject() {
|
|
try {
|
|
return await IOUtils.readJSON(this.shortcutsFile);
|
|
} catch (e) {
|
|
// Recreate shortcuts file
|
|
Services.prefs.clearUserPref('zen.keyboard.shortcuts.version');
|
|
console.error('Error loading shortcuts file', e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async load() {
|
|
return (await this.loadObject())?.shortcuts;
|
|
}
|
|
|
|
async remove() {
|
|
await IOUtils.remove(this.shortcutsFile);
|
|
}
|
|
}
|
|
|
|
function zenGetDefaultShortcuts() {
|
|
// DO NOT CHANGE ANYTHING HERE
|
|
// For adding new default shortcuts, add them to inside the migration function
|
|
// and increment the version number.
|
|
|
|
console.info('Zen CKS: Loading default shortcuts...');
|
|
let keySet = document.getElementById(ZEN_MAIN_KEYSET_ID);
|
|
let newShortcutList = [];
|
|
|
|
// Firefox's standard keyset. Reverse order to keep the order of the keys
|
|
for (let i = keySet.children.length - 1; i >= 0; i--) {
|
|
let key = keySet.children[i];
|
|
let parsed = KeyShortcut.parseFromXHTML(key);
|
|
newShortcutList.push(parsed);
|
|
}
|
|
|
|
// Compact mode's keyset
|
|
newShortcutList.push(
|
|
new KeyShortcut(
|
|
'zen-compact-mode-toggle',
|
|
'C',
|
|
'',
|
|
ZEN_COMPACT_MODE_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({ accel: true, alt: true }),
|
|
'code:gZenCompactModeManager.toggle()',
|
|
'zen-compact-mode-shortcut-toggle'
|
|
)
|
|
);
|
|
newShortcutList.push(
|
|
new KeyShortcut(
|
|
'zen-compact-mode-show-sidebar',
|
|
'S',
|
|
'',
|
|
ZEN_COMPACT_MODE_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({ accel: true, alt: true }),
|
|
'code:gZenCompactModeManager.toggleSidebar()',
|
|
'zen-compact-mode-shortcut-show-sidebar'
|
|
)
|
|
);
|
|
newShortcutList.push(
|
|
new KeyShortcut(
|
|
'zen-compact-mode-show-toolbar',
|
|
'T',
|
|
'',
|
|
ZEN_COMPACT_MODE_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({ accel: true, alt: true }),
|
|
'code:gZenCompactModeManager.toggleToolbar()',
|
|
'zen-compact-mode-shortcut-show-toolbar'
|
|
)
|
|
);
|
|
|
|
// Workspace's keyset
|
|
for (let i = 10; i > 0; i--) {
|
|
newShortcutList.push(
|
|
new KeyShortcut(
|
|
`zen-workspace-switch-${i}`,
|
|
'',
|
|
'',
|
|
ZEN_WORKSPACE_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({}),
|
|
`code:ZenWorkspaces.shortcutSwitchTo(${i - 1})`,
|
|
`zen-workspace-shortcut-switch-${i}`
|
|
)
|
|
);
|
|
}
|
|
newShortcutList.push(
|
|
new KeyShortcut(
|
|
'zen-workspace-forward',
|
|
'E',
|
|
'',
|
|
ZEN_WORKSPACE_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({ accel: true, alt: true }),
|
|
'code:ZenWorkspaces.changeWorkspaceShortcut()',
|
|
'zen-workspace-shortcut-forward'
|
|
)
|
|
);
|
|
newShortcutList.push(
|
|
new KeyShortcut(
|
|
'zen-workspace-backward',
|
|
'Q',
|
|
'',
|
|
ZEN_WORKSPACE_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({ accel: true, alt: true }),
|
|
'code:ZenWorkspaces.changeWorkspaceShortcut(-1)',
|
|
'zen-workspace-shortcut-backward'
|
|
)
|
|
);
|
|
|
|
// Other keyset
|
|
newShortcutList.push(
|
|
new KeyShortcut(
|
|
'zen-toggle-web-panel',
|
|
'P',
|
|
'',
|
|
ZEN_OTHER_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({ alt: true }),
|
|
'code:gZenBrowserManagerSidebar.toggle()',
|
|
'zen-web-panel-shortcut-toggle'
|
|
)
|
|
);
|
|
newShortcutList.push(
|
|
new KeyShortcut(
|
|
'zen-toggle-sidebar',
|
|
'B',
|
|
'',
|
|
ZEN_OTHER_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({ alt: true }),
|
|
'code:gZenVerticalTabsManager.toggleExpand()',
|
|
'zen-sidebar-shortcut-toggle'
|
|
)
|
|
);
|
|
|
|
// Split view
|
|
newShortcutList.push(
|
|
new KeyShortcut(
|
|
'zen-split-view-grid',
|
|
'G',
|
|
'',
|
|
ZEN_SPLIT_VIEW_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({ accel: true, alt: true }),
|
|
"code:gZenViewSplitter.toggleShortcut('grid')",
|
|
'zen-split-view-shortcut-grid'
|
|
)
|
|
);
|
|
newShortcutList.push(
|
|
new KeyShortcut(
|
|
'zen-split-view-vertical',
|
|
'V',
|
|
'',
|
|
ZEN_SPLIT_VIEW_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({ accel: true, alt: true }),
|
|
"code:gZenViewSplitter.toggleShortcut('vsep')",
|
|
'zen-split-view-shortcut-vertical'
|
|
)
|
|
);
|
|
newShortcutList.push(
|
|
new KeyShortcut(
|
|
'zen-split-view-horizontal',
|
|
'H',
|
|
'',
|
|
ZEN_SPLIT_VIEW_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({ accel: true, alt: true }),
|
|
"code:gZenViewSplitter.toggleShortcut('hsep')",
|
|
'zen-split-view-shortcut-horizontal'
|
|
)
|
|
);
|
|
newShortcutList.push(
|
|
new KeyShortcut(
|
|
'zen-split-view-unsplit',
|
|
'U',
|
|
'',
|
|
ZEN_SPLIT_VIEW_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({ accel: true, alt: true }),
|
|
"code:gZenViewSplitter.toggleShortcut('unsplit')",
|
|
'zen-split-view-shortcut-unsplit'
|
|
)
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return newShortcutList;
|
|
}
|
|
|
|
class ZenKeyboardShortcutsVersioner {
|
|
static LATEST_KBS_VERSION = 4;
|
|
|
|
constructor() {}
|
|
|
|
get version() {
|
|
return Services.prefs.getIntPref('zen.keyboard.shortcuts.version', 0);
|
|
}
|
|
|
|
set version(version) {
|
|
Services.prefs.setIntPref('zen.keyboard.shortcuts.version', version);
|
|
}
|
|
|
|
getVersionedData(data) {
|
|
return {
|
|
shortcuts: data,
|
|
};
|
|
}
|
|
|
|
isVersionUpToDate() {
|
|
return this.version == ZenKeyboardShortcutsVersioner.LATEST_KBS_VERSION;
|
|
}
|
|
|
|
isVersionOutdated() {
|
|
return this.version < ZenKeyboardShortcutsVersioner.LATEST_KBS_VERSION;
|
|
}
|
|
|
|
migrateIfNeeded(data) {
|
|
if (!data) {
|
|
// Rebuid the shortcuts, just in case
|
|
this.version = 0;
|
|
}
|
|
|
|
if (this.isVersionUpToDate()) {
|
|
return data;
|
|
}
|
|
|
|
if (this.isVersionOutdated()) {
|
|
const version = this.version;
|
|
console.info(
|
|
'Zen CKS: Migrating shortcuts from version',
|
|
version,
|
|
'to',
|
|
ZenKeyboardShortcutsVersioner.LATEST_KBS_VERSION
|
|
);
|
|
const newData = this.migrate(data, version);
|
|
this.version = ZenKeyboardShortcutsVersioner.LATEST_KBS_VERSION;
|
|
return newData;
|
|
}
|
|
|
|
console.error('Unknown keyboar shortcuts version');
|
|
this.version = 0;
|
|
return this.migrateIfNeeded(data);
|
|
}
|
|
|
|
migrate(data, version) {
|
|
// Explain: How to add shortcuts
|
|
/*
|
|
new KeyShortcut(
|
|
'ID_OF_SHORTCUT',
|
|
'KEYCODE',
|
|
'',
|
|
GROUP_OF_SHORTCUT,
|
|
KeyShortcutModifiers.fromObject({ accel: true, alt: true }), //accel = Ctrl/Cmd, Alt = Alt/Option
|
|
'code:NATIVE_CODE_FUNCTION()',
|
|
'L10N_STRING_FOR_TRANSLATION'
|
|
)
|
|
*/
|
|
if (version < 1) {
|
|
// Migrate from 0 to 1
|
|
// Here, we do a complet reset of the shortcuts,
|
|
// since nothing seems to work properly.
|
|
data = zenGetDefaultShortcuts();
|
|
}
|
|
if (version < 2) {
|
|
// Migrate from 1 to 2
|
|
// In this new version, we are resolving the conflicts between
|
|
// shortcuts having keycode and key at the same time.
|
|
// If there's both, we remove the keycodes.
|
|
for (let shortcut of data) {
|
|
if (shortcut.getKeyCode() && shortcut.getKeyName()) {
|
|
shortcut.setNewBinding(shortcut.getKeyName());
|
|
}
|
|
}
|
|
data.push(
|
|
new KeyShortcut(
|
|
'zen-pinned-tab-reset-shortcut',
|
|
'',
|
|
'',
|
|
ZEN_OTHER_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({}),
|
|
'code:gZenPinnedTabManager.resetPinnedTab(gBrowser.selectedTab)',
|
|
'zen-pinned-tab-shortcut-reset'
|
|
)
|
|
);
|
|
}
|
|
if (version < 3) {
|
|
// Migrate from 2 to 3
|
|
// In this new version, there was this *really* annoying bug. Shortcuts
|
|
// detection for internal keys was not working properly, so every internal
|
|
// shortcut was being saved as a user-editable shortcut.
|
|
// This migration will fix this issue.
|
|
const defaultShortcuts = zenGetDefaultShortcuts();
|
|
// Get the default shortcut, compare the id and set the internal flag if needed
|
|
for (let shortcut of data) {
|
|
for (let defaultShortcut of defaultShortcuts) {
|
|
if (shortcut.getID() == defaultShortcut.getID()) {
|
|
shortcut._modifyInternalAttribute(defaultShortcut.isInternal());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
data.push(
|
|
// Added by gunir
|
|
// Prev Tab Ctrl+Left
|
|
new KeyShortcut(
|
|
'zen-previous-tab',
|
|
'VK_LEFT',
|
|
'',
|
|
ZEN_OTHER_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({ accel: true }),
|
|
"code:gBrowser.tabContainer.advanceSelectedTab(-1, true);",
|
|
'zen-shortcut-previous-tab'
|
|
),
|
|
// Next Tab Ctrl+Right
|
|
new KeyShortcut(
|
|
'zen-next-tab',
|
|
'VK_RIGHT',
|
|
'',
|
|
ZEN_OTHER_SHORTCUTS_GROUP,
|
|
KeyShortcutModifiers.fromObject({ accel: true }),
|
|
"code:gBrowser.tabContainer.advanceSelectedTab(1, true);",
|
|
'zen-shortcut-next-tab'
|
|
)
|
|
);
|
|
return data;
|
|
}
|
|
}
|
|
|
|
var gZenKeyboardShortcutsManager = {
|
|
loader: new ZenKeyboardShortcutsLoader(),
|
|
beforeInit() {
|
|
if (!this.inBrowserView) {
|
|
return;
|
|
}
|
|
// Create the main keyset before calling the async init function,
|
|
// This is because other browser-sets needs this element and the JS event
|
|
// handled wont wait for the async function to finish.
|
|
void(this.getZenKeyset());
|
|
|
|
this._hasCleared = Services.prefs.getBoolPref('zen.keyboard.shortcuts.disable-mainkeyset-clear', false);
|
|
|
|
this.init();
|
|
},
|
|
|
|
async init() {
|
|
if (this.inBrowserView) {
|
|
const loadedShortcuts = await this._loadSaved();
|
|
|
|
this._currentShortcutList = this.versioner.migrateIfNeeded(loadedShortcuts);
|
|
this._applyShortcuts();
|
|
|
|
await this._saveShortcuts();
|
|
|
|
console.info('Zen CKS: Initialized');
|
|
}
|
|
},
|
|
|
|
get inBrowserView() {
|
|
return window.location.href == 'chrome://browser/content/browser.xhtml';
|
|
},
|
|
|
|
async _loadSaved() {
|
|
var innerLoad = async () => {
|
|
let data = await this.loader.load();
|
|
if (!data || data.length == 0) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return KeyShortcut.parseFromSaved(data);
|
|
} catch (e) {
|
|
console.error('Zen CKS: Error parsing saved shortcuts. Resetting to defaults...', e);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const loadedShortcuts = await innerLoad();
|
|
this.versioner = new ZenKeyboardShortcutsVersioner(loadedShortcuts);
|
|
return loadedShortcuts;
|
|
},
|
|
|
|
getZenKeyset(browser = window) {
|
|
if (!browser.gZenKeyboardShortcutsManager._zenKeyset) {
|
|
const existingKeyset = browser.document.getElementById(ZEN_KEYSET_ID);
|
|
if (existingKeyset) {
|
|
browser.gZenKeyboardShortcutsManager._zenKeyset = existingKeyset;
|
|
return browser.gZenKeyboardShortcutsManager._zenKeyset;
|
|
}
|
|
|
|
browser.gZenKeyboardShortcutsManager._zenKeyset = browser.document.createXULElement('keyset');
|
|
browser.gZenKeyboardShortcutsManager._zenKeyset.id = ZEN_KEYSET_ID;
|
|
|
|
const mainKeyset = browser.document.getElementById(ZEN_MAIN_KEYSET_ID);
|
|
mainKeyset.after(browser.gZenKeyboardShortcutsManager._zenKeyset);
|
|
}
|
|
return browser.gZenKeyboardShortcutsManager._zenKeyset;
|
|
},
|
|
|
|
clearMainKeyset(element) {
|
|
if (this._hasCleared) {
|
|
return;
|
|
}
|
|
this._hasCleared = true;
|
|
const children = element.children;
|
|
for (let i = children.length - 1; i >= 0; i--) {
|
|
const key = children[i];
|
|
if (key.getAttribute('internal') == 'true') {
|
|
continue;
|
|
}
|
|
key.remove();
|
|
}
|
|
|
|
// Restore the keyset, https://searchfox.org/mozilla-central/rev/a59018f9ff34170810b43e12bf6f09a1512de7ab/dom/events/GlobalKeyListener.cpp#478
|
|
const parent = element.parentElement;
|
|
element.remove();
|
|
parent.prepend(element);
|
|
},
|
|
|
|
_applyShortcuts() {
|
|
for (const browser of ZenMultiWindowFeature.browsers) {
|
|
let mainKeyset = browser.document.getElementById(ZEN_MAIN_KEYSET_ID);
|
|
if (!mainKeyset) {
|
|
throw new Error('Main keyset not found');
|
|
}
|
|
browser.gZenKeyboardShortcutsManager.clearMainKeyset(mainKeyset);
|
|
|
|
const keyset = this.getZenKeyset(browser);
|
|
keyset.innerHTML = '';
|
|
|
|
// We dont check this anymore since we are skiping internal keys
|
|
//if (mainKeyset.children.length > 0) {
|
|
// throw new Error('Child list not empty');
|
|
//}
|
|
|
|
for (let key of this._currentShortcutList) {
|
|
if (key.isEmpty() || key.isInternal()) {
|
|
continue;
|
|
}
|
|
let child = key.toXHTMLElement(browser);
|
|
keyset.appendChild(child);
|
|
}
|
|
|
|
mainKeyset.after(keyset);
|
|
console.debug('Shortcuts applied...');
|
|
}
|
|
},
|
|
|
|
async resetAllShortcuts() {
|
|
await this.loader.remove();
|
|
Services.prefs.clearUserPref('zen.keyboard.shortcuts.version');
|
|
},
|
|
|
|
async _saveShortcuts() {
|
|
let json = [];
|
|
for (shortcut of this._currentShortcutList) {
|
|
json.push(shortcut.toJSONForm());
|
|
}
|
|
|
|
await this.loader.save(this.versioner.getVersionedData(json));
|
|
},
|
|
|
|
triggerShortcutRebuild() {
|
|
this._applyShortcuts();
|
|
},
|
|
|
|
async setShortcut(action, shortcut, modifiers) {
|
|
if (!action) {
|
|
throw new Error('Action cannot be null');
|
|
}
|
|
|
|
// Unsetting shortcut
|
|
for (let targetShortcut of this._currentShortcutList) {
|
|
if (targetShortcut.getID() != action) {
|
|
continue;
|
|
}
|
|
if (!shortcut && !modifiers) {
|
|
targetShortcut.clearKeybind();
|
|
} else {
|
|
targetShortcut.setNewBinding(shortcut);
|
|
targetShortcut.setModifiers(modifiers);
|
|
}
|
|
}
|
|
|
|
await this._saveShortcuts();
|
|
this.triggerShortcutRebuild();
|
|
},
|
|
|
|
async getModifiableShortcuts() {
|
|
let rv = [];
|
|
|
|
if (!this._currentShortcutList) {
|
|
this._currentShortcutList = await this._loadSaved();
|
|
}
|
|
|
|
for (let shortcut of this._currentShortcutList) {
|
|
if (shortcut.isUserEditable()) {
|
|
rv.push(shortcut);
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
},
|
|
|
|
checkForConflicts(shortcut, modifiers, id) {
|
|
const realShortcut = shortcut.toLowerCase();
|
|
for (let targetShortcut of this._currentShortcutList) {
|
|
if (targetShortcut.getID() == id) {
|
|
continue;
|
|
}
|
|
|
|
if (targetShortcut.getModifiers().equals(modifiers) && targetShortcut.getKeyNameOrCode()?.toLowerCase() == realShortcut) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
};
|
|
|
|
document.addEventListener("MozBeforeInitialXULLayout", () => {
|
|
if (Services.prefs.getBoolPref('zen.keyboard.shortcuts.enabled', false)) {
|
|
gZenKeyboardShortcutsManager.beforeInit();
|
|
}
|
|
}, { once: true });
|