Move ALL Windows to Primary Screen

Use Case

  • Use anytime you want to quickly move all of your windows to the primary (internal) screen, like during an emergency power down.

UPDATED: 2021-02-24 11:31 GMT-6

Change Log

Ver 2.1 Changes 2021-02-24

  • Fix error caused by a change in the macOS
    • Error: Can’t get origin of {{0.0, 0.0}, {3440.0, 1415.0}}.

VER 2.0 Changes

  • Script now searches for ALL Apps and ALL Windows, whether they are background or not, visible or not.
  • Added optional display of list of all windows that were found (enable the last Action).

Example of All Window list

Shows window coordinates BEFORE any were moved.


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

MACRO:   Move ALL Windows to Primary Screen

-~~~ VER: 2.1    2021-02-24 ~~~
Requires: KM 8.2.4+   macOS 10.11 (El Capitan)+
(Macro was written & tested using KM 9.0+ on macOS 10.14.5 (Mojave))

DOWNLOAD Macro File:

Move ALL Windows to Primary Screen.kmmacros
Note: This Macro was uploaded in a DISABLED state. You must enable before it can be triggered.


ReleaseNotes

Author.@JMichaelTX

PURPOSE:

  • Move ALL Windows to Primary (Internal) Screen

HOW TO USE:

  1. Trigger this macro
    .
  • All Windows NOT on the Primary Screen will be moved to the Primary Screen
  • The Top, Left Position will be offset (see below) to stagger the windows
  • The Window Height will be adjusted so that the Window Bottom is NOT below the Screen Bottom.
  • A Notification (from the Script) will be made when the script/macro completes.

NOTE: To see list of all windows, with coordinates before the move, ENABLE the last Display Text Action.

MACRO SETUP

  • There are no Actions designed for changes by end user.
  • However, you may adjust these Properties in the Script
    --- OFFSETS Used When Moving Windows ---
    property ptyOffsetX : 20
    property ptyOffsetY : 40
  • You may also ENABLE the last Action, Display Text, to see a list of all Windows before the move.
    • You are responsible for running the Macro, not me. :wink:
      .
  • Assign a Trigger to this maro. I prefer to put it in a Group with a Palette, and assign "M" as the trigger.
  • Move this macro to a Macro Group that is only Active when you need this Macro. This is probably a Global Group, in this case.
  • ENABLE this Macro.

TAGS: @Screen @Display @Window @Move @AS

USE AT YOUR OWN RISK

  • While I have given this limited testing, and to the best of my knowledge will do no harm, I cannot guarantee it.
  • If you have any doubts or questions:
    • Ask first
    • Turn on the KM Debugger from the KM Status Menu, and step through the macro, making sure you understand what it is doing with each Action.


AppleScript to Move All Windows

property ptyScriptName : "Move Windows to Main Screen"
property ptyScriptVer : "2.1" --  CHG formulas for extracting screen coordinates
property ptyScriptDate : "2021-02-24" # Search for ALL Apps
property ptyScriptAuthor : "JMichaelTX"

property LF : linefeed
(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

REQUIRED:
  1.  macOS El Capitan 10.11.6+
      (may work on Yosemite 10.10.5, but no guarantees)
      
Local:  /Users/Shared/Dropbox/SW/DEV/KM/Scripts/Move Windows to Main Screen V2.1.scpt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
*)

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

--- OFFSETS Used When Moving Windows ---
property ptyOffsetX : 20
property ptyOffsetY : 40

set scriptResults to "TBD"

