/*** This file is part of PulseAudio. Copyright 2006 Lennart Poettering Copyright 2017 BenjMIn Berg PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "module-prevent-dpms-off-symdef.h" PA_MODULE_AUTHOR("Benjamin Berg"); PA_MODULE_DESCRIPTION("Signal to graphical session that displays should not be suspended using DPMS"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(true); /* * This module signals that DPMS should not happen if a sink is not suspended * and device.requires.no_dpms_off is set on the active port (prefered) or on * the sink/source itself. * * Note that the sink might not be suspended for short periods of time even * though no playback is happening. This module does not special case these * corner cases for the sake of simplicity. * * As such the following events are relevant: * - SINK/SOURCE state change * - SINK/SOURCE unlinking * - SINK/SOURCE port changes * - SINK/SOURCE proplist changes * * The device.requires.no_dpms_off property is only checked when a sink/source * becomes active. */ static const char* const valid_modargs[] = { NULL, }; struct userdata { pa_core *core; pa_hashmap *inhibit_infos; }; struct dpms_inhibit_info { struct userdata *userdata; pa_sink *sink; pa_source *source; /* also store e.g. cookie or similar to uninhibit */ }; static void inhibit_dpms(struct userdata *u, pa_sink *sink, pa_source *source) { struct dpms_inhibit_info *d; if (sink && !pa_hashmap_get(u->inhibit_infos, sink)) { d = pa_xnew(struct dpms_inhibit_info, 1); d->userdata = u; d->source = NULL; d->sink = pa_sink_ref(sink); pa_hashmap_put(u->inhibit_infos, sink, d); pa_log_info("Sink %s is now inhibiting DPMS power management of monitors.", d->sink->name); } if (source && (d = pa_hashmap_get(u->inhibit_infos, source))) { d = pa_xnew(struct dpms_inhibit_info, 1); d->userdata = u; d->source = pa_source_ref(source); d->sink = NULL; pa_hashmap_remove_and_free(u->inhibit_infos, sink); pa_log_info("Source %s is now inhibiting DPMS power management of monitors.", d->source->name); } } static void uninhibit_dpms(struct userdata *u, pa_sink *sink, pa_source *source) { struct dpms_inhibit_info *d; if (sink && (d = pa_hashmap_get(u->inhibit_infos, sink))) { pa_log_info("Sink %s is no longer inhibiting DPMS power management of monitors.", d->sink->name); pa_hashmap_remove_and_free(u->inhibit_infos, sink); } if (source && (d = pa_hashmap_get(u->inhibit_infos, source))) { pa_log_info("Source %s is no longer inhibiting DPMS power management of monitors.", d->source->name); pa_hashmap_remove_and_free(u->inhibit_infos, sink); } } static void sync_dpms_inhibit_state(struct userdata *u, bool active, pa_sink *sink, pa_source *source) { bool inhibit; pa_assert(source || sink); if (active) { const char *prop_str = NULL; pa_device_port *port; inhibit = true; port = sink ? sink->active_port : source->active_port; if (port) prop_str = pa_proplist_gets(port->proplist, PA_PROP_DEVICE_REQUIRES_NO_DPMS_OFF); if (!prop_str) prop_str = pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_REQUIRES_NO_DPMS_OFF); /* Not set or set to boolean FALSE (or invalid). */ if (!prop_str || pa_parse_boolean(prop_str) <= 0) inhibit = false; } else { inhibit = false; } if (inhibit) inhibit_dpms(u, sink, source); else uninhibit_dpms(u, sink, source); } static pa_hook_result_t device_unlink_hook_cb(pa_core *c, pa_object *o, struct userdata *u) { pa_assert(c); pa_object_assert_ref(o); pa_assert(u); if (pa_sink_isinstance(o)) { pa_sink *s = PA_SINK(o); uninhibit_dpms(u, s, NULL); } else if (pa_source_isinstance(o)) { pa_source *s = PA_SOURCE(o); uninhibit_dpms(u, NULL, s); } return PA_HOOK_OK; } static pa_hook_result_t device_recheck_dpms_inhibit_state(pa_core *c, pa_object *o, struct userdata *u) { pa_assert(c); pa_object_assert_ref(o); pa_assert(u); if (pa_sink_isinstance(o)) { pa_sink *s = PA_SINK(o); pa_sink_state_t state = pa_sink_get_state(s); sync_dpms_inhibit_state(u, PA_SINK_IS_OPENED(state), s, NULL); } else if (pa_source_isinstance(o)) { pa_source *s = PA_SOURCE(o); pa_source_state_t state = pa_source_get_state(s); sync_dpms_inhibit_state(u, PA_SOURCE_IS_OPENED(state), NULL, s); } return PA_HOOK_OK; } static void dpms_inhibit_info_free(struct dpms_inhibit_info *d) { pa_assert(d); if (d->source) pa_source_unref(d->source); if (d->sink) pa_sink_unref(d->sink); pa_xfree(d); } int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; uint32_t idx; pa_sink *sink; pa_source *source; pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments."); goto fail; } m->userdata = u = pa_xnew(struct userdata, 1); u->core = m->core; u->inhibit_infos = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL, (pa_free_cb_t) dpms_inhibit_info_free); PA_IDXSET_FOREACH(sink, m->core->sinks, idx) device_recheck_dpms_inhibit_state(m->core, PA_OBJECT(sink), u); PA_IDXSET_FOREACH(source, m->core->sources, idx) device_recheck_dpms_inhibit_state(m->core, PA_OBJECT(source), u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_UNLINK_POST], PA_HOOK_NORMAL, (pa_hook_cb_t) device_unlink_hook_cb, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK_POST], PA_HOOK_NORMAL, (pa_hook_cb_t) device_unlink_hook_cb, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) device_recheck_dpms_inhibit_state, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) device_recheck_dpms_inhibit_state, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_PORT_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) device_recheck_dpms_inhibit_state, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_PORT_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) device_recheck_dpms_inhibit_state, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) device_recheck_dpms_inhibit_state, u); pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) device_recheck_dpms_inhibit_state, u); /* There is no PA_CORE_HOOK_PORT_PROPLIST_CHANGED */ pa_modargs_free(ma); return 0; fail: if (ma) pa_modargs_free(ma); return -1; } void pa__done(pa_module*m) { struct userdata *u; struct dpms_inhibit_info *d; void *state; pa_assert(m); if (!m->userdata) return; u = m->userdata; PA_HASHMAP_FOREACH(d, u->inhibit_infos, state) { if (d->sink) { pa_log_info("Uninhibiting DPMS off for sink %s on module unload.", d->sink->name); } if (d->source) { pa_log_info("Uninhibiting DPMS off for source %s on module unload.", d->source->name); } } pa_hashmap_free(u->inhibit_infos); pa_xfree(u); }