How can I get the keyboard state in Linux? - c

I want to check if the user pressed down the Shift key when the program starts. (That means, press down the Shift key before the program is started) It's a simple console program, nothing related to X.
This maybe similar to the Win32 GetKeyboardState() function.
I want to know whether I can do this and how, but not any pros and cons with accessing the terminal directly.

I think there would be a way to do this. The thing is that you would have to read directly from the keyboard device. You would not be getting input from the terminal. I have the same problem. I have a program that runs (in the background) and I want to know if the user is holding down the shift key.
I believe this is possible and a place to start might be /dev/input/by-path/*-kbd.
This file does give input every time a key is pressed or reptadly if it is held down so it might be worth a look. (Try cat /dev/input/by-path/*-kbd)
If you do figure this out I would love to hear how you did it.
EDIT: I have found the solution
I have figured out how do do this. My program is as follows:
#include <stdlib.h>
#include <stdio.h>
#include <linux/input.h>
void usage ( int argc, char *argv[] )
{
printf("Usage:\n\t%s key\n\nvalid keys are:\n\tlshift\t- Left Shift key\n" , argv[0]);
exit(EXIT_FAILURE);
}
int main ( int argc, char *argv[], char *env[] )
{
if ( argc != 2 ) usage(argc, argv);
int key;
if ( strcmp(argv[1], "lshift") == 0 ) key = KEY_LEFTSHIFT;
else if ( strcmp(argv[1], "rshift") == 0 ) key = KEY_RIGHTSHIFT;
else if ( strcmp(argv[1], "lalt") == 0 ) key = KEY_LEFTALT;
else if ( strcmp(argv[1], "ralt") == 0 ) key = KEY_RIGHTALT;
else if ( strcmp(argv[1], "lctrl") == 0 ) key = KEY_LEFTCTRL;
else if ( strcmp(argv[1], "rctrl") == 0 ) key = KEY_RIGHTCTRL;
FILE *kbd = fopen("/dev/input/by-path/platform-i8042-serio-0-event-kbd", "r");
char key_map[KEY_MAX/8 + 1]; // Create a byte array the size of the number of keys
memset(key_map, 0, sizeof(key_map)); // Initate the array to zero's
ioctl(fileno(kbd), EVIOCGKEY(sizeof(key_map)), key_map); // Fill the keymap with the current keyboard state
int keyb = key_map[key/8]; // The key we want (and the seven others arround it)
int mask = 1 << (key % 8); // Put a one in the same column as out key state will be in;
return !(keyb & mask); // Returns true if pressed otherwise false
}
The info message is lacking (I'm too lazy). But essentially the first argument is compared to a list of keys and the appropriate key identifier is used. It returns true if the key is pressed and false if not.
Please Note
You will need to change the name of they keyboard device. I do not know of a way to find the default keyboard device. (if you know I would love to hear ;) )
This works beautifully: I use it to start the autostart of Xorg if I hold down the shift key.

AFAIK this cannot be done without Xlib (aka. X) with no root level permissions.
Using XQueryKeymap() will do what you want. however you pointed out that X cannot be used. Regardless, opening display connection will also be required.
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <stdbool.h>
#include <stdio.h>
int main()
{
Display* dpy = XOpenDisplay(NULL);
char keys_return[32];
XQueryKeymap( dpy, keys_return );
KeyCode kc2 = XKeysymToKeycode( dpy, XK_Shift_L );
bool bShiftPressed = !!( keys_return[ kc2>>3 ] & ( 1<<(kc2&7) ) );
printf("Shift is %spressed\n", bShiftPressed ? "" : "not ");
XCloseDisplay(dpy);
}

I have found a very simple way through gtk/gdk.
int main ( int argc, char *argv[], char *env[] )
{
gtk_init(&argc, &argv);
GdkModifierType button_state;
gdk_window_get_pointer(NULL, NULL, NULL, &button_state);
if(button_state & GDK_CONTROL_MASK) {
printf("ctrl key is pressed");
}
}

You can't.
The Shift key isn't considered as a character key, so even if you access the terminal directly, you won't be able to detect this key.
Maybe you shouldn't have to. Imagine for example that you are using a US keyboard where numbers are accessible on the top row without modifiers, and also checking for the Shift key. People with other keyboard layout may have to use Shift modifiers to access the numbers. If your program react to this Shift press, then your program is basically unusable. The same thing applies for other modifier keys : you may detect some of them only after a normal character key is pressed. Or worse, they may need to use the Shift key to use 'enter' to run your program.
Also, what Shift key do you want to monitor? the one on the local machine, or the one where the user is? remember that SSH exists and is commonly used to access a pseudoterminal remotely.
If you are root and want to monitor the Shift key on the local machine, you can read the evdev devices for events about the Shift key. But this is only possible because of automatic key repeating, so you won't detect a Shift key that is pressed right before running your program, but only a few second before.
Of course you can't do that on the remote machine, that would be a security flaw.
And anyway, why would you want to do that? Wouldn't an X application be the right thing to do in your case?

Related

ncurses "get_wch" function behavior

I try to understand how the get_wch function from ncurses works.
Here is a piece of code that I use to test it under TTY, terminator, and konsole:
#include <stdlib.h>
#include <curses.h>
#include <locale.h>
int main(void)
{
// initialize curses
initscr();
setlocale(LC_ALL, ""); // Just a check to see if something change with it
wint_t char_code;
int key_code = get_wch(&char_code);
char truc [20];
sprintf(truc, "%d / %d", key_code, char_code);
refresh();
getch();
endwin();
printf("%d\n", KEY_CODE_YES);
printf(truc);
printf("\n");
return 0;
}
When I hit a "classic" key like 'a' or '?', I get a char_code (UTF-8 code, I supposed). But when I hit a function key like F1 or F12, I get a char_code 27, and a key_code 0, except for F11 (key_code: KEY_CODE_YES, char_code: 410).
The documentation say that:
When get_wch, wget_wch, mvget_wch, and mvwget_wch functions successfully report the pressing of a function key, they return KEY_CODE_YES. When they successfully report a wide character, they return OK.
F1 to F12 are the so called "function keys" ? If I'm right, could you explain me why the function return 0 as a key_code when I hit a Fx key ?
You'll get KEY_CODE_YES only with keypad set to true, see keypad man page about that :
The keypad option enables the keypad of the user's terminal. If enabled (bf is TRUE),
the user can press a function key (such as an arrow key) and wgetch returns a single value
representing the function key, as in KEY_LEFT. If disabled (bf is FALSE), curses does not
treat function keys specially and the program has to interpret the escape sequences
itself. If the keypad in the terminal can be turned on (made to transmit) and off (made to
work locally), turning on this option causes the terminal keypad to be turned on when
wgetch is called. The default value for keypad is false.
And you're not getting keycode 0 but the success status OK from get_wch, see related manpage.
Without it, you'll have to get multiple char_code to get the key code like :
wint_t char_code;
wint_t char_code2 = 0, char_code3 = 0;
int key_code = get_wch(&char_code);
if (key_code == OK) key_code = get_wch(&char_code2);
if (key_code == OK) key_code = get_wch(&char_code3);
For function keys, you'll the escape code in char_code (0x27), the function code in char_code2 (0x4b) and the specific function key_code in char_code3.
EDIT As noticed by #dratenik, with xterm/VT220+ terminals, the F5-F12 function keys output four char_code, so you'll need to query another char_code when you get [ (0x5b) in char_code2 and 1 (0x31) in char_code3 to get the specific function keys key_code for theses function keys. You'll find the possible key codes in this useful link provided by #dratenik.

C xtest emitting key presses for every Unicode character

I wanted to make a program to simulate key presses. I think i am mostly done but i have done something wrong i guess because it is not doing what i expect it to do. I have made a small example program to illustrate the issue. The main problem is that if i want to generate capital letters it does not work with strings like 'zZ'. It is generating only small letters 'zz'. Although symbols like '! $ & _ >' etc. work fine (that require shift on my German keyboard layout) and even multi byte ones like 'πŸ’£' . What i am doing is this:
preamble:
So basically the main problem by emulating key presses is first the layout that changes from user to user and most importantly modifier keys. So if you go the naive route and get a keysym with XStringToKeysym() get a keycode from that keysym with XKeysymToKeycode() and fire that event its not working like most 'newcomers' would expect (like me). The problem here is, that multiple keysyms are mapped to the same keycode. Like the keysysm for 'a' and 'A' are mapped to the same keycode because they're on the same physikal button on your keyboard that is linked to that keycode. So if you go the route from above you end up with the same keycode although the keysyms are different but mapped to the same button/keycode. And there is usually no way around this because it is not clear how the 'A' came to existence in the first place. shift+a or caps+a or you have a fancy keyboard with an 'a' and 'A' button on it. The other problem is how do i emit key presses for buttons that are not even on the keyboard of that person running that application. Like what key is pressed on an english layout if i want to type a 'Γ„' (german umlaut). This does not work because XKeysymToKeycode() will not return a proper keycode for this because there is no keysym mapping for it with that layout.
my approach:
What i am tying to do to circumvent this is finding a keycode that is not being used. You have 255-8 keycodes at your disposal but a regular keyboard has only ~110 keys on it so there is usually some space left. I am trying to find one of those keycodes that are unmapped on the current layout and use it to assign my own keysyms on it. Then i get a keysym from my char i got by iterating over my string and pass it to XStringToKeysym() which gives me the appropriate keysym. In case of β€™πŸ’£β€™ that is in most cases not mapped to any keyboard layout i know of. So i map it to the unused keycode and press it with XTestFakeKeyEvent() and repeat that for every char in the string. This works great with all fancy glyph one can think of but it does not work with simple letters and i really don't know why :( in my debugging sessions keysyms and keycodes seem to be correct its just that XTestFakeKeyEvent() does not do the right things in that case. Its possible that i fucked something up at the keymapping part but i am not really sure whats the problem here and i hope someone has a good idea and can help me find a way to a working solution.
I am just using this unicode notation in the strings array because i don't want to deal with this in the example here. Just assume there is code producing this from an arbitrary input string.
be aware that the code below can ruin your keymapping in such a way that you're not able to type and use your keyboard anymore and need to restart your X-Server/PC ... i hope it does not in its current state (working fine here) just be aware if you fiddle with the code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>
#include <unistd.h>
//gcc -g enigo2.c -lXtst -lX11
int main(int argc, char *argv[])
{
Display *dpy;
dpy = XOpenDisplay(NULL);
//my test string already transformed into unicode
//ready to be consumed by XStringToKeysym
const char *strings[] = {
"U1f4a3",// πŸ’£
"U007A", //z
"U005A", //Z
"U002f", //'/'
"U005D", //]
"U003a", //:
"U002a", //*
"U0020", //' '
"U0079", //y
"U0059", //Y
"U0020", //' '
"U0031", //1
"U0021", //!
"U0020", //' '
"U0036", //6
"U0026", //&
"U0020", //' '
"U0034", //4
"U0024", //$
"U0020", //' '
"U002D", //-
"U005F", //_
"U0020", //' '
"U003C", //<
"U003E", //>
"U0063", //c
"U0043", //C
"U006f", //o
"U004f", //O
"U00e4", //Γ€
"U00c4", //Γ„
"U00fc", //ΓΌ
"U00dc", //Ü
};
KeySym *keysyms = NULL;
int keysyms_per_keycode = 0;
int scratch_keycode = 0; // Scratch space for temporary keycode bindings
int keycode_low, keycode_high;
//get the range of keycodes usually from 8 - 255
XDisplayKeycodes(dpy, &keycode_low, &keycode_high);
//get all the mapped keysyms available
keysyms = XGetKeyboardMapping(
dpy,
keycode_low,
keycode_high - keycode_low,
&keysyms_per_keycode);
//find unused keycode for unmapped keysyms so we can
//hook up our own keycode and map every keysym on it
//so we just need to 'click' our once unmapped keycode
int i;
for (i = keycode_low; i <= keycode_high; i++)
{
int j = 0;
int key_is_empty = 1;
for (j = 0; j < keysyms_per_keycode; j++)
{
int symindex = (i - keycode_low) * keysyms_per_keycode + j;
// test for debugging to looking at those value
// KeySym sym_at_index = keysyms[symindex];
// char *symname;
// symname = XKeysymToString(keysyms[symindex]);
if(keysyms[symindex] != 0) {
key_is_empty = 0;
} else {
break;
}
}
if(key_is_empty) {
scratch_keycode = i;
break;
}
}
XFree(keysyms);
XFlush(dpy);
usleep(200 * 1000);
int arraysize = 33;
for (int i = 0; i < arraysize; i++)
{
//find the keysym for the given unicode char
//map that keysym to our previous unmapped keycode
//click that keycode/'button' with our keysym on it
KeySym sym = XStringToKeysym(strings[i]);
KeySym keysym_list[] = { sym };
XChangeKeyboardMapping(dpy, scratch_keycode, 1, keysym_list, 1);
KeyCode code = scratch_keycode;
usleep(90 * 1000);
XTestFakeKeyEvent(dpy, code, True, 0);
XFlush(dpy);
usleep(90 * 1000);
XTestFakeKeyEvent(dpy, code, False, 0);
XFlush(dpy);
}
//revert scratch keycode
{
KeySym keysym_list[] = { 0 };
XChangeKeyboardMapping(dpy, scratch_keycode, 1, keysym_list, 1);
}
usleep(100 * 1000);
XCloseDisplay(dpy);
return 0;
}
When you send a single keysym for a given keycode to XChangeKeyboardMapping and it is a letter, it automatically fills correct upper and lower case equivalents for shift and capslock modifiers. That is, after
XChangeKeyboardMapping(dpy, scratch_keycode, 1, &keysym, 1);
the keycode map for scratch_keycode effectively changes (on my machine) to
tolower(keysym), toupper(keysym), tolower(keysym), toupper(keysym), tolower(keysym), toupper(keysym), 0, 0, 0, 0, ...
In order to inhibit this behaviour, send 2 identical keysyms per keycode:
KeySym keysym_list[2] = { sym, sym };
XChangeKeyboardMapping(dpy, scratch_keycode, 2, keysym_list, 1);
This will fill both shifted and unshifted positions with the same keysym.

Issues with repeated key checking with getch()

I am having issues with repeating key checking using a function that utilizes getch().
Here is a code example:
static char g_keybuffer[256];
_Bool IsKeyDown(char c)
{
char ch;
if(kbhit())
ch = getch();
if(ch == -32 || ch == 224)
{
ch = getch();
}
g_keybuffer[ch] = 1;
if(g_keybuffer[c] == 1)
{
g_keybuffer[c] = 0;
return 1;
}
return 0;
}
/*
*
*/
int main(int argc, char** argv) {
while(1)
{
if(IsKeyDown('a'))
{
printf("Test\n");
}
if(IsKeyDown('a'))
{
printf("Hello\n");
}
else if(IsKeyDown('b'))
{
printf("World\n");
}
Sleep(100);
}
return (EXIT_SUCCESS);
}
I know why the problem occurs. When a key is pressed, kbhit is true once per loop, and sets ch to the character retrieved from the buffer. When IsKeyDown is used, if it is equal to the parameter, the key in the buffer g_keybuffer is set equal to zero to avoid having a key be "down" infinitely. The problem with this is if you want to check if the same key is down more than once, only the first instance of IsKeyDown will be ran, with the rest being invalid due to the g_keybuffer of the key now being 0.
Does anyone know how I can change IsKeyDown to give it the ability to check the same key multiple times per looping? I'm stuck.
Your problem is because you are setting g_keybuffer[c] to 0 after you get a hit for the key state. I'm guessing you have done this to avoid getting the same result twice - but that is just a workaround. The only way to do what you want to do properly is to choose a library that is actually made to capture the keyboard state.
Most graphics libraries have functions for capturing keyboard states. I don't know of any solutions thought that don't involve a little overhead if you are just writing a small program.

