Skip to content

Commit

Permalink
Merge pull request #5 from Nirad/keystoke
Browse files Browse the repository at this point in the history
KeyStrokeSender for OS X platform
  • Loading branch information
cbnolok authored Jul 18, 2020
2 parents 3c7e1e4 + 72d7bd3 commit f3542b0
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 106 deletions.
1 change: 1 addition & 0 deletions src/Leviathan.pro
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ unix:!win32 {
LIBS += -fopenmp # link against OpenMP library
} else {
LIBS += -L/usr/lib -lz # dynamically link zlib
LIBS += -framework ApplicationServices # for KeystrokeSender OSX

# UNTESTED
# looks like OpenMP support here began only with recent LLVM versions?
Expand Down
309 changes: 203 additions & 106 deletions src/keystrokesender/keystrokesender_mac.cpp
Original file line number Diff line number Diff line change
@@ -1,178 +1,275 @@
#if defined(__APPLE__)

#include <Carbon/Carbon.h>
#include "keystrokesender_mac.h"
#include <chrono>
#include <thread>
#include <sstream>

#define PLACEHOLDER void(0)
namespace ks
{

KeystrokeSender_Mac::KeystrokeSender_Mac(bool setFocusToWindow) :
m_setFocusToWindow(setFocusToWindow)
CGKeyCode KeyboardCodeFromCharCode(char charCode) {
switch (charCode)
{
case 'a': case 'A': return kVK_ANSI_A;
case 'b': case 'B': return kVK_ANSI_B;
case 'c': case 'C': return kVK_ANSI_C;
case 'd': case 'D': return kVK_ANSI_D;
case 'e': case 'E': return kVK_ANSI_E;
case 'f': case 'F': return kVK_ANSI_F;
case 'g': case 'G': return kVK_ANSI_G;
case 'h': case 'H': return kVK_ANSI_H;
case 'i': case 'I': return kVK_ANSI_I;
case 'j': case 'J': return kVK_ANSI_J;
case 'k': case 'K': return kVK_ANSI_K;
case 'l': case 'L': return kVK_ANSI_L;
case 'm': case 'M': return kVK_ANSI_M;
case 'n': case 'N': return kVK_ANSI_N;
case 'o': case 'O': return kVK_ANSI_O;
case 'p': case 'P': return kVK_ANSI_P;
case 'q': case 'Q': return kVK_ANSI_Q;
case 'r': case 'R': return kVK_ANSI_R;
case 's': case 'S': return kVK_ANSI_S;
case 't': case 'T': return kVK_ANSI_T;
case 'u': case 'U': return kVK_ANSI_U;
case 'v': case 'V': return kVK_ANSI_V;
case 'w': case 'W': return kVK_ANSI_W;
case 'x': case 'X': return kVK_ANSI_X;
case 'y': case 'Y': return kVK_ANSI_Y;
case 'z': case 'Z': return kVK_ANSI_Z;
case '-': case '_': return kVK_ANSI_Minus;
case '.': case '>': return kVK_ANSI_Period;
case ' ': return kVK_Space;
}
return 0;
}

int KeystrokeSender_Mac::findWindowPid()
{
const QString& name = "Ultima Online";
int Pid = 0;

CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID);
CFIndex numWindows = CFArrayGetCount(windowList);
CFStringRef nameRef = name.toCFString();

for(int i = 0; i < (int)numWindows; i++) {
CFDictionaryRef info = (CFDictionaryRef)CFArrayGetValueAtIndex(windowList, i);
CFStringRef thisWindowName = (CFStringRef)CFDictionaryGetValue(info, kCGWindowName);
if(thisWindowName && CFStringFind(thisWindowName, nameRef, 0).location != kCFNotFound) {
CFNumberRef thisPidNumber = (CFNumberRef)CFDictionaryGetValue(info, kCGWindowOwnerPID);
CFNumberGetValue(thisPidNumber, kCFNumberIntType, &Pid);
break;
}
}

CFRelease(nameRef);
CFRelease(windowList);
return Pid;
}

/*
bool KeystrokeSender_Mac::_sendChar(const char ch)
std::string KeystrokeSender_Mac::exec(const char* cmd)
{
PLACEHOLDER;
FILE* pipe = popen(cmd, "r");
if (!pipe) return "ERROR";
char buffer[128];
std::string result = "";
while(!feof(pipe))
{
if(fgets(buffer, 128, pipe) != NULL)
{
result += buffer;
}
}
pclose(pipe);
return result;
}
*/

bool KeystrokeSender_Mac::sendChar(const char ch)
void KeystrokeSender_Mac::focusWindow(int pid)
{
PLACEHOLDER;
std::string cmd = "osascript -e 'tell application \"System Events\" to set frontmost of the first process whose unix id is "+std::to_string(pid)+" to true'";

exec(&cmd[0]);
}

/*
bool KeystrokeSender_Mac::_sendEnter()
void KeystrokeSender_Mac::setClipboard(std::string text)
{
PLACEHOLDER;
std::stringstream cmd;
cmd << "printf \"" << text << "\" | pbcopy";
exec(cmd.str().c_str());
}
*/

bool KeystrokeSender_Mac::sendEnter()
KeystrokeSender_Mac::KeystrokeSender_Mac(bool setFocusToWindow) :
m_setFocusToWindow(setFocusToWindow)
{
PLACEHOLDER;
}

/*
bool KeystrokeSender_Mac::_sendString(const std::string& str, bool enterTerminated)
bool KeystrokeSender_Mac::sendChar(const char ch)
{
PLACEHOLDER;
sendCharFast(ch);
return true;
}

bool KeystrokeSender_Mac::sendEnter()
{
sendEnterFast();
return true;
}
*/

bool KeystrokeSender_Mac::sendString(const std::string& str, bool enterTerminated)
{
PLACEHOLDER;
sendStringFast(str,enterTerminated,false);
return true;
}

bool KeystrokeSender_Mac::sendStrings(const std::vector<std::string>& strings, bool enterTerminated)
{
PLACEHOLDER;
sendStringsFast(strings,enterTerminated,false);
return true;
}


/* static functions */

KSError KeystrokeSender_Mac::sendCharFast(const char ch, bool setFocusToWindow)
{
PLACEHOLDER;
int pid = findWindowPid();
if (pid ==0)
return KSError::NoWindow;
CGKeyCode key = KeyboardCodeFromCharCode(ch);

CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);

CGEventRef keyDown = CGEventCreateKeyboardEvent(source, key, TRUE);
CGEventRef keyUp = CGEventCreateKeyboardEvent(source, key, FALSE);

CGEventPostToPid(pid,keyDown);
CGEventPostToPid(pid, keyUp);

CFRelease(keyUp);
CFRelease(keyDown);
CFRelease(source);
}

