Conversation
cmsj
commented
Mar 24, 2026
- Checkpoint
- Fix some compiler warnings
- Fix channelCount property
- Fix docstrings issues and regenerate docs
Greptile SummaryThis PR introduces
Confidence Score: 4/5
Important Files Changed
Prompt To Fix All With AIThis is a comment left during a code review.
Path: Hammerspoon 2/Modules/hs.audiodevice/HSAudioDevice.swift
Line: 373-375
Comment:
**Debug `print` statement in `deinit`**
This `print(...)` call looks like a leftover debug trace. Other lifecycle logs in the module use `AKTrace`, and shipping production code with `print` to stdout is generally noisy. Consider replacing with `AKTrace` or removing it entirely.
```suggestion
deinit {
AKTrace("deinit of HSAudioDevice id=\(objectID)")
}
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: Hammerspoon 2/Modules/hs.audiodevice/HSAudioDevice.swift
Line: 590-618
Comment:
**`selfRetain` set before confirming any listener was registered**
`selfRetain = self` is assigned unconditionally at the top of `startWatcher()`, before the loop that adds CoreAudio property listeners. If every candidate either lacks the property or fails `AudioObjectAddPropertyListenerBlock`, `watcherRegistrations` remains empty. At that point:
- `watcherIsActive()` returns `false` (correct)
- `stopWatcher()` early-returns immediately because `watcherRegistrations.isEmpty` is true — so it never clears `selfRetain`
The device therefore retains itself permanently, causing a memory leak. The fix is to move `selfRetain = self` to after the loop, gated on at least one successful registration:
```swift
@objc func startWatcher() {
guard unsafe watcherRegistrations.isEmpty else { return }
let candidates: [(AudioObjectPropertySelector, AudioObjectPropertyScope)] = [ ... ]
for (selector, scope) in candidates {
// ... same as before
}
guard !watcherRegistrations.isEmpty else { return }
selfRetain = self // prevent GC while watcher is active
AKTrace(...)
}
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: Hammerspoon 2/Modules/hs.audiodevice/HSAudioDeviceModule.swift
Line: 110-112
Comment:
**Debug `print` statement in `deinit`**
Same pattern as in `HSAudioDevice` — this `print(...)` should use `AKTrace` (consistent with the rest of the module) or be removed before merge.
```suggestion
deinit {
AKTrace("Deinit of \(name)")
}
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "Fix docstrings issues and regenerate doc..." | Re-trigger Greptile |
|
|
||
| // MARK: - Capabilities | ||
|
|
There was a problem hiding this comment.
Debug
print statement in deinit
This print(...) call looks like a leftover debug trace. Other lifecycle logs in the module use AKTrace, and shipping production code with print to stdout is generally noisy. Consider replacing with AKTrace or removing it entirely.
| // MARK: - Capabilities | |
| deinit { | |
| AKTrace("deinit of HSAudioDevice id=\(objectID)") | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: Hammerspoon 2/Modules/hs.audiodevice/HSAudioDevice.swift
Line: 373-375
Comment:
**Debug `print` statement in `deinit`**
This `print(...)` call looks like a leftover debug trace. Other lifecycle logs in the module use `AKTrace`, and shipping production code with `print` to stdout is generally noisy. Consider replacing with `AKTrace` or removing it entirely.
```suggestion
deinit {
AKTrace("deinit of HSAudioDevice id=\(objectID)")
}
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
|
||
| AKTrace(unsafe "HSAudioDevice id=\(objectID): watcher started (\(watcherRegistrations.count) listeners)") | ||
| } | ||
|
|
||
| @objc func stopWatcher() { | ||
| guard unsafe !watcherRegistrations.isEmpty else { return } | ||
| for unsafe var registration in unsafe watcherRegistrations { | ||
| unsafe AudioObjectRemovePropertyListenerBlock(objectID, ®istration.address, .main, registration.block) | ||
| } | ||
| unsafe watcherRegistrations.removeAll() | ||
| selfRetain = nil // allow GC if no other strong refs | ||
| AKTrace("HSAudioDevice id=\(objectID): watcher stopped") | ||
| } | ||
|
|
||
| @objc func watcherIsActive() -> Bool { unsafe !watcherRegistrations.isEmpty } | ||
|
|
||
| /// Called on the main thread from the watcher block. | ||
| private func handleDevicePropertyChange(addresses: [AudioObjectPropertyAddress]) { | ||
| guard let callback = watcherCallback, callback.isObject else { return } | ||
| for address in addresses { | ||
| let event: String | ||
| switch (address.mSelector, address.mScope) { | ||
| case (kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput): event = "vmout" | ||
| case (kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeInput): event = "vmin" | ||
| case (kAudioDevicePropertyMute, kAudioDevicePropertyScopeOutput): event = "mout" | ||
| case (kAudioDevicePropertyMute, kAudioDevicePropertyScopeInput): event = "min" | ||
| case (kAudioDevicePropertyNominalSampleRate, _): event = "rate" | ||
| case (kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput): event = "dsout" | ||
| case (kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput): event = "dsin" |
There was a problem hiding this comment.
selfRetain set before confirming any listener was registered
selfRetain = self is assigned unconditionally at the top of startWatcher(), before the loop that adds CoreAudio property listeners. If every candidate either lacks the property or fails AudioObjectAddPropertyListenerBlock, watcherRegistrations remains empty. At that point:
watcherIsActive()returnsfalse(correct)stopWatcher()early-returns immediately becausewatcherRegistrations.isEmptyis true — so it never clearsselfRetain
The device therefore retains itself permanently, causing a memory leak. The fix is to move selfRetain = self to after the loop, gated on at least one successful registration:
@objc func startWatcher() {
guard unsafe watcherRegistrations.isEmpty else { return }
let candidates: [(AudioObjectPropertySelector, AudioObjectPropertyScope)] = [ ... ]
for (selector, scope) in candidates {
// ... same as before
}
guard !watcherRegistrations.isEmpty else { return }
selfRetain = self // prevent GC while watcher is active
AKTrace(...)
}Prompt To Fix With AI
This is a comment left during a code review.
Path: Hammerspoon 2/Modules/hs.audiodevice/HSAudioDevice.swift
Line: 590-618
Comment:
**`selfRetain` set before confirming any listener was registered**
`selfRetain = self` is assigned unconditionally at the top of `startWatcher()`, before the loop that adds CoreAudio property listeners. If every candidate either lacks the property or fails `AudioObjectAddPropertyListenerBlock`, `watcherRegistrations` remains empty. At that point:
- `watcherIsActive()` returns `false` (correct)
- `stopWatcher()` early-returns immediately because `watcherRegistrations.isEmpty` is true — so it never clears `selfRetain`
The device therefore retains itself permanently, causing a memory leak. The fix is to move `selfRetain = self` to after the loop, gated on at least one successful registration:
```swift
@objc func startWatcher() {
guard unsafe watcherRegistrations.isEmpty else { return }
let candidates: [(AudioObjectPropertySelector, AudioObjectPropertyScope)] = [ ... ]
for (selector, scope) in candidates {
// ... same as before
}
guard !watcherRegistrations.isEmpty else { return }
selfRetain = self // prevent GC while watcher is active
AKTrace(...)
}
```
How can I resolve this? If you propose a fix, please make it concise.|
|
||
| deinit { | ||
| print("Deinit of \(name)") |
There was a problem hiding this comment.
Debug
print statement in deinit
Same pattern as in HSAudioDevice — this print(...) should use AKTrace (consistent with the rest of the module) or be removed before merge.
| deinit { | |
| print("Deinit of \(name)") | |
| deinit { | |
| AKTrace("Deinit of \(name)") | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: Hammerspoon 2/Modules/hs.audiodevice/HSAudioDeviceModule.swift
Line: 110-112
Comment:
**Debug `print` statement in `deinit`**
Same pattern as in `HSAudioDevice` — this `print(...)` should use `AKTrace` (consistent with the rest of the module) or be removed before merge.
```suggestion
deinit {
AKTrace("Deinit of \(name)")
}
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
…ed from the main actor, and thus no longer needs to use MainActor.assumeIsolated{}