Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions cocos/input/types/key-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,18 @@ export enum KeyCode {
*/
KEY_Z = 90,

/**
* @en The left meta key (CMD on macOS, Windows key on Windows)
* @zh 左 Meta 键(macOS 上的 CMD 键,Windows 上的 Windows 键)
*/
META_LEFT = 91,

/**
* @en The right meta key (CMD on macOS, Windows key on Windows)
* @zh 右 Meta 键(macOS 上的 CMD 键,Windows 上的 Windows 键)
*/
META_RIGHT = 93,

/**
* @en The numeric keypad 0
* @zh 数字键盘 0
Expand Down
2 changes: 2 additions & 0 deletions pal/input/keycodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const code2KeyCode: Record<string, KeyCode> = {
ShiftRight: KeyCode.SHIFT_RIGHT,
ControlRight: KeyCode.CTRL_RIGHT,
AltRight: KeyCode.ALT_RIGHT,
MetaLeft: KeyCode.META_LEFT,
MetaRight: KeyCode.META_RIGHT,
Pause: KeyCode.PAUSE,
CapsLock: KeyCode.CAPS_LOCK,
Escape: KeyCode.ESCAPE,
Expand Down
3 changes: 3 additions & 0 deletions pal/input/native/keyboard-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,16 @@ const nativeKeyCode2KeyCode: Record<number, KeyCode> = {
20016: KeyCode.SHIFT_RIGHT,
20017: KeyCode.CTRL_RIGHT,
20018: KeyCode.ALT_RIGHT,
91: KeyCode.META_LEFT,
93: KeyCode.META_RIGHT,
};

function getKeyCode (event: jsb.KeyboardEvent): KeyCode {
if (event.code) {
if (event.code in code2KeyCode) {
return code2KeyCode[event.code];
} else {
// eslint-disable-next-line no-console
console.error(`Can not find keyCode for code: ${event.code}`);
}
}
Expand Down
64 changes: 64 additions & 0 deletions tests/pal/input-keycode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { KeyCode } from '../../cocos/input/types/key-code';
import { code2KeyCode } from '../../pal/input/keycodes';

describe('KeyCode enum', () => {
test('META_LEFT should be defined as 91', () => {
expect(KeyCode.META_LEFT).toBe(91);
});

test('META_RIGHT should be defined as 93', () => {
expect(KeyCode.META_RIGHT).toBe(93);
});

test('META_LEFT should match C++ EngineEvents.h value', () => {
// C++ side: META_LEFT = 91 (EngineEvents.h:233)
expect(KeyCode.META_LEFT).toBe(91);
});

test('META_RIGHT should match C++ EngineEvents.h value', () => {
// C++ side: META_RIGHT = 93 (EngineEvents.h:236)
expect(KeyCode.META_RIGHT).toBe(93);
});

test('META_LEFT and META_RIGHT should not conflict with adjacent enum values', () => {
// KEY_Z = 90, META_LEFT = 91, META_RIGHT = 93, NUM_0 = 96
expect(KeyCode.KEY_Z).toBe(90);
expect(KeyCode.META_LEFT).toBe(91);
expect(KeyCode.META_RIGHT).toBe(93);
expect(KeyCode.NUM_0).toBe(96);
// Verify no collision
expect(KeyCode.META_LEFT).not.toBe(KeyCode.KEY_Z);
expect(KeyCode.META_LEFT).not.toBe(KeyCode.META_RIGHT);
expect(KeyCode.META_RIGHT).not.toBe(KeyCode.NUM_0);
});
});

describe('code2KeyCode mapping', () => {
test('MetaLeft should map to KeyCode.META_LEFT (91)', () => {
expect(code2KeyCode.MetaLeft).toBe(KeyCode.META_LEFT);
expect(code2KeyCode.MetaLeft).toBe(91);
});

test('MetaRight should map to KeyCode.META_RIGHT (93)', () => {
expect(code2KeyCode.MetaRight).toBe(KeyCode.META_RIGHT);
expect(code2KeyCode.MetaRight).toBe(93);
});

test('MetaLeft/MetaRight should follow the same pattern as other modifier keys', () => {
// All modifier keys should have left/right variants mapped
expect(code2KeyCode.ShiftLeft).toBe(KeyCode.SHIFT_LEFT);
expect(code2KeyCode.ShiftRight).toBe(KeyCode.SHIFT_RIGHT);
expect(code2KeyCode.ControlLeft).toBe(KeyCode.CTRL_LEFT);
expect(code2KeyCode.ControlRight).toBe(KeyCode.CTRL_RIGHT);
expect(code2KeyCode.AltLeft).toBe(KeyCode.ALT_LEFT);
expect(code2KeyCode.AltRight).toBe(KeyCode.ALT_RIGHT);
expect(code2KeyCode.MetaLeft).toBe(KeyCode.META_LEFT);
expect(code2KeyCode.MetaRight).toBe(KeyCode.META_RIGHT);
});

test('MetaLeft/MetaRight should not be undefined', () => {
// This was the original bug - these mappings were missing
expect(code2KeyCode.MetaLeft).toBeDefined();
expect(code2KeyCode.MetaRight).toBeDefined();
});
});
112 changes: 112 additions & 0 deletions verify-meta-key-fix.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* Standalone verification for META_LEFT/META_RIGHT fix (#18466)
* Run: node verify-meta-key-fix.mjs
*
* This script directly reads and parses source files to verify the fix,
* without needing the full engine build environment.
*/

import { readFileSync } from 'fs';

let passed = 0;
let failed = 0;

function assert(condition, msg) {
if (condition) { console.log(` ✅ PASS: ${msg}`); passed++; }
else { console.log(` ❌ FAIL: ${msg}`); failed++; }
}

console.log('\n=== Verifying META_LEFT/META_RIGHT fix (Issue #18466) ===\n');

// ── 1. Check KeyCode enum in key-code.ts ──
console.log('1. KeyCode enum (cocos/input/types/key-code.ts):');
const keyCodeSrc = readFileSync('cocos/input/types/key-code.ts', 'utf-8');
const metaLeftMatch = keyCodeSrc.match(/META_LEFT\s*=\s*(\d+)/);
const metaRightMatch = keyCodeSrc.match(/META_RIGHT\s*=\s*(\d+)/);
assert(metaLeftMatch !== null, 'META_LEFT enum value exists');
assert(metaLeftMatch?.[1] === '91', `META_LEFT = ${metaLeftMatch?.[1]} (expected 91)`);
assert(metaRightMatch !== null, 'META_RIGHT enum value exists');
assert(metaRightMatch?.[1] === '93', `META_RIGHT = ${metaRightMatch?.[1]} (expected 93)`);

// Verify numeric ordering: KEY_Z=90, META_LEFT=91, META_RIGHT=93, NUM_0=96
const keyZMatch = keyCodeSrc.match(/KEY_Z\s*=\s*(\d+)/);
const num0Match = keyCodeSrc.match(/NUM_0\s*=\s*(\d+)/);
assert(keyZMatch?.[1] === '90', `KEY_Z = ${keyZMatch?.[1]} (expected 90)`);
assert(num0Match?.[1] === '96', `NUM_0 = ${num0Match?.[1]} (expected 96)`);

// Verify META_LEFT is placed AFTER KEY_Z and BEFORE NUM_0 in the file
const keyZPos = keyCodeSrc.indexOf('KEY_Z = 90');
const metaLeftPos = keyCodeSrc.indexOf('META_LEFT = 91');
const metaRightPos = keyCodeSrc.indexOf('META_RIGHT = 93');
const num0Pos = keyCodeSrc.indexOf('NUM_0 = 96');
assert(keyZPos < metaLeftPos && metaLeftPos < metaRightPos && metaRightPos < num0Pos,
'Enum placement order: KEY_Z(90) < META_LEFT(91) < META_RIGHT(93) < NUM_0(96)');

// ── 2. Check code2KeyCode mapping in keycodes.ts ──
console.log('\n2. Web mapping (pal/input/keycodes.ts):');
const keycodesSrc = readFileSync('pal/input/keycodes.ts', 'utf-8');
assert(keycodesSrc.includes('MetaLeft: KeyCode.META_LEFT'), 'MetaLeft → KeyCode.META_LEFT mapping exists');
assert(keycodesSrc.includes('MetaRight: KeyCode.META_RIGHT'), 'MetaRight → KeyCode.META_RIGHT mapping exists');

// Verify all modifier keys have mappings (completeness check)
const modifierPairs = [
['ShiftLeft', 'SHIFT_LEFT'], ['ShiftRight', 'SHIFT_RIGHT'],
['ControlLeft', 'CTRL_LEFT'], ['ControlRight', 'CTRL_RIGHT'],
['AltLeft', 'ALT_LEFT'], ['AltRight', 'ALT_RIGHT'],
['MetaLeft', 'META_LEFT'], ['MetaRight', 'META_RIGHT'],
];
for (const [code, keycode] of modifierPairs) {
assert(keycodesSrc.includes(`${code}: KeyCode.${keycode}`),
`${code} → KeyCode.${keycode}`);
}

// ── 3. Check nativeKeyCode2KeyCode in keyboard-input.ts ──
console.log('\n3. Native fallback (pal/input/native/keyboard-input.ts):');
const nativeKbSrc = readFileSync('pal/input/native/keyboard-input.ts', 'utf-8');
assert(nativeKbSrc.includes('91: KeyCode.META_LEFT'), 'Native keyCode 91 → KeyCode.META_LEFT');
assert(nativeKbSrc.includes('93: KeyCode.META_RIGHT'), 'Native keyCode 93 → KeyCode.META_RIGHT');

// ── 4. Verify C++ side consistency ──
console.log('\n4. C++ native layer consistency (native/cocos/engine/EngineEvents.h):');
const engineEventsSrc = readFileSync('native/cocos/engine/EngineEvents.h', 'utf-8');
const cppMetaLeft = engineEventsSrc.match(/META_LEFT\s*=\s*(\d+)/);
const cppMetaRight = engineEventsSrc.match(/META_RIGHT\s*=\s*(\d+)/);
assert(cppMetaLeft?.[1] === '91', `C++ META_LEFT = ${cppMetaLeft?.[1]} (expected 91)`);
assert(cppMetaRight?.[1] === '93', `C++ META_RIGHT = ${cppMetaRight?.[1]} (expected 93)`);
assert(metaLeftMatch?.[1] === cppMetaLeft?.[1], `TS META_LEFT (${metaLeftMatch?.[1]}) === C++ META_LEFT (${cppMetaLeft?.[1]})`);
assert(metaRightMatch?.[1] === cppMetaRight?.[1], `TS META_RIGHT (${metaRightMatch?.[1]}) === C++ META_RIGHT (${cppMetaRight?.[1]})`);

// ── 5. Verify JSB adapter handles 91/93 ──
console.log('\n5. JSB adapter (platforms/native/builtin/jsb-adapter/KeyboardEvent.js):');
const jsbSrc = readFileSync('platforms/native/builtin/jsb-adapter/KeyboardEvent.js', 'utf-8');
assert(jsbSrc.includes("keyCode === 91") && jsbSrc.includes("'MetaLeft'"),
'JSB adapter maps keyCode 91 → MetaLeft');
assert(jsbSrc.includes("keyCode === 93") && jsbSrc.includes("'MetaRight'"),
'JSB adapter maps keyCode 93 → MetaRight');

// ── 6. Verify macOS KeyCodeHelper maps SUPER keys to 91/93 ──
console.log('\n6. macOS KeyCodeHelper (native/cocos/platform/mac/KeyCodeHelper.cpp):');
const keyCodeHelperSrc = readFileSync('native/cocos/platform/mac/KeyCodeHelper.cpp', 'utf-8');
assert(keyCodeHelperSrc.includes('GLFW_KEY_LEFT_SUPER') && keyCodeHelperSrc.includes('91'),
'GLFW_KEY_LEFT_SUPER → 91');
assert(keyCodeHelperSrc.includes('GLFW_KEY_RIGHT_SUPER') && keyCodeHelperSrc.includes('93'),
'GLFW_KEY_RIGHT_SUPER → 93');

// ── 7. Bug reproduction: simulate the data flow ──
console.log('\n7. End-to-end data flow simulation:');
console.log(' macOS CMD press → native keyCode=91 → JSB code="MetaLeft" → code2KeyCode → KeyCode.META_LEFT=91');
assert(metaLeftMatch?.[1] === '91' && keycodesSrc.includes('MetaLeft: KeyCode.META_LEFT'),
'Full chain: CMD Left → keyCode 91 (was 0 before fix)');
assert(metaRightMatch?.[1] === '93' && keycodesSrc.includes('MetaRight: KeyCode.META_RIGHT'),
'Full chain: CMD Right → keyCode 93 (was 0 before fix)');

// ── Summary ──
console.log(`\n${'='.repeat(55)}`);
console.log(`Results: ${passed} passed, ${failed} failed, ${passed + failed} total`);
if (failed === 0) {
console.log('🎉 All checks passed! META_LEFT/META_RIGHT fix verified.');
console.log(' The full keycode chain from macOS native → TypeScript is now connected.\n');
} else {
console.log('💥 Some checks failed!\n');
process.exit(1);
}
Loading