@@ -25,6 +25,7 @@ extension EnvironmentValues {
2525struct KasetApp : App {
2626 /// App delegate for lifecycle management (background playback).
2727 @NSApplicationDelegateAdaptor ( AppDelegate . self) var appDelegate
28+ @Environment ( \. openWindow) private var openWindow
2829
2930 @State private var authService = AuthService ( )
3031 @State private var webKitManager = WebKitManager . shared
@@ -171,6 +172,29 @@ struct KasetApp: App {
171172 self . equalizerService. retryStartIfEnabled ( )
172173 }
173174 }
175+ . onChange ( of: self . playerService. isMiniPlayerVisible) { _, isVisible in
176+ if isVisible {
177+ MiniPlayerWindowController . shared. show (
178+ playerService: self . playerService,
179+ client: self . sharedClient,
180+ syncedLyricsService: self . syncedLyricsService
181+ )
182+ if self . playerService. miniPlayerMode == . switchFromMainWindow {
183+ self . hideMainWindow ( )
184+ }
185+ } else {
186+ MiniPlayerWindowController . shared. close ( )
187+ if self . playerService. consumeMiniPlayerMainWindowRestoreRequest ( ) {
188+ self . showMainWindow ( )
189+ }
190+ }
191+ }
192+ . onChange ( of: self . playerService. miniPlayerPanel) { _, _ in
193+ MiniPlayerWindowController . shared. syncWindowState ( )
194+ }
195+ . onChange ( of: self . settings. keepMiniPlayerOnTop) { _, _ in
196+ MiniPlayerWindowController . shared. syncWindowState ( )
197+ }
174198 }
175199 }
176200
@@ -314,6 +338,19 @@ struct KasetApp: App {
314338
315339 // Window menu - show main window
316340 CommandGroup ( after: . windowArrangement) {
341+ Button ( " Switch to Mini Player " ) {
342+ if self . playerService. isMiniPlayerVisible,
343+ self . playerService. miniPlayerMode == . switchFromMainWindow
344+ {
345+ _ = self . playerService. closeMiniPlayer ( )
346+ } else {
347+ self . playerService. openMiniPlayer ( mode: . switchFromMainWindow)
348+ }
349+ }
350+ . keyboardShortcut ( " m " , modifiers: [ . command, . shift] )
351+
352+ Divider ( )
353+
317354 Button ( " Kaset " ) {
318355 self . showMainWindow ( )
319356 }
@@ -332,20 +369,61 @@ struct KasetApp: App {
332369
333370 /// Shows the main window.
334371 private func showMainWindow( ) {
372+ guard !self . focusExistingMainWindow ( ) else { return }
373+
374+ self . openWindow ( id: " main " )
375+ NSApplication . shared. activate ( ignoringOtherApps: true )
376+
377+ Task { @MainActor in
378+ try ? await Task . sleep ( for: . milliseconds( 100 ) )
379+ _ = self . focusExistingMainWindow ( )
380+ }
381+ }
382+
383+ @discardableResult
384+ private func focusExistingMainWindow( ) -> Bool {
335385 // Find and show the main window
336386 for window in NSApplication . shared. windows where window. frameAutosaveName == " KasetMainWindow " {
337387 window. makeKeyAndOrderFront ( nil )
338388 NSApplication . shared. activate ( ignoringOtherApps: true )
339- return
389+ return true
390+ }
391+
392+ for window in NSApplication . shared. windows where window. title == " Kaset " && !Self. isAuxiliaryPlayerWindow ( window) {
393+ window. makeKeyAndOrderFront ( nil )
394+ NSApplication . shared. activate ( ignoringOtherApps: true )
395+ return true
340396 }
341397
342- // Fallback: find any main-capable window that's not the video window
398+ // Fallback: find any main-capable window that's not an auxiliary player window.
343399 for window in NSApplication . shared. windows where window. canBecomeMain {
344- if window . identifier ? . rawValue == AccessibilityID . VideoWindow . container {
400+ if Self . isAuxiliaryPlayerWindow ( window ) {
345401 continue
346402 }
347403 window. makeKeyAndOrderFront ( nil )
348404 NSApplication . shared. activate ( ignoringOtherApps: true )
405+ return true
406+ }
407+
408+ return false
409+ }
410+
411+ private static func isAuxiliaryPlayerWindow( _ window: NSWindow ) -> Bool {
412+ AccessibilityID . isAuxiliaryPlayerWindowIdentifier ( window. identifier? . rawValue)
413+ }
414+
415+ /// Hides the main window while keeping playback and auxiliary windows alive.
416+ private func hideMainWindow( ) {
417+ for window in NSApplication . shared. windows where window. frameAutosaveName == " KasetMainWindow " {
418+ window. orderOut ( nil )
419+ return
420+ }
421+
422+ for window in NSApplication . shared. windows where window. canBecomeMain {
423+ if Self . isAuxiliaryPlayerWindow ( window) {
424+ continue
425+ }
426+ window. orderOut ( nil )
349427 return
350428 }
351429 }
0 commit comments