|
#!/bin/zsh |
|
|
|
: <<'MANUAL' |
|
NAME |
|
macos-hidpi-tool.command |
|
|
|
PURPOSE |
|
This tool installs and selects HiDPI modes for compatible macOS displays. |
|
It discovers display identifiers and does not contain monitor-specific defaults. |
|
|
|
REQUIREMENTS |
|
Use a natively connected display on macOS. |
|
Install Apple Command Line Tools before you run this tool. |
|
Use a connection that supports the requested refresh rate. |
|
|
|
EXAMPLES |
|
List connected displays: |
|
zsh macos-hidpi-tool.command displays |
|
|
|
List modes for display 2: |
|
zsh macos-hidpi-tool.command modes --display-id 2 |
|
|
|
Suggest HiDPI modes for display 2: |
|
zsh macos-hidpi-tool.command recommend --display-id 2 |
|
|
|
Preview a HiDPI installation without system changes: |
|
zsh macos-hidpi-tool.command install --display-id 2 --logical 1920x1080 --refresh 60 --dry-run |
|
|
|
Install a HiDPI mode: |
|
zsh macos-hidpi-tool.command install --display-id 2 --logical 1920x1080 --refresh 60 |
|
|
|
Select an installed HiDPI mode: |
|
zsh macos-hidpi-tool.command set --display-id 2 --logical 1920x1080 --refresh 60 --hidpi |
|
|
|
Select an existing LoDPI mode: |
|
zsh macos-hidpi-tool.command set --display-id 2 --logical 2560x1440 --refresh 60 --lodpi |
|
|
|
Check a mode without changing it: |
|
zsh macos-hidpi-tool.command check --display-id 2 --logical 1920x1080 --refresh 60 --hidpi |
|
|
|
Remove only the override entries installed by this tool: |
|
zsh macos-hidpi-tool.command uninstall --display-id 2 |
|
|
|
NOTES |
|
A HiDPI installation uses a framebuffer that is twice the logical size. |
|
The requested refresh rate must already exist for the display connection. |
|
Restart the Mac after installing a new override entry. |
|
The override applies to all displays with the same vendor and product IDs. |
|
DisplayLink and some virtual displays do not support this method. |
|
MANUAL |
|
|
|
set -euo pipefail |
|
|
|
script_name="${0:t}" |
|
command_name="${1:-help}" |
|
if [[ $# -gt 0 ]]; then |
|
shift |
|
fi |
|
|
|
display_id="" |
|
logical_size="" |
|
refresh_rate="" |
|
scale_kind="" |
|
dry_run=0 |
|
|
|
print_usage() { |
|
print "Usage: $script_name COMMAND [OPTIONS]" |
|
print "" |
|
print "Commands:" |
|
print " displays" |
|
print " modes --display-id ID" |
|
print " recommend --display-id ID" |
|
print " install --display-id ID --logical WIDTHxHEIGHT --refresh HZ [--dry-run]" |
|
print " set --display-id ID --logical WIDTHxHEIGHT --refresh HZ (--hidpi|--lodpi)" |
|
print " check --display-id ID --logical WIDTHxHEIGHT --refresh HZ (--hidpi|--lodpi)" |
|
print " uninstall --display-id ID" |
|
print " self-test" |
|
print "" |
|
print "Install creates a 2x HiDPI framebuffer and a login task." |
|
print "Use --dry-run to validate an installation without system changes." |
|
} |
|
|
|
fail() { |
|
print -u2 "ERROR: $1" |
|
exit "${2:-1}" |
|
} |
|
|
|
require_value() { |
|
local option="$1" |
|
local value="${2:-}" |
|
[[ -n "$value" ]] || fail "$option requires a value." 2 |
|
} |
|
|
|
while [[ $# -gt 0 ]]; do |
|
case "$1" in |
|
--display-id) |
|
require_value "$1" "${2:-}" |
|
display_id="$2" |
|
shift 2 |
|
;; |
|
--logical) |
|
require_value "$1" "${2:-}" |
|
logical_size="$2" |
|
shift 2 |
|
;; |
|
--refresh) |
|
require_value "$1" "${2:-}" |
|
refresh_rate="$2" |
|
shift 2 |
|
;; |
|
--hidpi) |
|
[[ -z "$scale_kind" ]] || fail "Specify only one scaling type." 2 |
|
scale_kind="hidpi" |
|
shift |
|
;; |
|
--lodpi) |
|
[[ -z "$scale_kind" ]] || fail "Specify only one scaling type." 2 |
|
scale_kind="lodpi" |
|
shift |
|
;; |
|
--dry-run) |
|
dry_run=1 |
|
shift |
|
;; |
|
--help|-h) |
|
print_usage |
|
exit 0 |
|
;; |
|
*) |
|
fail "Unknown option: $1" 2 |
|
;; |
|
esac |
|
done |
|
|
|
case "$command_name" in |
|
displays|self-test) |
|
[[ -z "$display_id$logical_size$refresh_rate$scale_kind" && "$dry_run" == 0 ]] || fail "$command_name does not accept these options." 2 |
|
;; |
|
modes|recommend|uninstall) |
|
[[ "$display_id" == <-> ]] || fail "$command_name requires a numeric --display-id." 2 |
|
[[ -z "$logical_size$refresh_rate$scale_kind" && "$dry_run" == 0 ]] || fail "$command_name accepts only --display-id." 2 |
|
;; |
|
install) |
|
[[ "$display_id" == <-> ]] || fail "install requires a numeric --display-id." 2 |
|
[[ -n "$logical_size" ]] || fail "install requires --logical WIDTHxHEIGHT." 2 |
|
[[ -n "$refresh_rate" ]] || fail "install requires --refresh HZ." 2 |
|
[[ -z "$scale_kind" ]] || fail "install always creates a HiDPI mode. Do not specify --hidpi or --lodpi." 2 |
|
;; |
|
set|check) |
|
[[ "$display_id" == <-> ]] || fail "$command_name requires a numeric --display-id." 2 |
|
[[ -n "$logical_size" ]] || fail "$command_name requires --logical WIDTHxHEIGHT." 2 |
|
[[ -n "$refresh_rate" ]] || fail "$command_name requires --refresh HZ." 2 |
|
[[ "$scale_kind" == "hidpi" || "$scale_kind" == "lodpi" ]] || fail "$command_name requires --hidpi or --lodpi." 2 |
|
[[ "$dry_run" == 0 ]] || fail "$command_name does not accept --dry-run." 2 |
|
;; |
|
--help|help) |
|
print_usage |
|
exit 0 |
|
;; |
|
*) |
|
print_usage |
|
fail "Unknown command: $command_name" 2 |
|
;; |
|
esac |
|
|
|
parse_dimensions() { |
|
local value="$1" |
|
[[ "$value" == <->x<-> ]] || fail "Invalid size '$value'. Use WIDTHxHEIGHT." 2 |
|
local parsed_width="${value%x*}" |
|
local parsed_height="${value#*x}" |
|
(( parsed_width >= 320 && parsed_width <= 8192 )) || fail "Width must be between 320 and 8192." 2 |
|
(( parsed_height >= 200 && parsed_height <= 8192 )) || fail "Height must be between 200 and 8192." 2 |
|
print "$parsed_width $parsed_height" |
|
} |
|
|
|
if [[ -n "$logical_size" ]]; then |
|
read -r logical_width logical_height <<< "$(parse_dimensions "$logical_size")" |
|
fi |
|
|
|
if [[ -n "$refresh_rate" ]]; then |
|
[[ "$refresh_rate" == <-> || "$refresh_rate" == <->.<-> ]] || fail "Refresh rate must be numeric." 2 |
|
(( refresh_rate > 0 && refresh_rate <= 1000 )) || fail "Refresh rate is outside the supported validation range." 2 |
|
fi |
|
|
|
if ! xcrun --find swiftc >/dev/null 2>&1; then |
|
fail "Apple Command Line Tools are required. Run 'xcode-select --install' and try again." 3 |
|
fi |
|
|
|
temporary_dir="$(mktemp -d /tmp/macos-hidpi-tool.XXXXXX)" |
|
cleanup() { |
|
if [[ "$temporary_dir" == /tmp/macos-hidpi-tool.* && -d "$temporary_dir" ]]; then |
|
rm -rf "$temporary_dir" |
|
fi |
|
} |
|
trap cleanup EXIT |
|
|
|
swift_source="$temporary_dir/main.swift" |
|
temporary_helper="$temporary_dir/hidpi-helper" |
|
|
|
cat > "$swift_source" <<'SWIFT_SOURCE' |
|
import CoreGraphics |
|
import Foundation |
|
|
|
struct ParsedArguments { |
|
let command: String |
|
var values: [String: String] = [:] |
|
|
|
func requiredValue(_ name: String) -> String { |
|
guard let value = values[name] else { |
|
fail("Missing \(name).", code: 2) |
|
} |
|
return value |
|
} |
|
|
|
func uint32Value(_ name: String) -> UInt32 { |
|
guard let result = UInt32(requiredValue(name)) else { |
|
fail("Invalid \(name).", code: 2) |
|
} |
|
return result |
|
} |
|
|
|
func optionalUInt32Value(_ name: String) -> UInt32? { |
|
guard let value = values[name] else { return nil } |
|
guard let result = UInt32(value) else { |
|
fail("Invalid \(name).", code: 2) |
|
} |
|
return result |
|
} |
|
|
|
func intValue(_ name: String) -> Int { |
|
guard let result = Int(requiredValue(name)) else { |
|
fail("Invalid \(name).", code: 2) |
|
} |
|
return result |
|
} |
|
|
|
func doubleValue(_ name: String) -> Double { |
|
guard let result = Double(requiredValue(name)) else { |
|
fail("Invalid \(name).", code: 2) |
|
} |
|
return result |
|
} |
|
} |
|
|
|
struct ModeSpec { |
|
let logicalWidth: Int |
|
let logicalHeight: Int |
|
let renderedWidth: Int |
|
let renderedHeight: Int |
|
let refresh: Double |
|
|
|
init(arguments: ParsedArguments) { |
|
logicalWidth = arguments.intValue("--logical-width") |
|
logicalHeight = arguments.intValue("--logical-height") |
|
renderedWidth = arguments.intValue("--rendered-width") |
|
renderedHeight = arguments.intValue("--rendered-height") |
|
refresh = arguments.doubleValue("--refresh") |
|
} |
|
|
|
func matches(_ mode: CGDisplayMode) -> Bool { |
|
mode.width == logicalWidth && |
|
mode.height == logicalHeight && |
|
mode.pixelWidth == renderedWidth && |
|
mode.pixelHeight == renderedHeight && |
|
abs(mode.refreshRate - refresh) <= 0.05 |
|
} |
|
} |
|
|
|
func fail(_ message: String, code: Int32 = 1) -> Never { |
|
FileHandle.standardError.write(Data(("ERROR: \(message)\n").utf8)) |
|
exit(code) |
|
} |
|
|
|
func parseArguments() -> ParsedArguments { |
|
guard CommandLine.arguments.count >= 2 else { |
|
fail("The helper requires a command.", code: 2) |
|
} |
|
var parsed = ParsedArguments(command: CommandLine.arguments[1]) |
|
var index = 2 |
|
while index < CommandLine.arguments.count { |
|
let argument = CommandLine.arguments[index] |
|
if argument.hasPrefix("--") { |
|
guard index + 1 < CommandLine.arguments.count, |
|
!CommandLine.arguments[index + 1].hasPrefix("--") else { |
|
fail("Missing value for \(argument).", code: 2) |
|
} |
|
parsed.values[argument] = CommandLine.arguments[index + 1] |
|
index += 2 |
|
} else { |
|
fail("Unexpected helper argument: \(argument)", code: 2) |
|
} |
|
} |
|
return parsed |
|
} |
|
|
|
func onlineDisplayIDs() -> [CGDirectDisplayID] { |
|
var ids = [CGDirectDisplayID](repeating: 0, count: 64) |
|
var count: UInt32 = 0 |
|
let result = CGGetOnlineDisplayList(UInt32(ids.count), &ids, &count) |
|
guard result == .success else { |
|
fail("Could not enumerate displays (CoreGraphics error \(result.rawValue)).") |
|
} |
|
return Array(ids.prefix(Int(count))) |
|
} |
|
|
|
func displayByID(_ id: UInt32) -> CGDirectDisplayID { |
|
guard onlineDisplayIDs().contains(id) else { |
|
fail("Display ID \(id) is not online.", code: 10) |
|
} |
|
return id |
|
} |
|
|
|
func displayByIdentity(vendor: UInt32, product: UInt32, serial: UInt32?) -> CGDirectDisplayID { |
|
let matches = onlineDisplayIDs().filter { |
|
CGDisplayVendorNumber($0) == vendor && CGDisplayModelNumber($0) == product |
|
} |
|
guard !matches.isEmpty else { |
|
fail("No online display matches vendor \(vendor) and product \(product).", code: 10) |
|
} |
|
if let serial, serial != 0 { |
|
let exact = matches.filter { CGDisplaySerialNumber($0) == serial } |
|
guard exact.count == 1 else { |
|
fail("The saved display identity is missing or ambiguous. Connect only the intended display and reinstall.", code: 10) |
|
} |
|
return exact[0] |
|
} |
|
guard matches.count == 1 else { |
|
fail("Multiple online displays share this vendor/product identity and have no unique serial number.", code: 10) |
|
} |
|
return matches[0] |
|
} |
|
|
|
func validateInstallTarget(_ displayID: CGDirectDisplayID) { |
|
let vendor = CGDisplayVendorNumber(displayID) |
|
let product = CGDisplayModelNumber(displayID) |
|
guard vendor != 0, product != 0 else { |
|
fail("This display does not expose usable vendor/product identifiers.", code: 10) |
|
} |
|
guard CGDisplayIsInMirrorSet(displayID) == 0 else { |
|
fail("Mirrored displays are not supported. Disable mirroring and try again.", code: 10) |
|
} |
|
} |
|
|
|
func allModes(_ displayID: CGDirectDisplayID) -> [CGDisplayMode] { |
|
let options = [kCGDisplayShowDuplicateLowResolutionModes: true] as CFDictionary |
|
return (CGDisplayCopyAllDisplayModes(displayID, options) as? [CGDisplayMode]) ?? [] |
|
} |
|
|
|
func scalingKind(_ mode: CGDisplayMode) -> String { |
|
if mode.pixelWidth == mode.width && mode.pixelHeight == mode.height { |
|
return "LoDPI" |
|
} |
|
if mode.pixelWidth == mode.width * 2 && mode.pixelHeight == mode.height * 2 { |
|
return "HiDPI-2x" |
|
} |
|
return "HiDPI-scaled" |
|
} |
|
|
|
func describe(_ mode: CGDisplayMode) -> String { |
|
let rate = String(format: "%.2f", mode.refreshRate) |
|
return "logical=\(mode.width)x\(mode.height) rendered=\(mode.pixelWidth)x\(mode.pixelHeight) refresh=\(rate) scaling=\(scalingKind(mode)) mode-id=\(mode.ioDisplayModeID)" |
|
} |
|
|
|
func nativePanelMode(_ displayID: CGDirectDisplayID) -> CGDisplayMode? { |
|
let oneToOneModes = allModes(displayID).filter { |
|
$0.width == $0.pixelWidth && $0.height == $0.pixelHeight |
|
} |
|
return oneToOneModes.max { |
|
($0.pixelWidth * $0.pixelHeight, $0.refreshRate) < |
|
($1.pixelWidth * $1.pixelHeight, $1.refreshRate) |
|
} |
|
} |
|
|
|
func roundedEvenHeight(width: Int, nativeWidth: Int, nativeHeight: Int) -> Int { |
|
var height = Int((Double(width) * Double(nativeHeight) / Double(nativeWidth)).rounded()) |
|
if height % 2 != 0 { |
|
height += 1 |
|
} |
|
return height |
|
} |
|
|
|
func recommendationRange(ppi: Double?) -> ClosedRange<Double>? { |
|
guard let ppi else { return nil } |
|
if ppi < 100 { return nil } |
|
if ppi < 130 { return 0.58...0.66 } |
|
if ppi < 170 { return 0.60...0.72 } |
|
return 0.50...0.70 |
|
} |
|
|
|
func printRecommendations(_ displayID: CGDirectDisplayID) { |
|
guard let native = nativePanelMode(displayID) else { |
|
fail("Could not determine a native one-to-one display mode.", code: 15) |
|
} |
|
|
|
let nativeWidth = native.pixelWidth |
|
let nativeHeight = native.pixelHeight |
|
let millimeters = CGDisplayScreenSize(displayID) |
|
let diagonalMillimeters = hypot(Double(millimeters.width), Double(millimeters.height)) |
|
let diagonalInches = diagonalMillimeters > 0 ? diagonalMillimeters / 25.4 : 0 |
|
let diagonalPixels = hypot(Double(nativeWidth), Double(nativeHeight)) |
|
let ppi: Double? = diagonalInches > 0 ? diagonalPixels / diagonalInches : nil |
|
|
|
print("Display ID: \(displayID)") |
|
print("Vendor/product: 0x\(String(CGDisplayVendorNumber(displayID), radix: 16)) / 0x\(String(CGDisplayModelNumber(displayID), radix: 16))") |
|
print("Native panel: \(nativeWidth)x\(nativeHeight)") |
|
if let ppi { |
|
let diagonalText = String(format: "%.1f", diagonalInches) |
|
let ppiText = String(format: "%.1f", ppi) |
|
print("Physical size: \(Int(millimeters.width.rounded()))x\(Int(millimeters.height.rounded())) mm (\(diagonalText) inches)") |
|
print("Density: \(ppiText) PPI") |
|
if ppi < 100 { |
|
print("Assessment: LoDPI usually gives the best workspace. Use HiDPI mainly for a larger interface.") |
|
} else if ppi < 130 { |
|
print("Assessment: HiDPI is optional. It can improve text through supersampling.") |
|
} else if ppi < 170 { |
|
print("Assessment: HiDPI usually improves text and interface clarity.") |
|
} else { |
|
print("Assessment: HiDPI is strongly recommended for this pixel density.") |
|
} |
|
} else { |
|
print("Density: unavailable because the display does not report its physical size.") |
|
} |
|
|
|
let exactHalfWidth = nativeWidth / 2 |
|
let maximumWidth = Int((Double(nativeWidth) * 0.80).rounded(.down)) |
|
let commonWidths = [1024, 1152, 1280, 1344, 1440, 1536, 1600, 1680, 1728, 1920, 2048, 2176, 2304, 2560, 2752, 3008, 3200, 3360, 3840] |
|
var candidateWidths = [exactHalfWidth] |
|
for width in commonWidths where width > exactHalfWidth && width <= maximumWidth { |
|
if !candidateWidths.contains(where: { abs($0 - width) < 32 }) { |
|
candidateWidths.append(width) |
|
} |
|
} |
|
candidateWidths.sort() |
|
|
|
let preferredRange = recommendationRange(ppi: ppi) |
|
let modes = allModes(displayID) |
|
print("Suggestions:") |
|
for width in candidateWidths { |
|
let height = roundedEvenHeight(width: width, nativeWidth: nativeWidth, nativeHeight: nativeHeight) |
|
let framebufferWidth = width * 2 |
|
let framebufferHeight = height * 2 |
|
let widthRatio = Double(width) / Double(nativeWidth) |
|
let interfaceScale = Double(nativeWidth) / Double(width) |
|
let matchingModes = modes.filter { |
|
$0.width == width && |
|
$0.height == height && |
|
$0.pixelWidth == framebufferWidth && |
|
$0.pixelHeight == framebufferHeight |
|
} |
|
let rates = Set(matchingModes.map { Int($0.refreshRate.rounded()) }).sorted() |
|
let availability = rates.isEmpty ? "not installed" : rates.map(String.init).joined(separator: ",") + " Hz" |
|
var labels: [String] = [] |
|
if framebufferWidth == nativeWidth && framebufferHeight == nativeHeight { |
|
labels.append("exact 2x") |
|
} |
|
if let preferredRange, preferredRange.contains(widthRatio) { |
|
labels.append("recommended") |
|
} else if ppi != nil && ppi! < 100 && framebufferWidth == nativeWidth { |
|
labels.append("accessibility") |
|
} |
|
let labelText = labels.isEmpty ? "" : " [\(labels.joined(separator: ", "))]" |
|
let interfaceScaleText = String(format: "%.2f", interfaceScale) |
|
print(" \(width)x\(height) -> \(framebufferWidth)x\(framebufferHeight), UI scale \(interfaceScaleText)x, \(availability)\(labelText)") |
|
} |
|
print("Note: PPI is a guide. Viewing distance, GPU limits, and interface preference also matter.") |
|
} |
|
|
|
func targetMode(displayID: CGDirectDisplayID, spec: ModeSpec) -> CGDisplayMode? { |
|
let matches = allModes(displayID).filter(spec.matches) |
|
var unique: [Int32: CGDisplayMode] = [:] |
|
for mode in matches { |
|
unique[mode.ioDisplayModeID] = mode |
|
} |
|
guard unique.count <= 1 else { |
|
let ids = unique.keys.sorted().map(String.init).joined(separator: ", ") |
|
fail("The requested properties match multiple display modes (mode IDs: \(ids)). Refusing an ambiguous selection.", code: 14) |
|
} |
|
return unique.values.first |
|
} |
|
|
|
func refreshIsAvailable(displayID: CGDirectDisplayID, refresh: Double) -> Bool { |
|
allModes(displayID).contains { abs($0.refreshRate - refresh) <= 0.05 } |
|
} |
|
|
|
func encodedResolution(width: Int, height: Int) -> Data { |
|
guard width > 0, height > 0, width <= Int(UInt32.max), height <= Int(UInt32.max) else { |
|
fail("Framebuffer dimensions are outside the supported range.", code: 2) |
|
} |
|
var widthBE = UInt32(width).bigEndian |
|
var heightBE = UInt32(height).bigEndian |
|
var data = Data(bytes: &widthBE, count: MemoryLayout<UInt32>.size) |
|
data.append(Data(bytes: &heightBE, count: MemoryLayout<UInt32>.size)) |
|
return data |
|
} |
|
|
|
func loadPlist(path: String?) -> [String: Any] { |
|
guard let path, FileManager.default.fileExists(atPath: path) else { |
|
return [:] |
|
} |
|
do { |
|
let data = try Data(contentsOf: URL(fileURLWithPath: path)) |
|
let object = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) |
|
guard let dictionary = object as? [String: Any] else { |
|
fail("The existing override is not a property-list dictionary.") |
|
} |
|
return dictionary |
|
} catch { |
|
fail("Could not read the existing override: \(error)") |
|
} |
|
} |
|
|
|
func resolutions(in plist: [String: Any]) -> [Data] { |
|
guard let value = plist["scale-resolutions"] else { return [] } |
|
guard let result = value as? [Data] else { |
|
fail("The existing scale-resolutions value is not an array of data values.") |
|
} |
|
return result |
|
} |
|
|
|
func writePlist(_ plist: [String: Any], output: String) { |
|
do { |
|
let data = try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0) |
|
try data.write(to: URL(fileURLWithPath: output), options: .atomic) |
|
} catch { |
|
fail("Could not write the generated override: \(error)") |
|
} |
|
} |
|
|
|
func writeMergedOverride( |
|
input: String?, |
|
output: String, |
|
vendor: UInt32, |
|
product: UInt32, |
|
framebufferWidth: Int, |
|
framebufferHeight: Int |
|
) { |
|
var plist = loadPlist(path: input) |
|
var resolutionValues = resolutions(in: plist) |
|
let newResolution = encodedResolution(width: framebufferWidth, height: framebufferHeight) |
|
if !resolutionValues.contains(newResolution) { |
|
resolutionValues.append(newResolution) |
|
} |
|
plist["DisplayVendorID"] = Int(vendor) |
|
plist["DisplayProductID"] = Int(product) |
|
plist["scale-resolutions"] = resolutionValues |
|
writePlist(plist, output: output) |
|
} |
|
|
|
func overrideContains(path: String, framebufferWidth: Int, framebufferHeight: Int) -> Bool { |
|
let plist = loadPlist(path: path) |
|
let target = encodedResolution(width: framebufferWidth, height: framebufferHeight) |
|
return resolutions(in: plist).contains(target) |
|
} |
|
|
|
func writeOverrideRemovingResolution( |
|
input: String, |
|
output: String, |
|
framebufferWidth: Int, |
|
framebufferHeight: Int |
|
) { |
|
var plist = loadPlist(path: input) |
|
let target = encodedResolution(width: framebufferWidth, height: framebufferHeight) |
|
plist["scale-resolutions"] = resolutions(in: plist).filter { $0 != target } |
|
writePlist(plist, output: output) |
|
} |
|
|
|
let args = parseArguments() |
|
|
|
switch args.command { |
|
case "displays": |
|
for id in onlineDisplayIDs() { |
|
guard let current = CGDisplayCopyDisplayMode(id) else { continue } |
|
let main = CGDisplayIsMain(id) != 0 ? "yes" : "no" |
|
let builtin = CGDisplayIsBuiltin(id) != 0 ? "yes" : "no" |
|
let mirrored = CGDisplayIsInMirrorSet(id) != 0 ? "yes" : "no" |
|
print("id=\(id) vendor=0x\(String(CGDisplayVendorNumber(id), radix: 16)) product=0x\(String(CGDisplayModelNumber(id), radix: 16)) serial=\(CGDisplaySerialNumber(id)) main=\(main) builtin=\(builtin) mirrored=\(mirrored) \(describe(current))") |
|
} |
|
|
|
case "identify": |
|
let id = args.uint32Value("--display-id") |
|
let displayID = displayByID(id) |
|
validateInstallTarget(displayID) |
|
print("\(CGDisplayVendorNumber(displayID)) \(CGDisplayModelNumber(displayID)) \(CGDisplaySerialNumber(displayID))") |
|
|
|
case "modes": |
|
let id = args.uint32Value("--display-id") |
|
let displayID = displayByID(id) |
|
let currentID = CGDisplayCopyDisplayMode(displayID)?.ioDisplayModeID |
|
let sorted = allModes(displayID).sorted { |
|
($0.width, $0.height, $0.pixelWidth, $0.pixelHeight, $0.refreshRate) < |
|
($1.width, $1.height, $1.pixelWidth, $1.pixelHeight, $1.refreshRate) |
|
} |
|
var seen = Set<String>() |
|
for mode in sorted { |
|
let key = "\(mode.width)x\(mode.height):\(mode.pixelWidth)x\(mode.pixelHeight):\(String(format: "%.2f", mode.refreshRate))" |
|
guard seen.insert(key).inserted else { continue } |
|
let marker = mode.ioDisplayModeID == currentID ? "current=yes" : "current=no" |
|
print("\(marker) \(describe(mode))") |
|
} |
|
|
|
case "recommend": |
|
let id = args.uint32Value("--display-id") |
|
printRecommendations(displayByID(id)) |
|
|
|
case "supports-refresh": |
|
let id = args.uint32Value("--display-id") |
|
let refresh = args.doubleValue("--refresh") |
|
exit(refreshIsAvailable(displayID: displayByID(id), refresh: refresh) ? 0 : 12) |
|
|
|
case "check", "set": |
|
let spec = ModeSpec(arguments: args) |
|
|
|
let displayID: CGDirectDisplayID |
|
if let idText = args.values["--display-id"] { |
|
guard let id = UInt32(idText) else { |
|
fail("Invalid --display-id.", code: 2) |
|
} |
|
displayID = displayByID(id) |
|
} else { |
|
let vendor = args.uint32Value("--vendor") |
|
let product = args.uint32Value("--product") |
|
let serial = args.optionalUInt32Value("--serial") |
|
displayID = displayByIdentity(vendor: vendor, product: product, serial: serial) |
|
} |
|
|
|
guard let mode = targetMode(displayID: displayID, spec: spec) else { |
|
if let current = CGDisplayCopyDisplayMode(displayID) { |
|
print("Current: \(describe(current))") |
|
} |
|
fail("The exact requested mode is not available.", code: 11) |
|
} |
|
|
|
guard let current = CGDisplayCopyDisplayMode(displayID) else { |
|
fail("Could not read the current display mode.") |
|
} |
|
let isCurrent = spec.matches(current) |
|
|
|
if args.command == "check" { |
|
print("AVAILABLE: \(describe(mode))") |
|
print("Current: \(describe(current))") |
|
exit(0) |
|
} |
|
if isCurrent { |
|
print("Already active: \(describe(current))") |
|
exit(0) |
|
} |
|
|
|
var configuration: CGDisplayConfigRef? |
|
let beginResult = CGBeginDisplayConfiguration(&configuration) |
|
guard beginResult == .success, let configuration else { |
|
fail("Could not begin display configuration (CoreGraphics error \(beginResult.rawValue)).") |
|
} |
|
let configureResult = CGConfigureDisplayWithDisplayMode(configuration, displayID, mode, nil) |
|
guard configureResult == .success else { |
|
CGCancelDisplayConfiguration(configuration) |
|
fail("macOS refused the mode (CoreGraphics error \(configureResult.rawValue)).") |
|
} |
|
let commitResult = CGCompleteDisplayConfiguration(configuration, .permanently) |
|
guard commitResult == .success else { |
|
fail("macOS could not commit the mode (CoreGraphics error \(commitResult.rawValue)).") |
|
} |
|
guard let applied = CGDisplayCopyDisplayMode(displayID) else { |
|
fail("The mode changed but could not be verified.") |
|
} |
|
guard spec.matches(applied) else { |
|
fail("macOS committed a different mode: \(describe(applied))") |
|
} |
|
print("Applied: \(describe(applied))") |
|
|
|
case "make-override": |
|
let vendor = args.uint32Value("--vendor") |
|
let product = args.uint32Value("--product") |
|
let width = args.intValue("--rendered-width") |
|
let height = args.intValue("--rendered-height") |
|
let output = args.requiredValue("--output") |
|
writeMergedOverride( |
|
input: args.values["--input"], |
|
output: output, |
|
vendor: vendor, |
|
product: product, |
|
framebufferWidth: width, |
|
framebufferHeight: height |
|
) |
|
|
|
case "override-contains": |
|
let input = args.requiredValue("--input") |
|
let width = args.intValue("--rendered-width") |
|
let height = args.intValue("--rendered-height") |
|
exit(overrideContains(path: input, framebufferWidth: width, framebufferHeight: height) ? 0 : 13) |
|
|
|
case "remove-override-resolution": |
|
let input = args.requiredValue("--input") |
|
let output = args.requiredValue("--output") |
|
let width = args.intValue("--rendered-width") |
|
let height = args.intValue("--rendered-height") |
|
writeOverrideRemovingResolution( |
|
input: input, |
|
output: output, |
|
framebufferWidth: width, |
|
framebufferHeight: height |
|
) |
|
|
|
case "self-test": |
|
let data = encodedResolution(width: 4000, height: 2400) |
|
guard data.base64EncodedString() == "AAAPoAAACWA=" else { |
|
fail("Resolution encoding self-test failed.") |
|
} |
|
print("SELF-TEST PASSED") |
|
|
|
default: |
|
fail("Unknown helper command: \(args.command)", code: 2) |
|
} |
|
SWIFT_SOURCE |
|
|
|
print "Building native helper..." >&2 |
|
xcrun swiftc -O -framework CoreGraphics "$swift_source" -o "$temporary_helper" |
|
|
|
helper=("$temporary_helper") |
|
|
|
if [[ "$command_name" == "displays" ]]; then |
|
"${helper[@]}" displays |
|
exit 0 |
|
fi |
|
|
|
if [[ "$command_name" == "modes" ]]; then |
|
"${helper[@]}" modes --display-id "$display_id" |
|
exit 0 |
|
fi |
|
|
|
if [[ "$command_name" == "recommend" ]]; then |
|
"${helper[@]}" recommend --display-id "$display_id" |
|
exit 0 |
|
fi |
|
|
|
if [[ "$command_name" == "self-test" ]]; then |
|
"${helper[@]}" self-test |
|
generated_test_override="$temporary_dir/self-test.plist" |
|
"${helper[@]}" make-override --vendor 1 --product 2 --rendered-width 4000 --rendered-height 2400 --output "$generated_test_override" |
|
plutil -lint "$generated_test_override" |
|
encoded_value="$(plutil -extract scale-resolutions.0 raw -o - "$generated_test_override")" |
|
[[ "$encoded_value" == "AAAPoAAACWA=" ]] || fail "Generated override self-test failed." |
|
|
|
merged_test_override="$temporary_dir/self-test-merged.plist" |
|
"${helper[@]}" make-override --input "$generated_test_override" --vendor 1 --product 2 --rendered-width 3840 --rendered-height 2160 --output "$merged_test_override" |
|
"${helper[@]}" override-contains --input "$merged_test_override" --rendered-width 4000 --rendered-height 2400 |
|
"${helper[@]}" override-contains --input "$merged_test_override" --rendered-width 3840 --rendered-height 2160 |
|
|
|
removed_test_override="$temporary_dir/self-test-removed.plist" |
|
"${helper[@]}" remove-override-resolution --input "$merged_test_override" --output "$removed_test_override" --rendered-width 4000 --rendered-height 2400 |
|
if "${helper[@]}" override-contains --input "$removed_test_override" --rendered-width 4000 --rendered-height 2400; then |
|
fail "Semantic removal self-test failed." |
|
fi |
|
"${helper[@]}" override-contains --input "$removed_test_override" --rendered-width 3840 --rendered-height 2160 |
|
plutil -lint "$removed_test_override" |
|
print "OVERRIDE SELF-TEST PASSED" |
|
exit 0 |
|
fi |
|
|
|
if [[ "$command_name" == "set" || "$command_name" == "check" ]]; then |
|
if [[ "$scale_kind" == "hidpi" ]]; then |
|
rendered_width=$(( logical_width * 2 )) |
|
rendered_height=$(( logical_height * 2 )) |
|
else |
|
rendered_width="$logical_width" |
|
rendered_height="$logical_height" |
|
fi |
|
target_mode_args=( |
|
--display-id "$display_id" |
|
--logical-width "$logical_width" |
|
--logical-height "$logical_height" |
|
--rendered-width "$rendered_width" |
|
--rendered-height "$rendered_height" |
|
--refresh "$refresh_rate" |
|
) |
|
"${helper[@]}" "$command_name" "${target_mode_args[@]}" |
|
exit 0 |
|
fi |
|
|
|
identity_output="$("${helper[@]}" identify --display-id "$display_id")" || exit $? |
|
read -r vendor_id product_id serial_id <<< "$identity_output" |
|
[[ "$vendor_id" == <-> && "$product_id" == <-> && "$serial_id" == <-> ]] || fail "The helper returned an invalid display identity." |
|
vendor_hex="$(printf '%x' "$vendor_id")" |
|
product_hex="$(printf '%x' "$product_id")" |
|
device_key="${vendor_hex}-${product_hex}" |
|
bundle_id="local.macos-hidpi-tool.${device_key}" |
|
user_home="${HOME:?HOME is not set}" |
|
user_id="$(id -u)" |
|
support_root="$user_home/Library/Application Support/macOS-HiDPI-Tool" |
|
device_support="$support_root/$device_key" |
|
installed_helper="$support_root/hidpi-helper" |
|
launch_agent="$user_home/Library/LaunchAgents/$bundle_id.plist" |
|
override_dir="/Library/Displays/Contents/Resources/Overrides/DisplayVendorID-$vendor_hex" |
|
override_file="$override_dir/DisplayProductID-$product_hex" |
|
owned_resolutions="$device_support/owned-resolutions" |
|
timestamp="$(date +%Y%m%d-%H%M%S)" |
|
|
|
if [[ "$command_name" == "uninstall" ]]; then |
|
print "Display ID: $display_id" |
|
print "Vendor/product: 0x$vendor_hex / 0x$product_hex" |
|
launchctl bootout "gui/$user_id" "$launch_agent" 2>/dev/null || true |
|
|
|
if [[ -f "$launch_agent" ]]; then |
|
mkdir -p "$device_support/disabled" |
|
mv "$launch_agent" "$device_support/disabled/$bundle_id.plist.$timestamp" |
|
fi |
|
|
|
if [[ -f "$owned_resolutions" && -e "$override_file" ]]; then |
|
[[ ! -L "$override_file" ]] || fail "Refusing to edit a symlinked display override: $override_file" |
|
[[ -r "$override_file" ]] || fail "The current override is not readable: $override_file" |
|
|
|
working_override="$override_file" |
|
removal_index=0 |
|
while read -r owned_width owned_height extra; do |
|
[[ -z "${owned_width:-}" ]] && continue |
|
[[ "$owned_width" == <-> && "$owned_height" == <-> && -z "${extra:-}" ]] || fail "The ownership manifest is invalid: $owned_resolutions" |
|
removal_index=$(( removal_index + 1 )) |
|
next_override="$temporary_dir/uninstall-$removal_index.plist" |
|
"${helper[@]}" remove-override-resolution \ |
|
--input "$working_override" \ |
|
--output "$next_override" \ |
|
--rendered-width "$owned_width" \ |
|
--rendered-height "$owned_height" |
|
working_override="$next_override" |
|
done < "$owned_resolutions" |
|
(( removal_index > 0 )) || fail "The ownership manifest is empty: $owned_resolutions" |
|
plutil -lint "$working_override" |
|
|
|
original_hash="$(shasum -a 256 "$override_file" | awk '{print $1}')" |
|
print "macOS will request your administrator password." |
|
sudo -v |
|
current_hash="$(sudo shasum -a 256 "$override_file" | awk '{print $1}')" |
|
[[ "$current_hash" == "$original_hash" ]] || fail "The override changed during uninstall. No system file was replaced." |
|
sudo install -m 0644 "$working_override" "$override_file" |
|
|
|
mkdir -p "$device_support/disabled" |
|
mv "$owned_resolutions" "$device_support/disabled/owned-resolutions.$timestamp" |
|
print "Removed the override entries installed by this tool." |
|
elif [[ -f "$owned_resolutions" ]]; then |
|
mkdir -p "$device_support/disabled" |
|
mv "$owned_resolutions" "$device_support/disabled/owned-resolutions.$timestamp" |
|
print "The managed override no longer exists; ownership metadata was archived." |
|
else |
|
print "No owned resolution entries were found. The system override was not changed." |
|
fi |
|
print "Restart the Mac to reload the display configuration." |
|
exit 0 |
|
fi |
|
|
|
rendered_width=$(( logical_width * 2 )) |
|
rendered_height=$(( logical_height * 2 )) |
|
(( rendered_width <= 16384 && rendered_height <= 16384 )) || fail "The 2x framebuffer exceeds 16384 pixels." 2 |
|
|
|
if ! "${helper[@]}" supports-refresh --display-id "$display_id" --refresh "$refresh_rate"; then |
|
fail "Refresh rate $refresh_rate Hz is not exposed for this display connection." 12 |
|
fi |
|
|
|
generated_override="$temporary_dir/generated-override" |
|
existing_override="" |
|
existing_hash="" |
|
[[ ! -L "$override_file" ]] || fail "Refusing to edit a symlinked display override: $override_file" |
|
if [[ -e "$override_file" && ! -r "$override_file" ]]; then |
|
fail "The existing override is not readable: $override_file" |
|
fi |
|
if [[ -r "$override_file" ]]; then |
|
existing_override="$override_file" |
|
existing_hash="$(shasum -a 256 "$override_file" | awk '{print $1}')" |
|
fi |
|
|
|
make_override_args=( |
|
make-override |
|
--vendor "$vendor_id" |
|
--product "$product_id" |
|
--rendered-width "$rendered_width" |
|
--rendered-height "$rendered_height" |
|
--output "$generated_override" |
|
) |
|
if [[ -n "$existing_override" ]]; then |
|
make_override_args+=(--input "$existing_override") |
|
fi |
|
"${helper[@]}" "${make_override_args[@]}" |
|
plutil -lint "$generated_override" |
|
|
|
entry_preexisted=0 |
|
if [[ -n "$existing_override" ]] && "${helper[@]}" override-contains \ |
|
--input "$existing_override" \ |
|
--rendered-width "$rendered_width" \ |
|
--rendered-height "$rendered_height"; then |
|
entry_preexisted=1 |
|
fi |
|
|
|
print "Display ID: $display_id" |
|
print "Vendor/product/serial: 0x$vendor_hex / 0x$product_hex / $serial_id" |
|
print "Target logical size: ${logical_width}x${logical_height}" |
|
print "Target framebuffer: ${rendered_width}x${rendered_height}" |
|
print "Target refresh rate: $refresh_rate Hz" |
|
print "Override path: $override_file" |
|
print "The override applies to every display with this vendor/product pair." |
|
if [[ "$entry_preexisted" == 1 ]]; then |
|
print "The requested framebuffer entry already exists; this tool will not claim ownership of it." |
|
else |
|
print "The requested framebuffer entry will be added and tracked for safe uninstall." |
|
fi |
|
|
|
if [[ "$dry_run" == 1 ]]; then |
|
print "DRY RUN: validation passed. No files or settings were changed." |
|
exit 0 |
|
fi |
|
|
|
mkdir -p "$support_root" "$device_support" "$device_support/snapshots" "$user_home/Library/LaunchAgents" |
|
install -m 0755 "$temporary_helper" "$installed_helper" |
|
|
|
print "macOS will request your administrator password." |
|
sudo -v |
|
sudo install -d -m 0755 "$override_dir" |
|
|
|
if [[ -n "$existing_hash" ]]; then |
|
sudo test -f "$override_file" || fail "The override disappeared during installation. No system file was replaced." |
|
current_hash="$(sudo shasum -a 256 "$override_file" | awk '{print $1}')" |
|
[[ "$current_hash" == "$existing_hash" ]] || fail "The override changed during installation. No system file was replaced." |
|
cp -p "$override_file" "$device_support/snapshots/before-install-$timestamp.plist" |
|
else |
|
sudo test ! -e "$override_file" || fail "An override appeared during installation. No system file was replaced." |
|
fi |
|
|
|
sudo install -m 0644 "$generated_override" "$override_file" |
|
|
|
owned_entry="$rendered_width $rendered_height" |
|
if [[ "$entry_preexisted" == 0 ]] && { [[ ! -f "$owned_resolutions" ]] || ! grep -Fqx "$owned_entry" "$owned_resolutions"; }; then |
|
print -r -- "$owned_entry" >> "$owned_resolutions" |
|
fi |
|
|
|
agent_source="$temporary_dir/$bundle_id.plist" |
|
cat > "$agent_source" <<AGENT_PLIST |
|
<?xml version="1.0" encoding="UTF-8"?> |
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|
<plist version="1.0"> |
|
<dict> |
|
<key>Label</key> |
|
<string>$bundle_id</string> |
|
<key>ProgramArguments</key> |
|
<array> |
|
<string>$installed_helper</string> |
|
<string>set</string> |
|
<string>--vendor</string> |
|
<string>$vendor_id</string> |
|
<string>--product</string> |
|
<string>$product_id</string> |
|
<string>--serial</string> |
|
<string>$serial_id</string> |
|
<string>--logical-width</string> |
|
<string>$logical_width</string> |
|
<string>--logical-height</string> |
|
<string>$logical_height</string> |
|
<string>--rendered-width</string> |
|
<string>$rendered_width</string> |
|
<string>--rendered-height</string> |
|
<string>$rendered_height</string> |
|
<string>--refresh</string> |
|
<string>$refresh_rate</string> |
|
</array> |
|
<key>RunAtLoad</key> |
|
<true/> |
|
<key>StandardOutPath</key> |
|
<string>$device_support/apply.log</string> |
|
<key>StandardErrorPath</key> |
|
<string>$device_support/apply-error.log</string> |
|
</dict> |
|
</plist> |
|
AGENT_PLIST |
|
|
|
plutil -lint "$agent_source" |
|
install -m 0644 "$agent_source" "$launch_agent" |
|
launchctl bootout "gui/$user_id" "$launch_agent" 2>/dev/null || true |
|
launchctl bootstrap "gui/$user_id" "$launch_agent" 2>/dev/null || true |
|
launchctl kickstart -k "gui/$user_id/$bundle_id" 2>/dev/null || true |
|
|
|
target_mode_args=( |
|
--vendor "$vendor_id" |
|
--product "$product_id" |
|
--serial "$serial_id" |
|
--logical-width "$logical_width" |
|
--logical-height "$logical_height" |
|
--rendered-width "$rendered_width" |
|
--rendered-height "$rendered_height" |
|
--refresh "$refresh_rate" |
|
) |
|
|
|
if "$installed_helper" check "${target_mode_args[@]}" >/dev/null 2>&1; then |
|
"$installed_helper" set "${target_mode_args[@]}" |
|
print "Installation complete. The target mode is active." |
|
else |
|
print "Installation complete. Restart the Mac to load the new HiDPI mode." |
|
print "The login task will apply the target mode after restart." |
|
fi |