Зачем две темы в одной статье? Затем, что есть задачи, которые нуждаются в обоих механизмах, поэтому новичок, решивший освоить C++ для своих практических нужд, с помощью этой статьи может сразу приступить к реализации не слишком сложных проектов.
Для примера, вот список некоторых задач, которые решаются с помощью этих механизмов:

  • подмена/эмуляция нажатий клавиш, кнопок мыши;

  • автоматизация ввода, симуляция поведения пользователя в зависимости от содержимого на экране;

  • снимки с экрана (по нажатию клавиш или по таймингу);

  • замена статического содержимого с целью повышения комфорта работы (замена фона, повышение контраста шрифта, экранная лупа);

  • вывод компактных индикаторов/списков выбора в любой момент в любое место на экране;

  • превращение домашнего компьютера в медиацентр (беспроводная клавиатура + набор шаблонов для запуска фильмов, музыки, регулировки звука и частот + кресло/диван + большой монитор + печеньки :-) ).

Любопытно? Тогда прошу в статью!

Вначале расскажу о механизмах, а в конце, в примерах кода, о том, как применять эти механизмы, как достичь поставленных задач

Клавиатурные хуки

Что предлагает компания "мелкого софта" разработчику прикладных программ в плане взаимодействия с пользователем через клавиатуру и мышь? Оконный интерфейс и процедуру окна, которая обрабатывает все нажатия и клики. В первом же примере использования ввода клавиатуры показана схема построения такой процедуры.
Такой подход стратегически правильный для программиста классического интерфейса в перспективе, но ведь не все наши читатели собираются быть такими программистами, поэтому эта статья предлагает более простой и универсальный способ решать задачи для своих домашних потребностей без необходимости становится "классическим программистом" под Windows.

Определение из документации:

Хук (hook, ловушка, крючок, перехватчик) ‒ это точка в системном механизме обработки сообщений, где приложение может установить подпрограмму для мониторинга трафика сообщений в системе и обработки определенных типов сообщений, прежде чем они достигают целевой процедуры окна.
Проще говоря, хук перехватывает нужные события и делает с ними что пожелает.

Общие сведения о хуках

Особенности оконных процедур (ОП) и хуков в сравнении.
ОП имеет неприятное свойство расти в размере очень быстро, поскольку все оконные сообщения "стекаются" туда и размер ОП будет зависеть от кол-ва сообщений, которые вы хотите обработать, что ухудшает читабельность кода и поиск нужных мест.
Хук имеет свою процедуру и перенос туда обработки ввода разгрузит ОП.
ОП обрабатывает сообщения ввода только для активного окна в фокусе, к которому привязана; локальный хук делает то же самое, но глобальный хук (ГХ) обрабатывает ВСЕ сообщения ввода и ему не нужны окна. ГХ может работать в коде обычного консольного приложения, которое можно скрыть из панели задач; он может перехватывать все нажатия раньше любых ОП, менять их или удалять из потока, что открывает широкие возможности управления на вашем персональном компе. Все ГХ работают по принципу стека LIFO ‒ последний, кто зарегистрирован, первый получает доступ к потоку, поэтому ваше приложение может обладать монополией на доступ к потоку, если это необходимо.

Установка локальных и глобальных хуков практически идентична, также, как их процедуры обработки, а отличий совсем мало, так что освоение хуков позволяет вообще не использовать ОП даже при наличии окон в вашей программе (не во всех задачах). Установка хука выполняется функцией SetWindowsHookEx.
Например, вот как я устанавливаю глобальный и локальный хуки:

HHOOK hhook = NULL, hhookG = NULL;
hhookG = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProcG, NULL, 0);
if (!hhookG) return 1;
hhook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, NULL, GetCurrentThreadId());
if (!hhook) return 1;

Разница видна сразу: первый аргумент определяет тип процедуры перехвата, второй, соответственно, имя конкретной процедуры; 4-й ‒ идентификатор потока, для ГХ он не нужен.
ГХ не обязательно использовать в формате dll, он совершенно естественно живёт в любом коде, но для этого нужны два условия.
Периодическая работа одного из методов считывания клавиатуры и постоянная готовность считать новое нажатие, т.е. отсутствие задержки в коде на получения очередного сообщения.
Для локального хука задержка не критична, она способна лишь "подвесить" только одно окно, к которому привязана, а вот ГХ может легко застопорить всю систему.

