fix(macos): retain MainWindowController so the client stays alive
The main window controller was created as a local variable in ConnectWindowController.authSucceeded and never retained — ARC deallocated it immediately, which destroyed the VoiceCatClient (connection silently dropped), nil'd every button's weak target (clicks did nothing), and killed event delivery (channel list, messages, voice never worked). Symptom: TOFU (in the retained connect controller) worked, but everything in the main window was a zombie shell. Fix: store the MainWindowController in a new field on ConnectWindowController (which AppDelegate retains for the app's lifetime). Also add NSLog diagnostics in deinit/bootstrap/handleEvent so lifecycle and event delivery are observable from the terminal or Console.app.
This commit is contained in:
@@ -18,6 +18,7 @@ final class ConnectWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
private var servers: [SavedServer] = ServerListStore.load()
|
private var servers: [SavedServer] = ServerListStore.load()
|
||||||
private var client: VoiceCatClient?
|
private var client: VoiceCatClient?
|
||||||
private var identityDialogShown = false
|
private var identityDialogShown = false
|
||||||
|
private var mainWindowController: MainWindowController?
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
@@ -298,6 +299,7 @@ final class ConnectWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
private func authSucceeded(client: VoiceCatClient, selfUserId: UInt32, nickname: String) {
|
private func authSucceeded(client: VoiceCatClient, selfUserId: UInt32, nickname: String) {
|
||||||
client.onEvent = nil
|
client.onEvent = nil
|
||||||
let mainWC = MainWindowController(client: client, selfUserId: selfUserId, nickname: nickname)
|
let mainWC = MainWindowController(client: client, selfUserId: selfUserId, nickname: nickname)
|
||||||
|
self.mainWindowController = mainWC
|
||||||
mainWC.showWindow(nil)
|
mainWC.showWindow(nil)
|
||||||
self.client = nil
|
self.client = nil
|
||||||
close()
|
close()
|
||||||
|
|||||||
@@ -106,6 +106,10 @@ final class MainWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
|
|
||||||
required init?(coder: NSCoder) { fatalError() }
|
required init?(coder: NSCoder) { fatalError() }
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
NSLog("[VoiceCatMac] MainWindowController deinit — client and event handlers are gone")
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - UI construction
|
// MARK: - UI construction
|
||||||
|
|
||||||
private func buildUI() {
|
private func buildUI() {
|
||||||
@@ -447,6 +451,10 @@ final class MainWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
}
|
}
|
||||||
ownPermissions = client.getPermissions()
|
ownPermissions = client.getPermissions()
|
||||||
|
|
||||||
|
NSLog("[VoiceCatMac] bootstrap: channels=%d users=%d perms{admin=%d kick=%d} currentChannelId=%u",
|
||||||
|
channels.count, allUsers.count,
|
||||||
|
ownPermissions.isAdmin, ownPermissions.canKick, currentChannelId)
|
||||||
|
|
||||||
refreshChannelTree()
|
refreshChannelTree()
|
||||||
refreshUserList()
|
refreshUserList()
|
||||||
rebuildScopePicker()
|
rebuildScopePicker()
|
||||||
@@ -459,6 +467,9 @@ final class MainWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
// MARK: - Event handling
|
// MARK: - Event handling
|
||||||
|
|
||||||
private func handleEvent(_ event: VoiceCatEvent) {
|
private func handleEvent(_ event: VoiceCatEvent) {
|
||||||
|
NSLog("[VoiceCatMac] event type=%d result=%d userId=%u channelId=%u streamId=%u text=%@",
|
||||||
|
event.type.rawValue, event.result.rawValue, event.userId, event.channelId,
|
||||||
|
event.streamId, event.text ?? "(nil)")
|
||||||
switch event.type {
|
switch event.type {
|
||||||
case .channelList:
|
case .channelList:
|
||||||
channels = client.listChannels()
|
channels = client.listChannels()
|
||||||
|
|||||||
Reference in New Issue
Block a user