Applescript to manage keyboard contextual services

Although it has not been updated since '09, I kept using Apple’s utility Services Manager.app as a much better alternative to the System Preferences → Keyboard → Shortcuts → Services, where the small window makes it difficult to get a quick overview and manage a long list of the existing keyboard contextual services.
Unfortunately, that app is now incompatible with Sierra.

II thought of writing a quick applescript to reproduce the old app’s functions.

This is an extract from a pbs.plist file:

This is what I have:

set pbs_file to (path to desktop as text) & "pbs.plist"
tell application "System Events"
	set servicesStatusList to NSServicesStatus of (get value of contents of property list file pbs_file)
end tell
--> {|com.barebones.textwrangler - textwrangler/open file in textwrangler - openfile|:{enabled_context_menu:false, enabled_services_menu:false}, |com.apple.dictionary - look up in dictionary - dolookupservice|:{enabled_context_menu:true}, |com.apple.grab - capture screen using timer - timedselection|:{enabled_context_menu:true, enabled_services_menu:true}, |net.publicspace.abfr10 - a better finder rename 10. - openfile|:{enabled_context_menu:true, enabled_services_menu:true}, |com.lemkesoft.graphicconverter10 - graphicconverter 10: remove metadata - serviceremovemetadata|:{enabled_context_menu:false, enabled_services_menu:false}}

Note that the extracted “servicesStatusList” doesn’t preserve the XML order, but that’s not really important at this stage.

Unfortunately, I’m stack here and need help because I can’t work out how to convert the “servicesStatusList” list to a list of lists with the following format:

where:

  • there is one service per row;
  • the app and service names to be extracted from the service fun name string at key 1;
  • the converted list be sorted by the app name.
    For instance:

Please help with the conversion.

Once I have the converted list, I’ll use it as input to a Myriad Table for display and setting the services and context menu status using:

set pbs_file to (path to desktop as text) & "pbs.plist"
set s_full_name to "com.apple.Grab - Capture Screen using Timer - timedSelection"
set s_status to true -- or false
set c_status to true -- or false

ServiceAndContextMenuStatus(pbs_file, s_full_name, s_status, c_status)

on ServiceAndContextMenuStatus(pbs_file, s_full_name, s_status, c_status)
	tell application "System Events"
		set service_plist to (get property list item s_full_name of property list item "NSServicesStatus" of property list file pbs_file)
		tell service_plist
			set value of property list item "enabled_services_menu" to s_status
			set value of property list item "enabled_context_menu" to c_status
		end tell
	end tell
	do shell script " defaults read " & quoted form of POSIX path of pbs_file
end ServiceAndContextMenuStatus

Also, if there is a better way of doing it, I’m open to any suggestion.

Is this the sort of thing you’re after?

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
use tablesLib : script "Myriad Tables Lib" version "1.0.6"

-- get defaults
set defaults to current application's NSUserDefaults's alloc()'s init()
set theDict to (defaults's persistentDomainForName:"pbs")
set theValues to theDict's objectForKey:"NSServicesStatus"
set theKeys to theValues's allKeys()'s sortedArrayUsingSelector:"compare:"
-- build list for table
set theList to {}
set saveTID to AppleScript's text item delimiters
set AppleScript's text item delimiters to {" - "}
repeat with aKey in theKeys
	set newList to text items of (aKey as text)
	set theExtra to (theValues's valueForKey:aKey)
	set end of newList to (theExtra's valueForKey:"enabled_context_menu")
	set end of newList to (theExtra's valueForKey:"enabled_services_menu")
	set end of newList to aKey -- for restoring
	set end of theList to newList
end repeat
set AppleScript's text item delimiters to saveTID
-- show table
set theTable to make new table with data theList with title "Services" with prompt "Enable or disable services" editable columns {4, 5} row template {text, text, text, boolean, boolean, missing value} column headings {"Application", "Service name", "Short name"} with empty selection allowed
set theResult to display table theTable
if button number of theResult = 1 then
	-- make new NSServicesStatus dictionary
	set servicesDict to current application's NSMutableDictionary's |dictionary|()
	repeat with aResult in values returned of theResult
		set tempDict to current application's NSMutableDictionary's |dictionary|()
		if item 4 of aResult is not missing value then (tempDict's setObject:(item 4 of aResult) forKey:"enabled_context_menu")
		if item 5 of aResult is not missing value then (tempDict's setObject:(item 5 of aResult) forKey:"enabled_services_menu")
		(servicesDict's setObject:tempDict forKey:(item 6 of aResult))
	end repeat
	-- combine with old NSServicesStatus dictionar
	set newDict to theDict's mutableCopy()
	newDict's setObject:servicesDict forKey:"NSServicesStatus"
	-- write new defaults
	defaults's setPersistentDomain:newDict forName:"pbs"
end if

Hi Shane,

That’s perfect. Many thanks for a better, more elegant solution for extracting the data, as well as for a turnkey script.
It is exactly what I had in mind for using the Myriad table to build a services manager applet.

There are however other services not stored in the pbs.plist:

All user made services (I have about 20 of them) are stored in ~:Library:Services:. They are easy to collect and add to the Myriad table. However, I’m not sure how to enable/disable them other then moving them in and out of the Services folder. Or, maybe adding them to the pbs.plist file. Would that work? Do you have any suggestion?

There are also a few services provided by some 3rd party app. They don’t appear in pbs.plist but they do appear in the System Preferences/…/Services table. Where would I find them to add them to the Myriad table and how to enable/disable them? Any suggestion?

Thank you again for your help as well as for the exceedingly useful Myriad Table.

Chris

I suspect you’d need to move them in and out, but I really don’t know.

Sorry, I don’t really no. I’m not particularly a services user…

The complete list is available in the preferences file com.apple.ServicesMenu.Services.plist in :
NSServices > CFVendedServices

Yvan KOENIG running Sierra 10.12.2 in French (VALLAURIS, France) lundi 16 janvier 2017 11:57:39

Thank you Yvan,

I should have taken a closer look at the obvious name of some of the prefs files :wink:
I’ll try to adapt Shane’s method of reading from and writing to this file.

Although it may look like it, you’re not actually reading or writing to a file. Preferences are all handled by a daemon that keeps them in memory while the relevant process is running; the files are just a sort of backup, for when the power goes off or the process isn’t running. That’s why it’s important to use NSUserDefaults or the defaults command-line tool.

Thank you for the explanation.

Chris