1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//! The "Lookit!" crate checks for new devices in a cross-platform asynchronous
//! manner.  Returns the `RawFd` equivalent for the target platform.
//!
//!  - Linux: inotify on /dev/*
//!  - Web: JavaScript event listeners
//!  - Others: TODO
//!
//! ## Getting Started
//! ```rust, no_run
//! use lookit::Searcher;
//! use pasts::prelude::*;
//!
//! #[async_main::async_main]
//! async fn main(_spawner: impl async_main::Spawn) {
//!     let mut searcher = Searcher::with_camera();
//!     loop {
//!         let file = searcher.next().await;
//!         dbg!(file);
//!     }
//! }
//! ```
//!
//! ## Implementation
//! Input
//!  - inotify => /dev/input/event*
//!  - `window.addEventListener("gamepadconnected", function(e) { });`
//!
//! Audio
//!  - inotify => /dev/snd/pcm*
//!  - `navigator.mediaDevices.getUserMedia(constraints).then(function(s) {
//!    }).catch(function(denied_err) {})` // only one speakers connection ever
//!
//! MIDI
//!  - inotify => /dev/snd/midi*, if no /dev/snd then /dev/midi*
//!  - <https://developer.mozilla.org/en-US/docs/Web/API/MIDIAccess>
//!
//! Camera
//!  - inotify => /dev/video*
//!  - `navigator.mediaDevices.getUserMedia(constraints).then(function(s) {
//!    }).catch(function(denied_err) {})`

#![warn(
    anonymous_parameters,
    missing_copy_implementations,
    missing_debug_implementations,
    missing_docs,
    nonstandard_style,
    rust_2018_idioms,
    single_use_lifetimes,
    trivial_casts,
    trivial_numeric_casts,
    unreachable_pub,
    unused_extern_crates,
    unused_qualifications,
    variant_size_differences
)]

#[cfg_attr(target_os = "linux", path = "linux.rs")]
#[cfg_attr(not(target_os = "linux"), path = "mock.rs")]
mod platform;

use std::{cell::Cell, fmt};

use pasts::prelude::*;
use smelling_salts::Device;

/// Device kinds
enum Kind {
    Input(),
    Audio(),
    Midi(),
    Camera(),
}

enum Events {
    Read(),
    Write(),
    All(),
}

/// Platform implementation
struct Platform;

/// Interface should be implemented for each `Platform`
trait Interface {
    type Searcher: Notify<Event = Found> + Send + Unpin;

    /// Create a searcher for a specific type of device
    fn searcher(kind: Kind) -> Option<Self::Searcher>;

    /// Try to watch a found device for both read+write events
    fn open(found: Found, events: Events) -> Result<Device, Found>;
}

/// Lookit [`Notify`].  Lets you know when a device is [`Found`].
pub struct Searcher(Cell<Option<<Platform as Interface>::Searcher>>);

impl fmt::Debug for Searcher {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Searcher").finish_non_exhaustive()
    }
}

impl Searcher {
    /// Create new future checking for input devices.
    pub fn with_input() -> Self {
        Self(Platform::searcher(Kind::Input()).into())
    }

    /// Create new future checking for audio devices (speakers, microphones).
    pub fn with_audio() -> Self {
        Self(Platform::searcher(Kind::Audio()).into())
    }

    /// Create new future checking for MIDI devices.
    pub fn with_midi() -> Self {
        Self(Platform::searcher(Kind::Midi()).into())
    }

    /// Create new future checking for camera devices.
    pub fn with_camera() -> Self {
        Self(Platform::searcher(Kind::Camera()).into())
    }
}

impl Notify for Searcher {
    type Event = Found;

    fn poll_next(mut self: Pin<&mut Self>, task: &mut Task<'_>) -> Poll<Found> {
        let Some(ref mut notifier) = self.0.get_mut() else { return Pending };

        Pin::new(notifier).poll_next(task)
    }
}

/// Device found by the [`Searcher`] notifier.
pub struct Found(Cell<String>);

impl fmt::Debug for Found {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let path = self.0.take();

        f.debug_struct("Found").field("path", &path).finish()?;
        self.0.set(path);

        Ok(())
    }
}

impl Found {
    /// Connect to device (input + output)
    pub fn connect(self) -> Result<Device, Found> {
        Platform::open(self, Events::All())
    }

    /// Connect to device (input only)
    pub fn connect_input(self) -> Result<Device, Found> {
        Platform::open(self, Events::Read())
    }

    /// Connect to device (output only)
    pub fn connect_output(self) -> Result<Device, Found> {
        Platform::open(self, Events::Write())
    }
}