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
16pub 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#[derive(Debug)]
37pub enum ManagerEvent {
38 SessionCreated {
40 session_id: usize,
42 rx: mpsc::UnboundedReceiver<SessionUpdateEvent>,
44 source: String,
48 },
49 SessionRemoved {
51 session_id: usize,
53 },
54 CurrentSessionChanged {
57 session_id: Option<usize>,
60 },
61}
62
63#[derive(Debug, Clone, Copy)]
64pub enum ManagerCommand {
65 UpdateSessions,
66 CurrentSessionChanged,
67}
68
69impl SessionManager {
70 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 Ok(Some(current)) => {
179 match self
180 .sessions
181 .get(¤t.SourceAppUserModelId()?.to_string())
182 {
183 Some(session) => {
185 self.event_tx
186 .send(ManagerEvent::CurrentSessionChanged {
187 session_id: Some(session.id),
188 })
189 .ok();
190 }
191 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 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}