How can I remove Dock item in Yosemite?

G’day again, and thanks Shane

I got the original shell script from the net, I think from this site, but I haven’t the foggiest on how to write a shell script on removing an item.

I’ve experimented with…


try
	tell application "Dock" to quit
end try
do shell script ("defaults write com.apple.dock persistent-apps -array-delete '<dict><key>tile-data</key><dict><key>file-label</key> <string>" & "Mail Manager" & "</string> /key><integer>0</integer></dict></dict></dict>'")
try
	tell application "Dock" to activate
end try

But don’t know what the heck I’m doing! I’ve realised setting the path to the item is useless, cause I’ve already deleted it, so assume I’ve got to somehow refer to the item by the ‘file-label’, which has the item name. However, I don’t know if the ‘array-delete’ is correct, let alone how to address the ‘file-label’.

i also assume I’ve got to refer to the item by it’s dictionary matching item number, but how?

Can anyone help me out, please?

Regards

Santa

Model: Late 2014 retina i7, Yosemite
AppleScript: 2.4
Browser: Safari 600.2.5
Operating System: Other

Start by going to Terminal and typing man defaults. That tells you how to read and write. You will need to read the existing value into a string, change it how you want, then write it back.

G’day

I’ve tried to understand the Man pages, and spent the last few days on and off experimenting with code, with no success, so I thought I’d try and just delete the existing Dock item and replace it, but also no luck.

I simply have no idea what I’m doing, and searching the web for examples has proved fruitless.

As this is the last cosmetic item ti be resolved in my App, could someone please, please advise me on the correct code, before I lose my blasted mind.

Regards

Santa

My latest try


tell application "Dock" to quit
set prefsFile to (((path to preferences from user domain) as text) & "com.apple.dock.plist") as alias
tell application "System Events"
	prefsFile as text
	set poArray to (value of property list item "persistent-apps" of property list file the result)
	repeat with i from (count items of poArray) to 1 by -1
		set poItem to item i of poArray
		set tileDataRec to |tile-data| of poItem
		set tileName to |file-label| of tileDataRec
		if tileName = "Mail Manager" then
			do shell script "defaults write  com.apple.dock persistent-apps -array-delete   '<domain>   persistent-apps <key>" & ("Item " & (i - 1) as text) & "'"
		end if
 	end repeat
end tell

Model: Late 2014 retina i7, Yosemite
AppleScript: 2.4
Browser: Safari 600.2.5
Operating System: Other

Hi Santa,

AS Shane said, everything is unreliable. A simple way to remove something from Dock is to gui script Dock.

You can use a macro to remove items from Dock with System Events if you want.

gl,
kel

Kel, I want to re-vist this if possible.

I’m finding under El Capitan, when a dock icon is dragged off, it’s still listed in the persistent-icons .plist. I need to, if possible, either remove it from the .plist, and restore it, or, use the GUI to restore, but how.

Any answers please?

Regards

Santa


tell application "Dock"
	activate
	tell application "System Events" to tell process "Dock"
		try
			# click UI element "Microsoft Excel" of list 1
			tell UI element "Mail Manager" of list 1
				set visible to true
			end tell
			-- modify offsets if hot spot is not centered:
		end try
	end tell
	quit
end tell

AND, the script that worked under Yosemite, but locks up under El Capitan, as the dragged off “Mail Manager” .plist item remains behind.


set persistentAppsList to |persistent-apps| of pListItems
set dockAppsList to {}
try
	repeat with thisRecord in persistentAppsList
		set theTileData to |tile-data| of thisRecord as record
		if (|file-label| of theTileData as text) is "Mail Manager" then
			set end of dockAppsList to (|file-label| of theTileData as text)
		end if
	end repeat
end try
try
	tell application "Dock" to quit
end try
if "Mail Manager" is not in dockAppsList then
	do shell script ("sleep 0.2")
	try
		set item_path to ((path to applications folder) & "Mail Manager:Mail Manager.app" as text) as alias 
	end try
	do shell script ("sleep 0.2")
	try
		set item_path to POSIX path of item_path
		do shell script "defaults write com.apple.dock persistent-apps -array-add '<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>" & item_path & "</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>'"
	end try
end if

Model: late 2014 i7 iMac
AppleScript: 2.8.1
Browser: Safari 601.4.4
Operating System: Mac OS X (10.10)

Hello,

Perhaps that…

-- Replace app_name by the name of the desired application.

