How to tell which keyboard was used to press a key?

15,461

Solution 1

More digging revealed another solution using plain Bash and a normal user account. Script:

#!/usr/bin/env bash

set -o errexit -o nounset -o noclobber -o pipefail

# Remove leftover files and processes on exit
trap 'rm --recursive -- "$dir"; kill -- -$$' EXIT
dir="$(mktemp --directory)"
cd "$dir"

# Log key presses to file
xinput --list --id-only | while read id
do
    # Only check devices linked to an event source
    if xinput --list-props "$id" | grep --quiet --extended-regexp '^\s+Device Node.*/dev/input/event'
    then
        xinput test "$id" > "$id" &
    fi
done

# Check for key presses
while sleep 0.1
do
    for file in *
    do
        if [[ -s "$file" ]]
        then
            echo "$file"
            exit
        fi
    done
done

Solution 2

Disable device

Here's one idea towards identifying which keyboard is which. You can use the command xinput to enable and disable devices.

Example

$ xinput list
⎡ Virtual core pointer                      id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
⎜   ↳ SynPS/2 Synaptics TouchPad                id=12   [slave  pointer  (2)]
⎜   ↳ TPPS/2 IBM TrackPoint                     id=13   [slave  pointer  (2)]
⎜   ↳ Logitech USB Receiver                     id=9    [slave  pointer  (2)]
⎜   ↳ Logitech USB Receiver                     id=10   [slave  pointer  (2)]
⎣ Virtual core keyboard                     id=3    [master keyboard (2)]
    ↳ Virtual core XTEST keyboard               id=5    [slave  keyboard (3)]
    ↳ Power Button                              id=6    [slave  keyboard (3)]
    ↳ Video Bus                                 id=7    [slave  keyboard (3)]
    ↳ Sleep Button                              id=8    [slave  keyboard (3)]
    ↳ AT Translated Set 2 keyboard              id=11   [slave  keyboard (3)]
    ↳ ThinkPad Extra Buttons                    id=14   [slave  keyboard (3)]

The above output shows the various devices that I have on my Thinkpad laptop. I only have 1 keyboard attached, this one:

    ↳ AT Translated Set 2 keyboard              id=11   [slave  keyboard (3)]

Now take a look at the properties available through this device:

$ xinput list-props "AT Translated Set 2 keyboard"
Device 'AT Translated Set 2 keyboard':
    Device Enabled (124):   1
    Coordinate Transformation Matrix (126): 1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.

From the above you can see that it's enabled, so let's disable it:

$ xinput set-prop "AT Translated Set 2 keyboard" "Device Enabled" 0

To enable it:

$ xinput set-prop "AT Translated Set 2 keyboard" "Device Enabled" 1

The idea?

You could enable disable one of the keyboards using this command to determine which one you're on.

References

Solution 3

The question sounds a bit contradictory since you're citing X tools but ask for a solution that "ideally should work without X".

About your 4th finding: xinput will give you the correspondence

$ xinput list-props 11
Device 'AT Translated Set 2 keyboard':
    Device Enabled (145):   1
    Coordinate Transformation Matrix (147): 1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 1.000000
    Device Product ID (266):    1, 1
    Device Node (267):  "/dev/input/event0"

at least with the following version

$ xinput --version
xinput version 1.6.1
XI version on server: 2.3

First step: detecting the keyboard event device in C ---
#include <stdio.h>
//#include <unistd.h>
#include <fcntl.h>
#include <linux/input.h>

// typical use : sudo ./a.out /dev/input/event*
int main (int argc, char *argv[])
{
  struct input_event ev[64];
  int fd[argc],rd,idev,value, size = sizeof (struct input_event);
  char name[256] = "Unknown";

  if(argc==1) return -1;

  int ndev=1;
  while(ndev<argc && (fd[ndev] = open (argv[ndev], O_RDONLY|O_NONBLOCK)) != -1){
    ndev++;
  }
  fprintf (stderr,"Found %i devices.\n", ndev);
  if(ndev==1) return -1;

  while (1){
    for(idev=1; idev<argc; idev++){
      if( (rd=read (fd[idev], ev, size * 64)) >= size){
      value = ev[0].value;
      if (value != ' ' && ev[1].value == 1 && ev[1].type == 1){
        ioctl (fd[idev], EVIOCGNAME (sizeof (name)), name);
        printf ("%s\n", name);
        return idev;
      }
      }
    }
//    sleep(1);
  }
  return -1;
}

Many thanks to this page. I've stripped most safety checks from the code I borrowed there, for clarity, in real code you probably want them.

Note that key presses are echoed, so you may indeed want to kindly ask the user to hit a modifier key (Shift, Control...) rather than any key.

Second step: use xinput to get the X ID from the device name

Compile the above C source and use this way:

xinput list --id-only "keyboard:$(sudo ./a.out /dev/input/event*)"

Share:
15,461

Related videos on Youtube

l0b0
Author by

l0b0

Author, The newline Guide to Bash Scripting (https://www.newline.co/courses/newline-guide-to-bash-scripting). Hobby (https://gitlab.com/victor-engmark) &amp; work software developer.

Updated on September 18, 2022

Comments

  • l0b0
    l0b0 over 1 year

    I frequently work on pairing stations where there are multiple keyboards installed. I can use setxkbmap with -device <ID> to set the layout for a specific keyboard (using an ID from xinput), but often it's not obvious which keyboard I'm at. It would be better to avoid the back-and-forth of trying both keyboards, so I'd like to write a quick tool to get this information for setxkbmap. I'd expect a typical use case like the following:

    $ setxkbmap -device "$(get-keyboard-id)" -layout gb
    Press Enter to detect keyboard ID
    

    Which interface provides this information on Linux? Ideally it should work without X, but that's not a requirement (there doesn't seem to be many tools which support this without X).


    Findings so far:

    • Linux must know which keyboard I'm typing on to support different layouts for multiple keyboards simultaneously.
    • xinput → list.c → list_xi2XIQueryDevice provides device IDs usable by setxkbmap.
    • showkey and xev don't print keyboard IDs.
    • xinput list-props $ID shows where keyboard events are sent. However, using code from another answer it seems this device doesn't print anything to identify the keyboard.
    • One almost possible solution is to run xinput --test <ID> & for each keyboard ID and see which one returns something first. The problem with that is figuring out which "keyboards" are actually keyboards:

      $ xinput | grep keyboard
      ⎣ Virtual core keyboard                         id=3    [master keyboard (2)]
          ↳ Virtual core XTEST keyboard               id=5    [slave  keyboard (3)]
          ↳ Power Button                              id=6    [slave  keyboard (3)]
          ↳ Video Bus                                 id=7    [slave  keyboard (3)]
          ↳ Power Button                              id=8    [slave  keyboard (3)]
          ↳ Sleep Button                              id=9    [slave  keyboard (3)]
          ↳ WebCam SC-13HDL10931N                     id=10   [slave  keyboard (3)]
          ↳ AT Translated Set 2 keyboard              id=11   [slave  keyboard (3)]
      
    • Admin
      Admin over 10 years
      Perhaps you're looking for MPX.
    • Admin
      Admin over 10 years
      @IgnacioVazquez-Abrams Isn't that a massively more complicated solution?
    • Admin
      Admin almost 8 years
      "it seems this device doesn't print anything to identify the keyboard": what do you mean? If you less -f /dev/input/eventX and hit a key on the corresponding keyboard, you should see "garbage" showing up, so your keypresses are indeed directed into one dev file and not the others.
    • Admin
      Admin almost 8 years
      Have you tried this (referenced in another answer of that other question you cite)?
    • Admin
      Admin almost 8 years
      Edit: Note you have to add fflush(stdout); after the printf to see output as soon as keys are pressed. But since you only want to test for a keypress, you don't output anything and don't need to flush!
    • Admin
      Admin almost 8 years
      Found out the solution to your problem. Learned something also, thanks!
  • l0b0
    l0b0 over 10 years
    Isn't that even more work? My approach involves at minimum one command, at most three. This approach always involves three commands - disable, enable, then set layout (plus possibly a keyboard switch).
  • slm
    slm over 10 years
    @l0b0 - yeah I'm not thrilled with this approach either. I'm continuing to look but was putting this method here as "1 way". Not the ideal one though, I agree.
  • jthill
    jthill almost 8 years
    There's also /dev/input/by-id
  • l0b0
    l0b0 almost 8 years
    Thanks for the tip. I've cited X tools only because most tools seem to require X. I do not know how to work with /dev/input/event* - I tried tailting but to no avail.
  • L. Levrel
    L. Levrel almost 8 years
    @jthill On the machine I'm currently on, this dir only has links for the mouse.
  • jthill
    jthill almost 8 years
    Hunh. Okay, live and learn, mine's got my keyboard listed all pretty.
  • slm
    slm almost 8 years
    @lobo - This answer isn't going to get the bounty so don't worry about it, it had the votes before you started the bounty. stackoverflow.com/help/bounty. Also what is your anger towards me trying to help you here? I gave you not an ideal solution, but 1 way to accomplish your task. I provided this over 2+ years ago and this Q has sat here w/ 0 alternatives. I think you need to ask yourself if it's perhaps the question/approach that's the problem. Obviously just my $0.02 but it's enough already.
  • l0b0
    l0b0 almost 8 years
    My bad x 2: I didn't notice the bit about "created after the bounty started", and I appreciate that you wrote a very well formulated answer. But I can't upvote a solution which is more complicated than the original one, and I don't understand why others do.
  • L. Levrel
    L. Levrel almost 8 years
    @jthill: from tests on another machine, I think only USB devices have a symlink in "by-id/"
  • l0b0
    l0b0 almost 8 years
    Awesome! This works: setxkbmap -device $(xinput list --id-only "keyboard:$(sudo ./a.out /dev/input/event*)") -layout us -variant dvorak-alt-intl -option compose:caps. I'm going to have a try at converting it to a shell script which doesn't need root access, but it works. Thanks again for tracking down that code!
  • slm
    slm almost 8 years
    @l0b0 - other's voted it up 2+ years ago, no one has upvoted it recently. You're under no obligation to vote either way, I was only merely trying to help a cohort in need with "some" solution, whether it is ideal or not is up to you.
  • Fabian Röling
    Fabian Röling over 4 years
    @l0b0 My reason for upvoting: It's a single command that I can use to quickly and easily confirm my suspicion which keyboard it was, instead of needing to read an entire script to make sure it doesn't wipe my hard drive, then save and execute it. Or, in the case of the highest voted answer so far, compile C code. Also, creative ideas like this one deserve upvotes.
  • dragon788
    dragon788 over 2 years
    It appears the reference site got taken over by a lifestyle blogger (or bot) in 2019, here's the Wayback link with the full post. web.archive.org/web/20181215175035/http://www.thelinuxdaily.‌​com/…