-
Notifications
You must be signed in to change notification settings - Fork 30
/
DimManager.swift
176 lines (144 loc) · 6.29 KB
/
DimManager.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
//
// DimManager.swift
// Dimmer Bar
//
// Created by Trung Phan on 12/23/19.
// Copyright © 2019 Dwarves Foundation. All rights reserved.
//
import Foundation
import Cocoa
import Combine
enum DimMode: Int {
case single
case parallel
}
class DimManager {
//MARK: - Variable(s)
static let sharedInstance = DimManager()
let setting = SettingObservable()
private var windows: [NSWindow] = []
private var cancellableSet: Set<AnyCancellable> = []
//MARK: - Init
private init() {
self.observerActiveWindowChanged()
self.observeSettingChanged()
}
func dim(runningApplication: NSRunningApplication?, withDelay: Bool = true) {
guard DimManager.sharedInstance.setting.isEnabled else {
self.removeAllOverlay()
return
}
// Remove dim if user click to desktop
// This will also remove dim if user click to finder
// Improve: Find the other way to check if user click to desktop
if let bundle = runningApplication?.bundleIdentifier, bundle == "com.apple.finder" {
self.removeAllOverlay()
return
}
let color = NSColor.black.withAlphaComponent(CGFloat(DimManager.sharedInstance.setting.alpha/100.0))
DimManager.sharedInstance.windows(color: color, withDelay: withDelay) { [weak self] windows in
guard let strongSelf = self else {return}
strongSelf.removeAllOverlay()
strongSelf.windows = windows
}
}
func toggleDimming(isEnable: Bool) {
isEnable ? self.dim(runningApplication: self.getFrontMostApplication()) : self.removeAllOverlay()
}
func adjustDimmingLevel(alpha: Double) {
for overlayWindow in self.windows {
overlayWindow.backgroundColor = NSColor.black.withAlphaComponent(CGFloat(alpha/100.0))
}
}
}
//MARK: - Core function Helper Methods
extension DimManager {
private func getFrontMostApplication() -> NSRunningApplication? {
return NSWorkspace.shared.frontmostApplication
}
private func windows(color: NSColor, withDelay: Bool, didCreateWindows: @escaping ([NSWindow])->()) {
let delay = withDelay ? 0.2 : 0
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
guard let strongSelf = self else {return}
let windowInfos = strongSelf.getWindowInfos()
let screens = NSScreen.screens
let windows = screens.map { screen in
return strongSelf.windowForScreen(screen: screen, windowInfos: windowInfos, color: color)
}
didCreateWindows(windows)
}
}
private func windowForScreen(screen: NSScreen, windowInfos: [WindowInfo], color: NSColor) -> NSWindow {
let frame = NSRect(origin: .zero, size: screen.frame.size)
let overlayWindow = NSWindow.init(contentRect: frame, styleMask: .borderless, backing: .buffered, defer: false, screen: screen)
overlayWindow.isReleasedWhenClosed = false
overlayWindow.animationBehavior = .none
overlayWindow.backgroundColor = color
overlayWindow.ignoresMouseEvents = true
overlayWindow.collectionBehavior = [.transient, .fullScreenNone]
overlayWindow.level = .normal
var windowNumber = 0
switch self.setting.dimMode {
case .single:
windowNumber = windowInfos[safe: 0]?.number ?? 0
case .parallel:
// Get frontmost window of each screen
let newScreen = NSRect(x: screen.frame.minX, y: NSScreen.screens[0].frame.maxY - screen.frame.maxY, width: screen.frame.width, height: screen.frame.height)
let windowInfo = windowInfos.first(where: {
return newScreen.minX <= $0.bounds!.midX &&
newScreen.maxX >= $0.bounds!.midX &&
newScreen.minY <= $0.bounds!.midY &&
newScreen.maxY >= $0.bounds!.midY
})
windowNumber = windowInfo?.number ?? 0
}
overlayWindow.order(.below, relativeTo: windowNumber)
return overlayWindow
}
private func removeAllOverlay() {
for overlayWindow in self.windows {
overlayWindow.close()
}
self.windows.removeAll()
}
/// This func will return the window info of windows on all the screen
private func getWindowInfos() -> [WindowInfo] {
let options = CGWindowListOption([.excludeDesktopElements, .optionOnScreenOnly])
let windowsListInfo = CGWindowListCopyWindowInfo(options, CGWindowID(0))
let infoList = windowsListInfo as! [[String:Any]]
let windowInfos = infoList.map { WindowInfo.init(dict: $0) }.filter { $0.layer == 0 } // Filter out all the other item like Status Bar icon.
return windowInfos
}
}
extension DimManager {
private func observeSettingChanged() {
// DON'T receive this publisher on Main scheduler
// It will cause delay
// Still don't know why :-?
self.setting.$alpha
.removeDuplicates()
.sink(receiveValue: adjustDimmingLevel)
.store(in: &cancellableSet)
self.setting.$isEnabled
.receive(on: DispatchQueue.main)
.sink(receiveValue: toggleDimming)
.store(in: &cancellableSet)
self.setting.$dimMode
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] _ in
self?.dim(runningApplication: nil)
})
.store(in: &cancellableSet)
}
private func observerActiveWindowChanged() {
let nc = NSWorkspace.shared.notificationCenter
nc.addObserver(self, selector: #selector(workspaceDidReceiptAppllicatinActiveNotification), name: NSWorkspace.didActivateApplicationNotification, object: nil)
}
@objc private func workspaceDidReceiptAppllicatinActiveNotification(ntf: Notification) {
guard
let activeAppDict = ntf.userInfo as? [AnyHashable : NSRunningApplication],
let activeApplication = activeAppDict["NSWorkspaceApplicationKey"]
else { return }
self.dim(runningApplication: activeApplication)
}
}