3 Commits

Author SHA1 Message Date
loveuer
6285cabdd1 Major improvements and bug fixes for v1.1.0
Some checks failed
Build and Release / Build DMG for arm64 (push) Has been cancelled
Build and Release / Build DMG for x86_64 (push) Has been cancelled
Build and Release / Create Release (push) Has been cancelled
Features:
- Add auto-enable on startup based on config
- Add enabled state persistence in config.json
- Add limitations notice in GUI menu
- Simplify logging to single file (uskey.log)
- Change "Open Logs Folder" to "Open Config Folder"

Bug Fixes:
- Fix GUI status display mismatch on startup
- Fix first toggle click not working issue
- Revert password field changes that caused key blocking

UI Changes:
- Add ⚠️ emoji to "About Limitations" menu item
- Remove button action to fix menu refresh

🤖 Generated with [Qoder][https://qoder.com]
2025-12-05 13:48:15 +08:00
loveuer
99bbbbc73a Fix keyboard remapping in password fields and secure input contexts
Some checks failed
Build and Release / Build DMG for arm64 (push) Has been cancelled
Build and Release / Build DMG for x86_64 (push) Has been cancelled
Build and Release / Create Release (push) Has been cancelled
Changed event handling to create and post new keyboard events instead of
modifying existing events, which resolves issues with password fields in
web browsers and other secure input contexts that ignore modified events.

🤖 Generated with [Qoder][https://qoder.com]
2025-12-03 19:07:25 +08:00
loveuer
333bbc56f9 chore: add KEY_MAPPING.md, update README.md 2025-12-03 10:26:38 +08:00
6 changed files with 244 additions and 44 deletions

148
KEY_MAPPING.md Normal file
View File

@@ -0,0 +1,148 @@
# macOS Key Code Reference
This document lists common macOS key codes for keyboard remapping configuration.
## Alphanumeric Keys
| Key | Code | Description |
|-----|------|-------------|
| A | 0 | Letter A |
| S | 1 | Letter S |
| D | 2 | Letter D |
| F | 3 | Letter F |
| H | 4 | Letter H |
| G | 5 | Letter G |
| Z | 6 | Letter Z |
| X | 7 | Letter X |
| C | 8 | Letter C |
| V | 9 | Letter V |
| B | 11 | Letter B |
| Q | 12 | Letter Q |
| W | 13 | Letter W |
| E | 14 | Letter E |
| R | 15 | Letter R |
| Y | 16 | Letter Y |
| T | 17 | Letter T |
| 1 | 18 | Number 1 |
| 2 | 19 | Number 2 |
| 3 | 20 | Number 3 |
| 4 | 21 | Number 4 |
| 6 | 22 | Number 6 |
| 5 | 23 | Number 5 |
| = | 24 | Equal sign |
| 9 | 25 | Number 9 |
| 7 | 26 | Number 7 |
| - | 27 | Minus/Hyphen |
| 8 | 28 | Number 8 |
| 0 | 29 | Number 0 |
| ] | 30 | Right bracket |
| O | 31 | Letter O |
| U | 32 | Letter U |
| [ | 33 | Left bracket |
| I | 34 | Letter I |
| P | 35 | Letter P |
| L | 37 | Letter L |
| J | 38 | Letter J |
| ' | 39 | Quote/Apostrophe |
| K | 40 | Letter K |
| ; | 41 | Semicolon |
| \ | 42 | Backslash |
| , | 43 | Comma |
| / | 44 | Slash |
| N | 45 | Letter N |
| M | 46 | Letter M |
| . | 47 | Period/Dot |
| ` | 50 | Grave accent/Backtick |
## Function Keys
| Key | Code | Description |
|-----|------|-------------|
| Return | 36 | Enter/Return key |
| Tab | 48 | Tab key |
| Space | 49 | Space bar |
| Delete | 51 | Backspace/Delete |
| Escape | 53 | Escape key |
| Command | 55 | Command/Cmd key |
| Shift | 56 | Shift key |
| CapsLock | 57 | Caps Lock |
| Option | 58 | Option/Alt key |
| Control | 59 | Control key |
| RightShift | 60 | Right Shift key |
| RightOption | 61 | Right Option/Alt key |
| RightControl | 62 | Right Control key |
| Function | 63 | Function/Fn key |
## Special Keys
| Key | Code | Description |
|-----|------|-------------|
| F5 | 96 | F5 key |
| F6 | 97 | F6 key |
| F7 | 98 | F7 key |
| F3 | 99 | F3 key |
| F8 | 100 | F8 key |
| F9 | 101 | F9 key |
| F11 | 103 | F11 key |
| F13 | 105 | F13 key |
| F14 | 107 | F14 key |
| F10 | 109 | F10 key |
| F12 | 111 | F12 key |
| F15 | 113 | F15 key |
| Help | 114 | Help key |
| Home | 115 | Home key |
| PageUp | 116 | Page Up |
| ForwardDelete | 117 | Forward Delete (Del) |
| F4 | 118 | F4 key |
| End | 119 | End key |
| F2 | 120 | F2 key |
| PageDown | 121 | Page Down |
| F1 | 122 | F1 key |
| LeftArrow | 123 | Left Arrow |
| RightArrow | 124 | Right Arrow |
| DownArrow | 125 | Down Arrow |
| UpArrow | 126 | Up Arrow |
## Numpad Keys
| Key | Code | Description |
|-----|------|-------------|
| Keypad . | 65 | Decimal point |
| Keypad * | 67 | Multiply |
| Keypad + | 69 | Add |
| Keypad Clear | 71 | Clear |
| Keypad / | 75 | Divide |
| Keypad Enter | 76 | Enter |
| Keypad - | 78 | Subtract |
| Keypad = | 81 | Equal |
| Keypad 0 | 82 | Number 0 |
| Keypad 1 | 83 | Number 1 |
| Keypad 2 | 84 | Number 2 |
| Keypad 3 | 85 | Number 3 |
| Keypad 4 | 86 | Number 4 |
| Keypad 5 | 87 | Number 5 |
| Keypad 6 | 88 | Number 6 |
| Keypad 7 | 89 | Number 7 |
| Keypad 8 | 91 | Number 8 |
| Keypad 9 | 92 | Number 9 |
## Media and System Keys
| Key | Code | Description |
|-----|------|-------------|
| Mute | 74 | Volume Mute |
| VolumeUp | 72 | Volume Up |
| VolumeDown | 73 | Volume Down |
| Brightness Down | 145 | Display brightness down |
| Brightness Up | 144 | Display brightness up |
| Play/Pause | 16 | Media play/pause (F8) |
| Previous Track | 17 | Previous track (F7) |
| Next Track | 19 | Next track (F9) |
## Notes
- Key codes are decimal values used by macOS CGEvent API
- Some media keys may require special handling or may not be remappable
- Function keys (F1-F15) can have default system behaviors that may need to be overridden
- Modifier keys (Command, Shift, Control, Option) can be combined with other keys
- The codes listed are for US keyboard layout; other layouts may vary

View File

@@ -70,7 +70,9 @@ The configuration file is automatically created at `~/.config/uskey/config.json`
### Key Codes
Common macOS key codes:
For a complete list of macOS key codes, see [KEY_MAPPING.md](KEY_MAPPING.md).
Common examples:
- Backspace: 51
- Backslash `\`: 42
- Enter: 36
@@ -114,12 +116,14 @@ If you can't enable mapping after installation:
### Log Files Location
Logs are stored at: `~/.config/uskey/logs/uskey-YYYY-MM-DD.log`
Logs are stored at: `~/.config/uskey/logs/uskey.log`
The log file is cleared on each application start.
You can view logs by:
- **Menu Bar**: Click uskey icon → "View Current Log"
- **Finder**: Click uskey icon → "Open Logs Folder" (⌘L)
- **Terminal**: `tail -f ~/.config/uskey/logs/uskey-$(date +%Y-%m-%d).log`
- **Terminal**: `tail -f ~/.config/uskey/logs/uskey.log`
### Common Issues

View File

@@ -53,12 +53,16 @@ class AppDelegate: NSObject, NSApplicationDelegate {
statusBarController?.setupStatusBar()
Logger.info("Attempting to start event monitoring...")
if config.enabled {
Logger.info("Auto-enabling event monitoring (enabled: true in config)...")
if eventTapManager!.start() {
Logger.info("Event monitoring started successfully")
} else {
Logger.error("Failed to start event monitoring - check if app has accessibility permissions")
}
} else {
Logger.info("Event monitoring not auto-enabled (enabled: false in config)")
}
Logger.info("Application ready")
}

View File

@@ -54,10 +54,12 @@ struct MappingConfig: Codable {
struct Config: Codable {
let log: LogConfig
let mapping: MappingConfig
let enabled: Bool
init(log: LogConfig = LogConfig(), mapping: MappingConfig = MappingConfig()) {
init(log: LogConfig = LogConfig(), mapping: MappingConfig = MappingConfig(), enabled: Bool = true) {
self.log = log
self.mapping = mapping
self.enabled = enabled
}
static func load(from path: String) throws -> Config {
@@ -67,13 +69,21 @@ struct Config: Codable {
return try decoder.decode(Config.self, from: data)
}
static func save(_ config: Config, to path: String) throws {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let data = try encoder.encode(config)
try data.write(to: URL(fileURLWithPath: path))
}
static func createDefault(at path: String) throws {
let defaultConfig = Config(
log: LogConfig(level: .info),
mapping: MappingConfig(mappings: [
"backslash2backspace": KeyMapping(from: 42, to: 51),
"backspace2backslash": KeyMapping(from: 51, to: 42)
])
]),
enabled: true
)
let encoder = JSONEncoder()

View File

@@ -9,18 +9,11 @@ struct Logger {
let logsDir = getLogsDirectory()
try? FileManager.default.createDirectory(at: URL(fileURLWithPath: logsDir), withIntermediateDirectories: true)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let dateString = dateFormatter.string(from: Date())
logFilePath = "\(logsDir)/uskey.log"
logFilePath = "\(logsDir)/uskey-\(dateString).log"
if !FileManager.default.fileExists(atPath: logFilePath!) {
FileManager.default.createFile(atPath: logFilePath!, contents: nil)
}
logFileHandle = FileHandle(forWritingAtPath: logFilePath!)
logFileHandle?.seekToEndOfFile()
info("Logger initialized, log file: \(logFilePath!)")
}

View File

@@ -6,6 +6,7 @@ class StatusBarController {
private var statusItem: NSStatusItem?
private var eventTapManager: EventTapManager
private var config: Config
private var menu: NSMenu?
init(eventTapManager: EventTapManager, config: Config) {
self.eventTapManager = eventTapManager
@@ -17,8 +18,6 @@ class StatusBarController {
if let button = statusItem?.button {
button.image = NSImage(systemSymbolName: "keyboard", accessibilityDescription: "uskey")
button.action = #selector(statusBarButtonClicked)
button.target = self
}
updateMenu()
@@ -29,10 +28,10 @@ class StatusBarController {
}
private func updateMenu() {
let menu = NSMenu()
let newMenu = NSMenu()
menu.addItem(NSMenuItem(title: "uskey - Keyboard Remapper", action: nil, keyEquivalent: ""))
menu.addItem(NSMenuItem.separator())
newMenu.addItem(NSMenuItem(title: "uskey - Keyboard Remapper", action: nil, keyEquivalent: ""))
newMenu.addItem(NSMenuItem.separator())
let isEnabled = eventTapManager.isRunning()
let toggleItem = NSMenuItem(
@@ -41,49 +40,56 @@ class StatusBarController {
keyEquivalent: ""
)
toggleItem.target = self
menu.addItem(toggleItem)
newMenu.addItem(toggleItem)
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Current Mappings:", action: nil, keyEquivalent: ""))
newMenu.addItem(NSMenuItem.separator())
newMenu.addItem(NSMenuItem(title: "Current Mappings:", action: nil, keyEquivalent: ""))
let mappings = config.mapping.getAllMappings()
if mappings.isEmpty {
let item = NSMenuItem(title: " No mappings configured", action: nil, keyEquivalent: "")
item.isEnabled = false
menu.addItem(item)
newMenu.addItem(item)
} else {
for (from, to) in mappings.sorted(by: { $0.0 < $1.0 }) {
let item = NSMenuItem(title: " \(from)\(to)", action: nil, keyEquivalent: "")
item.isEnabled = false
menu.addItem(item)
newMenu.addItem(item)
}
}
menu.addItem(NSMenuItem.separator())
newMenu.addItem(NSMenuItem.separator())
let reloadItem = NSMenuItem(title: "Reload Configuration", action: #selector(reloadConfig), keyEquivalent: "r")
reloadItem.target = self
menu.addItem(reloadItem)
newMenu.addItem(reloadItem)
menu.addItem(NSMenuItem.separator())
newMenu.addItem(NSMenuItem.separator())
let openLogsItem = NSMenuItem(title: "Open Logs Folder", action: #selector(openLogsFolder), keyEquivalent: "l")
openLogsItem.target = self
menu.addItem(openLogsItem)
let openConfigItem = NSMenuItem(title: "Open Config Folder", action: #selector(openConfigFolder), keyEquivalent: "c")
openConfigItem.target = self
newMenu.addItem(openConfigItem)
if let _ = Logger.getLogFilePath() {
let viewLogItem = NSMenuItem(title: "View Current Log", action: #selector(viewCurrentLog), keyEquivalent: "")
viewLogItem.target = self
menu.addItem(viewLogItem)
newMenu.addItem(viewLogItem)
}
menu.addItem(NSMenuItem.separator())
newMenu.addItem(NSMenuItem.separator())
let aboutItem = NSMenuItem(title: "⚠️ About Limitations", action: #selector(showLimitations), keyEquivalent: "")
aboutItem.target = self
newMenu.addItem(aboutItem)
newMenu.addItem(NSMenuItem.separator())
let quitItem = NSMenuItem(title: "Quit", action: #selector(quitApp), keyEquivalent: "q")
quitItem.target = self
menu.addItem(quitItem)
newMenu.addItem(quitItem)
self.statusItem?.menu = menu
self.menu = newMenu
self.statusItem?.menu = newMenu
}
@objc private func toggleMapping() {
@@ -91,10 +97,12 @@ class StatusBarController {
if eventTapManager.isRunning() {
eventTapManager.stop()
Logger.info("Mapping disabled by user")
saveEnabledState(false)
} else {
Logger.info("Attempting to enable mapping...")
if eventTapManager.start() {
Logger.info("Mapping enabled by user")
saveEnabledState(true)
} else {
Logger.error("Failed to enable mapping - check accessibility permissions")
showAlert(title: "Error", message: "Failed to enable key mapping.\n\nPlease ensure:\n1. Accessibility permissions are granted\n2. The app is allowed in System Preferences > Privacy & Security > Accessibility\n\nCheck logs for details.")
@@ -103,6 +111,18 @@ class StatusBarController {
updateMenu()
}
private func saveEnabledState(_ enabled: Bool) {
do {
let configPath = Config.getConfigPath()
let updatedConfig = Config(log: config.log, mapping: config.mapping, enabled: enabled)
try Config.save(updatedConfig, to: configPath)
config = updatedConfig
Logger.info("Saved enabled state: \(enabled)")
} catch {
Logger.error("Failed to save enabled state: \(error)")
}
}
@objc private func reloadConfig() {
Logger.info("Reloading configuration...")
do {
@@ -118,10 +138,11 @@ class StatusBarController {
}
}
@objc private func openLogsFolder() {
let logsDir = Logger.getLogsDirectory()
Logger.info("Opening logs folder: \(logsDir)")
NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: logsDir)
@objc private func openConfigFolder() {
let homeDir = FileManager.default.homeDirectoryForCurrentUser
let configDir = homeDir.appendingPathComponent(".config/uskey").path
Logger.info("Opening config folder: \(configDir)")
NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: configDir)
}
@objc private func viewCurrentLog() {
@@ -136,6 +157,26 @@ class StatusBarController {
NSApplication.shared.terminate(nil)
}
@objc private func showLimitations() {
let alert = NSAlert()
alert.messageText = "Known Limitations"
alert.informativeText = """
Due to macOS security restrictions, key remapping may not work in the following scenarios:
• Password input fields (Secure Input Mode)
• Lock screen / Login screen
• System dialogs and some secure contexts
• Some applications with enhanced security
This is a macOS system limitation that affects all third-party keyboard remapping tools.
For most regular input scenarios, remapping works as expected.
"""
alert.alertStyle = .informational
alert.addButton(withTitle: "OK")
alert.runModal()
}
private func showAlert(title: String, message: String) {
let alert = NSAlert()
alert.messageText = title