From 6acc39a6abe7b2d68fbde62a724cc2fa0a033674 Mon Sep 17 00:00:00 2001 From: AsapShadzy Date: Fri, 3 Apr 2026 12:54:54 +0300 Subject: [PATCH] fix: app bundle crash, proper code signing, and hide-from-dock toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace SwiftPM auto-generated resource_bundle_accessor with custom BundleAccessor.swift that looks in Contents/Resources/, fixing the crash when launching from a .app bundle (Bundle.module couldn't find PhemyNative_PhemyNative.bundle at the .app root) - Remove manual Dock tile icon override — let macOS handle icon natively via CFBundleIconFile for proper rounded-rectangle styling - Add "Hide from Dock on close" toggle in General > Startup settings - Reduce menu bar icon from 22pt to 18pt (standard macOS size) - Update Package.swift to exclude Resources from SwiftPM bundling --- Package.swift | 5 +- Sources/PhemyNative/BundleAccessor.swift | 27 +++++++++++ Sources/PhemyNative/PhemyNativeApp.swift | 48 +++++++++++-------- .../Views/Settings/GeneralSettingsView.swift | 4 ++ 4 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 Sources/PhemyNative/BundleAccessor.swift diff --git a/Package.swift b/Package.swift index 9aed49b..f386fea 100644 --- a/Package.swift +++ b/Package.swift @@ -16,9 +16,8 @@ let package = Package( name: "PhemyNative", dependencies: ["CPhemyCore"], path: "Sources/PhemyNative", - resources: [ - .copy("Resources") - ], + // Resources loaded via custom BundleAccessor.swift (not SwiftPM auto-bundling) + exclude: ["Resources"], linkerSettings: [ .unsafeFlags([ "-L", ".", // libphemy_core.dylib in project root diff --git a/Sources/PhemyNative/BundleAccessor.swift b/Sources/PhemyNative/BundleAccessor.swift new file mode 100644 index 0000000..0d94bb8 --- /dev/null +++ b/Sources/PhemyNative/BundleAccessor.swift @@ -0,0 +1,27 @@ +import Foundation + +extension Foundation.Bundle { + /// Custom resource bundle accessor that works in both .app bundles and SwiftPM build directories. + static let module: Bundle = { + let bundleName = "PhemyNative_PhemyNative" + + let candidates = [ + // Inside .app bundle: Contents/Resources/ + Bundle.main.bundleURL.appendingPathComponent("Contents/Resources/\(bundleName).bundle"), + // Alongside executable (SwiftPM build output) + Bundle.main.bundleURL.appendingPathComponent("\(bundleName).bundle"), + // Alongside the binary directly + Bundle(for: BundleFinder.self).bundleURL.appendingPathComponent("\(bundleName).bundle"), + ] + + for candidate in candidates { + if let bundle = Bundle(path: candidate.path) { + return bundle + } + } + + fatalError("Unable to find resource bundle \(bundleName)") + }() +} + +private class BundleFinder {} diff --git a/Sources/PhemyNative/PhemyNativeApp.swift b/Sources/PhemyNative/PhemyNativeApp.swift index 27895f6..8e98123 100644 --- a/Sources/PhemyNative/PhemyNativeApp.swift +++ b/Sources/PhemyNative/PhemyNativeApp.swift @@ -14,8 +14,8 @@ struct PhemyNativeApp: App { guard let url = Bundle.module.url(forResource: "MenuBarIcon", withExtension: "png"), let image = NSImage(contentsOf: url) else { return nil } image.isTemplate = true - // 44px source is @2x — set point size to 22 so it renders at correct menu bar size - image.size = NSSize(width: 22, height: 22) + // 44px source is @2x — set point size to 18 for standard menu bar icon size + image.size = NSSize(width: 18, height: 18) return image }() @@ -27,10 +27,6 @@ struct PhemyNativeApp: App { }() init() { - // Set applicationIconImage early for About dialogs and window icons - if let icon = Self.appIcon { - NSApplication.shared.applicationIconImage = icon - } } var body: some Scene { @@ -48,9 +44,12 @@ struct PhemyNativeApp: App { MenuBarExtra { Button("Show Settings") { - NSApplication.shared.activate(ignoringOtherApps: true) - if let window = NSApplication.shared.windows.first { - window.makeKeyAndOrderFront(nil) + NSApp.setActivationPolicy(.regular) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + NSApplication.shared.activate(ignoringOtherApps: true) + if let window = NSApplication.shared.windows.first { + window.makeKeyAndOrderFront(nil) + } } } .keyboardShortcut(",", modifiers: .command) @@ -83,19 +82,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate { // when running as an unbundled executable UserDefaults.standard.set(false, forKey: "NSQuitAlwaysKeepsWindows") - // Ensure app appears in Dock (MenuBarExtra can default to accessory mode) + // Default: don't hide from dock on close + if UserDefaults.standard.object(forKey: "hideFromDockOnClose") == nil { + UserDefaults.standard.set(false, forKey: "hideFromDockOnClose") + } + + // Show in Dock on launch NSApp.setActivationPolicy(.regular) - // Set custom Dock tile icon (unbundled executables need explicit Dock tile update) - if let icon = PhemyNativeApp.appIcon { - NSApp.applicationIconImage = icon - let dockTile = NSApp.dockTile - let imageView = NSImageView(frame: NSRect(x: 0, y: 0, width: dockTile.size.width, height: dockTile.size.height)) - imageView.image = icon - imageView.imageScaling = .scaleProportionallyUpOrDown - dockTile.contentView = imageView - dockTile.display() - } + // Dock icon handled by CFBundleIconFile in Info.plist overlayPanel = RecordingOverlayPanel(recordingManager: recordingManager) @@ -161,7 +156,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate { return error } - /// Handle app re-activation (Dock click) — show settings window instead of default behavior. + /// When last window closes: if "hide from dock on close" is on, hide from Dock but stay in menu bar + func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + let hideOnClose = UserDefaults.standard.bool(forKey: "hideFromDockOnClose") + if hideOnClose { + DispatchQueue.main.async { + NSApp.setActivationPolicy(.accessory) + } + } + return false + } + + /// Handle app re-activation (Dock click) — show settings window. func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { if !flag { if let window = NSApplication.shared.windows.first { diff --git a/Sources/PhemyNative/Views/Settings/GeneralSettingsView.swift b/Sources/PhemyNative/Views/Settings/GeneralSettingsView.swift index c369a64..08f1cd5 100644 --- a/Sources/PhemyNative/Views/Settings/GeneralSettingsView.swift +++ b/Sources/PhemyNative/Views/Settings/GeneralSettingsView.swift @@ -3,6 +3,7 @@ import SwiftUI struct GeneralSettingsView: View { @ObservedObject var vm: SettingsViewModel @EnvironmentObject var theme: ThemeManager + @AppStorage("hideFromDockOnClose") private var hideFromDockOnClose = false var body: some View { VStack(alignment: .leading, spacing: Spacing.sectionGap) { @@ -59,6 +60,9 @@ struct GeneralSettingsView: View { .toggleStyle(.switch) .tint(theme.primary) .onChange(of: vm.settings.launchAtStartup) { vm.autoSave() } + Toggle("Hide from Dock on close", isOn: $hideFromDockOnClose) + .toggleStyle(.switch) + .tint(theme.primary) } // Reset