KSError KeystrokeSender_Mac::sendEnterFast(bool setFocusToWindow)
{
PLACEHOLDER;
int pid = findWindowPid();
if (pid ==0)
return KSError::NoWindow;
CGEventFlags flags_shift = kCGEventFlagMaskControl;
CGEventRef ev;
CGEventSourceRef source = CGEventSourceCreate (kCGEventSourceStateCombinedSessionState);
//press down
ev = CGEventCreateKeyboardEvent (source, kVK_Return, true);
CGEventSetFlags(ev,flags_shift | CGEventGetFlags(ev)); //combine flags
CGEventPostToPid(pid,ev);
CFRelease(ev);

//press up
ev = CGEventCreateKeyboardEvent (source, kVK_Return, false);
CGEventSetFlags(ev,flags_shift | CGEventGetFlags(ev)); //combine flags
CGEventPostToPid(pid,ev);
CFRelease(ev);
return KSError::Ok;
}

KSError KeystrokeSender_Mac::sendStringFast(const std::string& str, bool enterTerminated, bool setFocusToWindow)
{
PLACEHOLDER;
int pid = findWindowPid();

if (pid ==0)
return KSError::NoWindow;

//bring to front
if (setFocusToWindow)
focusWindow(pid);

//add cmd to clipboard
setClipboard(str);


CGEventFlags flags_cmd = kCGEventFlagMaskCommand;
CGEventRef ev;
CGEventRef evc;
CGEventSourceRef source = CGEventSourceCreate (kCGEventSourceStateCombinedSessionState);

//press down ctrl
evc = CGEventCreateKeyboardEvent (source, kVK_Command, true);
CGEventSetFlags(evc,flags_cmd | CGEventGetFlags(evc)); //combine flags
CGEventPostToPid(pid,evc);
CFRelease(evc);

std::this_thread::sleep_for(std::chrono::milliseconds(25));

//press down
ev = CGEventCreateKeyboardEvent (source, kVK_ANSI_V, true);
CGEventSetFlags(ev,flags_cmd | CGEventGetFlags(ev)); //combine flags
CGEventPostToPid(pid,ev);
CFRelease(ev);

//press up
ev = CGEventCreateKeyboardEvent (source, kVK_ANSI_V, false);
CGEventSetFlags(ev,flags_cmd | CGEventGetFlags(ev)); //combine flags
CGEventPostToPid(pid,ev);
CFRelease(ev);

//press up ctrl
evc = CGEventCreateKeyboardEvent (source, kVK_Command, false);
CGEventSetFlags(evc,CGEventGetFlags(evc)); //combine flags
CGEventPostToPid(pid,evc);
CFRelease(evc);

if(enterTerminated)
{
sendEnterFast(setFocusToWindow);
}

CFRelease(source);
return KSError::Ok;
}

