59a374293144bbe30335da1ba267a06b5ca5f9ce
[apps/homescreen.git] / homescreen / src / paclient.cpp
1 /*
2  * Copyright (C) 2016,2017 Konsulko Group
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "paclient.h"
18
19 #include <QtCore/QDebug>
20
21 PaClient::PaClient()
22         : m_init(false), m_ml(nullptr), m_mlapi(nullptr), m_ctx(nullptr)
23 {
24 }
25
26 PaClient::~PaClient()
27 {
28         if (m_init)
29                 close();
30 }
31
32 void PaClient::close()
33 {
34         if (!m_init) return;
35         pa_threaded_mainloop_stop(m_ml);
36         pa_threaded_mainloop_free(m_ml);
37         m_init = false;
38 }
39
40 static void set_sink_volume_cb(pa_context *c, int success, void *data)
41 {
42         Q_UNUSED(data);
43
44         if (!success)
45                 qWarning() << "PaClient: set sink volume: " <<
46                         pa_strerror(pa_context_errno(c));
47 }
48
49 void PaClient::incDecVolume(const int volume_delta)
50 {
51         pa_operation *o;
52         pa_context *c = context();
53         pa_sink_info *i = getDefaultSinkInfo();
54
55         if (volume_delta > 0)
56                 pa_cvolume_inc_clamp(&i->volume, volume_delta, 65536);
57         else
58                 pa_cvolume_dec(&i->volume, abs(volume_delta));
59
60         o = pa_context_set_sink_volume_by_index(c, i->index, &i->volume, set_sink_volume_cb, NULL);
61         if (!o) {
62                 qWarning() << "PaClient: set sink #" << i->index <<
63                         " volume: " << pa_strerror(pa_context_errno(c));
64                 return;
65         }
66         pa_operation_unref(o);
67 }
68
69 void get_sink_info_change_cb(pa_context *c,
70                 const pa_sink_info *i,
71                 int eol,
72                 void *data)
73 {
74         Q_UNUSED(c);
75         Q_ASSERT(i);
76         Q_ASSERT(data);
77
78         if (eol) return;
79
80         PaClient *self = reinterpret_cast<PaClient*>(data);
81         pa_sink_info *si = self->getDefaultSinkInfo();
82         if (i->index == si->index) {
83                 self->setDefaultSinkInfo(i);
84                 pa_cvolume *cvolume = &self->getDefaultSinkInfo()->volume;
85                 emit self->volumeExternallyChanged(pa_cvolume_avg(cvolume));
86         }
87 }
88
89 void get_default_sink_info_cb(pa_context *c,
90                 const pa_sink_info *i,
91                 int eol,
92                 void *data)
93 {
94         Q_UNUSED(c);
95         Q_ASSERT(i);
96         Q_ASSERT(data);
97
98         if (eol) return;
99
100         PaClient *self = reinterpret_cast<PaClient*>(data);
101         self->setDefaultSinkInfo(i);
102 }
103
104 pa_sink_info *PaClient::getDefaultSinkInfo(void)
105 {
106         return &m_default_sink_info;
107 }
108
109 void PaClient::setDefaultSinkInfo(const pa_sink_info *i)
110 {
111         m_default_sink_info.index = i->index;
112         m_default_sink_info.channel_map.channels = i->channel_map.channels;
113         pa_cvolume *cvolume = &m_default_sink_info.volume;
114         cvolume->channels = i->volume.channels;
115         for (int chan = 0; chan < i->channel_map.channels; chan++) {
116                 cvolume->values[chan] = i->volume.values[chan];
117         }
118 }
119
120 void get_server_info_cb(pa_context *c,
121                 const pa_server_info *i,
122                 void *data)
123 {
124         pa_operation *o;
125         o = pa_context_get_sink_info_by_name(c, i->default_sink_name, get_default_sink_info_cb, data);
126         if (!o) {
127                 qWarning() << "PaClient: get sink info by name: " <<
128                         pa_strerror(pa_context_errno(c));
129                 return;
130         }
131 }
132
133 void subscribe_cb(pa_context *c,
134                 pa_subscription_event_type_t type,
135                 uint32_t index,
136                 void *data)
137 {
138         pa_operation *o;
139
140         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) {
141                 qWarning("PaClient: unhandled subscribe event operation");
142                 return;
143         }
144
145         switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
146                 case PA_SUBSCRIPTION_EVENT_SINK:
147                         o = pa_context_get_sink_info_by_index(c, index, get_sink_info_change_cb, data);
148                         if (!o) {
149                                 qWarning() << "PaClient: get sink info by index: " <<
150                                         pa_strerror(pa_context_errno(c));
151                                 return;
152                         }
153                         break;
154                 default:
155                         qWarning("PaClient: unhandled subscribe event facility");
156         }
157 }
158
159 void context_state_cb(pa_context *c, void *data)
160 {
161         pa_operation *o;
162         PaClient *self = reinterpret_cast<PaClient*>(data);
163
164         switch (pa_context_get_state(c)) {
165                 case PA_CONTEXT_CONNECTING:
166                 case PA_CONTEXT_AUTHORIZING:
167                 case PA_CONTEXT_SETTING_NAME:
168                         break;
169                 case PA_CONTEXT_READY:
170                         o = pa_context_get_server_info(c, get_server_info_cb, data);
171                         if (!o) {
172                                 qWarning() << "PaClient: get server info: " <<
173                                         pa_strerror(pa_context_errno(c));
174                                 return;
175                         }
176                         pa_operation_unref(o);
177                         pa_context_set_subscribe_callback(c, subscribe_cb, data);
178                         o = pa_context_subscribe(c, (pa_subscription_mask_t)(PA_SUBSCRIPTION_MASK_SINK), NULL, NULL);
179                         if (!o) {
180                                 qWarning() << "PaClient: subscribe: " <<
181                                         pa_strerror(pa_context_errno(c));
182                                 return;
183                         }
184                         break;
185                 case PA_CONTEXT_TERMINATED:
186                         self->close();
187                         break;
188
189                 case PA_CONTEXT_FAILED:
190                 default:
191                         qCritical() << "PaClient: connection failed: " <<
192                                 pa_strerror(pa_context_errno(c));
193                         self->close();
194                         break;
195         }
196 }
197
198 void PaClient::init()
199 {
200         m_ml = pa_threaded_mainloop_new();
201         if (!m_ml) {
202                 qCritical("PaClient: failed to create mainloop");
203                 return;
204         }
205
206         pa_threaded_mainloop_set_name(m_ml, "PaClient mainloop");
207
208         m_mlapi = pa_threaded_mainloop_get_api(m_ml);
209
210         lock();
211
212         m_ctx = pa_context_new(m_mlapi, "HomeScreen");
213         if (!m_ctx) {
214                 qCritical("PaClient: failed to create context");
215                 unlock();
216                 pa_threaded_mainloop_free(m_ml);
217                 return;
218         }
219         pa_context_set_state_callback(m_ctx, context_state_cb, this);
220
221         if (pa_context_connect(m_ctx, 0, (pa_context_flags_t)0, 0) < 0) {
222                 qCritical("PaClient: failed to connect");
223                 pa_context_unref(m_ctx);
224                 unlock();
225                 pa_threaded_mainloop_free(m_ml);
226                 return;
227         }
228
229         if (pa_threaded_mainloop_start(m_ml) != 0) {
230                 qCritical("PaClient: failed to start mainloop");
231                 pa_context_unref(m_ctx);
232                 unlock();
233                 pa_threaded_mainloop_free(m_ml);
234                 return;
235         }
236
237         unlock();
238
239         m_init = true;
240 }