tell application "app_name" to quit
delay 0.5
tell application "System Events"
	tell UI element "app_name" of list 1 of process "Dock"		
		if not (exists) then return		
		perform action "AXShowMenu"
		click menu item "Options" of menu 1
		click menu item "Remove from Dock" of menu 1 of menu item "Options" of menu 1
	end tell
end tell

… but how to hide the menu while the operation?

G’day all,

After several days trying, and with help from members of the Applescript developed list, particularly Shane Stanly, This code, that generates a single icon for an app, in the dock, was achieved.


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

# Note my App name is 'Mail Manager.app', and it's located in a 'Mail Manager' folder in Applications.

on installDockIcon()
	-- get the info we need
	set defaultsObject to current application's NSUserDefaults's alloc()'s init()
	set theInfoRecord to (defaultsObject's persistentDomainForName:"com.apple.dock")
	set persistentAppsArray to theInfoRecord's objectForKey:"persistent-apps"
	set tileDataArray to persistentAppsArray's valueForKey:"tile-data"
	set theLabels to tileDataArray's valueForKey:"file-label"
	-- check for match
	set hasMatch to (theLabels's containsObject:"Mail Manager") as boolean
	set existsMMDockIcon to my installDockItemsTest()
	if not hasMatch or not existsMMDockIcon then
		-- make new tile-data record
		set item_path to (path to applications folder as text) & "Mail Manager:Mail Manager.app"
		set item_path to POSIX path of item_path
		set newValue to {|file-data|:{_CFURLString:item_path, _CFURLStringType:0}}
		-- add it to the old tile-data array
		set tileDataArray to tileDataArray's arrayByAddingObject:newValue
		-- add new tile-data array to the persistent-apps array
		set persistentAppsArray to persistentAppsArray's arrayByAddingObject:tileDataArray
		-- update theInfoRecord with the new persistent-apps array
		set theInfoRecordNew to (theInfoRecord's mutableCopy()) -- make mutable copy so you can change it
		theInfoRecordNew's setObject:persistentAppsArray forKey:"persistent-apps"
		-- store the new value
		defaultsObject's setPersistentDomain:theInfoRecordNew forName:"com.apple.dock"
		tell application "Dock" to quit
	end if
end installDockIcon

on installDockItemsTest()
	try
		tell application "System Events"
			tell process "Dock"
				set t to (title of UI elements of list 1)
				if theTest contains {"Mail Manager"} then return true
			end tell
		end tell
	end try
	return false
end installDockItemsTest

Oops, posted the above too soon. Turns out it can trash your Persistent Apps preferences.

This older script, with some of Shanes alterations, works without hassles.

Sorry about that!

Regards

Santa


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

my installDockIcon("Mail Manager")

on installDockIcon(theApp)
	try
		set item_path to ((path to applications folder) & "Mail Manager:" & theApp & ".app" as text) # NOTE: my App is  in a folder called 'Mail Manager'
	end try
	try
		
		set theInfo to (current application's NSUserDefaults's alloc()'s init()'s persistentDomainForName:"com.apple.dock") as record
		set theMatches to get theInfo's |persistent-apps|
		set MatchingList to {}
		set x to 0
		repeat with thisRecord in theMatches
			set x to x + 1
			set theTestMatch to get thisRecord's |tile-data|
			set theMMMatch to theTestMatch's |file-label|
			if theMMMatch as text is theApp then
				set end of MatchingList to item x of theMatches
			end if
		end repeat
		set existsMMDockIcon to my installDockItemsTest(theApp)
		if (count of MatchingList) < 1 or not existsMMDockIcon then
			try
				set item_path to POSIX path of item_path
				do shell script "defaults write com.apple.dock persistent-apps -array-add '<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>" & item_path & "</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>'"
				tell application "Dock" to quit # Automatically re-starts
			end try
		end if
	end try
end installDockIcon

on installDockItemsTest(theApp)
	try
		tell application "System Events"
			tell process "Dock"
				set t to (title of UI elements of list 1)
				if theTest contains {theApp} then return true
			end tell
		end tell
	end try
	return false
end installDockItemsTest

Is writing plist files with Applescript just broken now?

I’ve got a similar issue. We deploy branches of code for testing, so sometimes a user is moved to a whole different suite of our apps. All our internal references between apps are consistent in-branch, but sometimes users add applications to the dock - indeed, it’s handy for some of these apps to live in the dock. But then the user ends up with their dock copy pointing to the wrong branch… whatever apps are in their dock now don’t change when they change branches. Of course we can tell them to change them, but user behavior is usually the hardest thing to modify.

I’ve been making various attempts to remove the Dock items from the wrong branch automatically and add in the identical ones from the right branch. Adding them is no problem, it’s getting rid of the ones that from the old branch that I can’t figure out.

Any time I attempt to write the com.apple.dock.plist file, it ends up being 0 bytes.

It looks like scripting dockutil https://github.com/kcrawford/dockutil would work, but I’m always loathe to add another dependency that must be installed on all end user systems.

Here’s one of my scripting attempts, based on the work of alastor933 above. It all looks great until the last line doing the write.

set prefsFile to ((path to preferences from user domain) as text) & "com.apple.dock copy.plist"
set removeApps to {"TextEdit"}
tell application "System Events"
	set dockRec to (value of property list file prefsFile)
	set appRecords to |persistent-apps| of dockRec
	set newAppRecord to {}
	repeat with anAppRecord in appRecords
		if |file-label| of |tile-data| of anAppRecord is not in removeApps then copy contents of anAppRecord to end of newAppRecord
	end repeat
	set |persistent-apps| of dockRec to newAppRecord
	set (value of property list file prefsFile) to dockRec
end tell

If I’m reading the example with Shane’s code correctly, the ASObjC still is not writing the preferences file, only reading it so they can parse whether or not the App in question is already in the dock. The write only occurs via “do shell script defaults write…,” which already works fine for me… it’s the removal I can’t figure out.

Ah, I found this very relevant looking thread:

https://macscripter.net/viewtopic.php?id=41893

But I’m having trouble with the ASObjC handlers… I think how ASObjC works has changed since 2013… I can’t get them to work in-script with “Use Framework Foundation,” I get:


And in a library, I get "script [library name] doesn’t understand the “readPlistAt” message.

I don’t think there still is an “click the Bundle Contents button and check the AppleScript/Objective0bjC Library checkbox”

Are you putting the use framework statements in your script libraries that use ASObjC?

Yes. I just use the “Open this Scriplet in your Editor” button on MacScripter to open your script, which does have “Use Framework Foundation” at the top. I saved it as a new library, just called “test” for now.

Then I’m just calling it like this:

set prefsFile to (POSIX path of (path to preferences from user domain)) & "com.apple.dock copy.plist"
tell script "test" to set dockRecC to readPlistAt(prefsFile)

And I get:

The handler in Shane’s original script takes an “interleaved” parameter, not a “positional” one. So:

set prefsFile to (POSIX path of (path to preferences from user domain)) & "com.apple.dock copy.plist"
tell script "test" to set dockRecC to readPlistAt:prefsFile

What Nigel said, but it won’t compile like that – interleaved parameters must follow some kind of possessive. For example:

set dockRecC to script "test"'s readPlistAt:prefsFile

Or use underscores:

tell script "test" to set dockRecC to readPlistAt_(prefsFile)

Thanks!

Obviously, I still haven’t found time to go through Everyday Applescript Objective C.

I had guessed Nigel’s suggestion to try, but hit the error you mentioned.

I also found I can just put the handlers in the script instead of a library and use

set dockRecC to my readPlistAt:prefsPOSIX
  • Tom.

For reference, if anybody else needs to remove items from their Dock, this seems to work:

use framework "Foundation"
use scripting additions

set prefsFile to (POSIX path of (path to preferences from user domain)) & "com.apple.dock.plist"
set removeApps to {"TextEdit"}
set dockRec to my readPlistAt:prefsFile
set appRecords to |persistent-apps| of dockRec
set newAppRecord to {}
repeat with anAppRecord in appRecords
	if |file-label| of |tile-data| of anAppRecord is not in removeApps then copy contents of anAppRecord to end of newAppRecord
end repeat
set |persistent-apps| of dockRec to newAppRecord
my saveRecord:dockRec toPlistAt:prefsFile
repeat 2 times
	do shell script "killall Dock"
	delay 3
end repeat

on saveRecord:theRecord toPlistAt:posixPath
	set theDict to current application's NSDictionary's dictionaryWithDictionary:theRecord
	set thePath to current application's NSString's stringWithString:posixPath
	set thePath to thePath's stringByExpandingTildeInPath()
	theDict's writeToFile:thePath atomically:true
end saveRecord:toPlistAt:

on readPlistAt:posixPath
	set thePath to current application's NSString's stringWithString:posixPath
	set thePath to thePath's stringByExpandingTildeInPath()
	set theDict to current application's NSDictionary's dictionaryWithContentsOfFile:thePath
	return theDict as record
end readPlistAt:

Note, it’s unclear to me why I need to restart the Dock twice with a delay in between, but so far it’s working reliably on several tests with this. I tried different numbers of repeats and different delays… I wasn’t exhaustive about it, but the 2 repeats with a 3 second delay in between seems to be reliable on my machine. Your mileage may vary.

I don’t remember exactly from whom I got the following script. It is designed to remove the application icon in the Dock and is more compact.


use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

removeDockItem("VLC")

on removeDockItem(dockItemName)
	set dockDefaults to current application's class "NSUserDefaults"'s alloc()'s initWithSuiteName:"com.apple.dock"
	set apps to dockDefaults's objectForKey:"persistent-apps"
	if (apps's |count|() > 0) then
		set filter to current application's class "NSPredicate"'s predicateWithFormat:("%K != %@") argumentArray:({"tile-data.file-label", dockItemName})
		set appsCopy to apps's filteredArrayUsingPredicate:(filter)
		if (appsCopy's |count|() < apps's |count|()) then
			tell dockDefaults to setObject:(appsCopy) forKey:("persistent-apps")
			tell application "Dock" to quit -- Possibly a bit extreme.
		end if
	end if
end removeDockItem

Thanks! I’ll give that a shot.

Interestingly, the “tell dockDefaults to setObject:(appsCopy) forKey:(“persistent-apps”)” seems to make it so that a single “tell application “Dock” to quit” is sufficient.

I didn’t experiment with this much; I did use the “tell…quit” statement in your script, and maybe the difference was that vs. the “shell…killall” statement. Not important for me to get to the bottom of, this is working better.

I kept the less succinct Applescript Repeat Loop just because I’m familiar with it and can edit it as needed… I’m thinking I might want to use this script to re-arrange the dock to. The idea here is that when a user changes an entire branch of our code to test, the internal references in all our apps are consistent within whatever branch they move to, but if they added an application to the dock, then it’s got a hard-coded path pointing to something on the wrong branch… which can cause trouble.

So I want the script that switches their branch to also find and remove any apps pointing to apps on the old branch, and then add the equivalent script from the new branch.

But if I just run the handler as I have it, and then run a standard shell line to add the correct copy using (do shell script "defaults write …) then it’s going to re-arrange their dock.

So maybe I’ll also have this re-arrange it back for them. I’ll probably wait and see if there are any complaints.

Thanks again, Shane and KniazidisR! Great stuff, super helpful.

In case it’s useful to someone, here’s more complete code that keeps track of which apps were in the dock, and will only add back ones it actually removed.


use framework "Foundation"
use scripting additions


set checkApps to {"TextEdit","Mail","Whatever App Name," "Etc."}
set removedApps to remove_apps_from_dock(checkApps)
tell script "My Additions" to set scriptsFolder to POSIX path of get_scripts_folder()
set missingApps to {}
set appPaths to {}
repeat with anApp in removedApps
	try
		set thisAppPath to scriptsFolder & anApp & ".app"
		set thisAlias to (thisAppPath as POSIX file) as alias
		set appPaths to appPaths & thisAppPath
	on error
		set missingApps to missingApps & anApp
	end try
end repeat
if (count of missingApps) > 0 then
	set missinglist to ""
	repeat with anApp in missingApps
		set missinglist to return & anApp & missinglist
	end repeat
	display dialog "The following applications were in your dock, but don't exist on the new branch. The old copies will be removed from your dock and not replaced." & missinglist
end if
if (count of removedApps) > 0 then
	repeat with anAppPath in appPaths
		add_app_to_dock(anAppPath)
	end repeat
	tell application "Dock" to quit
end if

on remove_apps_from_dock(removeApps)
	set dockDefaults to current application's class "NSUserDefaults"'s alloc()'s initWithSuiteName:"com.apple.dock"
	set appRecord to dockDefaults's objectForKey:"persistent-apps"
	set newAppRecord to {}
	set removedApps to {}
	repeat with anAppRecord in appRecord
		set appName to (|file-label| of |tile-data| of anAppRecord) as text
		if appName is in removeApps then
			set removedApps to removedApps & appName
		else
			copy contents of anAppRecord to end of newAppRecord
		end if
	end repeat
	tell dockDefaults to setObject:(newAppRecord) forKey:("persistent-apps")
	return removedApps
end remove_apps_from_dock

on add_app_to_dock(appPath)
	if class of appPath is alias then set appPath to POSIX path of appPath
	do shell script "defaults write com.apple.dock persistent-apps -array-add '<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>" & appPath & "</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>'"
end add_app_to_dock

It calls a “get_scripts_folder()” handler from a library that I’m not including, because it would be localized to an individual’s setup. Replace as appropriate for wherever your apps are.