1 module sily.terminal.input; 2 3 import std.conv: to; 4 import std.algorithm.searching: canFind; 5 6 import sily.queue; 7 import sily.terminal; 8 import sily.vector: uvec2; 9 10 private Queue!Input inputQueue = Queue!Input(); 11 12 /// Add normal discard? 13 /// Returns last element and removes it from queue if `remove` is true 14 Input peekEvent(bool remove = true)() { 15 if (inputQueue.empty) return InputEvent(); 16 if (remove) { 17 return inputQueue.pop(); 18 } else { 19 return inputQueue.front; 20 } 21 } 22 23 /// Returns true if there's still input in buffer 24 bool queueEmpty() { 25 return inputQueue.empty; 26 } 27 28 /// Clears input queue 29 void discardAll() { 30 inputQueue.clear(); 31 } 32 33 bool queueHas(Input key) { 34 Input[] events = inputQueue.toArray(); 35 foreach (e; events) { 36 if (e.isKey(key)) { 37 return true; 38 } 39 } 40 return false; 41 } 42 43 /// Buffers input from getch 44 /// Returns: true if operation was sucessfull 45 bool pollEvent() { 46 if (!kbhit()) return false; 47 48 // 0: ctrl + 2 49 // 1 - 26: ctrl + [a-z] 50 // 8: ^H or ctrl + backspace 51 // 9: ^I or tab 52 // 13: ^M or enter 53 // 27: esc, ctrl + [ 54 // 27+27: alt + esc 55 // 28: ctrl + \ 56 // 29: ctrl + ] 57 // 30: ctrl + shift + 6 (^^), ctrl + shift + ` (^~) 58 // 31: ctrl + shift + - (^_) 59 // 32 - 126: look ascii table 60 // 127: del (backspace), ctrl + shift + / 61 // 27 [65-90]: alt + shift + [a-z] 62 // 27 [97-122]: alt + [a-z] 63 64 // 27 91 [65-68]: U,D,R,L 65 // 27 79 [80-83]: F[1-4] 66 // 27 91 49 [53, 55-57] 126: F[5-8] 67 // 27 91 50 [48,49,51,52] 126: F[9-12] 68 // 27 91 49 50 [65-68]: Shift + U,D,R,L 69 // 27 91 49 53 [65-68]: ^ + U,D,R,L 70 // 27 91 49 54 [65-68]: ^ + shift + U,D,R,L 71 // 27 91 51 126: del 72 // 27 91 [70, 72]: end, home 73 // 27 91 49 59 51 80: meta (win) 74 // TODO: figure out ctrl+shift 75 // TODO: figure out meta 76 // TODO: figure out scroll and middle click? 77 // TODO: figure out shift combos 78 // TODO: mouse? 79 // TODO: ctrl + numbers 80 81 // \e[A: A-D UDRL 82 83 // \e?: ?=97-122 alt + a-z 84 // \e?: ?=1-26 ctrl + alt + a-z 85 // \e?: ?=65-90 shift + alt + a-z 86 // \e[X;6u: X=97-122 ctrl + shift + a-z 87 // \e[X;8u: X=97-122 ctrl + shift + alt + a-z 88 // \e[X;9u: same, win + a-z 89 // \e[X;10u: same, win + shift + a-z 90 // \e[X;11u: same, win + alt + a-z 91 // \e[X;12u: same, win + alt + shift + a-z 92 // \e[X;13u: same, win + ctrl + a-z 93 // \e[X;14u: same, win + ctrl + shift + a-z 94 // \e[X;15u: same, win + ctrl + alt + a-z 95 // \e[X;16u: same, win + ctrl + shift + alt + a-z 96 97 // \eOP: f1 98 // \eOQ: f2 99 // \eOR: f3 100 // \eOS: f4 101 // \e[1;XY: X=look combos, Y=PQRS 102 103 // \e[X~: x=[15,17-21,23,24]: f5-12 104 105 // \e[1;XA: A-D, X = 2!s, 3!a, 4!as, 5!c, 6!cs, 7!ca, 8!csa, for meta look normal keys 106 string seq = ""; 107 while (kbhit()) { 108 seq ~= cast(char) getch(); 109 } 110 111 if (seq.length == 1) { 112 int key = cast(int) seq[0]; 113 114 // Control letters / Control sequences 115 if (key >= 1 && key <= 26) inputQueue.push(ikey(key + 96, Mod.c)); 116 // Shift letters 117 if (key >= 65 && key <= 90) inputQueue.push(ikey(key + 32, Mod.s)); 118 // Normal letters 119 if (key >= 97 && key <= 122) inputQueue.push(ikey(key)); 120 // Duplicates (same as some control seq) 121 if (key == 8) inputQueue.push(ikey(Key.backspace, Mod.c)); 122 if (key == 9) inputQueue.push(ikey(Key.tab)); 123 if (key == 13) inputQueue.push(ikey(Key.enter)); 124 if (key == 27) { 125 inputQueue.push(ikey(Key.escape)); 126 inputQueue.push(ikey(Key.leftBracket, Mod.c)); 127 } 128 // Space 129 if (key == 32) inputQueue.push(ikey(Key.space)); 130 // Numbers 131 if (key >= 48 && key <= 57) inputQueue.push(ikey(key)); 132 // TODO: Symbols 133 return true; 134 } 135 136 advaceMouseAll(); 137 138 // sequence 139 if (seq.length >= 2 && seq[0] == 27) { 140 if (seq[1] == '[') { 141 import std.stdio; 142 // normal sequence 143 144 // \e[96;x;yM - mouse wheel up 145 // \e[97;x;yM - mouse wheel down 146 // \e[32;x;yM - lmb 147 // \e[33;x;yM - mmb 148 // \e[34;x;yM - rmb 149 // \e[160;x;yM - mbb 150 // \e[161;x;yM - mfb 151 // 012345678 152 // 123456789 153 // e[96;...M 154 // e[161;...M 155 156 if (seq.length > 5 && seq[$-1] == 'M') { 157 string sq = seq[2..5]; 158 if (sq == "96;") { 159 advanceMouse(Button.wheelUp, true); 160 inputQueue.push(ikey(Key.mouseWheelUp, Mod.n, mouseButtonState(Button.wheelUp))); 161 } 162 if (sq == "97;") { 163 advanceMouse(Button.wheelDown, true); 164 inputQueue.push(ikey(Key.mouseWheelDown, Mod.n, mouseButtonState(Button.wheelDown))); 165 } 166 if (sq == "32;") { 167 advanceMouse(Button.left, true); 168 inputQueue.push(ikey(Key.mouseLeft, Mod.n, mouseButtonState(Button.left))); 169 } 170 if (sq == "33;") { 171 advanceMouse(Button.middle, true); 172 inputQueue.push(ikey(Key.mouseMiddle, Mod.n, mouseButtonState(Button.middle))); 173 } 174 if (sq == "34;") { 175 advanceMouse(Button.right, true); 176 inputQueue.push(ikey(Key.mouseRight, Mod.n, mouseButtonState(Button.right))); 177 } 178 if (["96;", "97;", "32;", "33;", "34;"].canFind(sq)) { 179 int x = 0; 180 int y = 0; 181 bool isX = true; 182 string tmp = ""; 183 foreach (ch; seq[5..$]) { 184 if (ch == ';') { 185 isX = false; 186 x = tmp.to!int - 1; 187 tmp = ""; 188 continue; 189 } 190 if (ch == 'M') break; 191 tmp ~= ch; 192 } 193 y = tmp.to!int - 1; 194 lastMousePos = uvec2(x, y); 195 } 196 } 197 198 } else 199 if (seq.length == 3 && seq[1] == 'O') { 200 // f1-4 201 switch (seq[2]) { 202 case 'P': inputQueue.push(ikey(Key.f1)); break; 203 case 'Q': inputQueue.push(ikey(Key.f2)); break; 204 case 'R': inputQueue.push(ikey(Key.f3)); break; 205 case 'S': inputQueue.push(ikey(Key.f4)); break; 206 default: break; 207 } 208 } else { 209 // letters 210 int key = cast(int) seq[1]; 211 // Control letters / Control sequences 212 if (key >= 1 && key <= 26) inputQueue.push(ikey(key + 96, Mod.ca)); 213 // Shift letters 214 if (key >= 65 && key <= 90) inputQueue.push(ikey(key + 32, Mod.sa)); 215 // Normal letters 216 if (key >= 97 && key <= 122) inputQueue.push(ikey(key, Mod.a)); 217 } 218 return true; 219 } 220 221 222 return false; 223 } 224 225 /// Returns new InputKey with set mod keys 226 Input ikey(uint key, uint mod = Mod.n, ButtonState state = ButtonState.press) { 227 Key enumKey = to!Key(key); 228 return ikey(enumKey, mod, state); 229 } 230 /// Ditto 231 Input ikey(Key key, uint mod = Mod.n, ButtonState state = ButtonState.press) { 232 // return Input(key, mod.hasFlag(Mod.c), mod.hasFlag(Mod.s), mod.hasFlag(Mod.a), mod.hasFlag(Mod.m)); 233 return Input(key, mod, state); 234 } 235 236 private bool hasFlag(uint flags, uint flag) { 237 return (flags & flag) == flag; 238 } 239 240 /// Input mod keys (ctrl, shift, alt) 241 alias Mod = InputMod; 242 /// Ditto 243 enum InputMod: uint { 244 /// No key 245 none = 0b0000, 246 /// Ditto 247 n = 0b0000, 248 /// Control 249 ctrl = 0b0001, 250 /// Ditto 251 c = 0b0001, 252 /// Shift 253 shift = 0b0010, 254 /// Ditto 255 s = 0b0010, 256 /// Alt (command) 257 alt = 0b0100, 258 /// Ditto 259 a = 0b0100, 260 /// Meta (win/option) 261 meta = 0b1000, 262 /// Ditto 263 m = 0b1000, 264 /// Control + Shift 265 cs = 0b0011, 266 /// Control + Alt (command) 267 ca = 0b0101, 268 /// Shift + Alt (command) 269 sa = 0b0110, 270 /// Control + Shift + Alt (command) 271 csa = 0b0111, 272 /// Meta + Control 273 mc = 0b1001, 274 /// Meta + Alt 275 ma = 0b1100, 276 /// Meta + Shift 277 ms = 0b1010, 278 /// Meta + Control + Shift 279 mcs = 0b1011, 280 /// Meta + Control + Alt 281 mca = 0b1101, 282 /// Meta + Shift + Alt 283 msa = 0b1110, 284 /// Meta + Control + Shift + Alt 285 mcsa = 0b1111, 286 /// All modifiers 287 all = 0b1111 288 } 289 290 /// Input event (keypress) 291 alias Input = InputEvent; 292 /// Ditto 293 struct InputEvent { 294 /// Pressed key 295 public Key key = Key.none; 296 // /// Control pressed 297 // public bool ctrl = false; 298 // /// Shift pressed 299 // public bool shift = false; 300 // /// Alt (command) pressed 301 // public bool alt = false; 302 // /// Meta (win/option) pressed 303 // public bool meta = false; 304 public uint mod = Mod.n; 305 306 public ButtonState state = ButtonState.press; 307 308 bool opEquals()(in Input b) const { 309 // return key == b.key && ctrl == b.ctrl && shift == b.shift && alt == b.alt && meta == b.meta; 310 return key == b.key && mod == b.mod; 311 } 312 313 /// Returns hash 314 size_t toHash() const @safe nothrow { 315 return typeid(this).getHash(&this); 316 } 317 318 /// Checks if two inputs are same 319 bool isKey(Input b) { 320 return this == b; 321 } 322 323 /// Checks if key has modifier m 324 bool hasMod(Mod m) { 325 return mod.hasFlag(m); 326 } 327 } 328 329 // 0 - released, 1 - pressing, 2 - pressed, 3 - releasing 330 // wheels always on 0:1 331 private ubyte[Button] mouseState; 332 333 static this() { 334 mouseState = [ 335 Button.left: 0, 336 Button.middle: 0, 337 Button.right: 0, 338 Button.wheelUp: 0, 339 Button.wheelDown: 0, 340 Button.forward: 0, 341 Button.backward: 0 342 ]; 343 } 344 345 private void advanceMouse(Button b, bool press = false) { 346 if (mouseState[b] == 0 && press) { 347 mouseState[b] = 1; 348 } else 349 if (mouseState[b] == 1) { 350 mouseState[b] = 2; 351 } else 352 if (mouseState[b] == 2 && press) { 353 mouseState[b] = 3; 354 } else 355 if (mouseState[b] == 3) { 356 mouseState[b] = 0; 357 } 358 359 if (b == Button.wheelUp || b == Button.wheelDown) { 360 if (mouseState[b] > 1) mouseState[b] = 0; 361 } 362 } 363 364 ButtonState mouseButtonState(Button b) { 365 return cast(ButtonState) mouseState[b]; 366 } 367 368 private void advaceMouseAll() { 369 foreach (k; mouseState.keys) { 370 advanceMouse(k); 371 } 372 } 373 374 private uvec2 lastMousePos = uvec2(1); 375 376 uvec2 mousePosition() { 377 return lastMousePos; 378 } 379 380 /// Mouse buttons 381 alias Button = MouseButton; 382 /// Ditto 383 enum MouseButton { 384 left, middle, right, wheelUp, wheelDown, forward, backward 385 } 386 387 /// Mouse button state 388 alias ButtonState = MouseButtonState; 389 /// Ditto 390 enum MouseButtonState: ubyte { 391 none = 0, press, hold, release 392 } 393 394 /// Normal input keys 395 alias Key = InputKey; 396 /// Ditto 397 enum InputKey { 398 none = -1, 399 quote, equals, comma, minus, slash, colon, period, leftBracket, rightBracket, 400 backslash, grave, up, down, left, right, meta, 401 f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, 402 enter, tab, backspace, escape, 403 404 space = 32, 405 num0 = 48, num1, num2, num3, num4, num5, num6, num7, num8, num9, 406 a = 97, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, 407 mouseWheelUp = 256, mouseWheelDown, mouseLeft, mouseMiddle, mouseRight 408 409 } 410 411 import std.stdio: write; 412 413 /// Enables/disables mouse input capture 414 void mouseEnable() { 415 write("\033[?1000;1006;1015h"); 416 } 417 /// Ditto 418 void mouseDisable() { 419 write("\033[?1000;1006;1015l"); 420 } 421 422