try
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  set frontApp to path to frontmost application as text -- use for dialogs
  
  --- GET COORDINATES OF PRIMARY VISIBLE SCREEN ---
  -- (all windows to the right, or to the left, of this will be moved)
  
  set primaryScreenVisibleFrame to current application's NSScreen's screens()'s firstObject()'s visibleFrame()
  
  set primaryScreenLeft to (item 1 of item 1 of primaryScreenVisibleFrame)
  set primaryScreenRight to (primaryScreenLeft) + (item 1 of item 2 of primaryScreenVisibleFrame)
  set primaryScreenTop to (item 2 of item 1 of primaryScreenVisibleFrame)
  set primaryScreenBottom to (primaryScreenTop) + (item 2 of item 2 of primaryScreenVisibleFrame)
  
  
  (*
--- ALTERNATE METHOD Using KM ---
tell application "Keyboard Maestro Engine"
  set AppleScript's text item delimiters to ","
  set primaryScreenVisibleFrame to text items of (process tokens "%ScreenVisible%Main%")
  set primaryScreenRight to item 3 of mainScreenFrame
end tell
*)
  
  --- GET ALL VISIBLE APPS ---
  
  tell application "System Events"
    set appNameList to (name of every application process)
  end tell
  
  --log " App List: " & (appNameList as text)
  
  set iWin to 0
  
  --- LOOP THROUGH EACH APP  ---
  
  set logStr to "Left, Top, Width, Height  ┃   App  ┃   WinRole  ┃   Win Title" & LF & LF
  
  display notification "START Search for Windows"
  
  repeat with oAppName in appNameList
    set appName to contents of oAppName
    
    -----------------------------------------------------
    tell application "System Events" to tell application process appName
      -----------------------------------------------------
      set winCount to count of windows
      
      --display notification " App: " & appName & "   # of Windows: " & winCount
      
      repeat with iW from 1 to winCount
        
        set oWin to window iW
        
        tell oWin
          set winTitle to title
          set {winLeft, winTop} to position
          set {winWidth, winHeight} to size
          
          set winBottom to winTop + winHeight
          
          if (((winLeft > primaryScreenRight) or ¬
            (winLeft < primaryScreenLeft))) then
            
            set logStr to logStr & my logWin({position & size & space & appName & space & role & space & title}, " ⬅︎⬅︎ ") & LF
            
            --- MOVE WINDOW ---
            
            set iWin to iWin + 1
            
            --- ADD OFFSETS to POSITION ---
            set newXPos to primaryScreenLeft + (ptyOffsetX * iWin)
            set newYPos to primaryScreenTop + (ptyOffsetY * iWin)
            set position of oWin to {newXPos, newYPos}
            
            --- MAKE SURE WIN BOTTOM DOES NOT EXCEED SCREEN BOTTOM ---
            set newBottom to newYPos + winHeight
            if (newBottom > primaryScreenBottom) then set newBottom to primaryScreenBottom
            set newHeight to newBottom - newYPos
            
            set size of oWin to {winWidth, newHeight}
          else
            set logStr to logStr & my logWin({position & size & space & appName & space & role & space & title}, " ") & LF
            
          end if
          
        end tell
        
        
      end repeat -- on each window
      
    end tell -- "System Events" --> appName
    
  end repeat -- oAppName in appNameList
  
  
  if (iWin > 0) then
    set msgStr to (iWin as text) & " Window(s) MOVED"
  else
    set msgStr to "NO Windows Were Found to Move."
  end if
  
  set msgTitleStr to ptyScriptName
  display notification msgStr with title msgTitleStr sound name "Tink.aiff"
  
  set scriptResults to "OK" & LF & msgStr & LF & LF & logStr
  
  --~~~~~~~~~~~~~ END TRY ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  
on error errMsg number errNum
  
  if errNum = -128 then ## User Canceled
    set errMsg to "[USER_CANCELED]"
  end if
  
  set scriptResults to "[ERROR]" & return & errMsg & return & return ¬
    & "SCRIPT: " & ptyScriptName & "   Ver: " & ptyScriptVer & return ¬
    & "Error Number: " & errNum
end try
--~~~~~~~~~~~~~~~~END ON ERROR ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

--- RETURN THE RESULTS TO THE KM EXECUTE SCRIPT ACTION ---
return scriptResults

