Moving and resizing windows using quicksilver

I recently received an external monitor to go with my laptop. With this new screenspace comes problems. I want to write a script that I can use with quicksilver that takes the frontmost window and sends it to the other monitor and resizes it based on some preference. I was thinking maybe some List that I can update within the script that holds the preferred ratio of window to screen based on the application. Since I am brand new to applescript, I am having some troubles with the syntax and getting started. I read some other posts and now I altered some code. As of now I’m just experimenting with sending one window to the right. This works in the scriptEditor application, but when I click the script outside the application I get an error, NSReceivererror 4. This error seems to be notorious. I do have universal access enabled as well as the gui stuff, but I still have the problem. Here is the code. Any help would be appreciated, and it’s great to be here. Seems like the place to be!:smiley:

tell application "System Events"
	-- setup current resolution details 
	-- These will have to be changed if/when the resolution changes 
	set resolutionX to 1280
	set resolutionY to 854
	-- 1st, determine current X & Y Positions 
	set theWindow to window 1 of (first process whose frontmost is true)
	set thePosition to position of theWindow
	--display alert name of theWindow & ": " & ((first item of thePosition) as string) & ", " & ((second item of thePosition) as string)
	set currXPos to (first item of thePosition)
	set currYPos to (second item of thePosition)
	-- Now we move the window to the right 
	set position of theWindow to {(currXPos + 1280), (currYPos - 500)}
end tell

The correct syntax for most apps is

tell application "YourApp" to set bounds of window 1 to (a,b,c,d) -- with a = topleft x coord, b = topleft y coord, c = bottomright x coord, d = bottomright  y coord

However, not all applications support it and there is no generic system level approach that I know of.

Hi Eelco,

you’re right, but talking to System Events (like GUI scripting) every process, which has an open window,
has also a position property.
You can see it with

tell application "System Events"
	set hasWindow to {}
	set hasNowindow to {}
	repeat with i in (get processes)
			set end of hasWindow to position of window 1 of i
		on error
			set end of hasNowindow to name of i
		end try
	end repeat
end tell

That’s awesome! I tried it out, I also added a list that keeps track of the names of the windows that are opened. The problem is that this list isn’t kept in any order. I need to find the window that is selected, for example firefox or mail, but when I try to open the script it thinks that the finder is the script runner is the opened application.

I thought about it a little more. So I do want to select the frontmost window and then move and resize it. The problem is that when I open the script that frontmost window no longer exists. There isn’t a frontmost window because now I am opening a script, the active application, and it doesn’t have a window. It would be easy if I knew which application that I was going to move, but I want this to be general so that I can use it on any window.

If you run your script from the script menu, then this shouldn’t happen. Your front application shouldn’t become the script. But if you run your script in such a way that your script becomes the frontmost application then you can get back to the original front application and set its name to a variable that you can use in your script to move the window.

tell application "System Events"
	keystroke tab using command down -- puts you back in the original application
	set origApp to name of first process whose frontmost is true -- gets the name of the original application
end tell
tell me to activate -- puts you back into the script if you need it

get origApp

Remarkable indeed.
Normally I’d work with bounds (the only thing that an app can set itself) but that’s apparently not on the menu of System Events’ consumables.
The other way around, position and size are not supported (/settable) via the app itelf.

Returning to the original question, my first shot at a solution would be the combination of both that seems to works well here:

tell application "System Events"
	set wdArray to {}
	set hasNowindow to {}
	set gp to (get processes)
	repeat with i from 1 to count of gp
		repeat with j from 1 to count of windows of (item i of gp)
				set end of wdArray to {gp's item i's name, name of window j of item i of gp, position of window j of item i of gp, size of window j of item i of gp}
			on error
				set end of hasNowindow to name of item i of gp
			end try
		end repeat
	end repeat
end tell
set deltaX to 10
set deltaY to 10
repeat with k in wdArray
	set nbds to {(k's item 3's item 1) + deltaX, (k's item 3's item 2) + deltaY, (k's item 3's item 1) + (k's item 4's item 1) + deltaX, (k's item 3's item 2) + (k's item 4's item 2) + deltaY} --, k's item 3's item 3, k's item 3's item 4}
	if k's item 2 as string ≠ "" then tell application (k's item 1) to set bounds of window (k's item 2 as string) to nbds
end repeat

This shifts (most) app windows to any (deltaX,deltaY), some offscreen checks may come in handy with multiple monitors.

Hence, to simplify this (and avoid System Events where possible):

set hasNoWindow to my wdShift(10,10)

on wdShift(deltaX, deltaY)
	tell application "System Events" to set gp to (name of application processes)
	set hasNowindow to {}
	repeat with i in gp
		tell application i
				get window 1's bounds
				repeat with j from 1 to count of windows
					set bds to window j's bounds
					set window j's bounds to {(bds's item 1) + deltaX, (bds's item 1) + deltaY, (bds's item 3) + deltaX, (bds's item 4) + deltaY}
				end repeat
			on error
				set end of hasNowindow to i as string
			end try
		end tell
	end repeat
	return hasNowindow
end wdShift

or try:

repeat 8 times
	my wdShift(random number from -20 to 20, random number from -20 to 20)
end repeat

This is what I ended up to make it work. I just set my script visible to false, and things seem to work.

tell application "System Events"
	set visible of process "MMR" to false
	set hasNowindow to {}
	repeat with i in (get processes)
			if (frontmost of i is true) then
				set frontWindow to window 1 of i
				set frontWindowPos to position of window 1 of i
				set appname to name of i
				set w to item 1 of frontWindowPos
				set h to item 2 of frontWindowPos
				if (w > 1280) then -- then it is in the right window
					set newWidth to (((w - 1280) / 1680) * 1280)
					set newHeight to ((((h + 520) / 1050) * 854) + 22)
				else --then it is in the left
					set newWidth to (((w / 1280) * 1680) + 1280)
					set newHeight to (((h / 800) * 1050) - 548)
				end if
				-- display dialog appname
				-- choose from list frontWindowPos
				tell application appname
					set position of frontWindow to {newWidth, newHeight}
				end tell
			end if
			--exit repeat -- have the frontmost
		on error
			set end of hasNowindow to name of i
		end try
	end repeat
end tell

I see that you apply resize and shift.
What are the pixel dimensions - and arrangements of your monitors that you based this on…?

Well, actually I haven’t resized anything yet. I just moved the screen to the left monitor or the right. Basically, that whole math part is getting the ratio of where the window is on the screen, and then I just place the window in the other monitor using that ratio. The reason the numbers look so weird is that the monitors dimensions aren’t lined up exactly.

they are 1280x854,
and 1680x1050

Now I have to actually resize these windows. What is the function to resize windows?


Not much more than something like:

set newWidth to round(4*width/3) 

as you already do.

to remain in the available monitor space, however, you will have to include some checks to see on which monitor the original window is.
finding out the screensize can be tricky, I use:

tell appliation "Finder" to set screenbounds to bounds of window of desktop

which returns the biggest square by adding the two screen sizes, whereas with different sizes you may end up in vacuum up/under the smaller screen.

The other way I tried is to call a shell script

tell (do shell script "defaults read /Library/Preferences/ | grep -w Width") to set {monitor1_width, monitor2_width} to {word 3 as number, word 6 as number}

but it has not always been reliable as I found out: it returns the size of the external monitor even when that has been disconnected.
My guess is that windowserver needs to be refreshed but I do not know the command for that.

I made a reliable script for this Eelco. You can see it here.