-
Notifications
You must be signed in to change notification settings - Fork 0
How the clicking thread works
We use a different thread so that the sleeps do not cause the GUI to freeze. This other thread can execute its own logic independently from the renderer, leaving no constraints for itself.
The clicking thread lives inside of the renderer within the struct RenderingContext. The type of this value is just HANDLE as we use native windows threads.
The rendering thread is simply the main thread.
These two are synchronized via the following structs in the renderer:
// Set to true to stop clicking_thread.
bool stop_click_threadf{ false };
bool prev_waiting_for_thread_exit{ false };
bool waiting_for_thread_exit{ false };As the comment states, once stop_click_threadf is set to true, this signals that the thread just exit cleanly. We don't use TerminateThread as it's generally bad practice and lazily terminates the thread.
The stop_click_threadf is set to true once you click the stop button. As seen by this real snippet of the code:
if (cc_button("Stop (F7)", "Stop auto-clicking.")) {
rcontext.stop_click_threadf = true;
// The working thread will set the above variable to false once it has exited.
// We must disable all buttons until this has happened.
rcontext.waiting_for_thread_exit = true;
rcontext.logln("Signaled clicking thread to exit, please wait...");
}The comment there describes what happens next. The clicking thread will set stop_click_threadf to false once it has successfully exited. After this, the field from before is used, waiting_for_thread_exit is set to true. This assignment causes the render loop to execute the following code each frame:
if (rcontext.waiting_for_thread_exit) {
ImGui::Text("Waiting for clicker thread to exit, please wait...");
rcontext.prev_waiting_for_thread_exit = rcontext.waiting_for_thread_exit;
rcontext.waiting_for_thread_exit = rcontext.stop_click_threadf == true;
}
else {
if (rcontext.prev_waiting_for_thread_exit) {
rcontext.prev_waiting_for_thread_exit = false;
on_stop_enable_input(rcontext);
rcontext.set_button_state(Button::Start, State::Clickable);
rcontext.set_button_state(Button::Stop, State::Unclickable);
CloseHandle(rcontext.clicking_thread);
rcontext.clicking_thread = INVALID_HANDLE_VALUE;
}
}We check if we're waiting for the thread to exit, if we are, we set prev_waiting_for_thread_exit to the current value of waiting_for_thread_exit. Having this second variable holding our previous state, allow us to see if the state has changed recently. We then set the current value for waiting_for_thread_exit to rcontext.stop_click_threadf == true. This expression would evaluate to false if the clicker thread had exited. (Remember, the clicker thread sets this to false upon exiting)
If waiting_for_thread_exit is false, the else branch will hit. We then check if we were previously waiting for the thread to exit via our second variable. This check prevent us from running this every frame. As if the clicker thread isn't running, rcontext.waiting_for_thread_exit will be false.
If we are, we re-enable all buttons and input widgets, then close the handle to the thread.
We hold state within the renderer about each widget and button in the window. They can either be State::Clickable or State::Unclickable. Widgets and certain buttons go to State::Unclickable when the clicker is running.
We do this to avoid race conditions. If the user is editing values while running, something could go wrong. Let's say the user has filled out their mouse click coordinates to be (0, 0). If the clicking thread is executing, and the user modifies the y value to (5). This could lead to us trying to read that address while the cpu is actively modifying it, which is undefined behaviour.
This also serves as a great way of visually notifying the user that the application is doing something. This is also signified by the the Start button changing to Stop (F7).
This is done inside of the actual thread itself. Here is the snippet that handles that:
if (GetAsyncKeyState(VK_F7) & 0x8000) {
// We have to trigger this to sync the UI.
info->prev_waiting_for_thread_exit = true;
info->waiting_for_thread_exit = false;
// Sleep for 100ms to allow the main thread to catch up with changes.
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Once we break, we will sync.
break;
}As you can see, we basically just simulate what happens when you press stop, but from within the thread. Also, please note that in that if statement, we break and don't return. This break causes the stop_click_threadf to be set to true once the thread is about to exit!
This application uses SendInput from the Windows API.
We simply send mouse events inside of an INPUT structure, more specifically inside of a MouseInput structure.
- For left mouse, we use
MOUSEEVENTF_LEFTDOWN&MOUSEEVENTF_LEFTUPto simulate keypresses. - For right mouse, we use
MOUSEEVENTF_RIGHTDOWN&MOUSEEVENTF_RIGHTUPto simulate keypresses. - For side buttons, we set the
mouseDatafield to the correctXBUTTONbased on what the user has selected. We then useMOUSEEVENTF_XDOWN&MOUSEEVENTF_XUPto simulate those keypresses.
If all of that above sounds very confusing, it is, because microsofts naming is a little confusing. Please read this to learn more.
Go back to the main page here