fix(macos): release overlay when mpv loses focus

This commit is contained in:
2026-05-16 17:41:58 -07:00
parent b6272b229e
commit a36e628512
6 changed files with 462 additions and 30 deletions
+17 -4
View File
@@ -7,7 +7,7 @@
// It works with both bundled and unbundled mpv installations.
//
// Usage: swift get-mpv-window-macos.swift
// Output: "x,y,width,height,focused", "minimized", "active", or "not-found"
// Output: "x,y,width,height,focused", "minimized", "active", "inactive", or "not-found"
//
import Cocoa
@@ -34,6 +34,7 @@ private enum WindowLookupResult {
case visible(WindowState)
case minimized
case active
case inactive
}
private let targetMpvSocketPath: String? = {
@@ -176,11 +177,17 @@ private func isFocusedMpvWindow(ownerPid: pid_t, frontmost: FrontmostApplication
}
private func isFrontmostTargetMpv(_ frontmost: FrontmostApplicationState?) -> Bool {
guard let frontmost = frontmost else {
guard let frontmost = frontmost, frontmost.isMpv else {
return false
}
return frontmost.isMpv && windowHasTargetSocket(frontmost.pid)
if windowHasTargetSocket(frontmost.pid) {
return true
}
// When macOS says mpv is frontmost but geometry APIs miss, keep the
// overlay stable even if ps cannot expose the socket argument.
return targetMpvSocketPath != nil
}
private func windowStateFromAccessibilityAPI() -> WindowLookupResult? {
@@ -307,9 +314,13 @@ private let lookupResult: WindowLookupResult? = {
if let cgWindow = windowStateFromCoreGraphics() {
return .visible(cgWindow)
}
if isFrontmostTargetMpv(frontmostApplicationState()) {
let frontmost = frontmostApplicationState()
if isFrontmostTargetMpv(frontmost) {
return .active
}
if frontmost != nil {
return .inactive
}
return nil
}()
@@ -323,6 +334,8 @@ if let result = lookupResult {
print("minimized")
case .active:
print("active")
case .inactive:
print("inactive")
}
} else {
print("not-found")
+23 -3
View File
@@ -59,12 +59,16 @@ test('focused mpv window follows the frontmost mpv app signal', () => {
test('frontmost mpv app emits active state when geometry lookup misses', () => {
assert.ok(
source.includes('case active'),
/case\s+\.active:/.test(source),
'helper should expose an active state without window geometry',
);
assert.ok(
source.includes('frontmost.isMpv && windowHasTargetSocket(frontmost.pid)'),
'active state should be limited to the frontmost target mpv process',
source.includes('if windowHasTargetSocket(frontmost.pid)'),
'active state should still accept a matching target socket when available',
);
assert.ok(
source.includes('return targetMpvSocketPath != nil'),
'active state should preserve frontmost mpv even if command-line socket detection fails',
);
assert.ok(
source.includes('return .active'),
@@ -72,3 +76,19 @@ test('frontmost mpv app emits active state when geometry lookup misses', () => {
);
assert.ok(source.includes('print("active")'), 'active state should be printed for the tracker');
});
test('frontmost non-mpv app emits inactive state when geometry lookup misses', () => {
assert.ok(
/case\s+\.inactive:/.test(source),
'helper should expose an inactive state without window geometry',
);
assert.ok(
source.includes('if frontmost != nil'),
'helper should distinguish a known non-mpv frontmost app from an unknown miss',
);
assert.ok(source.includes('return .inactive'), 'known non-mpv focus should return inactive');
assert.ok(
source.includes('print("inactive")'),
'inactive state should be printed for the tracker',
);
});