text: #!/usr/bin/env python
  
  # Written by Greg Till
  # November 2008
  # Public domain software
  
  from __future__ import division
  import gtk
  import os
  import wnck
  import time
  
  """scale.py: Unminimizes and re-minimizes windows for Compiz's Scale plugin
  
  More specifically:
  
  - Unminimize all minimized viewport windows 
  - Launch the Scale plugin of the Compiz window manager
  - Re-minimize previously minimized windows (other than any newly activated
      window and windows closed through Scale)
  
  In this script, you may customize the 'pluginDelay' variable. This variable
      refers to the duration between when Scale activates and when you may
      select a window using Scale. In rare instances, windows may spontaneously
      become active after they have been unminimized and after Scale has started,
      but before you have selected a window in Scale. A change in window status
      will trigger the reminimization process. By setting a short delay after
      Scale begins, these rare active window changes will be caught and ignored.
      Each such active window change will reset the time. Typically, it can be a
      small number (a fraction of a second) and is needed, if at all, only when
      the system is working under heavy load.
      
  Scale can show the windows in the current viewport or all viewports. To have
      Scale show windows from the current viewport (the default), the command
      near the end of this script that begins 'org/freedesktop/compiz...',
      should read as follows:
      
          '/org/freedesktop/compiz/scale/allscreens/initiate_key'
          
  To have Scale show windows from all viewports, change the command as follows:
      
          '/org/freedesktop/compiz/scale/allscreens/initiate_all_key'
          
  Regardless of which approach you use, only the windows in the current viewport
      are (un)minimized. To (un)minimize windows from all viewports would be a
      dizzying experience due to the rapid viewport changing it would
      require.
          
  """
      
  previousTime = 0
  
  def get_screen():
      """Get the screen object and refresh the screen"""
      screen = wnck.screen_get_default()
      screen.force_update()
      return screen
  
  def get_windows():
      """Get the list of all windows"""
      screen = get_screen()
      allWindows = screen.get_windows_stacked()
      return allWindows
              
  def get_tasklist_windows():
      """Get the list of windows in the tasklist"""
      # The tasklist can be configured to include windows from other viewports
      allWindows = get_windows()
      tasklistWindows = []
      for window in allWindows:
          if window.is_skip_tasklist():
              continue
          elif window.is_skip_pager():
              continue
          elif window.is_sticky():
              continue
          else:
              tasklistWindows.append(window)
      return tasklistWindows
  
  def get_viewport_windows(): 
      """Get the list of windows that are in the viewport"""
      allWindows = get_windows()
      screen = get_screen()
      workspace = screen.get_active_workspace()
      viewportWindows = []
      for window in allWindows:
          if window.is_in_viewport(workspace):
              viewportWindows.append(window)
      return viewportWindows
  
  def get_eligible_windows():
      """Get the list of windows that are in the viewport and the tasklist"""
      # Only these windows are subject to being unminimized
      eligibleWindows = []
      allWindows = get_windows()
      tasklistWindows = get_tasklist_windows()
      viewportWindows = get_viewport_windows()
      for window in allWindows:
          if window in tasklistWindows and window in viewportWindows:
              eligibleWindows.append(window)
      return eligibleWindows
              
  def get_ineligible_windows():
      """Get the list of windows that are in the viewport & not the tasklist"""
      # One of these windows will be activated later in the script 
      ineligibleWindows = []
      allWindows = get_windows()
      viewportWindows = get_viewport_windows()
      eligibleWindows = get_eligible_windows()
      activeWindow = get_active_window()
      for window in allWindows:
          if window in eligibleWindows:
              continue
          # Don't let the Desktop be included in the list of ineligibleWindows if
          #    it is currently active; otherwise, activating an ineligibleWindow
          #    later in the script will not trigger the initial active window
          #    change signal
          # Cannot refer to the Desktop by name because not every WM will list it
          elif window in viewportWindows and window != activeWindow:
              ineligibleWindows.append(window)
      return ineligibleWindows   
  
  def get_active_window():
      """Get the active window"""
      screen = get_screen()
      activeWindow = screen.get_active_window()
      return activeWindow
  
  def determine_minimized(windows):
      """Determine which windows in a given list are minimized"""
      minimizedWindows = []
      for window in windows:
          if window.is_minimized():
              minimizedWindows.append(window)
      return minimizedWindows
  
  def window_command(window, command):
      """Handle various window actions"""
      # Use wmctrl instead of equivilant commands in xwit and wnck (the latter
      #    two can cause the tasklist to blink and wnck requires X server
      #    timestamps for some actions)
      # Use xwit for minimizing because wmctrl does not have a
      #    working minimize function
      if command == 'close':
          os.popen('wmctrl -ic ' + str(window.get_xid()))
      if command == 'maximize_toggle':
          os.popen('wmctrl -ir ' + str(window.get_xid()) +
              ' -b toggle,maximized_vert,maximized_horz')
      if command == 'minimize':
          os.popen('xwit -iconify -id ' + str(window.get_xid()))
      if command == 'unminimize' or command == 'activate':
          os.popen('wmctrl -ia ' + str(window.get_xid()))
      return
  
  def reminimize(minimizedWindows):
      # Need a new list of eligible windows because one or more windows may
      #    have been closed by Scale
          activeWindow = get_active_window()
          newEligibleWindows = get_eligible_windows()
          for window in newEligibleWindows:
              if window == activeWindow:
                  continue
              elif window in minimizedWindows:
                  window_command(window, 'minimize')
          gtk.main_quit()
  
  def handler_reminimize(screen, window, firstIneligibleWin,
      minimizedWindows):
      """Re-minimize previously minimized windows after Scale finishes"""
      # 'active_window_changed' WnckScreen signal
      # 'window' argument reflects previously active window, not the current one
      # Window selection, window close, and show desktop actions in Scale all
      #    trigger this signal
      # Hitting escape in Scale yields no signal; nothing can be done by this
      #    script in this instance. This script won't close and windows will
      #    not reminimize until after the user activated another window
      # No actions from Scale are received until Scale after has finished; also,
      #    after it finishes, only the signal from its final action is received
      #    (thus, multiple window closures can occur but not result in multiple
      #    received signals)
      # This script also triggers the signal (before invoking Scale), when it
      #    activates an ineligible window
      # Thus, need to check if the active window is the same as the ineligible
      #    window activated by this script
      # If it is, then the script will wait until a window state change is
      #    caused by Scale, rather than this script (so hurry up and wait)
      # If it is not, then the script will reminimize windows, unless the user
      #    selects the desktop in Scale, which is addressed by a different
      #    handler
      # ------------------------------------------------------------------------
      # Duration in seconds between when Scale activates and when you may select
      #    a window using Scale. Used to catch late-breaking, spontaneous active
      #    window change events. Resets with each such event
      # Adjust this variable as necessary 
      pluginDelay = 0.3
      # ------------------------------------------------------------------------
      activeWindow = get_active_window()
      global previousTime
      currentTime = time.time()
      if activeWindow == firstIneligibleWin:
          # This is most likely triggered by the script itself
          # Pass and await selection of a window by the user
          previousTime = currentTime    
      elif activeWindow in minimizedWindows:
          elapsedTime = 0
          if previousTime:
              elapsedTime = currentTime - previousTime
          if elapsedTime < pluginDelay:
              # Too little time has elapsed, meaning that the window was most
              #    likely made active by late mapping; reset the active window
              previousTime = currentTime
              window_command(firstIneligibleWin, 'activate')
          else:
              # Elapsed time is long enough, so presume the window was selected
              #    by the user; reminimize
              reminimize(minimizedWindows)
      else:
          # Active window is an originally unminimized window; no mapping issue
          if minimizedWindows:
              reminimize(minimizedWindows)
          else:
              gtk.main_quit()
      return
       
  def handler_show_desktop(screen):
      # 'showing_desktop_changed' WnckScreen signal
      """Quit the script if the user selects the desktop in Scale"""
      screen = get_screen()
      toggledDesktop = screen.get_showing_desktop()
      if toggledDesktop:
          gtk.main_quit()
      return
      
  def main():
      # ************************************************************************
      # Unminimize all minimized viewport windows
      # ************************************************************************
      eligibleWindows = get_eligible_windows()
      ineligibleWindows = get_ineligible_windows() 
      if eligibleWindows:
          minimizedWindows = determine_minimized(eligibleWindows)
      else:
          os._exit(0)
      if minimizedWindows:
          for window in minimizedWindows:
              window_command(window, 'unminimize')
      # ************************************************************************
      # Launch the Scale plugin of the Compiz window manager
      # ************************************************************************
      # Aside from ESCaping out, Scale will exit upon one of three actions:
      #    Selecting a tasklist window, closing the last tasklist window, or
      #    showing the desktop
      # Activating a non-tasklist window in this script ensures that Scale will
      #    always generate an 'active_window_changed' event when it exits
      # Launch Scale
      scale=os.popen('dbus-send --type=method_call ' +
      '--dest=org.freedesktop.compiz ' +
      # ------------------------------------------------------------------------
      # Update this line as desired according to instructions at the top of this
      #    script
      '/org/freedesktop/compiz/scale/allscreens/initiate_key ' 
      # ------------------------------------------------------------------------
      + ' org.freedesktop.compiz.activate string:\'root\' ' +
      'int32:`xwininfo -root | grep id: | awk \'{ print $4 }\'`')
      # ************************************************************************
      # Re-minimize previously minimized windows (other than any newly activated
      #    window and windows closed through Scale)
      # ************************************************************************           
      firstIneligibleWin = ineligibleWindows[0]
      window_command(firstIneligibleWin, 'activate')
      screen = get_screen()
      screen.connect('active_window_changed', handler_reminimize,
          firstIneligibleWin, minimizedWindows)
      screen.connect('showing_desktop_changed', handler_show_desktop)
      gtk.main()
  
  if __name__ == '__main__':
      main()
  