diff --git a/Cargo.toml b/Cargo.toml index 4c78415f7a98ca013c4d9aa334841e3bb31e8225..2c1bdae9b9ab1342ee33d4929f28fd5c303fd796 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ trace_to_hitrace = ["trace/trace_to_hitrace", "machine/trace_to_hitrace"] hisysevent = ["hisysevent/hisysevent"] vfio = ["machine/vfio_device"] usb_uas = ["machine/usb_uas"] +usb_consumer = ["machine/usb_consumer"] virtio_rng = ["machine/virtio_rng"] virtio_scsi = ["machine/virtio_scsi"] vhost_vsock = ["machine/vhost_vsock"] diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 3dca2b4185028a50057e79935c404deccc6da0ce..da05e394584d3ad7a8ea175eaf88b87307df4c69 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -57,3 +57,4 @@ usb_uas = [] trace_to_logger = [] trace_to_ftrace = [] trace_to_hitrace = [] +usb_consumer = [] diff --git a/devices/src/usb/config.rs b/devices/src/usb/config.rs index aef89932a7a14ad4d202c152795692c2cf96bb8f..3fd8e7a602ee7b69d2cbf42c5c78659b6ccd4eb9 100644 --- a/devices/src/usb/config.rs +++ b/devices/src/usb/config.rs @@ -267,3 +267,4 @@ pub const USB_PRODUCT_ID_KEYBOARD: u16 = 0x0002; pub const USB_PRODUCT_ID_STORAGE: u16 = 0x0003; pub const USB_PRODUCT_ID_TABLET: u16 = 0x0004; pub const USB_PRODUCT_ID_UAS: u16 = 0x0005; +pub const USB_PRODUCT_ID_CONSUMER: u16 = 0x0006; diff --git a/devices/src/usb/consumer.rs b/devices/src/usb/consumer.rs new file mode 100644 index 0000000000000000000000000000000000000000..2feca3ebf5872daf0fc96ccc715c9f914f316c97 --- /dev/null +++ b/devices/src/usb/consumer.rs @@ -0,0 +1,258 @@ +// Copyright (c) 2025 Huawei Technologies Co.,Ltd. All rights reserved. +// +// StratoVirt is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +use std::sync::{Arc, Mutex, Weak}; + +use anyhow::Result; +use clap::Parser; +use log::{debug, info, warn}; +use once_cell::sync::Lazy; + +use super::descriptor::{ + UsbConfigDescriptor, UsbDescConfig, UsbDescDevice, UsbDescEndpoint, UsbDescIface, UsbDescOther, + UsbDescriptorOps, UsbDeviceDescriptor, UsbEndpointDescriptor, UsbInterfaceDescriptor, +}; +use super::hid::{Hid, HidType, CONSUMER_REPORT_DESCRIPTOR, QUEUE_LENGTH, QUEUE_MASK}; +use super::xhci::xhci_controller::{endpoint_number_to_id, XhciDevice}; +use super::{config::*, USB_DEVICE_BUFFER_DEFAULT_LEN}; +use super::{ + notify_controller, UsbDevice, UsbDeviceBase, UsbDeviceRequest, UsbPacket, UsbPacketStatus, +}; +use machine_manager::config::valid_id; +use ui::input::{register_consumer, unregister_consumer, ConsumerOpts, CONSUMER_UP}; +use util::gen_base_func; + +/// Consumer device descriptor +static DESC_DEVICE_CONSUMER: Lazy> = Lazy::new(|| { + Arc::new(UsbDescDevice { + device_desc: UsbDeviceDescriptor { + bLength: USB_DT_DEVICE_SIZE, + bDescriptorType: USB_DT_DEVICE, + idVendor: USB_VENDOR_ID_STRATOVIRT, + idProduct: USB_PRODUCT_ID_CONSUMER, + bcdDevice: 0, + iManufacturer: STR_MANUFACTURER_INDEX, + iProduct: STR_PRODUCT_CONSUMER_INDEX, + iSerialNumber: STR_SERIAL_CONSUMER_INDEX, + bcdUSB: 0x0100, + bDeviceClass: 0, + bDeviceSubClass: 0, + bDeviceProtocol: 0, + bMaxPacketSize0: 8, + bNumConfigurations: 1, + }, + configs: vec![Arc::new(UsbDescConfig { + config_desc: UsbConfigDescriptor { + bLength: USB_DT_CONFIG_SIZE, + bDescriptorType: USB_DT_CONFIGURATION, + wTotalLength: 0, + bNumInterfaces: 1, + bConfigurationValue: 1, + iConfiguration: STR_CONFIG_CONSUMER_INDEX, + bmAttributes: USB_CONFIGURATION_ATTR_ONE | USB_CONFIGURATION_ATTR_REMOTE_WAKEUP, + bMaxPower: 50, + }, + iad_desc: vec![], + interfaces: vec![DESC_IFACE_CONSUMER.clone()], + })], + }) +}); + +/// CONSUMER interface descriptor +static DESC_IFACE_CONSUMER: Lazy> = Lazy::new(|| { + Arc::new(UsbDescIface { + interface_desc: UsbInterfaceDescriptor { + bLength: USB_DT_INTERFACE_SIZE, + bDescriptorType: USB_DT_INTERFACE, + bInterfaceNumber: 0, + bAlternateSetting: 0, + bNumEndpoints: 1, + bInterfaceClass: USB_CLASS_HID, + bInterfaceSubClass: 0, + bInterfaceProtocol: 0, + iInterface: 0, + }, + other_desc: vec![Arc::new(UsbDescOther { + // HID descriptor + data: vec![ + 0x09, + 0x21, + 0x11, + 0x01, + 0x00, + 0x01, + 0x22, + CONSUMER_REPORT_DESCRIPTOR.len() as u8, + 0, + ], + })], + endpoints: vec![Arc::new(UsbDescEndpoint { + endpoint_desc: UsbEndpointDescriptor { + bLength: USB_DT_ENDPOINT_SIZE, + bDescriptorType: USB_DT_ENDPOINT, + bEndpointAddress: USB_DIRECTION_DEVICE_TO_HOST | 0x1, + bmAttributes: USB_ENDPOINT_ATTR_INT, + wMaxPacketSize: 8, + bInterval: 0xa, + }, + extra: Vec::new(), + })], + }) +}); + +/// String descriptor index +const STR_MANUFACTURER_INDEX: u8 = 1; +const STR_PRODUCT_CONSUMER_INDEX: u8 = 2; +const STR_CONFIG_CONSUMER_INDEX: u8 = 3; +const STR_SERIAL_CONSUMER_INDEX: u8 = 4; + +/// String descriptor +const DESC_STRINGS: [&str; 5] = [ + "", + "StratoVirt", + "StratoVirt USB Consumer", + "HID Consumer", + "6", +]; + +#[derive(Parser, Clone, Debug, Default)] +#[command(no_binary_name(true))] +pub struct UsbConsumerConfig { + #[arg(long)] + pub classtype: String, + #[arg(long, value_parser = valid_id)] + id: String, + #[arg(long)] + bus: Option, + #[arg(long)] + port: Option, +} + +/// USB Consumer device. +pub struct UsbConsumer { + base: UsbDeviceBase, + hid: Hid, + /// USB controller used to notify controller to transfer data. + cntlr: Option>>, +} + +pub struct UsbConsumerAdapter { + usb_consumer: Arc>, +} + +impl ConsumerOpts for UsbConsumerAdapter { + fn do_consumer_event(&mut self, keycode: u16, down: bool) -> Result<()> { + trace::usb_consumer_event(&keycode, &down); + + let mut scan_code = keycode; + if !down { + scan_code |= CONSUMER_UP; + } + + let mut locked_consumer = self.usb_consumer.lock().unwrap(); + if locked_consumer.hid.num >= QUEUE_LENGTH { + trace::usb_consumer_queue_full(); + return Ok(()); + } + + let index = ((locked_consumer.hid.head + locked_consumer.hid.num) & QUEUE_MASK) as usize; + locked_consumer.hid.num += 1; + locked_consumer.hid.consumer.keycodes[index] = scan_code; + drop(locked_consumer); + let clone_consumer = self.usb_consumer.clone(); + let ep_id = endpoint_number_to_id(true, 1); + notify_controller(&(clone_consumer as Arc>), ep_id) + } +} + +impl UsbConsumer { + pub fn new(config: UsbConsumerConfig) -> Self { + Self { + base: UsbDeviceBase::new(config.id, USB_DEVICE_BUFFER_DEFAULT_LEN), + hid: Hid::new(HidType::Consumer), + cntlr: None, + } + } +} + +impl UsbDevice for UsbConsumer { + gen_base_func!(usb_device_base, usb_device_base_mut, UsbDeviceBase, base); + + fn realize(mut self) -> Result>> { + self.base.reset_usb_endpoint(); + self.base.speed = USB_SPEED_FULL; + let mut s: Vec = DESC_STRINGS.iter().map(|&s| s.to_string()).collect(); + let prefix = &s[STR_SERIAL_CONSUMER_INDEX as usize]; + s[STR_SERIAL_CONSUMER_INDEX as usize] = self.base.generate_serial_number(prefix); + self.base.init_descriptor(DESC_DEVICE_CONSUMER.clone(), s)?; + let id = self.device_id().to_string(); + let consumer = Arc::new(Mutex::new(self)); + let consumer_adapter = Arc::new(Mutex::new(UsbConsumerAdapter { + usb_consumer: consumer.clone(), + })); + register_consumer(&id, consumer_adapter); + + Ok(consumer) + } + + fn unrealize(&mut self) -> Result<()> { + unregister_consumer(self.device_id()); + Ok(()) + } + + fn cancel_packet(&mut self, _packet: &Arc>) {} + + fn reset(&mut self) { + info!("Consumer device reset"); + self.base.remote_wakeup = 0; + self.base.addr = 0; + self.hid.reset(); + } + + fn handle_control(&mut self, packet: &Arc>, device_req: &UsbDeviceRequest) { + let mut locked_packet = packet.lock().unwrap(); + match self + .base + .handle_control_for_descriptor(&mut locked_packet, device_req) + { + Ok(handled) => { + if handled { + debug!("Consumer control handled by descriptor, return directly."); + return; + } + } + Err(e) => { + warn!( + "Received incorrect USB Consumer descriptor message: {:?}", + e + ); + locked_packet.status = UsbPacketStatus::Stall; + return; + } + } + self.hid + .handle_control_packet(&mut locked_packet, device_req, &mut self.base.data_buf); + } + + fn handle_data(&mut self, p: &Arc>) { + let mut locked_p = p.lock().unwrap(); + self.hid.handle_data_packet(&mut locked_p); + } + + fn set_controller(&mut self, cntlr: Weak>) { + self.cntlr = Some(cntlr); + } + + fn get_controller(&self) -> Option>> { + self.cntlr.clone() + } +} diff --git a/devices/src/usb/hid.rs b/devices/src/usb/hid.rs index 513005ef8d71ae8e99016551d79f26b3ee7ff824..a4c9f30c370df429ec0592b43f89bd3c0ff94bd2 100644 --- a/devices/src/usb/hid.rs +++ b/devices/src/usb/hid.rs @@ -10,13 +10,13 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use std::fmt::{Display, Formatter, Result as FmtResult}; - use log::error; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::sync::LazyLock; use super::config::*; use super::{UsbDeviceRequest, UsbPacket, UsbPacketStatus}; -use ui::input::set_kbd_led_state; +use ui::input::{set_kbd_led_state, MEDIA_NEXT, MEDIA_PLAY_PAUSE, MEDIA_PREVIOUS, MEDIA_STOP}; /// HID keycode const HID_KEYBOARD_LEFT_CONTROL: u8 = 0xe0; @@ -152,12 +152,35 @@ const KEYBOARD_REPORT_DESCRIPTOR: [u8; 63] = [ 0xc0, // End Collection ]; +/// consumer report descriptor +pub static CONSUMER_REPORT_DESCRIPTOR: LazyLock> = LazyLock::new(|| { + vec![ + 0x05, 0x0C, // Usage Page (Consumer) + 0x09, 0x01, // Usage (Consumer Control) + 0xa1, 0x01, // Collection (Application) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x04, // Report Count (4) + 0x09, 0xCD, // Usage (Play/Pause) + 0x09, 0xB7, // Usage (Stop) + 0x09, 0xB5, // Usage (Scan Previous Track) + 0x09, 0xB6, // Usage (Scan Next Track) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x75, 0x01, // Report Size (1) + 0x95, 0x04, // Report Count (4) + 0x81, 0x03, // Input (Const,Var,Abs) + 0xc0, // End Collection + ] +}); + /// HID type #[derive(Debug)] pub enum HidType { Mouse, Tablet, Keyboard, + Consumer, UnKnown, } @@ -222,6 +245,22 @@ impl HidPointer { } } +pub struct HidConsumer { + pub keycodes: [u16; QUEUE_LENGTH as usize], +} + +impl HidConsumer { + fn new() -> Self { + HidConsumer { + keycodes: [0; QUEUE_LENGTH as usize], + } + } + + fn reset(&mut self) { + self.keycodes.iter_mut().for_each(|x| *x = 0); + } +} + /// Human Interface Device. pub struct Hid { pub(crate) head: u32, @@ -231,6 +270,7 @@ pub struct Hid { idle: u8, pub(crate) keyboard: HidKeyboard, pub(crate) pointer: HidPointer, + pub(crate) consumer: HidConsumer, } impl Hid { @@ -243,6 +283,7 @@ impl Hid { idle: 0, keyboard: HidKeyboard::new(), pointer: HidPointer::new(), + consumer: HidConsumer::new(), } } @@ -253,6 +294,7 @@ impl Hid { self.idle = 0; self.keyboard.reset(); self.pointer.reset(); + self.consumer.reset(); } fn convert_to_hid_code(&mut self) { @@ -334,6 +376,24 @@ impl Hid { data } + fn consumer_poll(&mut self) -> Vec { + let mut data = vec![1]; + if self.num != 0 { + let slot = self.head & QUEUE_MASK; + self.increase_head(); + self.num -= 1; + let keycode = self.consumer.keycodes[slot as usize]; + data[0] = match keycode { + MEDIA_PLAY_PAUSE => 1, + MEDIA_STOP => 2, + MEDIA_PREVIOUS => 4, + MEDIA_NEXT => 8, + _ => 0, + }; + } + data + } + fn pointer_poll(&mut self) -> Vec { let index = self.head; if self.num != 0 { @@ -403,6 +463,11 @@ impl Hid { .clone_from_slice(&KEYBOARD_REPORT_DESCRIPTOR[..]); packet.actual_length = KEYBOARD_REPORT_DESCRIPTOR.len() as u32; } + HidType::Consumer => { + data[..CONSUMER_REPORT_DESCRIPTOR.len()] + .clone_from_slice(&CONSUMER_REPORT_DESCRIPTOR); + packet.actual_length = CONSUMER_REPORT_DESCRIPTOR.len() as u32; + } _ => { error!("Unknown HID type"); packet.status = UsbPacketStatus::Stall; @@ -438,6 +503,11 @@ impl Hid { data[0..buf.len()].copy_from_slice(buf.as_slice()); packet.actual_length = buf.len() as u32; } + HidType::Consumer => { + let buf = self.consumer_poll(); + data[0..buf.len()].copy_from_slice(buf.as_slice()); + packet.actual_length = buf.len() as u32; + } _ => { error!("Unsupported HID type for report"); packet.status = UsbPacketStatus::Stall; @@ -516,6 +586,9 @@ impl Hid { HidType::Tablet => { buf = self.pointer_poll(); } + HidType::Consumer => { + buf = self.consumer_poll(); + } _ => { error!("Unsupported HID device"); p.status = UsbPacketStatus::Stall; diff --git a/devices/src/usb/mod.rs b/devices/src/usb/mod.rs index 110152b84ff12b189f9dae00aa5036e8b4f455cf..5f7fb3e174be7d1fe3df04e72e0e6f741a8d0595 100644 --- a/devices/src/usb/mod.rs +++ b/devices/src/usb/mod.rs @@ -15,6 +15,8 @@ pub mod camera; #[cfg(feature = "usb_camera")] pub mod camera_media_type_guid; pub mod config; +#[cfg(feature = "usb_consumer")] +pub mod consumer; pub mod error; pub mod hid; pub mod keyboard; diff --git a/docs/config_guidebook.md b/docs/config_guidebook.md index 483cf9c4b20c2afa42766fd2300190035a773bd4..44a3c46cb9275eb324f2ca22226942df7f7bbb80 100644 --- a/docs/config_guidebook.md +++ b/docs/config_guidebook.md @@ -955,6 +955,20 @@ Three properties can be set for USB Uas. Note: "aio=off,direct=false" must be configured and other aio/direct values are not supported. +#### 2.13.8 USB Consumer +The USB consumer is a consumer that uses the USB protocol. It should be attached to USB controller. + +One property can be set for USB Consumer. + +* id: unique device id. + +```shell +-device usb-consumer,id= +``` + +Note: Only one consumer can be configured. + + ### 2.14 Virtio Scsi Controller Virtio Scsi controller is a pci device which can be attached scsi device. diff --git a/machine/Cargo.toml b/machine/Cargo.toml index ecead47e132177d1497ce55cbaa995176b783779..4863aa151052129ff49eaa6fa18f0a2e36d5756d 100644 --- a/machine/Cargo.toml +++ b/machine/Cargo.toml @@ -52,6 +52,7 @@ ramfb = ["devices/ramfb", "machine_manager/ramfb"] virtio_gpu = ["virtio/virtio_gpu", "machine_manager/virtio_gpu"] vfio_device = ["vfio", "hypervisor/vfio_device"] usb_uas = ["devices/usb_uas"] +usb_consumer = ["devices/usb_consumer"] virtio_rng = ["virtio/virtio_rng"] virtio_scsi = ["virtio/virtio_scsi"] vhost_vsock = ["virtio/vhost_vsock"] diff --git a/machine/src/lib.rs b/machine/src/lib.rs index 7b37ac7ca1d0ff070756a545af1ba7b7c5ca5659..ef2ff1827c3f2a142a1f43c03fc03ac4f0afb628 100644 --- a/machine/src/lib.rs +++ b/machine/src/lib.rs @@ -73,6 +73,8 @@ use devices::smbios::{SMBIOS_ANCHOR_FILE, SMBIOS_TABLE_FILE}; use devices::sysbus::{devices_register_sysbusdevops_type, to_sysbusdevops, SysBus, SysBusDevType}; #[cfg(feature = "usb_camera")] use devices::usb::camera::{UsbCamera, UsbCameraConfig}; +#[cfg(feature = "usb_consumer")] +use devices::usb::consumer::{UsbConsumer, UsbConsumerConfig}; use devices::usb::keyboard::{UsbKeyboard, UsbKeyboardConfig}; use devices::usb::storage::{UsbStorage, UsbStorageConfig}; use devices::usb::tablet::{UsbTablet, UsbTabletConfig}; @@ -1866,6 +1868,15 @@ pub trait MachineOps: MachineLifecycle { .realize() .with_context(|| "Failed to realize usb tablet device")? } + #[cfg(feature = "usb_consumer")] + "usb-consumer" => { + let config = + UsbConsumerConfig::try_parse_from(str_slip_to_clap(cfg_args, true, false))?; + let consumer = UsbConsumer::new(config); + consumer + .realize() + .with_context(|| "Failed to realize usb consumer device")? + } #[cfg(feature = "usb_camera")] "usb-camera" => { let token_id = match self.get_token_id() { @@ -2011,7 +2022,7 @@ pub trait MachineOps: MachineLifecycle { ("virtconsole" | "virtserialport", add_virtio_serial_port, vm_config, cfg_args), ("vhost-user-fs-pci" | "vhost-user-fs-device", add_virtio_fs, vm_config, cfg_args), ("nec-usb-xhci", add_usb_xhci, cfg_args), - ("usb-kbd" | "usb-storage" | "usb-uas" | "usb-tablet" | "usb-camera" | "usb-host", add_usb_device, vm_config, cfg_args); + ("usb-kbd" | "usb-storage" | "usb-uas" | "usb-tablet" | "usb-consumer" | "usb-camera" | "usb-host", add_usb_device, vm_config, cfg_args); #[cfg(feature = "vhostuser_block")] ("vhost-user-blk-device",add_vhost_user_blk_device, vm_config, cfg_args), #[cfg(feature = "vhostuser_block")] diff --git a/machine/src/standard_common/mod.rs b/machine/src/standard_common/mod.rs index a94090122523f9f18c35d2a827e62470b87f2ba6..4f44cf878f73c50e967d0d526b7685b55efeed74 100644 --- a/machine/src/standard_common/mod.rs +++ b/machine/src/standard_common/mod.rs @@ -1278,7 +1278,8 @@ impl DeviceInterface for StdMachine { ); } } - "usb-kbd" | "usb-tablet" | "usb-camera" | "usb-host" | "usb-storage" | "usb-uas" => { + "usb-kbd" | "usb-tablet" | "usb-consumer" | "usb-camera" | "usb-host" + | "usb-storage" | "usb-uas" => { let cfg_args = locked_vmconfig.add_device_config(args.as_ref()); if let Err(e) = self.add_usb_device(&mut vm_config_clone, &cfg_args) { error!("{:?}", e); diff --git a/machine_manager/src/cmdline.rs b/machine_manager/src/cmdline.rs index b056b571b680ca2eb03c175dcb3034c5264c2005..1092213299684aa96b5d123eefccce1a78d3db1d 100644 --- a/machine_manager/src/cmdline.rs +++ b/machine_manager/src/cmdline.rs @@ -256,6 +256,7 @@ pub fn create_args_parser<'a>() -> ArgParser<'a> { \n\t\tadd usb controller: -device nec-usb-xhci,id=,bus=,addr=<0xa>; \ \n\t\tadd usb keyboard: -device usb-kbd,id=; \ \n\t\tadd usb tablet: -device usb-tablet,id=; \ + \n\t\tadd usb consumer: -device usb-consumer,id=; \ \n\t\tadd usb storage: -device usb-storage,id=,drive=; \ \n\t\tadd scsi controller: -device virtio-scsi-pci,id=,bus=,addr=<0x3>[,multifunction=on|off][,iothread=][,num-queues=]; \ \n\t\tadd scsi hard disk: -device scsi-hd,scsi-id=<0>,bus=,lun=<0>,drive=,id=; \ diff --git a/machine_manager/src/machine.rs b/machine_manager/src/machine.rs index f23acb51ac592fc1f212efc6c70732a9cdd3feaa..3b684513ba3cecba2fadefd0d075cea7a79f8e29 100644 --- a/machine_manager/src/machine.rs +++ b/machine_manager/src/machine.rs @@ -363,6 +363,7 @@ pub trait DeviceInterface { ("nec-usb-xhci", "base-xhci"), ("usb-tablet", "usb-hid"), ("usb-kbd", "usb-hid"), + ("usb-consumer", "usb-hid"), ("usb-storage", "usb-storage-dev"), ("virtio-gpu-pci", "virtio-gpu"), ]; diff --git a/trace/trace_info/usb.toml b/trace/trace_info/usb.toml index 9defe6d287c5195b74d4f605c66bbfc5c4aecbf9..2c678ad524e57e7a27e1f4a3dea1cf22d23b604c 100644 --- a/trace/trace_info/usb.toml +++ b/trace/trace_info/usb.toml @@ -358,6 +358,18 @@ args = "hid_code: &dyn fmt::Debug, index: &dyn fmt::Debug, key: &dyn fmt::Debug" message = "hid_code {:?} index {:?} key {:?}" enabled = true +[[events]] +name = "usb_consumer_event" +args = "keycode: &dyn fmt::Debug, down: &dyn fmt::Debug" +message = "do consumer event keycode={:?} down={:?}" +enabled = true + +[[events]] +name = "usb_consumer_queue_full" +args = "" +message = "consumer queue is full" +enabled = true + [[events]] name = "usb_no_data_in_usb_device" args = "" diff --git a/ui/src/input.rs b/ui/src/input.rs index 6f1e10c77ef61e15bbc22b4c6a9c35b35a962bc3..44c076ef4576d1513658dd0b332318f52ad494a8 100644 --- a/ui/src/input.rs +++ b/ui/src/input.rs @@ -68,6 +68,13 @@ pub const NUM_LOCK_LED: u8 = 0x1; pub const CAPS_LOCK_LED: u8 = 0x2; pub const SCROLL_LOCK_LED: u8 = 0x4; +// consumer +pub const CONSUMER_PREFIX: u16 = 0xe000; +pub const CONSUMER_UP: u16 = 0x0100; +pub const MEDIA_PLAY_PAUSE: u16 = CONSUMER_PREFIX | 0x22; +pub const MEDIA_STOP: u16 = CONSUMER_PREFIX | 0x24; +pub const MEDIA_PREVIOUS: u16 = CONSUMER_PREFIX | 0x10; +pub const MEDIA_NEXT: u16 = CONSUMER_PREFIX | 0x19; static INPUTS: Lazy>> = Lazy::new(|| Arc::new(Mutex::new(Inputs::default()))); static LED_STATE: Lazy>> = @@ -258,6 +265,8 @@ struct Inputs { kbd_lists: HashMap>>, tablet_ids: Vec, tablet_lists: HashMap>>, + consumer_ids: Vec, + consumer_lists: HashMap>>, keyboard_state: KeyBoardState, btn_state: u32, } @@ -295,6 +304,22 @@ impl Inputs { } } + fn register_consumer(&mut self, device: &str, consumer: Arc>) { + self.consumer_ids.insert(0, device.to_string()); + self.consumer_lists.insert(device.to_string(), consumer); + } + + fn unregister_consumer(&mut self, device: &str) { + self.consumer_lists.remove(device); + let len = self.consumer_ids.len(); + for i in 0..len { + if self.consumer_ids[i] == device { + self.consumer_ids.remove(i); + break; + } + } + } + fn get_active_kbd(&mut self) -> Option>> { if !self.kbd_ids.is_empty() { let kbd = self.kbd_lists.get(&self.kbd_ids[0])?.clone(); @@ -304,6 +329,15 @@ impl Inputs { } } + fn get_active_consumer(&mut self) -> Option>> { + if !self.consumer_ids.is_empty() { + let consumer = self.consumer_lists.get(&self.consumer_ids[0])?.clone(); + Some(consumer) + } else { + None + } + } + fn get_active_mouse(&mut self) -> Option>> { if !self.tablet_ids.is_empty() { let mouse = self.tablet_lists.get(&self.tablet_ids[0])?.clone(); @@ -351,6 +385,14 @@ pub fn unregister_pointer(device: &str) { INPUTS.lock().unwrap().unregister_mouse(device); } +pub fn register_consumer(device: &str, consumer: Arc>) { + INPUTS.lock().unwrap().register_consumer(device, consumer); +} + +pub fn unregister_consumer(device: &str) { + INPUTS.lock().unwrap().unregister_consumer(device); +} + pub fn input_move_abs(axis: Axis, data: u32) -> Result<()> { let mut input_event = InputEvent::new(InputType::MoveEvent); let move_event = MoveEvent { axis, data }; @@ -408,6 +450,14 @@ pub fn key_event(keycode: u16, down: bool) -> Result<()> { Ok(()) } +pub fn consumer_event(keycode: u16, down: bool) -> Result<()> { + let consumer = INPUTS.lock().unwrap().get_active_consumer(); + if let Some(c) = consumer { + c.lock().unwrap().do_consumer_event(keycode, down)?; + } + Ok(()) +} + pub fn trigger_key(keycode: u16) -> Result<()> { key_event(keycode, true)?; key_event(keycode, false) @@ -517,6 +567,10 @@ pub trait PointerOpts: Send { fn sync(&mut self) -> Result<()>; } +pub trait ConsumerOpts: Send { + fn do_consumer_event(&mut self, keycode: u16, down: bool) -> Result<()>; +} + #[cfg(test)] mod tests { use anyhow::bail; diff --git a/ui/src/keycode.rs b/ui/src/keycode.rs index 16db2dbd431fa95fb2293a96c4d2c3c69ef9d73a..b10df32decaaf33aed83de16d0a5080eceef60f2 100644 --- a/ui/src/keycode.rs +++ b/ui/src/keycode.rs @@ -10,6 +10,7 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. +use crate::input::{MEDIA_NEXT, MEDIA_PLAY_PAUSE, MEDIA_PREVIOUS, MEDIA_STOP}; use std::collections::HashMap; pub enum DpyMod { @@ -198,6 +199,10 @@ pub enum KeyCode { MetaL, MetaR, Brokenbar, + MediaPlayPause, + MediaStop, + MediaPrevious, + MediaNext, } impl KeyCode { @@ -326,56 +331,60 @@ impl KeyCode { KeyCode::SuperL => 0x00DB, KeyCode::SuperR => 0x00DC, KeyCode::Menu => 0x00DD, - KeyCode::Exclam => 0x0102, // Shift 1 - KeyCode::At => 0x0103, // Shift 2 - KeyCode::Numbersign => 0x0104, // Shift 3 - KeyCode::Dollar => 0x0105, // Shift 4 - KeyCode::Percent => 0x0106, // Shift 5 - KeyCode::Asciicircum => 0x0107, // Shift 6 - KeyCode::Ampersand => 0x0108, // Shift 7 - KeyCode::Asterisk => 0x0109, // Shift 8 - KeyCode::Parenleft => 0x010A, // Shift 9 - KeyCode::Parenright => 0x010B, // Shift 0 - KeyCode::Underscore => 0x010C, // Shift Minus - KeyCode::Plus => 0x010D, // Shift Equal - KeyCode::KeyQ => 0x0110, // Shift q - KeyCode::KeyW => 0x0111, // Shift w - KeyCode::KeyE => 0x0112, // Shift e - KeyCode::KeyR => 0x0113, // Shift r - KeyCode::KeyT => 0x0114, // Shift t - KeyCode::KeyY => 0x0115, // Shift y - KeyCode::KeyU => 0x0116, // Shift u - KeyCode::KeyI => 0x0117, // Shift i - KeyCode::KeyO => 0x0118, // Shift o - KeyCode::KeyP => 0x0119, // Shift p - KeyCode::Braceleft => 0x011A, // Shift Bracketleft - KeyCode::Braceright => 0x011B, // Shift Bracketright - KeyCode::KeyA => 0x011E, // Shift a - KeyCode::KeyS => 0x011F, // Shift s - KeyCode::KeyD => 0x0120, // Shift d - KeyCode::KeyF => 0x0121, // Shift f - KeyCode::KeyG => 0x0122, // Shift g - KeyCode::KeyH => 0x0123, // Shift h - KeyCode::KeyJ => 0x0124, // Shift j - KeyCode::KeyK => 0x0125, // Shift k - KeyCode::KeyL => 0x0126, // Shift l - KeyCode::Colon => 0x0127, // Shift Semicolon - KeyCode::Quotedbl => 0x0128, // Shift Apostrophe - KeyCode::Asciitilde => 0x0129, // Shift Grave - KeyCode::Bar => 0x012B, // Shift Backslash - KeyCode::KeyZ => 0x012C, // Shift z - KeyCode::KeyX => 0x012D, // Shift x - KeyCode::KeyC => 0x012E, // Shift c - KeyCode::KeyV => 0x012F, // Shift v - KeyCode::KeyB => 0x0130, // Shift b - KeyCode::KeyN => 0x0131, // Shift n - KeyCode::KeyM => 0x0132, // Shift m - KeyCode::Less => 0x0133, // Shift Comma - KeyCode::Greater => 0x0134, // Shift Period - KeyCode::Question => 0x0135, // Shift Slash - KeyCode::MetaL => 0x0138, // Shift AltL - KeyCode::MetaR => 0x01B8, // Shift AltR - KeyCode::Brokenbar => 0x0956, // Shift Altgr Less + KeyCode::Exclam => 0x0102, // Shift 1 + KeyCode::At => 0x0103, // Shift 2 + KeyCode::Numbersign => 0x0104, // Shift 3 + KeyCode::Dollar => 0x0105, // Shift 4 + KeyCode::Percent => 0x0106, // Shift 5 + KeyCode::Asciicircum => 0x0107, // Shift 6 + KeyCode::Ampersand => 0x0108, // Shift 7 + KeyCode::Asterisk => 0x0109, // Shift 8 + KeyCode::Parenleft => 0x010A, // Shift 9 + KeyCode::Parenright => 0x010B, // Shift 0 + KeyCode::Underscore => 0x010C, // Shift Minus + KeyCode::Plus => 0x010D, // Shift Equal + KeyCode::KeyQ => 0x0110, // Shift q + KeyCode::KeyW => 0x0111, // Shift w + KeyCode::KeyE => 0x0112, // Shift e + KeyCode::KeyR => 0x0113, // Shift r + KeyCode::KeyT => 0x0114, // Shift t + KeyCode::KeyY => 0x0115, // Shift y + KeyCode::KeyU => 0x0116, // Shift u + KeyCode::KeyI => 0x0117, // Shift i + KeyCode::KeyO => 0x0118, // Shift o + KeyCode::KeyP => 0x0119, // Shift p + KeyCode::Braceleft => 0x011A, // Shift Bracketleft + KeyCode::Braceright => 0x011B, // Shift Bracketright + KeyCode::KeyA => 0x011E, // Shift a + KeyCode::KeyS => 0x011F, // Shift s + KeyCode::KeyD => 0x0120, // Shift d + KeyCode::KeyF => 0x0121, // Shift f + KeyCode::KeyG => 0x0122, // Shift g + KeyCode::KeyH => 0x0123, // Shift h + KeyCode::KeyJ => 0x0124, // Shift j + KeyCode::KeyK => 0x0125, // Shift k + KeyCode::KeyL => 0x0126, // Shift l + KeyCode::Colon => 0x0127, // Shift Semicolon + KeyCode::Quotedbl => 0x0128, // Shift Apostrophe + KeyCode::Asciitilde => 0x0129, // Shift Grave + KeyCode::Bar => 0x012B, // Shift Backslash + KeyCode::KeyZ => 0x012C, // Shift z + KeyCode::KeyX => 0x012D, // Shift x + KeyCode::KeyC => 0x012E, // Shift c + KeyCode::KeyV => 0x012F, // Shift v + KeyCode::KeyB => 0x0130, // Shift b + KeyCode::KeyN => 0x0131, // Shift n + KeyCode::KeyM => 0x0132, // Shift m + KeyCode::Less => 0x0133, // Shift Comma + KeyCode::Greater => 0x0134, // Shift Period + KeyCode::Question => 0x0135, // Shift Slash + KeyCode::MetaL => 0x0138, // Shift AltL + KeyCode::MetaR => 0x01B8, // Shift AltR + KeyCode::Brokenbar => 0x0956, // Shift Altgr Less + KeyCode::MediaPlayPause => MEDIA_PLAY_PAUSE, // Play/Pause + KeyCode::MediaStop => MEDIA_STOP, // Stop + KeyCode::MediaPrevious => MEDIA_PREVIOUS, // Scan Previous Track + KeyCode::MediaNext => MEDIA_NEXT, // Scan Next Track } } @@ -397,7 +406,11 @@ impl KeyCode { } #[cfg(all(target_env = "ohos", feature = "ohui_srv"))] -const KEY_CODE_OH: [(KeyCode, u16); 105] = [ +const KEY_CODE_OH: [(KeyCode, u16); 109] = [ + (KeyCode::MediaPlayPause, 0x000A), + (KeyCode::MediaStop, 0x000B), + (KeyCode::MediaPrevious, 0x000C), + (KeyCode::MediaNext, 0x000D), (KeyCode::Key0, 0x07D0), (KeyCode::Key1, 0x07D1), (KeyCode::Key2, 0x07D2), diff --git a/ui/src/ohui_srv/msg_handle.rs b/ui/src/ohui_srv/msg_handle.rs index 18ec699c0ed9d211ccc13b3c2a54b3076c49ecbd..33212428323bdfc5fab11d4a58d19e71fb975adf 100755 --- a/ui/src/ohui_srv/msg_handle.rs +++ b/ui/src/ohui_srv/msg_handle.rs @@ -28,10 +28,10 @@ use crate::{ input::{ self, get_kbd_led_state, input_button, input_move_abs, input_point_sync, keyboard_update, release_all_btn, release_all_key, trigger_key, Axis, ABS_MAX, CAPS_LOCK_LED, - INPUT_BUTTON_WHEEL_DOWN, INPUT_BUTTON_WHEEL_LEFT, INPUT_BUTTON_WHEEL_RIGHT, - INPUT_BUTTON_WHEEL_UP, INPUT_POINT_BACK, INPUT_POINT_FORWARD, INPUT_POINT_LEFT, - INPUT_POINT_MIDDLE, INPUT_POINT_RIGHT, KEYCODE_CAPS_LOCK, KEYCODE_NUM_LOCK, - KEYCODE_SCR_LOCK, NUM_LOCK_LED, SCROLL_LOCK_LED, + CONSUMER_PREFIX, INPUT_BUTTON_WHEEL_DOWN, INPUT_BUTTON_WHEEL_LEFT, + INPUT_BUTTON_WHEEL_RIGHT, INPUT_BUTTON_WHEEL_UP, INPUT_POINT_BACK, INPUT_POINT_FORWARD, + INPUT_POINT_LEFT, INPUT_POINT_MIDDLE, INPUT_POINT_RIGHT, KEYCODE_CAPS_LOCK, + KEYCODE_NUM_LOCK, KEYCODE_SCR_LOCK, NUM_LOCK_LED, SCROLL_LOCK_LED, }, keycode::{DpyMod, KeyCode}, }; @@ -88,15 +88,26 @@ impl WindowState { fn do_key_action(&self, keycode: u16, action: u16) -> Result<()> { let press = action != 0; - keyboard_update(press, keycode)?; - input::key_event(keycode, press).map_err(|e| { - anyhow!( - "do key event failed: code: {}, action: {}, {:?}", - keycode, - press, - e - ) - }) + if keycode & CONSUMER_PREFIX == CONSUMER_PREFIX { + input::consumer_event(keycode, press).map_err(|e| { + anyhow!( + "do consumer event failed: code: {}, action: {}, {:?}", + keycode, + press, + e + ) + }) + } else { + keyboard_update(press, keycode)?; + input::key_event(keycode, press).map_err(|e| { + anyhow!( + "do key event failed: code: {}, action: {}, {:?}", + keycode, + press, + e + ) + }) + } } fn move_pointer(&mut self, x: f64, y: f64) -> Result<()> {