-
Notifications
You must be signed in to change notification settings - Fork 16
Code Breakdown
cafali edited this page Aug 22, 2024
·
10 revisions
Breakdown for 1.1.9
-
Global Variables and Definitions
-
key1_code
,key2_code
,key3_code
, andkey4_code
are initialized to 'A', 'D', 'S', and 'W' respectively, but can be customized via the configuration file. -
keyStates1
andkeyStates2
areunordered_map
objects that manage the state (pressed or not) of keys for two distinct groups. -
activeKey1
,previousKey1
,activeKey2
, andpreviousKey2
track the currently active and previously pressed keys for each group. -
hHook
is used for the keyboard hook to intercept key events. -
nid
holds data for the system tray icon.
-
-
Main Function
- Loads key bindings from a configuration file using
LoadConfig()
. If loading fails, it handles the error. - Creates a named mutex to ensure only one instance of the application runs.
- Registers a window class and creates a window for the application.
- Initializes and adds a system tray icon using
InitNotifyIconData()
. - Sets a low-level keyboard hook with
SetWindowsHookEx()
and starts a message loop to handle Windows messages. - Cleans up resources on exit, including unhooking the keyboard hook and removing the system tray icon.
- Loads key bindings from a configuration file using
-
Keyboard Hook Procedure (KeyboardProc)
- Handles key down (
WM_KEYDOWN
) and key up (WM_KEYUP
) events. - Calls
handleKeyDown()
andhandleKeyUp()
to manage the state of keys and perform actions based on the current and previous key states.
- Handles key down (
-
System Tray Icon Handling (InitNotifyIconData and WndProc)
-
InitNotifyIconData()
initializes the system tray icon, including setting up the icon and tooltip text. -
WndProc()
processes messages for the window, including displaying a context menu when the tray icon is right-clicked. Options include exiting the application, viewing version info, and rebinding keys.
-
-
Configuration Management
-
LoadConfig()
reads configuration settings from a file and updateskey1_code
,key2_code
,key3_code
, andkey4_code
. If the file is missing, it callsCreateDefaultConfig()
to restore the configuration from a backup. -
CreateDefaultConfig()
restores the configuration from a backup file, handled byRestoreConfigFromBackup()
. -
RestoreConfigFromBackup()
copies a backup configuration file to the main configuration file's location and handles errors if the copy fails.
-
// SnapKey 1.1.9 src
#include <windows.h>
#include <shellapi.h>
#include <fstream>
#include <sstream>
#include <string>
#include <unordered_map>
using namespace std;
#define ID_TRAY_APP_ICON 1001
#define ID_TRAY_EXIT_CONTEXT_MENU_ITEM 3000
#define ID_TRAY_VERSION_INFO 3001
#define ID_TRAY_REBIND_KEYS 3002
#define WM_TRAYICON (WM_USER + 1)
struct KeyState
{
bool pressed = false;
};
// Global variables for key groups (A/D & S/W)
int key1_code = 'A';
int key2_code = 'D';
int key3_code = 'S';
int key4_code = 'W';
// Key state management for each group
unordered_map<int, KeyState> keyStates1;
unordered_map<int, KeyState> keyStates2;
int activeKey1 = 0;
int previousKey1 = 0;
int activeKey2 = 0;
int previousKey2 = 0;
HHOOK hHook = NULL;
NOTIFYICONDATA nid;
// Function declarations
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
void InitNotifyIconData(HWND hwnd);
bool LoadConfig(const std::string& filename);
void CreateDefaultConfig(const std::string& filename); // Declaration
void RestoreConfigFromBackup(const std::string& backupFilename, const std::string& destinationFilename); // Declaration
std::string GetVersionInfo(); // Declaration
int main()
{
// Load key bindings from config file
if (!LoadConfig("config.cfg")) {
// Handle failure to load config
}
// Create a named mutex
HANDLE hMutex = CreateMutex(NULL, TRUE, TEXT("SnapKeyMutex"));
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
MessageBox(NULL, TEXT("SnapKey is already running!"), TEXT("SnapKey"), MB_ICONINFORMATION | MB_OK);
return 1; // Exit the program
}
// Create a window class
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = TEXT("SnapKeyClass");
if (!RegisterClassEx(&wc)) {
MessageBox(NULL, TEXT("Window Registration Failed!"), TEXT("Error"), MB_ICONEXCLAMATION | MB_OK);
ReleaseMutex(hMutex);
CloseHandle(hMutex);
return 1;
}
// Create a window
HWND hwnd = CreateWindowEx(
0,
wc.lpszClassName,
TEXT("SnapKey"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
NULL, NULL, wc.hInstance, NULL);
if (hwnd == NULL) {
MessageBox(NULL, TEXT("Window Creation Failed!"), TEXT("Error"), MB_ICONEXCLAMATION | MB_OK);
ReleaseMutex(hMutex);
CloseHandle(hMutex);
return 1;
}
// Initialize and add the system tray icon
InitNotifyIconData(hwnd);
// Set the hook
hHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0);
if (hHook == NULL)
{
MessageBox(NULL, TEXT("Failed to install hook!"), TEXT("Error"), MB_ICONEXCLAMATION | MB_OK);
ReleaseMutex(hMutex); // Release the mutex before exiting
CloseHandle(hMutex); // Close the handle
return 1;
}
// Message loop
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Unhook the hook
UnhookWindowsHookEx(hHook);
// Remove the system tray icon
Shell_NotifyIcon(NIM_DELETE, &nid);
// Release and close the mutex
ReleaseMutex(hMutex);
CloseHandle(hMutex);
return 0;
}
void handleKeyDown(int keyCode)
{
// Handle key group 1 (key1_code and key2_code)
if (keyCode == key1_code || keyCode == key2_code)
{
auto& keyState = keyStates1[keyCode];
if (!keyState.pressed)
{
keyState.pressed = true;
if (activeKey1 == 0 || activeKey1 == keyCode)
{
activeKey1 = keyCode;
}
else
{
previousKey1 = activeKey1;
activeKey1 = keyCode;
INPUT input = {0};
input.type = INPUT_KEYBOARD;
input.ki.wVk = previousKey1;
input.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, &input, sizeof(INPUT));
}
}
}
// Handle key group 2 (key3_code and key4_code)
else if (keyCode == key3_code || keyCode == key4_code)
{
auto& keyState = keyStates2[keyCode];
if (!keyState.pressed)
{
keyState.pressed = true;
if (activeKey2 == 0 || activeKey2 == keyCode)
{
activeKey2 = keyCode;
}
else
{
previousKey2 = activeKey2;
activeKey2 = keyCode;
INPUT input = {0};
input.type = INPUT_KEYBOARD;
input.ki.wVk = previousKey2;
input.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, &input, sizeof(INPUT));
}
}
}
}
void handleKeyUp(int keyCode)
{
// Handle key group 1 (key1_code and key2_code)
if (keyCode == key1_code || keyCode == key2_code)
{
auto& keyState = keyStates1[keyCode];
if (previousKey1 == keyCode && !keyState.pressed)
{
previousKey1 = 0;
}
if (keyState.pressed)
{
keyState.pressed = false;
if (activeKey1 == keyCode && previousKey1 != 0)
{
activeKey1 = previousKey1;
previousKey1 = 0;
INPUT input = {0};
input.type = INPUT_KEYBOARD;
input.ki.wVk = activeKey1;
SendInput(1, &input, sizeof(INPUT));
}
}
}
// Handle key group 2 (key3_code and key4_code)
else if (keyCode == key3_code || keyCode == key4_code)
{
auto& keyState = keyStates2[keyCode];
if (previousKey2 == keyCode && !keyState.pressed)
{
previousKey2 = 0;
}
if (keyState.pressed)
{
keyState.pressed = false;
if (activeKey2 == keyCode && previousKey2 != 0)
{
activeKey2 = previousKey2;
previousKey2 = 0;
INPUT input = {0};
input.type = INPUT_KEYBOARD;
input.ki.wVk = activeKey2;
SendInput(1, &input, sizeof(INPUT));
}
}
}
}
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION)
{
KBDLLHOOKSTRUCT *pKeyBoard = (KBDLLHOOKSTRUCT *)lParam;
switch (wParam)
{
case WM_KEYDOWN:
handleKeyDown(pKeyBoard->vkCode);
break;
case WM_KEYUP:
handleKeyUp(pKeyBoard->vkCode);
break;
default:
break;
}
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
void InitNotifyIconData(HWND hwnd)
{
memset(&nid, 0, sizeof(NOTIFYICONDATA));
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = hwnd;
nid.uID = ID_TRAY_APP_ICON;
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
nid.uCallbackMessage = WM_TRAYICON;
// Load the tray icon from the current directory
HICON hIcon = (HICON)LoadImage(NULL, TEXT("icon.ico"), IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
if (hIcon)
{
nid.hIcon = hIcon;
}
else
{
// If loading the icon fails, fallback to a default icon
nid.hIcon = LoadIcon(NULL, IDI_APPLICATION);
}
lstrcpy(nid.szTip, TEXT("SnapKey"));
Shell_NotifyIcon(NIM_ADD, &nid);
}
std::string GetVersionInfo()
{
std::ifstream versionFile("meta/version");
if (!versionFile.is_open()) {
return "Version info not available";
}
std::string version;
std::getline(versionFile, version);
return version.empty() ? "Version info not available" : version;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_TRAYICON:
if (lParam == WM_RBUTTONDOWN)
{
POINT curPoint;
GetCursorPos(&curPoint);
SetForegroundWindow(hwnd);
// Create a context menu
HMENU hMenu = CreatePopupMenu();
AppendMenu(hMenu, MF_STRING, ID_TRAY_REBIND_KEYS, TEXT("Rebind Keys"));
AppendMenu(hMenu, MF_STRING, ID_TRAY_VERSION_INFO, TEXT("Version Info"));
AppendMenu(hMenu, MF_STRING, ID_TRAY_EXIT_CONTEXT_MENU_ITEM, TEXT("Exit SnapKey"));
// Display the context menu
TrackPopupMenu(hMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, curPoint.x, curPoint.y, 0, hwnd, NULL);
DestroyMenu(hMenu);
}
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_TRAY_EXIT_CONTEXT_MENU_ITEM:
PostQuitMessage(0);
break;
case ID_TRAY_VERSION_INFO:
{
std::string versionInfo = GetVersionInfo();
MessageBox(hwnd, versionInfo.c_str(), TEXT("Version Info"), MB_OK);
}
break;
case ID_TRAY_REBIND_KEYS:
{
// Open the config file with the default editor
ShellExecute(NULL, TEXT("open"), TEXT("config.cfg"), NULL, NULL, SW_SHOWNORMAL);
}
break;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
// Function to copy the backup file from the meta folder to the main directory
void RestoreConfigFromBackup(const std::string& backupFilename, const std::string& destinationFilename)
{
std::string sourcePath = "meta\\" + backupFilename;
std::string destinationPath = destinationFilename;
if (CopyFile(sourcePath.c_str(), destinationPath.c_str(), FALSE)) {
// Copy successful
MessageBox(NULL, TEXT(" Default config restored from backup successfully."), TEXT("SnapKey"), MB_ICONINFORMATION | MB_OK);
} else {
// backup.snapkey copy failed
DWORD error = GetLastError();
std::string errorMsg = " Failed to restore config from backup.";
MessageBox(NULL, errorMsg.c_str(), TEXT("SnapKey Error"), MB_ICONERROR | MB_OK);
}
}
// Restore config.cfg from backup.snapkey
void CreateDefaultConfig(const std::string& filename)
{
std::string backupFilename = "backup.snapkey";
RestoreConfigFromBackup(backupFilename, filename);
}
// Check for config.cfg
bool LoadConfig(const std::string& filename)
{
std::ifstream configFile(filename);
if (!configFile.is_open()) {
CreateDefaultConfig(filename); // Restore config from backup if file doesn't exist
return false;
}
std::string line;
while (std::getline(configFile, line)) {
std::istringstream iss(line);
std::string key;
int value;
if (std::getline(iss, key, '=') && (iss >> value)) {
if (key == "key1") {
key1_code = value;
} else if (key == "key2") {
key2_code = value;
} else if (key == "key3") {
key3_code = value;
} else if (key == "key4") {
key4_code = value;
}
}
}
return true;
}