1 /* This file is part of Clementine.
2 Copyright 2012, David Sansome <me@davidsansome.com>
3
4 Clementine is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 Clementine is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with Clementine. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "core/logging.h"
19 #include "engines/pulsedevicefinder.h"
20
PulseDeviceFinder()21 PulseDeviceFinder::PulseDeviceFinder()
22 : DeviceFinder("pulsesink"),
23 mainloop_(nullptr),
24 context_(nullptr) {
25 }
26
Initialise()27 bool PulseDeviceFinder::Initialise() {
28 mainloop_ = pa_mainloop_new();
29 if (!mainloop_) {
30 qLog(Warning) << "Failed to create pulseaudio mainloop";
31 return false;
32 }
33
34 return Reconnect();
35 }
36
Reconnect()37 bool PulseDeviceFinder::Reconnect() {
38 if (context_) {
39 pa_context_disconnect(context_);
40 pa_context_unref(context_);
41 }
42
43 context_ = pa_context_new(pa_mainloop_get_api(mainloop_),
44 "Clementine device finder");
45 if (!context_) {
46 qLog(Warning) << "Failed to create pulseaudio context";
47 return false;
48 }
49
50 if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0) {
51 qLog(Warning) << "Failed to connect pulseaudio context";
52 return false;
53 }
54
55 // Wait for the context to be connected.
56 forever {
57 const pa_context_state state = pa_context_get_state(context_);
58 if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) {
59 qLog(Warning) << "Connection to pulseaudio failed";
60 return false;
61 }
62
63 if (state == PA_CONTEXT_READY) {
64 return true;
65 }
66
67 pa_mainloop_iterate(mainloop_, true, nullptr);
68 }
69 }
70
ListDevices()71 QList<DeviceFinder::Device> PulseDeviceFinder::ListDevices() {
72 if (!context_ || pa_context_get_state(context_) != PA_CONTEXT_READY) {
73 return QList<Device>();
74 }
75
76 retry:
77 ListDevicesState state;
78 pa_context_get_sink_info_list(
79 context_, &PulseDeviceFinder::GetSinkInfoCallback, &state);
80
81 forever {
82 if (state.finished) {
83 return state.devices;
84 }
85
86 switch (pa_context_get_state(context_)) {
87 case PA_CONTEXT_READY:
88 break;
89 case PA_CONTEXT_FAILED:
90 case PA_CONTEXT_TERMINATED:
91 // Maybe pulseaudio died. Try reconnecting.
92 if (Reconnect()) {
93 goto retry;
94 }
95 return state.devices;
96 default:
97 return state.devices;
98 }
99
100 pa_mainloop_iterate(mainloop_, true, nullptr);
101 }
102 }
103
GetSinkInfoCallback(pa_context * c,const pa_sink_info * info,int eol,void * state_voidptr)104 void PulseDeviceFinder::GetSinkInfoCallback(pa_context* c,
105 const pa_sink_info* info,
106 int eol,
107 void* state_voidptr) {
108 ListDevicesState* state = reinterpret_cast<ListDevicesState*>(state_voidptr);
109
110 if (info) {
111 Device dev;
112 dev.device_property_value = QString::fromUtf8(info->name);
113 dev.description = QString::fromUtf8(info->description);
114 dev.icon_name = QString::fromUtf8(
115 pa_proplist_gets(info->proplist, "device.icon_name"));
116
117 state->devices.append(dev);
118 }
119
120 if (eol) {
121 state->finished = true;
122 }
123 }
124
~PulseDeviceFinder()125 PulseDeviceFinder::~PulseDeviceFinder() {
126 if (context_) {
127 pa_context_disconnect(context_);
128 pa_context_unref(context_);
129 }
130
131 if (mainloop_) {
132 pa_mainloop_free(mainloop_);
133 }
134 }
135