How to read/write ICNS resource?

finder
asobjc
foundation

(Jonas Whale) #1
use framework "Foundation"
use framework "AppKit"
use scripting additions

set fromPosix to "/Users/ionah/Desktop/111/"
set toPosix to "/Users/ionah/Desktop/222/"
set imageData to (current application's NSWorkspace's sharedWorkspace's iconForFile:fromPosix)
(current application's NSWorkspace's sharedWorkspace()'s setIcon:imageData forFile:toPosix options:0)
(current application's NSWorkspace's sharedWorkspace's noteFileSystemChanged:toPosix)

When copying a custom icon with the setIcon method, the result is not what is expected:
The pasted icon is always different from the original in terms of colors & shadow.

In late system 9, I had a script that was able to read an write the “icns” resource (number -16455 or -16496).

Is it possible to do this or something similar with AppleScriptObjC?

I can get the icon data from the pasteboard but can’t find how to read/write the resource from/to a file or folder.

use framework "Foundation"
use framework "AppKit"

set POSIXPath to "/Users/ionah/Desktop/333.icns"
set thePasteboard to current application's NSPasteboard's generalPasteboard()
set theData to thePasteboard's dataForType:"com.apple.icns"
theData's writeToFile:POSIXPath atomically:true

(Shane Stanley) #2

Icons are no longer stored as resources – they’re files in the bundle, with a suitable entry in the app’s Info.plist file.

I suspect the problem you’re seeing is because iconForFile: returns a 32 pixel x 32 pixel image, and setIcon:forFile:options: sets an image 512 pixels x 512 pixels.

You will probably do better just to use an image file.

use framework "Foundation"
use framework "AppKit"
use scripting additions

set fromPosix to "/Users/ionah/Desktop/111/Some pic.tif" -- whatever
set toPosix to "/Users/ionah/Desktop/222/"
set theImage to (current application's NSImage's alloc()'s initWithContentsOfFile:fromPosix)
(current application's NSWorkspace's sharedWorkspace()'s setIcon:theImage forFile:toPosix options:0)

(Jonas Whale) #3

I could not imagine such a weird thing!

I’m already using this code to copy/paste the content of an icns file as an icon.

I understand that for a default icon, but my problem is when I attempt to copy the custom icon from a file or folder to another one.


(Jonas Whale) #4

I thought I had found the solution…

use framework "Foundation"
use framework "AppKit"
use scripting additions

set destPath to "/Users/ionah/Desktop/Office Imports/555"

set thePasteboard to current application's NSPasteboard's generalPasteboard()
set listURL to thePasteboard's readObjectsForClasses:{current application's class "NSURL"} options:(missing value)
if listURL's |count|() = 0 then display alert "Le presse-papier n'a renvoyé aucune icône." message "Veuillez copier un élément du Finder." buttons {"Annuler"} cancel button 1
if listURL's |count|() > 1 then display alert "Le presse-papier a renvoyé trop d'éléments." message "Veuillez copier un fichier ou un dossier contenant une icône personnalisée ou fichier ICNS." buttons {"Annuler"} cancel button 1

set thePath to listURL's firstObject()'s |path|()
if thePath's hasSuffix:"icns" then
	set theData to (current application's NSImage's alloc()'s initWithContentsOfFile:thePath)
else
	set listImage to thePasteboard's readObjectsForClasses:{current application's class "NSImage"} options:(missing value)
	set theData to listImage's firstObject()
end if

set theWorkspace to (current application's NSWorkspace's sharedWorkspace())
(theWorkspace's setIcon:(missing value) forFile:destPath options:0)
set theResult to (theWorkspace's setIcon:theData forFile:destPath options:0)

(theWorkspace's noteFileSystemChanged:destPath)
return theResult

Now there’s another problem:
copy the icon from folder 1
paste it on folder 2
copy the icon from folder 2
paste it on folder 3
etc…

The result is a set of folders of which every icon is fading away.

Who is responsible?
The setIcon method or the read data process?
Or maybe the color space of my calibrated screens?


(Shane Stanley) #5

I fear the solution is simply don’t do that :disappointed:


(Jonas Whale) #6

:sob:

[20 chars for the forum]


(Jim Underwood) #7

Shane, this works really well for me. Thanks.


(Jonas Whale) #8

Jim,

The problem is not with initWithContentsOfFile: but with iconForFile: when copying the custom icon of a folder.
I tried to avoid this issue by using the pasteboard content.
It ends with what is described above.

The workaround, for me, is to go back to the old shell command invoking osxutils by Sveinbjorn Thordarson.

		if sourceFile ends with ".icns" then
			(current application's NSWorkspace's sharedWorkspace()'s setIcon:imageData forFile:destPath options:0)
			(current application's NSWorkspace's sharedWorkspace's noteFileSystemChanged:destPath)
		else
			set theShell to "/usr/local/bin/seticon " & quoted form of sourcePath & space & quoted form of destPath
			do shell script theShell
		end if

(Jim Underwood) #9

I’m using @ShaneStanley’s script to copy a custom icon (saved as a PNG file) to a file’s icon.

here’s the script I ran/tested:

use framework "Foundation"
use framework "AppKit"
use scripting additions

set fromPosix to "/Users/Shared/Dropbox/SW/DEV/Images/Evernote-icon-512x512.png" -- whatever
set toPosix to "/Users/Shared/Dropbox/SW/DEV/Images/TEST for Icon Set.EN.Note.inetloc"
set theImage to (current application's NSImage's alloc()'s initWithContentsOfFile:fromPosix)
(current application's NSWorkspace's sharedWorkspace()'s setIcon:theImage forFile:toPosix options:0)

I created the PNG file by:

  1. Copy Icon from Evernote app
  2. Save to file using SnagIT
  3. Resize to 512x512 (it was smaller)
  4. Save as PNG file using SnagIT

I also tried using a TIFF file, but did not see any difference, except that the TIFF file was >> than the PNG file (of course). So I don’t see any need to use TIFF.


(Jonas Whale) #10

@JMichaelTX
Jim, there’s something you’re missing:

It’s not about using initWithContentsOfFile which gets the content of an image file (png, tiff, jpg, icns…).
In this case, the icon placed on a file is perfect.
The problem is with iconForFile that takes the icon resource as you could do by using the file info window in Finder (commad+i).

Try the first script I posted:

use framework "Foundation"
use framework "AppKit"
use scripting additions

set fromPosix to "/Users/ionah/Desktop/111/"
set toPosix to "/Users/ionah/Desktop/222/"
set imageData to (current application's NSWorkspace's sharedWorkspace's iconForFile:fromPosix)
(current application's NSWorkspace's sharedWorkspace()'s setIcon:imageData forFile:toPosix options:0)
(current application's NSWorkspace's sharedWorkspace's noteFileSystemChanged:toPosix)

This is the kind of result I get with this script:

@ShaneStanley

You said iconForFile uses a 32x32 pixels icon rendering.
The strange thing is that if the icon size is under 128 I get the result shown in the above screen capture.

But if icon size is set to a value > 128, I get this:

The icons are perfectly rendered!
(They are the same icons. It’s just the icon view preference that is changed).

Maybe this behavior is only on my Mac…


(Shane Stanley) #11

@ionah, here’s something else to try:

set theURL to listURL's firstObject()
if (theURL's pathExtension()'s isEqualToString:"icns") as boolean then
	set theImage to (current application's NSImage's alloc()'s initWithContentsOfURL:theURL)
else
	set {theResult, theImage, theError} to theURL's getResourceValue:(reference) forKey:(current application's NSURLCustomIconKey) |error|:(reference)
end if

set theWorkspace to (current application's NSWorkspace's sharedWorkspace())
set theResult to (theWorkspace's setIcon:theImage forFile:(theURL's |path|()) options:0)

(Jonas Whale) #12

Hi Shane,

Thanks for the tip, but I can’t test this method: NSURLCustomIconKey is only compatible with 10.12 and I’m stuck with El Capitan.

But I suspect the result will be the same as with the icns ressource extracted from the pasteboard: a clean but fade out icon. (see post 4)

I tried another approach, but it ends the same:

set theIcon to (current application's NSWorkspace's sharedWorkspace()'s iconForFile:sourcePath)
set theData to current application's NSImage's alloc's initWithData:((theIcon's representations()'s lastObject())'s TIFFRepresentation())

set theResult to (current application's NSWorkspace's sharedWorkspace()'s setIcon:theData forFile:destPath options:2)
(current application's NSWorkspace's sharedWorkspace's noteFileSystemChanged:destPath)

I’m wondering if the problem could be with the color space from the image data? And if we could get the icon data with the accurate color space by using core image or core graphics?


(Shane Stanley) #13

The header file and docs say NSURLCustomIconKey was introduced in 10.6.


(Jonas Whale) #14

I think I have to buy a new pair of eyes…

Nevertheless, the following always returns: {true, missing value, missing value}

set theURL to current application's NSURL's fileURLWithPath:thePath
set {theResult, theImage, theError} to theURL's getResourceValue:(specifier) forKey:(current application's NSURLCustomIconKey) |error|:(specifier)

(Naturally, thePath is a folder with a custom icon)


(Jonas Whale) #15

NSURLEffectiveIconKey is returning well the icon pasted on a folder.

But I’m back where it all begins: the icon does not have the aspect it should.capture 001


(Shane Stanley) #16

Or screen. So here’s my script:

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"

set picPath to POSIX path of (choose file with prompt "Choose pic file")
set theImage to current application's NSImage's alloc()'s initWithContentsOfFile:picPath
set thePath to POSIX path of (path to desktop)
set thePath to current application's NSString's stringWithString:thePath
set anNSURL to current application's |NSURL|'s fileURLWithPath:thePath
set destURL to anNSURL's URLByAppendingPathComponent:"Test folder"
set fm to current application's NSFileManager's defaultManager()
fm's createDirectoryAtURL:destURL withIntermediateDirectories:true attributes:(missing value) |error|:(missing value)
set ws to current application's NSWorkspace's sharedWorkspace()
ws's setIcon:theImage forFile:(destURL's |path|()) options:0
set theImage to (ws's iconForFile:(destURL's |path|()))
repeat with i from 1 to 10
	set newURL to (destURL's URLByAppendingPathComponent:("Folder " & i))
	(fm's createDirectoryAtURL:newURL withIntermediateDirectories:true attributes:(missing value) |error|:(missing value))
	(ws's setIcon:theImage forFile:(newURL's |path|()) options:0)
	set theImage to (ws's iconForFile:(newURL's |path|()))
end repeat

And here’s what I get:

Just as you say. Except… I use an iMac with a retina screen, but I also have a non-retina screen attached to it. And when I drag that same window over to the other screen, it now looks like this:

So it looks like a color management issue, and it’s probably worth a bug report.


(Jonas Whale) #17

Totally agree!
But this is half of the problem.
The first issue is with content of file (icns, png, tiff…) like in your posts #16.
The second has something to do with transparency when using iconForFile or NSURLEffectiveIconKey like in my post #15.

But there is something I’m missing: how can a utility like osxutils render the icon perfectly? Is it using something we have no access to, like C+ code?

The author of osxutils has made an opensource project: osxiconutils
Can it be of any help?


(Shane Stanley) #18

It’s using C-based Carbon APIs. And I’m not sure it’s perfectly — more like consistently.


(Jonas Whale) #19

In short, there’s no solution for scripters.
I’m not sure my bug report will change Apple’s desinterest for icon managing.

Since when the Finder’s icon family class is “not available yet”?

Anyway, thank you for taking the time to try to make sense of all this mess.


(Shane Stanley) #20

Well there’s no solution for anyone — the same thing happens in Objective-C. I’ve written up a sample and logged a bug on it.

Probably since the same time as all the other things marked similarly: a long time ago.