Keyboard Media Keys, now (hopefully) Windows compatible

PaulStoffregen

Well-known member
I've been working on improved media keys support which can work with Windows. It's ready for initial testing. Hopefully there's still interest in this feature?

Code for Teensy LC & 3.x is on github now.

https://github.com/PaulStoffregen/cores

If you'd like to try this, download the cores repository and replace your hardware/teensy/avr/cores folder with its contents. On a Mac, control-click Arduino, then "Show Package Contents", and replace Contents/Java/hardware/teensy/avr/cores with the files from github.

I'm planning to back-port this to Teensy 2.0, after some testing. As you can see on the github commit log, this changed quite a lot of the USB keyboard code. I could really use some help with testing, especially on various Windows systems, and *especially* with the many non-US layouts. So far, I've done only initial testing on Windows 10, and only with "US English" keyboard layout.

The new media key support is intended to be used with Keyboard.press() and Keyboard.release(). Here's an example:

Code:
/* Buttons to USB Keyboard Example - Special Media Player Keys

   You must select Keyboard from the "Tools > USB Type" menu

   This example code is in the public domain.
*/

#include <Bounce.h>

// Create Bounce objects for each button.  The Bounce object
// automatically deals with contact chatter or "bounce", and
// it makes detecting changes very simple.
Bounce button0 = Bounce(0, 10);
Bounce button1 = Bounce(1, 10);  // 10 ms debounce time is appropriate
Bounce button2 = Bounce(2, 10);  // for most mechanical pushbuttons
// TODO: fix pin 3 button on my testing board....
Bounce button3 = Bounce(4, 10);
Bounce button4 = Bounce(5, 10);  // if a button is too "sensitive" 
Bounce button5 = Bounce(6, 10);  // you can increase this time.

void setup() {
  // Configure the pins for input mode with pullup resistors.
  // The pushbuttons connect from each pin to ground.  When
  // the button is pressed, the pin reads LOW because the button
  // shorts it to ground.  When released, the pin reads HIGH
  // because the pullup resistor connects to +5 volts inside
  // the chip.
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
}

void loop() {
  // Update all the buttons.  There should not be any long
  // delays in loop(), so this runs repetitively at a rate
  // faster than the buttons could be pressed and released.
  button0.update();
  button1.update();
  button2.update();
  button3.update();
  button4.update();
  button5.update();

  // Check each button for "falling" edge.
  // falling = high (not pressed - voltage from pullup resistor)
  //           to low (pressed - button connects pin to ground)
  if (button0.fallingEdge()) {
    Keyboard.press(KEY_MEDIA_PREV_TRACK);
    Keyboard.release(KEY_MEDIA_PREV_TRACK);
  }
  if (button1.fallingEdge()) {
    Keyboard.press(KEY_MEDIA_PLAY_PAUSE);
    Keyboard.release(KEY_MEDIA_PLAY_PAUSE);
  }
  if (button2.fallingEdge()) {
    Keyboard.press(KEY_MEDIA_NEXT_TRACK);
    Keyboard.release(KEY_MEDIA_NEXT_TRACK);
  }
  if (button3.fallingEdge()) {
    Keyboard.press(KEY_MEDIA_VOLUME_DEC);
    Keyboard.release(KEY_MEDIA_VOLUME_DEC);
  }
  if (button4.fallingEdge()) {
    Keyboard.press(KEY_MEDIA_VOLUME_INC);
    Keyboard.release(KEY_MEDIA_VOLUME_INC);
  }
  if (button5.fallingEdge()) {
    Keyboard.press(KEY_SYSTEM_POWER_DOWN);
    Keyboard.release(KEY_SYSTEM_POWER_DOWN);
    //Keyboard.press(KEY_MEDIA_EJECT);
    //delay(300);  // Mac OS-X will not recognize a very short eject press
    //Keyboard.release(KEY_MEDIA_EJECT);
  }
}

So far, these are the keys defined in keylayouts.h:

Code:
KEY_MEDIA_PLAY          
KEY_MEDIA_PAUSE         
KEY_MEDIA_RECORD        
KEY_MEDIA_FAST_FORWARD  
KEY_MEDIA_REWIND        
KEY_MEDIA_NEXT_TRACK    
KEY_MEDIA_PREV_TRACK    
KEY_MEDIA_STOP          
KEY_MEDIA_EJECT         
KEY_MEDIA_RANDOM_PLAY   
KEY_MEDIA_PLAY_PAUSE    
KEY_MEDIA_PLAY_SKIP     
KEY_MEDIA_MUTE          
KEY_MEDIA_VOLUME_INC    
KEY_MEDIA_VOLUME_DEC    
KEY_SYSTEM_POWER_DOWN
KEY_SYSTEM_SLEEP
KEY_SYSTEM_WAKE_UP

Unlike the old code, which was limited to only 8 keys (which only worked on Mac & Linux), this new way is intended to be able to support all the media (aka HID Consumer Usage Page) key definitions, and all the system control keys too.

