1 /+ 2 Copyright Elias Batek 2017 - 2018. 3 Distributed under the Boost Software License, Version 1.0. 4 (See accompanying file LICENSE_1_0.txt or copy at 5 https://www.boost.org/LICENSE_1_0.txt) 6 +/ 7 module midigamepad.lib.keyboard; 8 9 import core.sys.windows.winbase; 10 import core.sys.windows.windef : HKL; 11 import core.sys.windows.winuser; 12 import std.conv : to; 13 14 /++ 15 Determines whether the specified vKey is an extended key or not 16 17 See_Also: 18 https://msdn.microsoft.com/en-us/library/windows/desktop/ms646267(v=vs.85).aspx#extended_key_flag 19 +/ 20 bool hasExtendedScancode(uint vkey) 21 { 22 switch (vkey) 23 { 24 case VK_INSERT: 25 case VK_DELETE: 26 case VK_HOME: 27 case VK_END: 28 case VK_PRIOR: 29 case VK_NEXT: 30 case VK_LEFT: 31 case VK_UP: 32 case VK_RIGHT: 33 case VK_DOWN: 34 case VK_NUMLOCK: 35 case VK_PRINT: 36 return true; 37 38 default: 39 return false; 40 } 41 } 42 43 /++ 44 Sends a keydown event to the OS 45 46 Params: 47 vKey = virtual key to press 48 +/ 49 void synthesizeKeyDown(uint vKey) 50 { 51 synthesize(Scancode(vKey.toScancode.to!ushort, vKey.hasExtendedScancode), false); 52 } 53 54 /++ 55 Sends a keyup event to the OS 56 57 Params: 58 vKey = virtual key to release 59 +/ 60 void synthesizeKeyUp(uint vKey) 61 { 62 synthesize(Scancode(vKey.toScancode.to!ushort, vKey.hasExtendedScancode), true); 63 } 64 65 /++ 66 Synthesizes a key press/release by sending its scancode 67 68 Params: 69 scancode = scancode of the key to press 70 71 up = true ... UP 72 false ... DOWN 73 +/ 74 void synthesize(Scancode scancode, bool up) @nogc nothrow 75 { 76 INPUT input = INPUT(); 77 input.type = INPUT_KEYBOARD; 78 79 input.ki.time = 0; 80 input.ki.wVk = 0; 81 input.ki.dwExtraInfo = 0; 82 83 input.ki.dwFlags = KEYEVENTF_SCANCODE; 84 if (scancode.extended) 85 { 86 input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; 87 } 88 if (up) 89 { 90 input.ki.dwFlags |= KEYEVENTF_KEYUP; 91 } 92 93 input.ki.wScan = scancode.scancode; 94 95 const uint rslt = SendInput(1, &input, INPUT.sizeof); 96 assert(rslt != 0); 97 } 98 99 /++ 100 Returns: The scancode of the specified vKey 101 +/ 102 uint toScancode(uint vKey) @nogc nothrow 103 { 104 static HKL keyboardLayout = null; 105 106 if (keyboardLayout is null) 107 { 108 keyboardLayout = GetKeyboardLayout(0); 109 } 110 return MapVirtualKeyEx(vKey, MAPVK_VK_TO_VSC_EX, keyboardLayout); 111 } 112 113 /++ 114 Manager class for a key press/release loop 115 +/ 116 final class KeyboardSynthesizerLoopMgr 117 { 118 import core.thread : Thread; 119 import core.time : dur; 120 import std.algorithm.mutation : remove; 121 import std.algorithm.searching : canFind, countUntil; 122 import std.range : empty; 123 124 private 125 { 126 Scancode[] _down; 127 Scancode[] _requestUp; 128 Scancode[] _requestDown; 129 130 Thread _loopThread; 131 bool _loopThreadKill; 132 long _loopThreadTimeout = 50; 133 } 134 135 public 136 { 137 @property 138 { 139 /++ 140 Returns: true = loop thread is running 141 +/ 142 bool isRunning() @nogc nothrow 143 { 144 return ((this._loopThread !is null) && this._loopThread.isRunning); 145 } 146 } 147 } 148 149 public 150 { 151 /++ 152 Adds a key that will be "hold down" by the loop 153 +/ 154 void press(Scancode scancode) nothrow pure @safe 155 { 156 if (!_down.canFind(scancode)) 157 this._requestDown ~= scancode; 158 } 159 160 /++ ditto +/ 161 void pressKey(uint vKey) 162 { 163 this.press(Scancode(vKey.toScancode.to!ushort, vKey.hasExtendedScancode)); 164 } 165 166 /++ 167 "Releases" a key that is "hold down" by the loop 168 +/ 169 void release(Scancode scancode) nothrow pure @safe 170 in 171 { 172 assert(this._down.canFind(scancode)); 173 } 174 body 175 { 176 if (!this._requestUp.canFind(scancode)) 177 this._requestUp ~= scancode; 178 } 179 180 /++ ditto +/ 181 void releaseKey(uint vKey) 182 { 183 this.release(Scancode(vKey.toScancode.to!ushort, vKey.hasExtendedScancode)); 184 } 185 186 /++ 187 Starts the sending loop thread 188 189 This will crash if the loop thread is already running. 190 Check the .isRunning property first or call .tryStart() instead. 191 +/ 192 void start() 193 in 194 { 195 assert(!this.isRunning); 196 } 197 body 198 { 199 this._loopThread.destroy(); 200 this._loopThread = new Thread(&loop); 201 this._loopThreadKill = false; 202 this._loopThread.start(); 203 } 204 205 /++ 206 Kills the sending loop thread 207 +/ 208 void stop() @nogc nothrow pure @safe 209 { 210 this._loopThreadKill = true; 211 } 212 213 /++ 214 "Releases" a key if it has been "held down" by the loop 215 216 Returns: 217 true = if the key has been "held down" (which means the release will get performed) 218 +/ 219 bool tryRelease(Scancode scancode) nothrow pure @safe 220 { 221 if (!this._down.canFind(scancode)) 222 return false; 223 224 release(scancode); 225 return true; 226 } 227 228 /++ ditto +/ 229 bool tryReleaseKey(uint vKey) 230 { 231 return tryRelease(Scancode(vKey.toScancode.to!ushort, vKey.hasExtendedScancode)); 232 } 233 234 /++ 235 Starts the sending loop thread if it is not already running 236 237 Returns: 238 true = loop thread got started 239 false = loop thread has already been running 240 +/ 241 bool tryStart() 242 { 243 if (this.isRunning) 244 return false; 245 246 this.start(); 247 return true; 248 } 249 } 250 251 private 252 { 253 void loop() 254 { 255 do 256 { 257 if (!this._requestUp.empty) 258 { 259 foreach (Scancode scancode; this._requestUp) 260 { 261 // send keyUp 262 synthesize(scancode, true); 263 264 // can only occurre once, so just remove its first occurrence 265 const ptrdiff_t idx = this._down.countUntil(scancode); 266 this._down = this._down.remove(idx); 267 } 268 269 this._requestUp = []; 270 } 271 272 if (!this._requestDown.empty) 273 { 274 this._down ~= this._requestDown; 275 this._requestDown = []; 276 } 277 278 foreach (Scancode scancode; this._down) 279 { 280 // send keyDown 281 synthesize(scancode, false); 282 } 283 284 // wait for a while 285 Thread.sleep(dur!"msecs"(this._loopThreadTimeout)); 286 } 287 while (!this._loopThreadKill); 288 } 289 } 290 } 291 292 /++ 293 Represents a keyboard scancode 294 +/ 295 struct Scancode 296 { 297 @nogc nothrow pure @safe: 298 299 /++ 300 Specifies whether the scancode is an extended one 301 +/ 302 bool extended; 303 304 /++ 305 Keyboard scancode 306 +/ 307 ushort scancode; 308 309 /++ 310 ctor 311 +/ 312 this(ushort scancode, bool extended) 313 { 314 this.scancode = scancode; 315 this.extended = extended; 316 } 317 }