← All repositories

listless

A simple list app for Apple platforms

git clone https://code.inqk.net/listless.git


Scripts/screenshots-mac-desktop.swift (6526B)

1#!/usr/bin/env swift
2
3import AppKit
4import CoreGraphics
5import CoreText
6import Foundation
7import ImageIO
8
9guard CommandLine.arguments.count >= 3 else {
10    fputs("Usage: screenshots-mac-desktop.swift <window.png> <output.png> [wallpaper]\n", stderr)
11    exit(1)
12}
13
14let windowPath = CommandLine.arguments[1]
15let outputPath = CommandLine.arguments[2]
16let wallpaperPath =
17    CommandLine.arguments.count >= 4
18    ? CommandLine.arguments[3]
19    : "Marketing/sequoia-wallpaper.jpg"
20
21// MacBook Air 13" 4th Gen native resolution
22let canvasWidth: CGFloat = 2560
23let canvasHeight: CGFloat = 1664
24
25// Load wallpaper (supports HEIC, JPEG, PNG via ImageIO)
26guard let wpData = try? Data(contentsOf: URL(fileURLWithPath: wallpaperPath)),
27    let wpSource = CGImageSourceCreateWithData(wpData as CFData, nil),
28    let wallpaper = CGImageSourceCreateImageAtIndex(wpSource, 0, nil)
29else {
30    fputs("Failed to load wallpaper: \(wallpaperPath)\n", stderr)
31    exit(1)
32}
33
34// Load window screenshot
35guard let winProvider = CGDataProvider(filename: windowPath),
36    let windowImage = CGImage(
37        pngDataProviderSource: winProvider, decode: nil, shouldInterpolate: true,
38        intent: .defaultIntent)
39else {
40    fputs("Failed to load window image: \(windowPath)\n", stderr)
41    exit(1)
42}
43
44// Create canvas
45guard
46    let context = CGContext(
47        data: nil,
48        width: Int(canvasWidth),
49        height: Int(canvasHeight),
50        bitsPerComponent: 8,
51        bytesPerRow: 0,
52        space: CGColorSpaceCreateDeviceRGB(),
53        bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
54    )
55else {
56    fputs("Failed to create graphics context\n", stderr)
57    exit(1)
58}
59
60// Draw wallpaper: scale to fit width, align to top, crop from bottom
61let wpWidth = CGFloat(wallpaper.width)
62let wpHeight = CGFloat(wallpaper.height)
63let wpScale = canvasWidth / wpWidth
64let scaledWpHeight = wpHeight * wpScale
65// CG origin is bottom-left; align top edge of wallpaper to top of canvas
66let wpY = canvasHeight - scaledWpHeight
67context.draw(
68    wallpaper, in: CGRect(x: 0, y: wpY, width: canvasWidth, height: scaledWpHeight))
69
70// Draw menu bar: translucent strip at the top (@2x: 60px ≈ 30pt)
71let menuBarHeight: CGFloat = 60
72let menuBarY = canvasHeight - menuBarHeight
73context.setFillColor(CGColor(red: 1, green: 1, blue: 1, alpha: 0.25))
74context.fill(CGRect(x: 0, y: menuBarY, width: canvasWidth, height: menuBarHeight))
75
76// Helper to draw a menu bar SF Symbol via CTFont
77func drawMenuSymbol(_ name: String, at x: CGFloat, size: CGFloat) -> CGFloat {
78    guard let image = NSImage(systemSymbolName: name, accessibilityDescription: nil) else {
79        return x
80    }
81    let config = NSImage.SymbolConfiguration(pointSize: size, weight: .regular)
82    let configured = image.withSymbolConfiguration(config) ?? image
83    // Tint the symbol
84    let tinted = NSImage(size: configured.size, flipped: false) { rect in
85        menuTextColor.set()
86        rect.fill(using: .sourceOver)
87        configured.draw(in: rect, from: .zero, operation: .destinationIn, fraction: 1.0)
88        return true
89    }
90    let imgWidth = tinted.size.width
91    let imgHeight = tinted.size.height
92    let imgY = menuBarY + (menuBarHeight - imgHeight) / 2
93    guard let cgImage = tinted.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
94        return x
95    }
96    context.draw(cgImage, in: CGRect(x: x, y: imgY, width: imgWidth, height: imgHeight))
97    return x + imgWidth
98}
99
100// Helper to draw menu bar text
101let menuTextColor = NSColor(white: 0.0, alpha: 0.85)
102func drawMenuText(_ text: String, at x: CGFloat, size: CGFloat, weight: NSFont.Weight = .regular)
103    -> CGFloat
104{
105    let font = NSFont.systemFont(ofSize: size, weight: weight)
106    let attrs: [NSAttributedString.Key: Any] = [
107        .font: font,
108        .foregroundColor: menuTextColor,
109    ]
110    let attrStr = NSAttributedString(string: text, attributes: attrs)
111    let line = CTLineCreateWithAttributedString(attrStr)
112    let bounds = CTLineGetBoundsWithOptions(line, .useOpticalBounds)
113    let y = menuBarY + (menuBarHeight - bounds.height) / 2 - bounds.origin.y
114    context.textPosition = CGPoint(x: x, y: y)
115    CTLineDraw(line, context)
116    return x + bounds.width
117}
118
119// Left side: Apple logo, app name, menus
120let appleFont = NSFont(name: "SF Pro Display", size: 42) ?? NSFont.systemFont(ofSize: 42)
121let appleAttrs: [NSAttributedString.Key: Any] = [
122    .font: appleFont,
123    .foregroundColor: menuTextColor,
124]
125let appleString = NSAttributedString(string: "\u{F8FF}", attributes: appleAttrs)
126let appleLine = CTLineCreateWithAttributedString(appleString)
127let appleBounds = CTLineGetBoundsWithOptions(appleLine, .useOpticalBounds)
128let appleX: CGFloat = 32
129let appleTextY = menuBarY + (menuBarHeight - appleBounds.height) / 2 - appleBounds.origin.y
130context.textPosition = CGPoint(x: appleX, y: appleTextY)
131CTLineDraw(appleLine, context)
132
133var curX = appleX + appleBounds.width + 40
134curX = drawMenuText("Listless", at: curX, size: 28, weight: .bold) + 36
135curX = drawMenuText("File", at: curX, size: 28) + 36
136curX = drawMenuText("Edit", at: curX, size: 28) + 36
137curX = drawMenuText("View", at: curX, size: 28) + 36
138curX = drawMenuText("Window", at: curX, size: 28) + 36
139_ = drawMenuText("Help", at: curX, size: 28)
140
141// Right side: icons, date and time
142var rightX = canvasWidth - 260
143_ = drawMenuText("Wed 9 Apr  9:41 AM", at: rightX, size: 28)
144rightX -= 20
145_ = drawMenuSymbol("switch.2", at: rightX - 44, size: 32)
146_ = drawMenuSymbol("magnifyingglass", at: rightX - 100, size: 28)
147
148// Scale and draw window screenshot offset slightly right of centre
149let winWidth = CGFloat(windowImage.width)
150let winHeight = CGFloat(windowImage.height)
151let winScale = (canvasWidth * 0.4) / winWidth
152let scaledWinWidth = winWidth * winScale
153let scaledWinHeight = winHeight * winScale
154let winX = (canvasWidth - scaledWinWidth) / 2 + 400
155let availableHeight = canvasHeight - menuBarHeight
156let winY = (availableHeight - scaledWinHeight) / 2 + 150
157context.draw(
158    windowImage,
159    in: CGRect(x: winX, y: winY, width: scaledWinWidth, height: scaledWinHeight))
160
161// Save
162guard let resultImage = context.makeImage(),
163    let destination = CGImageDestinationCreateWithURL(
164        URL(fileURLWithPath: outputPath) as CFURL,
165        "public.png" as CFString,
166        1,
167        nil
168    )
169else {
170    fputs("Failed to create output\n", stderr)
171    exit(1)
172}
173
174CGImageDestinationAddImage(destination, resultImage, nil)
175guard CGImageDestinationFinalize(destination) else {
176    fputs("Failed to write output\n", stderr)
177    exit(1)
178}