Skip to content

Commit

Permalink
breaking: try to align key chars on macOS
Browse files Browse the repository at this point in the history
  • Loading branch information
gdh1995 committed Jan 1, 2024
1 parent 09caf48 commit 7f9c81c
Show file tree
Hide file tree
Showing 8 changed files with 499 additions and 23 deletions.
4 changes: 3 additions & 1 deletion i18n/zh/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@
"50_2": "启用时,仅当按住 Shift 键时才视为大写。部分选中时仅在 macOS 上忽略大小写锁定键。",
"53": "映射键盘右侧(或左侧)的修饰键",
"53_2": "将某侧 Alt、Ctrl、Shift 等修饰键视为“",
"53_3": "”键,“右Shift” 键(部分选中时“左Shift”)对应 ",
"53_3": "”键,“右Shift”键(部分选中时“左Shift”)对应 ",
"53_41": "在 about:config 上已启用 “privacy.resistFingerprinting”",
"53_42": "在此模式下,浏览器只允许部分简单的组合键。本选项一定程度上有助于在 macOS 上识别 Shift 键状态。",
"53_4": "。",
"53_5": "测试按键",
"53_6": "仅显示从浏览器接收到的按键名称,不受例外规则或 “mapKey” 的影响。",
Expand Down
4 changes: 3 additions & 1 deletion i18n/zh_TW/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,10 @@
"50_2": "啟用時,僅當按住 Shift 鍵時才視為大寫。部分選中時僅在 macOS 上忽略大小寫鎖定鍵。",
"53": "映射鍵盤右側(或左側)的修飾鍵",
"53_2": "將某側 Alt、Ctrl、Shift 等修飾鍵視為“",
"53_3": "”鍵,“右Shift” 鍵(部分選中時“左Shift”)對應 ",
"53_3": "”鍵,“右Shift”鍵(部分選中時“左Shift”)對應 ",
"53_4": "。",
"53_41": "在 about:config 上已啟用 “privacy.resistFingerprinting”",
"53_42": "在此模式下,瀏覽器只允許部分簡單的複合鍵。本選項一定程度上有助於在 macOS 識別 Shift 鍵狀態。",
"53_5": "測試按鍵",
"53_6": "僅顯示從瀏覽器接收到的按鍵名稱,不受例外規則或 “mapKey” 的影響。",
"53_7": "線上測試",
Expand Down
46 changes: 35 additions & 11 deletions lib/keyboard_utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
fgCache, clearTimeout_, timeout_, isAlive_, Stop_ as stopEvent, Lower, OnChrome, OnEdge, getTime, OnFirefox, abs_, os_, chromeVer_, keydownEvents_
fgCache, clearTimeout_, timeout_, isAlive_, Stop_, Lower, OnChrome, OnEdge, getTime, OnFirefox, abs_, os_, chromeVer_,
keydownEvents_, isAsContent
} from "./utils"

