feat: show notification badges on top of app icons (closes #523)
This commit is contained in:
1 parent
d3253ba4e1
commit
fb628341d1
32 files changed
+141
-36
No files matched your search
Binary file not shown.
@@ -127,6 +127,9 @@
|
|||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"Hidden" = "Hidden";
|
"Hidden" = "Hidden";
|
||||||
|
|
||||||
|
/* No comment provided by engineer. */
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "مخفي";
|
"Hidden" = "مخفي";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "Versteckt";
|
"Hidden" = "Versteckt";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Bunte Kreise beim Markieren mit Maus verbergen:";
|
"Hide colored circles on mouse hover:" = "Bunte Kreise beim Markieren mit Maus verbergen:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "Hidden";
|
"Hidden" = "Hidden";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "Ocultas";
|
"Hidden" = "Ocultas";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "Piilossa";
|
"Hidden" = "Piilossa";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "Cachées";
|
"Hidden" = "Cachées";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "Rejtett";
|
"Hidden" = "Rejtett";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "Tersembunyi";
|
"Hidden" = "Tersembunyi";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "Nascosto";
|
"Hidden" = "Nascosto";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "ウィンドウをしまう";
|
"Hidden" = "ウィンドウをしまう";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "숨겨진 화면";
|
"Hidden" = "숨겨진 화면";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "마우스를 올릴 때 색칠된 원들 숨기기:";
|
"Hide colored circles on mouse hover:" = "마우스를 올릴 때 색칠된 원들 숨기기:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "Verstoppen";
|
"Hidden" = "Verstoppen";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "Verborgen";
|
"Hidden" = "Verborgen";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "Escondido";
|
"Hidden" = "Escondido";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Esconder círculos coloridos onde o rato passar:";
|
"Hide colored circles on mouse hover:" = "Esconder círculos coloridos onde o rato passar:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "Скрытые";
|
"Hidden" = "Скрытые";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Скрыть цветные круги при наведении мышью:";
|
"Hide colored circles on mouse hover:" = "Скрыть цветные круги при наведении мышью:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "Gizli";
|
"Hidden" = "Gizli";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "隐藏";
|
"Hidden" = "隐藏";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "鼠标悬停时隐藏彩色圆圈:";
|
"Hide colored circles on mouse hover:" = "鼠标悬停时隐藏彩色圆圈:";
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hidden" = "隱藏";
|
"Hidden" = "隱藏";
|
||||||
|
|
||||||
|
/*No comment provided by engineer.*/
|
||||||
|
"Hide app badges:" = "Hide app badges:";
|
||||||
|
|
||||||
/*No comment provided by engineer.*/
|
/*No comment provided by engineer.*/
|
||||||
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
"Hide colored circles on mouse hover:" = "Hide colored circles on mouse hover:";
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ set -exu
|
|||||||
|
|
||||||
"$(pipenv --venv)"/bin/pyftsubset resources/SF-Pro-Text-Regular-Full.otf \
|
"$(pipenv --venv)"/bin/pyftsubset resources/SF-Pro-Text-Regular-Full.otf \
|
||||||
--output-file=resources/SF-Pro-Text-Regular.otf \
|
--output-file=resources/SF-Pro-Text-Regular.otf \
|
||||||
--text=""
|
--text=""
|
||||||
@@ -116,6 +116,10 @@ extension AXUIElement {
|
|||||||
return try attribute(kAXParentAttribute, AXUIElement.self)
|
return try attribute(kAXParentAttribute, AXUIElement.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func children() throws -> [AXUIElement]? {
|
||||||
|
return try attribute(kAXChildrenAttribute, [AXUIElement].self)
|
||||||
|
}
|
||||||
|
|
||||||
func windows() throws -> [AXUIElement]? {
|
func windows() throws -> [AXUIElement]? {
|
||||||
return try attribute(kAXWindowsAttribute, [AXUIElement].self)
|
return try attribute(kAXWindowsAttribute, [AXUIElement].self)
|
||||||
}
|
}
|
||||||
@@ -140,6 +144,10 @@ extension AXUIElement {
|
|||||||
return try attribute(kAXSubroleAttribute, String.self)
|
return try attribute(kAXSubroleAttribute, String.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appIsRunning() throws -> Bool? {
|
||||||
|
return try attribute(kAXIsApplicationRunningAttribute, Bool.self)
|
||||||
|
}
|
||||||
|
|
||||||
func closeButton() throws -> AXUIElement? {
|
func closeButton() throws -> AXUIElement? {
|
||||||
return try attribute(kAXCloseButtonAttribute, AXUIElement.self)
|
return try attribute(kAXCloseButtonAttribute, AXUIElement.self)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,8 +121,9 @@ func CGSGetWindowLevel(_ cid: CGSConnectionID, _ wid: CGWindowID, _ level: inout
|
|||||||
@_silgen_name("SLSRequestScreenCaptureAccess") @discardableResult
|
@_silgen_name("SLSRequestScreenCaptureAccess") @discardableResult
|
||||||
func SLSRequestScreenCaptureAccess() -> UInt8
|
func SLSRequestScreenCaptureAccess() -> UInt8
|
||||||
|
|
||||||
// for some reason, this attribute is missing from AXAttributeConstants
|
// for some reason, these attributes are missing from AXAttributeConstants
|
||||||
let kAXFullscreenAttribute = "AXFullScreen"
|
let kAXFullscreenAttribute = "AXFullScreen"
|
||||||
|
let kAXStatusLabelAttribute = "AXStatusLabel"
|
||||||
|
|
||||||
// enables/disables a symbolic hotkeys. These are system shortcuts such as command+tab or Spotlight
|
// enables/disables a symbolic hotkeys. These are system shortcuts such as command+tab or Spotlight
|
||||||
// it is possible to find all the existing hotkey IDs by using CGSGetSymbolicHotKeyValue on the first few hundred numbers
|
// it is possible to find all the existing hotkey IDs by using CGSGetSymbolicHotKeyValue on the first few hundred numbers
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class Application: NSObject {
|
|||||||
var axUiElement: AXUIElement?
|
var axUiElement: AXUIElement?
|
||||||
var axObserver: AXObserver?
|
var axObserver: AXObserver?
|
||||||
var isReallyFinishedLaunching = false
|
var isReallyFinishedLaunching = false
|
||||||
|
var dockLabel: String?
|
||||||
|
|
||||||
static func notifications(_ app: NSRunningApplication) -> [String] {
|
static func notifications(_ app: NSRunningApplication) -> [String] {
|
||||||
var n = [
|
var n = [
|
||||||
|
|||||||
@@ -73,6 +73,21 @@ class Applications {
|
|||||||
App.app.refreshOpenUi()
|
App.app.refreshOpenUi()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func refreshBadges() {
|
||||||
|
retryAxCallUntilTimeout {
|
||||||
|
let axDock = AXUIElementCreateApplication(Applications.list.first { $0.runningApplication.bundleIdentifier == "com.apple.dock" }!.runningApplication.processIdentifier)
|
||||||
|
let axList = try axDock.children()!.first { try $0.role() == "AXList" }!
|
||||||
|
let axAppDockItem = try axList.children()!.filter { try $0.subrole() == "AXApplicationDockItem" && ($0.appIsRunning() ?? false) }
|
||||||
|
try Applications.list.forEach { app in
|
||||||
|
if app.runningApplication.activationPolicy == .regular,
|
||||||
|
let bundleId = app.runningApplication.bundleIdentifier,
|
||||||
|
let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleId) {
|
||||||
|
app.dockLabel = try axAppDockItem.first { try $0.attribute(kAXURLAttribute, URL.self) == url }?.attribute(kAXStatusLabelAttribute, String.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static func isActualApplication(_ app: NSRunningApplication) -> Bool {
|
private static func isActualApplication(_ app: NSRunningApplication) -> Bool {
|
||||||
return (app.activationPolicy != .prohibited || isNotXpc(app)) && notAltTab(app)
|
return (app.activationPolicy != .prohibited || isNotXpc(app)) && notAltTab(app)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class Preferences {
|
|||||||
"minCellsPerRow": defaultsDependingOnScreenRatio_["minCellsPerRow"]!,
|
"minCellsPerRow": defaultsDependingOnScreenRatio_["minCellsPerRow"]!,
|
||||||
"maxCellsPerRow": defaultsDependingOnScreenRatio_["maxCellsPerRow"]!,
|
"maxCellsPerRow": defaultsDependingOnScreenRatio_["maxCellsPerRow"]!,
|
||||||
"shortcutStyle": "0",
|
"shortcutStyle": "0",
|
||||||
|
"hideAppBadges": "false",
|
||||||
]
|
]
|
||||||
|
|
||||||
// constant values
|
// constant values
|
||||||
@@ -96,6 +97,7 @@ class Preferences {
|
|||||||
static var fadeOutAnimation: Bool { defaults.bool("fadeOutAnimation") }
|
static var fadeOutAnimation: Bool { defaults.bool("fadeOutAnimation") }
|
||||||
static var hideSpaceNumberLabels: Bool { defaults.bool("hideSpaceNumberLabels") }
|
static var hideSpaceNumberLabels: Bool { defaults.bool("hideSpaceNumberLabels") }
|
||||||
static var hideStatusIcons: Bool { defaults.bool("hideStatusIcons") }
|
static var hideStatusIcons: Bool { defaults.bool("hideStatusIcons") }
|
||||||
|
static var hideAppBadges: Bool { defaults.bool("hideAppBadges") }
|
||||||
static var startAtLogin: Bool { defaults.bool("startAtLogin") }
|
static var startAtLogin: Bool { defaults.bool("startAtLogin") }
|
||||||
static var dontShowBlacklist: [String] { blacklistStringToArray(defaults.string("dontShowBlacklist")) }
|
static var dontShowBlacklist: [String] { blacklistStringToArray(defaults.string("dontShowBlacklist")) }
|
||||||
static var disableShortcutsBlacklist: [String] { blacklistStringToArray(defaults.string("disableShortcutsBlacklist")) }
|
static var disableShortcutsBlacklist: [String] { blacklistStringToArray(defaults.string("disableShortcutsBlacklist")) }
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ class Window {
|
|||||||
var cgWindowId: CGWindowID
|
var cgWindowId: CGWindowID
|
||||||
var title: String!
|
var title: String!
|
||||||
var thumbnail: NSImage?
|
var thumbnail: NSImage?
|
||||||
var icon: NSImage?
|
var icon: NSImage? { get { application.runningApplication.icon } }
|
||||||
var shouldShowTheUser = true
|
var shouldShowTheUser = true
|
||||||
var isTabbed: Bool = false
|
var isTabbed: Bool = false
|
||||||
var isHidden: Bool
|
var isHidden: Bool { get { application.runningApplication.isHidden } }
|
||||||
|
var dockLabel: Int? { get { application.dockLabel.flatMap { Int($0) } } }
|
||||||
var isFullscreen: Bool
|
var isFullscreen: Bool
|
||||||
var isMinimized: Bool
|
var isMinimized: Bool
|
||||||
var isOnAllSpaces: Bool
|
var isOnAllSpaces: Bool
|
||||||
@@ -35,8 +36,6 @@ class Window {
|
|||||||
self.cgWindowId = wid
|
self.cgWindowId = wid
|
||||||
self.spaceId = Spaces.currentSpaceId
|
self.spaceId = Spaces.currentSpaceId
|
||||||
self.spaceIndex = Spaces.currentSpaceIndex
|
self.spaceIndex = Spaces.currentSpaceIndex
|
||||||
self.icon = application.runningApplication.icon
|
|
||||||
self.isHidden = application.runningApplication.isHidden
|
|
||||||
self.isFullscreen = isFullscreen
|
self.isFullscreen = isFullscreen
|
||||||
self.isMinimized = isMinimized
|
self.isMinimized = isMinimized
|
||||||
self.isOnAllSpaces = false
|
self.isOnAllSpaces = false
|
||||||
|
|||||||
@@ -86,11 +86,7 @@ private func applicationHiddenOrShown(_ element: AXUIElement, _ pid: pid_t, _ ty
|
|||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let windows = Windows.list.filter {
|
let windows = Windows.list.filter {
|
||||||
// for AXUIElement of apps, CFEqual or == don't work; looks like a Cocoa bug
|
// for AXUIElement of apps, CFEqual or == don't work; looks like a Cocoa bug
|
||||||
let isFromApp = $0.application.runningApplication.processIdentifier == pid
|
return $0.application.runningApplication.processIdentifier == pid
|
||||||
if isFromApp {
|
|
||||||
$0.isHidden = type == kAXApplicationHiddenNotification
|
|
||||||
}
|
|
||||||
return isFromApp
|
|
||||||
}
|
}
|
||||||
App.app.refreshOpenUi(windows)
|
App.app.refreshOpenUi(windows)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,6 +270,8 @@ class App: AppCenterApplication, NSApplicationDelegate {
|
|||||||
guard appIsBeingUsed else { return }
|
guard appIsBeingUsed else { return }
|
||||||
Windows.refreshAllThumbnails()
|
Windows.refreshAllThumbnails()
|
||||||
guard appIsBeingUsed else { return }
|
guard appIsBeingUsed else { return }
|
||||||
|
Applications.refreshBadges()
|
||||||
|
guard appIsBeingUsed else { return }
|
||||||
refreshOpenUi()
|
refreshOpenUi()
|
||||||
guard appIsBeingUsed else { return }
|
guard appIsBeingUsed else { return }
|
||||||
thumbnailsPanel.show()
|
thumbnailsPanel.show()
|
||||||
|
|||||||
@@ -7,22 +7,21 @@ enum Symbols: String {
|
|||||||
case circledNumber0 = ""
|
case circledNumber0 = ""
|
||||||
case circledNumber10 = ""
|
case circledNumber10 = ""
|
||||||
case circledStar = ""
|
case circledStar = ""
|
||||||
|
case filledCircledStar = ""
|
||||||
case filledCircled = ""
|
case filledCircled = ""
|
||||||
case filledCircledMultiplySign = ""
|
case filledCircledMultiplySign = ""
|
||||||
case filledCircledMinusSign = ""
|
case filledCircledMinusSign = ""
|
||||||
case filledCircledPlusSign = ""
|
case filledCircledPlusSign = ""
|
||||||
|
case filledCircledNumber0 = ""
|
||||||
|
case filledCircledNumber10 = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Font icon using SF Symbols from the SF Pro font from Apple
|
// Font icon using SF Symbols from the SF Pro font from Apple
|
||||||
// see https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/
|
// see https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/
|
||||||
class ThumbnailFontIconView: ThumbnailTitleView {
|
class ThumbnailFontIconView: ThumbnailTitleView {
|
||||||
convenience init(_ symbol: Symbols, _ size: CGFloat = Preferences.fontIconSize, _ color: NSColor = .white, _ isBackground: Bool = false) {
|
convenience init(_ symbol: Symbols, _ size: CGFloat = Preferences.fontIconSize, _ color: NSColor = .white, _ shadow: NSShadow? = ThumbnailView.makeShadow(.darkGray)) {
|
||||||
// This helps SF symbols display vertically centered and not clipped at the bottom
|
// This helps SF symbols display vertically centered and not clipped at the bottom
|
||||||
if isBackground {
|
self.init(size, 3, shadow: shadow)
|
||||||
self.init(size, 3, shadow: nil)
|
|
||||||
} else {
|
|
||||||
self.init(size, 3)
|
|
||||||
}
|
|
||||||
string = symbol.rawValue
|
string = symbol.rawValue
|
||||||
font = NSFont(name: "SF Pro Text", size: size)
|
font = NSFont(name: "SF Pro Text", size: size)
|
||||||
textColor = color
|
textColor = color
|
||||||
@@ -31,32 +30,39 @@ class ThumbnailFontIconView: ThumbnailTitleView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// number should be in the interval [0-50]
|
// number should be in the interval [0-50]
|
||||||
func setNumber(_ number: UInt32) {
|
func setNumber(_ number: Int, _ filled: Bool) {
|
||||||
let (baseCharacter, offset) = baseCharacterAndOffset(number)
|
let (baseCharacter, offset) = baseCharacterAndOffset(number, filled)
|
||||||
assignIfDifferent(&string, String(UnicodeScalar(baseCharacter.unicodeScalars.first!.value + offset)!))
|
assignIfDifferent(&string, String(UnicodeScalar(Int(baseCharacter.unicodeScalars.first!.value) + offset)!))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func baseCharacterAndOffset(_ number: Int, _ filled: Bool) -> (String, Int) {
|
||||||
|
if number <= 9 {
|
||||||
|
// numbers alternate between empty and full circles; we skip the full circles
|
||||||
|
return ((filled ? Symbols.filledCircledNumber0 : Symbols.circledNumber0).rawValue, number * 2)
|
||||||
|
} else {
|
||||||
|
return ((filled ? Symbols.filledCircledNumber10 : Symbols.circledNumber10).rawValue, number - 10)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setStar() {
|
func setStar() {
|
||||||
assignIfDifferent(&string, Symbols.circledStar.rawValue)
|
assignIfDifferent(&string, Symbols.circledStar.rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func baseCharacterAndOffset(_ number: UInt32) -> (String, UInt32) {
|
func setFilledStar() {
|
||||||
if number <= 9 {
|
assignIfDifferent(&string, Symbols.filledCircledStar.rawValue)
|
||||||
// numbers alternate between empty and full circles; we skip the full circles
|
|
||||||
return (Symbols.circledNumber0.rawValue, number * UInt32(2))
|
|
||||||
} else {
|
|
||||||
return (Symbols.circledNumber10.rawValue, number - 10)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThumbnailFilledFontIconView: NSView {
|
class ThumbnailFilledFontIconView: NSView {
|
||||||
convenience init(_ thumbnailFontIconView: ThumbnailFontIconView, _ backgroundColor: NSColor) {
|
convenience init(_ thumbnailFontIconView: ThumbnailFontIconView, _ backgroundColor: NSColor, _ offset: Bool = false) {
|
||||||
self.init(frame: .zero)
|
self.init(frame: .zero)
|
||||||
translatesAutoresizingMaskIntoConstraints = false
|
translatesAutoresizingMaskIntoConstraints = false
|
||||||
let backgroundView = ThumbnailFontIconView(.filledCircled, thumbnailFontIconView.font!.pointSize, backgroundColor, true)
|
let backgroundView = ThumbnailFontIconView(.filledCircled, thumbnailFontIconView.font!.pointSize - (offset ? 2 : 0), backgroundColor)
|
||||||
addSubview(backgroundView)
|
addSubview(backgroundView)
|
||||||
addSubview(thumbnailFontIconView, positioned: .above, relativeTo: nil)
|
addSubview(thumbnailFontIconView, positioned: .above, relativeTo: nil)
|
||||||
fit(backgroundView.fittingSize.width, backgroundView.fittingSize.height)
|
if offset {
|
||||||
|
backgroundView.leftAnchor.constraint(equalTo: backgroundView.superview!.leftAnchor, constant: 1).isActive = true
|
||||||
|
}
|
||||||
|
fit(thumbnailFontIconView.fittingSize.width, thumbnailFontIconView.fittingSize.height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,9 +9,10 @@ class ThumbnailView: NSStackView {
|
|||||||
var minimizedIcon = ThumbnailFontIconView(.circledMinusSign)
|
var minimizedIcon = ThumbnailFontIconView(.circledMinusSign)
|
||||||
var hiddenIcon = ThumbnailFontIconView(.circledSlashSign)
|
var hiddenIcon = ThumbnailFontIconView(.circledSlashSign)
|
||||||
var spaceIcon = ThumbnailFontIconView(.circledNumber0)
|
var spaceIcon = ThumbnailFontIconView(.circledNumber0)
|
||||||
var closeIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledMultiplySign, Preferences.fontIconSize, NSColor(srgbRed: 1, green: 0.35, blue: 0.32, alpha: 1)), NSColor(srgbRed: 0.64, green: 0.03, blue: 0.02, alpha: 1))
|
var dockLabelIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledNumber0, (Preferences.fontIconSize * 0.7).rounded(), NSColor(srgbRed: 1, green: 0.30, blue: 0.25, alpha: 1), nil), NSColor.white, true)
|
||||||
var minimizeIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledMinusSign, Preferences.fontIconSize, NSColor(srgbRed: 0.91, green: 0.75, blue: 0.16, alpha: 1)), NSColor(srgbRed: 0.71, green: 0.55, blue: 0.09, alpha: 1))
|
var closeIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledMultiplySign, Preferences.fontIconSize, NSColor(srgbRed: 1, green: 0.35, blue: 0.32, alpha: 1), nil), NSColor(srgbRed: 0.64, green: 0.03, blue: 0.02, alpha: 1))
|
||||||
var maximizeIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledPlusSign, Preferences.fontIconSize, NSColor(srgbRed: 0.32, green: 0.76, blue: 0.17, alpha: 1)), NSColor(srgbRed: 0.04, green: 0.39, blue: 0.02, alpha: 1))
|
var minimizeIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledMinusSign, Preferences.fontIconSize, NSColor(srgbRed: 0.91, green: 0.75, blue: 0.16, alpha: 1), nil), NSColor(srgbRed: 0.71, green: 0.55, blue: 0.09, alpha: 1))
|
||||||
|
var maximizeIcon = ThumbnailFilledFontIconView(ThumbnailFontIconView(.filledCircledPlusSign, Preferences.fontIconSize, NSColor(srgbRed: 0.32, green: 0.76, blue: 0.17, alpha: 1), nil), NSColor(srgbRed: 0.04, green: 0.39, blue: 0.02, alpha: 1))
|
||||||
var hStackView: NSStackView!
|
var hStackView: NSStackView!
|
||||||
var mouseUpCallback: (() -> Void)!
|
var mouseUpCallback: (() -> Void)!
|
||||||
var mouseMovedCallback: (() -> Void)!
|
var mouseMovedCallback: (() -> Void)!
|
||||||
@@ -42,9 +43,16 @@ class ThumbnailView: NSStackView {
|
|||||||
hStackView.spacing = Preferences.intraCellPadding
|
hStackView.spacing = Preferences.intraCellPadding
|
||||||
setViews([hStackView, thumbnail], in: .leading)
|
setViews([hStackView, thumbnail], in: .leading)
|
||||||
addWindowControls()
|
addWindowControls()
|
||||||
|
addDockLabelIcon()
|
||||||
}
|
}
|
||||||
|
|
||||||
func addWindowControls() {
|
private func addDockLabelIcon() {
|
||||||
|
appIcon.addSubview(dockLabelIcon, positioned: .above, relativeTo: nil)
|
||||||
|
dockLabelIcon.topAnchor.constraint(equalTo: appIcon.topAnchor, constant: -4).isActive = true
|
||||||
|
dockLabelIcon.rightAnchor.constraint(equalTo: appIcon.rightAnchor, constant: 1).isActive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addWindowControls() {
|
||||||
thumbnail.addSubview(closeIcon, positioned: .above, relativeTo: nil)
|
thumbnail.addSubview(closeIcon, positioned: .above, relativeTo: nil)
|
||||||
thumbnail.addSubview(minimizeIcon, positioned: .above, relativeTo: nil)
|
thumbnail.addSubview(minimizeIcon, positioned: .above, relativeTo: nil)
|
||||||
thumbnail.addSubview(maximizeIcon, positioned: .above, relativeTo: nil)
|
thumbnail.addSubview(maximizeIcon, positioned: .above, relativeTo: nil)
|
||||||
@@ -117,10 +125,19 @@ class ThumbnailView: NSStackView {
|
|||||||
assignIfDifferent(&minimizedIcon.isHidden, !element.isMinimized || Preferences.hideStatusIcons)
|
assignIfDifferent(&minimizedIcon.isHidden, !element.isMinimized || Preferences.hideStatusIcons)
|
||||||
assignIfDifferent(&spaceIcon.isHidden, Spaces.isSingleSpace || Preferences.hideSpaceNumberLabels)
|
assignIfDifferent(&spaceIcon.isHidden, Spaces.isSingleSpace || Preferences.hideSpaceNumberLabels)
|
||||||
if !spaceIcon.isHidden {
|
if !spaceIcon.isHidden {
|
||||||
if element.isOnAllSpaces {
|
if element.spaceIndex > 30 || element.isOnAllSpaces {
|
||||||
spaceIcon.setStar()
|
spaceIcon.setStar()
|
||||||
} else {
|
} else {
|
||||||
spaceIcon.setNumber(UInt32(element.spaceIndex))
|
spaceIcon.setNumber(element.spaceIndex, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assignIfDifferent(&dockLabelIcon.isHidden, element.dockLabel == nil || Preferences.hideAppBadges || Preferences.iconSize == 0)
|
||||||
|
if !dockLabelIcon.isHidden, let dockLabel = element.dockLabel {
|
||||||
|
let view = dockLabelIcon.subviews[1] as! ThumbnailFontIconView
|
||||||
|
if dockLabel > 30 {
|
||||||
|
view.setFilledStar()
|
||||||
|
} else {
|
||||||
|
view.setNumber(dockLabel, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assignIfDifferent(&frame.size.width, max(thumbnail.frame.size.width + Preferences.intraCellPadding * 2, ThumbnailView.widthMin(screen)))
|
assignIfDifferent(&frame.size.width, max(thumbnail.frame.size.width + Preferences.intraCellPadding * 2, ThumbnailView.widthMin(screen)))
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class AppearanceTab: NSViewController, PreferencePane {
|
|||||||
LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide status icons:", comment: ""), "hideStatusIcons"),
|
LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide status icons:", comment: ""), "hideStatusIcons"),
|
||||||
LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Show standard tabs as windows:", comment: ""), "showTabsAsWindows"),
|
LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Show standard tabs as windows:", comment: ""), "showTabsAsWindows"),
|
||||||
LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide colored circles on mouse hover:", comment: ""), "hideColoredCircles"),
|
LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide colored circles on mouse hover:", comment: ""), "hideColoredCircles"),
|
||||||
|
LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide app badges:", comment: ""), "hideAppBadges"),
|
||||||
])
|
])
|
||||||
grid.column(at: 0).xPlacement = .trailing
|
grid.column(at: 0).xPlacement = .trailing
|
||||||
grid.rowAlignment = .lastBaseline
|
grid.rowAlignment = .lastBaseline
|
||||||
|
|||||||
Reference in new issue
Block a user