KSError KeystrokeSender_Mac::sendStringsFast(const std::vector<std::string>& strings, bool enterTerminated, bool setFocusToWindow)
{
PLACEHOLDER;
int pid = findWindowPid();

if (pid ==0)
return KSError::NoWindow;

//bring to front
if (setFocusToWindow)
{
focusWindow(pid);
}

for(auto s:strings)
{
sendStringFast(s,enterTerminated,false);
}
return KSError::Ok;
}


/* Static async functions */

KSError KeystrokeSender_Mac::sendCharFastAsync(const char ch, bool setFocusToWindow)
{
PLACEHOLDER;
return sendCharFast(ch,setFocusToWindow);
}

KSError KeystrokeSender_Mac::sendEnterFastAsync(bool setFocusToWindow)
{
PLACEHOLDER;
return sendEnterFast(setFocusToWindow);
}

KSError KeystrokeSender_Mac::sendStringFastAsync(const std::string& str, bool enterTerminated, bool setFocusToWindow)
{
PLACEHOLDER;
return sendStringFast(str,enterTerminated,setFocusToWindow);
}

KSError KeystrokeSender_Mac::sendStringsFastAsync(const std::vector<std::string> &strings, bool enterTerminated, bool setFocusToWindow)
{
PLACEHOLDER;
return sendStringsFast(strings,enterTerminated,setFocusToWindow);
}


}

#endif // defined (__APPLE__)


// Snippets taken here and there from the web
/*
#include <ApplicationServices/ApplicationServices.h>
void Press(int key);
void Release(int key);
void Click(int key);
int main() {
Press(56);
Click(6);
Release(56);
}
void Press(int key) {
// Create an HID hardware event source
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
// Create a new keyboard key press event
CGEventRef evt = CGEventCreateKeyboardEvent(src, (CGKeyCode) key, true);
// Post keyboard event and release
CGEventPost (kCGHIDEventTap, evt);
CFRelease (evt); CFRelease (src);
usleep(60);
}
void Release(int key) {
// Create an HID hardware event source
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
// Create a new keyboard key release event
CGEventRef evt = CGEventCreateKeyboardEvent(src, (CGKeyCode) key, false);
// Post keyboard event and release
CGEventPost (kCGHIDEventTap, evt);
CFRelease (evt); CFRelease (src);
usleep(60);
}
void Click(int key) {
Press(key);
Release(key);
}
------------------------------------
// From https://stackoverflow.com/questions/2379867/simulating-key-press-events-in-mac-os-x, by Dave DeLong
Here's code to simulate a Cmd-S action:
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
CGEventRef saveCommandDown = CGEventCreateKeyboardEvent(source, (CGKeyCode)1, YES);
CGEventSetFlags(saveCommandDown, kCGEventFlagMaskCommand);
CGEventRef saveCommandUp = CGEventCreateKeyboardEvent(source, (CGKeyCode)1, NO);
CGEventPost(kCGAnnotatedSessionEventTap, saveCommandDown);
CGEventPost(kCGAnnotatedSessionEventTap, saveCommandUp);
CFRelease(saveCommandUp);
CFRelease(saveCommandDown);
CFRelease(source);
A CGKeyCode is nothing more than an unsigned integer:
typedef uint16_t CGKeyCode; //From CGRemoteOperation.h
Your real issue will be turning a character (probably an NSString) into a keycode.
*/
Loading

0 comments on commit f3542b0

Please sign in to comment.