diff --git a/Core/Package.swift b/Core/Package.swift index e9620e51..081430b7 100644 --- a/Core/Package.swift +++ b/Core/Package.swift @@ -70,13 +70,11 @@ let package = Package( "SuggestionService", "GitHubCopilotService", "XPCShared", - "DisplayLink", "SuggestionWidget", "ChatService", "PromptToCodeService", "ServiceUpdateMigration", "ChatGPTChatTab", - .product(name: "CGEventObserver", package: "Tool"), .product(name: "Workspace", package: "Tool"), .product(name: "UserDefaultsObserver", package: "Tool"), .product(name: "AppMonitoring", package: "Tool"), @@ -248,7 +246,6 @@ let package = Package( .target(name: "FileChangeChecker"), .target(name: "LaunchAgentManager"), - .target(name: "DisplayLink"), .target( name: "UpdateChecker", dependencies: [ diff --git a/Core/Sources/DisplayLink/DisplayLink.swift b/Core/Sources/DisplayLink/DisplayLink.swift deleted file mode 100644 index 8afb5678..00000000 --- a/Core/Sources/DisplayLink/DisplayLink.swift +++ /dev/null @@ -1,64 +0,0 @@ -import Foundation -import QuartzCore - -public actor DisplayLink { - private var displayLink: CVDisplayLink! - private static var _shared = DisplayLink() - static var shared: DisplayLink? { - if let _shared { return _shared } - _shared = DisplayLink() - return _shared - } - - private var continuations: [UUID: AsyncStream.Continuation] = [:] - - public static func createStream() -> AsyncStream { - .init { continuation in - Task { - let id = UUID() - await DisplayLink.shared?.addContinuation(continuation, id: id) - continuation.onTermination = { _ in - Task { - await DisplayLink.shared?.removeContinuation(id: id) - } - } - } - } - } - - private init?() { - _ = CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(), &displayLink) - guard displayLink != nil else { return nil } - CVDisplayLinkSetOutputHandler(displayLink) { [weak self] _, _, _, _, _ in - guard let self else { return kCVReturnSuccess } - Task { await self.notifyContinuations() } - return kCVReturnSuccess - } - } - - deinit { - for continuation in continuations { - continuation.value.finish() - } - } - - func addContinuation(_ continuation: AsyncStream.Continuation, id: UUID) { - continuations[id] = continuation - if !continuations.isEmpty { - CVDisplayLinkStart(displayLink) - } - } - - func removeContinuation(id: UUID) { - continuations[id] = nil - if continuations.isEmpty { - CVDisplayLinkStop(displayLink) - } - } - - private func notifyContinuations() { - for continuation in continuations { - continuation.value.yield() - } - } -} diff --git a/Core/Sources/Service/RealtimeSuggestionController.swift b/Core/Sources/Service/RealtimeSuggestionController.swift index 63ed9a38..0d631e07 100644 --- a/Core/Sources/Service/RealtimeSuggestionController.swift +++ b/Core/Sources/Service/RealtimeSuggestionController.swift @@ -3,7 +3,6 @@ import AppKit import AsyncAlgorithms import AXExtension import AXNotificationStream -import CGEventObserver import Environment import Foundation import Logger @@ -13,7 +12,6 @@ import Workspace import XcodeInspector public actor RealtimeSuggestionController { - let eventObserver: CGEventObserverType = CGEventObserver(eventsOfInterest: [.keyDown]) private var task: Task? private var inflightPrefetchTask: Task? private var windowChangeObservationTask: Task? @@ -29,7 +27,6 @@ public actor RealtimeSuggestionController { Task { [weak self] in if let app = ActiveApplicationMonitor.shared.activeXcode { await self?.handleXcodeChanged(app) - await self?.startHIDObservation() } var previousApp = ActiveApplicationMonitor.shared.activeXcode for await app in ActiveApplicationMonitor.shared.createStream() { @@ -40,32 +37,8 @@ public actor RealtimeSuggestionController { if let app = ActiveApplicationMonitor.shared.activeXcode, app != previousApp { await self.handleXcodeChanged(app) } - - if ActiveApplicationMonitor.shared.activeXcode != nil { - await startHIDObservation() - } else { - await stopHIDObservation() - } - } - } - } - - private func startHIDObservation() { - if task == nil { - task = Task { [weak self, eventObserver] in - for await event in eventObserver.createStream() { - guard let self else { return } - await self.handleHIDEvent(event: event) - } } } - eventObserver.activateIfPossible() - } - - private func stopHIDObservation() { - task?.cancel() - task = nil - eventObserver.deactivate() } private func handleXcodeChanged(_ app: NSRunningApplication) { @@ -169,21 +142,6 @@ public actor RealtimeSuggestionController { } } - func handleHIDEvent(event: CGEvent) async { - guard await Environment.isXcodeActive() else { return } - - let keycode = Int(event.getIntegerValueField(.keyboardEventKeycode)) - let escape = 0x35 - - // Escape should cancel in-flight tasks. - // Except that when the completion panel is presented, it should trigger prefetch instead. - if keycode == escape { - if event.type == .keyDown { - await cancelInFlightTasks() - } - } - } - func triggerPrefetchDebounced(force: Bool = false) { inflightPrefetchTask = Task { @WorkspaceActor in try? await Task.sleep(nanoseconds: UInt64(( diff --git a/Tool/Package.swift b/Tool/Package.swift index 34740cc3..7868368c 100644 --- a/Tool/Package.swift +++ b/Tool/Package.swift @@ -22,7 +22,6 @@ let package = Package( .library(name: "Keychain", targets: ["Keychain"]), .library(name: "SharedUIComponents", targets: ["SharedUIComponents"]), .library(name: "UserDefaultsObserver", targets: ["UserDefaultsObserver"]), - .library(name: "CGEventObserver", targets: ["CGEventObserver"]), .library(name: "Workspace", targets: ["Workspace"]), .library( name: "AppMonitoring", @@ -192,13 +191,6 @@ let package = Package( ] ), - .target( - name: "CGEventObserver", - dependencies: [ - "Logger", - ] - ), - .target( name: "FocusedCodeFinder", dependencies: [ diff --git a/Tool/Sources/CGEventObserver/CGEventObserver.swift b/Tool/Sources/CGEventObserver/CGEventObserver.swift deleted file mode 100644 index cd838982..00000000 --- a/Tool/Sources/CGEventObserver/CGEventObserver.swift +++ /dev/null @@ -1,113 +0,0 @@ -import Cocoa -import Foundation -import Logger - -public protocol CGEventObserverType { - @discardableResult - func activateIfPossible() -> Bool - func deactivate() - func createStream() -> AsyncStream - var isEnabled: Bool { get } -} - -public final class CGEventObserver: CGEventObserverType { - public var isEnabled: Bool { port != nil } - - private var continuations: [UUID: AsyncStream.Continuation] = [:] - private var port: CFMachPort? - private let eventsOfInterest: Set - private let tapLocation: CGEventTapLocation = .cghidEventTap - private let tapPlacement: CGEventTapPlacement = .tailAppendEventTap - private let tapOptions: CGEventTapOptions = .defaultTap - - deinit { - for continuation in continuations { - continuation.value.finish() - } - CFMachPortInvalidate(port) - } - - public init(eventsOfInterest: Set) { - self.eventsOfInterest = eventsOfInterest - } - - public func createStream() -> AsyncStream { - .init { continuation in - let id = UUID() - addContinuation(continuation, id: id) - continuation.onTermination = { [weak self] _ in - self?.removeContinuation(id: id) - } - } - } - - private func addContinuation(_ continuation: AsyncStream.Continuation, id: UUID) { - continuations[id] = continuation - } - - private func removeContinuation(id: UUID) { - continuations[id] = nil - } - - public func deactivate() { - guard let port else { return } - Logger.service.info("CGEventObserver deactivated.") - CFMachPortInvalidate(port) - self.port = nil - } - - @discardableResult - public func activateIfPossible() -> Bool { - guard AXIsProcessTrusted() else { return false } - guard port == nil else { return true } - - let eoi = UInt64(eventsOfInterest.reduce(into: 0) { $0 |= 1 << $1.rawValue }) - - func callback( - tapProxy _: CGEventTapProxy, - eventType: CGEventType, - event: CGEvent, - continuationsPointer: UnsafeMutableRawPointer? - ) -> Unmanaged? { - guard AXIsProcessTrusted() else { - return .passRetained(event) - } - - if eventType == .tapDisabledByTimeout || eventType == .tapDisabledByUserInput { - return .passRetained(event) - } - - if let continuations = continuationsPointer? - .assumingMemoryBound(to: [UUID: AsyncStream.Continuation].self) - { - for continuation in continuations.pointee { - continuation.value.yield(event) - } - } - - return .passRetained(event) - } - - let tapLocation = tapLocation - let tapPlacement = tapPlacement - let tapOptions = tapOptions - - guard let port = withUnsafeMutablePointer(to: &continuations, { pointer in - CGEvent.tapCreate( - tap: tapLocation, - place: tapPlacement, - options: tapOptions, - eventsOfInterest: eoi, - callback: callback, - userInfo: pointer - ) - }) else { - return false - } - self.port = port - let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, port, 0) - CFRunLoopAddSource(RunLoop.main.getCFRunLoop(), runLoopSource, .commonModes) - Logger.service.info("CGEventObserver activated.") - return true - } -} diff --git a/Tool/Sources/Logger/Logger.swift b/Tool/Sources/Logger/Logger.swift index 538cd2c4..e78bf3a0 100644 --- a/Tool/Sources/Logger/Logger.swift +++ b/Tool/Sources/Logger/Logger.swift @@ -18,7 +18,6 @@ public final class Logger { public static let gitHubCopilot = Logger(category: "GitHubCopilot") public static let codeium = Logger(category: "Codeium") public static let langchain = Logger(category: "LangChain") - public static let python = Logger(category: "Python") #if DEBUG public static let temp = Logger(category: "Temp") #endif diff --git a/Tool/Sources/PythonHelper/InitializePython.swift b/Tool/Sources/PythonHelper/InitializePython.swift deleted file mode 100644 index 5e18523b..00000000 --- a/Tool/Sources/PythonHelper/InitializePython.swift +++ /dev/null @@ -1,37 +0,0 @@ -import Foundation -import Logger -import Python -import PythonKit -import PythonResources - -@PythonActor -var isPythonInitialized = false - -/// Initialize Python. -@PythonActor -public func initializePython() { - guard !isPythonInitialized else { return } - guard let sitePackagePath, let stdLibPath, let libDynloadPath else { - assertionFailure("Python is not installed! Please run `make setup` to install Python.") - Logger.python.info("Python is not installed!") - return - } - setenv("PYTHONHOME", stdLibPath, 1) - setenv("PYTHONPATH", "\(stdLibPath):\(libDynloadPath):\(sitePackagePath)", 1) - setenv("PYTHONIOENCODING", "utf-8", 1) - // Initialize python - Py_Initialize() - isPythonInitialized = true - // Immediately release the thread, so that we can ensure the GIL state later. - _ = PyEval_SaveThread() - - Task { - // All future task should run inside runPython. - try runPython { - let sys = Python.import("sys") - Logger.service - .info("Python Version: \(sys.version_info.major).\(sys.version_info.minor)") - } - } -} - diff --git a/Tool/Sources/PythonHelper/PythonThread.swift b/Tool/Sources/PythonHelper/PythonThread.swift deleted file mode 100644 index 7ac8fe62..00000000 --- a/Tool/Sources/PythonHelper/PythonThread.swift +++ /dev/null @@ -1,91 +0,0 @@ -import Foundation -import PythonKit - -/// A special thread that has a larger stack size. -final class PythonThread: Thread { - static let shared = { - let thread = PythonThread( - target: PythonThread.self, - selector: #selector(PythonThread.setup), - object: nil - ) - thread.name = "Python Thread" - thread.stackSize = 1_048_576 // so that langchain can be correctly imported. - return thread - }() - - @objc static func setup() { - CFRunLoopRun() - } - - @objc static func runPythonJob(_ job: PythonJob) { - job.run() - } - - func runPython(_ closure: @escaping () -> Void) { - if !isExecuting { - start() - } - - if Thread.current.isEqual(self) { - closure() - } else { - PythonThread.perform( - #selector(PythonThread.runPythonJob), - on: self, - with: PythonJob(closure: closure), - waitUntilDone: false - ) - } - } - - func runPythonAndWait(_ closure: @escaping () throws -> T) throws -> T { - if !isExecuting { - start() - } - - if Thread.current.isEqual(self) { - return try closure() - } else { - let job = PythonJob(closure: closure) - PythonThread.perform( - #selector(PythonThread.runPythonJob), - on: self, - with: job, - waitUntilDone: true - ) - guard let result = job.result else { - throw FailedToGetPythonJobResultError() - } - switch result { - case let .success(value): - if let value = value as? T { - return value - } else { - throw FailedToGetPythonJobResultError() - } - case let .failure(error): - throw error - } - } - } -} - -struct FailedToGetPythonJobResultError: Error, LocalizedError { - var errorDescription: String? { - "Failed to get PythonJob result." - } -} - -final class PythonJob: NSObject { - let closure: () throws -> Any - var result: Result? - init(closure: @escaping () throws -> Any) { - self.closure = closure - } - - func run() { - result = Result(catching: closure) - } -} - diff --git a/Tool/Sources/PythonHelper/RunPython.swift b/Tool/Sources/PythonHelper/RunPython.swift deleted file mode 100644 index 5d8eb158..00000000 --- a/Tool/Sources/PythonHelper/RunPython.swift +++ /dev/null @@ -1,65 +0,0 @@ -import Foundation -import Python -import PythonKit - -@globalActor -public actor PythonActor { - public static let shared = PythonActor() -} - -/// You MUST run every Python code with this function. -/// -/// It's important to note that `PythonKit` is not thread safe. You should be careful to not -/// Introduce any racing to avoid crashes. -/// -/// The python code will run between PyGILState_Ensure and PyGILState_Release. But I am -/// not sure whether it's the correct thing to do. -@PythonActor -public func runPython( - _ closure: @escaping () throws -> T -) throws -> T { - do { - let gilState = PyGILState_Ensure() - let result = try closure() - PyGILState_Release(gilState) - - return result - } catch let error as PythonError { - throw ReadablePythonError(error) - } catch { - throw error - } -} - -public extension PythonInterface { - /// Import a package from a thread with larger stack size. - /// - /// Sometimes the Python interpreter will crash when importing a package if the stack size - /// of the thread is too small. For example, `langchain`, `numpy`. - func attemptImportOnPythonThread(_ name: String) throws -> PythonObject { - try PythonThread.shared.runPythonAndWait { - let module = try Python.attemptImport(name) - return module - } - } -} - -public struct ReadablePythonError: Error, LocalizedError { - public var error: PythonError - - public init(_ error: PythonError) { - self.error = error - } - - public var errorDescription: String? { - switch error { - case let .exception(object, _): - return "\(object)" - case let .invalidCall(object): - return "Invalid call: \(object)" - case let .invalidModule(module): - return "Invalid module: \(module)" - } - } -} -