Skip to content

Commit

Permalink
media keys support
Browse files Browse the repository at this point in the history
  • Loading branch information
Univa committed Nov 28, 2023
1 parent bf4c7e5 commit 0702992
Show file tree
Hide file tree
Showing 10 changed files with 494 additions and 62 deletions.
35 changes: 35 additions & 0 deletions docs/src/content/docs/features/feature-media-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: Media Keys / Consumer Controls
description: How to enable media keys for your keyboard layout.
---

`rumcake` can be configured to send HID reports that consist of [variants in the USB consumer usage page](https://docs.rs/usbd-human-interface-device/latest/usbd_human_interface_device/page/enum.Consumer.html).
This includes media controls, application launchers, application controls, etc.

# Setup

## Required Cargo features

You must enable the following `rumcake` features:

- `media-keycodes`

## Required code

After enabling the `media-keycodes` feature, you can start using the `Keycode::Media` variants in your `KeyboardLayout` implementation:
The `Keycode::Media` variant must contain a `usbd_human_interface_device::page::Consumer` variant, which is re-exported as `rumcake::keyboard::Consumer`.

Example of usage:

```rust ins={2} ins="{Custom(Media(VolumeIncrement))}"
use keyberon::action::Action::*;
use rumcake::keyboard::{Consumer::*, Keycode::Media};

/* ... */

build_layout! {
{
[ Escape {Custom(Media(VolumeIncrement))} A B C]
}
}
```
1 change: 1 addition & 0 deletions docs/src/content/docs/features/feature-via-vial.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ No menu for RGB matrix is provided. RGB backlight animations still need to be im
- Lighting keycodes, except for `QK_BACKLIGHT_TOGGLE_BREATHING`. RGB keycodes only work for underglow, not an RGB backlight matrix.
- Momentary layers, and default layers (MO(x) and DF(x))
- Custom keycodes (`customKeycodes` in your JSON definition)
- Certain media keycodes. Support for this must be enabled manually. Check the ["Media Keys" doc](../feature-media-keys)
- QK_OUTPUT_BLUETOOTH and QK_OUTPUT_USB

Attempts to use unsupported keycodes will not result in any changes to your layout. It may show in the app, but reloading will revert the keycodes back to their previous state.
Expand Down
2 changes: 2 additions & 0 deletions rumcake-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ stm32 = []
nrf = []

storage = []

media-keycodes = []
11 changes: 11 additions & 0 deletions rumcake-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,17 @@ pub fn main(
// HID Keyboard Report sending
spawner.spawn(rumcake::usb_hid_kb_write_task!(kb_class)).unwrap();
});

if cfg!(feature = "media-keycodes") {
initialization.extend(quote! {
// HID consumer
let consumer_class = rumcake::usb::setup_usb_hid_consumer_writer(&mut builder);
});
spawning.extend(quote! {
// HID Consumer Report sending
spawner.spawn(rumcake::usb_hid_consumer_write_task!(consumer_class)).unwrap();
});
}
}