--~~~~~~~~~~~~~~~ END OF MAIN SCRIPT ~~~~~~~~~~~~~~~~~~~

on logWin(pListOfProp, pPrefixStr)
  local logWinStr
  
  set astid to AppleScript's text item delimiters
  
  set AppleScript's text item delimiters to ", "
  set logWinStr to pPrefixStr & (pListOfProp as text)
  set AppleScript's text item delimiters to astid
  
  set logWinStr to my changeString(",  ,", "  ┃  ", logWinStr)
  
  return logWinStr
  
end logWin

on changeString(pSearchForStr, pReplaceWithStr, pSourceStr)
  local item_list, changedStr, astid
  
  set astid to AppleScript's text item delimiters
  
  set AppleScript's text item delimiters to the pSearchForStr
  set the item_list to every text item of pSourceStr
  set AppleScript's text item delimiters to the pReplaceWithStr
  set changedStr to the item_list as string
  set AppleScript's text item delimiters to astid
  
  return changedStr
end changeString

Hi JMichael, I would love to use your macro but unfortunately I receive an error msg when I try it: “NO Windows were found to move”. It doesn’t matter if there is one ore more open windows on my secondary screen.

I’m running KeyboardMaestro 7.3.1 on Mac OS Sierra 10.12.6. MacBook Pro 15 2017, connected to external screen through VGA. Hope this helps.

Best regards

Unfortunately, I am unable to duplicate your environment.
I'm running macOS 10.11.6 on an iMac-27, with the external screen connected via a MiniDisplayPort (MDP) to DisplayPort (DP) cable.

You might try running this script to see if the Windows on your VBA monitor are found:

property LF : linefeed

tell application "System Events"
  
  set appNameList to (name of every application process where background only is false)
  
  
  tell (every application process where background only is false)
    set winNameList to name of windows
  end tell
  
end tell

set numApps to length of appNameList
set winNameListClean to {}
set AppleScript's text item delimiters to (LF & tab & "• ")
set scriptResults to ""

repeat with iA from 1 to numApps
  
  set oAppWin to item iA of winNameList
  set scriptResults to scriptResults & LF ¬
    & item iA of appNameList & LF ¬
    & my buildNumList(oAppWin, tab)
  
end repeat

return scriptResults

--~~~~~~~~~~~~~~~~~ END OF MAIN SCRIPT ~~~~~~~~~~~~~~~~~~~~~~~~~~


on buildNumList(pList, pPrefix)
  
  set listStr to ""
  
  if (pList ≠ {}) then
    repeat with iLine from 1 to length of pList
      set listStr to listStr & pPrefix & iLine & ". " & item iLine of pList & linefeed
    end repeat
    
  else
    set listStr to pPrefix & "• <NONE>" & linefeed
    
  end if
  
  return listStr
  
end buildNumList

Let me know if it finds your VBA Windows.

Thank you very much for your reply and your support!

The script returns all my open applications with the corresponding title of the application. All apps are numbered “1.”, the ones on my MacBooks display, as well as the one(s) on the secondary screen -> meaning the script “thinks” they are all on my main display and thus cannot move, right? I guess for the script to work it should result in “2.” for the apps on my secondary screen?

Unfortunately I can only test with the (original Apple) USB-C VGA adapter, it’s the only one I bought for business purposes because this is still the most commonly supported port.

OK, I have just uploaded a new version, which I hope will work for you.
The script now searches for all apps and all windows.

Please give Ver 2.0 a try, and let us know if it works for you.

Thanks for you help, I appreciate it!!

It’s interesting. It still doesn’t work but as you can see the output in my case also contains a negative (Top) number for the windows on my secondary screen. In my test case google chrome, Calcbot and Reminders are running on the external screen, the rest on the MacBooks internal screen. Maybe this could be the missing link?

==
OK
NO Windows Were Found to Move.

