How to Get List of Recent Items?

Infos about the recent documents are stored in : (path to application support from user domain as text) & “com.apple.sharedfilelist:com.apple.LSSharedFileList.RecentDocuments.sfl2”
Infos about the recent applications are stored in : (path to application support from user domain as text) & “com.apple.sharedfilelist:com.apple.LSSharedFileList.RecentApplications.sfl2”

How may we extract the names and/or the paths of the described recent items.

I found pieces of code which did that with old ways of storing these infos but none of them apply to the “new” one.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 9 septembre 2019 13:58:02

Those files are binary property lists archived with NSKeyedArchiver. The URLs are security scoped bookmarks which can be resolved with NSURL API

This script extracts the URLs as string paths into a variable documentPaths


use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

property |⌘| : a reference to current application

set recentDocumentsPath to POSIX path of (path to application support from user domain) & "com.apple.sharedfilelist/com.apple.LSSharedFileList.RecentDocuments.sfl2"
set plistData to |⌘|'s NSData's dataWithContentsOfFile:recentDocumentsPath
set recentDocuments to |⌘|'s NSKeyedUnarchiver's unarchiveObjectWithData:plistData
set documentPaths to {}
repeat with aDocument in (recentDocuments's objectForKey:"items")
	set documentBookmark to (aDocument's objectForKey:"Bookmark")
	set {documentURL, resolveError} to (|⌘|'s NSURL's URLByResolvingBookmarkData:documentBookmark options:0 relativeToURL:(missing value) bookmarkDataIsStale:(missing value) |error|:(reference))
	if resolveError is missing value then
		set end of documentPaths to documentURL's |path|()
	else
		display dialog resolveError's localizedDescription as text
	end if
end repeat

Thanks, Stefan, for this nice script.

I added to your script’s end documentPaths to return it, and this is list of NSString paths in Script Debugger, and list of IDs in Script Editor. It is little strange for me.

So, to get as strings list always:

set end of documentPaths to documentURL's |path|() as string

Thank you StefanK and KniazidisR.

I added some instructions allowing us to define which recents items we want to get.

use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

property |⌘| : a reference to current application

tell application "Finder"
	set cancelBtn to localized string "AL1" --> "Annuler"
	set OKBtn to localized string "AL4" --> "OK"
end tell
set begOfPath to (path to application support from user domain as text) & "com.apple.sharedfilelist:"
set item1 to "Documents"
set item2 to "Applications"
set item3 to "Choose an application"

set whatToDo to choose from list {item1, item2, item3} default items {item3}
if whatToDo is false then error number -128
set whatToDo to whatToDo's item 1
if whatToDo is item3 then
	set appName to name of (choose application)
	set appleApp to false
	if appName is in {"Keynote", "Numbers", "Pages"} then
		set appName to "Iwork." & appName
		set appleApp to true
	else if appName is "Script Editor" then
		set appName to "scripteditor2"
		set appleApp to true
	else if appName is "Imovie" then
		set appName to "imovieapp"
		set appleApp to true
	else if appName is "Xcode" then
		set appName to "dt.xcode"
		set appleApp to true
	else if appName is "QuickTime Player" then
		set appName to "quicktimeplayerx"
		set appleApp to true
	else if appName is "System Profiler???" then
		set appName to "systemprofiler"
		set appleApp to true
	else if appName is "BBEdit" then
		set appName to "com.barebones.bbedit"
	else if appName is "Libre Office" then
		set appName to "org.libreoffice.script"
	else if appName is "Script Debugger" then
		set appName to "com.latenightsw.scriptdebugger7"
	else if 5 = 6 then
		--
	end if
	if appleApp then
		set recentDocumentsPath to POSIX path of (begOfPath & "com.apple.LSSharedFileList.ApplicationRecentDocuments:com.apple." & appName & ".sfl2")
	else
		set recentDocumentsPath to POSIX path of (begOfPath & "com.apple.LSSharedFileList.ApplicationRecentDocuments:" & appName & ".sfl2")
	end if
else
	set recentDocumentsPath to POSIX path of (begOfPath & "com.apple.LSSharedFileList.Recent" & whatToDo & ".sfl2")
end if
try
	set plistData to |⌘|'s NSData's dataWithContentsOfFile:recentDocumentsPath
	set recentDocuments to |⌘|'s NSKeyedUnarchiver's unarchiveObjectWithData:plistData
	set documentPaths to {}
	repeat with aDocument in (recentDocuments's objectForKey:"items")
		set documentBookmark to (aDocument's objectForKey:"Bookmark")
		set {documentURL, resolveError} to (|⌘|'s NSURL's URLByResolvingBookmarkData:documentBookmark options:0 relativeToURL:(missing value) bookmarkDataIsStale:(missing value) |error|:(reference))
		if resolveError is missing value then
			set end of documentPaths to documentURL's |path|() as string
			--else
			--display dialog resolveError's localizedDescription as text
		end if
	end repeat
	
	documentPaths as list
on error
	error "The selected application is not treated or has no sfl2 associated file"
end try

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 9 septembre 2019 20:48:42

Added instructions required to treat particular cases of Apple applications and code required to treat three non apple applications.
Complete to fit your needs after looking at the contents of the folder :
[format]((path to application support from user domain as text) & “com.apple.sharedfilelist:com.apple.LSSharedFileList.ApplicationRecentDocuments:”)[/format]

Ivan Koenig.

I replaced your code line:

set appName to display dialog "Define an application" default answer "Iwork.numbers" buttons {cancelBtn, OKBtn} default button OKBtn cancel button cancelBtn

with this:

set appName to name of (choose application)

But I can’t understand. It seems that separately for applications the code does not work anyway?

Update: I apologize - your code works just fine if the application has the recent documents at the moment. If they are not already, an error is thrown. That is, you need to put the last part of the code in a try block. Or, in the if block:

if recentDocuments ≠ missing value then
	repeat with aDocument in (recentDocuments's objectForKey:"items")
		set documentBookmark to (aDocument's objectForKey:"Bookmark")
		set {documentURL, resolveError} to ¬
			(|⌘|'s NSURL's URLByResolvingBookmarkData:documentBookmark options:0 ¬
				relativeToURL:(missing value) bookmarkDataIsStale:(missing value) |error|:(reference))
		if resolveError is missing value then
			set end of documentPaths to documentURL's |path|() as string
			--else
			--display dialog resolveError's localizedDescription as text
		end if
	end repeat
end if

I’m quite sure that itsn’t a good idea.

If you select Numbers, you will get the name “Numbers” while the system requires “Iwork.numbers”

There is a workaround:

set appName to name of (choose application)
if appName is in {"Keynote", "Numbers", "Pages"} then set appName to "Iwork." & appName
if appName is "Imovie" then set appName to "imovieapp"
# Complete with non-Apple applications

Of course don’t ask me for an explanation of this non-standard behavior.

Maybe it would be useful to add “IBooks” to the list of applications requiring to be “corrected”.

PS: Since 1943/12/31, my first name is Yvan and I’m accustomed to it. Please, don’t torture it!

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 9 septembre 2019 21:22:36

This workaround is better for me, as I don’t like manual operations. So, thanks for your script. It goes to my Scripts library in this form:


use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

property |⌘| : a reference to current application

set {item1, item2, item3} to {"Documents", "Applications", "Choose an application"}
set whatToDo to choose from list {item1, item2, item3} default items {item3}
if whatToDo is false then error number -128
set whatToDo to whatToDo's item 1

if whatToDo is item3 then
	set appName to name of (choose application)
	if appName is in {"Keynote", "Numbers", "Pages"} then set appName to "Iwork." & appName
	set recentDocumentsPath to POSIX path of ((path to application support from user domain as text) & "com.apple.sharedfilelist:com.apple.LSSharedFileList.ApplicationRecentDocuments:com.apple." & ¬
		appName & ".sfl2")
else
	set recentDocumentsPath to POSIX path of ((path to application support from user domain as text) & "com.apple.sharedfilelist:com.apple.LSSharedFileList.Recent" & whatToDo & ".sfl2")
end if

set plistData to |⌘|'s NSData's dataWithContentsOfFile:recentDocumentsPath
set recentDocuments to |⌘|'s NSKeyedUnarchiver's unarchiveObjectWithData:plistData
set documentPaths to {}

if recentDocuments ≠ missing value then
	repeat with aDocument in (recentDocuments's objectForKey:"items")
		set documentBookmark to (aDocument's objectForKey:"Bookmark")
		set {documentURL, resolveError} to (|⌘|'s NSURL's URLByResolvingBookmarkData:documentBookmark ¬
			options:0 relativeToURL:(missing value) bookmarkDataIsStale:(missing value) |error|:(reference))
		if resolveError is missing value then
			set end of documentPaths to documentURL's |path|() as string
		else
			display dialog resolveError's localizedDescription as text
		end if
	end repeat
end if

documentPaths

I edited the script in message #4 so that it treat some non-standard Apple naming and show how to complete with non Apple products (I coded three examples).

I would be glad to know why many applications aren’t proposed by choose application.
It’s the case for many applications which I bought from the App Store and which are stored in a subfolder named “Applications_MAS” . Xcode or Stuffit Expander are proposed but EasyDraw or EasyFind aren’t.
Same behavior which many applications stored in a subfolder named “Applications perso”. BBEdit or Script Debugger are proposed but Libre Office isn’t.
Some applications stored in the Applications folder are missing too: Data Rescue.app, DiskMaker X 7 for High Sierra.app, GIMP-2.10.app, Malwarebytes.app are some of them

For info, here is a list of the sfl2 files dedicated to applications which are available on my SSD:

com.apple.console.sfl2
com.apple.dt.xcode.sfl2
com.apple.imovieapp.sfl2
com.apple.iphoto.sfl2
com.apple.iwork.keynote.sfl2
com.apple.iwork.numbers.sfl2
com.apple.iwork.pages.sfl2
com.apple.photos.sfl2
com.apple.preview.sfl2
com.apple.quicktimeplayerx.sfl2
com.apple.scripteditor2.sfl2
com.apple.systemprofiler.sfl2
com.apple.textedit.sfl2
com.autodesk.sketchbook.sfl2
com.barebones.bbedit.sfl2
com.barebones.textwrangler.sfl2
com.charlessoft.pacifist.sfl2
com.colliderli.iina.sfl2
com.etresoft.etrecheck4.sfl2
com.github.atom.sfl2
com.hankinsoft.osx.sqliteprofessional.sfl2
com.hp.customer.uploader.sfl2
com.latenightsw.scriptdebugger7.sfl2
com.mkanda.ocrtools.sfl2
com.ridiculousfish.hexfiend.sfl2
com.seriflabs.affinityphoto.sfl2
com.smileonmymac.pdfpenpro.sfl2
net.sourceforge.grandperspectiv.sfl2
org.clindberg.manopen.sfl2
org.libreoffice.script.sfl2
org.videolan.vlc.sfl2

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 9 septembre 2019 22:14:29

Hi, Yvan.

The list above without extensions is list of CFBundleIdentifier. I don’t know how, but is seems, this maybe used for automated getting required by system names.

How can I convert application name to its CFBundleIdentifier?

set appName to name of (choose application)
set appName to id of application appName

I applyed this and… Hm, it works:


use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

property |⌘| : a reference to current application

set {item1, item2, item3} to {"Documents", "Applications", "Choose an application"}
set whatToDo to choose from list {item1, item2, item3} default items {item3}
if whatToDo is false then error number -128
set whatToDo to whatToDo's item 1

if whatToDo is item3 then
	set appName to name of (choose application)
	set appName to id of application appName
	set recentDocumentsPath to POSIX path of ((path to application support from user domain as text) & "com.apple.sharedfilelist:com.apple.LSSharedFileList.ApplicationRecentDocuments:" & ¬
		appName & ".sfl2")
else
	set recentDocumentsPath to POSIX path of ((path to application support from user domain as text) & "com.apple.sharedfilelist:com.apple.LSSharedFileList.Recent" & whatToDo & ".sfl2")
end if

set plistData to |⌘|'s NSData's dataWithContentsOfFile:recentDocumentsPath
set recentDocuments to |⌘|'s NSKeyedUnarchiver's unarchiveObjectWithData:plistData
set documentPaths to {}

if recentDocuments ≠ missing value then
	repeat with aDocument in (recentDocuments's objectForKey:"items")
		set documentBookmark to (aDocument's objectForKey:"Bookmark")
		set {documentURL, resolveError} to (|⌘|'s NSURL's URLByResolvingBookmarkData:documentBookmark ¬
			options:0 relativeToURL:(missing value) bookmarkDataIsStale:(missing value) |error|:(reference))
		if resolveError is missing value then
			set end of documentPaths to documentURL's |path|() as string
		else
			display dialog resolveError's localizedDescription as text
		end if
	end repeat
end if

documentPaths

Here is a new version which trigger only existing files.

use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

property |⌘| : a reference to current application

Germaine()

on Germaine()
	set begOfPath to (path to application support from user domain as text) & "com.apple.sharedfilelist:"
	set begOfPathLong to begOfPath & "com.apple.LSSharedFileList.ApplicationRecentDocuments:"
	
	set specialCases to {"Applications", "Documents"}
	tell application "Finder"
		set theChoices to specialCases & name of files of folder begOfPathLong
	end tell
	
	set appName to choose from list theChoices
	if appName is false then error number -128
	set appName to appName's item 1
	if appName is in specialCases then
		set recentDocumentsPath to POSIX path of (begOfPath & "com.apple.LSSharedFileList.Recent" & appName & ".sfl2")
	else
		set recentDocumentsPath to POSIX path of (begOfPathLong & appName)
	end if
	
	try
		set plistData to |⌘|'s NSData's dataWithContentsOfFile:recentDocumentsPath
		set recentDocuments to |⌘|'s NSKeyedUnarchiver's unarchiveObjectWithData:plistData
		set documentPaths to {}
		repeat with aDocument in (recentDocuments's objectForKey:"items")
			set documentBookmark to (aDocument's objectForKey:"Bookmark")
			set {documentURL, resolveError} to (|⌘|'s NSURL's URLByResolvingBookmarkData:documentBookmark options:0 relativeToURL:(missing value) bookmarkDataIsStale:(missing value) |error|:(reference))
			if resolveError is missing value then
				set end of documentPaths to documentURL's |path|() as string
			end if
		end repeat
		
		documentPaths as list
	on error
		error "The selected application is not treated or has no sfl2 associated file"
	end try
end Germaine

I built another version in which I use a list of apps derived manually from the list of available files.
I feel that a version which doesn’t require some duty from the user is better.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) mardi 10 septembre 2019 09:07:24

Thanks, Yvan. I tried your last version too. I liked it. Now, with our last posts your code has not 1 but 2 extremely cool solutions. Thanks again to Stefan for starting point. :slight_smile:

Thanks.

As I am pig headed, I repeat what I already wrote:

I would be glad to know why many applications aren’t proposed by choose application.
It’s the case for many applications which I bought from the App Store and which are stored in a subfolder named “Applications_MAS” . Xcode or Stuffit Expander are proposed but EasyDraw or EasyFind aren’t.
Same behavior which many applications stored in a subfolder named “Applications perso”. BBEdit or Script Debugger are proposed but Libre Office isn’t.
Some applications stored in the Applications folder are missing too: Data Rescue.app, DiskMaker X 7 for High Sierra.app, GIMP-2.10.app, Malwarebytes.app are some of them

And I add a question: where is iBooks storing its list of recents items?
It has its own Recent items menu but I see no file storing such infos and the Recents Items > Documents menu doesn’t display books open in iBooks.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) mardi 10 septembre 2019 13:57:58

Because they aren’t scriptable?

From the Apple command reference:

“The choose application dialog initially presents a list of all applications registered with the system. To choose an application not in that list, use the Browse button, which allows the user to choose an application anywhere in the file system.”

As I understand, on Mac system registered apps is those from identified by Apple developers… It is intresting,were on Mac is registered apps path… It should be managed by Gatekeeper

So, my answer is: no, all applications are proposed by choose application, with Browse button in this dialog.

I suspect that’s well out of date — the dialog changed quite a long time ago (when script apps moved to bundles, I suspect — it was the move to bundles with Info.plist scriptability entries that made it feasible to easily check which are scriptable).

Go into Script Editor and choose File → Open Dictionary. The list will obviously only show scriptable apps and libraries. Click on the Kind column so applications are sorted before script libraries.

Leave the dialog open and go into Script Debugger and run this script:

choose application

Compare the lists in the two dialogs. I think you’ll find they’re identical.

You are right. I ran your scripts, Shane. The dialogs are almost same for Open dictionary and for Choose application. :slight_smile:

But this may again be a coincidence, based on Apple’s policy, to highlight the programs of those developers who pay. In any case, the user can select any application in the choose application through the “Browse” button, and this is important for this topic.

And, is one important difference: press Browse in Open dictionary, go to Applications folder. You can’t choose nonscriptable app. But doing this with Browse in Choose application dialog you can choose any application. So, I think, all is related to money and not to scriptability.

I don’t know, Shane, may be one scriptable app non registered to work on Mac?

Thank you Shane.
It makes sense.
I don’t believe that it’s a problem of money because most of the applications bought thru MacAppStore aren’t proposed although they came from registered developers. The proposed ones are : Amphetamine, Aperture, JSON Helper, Omnigrafle, SiteSucker, Stuffit Expander, The Unarchiver, XCode, Instruments and Simulator which belong to Xcode and iBooks Author. All of them are scriptable.

Being forced to click Browse is really annoying so I will stay with the script which offer only the applications which have a related sfl file.

So now the remaining question is : where is iBook storing it’s list of recent items?
It’s curious that an application made by Apple doesn’t apply the standard scheme.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) mardi 10 septembre 2019 16:47:05

You’re a hard man to convince. Save an applet in /Applications. See that it doesn’t show up in choose application. Now open its Info.plist file and add:

<key>NSAppleScriptEnabled</key>
<true/>

Open the script in an editor, make a change and save. Now try choose application again.

No money changed hands.

Not necessarily. Isn’t it one of those ported iOS apps? They may well follow a different scheme.

Yes it’s true. But you convinced me with the last test. It is very rare for a knowledgeable person to share his knowledge so easily. Thank you, Shane, for the great help on this site. I always read your posts with interest.