Get human readable name for Mac model on Ventura - swift

I have used the following code for get the model identifier of a Mac computer:
public static var modelIdentifier: String {
var size = 0
sysctlbyname("hw.model", nil, &size, nil, 0)
var machine = [CChar](repeating: 0, count: Int(size))
sysctlbyname("hw.model", &machine, &size, nil, 0)
return String(cString: machine)
}
This model identifier is used in this variable to get a human readable name of the device:
public static var marketingModel: String {
guard let currentIdentifier = NSLocale.current.languageCode else { return String.hyphen }
let modelIdentifier = self.modelIdentifier
var path = "/System/Library/PrivateFrameworks/ServerInformation.framework/Versions/A/Resources/"
path += currentIdentifier + ".lproj"
path += "/SIMachineAttributes.plist"
if let fileData = FileManager.default.contents(atPath: path) {
if let plistContents = try? PropertyListSerialization.propertyList(from: fileData, format: nil)
as? [String: Any]
{
if let contents = plistContents[modelIdentifier] as? [String: Any],
let localizable = contents["_LOCALIZABLE_"] as? [String: String]
{
let marketingModel = localizable["marketingModel"] ?? String.hyphen
return marketingModel
}
}
}
return String.hyphen
}
This returned strings like MacBook Pro (16-inch, 2019). However, on the latest MacOS Ventura build, no string is returned for Mac Mac Studio (Mac13,1).
Is there another way to get this string? Do I have to look in a different plist?

I have used the IORegistryExplorer and found a key that contains this information.
By running the following code in a playground, you can extract the name:
import Cocoa
import IOKit
let mainEntry = IORegistryEntryFromPath(kIOMainPortDefault, "IOService:/AppleARMPE/product")
let property = IORegistryEntryCreateCFProperty(mainEntry, "product-description" as CFString, kCFAllocatorDefault, 0)
if let bytes = property?.takeRetainedValue() as? Data
{
let array = [UInt8](bytes)
let terminatedModelString = String(cString: array)
Swift.print(terminatedModelString)
}
IOObjectRelease(mainEntry)
The key suggests that this will only work for Apple Silicon based machines. For Intel based machines, you can still use the code from my initial question.

Related

Swift macOS - find all files of a specific extension

I need to find the names of all files that have the extension .pub in the path ~/.ssh.
So in the end I need to have an array of names.
I solved it like this, I wonder if there is a possibility to write it in a better and more compact way as code.
func arrayFile(path: String, ext: String) -> [String] {
guard let desktopPath = NSSearchPathForDirectoriesInDomains(.desktopDirectory,.userDomainMask,true).first else { return [] }
let pathConfig = desktopPath.replacingOccurrences(of: "Desktop", with: path)
let filemanager: FileManager = FileManager()
let files = filemanager.enumerator(atPath: pathConfig)
var array = [String]()
while let file = files?.nextObject() as? String {
if file.hasSuffix(ext) {
let name = file.replacingOccurrences(of: ext, with: "")
array.append(name)
}
}
return array
}
let array = arrayFile(path: ".ssh/", ext: ".pub")
print(array)
Can you give me some suggestions?

CFDictionary get Value for Key in Swift3

