An app that snaps
About
My productivity at a computer is highly dependent on having shortcuts to snap windows.
The MacOS did not come have native snapping capabilities until recently. And still, it's not all apps that have them enabled.
I've been studying MacOS development for the last month, and thought it was finally time that I challenged myself to implementing an actual tool without following instructions.
Adventure
I was very fortunate that the Rectangle project is open. I easily able to find the line of code that performed the snapping action.
Note: While my adventure in the codebase was very brief, I was nevertheless impressed by the craftmanship in the codebase.
I'm going to dump my entire program (it's not very long). See the snapping
operation is in the snap function.
import ApplicationServices
import Cocoa
import Foundation
enum Direction {
case up
case down
case left
case right
}
func parseDirection(from: String) -> Direction? {
switch from {
case "up":
return .up
case "down":
return .down
case "left":
return .left
case "right":
return .right
default:
return nil
}
}
struct Dimension {
let menuHeight: CGFloat
let height: CGFloat
let width: CGFloat
}
func getDimension(of screen : NSScreen) -> Dimension {
let full = screen.frame
let visible = screen.visibleFrame
return Dimension(menuHeight: full.maxY - visible.maxY, height: visible.height, width: visible.width)
}
struct Destination {
let position: CGPoint
let size: CGSize
}
func calculateDestination(from: Direction) -> Destination {
let screen = NSScreen.main! // XXX: YOLO
let dimension = getDimension(of: screen)
let position: CGPoint
let size: CGSize
switch from {
case .up:
position = CGPoint(x: 0, y: 0)
size = CGSize(width: dimension.width, height: dimension.height / 2)
case .down:
position = CGPoint(x: 0, y: dimension.height / 2 + dimension.menuHeight)
size = CGSize(width: dimension.width, height: dimension.height / 2)
case .left:
position = CGPoint(x: 0, y: 0)
size = CGSize(width: dimension.width / 2, height: dimension.height)
case .right:
position = CGPoint(x: dimension.width / 2, y: 0)
size = CGSize(width: dimension.width / 2, height: dimension.height)
}
return Destination(position: position, size: size)
}
func getApps() -> [NSRunningApplication] {
return NSWorkspace.shared.runningApplications.filter { app in
app.activationPolicy == .regular
}
}
func getAppBy(name: String, from applications: [NSRunningApplication]) -> NSRunningApplication? {
return applications.first { NSRunningApplication in
NSRunningApplication.activationPolicy == .regular
&& NSRunningApplication.localizedName == name
}
}
func snap(_ application: NSRunningApplication, to destination: Destination) {
let element = AXUIElementCreateApplication(application.processIdentifier)
var _windows: CFTypeRef?
let result = AXUIElementCopyAttributeValue(element, kAXWindowsAttribute as CFString, &_windows)
guard result == .success, let windows = _windows as? [AXUIElement] else {
print("Failed to retrieve windows for the application.")
return
}
for window in windows {
var position = destination.position
guard let cgPointValue = AXValueCreate(.cgPoint, &position) else {
print("whops on position")
return
}
AXUIElementSetAttributeValue(window, kAXPositionAttribute as CFString, cgPointValue)
var size = destination.size
guard let cgSizeValue = AXValueCreate(.cgSize, &size) else {
print("whops on size")
return
}
AXUIElementSetAttributeValue(window, kAXSizeAttribute as CFString, cgSizeValue)
}
}
func main() {
let args = CommandLine.arguments
if args.count != 3 {
fputs(
"Bad argument count. The syntax is `macos-snap <Application Name> <\"up\"|\"down\"|\"left\"|\"right\">\n",
stderr)
return
}
let apps = getApps()
guard let app = getAppBy(name: args[1], from: apps) else {
print(
"Could not find app with name: \(args[1]). These are the available names of the apps that can be snapped:"
)
apps.forEach { NSRunningApplication in
if let name = NSRunningApplication.localizedName {
print(" - \(name)")
}
}
return
}
guard let direction = parseDirection(from: args[2]) else {
print(
"Could not parse direction: \(args[2]). Please provide <\"up\"|\"down\"|\"left\"|\"right\"> as direction."
)
return
}
let destination = calculateDestination(from: direction)
snap(app, to: destination)
}
main()