ОП используют две функции считывания потока сообщений ‒ PeekMessage и GetMessage.
Для работы хуков они тоже нужны, однако есть и другие возможности срабатывания хука ‒ если процесс активировал какой-либо диалог. Самый простейший ‒ MessageBox, если вы его вызвали, то оба хука будут считывать поток, но с той лишь разницей, что локальный ‒ только если MessageBox в фокусе, а глобальный ‒ пока диалог вообще на экране. Можете попробовать это в простейшем коде консольного приложения:

Код здесь
#include <iostream>
#include <windows.h>
#include <wingdi.h>
using namespace std;

HHOOK hhook, hhookG;
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK KeyboardProcG(int nCode, WPARAM wParam, LPARAM lParam);

int main() {
hhook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, NULL, GetCurrentThreadId());
hhookG = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProcG, NULL, 0);
MessageBox(NULL, "hello, world", "caption", 0);
UnhookWindowsHookEx(hhook);
UnhookWindowsHookEx(hhookG);
return 1;
}

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
cout << endl << " Local";
return CallNextHookEx(hhook, nCode, wParam, lParam);
}

LRESULT CALLBACK KeyboardProcG(int nCode, WPARAM wParam, LPARAM lParam) {
cout << endl << " Global";
return CallNextHookEx(hhookG, nCode, wParam, lParam);
}

Как лучше всего использовать хуки в коде?
Я предлагаю такой вариант:

for(;;) {
	for(BYTE i = 0; i < 20; i++) {
		Sleep(1);
		PeekMessage(&msg, (HWND) NULL, 0, 0, PM_REMOVE);
	}
	// ваш код
}

PeekMessage используется в бесконечном цикле с минимальной задержкой для прослушивания потока, GetMessage я не рекомендую (для новичков это будет сложней, а для профи мои советы не нужны).
Малый цикл и Sleep(1) нужны для разгрузки процессора, переменную i выставьте под свои нужды.

Процедуры хуков

Как вы уже видели, прототипы процедур хуков записываются одинаково, но значения их параметров отличаются.
Справка по этим процедурам: KeyboardProc и LowLevelKeyboardProc.

Значения аргументов:
int nCode ‒ вроде бы нужен, но я его не использую, не было необходимости. Почему? ‒ История об этом умалчивает :-)
WPARAM wParam ‒ код виртуального ключа в локальном хуке, а в глобальном код, определяющий состояние клавиши ‒ нажата или отжата. Если wParam == 256, то нажата, 257 ‒ отжата. Для клавиши ALT установлен другой код.
Коды виртуальных ключей

LPARAM lParam ‒ локальный ‒ флаги и коды; глобальный ‒ структура KBDLLHOOKSTRUCT.
В локальном самый важный флаг ‒ признак нажатия/отжатия. Чтобы вам не заморачиваться с выделением этого флага, то вот лайфхак:
if (lParam > 3000000000) клавиша отжата, если меньше, то нажата.
В структуре KBDLLHOOKSTRUCT самый важный член ‒ код клавиши vkCode, выделяется он так:

