Changeset View
Changeset View
Standalone View
Standalone View
backends/mixer_pulse.cpp
Show All 15 Lines | |||||
16 | * | 16 | * | ||
17 | * You should have received a copy of the GNU Library General Public | 17 | * You should have received a copy of the GNU Library General Public | ||
18 | * License along with this program; if not, write to the Free | 18 | * License along with this program; if not, write to the Free | ||
19 | * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 19 | * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
20 | */ | 20 | */ | ||
21 | 21 | | |||
22 | #include "mixer_pulse.h" | 22 | #include "mixer_pulse.h" | ||
23 | 23 | | |||
24 | #include "qtpamainloop.h" | ||||
25 | | ||||
24 | #include <cstdlib> | 26 | #include <cstdlib> | ||
25 | #include <QAbstractEventDispatcher> | 27 | #include <QAbstractEventDispatcher> | ||
26 | #include <QTimer> | 28 | #include <QTimer> | ||
27 | #include <QStringBuilder> | 29 | #include <QStringBuilder> | ||
28 | 30 | | |||
29 | #include <klocalizedstring.h> | 31 | #include <klocalizedstring.h> | ||
30 | 32 | | |||
31 | #include "core/mixer.h" | 33 | #include "core/mixer.h" | ||
32 | #include "core/ControlManager.h" | 34 | #include "core/ControlManager.h" | ||
33 | #include "core/GlobalConfig.h" | 35 | #include "core/GlobalConfig.h" | ||
34 | 36 | | |||
35 | #include <pulse/glib-mainloop.h> | | |||
36 | #include <pulse/ext-stream-restore.h> | 37 | #include <pulse/ext-stream-restore.h> | ||
37 | #if defined(HAVE_CANBERRA) | 38 | #if defined(HAVE_CANBERRA) | ||
38 | # include <canberra.h> | 39 | # include <canberra.h> | ||
39 | #endif | 40 | #endif | ||
40 | 41 | | |||
41 | // PA_VOLUME_UI_MAX landed in pulseaudio-0.9.23, so this can be removed when/if | 42 | // PA_VOLUME_UI_MAX landed in pulseaudio-0.9.23, so this can be removed when/if | ||
42 | // minimum requirement is ever bumped up (from 0.9.12 currently) | 43 | // minimum requirement is ever bumped up (from 0.9.12 currently) | ||
43 | #ifndef PA_VOLUME_UI_MAX | 44 | #ifndef PA_VOLUME_UI_MAX | ||
44 | #define PA_VOLUME_UI_MAX (pa_sw_volume_from_dB(+11.0)) | 45 | #define PA_VOLUME_UI_MAX (pa_sw_volume_from_dB(+11.0)) | ||
45 | #endif | 46 | #endif | ||
46 | 47 | | |||
47 | #define HAVE_SOURCE_OUTPUT_VOLUMES PA_CHECK_VERSION(1,0,0) | 48 | #define HAVE_SOURCE_OUTPUT_VOLUMES PA_CHECK_VERSION(1,0,0) | ||
48 | 49 | | |||
49 | #define KMIXPA_PLAYBACK 0 | 50 | #define KMIXPA_PLAYBACK 0 | ||
50 | #define KMIXPA_CAPTURE 1 | 51 | #define KMIXPA_CAPTURE 1 | ||
51 | #define KMIXPA_APP_PLAYBACK 2 | 52 | #define KMIXPA_APP_PLAYBACK 2 | ||
52 | #define KMIXPA_APP_CAPTURE 3 | 53 | #define KMIXPA_APP_CAPTURE 3 | ||
53 | #define KMIXPA_WIDGET_MAX KMIXPA_APP_CAPTURE | 54 | #define KMIXPA_WIDGET_MAX KMIXPA_APP_CAPTURE | ||
54 | 55 | | |||
55 | #define KMIXPA_EVENT_KEY "sink-input-by-media-role:event" | 56 | #define KMIXPA_EVENT_KEY "sink-input-by-media-role:event" | ||
56 | 57 | | |||
57 | static unsigned int refcount = 0; | 58 | static unsigned int refcount = 0; | ||
58 | static pa_glib_mainloop *s_mainloop = NULL; | | |||
59 | static pa_context *s_context = NULL; | 59 | static pa_context *s_context = NULL; | ||
60 | static enum { UNKNOWN, ACTIVE, INACTIVE } s_pulseActive = UNKNOWN; | 60 | static enum { UNKNOWN, ACTIVE, INACTIVE } s_pulseActive = UNKNOWN; | ||
61 | static int s_outstandingRequests = 0; | 61 | static int s_outstandingRequests = 0; | ||
62 | 62 | | |||
63 | #if defined(HAVE_CANBERRA) | 63 | #if defined(HAVE_CANBERRA) | ||
64 | static ca_context *s_ccontext = NULL; | 64 | static ca_context *s_ccontext = NULL; | ||
65 | #endif | 65 | #endif | ||
66 | 66 | | |||
▲ Show 20 Lines • Show All 617 Lines • ▼ Show 20 Line(s) | 681 | { | |||
684 | pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, NULL); | 684 | pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, NULL); | ||
685 | pa_ext_stream_restore_subscribe(c, 1, NULL, NULL); | 685 | pa_ext_stream_restore_subscribe(c, 1, NULL, NULL); | ||
686 | } | 686 | } | ||
687 | } else if (!PA_CONTEXT_IS_GOOD(state)) { | 687 | } else if (!PA_CONTEXT_IS_GOOD(state)) { | ||
688 | // If this is our probe phase, exit our context immediately | 688 | // If this is our probe phase, exit our context immediately | ||
689 | if (s_context != c) { | 689 | if (s_context != c) { | ||
690 | pa_context_disconnect(c); | 690 | pa_context_disconnect(c); | ||
691 | } else { | 691 | } else { | ||
692 | // If we're not probing, it means we've been disconnected from our | | |||
693 | // GLib context | | |||
694 | pa_context_unref(s_context); | 692 | pa_context_unref(s_context); | ||
695 | s_context = NULL; | 693 | s_context = NULL; | ||
696 | 694 | | |||
697 | // Remove all GUI elements | 695 | // Remove all GUI elements | ||
698 | QMap<int,Mixer_PULSE*>::iterator it; | 696 | QMap<int,Mixer_PULSE*>::iterator it; | ||
699 | for (it = s_mixers.begin(); it != s_mixers.end(); ++it) { | 697 | for (it = s_mixers.begin(); it != s_mixers.end(); ++it) { | ||
700 | (*it)->removeAllWidgets(); | 698 | (*it)->removeAllWidgets(); | ||
701 | } | 699 | } | ||
▲ Show 20 Lines • Show All 249 Lines • ▼ Show 20 Line(s) | 946 | { | |||
951 | return l_mixer; | 949 | return l_mixer; | ||
952 | } | 950 | } | ||
953 | 951 | | |||
954 | bool Mixer_PULSE::connectToDaemon() | 952 | bool Mixer_PULSE::connectToDaemon() | ||
955 | { | 953 | { | ||
956 | Q_ASSERT(NULL == s_context); | 954 | Q_ASSERT(NULL == s_context); | ||
957 | 955 | | |||
958 | qCDebug(KMIX_LOG) << "Attempting connection to PulseAudio sound daemon"; | 956 | qCDebug(KMIX_LOG) << "Attempting connection to PulseAudio sound daemon"; | ||
959 | pa_mainloop_api *api = pa_glib_mainloop_get_api(s_mainloop); | | |||
960 | Q_ASSERT(api); | | |||
961 | 957 | | |||
962 | s_context = pa_context_new(api, "KMix"); | 958 | // No need to create this until necessary | ||
959 | if (!m_mainloop) { | ||||
960 | // When bumping c++ requirement to c++14, | ||||
961 | // replace with `= std::make_unique<QtPaMainLoop>();` | ||||
962 | m_mainloop.reset(new QtPaMainLoop); | ||||
963 | } | ||||
964 | s_context = pa_context_new(&m_mainloop->pa_vtable, "KMix"); | ||||
963 | Q_ASSERT(s_context); | 965 | Q_ASSERT(s_context); | ||
964 | 966 | | |||
965 | if (pa_context_connect(s_context, NULL, PA_CONTEXT_NOFAIL, 0) < 0) { | 967 | if (pa_context_connect(s_context, NULL, PA_CONTEXT_NOFAIL, 0) < 0) { | ||
966 | pa_context_unref(s_context); | 968 | pa_context_unref(s_context); | ||
967 | s_context = NULL; | 969 | s_context = NULL; | ||
968 | return false; | 970 | return false; | ||
969 | } | 971 | } | ||
970 | pa_context_set_state_callback(s_context, &context_state_callback, NULL); | 972 | pa_context_set_state_callback(s_context, &context_state_callback, NULL); | ||
971 | return true; | 973 | return true; | ||
972 | } | 974 | } | ||
973 | 975 | | |||
974 | 976 | | |||
975 | Mixer_PULSE::Mixer_PULSE(Mixer *mixer, int devnum) : Mixer_Backend(mixer, devnum) | 977 | Mixer_PULSE::Mixer_PULSE(Mixer *mixer, int devnum) : Mixer_Backend(mixer, devnum) | ||
976 | { | 978 | { | ||
977 | if ( devnum == -1 ) | 979 | if ( devnum == -1 ) | ||
978 | m_devnum = 0; | 980 | m_devnum = 0; | ||
979 | 981 | | |||
980 | QString pulseenv = qgetenv("KMIX_PULSEAUDIO_DISABLE"); | 982 | QString pulseenv = qgetenv("KMIX_PULSEAUDIO_DISABLE"); | ||
981 | if (pulseenv.toInt()) | 983 | if (pulseenv.toInt()) | ||
982 | s_pulseActive = INACTIVE; | 984 | s_pulseActive = INACTIVE; | ||
983 | 985 | | |||
984 | // We require a glib event loop | | |||
985 | if (!QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("EventDispatcherGlib") && | | |||
986 | !QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("GlibEventDispatcher")) { | | |||
987 | qCDebug(KMIX_LOG) << "Disabling PulseAudio integration for lack of GLib event loop"; | | |||
988 | s_pulseActive = INACTIVE; | | |||
989 | } | | |||
990 | | ||||
991 | ++refcount; | 986 | ++refcount; | ||
992 | if (INACTIVE != s_pulseActive && 1 == refcount) | 987 | if (INACTIVE != s_pulseActive && 1 == refcount) | ||
993 | { | 988 | { | ||
994 | // First of all connect to PA via simple/blocking means and if that succeeds, | 989 | // First of all connect to PA via simple/blocking means and if that succeeds, | ||
995 | // use a fully async integrated mainloop method to connect and get proper support. | 990 | // use a fully async integrated mainloop method to connect and get proper support. | ||
996 | pa_mainloop *p_test_mainloop = pa_mainloop_new(); | 991 | pa_mainloop *p_test_mainloop = pa_mainloop_new(); | ||
997 | if (p_test_mainloop==nullptr) | 992 | if (p_test_mainloop==nullptr) | ||
998 | { | 993 | { | ||
Show All 27 Lines | |||||
1026 | s_pulseActive = INACTIVE; | 1021 | s_pulseActive = INACTIVE; | ||
1027 | pa_context_set_state_callback(p_test_context, &context_state_callback, NULL); | 1022 | pa_context_set_state_callback(p_test_context, &context_state_callback, NULL); | ||
1028 | for (;;) { | 1023 | for (;;) { | ||
1029 | pa_mainloop_iterate(p_test_mainloop, 1, NULL); | 1024 | pa_mainloop_iterate(p_test_mainloop, 1, NULL); | ||
1030 | 1025 | | |||
1031 | if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(p_test_context))) { | 1026 | if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(p_test_context))) { | ||
1032 | qCDebug(KMIX_LOG) << "PulseAudio probe complete."; | 1027 | qCDebug(KMIX_LOG) << "PulseAudio probe complete."; | ||
1033 | break; | 1028 | break; | ||
1034 | } | 1029 | } | ||
marten: m_mainloop = new QtPaMainLoop; | |||||
1035 | } | 1030 | } | ||
1036 | pa_context_disconnect(p_test_context); | 1031 | pa_context_disconnect(p_test_context); | ||
1037 | pa_context_unref(p_test_context); | 1032 | pa_context_unref(p_test_context); | ||
1038 | pa_mainloop_free(p_test_mainloop); | 1033 | pa_mainloop_free(p_test_mainloop); | ||
1039 | 1034 | | |||
1040 | if (INACTIVE != s_pulseActive) | 1035 | if (INACTIVE != s_pulseActive) | ||
1041 | { | 1036 | { | ||
1042 | // Reconnect via integrated mainloop | 1037 | // Reconnect via integrated mainloop | ||
1043 | s_mainloop = pa_glib_mainloop_new(NULL); | | |||
1044 | Q_ASSERT(s_mainloop); | | |||
1045 | | ||||
1046 | connectToDaemon(); | 1038 | connectToDaemon(); | ||
1047 | 1039 | | |||
1048 | #if defined(HAVE_CANBERRA) | 1040 | #if defined(HAVE_CANBERRA) | ||
1049 | int ret = ca_context_create(&s_ccontext); | 1041 | int ret = ca_context_create(&s_ccontext); | ||
1050 | if (ret < 0) { | 1042 | if (ret < 0) { | ||
1051 | qCDebug(KMIX_LOG) << "Disabling sound feedback, Canberra context create failed"; | 1043 | qCDebug(KMIX_LOG) << "Disabling sound feedback, Canberra context create failed"; | ||
1052 | s_ccontext = NULL; | 1044 | s_ccontext = NULL; | ||
1053 | } else | 1045 | } else | ||
Show All 14 Lines | 1058 | { | |||
1068 | 1060 | | |||
1069 | if (refcount > 0) | 1061 | if (refcount > 0) | ||
1070 | { | 1062 | { | ||
1071 | --refcount; | 1063 | --refcount; | ||
1072 | if (0 == refcount) | 1064 | if (0 == refcount) | ||
1073 | { | 1065 | { | ||
1074 | #if defined(HAVE_CANBERRA) | 1066 | #if defined(HAVE_CANBERRA) | ||
1075 | if (s_ccontext) { | 1067 | if (s_ccontext) { | ||
1076 | ca_context_destroy(s_ccontext); | 1068 | ca_context_destroy(s_ccontext); | ||
marten: delete m_mainloop | |||||
I thought about doing it initially, but it isn't really necessary. Since our "mainloop" isn't a real mainloop but just a wrapper around Qt's mainloop, deleting and creating it doesn't really do anything. sandsmark: I thought about doing it initially, but it isn't really necessary.
Since our "mainloop" isn't… | |||||
1077 | s_ccontext = NULL; | 1069 | s_ccontext = NULL; | ||
1078 | } | 1070 | } | ||
1079 | #endif | 1071 | #endif | ||
1080 | 1072 | | |||
1081 | if (s_context) { | 1073 | if (s_context) { | ||
1082 | pa_context_unref(s_context); | 1074 | pa_context_unref(s_context); | ||
1083 | s_context = NULL; | 1075 | s_context = NULL; | ||
1084 | } | 1076 | } | ||
1085 | | ||||
1086 | if (s_mainloop) { | | |||
1087 | pa_glib_mainloop_free(s_mainloop); | | |||
1088 | s_mainloop = NULL; | | |||
1089 | } | | |||
1090 | } | 1077 | } | ||
1091 | } | 1078 | } | ||
1092 | 1079 | | |||
1093 | closeCommon(); | 1080 | closeCommon(); | ||
1094 | } | 1081 | } | ||
1095 | 1082 | | |||
1096 | int Mixer_PULSE::open() | 1083 | int Mixer_PULSE::open() | ||
1097 | { | 1084 | { | ||
▲ Show 20 Lines • Show All 347 Lines • Show Last 20 Lines |
m_mainloop = new QtPaMainLoop;