Skip to content
Open
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
12 changes: 12 additions & 0 deletions Rectangle/AccessibilityElement.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// AccessibilityElement.swift

import Foundation
import ApplicationServices

class AccessibilityElement {
fileprivate let wrappedElement: AXUIElement
Expand Down Expand Up @@ -357,6 +358,17 @@ class AccessibilityElement {
windowElement?.wrappedElement.getValue(.minimized) as? Bool
}

@discardableResult
func minimize() -> Bool {
guard let windowElement,
windowElement.wrappedElement.isValueSettable(.minimized) == true else { return false }
return AXUIElementSetAttributeValue(
windowElement.wrappedElement,
NSAccessibility.Attribute.minimized.rawValue as CFString,
kCFBooleanTrue
) == .success
}

var title: String? {
wrappedElement.getValue(.title) as? String
}
Expand Down
19 changes: 18 additions & 1 deletion Rectangle/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -22005,6 +22005,23 @@
}
}
},
"repeat direction to maximize, down to minimize" : {
"extractionState" : "manual",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Richtung wiederholen zum Maximieren, unten zum Minimieren"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "repeat direction to maximize, down to minimize"
}
}
}
},
"Quit Rectangle" : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -33606,4 +33623,4 @@
}
},
"version" : "1.3"
}
}
25 changes: 25 additions & 0 deletions Rectangle/ShortcutManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,31 @@ class ShortcutManager {
return
}

let directionalHalfActions: Set<WindowAction> = [.leftHalf, .rightHalf, .topHalf, .bottomHalf]
if Defaults.subsequentExecutionMode.value == .windowsRepeat,
directionalHalfActions.contains(parameters.action),
let windowElement = parameters.windowElement ?? AccessibilityElement.getFrontWindowElement(),
let windowId = parameters.windowId ?? windowElement.getWindowId(),
!ShortcutCycle.isStale(lastAction: AppDelegate.windowHistory.lastRectangleActions[windowId], currentWindowRect: windowManager.logicalFrame(for: windowElement)),
isRepeatAction(parameters: parameters, windowElement: windowElement, windowId: windowId) {
if parameters.action == .bottomHalf {
if windowElement.minimize() {
AppDelegate.windowHistory.lastRectangleActions.removeValue(forKey: windowId)
} else {
NSSound.beep()
}
return
}
parameters = ExecutionParameters(
.maximize,
updateRestoreRect: parameters.updateRestoreRect,
screen: parameters.screen,
windowElement: windowElement,
windowId: windowId,
source: parameters.source
)
}

// Check if repeat cycles displays
if Defaults.subsequentExecutionMode.value == .cycleMonitor,
parameters.source != .titleBar,
Expand Down
4 changes: 3 additions & 1 deletion Rectangle/SubsequentExecutionMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ enum SubsequentExecutionMode: Int {
case acrossAndResize = 3 // across monitor for right/left, spectacle resize for all else
case cycleMonitor = 4
case resizeAndCycleQuadrants = 5
case windowsRepeat = 6

var title: String {
switch self {
Expand All @@ -18,6 +19,7 @@ enum SubsequentExecutionMode: Int {
case .acrossAndResize: return String(localized: "move to adjacent on left/right, or cycle size on side")
case .cycleMonitor: return String(localized: "cycle through displays")
case .resizeAndCycleQuadrants: return String(localized: "cycle positions on quadrants, cycle sizes on side")
case .windowsRepeat: return String(localized: "repeat direction to maximize, down to minimize")
}
}

Expand All @@ -28,7 +30,7 @@ enum SubsequentExecutionMode: Int {
}
}

static var ordered: [SubsequentExecutionMode] = [.none, .cycleMonitor, .resize, .acrossMonitor, .acrossAndResize, .resizeAndCycleQuadrants]
static var ordered: [SubsequentExecutionMode] = [.none, .cycleMonitor, .resize, .acrossMonitor, .acrossAndResize, .resizeAndCycleQuadrants, .windowsRepeat]
}

class SubsequentExecutionDefault: Default {
Expand Down
2 changes: 1 addition & 1 deletion Rectangle/WindowCalculation/LeftRightHalfCalculation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class LeftRightHalfCalculation: WindowCalculation, RepeatedExecutionsInThirdsCal
return calculateAcrossDisplays(params)
case .resize, .resizeAndCycleQuadrants:
return calculateResize(params)
case .none, .cycleMonitor:
case .none, .cycleMonitor, .windowsRepeat:
let screen = usableScreens.currentScreen
let oneHalfRect = calculateFirstRect(params.asRectParams())
return WindowCalculationResult(rect: oneHalfRect.rect, screen: screen, resultingAction: params.action)
Expand Down