aboutsummaryrefslogtreecommitdiff
path: root/Sources/phbar/Models/Notifier.swift
blob: 1124cd4d29f4270c53274bd50c0c8dc97b4e6d6d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import Foundation

// Darwin notifications via CoreFoundation — the macOS-native, signal-like way
// to trigger a refresh from the shell with no client binary:
//
//     notifyutil -p <prefix><name>
//
// e.g.   notifyutil -p house.panini.phbar.touch.workspaces
//
// (notify.h isn't part of Swift's Darwin module, so we go through
// CFNotificationCenter, which talks to the same subsystem.)
//
// The callback fires on an arbitrary thread; handlers must hop to the main
// actor themselves (see AppDelegate.handleRefresh).

/// Retains the handler for the lifetime of the registration.
private final class NotifyBox {
	let handler: @Sendable () -> Void
	init(_ handler: @escaping @Sendable () -> Void) { self.handler = handler }
}

enum Notifier {
	static let prefix = "house.panini.phbar.touch."

	/// Register `handler` to fire whenever the notification is posted.
	@discardableResult
	static func register(_ name: String, handler: @escaping @Sendable () -> Void)
		-> UnsafeMutableRawPointer
	{
		let fullName = prefix + name
		let ctx = Unmanaged.passRetained(NotifyBox(handler)).toOpaque()

		CFNotificationCenterAddObserver(
			CFNotificationCenterGetDarwinNotifyCenter(),
			ctx,
			{ _, observer, _, _, _ in
				guard let observer else { return }
				let box = Unmanaged<NotifyBox>.fromOpaque(observer).takeUnretainedValue()
				box.handler()
			},
			fullName as CFString,
			nil,
			.deliverImmediately
		)

		return ctx
	}

	/// Remove and release a registration. (Optional: process exit clears all.)
	static func cancel(_ token: UnsafeMutableRawPointer) {
		let center = CFNotificationCenterGetDarwinNotifyCenter()
		CFNotificationCenterRemoveEveryObserver(center, token)
		Unmanaged<NotifyBox>.fromOpaque(token).release()
	}
}