Apple - Save open applications and tabs state to file and be able to restore
NOTE: Answer still under construction...
One more section to write, detailing the scripts that will sync the running state of the applications. I have to write these scripts first.
The Objective
Broadly, to devise a means for two Macs to save and share information on the user environment, including application states/properties, loaded documents, and actively running applications.
Your initial request was to seamlessly move from one [machine] to the other and recreate the state of open applications I had in the previous one with a command. You've since relaxed your expectations a little, and clarified that "hot-reloading" applications is not sought, and minutiae such as window sizes and positions are not a concern.
Conceptual Methodology
1. Two scripts + a shared data file
My initial idea was to have a copy of a script (probably an AppleScript) on each machine that would have a handler that was responsible for getting information about the application environments and storing it in a shared file to which both machines would have access (e.g. via Dropbox, iCloud, ...). The script would have a separate handler that would retrieve the contents of this shared file, and apply the settings to the local environment. This would require the user to run the script once upon arriving at a machine in order to apply the settings; and once just before departing the machine in order to update the shared data file with new settings.
2. .savedState
synchronisation
My second thought was inspired from another person's notion of synchronising the file located at ~/Library/Preferences/ByHost/com.apple.loginwindow.*
. Whilst doing so would not be helpful, it raises the question of whether synchronising .savedState
files found in the folder ~/Library/Saved Application State
would be the solution.
Admittedly, I can't claim to know what the full ramifications of attempting to sync these files across two different systems would be. That's not to say that anything bad would happen, but it's unknown territory, so the potential for undesirable results is there. However, my feeling is that it would largely be safe, and probably quite innocuous, given that, if anything were to go awry from a bad .savedState
file, the solution is pretty much always to just delete the .savedState
file, and allow the application in question to generate a new one.
I suspect a combination of these two methods will be needed.
Syncing
Syncing the ~/
folder via Dropbox
Syncing the home folder would likely only provide you with a means to make documents available on each machine, but possibly not allow you to sync the ~/Library
folder if its location cannot be changed. Also, this folder is 26GB in size on my system, so it would involve paying a subscription for extra cloud storage space.
Ultimately, if syncing the home folder is limited to Downloads
, Documents
, Music
, and Movies
, it isn't much of a benefit over having the Documents
and Desktop
folders synced through iCloud.
However, having documents synced is a good idea, as it will be important to have access to the same files on both computers, and for changes on one to be reflected on the other. iCloud, whilst quite slow, is fine for this job.
Syncing the ~/Library
folder
The ~/Library
folder, or at least specific subfolders of it, would be a crucial goal if contemplating using .savedState
files to map application states from one system to another. A home cloud storage solution would be ideal for this task, as it doesn't levy any subscription fees; your sensitive data would be safe in your possession and not owned by Dropbox or Apple; and there'd be no speed throttling outside of that imposed by your ISP if syncing across the internet.
I already use Resilio Sync, which is free for individual use, and uses peer-to-peer transfers to keep selected folders in sync between any machines or devices on which you install the application. It would be a simple case of selecting the ~/Library
folder or the ~/Library/Saved Application State
folder to be monitored and synced in real-time. The folders can remain where they are, and if you choose to pay for additional features (one-off payment), you can selectively sync individual files within a folder, and encrypt them for data security. I really recommend this application, and it seems ideal for this specific situation.
Scripting
If synchronising the .savedState
files is successful, what might be needed is a script to open (or reopen) the applications that were running on the other machine. Reopening will hopefully get the application to read its updated .savedState
files and replicate the state it was in on the other system. I think this will have partial success, but perhaps not total.
Retrieving a list of running applications
This is trickier than it sounds because there are various ways to test if an application is running, and no one test is perfect. There are the applications in the foreground that are visible with windows; there are menu bar applications, some with no windows, running as background processes; and there could be hidden applications minimised in the dock, with no visible windows.
After experimenting with a few different methods in AppleScript, this line of code seems to produce a good, exhaustive list of running applications, including menu bar applications:
tell application "System Events" to set OpenApps to the ¬
name of every application process whose ¬
class of its menu bar contains menu bar and ¬
POSIX path of the application file does not start with "/System" and ¬
POSIX path of the application file does not start with "/Library"
It takes a couple of seconds to return a result, but I don't think that speed will matter too much if the scripts are implemented in a sensible way.
A Bash method of getting a list of running applications is this:
IFS=$'\r\n'; basename $( \
egrep -i -e '^/Applications/.*\.app/Contents/MacOS/[^/]+$' \
<<<"$(ps -U 501 -o 'comm')" ) | sort -u
It's fast (instantaneous), but does miss a couple of menu bar and background applications.
I think either method will ultimately be fine for what's needed, as the most important applications to capture are the ones you're interacting with in the foreground, and those ones are caught by most methods.
Storing information in a shared settings file
Writing the settings that need to be saved can be done to a .plist
file, which stores key/value pairs of data along with the data types. This allows any value to be saved and retrieved by its name. They're also relatively simply to read and edit programmatically using either AppleScript or the command line utility plutil
.
Let's assume the shared settings file will be saved at ~/Example/savedState.plist
where ~/Example/
is some sort of shared folder (e.g. a cloud storage folder) to which both computers have access.
Using AppleScript, this is how one might save a list of applications that are currently open:
property plist : "~/Example/SavedState.plist"
tell application "System Events"
if not (the file plist exists) then make new property list file ¬
with properties {name:plist}
set plistf to the property list file named plist
tell plistf to make new property list item with properties ¬
{name:"OpenApps", kind:list, value:OpenApps}
end tell
where the variable OpenApps
is taken from the AppleScript snippet above. Cleverly, the make new property list
command will overwrite any existing key/value item that already has the name "OpenApps"
, so it won't be duplicated.
Next, we need a way to quit applications on a system:
to kill(A as list, X as list)
local A, X
if A = {} then return
script jury
property A0 : item 1 of A
property An : rest of A
property OK : A0 is not in X
end script
tell the jury
ignoring application responses
if its OK then quit the application named (its A0)
kill(its An, X)
end ignoring
end tell
end kill
This is a recursive handler that is passed a list of applications to quit (parameter A
) and a list of applications that are to be excluded (parameter X
). It then goes through the list one by one and checks the application isn't one of the ones in X
before quitting it and moving onto the rest of the list.
Re-opening documents
By employing the use of synchronised .savedState
files, the hope is that, once an application on one system re-initialises (say, by closing and opening it again), it will re-open documents that were open on the other.
However, it may be prudent store our own list of documents that we can detect (to the best of our abilities) are open and active.
[NOTES FOR ME TO INTEGRATE INTO ANSWER]
- I Noticed that some
.savedState
files are stored in~/Library/Saved Application State
as aliases (symbolic links) instead of the actual file itself. These typically link to~/Library/Containers/%bundle-id%/Data/Library/Saved Application State/%file%
where%bundle-id%
is, for example,com.apple.Preview
, and its corresponding%file%
iscom.apple.Preview.savedState
.
This means that ~/Library/Containers
will likely have to be synchronised between the two machines as well.
.savedState
files are deleted whenever an application exits cleanly. Therefore, bear in mind that closing applications on one system will delete those data files, then potentially synchronise these deletions with the other machine.