diff --git a/noTunes.xcodeproj/project.pbxproj b/noTunes.xcodeproj/project.pbxproj index 2b5e014..ab3af79 100644 --- a/noTunes.xcodeproj/project.pbxproj +++ b/noTunes.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 9DA35CBC2C65254A005CEEBD /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA35CBB2C65254A005CEEBD /* SettingsView.swift */; }; + 9DA35CBE2C65286E005CEEBD /* SettingsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA35CBD2C65286E005CEEBD /* SettingsWindowController.swift */; }; + 9DA35CC12C653A51005CEEBD /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 9DA35CC02C653A51005CEEBD /* LaunchAtLogin */; }; E29B3C731E1D97CF00CB67E3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29B3C721E1D97CF00CB67E3 /* AppDelegate.swift */; }; E29B3C751E1D97CF00CB67E3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E29B3C741E1D97CF00CB67E3 /* Assets.xcassets */; }; E29B3C781E1D97CF00CB67E3 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = E29B3C761E1D97CF00CB67E3 /* MainMenu.xib */; }; @@ -25,6 +28,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 9DA35CBB2C65254A005CEEBD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 9DA35CBD2C65286E005CEEBD /* SettingsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsWindowController.swift; sourceTree = ""; }; E29B3C6F1E1D97CF00CB67E3 /* noTunes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = noTunes.app; sourceTree = BUILT_PRODUCTS_DIR; }; E29B3C721E1D97CF00CB67E3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; E29B3C741E1D97CF00CB67E3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -37,6 +42,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9DA35CC12C653A51005CEEBD /* LaunchAtLogin in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -66,6 +72,8 @@ E29B3C741E1D97CF00CB67E3 /* Assets.xcassets */, E29B3C761E1D97CF00CB67E3 /* MainMenu.xib */, E29B3C791E1D97CF00CB67E3 /* Info.plist */, + 9DA35CBB2C65254A005CEEBD /* SettingsView.swift */, + 9DA35CBD2C65286E005CEEBD /* SettingsWindowController.swift */, ); path = noTunes; sourceTree = ""; @@ -80,6 +88,7 @@ E29B3C6B1E1D97CF00CB67E3 /* Sources */, E29B3C6C1E1D97CF00CB67E3 /* Frameworks */, E29B3C6D1E1D97CF00CB67E3 /* Resources */, + 9DA35CC22C653A83005CEEBD /* Copy "Launch at Login Helper" */, E237C9DE1E27C8D20006F607 /* CopyFiles */, ); buildRules = ( @@ -87,6 +96,9 @@ dependencies = ( ); name = noTunes; + packageProductDependencies = ( + 9DA35CC02C653A51005CEEBD /* LaunchAtLogin */, + ); productName = noTunes; productReference = E29B3C6F1E1D97CF00CB67E3 /* noTunes.app */; productType = "com.apple.product-type.application"; @@ -119,6 +131,9 @@ Base, ); mainGroup = E29B3C661E1D97CF00CB67E3; + packageReferences = ( + 9DA35CBF2C653A51005CEEBD /* XCRemoteSwiftPackageReference "LaunchAtLogin-Legacy" */, + ); productRefGroup = E29B3C701E1D97CF00CB67E3 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -140,12 +155,36 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 9DA35CC22C653A83005CEEBD /* Copy "Launch at Login Helper" */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Copy \"Launch at Login Helper\""; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${BUILT_PRODUCTS_DIR}/LaunchAtLogin_LaunchAtLogin.bundle/Contents/Resources/copy-helper-swiftpm.sh\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ E29B3C6B1E1D97CF00CB67E3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E29B3C731E1D97CF00CB67E3 /* AppDelegate.swift in Sources */, + 9DA35CBE2C65286E005CEEBD /* SettingsWindowController.swift in Sources */, + 9DA35CBC2C65254A005CEEBD /* SettingsView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -199,7 +238,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -259,7 +298,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -348,6 +387,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 9DA35CBF2C653A51005CEEBD /* XCRemoteSwiftPackageReference "LaunchAtLogin-Legacy" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sindresorhus/LaunchAtLogin-Legacy"; + requirement = { + branch = main; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 9DA35CC02C653A51005CEEBD /* LaunchAtLogin */ = { + isa = XCSwiftPackageProductDependency; + package = 9DA35CBF2C653A51005CEEBD /* XCRemoteSwiftPackageReference "LaunchAtLogin-Legacy" */; + productName = LaunchAtLogin; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = E29B3C671E1D97CF00CB67E3 /* Project object */; } diff --git a/noTunes.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/noTunes.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 27d9f0a..919434a 100644 --- a/noTunes.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/noTunes.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/noTunes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/noTunes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..f3281e1 --- /dev/null +++ b/noTunes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "78526aafdff3d7bb5fccbf1f4499af4d3ea88e16e8819bf30db26c60f51192d8", + "pins" : [ + { + "identity" : "launchatlogin-legacy", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sindresorhus/LaunchAtLogin-Legacy", + "state" : { + "branch" : "main", + "revision" : "9a894d799269cb591037f9f9cb0961510d4dca81" + } + } + ], + "version" : 3 +} diff --git a/noTunes/AppDelegate.swift b/noTunes/AppDelegate.swift index 8424635..987d855 100644 --- a/noTunes/AppDelegate.swift +++ b/noTunes/AppDelegate.swift @@ -15,7 +15,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { let defaults = UserDefaults.standard let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) - + + struct settingsWindowControllerStruct { + @available(macOS 10.15, *) + static var settingsWindowController: SettingsWindowController? + } + @IBOutlet weak var statusMenu: NSMenu! @IBAction func hideIconClicked(_ sender: NSMenuItem) { @@ -27,6 +32,28 @@ class AppDelegate: NSObject, NSApplicationDelegate { @IBAction func quitClicked(_ sender: NSMenuItem) { NSApplication.shared.terminate(self) } + + @IBAction func settingsClicked(_ sender: NSMenuItem) { + if #available(macOS 10.15, *) { + if settingsWindowControllerStruct.settingsWindowController == nil { + settingsWindowControllerStruct.settingsWindowController = SettingsWindowController() + } + settingsWindowControllerStruct.settingsWindowController?.showWindow(self) + } else { + let alert = NSAlert() + alert.messageText = "Settings Unavailable" + alert.informativeText = "The settings screen is not available on this version of macOS. Please see the full documentation." + alert.addButton(withTitle: "Open Documentation") + alert.addButton(withTitle: "Cancel") + + let response = alert.runModal() + if response == .alertFirstButtonReturn { + if let url = URL(string: "https://github.com/tombonez/noTunes#usage") { + NSWorkspace.shared.open(url) + } + } + } + } @objc func statusBarButtonClicked(sender: NSStatusBarButton) { let event = NSApp.currentEvent! diff --git a/noTunes/Base.lproj/MainMenu.xib b/noTunes/Base.lproj/MainMenu.xib index f6e832c..7f22924 100644 --- a/noTunes/Base.lproj/MainMenu.xib +++ b/noTunes/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -26,6 +26,12 @@ + + + + + + diff --git a/noTunes/SettingsView.swift b/noTunes/SettingsView.swift new file mode 100644 index 0000000..a7ff8da --- /dev/null +++ b/noTunes/SettingsView.swift @@ -0,0 +1,73 @@ +// +// SettingsView.swift +// noTunes +// +// Created by Zein Hajj-Ali on 2024-08-08. +// Copyright © 2024 Twisted Digital Ltd. All rights reserved. +// + +import SwiftUI +import AppKit +import LaunchAtLogin + +@available(macOS 10.15, *) +struct SettingsView: View { + @State private var replacement: String = UserDefaults.standard.string(forKey: "replacement") ?? "" + + var body: some View { + VStack(alignment: .leading) { + Text("Replacement Application or URL") + .padding(.bottom, 5) + + HStack { + TextField("Enter application path or URL", text: $replacement, onCommit: { + UserDefaults.standard.set(replacement, forKey: "replacement") + }) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .frame(maxWidth: .infinity, alignment: .leading) + + Button(action: { + let panel = NSOpenPanel() + panel.title = "Select a Replacement Application" + panel.allowedFileTypes = ["app"] + panel.allowsMultipleSelection = false + panel.canChooseDirectories = false + panel.canChooseFiles = true + + if panel.runModal() == .OK { + if let url = panel.url { + replacement = url.path + UserDefaults.standard.set(replacement, forKey: "replacement") + } + } + }) { + Text("Browse...") + .frame(width: 60) + } + .padding(.leading, 5) + + + Button(action: { + UserDefaults.standard.removeObject(forKey: "replacement") + replacement = "" + }) { + Text("Reset") + .frame(width: 60) + } + .padding(.leading, 5) + } + .padding(.bottom, 20) + + LaunchAtLogin.Toggle() + + Spacer() + } + .frame(width: 400, height: 150) + .padding() + } +} + +@available(macOS 10.15, *) +#Preview { + SettingsView() +} diff --git a/noTunes/SettingsWindowController.swift b/noTunes/SettingsWindowController.swift new file mode 100644 index 0000000..9b7af42 --- /dev/null +++ b/noTunes/SettingsWindowController.swift @@ -0,0 +1,34 @@ +// +// SettingsWindowController.swift +// noTunes +// +// Created by Zein Hajj-Ali on 2024-08-08. +// Copyright © 2024 Twisted Digital Ltd. All rights reserved. +// + +import Cocoa +import SwiftUI + +@available(macOS 10.15, *) +class SettingsWindowController: NSWindowController { + + convenience init() { + let settingsView = SettingsView() + let hostingController = NSHostingController(rootView: settingsView) + let window = NSWindow(contentViewController: hostingController) + + self.init(window: window) + + window.title = "Settings" + window.styleMask = [.titled, .closable, .miniaturizable] + window.setFrame(NSRect(x: 0, y: 0, width: 300, height: 200), display: true) + window.center() + } + + override func showWindow(_ sender: Any?) { + super.showWindow(sender) + self.window?.center() + self.window?.makeKeyAndOrderFront(nil) + NSApp.activate(ignoringOtherApps: true) + } +}