const DEL = kChar.delete, BSP = kChar.backspace, SP = kChar.space
Expand All @@ -11,7 +12,7 @@ let keyIdCorrectionOffset_old_cr_ = OnChrome && Build.MinCVer < BrowserVer.MinEn
? Build.OS !== kBOS.MAC as number ? 185 as const : 300 as const : 0 as never as null
const _codeCorrectionMap = ["Semicolon", "Equal", "Comma", "Minus", "Period", "Slash", "Backquote",
"BracketLeft", "Backslash", "BracketRight", "Quote", "IntlBackslash"]
const kCrct = OnChrome && Build.MinCVer < BrowserVer.MinEnsured$KeyboardEvent$$Key
const kCrct = OnChrome && Build.MinCVer < BrowserVer.MinEnsured$KeyboardEvent$$Key || Build.OS & kBOS.MAC
? kChar.CharCorrectionList : 0 as never as null
const _modifierKeys: SafeEnum = {
__proto__: null as never,
Expand Down Expand Up @@ -76,7 +77,7 @@ export const char_ = (eventWrapper: HandlerNS.Event, forceASCII: number): kChar
= eventWrapper.e
const shiftKey = OnFirefox ? hasShift_ff!(event as KeyboardEvent) : event.shiftKey
// on macOS, Alt+T can cause `.key === "Unidentified"` - https://github.com/gdh1995/vimium-c/issues/615
let mapped: number | undefined, key = event.key!, isDeadKey = !OnEdge && (key === "Dead" || key === "Unidentified")
let mapped: number, key = event.key!, isDeadKey = !OnEdge && (key === "Dead" || key === "Unidentified")
if (OnChrome && Build.MinCVer < BrowserVer.MinEnsured$KeyboardEvent$$Key && !key) {
// since Browser.Min$KeyboardEvent$MayHave$$Key and before .MinEnsured$KeyboardEvent$$Key
// event.key may be an empty string if some modifier keys are held on
Expand Down Expand Up @@ -109,23 +110,46 @@ export const char_ = (eventWrapper: HandlerNS.Event, forceASCII: number): kChar
// see https://github.com/gdh1995/vimium-c/issues/435
: code.length < 2 || !isKeyShort ? key.startsWith("Arrow") && key.slice(5) || key
: (mapped = _codeCorrectionMap.indexOf(code)) < 0 ? code
: (OnChrome && Build.MinCVer < BrowserVer.MinEnsured$KeyboardEvent$$Key
: (OnChrome && Build.MinCVer < BrowserVer.MinEnsured$KeyboardEvent$$Key || Build.OS & kBOS.MAC
? kCrct! : kChar.CharCorrectionList)[mapped + 12 * +shiftKey]
}
key = shiftKey && key.length < 2 ? key : Lower(key)
} else if (key.length > 1 || key === " ") {
key = /*#__NOINLINE__*/ _getKeyName(event)
} else {
key = key.length > 1 || key === " " ? /*#__NOINLINE__*/ _getKeyName(event)
: fgCache.l & kKeyLayout.ignoreCaps ? shiftKey ? key.toUpperCase() : Lower(key) : key
key = fgCache.l & kKeyLayout.ignoreCaps ? shiftKey ? key.toUpperCase() : Lower(key) : key
if (Build.OS & kBOS.MAC && (Build.OS === kBOS.MAC as number || !os_)
&& (OnChrome || OnFirefox) && shiftKey && key < kChar.maxASCII) { // "~" is upper-case
mapped = getKeyStat_(event as typeof eventWrapper.e, 1)
const kSpecialModifier = OnChrome ? 6 : 4
key = !(mapped & kSpecialModifier) ? mapped & 1 || fgCache.l & kKeyLayout.ignoreCaps
|| !(event as typeof eventWrapper.e).getModifierState("CapsLock") ? key : Lower(key)
: (mapped = kCrct!.indexOf(key)) >= 0 ? kCrct![(mapped % 12) + 12]
: key > kChar.maxNotNum && key < kChar.minNotNum ? kChar.EnNumTrans[+key]
: key
}
}
return forceASCII === (kKeyLayout.inCmdIgnoreIfNotASCII | 1) ? key as kChar : eventWrapper.c = key as kChar
}

export const keybody_ = (key: string): kChar => (key.slice(key.lastIndexOf("-") + 1) || key && kChar.minus) as kChar

