Features: - Menu bar GUI with enable/disable toggle - JSON-based configuration system - File-based logging with debug support - CGEventTap-based key remapping - Custom app icon support - DMG installer packaging Core Components: - AppDelegate: Application lifecycle and initialization - EventTapManager: Event tap creation and management with proper pointer lifetime - KeyMapper: Key mapping logic and configuration loading - StatusBarController: Menu bar UI and user interactions - Logger: File and console logging with configurable levels - Config: JSON configuration parser with default creation Build System: - build-app.sh: Creates macOS .app bundle with icon - build-dmg.sh: Generates distributable DMG installer - create-icon.sh: Converts PNG to .icns format Documentation: - README.md: User guide and troubleshooting - BUILD.md: Build instructions and packaging - DEBUG.md: Debugging guide with log access 🤖 Generated with [Qoder](https://qoder.com)
86 lines
2.5 KiB
Swift
86 lines
2.5 KiB
Swift
@preconcurrency import Foundation
|
|
|
|
struct Logger {
|
|
nonisolated(unsafe) static var logLevel: LogLevel = .info
|
|
nonisolated(unsafe) private static var logFileHandle: FileHandle?
|
|
nonisolated(unsafe) private static var logFilePath: String?
|
|
|
|
static func setup() {
|
|
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-\(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!)")
|
|
}
|
|
|
|
static func getLogFilePath() -> String? {
|
|
return logFilePath
|
|
}
|
|
|
|
static func getLogsDirectory() -> String {
|
|
let homeDir = FileManager.default.homeDirectoryForCurrentUser
|
|
return homeDir.appendingPathComponent(".config/uskey/logs").path
|
|
}
|
|
|
|
static func debug(_ message: String) {
|
|
log(.debug, message)
|
|
}
|
|
|
|
static func info(_ message: String) {
|
|
log(.info, message)
|
|
}
|
|
|
|
static func warning(_ message: String) {
|
|
log(.warning, message)
|
|
}
|
|
|
|
static func error(_ message: String) {
|
|
log(.error, message)
|
|
}
|
|
|
|
private static func log(_ level: LogLevel, _ message: String) {
|
|
guard logLevel.shouldLog(level) else { return }
|
|
|
|
let timestamp = Date()
|
|
let formatter = DateFormatter()
|
|
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
|
let timeString = formatter.string(from: timestamp)
|
|
|
|
let levelString: String
|
|
switch level {
|
|
case .debug:
|
|
levelString = "DEBUG"
|
|
case .info:
|
|
levelString = "INFO"
|
|
case .warning:
|
|
levelString = "WARN"
|
|
case .error:
|
|
levelString = "ERROR"
|
|
}
|
|
|
|
let logMessage = "[\(timeString)] [\(levelString)] \(message)\n"
|
|
|
|
print(logMessage, terminator: "")
|
|
|
|
if let data = logMessage.data(using: .utf8) {
|
|
logFileHandle?.write(data)
|
|
}
|
|
}
|
|
|
|
static func cleanup() {
|
|
logFileHandle?.closeFile()
|
|
}
|
|
}
|