Log in

No account? Create an account


Keyboard shortcuts for positioning windows in Mac OS X

« previous entry | next entry »
30th Jul 2010 | 02:41

It took me a few false starts to work out how to create arbitrary keyboard shortcuts in Mac OS X 10.6 "Snow Leopard", so with any luck this article will make it easier for other people who want to do something similar...

The way to attach an arbitrary script to a keyboard shortcut is as follows:

  • Open Automator.app and create a new "service".
  • To the left of the Automator window is a library of actions, towards the bottom of which is the "Utilities" category. Select it.
  • In the list of actions immediately to the right are interesting things like "Run Shell Script" and "Run AppleScript". Since I wanted to script the Mac layer (not the Unix layer) I double-clicked on the latter.
  • A box appears into which you can type your script. More about that below.
  • When you save your script It will appear in the "services" submenu of every application.
  • The script is saved in ~/Library/Services/Something.workflow which is a kind of .app directory. The interesting bit is the XML inside .../Contents/document.wflow
  • Also in the Services submenu is an option to open Services Preferences. Select it.
  • This takes you to the Services list on the Keyboard Shortcuts pane of System Preferences which allows you to control what appears in the services submenu. Your new service should be at the bottom of the list. Bizarrely, you can't set a keyboard shortcut for it directly on this list, so just ensure its tickybox is turned on.
  • Click the "+" button to add an application shortcut.
  • Choose "All Applications" from the list.
  • Next to "Menu Title" type in the name of your service.
  • Give it a keyboard shortcut and click "Add".
  • Your service and its keyboard shortcut will now appear in the "Application Shortcuts" list.
  • Your keyboard shortcut should also appear next to your service in the services submenu in all applications.

I tried doing something similar using the AppleScript Editor and the script menu, but for some reason the script menu is not eligible for keyboard shortcuts.

I wanted shortcuts to move a window into the left or right halves of the screen or maximize it to full screen. This is slightly less straightforward than it should be. See the comments in the following for details:

tell application "Finder"
	set screenBounds to bounds of window of desktop
	set Xmin to item 1 of screenBounds
	set Ymin to item 2 of screenBounds
	set Xmax to item 3 of screenBounds
	set Ymax to item 4 of screenBounds
	-- use the following line to move windows to the right half of the screen
	set Xmin to (Xmin + Xmax) / 2
	-- or this line to move them to the left half
	set Xmin to (Xmin + Xmax) / 2
	-- or leave them out to maximize
	set Xmid to (Xmin + Xmax) / 2
	set Ymid to (Ymin + Ymax) / 2
end tell

-- normally you should be able to just use "name of current application"
-- but when testing this using osascript in Terminal.app you end up
-- trying to move osascript's windows not Terminal.app's, and that fails.
tell application "System Events"
	set myFrontMost to name of first item of (processes whose frontmost is true)
end tell

	tell application myFrontMost
		if resizable of front window then
			set bounds of front window to {Xmin, Ymin, Xmax, Ymax}
			-- just move non-resizable windows
			-- weirdly shaped preferences windows are amusing, though :-)
			set winBounds to bounds of front window
			set wXmin to item 1 of winBounds
			set wYmin to item 2 of winBounds
			set wXmax to item 3 of winBounds
			set wYmax to item 4 of winBounds
			set w to wXmax - wXmin
			set h to wYmax - wYmin
			set bounds of front window to {Xmid - w / 2, Ymid - h / 2, Xmid + w / 2, Ymid + h / 2}
		end if
	end tell
on error
	-- the above can fail if the target application is not scriptable enough
	-- one prominent example is Preview.app
	-- to solve that problem we use the GUI scripting functionality
	-- you need to go to System Preferences -> Universal Access
	-- and "enable access for assistive devices"
	tell application "System Events"
		tell process myFrontMost
			set position of front window to {Xmin, Ymin}
			set size of front window to {Xmax - Xmin, Ymax - Ymin}
		end tell
	end tell
end try

| Leave a comment |

Comments {10}

(Deleted comment)

Tony Finch

from: fanf
date: 30th Jul 2010 11:17 (UTC)

A bit of experimentation reveals that any Application Shortcuts you set through the Keyboard Preferences pane override the application's default shortcuts. So you can just add a Firefox-specific shortcut for the menu item "Quit Firefox" and set it to ⌥⌘Q or whatever. I don't think you can globally rebind Quit in one go since each app puts its name in its quit menu item. Note that ⇧⌘Q is bound to Log Out by default.

Reply | Parent | Thread

(Deleted comment)

Tony Finch

from: fanf
date: 30th Jul 2010 12:19 (UTC)

Hmm, I tested with Google Chrome and it also works with Safari but not with Firefox. This is clearly a bug in Firefox.

Reply | Parent | Thread

(Deleted comment)

Tony Finch

from: fanf
date: 30th Jul 2010 12:30 (UTC)

Reply | Parent | Thread

Overriding CMD-Q for Firefox

