1#![warn(missing_docs)]
2
3extern crate indexmap;
10extern crate regex;
11#[macro_use]
12extern crate slog;
13#[macro_use]
14extern crate winapi;
15
16mod com;
17pub mod ctsndcr;
18mod hresult;
19mod lazy;
20pub mod media;
21pub mod soundcore;
22mod winapiext;
23
24use futures::stream::Fuse;
25use futures::task::Context;
26use futures::{Stream, StreamExt};
27
28use indexmap::IndexMap;
29
30use std::collections::BTreeSet;
31use std::error::Error;
32use std::ffi::OsStr;
33use std::fmt;
34use std::pin::Pin;
35use std::task::Poll;
36
37use slog::Logger;
38
39use crate::com::event::ComEventIterator;
40use crate::media::{DeviceEnumerator, Endpoint, VolumeEvents, VolumeNotification};
41use crate::soundcore::{
42 SoundCore, SoundCoreEvent, SoundCoreEventIterator, SoundCoreEvents, SoundCoreFeature,
43 SoundCoreParamValue, SoundCoreParameter,
44};
45
46pub use crate::hresult::Win32Error;
47
48#[cfg(not(any(target_arch = "x86", feature = "ctsndcr_ignore_arch")))]
49compile_error!("This crate must be built for x86 for compatibility with sound drivers." +
50 "(build for i686-pc-windows-msvc or suppress this error using feature ctsndcr_ignore_arch)");
51
52#[derive(Debug)]
54pub struct EndpointConfiguration {
55 pub volume: Option<f32>,
57}
58
59#[derive(Debug)]
61pub struct Configuration {
62 pub endpoint: Option<EndpointConfiguration>,
64 pub creative: Option<IndexMap<String, IndexMap<String, SoundCoreParamValue>>>,
66}
67
68pub struct DeviceInfo {
70 pub id: String,
72 pub interface: String,
74 pub description: String,
76}
77
78pub fn list_devices(logger: &Logger) -> Result<Vec<DeviceInfo>, Box<dyn Error>> {
90 let endpoints = DeviceEnumerator::with_logger(logger.clone())?.get_active_audio_endpoints()?;
91 let mut result = Vec::with_capacity(endpoints.len());
92 for endpoint in endpoints {
93 let id = endpoint.id()?;
94 debug!(logger, "Querying endpoint {}...", id);
95 result.push(DeviceInfo {
96 id,
97 interface: endpoint.interface()?,
98 description: endpoint.description()?,
99 })
100 }
101 Ok(result)
102}
103
104fn get_endpoint(logger: Logger, device_id: Option<&OsStr>) -> Result<Endpoint, Win32Error> {
105 let enumerator = DeviceEnumerator::with_logger(logger)?;
106 Ok(match device_id {
107 Some(id) => enumerator.get_endpoint(id)?,
108 None => enumerator.get_default_audio_endpoint()?,
109 })
110}
111
112pub fn dump(logger: &Logger, device_id: Option<&OsStr>) -> Result<Configuration, Box<dyn Error>> {
122 let endpoint = get_endpoint(logger.clone(), device_id)?;
123
124 let endpoint_output = EndpointConfiguration {
125 volume: Some(endpoint.get_volume()?),
126 };
127
128 let id = endpoint.id()?;
129 debug!(logger, "Found device {}", id);
130 let clsid = endpoint.clsid()?;
131 debug!(
132 logger,
133 "Found clsid {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
134 clsid.Data1,
135 clsid.Data2,
136 clsid.Data3,
137 clsid.Data4[0],
138 clsid.Data4[1],
139 clsid.Data4[2],
140 clsid.Data4[3],
141 clsid.Data4[4],
142 clsid.Data4[5],
143 clsid.Data4[6],
144 clsid.Data4[7]
145 );
146 let core = SoundCore::for_device(&clsid, &id, logger.clone())?;
147
148 let mut context_output = IndexMap::new();
149 for feature in core.features(0) {
150 let feature = feature?;
151 debug!(logger, "{:08x} {}", feature.id, feature.description);
152
153 let mut feature_output = IndexMap::new();
154 for parameter in feature.parameters() {
155 let parameter = parameter?;
156 debug!(logger, " {} {}", parameter.id, parameter.description);
157 debug!(logger, " attributes: {}", parameter.attributes);
158 if let Some(size) = parameter.size {
159 debug!(logger, " size: {}", size);
160 }
161 if parameter.attributes & 1 == 0 {
163 match parameter.kind {
164 1 => {
165 let value = parameter.get()?;
166 debug!(logger, " value: {:?}", value);
167 match value {
168 SoundCoreParamValue::None => {}
169 _ => {
170 feature_output.insert(parameter.description.clone(), value);
171 }
172 }
173 }
174 0 | 2 | 3 => {
175 let value = parameter.get()?;
176 debug!(logger, " minimum: {:?}", parameter.min_value);
177 debug!(logger, " maximum: {:?}", parameter.max_value);
178 debug!(logger, " step: {:?}", parameter.step_size);
179 debug!(logger, " value: {:?}", value);
180 match value {
181 SoundCoreParamValue::None => {}
182 _ => {
183 feature_output.insert(parameter.description.clone(), value);
184 }
185 }
186 }
187 5 => {}
188 _ => {
189 debug!(logger, " kind: {}", parameter.kind);
190 }
191 }
192 }
193 }
194 if !feature_output.is_empty() {
196 context_output.insert(feature.description.clone(), feature_output);
197 }
198 }
199
200 Ok(Configuration {
201 endpoint: Some(endpoint_output),
202 creative: Some(context_output),
203 })
204}
205
206pub fn set(
227 logger: &Logger,
228 device_id: Option<&OsStr>,
229 configuration: &Configuration,
230 mute: bool,
231) -> Result<(), Box<dyn Error>> {
232 let endpoint = get_endpoint(logger.clone(), device_id)?;
233 let mute_unmute = mute && !endpoint.get_mute()?;
234 if mute_unmute {
235 endpoint.set_mute(true)?;
236 }
237 let result = set_internal(logger, configuration, &endpoint);
238 if mute_unmute {
239 endpoint.set_mute(false)?;
240 }
241
242 result
243}
244
245pub fn watch(
257 logger: &Logger,
258 device_id: Option<&OsStr>,
259) -> Result<SoundCoreEventIterator, Box<dyn Error>> {
260 let endpoint = get_endpoint(logger.clone(), device_id)?;
261 let id = endpoint.id()?;
262 let clsid = endpoint.clsid()?;
263 let core = SoundCore::for_device(&clsid, &id, logger.clone())?;
264
265 Ok(core.events()?)
266}
267
268#[derive(Debug)]
270pub enum SoundCoreOrVolumeEvent {
271 SoundCore(SoundCoreEvent),
273 Volume(VolumeNotification),
275}
276
277struct SoundCoreAndVolumeEvents {
278 sound_core: Fuse<SoundCoreEvents>,
279 volume: Fuse<VolumeEvents>,
280}
281
282impl Stream for SoundCoreAndVolumeEvents {
283 type Item = Result<SoundCoreOrVolumeEvent, Win32Error>;
284
285 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
286 if let Poll::Ready(Some(item)) = Pin::new(&mut self.sound_core).poll_next(cx) {
287 Poll::Ready(Some(match item {
288 Ok(item) => Ok(SoundCoreOrVolumeEvent::SoundCore(item)),
289 Err(err) => Err(err),
290 }))
291 } else if let Poll::Ready(Some(item)) = Pin::new(&mut self.volume).poll_next(cx) {
292 Poll::Ready(Some(Ok(SoundCoreOrVolumeEvent::Volume(item))))
293 } else {
294 Poll::Pending
295 }
296 }
297}
298
299pub struct SoundCoreAndVolumeEventIterator {
304 inner: ComEventIterator<SoundCoreAndVolumeEvents>,
305}
306
307impl Iterator for SoundCoreAndVolumeEventIterator {
308 type Item = Result<SoundCoreOrVolumeEvent, Win32Error>;
309
310 fn next(&mut self) -> Option<Self::Item> {
311 self.inner.next()
312 }
313}
314
315pub fn watch_with_volume(
327 logger: &Logger,
328 device_id: Option<&OsStr>,
329) -> Result<SoundCoreAndVolumeEventIterator, Box<dyn Error>> {
330 let endpoint = get_endpoint(logger.clone(), device_id)?;
331 let id = endpoint.id()?;
332 let clsid = endpoint.clsid()?;
333 let core = SoundCore::for_device(&clsid, &id, logger.clone())?;
334
335 let core_events = core.event_stream()?;
336 let volume_events = endpoint.event_stream()?;
337
338 Ok(SoundCoreAndVolumeEventIterator {
339 inner: ComEventIterator::new(SoundCoreAndVolumeEvents {
340 sound_core: core_events.fuse(),
341 volume: volume_events.fuse(),
342 }),
343 })
344}
345
346#[derive(Debug)]
347struct UnsupportedValueError {
348 feature: String,
349 parameter: String,
350 expected: &'static str,
351 actual: &'static str,
352}
353
354impl fmt::Display for UnsupportedValueError {
355 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
356 write!(
357 f,
358 "Unsupported value for {}.{}. Expected {}, got {}.",
359 self.feature, self.parameter, self.expected, self.actual
360 )
361 }
362}
363
364impl Error for UnsupportedValueError {
365 fn description(&self) -> &str {
366 "The provided value was not compatible with the specified parameter."
367 }
368
369 fn cause(&self) -> Option<&dyn Error> {
370 None
371 }
372}
373
374fn coerce_soundcore(
375 feature: &SoundCoreFeature,
376 parameter: &SoundCoreParameter,
377 value: &SoundCoreParamValue,
378) -> Result<SoundCoreParamValue, UnsupportedValueError> {
379 match (value, parameter.kind) {
380 (&SoundCoreParamValue::Float(f), 0) => Ok(SoundCoreParamValue::Float(f)),
381 (&SoundCoreParamValue::U32(i), 0) => Ok(SoundCoreParamValue::Float(i as f32)),
382 (&SoundCoreParamValue::I32(i), 0) => Ok(SoundCoreParamValue::Float(i as f32)),
383 (&SoundCoreParamValue::Bool(b), 1) => Ok(SoundCoreParamValue::Bool(b)),
384 (&SoundCoreParamValue::U32(i), 2) => Ok(SoundCoreParamValue::U32(i)),
385 (&SoundCoreParamValue::I32(i), 2) if 0 <= i => Ok(SoundCoreParamValue::U32(i as u32)),
386 (&SoundCoreParamValue::I32(i), 3) => Ok(SoundCoreParamValue::I32(i)),
387 (&SoundCoreParamValue::U32(i), 3) if i <= i32::max_value() as u32 => {
388 Ok(SoundCoreParamValue::I32(i as i32))
389 }
390 _ => {
391 let actual = match *value {
392 SoundCoreParamValue::Float(_) => "float",
393 SoundCoreParamValue::Bool(_) => "bool",
394 SoundCoreParamValue::I32(_) => "int",
395 SoundCoreParamValue::U32(_) => "uint",
396 SoundCoreParamValue::None => "<unsupported>",
397 };
398 Err(UnsupportedValueError {
399 feature: feature.description.to_owned(),
400 parameter: parameter.description.to_owned(),
401 expected: match parameter.kind {
402 0 => "float",
403 1 => "bool",
404 2 => "uint",
405 3 => "int",
406 _ => "<unsupported>",
407 },
408 actual,
409 })
410 }
411 }
412}
413
414fn set_internal(
415 logger: &Logger,
416 configuration: &Configuration,
417 endpoint: &Endpoint,
418) -> Result<(), Box<dyn Error>> {
419 if let Some(ref creative) = configuration.creative {
420 let id = endpoint.id()?;
421 debug!(logger, "Found device {}", id);
422 let clsid = endpoint.clsid()?;
423 debug!(
424 logger,
425 "Found clsid \
426 {{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
427 clsid.Data1,
428 clsid.Data2,
429 clsid.Data3,
430 clsid.Data4[0],
431 clsid.Data4[1],
432 clsid.Data4[2],
433 clsid.Data4[3],
434 clsid.Data4[4],
435 clsid.Data4[5],
436 clsid.Data4[6],
437 clsid.Data4[7]
438 );
439 let core = SoundCore::for_device(&clsid, &id, logger.clone())?;
440
441 let mut unhandled_feature_names = BTreeSet::<&str>::new();
442 for (key, _) in creative.iter() {
443 unhandled_feature_names.insert(key);
444 }
445
446 for feature in core.features(0) {
447 let feature = feature?;
448 trace!(logger, "Looking for {} settings...", feature.description);
449 if let Some(feature_table) = creative.get(&feature.description) {
450 unhandled_feature_names.remove(&feature.description[..]);
451 let mut unhandled_parameter_names = BTreeSet::<&str>::new();
452 for (key, _) in feature_table.iter() {
453 unhandled_parameter_names.insert(key);
454 }
455
456 for parameter in feature.parameters() {
457 let mut parameter = parameter?;
458 trace!(
459 logger,
460 "Looking for {}.{} settings...",
461 feature.description,
462 parameter.description
463 );
464 if let Some(value) = feature_table.get(¶meter.description) {
465 unhandled_parameter_names.remove(¶meter.description[..]);
466 let value = &coerce_soundcore(&feature, ¶meter, value)?;
467 if let Err(error) = parameter.set(value) {
468 error!(
469 logger,
470 "Could not set parameter {}.{}: {}",
471 feature.description,
472 parameter.description,
473 error
474 );
475 }
476 }
477 }
478 for unhandled in unhandled_parameter_names {
479 warn!(
480 logger,
481 "Could not find parameter {}.{}", feature.description, unhandled
482 );
483 }
484 }
485 }
486 for unhandled in unhandled_feature_names {
487 warn!(logger, "Could not find feature {}", unhandled);
488 }
489 }
490 if let Some(ref endpoint_config) = configuration.endpoint {
491 if let Some(v) = endpoint_config.volume {
492 endpoint.set_volume(v)?;
493 }
494 }
495 Ok(())
496}