USB HID implementation for usb-device.
Also implements a cheeky procedural macro for generating HID descriptors.
Examples:
License: MIT License
USB HID implementation for usb-device.
Also implements a cheeky procedural macro for generating HID descriptors.
Examples:
We can provide comments then so people know they want the HID one for mice, keyboards joystics etc vs googling
Im writing a USB volume control using a Pi Pico - and Im having issues with the volume control sticking or repeating on either direction (up or down) intermittently.
At first I thought that it was some sort of priority issue with the USB IRQ, but after going through the USB HID documentation I noticed that the media volume/up keys (0xe9 and 0xea usage ids) are marked as RTC or re-triggerable - which says they are re-triggered for as long as the key is "asserted."
Im not sure what asserted means in that regard - how does the USB host know when the key is released? I tried sending an additional Zero report right after the media key report - but that had no effect.
When debugging it I also noticed that whenever the key does "stick" (aka repeat), for as long as its repeated and sometimes for a few moments after my actual keyboard clicks on my actual keyboard don't register - which leads me back to believe that this has more to do with the USB device hogging the bus somehow.
A few more anecdotes -
Any ideas?
Hi,
can you add a keyboard example? I want to build a mechanical keyboard with an ARM controller running Rust instead of C
and merry christmas
The library works fine on devices conencted to a linux machine but not a windows one.
After inspecting the usb initialisation sequence using wireshark, I noticed that wireshark also though the usbhid-report descriptor was malformed.
The offending Item is a '0x80'.
After comparing the generated descriptor with the example one in the HID spec I noticed it is an abbreviation for '0x81 0x00'.
Replacing the '0x80' with '0x81 0x00' and using that as the new descriptor made my device work under Windows.
I also found someone on stackoverflow who noticed similar behavior
Unspecified zero-sized items are not accepted by Windows HID parser (while they work on Mac OS, iOS and Linux). โ Nipo May 7 '16 at 16:40 ( https://stackoverflow.com/questions/36750287/two-byte-report-count-for-hid-report-descriptor/36835781 )
Lines 335 to 344 in f0ddfa7
Removing the mentioned lines would fix the problem.
I don't know a lot about USB hid though, so there might be a better way.
Due to limitations of serde and serialize it seems that only arrays up to 32 are considered.
#[gen_hid_descriptor(
(collection = APPLICATION, usage_page = 0xFF1C, usage = 0x1100) = {
rx=output;
tx=input;
}
)]
struct HidioReport {
rx: [u8; 64],
tx: [u8; 64],
}
error[E0277]: the trait bound `[u8; 64]: Serialize` is not satisfied
--> usbd-hid-io/src/lib.rs:32:1
|
32 | / #[gen_hid_descriptor(
33 | | (collection = APPLICATION, usage_page = 0xFF1C, usage = 0x1100) = {
34 | | rx=output;
35 | | tx=input;
36 | | }
37 | | )]
| | ^
| | |
| |__the trait `Serialize` is not implemented for `[u8; 64]`
| in this macro invocation
|
::: /home/hyatt/.cargo/registry/src/github.com-1ecc6299db9ec823/usbd-hid-macros-0.5.0/src/lib.rs:202:1
|
202 | pub fn gen_hid_descriptor(args: TokenStream, input: TokenStream) -> TokenStream {
| ------------------------------------------------------------------------------- in this expansion of `#[gen_hid_descriptor]`
|
= help: the following implementations were found:
<[T; 0] as Serialize>
<[T; 10] as Serialize>
<[T; 11] as Serialize>
<[T; 12] as Serialize>
and 30 others
error: aborting due to previous error; 2 warnings emitted
For more information about this error, try `rustc --explain E0277`.
error: could not compile `usbd-hid-io`
I believe https://github.com/est31/serde-big-array can help, but I haven't figured out how to use it in this case.
I'd like to push some crates that depend on newer features (get/set protocol).
Hi, I recently ran into a problem using this library where the MediaKeyboardReport.desc()
was expanding to:
[
5u8, 12u8, 9u8, 1u8, 161u8, 1u8, 5u8, 12u8, 25u8, 0u8, 42u8, 20u8, 5u8, 21u8, 0u8,
39u8, 255u8, 255u8, 0u8, 0u8, 117u8, 16u8, 149u8, 1u8, 129u8, 2u8, 192u8,
]
The expected value is:
[
5u8, 12u8, 9u8, 1u8, 161u8, 1u8, 5u8, 12u8, 25u8, 0u8, 42u8, 20u8, 5u8, 21u8, 0u8,
39u8, 255u8, 255u8, 0u8, 0u8, 117u8, 16u8, 149u8, 1u8, 129u8, 0u8, 192u8,
]
... with the second-to-last byte differing. Specifically, the data type flag is being set to variable when the declaration says it's an array. I tracked it down to dtolnay/syn@c09a2e5 ` but I am not very experienced with proc macros so I am not sure if this is a new bug in syn or if the HID macros were just relying on an old bug in syn that was fixed.
Hi
After realizing that the KeyboardReport class doesn't support media keys I decided to try to implement it for a little demo of a infrared receiver controlling the media player on my PC.
This my first ever encounter with USB and USB descriptors and based on googling and testing until it worked.
My first attempt was to add a new MediaKeyboardReport #9
Then I decided to just add it to the KeyboardReport, and that seems to work as well. #10
Both branches tested on Linux, and Linux only.
The demo can be found here:
https://github.com/jkristell/infrared/blob/cmd/examples/stm32f103-examples/examples/kbd.rs
If you think this is good enough for adding we should probably add an enum with the keycodes for the Media keys as well.
I use the gen_hid_descriptor
macro like this:
#[gen_hid_descriptor(
(collection = APPLICATION, usage_page = 0xaaaa, usage = 0xaa) = {
(usage = 0xbb,) = {
input_buffer = input;
};
(usage = 0xcc,) = {
output_buffer = output;
};
}
)]
pub struct Report {
input_buffer: [u8; 64],
output_buffer: [u8; 64],
}
the resulting descriptor looks like this:
0x06, 0xAA, 0xAA, // Usage Page (Reserved 0xAAAA)
0x09, 0xAA, // Usage (0xAA)
0xA1, 0x01, // Collection (Application)
0x09, 0xAA, // Usage (0xBB)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8)
0x95, 0x40, // Report Count (64)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0xBB, // Usage (0xCC)
0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0, // End Collection
As you can see, the logical min & max, as well as report size & count are skipped for the output. I suspect this is correct and because they are identical to the input (I'm not an expert on HID). However, I'm need them to be in there for robust device discovery. It would be great if I could disable this optimization somehow.
I'm trying to create a descriptor for a joystick with 10 buttons:
#[gen_hid_descriptor(
(collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = JOYSTICK) = {
(usage = X,) = {x=input;};
(usage = Y,) = {y=input;};
(usage_page = BUTTON, usage_min = BUTTON_1, usage_max = 10) = {
#[packed_bits 10] #[item_settings data,variable,absolute] buttons=input;
};
}
)]
struct InputReport {
x: u8,
y: u8,
buttons: u16,
}
This yields the following warning:
warning: borrow of packed field is unsafe and requires unsafe function or block (error E0133)
--> targets\stm32f072-disco\src\main.rs:23:1
|
23 | / #[gen_hid_descriptor(
24 | | (collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = JOYSTICK) = {
25 | | (usage = X,) = {x=input;};
26 | | (usage = Y,) = {y=input;};
... |
30 | | }
31 | | )]
| |__^
|
= note: `#[warn(safe_packed_borrows)]` on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #46043 <https://github.com/rust-lang/rust/issues/46043>
= note: fields of packed structs might be misaligned: dereferencing a misaligned pointer or even just creating a misaligned reference is undefined behavior
= note: this warning originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
Hi, I'm building a keyboard and am currently using this crate. I'm currently using it on MacOS.
I noticed I'm receiving phantom key inputs when I hold down 4 or more keys. Normally this would sound like ghosting, but my keyboard has diodes on every switch, and I verified with probe-run
and defmt
that I'm not adding these characters to the KeyboardReport
struct.
a
gets inserted after the fourth pressb
gets inserted after the fifth pressc
gets inserted after the sixth pressFor example:
q
, w
, e
r
results in qwera
instead of qwer
q
, w
, e
, r
t
results in qweratb
instead of qwert
q
, w
, e
, r
, t
y
results in qweratbyc
instead of qwerty
I eventually narrowed this down to the keyboard HID descriptor. I swapped it out for one from another project, and now the keyboard works properly with the scenarios above:
I don't yet know enough about USB HID descriptors to say what's going on, but it feels like a byte is out of place somewhere.
There seems to be some compatibility issue with Win10 and the generated descriptor(s).
#[gen_hid_descriptor(
(collection = APPLICATION, usage = 0x01, usage_page = 0xff00) = {
#[item_settings data] data=input;
}
)]
#[allow(dead_code)]
struct Data {
data: u8,
}
The generated descriptor looks like this.
data [
0x6, 0x0, 0xff, // USEGE PAGE Vendor
0x9, 0x1, // USAGE Vendor
0xa1, 0x1, // Collection (Application)
0x15, 0x0, // Logical minimum
0x26, 0xff, 0x0, // Logical maximum
0x75, 0x8, // Report size 8
0x95, 0x1, // Report count 1
0x81, 0x0, // Input
0xc0, // End application collection
]
This works well under Linux (tested under arch) and OSX. Hower under Win10 the device is not properly detected/configured (USB Device Viewer
shows the descriptors correctly but claims the devic to be in low power state.
Adding a usage
before Input
solves the issue.
data [
0x6, 0x0, 0xff, // USEGE PAGE Vendor
0x9, 0x1, // USAGE Vendor
0xa1, 0x1, // Collection (Application)
0x15, 0x0, // Logical minimum
0x26, 0xff, 0x0, // Logical maximum
0x75, 0x8, // Report size 8
0x95, 0x1, // Report count 1
0x09, 0x01, // USAGE Vendor <---- manually added here
0x81, 0x0, // Input
0xc0, // End application collection
]
Either MS is out of specs (the wrapping usage should be inherited) or Linux/OSX just playing nice guessing, not sure.
Maybe there is already some workaround for this problem that I missed. If not, perhaps a syntax extension to the #[item_settings usage = 0x01, ...]
would be possible. Or maybe let the proc macro always emit inherited usage
from the wrapping collection (in this case).
Admittedly I'm a USB novice, so not sure what's best here.
/Per
Add a better way to create a Joystick Report.
I'm not sure this issue is related to this crate, it can be deleted if it's considered off-topic.
I made a firmware for a macro keypad using this crate, initially everything worked fine, tested it on PCs with Fedora 36 Gnome, Ubuntu 22.04 and Raspberry Pi OS. The problem appeared on Fedora KDE, where after the device sends a few reports to the PC, the main thread seems to freeze, while the interrupt handler continues trigger. It only happens while logged in to the desktop environment, works fine in the login screen or on a non-graphical TTY.
Possibly relevant code from the project:
#[entry]
fn main() -> ! {
/*
...
Initialization code
...
*/
loop {
delay.delay_ms(30);
let reports = keys::get_keys(keys);
reports.into_iter().flatten().for_each(|report| {
push_keyboard_report(report);
});
}
}
pub fn get_keys(keys: &[&dyn InputPin<Error = Infallible>]) -> [Option<KeyboardReport>; 9] {
let mut ret = [None; 9];
let mut pressed = false;
for i in 0..keys.len() {
if keys[i].is_low().unwrap() {
let mut report = new_report();
interrupt::free(|cs| {
let buttons = &KEYS.borrow(cs).borrow().keys;
report.modifier = buttons[i].modifier;
report.keycodes = buttons[i].keycodes;
});
ret[i] = Some(report);
pressed = true;
}
}
if !pressed {
ret[0] = Some(new_report());
}
ret
}
fn push_keyboard_report(report: KeyboardReport) -> Result<usize, usb_device::UsbError> {
cortex_m::interrupt::free(|_| unsafe {
USB_HID.as_mut().map(|hid| hid.push_input(&report))
})
.unwrap()
}
#[interrupt]
unsafe fn USBCTRL_IRQ() {
// Handle USB request
let usb_dev = USB_DEVICE.as_mut().unwrap();
let usb_hid = USB_HID.as_mut().unwrap();
//let serial = USB_SERIAL.as_mut().unwrap();
usb_dev.poll(&mut [usb_hid]);
}
Tried to put defmt prints and it doesn't seem to freeze in the same place everytime. GDB doesn't really help because breakpoints also block the interrupt handling.
Any ideas on what could cause it or how could I continue to debug it?
Hi - apologies in advance if this is a really stupid question - I'm just starting to explore rust ๐
I'm trying to use your library for the hid descriptor macros (great work btw!) but I'm getting some compiler errors (using cargo build
) while it's trying to compile ssmarshal v1.0.0
:
error[E0277]: the trait bound `Error: StdError` is not satisfied
--> /home/will/.cargo/registry/src/github.com-1ecc6299db9ec823/ssmarshal-1.0.0/src/lib.rs:64:6
|
64 | impl serde::de::Error for Error {
| ^^^^^^^^^^^^^^^^ the trait `StdError` is not implemented for `Error`
|
::: /home/will/.cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.125/src/de/mod.rs:297:1
|
297 | declare_error_trait!(Error: Sized + StdError);
| ---------------------------------------------- required by this bound in `serde::de::Error`
... (one more of these)
Since your last release a few months ago, and the 1.0.0
version of ssmarshal
was released 3 years ago, I figured you must not be seeing those?
I tested with latest stable rust v1.51.0
and additionally rustc
version 1.49.0
(i.e. the version you may have been using at the time of last push to this repo?) but I'm still seeing the same errors. Unsure what else to try.
I realise this is not necessarily an issue with usbd-hid
but the ssmarshal
repo looks decidedly stale ๐ any ideas where I might be going wrong?
Hello, I'm trying to create a composite descriptor which includes KeyboardReport
/MouseReport
/SystemControlReport
etc.
The descriptor is defined as:
#[gen_hid_descriptor(
(report_id = 0x01, collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = KEYBOARD) = {
(usage_page = KEYBOARD, usage_min = 0xE0, usage_max = 0xE7) = {
#[packed_bits 8] #[item_settings data,variable,absolute] modifier=input;
};
(usage_min = 0x00, usage_max = 0xFF) = {
#[item_settings constant,variable,absolute] reserved=input;
};
(usage_page = LEDS, usage_min = 0x01, usage_max = 0x05) = {
#[packed_bits 5] #[item_settings data,variable,absolute] leds=output;
};
(usage_page = KEYBOARD, usage_min = 0x00, usage_max = 0xDD) = {
#[item_settings data,array,absolute] keycodes=input;
};
},
(report_id = 0x02, collection = APPLICATION, usage_page = CONSUMER, usage = CONSUMER_CONTROL) = {
(usage_page = CONSUMER, usage_min = 0x00, usage_max = 0x514) = {
#[item_settings data,array,absolute,not_null] usage_id=input;
};
},
(report_id = 0x03, collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = SYSTEM_CONTROL) = {
(usage_min = 0x81, usage_max = 0xB7, logical_min = 1) = {
#[item_settings data,array,absolute,not_null] usage_id=input;
};
}
)]
#[allow(dead_code)]
pub struct MyReport {
pub modifier: u8,
pub reserved: u8,
pub leds: u8,
pub keycodes: [u8; 6],
pub usage_id: u16,
}
But I have problems about implementing usbd_hid::descriptor::AsInputReport
for MyReport
.
There is no documentation/example about how to implement AsInputReport
trait for a custom report. @twitchyliquid64 any suggestions?
Hi Guys
I would like to write a small application using SystemControlReport's to put a PC to sleep or wake it up. Is there any example code that shows how this could be done?
My initial attempts to use SystemControlReport's does not seem to do anything (tested on Mac, Windows 10 and Linux!)
The base code has been modified from the twitching usb mouse example (which is working fine).
pseudo code is:
let usb_hid = HIDClass::new(bus_ref, SystemControlReport::desc(), 60);
let usb_dev = UsbDeviceBuilder::new(bus_ref, UsbVidPid(0x16c0, 0x27da))
.manufacturer("My_Comany")
.product("My_Product")
.serial_number("0000")
.device_class(0)
.build();
let report = SystemControlReport {
usage_id: SystemControlKey::Sleep.into()
}
critical_section::with(|_| unsafe {
USB_HID.as_mut().map(|hid| hid.push_input(&report))
}).unwrap()
The sending of the sleep is triggered by a GPIO pin, and I can see that the report is being sent, with the return value being 1, which I believe means that one byte was successfully sent (which is the size of the SystemControlKey event.
Any ideas? Or any pointers to functional example code?
My byteorder
PR (#46) that was just merged corrects a big conflict with embedded-graphics
and it'd be nice to not have to put:
usbd-hid = { git = "https://github.com/twitchyliquid64/usbd-hid.git", branch = "master" }
...in my Cargo.toml
.
That is all ๐
I tried adding a 4th (1 << 3
) and 5th (1 << 4
) button to my MouseReport but that doesn't work. Apparently mouse wheel events (which appear on the host OS as buttons 4 and 5) are special somehow. Likely the gen_hid_descriptor
call in descriptor.rs
needs adjusting but I don't know enough to make adjustments on my own.
Is there a way to change how I'm using this crate so that I can send wheel events?
The 0.8 releases appear to break builds that depend on usbd-hid 0.7 or 0.6. Here's a reproduction:
cargo new --lib foo && cd foo
cargo add [email protected]
cargo build
Here's the build failure:
Compiling usbd-hid-macros v0.6.0
error[E0277]: the trait bound `usbd_hid_descriptors::MainItemKind: From<std::string::String>` is not satisfied
--> /Users/mciantyre/.cargo/registry/src/index.crates.io-6f17d22bba15001f/usbd-hid-macros-0.6.0/src/spec.rs:512:43
|
512 | self.set_item(name, item_kind.into(), settings, bits, quirks);
| ^^^^ the trait `From<std::string::String>` is not implemented for `usbd_hid_descriptors::MainItemKind`, which is required by `std::string::String: Into<_>`
|
= help: the trait `From<&str>` is implemented for `usbd_hid_descriptors::MainItemKind`
= help: for that trait implementation, expected `&str`, found `std::string::String`
= note: required for `std::string::String` to implement `Into<usbd_hid_descriptors::MainItemKind>`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `usbd-hid-macros` (lib) due to 1 previous error
foo v0.1.0 (/tmp/foo)
โโโ usbd-hid v0.7.0
โโโ serde v1.0.204
โโโ ssmarshal v1.0.0
โ โโโ encode_unicode v0.3.6
โ โโโ serde v1.0.204
โโโ usb-device v0.3.2
โ โโโ heapless v0.8.0
โ โ โโโ hash32 v0.3.1
โ โ โ โโโ byteorder v1.4.3
โ โ โโโ stable_deref_trait v1.2.0
โ โโโ portable-atomic v1.6.0
โโโ usbd-hid-macros v0.6.0 (proc-macro)
โโโ byteorder v1.4.3
โโโ proc-macro2 v1.0.86
โ โโโ unicode-ident v1.0.12
โโโ quote v1.0.36
โ โโโ proc-macro2 v1.0.86 (*)
โโโ serde v1.0.204
โโโ syn v1.0.109
โ โโโ proc-macro2 v1.0.86 (*)
โ โโโ quote v1.0.36 (*)
โ โโโ unicode-ident v1.0.12
โโโ usbd-hid-descriptors v0.8.0
โโโ bitfield v0.14.0
919616b shows that usbd-hid-macros 0.6 is allowed to depend on any version of usbd-hid-descriptors starting from 0.1.2. In this reproduction, usbd-hid-macros selects usbd-hid-descriptors 0.8.0 as its dependency. These seem to be incompatible.
Since this crate has little documentation (if I am wrong please tell me) and I am a newbie to usb communications, I thought I'd just ask.
I want to use a Raspberry Pi Zero 2W (2 USB Ports) as a middleman between an otherwise working usb connection.
In the first step I basically need to be wireshark, just as hardware.
The first port is the input, where the raspi is the HID host. The second port is the output, where the raspi has to simulate being the device (necessary hardware information is available) and be the HID client to send data to its unknown host.
Is this crate low-level enough for this task or is this more of an high-level abstraction? Since I have to be able to fake and complete control what my device sends, I'd think I need something quite low level
Hi, currently the KeyboardReport
only supports reporting up to 6 key presses, not including modifier keys. I'd like to request that it be updated to support an arbitrary number of keypresses.
More can be read here: https://www.devever.net/~hl/usbnkro
Hello, I'm testing this crate for usbd usages. Everything is fine when I create less than 3 HIDClass
, but if I create 4 HIDClass
, the usb device just doesn't work:
I defined the following 4 HIDClass
:
No configuration descriptor is send from device:
The following picture is the worked case, when I created only 3 devices(hid, consumer_control_hid, custom_hid
):
In most cases USB HID Class implementations don't need both an EndpointOut and EndpointIn. This is especially painful for MCUs that have a limited number of endpoints (AVR, atsam4; 7 endpoints total).
Ideally the new (or some other designator) should indicate whether or not EndpointIn and/or EndpointOut is required for the given descriptor.
For example, Keyboards do not use an OUT endpoint as set_report is used instead to pass the LED information.
I'm going to try and ponder a PR that could address this issue.
pub fn new<'a>(alloc: &'a UsbBusAllocator<B>, report_descriptor: &'static [u8], poll_ms: u8) -> HIDClass<'a, B> {
HIDClass {
if_num: alloc.interface(),
out_ep: alloc.interrupt(64, poll_ms),
in_ep: alloc.interrupt(64, poll_ms),
report_descriptor: report_descriptor,
}
}
Hi, I think it would be more straightforward of an API to have an enum for the the different keyboard scancodes. I am happy to contribute such an enum - I would just populate it with the contents of https://usb.org/sites/default/files/hut1_22.pdf#page=83 right?
Hi, I was trying to build using esp-hal, and got the dependency conflict you seem to have noticed in the following commit:
The usb-hid crate was released, AFAICT the esb-hid-macros was not. Could that one be released? regards, Frank
Technically I ran it on atsamd51, so not exactly your example, but figure better to discuss here.
the push_mouse_movement unwrap specifically
you can avoid panic
let _ = push_mouse_movement(MouseReport { buttons: 0, x, y });
Still doesnt appear as a mouse, but then logging then shows
UsbBus::write 3 bytes [0, 0, 5] to ep EndpointAddress(129) -> BUSY trcpt1=false
UsbBus::write 3 bytes [0, 0, 251] to ep EndpointAddress(129) -> BUSY trcpt1=false
Maybe if I can get it to recognize better itll read and not be busy? not sure. Just wanted to track somewhere
The implementation of the gen_hid_descriptor
macro seems lack logical_max
field, is that right? How can i do to add logical_max
field?
code here: https://github.com/twitchyliquid64/usbd-hid/blob/master/macros/src/spec.rs#L129
if someone else designs the report, like mousereport, you dont actually know that buttons are supposed to be packed_bits, nor do you know you're hardcoded to only have 3 buttons
This could simply be documented, but seems brittle.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.