11import Cocoa
22import Core
3+ import Darwin
34import SwiftUI
45
56struct ConfigWindow : View {
@@ -39,6 +40,8 @@ struct ConfigWindow: View {
3940 @State private var debugTypoCorrectionState : DebugTypoCorrectionState = . notDownloaded
4041 @State private var debugTypoCorrectionDownloadInProgress = false
4142 @State private var debugTypoCorrectionErrorMessage : String ?
43+ @State private var converterProcessRestartInProgress = false
44+ @State private var converterProcessRestartMessage : String ?
4245
4346 private enum Tab : String , CaseIterable , Hashable {
4447 case basic = " 基本 "
@@ -64,6 +67,17 @@ struct ConfigWindow: View {
6467 case successfulUpdate
6568 }
6669
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+
6781 private var azooKeyApplicationSupportDirectoryURL : URL {
6882 AppGroup . applicationSupportDirectoryURL ( )
6983 }
@@ -174,6 +188,60 @@ struct ConfigWindow: View {
174188 }
175189 }
176190
191+ @MainActor
192+ private func restartConverterProcess( ) {
193+ guard !self . converterProcessRestartInProgress else {
194+ return
195+ }
196+ self . converterProcessRestartInProgress = true
197+ 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+ }
212+ }
213+ }
214+ }
215+
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+
177245 private func openAzooKeyDataDirectoryInFinder( ) {
178246 do {
179247 try FileManager . default. createDirectory (
@@ -718,6 +786,24 @@ struct ConfigWindow: View {
718786 }
719787 }
720788 }
789+ LabeledContent ( " Converter Process " ) {
790+ VStack ( alignment: . trailing, spacing: 4 ) {
791+ Button ( " 再起動 " ) {
792+ self . restartConverterProcess ( )
793+ }
794+ . disabled ( self . converterProcessRestartInProgress)
795+ if self . converterProcessRestartInProgress {
796+ ProgressView ( )
797+ . controlSize ( . small)
798+ }
799+ if let converterProcessRestartMessage {
800+ Text ( converterProcessRestartMessage)
801+ . font ( . caption2)
802+ . foregroundStyle ( . secondary)
803+ . fixedSize ( horizontal: false , vertical: true )
804+ }
805+ }
806+ }
721807 } header: {
722808 Label ( " 開発者向け設定 " , systemImage: " hammer " )
723809 }
0 commit comments