| 1 | //! Desktop notification when a render ends while the window is unfocused |
| 2 | //! (Tim t1025u). Freedesktop notifications on Linux/BSD, a toast on Windows. |
| 3 | |
| 4 | /// How a single render or the Render Alles queue ended. |
| 5 | #[derive(Clone, Debug, PartialEq, Eq)] |
| 6 | pub enum RenderOutcome { |
| 7 | /// One render (or the whole Render Alles queue) finished; `text` is the |
| 8 | /// status line also shown in the window. |
| 9 | Done { ok: bool, text: String }, |
| 10 | } |
| 11 | |
| 12 | /// The toast to show, if any. |
| 13 | #[derive(Clone, Debug, PartialEq, Eq)] |
| 14 | pub struct Toast { |
| 15 | pub summary: String, |
| 16 | pub body: String, |
| 17 | } |
| 18 | |
| 19 | /// Decide whether to notify: only when the window is not focused. |
| 20 | pub fn toast_for(outcome: &RenderOutcome, window_focused: bool) -> Option<Toast> { |
| 21 | if window_focused { |
| 22 | return None; |
| 23 | } |
| 24 | let RenderOutcome::Done { ok, text } = outcome; |
| 25 | let summary = if *ok { |
| 26 | crate::i18n::t("Render klaar") |
| 27 | } else { |
| 28 | crate::i18n::t("Render niet gelukt") |
| 29 | }; |
| 30 | Some(Toast { |
| 31 | summary, |
| 32 | body: crate::i18n::t(text), |
| 33 | }) |
| 34 | } |
| 35 | |
| 36 | /// Show the toast on a short-lived thread; delivery errors are ignored. |
| 37 | pub fn show(toast: Toast) { |
| 38 | let _ = std::thread::Builder::new() |
| 39 | .name("render-notification".into()) |
| 40 | .spawn(move || { |
| 41 | let mut n = notify_rust::Notification::new(); |
| 42 | n.summary(&["Paperdoll Renderer", &toast.summary].join(": ")) |
| 43 | .body(&toast.body) |
| 44 | .appname("Paperdoll Renderer"); |
| 45 | #[cfg(all(unix, not(target_os = "macos")))] |
| 46 | { |
| 47 | n.icon(crate::icon::APP_ID) |
| 48 | .hint(notify_rust::Hint::DesktopEntry(crate::icon::APP_ID.into())); |
| 49 | } |
| 50 | let _ = n.show(); |
| 51 | }); |
| 52 | } |
| 53 | |
| 54 | #[cfg(test)] |
| 55 | mod tests { |
| 56 | use super::*; |
| 57 | |
| 58 | #[test] |
| 59 | fn focused_window_gets_no_toast() { |
| 60 | let o = RenderOutcome::Done { |
| 61 | ok: true, |
| 62 | text: "Batch opgeslagen: x".into(), |
| 63 | }; |
| 64 | assert_eq!(toast_for(&o, true), None); |
| 65 | } |
| 66 | |
| 67 | #[test] |
| 68 | fn unfocused_window_gets_one_toast_with_status() { |
| 69 | let o = RenderOutcome::Done { |
| 70 | ok: true, |
| 71 | text: "Batch opgeslagen: /p/out".into(), |
| 72 | }; |
| 73 | let t = toast_for(&o, false).unwrap(); |
| 74 | assert!(t.body.contains("/p/out")); |
| 75 | let f = toast_for( |
| 76 | &RenderOutcome::Done { |
| 77 | ok: false, |
| 78 | text: "boom".into(), |
| 79 | }, |
| 80 | false, |
| 81 | ) |
| 82 | .unwrap(); |
| 83 | assert_ne!(t.summary, f.summary); |
| 84 | } |
| 85 | } |