Skip to content

Commit

Permalink
Merge pull request octalmage#527 from circuit/UnicodeTap
Browse files Browse the repository at this point in the history
Unicode tap
  • Loading branch information
oktapodia authored Feb 13, 2020
2 parents 24231df + 7696d7f commit 06156d0
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 51 deletions.
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface Screen {
export function setKeyboardDelay(ms: number) : void
export function keyTap(key: string, modifier?: string | string[]) : void
export function keyToggle(key: string, down: string, modifier?: string | string[]) : void
export function unicodeTap(value: number) : void
export function typeString(string: string) : void
export function typeStringDelayed(string: string, cpm: number) : void
export function setMouseDelay(delay: number) : void
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
"scripts": {
"test": "run-script-os",
"test:darwin:linux": "jasmine 'test/**/*.js'",
"test-keyboard": "node test/keyboard.js",
"test:win32": "jasmine test/**/*.js",
"install": "prebuild-install || node-gyp rebuild",
"install-debug": "prebuild-install || node-gyp rebuild --debug",
"prebuild": "prebuild --all"
},
"repository": {
Expand Down Expand Up @@ -44,6 +46,7 @@
"prebuild-install": "^5.3.3"
},
"devDependencies": {
"tape": "^4.8.0",
"jasmine": "^2.99.0",
"prebuild": "^9.1.1",
"run-script-os": "^1.0.3",
Expand Down
68 changes: 33 additions & 35 deletions src/keypress.c
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,12 @@ void tapKey(char c, MMKeyFlags flags)
}

#if defined(IS_MACOSX)
void toggleUnicodeKey(unsigned long ch, const bool down)
void toggleUnicode(UniChar ch, const bool down)
{
/* This function relies on the convenient
* CGEventKeyboardSetUnicodeString(), which allows us to not have to
* convert characters to a keycode, but does not support adding modifier
* flags. It is therefore only used in typeString() and typeStringDelayed()
* flags. It is therefore only used in typeStringDelayed()
* -- if you need modifier keys, use the above functions instead. */
CGEventRef keyEvent = CGEventCreateKeyboardEvent(NULL, 0, down);
if (keyEvent == NULL) {
Expand All @@ -250,28 +250,43 @@ void toggleUnicodeKey(unsigned long ch, const bool down)
CGEventPost(kCGSessionEventTap, keyEvent);
CFRelease(keyEvent);
}

void toggleUniKey(char c, const bool down)
{
toggleUnicodeKey(c, down);
}
#else
#define toggleUniKey(c, down) toggleKey(c, down, MOD_NONE)
#endif

static void tapUniKey(char c)
void unicodeTap(const unsigned value)
{
toggleUniKey(c, true);
toggleUniKey(c, false);
#if defined(IS_MACOSX)
UniChar ch = (UniChar)value; // Convert to unsigned char

toggleUnicode(ch, true);
toggleUnicode(ch, false);
#elif defined(IS_WINDOWS)
INPUT ip;

// Set up a generic keyboard event.
ip.type = INPUT_KEYBOARD;
ip.ki.wVk = 0; // Virtual-key code
ip.ki.wScan = value; // Hardware scan code for key
ip.ki.time = 0; // System will provide its own time stamp.
ip.ki.dwExtraInfo = 0; // No extra info. Use the GetMessageExtraInfo function to obtain this information if needed.
ip.ki.dwFlags = KEYEVENTF_UNICODE; // KEYEVENTF_KEYUP for key release.

SendInput(1, &ip, sizeof(INPUT));
#endif
}

void typeString(const char *str)
void typeStringDelayed(const char *str, const unsigned cpm)
{
unsigned long n;
unsigned short c;
unsigned short c1;
unsigned short c2;
unsigned short c3;
unsigned long n;

/* Characters per second */
const double cps = (double)cpm / 60.0;

/* Average milli-seconds per character */
const double mspc = (cps == 0.0) ? 0.0 : 1000.0 / cps;

while (*str != '\0') {
c = *str++;
Expand All @@ -298,27 +313,10 @@ void typeString(const char *str)
n = ((c & 0x07) << 18) | (c1 << 12) | (c2 << 6) | c3;
}

#if defined(IS_MACOSX)
toggleUnicodeKey(n, true);
toggleUnicodeKey(n, false);
#else
toggleUniKey(n, true);
toggleUniKey(n, false);
#endif
unicodeTap(n);

}
}

void typeStringDelayed(const char *str, const unsigned cpm)
{
/* Characters per second */
const double cps = (double)cpm / 60.0;

/* Average milli-seconds per character */
const double mspc = (cps == 0.0) ? 0.0 : 1000.0 / cps;

while (*str != '\0') {
tapUniKey(*str++);
microsleep(mspc + (DEADBEEF_UNIFORM(0.0, 62.5)));
if (mspc > 0) {
microsleep(mspc + (DEADBEEF_UNIFORM(0.0, 62.5)));
}
}
}
8 changes: 4 additions & 4 deletions src/keypress.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ void tapKeyCode(MMKeyCode code, MMKeyFlags flags);
void toggleKey(char c, const bool down, MMKeyFlags flags);
void tapKey(char c, MMKeyFlags flags);

/* Sends a UTF-8 string without modifiers. */
void typeString(const char *str);
/* Sends a Unicode character without modifiers. */
void unicodeTap(const unsigned value);

/* Macro to convert WPM to CPM integers.
* (the average English word length is 5.1 characters.) */
#define WPM_TO_CPM(WPM) (unsigned)(5.1 * WPM)

/* Sends a string with partially random delays between each letter. Note that
* deadbeef_srand() must be called before this function if you actually want
/* Sends a UTF-8 string without modifiers and with partially random delays between each letter.
* Note that deadbeef_srand() must be called before this function if you actually want
* randomness. */
void typeStringDelayed(const char *str, const unsigned cpm);

Expand Down
48 changes: 36 additions & 12 deletions src/robotjs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ NAN_METHOD(keyToggle)
}
}

