/* * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors * Copyright (C) 2004-2021 Kim Woelders * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies of the Software, its documentation and marketing & publicity * materials, and acknowledgment shall be given in the documentation, materials * and software packages that this Software was used. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "E.h" #include "dialog.h" #include "emodule.h" #include "file.h" #include "list.h" #include "settings.h" #include "sound.h" #include "sounds.h" #define N_SOUNDS (SOUND_NOT_USED - 1) typedef struct { dlist_t list; char *name; char *file; Sample *sample; } SoundClass; #define SC_NAME(sc) ((sc) ? (sc)->name : "(none)") static struct { char enable; char *theme; unsigned int mask1, mask2; char *engine; } Conf_sound; static struct { char cfg_loaded; char *theme_path; } Mode_sound; #define SOUND_THEME_PATH ((Mode_sound.theme_path) ? Mode_sound.theme_path : Mode.theme.path) static LIST_HEAD(sound_list); #if !USE_MODULES extern const SoundOps SoundOps_esd; extern const SoundOps SoundOps_pulse; extern const SoundOps SoundOps_sndio; extern const SoundOps SoundOps_alsa; extern const SoundOps SoundOps_player; typedef struct { const char *name; const SoundOps *ops; } SoundEngine; static const SoundEngine SoundEngines[] = { #if USE_SOUND_ESOUND {"esound", &SoundOps_esd}, #endif #if USE_SOUND_PULSE {"pulse", &SoundOps_pulse}, #endif #if USE_SOUND_SNDIO {"sndio", &SoundOps_sndio}, #endif #if USE_SOUND_ALSA {"alsa", &SoundOps_alsa}, #endif #if USE_SOUND_PLAYER {"player", &SoundOps_player}, #endif }; #endif /* USE_MODULES */ static const SoundOps *ops = NULL; static void _SoundConfigLoad(void); static const char *const sound_names[N_SOUNDS] = { "SOUND_ALERT", /* 0 0x00000001 */ "SOUND_BUTTON_CLICK", "SOUND_BUTTON_RAISE", "SOUND_DEICONIFY", "SOUND_DESKTOP_LOWER", /* 4 0x00000010 */ "SOUND_DESKTOP_RAISE", "SOUND_DESKTOP_SHUT", "SOUND_ERROR_IPC", "SOUND_EXIT", /* 8 0x00000100 */ "SOUND_FOCUS_SET", "SOUND_ICONIFY", "SOUND_INSERT_KEYS", "SOUND_LOGOUT", /* 12 0x00001000 */ "SOUND_LOWER", "SOUND_MENU_SHOW", "SOUND_MOVE_AREA_DOWN", "SOUND_MOVE_AREA_LEFT", /* 16 0x00010000 */ "SOUND_MOVE_AREA_RIGHT", "SOUND_MOVE_AREA_UP", "SOUND_MOVE_RESIST", "SOUND_MOVE_START", /* 20 0x00100000 */ "SOUND_MOVE_STOP", "SOUND_RAISE", "SOUND_RESIZE_START", "SOUND_RESIZE_STOP", /* 24 0x01000000 */ "SOUND_SCANNING", "SOUND_SETTINGS_ACTIVE", "SOUND_SETTINGS_ALL", "SOUND_SETTINGS_AREA", /* 28 0x10000000 */ "SOUND_SETTINGS_AUDIO", NULL, "SOUND_SETTINGS_BG", "SOUND_SETTINGS_COMPOSITE", /* 0 0x00000001 */ "SOUND_SETTINGS_DESKTOPS", "SOUND_SETTINGS_FOCUS", "SOUND_SETTINGS_FX", "SOUND_SETTINGS_GROUP", /* 4 0x00000010 */ "SOUND_SETTINGS_ICONBOX", "SOUND_SETTINGS_MENUS", "SOUND_SETTINGS_MISCELLANEOUS", "SOUND_SETTINGS_MOVERESIZE", /* 8 0x00000100 */ "SOUND_SETTINGS_PAGER", "SOUND_SETTINGS_PLACEMENT", "SOUND_SETTINGS_SESSION", "SOUND_SETTINGS_TOOLTIPS", /* 12 0x00001000 */ "SOUND_SETTINGS_TRANS", "SOUND_SHADE", "SOUND_SLIDEOUT_SHOW", "SOUND_STARTUP", /* 16 0x00010000 */ "SOUND_UNSHADE", "SOUND_WAIT", "SOUND_WINDOW_BORDER_CHANGE", "SOUND_WINDOW_CHANGE_LAYER_DOWN", /* 20 0x00100000 */ "SOUND_WINDOW_CHANGE_LAYER_UP", "SOUND_WINDOW_CLOSE", "SOUND_WINDOW_SLIDE", "SOUND_WINDOW_SLIDE_END", /* 24 0x01000000 */ "SOUND_WINDOW_STICK", "SOUND_WINDOW_UNSTICK", }; static const SoundOps * _SoundOpsLookup1(const char *name) { #if USE_MODULES return ModLoadSym("sound", "SoundOps", name); #else unsigned int i; const SoundEngine *se; for (i = 0; i < E_ARRAY_SIZE(SoundEngines); i++) { se = &SoundEngines[i]; if (strcmp(se->name, name) == 0) return se->ops; } return NULL; #endif } static const SoundOps * _SoundOpsLookup(void) { const SoundOps *so; if (Conf_sound.engine) { so = _SoundOpsLookup1(Conf_sound.engine); if (so) return so; } /* Try default */ EFREE_DUP(Conf_sound.engine, DEFAULT_SOUND_ENGINE); so = _SoundOpsLookup1(Conf_sound.engine); return so; } static void _SclassSampleDestroy(void *data, void *user_data __UNUSED__) { SoundClass *sclass = (SoundClass *) data; if (!sclass || !sclass->sample) return; if (ops) ops->SampleDestroy(sclass->sample); sclass->sample = NULL; } static SoundClass * _SclassCreate(const char *name, const char *file) { SoundClass *sclass; sclass = EMALLOC(SoundClass, 1); if (!sclass) return NULL; LIST_PREPEND(SoundClass, &sound_list, sclass); sclass->name = Estrdup(name); sclass->file = Estrdup(file); sclass->sample = NULL; return sclass; } static void _SclassDestroy(SoundClass * sclass) { if (!sclass) return; LIST_REMOVE(SoundClass, &sound_list, sclass); _SclassSampleDestroy(sclass, NULL); Efree(sclass->name); Efree(sclass->file); Efree(sclass); } static void _SclassApply(SoundClass * sclass) { if (!sclass || !Conf_sound.enable) return; if (!sclass->sample) { char *file; file = FindFile(sclass->file, SOUND_THEME_PATH, FILE_TYPE_SOUND); if (file) { sclass->sample = ops->SampleLoad(file); Efree(file); } if (!sclass->sample) { DialogOK(_("Error finding sound file"), _("Warning! Enlightenment was unable to load the\n" "following sound file:\n%s\n" "Enlightenment will continue to operate, but you\n" "may wish to check your configuration settings.\n"), sclass->file); _SclassDestroy(sclass); return; } } ops->SamplePlay(sclass->sample); } static int _SclassMatchName(const void *data, const void *match) { return strcmp(((const SoundClass *)data)->name, (const char *)match); } static SoundClass * _SclassFind(const char *name) { return LIST_FIND(SoundClass, &sound_list, _SclassMatchName, name); } static void _SoundPlayByName(const char *name) { SoundClass *sclass; if (!Conf_sound.enable) return; if (!name || !*name) return; sclass = _SclassFind(name); if (EDebug(EDBUG_TYPE_SOUND)) Eprintf("%s: %s file=%s\n", "SclassApply", name, SC_NAME(sclass)); _SclassApply(sclass); } #define _SoundMasked(i) \ (((i) <= 32) ? Conf_sound.mask1 & (1 << ((i) - 1)) : \ Conf_sound.mask2 & (1 << ((i) - 33))) void SoundPlay(int sound) { if (!Conf_sound.enable) return; if (sound <= 0 || sound > N_SOUNDS) return; if (_SoundMasked(sound)) return; _SoundPlayByName(sound_names[sound - 1]); } static int _SoundFree(const char *name) { SoundClass *sclass; sclass = _SclassFind(name); _SclassDestroy(sclass); return !!sclass; } static void _SoundInit(void) { if (!Conf_sound.enable) return; if (!ops) ops = _SoundOpsLookup(); if (!ops || ops->Init()) { Conf_sound.enable = 0; DialogOK(_("Error initialising sound"), _ ("Audio was enabled for Enlightenment but there was an error\n" "communicating with the audio server (%s).\n" "Audio will now be disabled.\n"), Conf_sound.engine); return; } _SoundConfigLoad(); } static void _SoundExit(void) { SoundClass *sc; LIST_FOR_EACH(SoundClass, &sound_list, sc) _SclassSampleDestroy(sc, NULL); if (ops) { ops->Exit(); ops = NULL; } Conf_sound.enable = 0; } static void _SoundConfigure(int enable) { if (Conf_sound.enable == enable) return; Conf_sound.enable = enable; if (Conf_sound.enable) _SoundInit(); else _SoundExit(); } /* * Configuration load/save */ static int _SoundConfigParse(FILE * fs) { int err = 0; char s[FILEPATH_LEN_MAX]; char s1[FILEPATH_LEN_MAX]; char s2[FILEPATH_LEN_MAX]; int i1, fields; while (GetLine(s, sizeof(s), fs)) { i1 = -1; fields = sscanf(s, "%d", &i1); if (fields == 1) /* Just skip the numeric config stuff */ continue; s1[0] = s2[0] = '\0'; fields = sscanf(s, "%4000s %4000s", s1, s2); if (fields != 2) { Eprintf("*** Ignoring line: %s\n", s); continue; } _SclassCreate(s1, s2); } #if 0 /* Errors here are just ignored */ if (err) ConfigAlertLoad("Sound"); #endif return err; } static void _SoundConfigLoad(void) { if (Mode_sound.cfg_loaded) return; Mode_sound.cfg_loaded = 1; Efree(Mode_sound.theme_path); if (Conf_sound.theme) Mode_sound.theme_path = ThemePathFind(Conf_sound.theme); else Mode_sound.theme_path = NULL; ConfigFileLoad("sound.cfg", SOUND_THEME_PATH, _SoundConfigParse, 1); } static void _SoundConfigUnload(void) { SoundClass *sc, *tmp; LIST_FOR_EACH_SAFE(SoundClass, &sound_list, sc, tmp) { _SclassDestroy(sc); } Mode_sound.cfg_loaded = 0; } static void _SoundEnableChange(void *item __UNUSED__, const char *sval) { _SoundConfigure(!!atoi(sval)); } static void _SoundEngineChange(void *item __UNUSED__, const char *sval) { EFREE_DUP(Conf_sound.engine, sval); if (Conf_sound.enable) { _SoundConfigure(0); _SoundConfigure(1); } } static void _SoundThemeChange(void *item __UNUSED__, const char *theme) { if (*theme == '\0') theme = NULL; _SoundConfigUnload(); EFREE_DUP(Conf_sound.theme, theme); _SoundConfigLoad(); } /* * Sound module */ static void _SoundSighan(int sig, void *prm __UNUSED__) { switch (sig) { case ESIGNAL_INIT: memset(&Mode_sound, 0, sizeof(Mode_sound)); break; case ESIGNAL_CONFIGURE: _SoundInit(); break; case ESIGNAL_START: if (!Conf_sound.enable) break; SoundPlay(SOUND_STARTUP); _SoundFree("SOUND_STARTUP"); break; case ESIGNAL_EXIT: /* if (Mode.wm.master) */ _SoundExit(); break; } } #if ENABLE_DIALOGS /* * Configuration dialog */ static char tmp_audio; static void _Dlg_ApplySound(Dialog * d __UNUSED__, int val __UNUSED__, void *data __UNUSED__) { _SoundConfigure(tmp_audio); autosave(); } static void _DlgFillSound(Dialog * d __UNUSED__, DItem * table, void *data __UNUSED__) { DItem *di; tmp_audio = Conf_sound.enable; DialogItemTableSetOptions(table, 2, 0, 0, 0); di = DialogAddItem(table, DITEM_CHECKBUTTON); DialogItemSetColSpan(di, 2); DialogItemSetText(di, _("Enable sounds")); DialogItemCheckButtonSetPtr(di, &tmp_audio); } const DialogDef DlgSound = { "CONFIGURE_AUDIO", N_("Sound"), N_("Audio Settings"), 0, SOUND_SETTINGS_AUDIO, "pix/sound.png", N_("Enlightenment Audio\n" "Settings Dialog"), _DlgFillSound, DLG_OAC, _Dlg_ApplySound, NULL }; #endif /* ENABLE_DIALOGS */ /* * IPC functions */ static void SoundIpc(const char *params) { const char *p; char cmd[128], prm[4096]; int len; SoundClass *sc; cmd[0] = prm[0] = '\0'; p = params; if (p) { len = 0; sscanf(p, "%100s %4000s %n", cmd, prm, &len); p += len; } if (!strncmp(cmd, "del", 3)) { _SoundFree(prm); } else if (!strncmp(cmd, "list", 2)) { LIST_FOR_EACH(SoundClass, &sound_list, sc) IpcPrintf("%s\n", sc->name); } else if (!strncmp(cmd, "new", 3)) { _SclassCreate(prm, p); } else if (!strncmp(cmd, "off", 2)) { _SoundConfigure(0); autosave(); } else if (!strncmp(cmd, "on", 2)) { _SoundConfigure(1); autosave(); } else if (!strncmp(cmd, "play", 2)) { _SoundPlayByName(prm); } } static const IpcItem SoundIpcArray[] = { { SoundIpc, "sound", "snd", "Sound functions", " sound add Create soundclass\n" " sound del Delete soundclass\n" " sound list Show all sounds\n" " sound off Disable sounds\n" " sound on Enable sounds\n" " sound play Play sounds\n"} }; static const CfgItem SoundCfgItems[] = { CFG_FUNC_BOOL(Conf_sound, enable, 0, _SoundEnableChange), CFG_FUNC_STR(Conf_sound, theme, _SoundThemeChange), CFG_ITEM_HEX(Conf_sound, mask1, 0), CFG_ITEM_HEX(Conf_sound, mask2, 0), CFG_FUNC_STR(Conf_sound, engine, _SoundEngineChange), }; /* * Module descriptor */ extern const EModule ModSound; const EModule ModSound = { "sound", "audio", _SoundSighan, MOD_ITEMS(SoundIpcArray), MOD_ITEMS(SoundCfgItems) };