Last active
January 7, 2024 15:50
-
-
Save yuyoyuppe/d07d0691d70d489760845bdcd02c6db1 to your computer and use it in GitHub Desktop.
This application uses Windows On-Screen keyboard to type provided text.
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
#include <array> | |
#include <string> | |
#include <string_view> | |
#include <format> | |
#include <cstdio> | |
#include <cctype> | |
#include <vector> | |
#include <filesystem> | |
#include <windows.h> | |
#include <Winuser.h> | |
#include <shellapi.h> | |
namespace fs = std::filesystem; | |
const int WINDOW_WIDTH = 1660; | |
const int WINDOW_HEIGHT = 514; | |
const int ALPHANUM_BUTTON_SIZE = 73; | |
const int FIRST_ALPHA_ROW_START_HORIZ_OFFSET = 126; | |
const int FIRST_ALPHA_ROW_START_VERT_OFFSET = 200; | |
const int BUTTON_MARGIN = 4; | |
const wchar_t WINDOW_TITLE[] = L"On-Screen Keyboard"; | |
constexpr auto npos = std::string_view::npos; | |
// assumes qwerty keyboard layout | |
constexpr std::array<const std::string_view, 3> ALPHA_ROWS = {"qwertyuiop", "asdfghjkl", "zxcvbnm"}; | |
constexpr std::string_view NUM_SYMBOL_ROW = "!@#$%^&*()_+"; | |
POINT get_alpha_offset(const char letter) { | |
size_t row_idx = {}; | |
size_t col_idx = {}; | |
for(row_idx = 0; row_idx < size(ALPHA_ROWS); ++row_idx) { | |
if(col_idx = ALPHA_ROWS[row_idx].find(letter); col_idx != npos) | |
break; | |
} | |
POINT p{.x = FIRST_ALPHA_ROW_START_HORIZ_OFFSET + ALPHANUM_BUTTON_SIZE / 2, | |
.y = FIRST_ALPHA_ROW_START_VERT_OFFSET + ALPHANUM_BUTTON_SIZE / 2}; | |
// n-th button horizontal offset | |
p.x += static_cast<LONG>(col_idx) * (ALPHANUM_BUTTON_SIZE + BUTTON_MARGIN); | |
// n-th row horizontal offset | |
p.x += static_cast<LONG>(row_idx) * ALPHANUM_BUTTON_SIZE / 2; | |
// n-th row vertical offset | |
p.y += static_cast<LONG>(row_idx) * (ALPHANUM_BUTTON_SIZE + BUTTON_MARGIN); | |
return p; | |
} | |
POINT get_left_shift_offset() { | |
auto p = get_alpha_offset('z'); | |
p.x -= ALPHANUM_BUTTON_SIZE; | |
return p; | |
} | |
POINT get_num_symbol_offset(const size_t num_sym_idx) { | |
auto p = get_alpha_offset('q'); | |
p.y -= ALPHANUM_BUTTON_SIZE; | |
p.x += ALPHANUM_BUTTON_SIZE; | |
p.x += static_cast<LONG>(ALPHANUM_BUTTON_SIZE * num_sym_idx); | |
return p; | |
} | |
POINT get_num_offset(const char number) { | |
auto p = get_alpha_offset('q'); | |
p.y -= ALPHANUM_BUTTON_SIZE; | |
p.x += ALPHANUM_BUTTON_SIZE; | |
const LONG num_idx = number == '0' ? 9 : number - '1'; | |
p.x += ALPHANUM_BUTTON_SIZE * num_idx; | |
return p; | |
} | |
POINT get_enter_offset() { | |
auto p = get_alpha_offset('l'); | |
p.x += ALPHANUM_BUTTON_SIZE * 3; | |
return p; | |
} | |
void click_at_window_coords(HWND hwnd, POINT p) { | |
RECT rect{}; | |
GetWindowRect(hwnd, &rect); | |
p.x += rect.left; | |
p.y += rect.top; | |
SetCursorPos(p.x, p.y); | |
INPUT input[2] = {{.type = INPUT_MOUSE, .mi = {.dx = p.x, .dy = p.y, .dwFlags = MOUSEEVENTF_LEFTDOWN}}}; | |
input[1] = input[0]; | |
input[1].mi.dwFlags = MOUSEEVENTF_LEFTUP; | |
SendInput(2, input, sizeof(INPUT)); | |
} | |
void type_symbol_with_on_screen_keyboard(const HWND hwnd, const char symbol) { | |
std::vector<POINT> to_click; | |
if(isalpha(symbol)) { | |
if(isupper(symbol)) | |
to_click.push_back(get_left_shift_offset()); | |
to_click.push_back(get_alpha_offset(static_cast<char>(tolower(symbol)))); | |
} else if(const size_t num_sym_idx = NUM_SYMBOL_ROW.find(symbol); num_sym_idx != npos) { | |
to_click.push_back(get_left_shift_offset()); | |
to_click.push_back(get_num_symbol_offset(num_sym_idx)); | |
} else if(symbol >= '0' && symbol <= '9') | |
to_click.push_back(get_num_offset(symbol)); | |
else if(symbol == '\n') | |
to_click.push_back(get_enter_offset()); | |
else { | |
printf("Unsupported symbol '%c'!\n", symbol); | |
} | |
for(const auto point : to_click) | |
click_at_window_coords(hwnd, point); | |
} | |
void setup_on_screen_keyboard_window(const HWND hwnd) { | |
SetWindowPos(hwnd, HWND_TOP, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, SWP_NOMOVE | SWP_SHOWWINDOW); | |
SetForegroundWindow(hwnd); | |
ShowWindow(hwnd, SW_NORMAL); | |
} | |
void Demo(const HWND hwnd) { | |
printf("Demo started!\n"); | |
printf("Typing numeric symbols...\n"); | |
for(const auto sym : NUM_SYMBOL_ROW) { | |
type_symbol_with_on_screen_keyboard(hwnd, sym); | |
Sleep(125); | |
} | |
printf("Typing lowercase letters...\n"); | |
for(const auto letter_row : ALPHA_ROWS) | |
for(const auto letter : letter_row) { | |
type_symbol_with_on_screen_keyboard(hwnd, letter); | |
Sleep(125); | |
} | |
printf("Typing uppercase letters...\n"); | |
for(const auto letter_row : ALPHA_ROWS) | |
for(const auto letter : letter_row) { | |
type_symbol_with_on_screen_keyboard(hwnd, static_cast<char>(toupper(letter))); | |
Sleep(125); | |
} | |
printf("Typing numbers...\n"); | |
for(int i = 0; i < 10; ++i) { | |
type_symbol_with_on_screen_keyboard(hwnd, static_cast<char>('0' + i)); | |
Sleep(125); | |
} | |
printf("Demo finished!\n"); | |
} | |
bool is_elevated() { | |
bool isElevated = false; | |
HANDLE hToken = NULL; | |
if(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { | |
TOKEN_ELEVATION elevation; | |
DWORD dwSize; | |
if(GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &dwSize)) | |
isElevated = elevation.TokenIsElevated != 0; | |
CloseHandle(hToken); | |
} | |
return isElevated; | |
} | |
struct Config { | |
bool demo = false; | |
int sleep_ms = 3000; | |
std::string text_to_type; | |
}; | |
std::string u16toAscii(const wchar_t * wide_string) { | |
std::string ascii_str; | |
while(*wide_string) { | |
ascii_str += static_cast<char>(*wide_string); | |
++wide_string; | |
} | |
return ascii_str; | |
} | |
Config parse_cmd_args() { | |
Config c; | |
int n = {}; | |
LPWSTR * args_w = CommandLineToArgvW(GetCommandLineW(), &n); | |
if(!args_w) | |
std::quick_exit(1); | |
std::vector<std::string> args; | |
for(int i = 1; i < n; i++) | |
args.push_back(u16toAscii(args_w[i])); | |
switch(n - 1) { | |
case 0: { | |
const auto exec_name = fs::path{u16toAscii(args_w[0])}.filename().replace_extension().string(); | |
_printf_p(R"d(Usage: | |
%1$s <text> Types <text> using Windows On-Screen keyboard then presses enter. | |
%1$s <delay> <text> Same as above, but also with a specified delay. | |
%1$s demo Performs a demo by typying each supported key. | |
)d", | |
exec_name.c_str()); | |
std::quick_exit(0); | |
break; | |
} | |
case 1: | |
c.text_to_type = args[0]; | |
c.demo = c.text_to_type == "demo"; | |
break; | |
case 2: | |
std::from_chars(args[0].c_str(), args[0].c_str() + args[0].size(), c.sleep_ms); | |
c.text_to_type = args[1]; | |
break; | |
} | |
LocalFree(args_w); | |
return c; | |
} | |
void KillProcessByHandle(HWND hwnd) { | |
DWORD processID; | |
GetWindowThreadProcessId(hwnd, &processID); | |
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, processID); | |
if(hProcess != NULL) { | |
TerminateProcess(hProcess, 1); | |
CloseHandle(hProcess); | |
} | |
} | |
int main() { | |
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); | |
if(!is_elevated()) { | |
printf("We must be elevated to be able to resize '%ws' window!\n", WINDOW_TITLE); | |
return 1; | |
} | |
const HWND hwnd = FindWindowW(nullptr, WINDOW_TITLE); | |
if(!hwnd) { | |
printf("Couldn't find '%ws' window!", WINDOW_TITLE); | |
return 1; | |
} | |
setup_on_screen_keyboard_window(hwnd); | |
const auto cfg = parse_cmd_args(); | |
printf("Waiting %d seconds, capture the mouse by a Hyper-V window in the meantime!\n", cfg.sleep_ms / 1000); | |
Sleep(cfg.sleep_ms); | |
if(cfg.demo) | |
Demo(hwnd); | |
else { | |
for(const char symbol : cfg.text_to_type) { | |
Sleep(20); | |
type_symbol_with_on_screen_keyboard(hwnd, symbol); | |
} | |
Sleep(20); | |
type_symbol_with_on_screen_keyboard(hwnd, '\n'); | |
Sleep(200); | |
KillProcessByHandle(hwnd); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment