/* Copyright (C) 2019-2022 by Jacob Alexander
* Copyright (C) 2019 by Rowan Decker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
extern crate tokio;
use capnp::capability::Promise;
use capnp_rpc::pry;
use hid_io_core::common_capnp::NodeType;
use hid_io_core::hidio_capnp;
use hid_io_core::keyboard_capnp;
use hid_io_core::logging::setup_logging_lite;
use rand::Rng;
use std::io::Read;
use std::io::Write;
#[derive(Default)]
pub struct KeyboardSubscriberImpl {
pub hall_effect_switch_data: Vec<u16>,
}
impl keyboard_capnp::keyboard::subscriber::Server for KeyboardSubscriberImpl {
fn update(
&mut self,
params: keyboard_capnp::keyboard::subscriber::UpdateParams,
_results: keyboard_capnp::keyboard::subscriber::UpdateResults,
) -> Promise<(), ::capnp::Error> {
let signal = pry!(pry!(params.get()).get_signal());
// Only read cli messages
if let Ok(signaltype) = signal.get_data().which() {
match signaltype {
hid_io_core::keyboard_capnp::keyboard::signal::data::Which::Cli(cli) => {
let cli = cli.unwrap();
print!("{}", cli.get_output().unwrap());
std::io::stdout().flush().unwrap();
}
hid_io_core::keyboard_capnp::keyboard::signal::data::Which::Manufacturing(res) => {
let res = res.unwrap();
println!("{:?}:{} => ", res.get_cmd(), res.get_arg());
match res.get_cmd().unwrap() {
keyboard_capnp::keyboard::signal::manufacturing_result::Command::LedTestSequence => match res.get_arg() {
2 | 3 => {
// LED short/open test
let mut pos: usize = 0;
let data = res.get_data().unwrap();
loop {
let chipid = data.get(pos as u32);
pos += 1;
let buffer_len = data.get(pos as u32);
pos += 1;
let buffer =
&data.as_slice().unwrap()[pos..pos + buffer_len as usize];
pos += buffer_len as usize;
println!("ChipId: {} {}", chipid, buffer_len);
for byte in buffer {
print!("{:02x} ", byte);
}
println!();
if pos >= data.len().try_into().unwrap() {
break;
}
}
}
_ => {
println!("Manufacturing command {} not implemented", res.get_arg())
}
},
keyboard_capnp::keyboard::signal::manufacturing_result::Command::HallEffectSensorTest => match res.get_arg() {
2 => {
let split = res.get_data().unwrap().len() / 2 / 6;
let mut tmp = vec![];
let mut pos = 0;
for byte in res.get_data().unwrap() {
tmp.push(byte);
if tmp.len() == 2 {
// Check if header (strobe) or sense data
if pos % 7 == 0 {
print!("{:>4} ", tmp[0]);
} else {
let data = u16::from_le_bytes([tmp[0], tmp[1]]);
print!("{:>4} ", data);
}
tmp.clear();
pos += 1;
if pos % split == 0 {
println!();
}
}
}
println!();
}
_ => {
for byte in res.get_data().unwrap() {
print!("{} ", byte);
}
println!();
}
},
_ => {
for byte in res.get_data().unwrap() {
print!("{} ", byte);
}
println!();
}
}
std::io::stdout().flush().unwrap();
}
_ => {}
}
}
Promise::ok(())
}
}
#[tokio::main]
pub async fn main() -> Result<(), ::capnp::Error> {
setup_logging_lite().ok();
tokio::task::LocalSet::new().run_until(try_main()).await
}
async fn try_main() -> Result<(), ::capnp::Error> {
// Prepare hid-io-core connection
let mut hidio_conn = hid_io_client::HidioConnection::new().unwrap();
let mut rng = rand::thread_rng();
// Serial is used for automatic reconnection if hid-io goes away and comes back
let mut serial = "".to_string();
loop {
// Connect and authenticate with hid-io-core
let (hidio_auth, _hidio_server) = hidio_conn
.connect(
hid_io_client::AuthType::Priviledged,
NodeType::HidioApi,
"RPC Test".to_string(),
format!("{:x} - pid:{}", rng.gen::<u64>(), std::process::id()),
true,
std::time::Duration::from_millis(1000),
)
.await?;
let hidio_auth = hidio_auth.expect("Could not authenticate to hid-io-core");
let nodes_resp = {
let request = hidio_auth.nodes_request();
request.send().promise.await.unwrap()
};
let nodes = nodes_resp.get()?.get_nodes()?;
let args: Vec<_> = std::env::args().collect();
let nid = match args.get(1) {
Some(n) => n.parse().unwrap(),
None => {
let id;
let serial_matched: Vec<_> = nodes
.iter()
.filter(|n| n.get_serial().unwrap() == serial)
.collect();
// First attempt to match serial number
if !serial.is_empty() && serial_matched.len() == 1 {
let n = serial_matched[0];
println!("Re-registering to {}", hid_io_client::format_node(n));
id = n.get_id();
} else {
let keyboards: Vec<_> = nodes
.iter()
.filter(|n| {
n.get_type().unwrap() == NodeType::UsbKeyboard
|| n.get_type().unwrap() == NodeType::BleKeyboard
})
.collect();
// Next, if serial number is unset and there is only one keyboard, automatically attach
if serial.is_empty() && keyboards.len() == 1 {
let n = keyboards[0];
println!("Registering to {}", hid_io_client::format_node(n));
id = n.get_id();
// Otherwise display a list of keyboard nodes
} else {
println!();
for n in keyboards {
println!(" * {} - {}", n.get_id(), hid_io_client::format_node(n));
}
print!("Please choose a device: ");
std::io::stdout().flush()?;
let mut n = String::new();
std::io::stdin().read_line(&mut n)?;
id = n.trim().parse().unwrap();
}
}
id
}
};
let device = nodes.iter().find(|n| n.get_id() == nid);
if device.is_none() {
eprintln!("Could not find node: {}", nid);
std::process::exit(1);
}
let device = device.unwrap();
serial = device.get_serial().unwrap().to_string();
// Build subscription callback
let subscription = capnp_rpc::new_client(KeyboardSubscriberImpl::default());
// Subscribe to cli messages
let subscribe_req = {
let node = match device.get_node().which().unwrap() {
hid_io_core::common_capnp::destination::node::Which::Keyboard(n) => n.unwrap(),
hid_io_core::common_capnp::destination::node::Which::Daemon(_) => {
std::process::exit(1);
}
};
let mut request = node.subscribe_request();
let mut params = request.get();
params.set_subscriber(subscription);
// Build list of options
let mut options = params.init_options(1);
let mut cli_option = options.reborrow().get(0);
cli_option.set_type(keyboard_capnp::keyboard::SubscriptionOptionType::CliOutput);
request
};
let _callback = subscribe_req.send().promise.await.unwrap();
println!("READY");
let (vt_tx, mut vt_rx) = tokio::sync::mpsc::channel::<u8>(100);
std::thread::spawn(move || loop {
#[allow(clippy::significant_drop_in_scrutinee)]
for byte in std::io::stdin().lock().bytes() {
if let Ok(b) = byte {
if let Err(e) = vt_tx.blocking_send(b) {
println!("Restarting stdin loop: {}", e);
return;
}
} else {
println!("Lost stdin");
std::process::exit(2);
}
}
});
loop {
let mut vt_buf = vec![];
// Await the first byte
match vt_rx.recv().await {
Some(c) => {
vt_buf.push(c);
}
None => {
println!("Lost socket");
::std::process::exit(1);
}
}
// Loop over the rest of the buffer
loop {
match vt_rx.try_recv() {
Ok(c) => {
vt_buf.push(c);
}
Err(tokio::sync::mpsc::error::TryRecvError::Empty) => {
// Done, can begin sending cli message to device
break;
}
Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => {
println!("Lost socket (buffer)");
::std::process::exit(1);
}
}
}
if let Ok(nodetype) = device.get_node().which() {
match nodetype {
hid_io_core::common_capnp::destination::node::Which::Keyboard(node) => {
let node = node?;
let _command_resp = {
// Cast/transform keyboard node to a hidio node
let mut request = hidio_capnp::node::Client {
client: node.client,
}
.cli_command_request();
request.get().set_command(&String::from_utf8(vt_buf)?);
match request.send().promise.await {
Ok(response) => response,
Err(e) => {
println!("Dead: {}", e);
break;
}
}
};
}
hid_io_core::common_capnp::destination::node::Which::Daemon(_node) => {}
}
}
}
}
}