Friday, April 27, 2012

easy window positioning under MacOSX

One of the best feature Windows 7 has is this ability to quickly move windows through a simpl keystroke :

  • Windows-Key + Left-Arrow : move/resize the window on the left-half part of the screen
  • Windows-Key + Shift + Left-Arrow : move the window to the next display screen, if you have one
  • etc.
I figured out to mimic this feature under MacOSX, thanks to Applescript, Automator and Service feature with its shortcut capability. Of course this is not as good as what I have under Win7... but it helps.

Note that if I didn't have to travel for 5 hours from Hawaii, I would not have found the energy to fight with Applescript and Automator. Those tools are ugly for people who are used to write code... 

Applescript code

Here is the code for moving the active window the right-half of the screen :

set front_app to (path to frontmost application as Unicode text)
tell application "Finder"
    set dt to desktop
    set _scrbounds to bounds of window of dt
end tell
tell application front_app
    set mywin to window 1
    set _bounds to mywin's bounds
    set mywin's bounds to {(item 3 of _scrbounds) / 2, 0, (item 3 of _scrbounds), item 4 of _scrbounds}
end tell

Few explanations about this code :
  • we find the front-application name
  • get back the screen size by asking the finder's window
  • get the boundaries of the window 1 (the active one)
  • resize this window and move it
I have to confess that I am not an applescript expert. In fact I wanted to confess that this language is the worst language I ever experienced. The fact that you write as you would write English makes it very suspicious for those like me who are used to write regular C++ code... it simply adds more confusion.

Automator

The next tool that seemed useful is Automator. Again, I am not an expert in this tool, but I saw that it allows you to generate simple actions. And more importantly: it allows you to create so-called services : operations that are contextually available in some applications, depending on what got selected etc.

First, I created a new project, using "service" from Automator's wizard :
I allowed this service to be accessible from any application. I did setup the service so that it doesn't receive anything at all. 

The reason is that this service will take its input from the active window, as shown in the script.

I added the one component I needed : the applescript component

You will then copy the script code within the code put by default in this window :

on run {input, parameters}
... put the code...
end run

Keyboard Shortcut of the services

Now, you should have the service available in the service menu.

The last step is to define a shortcut key stroke (as you can see on the right side of the menu items)
You need to go to system preferences application, in the keyboard section. You should be able to find your service in the section "General" (put here because the input of the service doesn't was set to No input).

More complex script

I wrote another more complex script that will setup windows of the same application in Mosaic mode
  • for # of windows <= 4 : put them in 2 rows by 2 columns
  • for # of windows = 2 : put the two windows on 2 columns, side by side
  • for # of windows > 4 : put the windows in 3 columns by N rows
set front_app to (path to frontmost application as Unicode text)
tell application "Finder"
    set dt to desktop
    set _scrbounds to bounds of window of dt
end tell
tell application front_app
    -- activate
    set nWins to count of window
    if nWins > 4 then
        set nx to 3
        set ny to round ((nWins / 3) - 0.5)
        set w to (item 3 of _scrbounds) / 3
        set h to (item 4 of _scrbounds) / ny
    else
        set nx to 2
        if nWins > 2 then
            set ny to 2
        else
            set ny to 1
        end if
        set w to (item 3 of _scrbounds) / 2
        set h to (item 4 of _scrbounds) / ny
    end if
    set n to 0
    repeat with y from 0 to ny - 1
        repeat with x from 0 to nx - 1
            if n < nWins then
                set toto to window (n + 1)
            set _bounds to toto's bounds
            set toto's bounds to {x * w, y * h, (x + 1) * w, (y + 1) * h}
            set n to n + 1
            end if
        end repeat
    end repeat
end tell

This code can certainly be optimized. But franckly, applescript is such a PITA that I really can't anymore :-)
Feel free to modify it or improve it.

I personally mapped this script on Command+Shift+Up Arrow

Note: The keyboard shortcut setup of the system preference is really clunky. Some shortcuts won't work (because they are reserved to specific actions, already). And I saw on my Macbook Air that left/right/up/down arrows capture are mixed-up...

Have fun...