Skip to content

Instantly share code, notes, and snippets.

@yuyoyuppe
Last active January 7, 2024 15:50
Show Gist options
  • Save yuyoyuppe/d07d0691d70d489760845bdcd02c6db1 to your computer and use it in GitHub Desktop.
Save yuyoyuppe/d07d0691d70d489760845bdcd02c6db1 to your computer and use it in GitHub Desktop.
This application uses Windows On-Screen keyboard to type provided text.
#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