Hotkeys on Demand
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 thewm
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 thehotkeys
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.
- This is redundant on Windows 7 and later which already provide a hotkey for the purpose. �?