KBDLLHOOKSTRUCT * kbs = (KBDLLHOOKSTRUCT *) lParam;
switch (kbs->vkCode) {
	case VK_RWIN: // правая WIN

И последнее по хукам, возврат функции. Если нужно передать значение потока другим процессам, то используйте

return CallNextHookEx(hhook, nCode, wParam, lParam);

а если заблокировать передачу, то return 1

Пример использования хуков

Напоследок стоит ещё коснуться функций GetAsyncKeyState и GetKeyState ‒ с первой я разобрался хорошо, а вот логику второй понять не смог и не понял, зачем она может мне пригодиться, так что скажу только про первую.
На уровне системы есть карта триггеров всех клавиш, где каждый триггер показывает одно из трёх состояний клавиши ‒ не была нажата, была нажата, сейчас нажата.
Эта карта одна для всех процессов, поэтому, если два процесса будут опрашивать один триггер, то считать сможет только первый процесс, потому что после запуска функции триггер всегда переходит в нулевое состояние и остаётся таким, пока его клавиша снова не будет нажата.
Полезное свойство функции в том, что она абсолютно не зависит от всех других методов считывания клавиш, т.е. для её работы не нужно устанавливать хуки, не нужно периодически проверять буфер клавиатуры и другие функции не очищают эти триггеры. Но в этом и её недостаток, потому что вы можете получить состояние триггера только после запуска функции (ловушка на триггер не ставится) и только если нет других процессов, которые опрашивают тот же триггер; и никакой другой инфы про нажатие клавиш вы не получите. Использовать эту функцию имеет смысл только в тех редких случаях, когда невозможно использовать хук, например, в коде инжектора или шпиона.

Работа с растром

Что предлагает компания "мелкого софта" разработчику прикладных программ для работы с растром? Набор графических объектов, выбираемых в контексте устройства (DC).

Контекст устройства (КУ) ‒ это структура, которая определяет набор графических объектов и связанных с ними атрибутов, а также графические режимы, влияющие на выходные данные.

Для работы непосредственно с буфером битовых данных есть функции передачи битовых блоков между КУ, растровым изображением (РИ) и буфером.
В первом же примере из документации "Захват изображения" показано создание рисунка и передача битовых блоков в буфер.
Когда я писал свой первый код для работы с растром, то взял функции из примера, переписал под свои нужды и всё заработало. Но позже я понял, что показанное в примере является весьма неэффективным способом извлечения битовых данных, потому что там, на самом деле, передача данных происходит дважды ‒ вначале через BitBlt, потом через GetDIBits.

Когда я узнал, что есть способ передачи данных за один раз, то мысленно воскликнул: "а что, так можно было?! Так что же вы сразу то не сказали?!"
И я покажу вам компактный код, как это делается "легко и просто", до которого я дошёл путём чтения сторонних сайтов и разных экспериментов.

Есть несколько функций для создания РИ, но только одна подходит для нашей статьи ‒ CreateDIBSection.
Её преимущество в том, что она после создания РИ предоставляет указатель на расположение битовых значений DIB. Почему другие функции не хотят предоставлять такой указатель ‒ загадка от ведущих разработчиков известной компании.
Теперь кратко о том, как её использовать.
Берём КУ экрана (если один монитор)
HDC hdc = GetDC(0);
Создаём совместимый КУ в памяти для размещения там РИ
HDC hdcMem = CreateCompatibleDC(hdc);
Создание структуры BITMAPINFOHEADER найдёте в примере. Скажу только, что член biHeight я ставлю отрицательным, чтобы начало координат BitBlt соответствовало первому элементу массива. Если biHeight положительный, то первый байт массива указывает на левый нижний угол изображения, а если отрицательный, то на левый верхний. Член biBitCount будет равен 24, а не 32, как в примере документации.

PBYTE pb=nullptr;
HBITMAP hBitmap = CreateDIBSection(hdcMem, (BITMAPINFO*)&bi, DIB_RGB_COLORS, (void**)&pb, 0,0);
if (pb == nullptr) return 2;

Если указатель pb существует, то РИ с буфером создано успешно и его нужно выбрать в КУ.
SelectObject(hdcMem, hBitmap);

После этого передадим битовые блоки с экрана в буфер, чтобы их можно было отредактировать и передать обратно на экран. Для этого есть две функции: BitBlt и SetDIBitsToDevice, но вторую использовать не рекомендую, потому что установка аргументов там крайне сложная:
SetDIBitsToDevice(hdc, x, y, w, h, x, y, h+y*2-hScr, hScr, pb, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
С BitBlt всё просто, там вопросов не должно возникнуть (после чтения справки).

Важные мелочи

Перед показом кода нужно рассказать кое-что из опыта того, что я уже делал, т.е. я реализовал почти всё, что описал в начале статьи (автоматизация ввода, эмуляция нажатий, ...).

  1. GetDC(0) ‒ очень простой способ доступа к растру на экране на ввод и вывод. Но выполнять эту функцию придётся почти каждый раз, когда вы хотите считать данные или записать. Это связано с тем, что все КУ фактически расположены в памяти и программа никогда не работает с устройством напрямую ‒ система сама пересылает данные между КУ и устройством. А КУ устройств назначаются динамически всякий раз, когда другой процесс делает ввод/вывод. GetDC(0) можно вызвать один раз для ввода и вывода, если между операциями проходят единицы миллисекунд. Это не относится к GetDC(hwnd), потому что для окна КУ назначается и не меняется. Если вам вдруг будет нужно получить КУ чужого окна, то самый простой способ ‒ это GetForegroundWindow.

  2. GetPixel ‒ очень удобный способ получения отдельных пикселей для того, чтобы узнать, что происходит на экране. Используется для автоматизации ввода. Но для полного сканирования блоков растра не подходит, ибо в цикле это будет ооочень медленно.

COLORREF pixel;
pixel = GetPixel(hdc, x, y);
if(GetRValue(pixel)==0 && GetGValue(pixel)==255 && GetBValue(pixel)==0) { // зелёный
  1. Эмуляция нажатий, кликов. Я использую keybd_event и mouse_event, хотя рекомендуется SendInput. Они проще, а мне лень :) Вот перемещение мыши в координаты x, y и левый клик

mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, x, y, 0, NULL);
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, NULL);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, NULL);

