From 8e86863b12856cb5445ffe04f347b3a689aacdae Mon Sep 17 00:00:00 2001 From: Nikos Kanellopoulos Date: Fri, 30 May 2025 16:29:44 +0300 Subject: [PATCH 1/3] feat: Add Unicode keycode support via UC() macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for QMK's Unicode keycodes using the UC() macro syntax. Users can now enter Unicode characters through the "Any" keycode modal. Supported formats: - UC(0x00E4) - with 0x prefix - UC(00E4) - without 0x prefix The implementation: - Parses UC(hex) syntax in the custom keycode modal - Validates hex codes are within QMK's supported range (0x0000-0x7FFF) - Converts to QMK Unicode keycode format (0x8000 | codepoint) - Displays keycodes back in UC() format for clarity Example usage: - UC(0x00E4) for ä - UC(0x2764) for ❤ - UC(0x03B1) for α --- src/utils/advanced-keys.ts | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/utils/advanced-keys.ts b/src/utils/advanced-keys.ts index 6ddf2cc4c..4a8209f8f 100644 --- a/src/utils/advanced-keys.ts +++ b/src/utils/advanced-keys.ts @@ -79,6 +79,10 @@ const topLevelMacroToValue = { MACRO: '_QK_MACRO', // MACRO(n) }; +// QMK Unicode constants +const QK_UNICODE = 0x8000; +const QK_UNICODE_MAX = 0xFFFF; + const modifierKeyToValue = { LCTL: modCodes.QK_LCTL, C: modCodes.QK_LCTL, @@ -163,6 +167,8 @@ export const advancedStringToKeycode = ( return parseTopLevelMacro(parts, basicKeyToByte); } else if (Object.keys(modifierKeyToValue).includes(parts[0])) { return parseModifierCode(parts, basicKeyToByte); + } else if (parts[0] === 'UC') { + return parseUnicodeCode(parts); } return 0; }; @@ -172,6 +178,12 @@ export const advancedKeycodeToString = ( basicKeyToByte: Record, byteToKey: Record, ): string | null => { + // Check if it's a Unicode keycode first + if (inputKeycode >= QK_UNICODE && inputKeycode <= QK_UNICODE_MAX) { + const codePoint = inputKeycode & 0x7FFF; + return `UC(0x${codePoint.toString(16).toUpperCase().padStart(4, '0')})`; + } + let valueToRange: [number, string][] = Object.entries( quantumRanges(basicKeyToByte), ).map(([key, value]) => [value, key]); @@ -400,6 +412,33 @@ const parseModifierCode = ( return bytes.reduce((acc, byte) => acc | byte, 0); }; +const parseUnicodeCode = (inputParts: string[]): number => { + if (inputParts.length < 2 || !inputParts[1]) { + return 0; + } + + const parameter = inputParts[1].trim(); + let codePoint = 0; + + // Parse hex value (with or without 0x prefix) + if (parameter.startsWith('0X')) { + codePoint = parseInt(parameter, 16); + } else if (/^[0-9A-F]+$/i.test(parameter)) { + // Plain hex without 0x prefix + codePoint = parseInt(parameter, 16); + } else { + return 0; + } + + // Validate code point is within QMK's supported range (0 to 0x7FFF) + if (codePoint < 0 || codePoint > 0x7FFF) { + return 0; + } + + // Return the QMK Unicode keycode (QK_UNICODE | codePoint) + return QK_UNICODE | codePoint; +}; + export const anyKeycodeToString = ( input: number, basicKeyToByte: Record, From 8e097eaacce4b3550ec5316a29bcb44321f77808 Mon Sep 17 00:00:00 2001 From: Nikos Kanellopoulos Date: Fri, 30 May 2025 17:50:45 +0300 Subject: [PATCH 2/3] feat: Add Unicode Map keycode support (UM and UP macros) Add support for QMK's Unicode Map keycodes to access the full Unicode range. Supported formats: - UM(index) - Unicode Map array index (0-16383) Allows access to full Unicode range including emoji - UP(i, j) - Unicode Pair for lowercase/uppercase (0-127 each) Automatically switches based on Shift/Caps Lock state Implementation: - Parse UM(index) syntax and convert to QK_UNICODEMAP format - Parse UP(i,j) syntax and convert to QK_UNICODEMAP_PAIR format - Display keycodes back in their original format for clarity This extends the previous UC() support to cover all QMK Unicode features except UCIS (which requires custom keycode implementation). --- src/utils/advanced-keys.ts | 69 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/src/utils/advanced-keys.ts b/src/utils/advanced-keys.ts index 4a8209f8f..4ad6e6539 100644 --- a/src/utils/advanced-keys.ts +++ b/src/utils/advanced-keys.ts @@ -82,6 +82,10 @@ const topLevelMacroToValue = { // QMK Unicode constants const QK_UNICODE = 0x8000; const QK_UNICODE_MAX = 0xFFFF; +const QK_UNICODEMAP = 0x8000; +const QK_UNICODEMAP_MAX = 0xBFFF; +const QK_UNICODEMAP_PAIR = 0xC000; +const QK_UNICODEMAP_PAIR_MAX = 0xFFFF; const modifierKeyToValue = { LCTL: modCodes.QK_LCTL, @@ -169,6 +173,10 @@ export const advancedStringToKeycode = ( return parseModifierCode(parts, basicKeyToByte); } else if (parts[0] === 'UC') { return parseUnicodeCode(parts); + } else if (parts[0] === 'UM') { + return parseUnicodeMapCode(parts); + } else if (parts[0] === 'UP') { + return parseUnicodePairCode(parts); } return 0; }; @@ -180,8 +188,22 @@ export const advancedKeycodeToString = ( ): string | null => { // Check if it's a Unicode keycode first if (inputKeycode >= QK_UNICODE && inputKeycode <= QK_UNICODE_MAX) { - const codePoint = inputKeycode & 0x7FFF; - return `UC(0x${codePoint.toString(16).toUpperCase().padStart(4, '0')})`; + // Unicode Map (UM) + if (inputKeycode >= QK_UNICODEMAP && inputKeycode <= QK_UNICODEMAP_MAX) { + const index = inputKeycode & 0x3FFF; + return `UM(${index})`; + } + // Unicode Pair (UP) + else if (inputKeycode >= QK_UNICODEMAP_PAIR && inputKeycode <= QK_UNICODEMAP_PAIR_MAX) { + const lowerIndex = inputKeycode & 0x7F; + const upperIndex = (inputKeycode >> 7) & 0x7F; + return `UP(${lowerIndex}, ${upperIndex})`; + } + // Basic Unicode (UC) + else { + const codePoint = inputKeycode & 0x7FFF; + return `UC(0x${codePoint.toString(16).toUpperCase().padStart(4, '0')})`; + } } let valueToRange: [number, string][] = Object.entries( @@ -439,6 +461,49 @@ const parseUnicodeCode = (inputParts: string[]): number => { return QK_UNICODE | codePoint; }; +const parseUnicodeMapCode = (inputParts: string[]): number => { + if (inputParts.length < 2 || !inputParts[1]) { + return 0; + } + + const parameter = inputParts[1].trim(); + const index = parseInt(parameter, 10); + + // Validate index is within valid range (0 to 0x3FFF = 16383) + if (isNaN(index) || index < 0 || index > 0x3FFF) { + return 0; + } + + // Return the QMK Unicode Map keycode (QK_UNICODEMAP | index) + return QK_UNICODEMAP | index; +}; + +const parseUnicodePairCode = (inputParts: string[]): number => { + if (inputParts.length < 2 || !inputParts[1]) { + return 0; + } + + const parameter = inputParts[1].trim(); + const indices = parameter.split(',').map(s => s.trim()); + + if (indices.length !== 2) { + return 0; + } + + const lowerIndex = parseInt(indices[0], 10); + const upperIndex = parseInt(indices[1], 10); + + // Validate indices are within valid range (0 to 0x7F = 127) + if (isNaN(lowerIndex) || lowerIndex < 0 || lowerIndex > 0x7F || + isNaN(upperIndex) || upperIndex < 0 || upperIndex > 0x7F) { + return 0; + } + + // Return the QMK Unicode Pair keycode + // UP(i, j) = QK_UNICODEMAP_PAIR | (i & 0x7F) | ((j & 0x7F) << 7) + return QK_UNICODEMAP_PAIR | (lowerIndex & 0x7F) | ((upperIndex & 0x7F) << 7); +}; + export const anyKeycodeToString = ( input: number, basicKeyToByte: Record, From 35cccc9fbbf915be81d0f5a6e89eba8589ca4d27 Mon Sep 17 00:00:00 2001 From: Nikos Kanellopoulos Date: Sat, 31 May 2025 22:37:19 +0300 Subject: [PATCH 3/3] docs: Add Unicode feature documentation - Document UC(), UM(), and UP() keycode usage in VIA - Include firmware configuration requirements - Add examples with emoji and Greek letters - Note that only one Unicode mode can be enabled at a time - Provide OS-specific setup instructions - Include troubleshooting guide --- Unicode.md | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 Unicode.md diff --git a/Unicode.md b/Unicode.md new file mode 100644 index 000000000..9259297e1 --- /dev/null +++ b/Unicode.md @@ -0,0 +1,186 @@ +# Unicode Support in VIA + +This guide explains how to use Unicode keycodes in VIA and configure your QMK firmware to support them. + +## Overview + +VIA now supports three types of Unicode keycodes through the "Any" keycode feature: + +- **UC(hex)** - Basic Unicode (up to U+7FFF) +- **UM(index)** - Unicode Map (full Unicode range via array index) +- **UP(i,j)** - Unicode Pair (lowercase/uppercase switching) + +## Prerequisites + +### Operating System Configuration + +Before using Unicode features, ensure your OS is configured for Unicode input: + +- **Windows**: WinCompose or the built-in hex input method +- **macOS**: Unicode Hex Input keyboard layout +- **Linux**: IBus or the Ctrl+Shift+U method + +### Firmware Requirements + +**Important**: You can only enable ONE of these options at a time. Enabling both `UNICODE_ENABLE` and `UNICODEMAP_ENABLE` will result in compilation errors. + +#### For UC() keycodes: +Add to your `rules.mk`: +```makefile +UNICODE_ENABLE = yes +# UNICODEMAP_ENABLE = no # Must be disabled or omitted +``` + +Add to your `config.h`: +```c +#define UNICODE_SELECTED_MODES UC_WIN // or UC_MAC, UC_LNX +``` + +#### For UM() and UP() keycodes: +Add to your `rules.mk`: +```makefile +UNICODEMAP_ENABLE = yes +# UNICODE_ENABLE = no # Must be disabled or omitted +``` + +## Using Unicode in VIA + +### Basic Unicode - UC() + +1. Click on a key in VIA +2. Click the "Any" button +3. Enter a Unicode keycode: + - `UC(0x00E4)` - ä (with 0x prefix) + - `UC(00E4)` - ä (without 0x prefix) + +Common examples: +- `UC(0x00A9)` - © (Copyright) +- `UC(0x2764)` - ❤ (Heart) +- `UC(0x03B1)` - α (Greek alpha) +- `UC(0x2318)` - ⌘ (Command) + +**Limitation**: UC() only supports characters up to U+7FFF. + +### Unicode Map - UM() + +For full Unicode support (including emoji), use Unicode Map: + +1. Define a `unicode_map` array in your keymap.c: +```c +enum unicode_names { + EYES, + FIRE, + ROCKET, + HEART, + SMILE, + THINK, + PARTY, + COOL +}; + +const uint32_t PROGMEM unicode_map[] = { + [EYES] = 0x1F440, // 👀 + [FIRE] = 0x1F525, // 🔥 + [ROCKET] = 0x1F680, // 🚀 + [HEART] = 0x2764, // ❤️ + [SMILE] = 0x1F604, // 😄 + [THINK] = 0x1F914, // 🤔 + [PARTY] = 0x1F389, // 🎉 + [COOL] = 0x1F60E // 😎 +}; +``` + +2. In VIA, use the index: + - `UM(0)` - 👀 (eyes) + - `UM(1)` - 🔥 (fire) + - `UM(2)` - 🚀 (rocket) + +### Unicode Pair - UP() + +For characters that have lowercase/uppercase variants: + +1. Define pairs in your unicode_map: +```c +enum unicode_names { + GREEK_LOWER_ALPHA, + GREEK_UPPER_ALPHA, + GREEK_LOWER_BETA, + GREEK_UPPER_BETA +}; + +const uint32_t PROGMEM unicode_map[] = { + [GREEK_LOWER_ALPHA] = 0x03B1, // α + [GREEK_UPPER_ALPHA] = 0x0391, // Α + [GREEK_LOWER_BETA] = 0x03B2, // β + [GREEK_UPPER_BETA] = 0x0392 // Β +}; +``` + +2. In VIA, use: + - `UP(0,1)` - α/Α (switches with Shift/Caps Lock) + - `UP(2,3)` - β/Β + +## Complete Example + +Here's a complete example for a keymap with emoji support: + +```c +// In keymap.c +enum unicode_names { + // Emoji + EYES, FIRE, ROCKET, HEART, SMILE, THINK, PARTY, COOL, + // Greek letters (lowercase, uppercase) + GR_ALPHA_L, GR_ALPHA_U, + GR_BETA_L, GR_BETA_U +}; + +const uint32_t PROGMEM unicode_map[] = { + // Emoji + [EYES] = 0x1F440, // 👀 + [FIRE] = 0x1F525, // 🔥 + [ROCKET] = 0x1F680, // 🚀 + [HEART] = 0x2764, // ❤️ + [SMILE] = 0x1F604, // 😄 + [THINK] = 0x1F914, // 🤔 + [PARTY] = 0x1F389, // 🎉 + [COOL] = 0x1F60E, // 😎 + // Greek pairs + [GR_ALPHA_L] = 0x03B1, // α + [GR_ALPHA_U] = 0x0391, // Α + [GR_BETA_L] = 0x03B2, // β + [GR_BETA_U] = 0x0392 // Β +}; +``` + +Then in VIA: +- `UM(0)` through `UM(7)` for emoji +- `UP(8,9)` for α/Α +- `UP(10,11)` for β/Β + +## Troubleshooting + +### Characters not appearing +1. Check your OS Unicode input method is enabled +2. Verify firmware has UNICODE_ENABLE or UNICODEMAP_ENABLE +3. Ensure the correct input mode (UC_WIN/UC_MAC/UC_LNX) + +### Invalid keycode in VIA +1. For UC(), ensure hex value is ≤ 0x7FFF +2. For UM(), ensure index exists in your unicode_map +3. For UP(), ensure both indices are ≤ 127 + +### Emoji showing as boxes +- Your application/font may not support emoji +- Try in a modern browser or text editor + +## Legacy Aliases + +VIA also supports legacy aliases: +- `X()` is equivalent to `UM()` +- `XP()` is equivalent to `UP()` + +## Resources + +- [QMK Unicode Documentation](https://docs.qmk.fm/#/feature_unicode) +- [Unicode Character Table](https://unicode-table.com/) +- [Emoji Unicode List](https://unicode.org/emoji/charts/full-emoji-list.html) \ No newline at end of file