Esempio n. 1
0
static void m_pa_context_subscribe_cb(pa_context *c,                            
                                      pa_subscription_event_type_t t,           
                                      uint32_t idx,                             
                                      void *user_data)                           
{                                                                               
    if (!c) {
        printf("m_pa_context_subscribe_cb() invalid arguement\n");
        return;
    }
                                                                                
    switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {                          
        case PA_SUBSCRIPTION_EVENT_SINK:                                        
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
                pa_context_get_sink_info_by_index(c, idx, m_pa_sink_event_cb, "sink_new");
            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
                pa_context_get_sink_info_by_index(c, idx, m_pa_sink_event_cb, "sink_changed");
            }                                                                   
            break;
        /* TODO: it does not need to test so much kind of event signal
        case PA_SUBSCRIPTION_EVENT_SOURCE:                                      
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
                pa_context_get_source_info_by_index(c, idx, m_pa_source_new_cb, NULL);
            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
                pa_context_get_source_info_by_index(c, idx, m_pa_source_changed_cb, NULL);
            }                                                                   
            break;
        case PA_SUBSCRIPTION_EVENT_SINK_INPUT:                                  
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
                pa_context_get_sink_input_info(c, idx, m_pa_sink_input_new_cb, NULL);
            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
                pa_context_get_sink_input_info(c, idx, m_pa_sink_input_changed_cb, NULL);
            }                                                                   
            break;                                                              
        case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:                               
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
                pa_context_get_source_output_info(c, idx, m_pa_source_output_new_cb, NULL);
            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
                pa_context_get_source_output_info(c, idx, m_pa_source_output_changed_cb, NULL);
            }                                                                   
            break;                                                              
        case PA_SUBSCRIPTION_EVENT_CLIENT:                                      
            break;                                                              
        case PA_SUBSCRIPTION_EVENT_SERVER:                                      
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
                pa_context_get_server_info(c, m_pa_server_new_cb, NULL);        
            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
                pa_context_get_server_info(c, m_pa_server_changed_cb, NULL);    
            }                                                                   
            break;
        case PA_SUBSCRIPTION_EVENT_CARD:                                        
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
                pa_context_get_card_info_by_index(c, idx, m_pa_card_new_cb, NULL);
            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
                pa_context_get_card_info_by_index(c, idx, m_pa_card_changed_cb, NULL);
            }                                                                   
            break;
        */
    }                                                                           
}
Esempio n. 2
0
void on_event(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
{
    if((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
        /* sink input changed */
        if((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
            if(idx != PA_INVALID_INDEX)
                pa_context_get_sink_input_info(c, idx, on_sink_input_update, NULL);
        }
        /* sink input removed */
        if((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
            if(idx != PA_INVALID_INDEX) {
                sink_input_remove(idx);
                view_update();
            }
        }
        /* sink input added */
        if((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
            if(idx != PA_INVALID_INDEX)
                pa_context_get_sink_input_info(c, idx, on_sink_input_update, NULL);
        }
    }
    if((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) {
        /* sink changed */
        if((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
            if(idx != PA_INVALID_INDEX)
                pa_context_get_sink_info_by_index(c, idx, on_sink_update, NULL);
        }
    }
}
Esempio n. 3
0
static void
gst_pulsemixer_ctrl_subscribe_cb (pa_context * context,
    pa_subscription_event_type_t t, uint32_t idx, void *userdata)
{
  GstPulseMixerCtrl *c = GST_PULSEMIXER_CTRL (userdata);
  pa_operation *o = NULL;

  /* Called from the background thread! */

  if (c->index != idx)
    return;

  if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
    return;

  if (c->type == GST_PULSEMIXER_SINK)
    o = pa_context_get_sink_info_by_index (c->context, c->index,
        gst_pulsemixer_ctrl_sink_info_cb, c);
  else
    o = pa_context_get_source_info_by_index (c->context, c->index,
        gst_pulsemixer_ctrl_source_info_cb, c);

  if (!o) {
    GST_WARNING_OBJECT (c->object, "Failed to get sink info: %s",
        pa_strerror (pa_context_errno (c->context)));
    return;
  }

  pa_operation_unref (o);

  c->outstandig_queries++;
}
Esempio n. 4
0
void Sink::update()
{
    pa_operation *o;
    o = pa_context_get_sink_info_by_index(d->context->cObject(),
			(Device::d)->index, Sink::sink_cb, this);
    pa_operation_unref(o);
}
void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) {

    switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
	
    case PA_SUBSCRIPTION_EVENT_SINK:
	if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
	    printf("Removing sink index %d\n", index);
	else {
	    pa_operation *o;
	    if (!(o = pa_context_get_sink_info_by_index(c, index, sink_cb, NULL))) {
		show_error(_("pa_context_get_sink_info_by_index() failed"));
		return;
	    }
	    pa_operation_unref(o);
	}
	break;

    case PA_SUBSCRIPTION_EVENT_SOURCE:
	if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
	    printf("Removing source index %d\n", index);
	else {
	    pa_operation *o;
	    if (!(o = pa_context_get_source_info_by_index(c, index, source_cb, NULL))) {
		show_error(_("pa_context_get_source_info_by_index() failed"));
		return;
	    }
	    pa_operation_unref(o);
	}
	break;
    }
}
static PyObject* PulseAudio_set_volume(output_PulseAudio *self, PyObject *args)
{
    pa_cvolume cvolume;
    pa_operation *op;
    struct get_volume_cb_data cb_data = {self->mainloop, &cvolume};
    double new_volume_d;
    pa_volume_t new_volume;

    if (!PyArg_ParseTuple(args, "d", &new_volume_d))
        return NULL;

    /*ensure output stream is still running*/
    /*FIXME*/

    /*convert volume to integer pa_volume_t value between
      PA_VOLUME_MUTED and PA_VOLUME_NORM*/
    new_volume = round(new_volume_d * PA_VOLUME_NORM);

    pa_threaded_mainloop_lock(self->mainloop);

    /*query stream info for current sink*/
    op = pa_context_get_sink_info_by_index(
        self->context,
        pa_stream_get_device_index(self->stream),
        (pa_sink_info_cb_t)get_volume_callback,
        &cb_data);

    /*wait for callback to complete*/
    while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) {
        pa_threaded_mainloop_wait(self->mainloop);
    }

    pa_operation_unref(op);

    /*scale values using the new volume setting*/
    pa_cvolume_scale(&cvolume, new_volume);

    /*set sink's volume values*/
    op = pa_context_set_sink_volume_by_index(
        self->context,
        pa_stream_get_device_index(self->stream),
        &cvolume,
        (pa_context_success_cb_t)set_volume_callback,
        self->mainloop);

    /*wait for callback to complete*/
    while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) {
        pa_threaded_mainloop_wait(self->mainloop);
    }

    pa_operation_unref(op);

    pa_threaded_mainloop_unlock(self->mainloop);

    Py_INCREF(Py_None);
    return Py_None;
}
Esempio n. 7
0
/**
 * Callback to analyze events emitted by the server.
 */
static void
xvd_subscribed_events_callback (pa_context                     *c,
                                enum pa_subscription_event_type t,
                                uint32_t                        index,
                                void                           *userdata)
{
  XvdInstance  *i = (XvdInstance *) userdata;
  pa_operation *op = NULL;

  if (!c || !userdata)
    {
      g_critical ("xvd_subscribed_events_callback: invalid argument");
      return;
    }

  switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
    {
      /* change on a sink, re-fetch it */
      case PA_SUBSCRIPTION_EVENT_SINK:
        if (i->sink_index != index)
          return;

        if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
          i->sink_index = PA_INVALID_INDEX;
        else
          {
             op = pa_context_get_sink_info_by_index (c,
                                                     index,
                                                     xvd_update_sink_callback,
                                                     userdata);

             if (!op)
               {
                 g_warning ("xvd_subscribed_events_callback: failed to get sink info");
                 return;
               }
             pa_operation_unref (op);
          }
      break;
      /* change on the server, re-fetch everything */
      case PA_SUBSCRIPTION_EVENT_SERVER:
        op = pa_context_get_server_info (c,
                                         xvd_server_info_callback,
                                         userdata);

        if (!op)
          {
            g_warning("xvd_subscribed_events_callback: failed to get server info");
            return;
          }
        pa_operation_unref(op);
      break;
    }
}
Esempio n. 8
0
static void sink_event(pa_context *ctx, unsigned type, uint32_t idx,
                       audio_output_t *aout)
{
    pa_operation *op = NULL;

    switch (type)
    {
        case PA_SUBSCRIPTION_EVENT_NEW:
            op = pa_context_get_sink_info_by_index(ctx, idx, sink_add_cb,
                                                   aout);
            break;
        case PA_SUBSCRIPTION_EVENT_CHANGE:
            op = pa_context_get_sink_info_by_index(ctx, idx, sink_mod_cb,
                                                   aout);
            break;
        case PA_SUBSCRIPTION_EVENT_REMOVE:
            sink_del(idx, aout);
            break;
    }
    if (op != NULL)
        pa_operation_unref(op);
}
void context_state_callback(pa_context * context, gpointer data)
{
    //printf("context state callback\n");
    int i;

    switch (pa_context_get_state(context)) {
    case PA_CONTEXT_READY:{
            for (i = 0; i < 255; i++) {
                pa_context_get_sink_info_by_index(context, i, pa_sink_cb, data);
            }
        }

    default:
        return;
    }

}
Esempio n. 10
0
void backend_volume_set(context_t *c, backend_entry_type type, uint32_t idx, int i, int v) {
    volume_callback_t *volume = (volume_callback_t*)malloc(sizeof(volume_callback_t));
    volume->index = i;
    volume->value = v;
    switch(type) {
        case SINK:
            pa_context_get_sink_info_by_index(c->context, idx, _cb_s_sink, volume);
            break;
        case SINK_INPUT:
            pa_context_get_sink_input_info(c->context, idx, _cb_s_sink_input, volume);
            break;
        case SOURCE:
            pa_context_get_source_info_by_index(c->context, idx, _cb_s_source, volume);
            break;
        case SOURCE_OUTPUT:
            pa_context_get_source_output_info(c->context, idx, _cb_s_source_output, volume);
            break;
        default:
            break;
    }
}
static PyObject* PulseAudio_get_volume(output_PulseAudio *self, PyObject *args)
{
    pa_cvolume cvolume;
    pa_operation *op;
    struct get_volume_cb_data cb_data = {self->mainloop, &cvolume};
    double max_volume;
    double norm_volume;

    pa_threaded_mainloop_lock(self->mainloop);

    /*ensure outuput stream is still running*/
    /*FIXME*/

    /*query stream info for current sink*/
    op = pa_context_get_sink_info_by_index(
        self->context,
        pa_stream_get_device_index(self->stream),
        (pa_sink_info_cb_t)get_volume_callback,
        &cb_data);

    /*wait for callback to complete*/
    while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) {
        pa_threaded_mainloop_wait(self->mainloop);
    }

    /*ensure operation has completed successfully before using cvolume*/
    /*FIXME*/

    pa_operation_unref(op);

    pa_threaded_mainloop_unlock(self->mainloop);

    /*convert cvolume to double*/
    max_volume = pa_cvolume_max(&cvolume);
    norm_volume = PA_VOLUME_NORM;

    /*return double converted to Python object*/
    return PyFloat_FromDouble(max_volume / norm_volume);
}
Esempio n. 12
0
void Context::subscribeCallback(pa_context *context, pa_subscription_event_type_t type, uint32_t index)
{
    Q_ASSERT(context == m_context);

    switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
    case PA_SUBSCRIPTION_EVENT_SINK:
        if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
            m_sinks.removeEntry(index);
        } else {
            if (!PAOperation(pa_context_get_sink_info_by_index(context, index, sink_cb, this))) {
                qCWarning(PLASMAPA) << "pa_context_get_sink_info_by_index() failed";
                return;
            }
        }
        break;

    case PA_SUBSCRIPTION_EVENT_SOURCE:
        if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
            m_sources.removeEntry(index);
        } else {
            if (!PAOperation(pa_context_get_source_info_by_index(context, index, source_cb, this))) {
                qCWarning(PLASMAPA) << "pa_context_get_source_info_by_index() failed";
                return;
            }
        }
        break;

    case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
        if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
            m_sinkInputs.removeEntry(index);
        } else {
            if (!PAOperation(pa_context_get_sink_input_info(context, index, sink_input_callback, this))) {
                qCWarning(PLASMAPA) << "pa_context_get_sink_input_info() failed";
                return;
            }
        }
        break;

    case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
        if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
            m_sourceOutputs.removeEntry(index);
        } else {
            if (!PAOperation(pa_context_get_source_output_info(context, index, source_output_cb, this))) {
                qCWarning(PLASMAPA) << "pa_context_get_sink_input_info() failed";
                return;
            }
        }
        break;

    case PA_SUBSCRIPTION_EVENT_CLIENT:
        if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
            m_clients.removeEntry(index);
        } else {
            if (!PAOperation(pa_context_get_client_info(context, index, client_cb, this))) {
                qCWarning(PLASMAPA) << "pa_context_get_client_info() failed";
                return;
            }
        }
        break;

    case PA_SUBSCRIPTION_EVENT_CARD:
        if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
            m_cards.removeEntry(index);
        } else {
            if (!PAOperation(pa_context_get_card_info_by_index(context, index, card_cb, this))) {
                qCWarning(PLASMAPA) << "pa_context_get_card_info_by_index() failed";
                return;
            }
        }
        break;

    case PA_SUBSCRIPTION_EVENT_SERVER:
        if (!PAOperation(pa_context_get_server_info(context, server_cb, this))) {
            qCWarning(PLASMAPA) << "pa_context_get_server_info() failed";
            return;
        }
        break;

    }
}
Esempio n. 13
0
AudioOutputSettings* AudioOutputPulseAudio::GetOutputSettings()
{
    AudioFormat fmt;
    m_aosettings = new AudioOutputSettings();
    QString fn_log_tag = "OpenDevice, ";

    /* Start the mainloop and connect a context so we can retrieve the
       parameters of the default sink */
    mainloop = pa_threaded_mainloop_new();
    if (!mainloop)
    {
        VBERROR(fn_log_tag + "Failed to get new threaded mainloop");
        delete m_aosettings;
        return NULL;
    }

    pa_threaded_mainloop_start(mainloop);
    pa_threaded_mainloop_lock(mainloop);

    if (!ContextConnect())
    {
        pa_threaded_mainloop_unlock(mainloop);
        pa_threaded_mainloop_stop(mainloop);
        delete m_aosettings;
        return NULL;
    }

    /* Get the samplerate and channel count of the default sink, supported rate
       and channels are added in SinkInfoCallback */
    /* We should in theory be able to feed pulse any samplerate but allowing it
       to resample results in weird behaviour (odd channel maps, static) post
       pause / reset */
    pa_operation *op = pa_context_get_sink_info_by_index(pcontext, 0,
                       SinkInfoCallback,
                       this);
    if (op)
    {
        pa_operation_unref(op);
        pa_threaded_mainloop_wait(mainloop);
    }
    else
        VBERROR("Failed to determine default sink samplerate");

    pa_threaded_mainloop_unlock(mainloop);

    // All formats except S24 (pulse wants S24LSB)
    while ((fmt = m_aosettings->GetNextFormat()))
    {
        if (fmt == FORMAT_S24
// define from PA 0.9.15 only
#ifndef PA_MAJOR
                || fmt == FORMAT_S24LSB
#endif
           )
            continue;
        m_aosettings->AddSupportedFormat(fmt);
    }

    pa_context_disconnect(pcontext);
    pa_context_unref(pcontext);
    pcontext = NULL;
    pa_threaded_mainloop_stop(mainloop);
    mainloop = NULL;

    return m_aosettings;
}
Esempio n. 14
0
void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index)
{
	switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
		case PA_SUBSCRIPTION_EVENT_SINK:
			if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
                //w->removeSink(index);
				printf("INF: suppose to removeSink(index)\n");
			else {
				pa_operation *o;

				if (!(o = pa_context_get_sink_info_by_index(c, index, sink_cb, NULL))) {
                    //show_error("pa_context_get_sink_info_by_index() failed");
					printf("func.c:<error> pa_context_get_sink_info_by_index() failed\n");
					return;
				}
				pa_operation_unref(o);
			}

			break;

		case PA_SUBSCRIPTION_EVENT_SOURCE:
			if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
                //w->removeSource(index);
				printf("func.c:INF: suppose to removeSource(index)\n");

			else {
				pa_operation *o;
				if (!(o = pa_context_get_source_info_by_index(c, index, source_cb, NULL))) {
                    //show_error("pa_context_get_source_info_by_index() failed");
					printf("func.c:<error> pa_context_get_source_info_by_index() failed\n");
					return;
				}
				pa_operation_unref(o);
			}

			break;

		case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
			if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
                //w->removeSinkInput(index);
				printf("func.c:INF: suppose to removeSinkInput(index)\n");
			else {
				pa_operation *o;
				if (!(o = pa_context_get_sink_input_info(c, index, sink_input_cb, NULL))) {
                    //show_error("pa_context_get_sink_input_info() failed");
					printf("func.c:<error> pa_context_get_sink_input_info() failed");
					return;
				}
				pa_operation_unref(o);
			}
			break;

		case PA_SUBSCRIPTION_EVENT_CLIENT:
			if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
                //w->removeClient(index);
				printf("func.c:INF: suppose to removeClient(index)\n");

			else {
				pa_operation *o;
				if (!(o = pa_context_get_client_info(c, index, client_cb, NULL))) {
                    //show_error("pa_context_get_client_info() failed");
					printf("func.c:<error> pa_context_get_client_info() failed\n");
					return;
				}
				pa_operation_unref(o);
			}
			break;

		case PA_SUBSCRIPTION_EVENT_SERVER:
			printf("func.c: pa_subscription_event_server triggered\n");
			{
				pa_operation *o;
				if (!(o = pa_context_get_server_info(c, server_info_cb, NULL))) {
					//show_error("pa_context_get_server_info() failed");
					printf("func.c:<error> pa_context_get_server_info() failed\n");
					return;
				}
				pa_operation_unref(o);
			}

			break;
	}
}
Esempio n. 15
0
int main(int argc, char *const *argv) {
    static const char *context_name = "i3blocks-pulse-volume";
    static const char *usage_str = "Usage: %s [-h] [-d] [-s INDEX] [-m FUNC]\n"
            "Options:\n"
            "  -s INDEX: pulseaudio sink index on which to wait for "
            "changes (default: 0)\n"
            "  -m FUNC : function used to compute the displayed volume value\n"
            "            in there are multiple channels (eg. left/right):\n"
            "             * avg: use average volume of all channels (default)\n"
            "             * min: use minimum volume of all channels\n"
            "             * max: use maximum volume of all channels\n"
            "  -d      : use decibel notation instead of 0-100 percentage; "
            "the sink may\n"
            "            not support this feature\n";

    options_t options;
    options.calculator = pa_cvolume_avg;
    options.use_decibel = 0;
    options.observed_index = 0;

    sink_info_data_t sink_info_data;
    sink_info_data.sink_ready = 0;
    sink_info_data.sink_changed = 0;

    pa_mainloop *pa_ml = NULL;
    pa_mainloop_api *pa_ml_api = NULL;
    pa_operation *pa_op = NULL;
    pa_context *pa_ctx = NULL;

    enum state_t state = FIRST_SINK_INFO;
    int pa_ready = CONN_WAIT;

    int opt;
    while ((opt = getopt(argc, argv, "m:s:dh")) != -1) {
        if (opt == 'm') {
            if (strcmp(optarg, "min") == 0) {
                options.calculator = pa_cvolume_min;
            } else if (strcmp(optarg, "max") == 0) {
                options.calculator = pa_cvolume_max;
            } else if (strcmp(optarg, "avg") == 0) {
                options.calculator = pa_cvolume_avg;
            } else {
                fprintf(stderr, usage_str, argv[0]);
                return 1;
            }
        } else if (opt == 's') {
            // Parse observed sink index
            errno = 0;
            char *endptr;
            uint32_t *oind = &options.observed_index;
            *oind = strtoul(optarg, &endptr, 10);
            if ((errno == ERANGE) || (errno != 0 && *oind == 0) ||
                endptr == optarg) {
                fprintf(stderr, "%s: invalid sink index: %s\n", argv[0],
                        optarg);
                fprintf(stderr, usage_str, argv[0]);
                return 1;
            }
        } else if (opt == 'd') {
            options.use_decibel = 1;
        } else if (opt == 'h') {
            fprintf(stderr, usage_str, argv[0]);
            return 0;
        } else {
            fprintf(stderr, usage_str, argv[0]);
            return 1;
        }
    }

    // Needed to filter out sink in callbacks
    sink_info_data.observed_index = options.observed_index;

    if ((pa_ml = pa_mainloop_new()) == NULL) {
        fprintf(stderr, "error: failed to allocate pulseaudio mainloop.\n");
        return 2;
    }
    if ((pa_ml_api = pa_mainloop_get_api(pa_ml)) == NULL) {
        fprintf(stderr, "error: failed to allocate pulseaudio mainloop API.\n");
        return 3;
    }
    if ((pa_ctx = pa_context_new(pa_ml_api, context_name)) == NULL) {
        fprintf(stderr, "error: failed to allocate pulseaudio context.\n");
        return 4;
    }

    if (pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFAIL, NULL) < 0) {
        fprintf(stderr, "error: failed to connect to pulseaudio context: %s\n",
                pa_strerror(pa_context_errno(pa_ctx)));
        return 5;
    }

    pa_context_set_state_callback(pa_ctx, state_cb, &pa_ready);
    pa_context_set_subscribe_callback(pa_ctx, subscribe_cb, &sink_info_data);

    while (1) {
        if (pa_ready == CONN_WAIT) {
            pa_mainloop_iterate(pa_ml, 1, NULL);
            continue;
        }

        if (pa_ready == CONN_FAILED) {
            pa_context_disconnect(pa_ctx);
            pa_context_unref(pa_ctx);
            pa_mainloop_free(pa_ml);
            fprintf(stderr,
                    "error: failed to connect to pulseaudio context.\n");
            return 6;
        }

        // Main loop automaton
        switch (state) {
            case FIRST_SINK_INFO:
                // First run
                pa_op = pa_context_get_sink_info_by_index(pa_ctx,
                            options.observed_index,
                            sink_info_cb, &sink_info_data);
                state = SUBSCRIBE;
                break;
            case SUBSCRIBE:
                if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
                    pa_operation_unref(pa_op);
                    if (!sink_info_data.sink_ready) {
                        fprintf(stderr, "error: sink %u does not exist.\n",
                                options.observed_index);
                        pa_context_disconnect(pa_ctx);
                        pa_context_unref(pa_ctx);
                        pa_mainloop_free(pa_ml);
                        return 7;
                    }
                    if (options.use_decibel &&
                        !sink_info_data.sink.can_decibel) {
                        fprintf(stderr,
                                "error: sink %u does not support decibel; "
                                "try without `-d`.\n",
                                options.observed_index);
                        pa_context_disconnect(pa_ctx);
                        pa_context_unref(pa_ctx);
                        pa_mainloop_free(pa_ml);
                        return 8;
                    }
                    // Show volume once at start
                    show_volume(&sink_info_data.sink, &options);
                    // Subsequent runs: wait for changes
                    pa_op = pa_context_subscribe(pa_ctx,
                                                 PA_SUBSCRIPTION_MASK_SINK,
                                                 NULL, &sink_info_data);
                    state = SUBSCRIBED;
                }
                break;
            case SUBSCRIBED:
                if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
                    pa_operation_unref(pa_op);
                    state = WAIT_FOR_CHANGE;
                }
                break;
            case WAIT_FOR_CHANGE:
                if (sink_info_data.sink_changed) {
                    sink_info_data.sink_changed = 0;
                    pa_op = pa_context_get_sink_info_by_index(pa_ctx,
                                options.observed_index, sink_info_cb,
                                &sink_info_data);
                    state = SHOW_CHANGE;
                }
                break;
            case SHOW_CHANGE:
                if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
                    pa_operation_unref(pa_op);
                    show_volume(&sink_info_data.sink, &options);
                    state = WAIT_FOR_CHANGE;
                }
                break;
            default:
                return 7;
        }
        pa_mainloop_iterate(pa_ml, 1, NULL);
    }
}
    static void onContextSubscription(pa_context* context, pa_subscription_event_type_t event, uint32_t idx, void* userData)
    {        
        long facility = (event & PA_SUBSCRIPTION_EVENT_FACILITY_MASK);
        long eventType = (event & PA_SUBSCRIPTION_EVENT_TYPE_MASK);

//        static QHash< long, QString > facilities;

//        if (facilities.isEmpty())
//        {
//            facilities.insert(PA_SUBSCRIPTION_EVENT_SINK, "PA_SUBSCRIPTION_EVENT_SINK");
//            facilities.insert(PA_SUBSCRIPTION_EVENT_SOURCE, "PA_SUBSCRIPTION_EVENT_SOURCE");
//            facilities.insert(PA_SUBSCRIPTION_EVENT_SINK_INPUT, "PA_SUBSCRIPTION_EVENT_SINK_INPUT");
//            facilities.insert(PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT, "PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT");
//            facilities.insert(PA_SUBSCRIPTION_EVENT_MODULE, "PA_SUBSCRIPTION_EVENT_MODULE");
//            facilities.insert(PA_SUBSCRIPTION_EVENT_CLIENT, "PA_SUBSCRIPTION_EVENT_CLIENT");
//            facilities.insert(PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE, "PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE");
//            facilities.insert(PA_SUBSCRIPTION_EVENT_CARD, "PA_SUBSCRIPTION_EVENT_CARD");
//        }

//        static QHash< long, QString > eventTypes;

//        if (eventTypes.isEmpty())
//        {
//            eventTypes.insert(PA_SUBSCRIPTION_EVENT_NEW, "PA_SUBSCRIPTION_EVENT_NEW");
//            eventTypes.insert(PA_SUBSCRIPTION_EVENT_CHANGE, "PA_SUBSCRIPTION_EVENT_CHANGE");
//            eventTypes.insert(PA_SUBSCRIPTION_EVENT_REMOVE, "PA_SUBSCRIPTION_EVENT_REMOVE");
//        }

//        qDebug() << "Facility:" << facilities.value(facility, "Other") <<
//                    "Event Type:" << eventTypes.value(eventType, "Other") <<
//                    "idx:" << idx;

        PulseAudioWrapperPrivate* d = reinterpret_cast< PulseAudioWrapperPrivate* >(userData);

        if (facility == PA_SUBSCRIPTION_EVENT_CARD)
        {
            if (eventType == PA_SUBSCRIPTION_EVENT_NEW ||
                    eventType == PA_SUBSCRIPTION_EVENT_CHANGE)
            {
                pa_operation_unref(pa_context_get_card_info_by_index(
                                       PulseAudioWrapperPrivate::paContext,
                                       idx,
                                       &PulseAudioWrapperPrivate::onCardInfoByIndex,
                                       userData));
            }
            else if (eventType == PA_SUBSCRIPTION_EVENT_REMOVE)
            {
                qDebug() << "Removing card at idx: " << idx;

                if (d->cardsByIndex.contains(idx))
                {
                    PulseAudioCard* card = d->cardsByIndex.value(idx);

                    d->cards.remove(card);
                    d->cardsByIndex.remove(idx);
                    d->cardsByName.remove(card->name());

                    delete card;
                }
                else
                    qDebug() << "No card at idx " << idx;
            }
        }
        else if (facility == PA_SUBSCRIPTION_EVENT_SINK)
        {
            if (eventType == PA_SUBSCRIPTION_EVENT_NEW ||
                    eventType == PA_SUBSCRIPTION_EVENT_CHANGE)
            {
                pa_operation_unref(pa_context_get_sink_info_by_index(
                                       PulseAudioWrapperPrivate::paContext,
                                       idx,
                                       &PulseAudioWrapperPrivate::onSinkInfoByIndex,
                                       userData));
            }
            else if (eventType == PA_SUBSCRIPTION_EVENT_REMOVE)
            {
                qDebug() << "Removing sink at idx: " << idx;

                if (d->sinksByIndex.contains(idx))
                {
                    PulseAudioSink* sink = d->sinksByIndex.value(idx);

                    d->sinks.remove(sink);
                    d->sinksByIndex.remove(idx);
                    d->sinksByName.remove(sink->name());

                    delete sink;
                }
                else
                    qDebug() << "No sink at idx " << idx;
            }
        }
        else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE)
        {
            if (eventType == PA_SUBSCRIPTION_EVENT_NEW)
            {
                pa_operation_unref(pa_context_get_source_info_by_index(
                                       PulseAudioWrapperPrivate::paContext,
                                       idx,
                                       &PulseAudioWrapperPrivate::onSourceInfoByIndex,
                                       userData));
            }
            else if (eventType == PA_SUBSCRIPTION_EVENT_REMOVE)
            {
                qDebug() << "Removing source at idx: " << idx;

                if (d->sourcesByIndex.contains(idx))
                {
                    PulseAudioSource* source = d->sourcesByIndex.value(idx);

                    QString sourceName = source->name();

                    d->sources.remove(source);
                    d->sourcesByIndex.remove(idx);
                    d->sourcesByName.remove(source->name());

                    delete source;

                    emit d->q->sourceRemoved(idx, sourceName);
                }
                else
                    qDebug() << "No source at idx " << idx;
            }
        }
    }