Last active
January 8, 2024 06:05
-
-
Save PlugFox/5b7b9cc559f71bbaf98f8c6e20a29029 to your computer and use it in GitHub Desktop.
Win32 Keyboard Dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void main([List<String>? arguments]) { | |
Keyboard.add(VK.Q, (state) { | |
if (state.up) return false; | |
if (!Keyboard.isShiftPressed) return false; | |
l.i('Shift + Q pressed'); | |
Keyboard.send(VK.W); | |
return true; // Return true to prevent default action | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ignore_for_file: constant_identifier_names | |
import 'dart:async'; | |
import 'dart:ffi' as ffi; | |
import 'package:ffi/ffi.dart' as ffi; | |
import 'package:meta/meta.dart'; | |
import 'package:win32/win32.dart' as win; | |
typedef KeyboardCallback = bool Function(KeyState state); | |
/// Key state | |
@immutable | |
final class KeyState { | |
const KeyState._({ | |
required this.key, | |
required this.vkCode, | |
required this.scanCode, | |
required this.flags, | |
required this.time, | |
required this.dwExtraInfo, | |
required this.down, | |
required this.wParam, | |
}); | |
/// The virtual key. | |
final VK key; | |
/// Virtual key code. | |
final int vkCode; | |
/// Scan code. | |
final int scanCode; | |
/// Flags. | |
final int flags; | |
/// Time | |
final int time; | |
/// Extra info. | |
final int dwExtraInfo; | |
/// Whether the key is down (pressed). | |
final bool down; | |
/// Whether the key is up (not pressed). | |
bool get up => !down; | |
/// Is the Alt key pressed. | |
bool get alt => (flags & win.LLKHF_ALTDOWN) != 0; | |
/// Whether the key is toggled. | |
final int wParam; | |
@override | |
String toString() => '${key.name} ${down ? 'DOWN' : 'UP'}'; | |
} | |
/// KeyboardObserver namespace | |
class Keyboard { | |
Keyboard._(); | |
static int _hHook = 0; | |
static final Map<VK, List<KeyboardCallback>> _callbacks = <VK, List<KeyboardCallback>>{}; | |
static void _init() { | |
if (_hHook != 0) return; | |
_callbacks.clear(); | |
final pointer = ffi.Pointer.fromFunction<ffi.IntPtr Function(ffi.Int32, ffi.IntPtr, ffi.IntPtr)>(_handle, 0); | |
_hHook = win.SetWindowsHookEx(win.WH_KEYBOARD_LL, pointer, 0, 0); | |
if (_hHook == 0) throw Exception('Failed to set hook.'); | |
final msg = ffi.calloc<win.MSG>(); | |
Timer.periodic(Duration.zero, (timer) { | |
if (_hHook == 0) { | |
timer.cancel(); | |
return; | |
} | |
//win.PostThreadMessage(win.GetCurrentThreadId(), win.WM_NULL, 0, 0); | |
// Использование PeekMessage вместо GetMessage | |
if (win.PeekMessage(msg, win.NULL, 0, 0, win.PM_REMOVE) != 0) { | |
win.TranslateMessage(msg); | |
win.DispatchMessage(msg); | |
} | |
}); | |
} | |
/// Notify all callbacks for key press | |
/// Return true if any callback returns true and mark event as handled | |
static bool _notify(int wParam, int lParam) { | |
const events = <int>{win.WM_KEYDOWN, win.WM_SYSKEYDOWN, win.WM_KEYUP, win.WM_SYSKEYUP}; | |
if (!events.contains(wParam)) return false; | |
final kbhPointer = ffi.Pointer<win.KBDLLHOOKSTRUCT>.fromAddress(lParam); | |
final kbh = kbhPointer.ref; | |
final key = VK.fromCodeOrNull(kbh.vkCode); | |
if (key == null) return false; | |
final callbacks = _callbacks[key]; | |
if (callbacks == null || callbacks.isEmpty) return false; | |
final state = KeyState._( | |
key: key, | |
scanCode: kbh.scanCode, | |
vkCode: kbh.vkCode, | |
flags: kbh.flags, | |
time: kbh.time, | |
dwExtraInfo: kbh.dwExtraInfo, | |
down: wParam == win.WM_KEYDOWN || wParam == win.WM_SYSKEYDOWN, | |
wParam: wParam, | |
); | |
var result = false; | |
for (final callback in callbacks) { | |
result |= callback(state); | |
} | |
return result; | |
} | |
/// Handle keyboard event | |
static int _handle(int nCode, int wParam, int lParam) { | |
// Проверка, что сообщение не обработано и можно его обработать | |
// Вернуть 1, чтобы блокировать обработку клавиши системой | |
if (!_$isSimulatedKeypress && nCode >= 0 && _notify(wParam, lParam)) return 1; | |
// Передать управление следующему обработчику в цепочке | |
return win.CallNextHookEx(0, nCode, wParam, lParam); | |
} | |
/// Add callback for key press | |
/// If every callback returns false, the key press will be passed to the next hook in the chain. | |
/// If any callback returns true, the key press will not be passed to the next hook in the chain. | |
static void add(VK key, KeyboardCallback callback) { | |
_init(); | |
_callbacks.putIfAbsent(key, () => <KeyboardCallback>[]).add(callback); | |
} | |
/// Remove callback for key press | |
static void remove(VK key, KeyboardCallback callback) => _callbacks[key]?.remove(callback); | |
/// Remove all callbacks and unhook | |
static void clear() { | |
_callbacks.clear(); | |
if (_hHook != 0) { | |
win.UnhookWindowsHookEx(_hHook); | |
_hHook = 0; | |
} | |
} | |
/// Is the hook installed | |
static bool get isHooked => _hHook != 0; | |
/// Check if the key is pressed | |
static bool isKeyPressed(VK key) => win.GetKeyState(key.code) & 0x8000 != 0; | |
/// Is the shift key pressed | |
static bool get isShiftPressed => win.GetKeyState(win.VK_SHIFT) & 0x8000 != 0; | |
/// Is the control key pressed | |
static bool get isControlPressed => win.GetKeyState(win.VK_CONTROL) & 0x8000 != 0; | |
/// Is the alt key pressed | |
static bool get isAltPressed => win.GetKeyState(win.VK_MENU) & 0x8000 != 0; | |
/// Is the caps lock key pressed | |
static bool get isCapsLockOn => win.GetKeyState(win.VK_CAPITAL) & 0x0001 != 0; | |
/// Is the num lock key pressed | |
static bool get isNumLockOn => win.GetKeyState(win.VK_NUMLOCK) & 0x0001 != 0; | |
/// Is the scroll lock key pressed | |
static bool get isScrollLockOn => win.GetKeyState(win.VK_SCROLL) & 0x0001 != 0; | |
static bool _$isSimulatedKeypress = false; | |
/// Send key press | |
static void send(VK key) { | |
ffi.Pointer<win.INPUT>? input; | |
try { | |
_$isSimulatedKeypress = true; | |
input = ffi.calloc<win.INPUT>() | |
..ref.type = win.INPUT_KEYBOARD | |
..ref.ki.wVk = key.code | |
..ref.ki.dwFlags = 0 | |
..ref.ki.time = 0 | |
..ref.ki.dwExtraInfo = 0; | |
win.SendInput(1, input, ffi.sizeOf<win.INPUT>()); // Нажатие клавиши | |
input.ref.ki.dwFlags = win.KEYEVENTF_KEYUP; | |
win.SendInput(1, input, ffi.sizeOf<win.INPUT>()); // Отпускание клавиши | |
} finally { | |
_$isSimulatedKeypress = false; | |
if (input != null) ffi.calloc.free(input); // Освобождение памяти | |
} | |
} | |
} | |
/// Virtual Key codes table | |
/// Symbolic constant name = decimal value 1..255 | |
/// [Virtual-Key Codes](https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes) | |
enum VK { | |
/// Left mouse button | |
LBUTTON(1), | |
/// Right mouse button | |
RBUTTON(2), | |
/// Control-break processing | |
CANCEL(3), | |
/// Middle mouse button (three-button mouse) | |
MBUTTON(4), | |
/// Windows 2000: X1 mouse button | |
XBUTTON1(5), | |
/// Windows 2000: X2 mouse button | |
XBUTTON2(6), | |
/// BACKSPACE key | |
BACK(8), | |
/// TAB key | |
TAB(9), | |
/// CLEAR key | |
CLEAR(12), | |
/// ENTER key | |
RETURN(13), | |
/// SHIFT key | |
@Deprecated('Use LSHIFT instead') | |
SHIFT(16, extendedKey: true), | |
/// CTRL key | |
@Deprecated('Use LCONTROL instead') | |
CONTROL(17, extendedKey: true), | |
/// ALT key | |
@Deprecated('Use LMENU instead') | |
MENU(18, extendedKey: true), | |
/// PAUSE key | |
PAUSE(19), | |
/// CAPS LOCK key | |
CAPITAL(20), | |
/// IME Kana mode | |
KANA(21), | |
/// IME Hanguel mode (maintained for compatibility; use VK_HANGUL) | |
HANGUEL(21), | |
/// IME Hangul mode | |
HANGUL(21), | |
/// IME Junja mode | |
JUNJA(23), | |
/// IME final mode | |
FINAL(24), | |
/// IME Hanja mode | |
HANJA(25), | |
/// IME Kanji mode | |
KANJI(25), | |
/// ESC key | |
ESCAPE(27), | |
/// IME convert | |
CONVERT(28), | |
/// IME nonconvert | |
NONCONVERT(29), | |
/// IME accept | |
ACCEPT(30), | |
/// IME mode change request | |
MODECHANGE(31), | |
/// SPACEBAR | |
SPACE(32), | |
/// PAGE UP key | |
PRIOR(33), | |
/// PAGE DOWN key | |
NEXT(34), | |
/// END key | |
END(35), | |
/// HOME key | |
HOME(36), | |
/// LEFT ARROW key | |
LEFT(37), | |
/// UP ARROW key | |
UP(38), | |
/// RIGHT ARROW key | |
RIGHT(39), | |
/// DOWN ARROW key | |
DOWN(40), | |
/// SELECT key | |
SELECT(41), | |
/// PRINT key | |
PRINT(42), | |
/// EXECUTE key | |
EXECUTE(43), | |
/// PRINT SCREEN key | |
SNAPSHOT(44), | |
/// INS key | |
INSERT(45), | |
/// DEL key | |
DELETE(46), | |
/// HELP key | |
HELP(47), | |
/// 0 key | |
KEY0(48), | |
/// 1 key | |
KEY1(49), | |
/// 2 key | |
KEY2(50), | |
/// 3 key | |
KEY3(51), | |
/// 4 key | |
KEY4(52), | |
/// 5 key | |
KEY5(53), | |
/// 6 key | |
KEY6(54), | |
/// 7 key | |
KEY7(55), | |
/// 8 key | |
KEY8(56), | |
/// 9 key | |
KEY9(57), | |
/// A key | |
A(65), | |
/// B key | |
B(66), | |
/// C key | |
C(67), | |
/// D key | |
D(68), | |
/// E key | |
E(69), | |
/// F key | |
F(70), | |
/// G key | |
G(71), | |
/// H key | |
H(72), | |
/// I key | |
I(73), | |
/// J key | |
J(74), | |
/// K key | |
K(75), | |
/// L key | |
L(76), | |
/// M key | |
M(77), | |
/// N key | |
N(78), | |
/// O key | |
O(79), | |
/// P key | |
P(80), | |
/// Q key | |
Q(81), | |
/// R key | |
R(82), | |
/// S key | |
S(83), | |
/// T key | |
T(84), | |
/// U key | |
U(85), | |
/// V key | |
V(86), | |
/// W key | |
W(87), | |
/// X key | |
X(88), | |
/// Y key | |
Y(89), | |
/// Z key | |
Z(90), | |
/// Left Windows key (Microsoft® Natural® keyboard) | |
LWIN(91), | |
/// Right Windows key (Natural keyboard) | |
RWIN(92), | |
/// Applications key (Natural keyboard) | |
APPS(93), | |
/// Computer Sleep key | |
SLEEP(95), | |
/// Numeric keypad 0 key | |
NUMPAD0(96), | |
/// Numeric keypad 1 key | |
NUMPAD1(97), | |
/// Numeric keypad 2 key | |
NUMPAD2(98), | |
/// Numeric keypad 3 key | |
NUMPAD3(99), | |
/// Numeric keypad 4 key | |
NUMPAD4(100), | |
/// Numeric keypad 5 key | |
NUMPAD5(101), | |
/// Numeric keypad 6 key | |
NUMPAD6(102), | |
/// Numeric keypad 7 key | |
NUMPAD7(103), | |
/// Numeric keypad 8 key | |
NUMPAD8(104), | |
/// Numeric keypad 9 key | |
NUMPAD9(105), | |
/// Multiply key | |
MULTIPLY(106), | |
/// Add key | |
ADD(107), | |
/// Separator key | |
SEPARATOR(108), | |
/// Subtract key | |
SUBTRACT(109), | |
/// Decimal key | |
DECIMAL(110), | |
/// Divide key | |
DIVIDE(111), | |
/// F1 key | |
F1(112), | |
/// F2 key | |
F2(113), | |
/// F3 key | |
F3(114), | |
/// F4 key | |
F4(115), | |
/// F5 key | |
F5(116), | |
/// F6 key | |
F6(117), | |
/// F7 key | |
F7(118), | |
/// F8 key | |
F8(119), | |
/// F9 key | |
F9(120), | |
/// F10 key | |
F10(121), | |
/// F11 key | |
F11(122), | |
/// F12 key | |
F12(123), | |
/// F13 key | |
F13(124), | |
/// F14 key | |
F14(125), | |
/// F15 key | |
F15(126), | |
/// F16 key | |
F16(127), | |
/// F17 key | |
F17(128), | |
/// F18 key | |
F18(129), | |
/// F19 key | |
F19(130), | |
/// F20 key | |
F20(131), | |
/// F21 key | |
F21(132), | |
/// F22 key | |
F22(133), | |
/// F23 key | |
F23(134), | |
/// F24 key | |
F24(135), | |
/// NUM LOCK key | |
NUMLOCK(144), | |
/// SCROLL LOCK key | |
SCROLL(145), | |
/// Left SHIFT key | |
LSHIFT(160), | |
/// Right SHIFT key | |
RSHIFT(161), | |
/// Left CONTROL key | |
LCONTROL(162), | |
/// Right CONTROL key | |
RCONTROL(163), | |
/// Left MENU key | |
LMENU(164), | |
/// Right MENU key | |
RMENU(165), | |
/// Windows 2000: Browser Back key | |
BROWSER_BACK(166), | |
/// Windows 2000: Browser Forward key | |
BROWSER_FORWARD(167), | |
/// Windows 2000: Browser Refresh key | |
BROWSER_REFRESH(168), | |
/// Windows 2000: Browser Stop key | |
BROWSER_STOP(169), | |
/// Windows 2000: Browser Search key | |
BROWSER_SEARCH(170), | |
/// Windows 2000: Browser Favorites key | |
BROWSER_FAVORITES(171), | |
/// Windows 2000: Browser Start and Home key | |
BROWSER_HOME(172), | |
/// Windows 2000: Volume Mute key | |
VOLUME_MUTE(173), | |
/// Windows 2000: Volume Down key | |
VOLUME_DOWN(174), | |
/// Windows 2000: Volume Up key | |
VOLUME_UP(175), | |
/// Windows 2000: Next Track key | |
MEDIA_NEXT_TRACK(176), | |
/// Windows 2000: Previous Track key | |
MEDIA_PREV_TRACK(177), | |
/// Windows 2000: Stop Media key | |
MEDIA_STOP(178), | |
/// Windows 2000: Play/Pause Media key | |
MEDIA_PLAY_PAUSE(179), | |
/// Windows 2000: Start Mail key | |
LAUNCH_MAIL(180), | |
/// Windows 2000: Select Media key | |
LAUNCH_MEDIA_SELECT(181), | |
/// Windows 2000: Start Application 1 key | |
LAUNCH_APP1(182), | |
/// Windows 2000: Start Application 2 key | |
LAUNCH_APP2(183), | |
/// Windows 2000: For the US standard keyboard, the ';:' key | |
OEM_1(186), | |
/// Windows 2000: For any country/region, the '+' key | |
OEM_PLUS(187), | |
/// Windows 2000: For any country/region, the ',' key | |
OEM_COMMA(188), | |
/// Windows 2000: For any country/region, the '-' key | |
OEM_MINUS(189), | |
/// Windows 2000: For any country/region, the '.' key | |
OEM_PERIOD(190), | |
/// Windows 2000: For the US standard keyboard, the '/?' key | |
OEM_2(191), | |
/// Windows 2000: For the US standard keyboard, the '`~' key | |
OEM_3(192), | |
/// Windows 2000: For the US standard keyboard, the '[{' key | |
OEM_4(219), | |
/// Windows 2000: For the US standard keyboard, the '\|' key | |
OEM_5(220), | |
/// Windows 2000: For the US standard keyboard, the ']}' key | |
OEM_6(221), | |
/// Windows 2000: For the US standard keyboard, | |
/// the 'single-quote/double-quote' key | |
OEM_7(222), | |
/// Windows 2000: Used for miscellaneous characters, | |
/// it can vary by keyboard. | |
OEM_8(223), | |
/// Windows 2000: Either the angle bracket key | |
/// or the backslash key on the RT 102-key keyboard | |
OEM_102(226), | |
/// Windows 95/98, Windows NT 4.0, Windows 2000: IME PROCESS key | |
PROCESSKEY(229), | |
/// Windows 2000: Used to pass Unicode characters | |
/// as if they were keystrokes. The VK_PACKET key is | |
/// the low word of a 32-bit Virtual Key value used | |
/// for non-keyboard input methods. For more information, | |
/// see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP | |
PACKET(231), | |
/// Attn key | |
ATTN(246), | |
/// CrSel key | |
CRSEL(247), | |
/// ExSel key | |
EXSEL(248), | |
/// Erase EOF key | |
EREOF(249), | |
/// Play key | |
PLAY(250), | |
/// Zoom key | |
ZOOM(251), | |
/// Reserved for future use | |
NONAME(252), | |
/// PA1 key | |
PA1(253), | |
/// Clear key | |
OEM_CLEAR(254); | |
/// Virtual key code. | |
const VK(this.code, {this.extendedKey = false}); | |
/// Returns the virtual key for the given [code]. | |
factory VK.fromCode(int code) => _map[code] ?? (throw ArgumentError('Invalid code: $code')); | |
/// Returns the virtual key for the given [code] or null if not found. | |
static VK? fromCodeOrNull(int code) => _map[code]; | |
/// The map of virtual keys. | |
static final Map<int, VK> _map = <int, VK>{ | |
for (final vk in VK.values) vk.code: vk, | |
}; | |
/// The virtual key code. | |
final int code; | |
/// Whether the key is extended. | |
final bool extendedKey; | |
/// The name of the virtual key. | |
String get hex => '0x${code.toRadixString(16).padLeft(2, '0')}'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment