Skip to main content

ui/
sound.rs

1// sound -- the system's sounds from the UI (plat::audio::sound)
2//
3//   beep(SOUND_*) -> bool    the system's sound for an event (plat::audio::SOUND_INFO,
4//                            SOUND_WARNING, SOUND_ERROR, SOUND_QUESTION, SOUND_NOTIFY,
5//                            SOUND_DEFAULT) unless sounds are off -> false: nothing played
6//   dialog_sound(SOUND_*)    right after dialog_begin returned true: the sound once, when the
7//                            dialog has just opened, where the platform's message dialogs play
8//                            one (plat::audio::dialog_sounds: Windows like MessageBox, Linux
9//                            desktops while event sounds are on; not Android, not the browser)
10//   set_sounds(on), sounds() the app's own switch (default on; an app setting "Klänge");
11//                            jfgui_SOUNDS=0 turns them off for the process
12//
13// An app whose PERMISSIONS lack `audio.output` plays nothing: beep is refused like any other
14// access (an ask line asks, "PERMDENY audio.output" once, plat::policy::denials), dialog_sound
15// stays silent without a question or a record (plat::policy::audio_granted).
16//
17// Headless runs (scripts, --shot) play nothing: plat::audio's dry run prints "sound: NAME", and
18// the script command `expect sound KIND|none` checks the last sound requested (auto.rs).
19
20use plat::audio;
21
22use super::Ui;
23
24/// the switch and the last request (scripts)
25#[derive(Default)]
26pub(crate) struct S {
27    off: bool,
28    pub(crate) last: Option<u32>,
29}
30
31impl Ui {
32    /// the system's sound for kind (plat::audio::SOUND_*) -> false: off, unknown, not
33    /// permitted (`audio.output`) or not played
34    pub fn beep(&mut self, kind: u32) -> bool {
35        if !self.sounds() || audio::sound_name(kind).is_empty() || !plat::policy::audio_allowed() {
36            return false;
37        }
38        self.m.sound.last = Some(kind);
39        audio::set_dry_run(self.headless || self.hr_replaying()); // (hot reload's replay: silent)
40        audio::sound(kind)
41    }
42
43    /// inside an open content dialog: its sound, once when it has just opened, where message
44    /// dialogs play one on this platform and the app may play sound -> whether a sound was
45    /// asked for
46    pub fn dialog_sound(&mut self, kind: u32) -> bool {
47        let c = &mut self.wp.chrome;
48        if !c.dlg_active || !c.dlg_fresh {
49            return false;
50        }
51        c.dlg_fresh = false;
52        audio::dialog_sounds() && plat::policy::audio_granted() && self.beep(kind)
53    }
54
55    /// the app's switch for its sounds (beep, dialog_sound); on by default
56    pub fn set_sounds(&mut self, on: bool) {
57        self.m.sound.off = !on;
58    }
59
60    /// sounds are on: the app's switch and jfgui_SOUNDS (0: off)
61    pub fn sounds(&self) -> bool {
62        !self.m.sound.off && !plat::env("jfgui_SOUNDS").is_some_and(|v| v.starts_with('0'))
63    }
64}