Вот сочетание Alt+Tab

keybd_event(VK_LMENU, 0, 0, NULL);
keybd_event(VK_TAB, 0, 0, NULL);
keybd_event(VK_TAB, 0, KEYEVENTF_KEYUP, NULL);
keybd_event(VK_LMENU, 0, KEYEVENTF_KEYUP, NULL);

Другие моменты смотри в разделе ниже.

Примеры кода

Нужно иметь ввиду, что на результат запуска и работы этих программ влияют следующие факторы: наличие второго монитора, права запуска приложений, матрица монитора (IPS - отлично, TN - удовлетворительно), разрешение экрана и масштаб шрифтов, ...
Также не забудьте указать компилятору использовать библиотеку gdi32.

Замена фона и шрифта

Программа меняет фон и шрифт (незначительно) на обоих цветовых схемах хабра, для улучшения восприятия (на мой взгляд). Используется весь экран кроме 100 пикселей сверху и снизу, настройка производилась на экране с IPS матрицей при 1920х1080, масштаб страницы увеличен.
Клавиши управления (глобальные): правая ALT ‒ включение/отключение функции, правая WIN ‒ выход. Если у вас нет правой WIN, то поменяйте на другую.
При нажатии клавиш в левом верхнем углу появится индикация режима на 0,8 сек.

Схема работы

Создаётся РИ с доступом к буферу данных через указатель pb; РИ обязательно создаётся с полным разрешением экрана, чтобы избежать неожиданный выход за границы буфера.
В бесконечном цикле for(;;) проверяется поток ввода, состояние режимов (condit) и вывод индикатора. Работа с таймером через библиотеку chrono.
Процедура SetBB считывает содержимое экрана, ищет нужные пиксели для замены и выводит картинку обратно, если искомое найдено (bOut). Проверка if(pbEnd > pbMax) нужна исключительно для тестирования, используйте её всякий раз, пока код не будет "отполирован до блеска" :) . ipb ‒ временный указатель для цикла, pb + screenW*y*3 ‒ начало блока, pbEnd ‒ конец.
В остальном код очень простой. Если будет странно, почему BITMAPINFOHEADER используется как BITMAPINFO, то это взято из примера в документации.

Код здесь
#include <iostream>
#include <windows.h>
#include <wingdi.h>
using namespace std;

HHOOK hhook = NULL;
BYTE condit = 0;
PBYTE pb = nullptr;
HDC hdcMem;
HBITMAP hBitmap;
LONG screenW, screenH;
auto timerB = chrono::high_resolution_clock::now();
auto timerE = timerB;
bool bTimer = false;
HFONT hf;

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
void SetBB();

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow)
{
FreeConsole();
LOGFONT lf;
ZeroMemory(&lf, sizeof(lf));
CHAR ch[] = "Arial";
lstrcpy(lf.lfFaceName, ch);
lf.lfHeight = 40;
lf.lfWeight = 1000;
hf = CreateFontIndirectA(&lf);
screenW = GetSystemMetrics(SM_CXSCREEN);
screenH = GetSystemMetrics(SM_CYSCREEN);

BITMAPINFOHEADER bi;
ZeroMemory(&bi, sizeof(bi));
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = screenW;
bi.biHeight = -screenH; // -
bi.biPlanes = 1;
bi.biBitCount = 24;
bi.biCompression = BI_RGB;

HDC hdc = GetDC(0);
hdcMem = CreateCompatibleDC(hdc);
hBitmap = CreateDIBSection(NULL, (BITMAPINFO*)&bi, DIB_RGB_COLORS, (void**)&pb, 0,0);
if (pb == nullptr) return 2;
SelectObject(hdcMem, hBitmap);

hhook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0);
if (!hhook) return 3;

MSG msg;

