mirror of
https://github.com/morgan9e/VirtualDisplay
synced 2026-04-13 15:55:02 +09:00
166 lines
5.3 KiB
Swift
166 lines
5.3 KiB
Swift
import Cocoa
|
||
|
||
final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
||
|
||
private var statusItem: NSStatusItem!
|
||
private let menu = NSMenu()
|
||
|
||
private struct Physical {
|
||
let name: String
|
||
let pixelWidth: Int
|
||
let pixelHeight: Int
|
||
let refreshRate: Int
|
||
}
|
||
|
||
private var lastPhysical: Physical?
|
||
|
||
// lifecycle
|
||
|
||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||
if let button = statusItem.button {
|
||
if let img = NSImage(systemSymbolName: "rectangle.on.rectangle",
|
||
accessibilityDescription: "VirtualDisplay") {
|
||
img.isTemplate = true
|
||
button.image = img
|
||
} else {
|
||
button.title = "VD"
|
||
}
|
||
}
|
||
menu.delegate = self
|
||
menu.autoenablesItems = false
|
||
statusItem.menu = menu
|
||
|
||
ProcessInfo.processInfo.disableAutomaticTermination("VirtualDisplay running")
|
||
}
|
||
|
||
func applicationWillTerminate(_ notification: Notification) {
|
||
DisplayManager.shared.disable()
|
||
}
|
||
|
||
// NSMenuDelegate
|
||
|
||
func menuNeedsUpdate(_ menu: NSMenu) {
|
||
guard menu === self.menu else { return }
|
||
rebuild()
|
||
}
|
||
|
||
private func rebuild() {
|
||
menu.removeAllItems()
|
||
|
||
guard let phys = physicalDisplay() else {
|
||
addDisabled("No display detected")
|
||
menu.addItem(.separator())
|
||
addQuit()
|
||
return
|
||
}
|
||
|
||
addDisabled("\(phys.name) (\(phys.pixelWidth)×\(phys.pixelHeight) @ \(phys.refreshRate)Hz)")
|
||
menu.addItem(.separator())
|
||
|
||
let activeScale = DisplayManager.shared.currentScale
|
||
for s in availableScales(physicalPixelWidth: phys.pixelWidth,
|
||
physicalPixelHeight: phys.pixelHeight) {
|
||
let item = NSMenuItem(title: "\(s)%",
|
||
action: #selector(chooseScale(_:)),
|
||
keyEquivalent: "")
|
||
item.target = self
|
||
item.tag = s
|
||
item.state = (activeScale == s) ? .on : .off
|
||
menu.addItem(item)
|
||
}
|
||
|
||
if activeScale != nil {
|
||
menu.addItem(.separator())
|
||
addAction("Open Display Settings…", #selector(openDisplaySettings(_:)))
|
||
menu.addItem(.separator())
|
||
addAction("Disable", #selector(disable(_:)))
|
||
}
|
||
|
||
menu.addItem(.separator())
|
||
addQuit()
|
||
}
|
||
|
||
// Menu helpers
|
||
|
||
private func addDisabled(_ title: String) {
|
||
let item = NSMenuItem(title: title, action: nil, keyEquivalent: "")
|
||
item.isEnabled = false
|
||
menu.addItem(item)
|
||
}
|
||
|
||
private func addAction(_ title: String, _ action: Selector) {
|
||
let item = NSMenuItem(title: title, action: action, keyEquivalent: "")
|
||
item.target = self
|
||
menu.addItem(item)
|
||
}
|
||
|
||
private func addQuit() {
|
||
menu.addItem(NSMenuItem(title: "Quit",
|
||
action: #selector(NSApplication.terminate(_:)),
|
||
keyEquivalent: "q"))
|
||
}
|
||
|
||
// Physical display detection
|
||
|
||
private func physicalDisplay() -> Physical? {
|
||
if let live = liveLookup() { lastPhysical = live }
|
||
return lastPhysical
|
||
}
|
||
|
||
private func liveLookup() -> Physical? {
|
||
let ourID = DisplayManager.shared.virtualDisplayID
|
||
|
||
var count: UInt32 = 0
|
||
guard CGGetActiveDisplayList(0, nil, &count) == .success, count > 0 else {
|
||
return nil
|
||
}
|
||
var ids = [CGDirectDisplayID](repeating: 0, count: Int(count))
|
||
guard CGGetActiveDisplayList(count, &ids, &count) == .success,
|
||
let id = ids.first(where: { $0 != ourID }),
|
||
let mode = CGDisplayCopyDisplayMode(id) else {
|
||
return nil
|
||
}
|
||
|
||
let key = NSDeviceDescriptionKey("NSScreenNumber")
|
||
let name = NSScreen.screens.first {
|
||
($0.deviceDescription[key] as? NSNumber)?.uint32Value == id
|
||
}?.localizedName ?? lastPhysical?.name ?? "Display"
|
||
let hz = mode.refreshRate > 0 ? Int(mode.refreshRate.rounded()) : 60
|
||
|
||
return Physical(
|
||
name: name,
|
||
pixelWidth: mode.pixelWidth,
|
||
pixelHeight: mode.pixelHeight,
|
||
refreshRate: hz
|
||
)
|
||
}
|
||
|
||
// Actions
|
||
|
||
@objc private func chooseScale(_ sender: NSMenuItem) {
|
||
guard let phys = physicalDisplay() else { return }
|
||
let cfg = vdConfig(physicalPixelWidth: phys.pixelWidth,
|
||
physicalPixelHeight: phys.pixelHeight,
|
||
refreshRate: phys.refreshRate,
|
||
scalePercent: sender.tag)
|
||
DisplayManager.shared.setScale(sender.tag, config: cfg)
|
||
}
|
||
|
||
@objc private func openDisplaySettings(_ sender: NSMenuItem) {
|
||
let urls = [
|
||
"x-apple.systempreferences:com.apple.Displays-Settings.extension",
|
||
"x-apple.systempreferences:com.apple.preference.displays",
|
||
]
|
||
for str in urls {
|
||
if let url = URL(string: str), NSWorkspace.shared.open(url) {
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
@objc private func disable(_ sender: NSMenuItem) {
|
||
DisplayManager.shared.disable()
|
||
}
|
||
}
|