Channels ▼
RSS

Keys to the Kingdom


DDJ, Spring1997: Keys to the Kingdom

Al is a contributing editor for Dr. Dobb's Sourcebook. You can contact him at http://www.al-williams.com/awc.


Insert

Although Borland bills Delphi as a good replacement for Microsoft's Visual Basic (VB), it is not compatible with Visual Basic. Porting a VB program to Delphi (or any other language) is no easy task. Besides the language differences, there are several statements in VB that don't have direct equivalents in other languages. Consider the SendKeys call, for example.

SendKeys is one of those statements that you either don't need or can't live without. Its sole purpose is to send fake keystrokes to the system. You might think this is a simple task. Why not just post WM_CHAR or WM_KEYDOWN messages? There are several problems with this simple approach. Although you can easily fake simple characters to single applications, there is no simple way to send the messages to whatever application is active. Further, you can't affect the state of the keyboard, which many applications check. For example, a program might detect the Esc key, then examine the keyboard state to find out if the Shift or Ctrl keys are down. Posting a simple message won't handle these programs. Unless you are using VB, you clearly need a better way to send keystrokes.

The Win32 API provides a useful function, keybd_event, to address this problem. Win16 has a similar function, but it is designed for use by device drivers and isn't easy to use (see the accompanying text box entitled "The 16-bit Solution"). However, the Win32 version is easy to use from any language.

In this column, I'll present a 32-bit Delphi program that implements a simple keyboard macro program. Almost all of the functionality of the program is encapsulated in a reusable unit that practically duplicates the VB SendKeys functions and syntax (see the accompanying text box entitled "Using SendKeys"). Even if you don't use Delphi, you can easily adapt the techniques this program uses to nearly any programming language.

Who Cares?

Why do you want a SendKeys function? Good question. (If you are porting VB code that uses SendKeys, you can skip this section—you already know.) There are many times when sending a keystroke to the system or another program can be very useful. You can manipulate utility programs, implement macro recorders, and automate testing, just to name three uses.

If you are even more ambitious, you could provide similar mouse capabilities by using mouse_event. Armed with these two functions and a knowledge of Windows hooks, you could implement a very powerful macro recorder, script language, or a sophisticated automatic-test program.

Here's a real life example: I recently wanted a way to advance PowerPoint slides by remote control. I looked at the various presentation remote controls on the market. They all cost over $100 and did things I didn't care about. All I wanted was to advance slides. I bought a $20 wireless doorbell from Radio Shack and wired it to a serial port connector. Then I wrote some simple software to "press" the Enter key (or any other string I designated) when it detected the remote control click. This type of software is simple to write with SendKeys.

About keybd_event

The keybd_event call takes a few parameters, most of which are straightforward. The first argument is, as you might expect, a virtual key code that corresponds to the key you want to simulate. The second argument is the scan code for that key. The next argument is a flag. If you pass a zero, it indicates that you wish to simulate the key going down. If you pass KEYEVENTF_KEYUP, the call simulates the key being released. The KEYEVENTF_EXTENDEDKEY flag indicates that the key is an extended key (that is, the extra cursor keys on an enhanced keyboard). The final argument is additional data that you can safely ignore.

Don't forget that virtual key codes are not ASCII characters. There is a virtual key code for each key on the keyboard. For example, there is one virtual key code for the 5 key. There isn't a code for the percent sign that appears above the 5 on the key. When a program sees a WM_KEYDOWN message for the 5 key, it has to examine the state of the Shift key to determine if it is a 5 or a percent sign. Normally, the TranslateMessage function takes care of this logic and generates a WM_CHAR message for the appropriate ASCII character. To simulate a % key, you must simulate a Shift-key press, a 5-key press, a 5-key release, and a Shift-key release.

Of course, you usually don't want to know all of these details just to send an ASCII character. That's why you call the VkKeyScan function. This call converts an ASCII character into a special 32-bit value. The 16 bits at the bottom of this value contain the corresponding virtual key code. The 16 bits at the top contain flags to tell you which of the Shift, Ctrl, or Alt keys should be down for the character. This simplifies handling ASCII characters. Of course, to send a special character (like a cursor key) you can use the special key's virtual key code directly.

The other detail you usually don't care about is the scan code. It turns out that most programs never examine the scan code anyway. However, the scan code is easy to obtain. Simply call MapVirtualKey, passing it the virtual key code and a zero. This returns the correct scan code. Armed with this information, it is easy to call keybd_event for any key.

Inside the SendKeys Unit

Once you know how to call keybd_event, writing the SendKeys unit isn't very difficult; see Listing One . To simplify the logic, I broke the code into several procedures and functions:

  • cvtkey accepts a key and converts it to a virtual key code, shift state, and repeat count. It processes the special characters that represent shift keys and other codes.

  • procbrace handles the special named keys (like Home or Enter) which are represented by a name surrounded by curly-brace ({}) character. This may be a single character or a special keyword. In either case, there may be a space and a repeat count before the closing brace. The tbl array holds the first three characters of the keywords (only the first three letters count).

  • keybd is a simple wrapper around keybd_event that finds the scan code and selects the proper flag argument.

  • GetNum is a simple parsing routine that pulls a number from the input stream.

