28
28
#include "widget-settings.h"
30
30
static void pama_sink_input_widget_class_init(PamaSinkInputWidgetClass *klass);
31
static void pama_sink_input_widget_init(PamaSinkInputWidget *self);
31
static void pama_sink_input_widget_init(PamaSinkInputWidget *widget);
32
32
static void pama_sink_input_widget_set_property(GObject *gobject, guint property_id, const GValue *value, GParamSpec *pspec);
33
33
static GObject* pama_sink_input_widget_constructor(GType gtype, guint n_properties, GObjectConstructParam *properties);
34
34
static void pama_sink_input_widget_dispose(GObject *gobject);
35
35
static void pama_sink_input_widget_weak_ref_notify(gpointer data, GObject *where_the_object_was);
37
static void pama_sink_input_widget_sink_input_notify(GObject *gobject, GParamSpec *pspec, PamaSinkInputWidget *self);
39
static void pama_sink_input_widget_update_values(PamaSinkInputWidget *self);
40
static void pama_sink_input_widget_mute_toggled(GtkToggleButton *togglebutton, PamaSinkInputWidget *self);
41
static void pama_sink_input_widget_volume_changed(GtkRange *range, PamaSinkInputWidget *self);
43
static void pama_sink_input_widget_sink_button_clicked(GtkButton *button, PamaSinkInputWidget *self);
44
static void pama_sink_input_widget_sink_menu_hidden(GtkWidget *menu_window, PamaSinkInputWidget *self);
45
static void pama_sink_input_widget_sink_menu_item_toggled(GtkCheckMenuItem *item, PamaSinkInputWidget *self);
37
static void pama_sink_input_widget_sink_input_notify(GObject *gobject, GParamSpec *pspec, PamaSinkInputWidget *widget);
39
static void pama_sink_input_widget_update_values(PamaSinkInputWidget *widget);
40
static void pama_sink_input_widget_mute_toggled(GtkToggleButton *togglebutton, PamaSinkInputWidget *widget);
41
static void pama_sink_input_widget_volume_changed(GtkRange *range, PamaSinkInputWidget *widget);
43
static void pama_sink_input_widget_sink_button_clicked(GtkButton *button, PamaSinkInputWidget *widget);
44
static void pama_sink_input_widget_sink_menu_hidden(GtkWidget *menu_window, PamaSinkInputWidget *widget);
45
static void pama_sink_input_widget_sink_menu_item_toggled(GtkCheckMenuItem *item, PamaSinkInputWidget *widget);
47
struct _PamaSinkInputWidgetPrivate
49
GtkWidget *icon, *name, *volume, *value, *mute, *sink_button, *sink_menu, *sink_button_image;
50
GtkSizeGroup *icon_sizegroup;
51
PamaPulseContext *context;
52
PamaPulseSinkInput *sink_input;
55
gulong context_notify_handler_id, sink_input_notify_handler_id;
47
58
G_DEFINE_TYPE(PamaSinkInputWidget, pama_sink_input_widget, GTK_TYPE_HBOX);
59
#define PAMA_SINK_INPUT_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PAMA_TYPE_SINK_INPUT_WIDGET, PamaSinkInputWidgetPrivate))
94
113
g_cclosure_marshal_VOID__VOID,
117
g_type_class_add_private(klass, sizeof(PamaSinkInputWidgetPrivate));
99
static void pama_sink_input_widget_init(PamaSinkInputWidget *self)
120
static void pama_sink_input_widget_init(PamaSinkInputWidget *widget)
103
124
static void pama_sink_input_widget_set_property(GObject *gobject, guint property_id, const GValue *value, GParamSpec *pspec)
105
PamaSinkInputWidget *self = PAMA_SINK_INPUT_WIDGET(gobject);
126
PamaSinkInputWidget *widget = PAMA_SINK_INPUT_WIDGET(gobject);
127
PamaSinkInputWidgetPrivate *priv = PAMA_SINK_INPUT_WIDGET_GET_PRIVATE(widget);
107
129
switch(property_id)
109
131
case PROP_SINK_INPUT:
110
self->sink_input = g_value_get_object(value);
111
g_object_weak_ref(G_OBJECT(self->sink_input), pama_sink_input_widget_weak_ref_notify, self);
132
priv->sink_input = g_value_get_object(value);
133
g_object_weak_ref(G_OBJECT(priv->sink_input), pama_sink_input_widget_weak_ref_notify, widget);
114
136
case PROP_CONTEXT:
115
self->context = g_value_get_object(value);
116
g_object_weak_ref(G_OBJECT(self->context), pama_sink_input_widget_weak_ref_notify, self);
137
priv->context = g_value_get_object(value);
138
g_object_weak_ref(G_OBJECT(priv->context), pama_sink_input_widget_weak_ref_notify, widget);
141
case PROP_ICON_SIZEGROUP:
142
priv->icon_sizegroup = g_value_dup_object(value);
127
153
GtkWidget *inner_box, *volume, *value, *mute, *sink_button, *sink_button_image;
129
155
GObject *gobject = G_OBJECT_CLASS(pama_sink_input_widget_parent_class)->constructor(gtype, n_properties, properties);
130
PamaSinkInputWidget *self = PAMA_SINK_INPUT_WIDGET(gobject);
156
PamaSinkInputWidget *widget = PAMA_SINK_INPUT_WIDGET(gobject);
157
PamaSinkInputWidgetPrivate *priv = PAMA_SINK_INPUT_WIDGET_GET_PRIVATE(widget);
132
if (NULL == self->sink_input)
159
if (NULL == priv->sink_input)
133
160
g_error("An attempt was made to create a sink_input widget with no sink_input.");
134
if (NULL == self->context)
161
if (NULL == priv->context)
135
162
g_error("An attempt was made to create a sink_input widget with no context.");
163
if (NULL == priv->icon_sizegroup)
164
g_error("An attempt was made to create a sink_input widget with no icon sizegroup");
137
166
alignment = gtk_alignment_new(0, 0.5, 0, 0);
138
167
gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 0, 0, 0, 6);
139
gtk_box_pack_start(GTK_BOX(self), alignment, FALSE, FALSE, 0);
168
gtk_box_pack_start(GTK_BOX(widget), alignment, FALSE, FALSE, 0);
141
170
icon = gtk_image_new();
142
171
gtk_container_add(GTK_CONTAINER(alignment), icon);
172
gtk_size_group_add_widget(priv->icon_sizegroup, icon);
145
175
name = g_object_new(GTK_TYPE_LABEL,
146
176
"ellipsize", PANGO_ELLIPSIZE_MIDDLE,
148
178
"width-chars", WIDGET_NAME_WIDTH_IN_CHARS,
151
gtk_box_pack_start(GTK_BOX(self), name, FALSE, FALSE, 0);
181
gtk_box_pack_start(GTK_BOX(widget), name, FALSE, FALSE, 0);
154
184
alignment = gtk_alignment_new(0, 0.5, 0, 0);
155
gtk_box_pack_start(GTK_BOX(self), alignment, FALSE, FALSE, 0);
157
inner_box = gtk_hbox_new(FALSE, 6);
158
gtk_container_add(GTK_CONTAINER(alignment), inner_box);
185
gtk_box_pack_start(GTK_BOX(widget), alignment, FALSE, FALSE, 0);
160
187
volume = gtk_hscale_new_with_range(0, WIDGET_VOLUME_SLIDER_DB_RANGE, 2.5); /* for scroll steps of 5dB */
161
188
gtk_scale_set_draw_value (GTK_SCALE(volume), FALSE);
162
189
gtk_widget_set_size_request(volume, WIDGET_VOLUME_SLIDER_WIDTH, -1);
163
gtk_box_pack_start(GTK_BOX(inner_box), volume, FALSE, FALSE, 0);
164
self->volume = volume;
190
gtk_container_add(GTK_CONTAINER(alignment), volume);
191
priv->volume = volume;
193
alignment = gtk_alignment_new(0, 0.5, 0, 0);
194
gtk_box_pack_start(GTK_BOX(widget), alignment, FALSE, FALSE, 0);
196
inner_box = gtk_hbox_new(FALSE, 6);
197
gtk_container_add(GTK_CONTAINER(alignment), inner_box);
166
199
value = g_object_new(GTK_TYPE_LABEL,
167
200
"width-chars", WIDGET_VALUE_WIDTH_IN_CHARS,
170
203
gtk_box_pack_start(GTK_BOX(inner_box), value, FALSE, FALSE, 0);
173
206
mute = gtk_check_button_new();
174
207
gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(mute), FALSE);
175
208
gtk_widget_set_tooltip_text(GTK_WIDGET(mute), _("Mute this application's audio output"));
176
209
gtk_container_add(GTK_CONTAINER(mute), gtk_image_new_from_icon_name("audio-volume-muted", GTK_ICON_SIZE_MENU));
177
210
gtk_box_pack_start(GTK_BOX(inner_box), mute, FALSE, FALSE, 0);
180
213
sink_button = gtk_button_new();
181
214
gtk_widget_set_tooltip_text(GTK_WIDGET(sink_button), _("Select which output device to use for this application"));
182
215
gtk_box_pack_start(GTK_BOX(inner_box), sink_button, FALSE, FALSE, 0);
183
self->sink_button = sink_button;
216
priv->sink_button = sink_button;
185
218
sink_button_image = gtk_image_new_from_icon_name("audio-card", GTK_ICON_SIZE_MENU);
186
219
gtk_container_add(GTK_CONTAINER(sink_button), sink_button_image);
187
self->sink_button_image = sink_button_image;
189
pama_sink_input_widget_update_values(self);
191
g_signal_connect(volume, "value-changed", G_CALLBACK(pama_sink_input_widget_volume_changed), self);
192
g_signal_connect(mute, "toggled", G_CALLBACK(pama_sink_input_widget_mute_toggled), self);
193
g_signal_connect(sink_button, "clicked", G_CALLBACK(pama_sink_input_widget_sink_button_clicked), self);
195
self->sink_input_notify_handler_id = g_signal_connect(self->sink_input, "notify::volume", G_CALLBACK(pama_sink_input_widget_sink_input_notify), self);
220
priv->sink_button_image = sink_button_image;
222
pama_sink_input_widget_update_values(widget);
224
g_signal_connect(volume, "value-changed", G_CALLBACK(pama_sink_input_widget_volume_changed), widget);
225
g_signal_connect(mute, "toggled", G_CALLBACK(pama_sink_input_widget_mute_toggled), widget);
226
g_signal_connect(sink_button, "clicked", G_CALLBACK(pama_sink_input_widget_sink_button_clicked), widget);
228
priv->sink_input_notify_handler_id = g_signal_connect(priv->sink_input, "notify::volume", G_CALLBACK(pama_sink_input_widget_sink_input_notify), widget);
230
/* We no longer need to keep a reference to the icon sizegroup */
231
g_object_unref(priv->icon_sizegroup);
232
priv->icon_sizegroup = NULL;
199
236
static void pama_sink_input_widget_dispose(GObject *gobject)
201
PamaSinkInputWidget *self = PAMA_SINK_INPUT_WIDGET(gobject);
238
PamaSinkInputWidget *widget = PAMA_SINK_INPUT_WIDGET(gobject);
239
PamaSinkInputWidgetPrivate *priv = PAMA_SINK_INPUT_WIDGET_GET_PRIVATE(widget);
203
if (self->sink_input)
241
if (priv->sink_input)
205
if (self->sink_input_notify_handler_id)
243
if (priv->sink_input_notify_handler_id)
207
g_signal_handler_disconnect(self->sink_input, self->sink_input_notify_handler_id);
208
self->sink_input_notify_handler_id = 0;
245
g_signal_handler_disconnect(priv->sink_input, priv->sink_input_notify_handler_id);
246
priv->sink_input_notify_handler_id = 0;
211
g_object_weak_unref(G_OBJECT(self->sink_input), pama_sink_input_widget_weak_ref_notify, self);
212
self->sink_input = NULL;
217
g_object_weak_unref(G_OBJECT(self->context), pama_sink_input_widget_weak_ref_notify, self);
218
self->context = NULL;
223
g_object_unref(self->sink_menu);
224
self->sink_menu = NULL;
249
g_object_weak_unref(G_OBJECT(priv->sink_input), pama_sink_input_widget_weak_ref_notify, widget);
250
priv->sink_input = NULL;
255
g_object_weak_unref(G_OBJECT(priv->context), pama_sink_input_widget_weak_ref_notify, widget);
256
priv->context = NULL;
261
g_object_unref(priv->sink_menu);
262
priv->sink_menu = NULL;
265
if (priv->icon_sizegroup)
267
g_object_unref(priv->icon_sizegroup);
268
priv->icon_sizegroup = NULL;
227
271
G_OBJECT_CLASS(pama_sink_input_widget_parent_class)->dispose(gobject);
229
273
static void pama_sink_input_widget_weak_ref_notify(gpointer data, GObject *where_the_object_was)
231
PamaSinkInputWidget *self = data;
233
if ((GObject *)self->sink_input == where_the_object_was)
234
self->sink_input = NULL;
236
if ((GObject *)self->context == where_the_object_was)
237
self->context = NULL;
275
PamaSinkInputWidget *widget = data;
276
PamaSinkInputWidgetPrivate *priv = PAMA_SINK_INPUT_WIDGET_GET_PRIVATE(widget);
278
if ((GObject *)priv->sink_input == where_the_object_was)
279
priv->sink_input = NULL;
281
if ((GObject *)priv->context == where_the_object_was)
282
priv->context = NULL;
239
284
/* widget is no longer usable without these items. */
240
gtk_object_destroy(GTK_OBJECT(self));
244
static void pama_sink_input_widget_sink_input_notify(GObject *gobject, GParamSpec *pspec, PamaSinkInputWidget *self)
246
pama_sink_input_widget_update_values(self);
247
g_signal_emit(self, widget_signals[REORDER_REQUEST_SIGNAL], 0);
250
static void pama_sink_input_widget_update_values(PamaSinkInputWidget *self)
285
gtk_object_destroy(GTK_OBJECT(widget));
289
static void pama_sink_input_widget_sink_input_notify(GObject *gobject, GParamSpec *pspec, PamaSinkInputWidget *widget)
291
pama_sink_input_widget_update_values(widget);
292
g_signal_emit(widget, widget_signals[REORDER_REQUEST_SIGNAL], 0);
295
static void pama_sink_input_widget_update_values(PamaSinkInputWidget *widget)
297
PamaSinkInputWidgetPrivate *priv = PAMA_SINK_INPUT_WIDGET_GET_PRIVATE(widget);
253
299
gchar *stream_name, *client_name;
254
300
gchar *hostname, *application_id;
290
336
temp = g_markup_printf_escaped("<b>%s</b>\n%s", client_name, stream_name);
292
338
temp = g_markup_printf_escaped("<b>%s</b> (on %s)\n%s", client_name, hostname, stream_name);
293
gtk_label_set_markup(GTK_LABEL(self->name), temp);
294
gtk_widget_set_tooltip_markup(GTK_WIDGET(self->name), temp);
339
gtk_label_set_markup(GTK_LABEL(priv->name), temp);
340
gtk_widget_set_tooltip_markup(GTK_WIDGET(priv->name), temp);
296
342
g_free(stream_name);
297
343
g_free(client_name);
299
345
volume_dB = pa_sw_volume_to_dB(volume);
300
346
if (isinf(volume_dB))
301
gtk_label_set_text(GTK_LABEL(self->value), "-∞dB");
347
gtk_label_set_text(GTK_LABEL(priv->value), "-∞dB");
304
350
temp = g_strdup_printf("%+.1fdB", volume_dB);
305
gtk_label_set_text(GTK_LABEL(self->value), temp);
351
gtk_label_set_text(GTK_LABEL(priv->value), temp);
309
gtk_range_set_value(GTK_RANGE(self->volume), volume_dB + WIDGET_VOLUME_SLIDER_DB_RANGE);
310
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->mute), mute);
355
gtk_range_set_value(GTK_RANGE(priv->volume), volume_dB + WIDGET_VOLUME_SLIDER_DB_RANGE);
356
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->mute), mute);
312
gtk_widget_set_sensitive(self->volume, !mute);
313
gtk_widget_set_sensitive(self->value, !mute);
358
gtk_widget_set_sensitive(priv->volume, !mute);
359
gtk_widget_set_sensitive(priv->value, !mute);
315
361
// Ideally, this would be done by checking the sink input's flags for
316
362
// PA_STREAM_DONT_MOVE, but we don't have that information
317
363
is_pulseaudio = 0 == strcmp(application_id, "org.PulseAudio.PulseAudio");
318
gtk_widget_set_sensitive(self->sink_button, !is_pulseaudio);
364
gtk_widget_set_sensitive(priv->sink_button, !is_pulseaudio);
320
g_object_set(self->sink_button_image,
366
g_object_set(priv->sink_button_image,
321
367
"gicon", pama_pulse_sink_build_gicon(sink),
324
370
g_object_unref(client);
325
371
g_object_unref(sink);
327
self->updating = FALSE;
330
static void pama_sink_input_widget_mute_toggled (GtkToggleButton *togglebutton, PamaSinkInputWidget *self)
335
pama_pulse_sink_input_set_mute(self->sink_input, gtk_toggle_button_get_active(togglebutton));
337
static void pama_sink_input_widget_volume_changed (GtkRange *range, PamaSinkInputWidget *self)
342
pama_pulse_sink_input_set_volume(self->sink_input, pa_sw_volume_from_dB(gtk_range_get_value(range) - WIDGET_VOLUME_SLIDER_DB_RANGE));
373
priv->updating = FALSE;
376
static void pama_sink_input_widget_mute_toggled (GtkToggleButton *togglebutton, PamaSinkInputWidget *widget)
378
PamaSinkInputWidgetPrivate *priv = PAMA_SINK_INPUT_WIDGET_GET_PRIVATE(widget);
382
pama_pulse_sink_input_set_mute(priv->sink_input, gtk_toggle_button_get_active(togglebutton));
384
static void pama_sink_input_widget_volume_changed (GtkRange *range, PamaSinkInputWidget *widget)
386
PamaSinkInputWidgetPrivate *priv = PAMA_SINK_INPUT_WIDGET_GET_PRIVATE(widget);
390
pama_pulse_sink_input_set_volume(priv->sink_input, pa_sw_volume_from_dB(gtk_range_get_value(range) - WIDGET_VOLUME_SLIDER_DB_RANGE));
345
393
static gint _sink_menu_sort_function(gconstpointer a, gconstpointer b)
349
if (result = pama_pulse_sink_compare_by_hostname(a, b))
395
gint result = pama_pulse_sink_compare_by_hostname(a, b);
352
399
return pama_pulse_sink_compare_by_description(a, b);
354
static void pama_sink_input_widget_sink_button_clicked(GtkButton *button, PamaSinkInputWidget *self)
401
static void pama_sink_input_widget_sink_button_clicked(GtkButton *button, PamaSinkInputWidget *widget)
403
PamaSinkInputWidgetPrivate *priv = PAMA_SINK_INPUT_WIDGET_GET_PRIVATE(widget);
356
404
GtkWidget *item, *last = NULL;
357
405
GtkWidget *label;
358
406
GSList *sinks, *iter;
408
456
g_free(hostname);
411
g_signal_connect(item, "toggled", G_CALLBACK(pama_sink_input_widget_sink_menu_item_toggled), self);
412
gtk_menu_shell_append(GTK_MENU_SHELL(self->sink_menu), item);
459
g_signal_connect(item, "toggled", G_CALLBACK(pama_sink_input_widget_sink_menu_item_toggled), widget);
460
gtk_menu_shell_append(GTK_MENU_SHELL(priv->sink_menu), item);
413
461
gtk_widget_show_all(item);
416
464
g_slist_free(sinks);
417
465
g_object_unref(current_sink);
419
gtk_menu_popup(GTK_MENU(self->sink_menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
467
gtk_menu_popup(GTK_MENU(priv->sink_menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
422
static void pama_sink_input_widget_sink_menu_hidden(GtkWidget *menu_window, PamaSinkInputWidget *self)
470
static void pama_sink_input_widget_sink_menu_hidden(GtkWidget *menu_window, PamaSinkInputWidget *widget)
424
g_object_unref(self->sink_menu);
425
self->sink_menu = NULL;
472
PamaSinkInputWidgetPrivate *priv = PAMA_SINK_INPUT_WIDGET_GET_PRIVATE(widget);
473
g_object_unref(priv->sink_menu);
474
priv->sink_menu = NULL;
427
static void pama_sink_input_widget_sink_menu_item_toggled(GtkCheckMenuItem *item, PamaSinkInputWidget *self)
476
static void pama_sink_input_widget_sink_menu_item_toggled(GtkCheckMenuItem *item, PamaSinkInputWidget *widget)
478
PamaSinkInputWidgetPrivate *priv = PAMA_SINK_INPUT_WIDGET_GET_PRIVATE(widget);
429
479
PamaPulseSink *sink;
431
481
if (!gtk_check_menu_item_get_active(item))