Post

Replies

Boosts

Views

Activity

Reading Finder Info ATTR_CMN_FNDRINFO with getattrlistbulk
After successfully implementing a scan with getattrlistbulk (thanks to https://developer.apple.com/forums/thread/656787/), I'm now trying for each file to get whether the extension is shown in the Finder or not. I think this could be read from the Finder Info (i.e.ATTR_CMN_FNDRINFO), although I'm not sure, since there doesn't seem do be any documentation about what this attribute actually contains. From the setattrlist documentation archive it seems that it is char[32], and I've tried different changes within readUnaligned() so that it would work with an array, but I couldn't get it to work. Below is my current implementation. The comment How to get Finder Info? shows where the implementation currently doesn't work. class AppDelegate: NSObject, NSApplicationDelegate { private static var attributeKeys: attrlist = { var attributeKeys = attrlist() attributeKeys.bitmapcount = u_short(ATTR_BIT_MAP_COUNT) attributeKeys.commonattr = attrgroup_t(ATTR_CMN_RETURNED_ATTRS) | attrgroup_t(bitPattern: ATTR_CMN_ERROR | ATTR_CMN_NAME | ATTR_CMN_OBJTYPE | ATTR_CMN_MODTIME | ATTR_CMN_FNDRINFO | ATTR_CMN_FILEID) attributeKeys.fileattr = attrgroup_t(bitPattern: ATTR_FILE_DATALENGTH) return attributeKeys }() private let bufferWithAlignment16 = UnsafeMutableRawBufferPointer.allocate(byteCount: 256, alignment: 16) func applicationDidFinishLaunching(_ notification: Notification) { let openPanel = NSOpenPanel() openPanel.canChooseDirectories = true openPanel.canChooseFiles = false openPanel.runModal() try! scan(directoryPath: openPanel.urls[0].path) } deinit { bufferWithAlignment16.deallocate() } func scan(directoryPath: String) throws { let fileDescriptor = open(directoryPath, O_RDONLY) if fileDescriptor < 0 { throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) } defer { let result = close(fileDescriptor) assert(result == 0) } let attributeBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 2048, alignment: 16) defer { attributeBuffer.deallocate() } while true { let itemCount = Int(getattrlistbulk(fileDescriptor, &AppDelegate.attributeKeys, attributeBuffer.baseAddress!, attributeBuffer.count, 0)) if itemCount == 0 { return } else if itemCount > 0 { var entryOffset = attributeBuffer.baseAddress! for _ in 0..<itemCount { let length = Int(entryOffset.load(as: UInt32.self)) try unpackResources(at: entryOffset + MemoryLayout<UInt32>.size, parentDirectory: directoryPath) entryOffset += length } } else if errno != EINTR { throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) } } } private func unpackResources(at attributeOffset: UnsafeMutableRawPointer, parentDirectory: String) throws { var attributeOffset = attributeOffset let returned = attributeOffset.load(as: attribute_set_t.self) attributeOffset += MemoryLayout<attribute_set_t>.size var error: Error? if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_ERROR)) != 0 { error = NSError(domain: NSPOSIXErrorDomain, code: Int(attributeOffset.load(as: UInt32.self))) attributeOffset += MemoryLayout<UInt32>.size } let name: String if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_NAME)) != 0 { let nameInfo = attributeOffset.load(as: attrreference_t.self) name = String(cString: (attributeOffset + Int(nameInfo.attr_dataoffset)).assumingMemoryBound(to: CChar.self)) attributeOffset += MemoryLayout<attrreference_t>.size } else { name = "" } let fileType: fsobj_type_t if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_OBJTYPE)) != 0 { fileType = attributeOffset.load(as: fsobj_type_t.self) attributeOffset += MemoryLayout<fsobj_type_t>.size } else { fileType = VNON.rawValue } var modificationDate: Date? if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_MODTIME)) != 0 { modificationDate = Date(timeIntervalSince1970: TimeInterval(readUnaligned(pointer: attributeOffset, as: timespec.self).tv_sec)) attributeOffset += MemoryLayout<timespec>.size } // How to get Finder Info? let finderInfo: [Int8] if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_FNDRINFO)) != 0 { finderInfo = readUnaligned(pointer: attributeOffset, as: [Int8].self) attributeOffset += MemoryLayout<Int8>.size } else { finderInfo = [] } let fileId: UInt64 if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_FILEID)) != 0 { fileId = readUnaligned(pointer: attributeOffset, as: UInt64.self) attributeOffset += MemoryLayout<UInt64>.size } else { fileId = 0 } let size: Int64 if (returned.fileattr & attrgroup_t(bitPattern: ATTR_FILE_DATALENGTH)) != 0 { size = Int64(readUnaligned(pointer: attributeOffset, as: off_t.self)) attributeOffset += MemoryLayout<off_t>.size } else { size = 0 } if let error = error { throw error } let isDirectory = fileType == VDIR.rawValue let isSymbolicLink = fileType == VLNK.rawValue print(name, isDirectory, isSymbolicLink, modificationDate as Any, finderInfo, fileId, size) } private func readUnaligned<Result>(pointer: UnsafeRawPointer, as: Result.Type) -> Result { bufferWithAlignment16.copyMemory(from: UnsafeRawBufferPointer(start: pointer, count: MemoryLayout<Result>.size)) return bufferWithAlignment16.baseAddress!.load(as: Result.self) } }
1
0
525
Apr ’23
Setting URLResourceKey.fileSecurityKey raises error on some systems
A customer reported that when my app creates directories on their NAS, an error is shown. With their cooperation I boiled the source of the error down to setting URLResourceKey.fileSecurityKey on the directory URL, or setting any of FileAttributeKey.groupOwnerAccountID, .groupOwnerAccountName, .ownerAccountID or .ownerAccountName with FileManager. I thought that maybe URLResourceKey.volumeSupportsExtendedSecurityKey would allow me to determine in advance if setting any of these attributes works, but it seems that the result is false on one of my exFAT drives which doesn't yield any error when setting any of the attributes. I don't even know what "extended security" means in this case, and it doesn't seem to be documented. Should I rely on URLResourceKey.volumeSupportsExtendedSecurityKey? I tried running chown -vv myusername:admin on a file on that exFAT drive: even if the output includes the text 501:20 -&gt; 501:80, which I assume means that the group should have been changed to admin, running the command again yields the exact same output and running stat shows that the group is still staff.
1
0
931
Apr ’23
Error when trying to set access permissions of file owned by user
I'm trying to copy the access permissions of a source directory to a destination directory. The Finder Info panel of the source directory shows the following permissions: Source directory: I: Read & write Everyone: No access Destination directory: I: Read & write Staff: Read only Everyone: Read only The following code succeeds in creating a file in the destination directory, but then fails when setting the URL.fileSecurityKey resource: let openPanel = NSOpenPanel() openPanel.canChooseDirectories = true openPanel.canChooseFiles = false openPanel.runModal() let source = openPanel.urls[0] openPanel.runModal() var destination = openPanel.urls[0] do { try Data().write(to: destination.appendingPathComponent("asd")) try destination.setResourceValues(source.resourceValues(forKeys: [.fileSecurityKey])) } catch { fatalError(error.localizedDescription) } The error message is: You don’t have permission to save the file “destination” in the folder “parent” I thought that an app run by a user has the same permissions as the user itself, and since I have read & write access to both the source and destination directories, the app should be able to copy the access permissions without issues. What is the problem?
1
0
407
Apr ’23
SecItemCopyMatching doesn't find key stored with SecItemAdd
I'm writing an app that uses the App Store Connect API and would like to store the private key contained in the .p8 file downloaded from the website in the keychain. The following code successfully stores a key in the keychain with SecItemAdd, then tries to read it immediately, but without success (the error code of SecItemCopyMatching is errSecItemNotFound and the console outputs nil). Running the code a second time causes SecItemAdd to fail with code errSecDuplicateItem, and SecItemCopyMatching again with code errSecItemNotFound. What am I doing wrong? class AppDelegate: NSObject, NSApplicationDelegate { private let secApplicationTag = "com.example.app".data(using: .utf8)! func applicationDidFinishLaunching(_ aNotification: Notification) { do { try storeKey("asdf") print(try readKey() as Any) } catch { print(error) } } private func storeKey(_ key: String) throws { guard let data = Data(base64Encoded: key) else { fatalError() } let status = SecItemAdd([kSecClass as String: kSecClassKey, kSecAttrLabel as String: "Asdf", kSecAttrApplicationTag as String: secApplicationTag, kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, kSecValueData as String: data, kSecAttrSynchronizable as String: true] as [String: Any] as CFDictionary, nil) if status != errSecSuccess { throw NSError(domain: NSOSStatusErrorDomain, code: Int(status)) } } private func readKey() throws -> String? { var item: CFTypeRef? let status = SecItemCopyMatching([kSecClass as String: kSecClassKey, kSecAttrApplicationTag as String: secApplicationTag, kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, kSecReturnData as String: true] as [String: Any] as CFDictionary, &item) switch status { case errSecSuccess: let data = item as! Data return (data as Data).base64EncodedString() case errSecItemNotFound: return nil default: throw NSError(domain: NSOSStatusErrorDomain, code: Int(status)) } } }
1
0
971
Apr ’23
Components of NSDatePicker inside NSTableView cannot be selected
In my table view, each row contains a popup button and a date picker. When clicking on the popup button, it behaves as expected: the menu appears and I can change the selected item. But when clicking on the date components of date picker, the table view row is selected, as if I'm clicking on a non-interactive control. Only when clicking on the stepper (the up/down arrows on the right) does the first date component get selected, which then allows me to click on the other ones as well. Can I change this behaviour so that clicking on the date picker behaves as if I'm directly interacting with it, instead of the table view?
Topic: UI Frameworks SubTopic: AppKit Tags:
1
0
471
Apr ’23
SecItemAdd creates keychain item with label "octagon-com.apple.security.keychain" instead of provided kSecAttrLabel
I use the following code to save a private key with a custom label, but the Keychain app shows an entry with name and account octagon-com.apple.security.keychain and type Octagon Account State (com.apple.security.keychain,defaultContext). (This entry, by the way, stays in the Keychain app even after trying to remove it from the Keychain app itself.) Can these values be customized, and what is kSecAttrLabel if it's not displayed in the Keychain app? The documentation only reads The corresponding value is of type CFString and contains the user-visible label for this item. class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { do { try storeKey("asdf") } catch { print(error) } } private func storeKey(_ key: String) throws { guard let data = Data(base64Encoded: key) else { fatalError() } let status = SecItemAdd([kSecClass as String: kSecClassKey, kSecAttrLabel as String: "Asdf", kSecAttrApplicationTag as String: "com.example.app2".data(using: .utf8)!, kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, kSecValueData as String: data, kSecAttrSynchronizable as String: true] as [String: Any] as CFDictionary, nil) if status != errSecSuccess { throw NSError(domain: NSOSStatusErrorDomain, code: Int(status)) } } }
1
0
1.8k
Apr ’23
App Store Connect API: why do I only have to provide a checksum when uploading app screenshots and not for app event screenshots?
When uploading app screenshots, I have to provide a sourceFileChecksum and uploaded flag: https://developer.apple.com/documentation/appstoreconnectapi/appscreenshotupdaterequest/data/attributes But that's not the case for app event screenshots, I only have to provide the uploadedflag: https://developer.apple.com/documentation/appstoreconnectapi/appeventscreenshotupdaterequest/data/attributes The same is true for app previews and app event video clips. Why is this different?
1
0
461
Jul ’23
Determinate spinning NSProgressIndicator doesn't adapt to frame size and gets cut off
The following code should produce 6 spinning progress indicators of varying sizes: 3 indeterminate and 3 determinate ones. The first two of the 3 determinate ones are either entirely or partially cut off, which doesn't happen with the indeterminate ones. What's the problem? var progress = NSProgressIndicator(frame: CGRect(x: 0, y: 0, width: 16, height: 16)) progress.style = .spinning view.addSubview(progress) progress = NSProgressIndicator(frame: CGRect(x: 50, y: 0, width: 24, height: 24)) progress.style = .spinning view.addSubview(progress) progress = NSProgressIndicator(frame: CGRect(x: 100, y: 0, width: 32, height: 32)) progress.style = .spinning view.addSubview(progress) progress = NSProgressIndicator(frame: CGRect(x: 150, y: 0, width: 16, height: 16)) progress.style = .spinning progress.isIndeterminate = false progress.doubleValue = 50 progress.maxValue = 100 view.addSubview(progress) progress = NSProgressIndicator(frame: CGRect(x: 200, y: 0, width: 24, height: 24)) progress.style = .spinning progress.isIndeterminate = false progress.doubleValue = 50 progress.maxValue = 100 view.addSubview(progress) progress = NSProgressIndicator(frame: CGRect(x: 250, y: 0, width: 32, height: 32)) progress.style = .spinning progress.isIndeterminate = false progress.doubleValue = 50 progress.maxValue = 100 view.addSubview(progress)
Topic: UI Frameworks SubTopic: AppKit Tags:
1
0
702
Aug ’23
UISplitViewController displays button to change the display mode even when presentsWithGesture = false
I have a document-based app which displays a view controller with a navigation bar (i.e. it's inside a navigation controller) which is also the detail view controller of a split view controller. I'm using this sample code to just show a back button in the navigation bar of the document view controller: class DocumentViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() splitViewController!.presentsWithGesture = false navigationItem.backAction = UIAction(handler: { _ in }) } } In a regular width, this works as expected: only the back button is displayed. In a compact width such as a portrait iPhone, the split view seems to display the navigation bar button to show the master view controller (the one with the icon to the right of the back button, labeled “Root View Controller"). According to the documentation of presentsWithGesture: When this property is false, the split view controller doesn’t install a gesture recognizer for changing the display mode. The split view controller also doesn’t display a button to change the display mode. Is this a bug, or an error in the documentation, or am I doing something wrong?
Topic: UI Frameworks SubTopic: UIKit Tags:
1
0
614
Aug ’23
Get executable path from audit token provided by NEFilterDataProvider
I'm using this code to get the path of an executable from the audit token provided in NEFilterDataProvider.handleNewFlow(_:), forwarded from the Network Extension to the main app via IPC: private func securePathFromAuditToken(_ auditToken: Data) throws -> String? { let secFlags = SecCSFlags() var secCode: SecCode? var status = SecCodeCopyGuestWithAttributes(nil, [kSecGuestAttributeAudit: auditToken] as CFDictionary, secFlags, &secCode) guard let secCode = secCode else { throw SecError(status) } var secStaticCode: SecStaticCode? status = SecCodeCopyStaticCode(secCode, secFlags, &secStaticCode) guard let secStaticCode = secStaticCode else { throw SecError(status) } var url: CFURL? status = SecCodeCopyPath(secStaticCode, secFlags, &url) guard let url = url as URL? else { throw NSError(domain: NSOSStatusErrorDomain, code: Int(status)) } return nil } But it seems that some processes like trustd, rapportd, nsurlsessiond and timed have a non-nil path. For these executables I have to resort to this code, which I have read is not as secure: private func insecurePathFromAuditToken(_ auditToken: Data) throws -> String? { if auditToken.count == MemoryLayout<audit_token_t>.size { let pid = auditToken.withUnsafeBytes { buffer in audit_token_to_pid(buffer.baseAddress!.assumingMemoryBound(to: audit_token_t.self).pointee) } let pathbuf = UnsafeMutablePointer<Int8>.allocate(capacity: Int(PROC_PIDPATHINFO_SIZE)) defer { pathbuf.deallocate() } let ret = proc_pidpath(pid, pathbuf, UInt32(PROC_PIDPATHINFO_SIZE)) if ret <= 0 { throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) } return String(cString: pathbuf) } return nil } This seems to happen with both NEFilterFlow.sourceAppAuditToken and sourceProcessAuditToken. Is this expected? Can it really be that some executables shipped with macOS are not signed?
1
0
769
Oct ’23
New iPad 13" screenshots with App Store Connect API
The App Store Connect API documentation still doesn't list the new 13" iPad display type: https://developer.apple.com/documentation/appstoreconnectapi/screenshotdisplaytype When adding screenshots to 13" iPads on the website, they still seem to use the display type APP_IPAD_PRO_3GEN_129 when listed by the API, and uploading to that same type uploads them to the 13" display type instead, but then there is the requirement that one still has to upload screenshots for 12.9" display type, without an apparent way of doing so. I would expect to have an option to upload to 13" display type that is also used for 12.9" display type. Do we have to wait for Apple to update the documentation or does someone know a workaround?
1
1
1.7k
May ’24
Open new document in SwiftUI on iOS
I'm trying to use @Environment(\.openDocument) private var openDocument but it seems to only be available on macOS. How can I open a document programmatically on iOS? My app has a custom interface for browsing and opening files, so that a tapped file should be shown in the current window, replacing the current file if it exists.
1
0
491
Jun ’24
Xcode fails to compile visionOS app that has a Front layer in AppIcon asset
I'm trying to create the app icon for my visionOS app. The Assets catalog already contains AppIcon for iOS and I've added another AppIcon for visionOS. If I only add the Back layer of the visionOS icon, compiling succeeds despite there being an error The visionOS App Icon "AppIcon" must have at least 2 layers with applicable content. Although it has 3 layers, only 1 has applicable content. As soon as I add one of the other two layers, say the Front layer, compiling fails, but this time Xcode only shows a generic compiler error Command CompileAssetCatalogVariant emitted errors but did not return a nonzero exit code to indicate failure If I click that message, a long build log opens containing among other things: 2024-10-31 11:28:15.258 AssetCatalogSimulatorAgent[66919:1456355] -[TDTextureRawRenditionSpec _createImageRefWithURL:andDocument:format:] Texture image asset file:///~/Documents/apps/myApp/xcode/iOS/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/icon_layer3.heic not in one of supported formats ... libc++abi: terminating due to uncaught exception of type NSException Command CompileAssetCatalogVariant failed with a nonzero exit code What is the problem? I filed FB15642844.
1
0
489
Nov ’24
SKNode.zPosition causes nodes to flicker by reordering them for 1 frame
When running the sample code below, every 3 seconds the middle sprite is replaced by a new one. When this happens, most of the time a flicker is noticeable. When recording the screen and stepping through the recording frame by frame, I noticed that the flicker is caused by a temporary reordering of the nodes’. Below you find two screenshots of two consecutive frames where the reordering is clearly visible. This only happens for a SpriteKit scene used as an overlay for a SceneKit scene. Commenting out buttons.zPosition = 1 or avoiding the fade in/out animations solves the issue. I have created FB15945016. import SceneKit import SpriteKit class GameViewController: NSViewController { let overlay = SKScene() var buttons: SKNode! var previousButton: SKSpriteNode! var nextButton: SKSpriteNode! var pageContainer: SKNode! var pageViews = [SKNode]() var page = 0 override func viewDidLoad() { super.viewDidLoad() let scene = SCNScene(named: "art.scnassets/ship.scn")! let scnView = self.view as! SCNView scnView.scene = scene overlay.anchorPoint = CGPoint(x: 0.5, y: 0.5) scnView.overlaySKScene = overlay buttons = SKNode() buttons.zPosition = 1 overlay.addChild(buttons) previousButton = SKSpriteNode(systemImage: "arrow.uturn.backward.circle") previousButton.position = CGPoint(x: -100, y: 0) buttons.addChild(previousButton) nextButton = SKSpriteNode(systemImage: "arrow.uturn.forward.circle") nextButton.position = CGPoint(x: 100, y: 0) buttons.addChild(nextButton) pageContainer = SKNode() pageViews = [SKSpriteNode(systemImage: "square.and.arrow.up"), SKSpriteNode(systemImage: "eraser")] overlay.addChild(pageContainer) setPage(0) Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { [self] _ in setPage((page + 1) % 2) } } func setPage(_ page: Int) { pageViews[self.page].run(.sequence([ .fadeOut(withDuration: 0.2), .removeFromParent() ]), withKey: "fade") self.page = page let pageView = pageViews[page] pageView.alpha = 0 pageView.run(.fadeIn(withDuration: 0.2), withKey: "fade") pageContainer.addChild(pageView) } override func viewDidLayout() { overlay.size = view.frame.size } } extension SKSpriteNode { public convenience init(systemImage: String) { self.init() let width = 100.0 let image = NSImage(systemSymbolName: systemImage, accessibilityDescription: nil)!.withSymbolConfiguration(.init(hierarchicalColor: NSColor.black))! let scale = NSScreen.main!.backingScaleFactor image.size = CGSize(width: width * scale, height: width / image.size.width * image.size.height * scale) texture = SKTexture(image: image) size = CGSize(width: width, height: width / image.size.width * image.size.height) } }
1
0
708
Nov ’24
Feedback Assistant: „This Feedback will no longer be monitored, and incoming messages will not be reviewed.“
Every now and then I get this very frustrating message on Feedback Assistant. For instance, in FB14696726 I reported an issue with the App Store Connect API. 4 weeks later, I got a reply, asking among other things for a „correlation key and Charles log“. I immediately replied saying that I didn‘t know what those are, and they replied After reviewing your feedback, it is unclear what the exact issue is. I pointed out that I had asked a question which was left unanswered, and they replied explaining what the correlation key is. Then I asked again what the Charles log is. They replied The Apple Developer website provides access to a range of videos covering various topics on using and developing with Apple technologies. You can find these videos on our Development Videos page: http://developer.apple.com/videos. I opened the link and searched for „Charles“ but there were no results, so I asked to kindly point me to the video answering my question. They replied 3 months later (today): Following up on our last message, we believe this issue is either resolved or not reproducible with the information provided and will now consider this report closed internally. This Feedback will no longer be monitored, and incoming messages will not be reviewed. This is not the first time I ask for clarification and get back a message basically telling me that „we won‘t answer any questions you may have and won‘t hear anything you still may have to say about this issue“. They didn‘t even ask me to verify if the issue is resolved or not, like they sometimes do. No, they just shut the door in my face. I just wanted to share this frustrating experience. Perhaps an Apple engineer wants to say something about it or a developer has had a similar experience?
1
0
574
Dec ’24