Left, Top, Width, Height ┃ App ┃ WinRole ┃ Win Title

0, 23, 840, 1022 ┃ Finder ┃ AXWindow ┃ Downloads
816, 445, 863, 600 ┃ Activity Monitor ┃ AXWindow ┃ Aktivitätsanzeige (Alle Prozesse)
1316, 42, 344, 63 ┃ NotificationCenter ┃ AXWindow ┃ missing value
0, 23, 1680, 1022 ┃ Mail ┃ AXWindow ┃ Eingang (14 gefilterte Nachrichten)
0, 23, 1680, 1022 ┃ Safari ┃ AXWindow ┃ Move ALL Windows to Primary Screen - Macro Library - Keyboard Maestro Discourse
588, -877, 1260, 788 ┃ Google Chrome ┃ AXWindow ┃ Qstack - YouTube
226, 23, 1266, 1023 ┃ Things3 ┃ AXWindow ┃ Heute
1067, -653, 603, 449 ┃ Calcbot ┃ AXWindow ┃ Main
0, 23, 1680, 1022 ┃ Microsoft OneNote ┃ AXWindow ┃ Customers/Partners
1345, 23, 335, 1022 ┃ Cisco Jabber ┃ AXWindow ┃ Cisco Jabber
0, 23, 1680, 1022 ┃ Microsoft PowerPoint ┃ AXWindow ┃ the evolution of the datacenter
235, 84, 1200, 900 ┃ Keyboard Maestro ┃ AXWindow ┃ Keyboard Maestro Editor
498, -919, 1260, 788 ┃ Reminders ┃ AXWindow ┃ Erinnerungen

OK, that explains the issue. The script is checking ONLY for windows that are to the right or left of the primary screen. In your case, the other screen is above the primary screen.

I didn't even consider cases of the other screens being above/below the primary screen. That's not hard to do, but it may take me a day or so to make the mods. I won't be able to test. Would you be willing to test it for me?

Absolutely! Thank you for taking your time for this. I’m happy to test it for you :blush:

Add: Can test it on Monday CEST as soon as I’m back at work. I don’t have an external display at home.

Ready for testing

Out of curiousity I changed the virtual location of the second screen to the side and now your script worked. Would be great if you addressed the variant “external above internal”, as well :slight_smile:

OK
1 Window(s) MOVED

Left, Top, Width, Height ┃ App ┃ WinRole ┃ Win Title

0, 23, 1680, 1022 ┃ Finder ┃ AXWindow ┃ Slides
1316, 42, 344, 63 ┃ NotificationCenter ┃ AXWindow ┃ missing value
241, 23, 1200, 1023 ┃ Things3 ┃ AXWindow ┃ Heute
:arrow_left:︎:arrow_left:︎ 1920, 135, 1440, 810 ┃ Microsoft OneNote ┃ AXWindow ┃ Tech Stuff
1345, 23, 335, 1022 ┃ Cisco Jabber ┃ AXWindow ┃ Cisco Jabber
235, 84, 1200, 900 ┃ Keyboard Maestro ┃ AXWindow ┃ Keyboard Maestro Editor
0, 23, 1680, 1022 ┃ iTunes ┃ AXWindow ┃ iTunes

Hi Michael, did you have the chance to look into the code? :blush:

@JMichaelTX I am volunteering for testing this as well. It looks like an essential macro for me to be able to plug in a projector at the office without having my browser, emails… showing up in front of everybody.

Hi @JMichaelTX,

Did you determine how to detect screens that are positioned above the primary screen?

I get the following error when I try running this:

[ERROR]

Can’t get origin of {{0.0, 0.0}, {1680.0, 1027.0}}.

SCRIPT: Move Windows to Main Screen Ver: 2.0

Error Number: -1728

It appears that how ASObjC reports primary screen frame has changed, so that's why you got the error. I have fixed this.

I think so but I can't test because I don't have screens above or below my main screen.

