Skip to content

Instantly share code, notes, and snippets.

@AltimorTASDK
Created January 9, 2015 23:28
Show Gist options
  • Save AltimorTASDK/983463493e04993dabc2 to your computer and use it in GitHub Desktop.
Save AltimorTASDK/983463493e04993dabc2 to your computer and use it in GitHub Desktop.
#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