import AppKit import Darwin import Foundation public let socketPath = "/tmp/\(aeroSpaceAppId)-\(unixUserName).sock" public let unixUserName = NSUserName() public let mainModeId = "main" @TaskLocal public var refreshSessionEvent: RefreshSessionEvent? = nil @TaskLocal private var recursionDetectorDuringTermination = false public func bugPrompt( _ __message: String = "", isDie: Bool = false, file: StaticString = #fileID, line: Int = #line, column: Int = #column, function: String = #function, ) -> String { let _message = __message.contains("\n") ? "\n" + __message.prefixLines(with: " ") : __message let thread = Thread.current return """ Please report to: https://github.com/nagasrinath/DeeWM/discussions/categories/potential-bugs Please describe what you did to trigger this error Message: \(_message) Version: \(aeroSpaceAppVersion) Git hash: \(gitHash) refreshSessionEvent: \(refreshSessionEvent.prettyDescription) Date: \(Date.now) Thread name: \(thread.name.prettyDescription) Is main thread: \(thread.isMainThread) axTaskLocalAppThreadToken: \(axTaskLocalAppThreadToken.prettyDescription) macOS version: \(ProcessInfo().operatingSystemVersionString) Coordinate: \(file):\(line):\(column) \(function) recursionDetectorDuringTermination: \(recursionDetectorDuringTermination) cli: \(isCli) die: \(isDie) Monitor count: \(NSScreen.screens.count) Displays have separate spaces: \(NSScreen.screensHaveSeparateSpaces) Stacktrace: \(getStringStacktrace()) """ } public func dieT( _ __message: String = "", file: StaticString = #fileID, line: Int = #line, column: Int = #column, function: String = #function, ) -> T { let message = bugPrompt(__message, isDie: true, file: file, line: line, column: column, function: function) if !isUnitTest && isServer { showMessageInGui( filenameIfConsoleApp: recursionDetectorDuringTermination ? "deewm-runtime-error-recursion.txt" : "deewm-runtime-error.txt", title: "DeeWM Runtime Error", message: message, ) } if let terminationHandler, !recursionDetectorDuringTermination { MainActor.runSync { $recursionDetectorDuringTermination.withValue(true) { terminationHandler.beforeTermination() } } } fatalError("\n" + message) } extension MainActor { static func runSync(block: @escaping @MainActor () -> ()) { switch Thread.isMainThread { case true: MainActor.assumeIsolated(block) case false: DispatchQueue.main.asyncAndWait { block() } } } } public enum RefreshSessionEvent: Sendable, CustomStringConvertible { case configAutoReload case globalObserver(String) case globalObserverLeftMouseUp case menuBarButton case hotkeyBinding case startup case socketServer(any CmdArgs) case resetManipulatedWithMouse case ax(String) case focusFollowsMouse public var isStartup: Bool { if case .startup = self { return true } else { return false } } public var isFocusFollowsMouse: Bool { if case .focusFollowsMouse = self { return true } else { return false } } public var description: String { switch self { case .ax(let str): "ax(\(str))" case .configAutoReload: "configAutoReload" case .globalObserver(let str): "globalObserver(\(str))" case .globalObserverLeftMouseUp: "globalObserverLeftMouseUp" case .hotkeyBinding: "hotkeyBinding" case .menuBarButton: "menuBarButton" case .resetManipulatedWithMouse: "resetManipulatedWithMouse" case .socketServer(let args): "socketServer: \(args)" case .startup: "startup" case .focusFollowsMouse: "focusFollowsMouse" } } } // periphery:ignore public func throwT(_ error: E) throws(E) -> T { throw error } public func getStringStacktrace() -> String { Thread.callStackSymbols.joined(separator: "\n") } @inlinable public func die( _ message: String = "", file: StaticString = #fileID, line: Int = #line, column: Int = #column, function: String = #function, ) -> Never { dieT(message, file: file, line: line, column: column, function: function) } public func check( _ condition: Bool, _ message: @autoclosure () -> String = "", file: StaticString = #fileID, line: Int = #line, column: Int = #column, function: String = #function, ) { if !condition { die(message(), file: file, line: line, column: column, function: function) } } public var isUnitTest: Bool { NSClassFromString("XCTestCase") != nil } extension CaseIterable where Self: RawRepresentable, RawValue == String { public static var cliArgsCases: [String] { allCases.map(\.rawValue) } public static var unionLiteral: String { cliArgsCases.joinedCliArgs } } extension [String] { public var joinedCliArgs: String { "(" + self.joined(separator: "|") + ")" } } extension Int { public func toDouble() -> Double { Double(self) } } public func + (lhs: [K: V], rhs: [K: V]) -> [K: V] { lhs.merging(rhs) { _, r in r } } extension String { public func removePrefix(_ prefix: String) -> String { hasPrefix(prefix) ? String(dropFirst(prefix.count)) : self } public func prependLines(_ prefix: String) -> String { split(separator: "\n").map { prefix + $0 }.joined(separator: "\n") } } extension Bool { /// Implication /// | a | b | a.implies(b) | /// |-------|-------|--------------| /// | false | false | true | /// | false | true | true | /// | true | false | false | /// | true | true | true | public func implies(_ mustHold: @autoclosure () -> Bool) -> Bool { !self || mustHold() } } extension URL { public func open(with url: URL) { NSWorkspace.shared.open([self], withApplicationAt: url, configuration: NSWorkspace.OpenConfiguration()) } } public func eprint(_ msg: String) { unsafe fputs(msg + "\n", stderr) } public func exit(_ exitCode: Int32, out: String? = nil, err: String? = nil) -> Never { exitT(exitCode, out: out, err: err) } public func exitT(_ exitCode: Int32, out: String? = nil, err: String? = nil) -> T { if let out { print(out) } if let err { eprint(err) } exit(exitCode) } /// 'id' stands for 'identity'. It's a common name in functional programming public func id(_ t: T) -> T { t } @inlinable public func zipIfCountsAreEqual(_ c1: C1, _ c2: C2) -> Zip2Sequence? where C1: Collection, C2: Collection { switch c1.count == c2.count { case true: zip(c1, c2) case false: nil } }