Tijmen 01:06, 4 September 2009

launch_or_activateOne feature of the new Windows 7 SuperBar that I particularly like is the ability to pin applications. This creates a single button that can be used to both launch the application (when it isn't already running) or to activate it if the app was already active. What I like about this concept, is that it expresses the user's wish much better in this way: a button to run Word can now be used to just "give me Word, I don't care if it's already running".

Since I usually work more keyboard- than mouse-centric, I came up with a way to mimic this behavior in AutoHotkey. I wrote a script that offers the following functionality:

  • either start or activate a particular application (based on a shortcut key obviously, this being an AutoHotkey post)
  • if the application is already running, cycle through the active windows (useful for Firefox or Explorer windows)
  • make it easy to "force-launch": force a new instance/window, regardless of whether it was already up and running.
  • give visual feedback on the launched-or-activated application

It took me a little time to work out the AutoHotkey script syntax, especially for dealing with arrays. In the script you set up an array of shortcut information, which serves as a settings registry. It contains the following parts:

  • display label: text that is shown in the visual feedback
  • a (unique) name: this is used to bind that particular command to a hotkey
  • a groupname: if a groupname is given, the script tries to loop through all the windows that match the window ID that is assigned to this entry
  • windowId: set this to the "ahk_class" of the window. You can use Window Spy (right click on any running AutoHotKey script tray icon) to find out this value for each command you want to add to the registry. It will probably work with Windows titles as well, but I never tried this since that method is a lot less reliable.
  • runcommand: the actual command you want the shortcut to point to if it needs to launch the application (for instance, "c:\windows\system32\calc.exe")

Adding a entry goes like this:

;--1. SETUP
;     enter label, name, groupname, windowId, runcommand
AddShortcut("Windows Explorer", "Explorer", "ExplorerGroup", "ahk_class CabinetWClass", "C:\windows\explorer.exe /select,d:" )

The next step involves hooking up the shortcut keys. This is standard AutoHotkey suff, so please refer to the AHK manual for doing this. An example for Explorer and Firefox:

#E::MyNewActivate("Explorer")
+#E::MyRun("Explorer")
F19::MyNewActivate("FF")
+F19::MyRun("FF")

So, Windows key + E does the launch-or-activate trick, whereas Shift + Win + E forces a new Explorer window.

The rest of the script adds some visual eye candy that is in no way functionally necessary, but it gives a nice, semi-transparent window that fades quickly. The three possible windows are shown at the top of this post.

The entire script is listed below; click expand source to view or copy.

#InstallKeybdHook

;--AUTOEXEC SECTION

GroupArrayCount := 0
SetTitleMatchMode 2

;--DISPLAY SETTINGS
;
INITIAL_TRANSPARENCY := 150
DELTA_TRANSPARENCY := 25
INITIAL_DELAY := 400
DELTA_DELAY := 50

;--1. SETUP
;     enter label, name, groupname, windowId, runcommand
;
AddShortcut("Windows Explorer", "Explorer",   "ExplorerGroup", "ahk_class CabinetWClass", "C:\windows\explorer.exe /select,d:" )
AddShortcut("Outlook", "Outlook", "OutlookGroup", "ahk_class rctrl_renwnd32", "C:\Program Files (x86)\Microsoft Office\Office12\outlook.exe")
AddShortcut("Firefox", "FF", "FireFoxGroup", "ahk_class MozillaUIWindowClass", "C:\Program Files (x86)\Mozilla Firefox\firefox.exe" )
AddShortcut("Calculator", "Calculator", "", "ahk_class CalcFrame", "c:\windows\system32\calc.exe")

;--2. ASSIGN KEYS
; win + E does a "run or activate", shift + win + E forces a new window
#E::MyNewActivate("Explorer")
+#E::MyRun("Explorer")

F14::MyNewActivate("Outlook")
F19::MyNewActivate("FF")
+F19::MyRun("FF")

;--Ctrl + Shift + Esc starts Process Explorer (task manager on steroids)
+^Escape::MyNewActivate("PE")
#C::MyNewActivate("Calculator")
;
;--/AUTOEXEC SECTION


; FUNCTION SECTION
GlobalTrans := INITIAL_TRANSPARENCY

;--CloseWin has to be an AHK subroutine, SetTimer doesn't work with functions
;  also note: subroutines do not require the "global" keyword
;             to manipulate global vars
CloseWin:
  If (GlobalTrans <= 0)
  {
    Gui, Destroy  
  }
  else
  {
    WinSet Transparent, %GlobalTrans%, ahk_class AutoHotkeyGUI
    GlobalTrans := GlobalTrans - DELTA_TRANSPARENCY
    ; negative time means run only once
    SetTimer, CloseWin, -%DELTA_DELAY%
  }
return


ShowText(text, textColor)
{
  global
  GlobalTrans := INITIAL_TRANSPARENCY

  Gui, Destroy
  SetTimer, CloseWin, Off
  Gui +LastFound +AlwaysOnTop -Caption +ToolWindow
  Gui, Color, 404040
  Gui, Font, s32
  Gui, Add, Text, c%textColor%, %text%
  SetTimer, CloseWin, -%INITIAL_DELAY%
  Gui, Show, Center NoActivate
  WinSet Transparent, %GlobalTrans%, ahk_class AutoHotkeyGUI
}

AddShortcut(label, key, groupName, windowId, runCommand)
{
  global
  GroupArrayCount += 1
  Labels%GroupArrayCount% := label
  Keys%GroupArrayCount% := key
  if (groupName != "")
  {
    Groups%GroupArrayCount% := groupName
  }
  Ids%GroupArrayCount% := windowId
  Commands%GroupArrayCount% := runCommand
  if (groupName != "")
  {
    GroupAdd %groupName%,%windowId%
  }
}

MyNewActivate(key)
{
  global
  Loop %GroupArrayCount%
  {
    if (key == Keys%A_Index%)
    {
      local thisId := Ids%A_Index%
      local label := Labels%A_Index%
      if WinExist(thisId)
      {
        ShowText("switching to " . label, "Lime")
        local thisGroup := Groups%A_Index%
        if (thisGroup == "")
        {
          WinActivate %thisId%
        }
        else
        {
          GroupActivate %thisGroup%,r
        }
      }
      else
      {
        ShowText("launching " . label, "FF8800")
        local thisCommand := Commands%A_Index%
        run, %thisCommand%
      }
      break ; terminate the lookup
    }
  }
}

MyRun(key)
{
  global
  Loop %GroupArrayCount%
  {
    if (key == Keys%A_Index%)
    {
      local label := Labels%A_Index%
      ShowText("force new " . label, "FF3300")
      local thisCommand := Commands%A_Index%
      run, %thisCommand%
      break ; terminate the lookup
    }
  }
}

Note that line 46 should read If (GlobalTrans <= 0), but the AHK brush for SyntaxHighlighter gets a bit confused here (since the semicolon ";" is also the AHK comment character). I found this brush here.