Skip to content

Commit

Permalink
#39 Ability to record window resize history
Browse files Browse the repository at this point in the history
  • Loading branch information
MrKai77 committed Sep 22, 2023
1 parent 78139ba commit bc361e4
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 7 deletions.
4 changes: 4 additions & 0 deletions Loop/Extensions/Defaults+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ extension Defaults.Keys {
"centerKeybind",
default: [[.kVK_Return]]
)
static let lastDirectionKeybind = Key<[Set<CGKeyCode>]>(
"lastDirectionKeybind",
default: [[.kVK_ANSI_Z]]
)

// Halves
static let topHalfKeybind = Key<[Set<CGKeyCode>]>(
Expand Down
11 changes: 10 additions & 1 deletion Loop/Window Management/Window.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import SwiftUI

class Window {
// private let kAXFullscreenAttribute = "AXFullScreen"
let axWindow: AXUIElement

init?(element: AXUIElement) {
Expand Down Expand Up @@ -100,6 +99,13 @@ class Window {
}
}

var cgWindowID: CGWindowID? {
var windowId = CGWindowID(0)
let result = _AXUIElementGetWindow(self.axWindow, &windowId)
guard result == .success else { return nil }
return windowId
}

/// MacOS doesn't provide us a way to find the minimum size of a window from the accessibility API.
/// So we deliberately force-resize the window to 0x0 and see how small it goes, take note of the frame,
/// then we resotere the original window size. However, this does have one big consequence. The user
Expand Down Expand Up @@ -150,3 +156,6 @@ class Window {
}
}
}

@_silgen_name("_AXUIElementGetWindow") @discardableResult
func _AXUIElementGetWindow(_ axUiElement: AXUIElement, _ wid: inout CGWindowID) -> AXError
4 changes: 4 additions & 0 deletions Loop/Window Management/WindowDirection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum WindowDirection: CaseIterable {
case noAction
case maximize
case center
case lastDirection

// Halves
case topHalf
Expand Down Expand Up @@ -77,6 +78,7 @@ enum WindowDirection: CaseIterable {
case .noAction: nil
case .maximize: "Maximize"
case .center: "Center"
case .lastDirection: nil

case .topHalf: "Top Half"
case .rightHalf: "Right Half"
Expand Down Expand Up @@ -107,6 +109,7 @@ enum WindowDirection: CaseIterable {
case .noAction: [[]]
case .maximize: Defaults[.maximizeKeybind]
case .center: Defaults[.centerKeybind]
case .lastDirection: Defaults[.lastDirectionKeybind]

case .topHalf: Defaults[.topHalfKeybind]
case .rightHalf: Defaults[.rightHalfKeybind]
Expand Down Expand Up @@ -249,6 +252,7 @@ enum WindowDirection: CaseIterable {
case .noAction: nil
case .maximize: CGRect(x: 0, y: 0, width: 1.0, height: 1.0)
case .center: nil
case .lastDirection: nil

// Halves
case .topHalf: CGRect(x: 0, y: 0, width: 1.0, height: 1.0/2.0)
Expand Down
74 changes: 68 additions & 6 deletions Loop/Window Management/WindowEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,61 @@
import SwiftUI
import Defaults

struct WindowRecords {
static private var records: [WindowRecords.Record] = []

private struct Record {
var cgWindowID: CGWindowID
var initialFrame: CGRect
var currentFrame: CGRect
var directionRecords: [WindowDirection]
}

/// This will erase ALL previous records of the window, and start a fresh new record for the selected window.
/// - Parameter window: The window to record
static func recordFirst(for window: Window) {
guard let cgWindowID = window.cgWindowID else { return }
WindowRecords.records.removeAll(where: { $0.cgWindowID == cgWindowID })

WindowRecords.records.append(
WindowRecords.Record(
cgWindowID: cgWindowID,
initialFrame: window.frame,
currentFrame: window.frame,
directionRecords: [.noAction]
)
)
}

static func record(_ window: Window, _ direction: WindowDirection) {
guard let cgWindowID = window.cgWindowID,
WindowRecords.hasBeenRecorded(window),
let idx = WindowRecords.records.firstIndex(where: { $0.cgWindowID == cgWindowID }) else {
return
}

WindowRecords.records[idx].currentFrame = window.frame
WindowRecords.records[idx].directionRecords.insert(direction, at: 0)

print(WindowRecords.records)
}

static func hasBeenRecorded(_ window: Window) -> Bool {
guard let cgWindowID = window.cgWindowID else { return false }
return WindowRecords.records.contains(where: { $0.cgWindowID == cgWindowID })
}

static func getLastDirection(for window: Window) -> WindowDirection {
guard let cgWindowID = window.cgWindowID,
WindowRecords.hasBeenRecorded(window),
let idx = WindowRecords.records.firstIndex(where: { $0.cgWindowID == cgWindowID }) else {
return .noAction
}

return WindowRecords.records[idx].directionRecords[1]
}
}

struct WindowEngine {

/// Resize a Window
Expand All @@ -16,8 +71,13 @@ struct WindowEngine {
/// - direction: WindowDirection
/// - screen: Screen the window should be resized on
static func resize(_ window: Window, to direction: WindowDirection, _ screen: NSScreen) {
guard direction != .noAction else { return }
window.setFullscreen(false)

if !WindowRecords.hasBeenRecorded(window) {
WindowRecords.recordFirst(for: window)
}

let oldWindowFrame = window.frame
guard let screenFrame = screen.safeScreenFrame,
let currentWindowFrame = WindowEngine.generateWindowFrame(oldWindowFrame, screenFrame, direction) else {
Expand Down Expand Up @@ -47,6 +107,8 @@ struct WindowEngine {
} else {
window.setFrame(targetWindowFrame, animate: false)
WindowEngine.handleSizeConstrainedWindow(window: window, screenFrame: screenFrame)

WindowRecords.record(window, direction)
}
}

Expand All @@ -56,12 +118,12 @@ struct WindowEngine {
guard let app = NSWorkspace.shared.runningApplications.first(where: { $0.isActive }),
let window = Window(pid: app.processIdentifier) else { return nil }

#if DEBUG
print("===== NEW WINDOW =====")
print("Frontmost window: \(window.axWindow)")
print("kAXWindowRole: \(window.role?.rawValue ?? "N/A")")
print("kAXStandardWindowSubrole: \(window.subrole?.rawValue ?? "N/A")")
#endif
// #if DEBUG
// print("===== NEW WINDOW =====")
// print("Frontmost window: \(window.axWindow)")
// print("kAXWindowRole: \(window.role?.rawValue ?? "N/A")")
// print("kAXStandardWindowSubrole: \(window.subrole?.rawValue ?? "N/A")")
// #endif

return window
}
Expand Down

0 comments on commit bc361e4

Please sign in to comment.