I've a problem with accessing a specific (or any) key in a CFDictionary.
Honestly I don't really get the way you need to do this in Swift and I think it's overly complicated...
My Code:
if let session = DASessionCreate(kCFAllocatorDefault) {
let mountedVolumes = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: [], options: [])!
for volume in mountedVolumes {
if let disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, volume as CFURL) {
let diskinfo = DADiskCopyDescription(disk);
var volumeValue = CFDictionaryGetValue(diskinfo, <#T##key: UnsafeRawPointer!##UnsafeRawPointer!#>)
}
}
What I want to achieve: Get the Value for the Key or field DAVolumeNetwork into the variable volumeValue.
I guess I need to pass the kDADiskDescriptionVolumeNetworkKey to the CFDictionaryGetValue Method? How do I achieve this?
Don't use CFDictionary in Swift. (It is possible, but not worth the effort, see below.)
CFDictionary is toll-free
bridged with NSDictionary, which in turn can be cast to a Swift Dictionary.
The value of the kDADiskDescriptionVolumeNetworkKey key is a
CFBoolean which can be cast to a Swift Bool.
Example:
if let session = DASessionCreate(kCFAllocatorDefault),
let mountedVolumes = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: []) {
for volume in mountedVolumes {
if let disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, volume as CFURL),
let diskinfo = DADiskCopyDescription(disk) as? [NSString: Any] {
if let networkValue = diskinfo[kDADiskDescriptionVolumeNetworkKey] as? Bool {
print(networkValue)
}
}
}
}
Just for the sake of completeness: This is the necessary pointer
juggling to call CFDictionaryGetValue in Swift 3:
if let session = DASessionCreate(kCFAllocatorDefault),
let mountedVolumes = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: []) {
for volume in mountedVolumes {
if let disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, volume as CFURL),
let diskinfo = DADiskCopyDescription(disk) {
if let ptr = CFDictionaryGetValue(diskinfo, Unmanaged.passUnretained(kDADiskDescriptionVolumeNetworkKey).toOpaque()) {
let networkValue = Unmanaged<NSNumber>.fromOpaque(ptr).takeUnretainedValue()
print(networkValue.boolValue)
}
}
}
}
You can cast CFDictionaryRef to a Swift dictionary:
if let disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, volume as CFURL), let diskinfo = DADiskCopyDescription(disk) as? [String: AnyObject] {
}
and then cast kDADiskDescriptionVolumeNetworkKey to a Swift string:
var volumeValue = diskinfo[kDADiskDescriptionVolumeNetworkKey as String]

Rewriting metadata into .jpg file by using Swift 2.2 OS X

I'm try rewrite metadata from JPG file. I want addition one keyword to metadata. Xcode don't give any errors, but file not changed.
Here is my code:
var pathToOpenFile:NSURL?
Next I write path from file to variable "pathToOpenFile".
If user pushed ENTER button into NSTextField, then work action:
#IBAction func endEditKeys(sender: AnyObject) {
if (pathToOpenFile != nil) {
let imageSource = CGImageSourceCreateWithURL(pathToOpenFile!, nil)
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource!, 0, nil)! as NSDictionary;
let exifDict = imageProperties.valueForKey("{IPTC}") as! NSDictionary;
var Keywords:[String] = exifDict.valueForKey("Keywords") as! [String];
Keywords.append("ANY")
exifDict.setValue(Keywords, forKey: "Keywords")
let type = CGImageSourceGetType(imageSource!)
let count = CGImageSourceGetCount(imageSource!)
let mutableData = NSMutableData(contentsOfURL: pathToOpenFile!)
let destination = CGImageDestinationCreateWithData(mutableData!, type!, count, nil)
let removeExifProperties: CFDictionary = exifDict
for i in 0..<count {
CGImageDestinationAddImageFromSource(destination!, imageSource!, i, removeExifProperties)
}
CGImageDestinationFinalize(destination!)
}
}
Can you help me, why it isn't work (not change metadata)? Thank you!
Thanks all!
It is working, just need rewrite .JPG file in end.
if let _ = try? mutableData!.writeToURL(pathToOpenFile!, options: NSDataWritingOptions.AtomicWrite) {
// if you need, do anything. For example "print ("Savig file")"
}

How to get WindowCount of other applications using Swift

