mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-19 13:35:54 +00:00
Improve setting front logic
This commit is contained in:
parent
b5d043cba8
commit
936806edd6
1 changed files with 133 additions and 22 deletions
|
|
@ -5,38 +5,65 @@ import SwiftUI
|
|||
/// Helper to open the Settings window programmatically when SettingsLink cannot be used
|
||||
@MainActor
|
||||
enum SettingsOpener {
|
||||
/// SwiftUI's hardcoded settings window identifier
|
||||
private static let settingsWindowIdentifier = "com_apple_SwiftUI_Settings_window"
|
||||
|
||||
/// Opens the Settings window using the environment action via notification
|
||||
/// This is needed for cases where we can't use SettingsLink (e.g., from notifications)
|
||||
static func openSettings() {
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
|
||||
// Post notification to trigger openSettings environment action
|
||||
NotificationCenter.default.post(name: .openSettingsRequest, object: nil)
|
||||
|
||||
// Ensure settings window comes to front after opening
|
||||
Task {
|
||||
try? await Task.sleep(for: .milliseconds(100))
|
||||
bringSettingsToFront()
|
||||
// Try the direct menu item approach first (from VibeMeter)
|
||||
if openSettingsViaMenuItem() {
|
||||
// Successfully opened via menu item
|
||||
Task {
|
||||
try? await Task.sleep(for: .milliseconds(100))
|
||||
focusSettingsWindow()
|
||||
}
|
||||
} else {
|
||||
// Fallback to notification approach
|
||||
NotificationCenter.default.post(name: .openSettingsRequest, object: nil)
|
||||
|
||||
Task {
|
||||
try? await Task.sleep(for: .milliseconds(150))
|
||||
focusSettingsWindow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Brings the settings window to the front if it exists
|
||||
static func bringSettingsToFront() {
|
||||
// Find the settings window by looking for the preferences window
|
||||
if let settingsWindow = NSApp.windows.first(where: { window in
|
||||
/// Opens settings via the native menu item (more reliable)
|
||||
private static func openSettingsViaMenuItem() -> Bool {
|
||||
let kAppMenuInternalIdentifier = "app"
|
||||
let kSettingsLocalizedStringKey = "Settings\\U2026"
|
||||
|
||||
if let internalItemAction = NSApp.mainMenu?.item(
|
||||
withInternalIdentifier: kAppMenuInternalIdentifier)?.submenu?.item(
|
||||
withLocalizedTitle: kSettingsLocalizedStringKey)?.internalItemAction {
|
||||
internalItemAction()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/// Focuses the settings window without level manipulation
|
||||
static func focusSettingsWindow() {
|
||||
// First try the SwiftUI settings window identifier
|
||||
if let settingsWindow = NSApp.windows.first(where: {
|
||||
$0.identifier?.rawValue == settingsWindowIdentifier
|
||||
}) {
|
||||
settingsWindow.makeKeyAndOrderFront(nil)
|
||||
settingsWindow.orderFrontRegardless()
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
} else if let settingsWindow = NSApp.windows.first(where: { window in
|
||||
// Fallback to title-based search
|
||||
window.isVisible &&
|
||||
(window.styleMask.contains(.titled) &&
|
||||
window.title.localizedCaseInsensitiveContains("settings") ||
|
||||
window.styleMask.contains(.titled) &&
|
||||
(window.title.localizedCaseInsensitiveContains("settings") ||
|
||||
window.title.localizedCaseInsensitiveContains("preferences"))
|
||||
}) {
|
||||
settingsWindow.makeKeyAndOrderFront(nil)
|
||||
settingsWindow.level = .floating
|
||||
|
||||
// Reset to normal level after a brief moment
|
||||
Task {
|
||||
try? await Task.sleep(for: .milliseconds(50))
|
||||
settingsWindow.level = .normal
|
||||
}
|
||||
settingsWindow.orderFrontRegardless()
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +73,7 @@ enum SettingsOpener {
|
|||
|
||||
Task {
|
||||
// Small delay to ensure the settings window is fully initialized
|
||||
try? await Task.sleep(for: .milliseconds(100))
|
||||
try? await Task.sleep(for: .milliseconds(150))
|
||||
NotificationCenter.default.post(
|
||||
name: .openSettingsTab,
|
||||
object: tab
|
||||
|
|
@ -100,7 +127,7 @@ struct HiddenWindowView: View {
|
|||
|
||||
// Additional check to bring settings to front after environment action
|
||||
try? await Task.sleep(for: .milliseconds(150))
|
||||
SettingsOpener.bringSettingsToFront()
|
||||
SettingsOpener.focusSettingsWindow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -111,4 +138,88 @@ struct HiddenWindowView: View {
|
|||
|
||||
extension Notification.Name {
|
||||
static let openSettingsRequest = Notification.Name("openSettingsRequest")
|
||||
}
|
||||
|
||||
// MARK: - NSMenuItem Extensions (Private)
|
||||
|
||||
extension NSMenuItem {
|
||||
/// An internal SwiftUI menu item identifier that should be a public property on `NSMenuItem`.
|
||||
fileprivate var internalIdentifier: String? {
|
||||
guard let id = Mirror.firstChild(
|
||||
withLabel: "id", in: self)?.value
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return "\(id)"
|
||||
}
|
||||
|
||||
/// A callback which is associated directly with this `NSMenuItem`.
|
||||
fileprivate var internalItemAction: (() -> Void)? {
|
||||
guard
|
||||
let platformItemAction = Mirror.firstChild(
|
||||
withLabel: "platformItemAction", in: self)?.value,
|
||||
let typeErasedCallback = Mirror.firstChild(
|
||||
in: platformItemAction)?.value
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Mirror.firstChild(
|
||||
in: typeErasedCallback)?.value as? () -> Void
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSMenu Extensions (Private)
|
||||
|
||||
extension NSMenu {
|
||||
/// Get the first `NSMenuItem` whose internal identifier string matches the given value.
|
||||
fileprivate func item(withInternalIdentifier identifier: String) -> NSMenuItem? {
|
||||
items.first(where: {
|
||||
$0.internalIdentifier?.elementsEqual(identifier) ?? false
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the first `NSMenuItem` whose title is equivalent to the localized string referenced
|
||||
/// by the given localized string key in the localization table identified by the given table name
|
||||
/// from the bundle located at the given bundle path.
|
||||
fileprivate func item(
|
||||
withLocalizedTitle localizedTitleKey: String,
|
||||
inTable tableName: String = "MenuCommands",
|
||||
fromBundle bundlePath: String = "/System/Library/Frameworks/AppKit.framework") -> NSMenuItem? {
|
||||
guard let localizationResource = Bundle(path: bundlePath) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return item(withTitle: NSLocalizedString(
|
||||
localizedTitleKey,
|
||||
tableName: tableName,
|
||||
bundle: localizationResource,
|
||||
comment: ""))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mirror Extensions (Helper)
|
||||
|
||||
private extension Mirror {
|
||||
/// The unconditional first child of the reflection subject.
|
||||
var firstChild: Child? { children.first }
|
||||
|
||||
/// The first child of the reflection subject whose label matches the given string.
|
||||
func firstChild(withLabel label: String) -> Child? {
|
||||
children.first(where: {
|
||||
$0.label?.elementsEqual(label) ?? false
|
||||
})
|
||||
}
|
||||
|
||||
/// The unconditional first child of the given subject.
|
||||
static func firstChild(in subject: Any) -> Child? {
|
||||
Mirror(reflecting: subject).firstChild
|
||||
}
|
||||
|
||||
/// The first child of the given subject whose label matches the given string.
|
||||
static func firstChild(
|
||||
withLabel label: String, in subject: Any) -> Child? {
|
||||
Mirror(reflecting: subject).firstChild(withLabel: label)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue