hid_io_core/module/
mod.rs

1/* Copyright (C) 2017-2022 by Jacob Alexander
2 *
3 * This file is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This file is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this file.  If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/// Platform specific character output and IME control
18pub mod daemonnode;
19pub mod displayserver;
20pub mod vhid;
21
22use crate::api;
23use crate::built_info;
24use crate::device;
25use crate::mailbox;
26use hid_io_protocol::commands::*;
27use hid_io_protocol::{HidIoCommandId, HidIoPacketType};
28use tokio_stream::{wrappers::BroadcastStream, StreamExt};
29
30/// Max number of commands supported by this hid-io-core processor
31/// can be increased as necessary.
32const CMD_SIZE: usize = 200;
33
34/// hid-io-protocol CommandInterface for top-level module
35/// Used to serialize the Ack packets before sending them through the mailbox
36struct CommandInterface {
37    src: mailbox::Address,
38    dst: mailbox::Address,
39    mailbox: mailbox::Mailbox,
40}
41
42impl
43    Commands<
44        { mailbox::HIDIO_PKT_BUF_DATA_SIZE },
45        { mailbox::HIDIO_PKT_BUF_DATA_SIZE - 1 },
46        { mailbox::HIDIO_PKT_BUF_DATA_SIZE - 2 },
47        { mailbox::HIDIO_PKT_BUF_DATA_SIZE - 4 },
48        CMD_SIZE,
49    > for CommandInterface
50{
51    fn tx_packetbuffer_send(
52        &mut self,
53        buf: &mut mailbox::HidIoPacketBuffer,
54    ) -> Result<(), CommandError> {
55        if let Some(rcvmsg) = self.mailbox.try_send_message(mailbox::Message {
56            src: self.src,
57            dst: self.dst,
58            data: buf.clone(),
59        })? {
60            // Handle ack/nak
61            self.rx_message_handling(rcvmsg.data)?;
62        }
63        Ok(())
64    }
65
66    fn h0000_supported_ids_cmd(
67        &mut self,
68        _data: h0000::Cmd,
69    ) -> Result<h0000::Ack<CMD_SIZE>, h0000::Nak> {
70        let ids = heapless::Vec::from_slice(&crate::supported_ids()).unwrap();
71        Ok(h0000::Ack { ids })
72    }
73
74    fn h0001_info_cmd(
75        &mut self,
76        data: h0001::Cmd,
77    ) -> Result<h0001::Ack<{ mailbox::HIDIO_PKT_BUF_DATA_SIZE - 1 }>, h0001::Nak> {
78        let mut ack = h0001::Ack::<{ mailbox::HIDIO_PKT_BUF_DATA_SIZE - 1 }> {
79            property: data.property,
80            os: h0001::OsType::Unknown,
81            number: 0,
82            string: heapless::String::from(""),
83        };
84        match data.property {
85            h0001::Property::MajorVersion => {
86                ack.number = built_info::PKG_VERSION_MAJOR.parse::<u16>().unwrap();
87            }
88            h0001::Property::MinorVersion => {
89                ack.number = built_info::PKG_VERSION_MINOR.parse::<u16>().unwrap();
90            }
91            h0001::Property::PatchVersion => {
92                ack.number = built_info::PKG_VERSION_PATCH.parse::<u16>().unwrap();
93            }
94            h0001::Property::OsType => {
95                ack.os = match built_info::CFG_OS {
96                    "windows" => h0001::OsType::Windows,
97                    "macos" => h0001::OsType::MacOs,
98                    "ios" => h0001::OsType::Ios,
99                    "linux" => h0001::OsType::Linux,
100                    "android" => h0001::OsType::Android,
101                    "freebsd" => h0001::OsType::FreeBsd,
102                    "openbsd" => h0001::OsType::OpenBsd,
103                    "netbsd" => h0001::OsType::NetBsd,
104                    _ => h0001::OsType::Unknown,
105                };
106            }
107            h0001::Property::OsVersion => match sys_info::os_release() {
108                Ok(version) => {
109                    ack.string = heapless::String::from(version.as_str());
110                }
111                Err(e) => {
112                    error!("OS Release retrieval failed: {}", e);
113                    return Err(h0001::Nak {
114                        property: h0001::Property::OsVersion,
115                    });
116                }
117            },
118            h0001::Property::HostSoftwareName => {
119                ack.string = heapless::String::from(built_info::PKG_NAME);
120            }
121            _ => {
122                return Err(h0001::Nak {
123                    property: h0001::Property::Unknown,
124                });
125            }
126        }
127        Ok(ack)
128    }
129
130    fn h0030_openurl_cmd(
131        &mut self,
132        data: h0030::Cmd<{ mailbox::HIDIO_PKT_BUF_DATA_SIZE }>,
133    ) -> Result<h0030::Ack, h0030::Nak> {
134        debug!("Open url: {}", data.url);
135        let url = String::from(data.url.as_str());
136        if let Err(err) = open::that(url.clone()) {
137            error!("Failed to open url: {:?} - {:?}", url, err);
138            Err(h0030::Nak {})
139        } else {
140            Ok(h0030::Ack {})
141        }
142    }
143}
144
145/// Supported Ids by this module
146/// recursive option applies supported ids from child modules as well
147pub fn supported_ids(recursive: bool) -> Vec<HidIoCommandId> {
148    let mut ids = vec![
149        HidIoCommandId::GetInfo,
150        HidIoCommandId::OpenUrl,
151        HidIoCommandId::SupportedIds,
152    ];
153    if recursive {
154        ids.extend(displayserver::supported_ids().iter().cloned());
155    }
156    ids
157}
158
159/// Device initialization
160/// Sets up a scanning thread per Device type (using tokio).
161/// Each scanning thread will create a new thread per device found.
162/// The scanning thread is required in case devices are plugged/unplugged while running.
163/// If a device is unplugged, the Device thread will exit.
164pub async fn initialize(mailbox: mailbox::Mailbox) {
165    info!("Initializing modules...");
166
167    // Setup local thread
168    // Due to some of the setup in the Module struct we need to run processing in the same local
169    // thread.
170    let mailbox1 = mailbox.clone();
171    let data = tokio::spawn(async move {
172        // Setup receiver stream
173        let sender = mailbox1.clone().sender.clone();
174        let receiver = sender.clone().subscribe();
175        tokio::pin! {
176            let stream = BroadcastStream::new(receiver)
177                .filter(Result::is_ok).map(Result::unwrap)
178                .take_while(|msg|
179                    msg.src != mailbox::Address::DropSubscription &&
180                    msg.dst != mailbox::Address::CancelAllSubscriptions
181                )
182                .filter(|msg| msg.dst == mailbox::Address::Module || msg.dst == mailbox::Address::All)
183                .filter(|msg| supported_ids(false).contains(&msg.data.id))
184                .filter(|msg| msg.data.ptype == HidIoPacketType::Data || msg.data.ptype == HidIoPacketType::NaData);
185        }
186
187        // Process filtered message stream
188        while let Some(msg) = stream.next().await {
189            // Process buffer using hid-io-protocol
190            let mut intf = CommandInterface {
191                src: msg.dst, // Replying to message
192                dst: msg.src, // Replying to message
193                mailbox: mailbox1.clone(),
194            };
195            if let Err(err) = intf.rx_message_handling(msg.clone().data) {
196                warn!("Failed to process({:?}): {:?}", err, msg);
197            }
198        }
199    });
200
201    // NAK unsupported command ids
202    let mailbox2 = mailbox.clone();
203    let naks = tokio::spawn(async move {
204        // Setup receiver stream
205        let sender = mailbox2.clone().sender.clone();
206        let receiver = sender.clone().subscribe();
207        tokio::pin! {
208            let stream = BroadcastStream::new(receiver)
209                .filter(Result::is_ok).map(Result::unwrap)
210                .take_while(|msg|
211                    msg.src != mailbox::Address::DropSubscription &&
212                    msg.dst != mailbox::Address::CancelAllSubscriptions
213                )
214                .filter(|msg| !(
215                    supported_ids(true).contains(&msg.data.id) ||
216                    api::supported_ids().contains(&msg.data.id) ||
217                    device::supported_ids(true).contains(&msg.data.id)
218                ))
219                .filter(|msg| msg.data.ptype == HidIoPacketType::Data || msg.data.ptype == HidIoPacketType::NaData);
220        }
221
222        // Process filtered message stream
223        while let Some(msg) = stream.next().await {
224            warn!("Unknown command ID: {:?} ({})", msg.data.id, msg.data.ptype);
225            // Only send NAK with Data packets (NaData packets don't have acknowledgements, so just
226            // warn)
227            if msg.data.ptype == HidIoPacketType::Data {
228                msg.send_nak(sender.clone(), vec![]);
229            }
230        }
231    });
232
233    let (_, _, _, _, _) = tokio::join!(
234        daemonnode::initialize(mailbox.clone()),
235        displayserver::initialize(mailbox.clone()),
236        naks,
237        data,
238        vhid::initialize(mailbox.clone()),
239    );
240}
241
242/// Used when displayserver feature is disabled
243#[cfg(not(feature = "displayserver"))]
244mod displayserver {
245    use crate::mailbox;
246    use hid_io_protocol::HidIoCommandId;
247
248    pub async fn initialize(_mailbox: mailbox::Mailbox) {}
249    pub fn supported_ids() -> Vec<HidIoCommandId> {
250        vec![]
251    }
252}
253
254/// Used when displayserver feature is disabled
255#[cfg(not(feature = "dev-capture"))]
256mod vhid {
257    use crate::mailbox;
258
259    pub async fn initialize(_mailbox: mailbox::Mailbox) {}
260}