捕获键盘挂钩上的死键
Capture dead keys on Keyboard hook
参照this question, and more specifically to this answer, it seems that dead keys are captured on a keyboad hook only after a MSB操纵。答案的作者留下了一个固定的代码,但我不知道 Delphi 等价的是什么。
我的实际代码:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
Messages,
SysUtils;
var
hhk: HHOOK;
Msg: TMsg;
{==================================================================================}
function ReverseBits(b: Byte): Byte;
var
i: Integer;
begin
Result := 0;
for i := 1 to 8 do
begin
Result := (Result shl 1) or (b and 1);
b := b shr 1;
end;
end;
function GetCharFromVirtualKey(Key: Word): string;
var
keyboardState: TKeyboardState;
keyboardLayout: HKL;
asciiResult: Integer;
begin
GetKeyboardState(keyboardState);
keyboardLayout := GetKeyboardLayout(0);
SetLength(Result, 2);
asciiResult := ToAsciiEx(Key, ReverseBits(MapVirtualKey(Key, MAPVK_VK_TO_CHAR)), keyboardState, @Result[1], 0, keyboardLayout);
case asciiResult of
0:
Result := '';
1:
SetLength(Result, 1);
2:
;
else
Result := '';
end;
end;
{==================================================================================}
function LowLevelKeyboardProc(nCode: Integer; wParam: wParam; lParam: lParam): LRESULT; stdcall;
type
PKBDLLHOOKSTRUCT = ^TKBDLLHOOKSTRUCT;
TKBDLLHOOKSTRUCT = record
vkCode: cardinal;
scanCode: cardinal;
flags: cardinal;
time: cardinal;
dwExtraInfo: Cardinal;
end;
PKeyboardLowLevelHookStruct = ^TKeyboardLowLevelHookStruct;
TKeyboardLowLevelHookStruct = TKBDLLHOOKSTRUCT;
var
LKBDLLHOOKSTRUCT: PKeyboardLowLevelHookStruct;
begin
case nCode of
HC_ACTION:
begin
if wParam = WM_KEYDOWN then
begin
LKBDLLHOOKSTRUCT := PKeyboardLowLevelHookStruct(lParam);
Writeln(GetCharFromVirtualKey(LKBDLLHOOKSTRUCT^.vkCode));
end;
end;
end;
Result := CallNextHookEx(hhk, nCode, wParam, lParam);
end;
procedure InitHook;
begin
hhk := SetWindowsHookEx(WH_KEYBOARD_LL, @LowLevelKeyboardProc, HInstance, 0);
if hhk = 0 then
RaiseLastOSError;
end;
procedure KillHook;
begin
if (hhk <> 0) then
UnhookWindowsHookEx(hhk);
end;
begin
try
InitHook;
while GetMessage(Msg, 0, 0, 0) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
在回答@fpiette 的最后一条评论时,这是我的解决方案:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
Messages,
SysUtils;
var
hhk: HHOOK;
Msg: TMsg;
function IsTextCharForKeyTip(AKey: Word): Boolean;
var
keyboardLayout: HKL;
ActiveWindow: HWND;
ActiveThreadID: DWord;
ARes: UINT;
begin
ActiveWindow := GetForegroundWindow;
ActiveThreadID := GetWindowThreadProcessId(ActiveWindow, nil);
keyboardLayout := GetKeyboardLayout(ActiveThreadID);
ARes := MapVirtualKeyEx(AKey, MAPVK_VK_TO_CHAR, keyboardLayout);
Result := ((ARes and $FFFF0000) = 0) and (Char(ARes) <> ' ') and (CharInSet(Char(ARes), [#32..#255]));
end;
function LowLevelKeyboardProc(nCode: Integer; wParam: wParam; lParam: lParam): LRESULT; stdcall;
type
PKBDLLHOOKSTRUCT = ^TKBDLLHOOKSTRUCT;
TKBDLLHOOKSTRUCT = record
vkCode: cardinal;
scanCode: cardinal;
flags: cardinal;
time: cardinal;
dwExtraInfo: cardinal;
end;
PKeyboardLowLevelHookStruct = ^TKeyboardLowLevelHookStruct;
TKeyboardLowLevelHookStruct = TKBDLLHOOKSTRUCT;
var
LKBDLLHOOKSTRUCT: PKeyboardLowLevelHookStruct;
keyboardState: TKeyboardState;
keyboardLayout: HKL;
ScanCode: Integer;
ActiveWindow: HWND;
ActiveThreadID: DWord;
CapsKey, ShiftKey: Boolean;
KeyString: string;
begin
Result := CallNextHookEx(hhk, nCode, wParam, lParam);
case nCode of
HC_ACTION:
begin
CapsKey := False;
ShiftKey := False;
if Odd(GetKeyState(VK_CAPITAL)) then
if GetKeyState(VK_SHIFT) < 0 then
CapsKey := False
else
CapsKey := True
else if GetKeyState(VK_SHIFT) < 0 then
ShiftKey := True
else
ShiftKey := False;
if GetKeyState(VK_BACK) < 0 then
Write('[Backspace]') { #08 }; // #08, overwrites what was deleted on the console :D.
if GetKeyState(VK_SPACE) < 0 then
Write(#32);
if GetKeyState(VK_TAB) < 0 then
Write(#09);
case wParam of
WM_KEYDOWN, WM_SYSKEYDOWN:
begin
LKBDLLHOOKSTRUCT := PKeyboardLowLevelHookStruct(lParam);
if (not IsTextCharForKeyTip(LKBDLLHOOKSTRUCT^.vkCode)) then
Exit;
SetLength(KeyString, 2);
ActiveWindow := GetForegroundWindow;
ActiveThreadID := GetWindowThreadProcessId(ActiveWindow, nil);
keyboardLayout := GetKeyboardLayout(ActiveThreadID);
GetKeyboardState(keyboardState);
ScanCode := MapVirtualKeyEx(LKBDLLHOOKSTRUCT^.vkCode, MAPVK_VK_TO_CHAR, keyboardLayout);
ToAsciiEx(LKBDLLHOOKSTRUCT^.vkCode, ScanCode, keyboardState, @KeyString[1], 0, keyboardLayout);
if CapsKey or ShiftKey then
Write(UpperCase(KeyString[1]))
else
Write(LowerCase(KeyString[1]));
end;
end;
end;
end;
end;
procedure InitHook;
begin
hhk := SetWindowsHookEx(WH_KEYBOARD_LL, @LowLevelKeyboardProc, HInstance, 0);
if hhk = 0 then
RaiseLastOSError;
end;
procedure KillHook;
begin
if (hhk <> 0) then
UnhookWindowsHookEx(hhk);
end;
begin
try
InitHook;
while True do
begin
if PeekMessage(Msg, 0, 0, 0, 0) then
begin
GetMessage(Msg, 0, 0, 0);
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
参照this question, and more specifically to this answer, it seems that dead keys are captured on a keyboad hook only after a MSB操纵。答案的作者留下了一个固定的代码,但我不知道 Delphi 等价的是什么。
我的实际代码:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
Messages,
SysUtils;
var
hhk: HHOOK;
Msg: TMsg;
{==================================================================================}
function ReverseBits(b: Byte): Byte;
var
i: Integer;
begin
Result := 0;
for i := 1 to 8 do
begin
Result := (Result shl 1) or (b and 1);
b := b shr 1;
end;
end;
function GetCharFromVirtualKey(Key: Word): string;
var
keyboardState: TKeyboardState;
keyboardLayout: HKL;
asciiResult: Integer;
begin
GetKeyboardState(keyboardState);
keyboardLayout := GetKeyboardLayout(0);
SetLength(Result, 2);
asciiResult := ToAsciiEx(Key, ReverseBits(MapVirtualKey(Key, MAPVK_VK_TO_CHAR)), keyboardState, @Result[1], 0, keyboardLayout);
case asciiResult of
0:
Result := '';
1:
SetLength(Result, 1);
2:
;
else
Result := '';
end;
end;
{==================================================================================}
function LowLevelKeyboardProc(nCode: Integer; wParam: wParam; lParam: lParam): LRESULT; stdcall;
type
PKBDLLHOOKSTRUCT = ^TKBDLLHOOKSTRUCT;
TKBDLLHOOKSTRUCT = record
vkCode: cardinal;
scanCode: cardinal;
flags: cardinal;
time: cardinal;
dwExtraInfo: Cardinal;
end;
PKeyboardLowLevelHookStruct = ^TKeyboardLowLevelHookStruct;
TKeyboardLowLevelHookStruct = TKBDLLHOOKSTRUCT;
var
LKBDLLHOOKSTRUCT: PKeyboardLowLevelHookStruct;
begin
case nCode of
HC_ACTION:
begin
if wParam = WM_KEYDOWN then
begin
LKBDLLHOOKSTRUCT := PKeyboardLowLevelHookStruct(lParam);
Writeln(GetCharFromVirtualKey(LKBDLLHOOKSTRUCT^.vkCode));
end;
end;
end;
Result := CallNextHookEx(hhk, nCode, wParam, lParam);
end;
procedure InitHook;
begin
hhk := SetWindowsHookEx(WH_KEYBOARD_LL, @LowLevelKeyboardProc, HInstance, 0);
if hhk = 0 then
RaiseLastOSError;
end;
procedure KillHook;
begin
if (hhk <> 0) then
UnhookWindowsHookEx(hhk);
end;
begin
try
InitHook;
while GetMessage(Msg, 0, 0, 0) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
在回答@fpiette 的最后一条评论时,这是我的解决方案:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
Messages,
SysUtils;
var
hhk: HHOOK;
Msg: TMsg;
function IsTextCharForKeyTip(AKey: Word): Boolean;
var
keyboardLayout: HKL;
ActiveWindow: HWND;
ActiveThreadID: DWord;
ARes: UINT;
begin
ActiveWindow := GetForegroundWindow;
ActiveThreadID := GetWindowThreadProcessId(ActiveWindow, nil);
keyboardLayout := GetKeyboardLayout(ActiveThreadID);
ARes := MapVirtualKeyEx(AKey, MAPVK_VK_TO_CHAR, keyboardLayout);
Result := ((ARes and $FFFF0000) = 0) and (Char(ARes) <> ' ') and (CharInSet(Char(ARes), [#32..#255]));
end;
function LowLevelKeyboardProc(nCode: Integer; wParam: wParam; lParam: lParam): LRESULT; stdcall;
type
PKBDLLHOOKSTRUCT = ^TKBDLLHOOKSTRUCT;
TKBDLLHOOKSTRUCT = record
vkCode: cardinal;
scanCode: cardinal;
flags: cardinal;
time: cardinal;
dwExtraInfo: cardinal;
end;
PKeyboardLowLevelHookStruct = ^TKeyboardLowLevelHookStruct;
TKeyboardLowLevelHookStruct = TKBDLLHOOKSTRUCT;
var
LKBDLLHOOKSTRUCT: PKeyboardLowLevelHookStruct;
keyboardState: TKeyboardState;
keyboardLayout: HKL;
ScanCode: Integer;
ActiveWindow: HWND;
ActiveThreadID: DWord;
CapsKey, ShiftKey: Boolean;
KeyString: string;
begin
Result := CallNextHookEx(hhk, nCode, wParam, lParam);
case nCode of
HC_ACTION:
begin
CapsKey := False;
ShiftKey := False;
if Odd(GetKeyState(VK_CAPITAL)) then
if GetKeyState(VK_SHIFT) < 0 then
CapsKey := False
else
CapsKey := True
else if GetKeyState(VK_SHIFT) < 0 then
ShiftKey := True
else
ShiftKey := False;
if GetKeyState(VK_BACK) < 0 then
Write('[Backspace]') { #08 }; // #08, overwrites what was deleted on the console :D.
if GetKeyState(VK_SPACE) < 0 then
Write(#32);
if GetKeyState(VK_TAB) < 0 then
Write(#09);
case wParam of
WM_KEYDOWN, WM_SYSKEYDOWN:
begin
LKBDLLHOOKSTRUCT := PKeyboardLowLevelHookStruct(lParam);
if (not IsTextCharForKeyTip(LKBDLLHOOKSTRUCT^.vkCode)) then
Exit;
SetLength(KeyString, 2);
ActiveWindow := GetForegroundWindow;
ActiveThreadID := GetWindowThreadProcessId(ActiveWindow, nil);
keyboardLayout := GetKeyboardLayout(ActiveThreadID);
GetKeyboardState(keyboardState);
ScanCode := MapVirtualKeyEx(LKBDLLHOOKSTRUCT^.vkCode, MAPVK_VK_TO_CHAR, keyboardLayout);
ToAsciiEx(LKBDLLHOOKSTRUCT^.vkCode, ScanCode, keyboardState, @KeyString[1], 0, keyboardLayout);
if CapsKey or ShiftKey then
Write(UpperCase(KeyString[1]))
else
Write(LowerCase(KeyString[1]));
end;
end;
end;
end;
end;
procedure InitHook;
begin
hhk := SetWindowsHookEx(WH_KEYBOARD_LL, @LowLevelKeyboardProc, HInstance, 0);
if hhk = 0 then
RaiseLastOSError;
end;
procedure KillHook;
begin
if (hhk <> 0) then
UnhookWindowsHookEx(hhk);
end;
begin
try
InitHook;
while True do
begin
if PeekMessage(Msg, 0, 0, 0, 0) then
begin
GetMessage(Msg, 0, 0, 0);
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.