//Get the acutal key.
//Get the actual key.
switch(CheckKeyCodes(k, &key))
{
case -1:
Expand All @@ -590,36 +590,57 @@ NAN_METHOD(keyToggle)
break;
default:
toggleKeyCode(key, down, flags);
microsleep(keyboardDelay);
microsleep(keyboardDelay);
}

info.GetReturnValue().Set(Nan::New(1));
}

NAN_METHOD(unicodeTap)
{
size_t value = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();

if (value != 0) {
unicodeTap(value);

info.GetReturnValue().Set(Nan::New(1));
} else {
return Nan::ThrowError("Invalid character typed.");
}
}

NAN_METHOD(typeString)
{
char *str;
Nan::Utf8String string(info[0]);
if (info.Length() > 0) {
char *str;
Nan::Utf8String string(info[0]);

str = *string;
str = *string;

typeString(str);
typeStringDelayed(str, 0);

info.GetReturnValue().Set(Nan::New(1));
info.GetReturnValue().Set(Nan::New(1));
} else {
return Nan::ThrowError("Invalid number of arguments.");
}
}

NAN_METHOD(typeStringDelayed)
{
char *str;
Nan::Utf8String string(info[0]);
if (info.Length() > 0) {
char *str;
Nan::Utf8String string(info[0]);

str = *string;
str = *string;

size_t cpm = Nan::To<int32_t>(info[1]).FromJust();

typeStringDelayed(str, cpm);
typeStringDelayed(str, cpm);

info.GetReturnValue().Set(Nan::New(1));
info.GetReturnValue().Set(Nan::New(1));
} else {
return Nan::ThrowError("Invalid number of arguments.");
}
}

