Skip to content

Commit aa8c27b

Browse files
erikbdevwilliambj1
andauthored
Improvements on WiFiMenuItem (#183)
- Fixed highlight issue on macOS Big Sur - Shows "Disconnect from " if auto join is not enabled - Only show "About HeliPort" if Options + WiFi icon is pressed, was a request but not necessary. - Minor fixes with WiFiMenuItemView. - Improved showing disconnect menu only if is connected, auto join is not enabled, or if options is pressed and there is a network connection. - Fixed minor issue with Log. - Removed colon from "Disconnect from " so it can be consistent with localizations. Co-authored-by: Bat.bat <[email protected]>
1 parent c5eda6f commit aa8c27b

File tree

3 files changed

+175
-90
lines changed

3 files changed

+175
-90
lines changed

HeliPort/Appearance/StatusBarIconManager.swift

+17-2
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,10 @@ class StatusBarIcon: NSObject {
7878
statusBar.button?.image = #imageLiteral(resourceName: "WiFiStateError")
7979
}
8080

81-
class func signalStrength(RSSI: Int16) {
81+
class func signalStrength(rssi: Int16) {
8282
timer?.invalidate()
8383
timer = nil
84-
let signalImage = WifiMenuItemView.getRssiImage(Int(RSSI))
84+
let signalImage = getRssiImage(rssi)
8585
statusBar.button?.image = signalImage
8686
}
8787

@@ -107,4 +107,19 @@ class StatusBarIcon: NSObject {
107107
}
108108
}
109109
}
110+
111+
class func getRssiImage(_ RSSI: Int16) -> NSImage? {
112+
var signalImageName: NSImage
113+
switch RSSI {
114+
case ..<(-100):
115+
signalImageName = #imageLiteral(resourceName: "WiFiStateScanning1")
116+
case ..<(-80):
117+
signalImageName = #imageLiteral(resourceName: "WiFiSignalStrengthFair")
118+
case ..<(-60):
119+
signalImageName = #imageLiteral(resourceName: "WiFiSignalStrengthGood")
120+
default:
121+
signalImageName = #imageLiteral(resourceName: "WiFiStateOn")
122+
}
123+
return signalImageName
124+
}
110125
}

HeliPort/Appearance/StatusMenu.swift

+15-4
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ final class StatusMenu: NSMenu, NSMenuDelegate {
5555
get_station_info(&staInfo)
5656
DispatchQueue.main.async {
5757
guard isReachable else { StatusBarIcon.warning(); return }
58-
StatusBarIcon.signalStrength(RSSI: staInfo.rssi)
58+
StatusBarIcon.signalStrength(rssi: staInfo.rssi)
5959
}
6060
}
6161
case ITL80211_S_SCAN:
@@ -81,6 +81,7 @@ final class StatusMenu: NSMenu, NSMenuDelegate {
8181
toggleLaunchItem,
8282
checkUpdateItem,
8383
quitSeparator,
84+
aboutItem,
8485
quitItem
8586
]
8687