how do I get the current WindowCount of other applications?
I'm using Swift as programming language and developing for OS X.
Have a nice day.
Import CoreGraphics and check out CGWindowListCopyWindowInfo:
import CoreGraphics
let kCGNullWindowID: UInt32 = 0
// Check the documentation for other options
guard let windows = CGWindowListCopyWindowInfo([.OptionOnScreenOnly], kCGNullWindowID) as NSArray? as? [[String: AnyObject]] else {
fatalError("Can't find windows")
}
var windowCount = [String: Int]()
windows.forEach {
let ownerName = $0[kCGWindowOwnerName as String] as! String
if let count = windowCount[ownerName] {
windowCount[ownerName] = count + 1
} else {
windowCount[ownerName] = 1
}
}
print(windowCount)

Write to plist file in Swift

I have a sample plist-file, favCities.plist. This is my sample code:
override func viewDidLoad() {
super.viewDidLoad()
let path = NSBundle.mainBundle().pathForResource("favCities", ofType: "plist")
var plistArray = NSArray(contentsOfFile: path) as [Dictionary<String, String>]
var addDic: Dictionary = ["ZMV": "TEST", "Name": "TEST", "Country": "TEST"]
plistArray += addDic
(plistArray as NSArray).writeToFile(path, atomically: true)
var plistArray2 = NSArray(contentsOfFile: path)
for tempDict1 in plistArray2 {
var tempDict2: NSDictionary = tempDict1 as NSDictionary
var cityName: String = tempDict2.valueForKey("Name") as String
var cityZMV: String = tempDict2.valueForKey("ZMV") as String
var cityCountry: String = tempDict2.valueForKey("Country") as String
println("City: \(cityName), ZMV: \(cityZMV), Country: \(cityCountry)")
}
At first glance, everything works well. The output looks like this:
City: Moscow, ZMV: 00000.1.27612, Country: RU
City: New York, ZMV: 10001.5.99999, Country: US
City: TEST, ZMV: TEST, Country: TEST
But when I interrupt the app, I see that my file favCities.plist has not changed. There are still two values. These values ​​- City: TEST, ZMV: TEST, Country: TEST - were not added. If I restarted the application, then again I see 3 lines of output, although there should be 4.
What is wrong?
UPDATED:
I was changed code to this:
override func viewDidLoad() {
super.viewDidLoad()
let fileManager = (NSFileManager.defaultManager())
let directorys : [String]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory,NSSearchPathDomainMask.AllDomainsMask, true) as? [String]
if (directorys! != nil){
let directories:[String] = directorys!;
let dictionary = directories[0];
let plistfile = "favCities.plist"
let plistpath = dictionary.stringByAppendingPathComponent(plistfile);
println("\(plistpath)")
var plistArray = NSArray(contentsOfFile: plistpath) as [Dictionary<String, String>]
var addDic: Dictionary = ["ZMV": "TEST", "Name": "TEST", "Country": "TEST"]
plistArray += addDic
(plistArray as NSArray).writeToFile(plistpath, atomically: false)
var plistArray2 = NSArray(contentsOfFile: plistpath)
for tempDict1 in plistArray2 {
var tempDict2: NSDictionary = tempDict1 as NSDictionary
var cityName: String = tempDict2.valueForKey("Name") as String
var cityZMV: String = tempDict2.valueForKey("ZMV") as String
var cityCountry: String = tempDict2.valueForKey("Country") as String
println("City: \(cityName), ZMV: \(cityZMV), Country: \(cityCountry)")
}
}
else {
println("ERROR!")
}
}
Now when you run the application the number of rows in the output increases:
City: Moscow, ZMV: 00000.1.27612, Country: RU
City: New York, ZMV: 10001.5.99999, Country: US
City: TEST, ZMV: TEST, Country: TEST
City: TEST, ZMV: TEST, Country: TEST
........
BUT! If view the file favCities.plist, which is located in the project folder (Project Navigator in Xcode), it still remains unchanged - there are two lines!
If walk along the path, which is stored in the variable plistpath - /Users/admin/Library/Developer/CoreSimulator/Devices/55FD9B7F-78F6-47E2-9874-AF30A21CD4A6/data/Containers/Data/Application/DEE6C3C8-6A44-4255-9A87-2CEF6082A63A/Documents/
Then there is one more file favCities.plist. It contains all the changes that make the application. What am I doing wrong? How can I see all the changes in a file that is located in the project folder (Project Navigator)?
Mostly, people want to store a list of something, so here is my share on how to do this, also, here I don't copy the plist file, I just create it. The actual saving/loading is quite similar to the answer from Rebeloper
xcode 7 beta, Swift 2.0
saving
func SaveItemFavorites(items : Array<ItemFavorite>) -> Bool
{
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let docuDir = paths.firstObject as! String
let path = docuDir.stringByAppendingPathComponent(ItemFavoritesFilePath)
let filemanager = NSFileManager.defaultManager()
let array = NSMutableArray()
for var i = 0 ; i < items.count ; i++
{
let dict = NSMutableDictionary()
let ItemCode = items[i].ItemCode as NSString
dict.setObject(ItemCode, forKey: "ItemCode")
//add any aditional..
array[i] = dict
}
let favoritesDictionary = NSDictionary(object: array, forKey: "favorites")
//check if file exists
if(!filemanager.fileExistsAtPath(path))
{
let created = filemanager.createFileAtPath(path, contents: nil, attributes: nil)
if(created)
{
let succeeded = favoritesDictionary.writeToFile(path, atomically: true)
return succeeded
}
return false
}
else
{
let succeeded = notificationDictionary.writeToFile(path, atomically: true)
return succeeded
}
}
Little note from the docs:
NSDictionary.writeToFile(path:atomically:)
This method recursively validates that all the contained objects are property list objects (instances of NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary) before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.
So whatever you set at dict.SetObject() should be one of the above mentioned types.
loading
private let ItemFavoritesFilePath = "ItemFavorites.plist"
func LoadItemFavorites() -> Array<ItemFavorite>
{
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let docuDir = paths.firstObject as! String
let path = docuDir.stringByAppendingPathComponent(ItemFavoritesFilePath)
let dict = NSDictionary(contentsOfFile: path)
let dictitems : AnyObject? = dict?.objectForKey("favorites")
var favoriteItemsList = Array<ItemFavorite>()
if let arrayitems = dictitems as? NSArray
{
for var i = 0;i<arrayitems.count;i++
{
if let itemDict = arrayitems[i] as? NSDictionary
{
let ItemCode = itemDict.objectForKey("ItemCode") as? String
//get any additional
let ItemFavorite = ItemFavorite(item: ItemCode)
favoriteItemsList.append(ItemFavorite)
}
}
}
return favoriteItemsList
}
Apart from the fact that the application bundle is read-only (for obvious reasons), since Swift 4 there is PropertyListDecoder/Encoder to read and write property lists without the bridged Objective-C APIs.
First create a struct for the model conforming to Codable
struct FavCity : Codable {
let city, zmv, country: String
}
Then specify two URLs, the url of the default file in the bundle and one URL in the documents directory to be able to modify the file
let fileManager = FileManager.default
let applicationBundleFileURL = Bundle.main.url(forResource: "favCities",
withExtension: "plist")!
let documentsFileURL = try! fileManager.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
.appendingPathComponent("favCities.plist")
the try! doesn't matter because the system makes sure that the folder Documents exists.
Create a new favorite city
let newCity = FavCity(city: "TEST", zmv: "TEST", country: "TEST")
Now read the file in the documents directory. If it doesn't exist read the file in the bundle. Finally append the new city and write the property list data back to the documents directory
let data : Data
do {
data = try Data(contentsOf: documentsFileURL)
} catch {
data = try! Data(contentsOf: applicationBundleFileURL)
}
do {
var favCities = try PropertyListDecoder().decode([FavCity].self, from: data)
favCities.append(newCity)
let newData = try PropertyListEncoder().encode(favCities)
try newData.write(to: documentsFileURL)
} catch {
print(error)
}