NAN_METHOD(setKeyboardDelay)
Expand Down Expand Up @@ -876,6 +897,9 @@ NAN_MODULE_INIT(InitAll)
Nan::Set(target, Nan::New("keyToggle").ToLocalChecked(),
Nan::GetFunction(Nan::New<FunctionTemplate>(keyToggle)).ToLocalChecked());

Nan::Set(target, Nan::New("unicodeTap").ToLocalChecked(),
Nan::GetFunction(Nan::New<FunctionTemplate>(unicodeTap)).ToLocalChecked());

Nan::Set(target, Nan::New("typeString").ToLocalChecked(),
Nan::GetFunction(Nan::New<FunctionTemplate>(typeString)).ToLocalChecked());

Expand Down
72 changes: 72 additions & 0 deletions test/keyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,75 @@ describe('Keyboard', () => {
}
});
});

test('Tap a Unicode character.', function(t)
{
t.plan(7);
t.ok(robot.unicodeTap("r".charCodeAt(0)) === 1, 'successfully tapped "r".');
t.ok(robot.unicodeTap("ά".charCodeAt(0)) === 1, 'successfully tapped "ά".');
t.ok(robot.unicodeTap("ö".charCodeAt(0)) === 1, 'successfully tapped "ö".');
t.ok(robot.unicodeTap("ち".charCodeAt(0)) === 1, 'successfully tapped "ち".');
t.ok(robot.unicodeTap("嗨".charCodeAt(0)) === 1, 'successfully tapped "嗨".');
t.ok(robot.unicodeTap("ఝ".charCodeAt(0)) === 1, 'successfully tapped "ఝ".');

t.throws(function()
{
robot.unicodeTap();
}, /Invalid character typed./, 'tap nothing.');
});

test('Test Key Toggle.', function(t)
{
t.plan(4);

t.ok(robot.keyToggle("a", "down") === 1, 'Successfully pressed a.');
t.ok(robot.keyToggle("a", "up") === 1, 'Successfully released a.');

t.throws(function()
{
t.ok(robot.keyToggle("ά", "down") === 1, 'Successfully pressed ά.');
t.ok(robot.keyToggle("ά", "up") === 1, 'Successfully released ά.');
}, /Invalid key code specified./, 'exception tapping ά.');

t.throws(function()
{
t.ok(robot.keyToggle("嗨", "down") === 1, 'Successfully pressed 嗨.');
t.ok(robot.keyToggle("嗨", "up") === 1, 'Successfully released 嗨.');
}, /Invalid key code specified./, 'exception tapping 嗨.');
});

test('Type Ctrl+Shift+RightArrow.', function(t)
{
t.plan(2);

var modifiers = []
modifiers.push('shift')
modifiers.push('control')

t.ok(robot.keyToggle("right", "down", modifiers) === 1, 'Successfully pressed Ctrl+Shift+RightArrow.');
t.ok(robot.keyToggle("right", "up", modifiers) === 1, 'Successfully released Ctrl+Shift+RightArrow.');
});

test('Type a string.', function(t)
{
t.plan(2);
t.ok(robot.typeString("Typed rάöち嗨ఝ 1") === 1, 'successfully typed "Typed rάöち嗨ఝ 1".');

t.throws(function()
{
t.ok(robot.typeString() === 1, 'Successfully typed nothing.');
}, /Invalid number of arguments./, 'exception tapping nothing.');
});

test('Type a string with delay.', function(t)
{
t.plan(2);

// 10 characters per minute -> 3 seconds to write the whole sentence here.
t.ok(robot.typeStringDelayed("Typed rάöち嗨ఝ with delay 1", 600) === 1, 'successfully typed with delay "Typed rάöち嗨ఝ with delay 1".');

t.throws(function()
{
t.ok(robot.typeStringDelayed() === 1, 'Successfully typed nothing.');
}, /Invalid number of arguments./, 'exception tapping nothing.');
});

0 comments on commit 06156d0

Please sign in to comment.