Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AppleScript: Use Lion Fullscreen

There doesn't seem to be any information on this in the official Apple documentation. How do you make an application use Lion's new fullscreen feature via AppleScript?

like image 739
Abdulla Avatar asked Nov 21 '11 16:11

Abdulla


3 Answers

Not only is it not documented, but the behavior is downright byzantine. The following applies to Mountain Lion (as of 10.8.1), but I suspect it applies to Lion equally.

The short of it:

You can only examine a fullscreen window's state if it is the active window of the frontmost application. Thus, you must first activate any window you want to examine.

If that window is indeed currently in fullscreen mode but not the active one, activating will take some time - the duration of the transition animation - only after which the window is accessible programmatically.

As long as you're only interested in the active (front) window of the active (frontmost) application, everything's dandy; you're in for pain otherwise.

Below are scripts that do the following:

  • Indicate if the active window in the active (frontmost) application is in fullscreen mode.
  • Toggle fullscreen status of the active window in the active application.
  • Indicate if a specifiable application has any fullscreen windows.
  • A general-purpose fullscreen-management script that can target a specifiable application; this is much more complex than it should have to be.

Finally, there's some additional background information at the end - great fun.

Note that the scripts below require access for assistive devices to be enabled via System Preferences > Accessibility, or via the following command: tell application "System Events" to set UI elements enabled to true; administrative privileges are required.

Indicates if the active window in the active (frontmost) application is in fullscreen mode

(* 
  Indicates if the active window of the active application is currently in fullscreen mode.
  Fails silently in case of error and returns false.
*)
on isFullScreen()
    tell application "System Events"
        try
            tell front window of (first process whose frontmost is true)
                return get value of attribute "AXFullScreen"
            end tell
        end try
    end tell
    return false
end isFullScreen

Toggles fullscreen status of the active window in the active application

(* 
  Toggles fullscreen status of the active window of the active application.
  Return value indicates if the window is in fullscreen mode *after* toggling.
  Fails silently in case of error, e.g., if the active application doesn't support fullscreen mode, and returns false.
*)
on toggleFullScreen()
    set isFullScreenAfter to false
    tell application "System Events"
        try
            tell front window of (first process whose frontmost is true)
                set isFullScreen to get value of attribute "AXFullScreen"
                set isFullScreenAfter to not isFullScreen
                set value of attribute "AXFullScreen" to isFullScreenAfter
            end tell
        end try
    end tell
    return isFullScreenAfter
end toggleFullScreen

Indicates if a specifiable application has any fullscreen windows

** Note: This subroutine will only work with AppleScript-enabled applications.**

(*
 Determine if the specified, *AppleScript-enabled* application currently has windows in fullscreen mode or not.
 Note: Assumes that the application name is the same as the process name.
*)
on hasFullScreenWindows(appName)
    -- We compare the count of visible application windows to the count of the application *process'* windows.
    -- Since process windows either do not include the fullscreen windows or, if a fullscreen window
    -- is active, only report that one window, a discrepancy tells us that there must be at least one fullscreen window.
    set countAllWindows to count (windows of application appName whose visible is true)
    tell application "System Events" to set countProcessWindows to count windows of process appName
    if countAllWindows is not countProcessWindows then
        set hasAny to true
    else
        set hasAny to false
        -- The app-window count equals the process-window count.
        -- We must investigate one additional case: the app may be currently frontmost and could have
        -- a single window that is in fullscreen mode.
        tell application "System Events"
            set activeProcName to name of first process whose frontmost is true
            if activeProcName is appName then
                tell process appName
                    tell front window
                        set hasAny to get value of attribute "AXFullScreen"
                    end tell
                end tell
            end if
        end tell
    end if
    return hasAny
end hasFullScreenWindows

General-purpose fullscreen-management script that can target a specifiable application

** Note: This subroutine will only work with AppleScript-enabled applications.**

