Sending virtual keystrokes on OS X

Going through my comments backlog I found a question asking how to send virtual keystrokes on OS X. Short answer is to use the CGEvent functions in MacApi.CoreGraphics – in a nutshell,

  1. Call CGEventSourceCreate to allocate a CGEventSourceRef.
  2. For each keystroke, create a CGEventRef by calling CGEventCreateKeyboardEvent; this takes the CGEventSourceRef just allocated plus an OS X virtual key code. For possible values of the latter, see the MacApi.KeyCodes unit, assuming you have a recent Delphi version, otherwise scan the source for KEY_xxx values. Also, note that you will need separate events for first pressing a key down, then unpressing it.
  3. If required, call CGEventSetFlags on a given CGEventRef to assign a modifier key (alt/shift/ctrl/command); in the case of a character, you’ll typically want to call CGEventKeyboardSetUnicodeString as well rather than messing about trying to figure out the correct key code.
  4. Actually perform the event by passing the CGEventRef to CGEventPost, using kCGHIDEventTap for the first parameter.
  5. Clean up by passing each CGEventRef then the CGEventSourceRef to CFRelease.

The following follows these steps to generate key down and key up events for a Cmd+L shortcut:

uses
  System.SysUtils, MacApi.CocoaTypes, MacApi.CoreFoundation, MacApi.CoreGraphics, MacApi.KeyCodes;

procedure PressCmdLShortcut;
var
  EventSourceRef: CGEventSourceRef;
  KeyDownRef, KeyUpRef: CGEventRef;
begin
  KeyDownRef := nil;
  KeyUpRef := nil;
  EventSourceRef := CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
  if EventSourceRef = nil then RaiseLastOSError;
  try
    KeyDownRef := CGEventCreateKeyboardEvent(EventSourceRef, KEY_L, 1); //1 = key down
    if KeyDownRef = nil then RaiseLastOSError;
    CGEventSetFlags(KeyDownRef, kCGEventFlagMaskCommand);
    KeyUpRef := CGEventCreateKeyboardEvent(EventSourceRef, KEY_L, 0);  //0 = key up
    if KeyUpRef = nil then RaiseLastOSError;
    CGEventSetFlags(KeyUpRef, kCGEventFlagMaskCommand);
    CGEventPost(kCGHIDEventTap, KeyDownRef);
    CGEventPost(kCGHIDEventTap, KeyUpRef);
  finally
    if KeyDownRef <> nil then CFRelease(KeyDownRef);
    if KeyUpRef <> nil then CFRelease(KeyUpRef);
    CFRelease(EventSourceRef);
  end;
end;

Alternatively, you can use a little cross platform (Mac/Windows) wrapper interface I’ve written… more on which in my next post!

Advertisements

One thought on “Sending virtual keystrokes on OS X

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s