Skip to content

Commit

Permalink
feat: add support for macOS 13 Ventura
Browse files Browse the repository at this point in the history
  • Loading branch information
ShlomoCode committed Aug 23, 2024
1 parent 5225fa9 commit 810af86
Show file tree
Hide file tree
Showing 13 changed files with 148 additions and 75 deletions.
4 changes: 2 additions & 2 deletions DockDoor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.1.5;
PRODUCT_BUNDLE_IDENTIFIER = com.ethanbills.DockDoor;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -591,7 +591,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.1.5;
PRODUCT_BUNDLE_IDENTIFIER = com.ethanbills.DockDoor;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
46 changes: 26 additions & 20 deletions DockDoor/Components/Marquee.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ struct TheMarquee<C: View>: View {
if !animating { return }
let offsetAmount = contentSize.width + spacingBetweenElements
let duration = offsetAmount / speedPtsPerSec

withAnimation(.linear(duration: duration)) {
offset = -offsetAmount
} completion: {
}

// Simulate the completion handler taht only available on macOS 14.0+
doAfter(duration) {
offset = 0
doAfter(secsBeforeLooping) {
if animating {
Expand Down Expand Up @@ -63,8 +67,8 @@ struct TheMarquee<C: View>: View {
.measure($containerSize)
.compositingGroup()
.opacity(measured ? 1 : 0)
.onChange(of: containerSize) { _, _ in startAnimation() }
.onChange(of: contentSize) { _, _ in startAnimation() }
.onChange(of: containerSize) { _ in startAnimation() }
.onChange(of: contentSize) { _ in startAnimation() }
.onAppear { startAnimation() }
.onDisappear { animating = false }
}
Expand All @@ -76,23 +80,25 @@ extension View {
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)
if #available(macOS 14.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 {
Expand Down
9 changes: 6 additions & 3 deletions DockDoor/Utilities/DockObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ final class DockObserver {
private func setupEventTap() {
guard AXIsProcessTrusted() else {
print("Debug: Accessibility permission not granted")
MessageUtil.showMessage(title: String(localized: "Permission error"),
message: String(localized: "You need to give DockDoor access to the accessibility API in order for it to function."),
completion: { _ in SystemPreferencesHelper.openAccessibilityPreferences() })
MessageUtil.showAlert(
title: String(localized: "Permission error"),
message: String(localized: "You need to give DockDoor access to the accessibility API in order for it to function."),
actions: [.ok],
completion: { _ in SystemPreferencesHelper.openAccessibilityPreferences() }
)
return
}

Expand Down
27 changes: 19 additions & 8 deletions DockDoor/Utilities/MessageUtil.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,31 @@ enum MessageUtil {
case cancel
}

static func showMessage(title: String, message: String, completion: @escaping (ButtonAction) -> Void) {
static func showAlert(title: String, message: String, actions: [ButtonAction], completion: ((ButtonAction) -> Void)? = nil) {
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.alertStyle = .warning
alert.addButton(withTitle: String(localized: "OK"))
alert.addButton(withTitle: String(localized: "Cancel"))

for action in actions {
switch action {
case .ok:
alert.addButton(withTitle: String(localized: "OK"))
case .cancel:
alert.addButton(withTitle: String(localized: "Cancel"))
}
}

let modalResult = alert.runModal()
switch modalResult {
case .alertFirstButtonReturn: // OK button
completion(.ok)
default: // Cancel button or other (e.g., window closed)
completion(.cancel)
let buttonAction: ButtonAction = switch modalResult {
case .alertFirstButtonReturn:
actions[0]
case .alertSecondButtonReturn:
actions[1]
default:
actions.last ?? .cancel
}

completion?(buttonAction)
}
}
15 changes: 10 additions & 5 deletions DockDoor/Utilities/Misc Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ import Cocoa
import Defaults

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()
MessageUtil.showAlert(
title: String(localized: "Restart required"),
message: String(localized: "Please restart the application to apply your changes. Click OK to quit the app."),
actions: [.ok, .cancel],
completion: { result in
if result == .ok {
let appDelegate = NSApplication.shared.delegate as! AppDelegate
appDelegate.restartApp()
}
}
})
)
}

func resetDefaultsToDefaultValues() {
Expand Down
58 changes: 45 additions & 13 deletions DockDoor/Utilities/WindowUtil.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,34 +77,66 @@ enum WindowUtil {

// MARK: - Helper Functions

static func captureWindowImage(window: SCWindow) async throws -> CGImage {
clearExpiredCache()
/// Captures an image of a window using legacy methods for macOS versions earlier than 14.0.
/// This function is used as a fallback when the ScreenCaptureKit's captureScreenshot API are not available.
private static func captureImageLegacy(of window: SCWindow) async throws -> CGImage {
let windowRect = CGRect(
x: window.frame.origin.x,
y: window.frame.origin.y,
width: CGFloat(window.frame.width),
height: CGFloat(window.frame.height)
)

if let cachedImage = getCachedImage(window: window) {
return cachedImage
guard let cgImage = CGWindowListCreateImage(windowRect, .optionIncludingWindow, window.windowID, [.boundsIgnoreFraming, .bestResolution]) else {
throw NSError(domain: "WindowCaptureError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to create image for window"])
}

return cgImage
}

/// Captures an image of a window using ScreenCaptureKit's for macOS versions 14.0 and later.
@available(macOS 14.0, *)
private static func captureImageModern(of window: SCWindow) async throws -> CGImage {
let filter = SCContentFilter(desktopIndependentWindow: window)
let config = SCStreamConfiguration()

// Get the scale factor of the display containing the window
let scaleFactor = await getScaleFactorForWindow(windowID: window.windowID)
// Convert points to pixels
let width = Int(window.frame.width * scaleFactor) / Int(Defaults[.windowPreviewImageScale])
let height = Int(window.frame.height * scaleFactor) / Int(Defaults[.windowPreviewImageScale])

config.width = width
config.height = height
config.scalesToFit = false
config.backgroundColor = .clear
config.captureResolution = .best
config.ignoreGlobalClipDisplay = true
config.ignoreShadowsDisplay = true
config.shouldBeOpaque = false
if #available(macOS 14.2, *) { config.includeChildWindows = false }
config.showsCursor = false

// Get the scale factor of the display containing the window
let scaleFactor = await getScaleFactorForWindow(windowID: window.windowID)
if #available(macOS 14.2, *) {
config.includeChildWindows = false
}

// Convert points to pixels
config.width = Int(window.frame.width * scaleFactor) / Int(Defaults[.windowPreviewImageScale])
config.height = Int(window.frame.height * scaleFactor) / Int(Defaults[.windowPreviewImageScale])
return try await SCScreenshotManager.captureImage(contentFilter: filter, configuration: config)
}

config.showsCursor = false
config.captureResolution = .best
/// Main function to capture a window image using ScreenCaptureKit, with fallback to legacy methods for older macOS versions.
static func captureWindowImage(window: SCWindow) async throws -> CGImage {
clearExpiredCache()

let image = try await SCScreenshotManager.captureImage(contentFilter: filter, configuration: config)
if let cachedImage = getCachedImage(window: window) {
return cachedImage
}

// Use ScreenCaptureKit's API if available, otherwise fall back to a legacy (deprecated) API
let image: CGImage = if #available(macOS 14.0, *) {
try await captureImageModern(of: window)
} else {
try await captureImageLegacy(of: window)
}

let cachedImage = CachedImage(image: image, timestamp: Date(), windowname: window.title)
imageCache[window.windowID] = cachedImage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import Defaults
import FluidGradient
import SwiftUI

@Observable class ScreenCenteredFloatingWindow {
class ScreenCenteredFloatingWindow: ObservableObject {
static let shared = ScreenCenteredFloatingWindow()

var currIndex: Int = 0
var windowSwitcherActive: Bool = false
var fullWindowPreviewActive: Bool = false
@Published var currIndex: Int = 0
@Published var windowSwitcherActive: Bool = false
@Published var fullWindowPreviewActive: Bool = false

enum WindowState {
case windowSwitcher
Expand Down
4 changes: 2 additions & 2 deletions DockDoor/Views/Hover Window/WindowPreviewHoverContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ struct WindowPreviewHoverContainer: View {
runUIUpdates()
}
}
.onChange(of: ScreenCenteredFloatingWindow.shared.currIndex) { _, newIndex in
.onChange(of: ScreenCenteredFloatingWindow.shared.currIndex) { newIndex in
withAnimation {
scrollProxy.scrollTo("\(appName)-\(newIndex)", anchor: .center)
}
}
.onChange(of: windows) { _, _ in
.onChange(of: windows) { _ in
runUIUpdates()
}
}
Expand Down
26 changes: 20 additions & 6 deletions DockDoor/Views/Settings/AppearanceSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ struct AppearanceSettingsView: View {
@Default(.trafficLightButtonsVisibility) var trafficLightButtonsVisibility
@Default(.trafficLightButtonsPosition) var trafficLightButtonsPosition

@State private var previousTrafficLightButtonsPosition: TrafficLightButtonsPosition
@State private var previousWindowTitlePosition: WindowTitlePosition

init() {
_previousTrafficLightButtonsPosition = State(initialValue: Defaults[.trafficLightButtonsPosition])
_previousWindowTitlePosition = State(initialValue: Defaults[.windowTitlePosition])
}

var body: some View {
VStack(alignment: .leading, spacing: 10) {
Toggle(isOn: $showAnimations, label: {
Expand All @@ -40,17 +48,20 @@ struct AppearanceSettingsView: View {
.tag(position)
}
}
.onChange(of: trafficLightButtonsPosition) { oldValue, newValue in
.onChange(of: trafficLightButtonsPosition) { newValue in
if newValue.rawValue == windowTitlePosition.rawValue {
MessageUtil.showMessage(
MessageUtil.showAlert(
title: String(localized: "Elements Overlap"),
message: String(localized: "The selected positions for Traffic Light Buttons and Window Title will overlap."),
actions: [.ok, .cancel],
completion: { result in
if result == .cancel {
trafficLightButtonsPosition = oldValue
trafficLightButtonsPosition = previousTrafficLightButtonsPosition
}
}
)
} else {
previousTrafficLightButtonsPosition = newValue
}
}
.pickerStyle(SegmentedPickerStyle())
Expand Down Expand Up @@ -111,17 +122,20 @@ struct AppearanceSettingsView: View {
.tag(position)
}
}
.onChange(of: windowTitlePosition) { oldValue, newValue in
.onChange(of: windowTitlePosition) { newValue in
if newValue.rawValue == trafficLightButtonsPosition.rawValue {
MessageUtil.showMessage(
MessageUtil.showAlert(
title: String(localized: "Elements Overlap"),
message: String(localized: "The selected positions for Traffic Light Buttons and Window Title will overlap."),
actions: [.ok, .cancel],
completion: { result in
if result == .cancel {
windowTitlePosition = oldValue
windowTitlePosition = previousWindowTitlePosition
}
}
)
} else {
previousWindowTitlePosition = newValue
}
}
.scaledToFit()
Expand Down
12 changes: 7 additions & 5 deletions DockDoor/Views/Settings/GradientColorPaletteSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct GradientColorPaletteSettingsView: View {
if editingIndex != nil, editingColor != nil {
ColorPicker("Edit Color", selection: $tempColor)
.labelsHidden()
.onChange(of: tempColor) { _, newValue in
.onChange(of: tempColor) { newValue in
colorUpdatePublisher.send(newValue)
}
}
Expand Down Expand Up @@ -127,9 +127,10 @@ struct GradientColorPaletteSettingsView: View {
}

private func showMinimumColorsAlert() {
MessageUtil.showMessage(
MessageUtil.showAlert(
title: "Cannot Remove Color",
message: "Minimum number of colors reached."
message: "Minimum number of colors reached.",
actions: [.ok, .cancel]
) { action in
switch action {
case .ok:
Expand All @@ -141,9 +142,10 @@ struct GradientColorPaletteSettingsView: View {
}

private func showMaximumColorsAlert() {
MessageUtil.showMessage(
MessageUtil.showAlert(
title: "Cannot Add Color",
message: "Maximum number of colors (\(maxColors)) reached."
message: "Maximum number of colors (\(maxColors)) reached.",
actions: [.ok, .cancel]
) { action in
switch action {
case .ok:
Expand Down
Loading

0 comments on commit 810af86

Please sign in to comment.