1 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
6 changed files with 94 additions and 51 deletions

View File

@@ -116,12 +116,14 @@ If you can't enable mapping after installation:
### Log Files Location ### 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: You can view logs by:
- **Menu Bar**: Click uskey icon → "View Current Log" - **Menu Bar**: Click uskey icon → "View Current Log"
- **Finder**: Click uskey icon → "Open Logs Folder" (⌘L) - **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 ### Common Issues

View File

@@ -53,11 +53,15 @@ class AppDelegate: NSObject, NSApplicationDelegate {
statusBarController?.setupStatusBar() statusBarController?.setupStatusBar()
Logger.info("Attempting to start event monitoring...") if config.enabled {
if eventTapManager!.start() { Logger.info("Auto-enabling event monitoring (enabled: true in config)...")
Logger.info("Event monitoring started successfully") if eventTapManager!.start() {
Logger.info("Event monitoring started successfully")
} else {
Logger.error("Failed to start event monitoring - check if app has accessibility permissions")
}
} else { } 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") Logger.info("Application ready")

View File

@@ -54,10 +54,12 @@ struct MappingConfig: Codable {
struct Config: Codable { struct Config: Codable {
let log: LogConfig let log: LogConfig
let mapping: MappingConfig 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.log = log
self.mapping = mapping self.mapping = mapping
self.enabled = enabled
} }
static func load(from path: String) throws -> Config { static func load(from path: String) throws -> Config {
@@ -67,13 +69,21 @@ struct Config: Codable {
return try decoder.decode(Config.self, from: data) 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 { static func createDefault(at path: String) throws {
let defaultConfig = Config( let defaultConfig = Config(
log: LogConfig(level: .info), log: LogConfig(level: .info),
mapping: MappingConfig(mappings: [ mapping: MappingConfig(mappings: [
"backslash2backspace": KeyMapping(from: 42, to: 51), "backslash2backspace": KeyMapping(from: 42, to: 51),
"backspace2backslash": KeyMapping(from: 51, to: 42) "backspace2backslash": KeyMapping(from: 51, to: 42)
]) ]),
enabled: true
) )
let encoder = JSONEncoder() let encoder = JSONEncoder()
@@ -90,4 +100,4 @@ struct Config: Codable {
return configDir.appendingPathComponent("config.json").path return configDir.appendingPathComponent("config.json").path
} }
} }

View File

@@ -38,14 +38,7 @@ class EventTapManager {
if manager.keyMapper.hasMappingFor(keyCode: keyCode) { if manager.keyMapper.hasMappingFor(keyCode: keyCode) {
if let mappedKey = manager.keyMapper.getMappedKey(for: keyCode) { if let mappedKey = manager.keyMapper.getMappedKey(for: keyCode) {
Logger.debug("Remapping: \(keyCode) -> \(mappedKey)") Logger.debug("Remapping: \(keyCode) -> \(mappedKey)")
event.setIntegerValueField(.keyboardEventKeycode, value: mappedKey)
let newEvent = CGEvent(keyboardEventSource: nil, virtualKey: CGKeyCode(mappedKey), keyDown: type == .keyDown)
if let newEvent = newEvent {
newEvent.flags = event.flags
newEvent.post(tap: .cghidEventTap)
}
return nil
} }
} }

View File

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

View File

@@ -6,6 +6,7 @@ class StatusBarController {
private var statusItem: NSStatusItem? private var statusItem: NSStatusItem?
private var eventTapManager: EventTapManager private var eventTapManager: EventTapManager
private var config: Config private var config: Config
private var menu: NSMenu?
init(eventTapManager: EventTapManager, config: Config) { init(eventTapManager: EventTapManager, config: Config) {
self.eventTapManager = eventTapManager self.eventTapManager = eventTapManager
@@ -17,8 +18,6 @@ class StatusBarController {
if let button = statusItem?.button { if let button = statusItem?.button {
button.image = NSImage(systemSymbolName: "keyboard", accessibilityDescription: "uskey") button.image = NSImage(systemSymbolName: "keyboard", accessibilityDescription: "uskey")
button.action = #selector(statusBarButtonClicked)
button.target = self
} }
updateMenu() updateMenu()
@@ -29,10 +28,10 @@ class StatusBarController {
} }
private func updateMenu() { private func updateMenu() {
let menu = NSMenu() let newMenu = NSMenu()
menu.addItem(NSMenuItem(title: "uskey - Keyboard Remapper", action: nil, keyEquivalent: "")) newMenu.addItem(NSMenuItem(title: "uskey - Keyboard Remapper", action: nil, keyEquivalent: ""))
menu.addItem(NSMenuItem.separator()) newMenu.addItem(NSMenuItem.separator())
let isEnabled = eventTapManager.isRunning() let isEnabled = eventTapManager.isRunning()
let toggleItem = NSMenuItem( let toggleItem = NSMenuItem(
@@ -41,49 +40,56 @@ class StatusBarController {
keyEquivalent: "" keyEquivalent: ""
) )
toggleItem.target = self toggleItem.target = self
menu.addItem(toggleItem) newMenu.addItem(toggleItem)
menu.addItem(NSMenuItem.separator()) newMenu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Current Mappings:", action: nil, keyEquivalent: "")) newMenu.addItem(NSMenuItem(title: "Current Mappings:", action: nil, keyEquivalent: ""))
let mappings = config.mapping.getAllMappings() let mappings = config.mapping.getAllMappings()
if mappings.isEmpty { if mappings.isEmpty {
let item = NSMenuItem(title: " No mappings configured", action: nil, keyEquivalent: "") let item = NSMenuItem(title: " No mappings configured", action: nil, keyEquivalent: "")
item.isEnabled = false item.isEnabled = false
menu.addItem(item) newMenu.addItem(item)
} else { } else {
for (from, to) in mappings.sorted(by: { $0.0 < $1.0 }) { for (from, to) in mappings.sorted(by: { $0.0 < $1.0 }) {
let item = NSMenuItem(title: " \(from)\(to)", action: nil, keyEquivalent: "") let item = NSMenuItem(title: " \(from)\(to)", action: nil, keyEquivalent: "")
item.isEnabled = false 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") let reloadItem = NSMenuItem(title: "Reload Configuration", action: #selector(reloadConfig), keyEquivalent: "r")
reloadItem.target = self 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") let openConfigItem = NSMenuItem(title: "Open Config Folder", action: #selector(openConfigFolder), keyEquivalent: "c")
openLogsItem.target = self openConfigItem.target = self
menu.addItem(openLogsItem) newMenu.addItem(openConfigItem)
if let _ = Logger.getLogFilePath() { if let _ = Logger.getLogFilePath() {
let viewLogItem = NSMenuItem(title: "View Current Log", action: #selector(viewCurrentLog), keyEquivalent: "") let viewLogItem = NSMenuItem(title: "View Current Log", action: #selector(viewCurrentLog), keyEquivalent: "")
viewLogItem.target = self 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") let quitItem = NSMenuItem(title: "Quit", action: #selector(quitApp), keyEquivalent: "q")
quitItem.target = self quitItem.target = self
menu.addItem(quitItem) newMenu.addItem(quitItem)
self.statusItem?.menu = menu self.menu = newMenu
self.statusItem?.menu = newMenu
} }
@objc private func toggleMapping() { @objc private func toggleMapping() {
@@ -91,10 +97,12 @@ class StatusBarController {
if eventTapManager.isRunning() { if eventTapManager.isRunning() {
eventTapManager.stop() eventTapManager.stop()
Logger.info("Mapping disabled by user") Logger.info("Mapping disabled by user")
saveEnabledState(false)
} else { } else {
Logger.info("Attempting to enable mapping...") Logger.info("Attempting to enable mapping...")
if eventTapManager.start() { if eventTapManager.start() {
Logger.info("Mapping enabled by user") Logger.info("Mapping enabled by user")
saveEnabledState(true)
} else { } else {
Logger.error("Failed to enable mapping - check accessibility permissions") 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.") 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() 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() { @objc private func reloadConfig() {
Logger.info("Reloading configuration...") Logger.info("Reloading configuration...")
do { do {
@@ -118,10 +138,11 @@ class StatusBarController {
} }
} }
@objc private func openLogsFolder() { @objc private func openConfigFolder() {
let logsDir = Logger.getLogsDirectory() let homeDir = FileManager.default.homeDirectoryForCurrentUser
Logger.info("Opening logs folder: \(logsDir)") let configDir = homeDir.appendingPathComponent(".config/uskey").path
NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: logsDir) Logger.info("Opening config folder: \(configDir)")
NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: configDir)
} }
@objc private func viewCurrentLog() { @objc private func viewCurrentLog() {
@@ -136,6 +157,26 @@ class StatusBarController {
NSApplication.shared.terminate(nil) 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) { private func showAlert(title: String, message: String) {
let alert = NSAlert() let alert = NSAlert()
alert.messageText = title alert.messageText = title