for(;;) {
	for(BYTE i=0; i<20; i++) {
		Sleep(1);
		PeekMessage(&msg, (HWND) NULL, 0, 0, PM_REMOVE);
	}
	if (condit == 9) break;
	if (condit == 1) SetBB();
	if (bTimer) {
		timerE = chrono::high_resolution_clock::now();
		UINT msCount = chrono::duration_cast<chrono::milliseconds>(timerE - timerB).count();
		if (msCount > 800) {
			bTimer = false;
			hdc = GetDC(0);
			SelectObject(hdc, hf);
			SetBkColor(hdc, RGB(0,0,0));
			TextOut(hdc, 2, 2, "0", 1);
			if (condit == 8) condit = 9;
		}
	}
	if (bTimer) {
		hdc = GetDC(0);
		SelectObject(hdc, hf);
		SetBkColor(hdc, RGB(0,220,0));
		switch (condit) {
			case 0:
				TextOut(hdc, 2, 2, "0", 1); break;
			case 1:
				TextOut(hdc, 2, 2, "1", 1); break;
			case 8:
				TextOut(hdc, 2, 2, "-", 1); break;
		}
	}
}

DeleteObject(hBitmap);
DeleteDC(hdcMem);
return 1;
}


LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
KBDLLHOOKSTRUCT * kbs = (KBDLLHOOKSTRUCT *) lParam;
//cout << " " << kbs->vkCode << " " << kbs->scanCode << ' ' << wParam << endl;
switch (kbs->vkCode) {
	case VK_RWIN:
		if (wParam != 257) return 1;
		timerB = chrono::high_resolution_clock::now();
		bTimer = true; condit = 8; return 1;
		break;
	case VK_RMENU:
		if (wParam != 257) return 1;
		timerB = chrono::high_resolution_clock::now();
		bTimer = true; condit = 1 - condit; return 1;
		break;
}

return CallNextHookEx(hhook, nCode, wParam, lParam);
}


void SetBB() {
PBYTE ipb = nullptr;
LONG w = screenW;
LONG h = screenH - 200;
LONG x = 0, y = 100;
bool bOut = false;
HDC hdc = GetDC(0);
BitBlt(hdcMem, x, y, w, h, hdc, x, y, SRCCOPY);

PBYTE pbMax = pb + screenW*screenH*3;
PBYTE pbEnd = pb + screenW*(h+y)*3;
if(pbEnd > pbMax) exit(0);

for(ipb = pb + screenW*y*3; ipb < pbEnd; ipb += 3) {
	// white
	if (*ipb == 255 && *(ipb +1) == 255 && *(ipb +2) == 255) {
		*ipb = 250; *(ipb +1) = 255; *(ipb +2) = 245;
		bOut = true;
	}
	if (*ipb == 51 && *(ipb +1) == 51 && *(ipb +2) == 51) {
		*ipb = 0; *(ipb +1) = 0; *(ipb +2) = 0;
		bOut = true;
	}
	if (*ipb == 119 && *(ipb +1) == 117 && *(ipb +2) == 111) {
		*ipb = 0; *(ipb +1) = 0; *(ipb +2) = 0;
		bOut = true;
	}
	// black
	if (*ipb == 221 && *(ipb +1) == 221 && *(ipb +2) == 221) {
		*ipb = 255; *(ipb +1) = 255; *(ipb +2) = 255;
		bOut = true;
	}
	if (*ipb == 170 && *(ipb +1) == 170 && *(ipb +2) == 170) {
		*ipb = 255; *(ipb +1) = 255; *(ipb +2) = 255;
		bOut = true;
	}
	if (*ipb == 23 && *(ipb +1) == 23 && *(ipb +2) == 23) {
		*ipb = 32; *(ipb +1) = 32; *(ipb +2) = 32;
		bOut = true;
	}
}

if(bOut) BitBlt(hdc, x, y, w, h, hdcMem, x, y, SRCCOPY);
ReleaseDC(0, hdc);
}

Всплывающее окно

Показывает всплывающее окно поверх всех окон, которое становится активным и которое не видно в панели задач.
Не всегда достаточно только вывода непосредственно на экран ‒ окно необходимо как изолированное пространство для вывода во многих случаях, но, поскольку концепция данной темы поддерживает минимализм и быструю реакцию на запрос пользователя, то окно, которое мгновенно появляется по нажатию одной клавиши и так же быстро исчезает ‒ именно то средство, которое поможет реализовать поставленные в начале статьи задачи.
Смысл использования такого окна: выбор режима работы программы, вывод индикаторов на длительное время в любое место на экране.

Схема работы

