2

While in Split View in macOS, clicking the top of a window and dragging it to the left or right will fluidly switch positions of the two windows. I use this feature a lot and can't find any online information about a keyboard shortcut to do this.

Can anyone come up with an AppleScript script or solution for this without using a different window management app?

2

AFAIK There is no native default keyboard shortcut to swap the position of windows in Split View.

AFAIK There is no native default preference to set a keyboard shortcut to swap the position of windows in Split View.

That said, this answer presents an Automator Service/Quick Action method to swap the position of windows in Split View, which uses the third-party utility cliclick to perform the mouse click and drag events necessary to accomplish the task, while using the third-party utility displayplacer to provide Display resolutions along with which Display is the primary Display in multiple Display scenarios.

This currently works with:

  • One or two Displays max.
  • Primary Display on left or right.
  • Split View on left, right, or both Displays, regardless of which is the primary Display.
  • Both Displays must be aligned at their top in: System Preferences > Displays > Arrangement
    • An updated script to handle offset alignments may come at a future date. (Emphasis on may!)

The example shell script code, shown below, is used in an Automator Service/Quick Action with settings Workflow receives [no input] in [any application], using a Run Shell Script action with settings Shell: [/bin/zsh] and Pass input: [to stdin], using the example shell script code, then assigned a keyboard shortcut for the Automator Service/Quick Action in: System Preferences > Keyboard > Shortcuts > Services

I set the keyboard shortcut for the Automator Service/Quick Action, name Swap Split View Windows, to: ⌥⌘/

Note that assigning a global keyboard shortcut can be difficult to not step on a keyboard shortcut of whichever application has focus when the keyboard shortcut is pressed, and also may require adding that application to: System Preferences > Security & Privacy > Privacy or respond to a security related dialog box (more than once).

It may be better to use any of the third-party applications that can handle triggering scripts with a keyboard shortcut, then it is to use an Automator Service/Quick Action. I use applications FastScripts and Hammerspoon for many of my global keyboard shortcuts.

One reason I like using third-party applications to handle script execution and global keyboard shortcuts is that some of them intercept the keyboard shortcut before the application that has focus can receive it. This has its pros and cons in that if the keyboard shortcut is really meant for the application that has focus it's not received and whatever it is assigned to as a global keyboard shortcut triggers. Manually clicking the target menu item becomes necessary in such a case, or temporarily disabling the third-party application.

The bottom line here is that one just needs to remain aware of the modifications one has made to the system and what the potential conflicts may be.

You have been warned!



Example shell script code:

#!/bin/zsh

    # Fully Qualified Pathname of 'cliclick'  
    # and 'displayplacer' binary executable,  
    # tokenized for ease of code maintenance.

click="/usr/local/bin/cliclick"
display="/usr/local/bin/displayplacer"


###########################################################
#                                                         #
# DO NOT MODIFY BELOW UNLESS YOU KNOW WHAT YOU ARE DOING! #
#                                                         #
###########################################################


    # Verify binary executables exist and notify if not.

if [ ! -e "${click}" ]; then
    osascript -e "display alert \"The '\" & \"${click}\" & \"' binary executable is missing!...\" as critical" >/dev/null
    exit
fi
if [ ! -e "${display}" ]; then
    osascript -e "display alert \"The '\" & \"${display}\" & \"' binary executable is missing!...\" as critical" >/dev/null
    exit
fi  

##########################################################

    #     Variables
    #
    #   click = Cliclick Command Line Utility
    # display = Displayplacer Command Line Utility
    #    ditf = Display Info Temp File
    #      dc = Display Count
    #      md = Mirror Displays [Off|On]
    #     pdw = Primary Display Width   
    #     sdw = Secondary Display Width
    #   pdoyc = Primary Display Origin Y Coordinate
    #   sdoyc = Secondary Display Origin Y Coordinate
    #     tdw = Total Display Width
    #     dyc = Drag Y Coordinate
    #    doyc = Display Offset Y Coordinate
    #   mryco = Mouse Return Y Coordinate Offset
    #     efo = Easing Factor Option of cliclick command    
    #       c = x coordinate of cliclick c command
    #      dd = x coordinate of cliclick dd command
    #     wm1 = Wait Milliseconds of cliclick w command
    #      xy = Current Mouse Coordinates
    #       x = Horizontal Mouse Coordinate
    #       y = Vertical Mouse Coordinate   


