Skip to content

Instantly share code, notes, and snippets.

@mathieucarbou
Created March 13, 2017 00:55
Show Gist options
  • Save mathieucarbou/9ea4f5486261e3f72c8a68cacbc964f7 to your computer and use it in GitHub Desktop.
Save mathieucarbou/9ea4f5486261e3f72c8a68cacbc964f7 to your computer and use it in GitHub Desktop.
// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
/*
==============================================================================
SendKeys.cpp
This file is part of MIDI2LR. Copyright 2015-2017 by Rory Jaffe.
MIDI2LR is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
MIDI2LR is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
MIDI2LR. If not, see <http://www.gnu.org/licenses/>.
==============================================================================
*/
#include "SendKeys.h"
#include <algorithm>
#include <cctype>
#include <mutex>
#include <stdexcept>
#include <unordered_map>
#include <vector>
#ifdef _WIN32
#include "Windows.h"
#else
#import <CoreFoundation/CoreFoundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <Carbon/Carbon.h>
#include <cassert>
#include <libproc.h>
#include <thread>
#include <locale>
#include <codecvt>
#include <algorithm>
#include <iostream>
pid_t GetPID() {
pid_t pids[1024];
std::string LR{"Lightroom"};
int numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0);
proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids));
for (int i = 0; i < numberOfProcesses; ++i) {
if (pids[i] == 0) {
continue;
}
char name[1024];
proc_name(pids[i], name, sizeof(name));
if (LR.compare(name) == 0) {
return pids[i];
}
}
return 0;
}
#endif
namespace {
std::string to_lower(const std::string& in)
{
auto s = in;
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::toupper(c); });
return s;
}
#ifdef _WIN32
wchar_t MBtoWChar(const std::string& key)
{
wchar_t full_character;
const auto return_value = MultiByteToWideChar(CP_UTF8, 0, key.data(),
static_cast<int>(key.size()), &full_character, 1);
if (return_value == 0) {
const auto er = GetLastError();
if (er == ERROR_INVALID_FLAGS || er == ERROR_INVALID_PARAMETER)
throw std::invalid_argument("Bad argument to MultiByteToWideChar.");
if (er == ERROR_INSUFFICIENT_BUFFER)
throw std::length_error("Insufficient buffer for MultiByteToWideChar.");
if (er == ERROR_NO_UNICODE_TRANSLATION)
throw std::domain_error("Unable to translate: MultiByteToWideChar.");
throw std::runtime_error("Unknown error: MultiByteToWideChar.");
}
return full_character;
}
HKL GetLanguage(const std::string& program_name)
{
const auto hLRWnd = FindWindow(NULL, program_name.c_str());
if (hLRWnd) {
// get language that LR is using (if hLrWnd is found)
const auto thread_id = GetWindowThreadProcessId(hLRWnd, NULL);
return GetKeyboardLayout(thread_id);
}
else { // use keyboard of MIDI2LR application
return GetKeyboardLayout(0);
}
}
#else
std::u16string utf8_to_utf16 (const std::string& utf_str)
{
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter;
return converter.from_bytes(utf_str);
}
#endif
static const std::unordered_map<std::string, unsigned char> key_map_ = {
#ifdef _WIN32
{"backspace", VK_BACK},
{"cursor down", VK_DOWN},
{"cursor left", VK_LEFT},
{"cursor right", VK_RIGHT},
{"cursor up", VK_UP},
{"delete", VK_DELETE},
{"end", VK_END},
{"escape", VK_ESCAPE},
{"home", VK_HOME},
{"page down", VK_NEXT},
{"page up", VK_PRIOR},
{"return", VK_RETURN},
{"space", VK_SPACE},
{"tab", VK_TAB},
{"f1", VK_F1},
{"f2", VK_F2},
{"f3", VK_F3},
{"f4", VK_F4},
{"f5", VK_F5},
{"f6", VK_F6},
{"f7", VK_F7},
{"f8", VK_F8},
{"f9", VK_F9},
{"f10", VK_F10},
{"f11", VK_F11},
{"f12", VK_F12},
{"f13", VK_F13},
{"f14", VK_F14},
{"f15", VK_F15},
{"f16", VK_F16},
{"f17", VK_F17},
{"f18", VK_F18},
{"f19", VK_F19},
{"f20", VK_F20},
{"numpad 0", VK_NUMPAD0},
{"numpad 1", VK_NUMPAD1},
{"numpad 2", VK_NUMPAD2},
{"numpad 3", VK_NUMPAD3},
{"numpad 4", VK_NUMPAD4},
{"numpad 5", VK_NUMPAD5},
{"numpad 6", VK_NUMPAD6},
{"numpad 7", VK_NUMPAD7},
{"numpad 8", VK_NUMPAD8},
{"numpad 9", VK_NUMPAD9},
{"numpad add", VK_ADD},
{"numpad subtract", VK_SUBTRACT},
{"numpad multiply", VK_MULTIPLY},
{"numpad divide", VK_DIVIDE},
{"numpad decimal", VK_DECIMAL}
#else
{
"backspace", kVK_Delete
},
{"cursor down", kVK_DownArrow},
{"cursor left", kVK_LeftArrow},
{"cursor right", kVK_RightArrow},
{"cursor up", kVK_UpArrow},
{"delete", kVK_ForwardDelete},
{"end", kVK_End},
{"escape", kVK_Escape},
{"home", kVK_Home},
{"page down", kVK_PageDown},
{"page up", kVK_PageUp},
{"return", kVK_Return},
{"space", kVK_Space},
{"tab", kVK_Tab},
{"f1", kVK_F1},
{"f2", kVK_F2},
{"f3", kVK_F3},
{"f4", kVK_F4},
{"f5", kVK_F5},
{"f6", kVK_F6},
{"f7", kVK_F7},
{"f8", kVK_F8},
{"f9", kVK_F9},
{"f10", kVK_F10},
{"f11", kVK_F11},
{"f12", kVK_F12},
{"f13", kVK_F13},
{"f14", kVK_F14},
{"f15", kVK_F15},
{"f16", kVK_F16},
{"f17", kVK_F17},
{"f18", kVK_F18},
{"f19", kVK_F19},
{"f20", kVK_F20},
//using ANSI layout codes for keypad, may cause problems in some languages
{"numpad 0", kVK_ANSI_Keypad0},
{"numpad 1", kVK_ANSI_Keypad1},
{"numpad 2", kVK_ANSI_Keypad2},
{"numpad 3", kVK_ANSI_Keypad3},
{"numpad 4", kVK_ANSI_Keypad4},
{"numpad 5", kVK_ANSI_Keypad5},
{"numpad 6", kVK_ANSI_Keypad6},
{"numpad 7", kVK_ANSI_Keypad7},
{"numpad 8", kVK_ANSI_Keypad8},
{"numpad 9", kVK_ANSI_Keypad9},
{"numpad add", kVK_ANSI_KeypadPlus},
{"numpad subtract", kVK_ANSI_KeypadMinus},
{"numpad multiply", kVK_ANSI_KeypadMultiply},
{"numpad divide", kVK_ANSI_KeypadDivide},
{"numpad decimal", kVK_ANSI_KeypadDecimal}
#endif
};
static std::mutex mutex_sending_{};
}
void RSJ::SendKeyDownUp(const std::string& key, const bool alt_opt,
const bool control_cmd, const bool shift)
{
const auto mapped_key = key_map_.find(to_lower(key));
const auto in_keymap = mapped_key != key_map_.end();
#ifdef _WIN32
BYTE vk = 0;
BYTE vk_modifiers = 0;
if (in_keymap)
vk = mapped_key->second;
else {// Translate key code to keyboard-dependent scan code, may be UTF-8
const auto language_id = GetLanguage("Lightroom");
const auto vk_code_and_shift = VkKeyScanExW(MBtoWChar(key), language_id);
vk = LOBYTE(vk_code_and_shift);
vk_modifiers = HIBYTE(vk_code_and_shift);
}
//construct virtual keystroke sequence
std::vector<size_t> strokes{vk}; // start with actual key, then mods
if (shift || (vk_modifiers & 0x1)) {
strokes.push_back(VK_SHIFT);
}
if ((vk_modifiers & 0x06) == 0x06) {
strokes.push_back(VK_RMENU); //AltGr
if (control_cmd)
strokes.push_back(VK_CONTROL);
if (alt_opt)
strokes.push_back(VK_MENU);
}
else {
if (control_cmd || (vk_modifiers & 0x2)) {
strokes.push_back(VK_CONTROL);
}
if (alt_opt || (vk_modifiers & 0x4)) {
strokes.push_back(VK_MENU);
}
}
// construct input event.
INPUT ip;
constexpr auto size_ip = sizeof(ip);
ip.type = INPUT_KEYBOARD;
//ki: wVk, wScan, dwFlags, time, dwExtraInfo
ip.ki = {0, 0, 0, 0, 0};
//send key down strokes
std::lock_guard<decltype(mutex_sending_)> lock(mutex_sending_);
for (auto it = strokes.crbegin(); it != strokes.crend(); ++it) {
ip.ki.wVk = static_cast<WORD>(*it);
SendInput(1, &ip, size_ip);
}
//send key up strokes
ip.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
for (const auto it : strokes) {
ip.ki.wVk = static_cast<WORD>(it);
SendInput(1, &ip, size_ip);
}
#else
ProcessSerialNumber psn;
GetFrontProcess(&psn);
const CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
CGEventRef d;
CGEventRef u;
uint64_t flags = 0;
std::cout << "in_keymap = " + std::to_string(in_keymap) + "\n";
if (in_keymap) {
const auto vk = mapped_key->second;
std::cout << "vk = " + std::to_string(vk) + "\n";
d = CGEventCreateKeyboardEvent(source, vk, true);
u = CGEventCreateKeyboardEvent(source, vk, false);
} else {
const UniChar key_character(static_cast<UniChar>(utf8_to_utf16(key)[0]));
std::cout << "key = " + key + "\n";
std::cout << "key_character = " + std::to_string(key_character) + "\n";
d = CGEventCreateKeyboardEvent(source, 0, true);
u = CGEventCreateKeyboardEvent(source, 0, false);
const UniChar c = 'c';
CGEventKeyboardSetUnicodeString(d, 1, &c);
CGEventKeyboardSetUnicodeString(u, 1, &c);
// d = CGEventCreateKeyboardEvent(source, kVK_ANSI_C, true);
// u = CGEventCreateKeyboardEvent(source, kVK_ANSI_C, false);
flags = CGEventGetFlags(d); //in case KeyCode has associated flag
}
if (control_cmd) flags |= kCGEventFlagMaskCommand;
if (alt_opt) flags |= kCGEventFlagMaskAlternate;
if (shift) flags |= kCGEventFlagMaskShift;
if (flags) {
CGEventSetFlags(d, static_cast<CGEventFlags>(flags));
CGEventSetFlags(u, static_cast<CGEventFlags>(flags));
}
std::cout << "control_cmd = " + std::to_string(control_cmd) + "\n";
std::cout << "alt_opt = " + std::to_string(alt_opt) + "\n";
std::cout << "shift = " + std::to_string(shift) + "\n";
std::cout << "flags = " + std::to_string(flags) + "\n";
{
std::lock_guard<decltype(mutex_sending_)> lock(mutex_sending_);
CGEventPostToPSN(&psn, d);
CGEventPostToPSN(&psn, u);
}
CFRelease(d);
CFRelease(u);
CFRelease(source);
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment