键盘重映射禁用 CtrlAltDel 键的利弊

目录

前言

一、Scancode Map 的规范

二、禁用 CtrlAltDel 的方法及其缺陷

三、编程实现和测试

3.1 C++ 实现的简易修改工具

3.2 C# 实现的窗口工具

四、总结


本文属于原创文章,转载请注明出处:

https://blog.csdn.net/qq_59075481/article/details/136104444。

前言

在 Ndr-LRPC Hook 和 WMsg Hook 等方法完善前,网络上公开的禁用 CtrlAltDel 键的方法是使用 "Scancode Map" 键盘扫描码映射表这个方法,本质上是利用微软提供的注册表设置来达到屏蔽的效果。这确实在前一阶段是较好的解决方案,所以在更新完前两种方案后,我不打算对这个方法避而不谈,相反,我觉得该方法可以用于更广泛的方面,甚至许多键盘快捷键修改程序就利用了类似的方法。本文将就具体的实现细节给出通用修改工具以及谈谈这种方法存在优缺点。工具有两个,一个是我重写的 C++ 简化实现,另一个是基于 C# 的开源工具(查阅资料时偶然发现)。

一、Scancode Map 的规范

Scancode Map 注册表项位于注册表如下路径:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout

它是 Keyboard Layout 子键下名为"Scancode Map"的二进制值项,如果没有你可以新建一个。

这个值项可实现对键盘按键的映射。这里映射的意思可理解为“替换”,可将任意一键替换成其它键,或者禁用。

Scancode Map 注册表项具有类似下面的格式:

以 16 进制表示,可分为五个部分,为了方便讲解,相邻字节之间用逗号隔开。

"Scancode Map" = 00,00,00,00,00,00,00,00,02,00,00,00,01,00, 02,00,00,00,00,00

前 8 个字节: 表示版本信息号,一般为 0。
紧接着的 4 个字节: 表示映射键的总数 + 1。按照二进制的读写规则,低位在左,高位在右。02 00 00 00 这个数实际就是:00 00 00 02 ,该用例表示当前修改 1 个键。
紧接着的 2 个字节: 表示替换后按键的“扫描码”。如:ESC 键的扫描码是 01 ,所以就表示 01 00 。再比如左 Ctrl 键扫描码是 1D 00, 而右 Ctrl 键是 1D E0 。
紧接着的 2 个字节:表示原按键的“扫描码”,格式同上。
最后以四个 00 结束,(8个也行)。

键盘的按键扫描码可以参考下面这幅图(已经按照书写格式排列):

按键名称-扫描码对照表

图片来源:https://www.bilibili.com/read/cv8001022/

二、禁用 CtrlAltDel 的方法及其缺陷

根据上面提供的信息,我们了解到需要禁用 Ctrl + Alt + Del 键,只禁用 Del 就可以了,不需要修改左键,原因是 Ctrl / Alt 需要经常用,而 Del 用的频率较少。

经查表,Del 键对应的扫描码是 53 E0,所以按照规则,我们只需要将 53 E0 映射为其他按键或者映射为空即可,比如下图:
 

注册表按键映射表

这张图是注册表中修改过后的结果,圈出来的就是需要关注的的位置, 02 表示总数加一,连同后面 3 个 00,一共四个字节;后面两字节是替换的扫描码,我这里替换成数字 1(4F 00)。原键的扫描码是 53 E0。

这个方法优点就是实现简单。 

当然这个方法缺陷很明显,不可避免的按键要被无差别禁用,Del 键却还有其他功能,所以会影响使用体验。

三、编程实现和测试

那么如何通过代码来实现修改呢?很简单,就是读取注册表和修改注册表,只不过需要按照字节序构建数据。

3.1 C++ 实现的简易修改工具

为了方便制作一个简易的测试工具,我为工具定义了 config.cfg 配置文件的规则,样例如下:

# 此处填写要修改的总数,当然程序写入注册表时,按照规则注册表的记录总数比用户实际修改的个数多加1,这在之前也介绍过。
[ReMapKeyNum]
3
# 修改的键的顺序
[MapKeyRank]
1
# 修改的键的原始扫描码,格式:十六进制,两个字符。
[MapKeyOriCode]
1d00
# 修改的键的新扫描码,格式:十六进制,两个字符。
[MapKeyNewCode]
1de0
[MapKeyRank]
2
[MapKeyOriCode]
3800
[MapKeyNewCode]
38e0
[MapKeyRank]
3
[MapKeyOriCode]
53e0
[MapKeyNewCode]
0000