@@ -119,7 +120,7 @@ final class StatusMenu: NSMenu, NSMenuDelegate {
119120

120121
hiddenItems.forEach { $0.isHidden = !visible }
121122
enabledNetworkCardItems.forEach { $0.isHidden = !isNetworkCardAvailable }
122-
connectedNetworkInfoItems.forEach { $0.isHidden = !(visible && status == ITL80211_S_RUN) }
123+
connectedNetworkInfoItems.forEach { $0.isHidden = !(visible && self.isNetworkConnected) }
123124
notImplementedItems.forEach { $0.isHidden = true }
124125
}
125126
}
@@ -490,7 +491,11 @@ final class StatusMenu: NSMenu, NSMenuDelegate {
490491
var nss: String = .unavailable
491492
self.isNetworkConnected = false
492493
var staInfo = station_info_t()
494+
var entity: NetworkInfoStorageEntity?
495+
var hideDisconnect = true
493496
if self.status == ITL80211_S_RUN && get_station_info(&staInfo) == KERN_SUCCESS {
497+
entity = CredentialsManager.instance.getStorageFromSsid(String(cString: &staInfo.ssid.0))
498+
hideDisconnect = entity?.autoJoin ?? false
494499
self.isNetworkConnected = true
495500
let bsd = String(self.bsdItem.title).replacingOccurrences(of: String.interfaceName, with: "",
496501
options: .regularExpression, range: nil)
@@ -516,9 +521,14 @@ final class StatusMenu: NSMenu, NSMenuDelegate {
516521
noise = "\(staInfo.noise) dBm"
517522
txRate = "\(staInfo.rate) Mbps"
518523
phyMode = staInfo.op_mode.description
519-
mcsIndex = String(staInfo.cur_mcs)
524+
mcsIndex = "\(staInfo.cur_mcs)"
520525
nss = .unknown
521526
}
527+
528+
if self.showAllOptions && self.isNetworkConnected {
529+
hideDisconnect = false
530+
}
531+
522532
DispatchQueue.main.async {
523533
self.disconnectItem.title = .disconnectNet + disconnectName
524534
self.ipAddresssItem.title = .ipAddr + ipAddr
@@ -534,6 +544,7 @@ final class StatusMenu: NSMenu, NSMenuDelegate {
534544
self.phyModeItem.title = .phyModeStr + phyMode
535545
self.mcsIndexItem.title = .mcsStr + mcsIndex
536546
self.nssItem.title = .nssStr + nss
547+
self.disconnectItem.isHidden = hideDisconnect
537548
guard self.isNetworkCardEnabled,
538549
let wifiItemView = self.networkItemList.first?.view as? WifiMenuItemView else { return }
539550
wifiItemView.visible = self.isNetworkConnected
@@ -611,7 +622,7 @@ private extension String {
611622
static let aboutHeliport = NSLocalizedString("About HeliPort")
612623
static let quitHeliport = NSLocalizedString("Quit HeliPort")
613624
static let launchLogin = NSLocalizedString("Launch At Login")
614-
static let disconnectNet = NSLocalizedString("Disconnect from: ")
625+
static let disconnectNet = NSLocalizedString("Disconnect from ")
615626
static let ipAddr = NSLocalizedString(" IP Address: ")
616627
static let routerStr = NSLocalizedString(" Router: ")
617628
static let internetStr = NSLocalizedString(" Internet: ")

HeliPort/Appearance/WiFiMenuItemView.swift

+143-84
Original file line numberDiff line numberDiff line change
@@ -18,126 +18,192 @@ import Cocoa
1818

1919
class WifiMenuItemView: NSView {
2020

21-
let statusImage: NSImageView = {
21+
// MARK: Initializers
22+
23+
private var currentWindow: NSWindow?
24+
private var heightConstraint: NSLayoutConstraint!
25+
26+
private let menuBarHeight: CGFloat = {
27+
if #available(macOS 11, *) {
28+
return 22
29+
} else {
30+
return 19
31+
}
32+
}()
33+
34+
private let effectView: NSVisualEffectView = {
35+
let effectView = NSVisualEffectView()
36+
effectView.material = .popover
37+
effectView.state = .active
38+
effectView.isEmphasized = true
39+
effectView.blendingMode = .behindWindow
40+
return effectView
41+
}()
42+
43+
private let statusImage: NSImageView = {
2244
let statusImage = NSImageView()
23-
statusImage.image = NSImage(named: NSImage.menuOnStateTemplateName)
24-
statusImage.image?.isTemplate = true
25-
statusImage.translatesAutoresizingMaskIntoConstraints = false
2645
statusImage.setContentHuggingPriority(.defaultHigh, for: .horizontal)
2746
statusImage.isHidden = true
28-
return statusImage
29-
}()
3047

31-
let ssidLabel: NSTextField = {
32-
let ssidLabel = NSTextField()
33-
ssidLabel.isBordered = false
34-
ssidLabel.usesSingleLineMode = true
35-
ssidLabel.maximumNumberOfLines = 1
36-
ssidLabel.drawsBackground = false
37-
ssidLabel.isEditable = false
38-
ssidLabel.isSelectable = false
39-
ssidLabel.font = NSFont.systemFont(ofSize: 14)
40-
ssidLabel.translatesAutoresizingMaskIntoConstraints = false
41-
return ssidLabel
48+
if #available(OSX 11.0, *) {
49+
statusImage.image = NSImage(named: NSImage.menuOnStateTemplateName)?
50+
.withSymbolConfiguration(NSImage.SymbolConfiguration(pointSize: 13,
51+
weight: .bold,
52+
scale: .small))
53+
} else {
54+
statusImage.image = NSImage(named: NSImage.menuOnStateTemplateName)
55+
}
56+
57+
return statusImage
4258
}()
4359

44-
let lockImage: NSImageView = {
60+
private let lockImage: NSImageView = {
4561
let lockImage = NSImageView()
46-
lockImage.image = NSImage.init(named: NSImage.lockLockedTemplateName)
47-
lockImage.image?.isTemplate = true
48-
lockImage.translatesAutoresizingMaskIntoConstraints = false
4962
lockImage.setContentHuggingPriority(.defaultHigh, for: .horizontal)
63+
64+
if #available(OSX 11.0, *) {
65+
lockImage.image = NSImage(named: NSImage.lockLockedTemplateName)?
66+
.withSymbolConfiguration(NSImage.SymbolConfiguration(pointSize: 14,
67+
weight: .semibold,
68+
scale: .medium))
69+
} else {
70+
lockImage.image = NSImage(named: NSImage.lockLockedTemplateName)
71+
}
72+
5073
return lockImage
5174
}()
5275

53-
let signalImage: NSImageView = {
76+
private let signalImage: NSImageView = {
5477
let signalImage = NSImageView()
55-
signalImage.image?.isTemplate = true
56-
signalImage.translatesAutoresizingMaskIntoConstraints = false
5778
signalImage.setContentHuggingPriority(.defaultHigh, for: .horizontal)
5879
return signalImage
5980
}()
6081

61-
var isMouseOver: Bool = false {
62-
willSet(hover) {
63-
(superview as? NSVisualEffectView)?.material = hover ? .selection : .popover
64-
(superview as? NSVisualEffectView)?.isEmphasized = hover
65-
ssidLabel.textColor = hover ? .white : .textColor
66-
if #available(OSX 10.14, *) {
67-
statusImage.contentTintColor = hover ? .white : .textColor
68-
lockImage.contentTintColor = hover ? .white : .textColor
69-
signalImage.contentTintColor = hover ? .white : .textColor
70-
}
82+
private let ssidLabel: NSTextField = {
83+
let ssidLabel = NSTextField(labelWithString: "")
84+
85+
if #available(macOS 11, *) {
86+
ssidLabel.font = NSFont.menuFont(ofSize: 0)
87+
} else {
88+
ssidLabel.font = NSFont.systemFont(ofSize: 14)
89+
}
90+
91+
return ssidLabel
92+
}()
93+
94+
public init(networkInfo: NetworkInfo) {
95+
self.networkInfo = networkInfo
96+
super.init(frame: NSRect(x: 0, y: 0, width: 0, height: menuBarHeight))
97+
98+
self.addSubview(effectView)
99+
effectView.addSubview(statusImage)
100+
effectView.addSubview(ssidLabel)
101+
effectView.addSubview(lockImage)
102+
effectView.addSubview(signalImage)
103+
104+
setupLayout()
105+
}
106+
107+
required init?(coder: NSCoder) {
108+
fatalError("init(coder:) has not been implemented")
109+
}
110+
111+
// MARK: Public
112+
113+
public var networkInfo: NetworkInfo {
114+
willSet(networkInfo) {
115+
ssidLabel.stringValue = networkInfo.ssid
116+
lockImage.isHidden = networkInfo.auth.security == ITL80211_SECURITY_NONE
117+
signalImage.image = StatusBarIcon.getRssiImage(Int16(networkInfo.rssi))
118+
layoutSubtreeIfNeeded()
71119
}
72120
}
73121

74-
var visible: Bool = true {
122+
public var visible: Bool = true {
75123
willSet(visible) {
76-
heightConstraint.constant = visible ? 19 : 0
124+
isHidden = !visible
125+
heightConstraint.constant = visible ? menuBarHeight : 0
77126
layoutSubtreeIfNeeded()
78127
}
79128
}
80129

81-
var connected: Bool = false {
130+
public var connected: Bool = false {
82131
willSet(connected) {
83132
statusImage.isHidden = !connected
84133
}
85134
}
86135

87-
var currentWindow: NSWindow?
136+
public func checkHighlight() {
137+
if visible, let position = currentWindow?.mouseLocationOutsideOfEventStream {
138+
isMouseOver = bounds.contains(convert(position, from: nil))
139+
}
140+
}
141+
142+
// MARK: Private
88143

89-
var networkInfo: NetworkInfo {
90-
willSet(networkInfo) {
91-
ssidLabel.stringValue = networkInfo.ssid
92-
lockImage.isHidden = networkInfo.auth.security == ITL80211_SECURITY_NONE
93-
signalImage.image = WifiMenuItemView.getRssiImage(networkInfo.rssi)
94-
layoutSubtreeIfNeeded()
144+
private var isMouseOver: Bool = false {
145+
willSet(hover) {
146+
effectView.material = hover ? .selection : .popover
147+
effectView.isEmphasized = hover
148+
149+
ssidLabel.textColor = hover ? .selectedMenuItemTextColor : .controlTextColor
150+
151+
statusImage.cell?.backgroundStyle = hover ? .emphasized : .normal
152+
lockImage.cell?.backgroundStyle = hover ? .emphasized : .normal
153+
signalImage.cell?.backgroundStyle = hover ? .emphasized : .normal
95154
}
96155
}
97156

98-
var heightConstraint: NSLayoutConstraint!
157+
private func setupLayout() {
158+
159+
let effectPadding: CGFloat
160+
let statusPadding: CGFloat
161+
let statusWidth: CGFloat
162+
let lockWidth: CGFloat
163+
if #available(macOS 11, *) {
164+
effectView.wantsLayer = true
165+
effectView.layer?.cornerRadius = 4
166+
effectView.layer?.masksToBounds = true
167+
effectPadding = 5
168+
statusPadding = 10
169+
statusWidth = 15
170+
lockWidth = 16
171+
} else {
172+
effectPadding = 0
173+
statusPadding = 6
174+
statusWidth = 12
175+
lockWidth = 10
176+
}
99177

100-
func setupLayout() {
101-
heightConstraint = heightAnchor.constraint(equalToConstant: 19)
178+
heightConstraint = heightAnchor.constraint(equalToConstant: menuBarHeight)
102179
heightConstraint.priority = NSLayoutConstraint.Priority(rawValue: 1000)
103180
heightConstraint.isActive = true
104181

105182
statusImage.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
106-
statusImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 6).isActive = true
107-
statusImage.widthAnchor.constraint(equalToConstant: 12).isActive = true
183+
statusImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: statusPadding).isActive = true
184+
statusImage.widthAnchor.constraint(equalToConstant: statusWidth).isActive = true
108185

109186
ssidLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
110187
ssidLabel.leadingAnchor.constraint(equalTo: statusImage.trailingAnchor, constant: 3).isActive = true
111-
ssidLabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
112-
ssidLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
113188

114189
lockImage.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
115190
lockImage.leadingAnchor.constraint(equalTo: ssidLabel.trailingAnchor, constant: 10).isActive = true
116-
lockImage.widthAnchor.constraint(equalToConstant: 10).isActive = true
191+
lockImage.widthAnchor.constraint(equalToConstant: lockWidth).isActive = true
117192

118193
signalImage.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 1).isActive = true
119194
signalImage.leadingAnchor.constraint(equalTo: lockImage.trailingAnchor, constant: 12).isActive = true
120195
signalImage.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -12).isActive = true
121196
signalImage.widthAnchor.constraint(equalToConstant: 18).isActive = true
122-
}
123197

124-
init(networkInfo: NetworkInfo) {
125-
self.networkInfo = networkInfo
126-
super.init(frame: NSRect.zero)
127-
128-
self.addSubview(statusImage)
129-
self.addSubview(ssidLabel)
130-
self.addSubview(lockImage)
131-
self.addSubview(signalImage)
132-
133-
setupLayout()
198+
effectView.translatesAutoresizingMaskIntoConstraints = false
199+
effectView.subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
200+
effectView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: effectPadding).isActive = true
201+
effectView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -effectPadding).isActive = true
202+
effectView.topAnchor.constraint(equalTo: topAnchor).isActive = true
203+
effectView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
134204
}
135205

136-
func checkHighlight() {
137-
if visible, let position = currentWindow?.mouseLocationOutsideOfEventStream {
138-
isMouseOver = bounds.contains(convert(position, from: nil))
139-
}
140-
}
206+
// MARK: Overrides
141207

142208
override func mouseUp(with event: NSEvent) {
143209
isMouseOver = false // NSWindow pop up could escape mouseExit
@@ -159,22 +225,15 @@ class WifiMenuItemView: NSView {
159225
checkHighlight()
160226
}
161227

162-
required init?(coder: NSCoder) {
163-
fatalError("init(coder:) has not been implemented")
164-
}
165-
166-
class func getRssiImage(_ RSSI: Int) -> NSImage? {
167-
var signalImageName: NSImage
168-
switch RSSI {
169-
case ..<(-100):
170-
signalImageName = #imageLiteral(resourceName: "WiFiStateScanning1")
171-
case ..<(-80):
172-
signalImageName = #imageLiteral(resourceName: "WiFiSignalStrengthFair")
173-
case ..<(-60):
174-
signalImageName = #imageLiteral(resourceName: "WiFiSignalStrengthGood")
175-
default:
176-
signalImageName = #imageLiteral(resourceName: "WiFiStateOn")
228+
override func layout() {
229+
super.layout()
230+
if #available(macOS 11, *) {
231+
effectView.frame = CGRect(x: 5, // effectPadding
232+
y: 0,
233+
width: bounds.width - 10, // effectPadding * 2
234+
height: bounds.height)
235+
} else {
236+
effectView.frame = bounds
177237
}
178-
return signalImageName
179238
}
180239
}

0 commit comments

Comments
 (0)