One reason I don’t reboot very often is because I use the desktops in mission control as project spaces. I typically have 3 to 9 projects in active use each week. Each project typically have a few windows from: Safari, Text Edit, Stickies, and Terminal, plus possibly another app or 2. I use Stickies in large font tucked under the dock to label each desktop (so I can identify the project easily in MC - because it doesn’t show the dock). If I have to reboot, that’s a lot of windows all getting merged onto desktop 1 and it takes awhile to sort them out.
If I could just figure out a way to automatically re-distribute Stickies, Text Edit, and Terminal windows after reboot, that would solve 90% of my problem. (I think somehow Safari remembers which desktop each window was on.)
Does anyone have any pointers for a script that could:
- Trigger upon shutdown to memorize which desktop each window of a list of specific apps is on
- Trigger after reboot (or even manually trigger) to restore the memorized windows to their pre-reboot desktop
Just thinking off the top of my head, I can imagine a script that, to memorize desktop assignments of each window, cycles through the desktops and switches to each app and cycles through the windows to record what desktop they’re on in a lookup (text) file.
The problem is, I don’t know the Applescript elements or any key commands to send a window to a specific desktop. Does anyone know how to do this?
Browser: Safari 605.1.15
Operating System: macOS 10.14
I managed to create a script that can memorize what windows are open on which desktops. As a proof of concept, it’s OK, albeit hacky. It needs however to save the windows and save them in a way I can manipulate them later using a companion script instead of just as a set of window names displayed in a dialog. It has a couple issues:
- Stickies window names are truncated to not include hard returns (possibly making the window name unusable)
- All 3 of my Fluid apps are identified inaccurately as FluidApp.app
- My method of determining the number of desktops present is not likely to work on all versions of macOS
- I don’t have a way of omitting apps assigned to all desktops
Any help in figuring out how to move windows to their original desktops (as recorded by some improved version of this script) would be appreciated. I had had an idea to simply quit an app and then launch it using a shell command like open -a app -F
and then just switching desktops using keystrokes (like the below script does) and opening the associated files when I’m on that desktop. That could work in a limited fashion, however the -F argument doesn't work for untitled/unsaved documents. Those always open regardless of
-F`. That would mean however that I would have to get the file paths associated with the open windows in the script below. I would settle for what is possible this way and just let the untitled docs end up wherever. That would be better than nothing. Though I could launch the app from the desktop where the first untitled doc was open…
--This applescript reports a list of application windows present on each desktop
--This is only a sample script intended to eventually be used to restore all application windows to their pre-reboot desktops
tell application "System Events"
set windows_string to ""
set numDesktops to (first paragraph of (do shell script "strings ~/Library/Preferences/com.apple.spaces.plist | grep -c ^\\\\$")) + 1
--the following tcsh command can determine the number of desktops:
-- @ x = ( `strings ~/Library/Preferences/com.apple.spaces.plist | grep -c '^\$'` + 1 ); echo $x
-- switch to the first desktop:
repeat with aDesktop from 1 to numDesktops
key code 123 using {control down}
end repeat
repeat with aDesktop from 1 to numDesktops
set windows_string to windows_string & return & return & "Desktop " & (aDesktop as string) & return
delay 1
get (the name of every application process whose class of windows contains window)
repeat with P in the result
set windows_string to windows_string & return & return & P & return
get (every window of process (contents of P) whose value of attribute "AXMinimized" is false)
repeat with W in the result
set window_name to ((name of W) as string)
--Stickies window names can be multi-line, so this trims from the first hard return onward
set better_window_name to (my replacePattern:"[\\n].*" inString:window_name usingThis:"")
if window_name is not equal to "" then
try
set windows_string to windows_string & better_window_name & return
on error
set windows_string to windows_string & "couldn't get window name" & return
end try
end if
end repeat
end repeat
--switch to the next desktop
key code 124 using {control down}
end repeat
display dialog "List of windows on this desktop: " & return & return & windows_string
end tell
--Call like this: set res to my replacePattern:"\\s+" inString:"1 subtratcing-these: -2 3 4" usingThis:"-"
use framework "Foundation"
use scripting additions
on replacePattern:thePattern inString:theString usingThis:theTemplate
set theRegEx to current application's NSRegularExpression's regularExpressionWithPattern:thePattern options:0 |error|:(missing value)
set theResult to theRegEx's stringByReplacingMatchesInString:theString options:0 range:{location:0, |length|:length of theString} withTemplate:theTemplate
return theResult as text
end replacePattern:inString:usingThis:
I’m another step closer…
This is another proof of concept. And it works on untitled documents too. Despite this example, I wouldn’t use it for Safari windows because:
- It loses the window history
- Safari already restores windows to their original desktops
But regardless, I should be able to use this code to update the first script to get & save document file paths and then to process the saved paths and close & reopen windows on the target desktop by switching to them using control-arrow keystrokes…
--This is a proof of concept that docs open in an app can be closed on another desktop and be reopened on another desktop. It works with untitled documents as well as saved documents. When an app launches (even with open ... -F) all reopened windows open on the current desktop instead of the desktop where they were before a reboot. This simply proves that you can close & re-open the docs on whichever desktop you are currently on. Combined with my "ReportWindowsOnEachDesktop" script, this could allow all windows to be re-opened on their original desktop...
--switch to a different desktop and run...
tell application "Safari"
set p to get URL of document 1 of application "Safari"
close document 1
--display dialog p
do shell script "open '" & p & "'"
end tell
tell application "TextEdit"
set doclist to every document
set pathlist to {}
repeat with adoc in doclist
--display dialog path of adoc as text
set p to path of adoc as text
set end of pathlist to p
--display dialog p
close adoc saving yes
end repeat
repeat with apath in pathlist
--display dialog path of adoc as text
--display dialog p
do shell script "open -a 'TextEdit' '" & apath & "'"
end repeat
return doclist
end tell
I’m still making progress. This is a modification of the first script to save the desktop, app, and path of each open document. It unfortunately doesn’t work with Stickies, but I have an idea on how to possibly circumvent that issue. This script skips any app that doesn’t have open “document” windows.
--This applescript reports a list of application windows present on each desktop
--This is only a sample script intended to eventually be used to restore all application windows to their pre-reboot desktops
tell application "System Events"
set windows_string to ""
set docs_string to ""
set numDesktops to (first paragraph of (do shell script "strings ~/Library/Preferences/com.apple.spaces.plist | grep -c ^\\\\$")) + 1
-- switch to the first desktop:
repeat with aDesktop from 1 to numDesktops
key code 123 using {control down}
end repeat
repeat with aDesktop from 1 to numDesktops
set windows_string to windows_string & return & return & "Desktop " & (aDesktop as string) & return
delay 1
get (the name of every application process whose class of windows contains window)
repeat with P in the result
set windows_string to windows_string & return & return & P & return
set alldocs to {}
set doclist to my getAppDocs(P)
set docnames to first item in doclist
set docpaths to second item in doclist
set adoc to null
--For some reason, this stopped working for me: 'whose value of attribute "AXMinimized" is false' so I commented it out
get (every window of process (contents of P)) -- whose value of attribute "AXMinimized" is false)
repeat with W in the result
set window_name to ((name of W) as string)
try
set attrs to attributes of W
display dialog attrs
end try
--Stickies window names can be multi-line, so this trims from the first hard return onward
--Stickies isn't applescript-able. I may however be able to use my "StickiesBackup2" script to circumvent that issue
set better_window_name to (my replacePattern:"[\\n].*" inString:window_name usingThis:"")
if window_name is not equal to "" then
try
set i to 0
repeat with listdoc in docnames
set i to i + 1
if (listdoc as string) is window_name then
--set adoc to item i of alldocs
set adoc to item i of docpaths
end if
end repeat
set adocpath to adoc
if adocpath is not null then
set docs_string to docs_string & "Desktop " & (aDesktop as string) & tab & P & tab & adocpath & return
end if
--on error errstr
-- set docs_string to docs_string & "Desktop " & (aDesktop as string) & tab & P & tab & "ERROR for " & better_window_name & ": " & errstr & return
end try
try
set windows_string to windows_string & better_window_name & return
--on error
-- set windows_string to windows_string & "couldn't get window name" & return
end try
else
--set docs_string to docs_string & "Desktop " & (aDesktop as string) & tab & P & tab & "no docs" & return
end if
end repeat
end repeat
--switch to the next desktop
key code 124 using {control down}
end repeat
display dialog "List of documents on each desktop: " & return & return & docs_string
end tell
on getAppDocs(appName)
set doclist to {}
set myscript to "tell application \"" & appName & "\"" & return
set myscript to myscript & tab & "set docnames to {}" & return
set myscript to myscript & tab & "set docpaths to {}" & return
set myscript to myscript & tab & "try" & return
set myscript to myscript & tab & tab & "set docnames to name of every document" & return
set myscript to myscript & tab & tab & "set docpaths to path of every document" & return
set myscript to myscript & tab & "end try" & return
set myscript to myscript & tab & "return {docnames,docpaths}" & return
set myscript to myscript & "end tell" & return
try
run script myscript
set doclist to result as list
on error errstr
display dialog "Error running script:" & return & return & myscript & return & return & "Error: " & errstr
end try
return doclist
end getAppDocs
--Call like this: set res to my replacePattern:"\\s+" inString:"1 subtratcing-these: -2 3 4" usingThis:"-"
use framework "Foundation"
use scripting additions
on replacePattern:thePattern inString:theString usingThis:theTemplate
set theRegEx to current application's NSRegularExpression's regularExpressionWithPattern:thePattern options:0 |error|:(missing value)
set theResult to theRegEx's stringByReplacingMatchesInString:theString options:0 range:{location:0, |length|:length of theString} withTemplate:theTemplate
return theResult as text
end replacePattern:inString:usingThis:
It also assumes that window names are the same as the file names (which is not necessarily true).