gsmtc/
manager.rs

1use crate::{
2    session::{SessionCommand, SessionHandle, SessionUpdateEvent},
3    util::ResultExt,
4};
5use std::{collections::HashMap, sync::Arc};
6use tokio::sync::mpsc;
7use tracing::{event, Level};
8use windows::{
9    core::Result,
10    Foundation::{EventRegistrationToken, TypedEventHandler},
11    Media::Control::{
12        GlobalSystemMediaTransportControlsSession, GlobalSystemMediaTransportControlsSessionManager,
13    },
14};
15
16/// This is the main worker.
17/// It listens to the added/removed sessions.
18///
19/// Use [`create`](Self::create) to create a new instance.
20pub struct SessionManager {
21    sessions: HashMap<String, SessionHandle>,
22    next_session_id: usize,
23
24    manager: GlobalSystemMediaTransportControlsSessionManager,
25
26    event_tx: mpsc::UnboundedSender<ManagerEvent>,
27
28    loop_tx: Arc<mpsc::UnboundedSender<ManagerCommand>>,
29    loop_rx: mpsc::UnboundedReceiver<ManagerCommand>,
30
31    changed_token: EventRegistrationToken,
32    current_changed_token: EventRegistrationToken,
33}
34
35/// Events emitted by a [`SessionManager`](crate::SessionManager).
36#[derive(Debug)]
37pub enum ManagerEvent {
38    /// Occurs when a new session was created.
39    SessionCreated {
40        /// The _internal_ session-id of the created session.
41        session_id: usize,
42        /// A handle to events emitted by the session.
43        rx: mpsc::UnboundedReceiver<SessionUpdateEvent>,
44        /// The [source-app-user-model-id](https://learn.microsoft.com/uwp/api/windows.media.control.globalsystemmediatransportcontrolssession.sourceappusermodelid) of the session.
45        ///
46        /// This is the identifier for a session and assumed to be unique per session.
47        source: String,
48    },
49    /// Occurs when a previously added session was removed.
50    SessionRemoved {
51        /// The _internal_ session-id of the removed session.
52        session_id: usize,
53    },
54    /// Occurs when the current session has changed.
55    /// This is the session that the system believes is the one the user would most likely want to control.
56    CurrentSessionChanged {
57        /// The _internal_ session-id of the new session.
58        /// If this is [`None`](Option::None), then there's no current session.
59        session_id: Option<usize>,
60    },
61}
62
63#[derive(Debug, Clone, Copy)]
64pub enum ManagerCommand {
65    UpdateSessions,
66    CurrentSessionChanged,
67}
68
69impl SessionManager {
70    /// Creates a new [`SessionManager`](crate::SessionManager) and spawns its worker-task.
71    ///
72    /// This mainly calls [`RequestAsync`](https://learn.microsoft.com/uwp/api/windows.media.control.globalsystemmediatransportcontrolssessionmanager.requestasync) on the [`GlobalSystemMediaTransportControlsSessionManager`](https://learn.microsoft.com/uwp/api/windows.media.control.globalsystemmediatransportcontrolssessionmanager).
73    ///
74    /// The manager stops after the returned receiver is dropped and an attempt was made to send an event.
75    pub async fn create() -> Result<mpsc::UnboundedReceiver<ManagerEvent>> {
76        let this = GlobalSystemMediaTransportControlsSessionManager::RequestAsync()?.await?;
77
78        let (event_tx, event_rx) = mpsc::unbounded_channel();
79        let (loop_tx, loop_rx) = mpsc::unbounded_channel();
80        let loop_tx = Arc::new(loop_tx);
81
82        let update_token = {
83            let loop_tx = Arc::downgrade(&loop_tx);
84            this.SessionsChanged(&TypedEventHandler::new(move |_, _| {
85                event!(Level::DEBUG, "SessionsChanged");
86                if let Some(loop_tx) = loop_tx.upgrade() {
87                    loop_tx.send(ManagerCommand::UpdateSessions).ok();
88                }
89                Ok(())
90            }))?
91        };
92        let current_changed_token = {
93            let loop_tx = Arc::downgrade(&loop_tx);
94            this.CurrentSessionChanged(&TypedEventHandler::new(move |_, _| {
95                event!(Level::DEBUG, "Current SessionChanged");
96                if let Some(loop_tx) = loop_tx.upgrade() {
97                    loop_tx.send(ManagerCommand::CurrentSessionChanged).ok();
98                }
99                Ok(())
100            }))?
101        };
102
103        SessionManager {
104            sessions: Default::default(),
105            next_session_id: 0,
106            manager: this,
107            event_tx,
108            loop_tx,
109            loop_rx,
110
111            changed_token: update_token,
112            current_changed_token,
113        }
114        .spawn();
115
116        Ok(event_rx)
117    }
118
119    fn spawn(self) {
120        tokio::spawn(self.run());
121    }
122
123    async fn run(mut self) {
124        self.loop_tx.send(ManagerCommand::UpdateSessions).ok();
125        self.loop_tx
126            .send(ManagerCommand::CurrentSessionChanged)
127            .ok();
128        while let Some(cmd) = self.loop_rx.recv().await {
129            if let Err(e) = self.handle_command(cmd) {
130                event!(Level::ERROR, error = %e, command = ?cmd, "Manager encountered error - exiting");
131                break;
132            }
133        }
134        event!(Level::INFO, "Manager Loop Ended")
135    }
136
137    fn handle_command(&mut self, cmd: ManagerCommand) -> Result<()> {
138        match cmd {
139            ManagerCommand::UpdateSessions => {
140                let updated: Result<HashMap<String, GlobalSystemMediaTransportControlsSession>> =
141                    self.manager
142                        .GetSessions()?
143                        .into_iter()
144                        .map(|session| Ok((session.SourceAppUserModelId()?.to_string(), session)))
145                        .collect();
146                let mut updated = updated?;
147
148                let to_remove: Vec<(String, usize)> = self
149                    .sessions
150                    .iter()
151                    .filter_map(|(k, session)| {
152                        if updated.remove(k).is_some() {
153                            None
154                        } else {
155                            Some((k.clone(), session.id))
156                        }
157                    })
158                    .collect();
159
160                event!(Level::DEBUG, "Update: remove {} sessions", to_remove.len());
161
162                for (session, id) in to_remove {
163                    self.event_tx
164                        .send(ManagerEvent::SessionRemoved { session_id: id })
165                        .ok();
166                    if let Some(session) = self.sessions.remove(&session) {
167                        session.sender.send(SessionCommand::Close).ok();
168                    }
169                }
170
171                for (model_id, to_create) in updated.into_iter() {
172                    self.create_session(model_id, to_create)?;
173                }
174            }
175            ManagerCommand::CurrentSessionChanged => {
176                match self.manager.GetCurrentSession().opt() {
177                    // There's a new session
178                    Ok(Some(current)) => {
179                        match self
180                            .sessions
181                            .get(&current.SourceAppUserModelId()?.to_string())
182                        {
183                            // We have the session saved
184                            Some(session) => {
185                                self.event_tx
186                                    .send(ManagerEvent::CurrentSessionChanged {
187                                        session_id: Some(session.id),
188                                    })
189                                    .ok();
190                            }
191                            // It's a new session
192                            None => {
193                                let session_id = self.create_session(
194                                    current.SourceAppUserModelId()?.to_string(),
195                                    current,
196                                )?;
197                                self.event_tx
198                                    .send(ManagerEvent::CurrentSessionChanged {
199                                        session_id: Some(session_id),
200                                    })
201                                    .ok();
202                            }
203                        }
204                    }
205                    // There's no current session
206                    Ok(None) => {
207                        self.event_tx
208                            .send(ManagerEvent::CurrentSessionChanged { session_id: None })
209                            .ok();
210                    }
211                    Err(e) => {
212                        event!(Level::WARN, error = %e, "Could not get current session");
213                    }
214                };
215            }
216        }
217        Ok(())
218    }
219
220    fn create_session(
221        &mut self,
222        model_id: String,
223        session: GlobalSystemMediaTransportControlsSession,
224    ) -> Result<usize> {
225        let id = self.next_session_id;
226        self.next_session_id = self.next_session_id.overflowing_add(1).0;
227
228        let (tx, rx) = mpsc::unbounded_channel();
229
230        let (session, source) = SessionHandle::create(id, session, tx)?;
231        self.sessions.insert(model_id, session);
232
233        event!(Level::DEBUG, id, %source, "New Session");
234
235        self.event_tx
236            .send(ManagerEvent::SessionCreated {
237                session_id: id,
238                rx,
239                source,
240            })
241            .ok();
242
243        Ok(id)
244    }
245}
246
247impl Drop for SessionManager {
248    fn drop(&mut self) {
249        self.manager.RemoveSessionsChanged(self.changed_token).ok();
250        self.manager
251            .RemoveCurrentSessionChanged(self.current_changed_token)
252            .ok();
253    }
254}