Created
January 9, 2015 23:28
-
-
Save AltimorTASDK/983463493e04993dabc2 to your computer and use it in GitHub Desktop.
This file contains 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
#include <chrono> | |
#include <thread> | |
#include <iostream> | |
#include <string> | |
#include <vector> | |
#include <sstream> | |
#include <Windows.h> | |
/** | |
* perf_timer_delay - High resolution CPU timer sleep | |
* @us: Number of microseconds to sleep | |
* | |
* Loop until desired time has elapsed, yield to other threads to avoid | |
* uselessly consuming CPU time | |
*/ | |
static void perf_sleep(const int us) | |
{ | |
const auto start = std::chrono::high_resolution_clock::now(); | |
const auto end = start + std::chrono::microseconds(us); | |
do { | |
std::this_thread::yield(); | |
} while (std::chrono::high_resolution_clock::now() < end); | |
} | |
/** | |
* process_input - Process runtime input | |
* @input: User input string | |
* @enabled: Pointer to bool controlling whether macro is enabled | |
* @terminate: Pointer to bool to trigger program close | |
* @delay: Pointer to interval of macro | |
* | |
* Parse console input on a separate thread | |
*/ | |
static bool process_input( | |
const std::string &input, | |
bool *enabled, | |
bool *terminate, | |
double *delay) | |
{ | |
std::istringstream buffer(input); | |
std::vector<std::string> words; | |
// "explode" the input | |
for (std::string word; std::getline(buffer, word, ' ');) | |
words.push_back(std::move(word)); | |
const auto cmd = words.at(0); | |
if (cmd == "toggle") { | |
*enabled = !*enabled; | |
std::cout << (*enabled ? "Enabled" : "Disabled") << std::endl; | |
} else if (cmd == "exit") { | |
*terminate = true; | |
} else if (cmd == "delay") { | |
try { | |
*delay = std::stod(words.at(1)); | |
} catch (std::invalid_argument) { | |
std::cout << "Invalid delay specified" << std::endl; | |
} catch (std::out_of_range) { | |
std::cout << "Usage: delay <seconds>" << std::endl; | |
} | |
} else { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* main_loop - Main macro function | |
* @delay: Macro interval | |
* @trigger_vkey: Virtual keycode to activate macro | |
* @send_scancode: Scancode to send | |
* | |
* Check if LMB is held and if so press L at the specified interval | |
*/ | |
static void main_loop( | |
const double &delay, | |
const UINT trigger_vkey, | |
const UINT send_scancode) | |
{ | |
// High bit contains keypress state | |
if ((GetAsyncKeyState(VK_LBUTTON) & (1 << 15)) == 0) | |
return; | |
INPUT input; | |
input.type = INPUT_KEYBOARD; | |
input.ki.dwFlags = KEYEVENTF_SCANCODE; | |
input.ki.wScan = send_scancode; | |
input.ki.time = 0; | |
input.ki.dwExtraInfo = 0; | |
// convert to microseconds and halve for key down / key up | |
const auto delay_us = (int)(delay * .5 * 1e6); | |
SendInput(1, &input, sizeof(input)); | |
perf_sleep(delay_us); | |
input.ki.dwFlags |= KEYEVENTF_KEYUP; | |
SendInput(1, &input, sizeof(input)); | |
perf_sleep(delay_us); | |
} | |
/** | |
* parse_args - Parse command line arguments | |
* | |
* @argc: Number of elements in argv | |
* @argv: Command line arguments including exe name | |
* @trigger_vkey: Pointer to receive virtual key code to trigger macro | |
* @send_scancode: Pointer to receive scancode to send | |
* | |
* Return whether parsing was successful and convert virtual key to send to a | |
* scan code. | |
*/ | |
bool parse_args( | |
const int argc, | |
const char *argv[], | |
UINT *trigger_vkey, | |
UINT *send_scancode) | |
{ | |
if (argc <= 2) | |
return false; | |
UINT send_vkey; | |
try { | |
// Specify base as 0 so hex values can be passed | |
*trigger_vkey = std::stoi(argv[1], nullptr, 0); | |
send_vkey = std::stoi(argv[2], nullptr, 0); | |
} catch (std::invalid_argument) { | |
return false; | |
} | |
// Convert to scan code for SendInput | |
*send_scancode = MapVirtualKey(send_vkey, MAPVK_VK_TO_VSC); | |
return true; | |
} | |
/** | |
* main - Entry point | |
* @argc: Number of elements in argv | |
* @argv: Command line arguments including exe name | |
* | |
* Process command line input and enter main loop, yield to avoid extraneous | |
* CPU consumption | |
*/ | |
int main(const int argc, const char *argv[]) | |
{ | |
UINT trigger_vkey, send_scancode; | |
if (!parse_args(argc, argv, &trigger_vkey, &send_scancode)) { | |
std::cout << argv[0]; | |
std::cout << " <virtual key code to activate>"; | |
std::cout << " <virtual key code to send>"; | |
std::cout << std::endl; | |
std::cout << "http://www.kbdedit.com/manual/low_level_vk_list.html"; | |
std::cout << std::endl; | |
return EXIT_FAILURE; | |
} | |
std::cout << "Commands: \"toggle\", \"exit\", \"delay <seconds>\""; | |
std::cout << std::endl; | |
std::cout << "Enabled by default" << std::endl; | |
std::cout << "Default delay is 0.12" << std::endl; | |
std::cout << std::endl; | |
auto enabled = true; | |
auto terminate = false; | |
auto delay = .12; | |
std::thread input_thread([&enabled, &terminate, &delay] | |
{ | |
std::string input; | |
while (true) { | |
std::getline(std::cin, input); | |
if (!process_input(input, &enabled, &terminate, &delay)) | |
std::cout << "Invalid command" << std::endl; | |
} | |
}); | |
input_thread.detach(); | |
while (!terminate) { | |
main_loop(delay, trigger_vkey, send_scancode); | |
std::this_thread::yield(); | |
} | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment