Skip to content
Merged
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
6 changes: 4 additions & 2 deletions Crisp/Models/DisplayMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,10 @@ struct DisplayMode: Identifiable, Equatable {
// clean scaled resolutions (1600x900, 2048x1152, full 2560x1440 refresh set, ...) that
// CGS carries without any override, so we can offer and apply them directly like BetterDisplay.
let knownIDs = Set(modes.map { $0.id })
let nativeAR = rawModes.max(by: { $0.pixelWidth * $0.pixelHeight < $1.pixelWidth * $1.pixelHeight })
.map { Double($0.pixelWidth) / Double($0.pixelHeight) } ?? 0
let nativeAR = DisplayModeGeometry.nativeAspect(from: rawModes.map {
DisplayModeGeometry(width: $0.width, height: $0.height,
pixelWidth: $0.pixelWidth, pixelHeight: $0.pixelHeight)
})
modes += cgsHiddenHiDPIModes(for: displayID, excludingIDs: knownIDs,
maxPixelWidth: maxPixelWidth, nativeAspect: nativeAR)

Expand Down
27 changes: 27 additions & 0 deletions Crisp/Models/DisplayModeGeometry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
struct DisplayModeGeometry: Equatable {
let width: Int
let height: Int
let pixelWidth: Int
let pixelHeight: Int

static func nativeAspect(from modes: [DisplayModeGeometry]) -> Double {
let unscaled = modes.filter {
$0.pixelWidth == $0.width && $0.pixelHeight == $0.height
}
let candidates = unscaled.isEmpty ? modes : unscaled
guard let largest = candidates.max(by: {
$0.pixelWidth * $0.pixelHeight < $1.pixelWidth * $1.pixelHeight
}), largest.height > 0 else { return 0 }
return Double(largest.width) / Double(largest.height)
}

static func isResolutionMenuEligible(width: Int, height: Int) -> Bool {
min(width, height) >= 720 && max(width, height) >= 1280
}

static func hasSameOrientation(width: Int, height: Int,
as referenceWidth: Int, _ referenceHeight: Int) -> Bool {
if width == height || referenceWidth == referenceHeight { return true }
return (width > height) == (referenceWidth > referenceHeight)
}
}
8 changes: 7 additions & 1 deletion Crisp/Views/DisplayModeListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ struct DisplayModeSection: View {
let (nativeW, nativeH) = display.nativeResolution

let base = display.availableModes.filter {
$0.width >= 1280 && $0.height >= 720
DisplayModeGeometry.isResolutionMenuEligible(width: $0.width, height: $0.height)
&& DisplayModeGeometry.hasSameOrientation(
width: $0.width, height: $0.height, as: nativeW, nativeH
)
}

var grouped: [String: [DisplayMode]] = [:]
Expand Down Expand Up @@ -381,6 +384,9 @@ struct DisplayModeSection: View {
var seen = Set<String>()
return display.availableModes
.filter {
guard DisplayModeGeometry.hasSameOrientation(
width: $0.width, height: $0.height, as: nativeW, nativeH
) else { return false }
if hasNativeDefault, $0.isHiDPI, $0.width == nativeW, $0.height == nativeH { return false }
return ($0.isHiDPI && $0.width >= minWidth) || ($0.width == nativeW && $0.height == nativeH)
}
Expand Down
41 changes: 41 additions & 0 deletions CrispTests/DisplayModeGeometryTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import XCTest

final class DisplayModeGeometryTests: XCTestCase {
func testPortraitNativeAspectIgnoresLargerLandscapeHiDPIBacking() {
let modes = [
DisplayModeGeometry(
width: 1440, height: 2560,
pixelWidth: 1440, pixelHeight: 2560
),
DisplayModeGeometry(
width: 2560, height: 1440,
pixelWidth: 5120, pixelHeight: 2880
)
]

XCTAssertEqual(
DisplayModeGeometry.nativeAspect(from: modes),
1440.0 / 2560.0,
accuracy: 0.001
)
}

func testPortraitRetinaPointIsEligibleForResolutionMenu() {
XCTAssertTrue(
DisplayModeGeometry.isResolutionMenuEligible(width: 720, height: 1280)
)
}

func testPortraitMenuRejectsLandscapeModes() {
XCTAssertFalse(
DisplayModeGeometry.hasSameOrientation(
width: 1920, height: 1080, as: 1440, 2560
)
)
XCTAssertTrue(
DisplayModeGeometry.hasSameOrientation(
width: 1080, height: 1920, as: 1440, 2560
)
)
}
}
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Fast dev loop (Command Line Tools only, no Xcode):
# make dev compile, swap the binary into /Applications/Crisp.app, relaunch
# make compile compile the binary only (./Crisp-bin), no swap — quick build check
# make test generate the Xcode project and run unit tests
#
# Distributable DMG:
# make build signed universal (arm64 + x86_64) DMG via scripts/release.sh (dry run)
Expand All @@ -27,12 +28,13 @@ SWIFTC_FLAGS := -O -swift-version 5 -strict-concurrency=minimal -parse-as-librar
-Xlinker -undefined -Xlinker dynamic_lookup

.DEFAULT_GOAL := help
.PHONY: help dev compile build dmg release clean
.PHONY: help dev compile test build dmg release clean

help:
@echo "Crisp — make targets:"
@echo " make dev compile + swap into /Applications/Crisp.app + relaunch (dev.sh)"
@echo " make compile compile ./Crisp-bin only, no swap (quick build check)"
@echo " make test generate the Xcode project and run unit tests"
@echo " make build signed universal DMG, no Xcode (scripts/release.sh v$(VERSION))"
@echo " make dmg DMG via Xcode (scripts/build-dmg.sh)"
@echo " make release ARGS=\"vX.Y.Z notes.md --publish\" full release (scripts/release.sh)"
Expand All @@ -46,6 +48,12 @@ compile:
swiftc $(SWIFTC_FLAGS) $(SWIFT_SOURCES) -o Crisp-bin
@echo "Done. ./Crisp-bin built (not swapped into the app; use 'make dev' for that)."

test:
xcodegen generate
xcodebuild -quiet test -project Crisp.xcodeproj -scheme Crisp \
-destination 'platform=macOS' CODE_SIGNING_ALLOWED=NO \
SWIFT_VERSION=5 SWIFT_STRICT_CONCURRENCY=minimal

build:
./scripts/release.sh v$(VERSION)

Expand Down
24 changes: 24 additions & 0 deletions project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,27 @@ targets:
OTHER_LDFLAGS: "-Wl,-U,_SLSConfigureDisplayEnabled -Wl,-U,_SLSGetDisplayList"
GENERATE_INFOPLIST_FILE: YES
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
CrispTests:
type: bundle.unit-test
platform: macOS
deploymentTarget: "15.0"
sources:
- path: CrispTests
- path: Crisp/Models/DisplayModeGeometry.swift
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.crisp.tests
GENERATE_INFOPLIST_FILE: YES
SWIFT_VERSION: "6.0"
SWIFT_STRICT_CONCURRENCY: minimal
TEST_HOST: ""
BUNDLE_LOADER: ""

schemes:
Crisp:
build:
targets:
Crisp: all
test:
targets:
- CrispTests
Loading