export const hasShift_ff = OnFirefox ? (event: Pick<KeyboardEvent, "shiftKey" | "key" | "getModifierState">): boolean => {
const key = event.key!, upper = key.length === 1 ? key.toUpperCase() : ""
// if `privacy.resistFingerprinting` && CapsLock && A-Z, then Shift is reversed
return upper && Lower(key) !== upper && event.getModifierState("CapsLock") ? key !== upper : event.shiftKey
export const hasShift_ff = OnFirefox ? (
event: Pick<KeyboardEvent, "shiftKey" | "key" | "getModifierState" | "altKey" | "metaKey">): boolean => {
const key = event.key!
const lower = key.length === 1 && (Build.OS & ~kBOS.MAC || fgCache.l & kKeyLayout.inPrivResistFp_ff) ? Lower(key) : ""
if (lower && event.getModifierState("CapsLock") && key.toUpperCase() !== lower) {
// if `privacy.resistFingerprinting` && CapsLock && A-Z, then Shift is reversed
if (!(Build.OS & kBOS.MAC) || Build.OS & ~kBOS.MAC && os_) {
return key === lower
}
// try to minimize the affect that `inPrivResistFp` is not enabled correctly
if ((!(Build.OS & ~kBOS.MAC) || fgCache.l & kKeyLayout.inPrivResistFp_ff)
&& !event.altKey && !event.metaKey && isAsContent) {
return false
}
}
return event.shiftKey
} : 0 as never as null

export const getKeyStat_ = (event: Pick<KeyboardEvent, "altKey" | "ctrlKey" | "metaKey" | "shiftKey">
Expand Down Expand Up @@ -161,7 +185,7 @@ export const isEscape_ = (key: string): HandlerResult.AdvancedEsc | HandlerResul
/** handler section */

export const prevent_ = (event: ToPrevent): void => {
event.preventDefault(); stopEvent(event);
event.preventDefault(); Stop_(event)
}

export const replaceOrSuppressMost_ = ((id: kHandler, newHandler?: HandlerNS.Handler): void => {
Expand Down
14 changes: 14 additions & 0 deletions pages/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,20 @@
Right-Shift (left if partially selected) is "</span>&lt;s-modifier&gt;<span data-i="53_4">".</span>
</div></td>
</tr>
<tr>
<td class="caption"></td>
<td>
<label class="booleanOption" tabindex="-1" aria-hidden="true">
<input id="inPrivResistFp" type="checkbox" tabindex="-1" aria-hidden="true" />
<span data-i="53_41" class="checkboxHint" role="checkbox" tabindex="0" aria-hidden="false"
>Has enabled "privacy.resistFingerprinting" on about:config</span>
</label>
</td>
<td class="help"><div class="help-inner line-help" data-i="53_42">
Then the browser only allows simple composed keys.
On macOS, This helps a little when CapsLock is on.
</div></td>
</tr>
<tr id="testKeyInputBox">
<td class="caption"></td>
<td>
Expand Down
16 changes: 13 additions & 3 deletions pages/options_defs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Option_.prototype._onCacheUpdated = function<T extends keyof SettingsNS.AutoSync
frame && void post_(kPgReq.updateOmniPayload, { key: shortKey,
val: (val2 != null ? val2 : val as never) as SettingsNS.DirectlySyncedItems[typeof shortKey][1] })
}
Option_.onFgCacheUpdated_ && Option_.onFgCacheUpdated_()
Option_.onFgCacheUpdated_?.()
})
}
}
Expand Down Expand Up @@ -645,7 +645,7 @@ filterLinkHintsOption_.onSave_ = function (): void {
delayBinding_(filterLinkHintsOption_.element_, "change", filterLinkHintsOption_.onSave_, true)

const keyLayout = Option_.all_.keyLayout
const [elAlwaysIgnore, elIgnoreIfAlt, elIgnoreIfNotASCII, elIgnoreCaps, elMapModifier] =
const [elAlwaysIgnore, elIgnoreIfAlt, elIgnoreIfNotASCII, elIgnoreCaps, elMapModifier, elInPrivResistFp] =
$$<HTMLInputElement>("input", keyLayout.element_)
keyLayout.readValueFromElement_ = (): number => {
let flags: kKeyLayout = 0
Expand All @@ -656,6 +656,7 @@ keyLayout.readValueFromElement_ = (): number => {
flags |= elIgnoreIfNotASCII.checked ? kKeyLayout.ignoreIfNotASCII
: elIgnoreIfNotASCII.indeterminate ? kKeyLayout.inCmdIgnoreIfNotASCII : 0
flags |= elIgnoreCaps.checked ? kKeyLayout.ignoreCaps : elIgnoreCaps.indeterminate ? kKeyLayout.ignoreCapsOnMac : 0
flags |= elInPrivResistFp.checked ? kKeyLayout.inPrivResistFp_ff : 0
}
flags |= elMapModifier.checked ? kKeyLayout.mapRightModifiers
: elMapModifier.indeterminate ? kKeyLayout.mapLeftModifiers : 0
Expand All @@ -664,6 +665,7 @@ keyLayout.readValueFromElement_ = (): number => {
return flags
}
let _lastKeyLayoutValue: kKeyLayout
let _iprf_visible = true
keyLayout.populateElement_ = (value: number): void => {
const always = !!(value & kKeyLayout.alwaysIgnore)
elAlwaysIgnore.checked = always
Expand All @@ -674,24 +676,32 @@ keyLayout.populateElement_ = (value: number): void => {
elIgnoreCaps.indeterminate = !!(value & kKeyLayout.ignoreCapsOnMac)
elMapModifier.checked = !!(value & kKeyLayout.mapRightModifiers)
elMapModifier.indeterminate = !!(value & (kKeyLayout.mapLeftModifiers))
elInPrivResistFp.checked = !!(value & kKeyLayout.inPrivResistFp_ff)
_lastKeyLayoutValue = value
onAlwaysIgnoreChange()
if (Option_.onFgCacheUpdated_) {
void post_(kPgReq.updatePayload, { key: "l", val: value }).then((val2): void => {
(VApi!.z! as Generalized<NonNullable<VApiTy["z"]>>).l = val2 != null ? val2 : value
Option_.onFgCacheUpdated_ && Option_.onFgCacheUpdated_()
Option_.onFgCacheUpdated_!()
})
}
if (!OnFirefox && _iprf_visible) {
(elInPrivResistFp as HTMLElement as EnsuredMountedHTMLElement
).parentElement.parentElement.parentElement.style.display = "none"
_iprf_visible = false
}
}
const onAlwaysIgnoreChange = (ev?: EventToPrevent): void => {
const always = elAlwaysIgnore.checked
BooleanOption_.ToggleDisabled_(elIgnoreIfAlt, always)
BooleanOption_.ToggleDisabled_(elIgnoreIfNotASCII, always)
BooleanOption_.ToggleDisabled_(elIgnoreCaps, always)
BooleanOption_.ToggleDisabled_(elInPrivResistFp, always)
if (!ev) { /* empty */ }
else if (always) {
elIgnoreIfAlt.checked = elIgnoreIfNotASCII.checked = elIgnoreCaps.checked = true
elIgnoreIfNotASCII.indeterminate = elIgnoreCaps.indeterminate = false
elInPrivResistFp.checked = false
} else {
const old = keyLayout.innerFetch_()
if (typeof old === "number" && !(_lastKeyLayoutValue & kKeyLayout.alwaysIgnore)) {
Expand Down
12 changes: 6 additions & 6 deletions pages/options_wnd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,6 @@ let optionsInit1_ = function (): void {
}
transArgs = OnChrome || secondCond === "," ? ["beforeChromium", [key.slice(1).split(",", 1)[0]]]
: ["lackPermission", [secondCond]]
} else if (key === "mv3,nonFF") {
if (!Build.MV3 || OnFirefox) {
const tr = (el as EnsuredMountedHTMLElement).parentNode.parentNode.parentNode
tr.style.display = "none"
}
continue
} else {
if (!Build.MV3) { key === "action" ? (key = "browser_action") : key }
if (key in manifest_ || validKeys2.includes(key)) { continue }
Expand Down Expand Up @@ -603,8 +597,10 @@ delayBinding_("#testKeyInputBox", "focus", function KeyTester(_focusEvent: Event
}
return result
}
const tip_head = (testKeyInput.previousElementSibling as HTMLElement).textContent
let lastKey: KeyboardEvent | undefined, lastPrevented = kKeyCode.None, hasOutline = false
let lastKeyLayout: kKeyLayout
let tick = 0
testKeyInput.onkeydown = (event): void => {
hasOutline && (hasOutline = false, testKeyInput.classList.remove("outline"))
if (event.keyCode === kKeyCode.ime || event.key === "Process") {
Expand All @@ -624,6 +620,8 @@ delayBinding_("#testKeyInputBox", "focus", function KeyTester(_focusEvent: Event
const s2 = key2 === key ? "" : key2.length > 1 ? `<${key2}>` : key2 || "(empty)"
lastKey = event, lastKeyLayout = VApi.z!.l
text_(s2 ? `${s1} / ${s2}` : s1)
VApi.f(kFgCmd.insertMode, Object.setPrototypeOf<CmdOptions[kFgCmd.insertMode]>(
{ i: true, r: 0, k: "v-esc:test", p: true, h: tip_head + ` (${++tick})` }, null), 1, 0)
if (key === "enter" || key === "tab" || key === "s-tab" || isEsc || key === "f12") {
(key === "enter" || isEsc) && testKeyInput.blur()
return
Expand All @@ -639,12 +637,14 @@ delayBinding_("#testKeyInputBox", "focus", function KeyTester(_focusEvent: Event
if (VApi) {
testKeyInput.classList.add("outline")
hasOutline = true
tick = 0
const text = (testKeyInput.previousElementSibling as HTMLElement).textContent
VApi.f(kFgCmd.insertMode, Object.setPrototypeOf<CmdOptions[kFgCmd.insertMode]>(
{ i: true, r: 0, k: "v-esc:test", p: true, h: text }, null), 1, 0)
}
}
testKeyInput.onblur = (): void => {
tick = 0
if (VApi) {
VApi.f(kFgCmd.dispatchEventCmd, Object.setPrototypeOf<CmdOptions[kFgCmd.dispatchEventCmd]>(
{ type: "keydown", key: "Esc", esc: true }, null), 1, 0)
Expand Down
Loading

0 comments on commit 7f9c81c

Please sign in to comment.