But here is my revised script that should work. Please test and let us know if it works.

property ptyScriptName : "Move Windows to Main Screen"
property ptyScriptVer : "2.1" --  ADD Move Windows Above/Below Main Win
property ptyScriptDate : "2019-11-20" # Search for ALL Apps
property ptyScriptAuthor : "JMichaelTX"

property LF : linefeed
(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

REQUIRED:
  1.  macOS El Capitan 10.11.6+
      (may work on Yosemite 10.10.5, but no guarantees)
      
Local:  /Users/Shared/Dropbox/SW/DEV/KM/Scripts/@Move V2 All @Windows to Primary @Screen @KM @AS.scpt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
*)

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

--- OFFSETS Used When Moving Windows ---
property ptyOffsetX : 20
property ptyOffsetY : 40

set scriptResults to "TBD"

try
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  set frontApp to path to frontmost application as text -- use for dialogs
  
  --- GET COORDINATES OF PRIMARY VISIBLE SCREEN ---
  -- (all windows to the right, or to the left, of this will be moved)
  
  set primaryScreenVisibleFrame to current application's NSScreen's screens()'s firstObject()'s visibleFrame()
  
  --set primaryScreenRight to (x of origin of primaryScreenVisibleFrame) + (width of |size| of primaryScreenVisibleFrame)
  --set primaryScreenLeft to (x of origin of primaryScreenVisibleFrame)
  --set primaryScreenTop to (y of origin of primaryScreenVisibleFrame)
  --set primaryScreenBottom to (y of origin of primaryScreenVisibleFrame) + (height of |size| of primaryScreenVisibleFrame)
  
  set primaryScreenRight to (item 1 of item 1 of primaryScreenVisibleFrame) + (item 1 of item 2 of primaryScreenVisibleFrame)
  set primaryScreenLeft to (item 1 of item 1 of primaryScreenVisibleFrame)
  set primaryScreenTop to (item 2 of item 1 of primaryScreenVisibleFrame)
  set primaryScreenBottom to (item 2 of item 1 of primaryScreenVisibleFrame) + (item 2 of item 2 of primaryScreenVisibleFrame)
  
  (*
--- ALTERNATE METHOD Using KM ---
tell application "Keyboard Maestro Engine"
  set AppleScript's text item delimiters to ","
  set primaryScreenVisibleFrame to text items of (process tokens "%ScreenVisible%Main%")
  set primaryScreenRight to item 3 of mainScreenFrame
end tell
*)
  
  --- GET ALL VISIBLE APPS ---
  
  tell application "System Events"
    set appNameList to (name of every application process)
  end tell
  
  --log " App List: " & (appNameList as text)
  
  set iWin to 0
  
  --- LOOP THROUGH EACH APP  ---
  
  set logStr to "Left, Top, Width, Height  ┃   App  ┃   WinRole  ┃   Win Title" & LF & LF
  
  display notification "START Search for Windows"
  
  repeat with oAppName in appNameList
    set appName to contents of oAppName
    
    -----------------------------------------------------
    tell application "System Events" to tell application process appName
      -----------------------------------------------------
      set winCount to count of windows
      
      --display notification " App: " & appName & "   # of Windows: " & winCount
      
      repeat with iW from 1 to winCount
        
        set oWin to window iW
        
        tell oWin
          set winTitle to title
          set winSubRole to subrole
          
          if ((winTitle is not missing value) and (winSubRole ≠ "AXSystemDialog") and (winSubRole ≠ "AXUnknown")) then
            
            set {winLeft, winTop} to position
            set {winWidth, winHeight} to size
            
            set winBottom to winTop + winHeight
            
            if ((winLeft ≥ primaryScreenRight) or ¬
              (winLeft < primaryScreenLeft) or ¬
              (winTop < primaryScreenTop) or ¬
              (winTop > primaryScreenBottom)) then
              
              set logStr to logStr & my logWin({position & size & space & appName & space & role & space & title}, " ⬅︎⬅︎ ") & LF
              
              --- MOVE WINDOW ---
              
              set iWin to iWin + 1
              
              --- ADD OFFSETS to POSITION ---
              set newXPos to primaryScreenLeft + (ptyOffsetX * iWin)
              set newYPos to primaryScreenTop + (ptyOffsetY * iWin)
              set position of oWin to {newXPos, newYPos}
              
              --- MAKE SURE WIN BOTTOM DOES NOT EXCEED SCREEN BOTTOM ---
              set newBottom to newYPos + winHeight
              if (newBottom > primaryScreenBottom) then set newBottom to primaryScreenBottom
              set newHeight to newBottom - newYPos
              
              set size of oWin to {winWidth, newHeight}
            else
              set logStr to logStr & my logWin({position & size & space & appName & space & role & space & title}, " ") & LF
              
            end if
          end if -- winTitle NOT missing value
          
        end tell
        
        
      end repeat -- on each window
      
    end tell -- "System Events" --> appName
    
  end repeat -- oAppName in appNameList
  
  
  if (iWin > 0) then
    set msgStr to (iWin as text) & " Window(s) MOVED"
  else
    set msgStr to "NO Windows Were Found to Move."
  end if
  
  set msgTitleStr to ptyScriptName
  display notification msgStr with title msgTitleStr sound name "Tink.aiff"
  
  set scriptResults to "OK" & LF & msgStr & LF & LF & logStr
  
  --~~~~~~~~~~~~~ END TRY ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  
