Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6285cabdd1 |
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -53,12 +53,16 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
|
|
||||||
statusBarController?.setupStatusBar()
|
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() {
|
if eventTapManager!.start() {
|
||||||
Logger.info("Event monitoring started successfully")
|
Logger.info("Event monitoring started successfully")
|
||||||
} else {
|
} else {
|
||||||
Logger.error("Failed to start event monitoring - check if app has accessibility permissions")
|
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")
|
Logger.info("Application ready")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
|
||||||
|
|
||||||
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 = FileHandle(forWritingAtPath: logFilePath!)
|
||||||
logFileHandle?.seekToEndOfFile()
|
|
||||||
|
|
||||||
info("Logger initialized, log file: \(logFilePath!)")
|
info("Logger initialized, log file: \(logFilePath!)")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user