1use std::{
4 any::{Any, TypeId},
5 collections::HashMap,
6 ffi::c_void,
7};
8
9use crate::types::{PayloadBox, PayloadBuilderRegistry, Ustr};
10
11pub mod animations;
12pub mod commands;
13pub mod listeners;
14pub mod message;
15pub mod monitors;
16pub mod persistence;
17pub mod types;
18
19#[derive(Clone)]
20pub struct ActionDispatcher(pub futures_channel::mpsc::UnboundedSender<crate::message::Message>);
21
22impl ActionDispatcher {
23 pub fn dispatch(&self, command: &str) -> bool {
24 self.dispatch_payload(command, None)
25 }
26
27 pub fn dispatch_named(&self, name: &str) -> bool {
28 self.send(crate::listeners::ListenerAction::Named(Ustr::from(name)))
29 }
30
31 pub fn dispatch_payload(&self, name: &str, payload: Option<PayloadBox>) -> bool {
32 self.send(crate::listeners::ListenerAction::Payload {
33 name: Ustr::from(name),
34 payload,
35 })
36 }
37
38 pub fn dispatch_with_args(
39 &self,
40 name: &str,
41 args: &[Ustr],
42 builders: &PayloadBuilderRegistry,
43 ) -> bool {
44 match builders.build(name, args) {
45 Some(payload) => self.dispatch_payload(name, Some(payload)),
46 None => self.dispatch_named(name),
47 }
48 }
49
50 pub fn dispatch_action(&self, action: crate::listeners::ListenerAction) -> bool {
51 self.send(action)
52 }
53
54 fn send(&self, action: crate::listeners::ListenerAction) -> bool {
55 self
56 .0
57 .unbounded_send(crate::message::Message::FdUpdate(action))
58 .is_ok()
59 }
60}
61
62#[derive(Debug, PartialEq, Clone)]
63pub struct Workspace {
64 pub id: u64,
65 pub idx: u8,
66 pub name: Option<String>,
67 pub output: Option<String>,
68 pub is_urgent: bool,
69 pub is_active: bool,
70 pub is_focused: bool,
71}
72
73#[derive(Debug, PartialEq, Clone)]
74pub struct Monitor {
75 pub name: Ustr,
76 pub active_workspace: u32,
77 pub width: u32,
78 pub height: u32,
79 pub scale: f64,
80}
81
82impl Monitor {
83 pub fn named(name: impl Into<Ustr>) -> Self {
84 Self {
85 name: name.into(),
86 active_workspace: 0,
87 width: 0,
88 height: 0,
89 scale: 1.0,
90 }
91 }
92}
93
94#[derive(Debug, Clone)]
95pub struct Window {
96 pub id: u64,
97 pub title: String,
98 pub class: String,
99 pub is_active: bool,
100 pub metadata: HashMap<String, String>,
102}
103
104impl Window {
105 pub const META_PID: &'static str = "pid";
106 pub const META_WIDTH: &'static str = "width";
107 pub const META_HEIGHT: &'static str = "height";
108
109 pub fn pid(&self) -> Option<i32> {
110 self.metadata.get(Self::META_PID)?.parse().ok()
111 }
112
113 pub fn size(&self) -> Option<(u32, u32)> {
114 let width = self.metadata.get(Self::META_WIDTH)?.parse().ok()?;
115 let height = self.metadata.get(Self::META_HEIGHT)?.parse().ok()?;
116
117 Some((width, height))
118 }
119}
120
121#[doc(hidden)]
122trait HandleStateTuple: Sized {
123 fn run<R>(
124 state: &mut Store,
125 f: impl FnOnce(&mut Store, Self) -> miette::Result<R>,
126 ) -> miette::Result<R>;
127}
128
129#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
130pub struct StoreKey {
131 pub type_name: &'static str,
132 pub size: usize,
133 pub align: usize,
134}
135
136impl StoreKey {
137 pub fn of<T: 'static>() -> Self {
138 Self {
139 type_name: std::any::type_name::<T>(),
140 size: core::mem::size_of::<T>(),
141 align: core::mem::align_of::<T>(),
142 }
143 }
144
145 pub fn matches<T: 'static>(&self) -> bool {
146 self == &Self::of::<T>()
147 }
148}
149
150pub struct ErasedEntry {
151 pub key: StoreKey,
152 pub ptr: *mut c_void,
153 pub drop_fn: extern "C" fn(*mut c_void),
154}
155
156impl ErasedEntry {
157 pub fn new<T: 'static>(value: T) -> Self {
158 extern "C" fn drop_it<T>(ptr: *mut c_void) {
159 unsafe { drop(Box::from_raw(ptr as *mut T)) };
160 }
161 Self {
162 key: StoreKey::of::<T>(),
163 ptr: Box::into_raw(Box::new(value)) as *mut c_void,
164 drop_fn: drop_it::<T>,
165 }
166 }
167
168 pub fn take<T: 'static>(mut self) -> Option<T> {
169 if !self.key.matches::<T>() {
170 return None;
171 }
172 let ptr = self.ptr;
173 self.ptr = core::ptr::null_mut();
174 Some(unsafe { *Box::from_raw(ptr as *mut T) })
175 }
176}
177
178impl Drop for ErasedEntry {
179 fn drop(&mut self) {
180 if !self.ptr.is_null() {
181 (self.drop_fn)(self.ptr);
182 self.ptr = core::ptr::null_mut();
183 }
184 }
185}
186
187#[derive(Clone, Copy, PartialEq, Eq, Hash)]
188enum StoreId {
189 TypeId(TypeId),
190 Erased(StoreKey),
191}
192
193enum Entry {
194 Any(Box<dyn Any>),
195 Erased(ErasedEntry),
196}
197
198impl Entry {
199 fn ptr_mut<T: 'static>(&mut self) -> Option<*mut T> {
200 match self {
201 Entry::Any(boxed) => boxed.downcast_mut::<T>().map(|value| value as *mut T),
202 Entry::Erased(entry) => {
203 if entry.key.matches::<T>() {
204 Some(entry.ptr as *mut T)
205 } else {
206 None
207 }
208 }
209 }
210 }
211}
212
213pub struct Store {
214 contents: HashMap<StoreId, Entry>,
215}
216
217impl Store {
218 pub fn new() -> Self {
219 Self {
220 contents: Default::default(),
221 }
222 }
223
224 pub fn insert_raw(&mut self, type_id: TypeId, val: Box<dyn Any>) -> bool {
225 let id = StoreId::TypeId(type_id);
226 if self.contents.contains_key(&id) {
227 return false;
228 }
229
230 self.contents.insert(id, Entry::Any(val));
231 true
232 }
233
234 pub fn insert<T: 'static>(&mut self, val: T) -> bool {
235 let id = StoreId::Erased(StoreKey::of::<T>());
236 if self.contents.contains_key(&id) {
237 return false;
238 }
239
240 self
241 .contents
242 .insert(id, Entry::Erased(ErasedEntry::new(val)));
243 true
244 }
245
246 fn lookup_ids<T: 'static>() -> [StoreId; 2] {
247 [
248 StoreId::Erased(StoreKey::of::<T>()),
249 StoreId::TypeId(TypeId::of::<T>()),
250 ]
251 }
252
253 pub fn borrow<T: 'static>(&self) -> Option<&T> {
254 for id in Self::lookup_ids::<T>() {
255 match self.contents.get(&id) {
256 Some(Entry::Any(boxed)) => {
257 if let Some(value) = boxed.downcast_ref::<T>() {
258 return Some(value);
259 }
260 }
261 Some(Entry::Erased(entry)) if entry.key.matches::<T>() => {
262 return Some(unsafe { &*(entry.ptr as *const T) });
263 }
264 _ => {}
265 }
266 }
267 None
268 }
269
270 pub fn borrow_mut<T: 'static>(&mut self) -> Option<&mut T> {
271 for id in Self::lookup_ids::<T>() {
272 let matches = match self.contents.get_mut(&id) {
273 Some(entry) => entry.ptr_mut::<T>(),
274 None => None,
275 };
276 if let Some(ptr) = matches {
277 return Some(unsafe { &mut *ptr });
278 }
279 }
280 None
281 }
282
283 pub fn remove<T: 'static>(&mut self) -> Option<T> {
284 for id in Self::lookup_ids::<T>() {
285 let matches = matches!(self.contents.get(&id), Some(Entry::Erased(entry)) if entry.key.matches::<T>())
286 || matches!(self.contents.get(&id), Some(Entry::Any(boxed)) if boxed.is::<T>());
287 if !matches {
288 continue;
289 }
290 return match self.contents.remove(&id) {
291 Some(Entry::Any(boxed)) => boxed.downcast::<T>().ok().map(|boxed| *boxed),
292 Some(Entry::Erased(entry)) => entry.take::<T>(),
293 None => None,
294 };
295 }
296 None
297 }
298
299 pub fn erased_insert(&mut self, entry: ErasedEntry) -> bool {
300 let id = StoreId::Erased(entry.key);
301 if self.contents.contains_key(&id) {
302 return false;
303 }
304 self.contents.insert(id, Entry::Erased(entry));
305 true
306 }
307
308 pub fn erased_contains(&self, key: StoreKey) -> bool {
309 matches!(
310 self.contents.get(&StoreId::Erased(key)),
311 Some(Entry::Erased(entry)) if entry.key == key
312 )
313 }
314
315 pub fn erased_get(&self, key: StoreKey) -> Option<*mut c_void> {
316 match self.contents.get(&StoreId::Erased(key)) {
317 Some(Entry::Erased(entry)) if entry.key == key => Some(entry.ptr),
318 _ => None,
319 }
320 }
321
322 pub fn erased_remove(&mut self, key: StoreKey) -> Option<ErasedEntry> {
323 let id = StoreId::Erased(key);
324 let matches = matches!(self.contents.get(&id), Some(Entry::Erased(entry)) if entry.key == key);
325 if !matches {
326 return None;
327 }
328 match self.contents.remove(&id) {
329 Some(Entry::Erased(entry)) => Some(entry),
330 _ => None,
331 }
332 }
333
334 #[allow(warnings)]
335 pub fn handle<T, R>(
336 &mut self,
337 f: impl FnOnce(&mut Store, T) -> miette::Result<R>,
338 ) -> miette::Result<R>
339 where
340 T: HandleStateTuple,
341 {
342 T::run(self, f)
343 }
344}
345
346macro_rules! impl_handle_tuple {
347 ($($T:ident),+) => {
348 impl<$($T: 'static),+> HandleStateTuple for ($(&mut $T,)+) {
349 fn run<R>(
350 store: &mut Store,
351 f: impl FnOnce(&mut Store, Self) -> miette::Result<R>,
352 ) -> miette::Result<R> {
353 {
354 let keys = [$(StoreKey::of::<$T>()),+];
355
356 for i in 0..keys.len() {
357 for j in (i + 1)..keys.len() {
358 if keys[i] == keys[j] {
359 miette::bail!("Duplicate type requested");
360 }
361 }
362 }
363 }
364
365 $(
366 #[allow(non_snake_case)]
367 let mut $T = {
368 let mut taken = None;
369 for id in Store::lookup_ids::<$T>() {
370 if let Some(entry) = store.contents.remove(&id) {
371 taken = Some((id, entry));
372 break;
373 }
374 }
375
376 taken.ok_or_else(|| miette::miette!("Missing instance"))?
377 };
378 )+
379
380 let result = {
381 let tuple = (
382 $(
383 {
384 let ptr = $T
385 .1
386 .ptr_mut::<$T>()
387 .ok_or_else(|| miette::miette!("Type mismatch"))?;
388
389 unsafe { &mut *ptr }
390 },
391 )+
392 );
393
394 f(store, tuple)
395 };
396
397 $(
398 store.contents.insert($T.0, $T.1);
399 )+
400
401 result
402 }
403 }
404 };
405}
406
407impl_handle_tuple!(A, B);
408impl_handle_tuple!(A, B, C);
409impl_handle_tuple!(A, B, C, D);
410impl_handle_tuple!(A, B, C, D, E);
411impl_handle_tuple!(A, B, C, D, E, F);