(*
Sets the fullscreen status for either the front window or all windows of the specified, *AppleScript-enabled* application.
The 2nd parameter can take the following values:
 0 … turn fullscreen OFF
 1 … turn fullscreen ON
 2 … toggle fullscreen
The 3rd parameter is used to specify whether *all* windows should be targeted.

Example:
  my setFullScreen("Safari", 2, false) toggles fullscreen status of Safari's front window. 

NOTE:
    - ONLY works with AppleScript-enabled applications.
    - The targeted application is also activated (also required for technical reasons).
    - If you target *all* windows of an application, this subroutine will activate them one by one, which
      is required for technical reasons, unfortunately.
      This means: Whenever you target *all* windows, expect a lot of visual activity, even when 
      the fullscreen status needs no changing; activity is prolonged when fullscreen transitions
      are involved.
     - If the target application has a mix of fullscreen and non-fullscreen windows and the application
      is not currently frontmost, the OS considers the first *non*-fullscreen window to
      be the front one, even if a fullscreen window was active when the application was
      last frontmost.
*)
on setFullScreen(appName, zeroForOffOneForOnTwoForToggle, allWindows)

    # Get window list and count.
    tell application appName
        set wapp_list to windows whose visible is true
        set wcount to count of wapp_list
        ## set wapp_names to name of windows whose visible is true
        ## log wapp_names
    end tell

    set MAX_TRIES to 20 # Max. number of attempts to obtain the relevant process window.

    set toggle to zeroForOffOneForOnTwoForToggle is 2
    set turnOn to false
    if not toggle then set turnOn to zeroForOffOneForOnTwoForToggle is 1

    if allWindows and wcount > 1 then -- Target *all* the application's windows.
        tell application "System Events"
            tell process appName
                set indexOfTrueFrontWin to -1
                set wproc_target to missing value
                set wproc_targetName to missing value
                -- Loop over application windows:
                -- Note that we have 2 extra iterations:
                --  Index 0 to determine the index of the true front window, and count + 1 to process the true front window last.
                repeat with i from 0 to wcount + 1
                    ## log "iteration " & i
                    if i ≠ 0 and i = indexOfTrueFrontWin then
                        ## log "ignoring true front win for now: " & i
                    else
                        set ok to false
                        if i ≠ 0 then
                            set wapp_index to i
                            if i = wcount + 1 then set wapp_index to indexOfTrueFrontWin
                            set wapp_target to get item wapp_index of wapp_list
                            set wapp_targetName to get name of wapp_target -- Note: We get the name up front, as accessing the property below sometimes fails.
                        end if
                        repeat with attempt from 1 to MAX_TRIES
                            ## log "looking for #" & i & ": [" & wapp_targetName & "] (" & id of wapp_target & ")"
                            # NOTE: We MUST activate the application and the specific window in case that window is in fullscreen mode.
                            #        Bizzarrely, without activating both, we would not gain access to that active window's *process* window,
                            #        which we need to examine and change fullscreen status.
                            if i ≠ 0 then
                                ## log "making front window: " & wapp_targetName
                                set index of wapp_target to 1 -- Make the window the front (active) one; we try this *repeatedly*, as it can get ignored if a switch from a previous window hasn't completed yet.
                            end if
                            set frontmost to true -- Activate the application; we also do this repeatedly in the interest of robustness.
                            delay 0.2 -- Note: Only when the window at hand is currently in fullscreen mode are several iterations needed - presumably, because switching to that window's space takes time.
                            try
                                -- Obtain the same window as a *process* window.
                                -- Note: This can fail before switching to a fullscreen window is complete.
                                set wproc_current to front window
                                -- See if the desired process window is now active.
                                -- Note that at this point a previous, fullscreen window may still be reported as the active one, so we must
                                -- test whether the process window just obtained it is the desired one. 
                                -- We test by *name* (window title), as that is the only property that the *application*
                                -- window class and the *process* window class (directly) share; sadly, only application windows
                                -- have an 'id' property.
                                -- (There is potential for making this more robust, though, by also comparing window sizes.)
                                if i = 0 then
                                    -- We determine the index of the *actual* front window, so we can process it *last*
                                    -- so we return to the same window that was originally active; with fullscreen windows
                                    -- involved, sadly, `front window` is NOT always the true front window. 
                                    set indexOfTrueFrontWin to 1
                                    repeat with ndx from 1 to wcount
                                        if name of (item ndx of wapp_list) is name of wproc_current then
                                            set indexOfTrueFrontWin to ndx
                                            exit repeat
                                        end if
                                    end repeat
                                    ## log "true front index: " & indexOfTrueFrontWin
                                    set ok to true
                                    exit repeat
                                else
                                    if (name of wproc_current) is wapp_targetName then
                                        ## log "processing: [" & name of wproc_current & "]"
                                        tell wproc_current
                                            set isFullScreen to get value of attribute "AXFullScreen"
                                            if toggle then set turnOn to not isFullScreen
                                            if isFullScreen is not turnOn then
                                                ## log "setting fullscreen to: " & turnOn
                                                set value of attribute "AXFullScreen" to turnOn
                                                delay 0.3 -- For good measure; it seems turning fullscreen *on* sometimes fails (you'll hear a pop sound).
                                            else
                                                ## log "no change needed"
                                            end if
                                        end tell
                                        set ok to true
                                        exit repeat
                                    else
                                        ## log "no match; waiting for '" & wapp_targetName & "', actual: '" & name of wproc_current & "'"
                                    end if
                                end if
                            end try
                        end repeat
                        if not ok then error "Obtaining process window '" & wapp_targetName & "' of application " & appName & " timed out."
                    end if
                end repeat
            end tell
        end tell
    else if wcount > 0 then -- Target *current* window only (if there is one).
        tell application "System Events"
            tell process appName
                # NOTE: We MUST activate the application in case its active window is in fullscreen mode.
                #       Bizzarrely, without activating, we would not gain access to that active window's *process* window.
                set frontmost to true
                set ok to false
                repeat with attempt from 1 to MAX_TRIES
                    delay 0.2 -- Note: Only when the active window is currently in fullscreen mode are several iterations needed - presumably, because switching to that window's space takes time.
                    try
                        -- Obtain the same window as a *process* window, as only a process window allows us to examine or
                        -- change fullscreen status.
                        tell front window -- Note: This can fail before switching to a fullscreen space is complete.
                            set isFullScreen to get value of attribute "AXFullScreen"
                            if toggle then set turnOn to not isFullScreen
                            if isFullScreen is not turnOn then
                                set value of attribute "AXFullScreen" to turnOn
                            end if
                        end tell
                        set ok to true
                        exit repeat
                    end try
                end repeat
                if not ok then error "Obtaining active process window of application" & appName & " timed out."
            end tell
        end tell
    end if

end setFullScreen

More background information:

  • The application windows collection - the one accessible in the context of a tell application ... block - always reports the total number of windows, whether they're in fullscreen mode or not. Unfortunately, such window objects canNOT be used to determine or set fullscreen mode - this must be done via the window objects reported by process objects in the context of the "System Events" application, as only they contain the relevant "AXFullScreen" attribute. It is important to note that the application windows collection - unlike the process windows collection - only works with applications that have AppleScript support.

  • Unfortunately, the window collection exposed by process objects in the context of the "System Events" application behaves strangely:

    ○ When an application is not frontmost or one of its NON-fullscreen windows is active, it only contains the NON-fullscreen windows

    ○ By contrast, when an application is frontmost and one of its fullscreen windows is active, it only ever contains that single fullscreen window, even if other windows (irrespective of whether they're fullscreen or not) exist.

    ○ Correlating application and process windows is tricky, because only application windows have an 'id' property; the only property the two types share directly is 'name' (i.e., the window title); both types also contains size information, though not in the same format.

    ○ (Also, process windows never include hidden windows, whereas the application-window collection must be filtered with whose visible is true to exclude hidden windows.)

  • As a result, if you want to process all windows of a given application, the basic approach is as follows:

    ○ Activate the application.

    ○ Loop over all (visible) application window objects.

    ○ Make each window the front window.

    ○ Wait for the corresponding process window to become programmatically accessible; this will take quite a while if the window activation involves a fullscreen transition.

    ○ Examine or change the fullscreen state of the process window (value of attribute "AXFullScreen").

  • If an application has only full-screen windows, AppleScript can get confused over what the front window is: what it reports as the front window may not be the one that is active when you activate the application with AppleScript or Cmd-tab to it.

  • When using activate to activate an application, a NON-fullscreen window of the target application will be activated, if there is one, even if a fullscreen window of that app was previously active. You can, however, set the index of a fullscreen window to 1 to activate it.

like image 160
mklement0 Avatar answered Jan 11 '23 10:01

mklement0


if you want to toggle between fullscreen and normal mode use this hint

tell application "iTunes"
    activate
    tell application "System Events" to tell window "iTunes" 
                                          of application process "iTunes"
        click (every button whose description contains "full screen")
    end tell
end tell
like image 43
Prashant Bhate Avatar answered Jan 11 '23 10:01

Prashant Bhate


You can detect full screen in Lion:

tell application "System Events"
tell process "Safari"
get value of attribute "AXFullScreen" of window 1
end tell
end tell
display dialog result as text

(from http://dougscripts.com/itunes/2011/07/detect-full-screen-mode/).

like image 32
gadgetmo Avatar answered Jan 11 '23 10:01

gadgetmo