ditf="/private/tmp/display.ssvw"
"${display}" list > "$ditf"
dc="$(awk '/current mode/ {i++} END { print i }' "$ditf" )"
md="$(system_profiler SPDisplaysDataType | awk '/Mirror:/{print $2; exit}')"
pdw="$(awk '/current mode/ { sub(/res:/, "" ,$3); sub(/x[0-9]+/, "", $3); print $3; exit }' "$ditf")"
sdw="$(awk '/current mode/ { v = $3 } END { sub(/res:/, "", v); sub(/x[0-9]+/, "", v); print v }' "$ditf")"
pdoyc="$(awk '/Origin:/ { sub(/\(.*,/, "", $2 ); sub(/\)/, "", $2 ); print $2; exit }' "$ditf")"
sdoyc="$(awk '/Origin:/ { v = $2 } END { sub(/\(.*,/, "", v); sub(/\)/, "", v); print v }' "$ditf")"
tdw="$(( pdw + sdw ))"
dyc="25"
doyc="$(( sdoyc + dyc ))"
mryco="200"
efo="0"
c="70"
dd="${c}"
wm1="125"
xy="$("${click}" p)"
[[ $xy =~ ([-0-9]+)(,)([-0-9]+) ]] && x="${match[1]}" y="${match[3]}"
[[ $y -lt "${mryco}" ]] && xy="${x},${mryco}"

##########################################################


cleanup() {
    # Delete Display Info Temp File
    # Using Z Shell Modifiers Syntax
    cd "${ditf:h}" || exit 
    rm "${ditf:t}" 
}

if [[ ${dc} -eq 1 ]]; then
            # Display Count equals one.
    "${click}" -e ${efo} c:${c},0 w:${wm1} dd:${c},${dyc} dm:${pdw},${dyc} du:${pdw},${dyc} m:${xy}
    cleanup 
    exit
fi

if [[ ${dc} -gt 1 ]] && [[ ${md} == On ]]; then
        # Display Count is greater than one and Mirror Displays is On.  
    "${click}" -e ${efo} c:${c},0 w:${wm1} dd:${c},${dyc} dm:${pdw},${dyc} du:${pdw},${dyc} m:${xy}
    cleanup 
    exit
fi

if [[ ${dc} -gt 2 ]] && [[ ${md} == Off ]]; then
        # Only coded for maximum of two displays at this time. Further development may come.
    osascript -e 'display alert "As currently coded this only works with maximum of two displays!"' >/dev/null
    cleanup
    exit
fi  

    # Display Count equals two and Mirror Displays is Off.

if [[ ${pdoyc} -eq 0 ]] && [[ ${sdoyc} -eq 0 ]]; then

        # Both displays are aligned at the top.

    if [[ ${x} =~ ^\-[0-9]+$ ]]; then

            # SD is on Left when "${x}" is negative and SV is on the Left.

            # Primary Display: Right
            # Secondary Display: Left, with Tops Aligned
            # Split View on Secondary Display: Left

        c="$(( -sdw + c ))"
        dd="${c}"
        "${click}" -e ${efo} c:=${c},${sdoyc} w:${wm1} dd:=${dd},${doyc} dm:=-1,${doyc} du:=-1,${doyc} m:${xy}
        cleanup
        exit
    fi

    if [[ ${x} -gt ${pdw} ]]; then

            # PD is on the Left when "${x}" is greater than PD width.

            # Primary Display: Left
            # Secondary Display: Right, with Tops Aligned
            # Split View on Secondary Display: Right

        c="$(( c + pdw ))"
        dd="${c}"
        "${click}" -e ${efo} c:${c},${sdoyc} w:${wm1} dd:${dd},${doyc} dm:${tdw},${doyc} du:${tdw},${doyc} m:${xy}

    else

            # Primary Display: [Right|Left]
            # Secondary Display: [Left|Right], with Tops Aligned
            # Split View on Primary Display: [Right|Left]

        "${click}" -e ${efo} c:${c},${sdoyc} w:${wm1} dd:${dd},${doyc} dm:${tdw},${doyc} du:${tdw},${doyc} m:${xy}

    fi

