Repeatedly Position a Window (For Machines That Suffer From the Window Positioning Bug)

Howdy folks, I'm sharing this macro to (hopefully) help others who have had issues with the window positioning bug.

See these posts/comments for more information:

This macro uses several variables passed via the execute a subroutine action, and then an AppleScript to repeatedly move the target window until it's position matches the coordinates supplied by the variables.

Setup and use is fairly straight-forward and well documented in the macro itself. Special shoutout to @Nige_S for help polishing the AppleScript for version 1.

I used versions 1+ for over a year and a half, and now version 2 for several weeks, and everything has worked quite well. But feel free to supply feedback if for some reason it doesn’t work properly or if you feel it can be improved.

-Chris

Download Macro(s): 16)[MG-SUBRT] Position window (subroutine).kmmacros (21 KB)

Macro-Image (click to expand/collapse)

Macro-Notes (click to expand/collapse)
  • Macros are always disabled when imported into the Keyboard Maestro Editor.
  • The user must ensure the macro is enabled.
  • The user must also ensure the macro's parent macro-group is enabled.
System Information (click to expand/collapse)
  • macOS 13.6
  • Keyboard Maestro v11.0.1
AppleScript (click to expand/collapse)
------------------------------------------------------------
# Author   : Chris Thomerson (@cdthomer)
#
# Version  : 3.0.0 (Now uses bounds to set position/size when possible)
# History  : 2.1.0 (Delay is now set from Keyboard Maestro)
#            2.0.1 (Added debugging variables)
#            2.0.0 (Added loop and error handling)
#            1.0.0 (Initial script)
#
# Created  : Monday, July 18, 2022
# Modified : Sunday, November 26, 2023
# macOS    : 12.6 (Monterey) - macOS 13.4.1 (Ventura)
# Tags     : Keyboard Maestro, Window Positioning
#
# PURPOSE
# Reposition a window via a Keyboard Maestro action on 
# machines that suffer from the KM repositioning bug
#
# DISCLAIMER
# Permission to use, copy, modify, and/or distribute this
# software for any purpose with or without fee is hereby
# granted.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS
# ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
# USE OR PERFORMANCE OF THIS SOFTWARE.
------------------------------------------------------------

# get variables from Keyboard Maestro
set apscDone1 to false
set loopCount1 to 0
repeat until apscDone1 is true or loopCount1 is 15
	try
		if application "Keyboard Maestro Engine" is not running then
			repeat until application "Keyboard Maestro Engine" is running
				tell application "Keyboard Maestro Engine" to launch
				delay 0.1
			end repeat
		end if
		set kmInst to system attribute "KMINSTANCE"
		tell application "Keyboard Maestro Engine"
			set appName to getvariable "instance__App Name" instance kmInst
			set windowNames to getvariable "instance__Window Name" instance kmInst
			set windowIndex to getvariable "instance__Window Index" instance kmInst
			set leftEdge to getvariable "instance__Left Edge" instance kmInst
			set topEdge to getvariable "instance__Top Edge" instance kmInst
			set width to getvariable "instance__Width" instance kmInst
			set height to getvariable "instance__Height" instance kmInst
			set commType to getvariable "instance__Command Type" instance kmInst
		end tell
		set apscDone1 to true
	on error
		set apscDone1 to false
		set loopCount1 to loopCount1 + 1
	end try
end repeat

### set variables manually for debugging
set debug to false
if debug is true then
	set appName to "OBS"
	set leftEdge to "0"
	set topEdge to "888"
	set width to "1512"
	set height to "944"
	set windowNames to "Education System"
	set windowIndex to "1"
	set commType to "Window Index"
end if

set delayInteger to 0.1 as text
set delayTimeout to 10 as integer
set windowFrame to leftEdge & "," & topEdge & "," & (leftEdge + width) & "," & (topEdge + height)

### delimit windowFrame variable
set saveTID to AppleScript's text item delimiters
set AppleScript's text item delimiters to {","}
set windowFrame to text items of windowFrame
set AppleScript's text item delimiters to saveTID

### sets window index to integer for use as a variable
set windowIndex to windowIndex as integer

tell application appName
	if commType is "Window Name" then # send command to window with specified name
		
		set openWindowNames to name of every window ### get the name of every open window
		
		### extract each window name from the parameter supplied from KM
		set saveTID to AppleScript's text item delimiters
		set AppleScript's text item delimiters to {"|"}
		set windowNames to text items of windowNames
		set AppleScript's text item delimiters to saveTID
		
		repeat with theWindow in windowNames ### loop through each window in the supplied parameter
			try ### try to set bounds using the application's AppleScript
				tell (first window whose name contains theWindow) to set its bounds to windowFrame
			on error ### if that fails, use System Events to set window position and size
				try
					tell application "System Events" to tell application process appName to tell (first window whose name contains theWindow)
						
						### set the variables to the proper format
						set thePosition to {leftEdge as integer, topEdge as integer} as list
						set theSize to {width as integer, height as integer} as list
						
						### position the window
						if debug is true then display notification "Setting position"
						set loopCount to 0
						repeat until its position is thePosition or loopCount is delayTimeout
							set its position to thePosition
							delay delayInteger
							set loopCount to loopCount + 1
						end repeat
						
						### size the window				
						if debug is true then display notification "Setting size"
						set loopCount to 0
						repeat until its size is theSize or loopCount is delayTimeout
							set its size to theSize
							delay delayInteger
							set loopCount to loopCount + 1
						end repeat
						
					end tell
				on error
					if debug is true then display notification "No windows found matching " & theWindow
				end try
			end try
		end repeat
		
	else if commType is "Window Index" then # send command to window with specified index
		try ### try to set bounds using the application's AppleScript
			tell window windowIndex to set its bounds to windowFrame
			
		on error ### if that fails, use System Events to set window position and size
			if debug is true then display notification "Bounds failed, now trying position and size"
			tell application "System Events" to tell application process appName to tell window windowIndex
				
				### set the variables to the proper format
				set thePosition to {leftEdge as integer, topEdge as integer} as list
				set theSize to {width as integer, height as integer} as list
				
				### position the window
				if debug is true then display notification "Setting position"
				set loopCount to 0
				repeat until its position is thePosition or loopCount is delayTimeout
					set its position to thePosition
					delay delayInteger
					set loopCount to loopCount + 1
				end repeat
				
				### size the window				
				if debug is true then display notification "Setting size"
				set loopCount to 0
				repeat until its size is theSize or loopCount is delayTimeout
					set its size to theSize
					delay delayInteger
					set loopCount to loopCount + 1
				end repeat
				
			end tell
		end try
	end if
end tell

UPDATE Monday, January 02, 2023
I converted this to a subroutine and added the ability to specify a window index instead of a name. See post 1 to download the updated macro.

2 Likes

Version 2.0.0 is up. The AppleScript has been completely overhauled to use bounds (when available) to set the size and position (which is much faster than setting size and position separately via System Events). If the bounds method fails (due to an application’s lack of AppleScript support), it reverts to using System Events to set size and position.

See post 1 for more details.

1 Like