Skip to content

Commit

Permalink
Merge branch 'master' of github.com:rxhanson/Rectangle
Browse files Browse the repository at this point in the history
  • Loading branch information
rxhanson committed Feb 27, 2022
2 parents c9525b6 + 15f7bcc commit b3bd99d
Show file tree
Hide file tree
Showing 10 changed files with 1,277 additions and 38 deletions.
9 changes: 9 additions & 0 deletions Rectangle.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
D0CFE33327A8CCB1004DA47B /* TopRightThirdCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CFE33227A8CCB1004DA47B /* TopRightThirdCalculation.swift */; };
D0CFE33527A8CD16004DA47B /* BottomLeftThirdCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CFE33427A8CD16004DA47B /* BottomLeftThirdCalculation.swift */; };
D0CFE33727A8CD51004DA47B /* BottomRightThirdCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CFE33627A8CD51004DA47B /* BottomRightThirdCalculation.swift */; };
FDE8FCE027C2950400EACCAA /* MultiWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE8FCDF27C2950400EACCAA /* MultiWindowManager.swift */; };
F0A0DFB36FCC3FCE6E184500 /* Pods_Rectangle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 20A533B9F2D3215AC7B85D1F /* Pods_Rectangle.framework */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -280,6 +281,8 @@
98D16A432592AD55005228CB /* MASShortcutMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MASShortcutMigration.swift; sourceTree = "<group>"; };
98D16A482592B460005228CB /* NotificationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationExtension.swift; sourceTree = "<group>"; };
98D4B6C425B6256C009C7BF6 /* TodoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoManager.swift; sourceTree = "<group>"; };
98DCAC1F27CA702900800FD4 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Main.strings; sourceTree = "<group>"; };
98DCAC2027CA702A00800FD4 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Main.strings; sourceTree = "<group>"; };
98ED1C722393DEE600CD0955 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Main.strings; sourceTree = "<group>"; };
98ED1C732393DEE600CD0955 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Main.strings; sourceTree = "<group>"; };
98ED1C742393DEF200CD0955 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Main.strings"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -317,6 +320,7 @@
E8C299EA27B5238100BC90C3 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Main.strings; sourceTree = "<group>"; };
F2D8480CC730811C953FC1B6 /* Pods_RectangleLauncher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RectangleLauncher.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F4F1FB66CBFB958E8BB16DCD /* Pods_RectangleMAS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RectangleMAS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
FDE8FCDF27C2950400EACCAA /* MultiWindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiWindowManager.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -368,6 +372,7 @@
isa = PBXGroup;
children = (
98192DDD2717201000015E66 /* ReverseAllManager.swift */,
FDE8FCDF27C2950400EACCAA /* MultiWindowManager.swift */,
);
path = MultiWindow;
sourceTree = "<group>";
Expand Down Expand Up @@ -748,6 +753,7 @@
"zh-Hant-HK",
"sv-SE",
ro,
id,
);
mainGroup = 9824700022AF9B7D0037B409;
productRefGroup = 9824700A22AF9B7D0037B409 /* Products */;
Expand Down Expand Up @@ -920,6 +926,7 @@
9821406022B3EFB200ABFB3F /* Defaults.swift in Sources */,
D0CFE33527A8CD16004DA47B /* BottomLeftThirdCalculation.swift in Sources */,
D0CFE33127A8CAED004DA47B /* TopLeftThirdCalculation.swift in Sources */,
FDE8FCE027C2950400EACCAA /* MultiWindowManager.swift in Sources */,
98192DDA270F606C00015E66 /* Debounce.swift in Sources */,
D04CE3002781794E00BD47B3 /* TopLeftNinthCalculation.swift in Sources */,
9821403122B38A0500ABFB3F /* TopHalfCalculation.swift in Sources */,
Expand Down Expand Up @@ -1035,6 +1042,7 @@
98FE8977246E79C400871535 /* zh-Hant-HK */,
98C96394279A3DBB000E3ED2 /* sv-SE */,
E8C299EA27B5238100BC90C3 /* ro */,
98DCAC2027CA702A00800FD4 /* id */,
);
name = Main.storyboard;
sourceTree = "<group>";
Expand All @@ -1061,6 +1069,7 @@
98FE8976246E79C400871535 /* zh-Hant-HK */,
98C96393279A3DBB000E3ED2 /* sv-SE */,
E8C299E927B5238100BC90C3 /* ro */,
98DCAC1F27CA702900800FD4 /* id */,
);
name = Main.storyboard;
sourceTree = "<group>";
Expand Down
19 changes: 17 additions & 2 deletions Rectangle/AccessibilityElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,7 @@ class AccessibilityElement {

static func normalizeCoordinatesOf(_ rect: CGRect, frameOfScreen: CGRect) -> CGRect {
var normalizedRect = rect
let frameOfScreenWithMenuBar = NSScreen.screens[0].frame as CGRect
normalizedRect.origin.y = frameOfScreenWithMenuBar.height - rect.maxY
normalizedRect.origin.y = frameOfScreen.height - rect.maxY
return normalizedRect
}

Expand Down Expand Up @@ -243,6 +242,22 @@ class AccessibilityElement {
return pid
}

func isMinimized() -> Bool? {
return self.rawValue(for: .minimized) as? Bool
}

func isHidden() -> Bool? {
return self.rawValue(for: .hidden) as? Bool
}

func isWindow() -> Bool {
return role() == kAXWindowRole
}

func isMainWindow() -> Bool? {
return self.rawValue(for: .main) as? Bool
}

private func getPosition() -> CGPoint? {
return self.value(for: .position)
}
Expand Down
4 changes: 3 additions & 1 deletion Rectangle/Defaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class Defaults {
static let applyGapsToMaximizeHeight = OptionalBoolDefault(key: "applyGapsToMaximizeHeight")
static let cornerSnapAreaSize = FloatDefault(key: "cornerSnapAreaSize", defaultValue: 20)
static let shortEdgeSnapAreaSize = FloatDefault(key: "shortEdgeSnapAreaSize", defaultValue: 145)
static let cascadeAllDeltaSize = FloatDefault(key: "cascadeAllDeltaSize", defaultValue: 30)

static var array: [Default] = [
launchOnLogin,
Expand Down Expand Up @@ -120,7 +121,8 @@ class Defaults {
applyGapsToMaximize,
applyGapsToMaximizeHeight,
cornerSnapAreaSize,
shortEdgeSnapAreaSize
shortEdgeSnapAreaSize,
cascadeAllDeltaSize
]
}

Expand Down
123 changes: 123 additions & 0 deletions Rectangle/MultiWindow/MultiWindowManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//
// MultiWindowManager.swift
// Rectangle
//
// Created by Mikhail (Dirondin) Polubisok on 2/20/22.
// Copyright © 2021 Ryan Hanson. All rights reserved.
//
import Cocoa
import MASShortcut

class MultiWindowManager {

static func execute(parameters: ExecutionParameters) -> Bool {
// TODO: Protocol and factory for all multi-window positioning algorithms
switch parameters.action {
case .reverseAll:
ReverseAllManager.reverseAll(windowElement: parameters.windowElement)
return true
case .tileAll:
tileAllWindowsOnScreen(windowElement: parameters.windowElement)
return true
case .cascadeAll:
cascadeAllWindowsOnScreen(windowElement: parameters.windowElement)
return true
default:
return false
}
}

static private func allWindowsOnScreen(windowElement: AccessibilityElement? = nil, sortByPID: Bool = false) -> (screens: UsableScreens, windows: [AccessibilityElement])? {
let screenDetection = ScreenDetection()

guard let windowElement = windowElement ?? AccessibilityElement.frontmostWindow(),
let screens = screenDetection.detectScreens(using: windowElement)
else {
NSSound.beep()
Logger.log("Can't detect screen for multiple windows")
return nil
}

let currentScreen = screens.currentScreen

var windows = AccessibilityElement.allWindows()
if sortByPID {
windows.sort(by: { (w1: AccessibilityElement, w2: AccessibilityElement) -> Bool in
return w1.getPid() > w2.getPid()
})
}

var actualWindows = [AccessibilityElement]()
for w in windows {
if Defaults.todo.userEnabled && TodoManager.isTodoWindow(w) { continue }
let screen = screenDetection.detectScreens(using: w)?.currentScreen
if screen == currentScreen
&& w.isWindow()
&& w.isSheet() != true
&& w.isMinimized() != true
&& w.isHidden() != true
&& w.isSystemDialog() != true {
actualWindows.append(w)
}
}

return (screens, actualWindows)
}

static func tileAllWindowsOnScreen(windowElement: AccessibilityElement? = nil) {
guard let (screens, windows) = allWindowsOnScreen(windowElement: windowElement, sortByPID: true) else {
return
}

let screenFrame = AccessibilityElement.normalizeCoordinatesOf(screens.visibleFrameOfCurrentScreen, frameOfScreen: screens.frameOfCurrentScreen)
let count = windows.count

let colums = Int(ceil(sqrt(CGFloat(count))))
let rows = Int(ceil(CGFloat(count) / CGFloat(colums)))
let size = CGSize(width: (screenFrame.maxX - screenFrame.minX) / CGFloat(colums), height: (screenFrame.maxY - screenFrame.minY) / CGFloat(rows))

for (ind, w) in windows.enumerated() {
let column = ind % Int(colums)
let row = ind / Int(colums)
tileWindow(w, screenFrame: screenFrame, size: size, column: column, row: row)
}
}

private static func tileWindow(_ w: AccessibilityElement, screenFrame: CGRect, size: CGSize, column: Int, row: Int) {
var rect = w.rectOfElement()

// TODO: save previous position in history

rect.origin.x = screenFrame.origin.x + size.width * CGFloat(column)
rect.origin.y = screenFrame.origin.y + size.height * CGFloat(row)
rect.size = size

w.setRectOf(rect)
}

static func cascadeAllWindowsOnScreen(windowElement: AccessibilityElement? = nil) {
guard let (screens, windows) = allWindowsOnScreen(windowElement: windowElement, sortByPID: true) else {
return
}

let screenFrame = AccessibilityElement.normalizeCoordinatesOf(screens.visibleFrameOfCurrentScreen, frameOfScreen: screens.frameOfCurrentScreen)

let delta = CGFloat(Defaults.cascadeAllDeltaSize.value)

for (ind, w) in windows.enumerated() {
cascadeWindow(w, screenFrame: screenFrame, delta: delta, index: ind)
}
}

private static func cascadeWindow(_ w: AccessibilityElement, screenFrame: CGRect, delta: CGFloat, index: Int) {
var rect = w.rectOfElement()

// TODO: save previous position in history

rect.origin.x = screenFrame.origin.x + delta * CGFloat(index)
rect.origin.y = screenFrame.origin.y + delta * CGFloat(index)

w.setRectOf(rect)
w.bringToFront()
}
}
4 changes: 2 additions & 2 deletions Rectangle/MultiWindow/ReverseAllManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import MASShortcut

class ReverseAllManager {

static func reverseAll() {
static func reverseAll(windowElement: AccessibilityElement? = nil) {
let sd = ScreenDetection()

let currentWindow = AccessibilityElement.frontmostWindow()
let currentWindow = windowElement ?? AccessibilityElement.frontmostWindow()
guard let currentScreen = sd.detectScreens(using: currentWindow)?.currentScreen else { return }

let windows = AccessibilityElement.allWindows()
Expand Down
3 changes: 1 addition & 2 deletions Rectangle/ShortcutManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ class ShortcutManager {
@objc func windowActionTriggered(notification: NSNotification) {
guard var parameters = notification.object as? ExecutionParameters else { return }

if parameters.action == .reverseAll {
ReverseAllManager.reverseAll()
if MultiWindowManager.execute(parameters: parameters) {
return
}

Expand Down
15 changes: 11 additions & 4 deletions Rectangle/WindowAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ enum WindowAction: Int {
bottomLeftEighth = 62,
bottomCenterLeftEighth = 63,
bottomCenterRightEighth = 64,
bottomRightEighth = 65
bottomRightEighth = 65,
tileAll = 66,
cascadeAll = 67

// Order matters here - it's used in the menu
static let active = [leftHalf, rightHalf, centerHalf, topHalf, bottomHalf,
Expand All @@ -95,7 +97,8 @@ enum WindowAction: Int {
bottomLeftNinth, bottomCenterNinth, bottomRightNinth,
topLeftThird, topRightThird, bottomLeftThird, bottomRightThird,
topLeftEighth, topCenterLeftEighth, topCenterRightEighth, topRightEighth,
bottomLeftEighth, bottomCenterLeftEighth, bottomCenterRightEighth, bottomRightEighth
bottomLeftEighth, bottomCenterLeftEighth, bottomCenterRightEighth, bottomRightEighth,
tileAll, cascadeAll
]

func post() {
Expand Down Expand Up @@ -184,6 +187,8 @@ enum WindowAction: Int {
case .bottomCenterLeftEighth: return "bottomCenterLeftEighth"
case .bottomCenterRightEighth: return "bottomCenterRightEighth"
case .bottomRightEighth: return "bottomRightEighth"
case .tileAll: return "tileAll"
case .cascadeAll: return "cascadeAll"
}
}

Expand Down Expand Up @@ -316,7 +321,7 @@ enum WindowAction: Int {
case .topLeftEighth, .topCenterLeftEighth, .topCenterRightEighth, .topRightEighth,
.bottomLeftEighth, .bottomCenterLeftEighth, .bottomCenterRightEighth, .bottomRightEighth:
return nil
case .specified, .reverseAll:
case .specified, .reverseAll, .tileAll, .cascadeAll:
return nil
}

Expand Down Expand Up @@ -454,6 +459,8 @@ enum WindowAction: Int {
case .bottomCenterRightEighth: return NSImage()
case .bottomRightEighth: return NSImage()
case .specified, .reverseAll: return NSImage()
case .tileAll: return NSImage()
case .cascadeAll: return NSImage()
}
}

Expand Down Expand Up @@ -493,7 +500,7 @@ enum WindowAction: Int {
return Defaults.applyGapsToMaximize.userDisabled ? .none : .both;
case .maximizeHeight:
return Defaults.applyGapsToMaximizeHeight.userDisabled ? .none : .vertical;
case .almostMaximize, .previousDisplay, .nextDisplay, .larger, .smaller, .center, .restore, .specified, .reverseAll:
case .almostMaximize, .previousDisplay, .nextDisplay, .larger, .smaller, .center, .restore, .specified, .reverseAll, .tileAll, .cascadeAll:
return .none
}
}
Expand Down
Loading

0 comments on commit b3bd99d

Please sign in to comment.