Files
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

396 lines
14 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import AppKit
import CoreGraphics
import Foundation
import OSLog
import SwiftUI
import MacToolsPluginKit
public final class DisplayResolutionPluginFactory: NSObject, MacToolsPluginBundleFactory {
public static func makeProvider(context: PluginRuntimeContext) throws -> any PluginProvider {
DisplayResolutionPluginProvider(context: context)
}
}
@MainActor
private struct DisplayResolutionPluginProvider: PluginProvider {
let context: PluginRuntimeContext
func makePlugins() -> [any MacToolsPlugin] {
[DisplayResolutionPlugin(localization: PluginLocalization(bundle: context.resourceBundle))]
}
}
private enum ControlID {
static let displayNavigation = "display-navigation"
static let openSystemSettings = "display-open-system-settings"
}
@MainActor
protocol DisplaySystemSettingsLauncher {
@discardableResult
func openDisplaySettings() -> Bool
}
@MainActor
struct WorkspaceDisplaySystemSettingsLauncher: DisplaySystemSettingsLauncher {
// Opens System Settings > Displays. The URL works for Ventura+ System Settings and the
// legacy System Preferences app.
private static let systemDisplaySettingsURL = URL(string: "x-apple.systempreferences:com.apple.preference.displays")!
@discardableResult
func openDisplaySettings() -> Bool {
NSWorkspace.shared.open(Self.systemDisplaySettingsURL)
}
}
@MainActor
final class DisplayResolutionPlugin: MacToolsPlugin, PluginPrimaryPanel, DisplayTopologyRefreshing {
private static let openSystemSettingsIcon = "gearshape"
let metadata: PluginMetadata
let primaryPanelDescriptor = PluginPrimaryPanelDescriptor(
controlStyle: .disclosure,
menuActionBehavior: .keepPresented
)
var onStateChange: (() -> Void)?
var requestPermissionGuidance: ((String) -> Void)?
var shortcutBindingResolver: ((String) -> ShortcutBinding?)?
private var isExpanded = false
private var selectedDisplayID: CGDirectDisplayID?
private var lastErrorMessage: String?
private let controller: DisplayResolutionControlling
private let systemSettingsLauncher: DisplaySystemSettingsLauncher
private let localization: PluginLocalization
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "cc.ggbond.mactools", category: "DisplayResolutionPlugin")
private var snapshot = DisplayResolutionSnapshot(displays: [])
init(
controller: DisplayResolutionControlling = DisplayResolutionController(),
systemSettingsLauncher: DisplaySystemSettingsLauncher = WorkspaceDisplaySystemSettingsLauncher(),
localization: PluginLocalization = PluginLocalization(bundle: .main)
) {
self.localization = localization
self.controller = controller
self.systemSettingsLauncher = systemSettingsLauncher
self.metadata = PluginMetadata(
id: "display-resolution",
title: localization.string("metadata.title", defaultValue: "显示器分辨率"),
iconName: "display",
iconTint: Color(nsColor: .systemBlue),
order: 30,
defaultDescription: localization.string(
"metadata.description",
defaultValue: "查看并切换每个显示器的分辨率"
)
)
refreshSnapshot()
}
var primaryPanelState: PluginPanelState {
let displays = snapshot.displays
let panelDisplays = displays.filter { !$0.modes.isEmpty }
if !panelDisplays.contains(where: { $0.display.id == selectedDisplayID }) {
selectedDisplayID = nil
}
guard !displays.isEmpty else {
selectedDisplayID = nil
return PluginPanelState(
subtitle: localization.string("panel.subtitle.noDisplays", defaultValue: "未检测到可用显示器"),
isOn: false,
isExpanded: false,
isEnabled: false,
isVisible: true,
detail: nil,
errorMessage: nil
)
}
guard !panelDisplays.isEmpty else {
selectedDisplayID = nil
return PluginPanelState(
subtitle: localization.string("panel.subtitle.noModes", defaultValue: "未检测到可用分辨率"),
isOn: false,
isExpanded: false,
isEnabled: false,
isVisible: true,
detail: nil,
errorMessage: nil
)
}
return PluginPanelState(
subtitle: subtitleForRowState(panelDisplays),
isOn: false,
isExpanded: isExpanded,
isEnabled: true,
isVisible: true,
detail: isExpanded ? buildDetail(for: panelDisplays) : nil,
errorMessage: lastErrorMessage
)
}
var permissionRequirements: [PluginPermissionRequirement] { [] }
var settingsSections: [PluginSettingsSection] { [] }
var shortcutDefinitions: [PluginShortcutDefinition] { [] }
func refresh() {
refreshSnapshot()
}
func refreshDisplayTopology() {
refreshSnapshot()
}
func handleAction(_ action: PluginPanelAction) {
switch action {
case let .setDisclosureExpanded(value):
isExpanded = value
if !value {
selectedDisplayID = nil
}
lastErrorMessage = nil
onStateChange?()
case let .setNavigationSelection(controlID, optionID):
guard
controlID == ControlID.displayNavigation,
let rawDisplayID = UInt32(optionID)
else {
return
}
let displayID = CGDirectDisplayID(rawDisplayID)
selectedDisplayID = displayID
lastErrorMessage = nil
onStateChange?()
case let .clearNavigationSelection(controlID):
guard controlID == ControlID.displayNavigation else {
return
}
selectedDisplayID = nil
lastErrorMessage = nil
onStateChange?()
case let .setSelection(controlID, optionID):
guard let displayID = Self.parseDisplayID(from: controlID), let modeId = Int32(optionID) else {
logger.error("invalid selection payload controlID=\(controlID, privacy: .public) optionID=\(optionID, privacy: .public)")
return
}
refreshSnapshot()
guard snapshot.displays.contains(where: { $0.display.id == displayID }) else {
handleApplyFailure(.displayUnavailable(displayID: displayID), displayID: displayID, modeId: modeId)
return
}
guard let target = snapshot.displays.first(where: { $0.display.id == displayID })?
.allModes
.first(where: { $0.modeId == modeId }) else {
handleApplyFailure(.modeNotFound(modeId: modeId), displayID: displayID, modeId: modeId)
return
}
logger.info("applying \(target.width)×\(target.height) on display \(displayID)")
switch controller.applyResolution(target, for: displayID) {
case .success:
refreshSnapshot()
lastErrorMessage = nil
logger.info("applied \(target.width)×\(target.height) on display \(displayID)")
onStateChange?()
case .failure(let error):
handleApplyFailure(error, displayID: displayID, modeId: modeId)
}
case let .invokeAction(controlID):
guard controlID == ControlID.openSystemSettings else {
return
}
let opened = systemSettingsLauncher.openDisplaySettings()
if !opened {
logger.error("failed to open system display settings via NSWorkspace")
}
case .setSwitch, .setDate, .setSlider:
return
}
}
func permissionState(for permissionID: String) -> PluginPermissionState {
PluginPermissionState(isGranted: true, footnote: nil)
}
func handlePermissionAction(id: String) {}
func handleSettingsAction(id: String) {}
func handleShortcutAction(id: String) {}
nonisolated static func visibleModes(_ modes: [DisplayResolutionInfo]) -> [DisplayResolutionInfo] {
guard let first = modes.first else { return [] }
let nativeAspect = modes.first(where: { $0.isNative })?.aspectRatio ?? first.aspectRatio
return modes.filter { mode in
abs(mode.aspectRatio - nativeAspect) < 0.005 || mode.isCurrent
}
}
nonisolated static func optionTitle(
for mode: DisplayResolutionInfo,
localization: PluginLocalization = PluginLocalization(bundle: .main)
) -> String {
var title = "\(mode.width)×\(mode.height)"
if mode.isNative {
title += localization.string("resolution.badge.native", defaultValue: " (原生)")
} else if mode.isDefault {
title += localization.string("resolution.badge.default", defaultValue: " (默认)")
} else if mode.isHiDPI {
title += " (HiDPI)"
} else {
title += " (LoDPI)"
}
return title
}
nonisolated static func parseDisplayID(from controlID: String) -> CGDirectDisplayID? {
let prefix = "display."
guard controlID.hasPrefix(prefix) else { return nil }
return CGDirectDisplayID(controlID.dropFirst(prefix.count))
}
private func refreshSnapshot() {
let displays = controller.listConnectedDisplays()
let panelDisplays = displays.map { display in
let modes = controller.listAvailableResolutions(for: display.id)
return PanelDisplay(
display: display,
modes: Self.visibleModes(modes),
allModes: modes
)
}
snapshot = DisplayResolutionSnapshot(displays: panelDisplays)
}
private func subtitleForRowState(_ displays: [PanelDisplay]) -> String {
if displays.count == 1, let display = displays.first {
let current = display.modes.first(where: { $0.isCurrent })
return current.map {
let displayName = display.display.isMain
? localization.string("display.main", defaultValue: "主屏")
: display.display.name
return "\(displayName) \($0.displayTitle)"
} ?? metadata.defaultDescription
}
return localization.format("panel.subtitle.displayCountFormat", defaultValue: "%d 个显示器", displays.count)
}
private func buildDetail(for displays: [PanelDisplay]) -> PluginPanelDetail {
let displayNavigation = PluginPanelControl(
id: ControlID.displayNavigation,
kind: .navigationList,
options: displays.map { display in
let currentSummary = display.modes.first(where: { $0.isCurrent })?.displayTitle
?? localization.string("display.currentResolution.unknown", defaultValue: "未知")
return PluginPanelControlOption(
id: String(display.display.id),
title: display.display.name,
subtitle: currentSummary
)
},
selectedOptionID: selectedDisplayID.map(String.init),
dateValue: nil,
minimumDate: nil,
displayedComponents: nil,
datePickerStyle: nil,
sectionTitle: nil,
isEnabled: true
)
let openSystemSettings = PluginPanelControl(
id: ControlID.openSystemSettings,
kind: .actionRow,
options: [],
selectedOptionID: nil,
dateValue: nil,
minimumDate: nil,
displayedComponents: nil,
datePickerStyle: nil,
sectionTitle: nil,
actionTitle: localization.string("panel.action.openSystemSettings", defaultValue: "打开系统显示器设置"),
actionIconSystemName: Self.openSystemSettingsIcon,
actionBehavior: .dismissBeforeHandling,
showsLeadingDivider: true,
isEnabled: true
)
let navigationSecondaryPanels = displays.map { display in
PluginPanelNavigationSecondaryPanel(
controlID: ControlID.displayNavigation,
optionID: String(display.display.id),
panel: secondaryPanel(for: display)
)
}
let selectedSecondaryPanel = selectedDisplayID.flatMap { selectedID in
displays.first(where: { $0.display.id == selectedID }).map(secondaryPanel(for:))
}
return PluginPanelDetail(
primaryControls: [displayNavigation, openSystemSettings],
secondaryPanel: selectedSecondaryPanel,
navigationSecondaryPanels: navigationSecondaryPanels
)
}
private func secondaryPanel(for display: PanelDisplay) -> PluginPanelSecondaryPanel {
let resolutionControl = PluginPanelControl(
id: "display.\(display.display.id)",
kind: .selectList,
options: display.modes.map {
PluginPanelControlOption(
id: String($0.modeId),
title: Self.optionTitle(for: $0, localization: localization),
subtitle: nil
)
},
selectedOptionID: display.modes.first(where: { $0.isCurrent }).map { String($0.modeId) },
dateValue: nil,
minimumDate: nil,
displayedComponents: nil,
datePickerStyle: nil,
sectionTitle: nil,
isEnabled: true
)
return PluginPanelSecondaryPanel(title: display.display.name, controls: [resolutionControl])
}
private func handleApplyFailure(
_ error: DisplayResolutionError,
displayID: CGDirectDisplayID,
modeId: Int32
) {
logger.error(
"apply failed display=\(displayID) modeId=\(modeId) reason=\(error.localizedDescription, privacy: .public)"
)
lastErrorMessage = localization.format(
"error.applyFailedFormat",
defaultValue: "切换失败:%@",
error.localizedDescription(localization: localization)
)
onStateChange?()
}
}
private struct PanelDisplay {
let display: DisplayInfo
let modes: [DisplayResolutionInfo]
let allModes: [DisplayResolutionInfo]
}
private struct DisplayResolutionSnapshot {
let displays: [PanelDisplay]
}