配置文件放在任意位置均可,目前已经实现功能的参数列表如下:

  • /enumerate :解析注册表中重映射按键的数据,该方法会逆编码原始按键名称(不过目前未实现区分左右按键)
  • /setremap <configFilePath>:指定一个路径作为配置文件路径,配置文件按照规范书写,程序将解析配置文件并用该配置文件覆盖注册表按键映射表
  • /queryActiveKey:动态查询键盘输入的按键的扫描码(不区分左右按键,这点需要改进)
  • /deleteAllMap:删除整个注册表按键映射表,恢复系统默认配置(需要二次确认操作)。注意:该操作无法撤销删除
  • /modifyDelKey:删除注册表按键映射表中指定的条目。该命令首先解析所有条目信息,随后通过输入要删除的 Item 编号即可实现删除
  • /modifyInsertKey:在指定的 Item 编号之后添加新的数据条目。需要手动输入三个参数,第一个为原始按键扫描码,第二个为修改后的按键扫描码,第三个为 Item 编号
  • /modifyAddKey:不需要指定位置编号,在结尾追加新的条目。
  • /statusMap <bEnable>:启用或禁用重映射键注册表项(该操作通过将映射表标记为 DISABLED 来禁用整张表,所以操作可以恢复),status 参数为 0 表示禁用,为 1 表示启用
  • /makebackup:备份注册表当前重映射表数据。该命令拷贝一个按照时间记录的注册表副本。
  • /recoverMap <backupTime>:恢复按照时间标签备份的副本,该操作将覆盖当前的注册表按键映射表。
  • /readbackup:读取所有注册表备份(按照时间标签列表显示)