else
        # As currently coded the displays must be aligned, and notify if not. Further development may come.
    osascript -e "display alert \"The displays must be aligned at their top in System Preferences > Display > Arrangement!...\" as critical" >/dev/null
fi

cleanup


Notes:

Change the fully qualified pathname of the cliclick binary executable in the first line of code as applicable. I have it located at: /usr/local/bin/cliclick

Change the fully qualified pathname of the cliclick binary executable in the first line of code as applicable. I have it located at: /usr/local/bin/cliclick

The mouse pointer must be on the Split View you want swapped. If it is not on a Split View, then the application whose frontmost will have its main menu opened, without anything else happening. This is unavoidable as coded, because to determine whether or not one is on a Space/Desktop that is in Split View is beyond the scope of its current code due to the complexity of coding for such a scenario.

While your question mentioned AppleScript, nonetheless, I went with the "or solution for this without using a different window management app" approach using a shell script with the use of the cliclick third-party utility, as I believe basic vanilla AppleScript is not capable of doing the job and would most likely require AppleScript-ObjC.

I find that using CheatSheet helps me to examine all the keyboard shortcut assigned to any given application.

The example shell script code, shown above, was tested under macOS Catalina with Language & Region settings in System Preferences set to English (US) — Primary and worked for me without issue1.

  • 1 Assumes necessary and appropriate setting in System Preferences > Security & Privacy > Privacy have been set/addressed as needed.

Note that I am not associated with the developers of any of the products mentioned herein, just a satisfied user of the aforementioned products.

7
  • I have it working partially and there are only two issues. It won't trigger with the basic macOS finder app. Also, it triggers on desktop 1 while trying to execute it on desktop 2 using a second monitor.
    – Ryan Jones
    Jun 6 at 4:08
  • @Ryan Jones, RE: "It won't trigger with the basic macOS finder app." -- It works with Finder for me. See the Notes: section regarding System Preferences > Security & Privacy > Privacy -- RE: "Also, it triggers on desktop 1 while trying to execute it on desktop 2 using a second monitor." -- I've added a note to the answer. Note: As currently coded, this only works on systems with a single display, or if multiple displays, the display that is the primary display. -- I also changed 100 to 200 in [ "$y" -lt 200 ] && xy="${x},200" and updated why in the appropriate place. Jun 6 at 15:46
  • Regarding the quick action not executing in Finder, In System Preferences > Security & Privacy > Privacy > Automation I have the following Finder boxes checked (Google Chrome, System Events, Terminal). I should mention that I'm using macOS Big Sur v11.4. RE: "Note: As currently coded, this only works on systems with a single display, or if multiple displays, the display that is the primary display." -- I appreciate the work you have done and will utilize this solution. If you get a second wind, I would appreciate code that works on both displays.
    – Ryan Jones
    Jun 6 at 17:37
  • @Ryan Jones, In System Preferences > Security & Privacy > Privacy > Accessibility do you have Finder there and checked? If not, then add it. --RE: "If you get a second wind, I would appreciate code that works on both displays." -- I have figured out how to make it work on a second display that is not the primary display by hard coding coordinates to match my setup. However, dynamically coding it may be an issue, I'll just have to research and test a dynamic coding approach. In the interim, using two Automator Quick Action with different code may be the way to go. Jun 6 at 18:17
  • @Ryan Jones, Let me work on it a bit and I'll update the answer with something, if only an interim solution if a dynamically coded one eats up too much of my time. Jun 6 at 18:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .