~ubuntu-branches/ubuntu/maverick/kdemultimedia/maverick-proposed

« back to all changes in this revision

Viewing changes to kmix/mixer_pulse.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Jonathan Thomas
  • Date: 2010-05-25 08:30:50 UTC
  • mfrom: (1.2.43 upstream)
  • Revision ID: james.westby@ubuntu.com-20100525083050-8o3otjqjwsnzjb6h
Tags: 4:4.4.80-0ubuntu1
* New upstream beta release:
  - Bump kde-sc-dev-latest to 4.4.80
  - Update various .install files
  - Refresh all patches
  - Add build-depends on libswscale-dev, libavcodec-dev, and libavformat-dev
    for new video thumbnailer backends
  - Add a new ffmpegthumbs package for the new video thumbnailer
* Switch to source format 3.0 (quilt):
  - Bump debhelper build-depend version to 7.3.16 or greater

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
 */
21
21
 
22
22
#include <cstdlib>
 
23
#include <QtCore/QAbstractEventDispatcher>
 
24
#include <QTimer>
23
25
 
24
26
#include "mixer_pulse.h"
25
27
#include "mixer.h"
26
28
 
27
 
static pa_context *context = NULL;
28
 
static pa_glib_mainloop *mainloop = NULL;
 
29
#include <pulse/glib-mainloop.h>
 
30
#include <pulse/ext-stream-restore.h>
 
31
 
 
32
 
 
33
#define KMIXPA_PLAYBACK     0
 
34
#define KMIXPA_CAPTURE      1
 
35
#define KMIXPA_APP_PLAYBACK 2
 
36
#define KMIXPA_APP_CAPTURE  3
 
37
#define KMIXPA_WIDGET_MAX KMIXPA_APP_CAPTURE
 
38
 
 
39
static unsigned int refcount = 0;
 
40
static pa_glib_mainloop *s_mainloop = NULL;
 
41
static pa_context *s_context = NULL;
 
42
static enum { UNKNOWN, ACTIVE, INACTIVE } s_pulseActive = UNKNOWN;
 
43
static int s_outstandingRequests = 0;
 
44
 
 
45
QMap<int,Mixer_PULSE*> s_mixers;
 
46
 
 
47
typedef QMap<int,devinfo> devmap;
 
48
static devmap outputDevices;
 
49
static devmap captureDevices;
 
50
static QMap<int,QString> clients;
 
51
static devmap outputStreams;
 
52
static devmap captureStreams;
 
53
static devmap outputRoles;
 
54
 
 
55
typedef struct {
 
56
    pa_channel_map channel_map;
 
57
    pa_cvolume volume;
 
58
    bool mute;
 
59
    QString device;
 
60
} restoreRule;
 
61
static QMap<QString,restoreRule> s_RestoreRules;
 
62
 
 
63
static void dec_outstanding(pa_context *c) {
 
64
    if (s_outstandingRequests <= 0)
 
65
        return;
 
66
 
 
67
    if (--s_outstandingRequests == 0)
 
68
    {
 
69
        s_pulseActive = ACTIVE;
 
70
 
 
71
        // If this is our probe phase, exit our context immediately
 
72
        if (s_context != c) {
 
73
            pa_context_disconnect(c);
 
74
        } else
 
75
          kDebug(67100) <<  "Reconnected to PulseAudio";
 
76
    }
 
77
}
 
78
 
 
79
static void translateMasksAndMaps(devinfo& dev)
 
80
{
 
81
    dev.chanMask = Volume::MNONE;
 
82
    dev.chanIDs.clear();
 
83
 
 
84
    if (dev.channel_map.channels != dev.volume.channels) {
 
85
        kError() << "Hiddeous Channel mixup map says " << dev.channel_map.channels << ", volume says: " << dev.volume.channels;
 
86
        return;
 
87
    }
 
88
    if (1 == dev.channel_map.channels && PA_CHANNEL_POSITION_MONO == dev.channel_map.map[0]) {
 
89
        // We just use the left channel to represent this.
 
90
        dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MLEFT);
 
91
        dev.chanIDs[0] = Volume::LEFT;
 
92
    } else {
 
93
        for (uint8_t i = 0; i < dev.channel_map.channels; ++i) {
 
94
            switch (dev.channel_map.map[i]) {
 
95
                case PA_CHANNEL_POSITION_MONO:
 
96
                    kWarning(67100) << "Channel Map contains a MONO element but has >1 channel - we can't handle this.";
 
97
                    return;
 
98
 
 
99
                case PA_CHANNEL_POSITION_FRONT_LEFT:
 
100
                    dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MLEFT);
 
101
                    dev.chanIDs[i] = Volume::LEFT;
 
102
                    break;
 
103
                case PA_CHANNEL_POSITION_FRONT_RIGHT:
 
104
                    dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MRIGHT);
 
105
                    dev.chanIDs[i] = Volume::RIGHT;
 
106
                    break;
 
107
                case PA_CHANNEL_POSITION_FRONT_CENTER:
 
108
                    dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MCENTER);
 
109
                    dev.chanIDs[i] = Volume::CENTER;
 
110
                    break;
 
111
                case PA_CHANNEL_POSITION_REAR_CENTER:
 
112
                    dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARCENTER);
 
113
                    dev.chanIDs[i] = Volume::REARCENTER;
 
114
                    break;
 
115
                case PA_CHANNEL_POSITION_REAR_LEFT:
 
116
                    dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MSURROUNDLEFT);
 
117
                    dev.chanIDs[i] = Volume::SURROUNDLEFT;
 
118
                    break;
 
119
                case PA_CHANNEL_POSITION_REAR_RIGHT:
 
120
                    dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MSURROUNDRIGHT);
 
121
                    dev.chanIDs[i] = Volume::SURROUNDRIGHT;
 
122
                    break;
 
123
                case PA_CHANNEL_POSITION_LFE:
 
124
                    dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MWOOFER);
 
125
                    dev.chanIDs[i] = Volume::WOOFER;
 
126
                    break;
 
127
                case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER:
 
128
                    dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARSIDELEFT);
 
129
                    dev.chanIDs[i] = Volume::REARSIDELEFT;
 
130
                    break;
 
131
                case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER:
 
132
                    dev.chanMask = (Volume::ChannelMask)( dev.chanMask | Volume::MREARSIDERIGHT);
 
133
                    dev.chanIDs[i] = Volume::REARSIDERIGHT;
 
134
                    break;
 
135
                default:
 
136
                    kWarning(67100) << "Channel Map contains a pa_channel_position we cannot handle " << dev.channel_map.map[i];
 
137
                    break;
 
138
            }
 
139
        }
 
140
    }
 
141
}
 
142
 
 
143
static QString getIconNameFromProplist(pa_proplist *l) {
 
144
    const char *t;
 
145
 
 
146
    if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ICON_NAME)))
 
147
        return t;
 
148
 
 
149
    if ((t = pa_proplist_gets(l, PA_PROP_WINDOW_ICON_NAME)))
 
150
        return t;
 
151
 
 
152
    if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ICON_NAME)))
 
153
        return t;
 
154
 
 
155
    if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ROLE))) {
 
156
 
 
157
        if (strcmp(t, "video") == 0 || strcmp(t, "phone") == 0)
 
158
            return t;
 
159
 
 
160
        if (strcmp(t, "music") == 0)
 
161
            return "audio";
 
162
 
 
163
        if (strcmp(t, "game") == 0)
 
164
            return "applications-games";
 
165
 
 
166
        if (strcmp(t, "event") == 0)
 
167
            return "dialog-information";
 
168
    }
 
169
 
 
170
    return "";
 
171
}
 
172
 
 
173
static void sink_cb(pa_context *c, const pa_sink_info *i, int eol, void *) {
 
174
 
 
175
    if (eol < 0) {
 
176
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
 
177
            return;
 
178
 
 
179
        kWarning(67100) << "Sink callback failure";
 
180
        return;
 
181
    }
 
182
 
 
183
    if (eol > 0) {
 
184
        dec_outstanding(c);
 
185
        if (s_mixers.contains(KMIXPA_PLAYBACK))
 
186
            s_mixers[KMIXPA_PLAYBACK]->triggerUpdate();
 
187
        return;
 
188
    }
 
189
 
 
190
    devinfo s;
 
191
    s.index = s.device_index = i->index;
 
192
    s.name = QString(i->name).replace(' ', '_');
 
193
    s.description = i->description;
 
194
    s.icon_name = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_ICON_NAME);
 
195
    s.volume = i->volume;
 
196
    s.channel_map = i->channel_map;
 
197
    s.mute = !!i->mute;
 
198
    s.stream_restore_rule = "";
 
199
 
 
200
    translateMasksAndMaps(s);
 
201
 
 
202
    bool is_new = !outputDevices.contains(s.index);
 
203
    outputDevices[s.index] = s;
 
204
    kDebug(67100) << "Got some info about sink: " << s.description;
 
205
 
 
206
    if (s_mixers.contains(KMIXPA_PLAYBACK)) {
 
207
        if (is_new)
 
208
            s_mixers[KMIXPA_PLAYBACK]->addWidget(s.index);
 
209
        else {
 
210
            int mid = s_mixers[KMIXPA_PLAYBACK]->id2num(s.name);
 
211
            if (mid >= 0) {
 
212
                MixSet *ms = s_mixers[KMIXPA_PLAYBACK]->getMixSet();
 
213
                (*ms)[mid]->setReadableName(s.description);
 
214
            }
 
215
        }
 
216
    }
 
217
}
 
218
 
 
219
static void source_cb(pa_context *c, const pa_source_info *i, int eol, void *) {
 
220
 
 
221
    if (eol < 0) {
 
222
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
 
223
            return;
 
224
 
 
225
        kWarning(67100) << "Source callback failure";
 
226
        return;
 
227
    }
 
228
 
 
229
    if (eol > 0) {
 
230
        dec_outstanding(c);
 
231
        if (s_mixers.contains(KMIXPA_CAPTURE))
 
232
            s_mixers[KMIXPA_CAPTURE]->triggerUpdate();
 
233
        return;
 
234
    }
 
235
 
 
236
    // Do something....
 
237
    if (PA_INVALID_INDEX != i->monitor_of_sink)
 
238
    {
 
239
        kDebug(67100) << "Ignoring Monitor Source: " << i->description;
 
240
        return;
 
241
    }
 
242
 
 
243
    devinfo s;
 
244
    s.index = s.device_index = i->index;
 
245
    s.name = QString(i->name).replace(' ', '_');
 
246
    s.description = i->description;
 
247
    s.icon_name = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_ICON_NAME);
 
248
    s.volume = i->volume;
 
249
    s.channel_map = i->channel_map;
 
250
    s.mute = !!i->mute;
 
251
    s.stream_restore_rule = "";
 
252
 
 
253
    translateMasksAndMaps(s);
 
254
 
 
255
    bool is_new = !captureDevices.contains(s.index);
 
256
    captureDevices[s.index] = s;
 
257
    kDebug(67100) << "Got some info about source: " << s.description;
 
258
 
 
259
    if (s_mixers.contains(KMIXPA_CAPTURE)) {
 
260
        if (is_new)
 
261
            s_mixers[KMIXPA_CAPTURE]->addWidget(s.index);
 
262
        else {
 
263
            int mid = s_mixers[KMIXPA_CAPTURE]->id2num(s.name);
 
264
            if (mid >= 0) {
 
265
                MixSet *ms = s_mixers[KMIXPA_CAPTURE]->getMixSet();
 
266
                (*ms)[mid]->setReadableName(s.description);
 
267
            }
 
268
        }
 
269
    }
 
270
}
 
271
 
 
272
static void client_cb(pa_context *c, const pa_client_info *i, int eol, void *) {
 
273
 
 
274
    if (eol < 0) {
 
275
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
 
276
            return;
 
277
 
 
278
        kWarning(67100) << "Client callback failure";
 
279
        return;
 
280
    }
 
281
 
 
282
    if (eol > 0) {
 
283
        dec_outstanding(c);
 
284
        return;
 
285
    }
 
286
 
 
287
    clients[i->index] = i->name;
 
288
    kDebug(67100) << "Got some info about client: " << i->name;
 
289
}
 
290
 
 
291
static void sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *) {
 
292
 
 
293
    if (eol < 0) {
 
294
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
 
295
            return;
 
296
 
 
297
        kWarning(67100) << "Sink Input callback failure";
 
298
        return;
 
299
    }
 
300
 
 
301
    if (eol > 0) {
 
302
        dec_outstanding(c);
 
303
        if (s_mixers.contains(KMIXPA_APP_PLAYBACK))
 
304
            s_mixers[KMIXPA_APP_PLAYBACK]->triggerUpdate();
 
305
        return;
 
306
    }
 
307
 
 
308
    const char *t;
 
309
    if ((t = pa_proplist_gets(i->proplist, "module-stream-restore.id"))) {
 
310
        if (strcmp(t, "sink-input-by-media-role:event") == 0) {
 
311
            kWarning(67100) << "Ignoring sink-input due to it being designated as an event and thus handled by the Event slider";
 
312
            return;
 
313
        }
 
314
    }
 
315
 
 
316
    QString prefix = QString("%1: ").arg(i18n("Unknown Application"));
 
317
    if (clients.contains(i->client))
 
318
        prefix = QString("%1: ").arg(clients[i->client]);
 
319
 
 
320
    devinfo s;
 
321
    s.index = i->index;
 
322
    s.device_index = i->sink;
 
323
    s.description = prefix + i->name;
 
324
    s.name = QString("stream:") + i->index;
 
325
    s.icon_name = getIconNameFromProplist(i->proplist);
 
326
    s.volume = i->volume;
 
327
    s.channel_map = i->channel_map;
 
328
    s.mute = !!i->mute;
 
329
    s.stream_restore_rule = t;
 
330
 
 
331
    translateMasksAndMaps(s);
 
332
 
 
333
    bool is_new = !outputStreams.contains(s.index);
 
334
    outputStreams[s.index] = s;
 
335
    kDebug(67100) << "Got some info about sink input (playback stream): " << s.description;
 
336
 
 
337
    if (s_mixers.contains(KMIXPA_APP_PLAYBACK)) {
 
338
        if (is_new)
 
339
            s_mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index);
 
340
        else {
 
341
            int mid = s_mixers[KMIXPA_APP_PLAYBACK]->id2num(s.name);
 
342
            if (mid >= 0) {
 
343
                MixSet *ms = s_mixers[KMIXPA_APP_PLAYBACK]->getMixSet();
 
344
                (*ms)[mid]->setReadableName(s.description);
 
345
            }
 
346
        }
 
347
    }
 
348
}
 
349
 
 
350
static void source_output_cb(pa_context *c, const pa_source_output_info *i, int eol, void *) {
 
351
 
 
352
    if (eol < 0) {
 
353
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
 
354
            return;
 
355
 
 
356
        kWarning(67100) << "Source Output callback failure";
 
357
        return;
 
358
    }
 
359
 
 
360
    if (eol > 0) {
 
361
        dec_outstanding(c);
 
362
        if (s_mixers.contains(KMIXPA_APP_CAPTURE))
 
363
            s_mixers[KMIXPA_APP_CAPTURE]->triggerUpdate();
 
364
        return;
 
365
    }
 
366
 
 
367
    /* NB Until Source Outputs support volumes, we just use the volume of the source itself */
 
368
    if (!captureDevices.contains(i->source)) {
 
369
        kWarning(67100) << "Source Output refers to a Source we don't have any info for :s";
 
370
        return;
 
371
    }
 
372
 
 
373
    QString prefix = QString("%1: ").arg(i18n("Unknown Application"));
 
374
    if (clients.contains(i->client))
 
375
        prefix = QString("%1: ").arg(clients[i->client]);
 
376
 
 
377
    devinfo s;
 
378
    s.index = i->index;
 
379
    s.device_index = i->source;
 
380
    s.description = prefix + i->name;
 
381
    s.name = QString("stream:") + i->index;
 
382
    s.icon_name = getIconNameFromProplist(i->proplist);
 
383
    //s.volume = i->volume;
 
384
    s.volume = captureDevices[i->source].volume;
 
385
    s.channel_map = i->channel_map;
 
386
    //s.mute = !!i->mute;
 
387
    s.mute = captureDevices[i->source].mute;
 
388
    s.stream_restore_rule = pa_proplist_gets(i->proplist, "module-stream-restore.id");
 
389
 
 
390
    translateMasksAndMaps(s);
 
391
 
 
392
    bool is_new = !captureStreams.contains(s.index);
 
393
    captureStreams[s.index] = s;
 
394
    kDebug(67100) << "Got some info about source output (capture stream): " << s.description;
 
395
 
 
396
    if (s_mixers.contains(KMIXPA_APP_CAPTURE)) {
 
397
        if (is_new)
 
398
            s_mixers[KMIXPA_APP_CAPTURE]->addWidget(s.index);
 
399
        else {
 
400
            int mid = s_mixers[KMIXPA_APP_CAPTURE]->id2num(s.name);
 
401
            if (mid >= 0) {
 
402
                MixSet *ms = s_mixers[KMIXPA_APP_CAPTURE]->getMixSet();
 
403
                (*ms)[mid]->setReadableName(s.description);
 
404
            }
 
405
        }
 
406
    }
 
407
}
 
408
 
 
409
 
 
410
static devinfo create_role_devinfo(const char* name) {
 
411
 
 
412
    Q_ASSERT(s_RestoreRules.contains(name));
 
413
 
 
414
    devinfo s;
 
415
    s.index = s.device_index = PA_INVALID_INDEX;
 
416
    s.description = i18n("Event Sounds");
 
417
    s.name = QString("restore:") + name;
 
418
    s.icon_name = "dialog-information";
 
419
    s.channel_map = s_RestoreRules[name].channel_map;
 
420
    s.volume = s_RestoreRules[name].volume;
 
421
    s.mute = s_RestoreRules[name].mute;
 
422
    s.stream_restore_rule = name;
 
423
 
 
424
    translateMasksAndMaps(s);
 
425
    return s;
 
426
}
 
427
 
 
428
 
 
429
void ext_stream_restore_read_cb(pa_context *c, const pa_ext_stream_restore_info *i, int eol, void *) {
 
430
 
 
431
    if (eol < 0) {
 
432
        dec_outstanding(c);
 
433
        kWarning(67100) << "Failed to initialize stream_restore extension: " << pa_strerror(pa_context_errno(s_context));
 
434
        return;
 
435
    }
 
436
 
 
437
    if (eol > 0) {
 
438
        dec_outstanding(c);
 
439
        // Special case: ensure that our media events exists.
 
440
        // On first login by a new users, this wont be in our database so we should create it.
 
441
        if (!outputRoles.contains(PA_INVALID_INDEX)) {
 
442
            // Create a fake rule
 
443
            restoreRule rule;
 
444
            rule.channel_map.channels = 1;
 
445
            rule.channel_map.map[0] = PA_CHANNEL_POSITION_MONO;
 
446
            rule.volume.channels = 1;
 
447
            rule.volume.values[0] = PA_VOLUME_NORM;
 
448
            rule.mute = false;
 
449
            rule.device = "";
 
450
            s_RestoreRules["sink-input-by-media-role:event"] = rule;
 
451
 
 
452
            devinfo s = create_role_devinfo("sink-input-by-media-role:event");
 
453
            outputRoles[s.index] = s;
 
454
            kDebug(67100) << "Initialising restore rule for new user: " << s.description;
 
455
 
 
456
            if (s_mixers.contains(KMIXPA_APP_PLAYBACK))
 
457
                s_mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index);
 
458
        }
 
459
 
 
460
        if (s_mixers.contains(KMIXPA_APP_PLAYBACK))
 
461
            s_mixers[KMIXPA_APP_PLAYBACK]->triggerUpdate();
 
462
        return;
 
463
    }
 
464
 
 
465
    kDebug(67100) << "Got some info about restore rule: " << i->name << i->device;
 
466
    restoreRule rule;
 
467
    rule.channel_map = i->channel_map;
 
468
    rule.volume = i->volume;
 
469
    rule.mute = !!i->mute;
 
470
    rule.device = i->device;
 
471
    s_RestoreRules[i->name] = rule;
 
472
 
 
473
    // We only want to know about Sound Events for now...
 
474
    if (strcmp(i->name, "sink-input-by-media-role:event") == 0) {
 
475
        devinfo s = create_role_devinfo(i->name);
 
476
        bool is_new = !outputRoles.contains(s.index);
 
477
        outputRoles[s.index] = s;
 
478
 
 
479
        if (is_new && s_mixers.contains(KMIXPA_APP_PLAYBACK))
 
480
            s_mixers[KMIXPA_APP_PLAYBACK]->addWidget(s.index);
 
481
    }
 
482
}
 
483
 
 
484
static void ext_stream_restore_subscribe_cb(pa_context *c, void *) {
 
485
 
 
486
    Q_ASSERT(c == s_context);
 
487
 
 
488
    pa_operation *o;
 
489
    if (!(o = pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, NULL))) {
 
490
        kWarning(67100) << "pa_ext_stream_restore_read() failed";
 
491
        return;
 
492
    }
 
493
 
 
494
    pa_operation_unref(o);
 
495
}
 
496
 
 
497
 
 
498
static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *) {
 
499
 
 
500
    Q_ASSERT(c == s_context);
 
501
 
 
502
    switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
 
503
        case PA_SUBSCRIPTION_EVENT_SINK:
 
504
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 
505
                if (s_mixers.contains(KMIXPA_PLAYBACK))
 
506
                    s_mixers[KMIXPA_PLAYBACK]->removeWidget(index);
 
507
            } else {
 
508
                pa_operation *o;
 
509
                if (!(o = pa_context_get_sink_info_by_index(c, index, sink_cb, NULL))) {
 
510
                    kWarning(67100) << "pa_context_get_sink_info_by_index() failed";
 
511
                    return;
 
512
                }
 
513
                pa_operation_unref(o);
 
514
            }
 
515
            break;
 
516
 
 
517
        case PA_SUBSCRIPTION_EVENT_SOURCE:
 
518
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 
519
                if (s_mixers.contains(KMIXPA_CAPTURE))
 
520
                    s_mixers[KMIXPA_CAPTURE]->removeWidget(index);
 
521
            } else {
 
522
                pa_operation *o;
 
523
                if (!(o = pa_context_get_source_info_by_index(c, index, source_cb, NULL))) {
 
524
                    kWarning(67100) << "pa_context_get_source_info_by_index() failed";
 
525
                    return;
 
526
                }
 
527
                pa_operation_unref(o);
 
528
            }
 
529
            break;
 
530
 
 
531
        case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
 
532
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 
533
                if (s_mixers.contains(KMIXPA_APP_PLAYBACK))
 
534
                    s_mixers[KMIXPA_APP_PLAYBACK]->removeWidget(index);
 
535
            } else {
 
536
                pa_operation *o;
 
537
                if (!(o = pa_context_get_sink_input_info(c, index, sink_input_cb, NULL))) {
 
538
                    kWarning(67100) << "pa_context_get_sink_input_info() failed";
 
539
                    return;
 
540
                }
 
541
                pa_operation_unref(o);
 
542
            }
 
543
            break;
 
544
 
 
545
        case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
 
546
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 
547
                if (s_mixers.contains(KMIXPA_APP_CAPTURE))
 
548
                    s_mixers[KMIXPA_APP_CAPTURE]->removeWidget(index);
 
549
            } else {
 
550
                pa_operation *o;
 
551
                if (!(o = pa_context_get_source_output_info(c, index, source_output_cb, NULL))) {
 
552
                    kWarning(67100) << "pa_context_get_sink_input_info() failed";
 
553
                    return;
 
554
                }
 
555
                pa_operation_unref(o);
 
556
            }
 
557
            break;
 
558
 
 
559
        case PA_SUBSCRIPTION_EVENT_CLIENT:
 
560
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 
561
                clients.remove(index);
 
562
            } else {
 
563
                pa_operation *o;
 
564
                if (!(o = pa_context_get_client_info(c, index, client_cb, NULL))) {
 
565
                    kWarning(67100) << "pa_context_get_client_info() failed";
 
566
                    return;
 
567
                }
 
568
                pa_operation_unref(o);
 
569
            }
 
570
            break;
 
571
 
 
572
    }
 
573
}
 
574
 
 
575
 
 
576
static void context_state_callback(pa_context *c, void *)
 
577
{
 
578
    pa_context_state_t state = pa_context_get_state(c);
 
579
    if (state == PA_CONTEXT_READY) {
 
580
        // Attempt to load things up
 
581
        pa_operation *o;
 
582
 
 
583
        // 1. Register for the stream changes (except during probe)
 
584
        if (s_context == c) {
 
585
            pa_context_set_subscribe_callback(c, subscribe_cb, NULL);
 
586
 
 
587
            if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
 
588
                                           (PA_SUBSCRIPTION_MASK_SINK|
 
589
                                            PA_SUBSCRIPTION_MASK_SOURCE|
 
590
                                            PA_SUBSCRIPTION_MASK_CLIENT|
 
591
                                            PA_SUBSCRIPTION_MASK_SINK_INPUT|
 
592
                                            PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), NULL, NULL))) {
 
593
                kWarning(67100) << "pa_context_subscribe() failed";
 
594
                return;
 
595
            }
 
596
            pa_operation_unref(o);
 
597
        }
 
598
 
 
599
        if (!(o = pa_context_get_sink_info_list(c, sink_cb, NULL))) {
 
600
            kWarning(67100) << "pa_context_get_sink_info_list() failed";
 
601
            return;
 
602
        }
 
603
        pa_operation_unref(o);
 
604
        s_outstandingRequests++;
 
605
 
 
606
        if (!(o = pa_context_get_source_info_list(c, source_cb, NULL))) {
 
607
            kWarning(67100) << "pa_context_get_source_info_list() failed";
 
608
            return;
 
609
        }
 
610
        pa_operation_unref(o);
 
611
        s_outstandingRequests++;
 
612
 
 
613
 
 
614
        if (!(o = pa_context_get_client_info_list(c, client_cb, NULL))) {
 
615
            kWarning(67100) << "pa_context_client_info_list() failed";
 
616
            return;
 
617
        }
 
618
        pa_operation_unref(o);
 
619
        s_outstandingRequests++;
 
620
 
 
621
        if (!(o = pa_context_get_sink_input_info_list(c, sink_input_cb, NULL))) {
 
622
            kWarning(67100) << "pa_context_get_sink_input_info_list() failed";
 
623
            return;
 
624
        }
 
625
        pa_operation_unref(o);
 
626
        s_outstandingRequests++;
 
627
 
 
628
        if (!(o = pa_context_get_source_output_info_list(c, source_output_cb, NULL))) {
 
629
            kWarning(67100) << "pa_context_get_source_output_info_list() failed";
 
630
            return;
 
631
        }
 
632
        pa_operation_unref(o);
 
633
        s_outstandingRequests++;
 
634
 
 
635
        /* These calls are not always supported */
 
636
        if ((o = pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, NULL))) {
 
637
            pa_operation_unref(o);
 
638
            s_outstandingRequests++;
 
639
 
 
640
            pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, NULL);
 
641
 
 
642
            if ((o = pa_ext_stream_restore_subscribe(c, 1, NULL, NULL)))
 
643
                pa_operation_unref(o);
 
644
        } else {
 
645
            kWarning(67100) << "Failed to initialize stream_restore extension: " << pa_strerror(pa_context_errno(s_context));
 
646
        }
 
647
    } else if (!PA_CONTEXT_IS_GOOD(state)) {
 
648
        // If this is our probe phase, exit our context immediately
 
649
        if (s_context != c) {
 
650
            pa_context_disconnect(c);
 
651
        } else {
 
652
            // If we're not probing, it means we've been disconnected from our
 
653
            // glib context
 
654
            pa_context_unref(s_context);
 
655
            s_context = NULL;
 
656
 
 
657
            // Remove all GUI elements
 
658
            QMap<int,Mixer_PULSE*>::iterator it;
 
659
            for (it = s_mixers.begin(); it != s_mixers.end(); ++it) {
 
660
                (*it)->removeAllWidgets();
 
661
            }
 
662
            // This one is not handled above.
 
663
            clients.clear();
 
664
 
 
665
            if (s_mixers.contains(KMIXPA_PLAYBACK)) {
 
666
                kWarning(67100) << "Connection to PulseAudio daemon closed. Attempting reconnection.";
 
667
                s_pulseActive = UNKNOWN;
 
668
                QTimer::singleShot(50, s_mixers[KMIXPA_PLAYBACK], SLOT(reinit()));
 
669
            }
 
670
        }
 
671
    }
 
672
}
 
673
 
 
674
static void setVolumeFromPulse(Volume& volume, const devinfo& dev)
 
675
{
 
676
    chanIDMap::const_iterator iter;
 
677
    for (iter = dev.chanIDs.begin(); iter != dev.chanIDs.end(); ++iter)
 
678
    {
 
679
        //kDebug(67100) <<  "Setting volume for channel " << iter.value() << " to " << (long)dev.volume.values[iter.key()] << " (" << ((100*(long)dev.volume.values[iter.key()]) / PA_VOLUME_NORM) << "%)";
 
680
        volume.setVolume(iter.value(), (long)dev.volume.values[iter.key()]);
 
681
    }
 
682
}
 
683
 
 
684
static pa_cvolume genVolumeForPulse(const devinfo& dev, Volume& volume)
 
685
{
 
686
    pa_cvolume cvol = dev.volume;
 
687
 
 
688
    chanIDMap::const_iterator iter;
 
689
    for (iter = dev.chanIDs.begin(); iter != dev.chanIDs.end(); ++iter)
 
690
    {
 
691
        cvol.values[iter.key()] = (uint32_t)volume.getVolume(iter.value());
 
692
        //kDebug(67100) <<  "Setting volume for channel " << iter.value() << " to " << cvol.values[iter.key()] << " (" << ((100*cvol.values[iter.key()]) / PA_VOLUME_NORM) << "%)";
 
693
    }
 
694
    return cvol;
 
695
}
 
696
 
 
697
static devmap* get_widget_map(int type, QString id = "")
 
698
{
 
699
    Q_ASSERT(type >= 0 && type <= KMIXPA_WIDGET_MAX);
 
700
 
 
701
    if (KMIXPA_PLAYBACK == type)
 
702
        return &outputDevices;
 
703
    else if (KMIXPA_CAPTURE == type)
 
704
        return &captureDevices;
 
705
    else if (KMIXPA_APP_PLAYBACK == type) {
 
706
        if (id.startsWith("restore:"))
 
707
            return &outputRoles;
 
708
        return &outputStreams;
 
709
    } else if (KMIXPA_APP_CAPTURE == type)
 
710
        return &captureStreams;
 
711
 
 
712
    Q_ASSERT(0);
 
713
    return NULL;
 
714
}
 
715
static devmap* get_widget_map(int type, int index)
 
716
{
 
717
    if (PA_INVALID_INDEX == (uint32_t)index)
 
718
        return get_widget_map(type, "restore:");
 
719
    return get_widget_map(type);
 
720
}
 
721
 
 
722
void Mixer_PULSE::addWidget(int index)
 
723
{
 
724
    devmap* map = get_widget_map(m_devnum, index);
 
725
 
 
726
    if (!map->contains(index)) {
 
727
        kWarning(67100) <<  "New " << m_devnum << " widget notified for index " << index << " but I cannot find it in my list :s";
 
728
        return;
 
729
    }
 
730
    addDevice((*map)[index]);
 
731
    emit controlsReconfigured(_mixer->id());
 
732
}
 
733
 
 
734
void Mixer_PULSE::removeWidget(int index)
 
735
{
 
736
    devmap* map = get_widget_map(m_devnum);
 
737
 
 
738
    if (!map->contains(index)) {
 
739
        //kWarning(67100) <<  "Removing " << m_devnum << " widget notified for index " << index << " but I cannot find it in my list :s";
 
740
        // Sometimes we ignore things (e.g. event sounds) so don't be too noisy here.
 
741
        return;
 
742
    }
 
743
 
 
744
    QString id = (*map)[index].name;
 
745
    map->remove(index);
 
746
 
 
747
    // We need to find the MixDevice that goes with this widget and remove it.
 
748
    MixSet::iterator iter;
 
749
    for (iter = m_mixDevices.begin(); iter != m_mixDevices.end(); ++iter)
 
750
    {
 
751
        if ((*iter)->id() == id)
 
752
        {
 
753
            delete *iter;
 
754
            m_mixDevices.erase(iter);
 
755
            emit controlsReconfigured(_mixer->id());
 
756
            return;
 
757
        }
 
758
    }
 
759
}
 
760
 
 
761
void Mixer_PULSE::removeAllWidgets()
 
762
{
 
763
    devmap* map = get_widget_map(m_devnum);
 
764
    map->clear();
 
765
 
 
766
    // Special case
 
767
    if (KMIXPA_APP_PLAYBACK == m_devnum)
 
768
        outputRoles.clear();
 
769
 
 
770
    MixSet::iterator iter;
 
771
    for (iter = m_mixDevices.begin(); iter != m_mixDevices.end(); ++iter)
 
772
    {
 
773
        delete *iter;
 
774
        m_mixDevices.erase(iter);
 
775
    }
 
776
    emit controlsReconfigured(_mixer->id());
 
777
}
 
778
 
 
779
void Mixer_PULSE::addDevice(devinfo& dev)
 
780
{
 
781
    if (dev.chanMask != Volume::MNONE) {
 
782
        MixSet *ms = 0;
 
783
        if (m_devnum == KMIXPA_APP_PLAYBACK && s_mixers.contains(KMIXPA_PLAYBACK))
 
784
            ms = s_mixers[KMIXPA_PLAYBACK]->getMixSet();
 
785
        else if (m_devnum == KMIXPA_APP_CAPTURE && s_mixers.contains(KMIXPA_CAPTURE))
 
786
            ms = s_mixers[KMIXPA_CAPTURE]->getMixSet();
 
787
 
 
788
        Volume v(dev.chanMask, PA_VOLUME_NORM, PA_VOLUME_MUTED, true, false);
 
789
        setVolumeFromPulse(v, dev);
 
790
        MixDevice* md = new MixDevice( _mixer, dev.name, dev.description, dev.icon_name, true, ms);
 
791
        md->addPlaybackVolume(v);
 
792
        md->setMuted(dev.mute);
 
793
        m_mixDevices.append(md);
 
794
    }
 
795
}
29
796
 
30
797
Mixer_Backend* PULSE_getMixer( Mixer *mixer, int devnum )
31
798
{
34
801
   return l_mixer;
35
802
}
36
803
 
 
804
bool Mixer_PULSE::connectToDaemon()
 
805
{
 
806
    Q_ASSERT(NULL == s_context);
 
807
 
 
808
    kDebug(67100) <<  "Attempting connection to PulseAudio sound daemon";
 
809
    pa_mainloop_api *api = pa_glib_mainloop_get_api(s_mainloop);
 
810
    Q_ASSERT(api);
 
811
 
 
812
    s_context = pa_context_new(api, "KMix KDE 4");
 
813
    Q_ASSERT(s_context);
 
814
 
 
815
    if (pa_context_connect(s_context, NULL, PA_CONTEXT_NOFAIL, 0) < 0) {
 
816
        pa_context_unref(s_context);
 
817
        s_context = NULL;
 
818
        return false;
 
819
    }
 
820
    pa_context_set_state_callback(s_context, &context_state_callback, NULL);
 
821
    return true;
 
822
}
 
823
 
37
824
 
38
825
Mixer_PULSE::Mixer_PULSE(Mixer *mixer, int devnum) : Mixer_Backend(mixer, devnum)
39
826
{
40
 
   if ( devnum == -1 )
41
 
      m_devnum = 0;
 
827
    if ( devnum == -1 )
 
828
        m_devnum = 0;
 
829
 
 
830
    QString pulseenv = qgetenv("KMIX_PULSEAUDIO_DISABLE");
 
831
    if (pulseenv.toInt())
 
832
        s_pulseActive = INACTIVE;
 
833
 
 
834
    // We require a glib event loop
 
835
    if (QLatin1String(QAbstractEventDispatcher::instance()->metaObject()->className())
 
836
            != "QGuiEventDispatcherGlib") {
 
837
        kDebug(67100) << "Disabling PulseAudio integration for lack of GLib event loop.";
 
838
        s_pulseActive = INACTIVE;
 
839
    }
 
840
 
 
841
 
 
842
    ++refcount;
 
843
    if (INACTIVE != s_pulseActive && 1 == refcount)
 
844
    {
 
845
        // First of all conenct to PA via simple/blocking means and if that succeeds,
 
846
        // use a fully async integrated mainloop method to connect and get proper support.
 
847
        pa_mainloop *p_test_mainloop;
 
848
        if (!(p_test_mainloop = pa_mainloop_new())) {
 
849
            kDebug(67100) << "PulseAudio support disabled: Unable to create mainloop";
 
850
            s_pulseActive = INACTIVE;
 
851
            goto endconstruct;
 
852
        }
 
853
 
 
854
        pa_context *p_test_context;
 
855
        if (!(p_test_context = pa_context_new(pa_mainloop_get_api(p_test_mainloop), "kmix-probe"))) {
 
856
            kDebug(67100) << "PulseAudio support disabled: Unable to create context";
 
857
            pa_mainloop_free(p_test_mainloop);
 
858
            s_pulseActive = INACTIVE;
 
859
            goto endconstruct;
 
860
        }
 
861
 
 
862
        kDebug(67100) << "Probing for PulseAudio...";
 
863
        // (cg) Convert to PA_CONTEXT_NOFLAGS when PulseAudio 0.9.19 is required
 
864
        if (pa_context_connect(p_test_context, NULL, static_cast<pa_context_flags_t>(0), NULL) < 0) {
 
865
            kDebug(67100) << QString("PulseAudio support disabled: %1").arg(pa_strerror(pa_context_errno(p_test_context)));
 
866
            pa_context_disconnect(p_test_context);
 
867
            pa_context_unref(p_test_context);
 
868
            pa_mainloop_free(p_test_mainloop);
 
869
            s_pulseActive = INACTIVE;
 
870
            goto endconstruct;
 
871
        }
 
872
 
 
873
        // Assume we are inactive, it will be set to active if appropriate
 
874
        s_pulseActive = INACTIVE;
 
875
        pa_context_set_state_callback(p_test_context, &context_state_callback, NULL);
 
876
        for (;;) {
 
877
          pa_mainloop_iterate(p_test_mainloop, 1, NULL);
 
878
 
 
879
          if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(p_test_context))) {
 
880
            kDebug(67100) << "PulseAudio probe complete.";
 
881
            break;
 
882
          }
 
883
        }
 
884
        pa_context_disconnect(p_test_context);
 
885
        pa_context_unref(p_test_context);
 
886
        pa_mainloop_free(p_test_mainloop);
 
887
 
 
888
 
 
889
        if (INACTIVE != s_pulseActive)
 
890
        {
 
891
            // Reconnect via integrated mainloop
 
892
            s_mainloop = pa_glib_mainloop_new(NULL);
 
893
            Q_ASSERT(s_mainloop);
 
894
 
 
895
            connectToDaemon();
 
896
        }
 
897
 
 
898
        kDebug(67100) <<  "PulseAudio status: " << (s_pulseActive==UNKNOWN ? "Unknown (bug)" : (s_pulseActive==ACTIVE ? "Active" : "Inactive"));
 
899
    }
 
900
 
 
901
endconstruct:
 
902
    s_mixers[m_devnum] = this;
42
903
}
43
904
 
44
905
Mixer_PULSE::~Mixer_PULSE()
45
906
{
46
 
   close();
 
907
    s_mixers.remove(m_devnum);
 
908
 
 
909
    if (refcount > 0)
 
910
    {
 
911
        --refcount;
 
912
        if (0 == refcount)
 
913
        {
 
914
            if (s_context) {
 
915
                pa_context_unref(s_context);
 
916
                s_context = NULL;
 
917
            }
 
918
 
 
919
            if (s_mainloop) {
 
920
                pa_glib_mainloop_free(s_mainloop);
 
921
                s_mainloop = NULL;
 
922
            }
 
923
        }
 
924
    }
47
925
}
48
926
 
49
927
int Mixer_PULSE::open()
50
928
{
51
 
    kDebug(67100) <<  "Trying Pulse sink";
52
 
    mainloop = pa_glib_mainloop_new(g_main_context_default());
53
 
    g_assert(mainloop);
54
 
    pa_mainloop_api *api = pa_glib_mainloop_get_api(mainloop);
55
 
    g_assert(api);
56
 
 
57
 
    context = pa_context_new(api, "KMix KDE 4");
58
 
    g_assert(context);
59
 
      //return Mixer::ERR_OPEN;
 
929
    //kDebug(67100) <<  "Trying Pulse sink";
 
930
 
 
931
    if (ACTIVE == s_pulseActive && m_devnum <= KMIXPA_APP_CAPTURE)
 
932
    {
 
933
        // Make sure the GUI layers know we are dynamic so as to always paint us
 
934
        _mixer->setDynamic();
 
935
 
 
936
        devmap::iterator iter;
 
937
        if (KMIXPA_PLAYBACK == m_devnum)
 
938
        {
 
939
            m_mixerName = i18n("Playback Devices");
 
940
            for (iter = outputDevices.begin(); iter != outputDevices.end(); ++iter)
 
941
                addDevice(*iter);
 
942
        }
 
943
        else if (KMIXPA_CAPTURE == m_devnum)
 
944
        {
 
945
            m_mixerName = i18n("Capture Devices");
 
946
            for (iter = captureDevices.begin(); iter != captureDevices.end(); ++iter)
 
947
                addDevice(*iter);
 
948
        }
 
949
        else if (KMIXPA_APP_PLAYBACK == m_devnum)
 
950
        {
 
951
            m_mixerName = i18n("Playback Streams");
 
952
            for (iter = outputRoles.begin(); iter != outputRoles.end(); ++iter)
 
953
                addDevice(*iter);
 
954
            for (iter = outputStreams.begin(); iter != outputStreams.end(); ++iter)
 
955
                addDevice(*iter);
 
956
        }
 
957
        else if (KMIXPA_APP_CAPTURE == m_devnum)
 
958
        {
 
959
            m_mixerName = i18n("Capture Streams");
 
960
            for (iter = captureStreams.begin(); iter != captureStreams.end(); ++iter)
 
961
                addDevice(*iter);
 
962
        }
 
963
 
 
964
        kDebug(67100) <<  "Using PulseAudio for mixer: " << m_mixerName;
 
965
        m_isOpen = true;
 
966
    }
60
967
 
61
 
/* 
62
 
      //
63
 
      // Mixer is open. Now define all of the mix devices.
64
 
      //
65
 
 
66
 
         for ( int idx = 0; idx < numDevs; idx++ )
67
 
         {
68
 
            Volume vol( 2, AUDIO_MAX_GAIN );
69
 
            QString id;
70
 
            id.setNum(idx);
71
 
            MixDevice* md = new MixDevice( _mixer, id,
72
 
               QString(MixerDevNames[idx]), MixerChannelTypes[idx]);
73
 
            md->addPlaybackVolume(vol);
74
 
            md->setRecSource( isRecsrcHW( idx ) );
75
 
            m_mixDevices.append( md );
76
 
         }
77
 
*/
78
 
 
79
 
    m_mixerName = "PULSE Audio Mixer";
80
 
 
81
 
    m_isOpen = true;
82
 
 
83
968
    return 0;
84
969
}
85
970
 
86
971
int Mixer_PULSE::close()
87
972
{
88
 
    if (context)
89
 
    {
90
 
        pa_context_unref(context);
91
 
        context = NULL;
92
 
    }
93
 
    if (mainloop)
94
 
    {
95
 
        pa_glib_mainloop_free(mainloop);
96
 
        mainloop = NULL;
97
 
    }
98
973
    return 1;
99
974
}
100
975
 
 
976
int Mixer_PULSE::id2num(const QString& id) {
 
977
    //kDebug(67100) << "id2num() id=" << id;
 
978
    int num = -1;
 
979
    // todo: Store this in a hash or similar
 
980
    int i;
 
981
    for (i = 0; i < m_mixDevices.size(); ++i) {
 
982
        if (m_mixDevices[i]->id() == id) {
 
983
            num = i;
 
984
            break;
 
985
        }
 
986
    }
 
987
    //kDebug(67100) << "id2num() num=" << num;
 
988
    return num;
 
989
}
 
990
 
101
991
int Mixer_PULSE::readVolumeFromHW( const QString& id, MixDevice *md )
102
992
{
103
 
/*   audio_info_t audioinfo;
104
 
   uint_t devMask = MixerSunPortMasks[devnum];
105
 
 
106
 
   Volume& volume = md->playbackVolume();
107
 
   int devnum = id2num(id);
108
 
   //
109
 
   // Read the current audio information from the driver
110
 
   //
111
 
   if ( ioctl( fd, AUDIO_GETINFO, &audioinfo ) < 0 )
112
 
   {
113
 
      return( Mixer::ERR_READ );
114
 
   }
115
 
   else
116
 
   {
117
 
      //
118
 
      // Extract the appropriate fields based on the requested device
119
 
      //
120
 
      switch ( devnum )
121
 
      {
122
 
         case MIXERDEV_MASTER_VOLUME :
123
 
            volume.setSwitchActivated( audioinfo.output_muted );
124
 
            GainBalanceToVolume( audioinfo.play.gain,
125
 
                                 audioinfo.play.balance,
126
 
                                 volume );
127
 
            break;
128
 
 
129
 
         case MIXERDEV_RECORD_MONITOR :
130
 
            md->setMuted(false);
131
 
            volume.setAllVolumes( audioinfo.monitor_gain );
132
 
            break;
133
 
 
134
 
         case MIXERDEV_INTERNAL_SPEAKER :
135
 
         case MIXERDEV_HEADPHONE :
136
 
         case MIXERDEV_LINE_OUT :
137
 
            md->setMuted( (audioinfo.play.port & devMask) ? false : true );
138
 
            GainBalanceToVolume( audioinfo.play.gain,
139
 
                                 audioinfo.play.balance,
140
 
                                 volume );
141
 
            break;
142
 
 
143
 
         case MIXERDEV_MICROPHONE :
144
 
         case MIXERDEV_LINE_IN :
145
 
         case MIXERDEV_CD :
146
 
            md->setMuted( (audioinfo.record.port & devMask) ? false : true );
147
 
            GainBalanceToVolume( audioinfo.record.gain,
148
 
                                 audioinfo.record.balance,
149
 
                                 volume );
150
 
            break;
151
 
 
152
 
         default :
153
 
            return Mixer::ERR_READ;
154
 
      }
155
 
      return 0;
156
 
   }*/
157
 
   return 0;
 
993
    devmap *map = get_widget_map(m_devnum, id);
 
994
 
 
995
    devmap::iterator iter;
 
996
    for (iter = map->begin(); iter != map->end(); ++iter)
 
997
    {
 
998
        if (iter->name == id)
 
999
        {
 
1000
            setVolumeFromPulse(md->playbackVolume(), *iter);
 
1001
            md->setMuted(iter->mute);
 
1002
            break;
 
1003
        }
 
1004
    }
 
1005
 
 
1006
    return 0;
158
1007
}
159
1008
 
160
1009
int Mixer_PULSE::writeVolumeToHW( const QString& id, MixDevice *md )
161
1010
{
162
 
/*   uint_t gain;
163
 
   uchar_t balance;
164
 
   uchar_t mute;
165
 
 
166
 
   Volume& volume = md->playbackVolume();
167
 
   int devnum = id2num(id);
168
 
   //
169
 
   // Convert the Volume(left vol, right vol) to the Gain/Balance Sun uses
170
 
   //
171
 
   VolumeToGainBalance( volume, gain, balance );
172
 
   mute = md->isMuted() ? 1 : 0;
173
 
 
174
 
   //
175
 
   // Read the current audio settings from the hardware
176
 
   //
177
 
   audio_info_t audioinfo;
178
 
   if ( ioctl( fd, AUDIO_GETINFO, &audioinfo ) < 0 )
179
 
   {
180
 
      return( Mixer::ERR_READ );
181
 
   }
182
 
 
183
 
   //
184
 
   // Now, based on the devnum that we are writing to, update the appropriate
185
 
   // volume field and twiddle the appropriate bitmask to enable/mute the
186
 
   // device as necessary.
187
 
   //
188
 
   switch ( devnum )
189
 
   {
190
 
      case MIXERDEV_MASTER_VOLUME :
191
 
         audioinfo.play.gain = gain;
192
 
         audioinfo.play.balance = balance;
193
 
         audioinfo.output_muted = mute;
194
 
         break;
195
 
 
196
 
      case MIXERDEV_RECORD_MONITOR :
197
 
         audioinfo.monitor_gain = gain;
198
 
         // no mute or balance for record monitor
199
 
         break;
200
 
 
201
 
      case MIXERDEV_INTERNAL_SPEAKER :
202
 
      case MIXERDEV_HEADPHONE :
203
 
      case MIXERDEV_LINE_OUT :
204
 
         audioinfo.play.gain = gain;
205
 
         audioinfo.play.balance = balance;
206
 
         if ( mute )
207
 
            audioinfo.play.port &= ~MixerSunPortMasks[devnum];
208
 
         else
209
 
            audioinfo.play.port |= MixerSunPortMasks[devnum];
210
 
         break;
211
 
 
212
 
      case MIXERDEV_MICROPHONE :
213
 
      case MIXERDEV_LINE_IN :
214
 
      case MIXERDEV_CD :
215
 
         audioinfo.record.gain = gain;
216
 
         audioinfo.record.balance = balance;
217
 
         if ( mute )
218
 
            audioinfo.record.port &= ~MixerSunPortMasks[devnum];
219
 
         else
220
 
            audioinfo.record.port |= MixerSunPortMasks[devnum];
221
 
         break;
222
 
 
223
 
      default :
224
 
         return Mixer::ERR_READ;
225
 
   }
226
 
 
227
 
   //
228
 
   // Now that we've updated the audioinfo struct, write it back to the hardware
229
 
   //
230
 
   if ( ioctl( fd, AUDIO_SETINFO, &audioinfo ) < 0 )
231
 
   {
232
 
      return( Mixer::ERR_WRITE );
233
 
   }
234
 
   else
235
 
   {
236
 
      return 0;
237
 
   }*/
238
 
   return 0;
 
1011
    devmap::iterator iter;
 
1012
    if (KMIXPA_PLAYBACK == m_devnum)
 
1013
    {
 
1014
        for (iter = outputDevices.begin(); iter != outputDevices.end(); ++iter)
 
1015
        {
 
1016
            if (iter->name == id)
 
1017
            {
 
1018
                pa_operation *o;
 
1019
 
 
1020
                pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume());
 
1021
                if (!(o = pa_context_set_sink_volume_by_index(s_context, iter->index, &volume, NULL, NULL))) {
 
1022
                    kWarning(67100) <<  "pa_context_set_sink_volume_by_index() failed";
 
1023
                    return Mixer::ERR_READ;
 
1024
                }
 
1025
                pa_operation_unref(o);
 
1026
 
 
1027
                if (!(o = pa_context_set_sink_mute_by_index(s_context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) {
 
1028
                    kWarning(67100) <<  "pa_context_set_sink_mute_by_index() failed";
 
1029
                    return Mixer::ERR_READ;
 
1030
                }
 
1031
                pa_operation_unref(o);
 
1032
 
 
1033
                return 0;
 
1034
            }
 
1035
        }
 
1036
    }
 
1037
    else if (KMIXPA_CAPTURE == m_devnum)
 
1038
    {
 
1039
        for (iter = captureDevices.begin(); iter != captureDevices.end(); ++iter)
 
1040
        {
 
1041
            if (iter->name == id)
 
1042
            {
 
1043
                pa_operation *o;
 
1044
 
 
1045
                pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume());
 
1046
                if (!(o = pa_context_set_source_volume_by_index(s_context, iter->index, &volume, NULL, NULL))) {
 
1047
                    kWarning(67100) <<  "pa_context_set_source_volume_by_index() failed";
 
1048
                    return Mixer::ERR_READ;
 
1049
                }
 
1050
                pa_operation_unref(o);
 
1051
 
 
1052
                if (!(o = pa_context_set_source_mute_by_index(s_context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) {
 
1053
                    kWarning(67100) <<  "pa_context_set_source_mute_by_index() failed";
 
1054
                    return Mixer::ERR_READ;
 
1055
                }
 
1056
                pa_operation_unref(o);
 
1057
 
 
1058
                return 0;
 
1059
            }
 
1060
        }
 
1061
    }
 
1062
    else if (KMIXPA_APP_PLAYBACK == m_devnum)
 
1063
    {
 
1064
        if (id.startsWith("stream:"))
 
1065
        {
 
1066
            for (iter = outputStreams.begin(); iter != outputStreams.end(); ++iter)
 
1067
            {
 
1068
                if (iter->name == id)
 
1069
                {
 
1070
                    pa_operation *o;
 
1071
 
 
1072
                    pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume());
 
1073
                    if (!(o = pa_context_set_sink_input_volume(s_context, iter->index, &volume, NULL, NULL))) {
 
1074
                        kWarning(67100) <<  "pa_context_set_sink_input_volume() failed";
 
1075
                        return Mixer::ERR_READ;
 
1076
                    }
 
1077
                    pa_operation_unref(o);
 
1078
 
 
1079
                    if (!(o = pa_context_set_sink_input_mute(s_context, iter->index, (md->isMuted() ? 1 : 0), NULL, NULL))) {
 
1080
                        kWarning(67100) <<  "pa_context_set_sink_input_mute() failed";
 
1081
                        return Mixer::ERR_READ;
 
1082
                    }
 
1083
                    pa_operation_unref(o);
 
1084
 
 
1085
                    return 0;
 
1086
                }
 
1087
            }
 
1088
        }
 
1089
        else if (id.startsWith("restore:"))
 
1090
        {
 
1091
            for (iter = outputRoles.begin(); iter != outputRoles.end(); ++iter)
 
1092
            {
 
1093
                if (iter->name == id)
 
1094
                {
 
1095
                    restoreRule &rule = s_RestoreRules[iter->stream_restore_rule];
 
1096
                    pa_ext_stream_restore_info info;
 
1097
                    info.name = iter->stream_restore_rule.toAscii().constData();
 
1098
                    info.channel_map = rule.channel_map;
 
1099
                    info.volume = genVolumeForPulse(*iter, md->playbackVolume());
 
1100
                    info.device = rule.device.isEmpty() ? NULL : rule.device.toAscii().constData();
 
1101
                    info.mute = (md->isMuted() ? 1 : 0);
 
1102
 
 
1103
                    pa_operation* o;
 
1104
                    if (!(o = pa_ext_stream_restore_write(s_context, PA_UPDATE_REPLACE, &info, 1, TRUE, NULL, NULL))) {
 
1105
                        kWarning(67100) <<  "pa_ext_stream_restore_write() failed" << info.channel_map.channels << info.volume.channels << info.name;
 
1106
                        return Mixer::ERR_READ;
 
1107
                    }
 
1108
                    pa_operation_unref(o);
 
1109
 
 
1110
                    return 0;
 
1111
                }
 
1112
            }
 
1113
        }
 
1114
    }
 
1115
    else if (KMIXPA_APP_CAPTURE == m_devnum)
 
1116
    {
 
1117
        for (iter = captureStreams.begin(); iter != captureStreams.end(); ++iter)
 
1118
        {
 
1119
            if (iter->name == id)
 
1120
            {
 
1121
                pa_operation *o;
 
1122
 
 
1123
                // NB Note that this is different from APP_PLAYBACK in that we set the volume on the source itself.
 
1124
                pa_cvolume volume = genVolumeForPulse(*iter, md->playbackVolume());
 
1125
                if (!(o = pa_context_set_source_volume_by_index(s_context, iter->device_index, &volume, NULL, NULL))) {
 
1126
                    kWarning(67100) <<  "pa_context_set_source_volume_by_index() failed";
 
1127
                    return Mixer::ERR_READ;
 
1128
                }
 
1129
                pa_operation_unref(o);
 
1130
 
 
1131
                if (!(o = pa_context_set_source_mute_by_index(s_context, iter->device_index, (md->isMuted() ? 1 : 0), NULL, NULL))) {
 
1132
                    kWarning(67100) <<  "pa_context_set_source_mute_by_index() failed";
 
1133
                    return Mixer::ERR_READ;
 
1134
                }
 
1135
                pa_operation_unref(o);
 
1136
 
 
1137
                return 0;
 
1138
            }
 
1139
        }
 
1140
    }
 
1141
 
 
1142
    return 0;
 
1143
}
 
1144
 
 
1145
/**
 
1146
* Move the stream to a new destination
 
1147
*/
 
1148
bool Mixer_PULSE::moveStream( const QString& id, const QString& destId ) {
 
1149
    Q_ASSERT(KMIXPA_APP_PLAYBACK == m_devnum || KMIXPA_APP_CAPTURE == m_devnum);
 
1150
 
 
1151
    kDebug(67100) <<  "Mixer_PULSE::moveStream(): Move Stream Requested - Stream: " << id << ", Destination: " << destId;
 
1152
 
 
1153
    // Lookup the stream index.
 
1154
    uint32_t stream_index = PA_INVALID_INDEX;
 
1155
    const char* stream_restore_rule = NULL;
 
1156
    devmap::iterator iter;
 
1157
    devmap *map = get_widget_map(m_devnum);
 
1158
    for (iter = map->begin(); iter != map->end(); ++iter) {
 
1159
        if (iter->name == id) {
 
1160
            stream_index = iter->index;
 
1161
            stream_restore_rule = iter->stream_restore_rule.isEmpty() ? NULL : iter->stream_restore_rule.toAscii().constData();
 
1162
            break;
 
1163
        }
 
1164
    }
 
1165
 
 
1166
    if (PA_INVALID_INDEX == stream_index) {
 
1167
        kError(67100) <<  "Mixer_PULSE::moveStream(): Cannot find stream index";
 
1168
        return false;
 
1169
    }
 
1170
 
 
1171
    if (destId.isEmpty()) {
 
1172
        // We want to remove any specific device in the stream restore rule.
 
1173
        if (!stream_restore_rule || !s_RestoreRules.contains(stream_restore_rule)) {
 
1174
            kWarning(67100) <<  "Mixer_PULSE::moveStream(): Trying to set Automatic on a stream with no rule";
 
1175
        } else {
 
1176
            restoreRule &rule = s_RestoreRules[stream_restore_rule];
 
1177
            pa_ext_stream_restore_info info;
 
1178
            info.name = stream_restore_rule;
 
1179
            info.channel_map = rule.channel_map;
 
1180
            info.volume = rule.volume;
 
1181
            info.device = NULL;
 
1182
            info.mute = rule.mute ? 1 : 0;
 
1183
 
 
1184
            pa_operation* o;
 
1185
            if (!(o = pa_ext_stream_restore_write(s_context, PA_UPDATE_REPLACE, &info, 1, TRUE, NULL, NULL))) {
 
1186
                kWarning(67100) <<  "pa_ext_stream_restore_write() failed" << info.channel_map.channels << info.volume.channels << info.name;
 
1187
                return Mixer::ERR_READ;
 
1188
            }
 
1189
            pa_operation_unref(o);
 
1190
        }
 
1191
    } else {
 
1192
        pa_operation* o;
 
1193
        if (KMIXPA_APP_PLAYBACK == m_devnum) {
 
1194
            if (!(o = pa_context_move_sink_input_by_name(s_context, stream_index, destId.toAscii().constData(), NULL, NULL))) {
 
1195
                kWarning(67100) <<  "pa_context_move_sink_input_by_name() failed";
 
1196
                return false;
 
1197
            }
 
1198
        } else {
 
1199
            if (!(o = pa_context_move_source_output_by_name(s_context, stream_index, destId.toAscii().constData(), NULL, NULL))) {
 
1200
                kWarning(67100) <<  "pa_context_move_source_output_by_name() failed";
 
1201
                return false;
 
1202
            }
 
1203
        }
 
1204
        pa_operation_unref(o);
 
1205
    }
 
1206
 
 
1207
    return true;
 
1208
}
 
1209
 
 
1210
void Mixer_PULSE::reinit()
 
1211
{
 
1212
    // We only support reinit on our primary mixer.
 
1213
    Q_ASSERT(KMIXPA_PLAYBACK == m_devnum);
 
1214
    connectToDaemon();
 
1215
}
 
1216
 
 
1217
void Mixer_PULSE::triggerUpdate()
 
1218
{
 
1219
    readSetFromHWforceUpdate();
 
1220
    readSetFromHW();
239
1221
}
240
1222
 
241
1223
void Mixer_PULSE::setRecsrcHW( const QString& /*id*/, bool /* on */ )
243
1225
   return;
244
1226
}
245
1227
 
246
 
bool Mixer_PULSE::isRecsrcHW( const QString& id )
 
1228
bool Mixer_PULSE::isRecsrcHW( const QString& /*id*/ )
247
1229
{
248
 
/*   int devnum = id2num(id);
249
 
   switch ( devnum )
250
 
   {
251
 
      case MIXERDEV_MICROPHONE :
252
 
      case MIXERDEV_LINE_IN :
253
 
      case MIXERDEV_CD :
254
 
         return true;
255
 
 
256
 
      default :
257
 
         return false;
258
 
   }*/
259
1230
   return false;
260
1231
}
261
1232