#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shellapi.h>
#include <cstdio>
#include <iostream>
//static DWORD g_mainThreadId = 0;
static HHOOK g_hook = nullptr;
static NOTIFYICONDATA g_notifyIconData = {};
static HWND g_hWnd = nullptr;
// 双击判定时间窗
static const DWORD kDoubleTapWindowMs = 1000;
static DWORD g_lastRightTick = 0;
static DWORD g_lastLeftTick = 0;
static DWORD g_lastUpTick = 0;
static DWORD g_lastDownTick = 0;
// 退出标志
bool g_exitRequested = false;
// 退出热键判断
static bool IsDown(int vk) { return (GetAsyncKeyState(vk) & 0x8000) != 0; }
//static bool WantExitHotkey() { return IsDown(VK_CONTROL) && IsDown(VK_MENU) && IsDown('Q'); }
static void Dbg(const char* s) {
OutputDebugStringA(s);
std::printf("%s", s);
std::fflush(stdout);
}
static void SendEndRespectPhysicalShift(bool wantShiftSelection) {
bool physicalShiftDown = (GetAsyncKeyState(VK_LSHIFT) & 0x8000) || (GetAsyncKeyState(VK_RSHIFT) & 0x8000);
// 我们是否需要“自己按下Shift”
bool needInjectShift = wantShiftSelection && !physicalShiftDown;
INPUT in[4] = {};
int n = 0;
if (needInjectShift) {
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_SHIFT;
in[n].ki.dwFlags = 0;
n++;
}
// END(注意:END 在 SendInput 里最好用 EXTENDEDKEY)
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_END;
in[n].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
n++;
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_END;
in[n].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
n++;
if (needInjectShift) {
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_SHIFT;
in[n].ki.dwFlags = KEYEVENTF_KEYUP;
n++;
}
SendInput(n, in, sizeof(INPUT));
}
static void SendHomeRespectPhysicalShift(bool wantShiftSelection) {
bool physicalShiftDown = (GetAsyncKeyState(VK_LSHIFT) & 0x8000) || (GetAsyncKeyState(VK_RSHIFT) & 0x8000);
bool needInjectShift = wantShiftSelection && !physicalShiftDown;
INPUT in[4] = {};
int n = 0;
if (needInjectShift) {
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_SHIFT;
in[n].ki.dwFlags = 0;
n++;
}
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_HOME;
in[n].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
n++;
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_HOME;
in[n].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
n++;
if (needInjectShift) {
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_SHIFT;
in[n].ki.dwFlags = KEYEVENTF_KEYUP;
n++;
}
SendInput(n, in, sizeof(INPUT));
}
static void SendCtrlEndRespectPhysicalShift(bool wantShiftSelection) {
bool physicalShiftDown =
(GetAsyncKeyState(VK_LSHIFT) & 0x8000) ||
(GetAsyncKeyState(VK_RSHIFT) & 0x8000);
bool physicalCtrlDown =
(GetAsyncKeyState(VK_LCONTROL) & 0x8000) ||
(GetAsyncKeyState(VK_RCONTROL) & 0x8000);
bool needInjectShift = wantShiftSelection && !physicalShiftDown;
bool needInjectCtrl = !physicalCtrlDown;
// 最多:Ctrl down, Shift down, End down/up, Ctrl up, Shift up
INPUT in[6] = {};
int n = 0;
if (needInjectCtrl) {
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_CONTROL;
in[n].ki.dwFlags = 0;
n++;
}
if (needInjectShift) {
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_SHIFT;
in[n].ki.dwFlags = 0;
n++;
}
// End
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_END;
in[n].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
n++;
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_END;
in[n].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
n++;
if (needInjectShift) {
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_SHIFT;
in[n].ki.dwFlags = KEYEVENTF_KEYUP;
n++;
}
if (needInjectCtrl) {
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_CONTROL;
in[n].ki.dwFlags = KEYEVENTF_KEYUP;
n++;
}
SendInput(n, in, sizeof(INPUT));
}
static void SendCtrlHomeRespectPhysicalShift(bool wantShiftSelection) {
bool physicalShiftDown =
(GetAsyncKeyState(VK_LSHIFT) & 0x8000) ||
(GetAsyncKeyState(VK_RSHIFT) & 0x8000);
bool physicalCtrlDown =
(GetAsyncKeyState(VK_LCONTROL) & 0x8000) ||
(GetAsyncKeyState(VK_RCONTROL) & 0x8000);
bool needInjectShift = wantShiftSelection && !physicalShiftDown;
bool needInjectCtrl = !physicalCtrlDown;
INPUT in[6] = {};
int n = 0;
if (needInjectCtrl) {
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_CONTROL;
in[n].ki.dwFlags = 0;
n++;
}
if (needInjectShift) {
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_SHIFT;
in[n].ki.dwFlags = 0;
n++;
}
// Home
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_HOME;
in[n].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
n++;
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_HOME;
in[n].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
n++;
if (needInjectShift) {
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_SHIFT;
in[n].ki.dwFlags = KEYEVENTF_KEYUP;
n++;
}
if (needInjectCtrl) {
in[n].type = INPUT_KEYBOARD;
in[n].ki.wVk = VK_CONTROL;
in[n].ki.dwFlags = KEYEVENTF_KEYUP;
n++;
}
SendInput(n, in, sizeof(INPUT));
}
// 低级键盘钩子回调函数
static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode == HC_ACTION) {
const KBDLLHOOKSTRUCT* k = reinterpret_cast<const KBDLLHOOKSTRUCT*>(lParam);
if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)) {
// 忽略我们自己注入的键,避免刷屏/递归
if (k->flags & LLKHF_INJECTED) {
return CallNextHookEx(g_hook, nCode, wParam, lParam);
}
// ✅ 打印:确认钩子确实收到了键
/* char buf[256];
std::snprintf(buf, sizeof(buf),
"[key] vk=%lu ctrl=%d alt=%d shift=%d\n",
(unsigned long)k->vkCode,
IsDown(VK_CONTROL) ? 1 : 0,
IsDown(VK_MENU) ? 1 : 0,
IsDown(VK_SHIFT) ? 1 : 0);
Dbg(buf);*/
// ✅ 退出热键:Ctrl+Alt+Q
if (IsDown(VK_CONTROL) && IsDown(VK_MENU) && (k->vkCode == 'Q')) {
Dbg("[exit] hotkey hit: Ctrl+Alt+Q\n");
// ✅ 最稳:让“当前线程”的消息循环退出
PostQuitMessage(0);
// (可选)如果你想用 PostThreadMessage,也可以这样写并检查返回值:
// BOOL ok = PostThreadMessageW(g_mainThreadId, WM_QUIT, 0, 0);
// if (!ok) {
// DWORD e = GetLastError();
// char b2[128];
// std::snprintf(b2, sizeof(b2), "[exit] PostThreadMessage failed err=%lu\n", (unsigned long)e);
// Dbg(b2);
// }
return 1; // 吃掉热键
}
// 下面是你原来的 Right 双击逻辑(略:保持不变,只要放这里就行)
if (k->vkCode == VK_RIGHT) {
DWORD now = GetTickCount();
DWORD delta = now - g_lastRightTick;
if (g_lastRightTick != 0 && delta <= kDoubleTapWindowMs) {
bool withShift = (GetAsyncKeyState(VK_LSHIFT) & 0x8000) || (GetAsyncKeyState(VK_RSHIFT) & 0x8000);
SendEndRespectPhysicalShift(withShift);
g_lastRightTick = 0;
return 1;
}
else {
g_lastRightTick = now;
}
}
if (k->vkCode == VK_LEFT) {
DWORD now = GetTickCount();
DWORD delta = now - g_lastLeftTick;
if (g_lastLeftTick != 0 && delta <= kDoubleTapWindowMs) {
bool withShift = (GetAsyncKeyState(VK_LSHIFT) & 0x8000) || (GetAsyncKeyState(VK_RSHIFT) & 0x8000);
SendHomeRespectPhysicalShift(withShift);
g_lastLeftTick = 0;
return 1; // 吃掉第二下 Left
}
else {
g_lastLeftTick = now;
}
}
// ↑↑ 双击:到文件最开头(Ctrl+Home),Shift 选中
if (k->vkCode == VK_UP) {
DWORD now = GetTickCount();
DWORD delta = now - g_lastUpTick;
if (g_lastUpTick != 0 && delta <= kDoubleTapWindowMs) {
bool withShift =
(GetAsyncKeyState(VK_LSHIFT) & 0x8000) ||
(GetAsyncKeyState(VK_RSHIFT) & 0x8000);
SendCtrlHomeRespectPhysicalShift(withShift);
g_lastUpTick = 0;
return 1; // 吃掉第二下 Up
}
else {
g_lastUpTick = now;
}
}
// ↓↓ 双击:到文件最末尾(Ctrl+End),Shift 选中
if (k->vkCode == VK_DOWN) {
DWORD now = GetTickCount();
DWORD delta = now - g_lastDownTick;
if (g_lastDownTick != 0 && delta <= kDoubleTapWindowMs) {
bool withShift =
(GetAsyncKeyState(VK_LSHIFT) & 0x8000) ||
(GetAsyncKeyState(VK_RSHIFT) & 0x8000);
SendCtrlEndRespectPhysicalShift(withShift);
g_lastDownTick = 0;
return 1; // 吃掉第二下 Down
}
else {
g_lastDownTick = now;
}
}
}
}
return CallNextHookEx(g_hook, nCode, wParam, lParam);
}
// 托盘图标右键菜单处理
void ShowTrayMenu(HWND hwnd) {
HMENU hMenu = CreatePopupMenu();
AppendMenu(hMenu, MF_STRING, 1, L"退出");
POINT pt;
GetCursorPos(&pt);
SetForegroundWindow(hwnd);
TrackPopupMenu(hMenu, TPM_LEFTBUTTON, pt.x, pt.y, 0, hwnd, NULL);
DestroyMenu(hMenu);
}
// 处理消息循环的部分
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_SYSCOMMAND:
if (wParam == SC_CLOSE) {
// 点击关闭按钮时也退出
g_exitRequested = true;
return 0;
}
break;
case WM_COMMAND:
if (LOWORD(wParam) == 1) {
g_exitRequested = true;
return 0;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_APP + 1:
if (lParam == WM_RBUTTONDOWN) {
ShowTrayMenu(hwnd);
}
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
// 创建托盘图标
void AddTrayIcon(HWND hwnd) {
g_notifyIconData.cbSize = sizeof(g_notifyIconData);
g_notifyIconData.uID = 1;
g_notifyIconData.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
g_notifyIconData.hIcon = LoadIcon(NULL, IDI_APPLICATION);
g_notifyIconData.hWnd = hwnd;
g_notifyIconData.uCallbackMessage = WM_APP + 1;
lstrcpy(g_notifyIconData.szTip, TEXT("右键退出"));
Shell_NotifyIcon(NIM_ADD, &g_notifyIconData);
}
void RemoveTrayIcon() {
Shell_NotifyIcon(NIM_DELETE, &g_notifyIconData);
}
int main() {
std::cout << "ctrl + alt + q 退出" << std::endl;
//g_mainThreadId = GetCurrentThreadId();
// 注册窗口类
const char* className = "TrayAppClass";
// 获取宽字符字符串的长度
int len = MultiByteToWideChar(CP_ACP, 0, className, -1, NULL, 0);
// 创建宽字符数组
wchar_t* wideClassName = new wchar_t[len];
// 将 char 字符串转换为宽字符字符串
MultiByteToWideChar(CP_ACP, 0, className, -1, wideClassName, len);
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WindowProc;
wc.lpszClassName = wideClassName;
wc.hInstance = GetModuleHandle(NULL);
RegisterClass(&wc);
g_hWnd = CreateWindowExW(
0,
wideClassName,
wideClassName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
wc.hInstance,
NULL
);
delete[] wideClassName;
if (!g_hWnd) {
MessageBox(NULL, TEXT("Failed to create window"), TEXT("Error"), MB_OK);
return 1;
}
AddTrayIcon(g_hWnd);
// 安装低级钩子
g_hook = SetWindowsHookExW(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandleW(NULL), 0);
if (!g_hook) {
MessageBox(NULL, TEXT("Failed to install keyboard hook"), TEXT("Error"), MB_OK);
return 1;
}
MSG msg;
while (!g_exitRequested && GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 清理
UnhookWindowsHookEx(g_hook);
RemoveTrayIcon();
return 0;
}