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}