Skip to content

Commit 11fa5cb

Browse files
committed
fix: restart converter server via xpc
1 parent 4930f72 commit 11fa5cb

5 files changed

Lines changed: 33 additions & 60 deletions

File tree

Core/Sources/ConverterServer/main.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Core
2+
import Darwin
23
import Foundation
34
import KanaKanjiConverterModuleWithDefaultDictionary
45

@@ -131,6 +132,9 @@ private final class ConverterServer: NSObject, ConverterServerXPCProtocol, @unch
131132
// swiftlint:disable:next cyclomatic_complexity
132133
private func handle(_ command: ConverterServerCommand) async throws -> ConverterServerResponse {
133134
switch command {
135+
case .shutdown:
136+
Self.scheduleShutdown()
137+
return ConverterServerResponse(snapshot: .empty)
134138
case .activate(let sessionID):
135139
let session = try getSession(sessionID)
136140
session.manager.activate()
@@ -847,6 +851,12 @@ private final class ConverterServer: NSObject, ConverterServerXPCProtocol, @unch
847851
return boolValue
848852
}
849853

854+
private static func scheduleShutdown() {
855+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
856+
exit(EXIT_SUCCESS)
857+
}
858+
}
859+
850860
@MainActor
851861
private func getSession(_ sessionID: String) throws -> ConverterSession {
852862
guard let session = sessions[sessionID] else {

Core/Sources/Core/XPC/ConverterServerXPCProtocol.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public enum ConverterServerCodec {
2323
}
2424

2525
public enum ConverterServerCommand: Codable, Sendable {
26+
case shutdown
2627
case activate(sessionID: String)
2728
case deactivate(sessionID: String)
2829
case listSettings(sessionID: String, capabilities: ConverterSettingClientCapabilities)

Core/Tests/CoreTests/XPCTests/ConverterServerContractTests.swift

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,30 +107,35 @@ import Testing
107107
let submit = ConverterServerCommand.submitSelectedReplaceSuggestion(sessionID: "session-1")
108108

109109
guard case .requestReplaceSuggestion(let requestSessionID, let leftSideContext) =
110-
try ConverterServerCodec.decodeCommand(from: ConverterServerCodec.encode(request)) else {
110+
try ConverterServerCodec.decodeCommand(from: ConverterServerCodec.encode(request)) else {
111111
Issue.record("Expected requestReplaceSuggestion command after round trip")
112112
return
113113
}
114114
#expect(requestSessionID == "session-1")
115115
#expect(leftSideContext == "左文脈")
116116

117117
guard case .selectReplaceSuggestionCandidate(let selectSessionID, let index) =
118-
try ConverterServerCodec.decodeCommand(from: ConverterServerCodec.encode(select)) else {
118+
try ConverterServerCodec.decodeCommand(from: ConverterServerCodec.encode(select)) else {
119119
Issue.record("Expected selectReplaceSuggestionCandidate command after round trip")
120120
return
121121
}
122122
#expect(selectSessionID == "session-1")
123123
#expect(index == 2)
124124

125125
guard case .submitSelectedReplaceSuggestion(let submitSessionID) =
126-
try ConverterServerCodec.decodeCommand(from: ConverterServerCodec.encode(submit)) else {
126+
try ConverterServerCodec.decodeCommand(from: ConverterServerCodec.encode(submit)) else {
127127
Issue.record("Expected submitSelectedReplaceSuggestion command after round trip")
128128
return
129129
}
130130
#expect(submitSessionID == "session-1")
131131
}
132132

133133
@Test func converterServerSettingsCommandsRoundTrip() throws {
134+
guard case .shutdown = try ConverterServerCodec.decodeCommand(from: ConverterServerCodec.encode(.shutdown)) else {
135+
Issue.record("Expected shutdown command after round trip")
136+
return
137+
}
138+
134139
let capabilities = ConverterSettingClientCapabilities(
135140
supportedKinds: [.toggle, .selector, .button],
136141
supportedActions: ["resetLearningData"],
@@ -144,15 +149,15 @@ import Testing
144149
)
145150

146151
guard case .listSettings(let listSessionID, let roundTripCapabilities) =
147-
try ConverterServerCodec.decodeCommand(from: ConverterServerCodec.encode(list)) else {
152+
try ConverterServerCodec.decodeCommand(from: ConverterServerCodec.encode(list)) else {
148153
Issue.record("Expected listSettings command after round trip")
149154
return
150155
}
151156
#expect(listSessionID == "session-1")
152157
#expect(roundTripCapabilities == capabilities)
153158

154159
guard case .updateSetting(let updateSessionID, let key, let value) =
155-
try ConverterServerCodec.decodeCommand(from: ConverterServerCodec.encode(update)) else {
160+
try ConverterServerCodec.decodeCommand(from: ConverterServerCodec.encode(update)) else {
156161
Issue.record("Expected updateSetting command after round trip")
157162
return
158163
}

azooKeyMac/InputController/ConverterServerClient.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ final class ConverterServerClient {
101101
)
102102
}
103103

104+
func restartServer(completion: @escaping (Bool) -> Void) {
105+
sendResolved(.shutdown) { [weak self] response in
106+
self?.invalidateConnection()
107+
completion(response != nil)
108+
}
109+
}
110+
104111
func send(
105112
_ commandBuilder: @escaping (String) -> ConverterServerCommand,
106113
completion: @escaping (ConverterServerResponse?) -> Void

azooKeyMac/Windows/ConfigWindow.swift

Lines changed: 5 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import Cocoa
22
import Core
3-
import Darwin
43
import SwiftUI
54

65
struct ConfigWindow: View {
@@ -25,6 +24,7 @@ struct ConfigWindow: View {
2524
@ConfigState private var keyboardLayout = Config.KeyboardLayout()
2625
@ConfigState private var aiBackend = Config.AIBackendPreference()
2726

27+
@State private var converterServerClient = ConverterServerClient()
2828
@State private var selectedTab: Tab = .basic
2929
@State private var zenzaiProfileHelpPopover = false
3030
@State private var zenzaiInferenceLimitHelpPopover = false
@@ -67,17 +67,6 @@ struct ConfigWindow: View {
6767
case successfulUpdate
6868
}
6969

70-
private enum ConverterProcessRestartError: LocalizedError {
71-
case launchctlFailed(String)
72-
73-
var errorDescription: String? {
74-
switch self {
75-
case .launchctlFailed(let message):
76-
"launchctl kickstart failed: \(message)"
77-
}
78-
}
79-
}
80-
8170
private var azooKeyApplicationSupportDirectoryURL: URL {
8271
AppGroup.applicationSupportDirectoryURL()
8372
}
@@ -195,53 +184,14 @@ struct ConfigWindow: View {
195184
}
196185
self.converterProcessRestartInProgress = true
197186
self.converterProcessRestartMessage = nil
198-
Task {
199-
do {
200-
try await Task.detached(priority: .utility) {
201-
try Self.restartConverterProcessService()
202-
}.value
203-
await MainActor.run {
204-
self.converterProcessRestartMessage = "再起動しました"
205-
self.converterProcessRestartInProgress = false
206-
}
207-
} catch {
208-
await MainActor.run {
209-
self.converterProcessRestartMessage = error.localizedDescription
210-
self.converterProcessRestartInProgress = false
211-
}
187+
self.converterServerClient.restartServer { success in
188+
DispatchQueue.main.async {
189+
self.converterProcessRestartMessage = success ? "再起動しました" : "Converter Processに再起動を依頼できませんでした"
190+
self.converterProcessRestartInProgress = false
212191
}
213192
}
214193
}
215194

216-
nonisolated private static func restartConverterProcessService() throws {
217-
let serviceName = "dev.ensan.inputmethod.azooKeyMac.ConverterServer"
218-
let process = Process()
219-
process.executableURL = URL(fileURLWithPath: "/bin/launchctl")
220-
process.arguments = [
221-
"kickstart",
222-
"-k",
223-
"gui/\(getuid())/\(serviceName)"
224-
]
225-
226-
let outputPipe = Pipe()
227-
let errorPipe = Pipe()
228-
process.standardOutput = outputPipe
229-
process.standardError = errorPipe
230-
231-
try process.run()
232-
process.waitUntilExit()
233-
234-
guard process.terminationStatus == 0 else {
235-
let standardError = String(data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
236-
let standardOutput = String(data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
237-
let message = [standardError, standardOutput]
238-
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
239-
.filter { !$0.isEmpty }
240-
.joined(separator: "\n")
241-
throw ConverterProcessRestartError.launchctlFailed(message.isEmpty ? "exit status \(process.terminationStatus)" : message)
242-
}
243-
}
244-
245195
private func openAzooKeyDataDirectoryInFinder() {
246196
do {
247197
try FileManager.default.createDirectory(

0 commit comments

Comments
 (0)