on error errMsg number errNum
  
  if errNum = -128 then ## User Canceled
    set errMsg to "[USER_CANCELED]"
  end if
  
  set scriptResults to "[ERROR]" & return & errMsg & return & return ¬
    & "SCRIPT: " & ptyScriptName & "   Ver: " & ptyScriptVer & return ¬
    & "Error Number: " & errNum
end try
--~~~~~~~~~~~~~~~~END ON ERROR ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

--- RETURN THE RESULTS TO THE KM EXECUTE SCRIPT ACTION ---
return scriptResults

--~~~~~~~~~~~~~~~ END OF MAIN SCRIPT ~~~~~~~~~~~~~~~~~~~

on logWin(pListOfProp, pPrefixStr)
  local logWinStr
  
  set astid to AppleScript's text item delimiters
  
  set AppleScript's text item delimiters to ", "
  set logWinStr to pPrefixStr & (pListOfProp as text)
  set AppleScript's text item delimiters to astid
  
  set logWinStr to my changeString(",  ,", "  ┃  ", logWinStr)
  
  return logWinStr
  
end logWin

on changeString(pSearchForStr, pReplaceWithStr, pSourceStr)
  local item_list, changedStr, astid
  
  set astid to AppleScript's text item delimiters
  
  set AppleScript's text item delimiters to the pSearchForStr
  set the item_list to every text item of pSourceStr
  set AppleScript's text item delimiters to the pReplaceWithStr
  set changedStr to the item_list as string
  set AppleScript's text item delimiters to astid
  
  return changedStr
end changeString

It worked beautifully!

It moved all apps from both of my windows that were above my primary.

The only problem I saw was a screen that was left on Desktop 2 (which was a virtual screen to the right of my primary screen). I was able to move it manually back to the primary.

Thanks!

1 Like

I second @tberry2112. I have a 27 inch screen above and to the left of my laptop main window, and the final version works perfectly. I'd suggest putting a not up with the initial download for unsuspecting users like me who start blaming Catalina for breaking a perfectly serviceable script!

Thanks for yet another great macro.
P.

1 Like

11 posts were split to a new topic: How Can I Resize All Windows on the Primary Monitor to Provide a Margin on the Right Side?

Same thing happend to me. I agreed it is not a wise thing to blame wihout knowing the results. Final version also works absolutely fine to me as well.

Just updated OP with this fix.