Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6285cabdd1 | ||
|
|
99bbbbc73a | ||
|
|
333bbc56f9 |
148
KEY_MAPPING.md
Normal file
148
KEY_MAPPING.md
Normal 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
|
||||
10
README.md
10
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -53,11 +53,15 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
statusBarController?.setupStatusBar()
|
||||
|
||||
Logger.info("Attempting to start event monitoring...")
|
||||
if eventTapManager!.start() {
|
||||
Logger.info("Event monitoring started successfully")
|
||||
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.error("Failed to start event monitoring - check if app has accessibility permissions")
|
||||
Logger.info("Event monitoring not auto-enabled (enabled: false in config)")
|
||||
}
|
||||
|
||||
Logger.info("Application ready")
|
||||
|
||||
@@ -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()
|
||||
@@ -90,4 +100,4 @@ struct Config: Codable {
|
||||
|
||||
return configDir.appendingPathComponent("config.json").path
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
FileManager.default.createFile(atPath: logFilePath!, contents: nil)
|
||||
|
||||
logFileHandle = FileHandle(forWritingAtPath: logFilePath!)
|
||||
logFileHandle?.seekToEndOfFile()
|
||||
|
||||
info("Logger initialized, log file: \(logFilePath!)")
|
||||
}
|
||||
@@ -82,4 +75,4 @@ struct Logger {
|
||||
static func cleanup() {
|
||||
logFileHandle?.closeFile()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user