From 607029424f90a3c91de3b5ad4154fde71c1ce9e5 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Thu, 29 Jun 2017 14:05:17 +0200 Subject: [PATCH] prevent-dpms-off: Add module to signal DPMS should not happen Whenever an HDMI/DP sink is in use DPMS should not be done by desktop environments. Add a property to the appropriate ports and inform the DE that DPMS off should be inhibited whenever a port or sink with the device.requires.no_dpms_off property is set to boolean true. TODO: Implement protocol to the DE to signal the situation --- src/Makefile.am | 7 + src/modules/alsa/mixer/paths/hdmi-output-0.conf | 1 + src/modules/alsa/mixer/paths/hdmi-output-1.conf | 1 + src/modules/alsa/mixer/paths/hdmi-output-2.conf | 1 + src/modules/alsa/mixer/paths/hdmi-output-3.conf | 1 + src/modules/alsa/mixer/paths/hdmi-output-4.conf | 1 + src/modules/alsa/mixer/paths/hdmi-output-5.conf | 1 + src/modules/alsa/mixer/paths/hdmi-output-6.conf | 1 + src/modules/alsa/mixer/paths/hdmi-output-7.conf | 1 + src/modules/module-prevent-dpms-off.c | 273 ++++++++++++++++++++++++ src/pulse/proplist.h | 3 + 11 files changed, 291 insertions(+) create mode 100644 src/modules/module-prevent-dpms-off.c diff --git a/src/Makefile.am b/src/Makefile.am index d4a649c9..19252769 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1197,6 +1197,7 @@ modlibexec_LTLIBRARIES += \ module-rescue-streams.la \ module-intended-roles.la \ module-suspend-on-idle.la \ + module-prevent-dpms-off.la \ module-echo-cancel.la \ module-http-protocol-tcp.la \ module-sine.la \ @@ -1963,6 +1964,12 @@ module_suspend_on_idle_la_LDFLAGS = $(MODULE_LDFLAGS) module_suspend_on_idle_la_LIBADD = $(MODULE_LIBADD) module_suspend_on_idle_la_CFLAGS = $(AM_CFLAGS) -DPA_MODULE_NAME=module_suspend_on_idle +# Prevent-DPMS-off module +module_prevent_dpms_off_la_SOURCES = modules/module-prevent-dpms-off.c +module_prevent_dpms_off_la_LDFLAGS = $(MODULE_LDFLAGS) +module_prevent_dpms_off_la_LIBADD = $(MODULE_LIBADD) +module_prevent_dpms_off_la_CFLAGS = $(AM_CFLAGS) + # echo-cancel module module_echo_cancel_la_SOURCES = \ modules/echo-cancel/module-echo-cancel.c \ diff --git a/src/modules/alsa/mixer/paths/hdmi-output-0.conf b/src/modules/alsa/mixer/paths/hdmi-output-0.conf index 33101470..ce96f6c9 100644 --- a/src/modules/alsa/mixer/paths/hdmi-output-0.conf +++ b/src/modules/alsa/mixer/paths/hdmi-output-0.conf @@ -5,6 +5,7 @@ eld-device = 3 [Properties] device.icon_name = video-display +device.requires.no_dpms_off = 1 [Jack HDMI/DP,pcm=3] required = ignore diff --git a/src/modules/alsa/mixer/paths/hdmi-output-1.conf b/src/modules/alsa/mixer/paths/hdmi-output-1.conf index d81ee789..f475279d 100644 --- a/src/modules/alsa/mixer/paths/hdmi-output-1.conf +++ b/src/modules/alsa/mixer/paths/hdmi-output-1.conf @@ -5,6 +5,7 @@ eld-device = 7 [Properties] device.icon_name = video-display +device.requires.no_dpms_off = 1 [Jack HDMI/DP,pcm=7] required = ignore diff --git a/src/modules/alsa/mixer/paths/hdmi-output-2.conf b/src/modules/alsa/mixer/paths/hdmi-output-2.conf index 349812fc..3b033755 100644 --- a/src/modules/alsa/mixer/paths/hdmi-output-2.conf +++ b/src/modules/alsa/mixer/paths/hdmi-output-2.conf @@ -5,6 +5,7 @@ eld-device = 8 [Properties] device.icon_name = video-display +device.requires.no_dpms_off = 1 [Jack HDMI/DP,pcm=8] required = ignore diff --git a/src/modules/alsa/mixer/paths/hdmi-output-3.conf b/src/modules/alsa/mixer/paths/hdmi-output-3.conf index 81463c94..da1769a9 100644 --- a/src/modules/alsa/mixer/paths/hdmi-output-3.conf +++ b/src/modules/alsa/mixer/paths/hdmi-output-3.conf @@ -5,6 +5,7 @@ eld-device = 9 [Properties] device.icon_name = video-display +device.requires.no_dpms_off = 1 [Jack HDMI/DP,pcm=9] required = ignore diff --git a/src/modules/alsa/mixer/paths/hdmi-output-4.conf b/src/modules/alsa/mixer/paths/hdmi-output-4.conf index d61ec754..8039a976 100644 --- a/src/modules/alsa/mixer/paths/hdmi-output-4.conf +++ b/src/modules/alsa/mixer/paths/hdmi-output-4.conf @@ -5,6 +5,7 @@ eld-device = 10 [Properties] device.icon_name = video-display +device.requires.no_dpms_off = 1 [Jack HDMI/DP,pcm=10] required = ignore diff --git a/src/modules/alsa/mixer/paths/hdmi-output-5.conf b/src/modules/alsa/mixer/paths/hdmi-output-5.conf index 02c15e89..29538935 100644 --- a/src/modules/alsa/mixer/paths/hdmi-output-5.conf +++ b/src/modules/alsa/mixer/paths/hdmi-output-5.conf @@ -5,6 +5,7 @@ eld-device = 11 [Properties] device.icon_name = video-display +device.requires.no_dpms_off = 1 [Jack HDMI/DP,pcm=11] required = ignore diff --git a/src/modules/alsa/mixer/paths/hdmi-output-6.conf b/src/modules/alsa/mixer/paths/hdmi-output-6.conf index 188a1adb..391a6a39 100644 --- a/src/modules/alsa/mixer/paths/hdmi-output-6.conf +++ b/src/modules/alsa/mixer/paths/hdmi-output-6.conf @@ -5,6 +5,7 @@ eld-device = 12 [Properties] device.icon_name = video-display +device.requires.no_dpms_off = 1 [Jack HDMI/DP,pcm=12] required = ignore diff --git a/src/modules/alsa/mixer/paths/hdmi-output-7.conf b/src/modules/alsa/mixer/paths/hdmi-output-7.conf index 80f4e372..484f0281 100644 --- a/src/modules/alsa/mixer/paths/hdmi-output-7.conf +++ b/src/modules/alsa/mixer/paths/hdmi-output-7.conf @@ -5,6 +5,7 @@ eld-device = 13 [Properties] device.icon_name = video-display +device.requires.no_dpms_off = 1 [Jack HDMI/DP,pcm=13] required = ignore diff --git a/src/modules/module-prevent-dpms-off.c b/src/modules/module-prevent-dpms-off.c new file mode 100644 index 00000000..702aa215 --- /dev/null +++ b/src/modules/module-prevent-dpms-off.c @@ -0,0 +1,273 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2017 Benjamin 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); +} diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h index cec3b357..a5040db9 100644 --- a/src/pulse/proplist.h +++ b/src/pulse/proplist.h @@ -208,6 +208,9 @@ PA_C_DECL_BEGIN /** For devices: device class. One of "sound", "modem", "monitor", "filter" */ #define PA_PROP_DEVICE_CLASS "device.class" +/** For devices: Whether DPMS OFF must not happen while this sink is in use. */ +#define PA_PROP_DEVICE_REQUIRES_NO_DPMS_OFF "device.requires.no_dpms_off" + /** For devices: form factor if applicable. One of "internal", "speaker", "handset", "tv", "webcam", "microphone", "headset", "headphone", "hands-free", "car", "hifi", "computer", "portable" */ #define PA_PROP_DEVICE_FORM_FACTOR "device.form_factor" -- 2.14.3