Hotkeys on Demand

Published , updated

There is no shortage of hotkey programs for Windows, many of them of high quality. And of course Windows itself allows you to define hotkeys. However, a hotkey program in Tcl is not only very simple to write, it offers the full flexibility and power of Tcl behind it. Meaning what exactly? Read on.

We will demonstrate the simplicity soon enough but what do we mean by power and flexibility?

  • Tcl being a full-blown programming language, you can program more or less any task to be associated with a hotkey. It is not limited to what the author of the hotkey program has built in.

  • Tcl being a dynamic programming language, it is easy to create one-off hotkey associated tasks on the fly for just that session. And given the high level nature of Tcl commands, these tasks can be as sophisticated as you wish.

Starting small

Let us start with a very simple script that will run everyone's favorite program, Notepad, when a hotkey is pressed.

package require twapi
wm withdraw .
lappend hotkeys [twapi::register_hotkey Ctrl-Alt-F10 "exec notepad.exe &"]
lappend hotkeys [twapi::register_hotkey Ctrl-Alt-F12 {
    foreach hotkey $hotkeys {
        twapi::unregister_hotkey $hotkey
    }
    exit
}]

That is your first hotkey program. Under 10 lines. Run this script using the GUI version of Tcl, wish, not the console version tclsh. Now when you press Ctrl-Alt-F10 you will see a Notepad window come up. Our hotkey program is running in the background with no visible windows so we need to provide a means to exit it. Accordingly, we also register the Ctrl-Alt-F12 hotkey to remove our hotkey bindings and exit the program.

The program steps are straightforward:

  • We load the TWAPI package which provides access to the Windows API and in particular, implements the hotkey related commands.

  • We want to run in the background without a visible window so we hide the wish main window with the wm command.

  • Registering a hotkey is a simple matter of calling register_hotkey and specifying a script to call when the hotkey is pressed. We collect the ids returned by each registration in the hotkeys variable so they can be unregistered later. Unregistering before exiting is simply good manners.

That is all there is to it.

Putting on weight

The above is really adequate for all practical purposes. To define a new hotkey you would edit the script and reload. However, a 10-liner is not worthy of a blog post so let us add some fluff and expand the program for demonstrative purposes.

We start off by restructuring and formalizing the short script above. The cost is that it is no longer a 10-liner but adding some structure is beneficial as programs get more complex and also makes for easier integration into a larger application.

So we place the basic commands to add, remove and list hotkeys into the hk namespace and create a namespace ensemble out of it so hk becomes a command with add, remove and list subcommands.

package require twapi
wm withdraw .
namespace eval hk {
    variable hotkeys;           # Will keep track of hotkeys in use
    proc add {hk script} {
        variable hotkeys
        if {[info exists hotkeys($hk)]} {
            remove $hk
        }
        set hotkeys($hk) [twapi::register_hotkey $hk $script]
    }
    proc remove {hk} {
        variable hotkeys
        if {[info exists hotkeys($hk)]} {
            twapi::unregister_hotkey $hotkeys($hk)
            unset hotkeys($hk)
        }
    }
    proc list {} {
        variable hotkeys
        return [array names hotkeys]
    }
    namespace export add remove list
    namespace ensemble create
}

This provides us our base script. We can then start embellishing it starting with our hot keys for Notepad and exiting as before.

hk add Ctrl-Alt-F12 {
    foreach hotkey [hk list] {
        hk remove $hotkey
    }
    exit
}
hk add Ctrl-Alt-F10 "exec notepad.exe &"

We mentioned one of the advantages about a hotkey program in Tcl is that the tasks assigned to a hotkey can involve computation and are not limited to a few predefined actions as in most hotkey programs. For example, I use the following hotkey definition on older versions of Windows[1] to stretch a window vertically to occupy the full height of the screen.

proc stretch_window {} {
    set w [twapi::get_foreground_window]
    lassign [twapi::get_window_coordinates $w] wleft wtop wright wbottom
    lassign [twapi::get_desktop_workarea] dleft dtop dright dbottom
    set width [expr {$wright - $wleft}]
    set height [expr {$dbottom - $dtop}]
    twapi::move_window $w $wleft $dtop
    twapi::resize_window $w $width $height
}
hk add Ctrl-Alt-F9 stretch_window

The other advantage of a Tcl based hotkey program is that we can make use of its dynamic nature to define new commands and assign hotkeys on the fly.

We need to have a means to enter hotkey definitions. We could simply modify the script to read in hotkey definitions and edit the latter. Instead, we will make use of the wish console prompt which will be more convenient.

The console is displayed simply by calling the console show command. However, we do not want the console to be always visible and clutter the screen so we will add a hotkey definition to our script to toggle the console visibility.

coroutine toggle_console while {1} {
    console hide 
    yield
    console show
    yield
}
hk add Ctrl-Alt-F11 toggle_console

Note we have defined the command as a Tcl coroutine. We could have as well implemented it as a normal proc but we define it as a coroutine to introduce those of you who are new to Tcl 8.6 to one of 8.6's most interesting and novel features.

We will not go into the details of what a coroutine is - see the reference and the Tcl wiki for that - but in a nutshell the above code fragment loops creates a coroutine called toggle_console implementing a infinite loop. When invoked, a coroutine runs until the next yield statement which returns control to the caller. The next time it is called, it continues from where it left off. Thus the above loop alternately hides and shows the console.

With that in place, we can hit Ctrl-Alt-F11 to show the console when we want access to the Tcl command line. Once we are done, we can hit Ctrl-Alt-F11 again to hide the console. We can now define new hotkeys on the fly.

For example, I recently needed to insert a number of GUID's while editing a file. Instead of manually using guidgen.exe, I typed Ctrl-Alt-F11 to bring up the console window and typed the following

hk add Ctrl-Alt-F7 {twapi::send_input_text [twapi::new_guid]}

and hit Ctrl-Alt-F11 again to hide the console.

Simply hitting Ctrl-Alt-F7 while in the editor would now insert a new GUID on demand. When no longer needed, the binding can be removed.

hk remove Ctrl-Alt-F7

The above provides the rudiments of a hotkey program which you can now enhance as per your needs. The entire script can be downloaded from here.


  1. This is redundant on Windows 7 and later which already provide a hotkey for the purpose. �?