if keyboard.via.is_some() || keyboard.vial.is_some() {
Expand Down
4 changes: 4 additions & 0 deletions rumcake/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ features = [
"display",
"split-peripheral",
"split-central",
"media-keycodes"
]

flavours = [
Expand Down Expand Up @@ -99,6 +100,9 @@ storage = ["rumcake-macros/storage"]
# Keyboard features
#

# Extra keycodes
media-keycodes = ["rumcake-macros/media-keycodes"]

# Via/Vial
via = []
vial = ["via", "_backlight"]
Expand Down
81 changes: 60 additions & 21 deletions rumcake/src/bluetooth/nrf_ble.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use usbd_human_interface_device::device::keyboard::{

use crate::hw::mcu::BLUETOOTH_ADVERTISING_MUTEX;
use crate::hw::BATTERY_LEVEL_STATE;
use crate::keyboard::KEYBOARD_REPORT_HID_SEND_CHANNEL;
use crate::keyboard::{CONSUMER_REPORT_HID_SEND_CHANNEL, KEYBOARD_REPORT_HID_SEND_CHANNEL};

#[cfg(feature = "usb")]
use crate::usb::USB_STATE;
Expand Down Expand Up @@ -644,35 +644,51 @@ where
let hid_fut = async {
// Discard any reports that haven't been processed due to lack of a connection
while KEYBOARD_REPORT_HID_SEND_CHANNEL.try_receive().is_ok() {}
while CONSUMER_REPORT_HID_SEND_CHANNEL.try_receive().is_ok() {}

#[cfg(feature = "usb")]
{
loop {
if !USB_STATE.get().await {
match select(
match select3(
USB_STATE_LISTENER.wait(),
KEYBOARD_REPORT_HID_SEND_CHANNEL.receive(),
CONSUMER_REPORT_HID_SEND_CHANNEL.receive(),
)
.await
{
select::Either::First(()) => {
select::Either3::First(()) => {
info!(
"[BT_HID] Bluetooth HID reports enabled = {}",
!USB_STATE.get().await
);
}
select::Either::Second(report) => {
// TODO: media keys
select::Either3::Second(report) => {
info!(
"[BT_HID] Writing HID keyboard report to bluetooth: {:?}",
"[BT_HID] Writing NKRO HID report to bluetooth: {:?}",
Debug2Format(&report)
);

if let Err(err) =
server.hids.keyboard_report_notify(&connection, report)
{
error!(
"[BT_HID] Couldn't write HID keyboard report: {:?}",
"[BT_HID] Couldn't write NKRO HID report: {:?}",
Debug2Format(&err)
);
};
}
select::Either3::Third(report) => {
info!(
"[BT_HID] Writing consumer HID report to bluetooth: {:?}",
Debug2Format(&report)
);

if let Err(err) =
server.hids.consumer_report_notify(&connection, report)
{
error!(
"[BT_HID] Couldn't write consumer HID report: {:?}",
Debug2Format(&err)
);
};
Expand All @@ -691,20 +707,43 @@ where
#[cfg(not(feature = "usb"))]
{
loop {
let report = KEYBOARD_REPORT_HID_SEND_CHANNEL.receive().await;

// TODO: media keys
info!(
"[BT_HID] Writing HID keyboard report to bluetooth: {:?}",
Debug2Format(&report)
);

if let Err(err) = server.hids.keyboard_report_notify(&connection, report) {
error!(
"[BT_HID] Couldn't write HID keyboard report: {:?}",
Debug2Format(&err)
);
};
match select(
KEYBOARD_REPORT_HID_SEND_CHANNEL.receive(),
CONSUMER_REPORT_HID_SEND_CHANNEL.receive(),
)
.await
{
select::Either::First(report) => {
info!(
"[BT_HID] Writing NKRO HID report to bluetooth: {:?}",
Debug2Format(&report)
);

if let Err(err) =
server.hids.keyboard_report_notify(&connection, report)
{
error!(
"[BT_HID] Couldn't write NKRO HID report: {:?}",
Debug2Format(&err)
);
};
}
select::Either::Second(report) => {
info!(
"[BT_HID] Writing consumer HID report to bluetooth: {:?}",
Debug2Format(&report)
);

if let Err(err) =
server.hids.consumer_report_notify(&connection, report)
{
error!(
"[BT_HID] Couldn't write consumer HID report: {:?}",
Debug2Format(&err)
);
};
}
}
}
}
};
Expand Down
53 changes: 48 additions & 5 deletions rumcake/src/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ use heapless::Vec;
use keyberon::debounce::Debouncer;
use keyberon::layout::{CustomEvent, Event, Layers, Layout as KeyberonLayout};
use keyberon::matrix::Matrix;
use usbd_human_interface_device::device::consumer::MultipleConsumerReport;
use usbd_human_interface_device::{
device::keyboard::NKROBootKeyboardReport, page::Keyboard as KeyboardKeycode,
};

#[cfg(feature = "media-keycodes")]
pub use usbd_human_interface_device::page::Consumer;

#[macro_export]
macro_rules! remap_matrix {
({$([$(#$no1:tt)* $($og_pos:ident $(#$no2:tt)*)* ])*} {$([$($new_pos:ident)*])*}) => {
Expand Down Expand Up @@ -256,6 +260,9 @@ pub enum Keycode {
/// Custom keycode, which can be used to run custom code. You can use
/// [`KeyboardLayout::on_custom_keycode`] to handle it.
Custom(u8),
#[cfg(feature = "media-keycodes")]
/// Media keycode, which can be any variant in [`usbd_human_interface_device::page::Consumer`]
Media(usbd_human_interface_device::page::Consumer),
#[cfg(feature = "underglow")]
/// Underglow keycode, which can be any variant in [`crate::underglow::animations::UnderglowCommand`]
Underglow(crate::underglow::animations::UnderglowCommand),
Expand Down Expand Up @@ -340,6 +347,17 @@ pub static KEYBOARD_REPORT_HID_SEND_CHANNEL: Channel<
1,
> = Channel::new();

/// Channel for sending consumer HID reports.
///
/// Channel messages should be consumed by the bluetooth task or USB task, so user-level code
/// should **not** attempt to receive messages from the channel, otherwise commands may not be
/// processed appropriately. You should only send to this channel.
pub static CONSUMER_REPORT_HID_SEND_CHANNEL: Channel<
ThreadModeRawMutex,
MultipleConsumerReport,
1,
> = Channel::new();

#[rumcake_macros::task]
pub async fn layout_collect<K: KeyboardLayout + 'static>(_k: K)
where
Expand All @@ -350,6 +368,9 @@ where
let mut last_keys = Vec::<KeyboardKeycode, 24>::new();
let layout = K::get_layout();

#[cfg(feature = "media-keycodes")]
let mut codes = [Consumer::Unassigned; 4];

loop {
let keys = {
let event = POLLED_EVENTS_CHANNEL.receive().await;
Expand All @@ -366,6 +387,17 @@ where
Keycode::Custom(id) => {
K::on_custom_keycode(id, true);
}
#[cfg(feature = "media-keycodes")]
Keycode::Media(keycode) => {
if let Some(c) =
codes.iter_mut().find(|c| matches!(c, Consumer::Unassigned))
{
*c = keycode;
}
CONSUMER_REPORT_HID_SEND_CHANNEL
.send(MultipleConsumerReport { codes })
.await;
}
#[cfg(feature = "underglow")]
Keycode::Underglow(command) => {
crate::underglow::UNDERGLOW_COMMAND_CHANNEL
Expand Down Expand Up @@ -397,13 +429,24 @@ where
.await;
}
},
CustomEvent::Release(keycode) =>
{
#[allow(irrefutable_let_patterns)]
if let Keycode::Custom(id) = keycode {
CustomEvent::Release(keycode) => match keycode {
Keycode::Custom(id) => {
K::on_custom_keycode(id, false);
}
}
#[cfg(feature = "media-keycodes")]
Keycode::Media(keycode) => {
if let Some(c) =
codes.iter_mut().find(|c| matches!(c, Consumer::Unassigned))
{
*c = keycode;
}
CONSUMER_REPORT_HID_SEND_CHANNEL
.send(MultipleConsumerReport { codes })
.await;
}
#[allow(unreachable_patterns)]
_ => {}
},
}

debug!("[KEYBOARD] Collecting keyboard keycodes");
Expand Down
4 changes: 3 additions & 1 deletion rumcake/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,9 @@ pub mod tasks {
pub use crate::display::__display_task_task;

#[cfg(feature = "usb")]
pub use crate::usb::{__start_usb_task, __usb_hid_kb_write_task_task};
pub use crate::usb::{
__start_usb_task, __usb_hid_consumer_write_task_task, __usb_hid_kb_write_task_task,
};

#[cfg(feature = "via")]
pub use crate::via::__usb_hid_via_read_task_task;
Expand Down
Loading

0 comments on commit 0702992

Please sign in to comment.