The Keyboard.press() and Keyboard.release() functions are meant to accept ASCII, and Unicode up to 0xC1FF (of course, each layout implements only a small fraction of the huge Unicode space), and non-ascii characters, and the many KEY_XYZ constants which map directly to HID usage numbers. Much of this code got reworked... and it seems to be functioning, but I haven't yet tested on non-US layouts. I could really use help testing from anyone using Teensy as a non-US keyboard.

If you find anything not working, please be sure to post a complete program and specify which layout and OS you're using, so I can try to reproduce and fix the problem.
 
If anyone wants to experiment with defining other keys, this is the spec where they're found:

http://www.usb.org/developers/hidpage/Hut1_12v2.pdf

The media/consumer keys are in table 17 starting on page 75. To use these, add an offset of 0xE400 to the Usage ID number in the spec, for the number you give to Keyboard.press() and Keyboard.release().

The system control keys are on page 27. To use these, add of offset of 0xE200. So to experiment with whatever "System Menu Exit" actually does (if anything), you'd use Keyboard.press(0xE288); to send that keystroke to your PC.
 
I'm interested in it, but finding time for everything is my challenge.

I think there would be great benefit in implementing many of the other ones like flight sim commands etc, so you could create a flight sim joystick that didn't need setting up, for example.

But i havent had a chance to test the media keys yet and won't for a bit.

I'd like to make a "Massive Sleep Bash Button" to sleep my PC - i've got the button ready and everything.
 
When I tested on Windows 10, this code instantly put it into sleep mode.

Code:
    Keyboard.press(KEY_SYSTEM_POWER_DOWN);
    Keyboard.release(KEY_SYSTEM_POWER_DOWN);

Ubuntu 14.04 and Mac OS-X 10.7.5 both display a dialog box asking to confirm, but Windows just instantly went to sleep mode!
 
Need More Keys...

I've back-ported the new Windows-compatible media keys to Teensy 2.0 & Teensy++ 2.0.

Hi Paul,

Just joining the discussion, been a Teensy fan/user for years. Thanks for all your work and contributions that help make this such a cool tool!

I'm working on a keyboard project and need FULL replacement for 104 keyboard (not so worried about media keys, but that'd be nice).
-- My background is in Assistive Technology - tech for folks with disabilities --

Forced by customers, I'm primarily a Windows person...

I have run into several concerns, firstly: we have the GUI key(s) only as a modifier. Using the Windows key alone, for example, works as a single key-send not as a modifier.
Is there some 'dead' key that I can send with your MODIFIERKEY_GUI key that gives me the same result as just pressing and releasing the Windows key (scan code-VKC 91/5B).
I see in your new updates the right-hand versions of Modifier keys, but I can't find a GUI Menu (scan code-VKC 93/5D) I'd love to have that.

As a thought, if it saves space dropping the Left-Hand Constants seems like it wouldn't matter since they are redundant.
also, you probably are doing so, but in case not, as an idea to save space in my code I store the constants as bytes and add the 15th bit when I use them (A = 4 + 16384)

I assume the naming conventions are why the constants for several characters are off - using the shifted names not the un-shifted names:
(KEY_TILDE, KEY_QUOTE, KEY_NUMBER, KEY_RIGHT_BRACE, KEY_LEFT_BRACE) it might help folks to add a note to that effect in the support pages...

By the way I have a Windows-based Key Watching tool I made that reports Scan Codes and Names for keys as pressed (using Windows API's so if you set the system to a Norwegian keyboard you get the names in Norwegian!)
I'd be happy to send you a copy if you like, or to use it to help test new changes to Teensy code...

Cheers, thanks and let me know if there is anything I can do to help grow the Teensy World!

Kirk
 
P.S.

Forgot one...

Is there some way to catch the Caps Lock / NumLock status when the Computer sends the status update?
i.e. I want my keyboard to have LED's for those two but need to know if the regular keyboard sent a keypress for either... so mine can match.
 
If you have 2 (or more) keyboards connected to a PC, each communicates with the PC, but they never communicate with each other. When you press a key on one of the keyboards, the PC doesn't send anything to the other keyboard to inform them of the activity on the unrelated keyboard.

When you press cap locks, the PC might or might not consider all the keyboards to be in cap lock mode. When you think about it for a moment, it's rather silly to have the caps lock key one any of your keyboards put all the others into caps lock mode. But some versions of Windows do this. I know Macintosh does not.

Before you get too far down the path, I recommend plugging 2 real keyboards into your PC and watching what effect each has on the other's LEDs. Whatever your PC does, understand it's how your operating system is handling things. Other PCs might be different. Not all operating systems are created equal. ;)
 
Never mind, I hacked your code and used the HID value directly it works!