完整代码如下(注意所有的修改除了备份数据其他操作对于大部分应用需要重启计算机生效):

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <Windows.h>
#include <iomanip>// 定义宏
#define REGISTRY_KEY L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layout"
#define REGISTRY_VALUE_NAME L"Scancode Map"
#define CONFIG_FILE_SECTION_REMAP_KEY_NUM "[ReMapKeyNum]"
#define CONFIG_FILE_SECTION_MAP_KEY_RANK "[MapKeyRank]"
#define CONFIG_FILE_SECTION_MAP_KEY_ORI_CODE "[MapKeyOriCode]"
#define CONFIG_FILE_SECTION_MAP_KEY_NEW_CODE "[MapKeyNewCode]"
#define BACKUP_KEY_PREFIX L"Scancode_Map_Back_"
#define DESABLED_KEY_PREFIX L"_DISABLED"// 全局变量用于记录按键状态
int remainingKeys = 0;
int remainingRounds = 0;
int numKeys = 0;
std::vector<std::pair<std::string, DWORD>> keyNameValuePairs;// 解析注册表中的重映射信息
bool ParseRemapInfo(std::vector<std::pair<WORD, WORD>>& remapInfo) {HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_READ, &hKey) == ERROR_SUCCESS) {DWORD bufferSize = 0;DWORD dataType;if (RegQueryValueEx(hKey, REGISTRY_VALUE_NAME, NULL, &dataType, NULL, &bufferSize) == ERROR_SUCCESS && dataType == REG_BINARY) {std::vector<BYTE> buffer(bufferSize);if (RegQueryValueEx(hKey, REGISTRY_VALUE_NAME, NULL, NULL, buffer.data(), &bufferSize) == ERROR_SUCCESS) {DWORD totalRemappedKeys = *(DWORD*)(buffer.data() + 8) - 1;for (DWORD i = 0; i < totalRemappedKeys; ++i) {WORD remappedScanCode = *(WORD*)(buffer.data() + 12 + i * 4);WORD originalScanCode = *(WORD*)(buffer.data() + 14 + i * 4);remapInfo.emplace_back(originalScanCode, remappedScanCode);}}RegCloseKey(hKey);return true;}else {RegCloseKey(hKey);return false;}}return false;
}// 枚举当前重映射的按键并逆向转换扫描码为键名
void EnumerateRemappedKeys(std::vector<std::pair<WORD, WORD>> remapInfo) {std::cout << "Remapped keys:\n";CHAR remappedKeyName[30] = { 0 }, originalKeyName[30] = { 0 };WORD remappedScanCode = 0, originalScanCode = 0;int newKeyret = 0, oldKeyret = 0;for (size_t i = 0; i < remapInfo.size(); ++i) {newKeyret = 0, oldKeyret = 0;memset(remappedKeyName, 0, sizeof(remappedKeyName));memset(originalKeyName, 0, sizeof(originalKeyName));originalScanCode = remapInfo[i].first;remappedScanCode = remapInfo[i].second;newKeyret = GetKeyNameTextA(remappedScanCode << 16,remappedKeyName, sizeof(remappedKeyName) / sizeof(*remappedKeyName));oldKeyret = GetKeyNameTextA(originalScanCode << 16,originalKeyName, sizeof(originalKeyName) / sizeof(*originalKeyName));// 逆向转换扫描码为键名std::cout << "Item " << i + 1 << ":\n";std::cout << "  Remapped Scan Code: 0x" << std::hex << remappedScanCode << "\n";std::cout << "  Remapped Key Name: " << (newKeyret > 0  ? remappedKeyName : "(null)") << "\n";std::cout << "  Original Scan Code: 0x" << std::hex << originalScanCode << "\n";std::cout << "  Original Key Name: " << (oldKeyret > 0 ? originalKeyName : "(null)") << "\n";}
}// 根据配置文件修改或添加重映射的按键
void ModifyRemappedKeys(const std::string& configFile) {std::ifstream file(configFile);if (!file.is_open()) {std::cerr << "Failed to open config file: " << configFile << std::endl;return;}std::string line;int totalRemappedKeys = 0;WORD tempCode = 0, theCode = 0;std::vector<WORD> originalScanCodes, newScanCodes;while (std::getline(file, line)) {if (line.empty() || line[0] == '#') // 绕过空行和注释continue;if (line.find(CONFIG_FILE_SECTION_REMAP_KEY_NUM) != std::string::npos) {// 循环绕过多行注释或空行do{std::getline(file, line); // 读取包含总重映射按键数的下一行}while (line.empty() || line[0] == '#');std::istringstream iss(line);iss >> totalRemappedKeys;originalScanCodes.reserve(totalRemappedKeys);newScanCodes.reserve(totalRemappedKeys);}else if (line.find(CONFIG_FILE_SECTION_MAP_KEY_ORI_CODE) != std::string::npos) {do {std::getline(file, line); // 读取包含原始扫描码的行} while (line.empty() || line[0] == '#');std::istringstream iss(line);std::string hexCode;iss >> hexCode;tempCode = static_cast<WORD>(std::stoi(hexCode, nullptr, 16));// stoi 读取时默认小端序,会导致数据颠倒,需要恢复一下theCode = ((tempCode & 0xFF) << 8) | ((tempCode >> 8) & 0xFF);originalScanCodes.push_back(theCode);}else if (line.find(CONFIG_FILE_SECTION_MAP_KEY_NEW_CODE) != std::string::npos) {do {std::getline(file, line); // 读取包含新扫描码的行} while (line.empty() || line[0] == '#');std::istringstream iss(line);std::string hexCode;iss >> hexCode;tempCode = static_cast<WORD>(std::stoi(hexCode, nullptr, 16));// stoi 读取时默认小端序,会导致数据颠倒,需要恢复一下theCode = ((tempCode & 0xFF) << 8) | ((tempCode >> 8) & 0xFF);newScanCodes.push_back(theCode);}}if (originalScanCodes.size() != totalRemappedKeys || newScanCodes.size() != totalRemappedKeys) {std::cerr << "Invalid configuration file format." << std::endl;return;}// 创建基于配置数据的缓冲区std::vector<BYTE> buffer(20 + totalRemappedKeys * 4, 0); // 头部(20 字节)+ 重映射按键数据*(DWORD*)(buffer.data() + 8) = totalRemappedKeys + 1; // 记录个数 = 实际个数 + 1(规则)for (int i = 0; i < totalRemappedKeys; ++i) {std::cout << "OriKeyCode: 0x" << std::hex << originalScanCodes[i] <<"\t NewKeyCode: 0x" << std::hex << newScanCodes[i] << "\n";// 写入数据到向量结构中*(WORD*)(buffer.data() + 12 + i * 4) = newScanCodes[i];*(WORD*)(buffer.data() + 14 + i * 4) = originalScanCodes[i];}// 修改注册表中的重映射按键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) {if (RegSetValueEx(hKey, REGISTRY_VALUE_NAME, 0, REG_BINARY, buffer.data(), buffer.size()) == ERROR_SUCCESS) {std::cout << "Scancode Map updated successfully.\n";}else {std::cerr << "Failed to update Scancode Map.\n";}RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key.\n";}
}// 钩子过程函数,处理键盘输入消息
LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) {if (nCode == HC_ACTION) {KBDLLHOOKSTRUCT* kbStruct = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {CHAR keyName[256];if (GetKeyNameTextA(kbStruct->scanCode << 16, keyName, sizeof(keyName)) > 0) {keyNameValuePairs.emplace_back(keyName, kbStruct->scanCode);if (--remainingKeys == 0) {std::cout << "Round completed.\n";if (--remainingRounds == 0) {std::cout << "Maximum rounds reached.\n";std::cout << "Continuous Input:\n";for (const auto& pair : keyNameValuePairs) {std::cout << pair.first << ": 0x" << std::hex << pair.second << "\n";}PostQuitMessage(0); // 退出消息循环}else {std::cout << "Continuous Input so far:\n";for (const auto& pair : keyNameValuePairs) {std::cout << pair.first << ": 0x" << std::hex << pair.second << "\n";}keyNameValuePairs.clear();remainingKeys = numKeys;std::cout << "\nPlease press " << numKeys << " keys per round.\n";}}}}}return CallNextHookEx(NULL, nCode, wParam, lParam);
}// 监视键盘输入以查询给定键的扫描码
void QueryScanCodeForKey() {// 键入循环参数do {std::cout << "Please press numbers of keys per round, " <<"and a maximum of rounds (for at least 1 key for 1 round)." << std::endl;std::cin >> numKeys >> remainingRounds;} while (numKeys < 1 || remainingRounds < 1);std::cout << "Monitoring " << numKeys << " keys per round, for a maximum of " << remainingRounds << " rounds.\n";remainingKeys = numKeys;// 安装键盘输入的钩子HHOOK keyboardHook = SetWindowsHookExW(WH_KEYBOARD_LL, KeyboardHookProc, NULL, 0);// 消息循环以保持钩子活动MSG msg;while (GetMessage(&msg, NULL, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}// 卸载钩子UnhookWindowsHookEx(keyboardHook);
}// 从注册表中删除所有重映射按键
void DeleteAllRemappedKeys() {// 二次确认std::cout << "Are you sure you want to delete all remapped keys? This action cannot be undone. (yes/no)\n";std::string confirmation;std::cin >> confirmation;if (confirmation != "yes") {std::cout << "Action cancelled.\n";return;}// 删除注册表中的重映射按键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) {LONG result = RegDeleteValue(hKey, REGISTRY_VALUE_NAME);if (result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND) {std::cout << "All remapped keys deleted successfully.\n";}else {std::cerr << "Failed to delete remapped keys.\n";}RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key.\n";}
}void BackupScancodeMap() {// 打开注册表键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_WRITE | KEY_READ, &hKey) == ERROR_SUCCESS) {// 获取当前 Scancode Map 值的大小DWORD dataSize = 0;DWORD dataType;if (RegQueryValueEx(hKey, REGISTRY_VALUE_NAME, NULL, &dataType, NULL, &dataSize) == ERROR_SUCCESS && dataType == REG_BINARY) {// 获取当前时间作为备份文件名的一部分SYSTEMTIME sysTime;GetLocalTime(&sysTime);std::wstringstream backupKeyNameStream;backupKeyNameStream << BACKUP_KEY_PREFIX << std::setw(4) << std::setfill(L'0') << sysTime.wYear << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wMonth << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wDay << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wHour << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wMinute << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wSecond;std::wstring backupKeyName = backupKeyNameStream.str();std::vector<BYTE> buffer(dataSize);if (RegQueryValueEx(hKey, REGISTRY_VALUE_NAME, NULL, NULL, buffer.data(), &dataSize) == ERROR_SUCCESS) {// 创建新的值项来存储备份数据LONG result = RegSetValueEx(hKey, backupKeyName.c_str(), 0, REG_BINARY, buffer.data(), dataSize);if (result == ERROR_SUCCESS) {std::wcout << L"Backup of Scancode Map value successful. Backup key name: " << backupKeyName << std::endl;// 询问用户是否删除原始键值项std::wcout << L"Do you want to delete the original registry key value? (Y/N): ";wchar_t response;std::wcin >> response;if (response == L'Y' || response == L'y') {if (RegDeleteValue(hKey, REGISTRY_VALUE_NAME) == ERROR_SUCCESS) {std::wcout << L"Original registry key value deleted successfully." << std::endl;}else {std::wcerr << L"Failed to delete original registry key value." << std::endl;}}else {std::wcout << L"Original registry key value not deleted." << std::endl;}}else {std::cerr << "Failed to write Scancode Map value to backup key: " << result << std::endl;}}else {std::cerr << "Failed to read registry value." << std::endl;}}else {std::cerr << "No Scancode Map value found in the registry." << std::endl;}// 关闭键RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key." << std::endl;}
}// 枚举并读取所有备份的 Scancode Map 值的键名称
void EnumerateAndPrintBackupKeys() {// 打开注册表键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) {// 枚举所有值项的名称DWORD index = 0;WCHAR subKeyName[MAX_PATH];DWORD subKeyNameSize = MAX_PATH;while (RegEnumValue(hKey, index, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) {// 检查值项的名称的前缀,如果是备份值项则输出时间信息if (wcsncmp(subKeyName, BACKUP_KEY_PREFIX, wcslen(BACKUP_KEY_PREFIX)) == 0) {std::wcout << L"Backup time: " << subKeyName << std::endl;}// 重置键名缓冲区大小subKeyNameSize = MAX_PATH;// 移动到下一个值项index++;}RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key." << std::endl;}
}// 还原指定时间的 Scancode Map 值备份
void RestoreBackupScancodeMap(const std::wstring& backupTime) {// 打开注册表键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_SET_VALUE | KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) {// 构造备份值项的名称std::wstring backupKeyName = BACKUP_KEY_PREFIX + backupTime;DWORD dataSize = 0;DWORD dataType;// 获取备份值项的大小if (RegQueryValueEx(hKey, backupKeyName.c_str(), NULL, &dataType, NULL, &dataSize) == ERROR_SUCCESS && dataType == REG_BINARY) {// 读取备份值项std::vector<BYTE> buffer(dataSize);if (RegQueryValueEx(hKey, backupKeyName.c_str(), NULL, NULL, buffer.data(), &dataSize) == ERROR_SUCCESS) {// 将备份值项的数据写入 Scancode Mapif (RegSetValueEx(hKey, REGISTRY_VALUE_NAME, 0, REG_BINARY, buffer.data(), dataSize) == ERROR_SUCCESS) {std::wcout << L"Restored Scancode Map from backup created at: " << backupTime << std::endl;}else {std::cerr << "Failed to restore Scancode Map from backup." << std::endl;}}}else {std::cerr << "Backup key not found or not a valid REG_BINARY value." << std::endl;}RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key." << std::endl;}
}// 更新重映射信息到注册表
bool UpdateRemapInfo(const std::vector<std::pair<WORD, WORD>>& remapInfo) {std::vector<BYTE> buffer(20 + remapInfo.size() * 4, 0);*(DWORD*)(buffer.data() + 8) = remapInfo.size() + 1;for (size_t i = 0; i < remapInfo.size(); ++i) {*(WORD*)(buffer.data() + 12 + i * 4) = remapInfo[i].second;*(WORD*)(buffer.data() + 14 + i * 4) = remapInfo[i].first;}HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) {if (RegSetValueEx(hKey, REGISTRY_VALUE_NAME, 0, REG_BINARY, buffer.data(), buffer.size()) == ERROR_SUCCESS) {RegCloseKey(hKey);return true;}RegCloseKey(hKey);}return false;
}// 删除重映射信息中的指定项
void DeleteRemapItemByIndex(std::vector<std::pair<WORD, WORD>>& remapInfo) {// 枚举原有数据EnumerateRemappedKeys(remapInfo);std::cout << "Enter the numbers of items you want to delete (e.g., 1 2 3), separated by spaces: ";std::string input;std::getline(std::cin, input);std::istringstream iss(input);std::vector<int> deleteIndices;int index;while (iss >> index) {deleteIndices.push_back(index - 1); // 索引从1开始,转换为从0开始}std::vector<std::pair<WORD, WORD>> updatedRemapInfo;for (size_t i = 0; i < remapInfo.size(); ++i) {if (std::find(deleteIndices.begin(), deleteIndices.end(), i) == deleteIndices.end()) {updatedRemapInfo.push_back(remapInfo[i]);}}if (UpdateRemapInfo(updatedRemapInfo)) {std::cout << "Remapped items deleted successfully.\n";}else {std::cerr << "Failed to delete remapped items.\n";}
}// 添加重映射按键
void AddRemapItemByIndex(std::vector<std::pair<WORD, WORD>>& remapInfo,std::pair<WORD, WORD> NewMapNode, size_t dwIdToInsert) {// 参数范围校验if (dwIdToInsert < 1 || dwIdToInsert > remapInfo.size() ){std::cerr << "Error invalid parameters.\n";return;}// 检查是否有重复的映射for (const auto& pair : remapInfo) {if (pair.first == NewMapNode.first || pair.second == NewMapNode.second) {std::cerr << "Error Duplicate remapped item.\n";return;}}// 向 remapInfo 中索引为 dwIdToInsert 前面插入元素 NewMapNode// 索引为 dwIdToInsert-1 后面插入等同于在索引为 dwIdToInsert 前插入 auto it = remapInfo.begin() + dwIdToInsert;remapInfo.insert(it, NewMapNode);// 利用新的结构信息更新注册表if (UpdateRemapInfo(remapInfo)) {std::cout << "Remapped items add successfully.\n";}else {std::cerr << "Failed to insert remapped items.\n";}
}// 禁用 Remap 注册表项
void ChangeScancodeMapStatus(BOOL bEnable) {// 打开注册表键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_WRITE | KEY_READ, &hKey) == ERROR_SUCCESS) {// 获取当前 Scancode Map 值的大小DWORD dataSize = 0;DWORD dataType;// 构造新的名称std::wstringstream disabledKeyNameStream;std::wstringstream checkKeyNameStream;disabledKeyNameStream << REGISTRY_VALUE_NAME << (bEnable ? L"" : DESABLED_KEY_PREFIX);std::wstring disabledKeyName = disabledKeyNameStream.str();checkKeyNameStream << REGISTRY_VALUE_NAME << (bEnable ? DESABLED_KEY_PREFIX : L"");std::wstring checkKeyName = checkKeyNameStream.str();if (RegQueryValueEx(hKey, checkKeyName.c_str(), NULL, &dataType, NULL, &dataSize) == ERROR_SUCCESS && dataType == REG_BINARY) {std::vector<BYTE> buffer(dataSize);if (RegQueryValueEx(hKey, checkKeyName.c_str(), NULL, NULL, buffer.data(), &dataSize) == ERROR_SUCCESS) {// 创建新的值项来存储备份数据LONG result = RegSetValueEx(hKey, disabledKeyName.c_str(), 0, REG_BINARY, buffer.data(), dataSize);if (result == ERROR_SUCCESS) {std::wcout << (bEnable ? L"Enable" : L"Disable")<< L" Scancode Map value successful. New key name: " << disabledKeyName << std::endl;// 删除原始键值项if (RegDeleteValue(hKey, checkKeyName.c_str()) == ERROR_SUCCESS) {std::wcout << L"Original registry key value deleted successfully." << std::endl;}else {std::wcerr << L"Failed to delete original registry key value." << std::endl;RegDeleteValue(hKey, disabledKeyName.c_str());}}else {std::cerr << "Failed to write Scancode Map value to disabled key: " << result << std::endl;}}else {std::cerr << "Failed to read registry value." << std::endl;}}else {if (RegQueryValueEx(hKey, disabledKeyName.c_str(), NULL, &dataType, NULL, &dataSize) == ERROR_SUCCESS && dataType == REG_BINARY) {std::wcout << L"Remapping keys were already "<< (bEnable ? L"enabled" : L"disabled")<< L" earlier." << std::endl;}else {std::cerr << "No Scancode Map value found in the registry." << std::endl;}}// 关闭键RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key." << std::endl;}
}// 将 char* 类型的字符串转换为 std::wstring
std::wstring ConvertToWideString(const char* str) {int size = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);if (size == 0) {// 转换失败return L"";}std::wstring result(size, L'\0');MultiByteToWideChar(CP_UTF8, 0, str, -1, &result[0], size);return result;
}int main(int argc, char* argv[]) {if (argc < 2) {std::cerr << "Usage: " << argv[0] << " <option> [configFile]\n";return 1;}std::string option = argv[1];if (option == "/enumerate") {   // 解析目前注册表中重映射键值的数据std::vector<std::pair<WORD, WORD>> remapInfo;if (ParseRemapInfo(remapInfo)) {EnumerateRemappedKeys(remapInfo);}else {std::cerr << "Failed to parse remap info.\n";}}else if (option == "/setremap") {  // 向注册表中覆盖写入重映射键值的数据if (argc != 3) {std::cerr << "Usage: " << argv[0] << " /setremap <configFilePath>\n";return 1;}std::string configFile = argv[2];ModifyRemappedKeys(configFile);}else if (option == "/queryActiveKey") {   // 动态查询按键的扫描码QueryScanCodeForKey();}else if (option == "/deleteAllMap")   // 删除所有重映射键数据{DeleteAllRemappedKeys();}else if (option == "/modifyDelKey") {    // 删除指定的重映射键数据std::vector<std::pair<WORD, WORD>> remapInfo;if (ParseRemapInfo(remapInfo)) {DeleteRemapItemByIndex(remapInfo);}else {std::cerr << "Failed to parse remap info.\n";}}else if (option == "/modifyInsertKey")      // 在指定的元素后面添加新的按键映射{std::vector<std::pair<WORD, WORD>> remapInfo;WORD oldScanCode = 0, newScanCode = 0;size_t index = 0;if (ParseRemapInfo(remapInfo)) {  // 解析注册表参数// 枚举原有数据EnumerateRemappedKeys(remapInfo);// 选择插入的位置和数据std::cout << "Enter the Original Key scan code: ";std::cin >> std::hex >> oldScanCode;std::cout << "Enter the Key scan code you wannt to replace to: ";std::cin >> std::hex >> newScanCode;std::cout << "Enter the number of item you wannt to insert after: ";std::cin >> index;// 调整字节顺序oldScanCode = ((oldScanCode & 0xFF00) >> 8) | ((oldScanCode & 0x00FF) << 8);newScanCode = ((newScanCode & 0xFF00) >> 8) | ((newScanCode & 0x00FF) << 8);// 写入数据AddRemapItemByIndex(remapInfo,std::make_pair(oldScanCode, newScanCode), index);}else {std::cerr << "Failed to parse remap info.\n";}}else if (option == "/modifyAddKey")     // 在映射表的末尾追加按键映射{std::vector<std::pair<WORD, WORD>> remapInfo;WORD oldScanCode = 0, newScanCode = 0;size_t index = 0;if (ParseRemapInfo(remapInfo)) {  // 解析注册表参数// 选择插入的位置和数据std::cout << "Enter the Original Key scan code: ";std::cin >> std::hex >> oldScanCode;std::cout << "Enter the Key scan code you wannt to replace to: ";std::cin >> std::hex >> newScanCode;index = remapInfo.size(); // 在结尾插入// 调整字节顺序oldScanCode = ((oldScanCode & 0xFF00) >> 8) | ((oldScanCode & 0x00FF) << 8);newScanCode = ((newScanCode & 0xFF00) >> 8) | ((newScanCode & 0x00FF) << 8);// 写入数据AddRemapItemByIndex(remapInfo,std::make_pair(oldScanCode, newScanCode), index);}else {std::cerr << "Failed to parse remap info.\n";}}else if (option == "/statusMap")   // 启用或者禁用重映射键(可恢复,1表示启用,0表示禁用){if (argc != 3) {std::cerr << "Usage: " << argv[0] << " /statusMap <bEnable>\n";return 1;}if (std::string(argv[2]) == "1"){std::cout << "Try to enable ReMap Key.\n";ChangeScancodeMapStatus(TRUE);}else if (std::string(argv[2]) == "0"){std::cout << "Try to disable ReMap Key.\n";ChangeScancodeMapStatus(FALSE);}}else if (option == "/makebackup") {  // 备份当前注册表重映射键数据BackupScancodeMap();}else if (option == "/recoverMap") {  // 按照备份日期还原数据if (argc != 3) {std::cerr << "Usage: " << argv[0] << " /recoverMap <backupTime>\n";return 1;}std::wstring backupTime = ConvertToWideString(argv[2]);if (backupTime == L"")return 1;RestoreBackupScancodeMap(backupTime);}else if (option == "/readbackup") {   // 读取备份文件列表EnumerateAndPrintBackupKeys();}else {std::cerr << "Invalid option.\n";return 1;}return 0;
}

程序需要管理员权限,设置清单文件即可:

标题

测试截图如下:

标题

3.2 C# 实现的窗口工具

Medo 制作的开源工具 Scancode Map 是一个不错的选择,它具有完备的查询、修改、删除等功能,我的程序一定程度上参考了他的思路。

这是该工具的界面视图:

Scancode Map 截图 1
Scancode Map 截图 2

原文链接为:Scancode Map 1.11 – Medo's Home Page。

网盘下载链接:https://pan.baidu.com/s/1Dxn8R3GEdol59ObYAMTLHw?pwd=vtq3

提取码:vtq3 (里面有我重新编译好的文件在 /source/bin 内,并且包含我写的控制台程序)

官网程序下载链接:https://www.medo64.com/download/scancodemap111.exe。

源代码 Github 链接:Release Most recent build · medo64/ScancodeMap · GitHub。

四、总结

本文简单介绍了使用 "Scancode Map" 键盘扫描码映射表禁用 CtrlAltDel 键的方法。浅谈了该方法的利弊。修改注册表屏蔽该按键的方法还有另外一个稳定的方法,就是利用 Disable 项隐藏相应的选项,这个方法也是以前讨论最多的方法,比按键扫描码用的还广泛,还有一个就是进程冻结法。但是都有缺点,比如可以注册表方法是可以轻易恢复的;进程冻结存在不稳定性,冻结后容易让进程陷入 RPC 死锁导致无法恢复。


【保留备用】

本文属于原创文章,转载请注明出处:

https://blog.csdn.net/qq_59075481/article/details/136104444。

发布于:2024.02.13,更新于:2024.02.13.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/681194.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

龙芯开启ssh服务——使用Putty连接

本文采用龙芯3A6000处理器&#xff0c;Loongnix操作系统。 为了能使用其他电脑远程操控龙芯电脑&#xff0c;需要打开loongnix的ssh服务&#xff0c;并在其他电脑里使用putty连接loongnix。 1 修改ssh配置文件 命令行输入&#xff1a; sudo vim /etc/ssh/sshd_config按下i插…

Linux-进程信号

Linux进程信号 初步认识信号信号的存储结构信号的处理方式信号的产生硬件异常产生的信号核心转储sigset_t信号集信号集的操作函数对block表的操作对pending表的操作对handler表的操作信号的捕捉用户态和内核态 信号的处理过程可重入函数volatile关键字 初步认识信号 生活中有哪…

LeetCode、136. 只出现一次的数字【简单,位运算】

文章目录 前言LeetCode、136. 只出现一次的数字【简单&#xff0c;位运算】题目链接与分类思路异或一遍运算 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术…

冲击可以通过峭度指标来检测,是如何来检测的,python示例代码

问题 冲击可以通过峭度指标来检测&#xff0c;是如何来检测的&#xff0c;可以给1个示例代码吗 思路 带冲击的信号其峭度指标>3不带冲击的信号其峭度指标在3左右可以通过滑动窗来检测在哪一段 示例代码 带冲击的信号峭度指标值 import numpy as np import matplotlib.…

2024.02.12

使用STM32Cubemx创建一个工程并且给出每一步的含 选择芯片创建工程 开启调试功能 配置时钟 配置时间树 工程管理 配置仿真器

幻兽帕鲁服务器原来的存档不想玩了,怎么清档?如何重来?

如果需要备份原存档的话&#xff0c;就先把存档导出来备份。或者手动去服务器文件里找到游戏存档文件夹&#xff0c;保存下载。 如无需备份原存档&#xff0c;则可以直接使用幻兽帕鲁应用模板&#xff0c;来重装服务器的操作系统。 方法很简单&#xff1a; 详细教程地…

odoo封装字段widget中无感知刷新数据

常规操作是直接刷新页面 window.location.reload(); 深入源码&#xff0c;发现页面controller层有reload 对应tree的字段widget this.__owl__.parent.parentWidget.__parentedParent.reload(); 对应form的字段widget this.__parentedParent.__parentedParent.reload()

APP inventor零基础移动应用开发

1.Android平台简介 Android由谷歌和开放手机联盟共同创建的一款针对手机的开源软件工具包 主要特色 ---开放性 – 丰富的硬件选择 – 开发商不受任何限制 – 无缝集成互联网服务 App Inventor是由Google公司开发的一款在线开放的Android编程工具软件&#xff0c;通过图形化…

huggingface学习|用dreambooth和lora对stable diffusion模型进行微调

目录 用dreambooth对stable-diffusion-v1-5模型进行微调&#xff08;一&#xff09;模型下载和环境配置&#xff08;二&#xff09;数据集准备&#xff08;三&#xff09;模型微调&#xff08;四&#xff09;运行微调后的模型 用lora对stable-diffusion-v1-5模型进行微调&#…

【C语言】动态内存深入了解(一口气刨根问底学完系列,全乎,建议三连点赞收藏)

目录 1.动态内存分配的原因 2.动态内存函数的介绍 2.1malloc和free函数 2.2calloc函数 2.3realloc函数 3. 常见的动态内存错误 3.1 对NULL指针的解引用操作 3.2 对动态开辟空间的越界访问 3.3 对非动态开辟内存使用free释放 3.4 使用free释放一块动态开辟内存的一部…

Swift 初见

Swift 初见 学习swift的记录 控制台输出 print("hello world")可以无分号&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 变量声明 let 常量声明 如果在常量声明后再次修改他会报错 var 变量声明 一个常量的值&#xf…

linux应用 进程间通信之共享内存(POSIX)

1、前言 1.1 定义 POSIX共享内存是一种在UNIX和类UNIX系统上可用的进程间通信机制。它允许多个进程共享同一块内存区域&#xff0c;从而可以在这块共享内存上进行读写操作。 1.2 应用场景 POSIX共享内存适用于需要高效地进行大量数据交换的场景&#xff0c;比如多个进程需要…

upload-labs文件上传漏洞靶场

第一关 <?php eval ($_POST[123]);?>发现他这个是通过客户端前端写了一个限制 我们禁用srcipt即可 蚁剑成功打开 第二关 我们上传文件2.php它提示我们文件类型不正确 我们可以联想到做了后缀检测 我们通过burp抓包修改后缀 第三关 我们上传一个.php文件不可上…

Peter算法小课堂—区间模型

Peter Pan来啦…… 最大不重叠区间数 二话不说&#xff0c;先来一道题 大家想想怎么贪心&#xff1f;我们可以将每一个美食摊位抽象成一个区间&#xff0c;区间左端点为开始排队时间&#xff0c;右端点为结束排队时间。其中&#xff0c;时间信息可以用数轴表示。 额……我们…

【分布式技术专题】「Zookeeper中间件」Paxos协议的原理和实际运行中的应用流程分析

Paxo算法介绍 Paxos算法是莱斯利兰伯特(Leslie Lamport)1990年提出的一种基于消息传递的一致性算法。 Paxos产生背景 Paxos算法是基于消息传递且具有高度容错特性的一致性算法&#xff0c;是目前公认的解决分布式一致性问题最有效的算法之一&#xff0c;其解决的问题就是在分…

轮播图 HarmonyOS 鸿蒙 ArkTS ArkUI

第一步&#xff1a;新建图片数组 State swiperimgs:Array<Object>[$r(app.media.a), //本地图片或者网络图片$r(app.media.b),$r(app.media.c),$r(app.media.d)] 第二步&#xff1a;写入轮播图代码 Column(){Swiper(){ForEach(this.swiperimgs, (item) > {Image(item…

【c语言】字符串常见函数 上

&#x1f388;个人主页&#xff1a;甜美的江 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;c语言 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&a…

【JVM篇】怎么解决内存泄漏问题

文章目录 &#x1f50e;什么是内存泄漏&#x1f6f8;解决内存泄漏⭐发现问题⭐诊断原因⭐修复问题 &#x1f50e;什么是内存泄漏 在Java中如果不再使用一个对象&#xff0c;但是这个对象仍然在GC Root的引用链上&#xff0c;这个对象就不会被垃圾回收器回收&#xff0c;这种情…

openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O

文章目录 openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O218.1 查看I/O状况218.2 性能参数分析 openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O 获取openGauss节点的CPU、内存、I/O和网络资源使用情况&#xf…

[C++]17:二叉树进阶

二叉树进阶 一.二叉搜索树&#xff1a;1.二叉搜索树的概念&#xff1a;2.二叉搜索树的实现---循环版本&#xff1a;1.二叉搜索树的基本结构&#xff1a;2.查找&#xff1a;3.插入&#xff1a;4.中序遍历&#xff1a;5.删除&#xff1a; 3.二叉搜索树的实现---递归版本&#xff…