diff --git a/Base.lproj/Main.storyboard b/Base.lproj/Main.storyboard index fc00bcb..e4f4888 100644 --- a/Base.lproj/Main.storyboard +++ b/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -586,27 +586,314 @@ - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + @@ -776,7 +1063,7 @@ - + @@ -810,7 +1097,7 @@ - + @@ -843,8 +1130,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -856,7 +1177,7 @@ - + @@ -894,11 +1215,12 @@ + - - - - - - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + - + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -1213,27 +1546,35 @@ Gw - + - + - + - - + + + The following combination of flags are used in system plists: +HiDPI +Unknown3 +HiDPI + Unknown1 +Retina + HiDPI + Unknown1 +Retina + HiDPI + Unknown2 + +Reboot is required to apply changes @@ -1250,12 +1591,14 @@ Gw - + - + + + diff --git a/RDM.xcodeproj/project.pbxproj b/RDM.xcodeproj/project.pbxproj index a0b9c34..e6e1a1c 100644 --- a/RDM.xcodeproj/project.pbxproj +++ b/RDM.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 358F04E3212D7E6500671ABD /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 358F04E2212D7E6500671ABD /* IOKit.framework */; }; 35959C0225757F1E0002A45F /* HexNumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35959C0125757F1E0002A45F /* HexNumberFormatter.swift */; }; 35959C0525763EE40002A45F /* TextFieldWithMonospacedDigits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35959C0425763EE40002A45F /* TextFieldWithMonospacedDigits.swift */; }; + 35B19E282589F79500ED61D7 /* DisplayIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35B19E272589F79500ED61D7 /* DisplayIcon.swift */; }; 35C9D3B0212C04DA00D652F7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C9D3AF212C04DA00D652F7 /* ViewController.swift */; }; 35C9D3B2212C04DD00D652F7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 35C9D3B1212C04DD00D652F7 /* Assets.xcassets */; }; 35C9D3B5212C04DD00D652F7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35C9D3B3212C04DD00D652F7 /* Main.storyboard */; }; @@ -44,6 +45,7 @@ 358F04E2212D7E6500671ABD /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; 35959C0125757F1E0002A45F /* HexNumberFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HexNumberFormatter.swift; sourceTree = ""; }; 35959C0425763EE40002A45F /* TextFieldWithMonospacedDigits.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldWithMonospacedDigits.swift; sourceTree = ""; }; + 35B19E272589F79500ED61D7 /* DisplayIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayIcon.swift; sourceTree = ""; }; 35C9D3AB212C04DA00D652F7 /* RDM.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RDM.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35C9D3AF212C04DA00D652F7 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 35C9D3B1212C04DD00D652F7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -126,6 +128,7 @@ 35CC4996212C54AF00704BD7 /* bridging.h */, 35959C0125757F1E0002A45F /* HexNumberFormatter.swift */, 35959C0425763EE40002A45F /* TextFieldWithMonospacedDigits.swift */, + 35B19E272589F79500ED61D7 /* DisplayIcon.swift */, ); path = src; sourceTree = ""; @@ -229,6 +232,7 @@ 8FC72A1E24F664BA004E10E1 /* Utils.swift in Sources */, 35959C0525763EE40002A45F /* TextFieldWithMonospacedDigits.swift in Sources */, 358D565D212DEDED00F9E9BF /* cmdline.mm in Sources */, + 35B19E282589F79500ED61D7 /* DisplayIcon.swift in Sources */, 35C9D3B0212C04DA00D652F7 /* ViewController.swift in Sources */, 358D565E212DEDED00F9E9BF /* main.mm in Sources */, 8F48E61724FC761C0073123B /* RestoreSettings.swift in Sources */, @@ -374,6 +378,7 @@ INFOPLIST_FILE = etc/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; + MARKETING_VERSION = 2.5.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = net.alkalay.RDM; @@ -399,6 +404,7 @@ INFOPLIST_FILE = etc/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; + MARKETING_VERSION = 2.5.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = net.alkalay.RDM; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/etc/Info.plist b/etc/Info.plist index 9462914..da4364e 100644 --- a/etc/Info.plist +++ b/etc/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.4.0 + $(MARKETING_VERSION) LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/src/DisplayIcon.swift b/src/DisplayIcon.swift new file mode 100644 index 0000000..438a7a7 --- /dev/null +++ b/src/DisplayIcon.swift @@ -0,0 +1,116 @@ +// +// DisplayIcon.swift +// RDM +// +// Created by usrsse2 on 16.12.2020. +// Copyright © 2020 гык-sse2. All rights reserved. +// + +import Cocoa + +public let kDisplayIcon = "display-icon" +public let kDisplayResolutionPreviewIcon = "display-resolution-preview-icon" + +public let kResolutionPreviewWidth = "resolution-preview-width" +public let kResolutionPreviewHeight = "resolution-preview-height" +public let kResolutionPreviewX = "resolution-preview-x" +public let kResolutionPreviewY = "resolution-preview-y" + +class DisplayIcon : NSObject +{ + @objc var Properties : [String : AnyHashable] + + @objc var DisplayIcon : NSImage? { + get { + if let imagePath = Properties[kDisplayIcon] as? String { + return NSImage(contentsOfFile: imagePath) + } + return nil + } + } + + @objc var DisplayResolutionPreviewIcon : NSImage? { + get { + if let imagePath = Properties[kDisplayResolutionPreviewIcon] as? String { + return NSImage(contentsOfFile: imagePath) + } + return nil + } + } + + private func GetValueOrZero(forKey key: String) -> Int { + if let i = Properties[key] as? Int { + return i + } + return 0 + } + + private func SetOrRemoveValue(forKey key: String, value: Int) { + if value == 0 { + Properties.removeValue(forKey: key) + } + else { + Properties[key] = value + } + } + + @objc var ResolutionPreviewWidth : Int { + get { + return GetValueOrZero(forKey: kResolutionPreviewWidth) + } + set(value) { + SetOrRemoveValue(forKey: kResolutionPreviewWidth, value: value) + } + } + + @objc var ResolutionPreviewHeight : Int { + get { + return GetValueOrZero(forKey: kResolutionPreviewHeight) + } + set(value) { + SetOrRemoveValue(forKey: kResolutionPreviewHeight, value: value) + } + } + + @objc var ResolutionPreviewX : Int { + get { + return GetValueOrZero(forKey: kResolutionPreviewX) + } + set(value) { + SetOrRemoveValue(forKey: kResolutionPreviewX, value: value) + } + } + + @objc var ResolutionPreviewY : Int { + get { + return GetValueOrZero(forKey: kResolutionPreviewY) + } + set(value) { + SetOrRemoveValue(forKey: kResolutionPreviewY, value: value) + } + } + + init(properties : [String : AnyHashable]) { + Properties = properties + } + + static func ==(lhs: DisplayIcon, rhs: DisplayIcon) -> Bool { + return lhs.Properties == rhs.Properties + } + + override func isEqual(_ object: Any?) -> Bool { + if let other = object as? DisplayIcon { + return + Properties == other.Properties || + Properties.filter({$0.key != kDisplayResolutionPreviewIcon && $0.key != kDisplayIcon}) == other.Properties.filter({$0.key != kDisplayResolutionPreviewIcon && $0.key != kDisplayIcon}) && + DisplayResolutionPreviewIcon?.tiffRepresentation == other.DisplayResolutionPreviewIcon?.tiffRepresentation && + DisplayIcon?.tiffRepresentation == other.DisplayIcon?.tiffRepresentation + } + return false + } + + override var hash: Int { + return 0 // Properties.hashValue //Int(_width | _height | (UInt32)(_hiDPIFlag & 0xffffffff)) + } + +} diff --git a/src/RDMAppDelegate.mm b/src/RDMAppDelegate.mm index 9d0d6a4..e0b73f5 100644 --- a/src/RDMAppDelegate.mm +++ b/src/RDMAppDelegate.mm @@ -148,11 +148,11 @@ - (void) refreshStatusMenu productID:CGDisplayModelNumber(display) displayName:screenName]]; - [submenu addItem:[[RestoreSettingsItem alloc] initWithTitle:@"Restore..." - action:@selector(restoreSettings:) - vendorID:CGDisplayVendorNumber(display) - productID:CGDisplayModelNumber(display) - displayName:screenName]]; + //[submenu addItem:[[RestoreSettingsItem alloc] initWithTitle:@"Restore..." + // action:@selector(restoreSettings:) + // vendorID:CGDisplayVendorNumber(display) + // productID:CGDisplayModelNumber(display) + // displayName:screenName]]; NSString* title = [currItem title]; diff --git a/src/Resolution.swift b/src/Resolution.swift index 92f33b0..eda54fe 100644 --- a/src/Resolution.swift +++ b/src/Resolution.swift @@ -9,9 +9,10 @@ import Foundation let kFlagHiDPI : UInt64 = 0x0000000100000000 -let kFlagUnknown1 : UInt64 = 0x0000000000020000 +let kFlagUnknown1 : UInt64 = 0x0000000000200000 let kFlagRetinaDisplay : UInt64 = 0x0000000800000000 -let kFlagUnknown2 : UInt64 = 0x0000000000080000 +let kFlagUnknown2 : UInt64 = 0x0000000000800000 +let kFlagUnknown3 : UInt64 = 0x0000000200000000 @objc class Resolution : NSObject { private var _width : UInt32 @@ -68,6 +69,7 @@ let kFlagUnknown2 : UInt64 = 0x0000000000080000 willChangeValue(forKey: "Retina") willChangeValue(forKey: "Unknown1") willChangeValue(forKey: "Unknown2") + willChangeValue(forKey: "Unknown3") let wasHiDPI = isHiDPI _hiDPIFlag = value if isHiDPI != wasHiDPI { @@ -83,6 +85,7 @@ let kFlagUnknown2 : UInt64 = 0x0000000000080000 didChangeValue(forKey: "Retina") didChangeValue(forKey: "Unknown1") didChangeValue(forKey: "Unknown2") + didChangeValue(forKey: "Unknown3") } } @@ -122,10 +125,19 @@ let kFlagUnknown2 : UInt64 = 0x0000000000080000 } } + @objc dynamic var Unknown3 : Bool { + get { + return RawFlags & kFlagUnknown3 != 0 + } + set(value) { + setFlag(kFlagUnknown3, value) + } + } + override init() { self._width = 0 self._height = 0 - self._hiDPIFlag = 0 + self._hiDPIFlag = kFlagRetinaDisplay | kFlagHiDPI | kFlagUnknown1 super.init() } @@ -182,4 +194,15 @@ let kFlagUnknown2 : UInt64 = 0x0000000000080000 && (lhs._width == rhs._width ) && (lhs._hiDPIFlag == rhs._hiDPIFlag) } + + override func isEqual(_ object: Any?) -> Bool { + if let other = object as? Resolution { + return _width == other._width && _height == other._height && _hiDPIFlag == other._hiDPIFlag + } + return false + } + + override var hash: Int { + return 0 //Int(_width | _height | (UInt32)(_hiDPIFlag & 0xffffffff)) + } } diff --git a/src/SheetViewController.swift b/src/SheetViewController.swift index cde84cd..0d102f3 100644 --- a/src/SheetViewController.swift +++ b/src/SheetViewController.swift @@ -74,8 +74,8 @@ import Cocoa super.viewWillAppear() _parent = (presentingViewController as! ViewController) - if _parent.arrayController.selectionIndexes.count > 0 { - _resolution = _parent.resolutions[_parent.arrayController.selectionIndex] + if _parent.arrayController.selectionIndexes.count == 1 { + _resolution = _parent.arrayController.selectedObjects.first as! Resolution } widthField .integerValue = Int(width) diff --git a/src/ViewController.swift b/src/ViewController.swift index 49cfb0a..ae95e54 100644 --- a/src/ViewController.swift +++ b/src/ViewController.swift @@ -24,15 +24,51 @@ let kTargetDefaultPixelsPerMillimeterKey = "target-default-ppmm" @IBOutlet var arrayController : NSArrayController! var calcController : SheetViewController! var plist : NSMutableDictionary! + @IBOutlet var iconArrayController : NSArrayController! @IBOutlet weak var displayName : NSTextField! @objc var vendorID : UInt32 = 0 @objc var productID : UInt32 = 0 @objc dynamic var resolutions : [Resolution] = [] - + @objc dynamic var icons : Set = [] + @IBOutlet weak var heightConstraint: NSLayoutConstraint! + @IBOutlet weak var widthConstraint: NSLayoutConstraint! + @IBOutlet weak var xConstraint: NSLayoutConstraint! + @IBOutlet weak var yConstraint: NSLayoutConstraint! + @IBOutlet weak var resolutionPreviewImage: NSImageView! // For help var helpPopover : NSPopover! - + + @objc dynamic var selectedIcon : NSImage? { + get { + if iconArrayController.selectedObjects.count == 1 { + return (iconArrayController.selectedObjects.first as? DisplayIcon)?.DisplayResolutionPreviewIcon + } + return nil + } + set (value) { + if let image = value { + let tmp = NSTemporaryDirectory() + UUID().uuidString + try! image.tiffRepresentation?.write(to: URL(fileURLWithPath: tmp)) + + let icon = icons.insert(DisplayIcon(properties: [ + kDisplayResolutionPreviewIcon: tmp, + kResolutionPreviewX: 0, + kResolutionPreviewY: 0, + kResolutionPreviewWidth: 160, + kResolutionPreviewHeight: 90 + ])).memberAfterInsert + iconArrayController.content = icons + iconArrayController.setSelectedObjects([icon]) + iconArrayController.rearrangeObjects() + } + else { + iconArrayController.setSelectedObjects([]) + } + } + } + + @objc var iconForBoardId : DisplayIcon? = nil @objc var displayProductName : String { get { return displayName.stringValue @@ -41,6 +77,19 @@ let kTargetDefaultPixelsPerMillimeterKey = "target-default-ppmm" displayName.stringValue = value } } + + var sourceIconsPlists : [String] { + get { + return ["/System\(ViewController.rootdir)/Icons.plist"] + (ViewController.supportsLibraryDisplays ? ["\(ViewController.rootdir)/Icons.plist"] : []) + } + } + + var destinationIconsPlist : String { + get { + let dstPlist = "\(ViewController.rootdir)/Icons.plist" + return ViewController.supportsLibraryDisplays ? dstPlist : "/System" + dstPlist + } + } var sourceDirs : [String] { get { @@ -87,12 +136,83 @@ let kTargetDefaultPixelsPerMillimeterKey = "target-default-ppmm" plist = NSMutableDictionary() } - resolutions = [] if let a = plist[kScaleResolutionsKey] { if let b = a as? NSArray { - resolutions = (b as Array).map { Resolution(nsdata: $0 as? NSData) } + resolutions.append(contentsOf: ((b as Array).map { Resolution(nsdata: $0 as? NSData) })) } } + + // Remove counterparts + resolutions = resolutions.filter({ (res) -> Bool in + return !(res.RawFlags == 0 && resolutions.contains(where: {$0.HiDPI && $0.height * 2 == res.height && $0.width * 2 == res.width})) + }) + + resolutions = Array(NSOrderedSet(array: resolutions)) as! [Resolution] + + var selection : [DisplayIcon] = [] + + var computerBoardId : String? = nil + if #available(OSX 10.13, *) { + let p = Process() + let pipe = Pipe() + p.executableURL = URL(fileURLWithPath: "/usr/sbin/ioreg") + p.arguments = ["-c", "IOPlatformExpertDevice", "-a"] + p.standardOutput = pipe + try! p.run() + let data = pipe.fileHandleForReading.readDataToEndOfFile() + var format = PropertyListSerialization.PropertyListFormat.xml + let dict = try! PropertyListSerialization.propertyList(from: data, options: [], format: &format) as! [String : AnyObject] + if let children = dict["IORegistryEntryChildren"] as? [AnyObject] { + if children.count == 1, + let item = children.first as? [String : AnyObject] { + if let data = item["board-id"] as? Data { + computerBoardId = String(data: data, encoding: String.Encoding.ascii) + } + } + } + } + + for iconsPlist in sourceIconsPlists.map { NSMutableDictionary(contentsOf: URL(fileURLWithPath: $0)) ?? nil }.filter({$0 != nil}) { + if let boards = iconsPlist!["board-ids"] as? [String : Any] { + for board in boards { + if let boardDict = board.value as? [String : AnyHashable] { + if boardDict[kDisplayResolutionPreviewIcon] != nil { + let icon = icons.insert(DisplayIcon(properties: boardDict)).memberAfterInsert + if board.key == computerBoardId { + iconForBoardId = icon + } + } + } + } + } + if let vendors = iconsPlist!["vendors"] as? [String : Any] { + let defaultDisplayIcon = vendors[kDisplayIcon] as? String + for vendor in vendors { + if let vendorDict = vendor.value as? [String : Any] { + let vendorDefaultDisplayIcon = vendorDict[kDisplayIcon] as? String ?? defaultDisplayIcon + if let products = vendorDict["products"] as? [String : Any] { + for product in products { + if var productDict = product.value as? [String : AnyHashable] { + if vendorDefaultDisplayIcon != nil && !productDict.keys.contains(kDisplayIcon) { + productDict[kDisplayIcon] = vendorDefaultDisplayIcon + } + if productDict[kDisplayResolutionPreviewIcon] != nil { + let (_, displayIcon) = icons.insert(DisplayIcon(properties: productDict)) + if String(format: "%x", vendorID).lowercased() == vendor.key.lowercased() && + String(format:"%x", productID).lowercased() == product.key.lowercased() { + selection = [displayIcon] + } + } + } + } + } + } + } + } + } + + print(icons.count) + // Initialize subviews calcController = (storyboard!.instantiateController(withIdentifier: "calculator") as! SheetViewController) @@ -106,11 +226,76 @@ let kTargetDefaultPixelsPerMillimeterKey = "target-default-ppmm" view.window!.standardWindowButton(.zoomButton)!.isHidden = true view.window!.styleMask.insert(.resizable) + self.iconArrayController.addObserver(self, forKeyPath: "selectedObjects", options: .new, context: nil) + + self.resolutionPreviewImage.imageScaling = NSImageScaling.scaleAxesIndependently + self.resolutionPreviewImage.image = NSImage(contentsOfFile: "/System/Library/CoreServices/DefaultBackground.jpg") + DispatchQueue.main.async { + self.iconArrayController.sortDescriptors = [NSSortDescriptor(key: "ResolutionPreviewWidth", ascending: true)] self.arrayController.content = self.resolutions + self.iconArrayController.content = self.icons + self.iconArrayController.setSelectedObjects(selection) } } - + + @IBAction func fitModel(_ sender: Any) { + if let icon = iconForBoardId { + iconArrayController.setSelectedObjects([icon]) + } + else { + iconArrayController.setSelectedObjects([]) + } + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + guard let keyPath = keyPath else { return } + switch keyPath { + case "selectedObjects": + willChangeValue(forKey: "selectedIcon") + if iconArrayController.selectedObjects.count == 1, + let icon = iconArrayController.selectedObjects.first as? DisplayIcon { + icon.addObserver(self, forKeyPath: "ResolutionPreviewX", options: .new, context: nil) + icon.addObserver(self, forKeyPath: "ResolutionPreviewY", options: .new, context: nil) + icon.addObserver(self, forKeyPath: "ResolutionPreviewWidth", options: .new, context: nil) + icon.addObserver(self, forKeyPath: "ResolutionPreviewHeight", options: .new, context: nil) + self.xConstraint.constant = CGFloat(icon.ResolutionPreviewX) + self.yConstraint.constant = CGFloat(-icon.ResolutionPreviewY) + self.widthConstraint.constant = CGFloat(icon.ResolutionPreviewWidth) + self.heightConstraint.constant = CGFloat(icon.ResolutionPreviewHeight) + } + else { + self.xConstraint.constant = 0 + self.yConstraint.constant = 0 + self.widthConstraint.constant = 0 + self.heightConstraint.constant = 0 + } + self.view.needsLayout = true + self.view.layout() + didChangeValue(forKey: "selectedIcon") + break + + case "ResolutionPreviewX", "ResolutionPreviewY", "ResolutionPreviewWidth", "ResolutionPreviewHeight": + if iconArrayController.selectedObjects.count == 1, + let icon = iconArrayController.selectedObjects.first as? DisplayIcon { + self.xConstraint.constant = CGFloat(icon.ResolutionPreviewX) + self.yConstraint.constant = CGFloat(-icon.ResolutionPreviewY) + self.widthConstraint.constant = CGFloat(icon.ResolutionPreviewWidth) + self.heightConstraint.constant = CGFloat(icon.ResolutionPreviewHeight) + } + else { + self.xConstraint.constant = 0 + self.yConstraint.constant = 0 + self.widthConstraint.constant = 0 + self.heightConstraint.constant = 0 + } + break + + default: + break + } + } + override func viewDidAppear() { super.viewDidAppear() view.window?.level = .floating // Always on top @@ -118,21 +303,58 @@ let kTargetDefaultPixelsPerMillimeterKey = "target-default-ppmm" @IBAction func add(_ sender: Any) { resolutions.append(Resolution()) + arrayController.content = resolutions arrayController.rearrangeObjects() } -// @IBOutlet weak var removeButton: NSButton! - @IBAction func remove(_ sender: Any) { if arrayController.selectionIndexes.count > 0 { resolutions.remove(at: arrayController.selectionIndexes) + arrayController.content = resolutions arrayController.rearrangeObjects() } } - - @IBAction func save(_ sender: Any) { + + func saveIcon() { var saveScripts = [String]() + var dict = (NSMutableDictionary(contentsOf: URL(fileURLWithPath: destinationIconsPlist)) as? [String : Any]) ?? [:] + let productDict = iconArrayController.selectedObjects.count == 1 ? (iconArrayController.selectedObjects[0] as? DisplayIcon)?.Properties : nil + let hexVendor = String(format: "%x", vendorID) + let hexProduct = String(format: "%x", productID) + + var vendors = dict["vendors"] as? [String : Any] ?? [:] + var vendorDict = vendors[hexVendor] as? [String : Any] ?? [:] + var products = vendorDict["products"] as? [String : Any] ?? [:] + if var pd = productDict { + if let iconPath = pd[kDisplayResolutionPreviewIcon] as? String { + if iconPath.hasPrefix(NSTemporaryDirectory()) { + pd[kDisplayResolutionPreviewIcon] = destinationFile + ".tiff" + saveScripts.append("mv '\(iconPath)' '\(destinationFile + ".tiff")'") + } + } + products[hexProduct] = pd + } + else { + products.removeValue(forKey: hexProduct) + } + vendorDict["products"] = products + vendors[hexVendor] = vendorDict + dict["vendors"] = vendors + + let tmpFile = NSTemporaryDirectory() + UUID().uuidString + NSDictionary(dictionary: dict).write(toFile: tmpFile, atomically: false) + saveScripts.append("mv '\(tmpFile)' '\(destinationIconsPlist)'") + if let error = NSAppleScript.executeAndReturnError(source: saveScripts.joined(separator: " && "), + asType: .shell, + withAdminPriv: true) { + NSAlert(fromDict: error).beginSheetModal(for: view.window!) + } else { + view.window!.close() + } + } + + @IBAction func save(_ sender: Any) { if let error = RestoreSettingsItem.backupSettings(originalPlistPath: sourceFiles.last!) { NSAlert(fromDict: error).beginSheetModal(for: view.window!) return @@ -142,12 +364,12 @@ let kTargetDefaultPixelsPerMillimeterKey = "target-default-ppmm" plist[kDisplayProductName] = displayProductName as NSString - resolutions = resolutions.sorted { (a, b) -> Bool in + let sortedResolutions = resolutions.sorted { (a, b) -> Bool in return a.width > b.width || a.width == b.width && a.height > b.height } - let hiDPIResolutions = resolutions.filter({($0).RawFlags & kFlagHiDPI != 0}) - let lowDPIResolutions = resolutions.filter({($0).RawFlags & kFlagHiDPI == 0}) + let hiDPIResolutions = sortedResolutions.filter({($0).RawFlags & kFlagHiDPI != 0}) + let lowDPIResolutions = sortedResolutions.filter({($0).RawFlags & kFlagHiDPI == 0}) let hiDPICounterparts = hiDPIResolutions.map { (r) -> Resolution in let res = Resolution() res.width = r.width * 2 @@ -164,16 +386,18 @@ let kTargetDefaultPixelsPerMillimeterKey = "target-default-ppmm" } plist.write(toFile: tmpFile, atomically: false) - saveScripts.append("mkdir -p '\(destinationDir)'") - saveScripts.append("cp '\(tmpFile)' '\(destinationFile)'") - saveScripts.append("rm '\(tmpFile)'") + let saveScripts = [ + "mkdir -p '\(destinationDir)'", + "cp '\(tmpFile)' '\(destinationFile)'", + "rm '\(tmpFile)'" + ] if let error = NSAppleScript.executeAndReturnError(source: saveScripts.joined(separator: " && "), asType: .shell, withAdminPriv: true) { NSAlert(fromDict: error).beginSheetModal(for: view.window!) } else { - view.window!.close() + saveIcon() } }