Initial commit: uskey - macOS keyboard remapper
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)
This commit is contained in:
85
Sources/Logger.swift
Normal file
85
Sources/Logger.swift
Normal file
@@ -0,0 +1,85 @@
|
||||
@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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user