Skip to content

Commit 5c622e2

Browse files
Fix quitting from Dock or AppleScript (#271)
Co-authored-by: Sertac Ozercan <sozercan@gmail.com> Co-authored-by: Sertaç Özercan <852750+sozercan@users.noreply.github.com>
1 parent b886bd8 commit 5c622e2

3 files changed

Lines changed: 43 additions & 1 deletion

File tree

Sources/Kaset/AppDelegate.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
1515
/// Using strong reference to prevent deallocation when window is hidden.
1616
private var mainWindow: NSWindow?
1717

18+
/// Tracks when the app is quitting so we can allow window closures.
19+
private var isTerminating = false
20+
1821
func applicationDidFinishLaunching(_: Notification) {
1922
DiagnosticsLogger.app.info("AppDelegate: applicationDidFinishLaunching")
2023
// Set up notification center delegate to show notifications in foreground
@@ -46,6 +49,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
4649
DiagnosticsLogger.player.info("Application will terminate - saved queue for persistence")
4750
}
4851

52+
func applicationShouldTerminate(_: NSApplication) -> NSApplication.TerminateReply {
53+
self.isTerminating = true
54+
return .terminateNow
55+
}
56+
4957
/// Registers for system sleep and wake notifications to handle playback appropriately.
5058
private func registerForSleepWakeNotifications() {
5159
let notificationCenter = NSWorkspace.shared.notificationCenter
@@ -250,7 +258,7 @@ extension AppDelegate: NSWindowDelegate {
250258
/// In UI test mode, close normally to avoid process conflicts.
251259
func windowShouldClose(_ sender: NSWindow) -> Bool {
252260
// In UI test mode, allow normal close behavior
253-
if UITestConfig.isUITestMode {
261+
if UITestConfig.isUITestMode || self.isTerminating {
254262
return true
255263
}
256264

Sources/Kaset/Resources/Kaset.sdef

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
<cocoa class="NSQuitCommand"/>
1010
</command>
1111

12+
<class name="application" code="capp" description="Kaset's top-level scripting object.">
13+
<cocoa class="NSApplication"/>
14+
<responds-to command="quit">
15+
<cocoa method="terminate:"/>
16+
</responds-to>
17+
</class>
18+
1219
</suite>
1320

1421
<!-- Kaset Suite -->
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Foundation
2+
import Testing
3+
4+
@Suite(.serialized, .tags(.service))
5+
struct ScriptingDefinitionTests {
6+
@Test("Standard quit command is bound to NSApplication terminate")
7+
func standardQuitCommandIsBoundToApplicationTerminate() throws {
8+
let testFileURL = URL(fileURLWithPath: #filePath)
9+
let repositoryRoot = testFileURL
10+
.deletingLastPathComponent()
11+
.deletingLastPathComponent()
12+
.deletingLastPathComponent()
13+
let sdefURL = repositoryRoot.appendingPathComponent("Sources/Kaset/Resources/Kaset.sdef")
14+
let data = try Data(contentsOf: sdefURL)
15+
let document = try XMLDocument(data: data)
16+
17+
let quitCommandNodes = try document.nodes(
18+
forXPath: "//suite[@name='Standard Suite']/command[@name='quit' and @code='aevtquit']/cocoa[@class='NSQuitCommand']"
19+
)
20+
#expect(!quitCommandNodes.isEmpty)
21+
22+
let terminateBindingNodes = try document.nodes(
23+
forXPath: "//suite[@name='Standard Suite']/class[@name='application' and @code='capp']/responds-to[@command='quit']/cocoa[@method='terminate:']"
24+
)
25+
#expect(!terminateBindingNodes.isEmpty)
26+
}
27+
}

0 commit comments

Comments
 (0)