Enumerating supplementary groups in a C Program

I'm wanting to get a list of supplementary groups for the user by sending a request through NSS. To my reckoning, the following program should let me enumerate all the groups (so I can then compare members):
#include <stdio.h>
#include <grp.h>
#include <stdlib.h>
struct group *groupStruct;
int main(){
setgrent();
while ( groupStruct=getgrent() )
printf("%s\n", groupStruct->gr_name);
endgrent();
return 0;
}
I'm basing this assumption on this part of the source code for id that gets executed with id -Gn (since that's the functionality I want to replicate). Looking at that it looks like it gets the list of groups via getugroups (0, NULL, username, gid) with getugroups() being defined in another file (essentially the same code found here). It looks like that is going through the same setgrent()/getgrent() procedure as above, so my feeling is that my simple program ought to enumerate the system's groups (instead, it only does the groups in /etc/group but I have winbind on this machine and id -Gn pulls in the winbind groups the user is a member of).
For posterity:
I still don't know why the id -Gn code works but not mine, but I think I fixed my own issue after a lot of back and forth. Basically I'm building a shared object and enumerating their current memberships by way of a helper program that uses initgroups/getgroups to set the running processes's (the helper executable) persona to the target user's default persona (what they would get after logging in. This is the full code of the helper program:
#include <stdio.h>
#include <unistd.h>
struct group *groupStruct;
int main(int argc, char *argv[]){
int numgroups, iter, retCode;
int numgroups_max = sysconf(_SC_NGROUPS_MAX) + 1;
gid_t groupList[numgroups_max];
if (argc != 2){
printf("Insufficient Arguments.\n");
return 1;
}
retCode=initgroups(argv[1], 0);
if (retCode != 0){
printf("Unspecified failure: %d\n\n", retCode);
return 1;
}
numgroups = getgroups(numgroups_max, groupList);
for (iter=0; iter <= numgroups; iter++){
if (iter != 0 && iter != numgroups )
printf(" ");
// "zero" means both "nothing more" and could be the root user's primary group, allow the first one through
if ( groupList[iter] == 0 && getuid() == 0 ){
if ( iter != 0 )
break;
}else if ( groupList[iter] == 0 )
break;
printf("%d", groupList[iter]);
}
return 0;
}
Username is hard coded just for testing purposes. After compiling and testing it produces the group ID's for the user. Changing the hard coded value (or pushing it into argv) resolves the problem. I pushed this into a helper executable because it's changing the running process's persona (at least the group membership portion).
I'll probably move it into the library's calling routine for performance/security (I can save the supplementary and primary groups before with getgroups and setgroups), but it was quicker for me to do a proof of concept in a separate executable.

Can't Read (!##$...and Capital Letters) from console with ReadConsoleInput

have wrote this app which reads input from console.
for(; ; )
{
GetNumberOfConsoleInputEvents(stdinInput, &numEvents);
if (numEvents != 0) {
INPUT_RECORD eventBuffer;
ReadConsoleInput(stdinInput, &eventBuffer, 1, &numEventsRead);
if (eventBuffer.EventType == KEY_EVENT) {
if(eventBuffer.Event.KeyEvent.bKeyDown)
{
printf("%c",eventBuffer.Event.KeyEvent.uChar.AsciiChar);
dataBuffer[bufferLen++] = eventBuffer.Event.KeyEvent.uChar.AsciiChar;
dataBuffer[bufferLen] = '\0';
if ( dataBuffer[bufferLen] == 99 || eventBuffer.Event.KeyEvent.uChar.AsciiChar == '\r' ) {
printf("User Wrote: %s\n",dataBuffer);
memset(dataBuffer,0,sizeof(dataBuffer));
bufferLen = 0;
}
}
}
}
}
It puts the data on a buffer and then it prints out the buffer. The problem occurs when im using Shift or CapsLock to write Capital letters or ! # # $ % characters. Then it prints out NOTHING.
Ive tried something with the VK_LSHIFT code but didn't worked.
Also if try to write something in other language than English it prints out something like this β–’β”œβ•žβ–’β”œβ•žβ–’β”œβ”‚β–’β”œβ”‚ It cannot recognize the other language.
Can someone give me a hint on how to fix those problems ?
Thanks!
ReadConsoleInput returns events for each keystroke. For example, if you type SHIFT+A to get a capital A then you'll receive four key events: SHIFT down, A down, A up, SHIFT up.
The SHIFT key does not have a corresponding ASCII code so eventBuffer.Event.KeyEvent.uChar.AsciiChar is set to zero. This zero terminates the string you are building in dataBuffer so you don't see anything typed after the SHIFT key.
The simplest fix is to ignore any key event with an ASCII code of zero.
Additionally, if you want this to work well with foreign languages you might do better to use ReadConsoleInputW and eventBuffer.Event.KeyEvent.uChar.UnicodeChar. Better yet, compile it all as a Unicode app.

Resources