void sendKeyboard( int sendKey){
unsigned int KeyCodeVal;


// Set the final PJRC Key Value:
// KEYBASE is defined as bit15: 16384 or 0x4000
KeyCodeVal = sendKey| KEYBASE;

Keyboard.set_modifier(0);
Keyboard.set_key1(KeyCodeVal);
//Keyboard.set_key2(KEY_B);
//Keyboard.set_key3(KEY_C);
//Keyboard.set_key4(KEY_D);
//Keyboard.set_key5(KEY_E);
//Keyboard.set_key6(KEY_F);
Keyboard.send_now();

// release all the keys at the same instant
Keyboard.set_modifier(0);
Keyboard.set_key1(0);
Keyboard.send_now();
}

Then call like this:

sendKeyboard(101);

-- 101 is the offset value for the Application Key as defined in the HID spec.

Again, thanks for building such a cool platform that makes things like this so easy!
 
Last edited:
Dang, I thought I could use the other HID codes the same way, but other than the Application-Menu key none of the ones I'm looking for are working.

Any thoughts?
I'm wondering if its a signed/unsigned integer issue since the numbers I need are above 127...

I'm still hoping to have full replacement for all the keys on a 104 keyboard.
 
Starting with Teensyduino 1.29, use Keyboard.press() and Keyboard.release() for the media keys.

Look at File > Examples > Teensy > USB_Keyboard > MediaButtons.
 
I've got a teensy 3.1 running as a usb keyboard+mouse. KEY_SYSTEM_SLEEP and KEY_SYSTEM_POWER_DOWN both put the computer to sleep though KEY_SYSTEM_WAKE_UP doesn't bring the computer out of sleep nor does sending a regular key (such as KEY_SPACE). What's odd is that both wired and wireless usb keyboards seem to bring it out of sleep just fine, both by pressing the 'moon' icon and by just hitting any random (non-modifier or F) key. I'm not that familiar with the inner workings of usb but I'd be grateful for any direction someone cold point me towards. I've been at this for a while now and just can't seem to get my head around it.
 
Thanks for the link; unfortunately that's the first discussion I came across and making the change in usb_desc.c and then using his button code in my loop didn't work in my case. I could very well be implementing it wrong but all I have to do is change that one line in usb-desc.c and I should then be able to use his code or KEY_SYSTEM_WAKE_UP on button press, correct?
 
Thanks for the link; unfortunately that's the first discussion I came across and making the change in usb_desc.c and then using his button code in my loop didn't work in my case. I could very well be implementing it wrong but all I have to do is change that one line in usb-desc.c and I should then be able to use his code or KEY_SYSTEM_WAKE_UP on button press, correct?
You can't just send a key, it won't be received. You have to use 'USB_CTL_RESUME'. You also need to make sure the USB descriptor is reloaded, e.g. on Windows it will be cached and you have to force a refresh.
 
Gave this a shot a second time. It does work if I immediately press my wake up button after the computer has gone to sleep. If I wait longer than about 5 seconds it stops bringing it out of sleep. Uninstalled the device in windows and switched it to a different hub just to be sure that the cache had cleared.
 
Serial + Media keys?

I've back-ported the new Windows-compatible media keys to Teensy 2.0 & Teensy++ 2.0.

https://github.com/PaulStoffregen/cores/commit/12c5d4972d819d094f413c44e44a104f6888db15

They can only be used in Keyboard+Mouse+Joystick mode. It works great with Windows 10. I believe all other versions of Windows should work too.

When Serial is added, the AVR chips don't have enough USB endpoints to add the media keys.


Hi Paul,

I see this post is a few years old now, but I am still working off Teensy2.0++ and wondering if there is anyway now to achieve both Serial (+ Keyboard) and the media keys working together?

Thanks,
Nathan
 
Nope. The number of USB endpoints limitation is due to the hardware in those old 8 bit chips. The hardware hasn't changed.

But of course newer hardware is available. You can do this with Teensy LC, which is less expensive than those old boards.
 
Nope. The number of USB endpoints limitation is due to the hardware in those old 8 bit chips. The hardware hasn't changed.

But of course newer hardware is available. You can do this with Teensy LC, which is less expensive than those old boards.


Hi Paul, if I was to remove the Joystick endpoints, would I be able to replace it with the Media keys and then still have Serial functionality?
 
Last edited:
In theory, should work. In practice, editing that old USB code isn't always easy.

After editing, if you have a compatible PC it's a good idea to check the descriptors with the USB2 command verifier program. It's a free download from www.usb.org. But it only works on some PCs with the right kind of USB controllers. The software temporarily replaces the Windows USB host controller driver to be able to work its magic, so it's not for the faint of heart! Back up your machine. It also requires a USB hub between the PC and Teensy (which is mentioned somewhere in the docs, but easy to miss).
 
Thanks Paul! I'll give it a go and let you know how I get along... I'll share the edits here if I can get it going
 
Media keys not working on teensy 2 and teensy 2++

Has anyone giving this a try yet? Maybe I ought to make an early 1.29 beta once the other new usb stuff is stable?

Hi Paul,

I tested teensy 2, teensy 2.0++ and teensy 3 on MacOS. The same code works with media keys with teensy 3.2 but not teensy 2 and teensy 2.0++. Is it the case the teensy 2 and 2.0++ libraries were not updated?

Regards,
Gustavo.
 
Back
Top