from: coordinatedorg
date: 30th Jul 2010 11:46 (UTC)

Unfortunately, that's never worked for me (FF3.6.8 on OS-X 10.6.4).
I have 'Quit Firefox' bound to SHIFT-CTRL-ALT-CMD-Q!
Firefox ignores the OS-X keyboard preferences, and still intercepts CMD-Q.

I've misplaced my bookmark to the relevant Bugzilla ID, but I believe this is a known problem and part of a wider-scale issue with Firefox's keyboard handling on OS-X.

Hoping this is fixed in FF4, but haven't tested whether this is fixed in FF4b yet...

Best wishes,

Reply | Parent | Thread

Tony Finch

Re: Overriding CMD-Q for Firefox

from: fanf
date: 30th Jul 2010 12:50 (UTC)

Looks like the relevant bug is https://bugzilla.mozilla.org/show_bug.cgi?id=429824

Reply | Parent | Thread

from: coordinatedorg
date: 30th Jul 2010 12:52 (UTC)

Cool. I've been using similar AppleScript with iTerm's
Library/Application\ Support/iTerm/AutoLaunch.scpt
to do similar things (open multiple SSH sessions in multiple tabs in multiple windows in different Spaces).
[Note to self: share this code soon!]

I also like MercuryMover for general window movement/placement (which supports limited keyboard "bookmarks"):

Reply | Thread

Tony Finch

from: fanf
date: 30th Jul 2010 12:59 (UTC)

For about 12 years I have had some very formulaic window positioning shortcuts in my X window manager setup. (That is, general window placement usually isn't what I want.) I'm pleased to find out that Mac OS X can do basically the same thing without add-ons :-)

Reply | Parent | Thread

Not just positioning, but layout too

from: ext_4004
date: 31st Jul 2010 01:04 (UTC)

I use an applescript to layout windows into non-overlapping 2-column views, so that the screen is filled and the windows are equi-sized. Mostly I use this in Adium when I have a bunch of conversation windows open, to tile them across the screen in a sensible way. I don't have an easy way of posting this as a file right now, but here it is inline for copy/pastage:

property numCols : 2
property screenWidth : 1440
property screenHeight : 900
-- If you don't want to hard-code your screen width, because eg. you use multiple screens with differing properties at different times, then uncomment the 2 lines below, or use the 'bounds of window of desktop of application "Finder"' trick
--set screenWidth to word 3 of (do shell script "defaults read /Library/Preferences/com.apple.windowserver | grep -w Width") as number
--set screenHeight to word 3 of (do shell script "defaults read /Library/Preferences/com.apple.windowserver | grep -w Height") as number

set menubarHeight to 22

tell application "System Events"
set frontApp to name of first application process whose frontmost is true
end tell

--some apps are wacky and put the windows higher for some reason, adjust for this bug.
if (frontApp is equal to "Finder" or frontApp is equal to "Microsoft Entourage") then
set menubarHeight to 44
end if
--leave room for the Excel Toolbar
if (frontApp is equal to "Microsoft Excel") then
set menubarHeight to 55
end if

tell application frontApp
-- We are going to not layout any windows that are invisible or that do not have a title
set windowList to (index of windows whose visible is true and name is not "" and name is not "Contacts")
set windowCount to count of windowList

-- Special case is there's only one window
if windowCount = 1 then
set numRows to 1
set numCols to 1
set numRows to round (windowCount / numCols)
end if

repeat with j from 0 to numRows - 1
repeat with i from 0 to numCols - 1
-- If we've done all the windows, then just get out
if (j * numCols + i + 1) > windowCount then
exit repeat
end if
-- Get a handle to the window we might want to resize
set theWindow to item (j * numCols + i + 1) of windowList

-- resize the window
set bounds of (window index theWindow) to {¬
round (i * screenWidth / numCols), ¬
menubarHeight + (round (j * (screenHeight - menubarHeight) / numRows)), ¬
round ((i * screenWidth / numCols) + (screenWidth / numCols)), ¬
round ((menubarHeight + (round (j * (screenHeight - menubarHeight) / numRows))) + (screenHeight - menubarHeight) / numRows) ¬
end repeat
end repeat
end tell
on error the error_message number the error_number
display dialog "Error: " & the error_number & ". " & the error_message buttons {"OK"} default button 1
end try

Reply | Thread

Thanks for the tip - however its not working yet

from: anonymous
date: 7th Sep 2010 11:27 (UTC)

I'm trying to follow instructions, and everything OK except all my 'Services' menus still say "No Services Apply". Any advice?

Rob Cole

Reply | Thread

Tony Finch

Re: Thanks for the tip - however its not working yet

from: fanf
date: 7th Sep 2010 12:37 (UTC)

I *think* (without testing) you need to go to system preferences -> keyboard -> keyboard shortcuts. Choose "services" in the left panel. Ensure the services that you want are enabled. Choose "application shortcuts" in the left panel to give services key shortcuts.

Reply | Parent | Thread