├── Design
└── Icons.sketch
├── Unsterificator
├── Feedback-Mono.caf
├── Feedback-Stereo.caf
├── Assets.xcassets
│ ├── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── mac16.png
│ │ ├── mac32.png
│ │ ├── mac64.png
│ │ ├── ipad152.png
│ │ ├── ipad76.png
│ │ ├── mac1024.png
│ │ ├── mac128.png
│ │ ├── mac256.png
│ │ ├── mac512.png
│ │ ├── ipadPro167.png
│ │ ├── iphone120.png
│ │ ├── iphone180.png
│ │ ├── settings58.png
│ │ ├── settings87.png
│ │ ├── appstore1024.png
│ │ ├── spotlight120.png
│ │ ├── spotlight80.png
│ │ ├── ipadSettings29.png
│ │ ├── ipadSettings58.png
│ │ ├── ipadSpotlight40.png
│ │ ├── ipadSpotlight80.png
│ │ ├── notification40.png
│ │ ├── notification60.png
│ │ ├── ipadNotification20.png
│ │ ├── ipadNotification40.png
│ │ └── Contents.json
│ ├── StatusItemMono.imageset
│ │ ├── mono.png
│ │ ├── mono@2x.png
│ │ ├── mono@3x.png
│ │ └── Contents.json
│ ├── StatusItemStereo.imageset
│ │ ├── stereo.png
│ │ ├── stereo@2x.png
│ │ ├── stereo@3x.png
│ │ └── Contents.json
│ └── AccentColor.colorset
│ │ └── Contents.json
├── Unsterificator.entitlements
├── Settings.swift
├── Info.plist
├── Unsterificator_Bridging_Header.h
├── AppDelegate.swift
├── NSMenu+.swift
├── Unsterificator.swift
├── StatusItemMenu.swift
├── AudioFeedback.swift
├── SettingsWindowController.swift
├── StatusItemController.swift
└── Base.lproj
│ └── Main.storyboard
├── .gitignore
├── README.MD
├── LICENSE
└── Unsterificator.xcodeproj
└── project.pbxproj
/Design/Icons.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Design/Icons.sketch
--------------------------------------------------------------------------------
/Unsterificator/Feedback-Mono.caf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Feedback-Mono.caf
--------------------------------------------------------------------------------
/Unsterificator/Feedback-Stereo.caf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Feedback-Stereo.caf
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/mac16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/mac16.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/mac32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/mac32.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/mac64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/mac64.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipad152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipad152.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipad76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipad76.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/mac1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/mac1024.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/mac128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/mac128.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/mac256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/mac256.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/mac512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/mac512.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipadPro167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipadPro167.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/iphone120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/iphone120.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/iphone180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/iphone180.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/settings58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/settings58.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/settings87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/settings87.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/StatusItemMono.imageset/mono.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/StatusItemMono.imageset/mono.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/appstore1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/appstore1024.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/spotlight120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/spotlight120.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/spotlight80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/spotlight80.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/StatusItemMono.imageset/mono@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/StatusItemMono.imageset/mono@2x.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/StatusItemMono.imageset/mono@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/StatusItemMono.imageset/mono@3x.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipadSettings29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipadSettings29.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipadSettings58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipadSettings58.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipadSpotlight40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipadSpotlight40.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipadSpotlight80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipadSpotlight80.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/notification40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/notification40.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/notification60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/notification60.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/StatusItemStereo.imageset/stereo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/StatusItemStereo.imageset/stereo.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/StatusItemStereo.imageset/stereo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/StatusItemStereo.imageset/stereo@2x.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/StatusItemStereo.imageset/stereo@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/StatusItemStereo.imageset/stereo@3x.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipadNotification20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipadNotification20.png
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipadNotification40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/Unsterificator/HEAD/Unsterificator/Assets.xcassets/AppIcon.appiconset/ipadNotification40.png
--------------------------------------------------------------------------------
/Unsterificator/Unsterificator.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Unsterificator/Settings.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | final class Settings: ObservableObject {
4 | static let current = Settings()
5 |
6 | @AppStorage("soundFeedbackEnabled")
7 | var soundFeedbackEnabled = true
8 |
9 | private init() { }
10 | }
11 |
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "platform" : "universal",
6 | "reference" : "systemIndigoColor"
7 | },
8 | "idiom" : "universal"
9 | }
10 | ],
11 | "info" : {
12 | "author" : "xcode",
13 | "version" : 1
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Unsterificator/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleIconFile
6 |
7 | LSMinimumSystemVersion
8 | $(MACOSX_DEPLOYMENT_TARGET)
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | __MACOSX
3 | *.pbxuser
4 | !default.pbxuser
5 | *.mode1v3
6 | !default.mode1v3
7 | *.mode2v3
8 | !default.mode2v3
9 | *.perspectivev3
10 | !default.perspectivev3
11 | *.xcworkspace
12 | !default.xcworkspace
13 | xcuserdata
14 | profile
15 | *.moved-aside
16 | DerivedData
17 | .idea/
18 | Crashlytics.sh
19 | generatechangelog.sh
20 | Pods/
21 | Carthage
22 | Provisioning
23 | Crashlytics.sh
--------------------------------------------------------------------------------
/Unsterificator/Unsterificator_Bridging_Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Unsterificator_Bridging_Header.h
3 | // Unsterificator
4 | //
5 | // Created by Guilherme Rambo on 17/05/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | @import Foundation;
10 |
11 | extern BOOL UAPlayStereoAsMonoIsEnabled();
12 | extern BOOL UAPlayStereoAsMonoIsSupported();
13 | extern void UAPlayStereoAsMonoSetEnabled(BOOL enabled);
14 |
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/StatusItemMono.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "mono.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "mono@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "mono@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "template"
25 | }
26 | }
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/StatusItemStereo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "stereo.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "stereo@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "stereo@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "template"
25 | }
26 | }
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # Unsterificator
2 |
3 | A simple menu bar icon that lets you toggle mono audio on/off by clicking on its icon.
4 |
5 | [Download the latest release here](http://github.com/insidegui/Unsterificator/releases/latest).
6 |
7 | ### But why?
8 |
9 | Sometimes you want to watch some video and for some reason the audio is coming from only the left or the right speaker, sometimes you want to listen to a podcast but each person's voice is to one side and you (like me) hate that, there are many reasons why this simple tool can be useful 😃
10 |
11 | ### How?
12 |
13 | It uses the accessibility API, so it's the same as toggling mono audio on/off using the Accessibility System Preferences pane.
--------------------------------------------------------------------------------
/Unsterificator/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Unsterificator
4 | //
5 | // Created by Guilherme Rambo on 17/05/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | @NSApplicationMain
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 |
14 | private lazy var statusController: StatusItemController = StatusItemController()
15 |
16 | func applicationDidFinishLaunching(_ aNotification: Notification) {
17 | statusController.install()
18 | }
19 |
20 | func applicationWillTerminate(_ aNotification: Notification) {
21 | // Insert code here to tear down your application
22 | }
23 |
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/Unsterificator/NSMenu+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSMenu.swift
3 | // ExtendedKit
4 | //
5 | // Created by Dave DeLong on 3/8/23.
6 | // https://github.com/davedelong/extendedswift
7 | //
8 |
9 | import Cocoa
10 |
11 | extension NSMenu {
12 |
13 | @discardableResult
14 | func addItem(withTitle title: String, target: AnyObject, action: Selector, representedObject: Any? = nil) -> NSMenuItem {
15 | let i = self.addItem(withTitle: title, action: action, keyEquivalent: "")
16 | i.target = target
17 | i.representedObject = representedObject
18 | return i
19 | }
20 |
21 | @discardableResult
22 | func addSeparator() -> NSMenuItem {
23 | let separator = NSMenuItem.separator()
24 | self.addItem(separator)
25 | return separator
26 | }
27 |
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/Unsterificator/Unsterificator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Unsterificator.swift
3 | // Unsterificator
4 | //
5 | // Created by Guilherme Rambo on 17/05/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | extension Notification.Name {
12 | static let UASoundSettingsDidChangeNotification = Notification.Name("UniversalAccessDomainSoundSettingsDidChangeNotification")
13 | }
14 |
15 | final class Unsterificator {
16 |
17 | init() {
18 | DistributedNotificationCenter.default().addObserver(forName: .UASoundSettingsDidChangeNotification, object: nil, queue: OperationQueue.main) { [weak self] _ in
19 | self?.settingDidChangeExternally?()
20 | }
21 | }
22 |
23 | var settingDidChangeExternally: (() -> Void)?
24 |
25 | var isSupported: Bool {
26 | return UAPlayStereoAsMonoIsSupported()
27 | }
28 |
29 | var isUnsterified: Bool {
30 | get {
31 | return UAPlayStereoAsMonoIsEnabled()
32 | }
33 | set {
34 | UAPlayStereoAsMonoSetEnabled(newValue)
35 | }
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Guilherme Rambo
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are met:
5 |
6 | - Redistributions of source code must retain the above copyright notice, this
7 | list of conditions and the following disclaimer.
8 |
9 | - Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/Unsterificator/StatusItemMenu.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 |
3 | protocol StatusItemMenuActions {
4 | func presentSettings(_ sender: NSMenuItem)
5 | func terminate(_ sender: NSMenuItem)
6 | }
7 |
8 | final class StatusItemMenu: NSMenu {
9 |
10 | private var actions: StatusItemMenuActions
11 |
12 | init(actions: StatusItemMenuActions) {
13 | self.actions = actions
14 |
15 | super.init(title: "Unsterificator")
16 |
17 | addItem(withTitle: "Settings…", target: self, action: #selector(configureKeyboardShortcut))
18 |
19 | addSeparator()
20 |
21 | addItem(withTitle: "Quit", target: self, action: #selector(StatusItemMenu.terminate))
22 | }
23 |
24 | required init(coder: NSCoder) {
25 | fatalError()
26 | }
27 |
28 | @objc private func configureKeyboardShortcut(_ sender: Any?) {
29 | guard let sender = sender as? NSMenuItem else {
30 | assertionFailure("Expected menu item action sender to me NSMenuItem")
31 | return
32 | }
33 | actions.presentSettings(sender)
34 | }
35 |
36 | @objc private func terminate(_ sender: Any?) {
37 | guard let sender = sender as? NSMenuItem else {
38 | assertionFailure("Expected menu item action sender to me NSMenuItem")
39 | return
40 | }
41 | actions.terminate(sender)
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/Unsterificator/AudioFeedback.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AudioToolbox
3 |
4 | final class AudioFeedback {
5 |
6 | private let filename: String
7 | private var systemSoundID: SystemSoundID = .zero
8 |
9 | init(filename: String) {
10 | self.filename = filename
11 | }
12 |
13 | func play() {
14 | guard Settings.current.soundFeedbackEnabled else { return }
15 |
16 | guard let soundID = getSystemSoundID() else { return }
17 |
18 | AudioServicesPlaySystemSound(soundID)
19 | }
20 |
21 | }
22 |
23 | extension AudioFeedback {
24 | static let stereo = AudioFeedback(filename: "Feedback-Stereo.caf")
25 | static let mono = AudioFeedback(filename: "Feedback-Mono.caf")
26 | }
27 |
28 | // MARK: - Sound Registration
29 |
30 | private extension AudioFeedback {
31 | func getSystemSoundID() -> SystemSoundID? {
32 | if systemSoundID != .zero { return systemSoundID }
33 | guard let registeredSoundID = registerSystemSoundID() else { return nil }
34 | return registeredSoundID
35 | }
36 |
37 | func registerSystemSoundID() -> SystemSoundID? {
38 | guard let fileURL = Bundle.main.url(forResource: filename, withExtension: nil) else {
39 | assertionFailure("Missing feedback sound file")
40 | return nil
41 | }
42 |
43 | var soundID: SystemSoundID = .zero
44 | let result = AudioServicesCreateSystemSoundID(fileURL as CFURL, &soundID)
45 | guard result == kIOReturnSuccess else {
46 | assertionFailure("Failed to register system sound. Error code \(result)")
47 | return nil
48 | }
49 | return soundID
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/Unsterificator/SettingsWindowController.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import LaunchAtLogin
3 | import KeyboardShortcuts
4 |
5 | final class SettingsWindowController: NSWindowController, NSWindowDelegate {
6 | convenience init() {
7 | let window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 200, height: 200), styleMask: [.titled, .closable, .fullSizeContentView], backing: .buffered, defer: false)
8 | self.init(window: window)
9 | window.delegate = self
10 | window.titlebarAppearsTransparent = true
11 | window.title = "Unsterificator Settings"
12 | let viewController = NSHostingController(rootView: SettingsScreen())
13 | self.contentViewController = viewController
14 | viewController.preferredContentSize = viewController.view.fittingSize
15 | }
16 |
17 | private var onCloseCallback: ((SettingsWindowController) -> Void)?
18 |
19 | func onClose(perform block: @escaping (SettingsWindowController) -> Void) {
20 | self.onCloseCallback = block
21 | }
22 |
23 | override func showWindow(_ sender: Any?) {
24 | NSApp.setActivationPolicy(.regular)
25 |
26 | super.showWindow(sender)
27 |
28 | window?.center()
29 |
30 | NSApp.activate(ignoringOtherApps: true)
31 | }
32 |
33 | func windowWillClose(_ notification: Notification) {
34 | NSApp.setActivationPolicy(.accessory)
35 |
36 | onCloseCallback?(self)
37 | }
38 | }
39 |
40 | extension KeyboardShortcuts.Name {
41 | static let toggleMonoAudio = Self("toggleMonoAudio")
42 | }
43 |
44 | struct SettingsScreen: View {
45 | @ObservedObject private var launchAtLogin = LaunchAtLogin.observable
46 | @ObservedObject private var settings = Settings.current
47 |
48 | var body: some View {
49 | Form {
50 | Toggle("Launch at login", isOn: $launchAtLogin.isEnabled)
51 | KeyboardShortcuts.Recorder("Keyboard Shortcut", name: .toggleMonoAudio)
52 | Toggle("Enable Feedback Sound", isOn: $settings.soundFeedbackEnabled)
53 | }
54 | .formStyle(.grouped)
55 | .frame(minWidth: 200, maxWidth: .infinity, minHeight: 160, maxHeight: .infinity)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Unsterificator/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "notification40.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "notification60.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "settings58.png",
17 | "idiom" : "iphone",
18 | "scale" : "2x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "settings87.png",
23 | "idiom" : "iphone",
24 | "scale" : "3x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "spotlight80.png",
29 | "idiom" : "iphone",
30 | "scale" : "2x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "filename" : "spotlight120.png",
35 | "idiom" : "iphone",
36 | "scale" : "3x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "iphone120.png",
41 | "idiom" : "iphone",
42 | "scale" : "2x",
43 | "size" : "60x60"
44 | },
45 | {
46 | "filename" : "iphone180.png",
47 | "idiom" : "iphone",
48 | "scale" : "3x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "ipadNotification20.png",
53 | "idiom" : "ipad",
54 | "scale" : "1x",
55 | "size" : "20x20"
56 | },
57 | {
58 | "filename" : "ipadNotification40.png",
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "ipadSettings29.png",
65 | "idiom" : "ipad",
66 | "scale" : "1x",
67 | "size" : "29x29"
68 | },
69 | {
70 | "filename" : "ipadSettings58.png",
71 | "idiom" : "ipad",
72 | "scale" : "2x",
73 | "size" : "29x29"
74 | },
75 | {
76 | "filename" : "ipadSpotlight40.png",
77 | "idiom" : "ipad",
78 | "scale" : "1x",
79 | "size" : "40x40"
80 | },
81 | {
82 | "filename" : "ipadSpotlight80.png",
83 | "idiom" : "ipad",
84 | "scale" : "2x",
85 | "size" : "40x40"
86 | },
87 | {
88 | "filename" : "ipad76.png",
89 | "idiom" : "ipad",
90 | "scale" : "1x",
91 | "size" : "76x76"
92 | },
93 | {
94 | "filename" : "ipad152.png",
95 | "idiom" : "ipad",
96 | "scale" : "2x",
97 | "size" : "76x76"
98 | },
99 | {
100 | "filename" : "ipadPro167.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "83.5x83.5"
104 | },
105 | {
106 | "filename" : "appstore1024.png",
107 | "idiom" : "ios-marketing",
108 | "scale" : "1x",
109 | "size" : "1024x1024"
110 | },
111 | {
112 | "filename" : "mac16.png",
113 | "idiom" : "mac",
114 | "scale" : "1x",
115 | "size" : "16x16"
116 | },
117 | {
118 | "filename" : "mac32.png",
119 | "idiom" : "mac",
120 | "scale" : "2x",
121 | "size" : "16x16"
122 | },
123 | {
124 | "filename" : "mac32.png",
125 | "idiom" : "mac",
126 | "scale" : "1x",
127 | "size" : "32x32"
128 | },
129 | {
130 | "filename" : "mac64.png",
131 | "idiom" : "mac",
132 | "scale" : "2x",
133 | "size" : "32x32"
134 | },
135 | {
136 | "filename" : "mac128.png",
137 | "idiom" : "mac",
138 | "scale" : "1x",
139 | "size" : "128x128"
140 | },
141 | {
142 | "filename" : "mac256.png",
143 | "idiom" : "mac",
144 | "scale" : "2x",
145 | "size" : "128x128"
146 | },
147 | {
148 | "filename" : "mac256.png",
149 | "idiom" : "mac",
150 | "scale" : "1x",
151 | "size" : "256x256"
152 | },
153 | {
154 | "filename" : "mac512.png",
155 | "idiom" : "mac",
156 | "scale" : "2x",
157 | "size" : "256x256"
158 | },
159 | {
160 | "filename" : "mac512.png",
161 | "idiom" : "mac",
162 | "scale" : "1x",
163 | "size" : "512x512"
164 | },
165 | {
166 | "filename" : "mac1024.png",
167 | "idiom" : "mac",
168 | "scale" : "2x",
169 | "size" : "512x512"
170 | }
171 | ],
172 | "info" : {
173 | "author" : "xcode",
174 | "version" : 1
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/Unsterificator/StatusItemController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatusItemController.swift
3 | // Unsterificator
4 | //
5 | // Created by Guilherme Rambo on 17/05/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import KeyboardShortcuts
11 |
12 | final class StatusItemController: NSObject {
13 |
14 | private lazy var unsterificator: Unsterificator = Unsterificator()
15 |
16 | private var statusItem: NSStatusItem!
17 |
18 | private var imageForCurrentState: NSImage {
19 | if unsterificator.isUnsterified {
20 | return .statusItemMono
21 | } else {
22 | return .statusItemStereo
23 | }
24 | }
25 |
26 | private var descriptionForCurrentState: String {
27 | if unsterificator.isUnsterified {
28 | return "playing stereo as mono"
29 | } else {
30 | return "stereo"
31 | }
32 | }
33 |
34 | private lazy var menu: StatusItemMenu = {
35 | StatusItemMenu(actions: self)
36 | }()
37 |
38 | func install() {
39 | statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
40 |
41 | guard let button = statusItem.button else {
42 | assertionFailure("NSStatusItem has no button!")
43 | return
44 | }
45 |
46 | button.target = self
47 | button.action = #selector(menuIconClicked)
48 | button.sendAction(on: [.leftMouseUp, .rightMouseUp])
49 |
50 | updateStatus()
51 |
52 | unsterificator.settingDidChangeExternally = { [weak self] in
53 | self?.updateStatus()
54 | }
55 |
56 | KeyboardShortcuts.onKeyUp(for: .toggleMonoAudio) { [weak self] in
57 | guard let self else { return }
58 | shortcutToggle()
59 | }
60 | }
61 |
62 | private func updateStatus() {
63 | statusItem.button?.image = imageForCurrentState
64 | statusItem.button?.toolTip = "Unsterificator (\(descriptionForCurrentState))"
65 | }
66 |
67 | @objc private func menuIconClicked(_ sender: Any?) {
68 | guard let event = NSApp.currentEvent else { return }
69 |
70 | if event.shouldShowMenu {
71 | showMenu(sender)
72 | } else {
73 | toggle(sender)
74 | }
75 | }
76 |
77 | private func showMenu(_ sender: Any?) {
78 | guard let referenceView = sender as? NSView else { return }
79 |
80 | let point = NSPoint(x: 0, y: referenceView.bounds.maxY + 4)
81 | menu.popUp(positioning: nil, at: point, in: referenceView)
82 | }
83 |
84 | private func toggle(_ sender: Any?) {
85 | unsterificator.isUnsterified = !unsterificator.isUnsterified
86 |
87 | updateStatus()
88 | }
89 |
90 | private func shortcutToggle() {
91 | flashStatusItem()
92 |
93 | toggle(nil)
94 |
95 | let feedback: AudioFeedback = unsterificator.isUnsterified ? .mono : .stereo
96 | feedback.play()
97 | }
98 |
99 | private func flashStatusItem() {
100 | statusItem.button?.isHighlighted = true
101 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
102 | self.statusItem.button?.isHighlighted = false
103 | }
104 | }
105 |
106 | // MARK: - Settings Window
107 |
108 | private var settingsController: NSWindowController?
109 |
110 | func showSettingsWindow() {
111 | if let settingsController {
112 | settingsController.showWindow(nil)
113 | return
114 | }
115 |
116 | let wc = SettingsWindowController()
117 | wc.onClose { [weak self] _ in
118 | self?.settingsController = nil
119 | }
120 | self.settingsController = wc
121 | wc.showWindow(nil)
122 | }
123 |
124 | }
125 |
126 | fileprivate extension NSEvent {
127 |
128 | var shouldShowMenu: Bool {
129 | return modifierFlags.contains(.control)
130 | || modifierFlags.contains(.option)
131 | || type == .rightMouseUp
132 | }
133 |
134 | }
135 |
136 | extension StatusItemController: StatusItemMenuActions {
137 | func presentSettings(_ sender: NSMenuItem) {
138 | RunLoop.current.perform(inModes: [.default]) {
139 | self.showSettingsWindow()
140 | }
141 | }
142 |
143 | func terminate(_ sender: NSMenuItem) {
144 | NSApp.terminate(sender)
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/Unsterificator.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 54;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | DD51576B1ECC78B900BFD5CD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD51576A1ECC78B900BFD5CD /* AppDelegate.swift */; };
11 | DD51576F1ECC78B900BFD5CD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DD51576E1ECC78B900BFD5CD /* Assets.xcassets */; };
12 | DD5157721ECC78B900BFD5CD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DD5157701ECC78B900BFD5CD /* Main.storyboard */; };
13 | DD51577B1ECC78F900BFD5CD /* UniversalAccess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD51577A1ECC78F900BFD5CD /* UniversalAccess.framework */; };
14 | DD51577E1ECC79AB00BFD5CD /* StatusItemController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD51577D1ECC79AB00BFD5CD /* StatusItemController.swift */; };
15 | DD5157841ECC7BE500BFD5CD /* Unsterificator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5157831ECC7BE500BFD5CD /* Unsterificator.swift */; };
16 | F4E155432BB5AB40008354CD /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = F4E155422BB5AB40008354CD /* LaunchAtLogin */; };
17 | F4E155462BB5AB7F008354CD /* NSMenu+.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4E155452BB5AB7F008354CD /* NSMenu+.swift */; };
18 | F4E1554A2BB5AC47008354CD /* StatusItemMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4E155492BB5AC47008354CD /* StatusItemMenu.swift */; };
19 | F4E1554C2BB5B1B8008354CD /* SettingsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4E1554B2BB5B1B8008354CD /* SettingsWindowController.swift */; };
20 | F4E155512BB5B445008354CD /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = F4E155502BB5B445008354CD /* KeyboardShortcuts */; };
21 | F4E155532BB5BCA2008354CD /* Feedback-Stereo.caf in Resources */ = {isa = PBXBuildFile; fileRef = F4E155522BB5BCA2008354CD /* Feedback-Stereo.caf */; };
22 | F4E155552BB5BCAB008354CD /* AudioFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4E155542BB5BCAB008354CD /* AudioFeedback.swift */; };
23 | F4E1555C2BB5BDAD008354CD /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4E1555B2BB5BDAD008354CD /* Settings.swift */; };
24 | F4E1555E2BB5BEBA008354CD /* Feedback-Mono.caf in Resources */ = {isa = PBXBuildFile; fileRef = F4E1555D2BB5BEBA008354CD /* Feedback-Mono.caf */; };
25 | /* End PBXBuildFile section */
26 |
27 | /* Begin PBXFileReference section */
28 | DD5157671ECC78B900BFD5CD /* Unsterificator.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Unsterificator.app; sourceTree = BUILT_PRODUCTS_DIR; };
29 | DD51576A1ECC78B900BFD5CD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
30 | DD51576E1ECC78B900BFD5CD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
31 | DD5157711ECC78B900BFD5CD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
32 | DD5157731ECC78B900BFD5CD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
33 | DD51577A1ECC78F900BFD5CD /* UniversalAccess.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniversalAccess.framework; path = ../../../../../System/Library/PrivateFrameworks/UniversalAccess.framework; sourceTree = ""; };
34 | DD51577C1ECC791200BFD5CD /* Unsterificator_Bridging_Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Unsterificator_Bridging_Header.h; sourceTree = ""; };
35 | DD51577D1ECC79AB00BFD5CD /* StatusItemController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusItemController.swift; sourceTree = ""; };
36 | DD5157831ECC7BE500BFD5CD /* Unsterificator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Unsterificator.swift; sourceTree = ""; };
37 | F4E155452BB5AB7F008354CD /* NSMenu+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMenu+.swift"; sourceTree = ""; };
38 | F4E155492BB5AC47008354CD /* StatusItemMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemMenu.swift; sourceTree = ""; };
39 | F4E1554B2BB5B1B8008354CD /* SettingsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsWindowController.swift; sourceTree = ""; };
40 | F4E155522BB5BCA2008354CD /* Feedback-Stereo.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Feedback-Stereo.caf"; sourceTree = ""; };
41 | F4E155542BB5BCAB008354CD /* AudioFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioFeedback.swift; sourceTree = ""; };
42 | F4E1555B2BB5BDAD008354CD /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; };
43 | F4E1555D2BB5BEBA008354CD /* Feedback-Mono.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Feedback-Mono.caf"; sourceTree = ""; };
44 | F4E1555F2BB5C287008354CD /* Unsterificator.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Unsterificator.entitlements; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | DD5157641ECC78B900BFD5CD /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | F4E155432BB5AB40008354CD /* LaunchAtLogin in Frameworks */,
53 | DD51577B1ECC78F900BFD5CD /* UniversalAccess.framework in Frameworks */,
54 | F4E155512BB5B445008354CD /* KeyboardShortcuts in Frameworks */,
55 | );
56 | runOnlyForDeploymentPostprocessing = 0;
57 | };
58 | /* End PBXFrameworksBuildPhase section */
59 |
60 | /* Begin PBXGroup section */
61 | DD51575E1ECC78B900BFD5CD = {
62 | isa = PBXGroup;
63 | children = (
64 | DD5157691ECC78B900BFD5CD /* Unsterificator */,
65 | DD5157681ECC78B900BFD5CD /* Products */,
66 | DD5157791ECC78F800BFD5CD /* Frameworks */,
67 | );
68 | sourceTree = "";
69 | };
70 | DD5157681ECC78B900BFD5CD /* Products */ = {
71 | isa = PBXGroup;
72 | children = (
73 | DD5157671ECC78B900BFD5CD /* Unsterificator.app */,
74 | );
75 | name = Products;
76 | sourceTree = "";
77 | };
78 | DD5157691ECC78B900BFD5CD /* Unsterificator */ = {
79 | isa = PBXGroup;
80 | children = (
81 | F4E1555F2BB5C287008354CD /* Unsterificator.entitlements */,
82 | DD5157821ECC79C300BFD5CD /* UI */,
83 | DD5157801ECC79B700BFD5CD /* Bootstrap */,
84 | DD51577F1ECC79AF00BFD5CD /* Resources */,
85 | DD5157811ECC79BB00BFD5CD /* Core */,
86 | );
87 | path = Unsterificator;
88 | sourceTree = "";
89 | };
90 | DD5157791ECC78F800BFD5CD /* Frameworks */ = {
91 | isa = PBXGroup;
92 | children = (
93 | DD51577A1ECC78F900BFD5CD /* UniversalAccess.framework */,
94 | );
95 | name = Frameworks;
96 | sourceTree = "";
97 | };
98 | DD51577F1ECC79AF00BFD5CD /* Resources */ = {
99 | isa = PBXGroup;
100 | children = (
101 | F4E155522BB5BCA2008354CD /* Feedback-Stereo.caf */,
102 | F4E1555D2BB5BEBA008354CD /* Feedback-Mono.caf */,
103 | DD51576E1ECC78B900BFD5CD /* Assets.xcassets */,
104 | DD5157701ECC78B900BFD5CD /* Main.storyboard */,
105 | DD5157731ECC78B900BFD5CD /* Info.plist */,
106 | );
107 | name = Resources;
108 | sourceTree = "";
109 | };
110 | DD5157801ECC79B700BFD5CD /* Bootstrap */ = {
111 | isa = PBXGroup;
112 | children = (
113 | F4E1555B2BB5BDAD008354CD /* Settings.swift */,
114 | DD51576A1ECC78B900BFD5CD /* AppDelegate.swift */,
115 | );
116 | name = Bootstrap;
117 | sourceTree = "";
118 | };
119 | DD5157811ECC79BB00BFD5CD /* Core */ = {
120 | isa = PBXGroup;
121 | children = (
122 | DD51577C1ECC791200BFD5CD /* Unsterificator_Bridging_Header.h */,
123 | DD5157831ECC7BE500BFD5CD /* Unsterificator.swift */,
124 | );
125 | name = Core;
126 | sourceTree = "";
127 | };
128 | DD5157821ECC79C300BFD5CD /* UI */ = {
129 | isa = PBXGroup;
130 | children = (
131 | F4E155442BB5AB76008354CD /* Components */,
132 | DD51577D1ECC79AB00BFD5CD /* StatusItemController.swift */,
133 | );
134 | name = UI;
135 | sourceTree = "";
136 | };
137 | F4E155442BB5AB76008354CD /* Components */ = {
138 | isa = PBXGroup;
139 | children = (
140 | F4E155452BB5AB7F008354CD /* NSMenu+.swift */,
141 | F4E155492BB5AC47008354CD /* StatusItemMenu.swift */,
142 | F4E1554B2BB5B1B8008354CD /* SettingsWindowController.swift */,
143 | F4E155542BB5BCAB008354CD /* AudioFeedback.swift */,
144 | );
145 | name = Components;
146 | sourceTree = "";
147 | };
148 | /* End PBXGroup section */
149 |
150 | /* Begin PBXNativeTarget section */
151 | DD5157661ECC78B900BFD5CD /* Unsterificator */ = {
152 | isa = PBXNativeTarget;
153 | buildConfigurationList = DD5157761ECC78B900BFD5CD /* Build configuration list for PBXNativeTarget "Unsterificator" */;
154 | buildPhases = (
155 | DD5157631ECC78B900BFD5CD /* Sources */,
156 | DD5157641ECC78B900BFD5CD /* Frameworks */,
157 | DD5157651ECC78B900BFD5CD /* Resources */,
158 | F4E155602BB5C461008354CD /* ShellScript */,
159 | );
160 | buildRules = (
161 | );
162 | dependencies = (
163 | );
164 | name = Unsterificator;
165 | packageProductDependencies = (
166 | F4E155422BB5AB40008354CD /* LaunchAtLogin */,
167 | F4E155502BB5B445008354CD /* KeyboardShortcuts */,
168 | );
169 | productName = Unsterificator;
170 | productReference = DD5157671ECC78B900BFD5CD /* Unsterificator.app */;
171 | productType = "com.apple.product-type.application";
172 | };
173 | /* End PBXNativeTarget section */
174 |
175 | /* Begin PBXProject section */
176 | DD51575F1ECC78B900BFD5CD /* Project object */ = {
177 | isa = PBXProject;
178 | attributes = {
179 | BuildIndependentTargetsInParallel = YES;
180 | LastSwiftUpdateCheck = 0830;
181 | LastUpgradeCheck = 1530;
182 | ORGANIZATIONNAME = "Guilherme Rambo";
183 | TargetAttributes = {
184 | DD5157661ECC78B900BFD5CD = {
185 | CreatedOnToolsVersion = 8.3.2;
186 | ProvisioningStyle = Automatic;
187 | };
188 | };
189 | };
190 | buildConfigurationList = DD5157621ECC78B900BFD5CD /* Build configuration list for PBXProject "Unsterificator" */;
191 | compatibilityVersion = "Xcode 3.2";
192 | developmentRegion = en;
193 | hasScannedForEncodings = 0;
194 | knownRegions = (
195 | en,
196 | Base,
197 | );
198 | mainGroup = DD51575E1ECC78B900BFD5CD;
199 | packageReferences = (
200 | F4E155412BB5AB40008354CD /* XCRemoteSwiftPackageReference "LaunchAtLogin" */,
201 | F4E1554F2BB5B445008354CD /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */,
202 | );
203 | productRefGroup = DD5157681ECC78B900BFD5CD /* Products */;
204 | projectDirPath = "";
205 | projectRoot = "";
206 | targets = (
207 | DD5157661ECC78B900BFD5CD /* Unsterificator */,
208 | );
209 | };
210 | /* End PBXProject section */
211 |
212 | /* Begin PBXResourcesBuildPhase section */
213 | DD5157651ECC78B900BFD5CD /* Resources */ = {
214 | isa = PBXResourcesBuildPhase;
215 | buildActionMask = 2147483647;
216 | files = (
217 | F4E1555E2BB5BEBA008354CD /* Feedback-Mono.caf in Resources */,
218 | F4E155532BB5BCA2008354CD /* Feedback-Stereo.caf in Resources */,
219 | DD51576F1ECC78B900BFD5CD /* Assets.xcassets in Resources */,
220 | DD5157721ECC78B900BFD5CD /* Main.storyboard in Resources */,
221 | );
222 | runOnlyForDeploymentPostprocessing = 0;
223 | };
224 | /* End PBXResourcesBuildPhase section */
225 |
226 | /* Begin PBXShellScriptBuildPhase section */
227 | F4E155602BB5C461008354CD /* ShellScript */ = {
228 | isa = PBXShellScriptBuildPhase;
229 | alwaysOutOfDate = 1;
230 | buildActionMask = 12;
231 | files = (
232 | );
233 | inputFileListPaths = (
234 | );
235 | inputPaths = (
236 | );
237 | outputFileListPaths = (
238 | );
239 | outputPaths = (
240 | );
241 | runOnlyForDeploymentPostprocessing = 0;
242 | shellPath = /bin/sh;
243 | shellScript = "LAUNCH_AT_LOGIN_BUNDLE=\"$CODESIGNING_FOLDER_PATH/Contents/Resources/LaunchAtLogin_LaunchAtLogin.bundle\"\nif [ -d \"$LAUNCH_AT_LOGIN_BUNDLE\" ]; then\n rm -R \"$LAUNCH_AT_LOGIN_BUNDLE\"\nfi\n";
244 | };
245 | /* End PBXShellScriptBuildPhase section */
246 |
247 | /* Begin PBXSourcesBuildPhase section */
248 | DD5157631ECC78B900BFD5CD /* Sources */ = {
249 | isa = PBXSourcesBuildPhase;
250 | buildActionMask = 2147483647;
251 | files = (
252 | DD5157841ECC7BE500BFD5CD /* Unsterificator.swift in Sources */,
253 | F4E155552BB5BCAB008354CD /* AudioFeedback.swift in Sources */,
254 | F4E1555C2BB5BDAD008354CD /* Settings.swift in Sources */,
255 | F4E1554A2BB5AC47008354CD /* StatusItemMenu.swift in Sources */,
256 | F4E155462BB5AB7F008354CD /* NSMenu+.swift in Sources */,
257 | DD51577E1ECC79AB00BFD5CD /* StatusItemController.swift in Sources */,
258 | F4E1554C2BB5B1B8008354CD /* SettingsWindowController.swift in Sources */,
259 | DD51576B1ECC78B900BFD5CD /* AppDelegate.swift in Sources */,
260 | );
261 | runOnlyForDeploymentPostprocessing = 0;
262 | };
263 | /* End PBXSourcesBuildPhase section */
264 |
265 | /* Begin PBXVariantGroup section */
266 | DD5157701ECC78B900BFD5CD /* Main.storyboard */ = {
267 | isa = PBXVariantGroup;
268 | children = (
269 | DD5157711ECC78B900BFD5CD /* Base */,
270 | );
271 | name = Main.storyboard;
272 | sourceTree = "";
273 | };
274 | /* End PBXVariantGroup section */
275 |
276 | /* Begin XCBuildConfiguration section */
277 | DD5157741ECC78B900BFD5CD /* Debug */ = {
278 | isa = XCBuildConfiguration;
279 | buildSettings = {
280 | ALWAYS_SEARCH_USER_PATHS = NO;
281 | CLANG_ANALYZER_NONNULL = YES;
282 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
283 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
284 | CLANG_CXX_LIBRARY = "libc++";
285 | CLANG_ENABLE_MODULES = YES;
286 | CLANG_ENABLE_OBJC_ARC = YES;
287 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
288 | CLANG_WARN_BOOL_CONVERSION = YES;
289 | CLANG_WARN_COMMA = YES;
290 | CLANG_WARN_CONSTANT_CONVERSION = YES;
291 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
292 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
293 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
294 | CLANG_WARN_EMPTY_BODY = YES;
295 | CLANG_WARN_ENUM_CONVERSION = YES;
296 | CLANG_WARN_INFINITE_RECURSION = YES;
297 | CLANG_WARN_INT_CONVERSION = YES;
298 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
299 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
300 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
301 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
302 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
303 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
304 | CLANG_WARN_STRICT_PROTOTYPES = YES;
305 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
306 | CLANG_WARN_UNREACHABLE_CODE = YES;
307 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
308 | COPY_PHASE_STRIP = NO;
309 | DEAD_CODE_STRIPPING = YES;
310 | DEBUG_INFORMATION_FORMAT = dwarf;
311 | ENABLE_STRICT_OBJC_MSGSEND = YES;
312 | ENABLE_TESTABILITY = YES;
313 | GCC_C_LANGUAGE_STANDARD = gnu99;
314 | GCC_DYNAMIC_NO_PIC = NO;
315 | GCC_NO_COMMON_BLOCKS = YES;
316 | GCC_OPTIMIZATION_LEVEL = 0;
317 | GCC_PREPROCESSOR_DEFINITIONS = (
318 | "DEBUG=1",
319 | "$(inherited)",
320 | );
321 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
322 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
323 | GCC_WARN_UNDECLARED_SELECTOR = YES;
324 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
325 | GCC_WARN_UNUSED_FUNCTION = YES;
326 | GCC_WARN_UNUSED_VARIABLE = YES;
327 | MACOSX_DEPLOYMENT_TARGET = 10.12;
328 | MTL_ENABLE_DEBUG_INFO = YES;
329 | ONLY_ACTIVE_ARCH = YES;
330 | SDKROOT = macosx;
331 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
332 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
333 | };
334 | name = Debug;
335 | };
336 | DD5157751ECC78B900BFD5CD /* Release */ = {
337 | isa = XCBuildConfiguration;
338 | buildSettings = {
339 | ALWAYS_SEARCH_USER_PATHS = NO;
340 | CLANG_ANALYZER_NONNULL = YES;
341 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
342 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
343 | CLANG_CXX_LIBRARY = "libc++";
344 | CLANG_ENABLE_MODULES = YES;
345 | CLANG_ENABLE_OBJC_ARC = YES;
346 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
347 | CLANG_WARN_BOOL_CONVERSION = YES;
348 | CLANG_WARN_COMMA = YES;
349 | CLANG_WARN_CONSTANT_CONVERSION = YES;
350 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
351 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
352 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
353 | CLANG_WARN_EMPTY_BODY = YES;
354 | CLANG_WARN_ENUM_CONVERSION = YES;
355 | CLANG_WARN_INFINITE_RECURSION = YES;
356 | CLANG_WARN_INT_CONVERSION = YES;
357 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
358 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
359 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
360 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
361 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
362 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
363 | CLANG_WARN_STRICT_PROTOTYPES = YES;
364 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
365 | CLANG_WARN_UNREACHABLE_CODE = YES;
366 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
367 | COPY_PHASE_STRIP = NO;
368 | DEAD_CODE_STRIPPING = YES;
369 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
370 | ENABLE_NS_ASSERTIONS = NO;
371 | ENABLE_STRICT_OBJC_MSGSEND = YES;
372 | GCC_C_LANGUAGE_STANDARD = gnu99;
373 | GCC_NO_COMMON_BLOCKS = YES;
374 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
375 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
376 | GCC_WARN_UNDECLARED_SELECTOR = YES;
377 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
378 | GCC_WARN_UNUSED_FUNCTION = YES;
379 | GCC_WARN_UNUSED_VARIABLE = YES;
380 | MACOSX_DEPLOYMENT_TARGET = 10.12;
381 | MTL_ENABLE_DEBUG_INFO = NO;
382 | SDKROOT = macosx;
383 | SWIFT_COMPILATION_MODE = wholemodule;
384 | SWIFT_OPTIMIZATION_LEVEL = "-O";
385 | };
386 | name = Release;
387 | };
388 | DD5157771ECC78B900BFD5CD /* Debug */ = {
389 | isa = XCBuildConfiguration;
390 | buildSettings = {
391 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
392 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
393 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
394 | CODE_SIGN_ENTITLEMENTS = Unsterificator/Unsterificator.entitlements;
395 | COMBINE_HIDPI_IMAGES = YES;
396 | CURRENT_PROJECT_VERSION = 202;
397 | DEAD_CODE_STRIPPING = YES;
398 | DEVELOPMENT_TEAM = 8C7439RJLG;
399 | ENABLE_HARDENED_RUNTIME = YES;
400 | FRAMEWORK_SEARCH_PATHS = (
401 | "$(inherited)",
402 | "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
403 | );
404 | GENERATE_INFOPLIST_FILE = YES;
405 | INFOPLIST_FILE = Unsterificator/Info.plist;
406 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
407 | INFOPLIST_KEY_LSUIElement = YES;
408 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Buddy Software LTD. All rights reserved.";
409 | INFOPLIST_KEY_NSMainStoryboardFile = Main;
410 | INFOPLIST_KEY_NSPrincipalClass = NSApplication;
411 | LD_RUNPATH_SEARCH_PATHS = (
412 | "$(inherited)",
413 | "@executable_path/../Frameworks",
414 | );
415 | MACOSX_DEPLOYMENT_TARGET = 13.0;
416 | MARKETING_VERSION = 2.0;
417 | PRODUCT_BUNDLE_IDENTIFIER = codes.rambo.Unsterificator;
418 | PRODUCT_NAME = "$(TARGET_NAME)";
419 | SWIFT_OBJC_BRIDGING_HEADER = Unsterificator/Unsterificator_Bridging_Header.h;
420 | SWIFT_VERSION = 5.0;
421 | VERSIONING_SYSTEM = "apple-generic";
422 | };
423 | name = Debug;
424 | };
425 | DD5157781ECC78B900BFD5CD /* Release */ = {
426 | isa = XCBuildConfiguration;
427 | buildSettings = {
428 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
429 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
430 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
431 | CODE_SIGN_ENTITLEMENTS = Unsterificator/Unsterificator.entitlements;
432 | COMBINE_HIDPI_IMAGES = YES;
433 | CURRENT_PROJECT_VERSION = 202;
434 | DEAD_CODE_STRIPPING = YES;
435 | DEVELOPMENT_TEAM = 8C7439RJLG;
436 | ENABLE_HARDENED_RUNTIME = YES;
437 | FRAMEWORK_SEARCH_PATHS = (
438 | "$(inherited)",
439 | "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
440 | );
441 | GENERATE_INFOPLIST_FILE = YES;
442 | INFOPLIST_FILE = Unsterificator/Info.plist;
443 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
444 | INFOPLIST_KEY_LSUIElement = YES;
445 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Buddy Software LTD. All rights reserved.";
446 | INFOPLIST_KEY_NSMainStoryboardFile = Main;
447 | INFOPLIST_KEY_NSPrincipalClass = NSApplication;
448 | LD_RUNPATH_SEARCH_PATHS = (
449 | "$(inherited)",
450 | "@executable_path/../Frameworks",
451 | );
452 | MACOSX_DEPLOYMENT_TARGET = 13.0;
453 | MARKETING_VERSION = 2.0;
454 | PRODUCT_BUNDLE_IDENTIFIER = codes.rambo.Unsterificator;
455 | PRODUCT_NAME = "$(TARGET_NAME)";
456 | SWIFT_OBJC_BRIDGING_HEADER = Unsterificator/Unsterificator_Bridging_Header.h;
457 | SWIFT_VERSION = 5.0;
458 | VERSIONING_SYSTEM = "apple-generic";
459 | };
460 | name = Release;
461 | };
462 | /* End XCBuildConfiguration section */
463 |
464 | /* Begin XCConfigurationList section */
465 | DD5157621ECC78B900BFD5CD /* Build configuration list for PBXProject "Unsterificator" */ = {
466 | isa = XCConfigurationList;
467 | buildConfigurations = (
468 | DD5157741ECC78B900BFD5CD /* Debug */,
469 | DD5157751ECC78B900BFD5CD /* Release */,
470 | );
471 | defaultConfigurationIsVisible = 0;
472 | defaultConfigurationName = Release;
473 | };
474 | DD5157761ECC78B900BFD5CD /* Build configuration list for PBXNativeTarget "Unsterificator" */ = {
475 | isa = XCConfigurationList;
476 | buildConfigurations = (
477 | DD5157771ECC78B900BFD5CD /* Debug */,
478 | DD5157781ECC78B900BFD5CD /* Release */,
479 | );
480 | defaultConfigurationIsVisible = 0;
481 | defaultConfigurationName = Release;
482 | };
483 | /* End XCConfigurationList section */
484 |
485 | /* Begin XCRemoteSwiftPackageReference section */
486 | F4E155412BB5AB40008354CD /* XCRemoteSwiftPackageReference "LaunchAtLogin" */ = {
487 | isa = XCRemoteSwiftPackageReference;
488 | repositoryURL = "https://github.com/davedelong/LaunchAtLogin.git";
489 | requirement = {
490 | branch = main;
491 | kind = branch;
492 | };
493 | };
494 | F4E1554F2BB5B445008354CD /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */ = {
495 | isa = XCRemoteSwiftPackageReference;
496 | repositoryURL = "https://github.com/sindresorhus/KeyboardShortcuts.git";
497 | requirement = {
498 | kind = upToNextMajorVersion;
499 | minimumVersion = 2.0.0;
500 | };
501 | };
502 | /* End XCRemoteSwiftPackageReference section */
503 |
504 | /* Begin XCSwiftPackageProductDependency section */
505 | F4E155422BB5AB40008354CD /* LaunchAtLogin */ = {
506 | isa = XCSwiftPackageProductDependency;
507 | package = F4E155412BB5AB40008354CD /* XCRemoteSwiftPackageReference "LaunchAtLogin" */;
508 | productName = LaunchAtLogin;
509 | };
510 | F4E155502BB5B445008354CD /* KeyboardShortcuts */ = {
511 | isa = XCSwiftPackageProductDependency;
512 | package = F4E1554F2BB5B445008354CD /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */;
513 | productName = KeyboardShortcuts;
514 | };
515 | /* End XCSwiftPackageProductDependency section */
516 | };
517 | rootObject = DD51575F1ECC78B900BFD5CD /* Project object */;
518 | }
519 |
--------------------------------------------------------------------------------
/Unsterificator/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
--------------------------------------------------------------------------------