I found an old VT100 Terminal editor written in C that I wanted to convert into a Windows Terminal console application, since the new windows terminal supports VT100 escape codes.
One of the questions I had while porting the Application to C under windows was:
How do I detect key sequences such as "Shift Arrow Key" and "Shift Ctrl Arrow Key". When I try to use getch() it doesn't seem to detect the shift key in combination with arrow keys.
Here's How I'm currently detecting key presses in my Console Application:
#define KEY_BACKSPACE 0x101
#define KEY_ESC 0x102
#define KEY_INS 0x103
#define KEY_DEL 0x104
#define KEY_LEFT 0x105
#define KEY_RIGHT 0x106
#define KEY_UP 0x107
#define KEY_DOWN 0x108
#define KEY_HOME 0x109
#define KEY_END 0x10A
#define KEY_ENTER 0x10B
#define KEY_TAB 0x10C
#define KEY_PGUP 0x10D
#define KEY_PGDN 0x10E
#define KEY_CTRL_LEFT 0x10F
#define KEY_CTRL_RIGHT 0x110
#define KEY_CTRL_UP 0x111
#define KEY_CTRL_DOWN 0x112
#define KEY_CTRL_HOME 0x113
#define KEY_CTRL_END 0x114
#define KEY_CTRL_TAB 0x115
#define KEY_SHIFT_LEFT 0x116
#define KEY_SHIFT_RIGHT 0x117
#define KEY_SHIFT_UP 0x118
#define KEY_SHIFT_DOWN 0x119
#define KEY_SHIFT_PGUP 0x11A
#define KEY_SHIFT_PGDN 0x11B
#define KEY_SHIFT_HOME 0x11C
#define KEY_SHIFT_END 0x11D
#define KEY_SHIFT_TAB 0x11E
#define KEY_SHIFT_CTRL_LEFT 0x11F
#define KEY_SHIFT_CTRL_RIGHT 0x120
#define KEY_SHIFT_CTRL_UP 0x121
#define KEY_SHIFT_CTRL_DOWN 0x122
#define KEY_SHIFT_CTRL_HOME 0x123
#define KEY_SHIFT_CTRL_END 0x124
#define KEY_F1 0x125
#define KEY_F3 0x126
#define KEY_F5 0x127
#define KEY_ALT_LEFT 0x128
#define KEY_ALT_RIGHT 0x129
#define KEY_ALT_UP 0x130
#define KEY_ALT_DOWN 0x131
#define KEY_ALT_HOME 0x132
#define KEY_ALT_END 0x133
#define KEY_ALT_TAB 0x134
#define KEY_ALT_PGUP 0x135
#define KEY_ALT_PGDN 0x136
#define KEY_UNKNOWN 0xFFF
#if defined(WIN32) || defined(_WIN64)
#include "windows.h"
#include "conio.h"
#endif
int getkey_win32() {
int c1;
int c2;
c1 = (int)getch();
// unmapable How to detect Shift Arrow Key??
//#define KEY_SHIFT_LEFT 0x116
//#define KEY_SHIFT_RIGHT 0x117
//#define KEY_SHIFT_UP 0x118
//#define KEY_SHIFT_DOWN 0x119
//#define KEY_SHIFT_PGUP 0x11A
//#define KEY_SHIFT_PGDN 0x11B
//#define KEY_SHIFT_HOME 0x11C
//#define KEY_SHIFT_END 0x11D
//#define KEY_SHIFT_CTRL_LEFT 0x11F
//#define KEY_SHIFT_CTRL_RIGHT 0x120
//#define KEY_SHIFT_CTRL_UP 0x121
//#define KEY_SHIFT_CTRL_DOWN 0x122
//#define KEY_SHIFT_CTRL_HOME 0x123
//#define KEY_SHIFT_CTRL_END 0x124
if (c1 == 0) {
c2 = (int)getch();
switch(c2) {
case 0x9b: return KEY_ALT_LEFT;
case 0x9d: return KEY_ALT_RIGHT;
case 0x98: return KEY_ALT_UP;
case 0xa0: return KEY_ALT_DOWN;
case 0x99: return KEY_ALT_PGUP;
case 0xa1: return KEY_ALT_PGDN;
case 0x97: return KEY_ALT_HOME;
case 0x9F: return KEY_ALT_END;
case 0x3b: return KEY_F1;
case 0x3d: return KEY_F3;
case 0x3f: return KEY_F5;
default: return KEY_UNKNOWN;
}
}
else if (c1 == 0xe0) {
c2 = (int)getch();
switch(c2) {
case 0x73: return KEY_CTRL_LEFT;
case 0x74: return KEY_CTRL_RIGHT;
case 0x8d: return KEY_CTRL_UP;
case 0x91: return KEY_CTRL_DOWN;
case 0x77: return KEY_CTRL_HOME;
case 0x75: return KEY_CTRL_END;
case 0x52: return KEY_INS;
case 0x53: return KEY_DEL;
case 0x4b: return KEY_LEFT;
case 0x4d: return KEY_RIGHT;
case 0x48: return KEY_UP;
case 0x50: return KEY_DOWN;
case 0x47: return KEY_HOME;
case 0x4f: return KEY_END;
case 0x49: return KEY_PGUP;
case 0x51: return KEY_PGDN;
default: return KEY_UNKNOWN;
}
}
else {
switch(c1) {
case 0x08: return KEY_BACKSPACE;
case 0x1b: return KEY_ESC;
case 0x0d: return KEY_ENTER;
case 0x09: return KEY_TAB;
default: return c1;
}
}
}
CodePudding user response:
Alas, getch()
is the wrong tool. You can use it, but you need to keep track of the shift state yourself. You can also use the GetKeyState()
or GetAsyncKeyState()
functions as exampled in the other answer.
The correct way to do this, until recently, was to use the Windows Console API function ReadConsoleInput()
, and again keep track.
These days, Windows Terminal / Windows Console applications are expected to follow the new Microsoft Ecosystem Roadmap which basically says “use virtual terminal sequences”. It isn’t a perfect replacement (not by any means) but it does have the added bonus of making your terminal application easily portable to *nixen.
There are two main references for virtual terminal input sequences:
- Thomas Dickey’s xterm reference
(Click here for the Special Keyboard Keys section) - Microsoft’s Console Virtual Terminal Sequences page
(Click here for the Input Sequences section)
There is one bit left: you must properly initialize the Windows Terminal / Windows Console to use the virtual terminal sequences. At the very minimum, I recommend something like:
HANDLE hstdin = GetStdHandle( STD_INPUT_HANDLE )
HANDLE hstdout = GetStdHandle( STD_OUTPUT_HANDLE )
DWORD input_mode, output_mode;
GetConsoleMode( hstdin, &input_mode );
GetConsoleMode( hstdout, &output_mode );
SetConsoleMode( hstdin, ENABLE_VIRTUAL_TERMINAL_INPUT | ENABLE_EXTENDED_FLAGS );
SetConsoleMode( hstdout, ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING );
UINT input_codepage = GetConsoleCP();
SetConsoleCP( 65001 );
setlocale( LC_ALL, ".UTF-8" );
printf(
"\033[\?1049h" // use alternate buffer
"\033[\?25l" ); // hide caret
You can add ENABLE_MOUSE_INPUT
to the input flags as well, if you wish. Read up on SetConsoleMode()
for more.
Finally, don’t forget to install an at_exit()
handler to restore things when you terminate.
printf(
"\033[\?1049l" // done with alternate buffer
"\033[\?25h" ); // show caret
SetConsoleCP( input_codepage );
SetConsoleMode( hstdin, input_mode );
SetConsoleMode( hstdout, output_mode );
There are all kinds of horrible ways your application can terminate as well. It is up to you whether or not you wish to install various other handlers for the random abnormal exit conditions you may encounter. Windows is pretty good about cleaning up after your program, but not perfect.
For a full-window application, it is also useful to track whether or not the user has resized the window.
There are a gazillion other details to worry about, but that should get you started.
Finally, as an unrelated but useful note, it is nice to know whether your console application was run from Explorer or not.
CodePudding user response:
Here's a program that I used to figure out how to detect Shift ArrowKey and Shift Ctrl ArrowKey. Just run the program and type the key combo to see the codes for it.
#include <windows.h>
#include <conio.h>
#include <stdio.h>
using namespace std;
int main()
{
int shiftkey;
int i1;
int i2;
while(1) {
i1 = (int)getch();
shiftkey = ((GetKeyState(VK_SHIFT) & 0x8000) == 0x8000);
printf("shift:%d i1:x ", shiftkey, i1);
if (i1 == 0xe0) {
i2 = (int)getch();
printf(" i2:x", i2);
}
else if (i1 == 0) {
i2 = (int)getch();
printf(" i2:x", i2);
}
printf("\n");
}
return 0;
}
CodePudding user response:
#define KEY_BACKSPACE 0x101
#define KEY_ESC 0x102
#define KEY_INS 0x103
#define KEY_DEL 0x104
#define KEY_LEFT 0x105
#define KEY_RIGHT 0x106
#define KEY_UP 0x107
#define KEY_DOWN 0x108
#define KEY_HOME 0x109
#define KEY_END 0x10A
#define KEY_ENTER 0x10B
#define KEY_TAB 0x10C
#define KEY_PGUP 0x10D
#define KEY_PGDN 0x10E
#define KEY_CTRL_LEFT 0x10F
#define KEY_CTRL_RIGHT 0x110
#define KEY_CTRL_UP 0x111
#define KEY_CTRL_DOWN 0x112
#define KEY_CTRL_HOME 0x113
#define KEY_CTRL_END 0x114
#define KEY_CTRL_TAB 0x115
#define KEY_SHIFT_LEFT 0x116
#define KEY_SHIFT_RIGHT 0x117
#define KEY_SHIFT_UP 0x118
#define KEY_SHIFT_DOWN 0x119
#define KEY_SHIFT_PGUP 0x11A
#define KEY_SHIFT_PGDN 0x11B
#define KEY_SHIFT_HOME 0x11C
#define KEY_SHIFT_END 0x11D
#define KEY_SHIFT_TAB 0x11E
#define KEY_SHIFT_CTRL_LEFT 0x11F
#define KEY_SHIFT_CTRL_RIGHT 0x120
#define KEY_SHIFT_CTRL_UP 0x121
#define KEY_SHIFT_CTRL_DOWN 0x122
#define KEY_SHIFT_CTRL_HOME 0x123
#define KEY_SHIFT_CTRL_END 0x124
#define KEY_F1 0x125
#define KEY_F3 0x126
#define KEY_F5 0x127
#define KEY_SHIFT_F1 0x200
#define KEY_SHIFT_F3 0x201
#define KEY_SHIFT_F5 0x202
#define KEY_ALT_LEFT 0x203
#define KEY_ALT_RIGHT 0x204
#define KEY_ALT_UP 0x205
#define KEY_ALT_DOWN 0x206
#define KEY_ALT_HOME 0x207
#define KEY_ALT_END 0x208
#define KEY_ALT_TAB 0x209
#define KEY_ALT_PGUP 0x20A
#define KEY_ALT_PGDN 0x20B
#define KEY_SHIFT_ALT_LEFT 0x20C
#define KEY_SHIFT_ALT_RIGHT 0x20D
#define KEY_SHIFT_ALT_UP 0x20E
#define KEY_SHIFT_ALT_DOWN 0x20F
#define KEY_SHIFT_ALT_HOME 0x210
#define KEY_SHIFT_ALT_END 0x211
#define KEY_SHIFT_ALT_TAB 0x212
#define KEY_SHIFT_ALT_PGUP 0x213
#define KEY_SHIFT_ALT_PGDN 0x214
#define KEY_SHIFT_INS 0x221
#define KEY_SHIFT_DEL 0x222
#define KEY_SHIFT_CTRL_PGUP 0x223
#define KEY_SHIFT_CTRL_PGDN 0x224
#define KEY_CTRL_PGUP 0x225
#define KEY_CTRL_PGDN 0x226
#define KEY_SHIFT_CTRL_INS 0x227
#define KEY_CTRL_INS 0x228
#define KEY_SHIFT_CTRL_DEL 0x229
#define KEY_CTRL_DEL 0x22A
#define KEY_SHIFT_ESC 0x22B
#define KEY_SHIFT_ENTER 0x22C
#define KEY_UNKNOWN 0xFFF
#if defined(WIN32) || defined(_WIN64)
#include "windows.h"
#include "conio.h"
int getkey__windows_console_app() {
int c1;
int c2;
int shift;
c1 = (int)getch();
shift = ((GetKeyState(VK_SHIFT) & 0x8000) == 0x8000);
if (c1 == 0) {
c2 = (int)getch();
switch(c2) {
case 0x3b: return (shift) ? KEY_SHIFT_F1 : KEY_F1;
case 0x3d: return (shift) ? KEY_SHIFT_F3 : KEY_F3;
case 0x3f: return (shift) ? KEY_SHIFT_F5 : KEY_F5;
case 0x9b: return (shift) ? KEY_SHIFT_ALT_LEFT : KEY_ALT_LEFT;
case 0x9d: return (shift) ? KEY_SHIFT_ALT_RIGHT : KEY_ALT_RIGHT;
case 0x98: return (shift) ? KEY_SHIFT_ALT_UP : KEY_ALT_UP;;
case 0xa0: return (shift) ? KEY_SHIFT_ALT_DOWN : KEY_ALT_DOWN;
case 0x99: return (shift) ? KEY_SHIFT_ALT_PGUP : KEY_ALT_PGUP;
case 0xa1: return (shift) ? KEY_SHIFT_ALT_PGDN : KEY_ALT_PGDN;
case 0x97: return (shift) ? KEY_SHIFT_ALT_HOME : KEY_ALT_HOME;
case 0x9F: return (shift) ? KEY_SHIFT_ALT_END : KEY_ALT_END;
default: return KEY_UNKNOWN;
}
}
else if (c1 == 0xe0) {
c2 = (int)getch();
switch(c2) {
case 0x4b: return (shift) ? KEY_SHIFT_LEFT : KEY_LEFT;
case 0x4d: return (shift) ? KEY_SHIFT_RIGHT : KEY_RIGHT;
case 0x48: return (shift) ? KEY_SHIFT_UP : KEY_UP;
case 0x50: return (shift) ? KEY_SHIFT_DOWN : KEY_DOWN;
case 0x49: return (shift) ? KEY_SHIFT_PGUP : KEY_PGUP;
case 0x51: return (shift) ? KEY_SHIFT_PGDN : KEY_PGDN;
case 0x52: return (shift) ? KEY_SHIFT_INS : KEY_INS;
case 0x53: return (shift) ? KEY_SHIFT_DEL : KEY_DEL;
case 0x47: return (shift) ? KEY_SHIFT_HOME : KEY_HOME;
case 0x4f: return (shift) ? KEY_SHIFT_END : KEY_END;
case 0x73: return (shift) ? KEY_SHIFT_CTRL_LEFT : KEY_CTRL_LEFT;
case 0x74: return (shift) ? KEY_SHIFT_CTRL_RIGHT : KEY_CTRL_RIGHT;
case 0x8d: return (shift) ? KEY_SHIFT_CTRL_UP : KEY_CTRL_UP;
case 0x91: return (shift) ? KEY_SHIFT_CTRL_DOWN : KEY_CTRL_DOWN;
case 0x86: return (shift) ? KEY_SHIFT_CTRL_PGUP : KEY_CTRL_PGUP;
case 0x76: return (shift) ? KEY_SHIFT_CTRL_PGDN : KEY_CTRL_PGDN;
case 0x92: return (shift) ? KEY_SHIFT_CTRL_INS : KEY_CTRL_INS;
case 0x93: return (shift) ? KEY_SHIFT_CTRL_DEL : KEY_CTRL_DEL;
case 0x77: return (shift) ? KEY_SHIFT_CTRL_HOME : KEY_CTRL_HOME;
case 0x75: return (shift) ? KEY_SHIFT_CTRL_END : KEY_CTRL_END;
default: return KEY_UNKNOWN;
}
}
else {
switch(c1) {
case 0x08: return KEY_BACKSPACE;
case 0x1b: return (shift) ? KEY_SHIFT_ESC : KEY_ESC;
case 0x0d: return (shift) ? KEY_SHIFT_ENTER : KEY_ENTER;
case 0x09: return (shift) ? KEY_SHIFT_TAB : KEY_TAB;
default: return c1;
}
}
}
#endif