Created
March 13, 2017 00:55
-
-
Save mathieucarbou/9ea4f5486261e3f72c8a68cacbc964f7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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