6

The Bug:

For certain special characters and symbols, AppleScript will fail to keystroke the actual character, and will instead simply type the letter a.


An Example:

The following AppleScript code:

tell application "System Events" to keystroke "² ³ é ½ ₭"

types the following string of text (no matter what application that I am in):

a a a a a

A Workaround:

One can effectively "type" the text by placing the special characters on the clipboard, and then pasting the clipboard, like so:

set the clipboard to "² ³ é ½ ₭"
tell application "System Events" to keystroke "v" using command down

I avoid using the above method to put text on the clipboard in AppleScript, because it necessarily converts the clipboard to rich text, in the process.

I prefer to employ the method provided here, to ensure that plain text is placed on the clipboard. But, this method converts special characters to mojibake.

For example, using the same string as above, this method puts the following text on the clipboard:

² ³ é ½ ₭

You can manually account for this issue, however, by adding a replace_chars statement for each special character.


The Question:

Is there a way to type special characters via AppleScript, without having to involve the clipboard?

For example, is there some way that I can add the desired special characters to a "bank" or system file somewhere, so that the System Events application is familiar with them?


7

It's possible with CoreGraphics (CGEventPost), it's a C API (it doesn't work in a Cocoa-AppleScript), but it's possible to call an executable from an AppleScript.


Here is the method to create the executable with Xcode (Version 8.3.3):

You can do it yourself (you can download Xcode from the App Store), or ask a trusted person to create the executable.

1- Open Xcode, select menu "File" --> "New" --> "Project..." enter image description here


2- Select macOS and Command Line Tool, click on the "Next" button. enter image description here


3- Name it TypeCharacters, select Objective-C as Language, click on the "Next" button. enter image description here


4- Save the project to the desired folder.


5- In the window of Xcode, select the main.m icon, clear the text in the window and paste one of these two codes:

The code need a loop because of a bug, the CGEventKeyboardSetUnicodeString() method truncates a string which exceeds twenty characters, or it's a limit (not documented).

This code use a loop to type a list of characters at each iteration, the list can contain one to twenty characters. (It's fast, 1 seconds to type 2400 characters in a TextEdit's document on my computer.)

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        if (argc > 1) {
            NSString *theString = [NSString stringWithUTF8String:argv[1]];
            NSUInteger len = [theString length];
            NSUInteger n, i = 0;
            CGEventRef keyEvent = CGEventCreateKeyboardEvent(nil, 0, true);
            unichar uChars[20];
            while (i < len) {
                n = i + 20;
                if (n>len){n=len;}
                [theString getCharacters:uChars range:NSMakeRange(i, n-i)];
                CGEventKeyboardSetUnicodeString(keyEvent, n-i, uChars);
                CGEventPost(kCGHIDEventTap, keyEvent); // key down
                CGEventSetType(keyEvent, kCGEventKeyUp);
                CGEventPost(kCGHIDEventTap, keyEvent); // key up (type 20 characters maximum)
                CGEventSetType(keyEvent, kCGEventKeyDown);
                i = n;
                [NSThread sleepForTimeInterval:0.004]; // wait 4/1000 of second, 0.002 it's OK on my computer, I use 0.004 to be safe, increase it If you still have issues
            }
            CFRelease(keyEvent);
        }
    }
    return 0;
}

This code use a loop to type one character at each iteration (It's slower than the first code).

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        if (argc > 1) {
            NSString *theString = [NSString stringWithUTF8String:argv[1]];
            UniChar uChar;
            CGEventRef keyEvent = CGEventCreateKeyboardEvent(nil, 0, true);
            for (int i = 0; i < [theString length]; i++)
            {
                uChar = [theString characterAtIndex:i];
                CGEventKeyboardSetUnicodeString(keyEvent, 1, &uChar);
                CGEventPost(kCGHIDEventTap, keyEvent); // key down
                CGEventSetType(keyEvent, kCGEventKeyUp);
                CGEventPost(kCGHIDEventTap, keyEvent); // key up (type the character)
                CGEventSetType(keyEvent, kCGEventKeyDown);
                [NSThread sleepForTimeInterval:0.001]; // wait 1/1000 of second, no need of this line on my computer, I use 0.001 to be safe, increase it If you still have issues
            }
            CFRelease(keyEvent);
        }
    }
    return 0;
}

Note : This code works on macOS Sierra and should work on El Capitan, but not on an older OS.

enter image description here


6- Select menu "Product" --> "Build".


