├── screenshot.png
├── CustomStatusBarWindow2
├── Assets.xcassets
│ ├── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── CustomStatusBarWindow2.entitlements
├── StatusBarMenuWindowController
│ ├── EventMonitor.swift
│ └── StatusBarMenuWindowController.swift
├── Info.plist
├── AppDelegate.swift
└── Base.lproj
│ └── Main.storyboard
├── CustomStatusBarWindow2.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcuserdata
│ └── inside.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── project.pbxproj
├── LICENSE
└── README.md
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/CustomStatusBarWindow/HEAD/screenshot.png
--------------------------------------------------------------------------------
/CustomStatusBarWindow2/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/CustomStatusBarWindow2.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CustomStatusBarWindow2/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/CustomStatusBarWindow2.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CustomStatusBarWindow2/CustomStatusBarWindow2.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/CustomStatusBarWindow2.xcodeproj/xcuserdata/inside.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | CustomStatusBarWindow2.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/CustomStatusBarWindow2/StatusBarMenuWindowController/EventMonitor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EventMonitor.swift
3 | // CustomStatusBarWindow2
4 | //
5 | // Created by Guilherme Rambo on 11/02/20.
6 | // Copyright © 2020 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | // Brought to you by: https://www.raywenderlich.com/450-menus-and-popovers-in-menu-bar-apps-for-macos
12 |
13 | public class EventMonitor {
14 | private var monitor: Any?
15 | private let mask: NSEvent.EventTypeMask
16 | private let handler: (NSEvent?) -> Void
17 |
18 | public init(mask: NSEvent.EventTypeMask, handler: @escaping (NSEvent?) -> Void) {
19 | self.mask = mask
20 | self.handler = handler
21 | }
22 |
23 | deinit {
24 | stop()
25 | }
26 |
27 | public func start() {
28 | monitor = NSEvent.addGlobalMonitorForEvents(matching: mask, handler: handler)
29 | }
30 |
31 | public func stop() {
32 | if monitor != nil {
33 | NSEvent.removeMonitor(monitor!)
34 | monitor = nil
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/CustomStatusBarWindow2/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | LSUIElement
26 |
27 | NSMainStoryboardFile
28 | Main
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/CustomStatusBarWindow2/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2021 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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This sample project demonstrates a custom `NSWindowController` that can be attached to a `NSStatusItem` on macOS, such that it behaves similarly to `NSPopover`, but without the popover animations and pointy bit. The code was extracted from my app [AirBuddy](https://airbuddy.app).
2 |
3 | 
4 |
5 | Creating the controller is easy, it needs a status item to be its "target", and a view controller for its contents:
6 |
7 | ```swift
8 | windowController = StatusBarMenuWindowController(
9 | statusItem: // your status item,
10 | contentViewController: // any view controller
11 | )
12 | ```
13 |
14 | **Important: the contentViewController must have its `preferredContentSize` set, the window will automatically resize itself based on that (including if it changes while visible).**
15 |
16 | To present the window attached to the status item:
17 |
18 | ```swift
19 | windowController.showWindow(sender)
20 | ```
21 |
22 | If the status item's underlying window can't be found, the window will be positioned centered on screen as a fallback.
23 |
24 | The implementation of `StatusBarMenuWindowController` also does a few things to make it behave more like system items like Control Center on macOS 11+, such as posting menu tracking notifications which cause the Menu Bar to stay expanded while it's visible on top of a full screen application.
--------------------------------------------------------------------------------
/CustomStatusBarWindow2/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // CustomStatusBarWindow2
4 | //
5 | // Created by Gui Rambo on 02/02/21.
6 | //
7 |
8 | import Cocoa
9 |
10 | @main
11 | class AppDelegate: NSObject, NSApplicationDelegate {
12 |
13 | private var statusItem: NSStatusItem!
14 |
15 | func applicationDidFinishLaunching(_ aNotification: Notification) {
16 | statusItem = NSStatusBar.system.statusItem(withLength: 28)
17 | statusItem.button?.title = "😊"
18 | statusItem.button?.action = #selector(toggleUIVisible)
19 | }
20 |
21 | private var windowController: StatusBarMenuWindowController?
22 |
23 | @objc func toggleUIVisible(_ sender: Any?) {
24 | if windowController == nil || windowController?.window?.isVisible == false {
25 | showUI(sender: sender)
26 | } else {
27 | hideUI()
28 | }
29 | }
30 |
31 | @objc func hideUI() {
32 | windowController?.close()
33 | }
34 |
35 | func showUI(sender: Any?) {
36 | if windowController == nil {
37 | windowController = StatusBarMenuWindowController(
38 | statusItem: statusItem,
39 | contentViewController: DummyContentViewController()
40 | )
41 | }
42 |
43 | windowController?.showWindow(sender)
44 | }
45 |
46 |
47 | }
48 |
49 | final class DummyContentViewController: NSViewController {
50 |
51 | convenience init() {
52 | self.init(nibName: nil, bundle: nil)
53 |
54 | preferredContentSize = NSSize(width: 290, height: 300)
55 | }
56 |
57 | override func loadView() {
58 | view = NSView()
59 | view.wantsLayer = true
60 | view.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/CustomStatusBarWindow2/StatusBarMenuWindowController/StatusBarMenuWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatusBarMenuWindowController.swift
3 | // CustomStatusBarWindow2
4 | //
5 | // Created by Gui Rambo on 02/02/21.
6 | //
7 |
8 | import Cocoa
9 | import os.log
10 |
11 | final class StatusBarMenuWindowController: NSWindowController {
12 |
13 | private let log = OSLog(subsystem: String(describing: StatusBarMenuWindowController.self), category: String(describing: StatusBarMenuWindowController.self))
14 |
15 | let statusItem: NSStatusItem?
16 |
17 | var windowWillClose: () -> Void = { }
18 |
19 | init(statusItem: NSStatusItem?, contentViewController: NSViewController) {
20 | self.statusItem = statusItem
21 |
22 | let window = NSWindow(
23 | contentRect: NSRect(x: 0, y: 0, width: 344, height: 320),
24 | styleMask: [.fullSizeContentView, .titled],
25 | backing: .buffered,
26 | defer: false,
27 | screen: statusItem?.button?.window?.screen
28 | )
29 |
30 | window.isMovable = false
31 | window.titleVisibility = .hidden
32 | window.titlebarAppearsTransparent = true
33 | window.level = .statusBar
34 | window.contentViewController = contentViewController
35 |
36 | // This doesn't look quite right on macOS < 11, hence the conditional.
37 | if #available(macOS 11.0, *) {
38 | window.isOpaque = false
39 | window.backgroundColor = .clear
40 | }
41 |
42 | super.init(window: window)
43 |
44 | window.delegate = self
45 | setupContentSizeObservation()
46 | }
47 |
48 | required init?(coder: NSCoder) {
49 | fatalError()
50 | }
51 |
52 | private var eventMonitor: EventMonitor?
53 |
54 | /// Posting this notification causes the system Menu Bar to stay put when the cursor leaves its area while over a full screen app.
55 | private func postBeginMenuTrackingNotification() {
56 | DistributedNotificationCenter.default().post(name: .init("com.apple.HIToolbox.beginMenuTrackingNotification"), object: nil)
57 | }
58 |
59 | /// Posting this notification reverses the effect of the notification above.
60 | private func postEndMenuTrackingNotification() {
61 | DistributedNotificationCenter.default().post(name: .init("com.apple.HIToolbox.endMenuTrackingNotification"), object: nil)
62 | }
63 |
64 | override func showWindow(_ sender: Any?) {
65 | postBeginMenuTrackingNotification()
66 |
67 | // Nasty, but necessary so that when our menu window shows up,
68 | // other windows from Menu Bar items go away.
69 | NSApp.activate(ignoringOtherApps: true)
70 |
71 | repositionWindow()
72 |
73 | window?.alphaValue = 1
74 |
75 | super.showWindow(sender)
76 |
77 | startMonitoringClicks()
78 | }
79 |
80 | private func startMonitoringClicks() {
81 | eventMonitor = EventMonitor(mask: [.leftMouseDown, .rightMouseDown], handler: { [weak self] event in
82 | guard let self = self else { return }
83 | self.close()
84 | })
85 | eventMonitor?.start()
86 | }
87 |
88 | override func close() {
89 | postEndMenuTrackingNotification()
90 |
91 | NSAnimationContext.beginGrouping()
92 | NSAnimationContext.current.completionHandler = {
93 | super.close()
94 |
95 | self.eventMonitor?.stop()
96 | self.eventMonitor = nil
97 | }
98 | window?.animator().alphaValue = 0
99 | NSAnimationContext.endGrouping()
100 | }
101 |
102 | // MARK: - Positioning relative to status item
103 |
104 | private struct Metrics {
105 | static let margin: CGFloat = 5
106 | }
107 |
108 | @objc func repositionWindow() {
109 | guard let referenceWindow = statusItem?.button?.window, let window = window else {
110 | os_log("Couldn't find reference window for repositioning status bar menu window, centering instead", log: self.log, type: .debug)
111 | self.window?.center()
112 | return
113 | }
114 |
115 | let width = contentViewController?.preferredContentSize.width ?? window.frame.width
116 | let height = contentViewController?.preferredContentSize.height ?? window.frame.height
117 | var x = referenceWindow.frame.origin.x + referenceWindow.frame.width / 2 - window.frame.width / 2
118 |
119 | if let screen = referenceWindow.screen {
120 | // If the window extrapolates the limits of the screen, reposition it.
121 | if (x + width) > (screen.visibleFrame.origin.x + screen.visibleFrame.width) {
122 | x = (screen.visibleFrame.origin.x + screen.visibleFrame.width) - width - Metrics.margin
123 | }
124 | }
125 |
126 | let rect = NSRect(
127 | x: x,
128 | y: referenceWindow.frame.origin.y - height - Metrics.margin,
129 | width: width,
130 | height: height
131 | )
132 |
133 | window.setFrame(rect, display: true, animate: false)
134 | }
135 |
136 | // MARK: - Auto size/position based on content controller
137 |
138 | private var contentSizeObservation: NSKeyValueObservation?
139 |
140 | override var contentViewController: NSViewController? {
141 | didSet {
142 | setupContentSizeObservation()
143 | }
144 | }
145 |
146 | private var previouslyObservedContentSize: NSSize?
147 |
148 | private func setupContentSizeObservation() {
149 | contentSizeObservation?.invalidate()
150 | contentSizeObservation = nil
151 |
152 | guard let controller = contentViewController else { return }
153 |
154 | contentSizeObservation = controller.observe(\.preferredContentSize, options: [.initial, .new]) { [weak self] controller, _ in
155 | self?.updateForNewContentSize(from: controller)
156 | }
157 | }
158 |
159 | private func updateForNewContentSize(from controller: NSViewController) {
160 | defer { previouslyObservedContentSize = controller.preferredContentSize }
161 |
162 | guard controller.preferredContentSize != previouslyObservedContentSize else { return }
163 |
164 | repositionWindow()
165 | }
166 |
167 | }
168 |
169 | // MARK: - Window delegate
170 |
171 | extension StatusBarMenuWindowController: NSWindowDelegate {
172 |
173 | func windowWillClose(_ notification: Notification) {
174 | windowWillClose()
175 | }
176 |
177 | func windowDidBecomeKey(_ notification: Notification) {
178 | statusItem?.button?.highlight(true)
179 | }
180 |
181 | func windowDidResignKey(_ notification: Notification) {
182 | statusItem?.button?.highlight(false)
183 | }
184 |
185 | }
186 |
187 |
--------------------------------------------------------------------------------
/CustomStatusBarWindow2.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | DDCE291C25C97B7E00044971 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCE291B25C97B7E00044971 /* AppDelegate.swift */; };
11 | DDCE292025C97B8300044971 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDCE291F25C97B8300044971 /* Assets.xcassets */; };
12 | DDCE292325C97B8300044971 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DDCE292125C97B8300044971 /* Main.storyboard */; };
13 | DDCE292C25C97B9800044971 /* StatusBarMenuWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCE292B25C97B9800044971 /* StatusBarMenuWindowController.swift */; };
14 | DDCE292E25C97BBE00044971 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCE292D25C97BBE00044971 /* EventMonitor.swift */; };
15 | /* End PBXBuildFile section */
16 |
17 | /* Begin PBXFileReference section */
18 | DDCE291825C97B7E00044971 /* CustomStatusBarWindow2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CustomStatusBarWindow2.app; sourceTree = BUILT_PRODUCTS_DIR; };
19 | DDCE291B25C97B7E00044971 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
20 | DDCE291F25C97B8300044971 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
21 | DDCE292225C97B8300044971 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
22 | DDCE292425C97B8300044971 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
23 | DDCE292525C97B8300044971 /* CustomStatusBarWindow2.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CustomStatusBarWindow2.entitlements; sourceTree = ""; };
24 | DDCE292B25C97B9800044971 /* StatusBarMenuWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarMenuWindowController.swift; sourceTree = ""; };
25 | DDCE292D25C97BBE00044971 /* EventMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMonitor.swift; sourceTree = ""; };
26 | /* End PBXFileReference section */
27 |
28 | /* Begin PBXFrameworksBuildPhase section */
29 | DDCE291525C97B7E00044971 /* Frameworks */ = {
30 | isa = PBXFrameworksBuildPhase;
31 | buildActionMask = 2147483647;
32 | files = (
33 | );
34 | runOnlyForDeploymentPostprocessing = 0;
35 | };
36 | /* End PBXFrameworksBuildPhase section */
37 |
38 | /* Begin PBXGroup section */
39 | DDCE290F25C97B7D00044971 = {
40 | isa = PBXGroup;
41 | children = (
42 | DDCE291A25C97B7E00044971 /* CustomStatusBarWindow2 */,
43 | DDCE291925C97B7E00044971 /* Products */,
44 | );
45 | sourceTree = "";
46 | };
47 | DDCE291925C97B7E00044971 /* Products */ = {
48 | isa = PBXGroup;
49 | children = (
50 | DDCE291825C97B7E00044971 /* CustomStatusBarWindow2.app */,
51 | );
52 | name = Products;
53 | sourceTree = "";
54 | };
55 | DDCE291A25C97B7E00044971 /* CustomStatusBarWindow2 */ = {
56 | isa = PBXGroup;
57 | children = (
58 | DDCE292F25C97BE100044971 /* StatusBarMenuWindowController */,
59 | DDCE291B25C97B7E00044971 /* AppDelegate.swift */,
60 | DDCE291F25C97B8300044971 /* Assets.xcassets */,
61 | DDCE292125C97B8300044971 /* Main.storyboard */,
62 | DDCE292425C97B8300044971 /* Info.plist */,
63 | DDCE292525C97B8300044971 /* CustomStatusBarWindow2.entitlements */,
64 | );
65 | path = CustomStatusBarWindow2;
66 | sourceTree = "";
67 | };
68 | DDCE292F25C97BE100044971 /* StatusBarMenuWindowController */ = {
69 | isa = PBXGroup;
70 | children = (
71 | DDCE292B25C97B9800044971 /* StatusBarMenuWindowController.swift */,
72 | DDCE292D25C97BBE00044971 /* EventMonitor.swift */,
73 | );
74 | path = StatusBarMenuWindowController;
75 | sourceTree = "";
76 | };
77 | /* End PBXGroup section */
78 |
79 | /* Begin PBXNativeTarget section */
80 | DDCE291725C97B7E00044971 /* CustomStatusBarWindow2 */ = {
81 | isa = PBXNativeTarget;
82 | buildConfigurationList = DDCE292825C97B8300044971 /* Build configuration list for PBXNativeTarget "CustomStatusBarWindow2" */;
83 | buildPhases = (
84 | DDCE291425C97B7E00044971 /* Sources */,
85 | DDCE291525C97B7E00044971 /* Frameworks */,
86 | DDCE291625C97B7E00044971 /* Resources */,
87 | );
88 | buildRules = (
89 | );
90 | dependencies = (
91 | );
92 | name = CustomStatusBarWindow2;
93 | productName = CustomStatusBarWindow2;
94 | productReference = DDCE291825C97B7E00044971 /* CustomStatusBarWindow2.app */;
95 | productType = "com.apple.product-type.application";
96 | };
97 | /* End PBXNativeTarget section */
98 |
99 | /* Begin PBXProject section */
100 | DDCE291025C97B7E00044971 /* Project object */ = {
101 | isa = PBXProject;
102 | attributes = {
103 | LastSwiftUpdateCheck = 1250;
104 | LastUpgradeCheck = 1250;
105 | TargetAttributes = {
106 | DDCE291725C97B7E00044971 = {
107 | CreatedOnToolsVersion = 12.5;
108 | };
109 | };
110 | };
111 | buildConfigurationList = DDCE291325C97B7E00044971 /* Build configuration list for PBXProject "CustomStatusBarWindow2" */;
112 | compatibilityVersion = "Xcode 9.3";
113 | developmentRegion = en;
114 | hasScannedForEncodings = 0;
115 | knownRegions = (
116 | en,
117 | Base,
118 | );
119 | mainGroup = DDCE290F25C97B7D00044971;
120 | productRefGroup = DDCE291925C97B7E00044971 /* Products */;
121 | projectDirPath = "";
122 | projectRoot = "";
123 | targets = (
124 | DDCE291725C97B7E00044971 /* CustomStatusBarWindow2 */,
125 | );
126 | };
127 | /* End PBXProject section */
128 |
129 | /* Begin PBXResourcesBuildPhase section */
130 | DDCE291625C97B7E00044971 /* Resources */ = {
131 | isa = PBXResourcesBuildPhase;
132 | buildActionMask = 2147483647;
133 | files = (
134 | DDCE292025C97B8300044971 /* Assets.xcassets in Resources */,
135 | DDCE292325C97B8300044971 /* Main.storyboard in Resources */,
136 | );
137 | runOnlyForDeploymentPostprocessing = 0;
138 | };
139 | /* End PBXResourcesBuildPhase section */
140 |
141 | /* Begin PBXSourcesBuildPhase section */
142 | DDCE291425C97B7E00044971 /* Sources */ = {
143 | isa = PBXSourcesBuildPhase;
144 | buildActionMask = 2147483647;
145 | files = (
146 | DDCE292C25C97B9800044971 /* StatusBarMenuWindowController.swift in Sources */,
147 | DDCE291C25C97B7E00044971 /* AppDelegate.swift in Sources */,
148 | DDCE292E25C97BBE00044971 /* EventMonitor.swift in Sources */,
149 | );
150 | runOnlyForDeploymentPostprocessing = 0;
151 | };
152 | /* End PBXSourcesBuildPhase section */
153 |
154 | /* Begin PBXVariantGroup section */
155 | DDCE292125C97B8300044971 /* Main.storyboard */ = {
156 | isa = PBXVariantGroup;
157 | children = (
158 | DDCE292225C97B8300044971 /* Base */,
159 | );
160 | name = Main.storyboard;
161 | sourceTree = "";
162 | };
163 | /* End PBXVariantGroup section */
164 |
165 | /* Begin XCBuildConfiguration section */
166 | DDCE292625C97B8300044971 /* Debug */ = {
167 | isa = XCBuildConfiguration;
168 | buildSettings = {
169 | ALWAYS_SEARCH_USER_PATHS = NO;
170 | CLANG_ANALYZER_NONNULL = YES;
171 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
172 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
173 | CLANG_CXX_LIBRARY = "libc++";
174 | CLANG_ENABLE_MODULES = YES;
175 | CLANG_ENABLE_OBJC_ARC = YES;
176 | CLANG_ENABLE_OBJC_WEAK = YES;
177 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
178 | CLANG_WARN_BOOL_CONVERSION = YES;
179 | CLANG_WARN_COMMA = YES;
180 | CLANG_WARN_CONSTANT_CONVERSION = YES;
181 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
182 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
183 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
184 | CLANG_WARN_EMPTY_BODY = YES;
185 | CLANG_WARN_ENUM_CONVERSION = YES;
186 | CLANG_WARN_INFINITE_RECURSION = YES;
187 | CLANG_WARN_INT_CONVERSION = YES;
188 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
189 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
190 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
191 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
192 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
193 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
194 | CLANG_WARN_STRICT_PROTOTYPES = YES;
195 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
196 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
197 | CLANG_WARN_UNREACHABLE_CODE = YES;
198 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
199 | COPY_PHASE_STRIP = NO;
200 | DEBUG_INFORMATION_FORMAT = dwarf;
201 | ENABLE_STRICT_OBJC_MSGSEND = YES;
202 | ENABLE_TESTABILITY = YES;
203 | GCC_C_LANGUAGE_STANDARD = gnu11;
204 | GCC_DYNAMIC_NO_PIC = NO;
205 | GCC_NO_COMMON_BLOCKS = YES;
206 | GCC_OPTIMIZATION_LEVEL = 0;
207 | GCC_PREPROCESSOR_DEFINITIONS = (
208 | "DEBUG=1",
209 | "$(inherited)",
210 | );
211 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
212 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
213 | GCC_WARN_UNDECLARED_SELECTOR = YES;
214 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
215 | GCC_WARN_UNUSED_FUNCTION = YES;
216 | GCC_WARN_UNUSED_VARIABLE = YES;
217 | MACOSX_DEPLOYMENT_TARGET = 11.1;
218 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
219 | MTL_FAST_MATH = YES;
220 | ONLY_ACTIVE_ARCH = YES;
221 | SDKROOT = macosx;
222 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
223 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
224 | };
225 | name = Debug;
226 | };
227 | DDCE292725C97B8300044971 /* Release */ = {
228 | isa = XCBuildConfiguration;
229 | buildSettings = {
230 | ALWAYS_SEARCH_USER_PATHS = NO;
231 | CLANG_ANALYZER_NONNULL = YES;
232 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
233 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
234 | CLANG_CXX_LIBRARY = "libc++";
235 | CLANG_ENABLE_MODULES = YES;
236 | CLANG_ENABLE_OBJC_ARC = YES;
237 | CLANG_ENABLE_OBJC_WEAK = YES;
238 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
239 | CLANG_WARN_BOOL_CONVERSION = YES;
240 | CLANG_WARN_COMMA = YES;
241 | CLANG_WARN_CONSTANT_CONVERSION = YES;
242 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
243 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
244 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
245 | CLANG_WARN_EMPTY_BODY = YES;
246 | CLANG_WARN_ENUM_CONVERSION = YES;
247 | CLANG_WARN_INFINITE_RECURSION = YES;
248 | CLANG_WARN_INT_CONVERSION = YES;
249 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
250 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
251 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
252 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
253 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
254 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
255 | CLANG_WARN_STRICT_PROTOTYPES = YES;
256 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
257 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
258 | CLANG_WARN_UNREACHABLE_CODE = YES;
259 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
260 | COPY_PHASE_STRIP = NO;
261 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
262 | ENABLE_NS_ASSERTIONS = NO;
263 | ENABLE_STRICT_OBJC_MSGSEND = YES;
264 | GCC_C_LANGUAGE_STANDARD = gnu11;
265 | GCC_NO_COMMON_BLOCKS = YES;
266 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
267 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
268 | GCC_WARN_UNDECLARED_SELECTOR = YES;
269 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
270 | GCC_WARN_UNUSED_FUNCTION = YES;
271 | GCC_WARN_UNUSED_VARIABLE = YES;
272 | MACOSX_DEPLOYMENT_TARGET = 11.1;
273 | MTL_ENABLE_DEBUG_INFO = NO;
274 | MTL_FAST_MATH = YES;
275 | SDKROOT = macosx;
276 | SWIFT_COMPILATION_MODE = wholemodule;
277 | SWIFT_OPTIMIZATION_LEVEL = "-O";
278 | };
279 | name = Release;
280 | };
281 | DDCE292925C97B8300044971 /* Debug */ = {
282 | isa = XCBuildConfiguration;
283 | buildSettings = {
284 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
285 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
286 | CODE_SIGN_ENTITLEMENTS = CustomStatusBarWindow2/CustomStatusBarWindow2.entitlements;
287 | CODE_SIGN_STYLE = Automatic;
288 | COMBINE_HIDPI_IMAGES = YES;
289 | DEVELOPMENT_TEAM = 8C7439RJLG;
290 | ENABLE_HARDENED_RUNTIME = YES;
291 | INFOPLIST_FILE = CustomStatusBarWindow2/Info.plist;
292 | LD_RUNPATH_SEARCH_PATHS = (
293 | "$(inherited)",
294 | "@executable_path/../Frameworks",
295 | );
296 | MACOSX_DEPLOYMENT_TARGET = 10.14;
297 | PRODUCT_BUNDLE_IDENTIFIER = codes.rambo.experiment.CustomStatusBarWindow2;
298 | PRODUCT_NAME = "$(TARGET_NAME)";
299 | SWIFT_VERSION = 5.0;
300 | };
301 | name = Debug;
302 | };
303 | DDCE292A25C97B8300044971 /* Release */ = {
304 | isa = XCBuildConfiguration;
305 | buildSettings = {
306 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
307 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
308 | CODE_SIGN_ENTITLEMENTS = CustomStatusBarWindow2/CustomStatusBarWindow2.entitlements;
309 | CODE_SIGN_STYLE = Automatic;
310 | COMBINE_HIDPI_IMAGES = YES;
311 | DEVELOPMENT_TEAM = 8C7439RJLG;
312 | ENABLE_HARDENED_RUNTIME = YES;
313 | INFOPLIST_FILE = CustomStatusBarWindow2/Info.plist;
314 | LD_RUNPATH_SEARCH_PATHS = (
315 | "$(inherited)",
316 | "@executable_path/../Frameworks",
317 | );
318 | MACOSX_DEPLOYMENT_TARGET = 10.14;
319 | PRODUCT_BUNDLE_IDENTIFIER = codes.rambo.experiment.CustomStatusBarWindow2;
320 | PRODUCT_NAME = "$(TARGET_NAME)";
321 | SWIFT_VERSION = 5.0;
322 | };
323 | name = Release;
324 | };
325 | /* End XCBuildConfiguration section */
326 |
327 | /* Begin XCConfigurationList section */
328 | DDCE291325C97B7E00044971 /* Build configuration list for PBXProject "CustomStatusBarWindow2" */ = {
329 | isa = XCConfigurationList;
330 | buildConfigurations = (
331 | DDCE292625C97B8300044971 /* Debug */,
332 | DDCE292725C97B8300044971 /* Release */,
333 | );
334 | defaultConfigurationIsVisible = 0;
335 | defaultConfigurationName = Release;
336 | };
337 | DDCE292825C97B8300044971 /* Build configuration list for PBXNativeTarget "CustomStatusBarWindow2" */ = {
338 | isa = XCConfigurationList;
339 | buildConfigurations = (
340 | DDCE292925C97B8300044971 /* Debug */,
341 | DDCE292A25C97B8300044971 /* Release */,
342 | );
343 | defaultConfigurationIsVisible = 0;
344 | defaultConfigurationName = Release;
345 | };
346 | /* End XCConfigurationList section */
347 | };
348 | rootObject = DDCE291025C97B7E00044971 /* Project object */;
349 | }
350 |
--------------------------------------------------------------------------------
/CustomStatusBarWindow2/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
--------------------------------------------------------------------------------