Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 31 additions & 42 deletions Loop/Core/WindowDragManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Defaults
import Scribe
import SwiftUI

@MainActor
final class WindowDragManager {
static let shared = WindowDragManager()
private init() {}
Expand Down Expand Up @@ -42,7 +43,6 @@ final class WindowDragManager {
!Defaults[.stashManagerStashedWindows].isEmpty
}

@MainActor
func addObservers() {
accessibilityCheckerTask = Task(priority: .background) { [weak self] in
for await status in AccessibilityManager.shared.stream(initial: true) {
Expand Down Expand Up @@ -90,7 +90,7 @@ final class WindowDragManager {
return
}

Task { @MainActor in
Task {
guard let initialMousePosition else {
initialMousePosition = currentMousePosition
return
Expand All @@ -110,9 +110,6 @@ final class WindowDragManager {
}

if let window = draggingWindow, let initialFrame = initialWindowFrame, hasWindowResized(window.frame, initialFrame) {
StashManager.shared.onWindowDragged(window.cgWindowID)
WindowRecords.eraseRecords(for: window)

if hasWindowMoved(window.frame, initialFrame) {
if Defaults[.restoreWindowFrameOnDrag] {
restoreInitialWindowSize(window)
Expand All @@ -131,28 +128,33 @@ final class WindowDragManager {
processSnapAction()
}
}

StashManager.shared.onWindowDragged(window.cgWindowID)
WindowRecords.eraseRecords(for: window)
}
}
}

private func leftMouseUp(_: CGEvent) {
Task { @MainActor in
guard Defaults[.windowSnapping] else {
return
}

Task {
previewController.close()

if let window = draggingWindow,
let screen = NSScreen.screenWithMouse,
let initialFrame = initialWindowFrame,
hasWindowMoved(window.frame, initialFrame) {
if Defaults[.windowSnapping] {
attemptWindowSnap(window)
}
WindowEngine.resize(window, to: .init(direction), on: screen)
}

previewController.close()
draggingWindow = nil

resetDragState()
}
}

@MainActor
private func setCurrentDraggingWindow() {
guard determineDraggedWindowTask == nil else {
return
Expand All @@ -163,9 +165,8 @@ final class WindowDragManager {
determineDraggedWindowTask = nil
}

guard
let draggingWindow = try? WindowUtility.windowAtPosition(currentMousePosition),
!draggingWindow.isAppExcluded
guard let draggingWindow = try? WindowUtility.windowAtPosition(currentMousePosition),
!draggingWindow.isAppExcluded
else {
didFailToResolveDraggedWindow = true
return
Expand Down Expand Up @@ -263,41 +264,29 @@ final class WindowDragManager {
await AccentColorController.shared.refresh()
}

direction = WindowDirection.getSnapDirection(
let newDirection = WindowDirection.getSnapDirection(
mouseLocation: currentMousePosition,
currentDirection: direction,
currentDirection: oldDirection,
screenFrame: screenFrame,
ignoredFrame: ignoredFrame
)

Log.info("Window snapping direction changed: \(direction.debugDescription)", category: .windowDragManager)
// Only update if direction actually changed
if newDirection != oldDirection {
direction = newDirection

previewController.open(screen: screen, window: draggingWindow, startingAction: nil)
previewController.setAction(to: WindowAction(direction))
} else {
direction = .noAction
previewController.close()
}

if direction != oldDirection {
if Defaults[.hapticFeedback] {
NSHapticFeedbackManager.defaultPerformer.perform(
NSHapticFeedbackManager.FeedbackPattern.alignment,
performanceTime: NSHapticFeedbackManager.PerformanceTime.now
)
}
}
}
Log.info("Window snapping direction changed: \(newDirection.debugDescription)", category: .windowDragManager)

private func attemptWindowSnap(_ window: Window) {
guard let screen = NSScreen.screenWithMouse else {
return
}
previewController.open(screen: screen, window: draggingWindow, startingAction: nil)
previewController.setAction(to: WindowAction(newDirection))

let snapDirection = direction
DispatchQueue.main.async {
WindowEngine.resize(window, to: .init(snapDirection), on: screen)
self.direction = .noAction
if newDirection != .noAction, Defaults[.hapticFeedback] {
NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .now)
}
}
} else if !(oldDirection == .noAction || oldDirection == .noSelection) {
direction = .noAction
previewController.close()
}
}
}
33 changes: 17 additions & 16 deletions Loop/Window Management/Window/WindowUtility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,28 @@ import Defaults
import Scribe

/// This enum is in charge of fetching windows in the user's workspace, which will be used by Loop.
@Loggable(style: .static)
enum WindowUtility {
/// Get the target window, depending on the user's preferences. This could be the frontmost window, or the window under the cursor.
/// - Returns: The target window
static func userDefinedTargetWindow() -> Window? {
var result: Window?

do {
Log.info("Getting window at cursor...", category: .windowUtility)
log.info("Getting window at cursor...")

if Defaults[.resizeWindowUnderCursor],
let mouseLocation = CGEvent.mouseLocation,
let window = try windowAtPosition(mouseLocation) {
result = window
}
} catch {
Log.warn("Failed to get window at cursor: \(error.localizedDescription)", category: .windowUtility)
if Defaults[.resizeWindowUnderCursor],
let mouseLocation = CGEvent.mouseLocation,
let window = windowAtPosition(mouseLocation) {
result = window
}

if result == nil {
do {
Log.info("Getting frontmost window...", category: .windowUtility)
log.info("Getting frontmost window...")

result = try frontmostWindow()
} catch {
Log.warn("Failed to get frontmost window: \(error.localizedDescription)", category: .windowUtility)
log.warn("Failed to get frontmost window: \(error.localizedDescription)")
}
}

Expand All @@ -53,11 +50,15 @@ enum WindowUtility {
/// Get the Window at a given position.
/// - Parameter position: The position to check for
/// - Returns: The window at the given position, if any
static func windowAtPosition(_ position: CGPoint) throws -> Window? {
// If we can find the window at a point using the Accessibility API, return it
if let element = try AXUIElement.systemWide.getElementAtPosition(position),
let windowElement: AXUIElement = try element.getValue(.window) {
return try Window(element: windowElement)
static func windowAtPosition(_ position: CGPoint) -> Window? {
do {
// If we can find the window at a point using the Accessibility API, return it
if let element = try AXUIElement.systemWide.getElementAtPosition(position),
let windowElement: AXUIElement = try element.getValue(.window) {
return try Window(element: windowElement)
}
} catch {
log.warn("Failed to determine element at position: \(error.localizedDescription)")
}

// If the previous method didn't work, loop through all windows on-screen and return the first one that contains the desired point
Expand Down