mirror of
https://github.com/yattee/yattee.git
synced 2026-07-21 06:42:01 +00:00
Stop macOS player shortcuts from swallowing typing in other windows
The player's keyDown monitor only checked that some window was key, so plain-key shortcuts (m, space, f, arrows) fired while typing into the Settings window's text fields. Guard on the window actually hosting the player controls and pass events through while a text field is editing.
This commit is contained in:
@@ -48,6 +48,10 @@ struct MacOSPlayerControlsView: View {
|
|||||||
@State private var showControls: Bool?
|
@State private var showControls: Bool?
|
||||||
@State private var keyboardMonitor: Any?
|
@State private var keyboardMonitor: Any?
|
||||||
|
|
||||||
|
/// Window hosting these controls; keyboard shortcuts only apply to events
|
||||||
|
/// destined for this window (not e.g. the Settings window).
|
||||||
|
@State private var hostWindow: NSWindow?
|
||||||
|
|
||||||
/// The active player controls layout (macOS preset). `nil` until loaded, so
|
/// The active player controls layout (macOS preset). `nil` until loaded, so
|
||||||
/// the bars don't flash a wrong default before the preset arrives.
|
/// the bars don't flash a wrong default before the preset arrives.
|
||||||
@State private var layout: PlayerControlsLayout?
|
@State private var layout: PlayerControlsLayout?
|
||||||
@@ -225,6 +229,10 @@ struct MacOSPlayerControlsView: View {
|
|||||||
}
|
}
|
||||||
.frame(width: geometry.size.width, height: geometry.size.height)
|
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||||
}
|
}
|
||||||
|
.background(
|
||||||
|
HostWindowReader(window: $hostWindow)
|
||||||
|
.allowsHitTesting(false)
|
||||||
|
)
|
||||||
.animation(.easeInOut(duration: 0.2), value: shouldShowControls)
|
.animation(.easeInOut(duration: 0.2), value: shouldShowControls)
|
||||||
.onContinuousHover { phase in
|
.onContinuousHover { phase in
|
||||||
switch phase {
|
switch phase {
|
||||||
@@ -290,8 +298,12 @@ struct MacOSPlayerControlsView: View {
|
|||||||
|
|
||||||
private func setupKeyboardMonitor() {
|
private func setupKeyboardMonitor() {
|
||||||
keyboardMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [self] event in
|
keyboardMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [self] event in
|
||||||
// Only handle events when the player window is key (active)
|
// Only handle keys destined for the window hosting the player;
|
||||||
guard NSApp.keyWindow != nil else { return event }
|
// pass through events for other windows (e.g. Settings).
|
||||||
|
guard let window = event.window, window === hostWindow else { return event }
|
||||||
|
|
||||||
|
// Let text fields receive typing (the field editor is an NSText).
|
||||||
|
if window.firstResponder is NSText { return event }
|
||||||
|
|
||||||
// When controls are locked, ignore playback keys (space/arrows/mute) and
|
// When controls are locked, ignore playback keys (space/arrows/mute) and
|
||||||
// only keep fullscreen (F) and exit-fullscreen (Esc) working.
|
// only keep fullscreen (F) and exit-fullscreen (Esc) working.
|
||||||
@@ -401,6 +413,41 @@ struct MacOSPlayerControlsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Host Window Reader
|
||||||
|
|
||||||
|
/// Reports the `NSWindow` hosting this view so the keyboard monitor can ignore
|
||||||
|
/// events destined for other windows. Tracks re-parenting (mini bar <-> sheet
|
||||||
|
/// <-> floating window) via `viewDidMoveToWindow`.
|
||||||
|
private struct HostWindowReader: NSViewRepresentable {
|
||||||
|
@Binding var window: NSWindow?
|
||||||
|
|
||||||
|
func makeNSView(context: Context) -> ReaderView {
|
||||||
|
let view = ReaderView()
|
||||||
|
view.onWindowChange = { newWindow in
|
||||||
|
if window !== newWindow { window = newWindow }
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNSView(_ nsView: ReaderView, context: Context) {
|
||||||
|
nsView.onWindowChange = { newWindow in
|
||||||
|
if window !== newWindow { window = newWindow }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ReaderView: NSView {
|
||||||
|
var onWindowChange: ((NSWindow?) -> Void)?
|
||||||
|
|
||||||
|
override func viewDidMoveToWindow() {
|
||||||
|
super.viewDidMoveToWindow()
|
||||||
|
let window = window
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.onWindowChange?(window)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Traffic Light Inset Reader
|
// MARK: - Traffic Light Inset Reader
|
||||||
|
|
||||||
/// Reports how far the top bar's leading content must be pushed to clear the
|
/// Reports how far the top bar's leading content must be pushed to clear the
|
||||||
|
|||||||
Reference in New Issue
Block a user