Код создания окна взят из классических примеров, для окна используются стили WS_EX_TOOLWINDOW, WS_EX_TOPMOST и WS_POPUPWINDOW. Член hbrBackground структуры WNDCLASSEX не заполнен, чтобы создать прозрачное окно и на моей 7-й винде так оно и есть, но на 10-й винде окно белое; видимо, чего-то я не понимаю :) .
Регистрируются клав. хуки локальный и глобальный. В локальном проверяется клавиша ESC ‒ закрытие окна, в глобальном правая WIN ‒ показать/скрыть окно.
При показе окно устанавливает верхний порядок в списке с помощью HWND_TOPMOST и получает фокус через SetForegroundWindow.
Работает программа одну минуту (если не будет закрыта). В остальном ничего особенного.

Код здесь
#include <iostream>
#include <windows.h>
using namespace std;

HWND hwndG;
HDC hdc;
HHOOK hhook = NULL, hhookG = NULL;
BYTE condit = 1;
HFONT hf;
WORD screenW, screenH;

// Function prototypes.
ATOM MyRegisterClass(HINSTANCE);
BOOL InitInstance(HINSTANCE);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK KeyboardProcG(int nCode, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
FreeConsole();
screenW = GetSystemMetrics(SM_CXSCREEN);
screenH = GetSystemMetrics(SM_CYSCREEN);

hhookG = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProcG, NULL, 0);
if (!hhookG) return 1;
hhook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, NULL, GetCurrentThreadId());
if (!hhook) return 1;

LOGFONT lf = {sizeof(lf)};
CHAR ch[] = "Arial";
lstrcpy(lf.lfFaceName, ch);
lf.lfHeight = 40;
lf.lfWeight = 1000;
hf = CreateFontIndirectA(&lf);
Sleep(100);

MyRegisterClass(hinstance);
if (!InitInstance(hinstance)) return 2;
MSG msg;

for(WORD i = 0; i < 6000; i++) {
	Sleep(10);
	PeekMessage(&msg, (HWND) NULL, 0, 0, PM_REMOVE);
	if (condit == 9) break;
}

UnhookWindowsHookEx(hhook);
UnhookWindowsHookEx(hhookG);
DeleteDC(hdc);
return 0;
}


ATOM MyRegisterClass(HINSTANCE hinstance) {
WNDCLASSEX wcx = {sizeof(WNDCLASSEX)};
wcx.cbSize = sizeof(wcx);
wcx.style = CS_HREDRAW | CS_VREDRAW; // redraw if size changes
wcx.lpfnWndProc = WndProc; // points to window procedure
wcx.hInstance = hinstance; // handle to instance
//wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcx.lpszClassName = "MainWClass";  // name of window class
return RegisterClassEx(&wcx);
}


BOOL InitInstance(HINSTANCE hinstance) {
HWND hwnd;
hwnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
	"MainWClass", "Sample", WS_POPUPWINDOW,
	screenW >>2, screenH >>2, screenW >>1, screenH >>1,
	(HWND) NULL, (HMENU) NULL, hinstance, (LPVOID) NULL);
if (!hwnd) return FALSE;

hwndG = hwnd;
ShowWindow(hwnd, 1);
hdc = GetDC(hwndG);
SelectObject(hdc, hf);
SetBkColor(hdc, RGB(0, 180, 0));
TextOut(hdc, 5, 5, "Press RWIN for hide/show window", 31);
TextOut(hdc, 5, 60, "Press ESC for exit", 18);
return TRUE;
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hWnd, message, wParam, lParam);
}


LRESULT CALLBACK KeyboardProcG(int nCode, WPARAM wParam, LPARAM lParam) {
KBDLLHOOKSTRUCT * kbs = (KBDLLHOOKSTRUCT *) lParam;
switch (kbs->vkCode) {
	case VK_RWIN:
		if (wParam != 257) return 1;
		condit = 1 - condit;
		if(condit == 1) {
			SetWindowPos(hwndG, HWND_TOPMOST, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
			SetForegroundWindow(hwndG);
			TextOut(hdc, 5, 5, "Press RWIN for hide/show window", 31);
			TextOut(hdc, 5, 60, "Press ESC for exit", 18);
		} else
			ShowWindow(hwndG, 0);
		return 1;
}
return CallNextHookEx(hhookG, nCode, wParam, lParam);
}


LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
switch (wParam) {
	case VK_ESCAPE:
		if (lParam > 3000000000) condit = 9;
		return 1; break;
}
return CallNextHookEx(hhook, nCode, wParam, lParam);
}

Описанные механизмы в примерах показал, основная концепция для достижения поставленных задач вроде как понятна. Если что забыл рассказать ‒ прошу в комментарии.

Комментарии (0)