aboutsummaryrefslogtreecommitdiff
path: root/pulse/src/runnable/mod.rs
blob: 9b58be730b039840049173be1881a183c24d0156 (plain) (blame)
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
166
167
168
169
170
171
172
173
174
175
176
use std::sync::mpsc::*;
use crate::config::*;
use swaystatus_plugin::*;
use crate::communication::*;
use std::convert::TryFrom;

pub mod pulse;
use pulse::{Pulse,MainLoopCreationError, PulseContext, PaContextState, SinkHandle};

pub struct PulseVolumeRunnable<'p> {
    config : &'p PulseVolumeConfig,
    to_main : Box<dyn MsgModuleToMain + 'p>,
    from_main : Receiver<MessagesFromMain>,
    pulse : Result<Pulse,MainLoopCreationError>
}

impl<'p : 's, 's> PulseVolumeRunnable<'p> {
    pub fn new(config : &'p PulseVolumeConfig, to_main : Box<dyn MsgModuleToMain + 'p>) -> (Self, SenderForMain) {
        let (s, r) = channel();
        let result = PulseVolumeRunnable {
            config,
            to_main,
            from_main : r,
            pulse: Pulse::create()
        };
        let sender = SenderForMain::new(s, result.pulse.as_ref().map_or(None,|x| Some(x.get_wake_up())));
        (result, sender)
    }
    fn send_error_to_main<E>(&self, err : E) where E : std::error::Error {
        self.to_main.send_update(Err(PluginError::ShowInsteadOfText(String::from("Error")))).expect("Tried to tell main thread that an error occured. Main thread isn't listening any more.");
        self.to_main.send_update(Err(PluginError::PrintToStdErr(err.to_string()))).expect("Tried to tell main thread that an error occured. Main thread isn't listening any more.");
    }

    fn format_and_send_updated_volume_to_main(&self, volume : &pulse::Volume) -> Result<(),PluginCommunicationError> {
        let formatted_volume = self.config.format_volume(volume.volume, volume.balance, volume.muted);
        match formatted_volume {
            Ok(msg) => { self.to_main.send_update(Ok(msg)) }
            Err(e) => {
                let full_message = e.to_string();
                match e {
                    FormattingError::EmptyMap{ numeric_fallback } => {
                        self.to_main.send_update(Err(PluginError::ShowInsteadOfText(numeric_fallback)))?;
                        self.to_main.send_update(Err(PluginError::PrintToStdErr(full_message)))
                    }
                }
            }
        }
    }
}

impl<'p> SwayStatusModuleRunnable for PulseVolumeRunnable<'p> {
    fn run(&self) {
        'outer : loop {
            let pulse = match &self.pulse {
                Err(x) => {
                    self.send_error_to_main(x);
                    return;
                }
                Ok(x) => x
            };
            let mut scratch = pulse::ContextScratch::default();
            let mut context = match PulseContext::create(pulse, &mut scratch) {
                Err(x) => {
                    self.send_error_to_main(x);
                    return;
                }
                Ok(x) => x
            };
            let mut curr_default_sink = None;
            let mut curr_volume = None;
            let mut sink_we_care_about = match &self.config.sink {
                crate::config::Sink::Default => { None }
                crate::config::Sink::Specific { sink_name } => { 
                    Some(match SinkHandle::try_from(sink_name as &str) {
                        Ok(x) => {x}
                        Err(e) => {
                            self.send_error_to_main(e);
                            return;
                        }
                    }) 
                }
            };
            loop {
                match context.get_state() {
                    PaContextState::Unconnected => { 
                        if let crate::config::Sink::Default = &self.config.sink {
                            sink_we_care_about = None; 
                        }
                        if let Err(e) = context.connect_and_set_callbacks() {
                            self.send_error_to_main(e);
                            match self.from_main.recv_timeout(std::time::Duration::from_secs(1)) {
                                Ok(x) => {
                                    if let MessagesFromMain::Quit = x {
                                        break 'outer;
                                    }
                                }
                                Err(e) => {
                                    if let RecvTimeoutError::Disconnected = e {
                                        break 'outer;
                                    }
                                }
                            }
                        }
                    }
                    PaContextState::Failed | PaContextState::Terminated => { 
                        //context is dead. Wait a second, and start over.
                        self.to_main.send_update(Err(PluginError::ShowInsteadOfText(String::from("Context died")))).expect("Tried to tell main thread that pulse context died. Main thread isn't listening.");
                        self.to_main.send_update(Err(PluginError::PrintToStdErr(String::from("Pulseaudio context entered either the Terminated or Failed state. Creating a new context and retrying")))).expect("Tried to tell main thread that pulse context died. Main thread isn't listening.");
                        match self.from_main.recv_timeout(std::time::Duration::from_secs(1)) {
                            Ok(x) => {
                                if let MessagesFromMain::Quit = x {
                                    break 'outer;
                                }
                            }
                            Err(e) => {
                                if let RecvTimeoutError::Disconnected = e {
                                    break 'outer;
                                }
                            }
                        }
                        continue 'outer;
                    }
                    PaContextState::Ready => {
                        if curr_default_sink.is_none() {
                            //this may trigger several redundant refreshes, but it _should_ only happen
                            //during startup, so we don't really care.
                            context.refresh_default_sink();
                        }
                    }
                    _ => {}
                }

                let iteration_result = context.iterate(&sink_we_care_about);
                if let Err(e) = iteration_result {
                   self.send_error_to_main(e);
                   break 'outer;
                }
                
                let pulse::IterationResult { default_sink, volume } = iteration_result.unwrap();
                if default_sink.is_some() && default_sink != curr_default_sink {
                    curr_default_sink = default_sink;
                    if let crate::config::Sink::Default = self.config.sink {
                        sink_we_care_about = curr_default_sink.clone();
                    }
                    if let Some(s) = &sink_we_care_about {
                        context.refresh_volume(s);
                    }
                }
                if volume.is_some() && volume != curr_volume {
                    curr_volume = volume;
                    self.format_and_send_updated_volume_to_main(curr_volume.as_ref().unwrap()).expect("Tried to inform main thread about volume update. Main thread isn't listening.");
                }
                match self.from_main.try_recv() {
                    Ok(x) => match x {
                        MessagesFromMain::Quit => {
                            break 'outer;
                        }
                        MessagesFromMain::Refresh => {
                            if let Some(s) = &sink_we_care_about {
                                context.refresh_volume(s);
                            }
                            if let crate::config::Sink::Default = self.config.sink {
                                context.refresh_default_sink();
                            }
                        }
                    }
                    Err(e) => {
                        if let TryRecvError::Disconnected = e {
                            break 'outer;
                        }
                    }
                }
            }
        }
    }
}