There are two flags for each modifier key. For example, the Shift-key flags are shift and letshift. The shift flag reflects the state of the Shift key in response to the "+" key. The letshift key, on the other hand, reflects the Shift-key state required to obtain the desired character (the program uses the VkKeyScan function to set letshift). The program clears letshift for each character. Ordinarily, it clears the shift flag, too. However, if you use a parenthesis, the code will be prevented from clearing the shift flag.

Sometimes, keystrokes can come too rapidly. You might send keystrokes to start a program, for example, then send more keystrokes before the program starts. To help prevent this, the SendKeys function delays 50 milliseconds between each keystroke. You can also program custom delays into scripts using the {PAUSE n} command, which causes the script to pause for n milliseconds. (By the way, this is not part of the standard Visual Basic SendKeys command.)

KeyPlay Details

The actual KeyPlay program is a reasonably simple Delphi application. I used the SDI program template to create the initial program. The form contains a single TMemo to hold the current macro script. If there is a file name on the command line, the form loads the file into the TMemo, then automatically executes the script. When the script is complete, the program closes. If you don't want the script to run automatically, prefix the file name with an asterisk. If you prevent the script from running, or load a script from the File menu, you can start it using the Play option on the File menu. Then the program does not automatically close.

When the script runs, the program minimizes itself into an icon. This helps prevent KeyPlay from sending keystrokes to itself. Running the script is simply a matter of iterating over each line in the TMemo and sending it to the SendKeys function.

File input and output is simple, too. Each TMemo contains a TStrings object (Lines). This TStrings object can read and write ASCII files easily. Just call LoadFromFile to read a file into the TMemo, and SaveToFile to save it back. (The source code for the KeyPlay program are available electronically.)

Ideas

Once you become used to the idea of sending keystrokes (or mouse actions using mouse_event), you'll find many uses for this technique. You could easily extend the KeyPlay program to send mouse events or even use a hook function to record actions. Although OLE Automation (or ActiveX Scripting, if you prefer) allows you to automate program operations, not all applications support it. Using the techniques presented here, you can automate nearly any program, although perhaps not as elegantly.


The 16-bit Solution

If you are programming Windows 3.x, you can still use a similar technique to simulate keystrokes. The _keybd_event call is similar to keybd_event. However, it is primarily aimed at device drivers. Therefore, you need to use assembly language to call it, and it isn't declared in any of the header files.

You can find a good article about using this technique under Windows 3.x in "Simulating Keyboard Input Between Programs Requires a (Key)stroke of Genius," by Jeffrey Richter (Microsoft Systems Journal, December 1992), which shows how to use the 16-bit version of this call.


Using SendKeys

Using SendKeys is quite simple. The SendKeys procedure accepts a string. SendKeys simulates the ordinary characters in this string. The only characters that have special meaning are: +, %, ^, (, ), {, }, and ~. The ~ character stands for the Enter key. The +, ^, and % characters indicate the Shift, Ctrl, and Alt key attributes. For example, to send "DDJ" a Control-A, the Enter key, and an Alt-X, you'd use the string DDJ^A~%X.

If you want a modifier key to apply to more than one character, surround the characters with parenthesis. For example, to send Control-A, Control-B, and Control-C, you might use ^(ABC). Notice that this is different from ^ABC, which would send a Control-A, an ordinary B, and an ordinary C.

The curly brace indicates a special character. If there is one character inside the brace, SendKeys quotes that character (which is usually a special character); for example, {+}{{} sends a plus sign and an open curly brace. You can also use a named key inside curly braces (see Table 1). Only the first three letters of the name are significant and case isn't important. For example, you can use {ENT} instead of {ENTER}, if you like.

Table 1: Named keys. (Only the first three letters are significant, so {DEL}, {DELETE}, and {DELIT} are the same.)

Name            Key

{BACKSPACE}       Backspace
{BS}              Backspace
{BKSP}            Backspace
{BREAK}           Break
{CAPS}            Caps Lock
{DELETE}          Delete
{DOWN}            Down arrow
{END}             End
{ENTER}           Enter (same as ~)
{ESCAPE}          Escape
{HELP}            Help key
{HOME}            Home
{INSERT}          Insert
{LEFT}            Left arrow
{NUMLOCK}         Num lock
{PGDN}            Page down
{PGUP}            Page up
{PRTSCR}          Print screen
{RIGHT}           Right arrow
{SCROLL}          Scroll lock
{TAB}             Tab
{UP}              Up arrow
{PAUSE}           Pause (see text)

You can also include a space and a repeat count inside the curly braces. For example, {A 10}{LEFT 9} sends ten letter "A"s and nine Left-Arrow keys. The only extension that this code makes to the standard Visual Basic statement is the {PAUSE} keyword. You always provide a repeat count with this keyword. It causes the script to pause for the specified number of milliseconds.

One other difference between the Visual Basic SendKeys statement and the implementation in this article is how the procedure continues. With Visual Basic, you can include a flag that prevents SendKeys from returning before the system processes the keys. In this implementation, SendKeys always returns after sending the keystrokes. There is no way to tell if an application has processed those keys when the procedure returns.

Listings


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video