7- Select the TypeCharacters icon in the Product folder, right-click and select the "Show in Finder" menu in the contextual menu.

enter image description here


8- From the Finder, move the "TypeCharacters" file to the desired folder, quit Xcode, that's all.


From an AppleScript, call the executable, like this

set myString to "ùéèà ² ³ é ½ ₭ "
do shell script "'/full path/of/TypeCharacters' " & quoted form of myString
  • Incredible answer. This solution works flawlessly on OS X 10.11.6 as well. I couldn't dream of a better solution. I also appreciate the detailed screenshots. Many, many thanks!!! – rubik's sphere Jul 7 '17 at 6:48
  • I have discovered a bug: If the length of myString exceeds 20 characters, then the string will be truncated. That is, any text that follows the twentieth character will not be typed. Do you know how to fix this? – rubik's sphere Jul 23 '17 at 2:57
  • Now ,I use a loop to type each character, (tested with a string of 2000 or more characters), I updated the code in my answer. – jackjr300 Jul 25 '17 at 19:28
  • Your new solution has solved the problem. Thanks! Side note, you removed the #import <Foundation/Foundation.h> line from your code in your edit, but this line is still necessary for a successful build. I know that this line of code is there by default, but some ambiguity exists because your instructions say to "clear the text in the window." – rubik's sphere Jul 26 '17 at 23:58
  • I think the new solution has a new bug. Every so often, the typed text will be missing a character or change the order of the text. This occurs no matter the string length. Here is a real example: The string that the TypeCharacters file was given was = 278,932 but the following string was typed: =2 78,93. – rubik's sphere Jul 27 '17 at 18:06
4

A workaround (with the clipboard):

1 - You can use a cocoa method from a shell:

set tString to "² ³ é ½ ₭"
my stringToClipboard(tString)
tell application "System Events" to keystroke "v" using command down

on stringToClipboard(t1)
    do shell script "/usr/bin/python -c 'import sys;from AppKit import NSPasteboard, NSPasteboardTypeString; cb=NSPasteboard.generalPasteboard();cb.declareTypes_owner_([NSPasteboardTypeString], None);cb.setString_forType_(sys.argv[1].decode(\"utf8\"), NSPasteboardTypeString)' " & quoted form of t1
end stringToClipboard

2 - Or a Cocoa-AppleScript Applet:

use framework "AppKit"
use scripting additions
set tString to "² ³ é ½ ₭"
my stringToClipboard(tString)
tell application "System Events" to keystroke "v" using command down

on stringToClipboard(t1)
    set ca to current application
    set cb to ca's NSPasteboard's generalPasteboard()
    cb's declareTypes:{ca's NSPasteboardTypeString} owner:(missing value)
    cb's setString:t1 forType:(ca's NSPasteboardTypeString)
end stringToClipboard
3

In order to keystroke a character, that character needs to be mapped to an actual key on your selected keyboard.

For instance, I can keystroke é and ² just fine, because I’m using the French — PC keyboard layout. However a standard American QWERTY keyboard does not feature those keys and will be unable to keystroke them.

2

You can define all your special character sets as variables, like this

on specialKeys(input)
    tell application "System Events"
        keystroke input
    end tell
end specialKeys


set characterSet1 to "²³é½₭ "
set characterSet2 to "³ "
set characterSet3 to "é "
set characterSet4 to "¬Ω "
set characterSet5 to "₭ "
set characterSet6 to characterSet1 & characterSet2 & characterSet3 & characterSet4 & characterSet5

specialKeys(characterSet1)
-- ² ³ é ½ ₭ is the result from calling specialKeys(characterSet1) After adding the text replacement for “a a a a a” in my keyboard preferences

Some special characters keystroke correctly when you set them as variables. Any of the special characters that you set as variables that do not keystroke properly, you can add “text replacements” in the system preferences for your keyboard.

Be sure to select all of the items and drag them to your desktop or wherever. This will create a .plist file which will be a backup of your original settings. You can drag this file back at anytime.

enter image description here

enter image description here

Then in ScriptEditor, make sure you have text replacements enabled in the “substitutions” sub menu of the edit menu.

enter image description here

enter image description here

  • 2
    The "Text Replacement" workaround works as desired in the majority of OS X applications (e.g., TextEdit, Notes, Script Editor, etc.), but I was hoping for a solution that would work in every text field in any application. Asking for a lot, I know! I still think that this is a very clever solution, so +1. Thank you for the answer. – rubik's sphere Jul 5 '17 at 5:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .