Skip to content

Instantly share code, notes, and snippets.

@szhu
Created November 3, 2022 16:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save szhu/83caf8853979baa19797c3bb4bf59b8d to your computer and use it in GitHub Desktop.
Save szhu/83caf8853979baa19797c3bb4bf59b8d to your computer and use it in GitHub Desktop.
Open 4 Stickies windows and drag them around to create a "hole".
import AppKit
import ApplicationServices
import CoreFoundation
import Foundation
// Helpers
// https://github.com/keith/ModMove/blob/main/ModMove/AXValue%2BHelper.swift
extension AXValue {
func toValue<T>() -> T? {
let pointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
let success = AXValueGetValue(self, AXValueGetType(self), pointer)
return success ? pointer.pointee : nil
}
static func from<T>(value: T, type: AXValueType) -> AXValue? {
let pointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
pointer.pointee = value
return AXValueCreate(type, pointer)
}
}
func setWindowBounds(_ axWindow: AXUIElement, _ bounds: CGRect) {
let axOrigin = AXValue.from(value: bounds.origin, type: .cgPoint)!
AXUIElementSetAttributeValue(axWindow, kAXPositionAttribute as CFString, axOrigin)
let axSize = AXValue.from(value: bounds.size, type: .cgSize)!
AXUIElementSetAttributeValue(axWindow, kAXSizeAttribute as CFString, axSize)
}
func getWindowBounds(_ axWindow: AXUIElement) -> CGRect {
var axOrigin: AnyObject?
AXUIElementCopyAttributeValue(axWindow, kAXPositionAttribute as CFString, &axOrigin)
var origin = CGPoint.zero
AXValueGetValue(axOrigin as! AXValue, AXValueType(rawValue: kAXValueCGPointType)!, &origin)
var axSize: AnyObject?
AXUIElementCopyAttributeValue(axWindow, kAXSizeAttribute as CFString, &axSize)
var size = CGSize.zero
AXValueGetValue(axSize as! AXValue, AXValueType(rawValue: kAXValueCGSizeType)!, &size)
return CGRect(x: origin.x, y: origin.y, width: size.width, height: size.height)
}
func minIndexOfList(_ list: [CGFloat]) -> Int {
var minIndex = 0
for i in 0 ..< list.count {
if list[i] < list[minIndex] {
minIndex = i
}
}
return minIndex
}
func maxIndexOfList(_ list: [CGFloat]) -> Int {
var maxIndex = 0
for i in 0 ..< list.count {
if list[i] > list[maxIndex] {
maxIndex = i
}
}
return maxIndex
}
//
class Main: NSObject {
@objc
func loop() {
guard let screen = NSScreen.main else {
fatalError("No main screen")
}
let screenBounds = screen.frame
guard let runningApp = NSRunningApplication.runningApplications(withBundleIdentifier: "com.apple.Stickies").first else {
fatalError("Stickies not running")
}
let axApp = AXUIElementCreateApplication(runningApp.processIdentifier)
var value: AnyObject?
guard AXUIElementCopyAttributeValue(axApp, kAXWindowsAttribute as CFString, &value) == .success else {
fatalError("Could not get window list")
}
guard let windowList = value as? [AXUIElement] else {
fatalError("Could not get window list (incorrect type)")
}
func rect(_ left: CGFloat, _ top: CGFloat, _ right: CGFloat, _ bottom: CGFloat) -> CGRect {
return CGRect(x: left, y: top, width: right - left, height: bottom - top)
}
guard windowList.count >= 4 else {
fatalError("Not enough windows")
}
let windowBounds = [
getWindowBounds(windowList[0]),
getWindowBounds(windowList[1]),
getWindowBounds(windowList[2]),
getWindowBounds(windowList[3]),
]
let iLeft = minIndexOfList(windowBounds.map { $0.maxX })
let windowLeft = windowList[iLeft]
var holeLeft = windowBounds[iLeft].maxX
let iRight = maxIndexOfList(windowBounds.map { $0.minX })
let windowRight = windowList[iRight]
var holeRight = windowBounds[iRight].minX
let iTop = minIndexOfList(windowBounds.map { $0.maxY })
let windowTop = windowList[iTop]
var holeTop = windowBounds[iTop].maxY
let iBottom = maxIndexOfList(windowBounds.map { $0.minY })
let windowBottom = windowList[iBottom]
var holeBottom = windowBounds[iBottom].minY
print("Hole (original): \(holeLeft), \(holeTop), \(holeRight), \(holeBottom)")
let screenLeft = screenBounds.minX
let screenTop = screenBounds.minY
let screenRight = screenBounds.maxX
let screenBottom = screenBounds.maxY
func bringWithinRange(_ value: CGFloat, _ min: CGFloat, _ max: CGFloat) -> CGFloat {
if value < min {
return min
} else if value > max {
return max
} else {
return value
}
}
holeLeft = bringWithinRange(holeLeft, screenLeft + 100, screenRight - 100)
holeRight = bringWithinRange(holeRight, screenLeft + 100, screenRight - 100)
if holeLeft >= holeRight {
holeLeft = holeRight
}
holeTop = bringWithinRange(holeTop, screenTop + 100, screenBottom - 100)
holeBottom = bringWithinRange(holeBottom, screenTop + 100, screenBottom - 100)
if holeTop >= holeBottom {
holeTop = holeBottom
}
print("Hole (adjusted): \(holeLeft), \(holeTop), \(holeRight), \(holeBottom)")
let targetBounds = [
// Left
rect(screenLeft, screenTop, holeLeft, screenBottom),
// Top
rect(screenLeft, screenTop + 25, screenRight, holeTop),
// Right
rect(holeRight, screenTop, screenRight, screenBottom),
// Bottom
rect(screenLeft, holeBottom, screenRight, screenBottom),
]
setWindowBounds(windowLeft, targetBounds[0])
setWindowBounds(windowTop, targetBounds[1])
setWindowBounds(windowRight, targetBounds[2])
setWindowBounds(windowBottom, targetBounds[3])
// var i = 0
// for window in windowList {
// if i >= targetBounds.count {
// break
// }
// print()
// print(getWindowBounds(window))
// print(targetBounds[i])
// setWindowBounds(window, targetBounds[i])
// i += 1
// }
}
}
let m = Main()
Timer.scheduledTimer(timeInterval: 0.3, target: m, selector: #selector(m.loop), userInfo: nil, repeats: true)
NSApplication.shared.run()
// https://stackoverflow.com/a/47489959/782045
// let type = CGWindowListOption.optionOnScreenOnly
// let windowList = CGWindowListCopyWindowInfo(type, kCGNullWindowID) as NSArray? as? [[String: AnyObject]]
// for entry in windowList! {
// let owner = entry[kCGWindowOwnerName as String] as! String
// print(owner)
// // var bounds = entry[kCGWindowBounds as String] as? [String: Int]
// let pid = entry[kCGWindowOwnerPID as String] as? Int32
// if owner == "Stickes" {
// let appRef = AXUIElementCreateApplication(pid!) // TopLevel Accessability Object of PID
// var value: AnyObject?
// _ = AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute as CFString, &value)
// if let windowList = value as? [AXUIElement] { print("windowList #\(windowList)")
// if let window = windowList.first {
// var newPoint = CGPoint(x: 0, y: 0)
// let position = AXValueCreate(AXValueType(rawValue: kAXValueCGPointType)!, &newPoint)!
// AXUIElementSetAttributeValue(window, kAXPositionAttribute as CFString, position)
// var newSize = CGSize(width: 400, height: 400)
// let size = AXValueCreate(AXValueType(rawValue: kAXValueCGSizeType)!, &newSize)!
// AXUIElementSetAttributeValue(window, kAXSizeAttribute as CFString, size)
// }
// }
// }
// }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment