Resizing Windows of Unscriptable Applications in AppleScript
I am working on an application that moves and resizes windows of other applications on OSX. The main application is written in Cocoa but the resizing part is done in AppleScript as Cocoa does not seem to have this kind of feature.
Here is the result of a regular call to A开发者_开发技巧ctionScript (passed as a string to NSAppleScript):
tell application "TextEdit"
set currentWindow to the window id 5184
set bounds of the currentWindow to {2855, 218, 3790, 578}
end tell
The Window ID is obtained using the CGWindowListCopyWindowInfo API introduced in OSX 10.6.
This approach works fine for most windows but fails for applications that are unscriptable. The most prominent example is OSX's own Preview.
I have tried "telling" System Events instead of Preview using variations of this code
tell application "System Events"
set bounds of window 5184 to {1920, -502, 2855, 578}
end tell
However, OSX gives me an error message:
"System Events got an error: Can’t set bounds of window 5184 to {1920, -502, 2855, 578}."
The same happens when just trying to get a reference to the window:
tell application "System Events"
get window 5184
end tell
I have doublechecked that the window exists and the window ID is correct.
What is the proper way to programmatically resize unscriptable windows on OSX? I can see it is possible from applications such as moom.
Any advice - be it Cocoa- or AppleScript-Based or something else entirely - is more than welcome.
Unfortunately, the AppleScripting the windows of an app is up to the developer of the app -- there's no clean way to do it in general. Look at this script I wrote some years ago for all the special cases:
-- Get screen bounds and origins
set f to (path to preferences from local domain as Unicode text) & "com.apple.windowserver.plist"
tell application "System Events" to set {{|Width|:w1, |Height|:h1, |OriginX|:OX1, |OriginY|:OY1}, {|Width|:w2, |Height|:h2, |OriginX|:OX2, |OriginY|:OY2}} to value of property list items of property list item 1 of property list item "DisplaySets" of property list file f
set SecondaryScreenBounds to {OX2, OY2, OX2 + w2, OY2 + h2}
set RHedge to OX1
set BOTedge to OY1
tell application "Finder"
-- Get the running apps (excluding those with special windows)
set |running| to name of processes whose visible is true and name is not "Finder" and name is not "QuickSilver" and name is not "CopyPaste" and name is not "DropCopy" and name is not "iPulse"
repeat with anApp in |running|
try -- for a scriptable App with window bounds property
tell application anApp
set allWindows to (every window)
repeat with aWindow in allWindows
set Wbounds to (get bounds of aWindow)
if item 1 of Wbounds > RHE or item 2 of Wbounds > BoE then my moveWindows(contents of anApp)
end repeat
end tell
on error -- for an App with window position & size properties
tell application "System Events"
tell application process anApp
set allWindows to (every window)
repeat with aWindow in allWindows
set {p1, p2} to aWindow's position
if p1 ≥ RHedge or p2 ≥ BOTedge then my moveWindows(contents of anApp)
end repeat
end tell
end tell
end try
end repeat
-- for the Finder
set allWindows to (every window whose visible is true)
repeat with aWindow in allWindows
set Wbounds to bounds of aWindow
if (item 1 of Wbounds) > RHedge or (item 2 of Wbounds) > BOTedge then
set bounds of aWindow to {200, 200, 1200, 800}
end if
end repeat
end tell
-- for Safari
if "Safari" is in |running| then tell application "Safari"
set Wind to name of windows
set Wbounds to bounds of windows
repeat with k from 1 to count Wind
set W to item k of Wind
set B to item k of Wbounds
if (item 1 of B) ≥ RHedge or (item 2 of B) ≥ BOTedge then
set bounds of window W to {200, 200, 1200, 800}
end if
end repeat
end tell
-- for HoudahSpot
if "HoudahSpot" is in |running| then tell application "System Events" to tell process "HoudahSpot"
set W to name of windows
set B to position of windows
repeat with k from 1 to count W
if item k of W is not missing value and (item 1 of item k of B) ≥ RHedge then set position of window (item k of W) to {100, 100}
end repeat
end tell
-- for Activity Monitor
if "Activity Monitor" is in |running| then tell application "System Events" to tell process "Activity Monitor"
set W to name of windows
set B to position of windows
repeat with k from 1 to count W
if item k of W is not missing value and (item 1 of item k of B) ≥ RHedge then set position of window (item k of W) to {100, 100}
end repeat
end tell
-- for 1Password
if "1Password" is in |running| then tell application "System Events" to tell process "1Password"
set W to name of windows
set B to position of windows
repeat with k from 1 to count W
if item k of W is not missing value and (item 1 of item k of B) ≥ RHedge then set position of window (item k of W) to {100, 100}
end repeat
end tell
-- for iCal
if "iCal" is in |running| then tell application "iCal"
set iCB to bounds of window "iCal"
if item 1 of iCB ≥ RHedge or item 2 of iCB ≥ BOTedge then
set bounds of window "iCal" to {100, 100, 1200, 1000}
end if
end tell
-- for a Help Window
tell application "System Events"
if exists process "Help Viewer" then tell process "Help Viewer"
set W to windows
repeat with w1 in W
set position of w1 to {200, 200}
end repeat
end tell
end tell
to moveWindows(anApp)
tell application "System Events"
if anApp is "ScriptLight" then
tell process "ScriptLight" to set position of window 1 to {200, 200}
else if anApp is "PowerKey" then
tell process "PowerKey" to set position of window "PowerKey" to {200, 200}
else if anApp is "Script Debugger 4" then
tell application process "Script Debugger 4"
set allWindows to (every window)
repeat with aWindow in allWindows
set {p1, p2} to aWindow's position
if p1 ≥ 1680 or p2 > 1050 then set aWindow's position to {100, 100}
end repeat
end tell
end if
end tell
end moveWindows
You mentioned Preview, so open a window in Preview and run this script.
tell application "System Events"
tell process "Preview"
set theWindows to windows
return properties of (item 1 of theWindows)
end tell
end tell
Look at the properties that are returned. There is no "id" property so you can't access windows that way. There is no "bounds" property so you can't set the bounds. Also notice that you have to get the windows from a "process" in the system events call. So if you want to use system events your code is pretty far off.
Your best bet would be to find the name of the window you want to target. Then in applescript you get theWindows as I have above, then loop through them checking their name. When you find the proper name you have found the appropriate window. Then you could set the "size" and "position" properties of that window.
Good luck because your task is a large one!
I was having the same issue trying to resize an application using similar logic to what you were using.
In my case I needed to resize an application to match the screen size and I found that you could use "System Events" if you grant script editor assistive access. If you save the script as an app you will need to grant that app access instead.
The following script simply grabs the screen size, reopens the app to make sure it is open, then uses "System Events" to set the size of the front window.
NOTE: I'm targeting an application called "cefclient" below
set theApp to "cefclient"
tell application "Finder"
set screenResolution to bounds of window of desktop
end tell
set screenWidth to item 3 of screenResolution
set screenHeight to item 4 of screenResolution
tell application theApp
activate
reopen
end tell
tell application "System Events"
tell process theApp
set the size of front window to {screenWidth, screenHeight}
end tell
end tell
精彩评论