From 148e81fcbb5995d0d771e9bb4c2bab3dc0f5e46d Mon Sep 17 00:00:00 2001 From: ShlomoCode <78599753+ShlomoCode@users.noreply.github.com> Date: Sun, 11 Aug 2024 23:00:42 +0300 Subject: [PATCH] run swiftformat --- DockDoor/logic/AppDelegate.swift | 85 ++++----- DockDoor/logic/DockObserver.swift | 95 +++++----- DockDoor/logic/DockUtils.swift | 49 ++--- DockDoor/logic/Settings.swift | 66 +++---- DockDoor/logic/Utilities/App Icon.swift | 36 ++-- DockDoor/logic/Utilities/CacheUtil.swift | 12 +- DockDoor/logic/Utilities/Extensions.swift | 2 +- DockDoor/logic/Utilities/KeybindHelper.swift | 57 +++--- DockDoor/logic/Utilities/MessageUtil.swift | 3 +- DockDoor/logic/Utilities/Misc Utils.swift | 36 ++-- .../Utilities/WindowClosureObservers.swift | 12 +- DockDoor/logic/Window.swift | 74 ++++---- DockDoor/logic/WindowsUtil.swift | 51 +++--- DockDoor/logic/api-wrappers/AXUIElement.swift | 95 +++++----- DockDoor/logic/api-wrappers/CGWindow.swift | 2 +- DockDoor/logic/api-wrappers/CGWindowID.swift | 2 +- DockDoor/logic/api-wrappers/PrivateApis.swift | 1 - DockDoor/logic/api-wrappers/Sysctl.swift | 22 +-- DockDoor/ui/Components/BlurView.swift | 9 +- DockDoor/ui/Components/FluidGradient.swift | 4 +- DockDoor/ui/Components/Marquee.swift | 84 +++++---- DockDoor/ui/Components/StackedShadow.swift | 10 +- DockDoor/ui/UIExtensions.swift | 7 +- DockDoor/ui/Views/FirstTimeView.swift | 25 +-- .../Hover Window/FullSizePreviewView.swift | 6 +- .../SharedPreviewWindowCoordinator.swift | 168 +++++++++--------- .../Hover Window/Traffic Light Buttons.swift | 12 +- .../ui/Views/Hover Window/WindowPreview.swift | 48 ++--- .../WindowPreviewHoverContainer.swift | 46 ++--- .../Settings/AppearanceSettingsView.swift | 26 +-- .../ui/Views/Settings/MainSettingsView.swift | 62 +++---- .../Settings/PermissionsSettingsView.swift | 22 +-- .../Views/Settings/UpdateSettingsView.swift | 39 ++-- .../Settings/WindowSwitcherSettingsView.swift | 35 ++-- DockDoor/ui/Views/Settings/settings.swift | 8 +- 35 files changed, 652 insertions(+), 659 deletions(-) diff --git a/DockDoor/logic/AppDelegate.swift b/DockDoor/logic/AppDelegate.swift index 1172d970..731bd5b8 100644 --- a/DockDoor/logic/AppDelegate.swift +++ b/DockDoor/logic/AppDelegate.swift @@ -6,17 +6,17 @@ // import Cocoa -import SwiftUI import Defaults import Settings import Sparkle +import SwiftUI class SettingsWindowControllerDelegate: NSObject, NSWindowDelegate { - func windowDidBecomeKey(_ notification: Notification) { + func windowDidBecomeKey(_: Notification) { NSApp.setActivationPolicy(.regular) // Show dock icon on open settings window } - - func windowWillClose(_ notification: Notification) { + + func windowWillClose(_: Notification) { NSApp.setActivationPolicy(.accessory) // Hide dock icon back } } @@ -28,9 +28,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { private var appClosureObserver: AppClosureObserver? private var keybindHelper: KeybindHelper? private var statusBarItem: NSStatusItem? - + private var updaterController: SPUStandardUpdaterController - + // settings private var firstTimeWindow: NSWindow? private lazy var settingsWindowController = SettingsWindowController( @@ -39,32 +39,32 @@ class AppDelegate: NSObject, NSApplicationDelegate { AppearanceSettingsViewController(), WindowSwitcherSettingsViewController(), PermissionsSettingsViewController(), - UpdatesSettingsViewController(updater: updaterController.updater) + UpdatesSettingsViewController(updater: updaterController.updater), ] ) private let settingsWindowControllerDelegate = SettingsWindowControllerDelegate() - + override init() { - self.updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) - self.updaterController.startUpdater() + updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) + updaterController.startUpdater() super.init() - + if let zoomButton = settingsWindowController.window?.standardWindowButton(.zoomButton) { zoomButton.isEnabled = false } - + settingsWindowController.window?.delegate = settingsWindowControllerDelegate } - - func applicationDidFinishLaunching(_ aNotification: Notification) { + + func applicationDidFinishLaunching(_: Notification) { NSApplication.shared.setActivationPolicy(.accessory) // Hide the menubar and dock icons - + if Defaults[.showMenuBarIcon] { - self.setupMenuBar() + setupMenuBar() } else { - self.removeMenuBar() + removeMenuBar() } - + if !Defaults[.launched] { handleFirstTimeLaunch() } else { @@ -75,12 +75,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } } - - func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { - self.openSettingsWindow(nil) + + func applicationShouldHandleReopen(_: NSApplication, hasVisibleWindows _: Bool) -> Bool { + openSettingsWindow(nil) return false } - + func setupMenuBar() { guard statusBarItem == nil else { return } let icon = NSImage(systemSymbolName: "door.right.hand.open", accessibilityDescription: nil)! @@ -89,80 +89,81 @@ class AppDelegate: NSObject, NSApplicationDelegate { button.image = icon button.action = #selector(statusBarButtonClicked(_:)) button.target = self - + // Create Menu Items let openSettingsMenuItem = NSMenuItem(title: String(localized: "Open Settings"), action: #selector(openSettingsWindow(_:)), keyEquivalent: "") openSettingsMenuItem.target = self let quitMenuItem = NSMenuItem(title: String(localized: "Quit DockDoor"), action: #selector(quitAppWrapper), keyEquivalent: "q") quitMenuItem.target = self - + // Create the Menu let menu = NSMenu() menu.addItem(openSettingsMenuItem) menu.addItem(NSMenuItem.separator()) menu.addItem(quitMenuItem) - + button.menu = menu } } - + func removeMenuBar() { guard let statusBarItem = statusBarItem else { return } NSStatusBar.system.removeStatusItem(statusBarItem) self.statusBarItem = nil } - - @objc func statusBarButtonClicked(_ sender: Any?) { + + @objc func statusBarButtonClicked(_: Any?) { // Show the menu if let button = statusBarItem?.button { button.menu?.popUp(positioning: nil, at: NSPoint(x: 0, y: button.bounds.maxY), in: button) } } - + @objc private func quitAppWrapper() { quitApp() } - - @objc func openSettingsWindow(_ sender: Any?) { + + @objc func openSettingsWindow(_: Any?) { settingsWindowController.show() } - + func quitApp() { NSApplication.shared.terminate(nil) } - + func restartApp() { // we use -n to open a new instance, to avoid calling applicationShouldHandleReopen // we use Bundle.main.bundlePath in case of multiple DockDoor versions on the machine Process.launchedProcess(launchPath: "/usr/bin/open", arguments: ["-n", Bundle.main.bundlePath]) - self.quitApp() + quitApp() } - + private func handleFirstTimeLaunch() { let contentView = FirstTimeView() - + // Save that the app has launched Defaults[.launched] = true - + // Create a hosting controller let hostingController = NSHostingController(rootView: contentView) - + // Create the settings window firstTimeWindow = NSWindow( contentRect: NSRect(origin: .zero, size: NSSize(width: 400, height: 400)), styleMask: [.titled, .closable, .resizable], - backing: .buffered, defer: false) + backing: .buffered, defer: false + ) firstTimeWindow?.center() firstTimeWindow?.setFrameAutosaveName("DockDoor Permissions") firstTimeWindow?.contentView = hostingController.view firstTimeWindow?.title = "DockDoor Permissions" - + // Make the window key and order it front firstTimeWindow?.makeKeyAndOrderFront(nil) - + // Calculate the preferred size of the SwiftUI view let preferredSize = hostingController.view.fittingSize - + // Resize the window to fit the content view firstTimeWindow?.setContentSize(preferredSize) } diff --git a/DockDoor/logic/DockObserver.swift b/DockDoor/logic/DockObserver.swift index 93cc5824..0e109664 100644 --- a/DockDoor/logic/DockObserver.swift +++ b/DockDoor/logic/DockObserver.swift @@ -4,50 +4,50 @@ // Created by Ethan Bills on 6/3/24. // -import Cocoa import ApplicationServices +import Cocoa -func handleSelectedDockItemChangedNotification(observer: AXObserver, element: AXUIElement, notificationName: CFString, _: UnsafeMutableRawPointer?) -> Void { +func handleSelectedDockItemChangedNotification(observer _: AXObserver, element _: AXUIElement, notificationName _: CFString, _: UnsafeMutableRawPointer?) { DockObserver.shared.processSelectedDockItemChanged() } final class DockObserver { static let shared = DockObserver() - + var axObserver: AXObserver? var lastAppUnderMouse: NSRunningApplication? private var hoverProcessingTask: Task? private var isProcessing: Bool = false - + private init() { setupSelectedDockItemObserver() } - + private func setupSelectedDockItemObserver() { guard let dockAppPID = NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.dock").first?.processIdentifier else { fatalError("Dock does found in running applications") } - + let dockAppElement = AXUIElementCreateApplication(dockAppPID) - + guard let children = try? dockAppElement.children(), let axList = children.first(where: { element in - return try! element.role() == kAXListRole + try! element.role() == kAXListRole }) else { fatalError("can't get dock items list element") } - + AXObserverCreate(dockAppPID, handleSelectedDockItemChangedNotification, &axObserver) guard let axObserver = axObserver else { return } - + do { - try axList.subscribeToNotification(axObserver, kAXSelectedChildrenChangedNotification, { + try axList.subscribeToNotification(axObserver, kAXSelectedChildrenChangedNotification) { CFRunLoopAddSource(CFRunLoopGetCurrent(), AXObserverGetRunLoopSource(axObserver), .commonModes) - }) + } } catch { fatalError("Failed to subscribe to notification: \(error)") } } - + func processSelectedDockItemChanged() { hoverProcessingTask?.cancel() hoverProcessingTask = Task { [weak self] in @@ -55,15 +55,15 @@ final class DockObserver { try Task.checkCancellation() guard !isProcessing, !ScreenCenteredFloatingWindow.shared.windowSwitcherActive else { return } isProcessing = true - + defer { isProcessing = false } - + if let currentAppUnderMouse = getCurrentAppUnderMouse() { if currentAppUnderMouse != lastAppUnderMouse { lastAppUnderMouse = currentAppUnderMouse - + Task { [weak self] in guard let self = self else { return } do { @@ -105,134 +105,135 @@ final class DockObserver { } } } - + func getDockIconFrameAtLocation(_ mouseLocation: CGPoint) -> CGRect? { guard let hoveredDockItem = gethoveredDockItem() else { print("No selected dock item found") return nil } - + var positionValue: CFTypeRef? var sizeValue: CFTypeRef? let positionResult = AXUIElementCopyAttributeValue(hoveredDockItem, kAXPositionAttribute as CFString, &positionValue) let sizeResult = AXUIElementCopyAttributeValue(hoveredDockItem, kAXSizeAttribute as CFString, &sizeValue) - + guard positionResult == .success, sizeResult == .success else { print("Failed to get position or size for selected dock item") return nil } - + let position = positionValue as! AXValue let size = sizeValue as! AXValue var positionPoint = CGPoint.zero var sizeCGSize = CGSize.zero AXValueGetValue(position, .cgPoint, &positionPoint) AXValueGetValue(size, .cgSize, &sizeCGSize) - + let iconRect = CGRect(origin: positionPoint, size: sizeCGSize) - + // Adjust mouse location to match the coordinate system of the dock icons let adjustedMouseLocation = CGPoint( x: mouseLocation.x, y: (DockObserver.screenContainMouse(mouseLocation)?.frame.height ?? NSScreen.main!.frame.height) - mouseLocation.y ) - + print("Checking icon rect: \(iconRect) with adjusted mouse location: \(adjustedMouseLocation)") - + if iconRect.contains(adjustedMouseLocation) { print("Matched icon rect: \(iconRect)") return iconRect } - + print("No matching icon rect found") return nil } - + func gethoveredDockItem() -> AXUIElement? { guard let dockAppPID = NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.dock").first?.processIdentifier else { print("Dock does found in running applications") return nil } - + let dockAppElement = AXUIElementCreateApplication(dockAppPID) - + var dockItems: CFTypeRef? - guard AXUIElementCopyAttributeValue(dockAppElement, kAXChildrenAttribute as CFString, &dockItems) == .success , let dockItems = dockItems as? [AXUIElement], !dockItems.isEmpty else { + guard AXUIElementCopyAttributeValue(dockAppElement, kAXChildrenAttribute as CFString, &dockItems) == .success, let dockItems = dockItems as? [AXUIElement], !dockItems.isEmpty else { print("Failed to get dock items or no dock items found") return nil } - + var hoveredDockItem: CFTypeRef? guard AXUIElementCopyAttributeValue(dockItems.first!, kAXSelectedChildrenAttribute as CFString, &hoveredDockItem) == .success, !dockItems.isEmpty, let hoveredDockItem = (hoveredDockItem as! [AXUIElement]).first else { // no app under nouse return nil } - + return hoveredDockItem } - + func getCurrentAppUnderMouse() -> NSRunningApplication? { guard let hoveredDockItem = gethoveredDockItem() else { return nil } - + var appURL: CFTypeRef? guard AXUIElementCopyAttributeValue(hoveredDockItem, kAXURLAttribute as CFString, &appURL) == .success, let appURL = appURL as? NSURL as? URL else { print("Failed to get app URL or convert NSURL to URL") return nil } - + let budle = Bundle(url: appURL) guard let bundleIdentifier = budle?.bundleIdentifier else { print("App has no valid bundle. app url: \(appURL)") // For example: scrcpy, Android studio emulator - + var dockItemTitle: CFTypeRef? guard AXUIElementCopyAttributeValue(hoveredDockItem, kAXTitleAttribute as CFString, &dockItemTitle) == .success, let dockItemTitle = dockItemTitle as? String else { print("Failed to get dock item title") return nil } - + if let app = WindowsUtil.findRunningApplicationByName(named: dockItemTitle) { return app } else { return nil } } - + guard let runningApp = NSRunningApplication.runningApplications(withBundleIdentifier: bundleIdentifier).first else { print("Current App is not running (\(bundleIdentifier))") return nil } - + print("Current App is running (\(runningApp))") return runningApp } - + static func screenContainMouse(_ point: CGPoint) -> NSScreen? { let screens = NSScreen.screens guard let primaryScreen = screens.first else { return nil } - + for screen in screens { let (offsetLeft, offsetTop) = computeOffsets(for: screen, primaryScreen: primaryScreen) - - if point.x >= offsetLeft && point.x <= offsetLeft + screen.frame.size.width && - point.y >= offsetTop && point.y <= offsetTop + screen.frame.size.height { + + if point.x >= offsetLeft, point.x <= offsetLeft + screen.frame.size.width, + point.y >= offsetTop, point.y <= offsetTop + screen.frame.size.height + { return screen } } - + return primaryScreen } - + private static func computeOffsets(for screen: NSScreen, primaryScreen: NSScreen) -> (CGFloat, CGFloat) { var offsetLeft = screen.frame.origin.x var offsetTop = primaryScreen.frame.size.height - (screen.frame.origin.y + screen.frame.size.height) - + if screen == primaryScreen { offsetTop = 0 offsetLeft = 0 } - + return (offsetLeft, offsetTop) } } diff --git a/DockDoor/logic/DockUtils.swift b/DockDoor/logic/DockUtils.swift index 16c700c7..bb9ea306 100644 --- a/DockDoor/logic/DockUtils.swift +++ b/DockDoor/logic/DockUtils.swift @@ -15,64 +15,65 @@ enum DockPosition { class DockUtils { static let shared = DockUtils() - + private let dockDefaults: UserDefaults? // Store a single instance - + private init() { dockDefaults = UserDefaults(suiteName: "com.apple.dock") } - + func isDockHidingEnabled() -> Bool { if let dockAutohide = dockDefaults?.bool(forKey: "autohide") { return dockAutohide } - + return false } - + func countIcons() -> (Int, Int) { let persistentAppsCount = dockDefaults?.array(forKey: "persistent-apps")?.count ?? 0 let recentAppsCount = dockDefaults?.array(forKey: "recent-apps")?.count ?? 0 return (persistentAppsCount + recentAppsCount, (persistentAppsCount > 0 && recentAppsCount > 0) ? 1 : 0) } - + func calculateDockWidth() -> CGFloat { let countIcons = countIcons() let iconCount = countIcons.0 let numberOfDividers = countIcons.1 let tileSize = tileSize() - + let baseWidth = tileSize * CGFloat(iconCount) let dividerWidth: CGFloat = 10.0 let totalDividerWidth = CGFloat(numberOfDividers) * dividerWidth - - if self.isMagnificationEnabled(), - let largeSize = dockDefaults?.object(forKey: "largesize") as? CGFloat { + + if isMagnificationEnabled(), + let largeSize = dockDefaults?.object(forKey: "largesize") as? CGFloat + { let extraWidth = (largeSize - tileSize) * CGFloat(iconCount) * 0.5 return baseWidth + extraWidth + totalDividerWidth } - + return baseWidth + totalDividerWidth } - + private func tileSize() -> CGFloat { return dockDefaults?.double(forKey: "tilesize") ?? 0 } - + private func largeSize() -> CGFloat { return dockDefaults?.double(forKey: "largesize") ?? 0 } - + func isMagnificationEnabled() -> Bool { return dockDefaults?.bool(forKey: "magnification") ?? false } - + func calculateDockHeight(_ forScreen: NSScreen?) -> CGFloat { - if self.isDockHidingEnabled() { + if isDockHidingEnabled() { return abs(largeSize() - tileSize()) } else { if let currentScreen = forScreen { - switch self.getDockPosition() { + switch getDockPosition() { case .right, .left: let size = abs(currentScreen.frame.width - currentScreen.visibleFrame.width) return size @@ -86,7 +87,7 @@ class DockUtils { return 0.0 } } - + func getStatusBarHeight(screen: NSScreen?) -> CGFloat { var statusBarHeight = 0.0 if let screen = screen { @@ -94,10 +95,10 @@ class DockUtils { } return statusBarHeight } - + func getDockPosition() -> DockPosition { guard let orientation = dockDefaults?.string(forKey: "orientation")?.lowercased() else { - if NSScreen.main!.visibleFrame.origin.y == 0 && !self.isDockHidingEnabled() { + if NSScreen.main!.visibleFrame.origin.y == 0 && !isDockHidingEnabled() { if NSScreen.main!.visibleFrame.origin.x == 0 { return .right } else { @@ -107,12 +108,12 @@ class DockUtils { return .bottom } } - + switch orientation { - case "left": return .left + case "left": return .left case "bottom": return .bottom - case "right": return .right - default: return .unknown + case "right": return .right + default: return .unknown } } } diff --git a/DockDoor/logic/Settings.swift b/DockDoor/logic/Settings.swift index 1f3c1b79..3488945d 100644 --- a/DockDoor/logic/Settings.swift +++ b/DockDoor/logic/Settings.swift @@ -9,42 +9,42 @@ let roughHeightCap = optimisticScreenSizeHeight / 3 let roughWidthCap = optimisticScreenSizeWidth / 3 extension Defaults.Keys { - static let sizingMultiplier = Key("sizingMultiplier", default: 3 ) - static let bufferFromDock = Key("bufferFromDock", default: 0 ) - static let hoverWindowOpenDelay = Key("openDelay", default: 0 ) - - static let screenCaptureCacheLifespan = Key("screenCaptureCacheLifespan", default: 60 ) - static let uniformCardRadius = Key("uniformCardRadius", default: true ) - static let tapEquivalentInterval = Key("tapEquivalentInterval", default: 1.5 ) - static let previewHoverAction = Key("previewHoverAction", default: .none ) - - static let showAnimations = Key("showAnimations", default: true ) - static let enableWindowSwitcher = Key("enableWindowSwitcher", default: true ) + static let sizingMultiplier = Key("sizingMultiplier", default: 3) + static let bufferFromDock = Key("bufferFromDock", default: 0) + static let hoverWindowOpenDelay = Key("openDelay", default: 0) + + static let screenCaptureCacheLifespan = Key("screenCaptureCacheLifespan", default: 60) + static let uniformCardRadius = Key("uniformCardRadius", default: true) + static let tapEquivalentInterval = Key("tapEquivalentInterval", default: 1.5) + static let previewHoverAction = Key("previewHoverAction", default: .none) + + static let showAnimations = Key("showAnimations", default: true) + static let enableWindowSwitcher = Key("enableWindowSwitcher", default: true) static let showMenuBarIcon = Key("showMenuBarIcon", default: true) - static let defaultCMDTABKeybind = Key("defaultCMDTABKeybind", default: true ) - static let launched = Key("launched", default: false ) - static let Int64maskCommand = Key("Int64maskCommand", default: 1048840 ) - static let Int64maskControl = Key("Int64maskControl", default: 262401 ) - static let Int64maskAlternate = Key("Int64maskAlternate", default: 524576 ) + static let defaultCMDTABKeybind = Key("defaultCMDTABKeybind", default: true) + static let launched = Key("launched", default: false) + static let Int64maskCommand = Key("Int64maskCommand", default: 1_048_840) + static let Int64maskControl = Key("Int64maskControl", default: 262_401) + static let Int64maskAlternate = Key("Int64maskAlternate", default: 524_576) static let UserKeybind = Key("UserKeybind", default: UserKeyBind(keyCode: 48, modifierFlags: Defaults[.Int64maskControl])) - + static let showAppName = Key("showAppName", default: true) - static let appNameStyle = Key("appNameStyle", default: .default ) - - static let showWindowTitle = Key("showWindowTitle", default: true ) + static let appNameStyle = Key("appNameStyle", default: .default) + + static let showWindowTitle = Key("showWindowTitle", default: true) static let windowTitleDisplayCondition = Key("windowTitleDisplayCondition", default: .all) static let windowTitleVisibility = Key("windowTitleVisibility", default: .whenHoveringPreview) - static let windowTitlePosition = Key("windowTitlePosition", default: .bottomLeft ) - - static let trafficLightButtonsVisibility = Key("trafficLightButtonsVisibility", default: .dimmedOnPreviewHover ) + static let windowTitlePosition = Key("windowTitlePosition", default: .bottomLeft) + + static let trafficLightButtonsVisibility = Key("trafficLightButtonsVisibility", default: .dimmedOnPreviewHover) static let trafficLightButtonsPosition = Key("trafficLightButtonsPosition", default: .topLeft) } enum WindowTitleDisplayCondition: String, CaseIterable, Defaults.Serializable { - case all = "all" - case dockPreviewsOnly = "dockPreviewsOnly" - case windowSwitcherOnly = "windowSwitcherOnly" - + case all + case dockPreviewsOnly + case windowSwitcherOnly + var localizedName: String { switch self { case .all: @@ -62,7 +62,7 @@ enum WindowTitlePosition: String, CaseIterable, Defaults.Serializable { case bottomRight case topRight case topLeft - + var localizedName: String { switch self { case .bottomLeft: @@ -81,7 +81,7 @@ enum AppNameStyle: String, CaseIterable, Defaults.Serializable { case `default` case embedded case popover - + var localizedName: String { switch self { case .default: @@ -97,7 +97,7 @@ enum AppNameStyle: String, CaseIterable, Defaults.Serializable { enum WindowTitleVisibility: String, CaseIterable, Defaults.Serializable { case whenHoveringPreview case alwaysVisible - + var localizedName: String { switch self { case .whenHoveringPreview: @@ -113,7 +113,7 @@ enum TrafficLightButtonsVisibility: String, CaseIterable, Defaults.Serializable case dimmedOnPreviewHover case fullOpacityOnPreviewHover case alwaysVisible - + var localizedName: String { switch self { case .never: @@ -133,7 +133,7 @@ enum TrafficLightButtonsPosition: String, CaseIterable, Defaults.Serializable { case topRight case bottomRight case bottomLeft - + var localizedName: String { switch self { case .topLeft: @@ -152,7 +152,7 @@ enum PreviewHoverAction: String, CaseIterable, Defaults.Serializable { case none case tap case previewFullSize - + var localizedName: String { switch self { case .none: diff --git a/DockDoor/logic/Utilities/App Icon.swift b/DockDoor/logic/Utilities/App Icon.swift index 5f81c454..7acfea8e 100644 --- a/DockDoor/logic/Utilities/App Icon.swift +++ b/DockDoor/logic/Utilities/App Icon.swift @@ -7,67 +7,67 @@ import AppKit -struct AppIconUtil { +enum AppIconUtil { // MARK: - Properties - + private static var iconCache: [String: (image: NSImage, timestamp: Date)] = [:] private static let cacheExpiryInterval: TimeInterval = 3600 // 1 hour - + // MARK: - App Icons - + static func getIcon(file path: URL) -> NSImage? { let cacheKey = path.path removeExpiredCacheEntries() - + if let cachedEntry = iconCache[cacheKey], Date().timeIntervalSince(cachedEntry.timestamp) < cacheExpiryInterval { return cachedEntry.image } - + guard FileManager.default.fileExists(atPath: path.path) else { return nil } - + let icon = NSWorkspace.shared.icon(forFile: path.path) iconCache[cacheKey] = (image: icon, timestamp: Date()) return icon } - + static func getIcon(bundleID: String) -> NSImage? { removeExpiredCacheEntries() - + if let cachedEntry = iconCache[bundleID], Date().timeIntervalSince(cachedEntry.timestamp) < cacheExpiryInterval { return cachedEntry.image } - + guard let path = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleID) else { return nil } - + let icon = getIcon(file: path) iconCache[bundleID] = (image: icon!, timestamp: Date()) return icon } - + static func getIcon(application: String) -> NSImage? { return getIcon(bundleID: application) } - + // MARK: - Bundles - + static func bundle(forBundleID: String) -> Bundle? { guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: forBundleID) else { return nil } - + return Bundle(url: url) } - + // MARK: - Cache Management - + static func clearCache() { iconCache.removeAll() } - + private static func removeExpiredCacheEntries() { let now = Date() iconCache = iconCache.filter { now.timeIntervalSince($0.value.timestamp) < cacheExpiryInterval } diff --git a/DockDoor/logic/Utilities/CacheUtil.swift b/DockDoor/logic/Utilities/CacheUtil.swift index 95b10a98..6809fe5b 100644 --- a/DockDoor/logic/Utilities/CacheUtil.swift +++ b/DockDoor/logic/Utilities/CacheUtil.swift @@ -8,11 +8,11 @@ struct CachedImage { let timestamp: Date } -final class CacheUtil { +enum CacheUtil { private static var imageCache: [CGWindowID: CachedImage] = [:] private static let cacheQueue = DispatchQueue(label: "com.dockdoor.cacheQueue", attributes: .concurrent) private static var cacheExpirySeconds: Double = Defaults[.screenCaptureCacheLifespan] - + /// Clears expired cache items based on cache expiry time. static func clearExpiredCache() { let now = Date() @@ -20,22 +20,22 @@ final class CacheUtil { imageCache = imageCache.filter { now.timeIntervalSince($0.value.timestamp) <= cacheExpirySeconds } } } - + /// Resets the image and icon cache. static func resetCache() { cacheQueue.async(flags: .barrier) { imageCache.removeAll() } } - + static func getCachedImage(for windowID: CGWindowID) -> CGImage? { if let cachedImage = imageCache[windowID], Date().timeIntervalSince(cachedImage.timestamp) <= cacheExpirySeconds { return cachedImage.image } return nil } - - static func setCachedImage(for windowID: CGWindowID, image: CGImage){ + + static func setCachedImage(for windowID: CGWindowID, image: CGImage) { imageCache[windowID] = CachedImage(image: image, timestamp: Date()) } } diff --git a/DockDoor/logic/Utilities/Extensions.swift b/DockDoor/logic/Utilities/Extensions.swift index 5b46ef14..3c57c127 100644 --- a/DockDoor/logic/Utilities/Extensions.swift +++ b/DockDoor/logic/Utilities/Extensions.swift @@ -28,7 +28,7 @@ extension Color { .sRGB, red: Double(r) / 255, green: Double(g) / 255, - blue: Double(b) / 255, + blue: Double(b) / 255, opacity: Double(a) / 255 ) } diff --git a/DockDoor/logic/Utilities/KeybindHelper.swift b/DockDoor/logic/Utilities/KeybindHelper.swift index 6f2062ec..b005dc0a 100644 --- a/DockDoor/logic/Utilities/KeybindHelper.swift +++ b/DockDoor/logic/Utilities/KeybindHelper.swift @@ -21,39 +21,39 @@ class KeybindHelper { private var eventTap: CFMachPort? private var runLoopSource: CFRunLoopSource? private var modifierValue: Int = 0 - + private init() { setupEventTap() } - + deinit { removeEventTap() } - + private func setupEventTap() { let eventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue) | (1 << CGEventType.flagsChanged.rawValue) - + eventTap = CGEvent.tapCreate( tap: .cgSessionEventTap, place: .headInsertEventTap, options: .defaultTap, eventsOfInterest: CGEventMask(eventMask), - callback: { proxy, type, event, refcon -> Unmanaged? in + callback: { proxy, type, event, _ -> Unmanaged? in return KeybindHelper.shared.handleEvent(proxy: proxy, type: type, event: event) }, userInfo: nil ) - + guard let eventTap = eventTap else { print("Failed to create event tap.") return } - + runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0) CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes) CGEvent.tapEnable(tap: eventTap, enable: true) } - + private func removeEventTap() { if let eventTap = eventTap, let runLoopSource = runLoopSource { CGEvent.tapEnable(tap: eventTap, enable: false) @@ -62,46 +62,45 @@ class KeybindHelper { self.runLoopSource = nil } } - - private func handleEvent(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent) -> Unmanaged? { + + private func handleEvent(proxy _: CGEventTapProxy, type: CGEventType, event: CGEvent) -> Unmanaged? { let keyCode = event.getIntegerValueField(.keyboardEventKeycode) let keyBoardShortcutSaved: UserKeyBind = Defaults[.UserKeybind] // UserDefaults.standard.getKeybind()! let shiftKeyCurrentlyPressed = event.flags.contains(.maskShift) var userDefinedKeyCurrentlyPressed = false - - if ((type == .flagsChanged) && (!Defaults[.defaultCMDTABKeybind])){ + + if type == .flagsChanged, !Defaults[.defaultCMDTABKeybind] { // New Keybind that the user has enforced, includes the modifier keys if event.flags.contains(.maskControl) { modifierValue = Defaults[.Int64maskControl] userDefinedKeyCurrentlyPressed = true - } - else if event.flags.contains(.maskAlternate) { + } else if event.flags.contains(.maskAlternate) { modifierValue = Defaults[.Int64maskAlternate] userDefinedKeyCurrentlyPressed = true } handleModifierEvent(modifierKeyPressed: userDefinedKeyCurrentlyPressed, shiftKeyPressed: shiftKeyCurrentlyPressed) } - - else if ((type == .flagsChanged) && (Defaults[.defaultCMDTABKeybind])){ + + else if type == .flagsChanged, Defaults[.defaultCMDTABKeybind] { // Default MacOS CMD + TAB keybind replaced handleModifierEvent(modifierKeyPressed: event.flags.contains(.maskCommand), shiftKeyPressed: shiftKeyCurrentlyPressed) } - - else if (type == .keyDown){ - if (isModifierKeyPressed && keyCode == keyBoardShortcutSaved.keyCode && modifierValue == keyBoardShortcutSaved.modifierFlags) || (Defaults[.defaultCMDTABKeybind] && event.flags.contains(.maskCommand) && keyCode == 48) { // Tab key - if SharedPreviewWindowCoordinator.shared.isVisible { // Check if HoverWindow is already shown - SharedPreviewWindowCoordinator.shared.cycleWindows(goBackwards: isShiftKeyPressed) // Cycle windows based on Shift key state + + else if type == .keyDown { + if (isModifierKeyPressed && keyCode == keyBoardShortcutSaved.keyCode && modifierValue == keyBoardShortcutSaved.modifierFlags) || (Defaults[.defaultCMDTABKeybind] && event.flags.contains(.maskCommand) && keyCode == 48) { // Tab key + if SharedPreviewWindowCoordinator.shared.isVisible { // Check if HoverWindow is already shown + SharedPreviewWindowCoordinator.shared.cycleWindows(goBackwards: isShiftKeyPressed) // Cycle windows based on Shift key state } else { - showHoverWindow() // Initialize HoverWindow if it's not open + showHoverWindow() // Initialize HoverWindow if it's not open } - return nil // Suppress the Tab key event + return nil // Suppress the Tab key event } } - + return Unmanaged.passUnretained(event) } - - private func handleModifierEvent(modifierKeyPressed : Bool, shiftKeyPressed : Bool){ + + private func handleModifierEvent(modifierKeyPressed: Bool, shiftKeyPressed: Bool) { if modifierKeyPressed != isModifierKeyPressed { isModifierKeyPressed = modifierKeyPressed } @@ -109,19 +108,19 @@ class KeybindHelper { if shiftKeyPressed != isShiftKeyPressed { isShiftKeyPressed = shiftKeyPressed } - + if !isModifierKeyPressed { SharedPreviewWindowCoordinator.shared.hidePreviewWindow() SharedPreviewWindowCoordinator.shared.selectAndBringToFrontCurrentWindow() } } - + private func showHoverWindow() { Task { [weak self] in guard let self = self else { return } let apps = NSWorkspace.shared.runningApplications let windows = apps.compactMap { app in - return try? WindowsUtil.getRunningAppWindows(for: app) + try? WindowsUtil.getRunningAppWindows(for: app) }.flatMap { $0 } await MainActor.run { [weak self] in guard let self = self else { return } diff --git a/DockDoor/logic/Utilities/MessageUtil.swift b/DockDoor/logic/Utilities/MessageUtil.swift index ac7c374a..03b55d5f 100644 --- a/DockDoor/logic/Utilities/MessageUtil.swift +++ b/DockDoor/logic/Utilities/MessageUtil.swift @@ -7,8 +7,7 @@ import Cocoa -struct MessageUtil { - +enum MessageUtil { enum ButtonAction { case ok case cancel diff --git a/DockDoor/logic/Utilities/Misc Utils.swift b/DockDoor/logic/Utilities/Misc Utils.swift index 9d8115e6..5f018588 100644 --- a/DockDoor/logic/Utilities/Misc Utils.swift +++ b/DockDoor/logic/Utilities/Misc Utils.swift @@ -5,21 +5,22 @@ // Created by Ethan Bills on 6/13/24. // +import Carbon import Cocoa import Defaults -import Carbon -func askUserToRestartApplication () -> Void { +func askUserToRestartApplication() { MessageUtil.showMessage(title: String(localized: "Restart required"), message: String(localized: "Please restart the application to apply your changes. Click OK to quit the app."), completion: { result in if result == .ok { let appDelegate = NSApplication.shared.delegate as! AppDelegate appDelegate.restartApp() - }}) + } + }) } func resetDefaultsToDefaultValues() { Defaults.removeAll() - + // reset the launched value Defaults[.launched] = true } @@ -46,24 +47,21 @@ func measureString(_ string: String, fontSize: CGFloat, fontWeight: NSFont.Weigh return size } -struct modifierConverter { +enum modifierConverter { static func toString(_ modifierIntValue: Int) -> String { if modifierIntValue == Defaults[.Int64maskCommand] { return "⌘" - } - else if modifierIntValue == Defaults[.Int64maskAlternate] { + } else if modifierIntValue == Defaults[.Int64maskAlternate] { return "⌥" - } - else if modifierIntValue == Defaults[.Int64maskControl] { + } else if modifierIntValue == Defaults[.Int64maskControl] { return "⌃" - } - else { + } else { return " " } } } -struct KeyCodeConverter { +enum KeyCodeConverter { static func toString(_ keyCode: UInt16) -> String { switch keyCode { case 48: @@ -75,21 +73,21 @@ struct KeyCodeConverter { case 36: return "↩︎" // Return symbol default: - + let source = TISCopyCurrentKeyboardInputSource().takeUnretainedValue() let layoutData = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) - + guard let data = layoutData else { return "?" } - + let layout = unsafeBitCast(data, to: CFData.self) let keyboardLayout = unsafeBitCast(CFDataGetBytePtr(layout), to: UnsafePointer.self) - + var keysDown: UInt32 = 0 var chars = [UniChar](repeating: 0, count: 4) - var realLength: Int = 0 - + var realLength = 0 + let result = UCKeyTranslate(keyboardLayout, keyCode, UInt16(kUCKeyActionDisplay), @@ -100,7 +98,7 @@ struct KeyCodeConverter { chars.count, &realLength, &chars) - + if result == noErr { return String(utf16CodeUnits: chars, count: realLength) } else { diff --git a/DockDoor/logic/Utilities/WindowClosureObservers.swift b/DockDoor/logic/Utilities/WindowClosureObservers.swift index cf8a16a3..3e763029 100644 --- a/DockDoor/logic/Utilities/WindowClosureObservers.swift +++ b/DockDoor/logic/Utilities/WindowClosureObservers.swift @@ -5,8 +5,8 @@ // Created by Ethan Bills on 6/30/24. // -import Foundation import AppKit +import Foundation class AppClosureObserver { static let shared = AppClosureObserver() @@ -14,18 +14,18 @@ class AppClosureObserver { private init() { setupObservers() } - + private func setupObservers() { let notificationCenter = NSWorkspace.shared.notificationCenter notificationCenter.addObserver(self, selector: #selector(appDidTerminate(_:)), name: NSWorkspace.didTerminateApplicationNotification, object: nil) notificationCenter.addObserver(self, selector: #selector(appDidActivate(_:)), name: NSWorkspace.didActivateApplicationNotification, object: nil) } - - @objc private func appDidTerminate(_ notification: Notification) { + + @objc private func appDidTerminate(_: Notification) { SharedPreviewWindowCoordinator.shared.hidePreviewWindow() } - - @objc private func appDidActivate(_ notification: Notification) { + + @objc private func appDidActivate(_: Notification) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { // add a delay to prevent interference with button clicks, which hide the window on their own if SharedPreviewWindowCoordinator.shared.isVisible { SharedPreviewWindowCoordinator.shared.hidePreviewWindow() diff --git a/DockDoor/logic/Window.swift b/DockDoor/logic/Window.swift index 3c9b92e8..0931f240 100644 --- a/DockDoor/logic/Window.swift +++ b/DockDoor/logic/Window.swift @@ -1,6 +1,6 @@ +import Cocoa import CoreGraphics import ScreenCaptureKit -import Cocoa class Window: Equatable { let wid: CGWindowID @@ -15,12 +15,12 @@ class Window: Equatable { var closeButton: AXUIElement? let isMinimized: Bool let isFullscreen: Bool - + // Equatable protocol static func == (lhs: Window, rhs: Window) -> Bool { return lhs.wid == rhs.wid } - + init(wid: CGWindowID, app: NSRunningApplication, level: CGWindowLevel, title: String!, size: CGSize?, appName: String, bundleID: String?, image: CGImage? = nil, axElement: AXUIElement, closeButton: AXUIElement? = nil, isMinimized: Bool, isFullscreen: Bool) { self.wid = wid self.app = app @@ -35,77 +35,77 @@ class Window: Equatable { self.isMinimized = isMinimized self.isFullscreen = isFullscreen } - + /// Toggles the full-screen state of a window. - func toggleFullScreen() { + func toggleFullScreen() { do { - if let isCurrentlyInFullScreen = try axElement.attribute(kAXFullscreenAttribute, Bool.self){ - self.axElement.setAttribute(kAXFullscreenAttribute, !isCurrentlyInFullScreen) + if let isCurrentlyInFullScreen = try axElement.attribute(kAXFullscreenAttribute, Bool.self) { + axElement.setAttribute(kAXFullscreenAttribute, !isCurrentlyInFullScreen) } } catch { print("An error occurred: \(error)") } } - + /// Toggles the minimize state of the window. func toggleMinimize() { - if self.isMinimized { + if isMinimized { // Un-minimize the window - self.axElement.setAttribute(kAXMinimizedAttribute, false) - - self.focus() + axElement.setAttribute(kAXMinimizedAttribute, false) + + focus() } else { // Minimize the window - self.axElement.setAttribute(kAXMinimizedAttribute, true) + axElement.setAttribute(kAXMinimizedAttribute, true) } } - + /// The following function was ported from https://github.com/Hammerspoon/hammerspoon/issues/370#issuecomment-545545468 - func makeKeyWindow(_ psn: ProcessSerialNumber) -> Void { - var cgWindowId_ = self.wid + func makeKeyWindow(_ psn: ProcessSerialNumber) { + var cgWindowId_ = wid var psn_ = psn - var bytes1 = [UInt8](repeating: 0, count: 0xf8) + var bytes1 = [UInt8](repeating: 0, count: 0xF8) bytes1[0x04] = 0xF8 bytes1[0x08] = 0x01 - bytes1[0x3a] = 0x10 - var bytes2 = [UInt8](repeating: 0, count: 0xf8) + bytes1[0x3A] = 0x10 + var bytes2 = [UInt8](repeating: 0, count: 0xF8) bytes2[0x04] = 0xF8 bytes2[0x08] = 0x02 - bytes2[0x3a] = 0x10 - memcpy(&bytes1[0x3c], &cgWindowId_, MemoryLayout.size) + bytes2[0x3A] = 0x10 + memcpy(&bytes1[0x3C], &cgWindowId_, MemoryLayout.size) memset(&bytes1[0x20], 0xFF, 0x10) - memcpy(&bytes2[0x3c], &cgWindowId_, MemoryLayout.size) + memcpy(&bytes2[0x3C], &cgWindowId_, MemoryLayout.size) memset(&bytes2[0x20], 0xFF, 0x10) - [bytes1, bytes2].forEach { bytes in - _ = bytes.withUnsafeBufferPointer() { pointer in + for bytes in [bytes1, bytes2] { + _ = bytes.withUnsafeBufferPointer { pointer in SLPSPostEventRecordTo(&psn_, &UnsafeMutablePointer(mutating: pointer.baseAddress)!.pointee) } } } - + /// Brings the window to the front and focuses it. func focus() { - self.app.activate() - + app.activate() + var psn = ProcessSerialNumber() - GetProcessForPID(self.app.processIdentifier, &psn) - _SLPSSetFrontProcessWithOptions(&psn, self.wid, SLPSMode.userGenerated.rawValue) - self.makeKeyWindow(psn) - - self.axElement.performAction(kAXRaiseAction) + GetProcessForPID(app.processIdentifier, &psn) + _SLPSSetFrontProcessWithOptions(&psn, wid, SLPSMode.userGenerated.rawValue) + makeKeyWindow(psn) + + axElement.performAction(kAXRaiseAction) } - + /// Close the window using its close button. func close() { - self.closeButton?.performAction(kAXPressAction) + closeButton?.performAction(kAXPressAction) } - + /// Terminates the window's application. func quitApp(force: Bool) { if force { - self.app.forceTerminate() + app.forceTerminate() } else { - self.app.terminate() + app.terminate() } } } diff --git a/DockDoor/logic/WindowsUtil.swift b/DockDoor/logic/WindowsUtil.swift index 02fa8111..b9056241 100644 --- a/DockDoor/logic/WindowsUtil.swift +++ b/DockDoor/logic/WindowsUtil.swift @@ -1,52 +1,51 @@ -import Cocoa import ApplicationServices -import ScreenCaptureKit +import Cocoa import Defaults - +import ScreenCaptureKit final class WindowsUtil { let filteredBundleIdentifiers: [String] = ["com.apple.notificationcenterui"] // filters widgets - + /// Captures the image of a given window. static func getWindowImage(windowID: CGWindowID, bestResolution: Bool) -> CGImage? { CacheUtil.clearExpiredCache() - + if let cachedImage = CacheUtil.getCachedImage(for: windowID) { return cachedImage } - + let image = windowID.screenshot(bestResolution: true) guard let image = image else { return nil } - + CacheUtil.setCachedImage(for: windowID, image: image) - + return image } - + /// Retrieves the running application by its name. static func findRunningApplicationByName(named applicationName: String) -> NSRunningApplication? { return NSWorkspace.shared.runningApplications.first { applicationName.contains($0.localizedName ?? "") || ($0.localizedName?.contains(applicationName) ?? false) } } - + // Helper function to get the scale factor for a given window private static func getScaleFactorForWindow(windowID: CGWindowID) async -> CGFloat { return await MainActor.run { guard let window = NSApplication.shared.window(withWindowNumber: Int(windowID)) else { return NSScreen.main?.backingScaleFactor ?? 2.0 } - + if NSScreen.screens.count > 1 { if let currentScreen = window.screen { return currentScreen.backingScaleFactor } } - + return NSScreen.main?.backingScaleFactor ?? 2.0 } } - + static func fetchWindowInfo(axWindow: AXUIElement, app: NSRunningApplication) -> Window? { if let wid = try? axWindow.cgWindowId(), let title = try? axWindow.title(), @@ -56,7 +55,8 @@ final class WindowsUtil { let level = try? wid.level(), let isFullscreen = try? axWindow.isFullscreen(), let isMinimized = try? axWindow.isMinimized(), - let closeButton = try? axWindow.closeButton() { + let closeButton = try? axWindow.closeButton() + { if AXUIElement.isActualWindow(app, wid, level, title, subrole, role, size) { let image = getWindowImage(windowID: wid, bestResolution: true) return Window( @@ -81,10 +81,10 @@ final class WindowsUtil { } return nil } - + static func getRunningAppWindows(for app: NSRunningApplication) throws -> [Window] { let appElement = AXUIElementCreateApplication(app.processIdentifier) - + guard let windows = try appElement.windows() else { return [] } @@ -96,7 +96,6 @@ final class WindowsUtil { } } } - } actor LimitedTaskGroup { @@ -104,12 +103,12 @@ actor LimitedTaskGroup { private let maxConcurrentTasks: Int private var runningTasks = 0 private let semaphore: AsyncSemaphore - + init(maxConcurrentTasks: Int) { self.maxConcurrentTasks = maxConcurrentTasks - self.semaphore = AsyncSemaphore(value: maxConcurrentTasks) + semaphore = AsyncSemaphore(value: maxConcurrentTasks) } - + func addTask(_ operation: @escaping () async throws -> T) { let task = Task { await semaphore.wait() @@ -118,17 +117,17 @@ actor LimitedTaskGroup { } tasks.append(task) } - + func waitForAll() async throws -> [T] { defer { tasks.removeAll() } - + return try await withThrowingTaskGroup(of: T.self) { group in for task in tasks { group.addTask { try await task.value } } - + var results: [T] = [] for try await result in group { results.append(result) @@ -141,11 +140,11 @@ actor LimitedTaskGroup { actor AsyncSemaphore { private var value: Int private var waiters: [CheckedContinuation] = [] - + init(value: Int) { self.value = value } - + func wait() async { if value > 0 { value -= 1 @@ -155,7 +154,7 @@ actor AsyncSemaphore { } } } - + func signal() { if let waiter = waiters.first { waiters.removeFirst() diff --git a/DockDoor/logic/api-wrappers/AXUIElement.swift b/DockDoor/logic/api-wrappers/AXUIElement.swift index 310615f8..2ec65361 100644 --- a/DockDoor/logic/api-wrappers/AXUIElement.swift +++ b/DockDoor/logic/api-wrappers/AXUIElement.swift @@ -1,10 +1,10 @@ -import Cocoa -import ApplicationServices.HIServices.AXUIElement -import ApplicationServices.HIServices.AXValue +import ApplicationServices.HIServices.AXActionConstants +import ApplicationServices.HIServices.AXAttributeConstants import ApplicationServices.HIServices.AXError import ApplicationServices.HIServices.AXRoleConstants -import ApplicationServices.HIServices.AXAttributeConstants -import ApplicationServices.HIServices.AXActionConstants +import ApplicationServices.HIServices.AXUIElement +import ApplicationServices.HIServices.AXValue +import Cocoa extension AXUIElement { static let globalTimeoutInSeconds = Float(120) @@ -20,11 +20,11 @@ extension AXUIElement { func axCallWhichCanThrow(_ result: AXError, _ successValue: inout T) throws -> T? { switch result { - case .success: return successValue - // .cannotComplete can happen if the app is unresponsive; we throw in that case to retry until the call succeeds - case .cannotComplete: throw AxError.runtimeError - // for other errors it's pointless to retry - default: return nil + case .success: return successValue + // .cannotComplete can happen if the app is unresponsive; we throw in that case to retry until the call succeeds + case .cannotComplete: throw AxError.runtimeError + // for other errors it's pointless to retry + default: return nil } } @@ -62,41 +62,41 @@ extension AXUIElement { // Some non-windows have cgWindowId == 0 (e.g. windows of apps starting at login with the checkbox "Hidden" checked) return wid != 0 && - // Finder's file copy dialogs are wide but < 100 height (see https://github.com/lwouis/alt-tab-macos/issues/1466) - // Sonoma introduced a bug: a caps-lock indicator shows as a small window. We try to hide it by filtering out tiny windows - size != nil && (size!.width > 100 || size!.height > 100) && ( - ( - books(runningApp) || - keynote(runningApp) || - preview(runningApp, subrole) || - iina(runningApp) || - openFlStudio(runningApp, title) || - crossoverWindow(runningApp, role, subrole, level) || - isAlwaysOnTopScrcpy(runningApp, level, role, subrole) - ) || ( - // CGWindowLevel == .normalWindow helps filter out iStats Pro and other top-level pop-overs, and floating windows - level == CGWindow.normalLevel && ( - [kAXStandardWindowSubrole, kAXDialogSubrole].contains(subrole) || - openBoard(runningApp) || - adobeAudition(runningApp, subrole) || - adobeAfterEffects(runningApp, subrole) || - steam(runningApp, title, role) || - worldOfWarcraft(runningApp, role) || - battleNetBootstrapper(runningApp, role) || - firefox(runningApp, role, size) || - vlcFullscreenVideo(runningApp, role) || - sanGuoShaAirWD(runningApp) || - dvdFab(runningApp) || - drBetotte(runningApp) || - androidEmulator(runningApp, title) || - autocad(runningApp, subrole) - ) && ( - mustHaveIfJetbrainApp(runningApp, title, subrole, size!) && - mustHaveIfSteam(runningApp, title, role) && - mustHaveIfColorSlurp(runningApp, title, subrole) + // Finder's file copy dialogs are wide but < 100 height (see https://github.com/lwouis/alt-tab-macos/issues/1466) + // Sonoma introduced a bug: a caps-lock indicator shows as a small window. We try to hide it by filtering out tiny windows + size != nil && (size!.width > 100 || size!.height > 100) && ( + ( + books(runningApp) || + keynote(runningApp) || + preview(runningApp, subrole) || + iina(runningApp) || + openFlStudio(runningApp, title) || + crossoverWindow(runningApp, role, subrole, level) || + isAlwaysOnTopScrcpy(runningApp, level, role, subrole) + ) || ( + // CGWindowLevel == .normalWindow helps filter out iStats Pro and other top-level pop-overs, and floating windows + level == CGWindow.normalLevel && ( + [kAXStandardWindowSubrole, kAXDialogSubrole].contains(subrole) || + openBoard(runningApp) || + adobeAudition(runningApp, subrole) || + adobeAfterEffects(runningApp, subrole) || + steam(runningApp, title, role) || + worldOfWarcraft(runningApp, role) || + battleNetBootstrapper(runningApp, role) || + firefox(runningApp, role, size) || + vlcFullscreenVideo(runningApp, role) || + sanGuoShaAirWD(runningApp) || + dvdFab(runningApp) || + drBetotte(runningApp) || + androidEmulator(runningApp, title) || + autocad(runningApp, subrole) + ) && ( + mustHaveIfJetbrainApp(runningApp, title, subrole, size!) && + mustHaveIfSteam(runningApp, title, role) && + mustHaveIfColorSlurp(runningApp, title, subrole) + ) ) ) - ) } private static func mustHaveIfJetbrainApp(_ runningApp: NSRunningApplication, _ title: String?, _ subrole: String?, _ size: NSSize) -> Bool { @@ -109,7 +109,7 @@ extension AXUIElement { ) } - private static func mustHaveIfColorSlurp(_ runningApp: NSRunningApplication, _ title: String?, _ subrole: String?) -> Bool { + private static func mustHaveIfColorSlurp(_ runningApp: NSRunningApplication, _: String?, _ subrole: String?) -> Bool { return runningApp.bundleIdentifier != "com.IdeaPunch.ColorSlurp" || subrole == kAXStandardWindowSubrole } @@ -197,11 +197,12 @@ extension AXUIElement { // VLC fullscreen video have subrole == AXUnknown if fullscreen'ed return (runningApp.bundleIdentifier?.hasPrefix("org.videolan.vlc") ?? false) && role == kAXWindowRole } - + static func isAndroidEmulator(_ app: NSRunningApplication) -> Bool { // NSRunningApplication provides no way to identify the emulator; we pattern match on its KERN_PROCARGS if app.bundleIdentifier == nil, - let executablePath = Sysctl.run([CTL_KERN, KERN_PROCARGS, app.processIdentifier]) { + let executablePath = Sysctl.run([CTL_KERN, KERN_PROCARGS, app.processIdentifier]) + { // example path: ~/Library/Android/sdk/emulator/qemu/darwin-x86_64/qemu-system-x86_64 return executablePath.range(of: "qemu-system[^/]*$", options: .regularExpression, range: nil, locale: nil) != nil } @@ -289,7 +290,7 @@ extension AXUIElement { let result = AXObserverAddNotification(axObserver, self, notification as CFString, nil) if result == .success || result == .notificationAlreadyRegistered { callback?() - } else if result != .notificationUnsupported && result != .notImplemented { + } else if result != .notificationUnsupported, result != .notImplemented { throw AxError.runtimeError } } diff --git a/DockDoor/logic/api-wrappers/CGWindow.swift b/DockDoor/logic/api-wrappers/CGWindow.swift index 6a584af9..18645778 100644 --- a/DockDoor/logic/api-wrappers/CGWindow.swift +++ b/DockDoor/logic/api-wrappers/CGWindow.swift @@ -39,7 +39,7 @@ extension CGWindow { return value(kCGWindowName, String.self) } - private func value(_ key: CFString, _ type: T.Type) -> T? { + private func value(_ key: CFString, _: T.Type) -> T? { return self[key] as? T } } diff --git a/DockDoor/logic/api-wrappers/CGWindowID.swift b/DockDoor/logic/api-wrappers/CGWindowID.swift index f0cd0029..4aa44c47 100644 --- a/DockDoor/logic/api-wrappers/CGWindowID.swift +++ b/DockDoor/logic/api-wrappers/CGWindowID.swift @@ -22,7 +22,7 @@ extension CGWindowID { return list.first } - private func cgProperty(_ key: String, _ type: T.Type) -> T? { + private func cgProperty(_ key: String, _: T.Type) -> T? { var value: AnyObject? CGSCopyWindowProperty(cgsMainConnectionId, self, key as CFString, &value) return value as? T diff --git a/DockDoor/logic/api-wrappers/PrivateApis.swift b/DockDoor/logic/api-wrappers/PrivateApis.swift index af382d10..d7531c71 100644 --- a/DockDoor/logic/api-wrappers/PrivateApis.swift +++ b/DockDoor/logic/api-wrappers/PrivateApis.swift @@ -28,7 +28,6 @@ func CGSMainConnectionID() -> CGSConnectionID @_silgen_name("_AXUIElementGetWindow") @discardableResult func _AXUIElementGetWindow(_ axUiElement: AXUIElement, _ wid: inout CGWindowID) -> AXError - // returns an array of CGImage of the windows which ID is given as `windowList`. `windowList` is supposed to be an array of IDs but in my test on High Sierra, the function ignores other IDs than the first, and always returns the screenshot of the first window in the array // * performance: the `HW` in the name seems to imply better performance, and it was observed by some contributors that it seems to be faster (see https://github.com/lwouis/alt-tab-macos/issues/45) than other methods // * quality: medium diff --git a/DockDoor/logic/api-wrappers/Sysctl.swift b/DockDoor/logic/api-wrappers/Sysctl.swift index 8f82879d..1eade90b 100644 --- a/DockDoor/logic/api-wrappers/Sysctl.swift +++ b/DockDoor/logic/api-wrappers/Sysctl.swift @@ -1,33 +1,33 @@ import Foundation -public struct Sysctl { +public enum Sysctl { static func run(_ name: String) -> String { - return run(name, { $0.baseAddress.flatMap { String(validatingUTF8: $0) } }) ?? "" + return run(name) { $0.baseAddress.flatMap { String(validatingUTF8: $0) } } ?? "" } - static func run(_ name: String, _ type: T.Type) -> T? { - return run(name, { $0.baseAddress?.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee } }) + static func run(_ name: String, _: T.Type) -> T? { + return run(name) { $0.baseAddress?.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee } } } static func run(_ keys: [Int32]) -> String? { - return data(keys)?.withUnsafeBufferPointer() { dataPointer -> String? in + return data(keys)?.withUnsafeBufferPointer { dataPointer -> String? in dataPointer.baseAddress.flatMap { String(validatingUTF8: $0) } } } private static func run(_ name: String, _ fn: (UnsafeBufferPointer) -> R?) -> R? { - return keys(name).flatMap { keys in data(keys)?.withUnsafeBufferPointer() { fn($0) } } + return keys(name).flatMap { keys in data(keys)?.withUnsafeBufferPointer { fn($0) } } } private static func data(_ keys: [Int32]) -> [Int8]? { - return keys.withUnsafeBufferPointer() { keysPointer in + return keys.withUnsafeBufferPointer { keysPointer in var requiredSize = 0 let preFlightResult = Darwin.sysctl(UnsafeMutablePointer(mutating: keysPointer.baseAddress), UInt32(keys.count), nil, &requiredSize, nil, 0) if preFlightResult != 0 { return nil } - let data = Array(repeating: 0, count: requiredSize) - let result = data.withUnsafeBufferPointer() { dataBuffer -> Int32 in + let data = [Int8](repeating: 0, count: requiredSize) + let result = data.withUnsafeBufferPointer { dataBuffer -> Int32 in return Darwin.sysctl(UnsafeMutablePointer(mutating: keysPointer.baseAddress), UInt32(keys.count), UnsafeMutableRawPointer(mutating: dataBuffer.baseAddress), &requiredSize, nil, 0) } if result != 0 { @@ -39,14 +39,14 @@ public struct Sysctl { private static func keys(_ name: String) -> [Int32]? { var keysBufferSize = Int(CTL_MAXNAME) - var keysBuffer = Array(repeating: 0, count: keysBufferSize) + var keysBuffer = [Int32](repeating: 0, count: keysBufferSize) _ = keysBuffer.withUnsafeMutableBufferPointer { (lbp: inout UnsafeMutableBufferPointer) in name.withCString { (nbp: UnsafePointer) in sysctlnametomib(nbp, lbp.baseAddress, &keysBufferSize) } } if keysBuffer.count > keysBufferSize { - keysBuffer.removeSubrange(keysBufferSize.. NSVisualEffectView { + func makeNSView(context _: Context) -> NSVisualEffectView { let effectView = NSVisualEffectView() effectView.state = .active effectView.material = .popover return effectView } - func updateNSView(_ nsView: NSVisualEffectView, context: Context) { - } + func updateNSView(_: NSVisualEffectView, context _: Context) {} } struct MaterialBlurView: NSViewRepresentable { var material: NSVisualEffectView.Material - func makeNSView(context: Context) -> NSVisualEffectView { + func makeNSView(context _: Context) -> NSVisualEffectView { let view = NSVisualEffectView() view.material = material view.blendingMode = .behindWindow @@ -30,5 +29,5 @@ struct MaterialBlurView: NSViewRepresentable { return view } - func updateNSView(_ nsView: NSVisualEffectView, context: Context) {} + func updateNSView(_: NSVisualEffectView, context _: Context) {} } diff --git a/DockDoor/ui/Components/FluidGradient.swift b/DockDoor/ui/Components/FluidGradient.swift index cadd6375..b0e21005 100644 --- a/DockDoor/ui/Components/FluidGradient.swift +++ b/DockDoor/ui/Components/FluidGradient.swift @@ -5,8 +5,8 @@ // Created by Ethan Bills on 7/11/24. // -import SwiftUI import FluidGradient +import SwiftUI func fluidGradient() -> some View { FluidGradient( @@ -20,7 +20,7 @@ func fluidGradient() -> some View { struct FluidGradientBorder: ViewModifier { let cornerRadius: CGFloat let lineWidth: CGFloat - + func body(content: Content) -> some View { content .overlay( diff --git a/DockDoor/ui/Components/Marquee.swift b/DockDoor/ui/Components/Marquee.swift index 661e6a20..8435d81e 100644 --- a/DockDoor/ui/Components/Marquee.swift +++ b/DockDoor/ui/Components/Marquee.swift @@ -1,12 +1,12 @@ // -// TheMarquee.swift +// Marquee.swift // NotchNook // // Created by Igor Marcossi on 16/06/24. // -import SwiftUI import SmoothGradient +import SwiftUI struct TheMarquee: View { var width: Double @@ -79,60 +79,58 @@ struct TheMarquee: View { extension View { func fadeOnEdges(axis: Axis, fadeLength: Double, disable: Bool = false) -> some View { - self - .mask { - if (!disable) { - GeometryReader { geo in - DynStack(direction: axis, spacing: 0) { - SmoothLinearGradient( - from: .black.opacity(0), - to: .black.opacity(1), - startPoint: axis == .horizontal ? .leading : .top, - endPoint: axis == .horizontal ? .trailing : .bottom, - curve: .easeInOut - ) - .frame(width: axis == .horizontal ? fadeLength : nil, height: axis == .vertical ? fadeLength : nil) - Color.black.frame(maxWidth: .infinity) - SmoothLinearGradient( - from: .black.opacity(0), - to: .black.opacity(1), - startPoint: axis == .horizontal ? .trailing : .bottom, - endPoint: axis == .horizontal ? .leading : .top, - curve: .easeInOut - ) - .frame(width: axis == .horizontal ? fadeLength : nil, height: axis == .vertical ? fadeLength : nil) - } + mask { + if !disable { + GeometryReader { _ in + DynStack(direction: axis, spacing: 0) { + SmoothLinearGradient( + from: .black.opacity(0), + to: .black.opacity(1), + startPoint: axis == .horizontal ? .leading : .top, + endPoint: axis == .horizontal ? .trailing : .bottom, + curve: .easeInOut + ) + .frame(width: axis == .horizontal ? fadeLength : nil, height: axis == .vertical ? fadeLength : nil) + Color.black.frame(maxWidth: .infinity) + SmoothLinearGradient( + from: .black.opacity(0), + to: .black.opacity(1), + startPoint: axis == .horizontal ? .trailing : .bottom, + endPoint: axis == .horizontal ? .leading : .top, + curve: .easeInOut + ) + .frame(width: axis == .horizontal ? fadeLength : nil, height: axis == .vertical ? fadeLength : nil) } - } else { - Color.black } + } else { + Color.black } + } } } extension View { func measure(_ sizeBinding: Binding) -> some View { - self - .background { - Color.clear - .background( - GeometryReader { geometry in - Color.clear - .preference(key: ViewSizeKey.self, value: geometry.size) - } - ) - .onPreferenceChange(ViewSizeKey.self) { size in - sizeBinding.wrappedValue = size + background { + Color.clear + .background( + GeometryReader { geometry in + Color.clear + .preference(key: ViewSizeKey.self, value: geometry.size) } - } + ) + .onPreferenceChange(ViewSizeKey.self) { size in + sizeBinding.wrappedValue = size + } + } } } struct ViewSizeKey: PreferenceKey { - static var defaultValue: CGSize = .zero - static func reduce(value: inout CGSize, nextValue: () -> CGSize) { - value = value + nextValue() - } + static var defaultValue: CGSize = .zero + static func reduce(value: inout CGSize, nextValue: () -> CGSize) { + value = value + nextValue() + } } func doAfter(_ seconds: Double, action: @escaping () -> Void) { diff --git a/DockDoor/ui/Components/StackedShadow.swift b/DockDoor/ui/Components/StackedShadow.swift index 7a82a1d4..cdf7020e 100644 --- a/DockDoor/ui/Components/StackedShadow.swift +++ b/DockDoor/ui/Components/StackedShadow.swift @@ -13,7 +13,7 @@ struct StackedShadow: ViewModifier { var x: CGFloat var y: CGFloat var color: Color - + init(stacked count: Int, radius: CGFloat = 10, x: CGFloat = 0, y: CGFloat = 0, color: Color = .black) { self.count = count self.radius = radius @@ -21,20 +21,20 @@ struct StackedShadow: ViewModifier { self.y = y self.color = color } - + func body(content: Content) -> some View { content .shadow(color: color.opacity(Double(1) / Double(count)), radius: radius, x: x, y: y) .modifier(RecursiveShadow(count: count - 1, radius: radius, x: x, y: y, color: color)) } - + private struct RecursiveShadow: ViewModifier { var count: Int var radius: CGFloat var x: CGFloat var y: CGFloat var color: Color - + func body(content: Content) -> some View { if count > 0 { content @@ -49,6 +49,6 @@ struct StackedShadow: ViewModifier { extension View { func shadow(stacked count: Int, radius: CGFloat = 10, x: CGFloat = 0, y: CGFloat = 0, color: Color = .black) -> some View { - self.modifier(StackedShadow(stacked: count, radius: radius, x: x, y: y, color: color)) + modifier(StackedShadow(stacked: count, radius: radius, x: x, y: y, color: color)) } } diff --git a/DockDoor/ui/UIExtensions.swift b/DockDoor/ui/UIExtensions.swift index 91407420..5465cb80 100644 --- a/DockDoor/ui/UIExtensions.swift +++ b/DockDoor/ui/UIExtensions.swift @@ -1,5 +1,5 @@ // -// dockStyle.swift +// UIExtensions.swift // DockDoor // // Created by Igor Marcossi on 14/06/24. @@ -9,7 +9,7 @@ import SwiftUI struct DockStyleModifier: ViewModifier { let cornerRadius: Double - + func body(content: Content) -> some View { content .background { @@ -32,7 +32,6 @@ struct DockStyleModifier: ViewModifier { extension View { func dockStyle(cornerRadius: Double = 19) -> some View { - self - .modifier(DockStyleModifier(cornerRadius: cornerRadius)) + modifier(DockStyleModifier(cornerRadius: cornerRadius)) } } diff --git a/DockDoor/ui/Views/FirstTimeView.swift b/DockDoor/ui/Views/FirstTimeView.swift index 712ffd3f..84f6f7cf 100644 --- a/DockDoor/ui/Views/FirstTimeView.swift +++ b/DockDoor/ui/Views/FirstTimeView.swift @@ -9,7 +9,7 @@ import SwiftUI struct FirstTimeView: View { @State private var showPermissions = false - + var body: some View { HStack { VStack(spacing: 20) { @@ -18,36 +18,36 @@ struct FirstTimeView: View { .aspectRatio(contentMode: .fit) .frame(width: 100, height: 100) .foregroundColor(.blue) - + Text("Welcome to DockDoor!") .font(.largeTitle) .fontWeight(.bold) - + Text("Enhance your dock experience!") .font(.subheadline) .foregroundColor(.secondary) - + Button("Get Started") { openPermissionsWindow() } .buttonStyle(LinkButtonStyle()) } .padding() - + Divider() - + VStack(alignment: .leading, spacing: 20) { Text("Why we need permissions") .font(.title2) .fontWeight(.bold) - + VStack(alignment: .leading, spacing: 10) { Text("Accessibility:") .font(.headline) Text("• To detect when you hover over the dock") Text("• Enables real-time interaction with dock items") } - + VStack(alignment: .leading, spacing: 10) { Text("Screen Capturing:") .font(.headline) @@ -59,18 +59,19 @@ struct FirstTimeView: View { } .dockStyle(cornerRadius: 0) } - + private func openPermissionsWindow() { let contentView = PermissionsSettingsView() - + // Create the hosting controller with the PermView let hostingController = NSHostingController(rootView: contentView) - + // Create the settings window let permissionsWindow = NSWindow( contentRect: NSRect(origin: .zero, size: NSSize(width: 200, height: 200)), styleMask: [.titled, .closable, .resizable], - backing: .buffered, defer: false) + backing: .buffered, defer: false + ) permissionsWindow.center() permissionsWindow.setFrameAutosaveName("DockDoor Permissions") permissionsWindow.contentView = hostingController.view diff --git a/DockDoor/ui/Views/Hover Window/FullSizePreviewView.swift b/DockDoor/ui/Views/Hover Window/FullSizePreviewView.swift index 7d5addc5..e4a6734a 100644 --- a/DockDoor/ui/Views/Hover Window/FullSizePreviewView.swift +++ b/DockDoor/ui/Views/Hover Window/FullSizePreviewView.swift @@ -5,15 +5,15 @@ // Created by Ethan Bills on 7/11/24. // -import SwiftUI import Defaults +import SwiftUI struct FullSizePreviewView: View { let window: Window let maxSize: CGSize - + @Default(.uniformCardRadius) var uniformCardRadius - + var body: some View { VStack(alignment: .center) { Group { diff --git a/DockDoor/ui/Views/Hover Window/SharedPreviewWindowCoordinator.swift b/DockDoor/ui/Views/Hover Window/SharedPreviewWindowCoordinator.swift index d5fe5077..ff353339 100644 --- a/DockDoor/ui/Views/Hover Window/SharedPreviewWindowCoordinator.swift +++ b/DockDoor/ui/Views/Hover Window/SharedPreviewWindowCoordinator.swift @@ -5,37 +5,37 @@ // Created by Ethan Bills on 6/5/24. // -import SwiftUI import Defaults import FluidGradient +import SwiftUI @Observable class ScreenCenteredFloatingWindow { static let shared = ScreenCenteredFloatingWindow() - + var currIndex: Int = 0 var windowSwitcherActive: Bool = false var fullWindowPreviewActive: Bool = false - + enum WindowState { case windowSwitcher case fullWindowPreview case both } - + func setShowing(_ state: WindowState? = .both, toState: Bool) { switch state { case .windowSwitcher: - self.windowSwitcherActive = toState + windowSwitcherActive = toState case .fullWindowPreview: - self.fullWindowPreviewActive = toState + fullWindowPreviewActive = toState case .both: - self.windowSwitcherActive = toState - self.fullWindowPreviewActive = toState + windowSwitcherActive = toState + fullWindowPreviewActive = toState case .none: return } } - + func setIndex(to: Int) { withAnimation(.easeInOut) { self.currIndex = to @@ -45,26 +45,26 @@ import FluidGradient final class SharedPreviewWindowCoordinator: NSWindow { static let shared = SharedPreviewWindowCoordinator() - + private var appName: String = "" private var windows: [Window] = [] private var onWindowTap: (() -> Void)? private var hostingView: NSHostingView? private var fullPreviewWindow: NSWindow? - + var windowSize: CGSize = getWindowSize() - + private var previousHoverWindowOrigin: CGPoint? - + private let debounceDelay: TimeInterval = 0.1 private var debounceWorkItem: DispatchWorkItem? private var lastShowTime: Date? - + private init() { super.init(contentRect: .zero, styleMask: .borderless, backing: .buffered, defer: false) setupWindow() } - + // Setup window properties private func setupWindow() { level = .floating @@ -72,17 +72,17 @@ final class SharedPreviewWindowCoordinator: NSWindow { collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] backgroundColor = .clear hasShadow = false - + let options: NSTrackingArea.Options = [.mouseEnteredAndExited, .activeAlways] - let trackingArea = NSTrackingArea(rect: self.frame, options: options, owner: self, userInfo: nil) + let trackingArea = NSTrackingArea(rect: frame, options: options, owner: self, userInfo: nil) contentView?.addTrackingArea(trackingArea) } - + // Hide the window and reset its state func hidePreviewWindow() { DispatchQueue.main.async { [weak self] in guard let self = self, self.isVisible else { return } - + self.hideFullPreviewWindow() self.contentView = nil self.hostingView = nil @@ -93,24 +93,25 @@ final class SharedPreviewWindowCoordinator: NSWindow { self.orderOut(nil) } } - + // Update the content view size and position private func updateContentViewSizeAndPosition(mouseLocation: CGPoint? = nil, mouseScreen: NSScreen, animated: Bool, centerOnScreen: Bool = false, - centeredHoverWindowState: ScreenCenteredFloatingWindow.WindowState? = nil) { + centeredHoverWindowState: ScreenCenteredFloatingWindow.WindowState? = nil) + { guard hostingView != nil else { return } - + ScreenCenteredFloatingWindow.shared.setShowing(centeredHoverWindowState, toState: centerOnScreen) // Reset the hosting view let hoverView = WindowPreviewHoverContainer(appName: appName, windows: windows, onWindowTap: onWindowTap, - dockPosition: DockUtils.shared.getDockPosition(), bestGuessMonitor: mouseScreen) + dockPosition: DockUtils.shared.getDockPosition(), bestGuessMonitor: mouseScreen) let newHostingView = NSHostingView(rootView: hoverView) - self.contentView = newHostingView - self.hostingView = newHostingView + contentView = newHostingView + hostingView = newHostingView let newHoverWindowSize = newHostingView.fittingSize - + let position = centerOnScreen ? centerWindowOnScreen(size: newHoverWindowSize, screen: mouseScreen) : calculateWindowPosition(mouseLocation: mouseLocation, windowSize: newHoverWindowSize, screen: mouseScreen) @@ -130,27 +131,27 @@ final class SharedPreviewWindowCoordinator: NSWindow { fullPreviewWindow?.backgroundColor = .clear fullPreviewWindow?.hasShadow = true } - + let padding: CGFloat = 40 let maxSize = CGSize( width: screen.visibleFrame.width - padding * 2, height: screen.visibleFrame.height - padding * 2 ) - + let previewView = FullSizePreviewView(window: window, maxSize: maxSize) let hostingView = NSHostingView(rootView: previewView) fullPreviewWindow?.contentView = hostingView - + let centerPoint = centerWindowOnScreen(size: maxSize, screen: screen) fullPreviewWindow?.setFrame(CGRect(origin: centerPoint, size: maxSize), display: true) fullPreviewWindow?.makeKeyAndOrderFront(nil) } - + func hideFullPreviewWindow() { fullPreviewWindow?.orderOut(nil) fullPreviewWindow = nil } - + // Center window on screen private func centerWindowOnScreen(size: CGSize, screen: NSScreen) -> CGPoint { return CGPoint( @@ -158,52 +159,51 @@ final class SharedPreviewWindowCoordinator: NSWindow { y: screen.frame.midY - (size.height / 2) ) } - + // Calculate window position based on the given dock icon frame and dock position private func calculateWindowPosition(mouseLocation: CGPoint?, windowSize: CGSize, screen: NSScreen) -> CGPoint { guard let mouseLocation = mouseLocation else { return .zero } - + let dockIconFrame = DockObserver.shared.getDockIconFrameAtLocation(mouseLocation) ?? .zero - + var xPosition = dockIconFrame.isEmpty ? mouseLocation.x : dockIconFrame.midX var yPosition = dockIconFrame.isEmpty ? mouseLocation.y : dockIconFrame.midY - + let screenFrame = screen.frame let dockPosition = DockUtils.shared.getDockPosition() let dockHeight = DockUtils.shared.calculateDockHeight(screen) - + // Adjust position based on dock position - switch dockPosition { - case .bottom: - yPosition = screenFrame.minY + dockHeight - xPosition -= (windowSize.width / 2) - case .left: - xPosition = screenFrame.minX + dockHeight - yPosition = screenFrame.height - yPosition - (windowSize.height / 2) - case .right: - xPosition = screenFrame.maxX - dockHeight - windowSize.width - yPosition = screenFrame.height - yPosition - (windowSize.height / 2) - default: - xPosition -= (windowSize.width / 2) - yPosition -= (windowSize.height / 2) - } - + switch dockPosition { + case .bottom: + yPosition = screenFrame.minY + dockHeight + xPosition -= (windowSize.width / 2) + case .left: + xPosition = screenFrame.minX + dockHeight + yPosition = screenFrame.height - yPosition - (windowSize.height / 2) + case .right: + xPosition = screenFrame.maxX - dockHeight - windowSize.width + yPosition = screenFrame.height - yPosition - (windowSize.height / 2) + default: + xPosition -= (windowSize.width / 2) + yPosition -= (windowSize.height / 2) + } + // Ensure window stays within screen bounds xPosition = max(screenFrame.minX, min(xPosition, screenFrame.maxX - windowSize.width)) + (dockPosition != .bottom ? Defaults[.bufferFromDock] : 0) yPosition = max(screenFrame.minY, min(yPosition, screenFrame.maxY - windowSize.height)) + (dockPosition == .bottom ? Defaults[.bufferFromDock] : 0) - + return CGPoint(x: xPosition, y: yPosition) } - - + // Apply window frame with optional animation private func applyWindowFrame(_ frame: CGRect, animated: Bool) { let shouldAnimate = animated && frame != self.frame - + if shouldAnimate { let distanceThreshold: CGFloat = 1800 let distance = previousHoverWindowOrigin.map { frame.origin.distance(to: $0) } ?? distanceThreshold + 1 - + if distance > distanceThreshold || !Defaults[.showAnimations] { setFrame(frame, display: true) } else { @@ -217,23 +217,24 @@ final class SharedPreviewWindowCoordinator: NSWindow { setFrame(frame, display: true) } } - + // Show window with debounce logic func showPreviewWindow(appName: String, windows: [Window], mouseLocation: CGPoint? = nil, mouseScreen: NSScreen? = nil, - overrideDelay: Bool = false, centeredHoverWindowState: ScreenCenteredFloatingWindow.WindowState? = nil, - onWindowTap: (() -> Void)? = nil) { + overrideDelay: Bool = false, centeredHoverWindowState: ScreenCenteredFloatingWindow.WindowState? = nil, + onWindowTap: (() -> Void)? = nil) + { let now = Date() let delay = overrideDelay ? 0.0 : Defaults[.hoverWindowOpenDelay] - + debounceWorkItem?.cancel() - - let isHoverWindowShowing = self.isVisible - + + let isHoverWindowShowing = isVisible + if let lastShowTime = lastShowTime, now.timeIntervalSince(lastShowTime) < debounceDelay { let workItem = DispatchWorkItem { [weak self] in self?.performShowWindow(appName: appName, windows: windows, mouseLocation: mouseLocation, mouseScreen: mouseScreen, centeredHoverWindowState: centeredHoverWindowState, onWindowTap: onWindowTap) } - + debounceWorkItem = workItem DispatchQueue.main.asyncAfter(deadline: .now() + debounceDelay, execute: workItem) } else { @@ -243,28 +244,29 @@ final class SharedPreviewWindowCoordinator: NSWindow { let workItem = DispatchWorkItem { [weak self] in self?.performShowWindow(appName: appName, windows: windows, mouseLocation: mouseLocation, mouseScreen: mouseScreen, centeredHoverWindowState: centeredHoverWindowState, onWindowTap: onWindowTap) } - + debounceWorkItem = workItem DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem) } } - + lastShowTime = now } - + // Perform the actual window showing private func performShowWindow(appName: String, windows: [Window], mouseLocation: CGPoint?, mouseScreen: NSScreen?, centeredHoverWindowState: ScreenCenteredFloatingWindow.WindowState? = nil, - onWindowTap: (() -> Void)?) { + onWindowTap: (() -> Void)?) + { let shouldCenterOnScreen = centeredHoverWindowState != .none - + guard !windows.isEmpty else { return } - + DispatchQueue.main.async { [weak self] in guard let self = self else { return } - + let screen = mouseScreen ?? NSScreen.main! - + hideFullPreviewWindow() // clean up any lingering fullscreen previews before presenting a new one // If in full window preview mode, show the full preview window and return early @@ -274,40 +276,40 @@ final class SharedPreviewWindowCoordinator: NSWindow { self.appName = appName self.windows = windows self.onWindowTap = onWindowTap - + self.updateHostingView(appName: appName, windows: windows, onWindowTap: onWindowTap, screen: screen) - + self.updateContentViewSizeAndPosition(mouseLocation: mouseLocation, mouseScreen: screen, animated: true, centerOnScreen: shouldCenterOnScreen, centeredHoverWindowState: centeredHoverWindowState) } - + self.makeKeyAndOrderFront(nil) } } - + // Update or create the hosting view private func updateHostingView(appName: String, windows: [Window], onWindowTap: (() -> Void)?, screen: NSScreen) { let hoverView = WindowPreviewHoverContainer(appName: appName, windows: windows, onWindowTap: onWindowTap, dockPosition: DockUtils.shared.getDockPosition(), bestGuessMonitor: screen) - - if let existingHostingView = self.hostingView { + + if let existingHostingView = hostingView { existingHostingView.rootView = hoverView } else { let newHostingView = NSHostingView(rootView: hoverView) - self.contentView = newHostingView - self.hostingView = newHostingView + contentView = newHostingView + hostingView = newHostingView } } - + // Cycle through windows func cycleWindows(goBackwards: Bool) { guard !windows.isEmpty else { return } - + let currentIndex = ScreenCenteredFloatingWindow.shared.currIndex let newIndex = (currentIndex + (goBackwards ? -1 : 1) + windows.count) % windows.count ScreenCenteredFloatingWindow.shared.setIndex(to: newIndex) } - + // Select and bring to front the current window func selectAndBringToFrontCurrentWindow() { guard !windows.isEmpty else { return } diff --git a/DockDoor/ui/Views/Hover Window/Traffic Light Buttons.swift b/DockDoor/ui/Views/Hover Window/Traffic Light Buttons.swift index eea0d978..ea964aa4 100644 --- a/DockDoor/ui/Views/Hover Window/Traffic Light Buttons.swift +++ b/DockDoor/ui/Views/Hover Window/Traffic Light Buttons.swift @@ -12,9 +12,9 @@ struct TrafficLightButtons: View { let displayMode: TrafficLightButtonsVisibility let hoveringOverParentWindow: Bool let onAction: () -> Void - + @State private var isHovering = false - + var body: some View { HStack(spacing: 6) { buttonFor(action: .quit, symbol: "power", color: Color(hex: "290133"), fillColor: .purple) @@ -31,7 +31,7 @@ struct TrafficLightButtons: View { } } } - + private var opacity: Double { switch displayMode { case .dimmedOnPreviewHover: @@ -44,7 +44,7 @@ struct TrafficLightButtons: View { return 0 } } - + private func buttonFor(action: WindowAction, symbol: String, color: Color, fillColor: Color) -> some View { Button(action: { performAction(action) @@ -61,7 +61,7 @@ struct TrafficLightButtons: View { .buttonStyle(.plain) .font(.system(size: 13)) } - + private func performAction(_ action: WindowAction) { switch action { case .quit: @@ -74,7 +74,7 @@ struct TrafficLightButtons: View { window.toggleFullScreen() } } - + private enum WindowAction { case quit, close, minimize, toggleFullScreen } diff --git a/DockDoor/ui/Views/Hover Window/WindowPreview.swift b/DockDoor/ui/Views/Hover Window/WindowPreview.swift index e040b63a..5fe24300 100644 --- a/DockDoor/ui/Views/Hover Window/WindowPreview.swift +++ b/DockDoor/ui/Views/Hover Window/WindowPreview.swift @@ -5,8 +5,8 @@ // Created by Ethan Bills on 7/4/24. // -import SwiftUI import Defaults +import SwiftUI struct WindowPreview: View { let window: Window @@ -16,45 +16,45 @@ struct WindowPreview: View { let maxWindowDimension: CGPoint let bestGuessMonitor: NSScreen let uniformCardRadius: Bool - + @Default(.windowTitlePosition) var windowTitlePosition @Default(.showWindowTitle) var showWindowTitle @Default(.windowTitleDisplayCondition) var windowTitleDisplayCondition @Default(.windowTitleVisibility) var windowTitleVisibility @Default(.trafficLightButtonsVisibility) var trafficLightButtonsVisibility @Default(.trafficLightButtonsPosition) var trafficLightButtonsPosition - + // preview popup action handlers @Default(.tapEquivalentInterval) var tapEquivalentInterval @Default(.previewHoverAction) var previewHoverAction - + @State private var isHoveringOverDockPeekPreview = false @State private var isHoveringOverWindowSwitcherPreview = false @State private var fullPreviewTimer: Timer? - + private var calculatedMaxDimensions: CGSize { - CGSize(width: self.bestGuessMonitor.frame.width * 0.75, height: self.bestGuessMonitor.frame.height * 0.75) + CGSize(width: bestGuessMonitor.frame.width * 0.75, height: bestGuessMonitor.frame.height * 0.75) } - + var calculatedSize: CGSize { guard let cgImage = window.image else { return .zero } - + let cgSize = CGSize(width: cgImage.width, height: cgImage.height) let aspectRatio = cgSize.width / cgSize.height let maxAllowedWidth = maxWindowDimension.x let maxAllowedHeight = maxWindowDimension.y - + var targetWidth = maxAllowedWidth var targetHeight = targetWidth / aspectRatio - + if targetHeight > maxAllowedHeight { targetHeight = maxAllowedHeight targetWidth = aspectRatio * targetHeight } - + return CGSize(width: targetWidth, height: targetHeight) } - + private func windowContent(isSelected: Bool) -> some View { Group { if let cgImage = window.image { @@ -67,11 +67,11 @@ struct WindowPreview: View { alignment: .center) .frame(maxWidth: calculatedMaxDimensions.width, maxHeight: calculatedMaxDimensions.height) } - + var body: some View { let isHighlightedInWindowSwitcher = (index == ScreenCenteredFloatingWindow.shared.currIndex && ScreenCenteredFloatingWindow.shared.windowSwitcherActive) let selected = isHoveringOverDockPeekPreview || isHighlightedInWindowSwitcher - + ZStack(alignment: .topTrailing) { VStack(spacing: 0) { windowContent(isSelected: selected) @@ -95,7 +95,7 @@ struct WindowPreview: View { return .topLeading } }()) { - if showWindowTitle && (windowTitleDisplayCondition == .all || (windowTitleDisplayCondition == .windowSwitcherOnly && ScreenCenteredFloatingWindow.shared.windowSwitcherActive) || (windowTitleDisplayCondition == .dockPreviewsOnly && !ScreenCenteredFloatingWindow.shared.windowSwitcherActive)) { + if showWindowTitle && (windowTitleDisplayCondition == .all || (windowTitleDisplayCondition == .windowSwitcherOnly && ScreenCenteredFloatingWindow.shared.windowSwitcherActive) || (windowTitleDisplayCondition == .dockPreviewsOnly && !ScreenCenteredFloatingWindow.shared.windowSwitcherActive)) { windowTitleOverlay(selected: selected) } } @@ -127,7 +127,7 @@ struct WindowPreview: View { .contentShape(Rectangle()) .onHover { over in withAnimation(.snappy(duration: 0.175)) { - if (!ScreenCenteredFloatingWindow.shared.windowSwitcherActive) { + if !ScreenCenteredFloatingWindow.shared.windowSwitcherActive { isHoveringOverDockPeekPreview = over handleFullPreviewHover(isHovering: over, action: previewHoverAction) } else { @@ -139,14 +139,14 @@ struct WindowPreview: View { handleWindowTap() } } - + private func handleFullPreviewHover(isHovering: Bool, action: PreviewHoverAction) { if isHovering && !ScreenCenteredFloatingWindow.shared.windowSwitcherActive { switch action { case .none: // Do nothing for .none break - + case .tap: // If the interval is 0, immediately trigger the tap action if tapEquivalentInterval == 0 { @@ -159,7 +159,7 @@ struct WindowPreview: View { } } } - + case .previewFullSize: // If the interval is 0, show the full window preview immediately if tapEquivalentInterval == 0 { @@ -193,22 +193,22 @@ struct WindowPreview: View { fullPreviewTimer = nil } } - + private func handleWindowTap() { - if (window.isMinimized) { + if window.isMinimized { window.toggleMinimize() } window.focus() onTap?() } - + @ViewBuilder private func windowTitleOverlay(selected: Bool) -> some View { - if (windowTitleVisibility == .alwaysVisible || selected), let windowTitle = window.title, !windowTitle.isEmpty, (windowTitle != window.appName || ScreenCenteredFloatingWindow.shared.windowSwitcherActive) { + if windowTitleVisibility == .alwaysVisible || selected, let windowTitle = window.title, !windowTitle.isEmpty, windowTitle != window.appName || ScreenCenteredFloatingWindow.shared.windowSwitcherActive { let maxLabelWidth = calculatedSize.width - 50 let stringMeasurementWidth = measureString(windowTitle, fontSize: 12).width + 5 let width = maxLabelWidth > stringMeasurementWidth ? stringMeasurementWidth : maxLabelWidth - + TheMarquee(width: width, secsBeforeLooping: 1, speedPtsPerSec: 20, nonMovingAlignment: .leading) { Text(window.title ?? "Hidden window") .font(.system(size: 12, weight: .medium)) diff --git a/DockDoor/ui/Views/Hover Window/WindowPreviewHoverContainer.swift b/DockDoor/ui/Views/Hover Window/WindowPreviewHoverContainer.swift index 9b79a9f5..3a0196d2 100644 --- a/DockDoor/ui/Views/Hover Window/WindowPreviewHoverContainer.swift +++ b/DockDoor/ui/Views/Hover Window/WindowPreviewHoverContainer.swift @@ -1,12 +1,12 @@ // -// HoverView.swift +// WindowPreviewHoverContainer.swift // DockDoor // // Created by Ethan Bills on 7/11/24. // -import SwiftUI import Defaults +import SwiftUI struct WindowPreviewHoverContainer: View { let appName: String @@ -14,27 +14,27 @@ struct WindowPreviewHoverContainer: View { let onWindowTap: (() -> Void)? let dockPosition: DockPosition let bestGuessMonitor: NSScreen - + @Default(.uniformCardRadius) var uniformCardRadius @Default(.showAppName) var showAppName @Default(.appNameStyle) var appNameStyle @Default(.windowTitlePosition) var windowTitlePosition - + @State private var showWindows: Bool = false @State private var hasAppeared: Bool = false @State private var appIcon: NSImage? = nil - + var maxWindowDimension: CGPoint { let thickness = SharedPreviewWindowCoordinator.shared.windowSize.height var maxWidth: CGFloat = 300 var maxHeight: CGFloat = 300 - + for window in windows { if let cgImage = window.image { let cgSize = CGSize(width: cgImage.width, height: cgImage.height) let widthBasedOnHeight = (cgSize.width * thickness) / cgSize.height let heightBasedOnWidth = (cgSize.height * thickness) / cgSize.width - + if dockPosition == .bottom || ScreenCenteredFloatingWindow.shared.windowSwitcherActive { maxWidth = max(maxWidth, widthBasedOnHeight) maxHeight = thickness @@ -44,10 +44,10 @@ struct WindowPreviewHoverContainer: View { } } } - + return CGPoint(x: maxWidth, y: maxHeight) } - + var body: some View { let orientationIsHorizontal = dockPosition == .bottom || ScreenCenteredFloatingWindow.shared.windowSwitcherActive ZStack { @@ -58,7 +58,7 @@ struct WindowPreviewHoverContainer: View { WindowPreview(window: windows[index], onTap: onWindowTap, index: index, dockPosition: dockPosition, maxWindowDimension: maxWindowDimension, bestGuessMonitor: bestGuessMonitor, uniformCardRadius: uniformCardRadius) - .id("\(appName)-\(index)") + .id("\(appName)-\(index)") } } .padding(14) @@ -85,9 +85,9 @@ struct WindowPreviewHoverContainer: View { .overlay(alignment: .topLeading) { hoverTitleBaseView(labelSize: measureString(appName, fontSize: 14)) } - .padding(.top, (!ScreenCenteredFloatingWindow.shared.windowSwitcherActive && appNameStyle == .popover && showAppName) ? 30 : 0) // Provide empty space above the window preview for the Popover title style when hovering over the Dock + .padding(.top, (!ScreenCenteredFloatingWindow.shared.windowSwitcherActive && appNameStyle == .popover && showAppName) ? 30 : 0) // Provide empty space above the window preview for the Popover title style when hovering over the Dock .padding(.all, 24) - .frame(maxWidth: self.bestGuessMonitor.visibleFrame.width, maxHeight: self.bestGuessMonitor.visibleFrame.height) + .frame(maxWidth: bestGuessMonitor.visibleFrame.width, maxHeight: bestGuessMonitor.visibleFrame.height) .onHover { isHovering in // .whenHovered { isHovering in let currentDockItem = DockObserver.shared.gethoveredDockItem() @@ -99,7 +99,7 @@ struct WindowPreviewHoverContainer: View { } } } - + @ViewBuilder private func hoverTitleBaseView(labelSize: CGSize) -> some View { if !ScreenCenteredFloatingWindow.shared.windowSwitcherActive && showAppName { @@ -160,7 +160,7 @@ struct WindowPreviewHoverContainer: View { } } } - + @ViewBuilder private func hoverTitleLabelView(labelSize: CGSize) -> some View { switch appNameStyle { @@ -182,7 +182,7 @@ struct WindowPreviewHoverContainer: View { gradient: Gradient( colors: [ Color.white.opacity(1.0), - Color.white.opacity(0.35) + Color.white.opacity(0.35), ] ), startPoint: .top, @@ -192,26 +192,26 @@ struct WindowPreviewHoverContainer: View { ) .blur(radius: 5) } - .frame(width: labelSize.width + 30) + .frame(width: labelSize.width + 30) ) case .embedded, .popover: Text(appName) } } - + private func runUIUpdates() { - self.runAnimation() - self.loadAppIcon() + runAnimation() + loadAppIcon() } - + private func runAnimation() { - self.showWindows = false - + showWindows = false + withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) { showWindows = true } } - + private func loadAppIcon() { if let bundleID = windows.first?.bundleID, let icon = AppIconUtil.getIcon(bundleID: bundleID) { DispatchQueue.main.async { diff --git a/DockDoor/ui/Views/Settings/AppearanceSettingsView.swift b/DockDoor/ui/Views/Settings/AppearanceSettingsView.swift index 2f18bc0d..4de08e17 100644 --- a/DockDoor/ui/Views/Settings/AppearanceSettingsView.swift +++ b/DockDoor/ui/Views/Settings/AppearanceSettingsView.swift @@ -5,9 +5,9 @@ // Created by ShlomoCode on 09/07/2024. // -import SwiftUI import Defaults import LaunchAtLogin +import SwiftUI struct AppearanceSettingsView: View { @Default(.showAnimations) var showAnimations @@ -20,17 +20,17 @@ struct AppearanceSettingsView: View { @Default(.windowTitlePosition) var windowTitlePosition @Default(.trafficLightButtonsVisibility) var trafficLightButtonsVisibility @Default(.trafficLightButtonsPosition) var trafficLightButtonsPosition - + var body: some View { VStack(alignment: .leading, spacing: 10) { Toggle(isOn: $showAnimations, label: { Text("Enable Hover Window Sliding Animation") }) - + Toggle(isOn: $uniformCardRadius, label: { Text("Use Uniform Image Preview Radius") }) - + Picker("Traffic Light Buttons Visibility", selection: $trafficLightButtonsVisibility) { ForEach(TrafficLightButtonsVisibility.allCases, id: \.self) { visibility in Text(visibility.localizedName) @@ -40,7 +40,7 @@ struct AppearanceSettingsView: View { .pickerStyle(MenuPickerStyle()) .scaledToFit() .layoutPriority(1) - + Picker("Traffic Light Buttons Position", selection: $trafficLightButtonsPosition) { ForEach(TrafficLightButtonsPosition.allCases, id: \.self) { position in Text(position.localizedName) @@ -63,13 +63,13 @@ struct AppearanceSettingsView: View { .pickerStyle(SegmentedPickerStyle()) .scaledToFit() .layoutPriority(1) - + Divider() - + Toggle(isOn: $showAppName) { Text("Show App Name in Dock Previews") } - + Picker(String(localized: "App Name Style"), selection: $appNameStyle) { ForEach(AppNameStyle.allCases, id: \.self) { style in Text(style.localizedName) @@ -80,13 +80,13 @@ struct AppearanceSettingsView: View { .scaledToFit() .layoutPriority(1) .disabled(!showAppName) - + Divider() - + Toggle(isOn: $showWindowTitle) { Text("Show Window Title in Previews") } - + Group { Picker("Show Window Title in", selection: $windowTitleDisplayCondition) { ForEach(WindowTitleDisplayCondition.allCases, id: \.self) { condtion in @@ -102,7 +102,7 @@ struct AppearanceSettingsView: View { } .pickerStyle(MenuPickerStyle()) .scaledToFit() - + Picker("Window Title Visibility", selection: $windowTitleVisibility) { ForEach(WindowTitleVisibility.allCases, id: \.self) { visibility in Text(visibility.localizedName) @@ -111,7 +111,7 @@ struct AppearanceSettingsView: View { } .scaledToFit() .pickerStyle(MenuPickerStyle()) - + Picker("Window Title Position", selection: $windowTitlePosition) { ForEach(WindowTitlePosition.allCases, id: \.self) { position in Text(position.localizedName) diff --git a/DockDoor/ui/Views/Settings/MainSettingsView.swift b/DockDoor/ui/Views/Settings/MainSettingsView.swift index f44d0275..67ee40ce 100644 --- a/DockDoor/ui/Views/Settings/MainSettingsView.swift +++ b/DockDoor/ui/Views/Settings/MainSettingsView.swift @@ -5,9 +5,9 @@ // Created by Ethan Bills on 6/13/24. // -import SwiftUI import Defaults import LaunchAtLogin +import SwiftUI var decimalFormatter: NumberFormatter { let formatter = NumberFormatter() @@ -23,7 +23,7 @@ struct MainSettingsView: View { @Default(.tapEquivalentInterval) var tapEquivalentInterval @Default(.previewHoverAction) var previewHoverAction @Default(.bufferFromDock) var bufferFromDock - + var body: some View { VStack(alignment: .leading, spacing: 10) { Section { @@ -31,17 +31,17 @@ struct MainSettingsView: View { Text("Want to support development?") Link("Buy me a coffee here, thank you!", destination: URL(string: "https://www.buymeacoffee.com/keplercafe")!) } - + HStack { Text("Want to see the app in your language?") Link("Contribute translation here!", destination: URL(string: "https://crowdin.com/project/dockdoor/invite?h=895e3c085646d3c07fa36a97044668e02149115")!) } } - + Divider() - + LaunchAtLogin.Toggle(String(localized: "Launch DockDoor at login")) - + Toggle(isOn: $showMenuBarIcon, label: { Text("Show Menu Bar Icon") }) @@ -53,7 +53,7 @@ struct MainSettingsView: View { appDelegate.removeMenuBar() } } - + Button("Reset All Settings to Defaults") { showResetConfirmation() } @@ -61,23 +61,23 @@ struct MainSettingsView: View { let appDelegate = NSApplication.shared.delegate as! AppDelegate appDelegate.quitApp() } - + Divider() - + HStack { Text("Hover Window Open Delay") .layoutPriority(1) Spacer() - Slider(value: $hoverWindowOpenDelay, in: 0...2, step: 0.1) + Slider(value: $hoverWindowOpenDelay, in: 0 ... 2, step: 0.1) TextField("", value: $hoverWindowOpenDelay, formatter: decimalFormatter) .frame(width: 38) .textFieldStyle(RoundedBorderTextFieldStyle()) Text("seconds") } - - VStack(alignment: .leading){ + + VStack(alignment: .leading) { HStack { - Slider(value: $bufferFromDock, in: -200...200, step: 20) { + Slider(value: $bufferFromDock, in: -200 ... 200, step: 20) { Text("Window Buffer") } .buttonStyle(PlainButtonStyle()) @@ -90,20 +90,20 @@ struct MainSettingsView: View { .font(.footnote) .foregroundColor(.gray) } - + SizePickerView() - + HStack { Text("Window Image Cache Lifespan") .layoutPriority(1) Spacer() - Slider(value: $screenCaptureCacheLifespan, in: 0...60, step: 5) + Slider(value: $screenCaptureCacheLifespan, in: 0 ... 60, step: 5) TextField("", value: $screenCaptureCacheLifespan, formatter: NumberFormatter()) .frame(width: 38) .textFieldStyle(RoundedBorderTextFieldStyle()) Text("seconds") } - + Picker("Preview Hover Action", selection: $previewHoverAction) { ForEach(PreviewHoverAction.allCases, id: \.self) { action in Text(action.localizedName).tag(action) @@ -111,11 +111,11 @@ struct MainSettingsView: View { } .pickerStyle(MenuPickerStyle()) .scaledToFit() - + HStack { Text("Preview Hover Delay") Spacer() - Slider(value: $tapEquivalentInterval, in: 0...2, step: 0.1) + Slider(value: $tapEquivalentInterval, in: 0 ... 2, step: 0.1) TextField("", value: $tapEquivalentInterval, formatter: decimalFormatter) .frame(width: 38) .textFieldStyle(RoundedBorderTextFieldStyle()) @@ -126,7 +126,7 @@ struct MainSettingsView: View { .padding(20) .frame(minWidth: 650) } - + private func showResetConfirmation() { MessageUtil.showMessage( title: String(localized: "Reset to Defaults"), @@ -146,21 +146,21 @@ struct MainSettingsView: View { struct SizePickerView: View { @Default(.sizingMultiplier) var sizingMultiplier @Default(.bufferFromDock) var bufferFromDock - + var body: some View { VStack(spacing: 20) { Picker("Window Size", selection: $sizingMultiplier) { - ForEach(2...10, id: \.self) { size in + ForEach(2 ... 10, id: \.self) { size in Text(getLabel(for: CGFloat(size))).tag(CGFloat(size)) } } .scaledToFit() - .onChange(of: sizingMultiplier) { _, newValue in + .onChange(of: sizingMultiplier) { _, _ in SharedPreviewWindowCoordinator.shared.windowSize = getWindowSize() } } } - + private func getLabel(for size: CGFloat) -> String { switch size { case 2: @@ -168,19 +168,19 @@ struct SizePickerView: View { case 3: return String(localized: "Default (Medium Large)", comment: "Window size option") case 4: - return String(localized:"Medium", comment: "Window size option") + return String(localized: "Medium", comment: "Window size option") case 5: - return String(localized:"Small", comment: "Window size option") + return String(localized: "Small", comment: "Window size option") case 6: - return String(localized:"Extra Small", comment: "Window size option") + return String(localized: "Extra Small", comment: "Window size option") case 7: - return String(localized:"Extra Extra Small", comment: "Window size option") + return String(localized: "Extra Extra Small", comment: "Window size option") case 8: - return String(localized:"What is this? A window for ANTS?", comment: "Window size option") + return String(localized: "What is this? A window for ANTS?", comment: "Window size option") case 9: - return String(localized:"Subatomic", comment: "Window size option") + return String(localized: "Subatomic", comment: "Window size option") case 10: - return String(localized:"Can you even see this?", comment: "Window size option") + return String(localized: "Can you even see this?", comment: "Window size option") default: return "Unknown Size" } diff --git a/DockDoor/ui/Views/Settings/PermissionsSettingsView.swift b/DockDoor/ui/Views/Settings/PermissionsSettingsView.swift index a70cb8c4..9d6f9486 100644 --- a/DockDoor/ui/Views/Settings/PermissionsSettingsView.swift +++ b/DockDoor/ui/Views/Settings/PermissionsSettingsView.swift @@ -5,9 +5,9 @@ // Created by Ethan Bills on 6/14/24. // -import SwiftUI -import Combine import AppKit +import Combine +import SwiftUI class PermissionsChecker: ObservableObject { @Published var accessibilityPermission: Bool = false @@ -53,7 +53,7 @@ class PermissionsChecker: ObservableObject { struct PermissionsSettingsView: View { @StateObject private var permissionsChecker = PermissionsChecker() - + var body: some View { VStack(alignment: .leading, spacing: 20) { HStack { @@ -61,11 +61,11 @@ struct PermissionsSettingsView: View { .foregroundColor(permissionsChecker.accessibilityPermission ? .green : .red) .scaleEffect(permissionsChecker.accessibilityPermission ? 1.2 : 1.0) .padding(10) - + Text("Accessibility Permissions") .font(.headline) } - + HStack { Image(systemName: permissionsChecker.screenRecordingPermission ? "checkmark.circle.fill" : "xmark.circle.fill") .foregroundColor(permissionsChecker.screenRecordingPermission ? .green : .red) @@ -75,7 +75,7 @@ struct PermissionsSettingsView: View { Text("Screen Recording Permissions") .font(.headline) } - + Button(action: openAccessibilityPreferences) { HStack { Image(systemName: "hand.raised.fill") @@ -83,7 +83,7 @@ struct PermissionsSettingsView: View { } } .buttonStyle(.bordered) - + Button(action: openScreenRecordingPreferences) { HStack { Image(systemName: "video.fill") @@ -91,7 +91,7 @@ struct PermissionsSettingsView: View { } } .buttonStyle(.bordered) - + Text("Please Restart the App to Apply Changes! :)") .font(.footnote) .foregroundColor(.secondary) @@ -99,18 +99,18 @@ struct PermissionsSettingsView: View { let appDelegate = NSApplication.shared.delegate as! AppDelegate appDelegate.restartApp() }) - .buttonStyle(.bordered) + .buttonStyle(.bordered) Spacer() } .padding([.top, .leading, .trailing], 20) .frame(minWidth: 650) } - + private func openAccessibilityPreferences() { SystemPreferencesHelper.openAccessibilityPreferences() } - + private func openScreenRecordingPreferences() { SystemPreferencesHelper.openScreenRecordingPreferences() } diff --git a/DockDoor/ui/Views/Settings/UpdateSettingsView.swift b/DockDoor/ui/Views/Settings/UpdateSettingsView.swift index 4f3662af..bfc3d851 100644 --- a/DockDoor/ui/Views/Settings/UpdateSettingsView.swift +++ b/DockDoor/ui/Views/Settings/UpdateSettingsView.swift @@ -5,8 +5,8 @@ // Created by Ethan Bills on 6/23/24. // -import SwiftUI import Sparkle +import SwiftUI final class UpdaterViewModel: ObservableObject { @Published var canCheckForUpdates = false @@ -14,33 +14,33 @@ final class UpdaterViewModel: ObservableObject { @Published var currentVersion: String @Published var isAutomaticChecksEnabled: Bool @Published var updateStatus: UpdateStatus = .noUpdates - + private let updater: SPUUpdater - + enum UpdateStatus { case noUpdates case checking case available(version: String) case error(String) } - + init(updater: SPUUpdater) { self.updater = updater - self.currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "Unknown" - self.isAutomaticChecksEnabled = updater.automaticallyChecksForUpdates - + currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "Unknown" + isAutomaticChecksEnabled = updater.automaticallyChecksForUpdates + updater.publisher(for: \.canCheckForUpdates) .assign(to: &$canCheckForUpdates) - + updater.publisher(for: \.lastUpdateCheckDate) .assign(to: &$lastUpdateCheckDate) } - + func checkForUpdates() { updateStatus = .checking updater.checkForUpdates() } - + func toggleAutomaticChecks() { isAutomaticChecksEnabled.toggle() updater.automaticallyChecksForUpdates = isAutomaticChecksEnabled @@ -49,15 +49,15 @@ final class UpdaterViewModel: ObservableObject { struct UpdateSettingsView: View { @StateObject private var viewModel: UpdaterViewModel - + init(updater: SPUUpdater) { _viewModel = StateObject(wrappedValue: UpdaterViewModel(updater: updater)) } - + var body: some View { VStack(alignment: .center) { updateStatusView.bold().padding(1) - + HStack(alignment: .center) { VStack(alignment: .center) { HStack(alignment: .center) { @@ -70,22 +70,21 @@ struct UpdateSettingsView: View { } } } - + Button(action: viewModel.checkForUpdates) { Label("Check for Updates", systemImage: "arrow.triangle.2.circlepath") } .disabled(!viewModel.canCheckForUpdates) - + Toggle("Automatically check for updates", isOn: $viewModel.isAutomaticChecksEnabled) .onChange(of: viewModel.isAutomaticChecksEnabled) { _, _ in viewModel.toggleAutomaticChecks() } - } .padding(10) .frame(width: 650) } - + private var updateStatusView: some View { Group { switch viewModel.updateStatus { @@ -95,20 +94,20 @@ struct UpdateSettingsView: View { case .checking: ProgressView() .scaleEffect(0.7) - case .available(let version): + case let .available(version): VStack { Label("Update available", systemImage: "arrow.down.circle.fill") .foregroundColor(.blue) Text("Version \(version)") .font(.caption) } - case .error(let message): + case let .error(message): Label(message, systemImage: "exclamationmark.triangle.fill") .foregroundColor(.red) } } } - + private let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .medium diff --git a/DockDoor/ui/Views/Settings/WindowSwitcherSettingsView.swift b/DockDoor/ui/Views/Settings/WindowSwitcherSettingsView.swift index 7485ca90..5a27292a 100644 --- a/DockDoor/ui/Views/Settings/WindowSwitcherSettingsView.swift +++ b/DockDoor/ui/Views/Settings/WindowSwitcherSettingsView.swift @@ -5,10 +5,9 @@ // Created by Hasan Sultan on 6/25/24. // - -import SwiftUI -import Defaults import Carbon +import Defaults +import SwiftUI class KeybindModel: ObservableObject { @Published var modifierKey: Int @@ -16,10 +15,9 @@ class KeybindModel: ObservableObject { @Published var currentKeybind: UserKeyBind? init() { - self.modifierKey = Defaults[.UserKeybind].modifierFlags - self.currentKeybind = Defaults[.UserKeybind] + modifierKey = Defaults[.UserKeybind].modifierFlags + currentKeybind = Defaults[.UserKeybind] } - } struct WindowSwitcherSettingsView: View { @@ -29,8 +27,8 @@ struct WindowSwitcherSettingsView: View { VStack(alignment: .leading, spacing: 10) { Toggle(isOn: $enableWindowSwitcher, label: { Text("Enable Window Switcher") - }).onChange(of: enableWindowSwitcher){ - _, newValue in + }).onChange(of: enableWindowSwitcher) { + _, _ in askUserToRestartApplication() } // Default CMD + TAB implementation checkbox @@ -51,13 +49,13 @@ struct WindowSwitcherSettingsView: View { struct InitializationKeyPickerView: View { @ObservedObject var viewModel = KeybindModel() - + var body: some View { VStack(spacing: 20) { Text("Set Initialization Key and Keybind") .font(.headline) .padding(.top, 20) - + Picker("Initialization Key", selection: $viewModel.modifierKey) { Text("Control (⌃)").tag(Defaults[.Int64maskControl]) Text("Option (⌥)").tag(Defaults[.Int64maskAlternate]) @@ -66,17 +64,17 @@ struct InitializationKeyPickerView: View { .pickerStyle(SegmentedPickerStyle()) .padding(.horizontal) .scaledToFit() - + Text("Press any key combination after holding the initialization key to set the keybind.") .multilineTextAlignment(.center) .padding(.horizontal) - + Button(action: { viewModel.isRecording = true }) { Text(viewModel.isRecording ? "Press the key combination..." : "Start Recording Keybind") } .keyboardShortcut(.defaultAction) .padding(.bottom, 20) - + if let keybind = viewModel.currentKeybind { Text("Current Keybind: \(printCurrentKeybind(keybind))") .padding() @@ -95,7 +93,7 @@ struct InitializationKeyPickerView: View { .frame(maxWidth: .infinity, alignment: .leading) .padding() } - + func printCurrentKeybind(_ shortcut: UserKeyBind) -> String { var parts: [String] = [] parts.append(modifierConverter.toString(shortcut.modifierFlags)) @@ -108,8 +106,8 @@ struct ShortcutCaptureView: NSViewRepresentable { @Binding var currentKeybind: UserKeyBind? @Binding var isRecording: Bool @Binding var modifierKey: Int - - func makeNSView(context: Context) -> NSView { + + func makeNSView(context _: Context) -> NSView { let view = NSView() NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in guard self.isRecording else { @@ -129,7 +127,6 @@ struct ShortcutCaptureView: NSViewRepresentable { } return view } - - - func updateNSView(_ nsView: NSView, context: Context) {} + + func updateNSView(_: NSView, context _: Context) {} } diff --git a/DockDoor/ui/Views/Settings/settings.swift b/DockDoor/ui/Views/Settings/settings.swift index 2465d4c6..7b688eb2 100644 --- a/DockDoor/ui/Views/Settings/settings.swift +++ b/DockDoor/ui/Views/Settings/settings.swift @@ -20,7 +20,7 @@ extension Settings.PaneIdentifier { let GeneralSettingsViewController: () -> SettingsPane = { let paneView = Settings.Pane( identifier: .general, - title: String(localized:"General", comment: "Settings tab title"), + title: String(localized: "General", comment: "Settings tab title"), toolbarIcon: NSImage(systemSymbolName: "gearshape.fill", accessibilityDescription: String(localized: "General settings"))! ) { MainSettingsView() @@ -32,7 +32,7 @@ let GeneralSettingsViewController: () -> SettingsPane = { let AppearanceSettingsViewController: () -> SettingsPane = { let paneView = Settings.Pane( identifier: .appearance, - title: String(localized:"Appearance", comment: "Settings Tab"), + title: String(localized: "Appearance", comment: "Settings Tab"), toolbarIcon: NSImage(systemSymbolName: "wand.and.stars.inverse", accessibilityDescription: String(localized: "Appearance settings"))! ) { AppearanceSettingsView() @@ -56,7 +56,7 @@ let WindowSwitcherSettingsViewController: () -> SettingsPane = { let PermissionsSettingsViewController: () -> SettingsPane = { let paneView = Settings.Pane( identifier: .permissions, - title: String(localized:"Permissions", comment: "Settings tab title"), + title: String(localized: "Permissions", comment: "Settings tab title"), toolbarIcon: NSImage(systemSymbolName: "lock.shield", accessibilityDescription: String(localized: "Permissions settings"))! ) { PermissionsSettingsView() @@ -68,7 +68,7 @@ let PermissionsSettingsViewController: () -> SettingsPane = { func UpdatesSettingsViewController(updater: SPUUpdater) -> SettingsPane { let paneView = Settings.Pane( identifier: .updates, - title: String(localized:"Updates", comment: "Settings tab title"), + title: String(localized: "Updates", comment: "Settings tab title"), toolbarIcon: NSImage(systemSymbolName: "arrow.triangle.2.circlepath", accessibilityDescription: String(localized: "Update settings"))! ) { UpdateSettingsView(updater: updater)