1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
3
* Copyright (C) 2008 William Jon McCann
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation; either version 2 of the License, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29
#include <glib/gi18n.h>
30
#include <gdk/gdkkeysyms.h>
32
#include <pulse/pulseaudio.h>
34
#include "gvc-mixer-stream.h"
35
#include "gvc-channel-bar.h"
36
#include "gvc-stream-status-icon.h"
38
#define GVC_STREAM_STATUS_ICON_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_STREAM_STATUS_ICON, GvcStreamStatusIconPrivate))
40
struct GvcStreamStatusIconPrivate
43
GvcMixerStream *mixer_stream;
59
static void gvc_stream_status_icon_class_init (GvcStreamStatusIconClass *klass);
60
static void gvc_stream_status_icon_init (GvcStreamStatusIcon *stream_status_icon);
61
static void gvc_stream_status_icon_finalize (GObject *object);
63
G_DEFINE_TYPE (GvcStreamStatusIcon, gvc_stream_status_icon, GTK_TYPE_STATUS_ICON)
66
on_adjustment_value_changed (GtkAdjustment *adjustment,
67
GvcStreamStatusIcon *icon)
74
volume = gtk_adjustment_get_value (adjustment);
76
/* Only push the volume if it's actually changed */
77
if (gvc_mixer_stream_set_volume(icon->priv->mixer_stream,
78
(pa_volume_t) round (volume)) != FALSE) {
79
gvc_mixer_stream_push_volume(icon->priv->mixer_stream);
84
update_dock (GvcStreamStatusIcon *icon)
89
g_return_if_fail (icon);
91
adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (icon->priv->bar)));
93
icon->priv->thaw = TRUE;
94
gtk_adjustment_set_value (adj,
95
gvc_mixer_stream_get_volume (icon->priv->mixer_stream));
96
is_muted = gvc_mixer_stream_get_is_muted (icon->priv->mixer_stream);
97
gvc_channel_bar_set_is_muted (GVC_CHANNEL_BAR (icon->priv->bar), is_muted);
98
icon->priv->thaw = FALSE;
102
popup_dock (GvcStreamStatusIcon *icon,
106
GtkOrientation orientation;
113
GdkRectangle monitor;
114
GtkRequisition dock_req;
118
screen = gtk_status_icon_get_screen (GTK_STATUS_ICON (icon));
119
res = gtk_status_icon_get_geometry (GTK_STATUS_ICON (icon),
124
g_warning ("Unable to determine geometry of status icon");
128
/* position roughly */
129
gtk_window_set_screen (GTK_WINDOW (icon->priv->dock), screen);
130
gvc_channel_bar_set_orientation (GVC_CHANNEL_BAR (icon->priv->bar),
133
monitor_num = gdk_screen_get_monitor_at_point (screen, area.x, area.y);
134
gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
136
gtk_container_foreach (GTK_CONTAINER (icon->priv->dock),
137
(GtkCallback) gtk_widget_show_all, NULL);
138
gtk_widget_get_preferred_size (icon->priv->dock, &dock_req, NULL);
140
if (orientation == GTK_ORIENTATION_VERTICAL) {
141
if (area.x + area.width + dock_req.width <= monitor.x + monitor.width) {
142
x = area.x + area.width;
144
x = area.x - dock_req.width;
146
if (area.y + dock_req.height <= monitor.y + monitor.height) {
149
y = monitor.y + monitor.height - dock_req.height;
152
if (area.y + area.height + dock_req.height <= monitor.y + monitor.height) {
153
y = area.y + area.height;
155
y = area.y - dock_req.height;
157
if (area.x + dock_req.width <= monitor.x + monitor.width) {
160
x = monitor.x + monitor.width - dock_req.width;
164
gtk_window_move (GTK_WINDOW (icon->priv->dock), x, y);
166
/* FIXME: without this, the popup window appears as a square
167
* after changing the orientation
169
gtk_window_resize (GTK_WINDOW (icon->priv->dock), 1, 1);
171
gtk_widget_show_all (icon->priv->dock);
174
gtk_grab_add (icon->priv->dock);
176
if (gdk_pointer_grab (gtk_widget_get_window (icon->priv->dock), TRUE,
177
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
178
GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK, NULL, NULL,
180
!= GDK_GRAB_SUCCESS) {
181
gtk_grab_remove (icon->priv->dock);
182
gtk_widget_hide (icon->priv->dock);
186
if (gdk_keyboard_grab (gtk_widget_get_window (icon->priv->dock), TRUE, time) != GDK_GRAB_SUCCESS) {
187
display = gtk_widget_get_display (icon->priv->dock);
188
gdk_display_pointer_ungrab (display, time);
189
gtk_grab_remove (icon->priv->dock);
190
gtk_widget_hide (icon->priv->dock);
194
gtk_widget_grab_focus (icon->priv->dock);
200
on_status_icon_activate (GtkStatusIcon *status_icon,
201
GvcStreamStatusIcon *icon)
203
popup_dock (icon, GDK_CURRENT_TIME);
207
on_menu_mute_toggled (GtkMenuItem *item,
208
GvcStreamStatusIcon *icon)
211
is_muted = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
212
gvc_channel_bar_set_is_muted (GVC_CHANNEL_BAR (icon->priv->bar), is_muted);
216
on_menu_activate_open_volume_control (GtkMenuItem *item,
217
GvcStreamStatusIcon *icon)
220
GdkAppLaunchContext *context;
224
context = gdk_app_launch_context_new ();
225
app = g_app_info_create_from_commandline ("gnome-control-center sound", "Sound preferences", 0, &error);
227
g_app_info_launch (app, NULL, G_APP_LAUNCH_CONTEXT (context), &error);
232
dialog = gtk_message_dialog_new (NULL,
236
_("Failed to start Sound Preferences: %s"),
238
g_signal_connect (dialog,
240
G_CALLBACK (gtk_widget_destroy),
242
gtk_widget_show (dialog);
243
g_error_free (error);
246
g_object_unref (context);
247
g_object_unref (app);
251
on_status_icon_popup_menu (GtkStatusIcon *status_icon,
254
GvcStreamStatusIcon *icon)
260
menu = gtk_menu_new ();
262
item = gtk_check_menu_item_new_with_mnemonic (_("_Mute"));
263
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
264
gvc_mixer_stream_get_is_muted (icon->priv->mixer_stream));
265
g_signal_connect (item,
267
G_CALLBACK (on_menu_mute_toggled),
269
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
271
item = gtk_image_menu_item_new_with_mnemonic (_("_Sound Preferences"));
272
image = gtk_image_new_from_icon_name ("multimedia-volume-control",
274
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
275
g_signal_connect (item,
277
G_CALLBACK (on_menu_activate_open_volume_control),
279
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
281
gtk_widget_show_all (menu);
282
gtk_menu_popup (GTK_MENU (menu),
285
gtk_status_icon_position_menu,
292
on_status_icon_scroll_event (GtkStatusIcon *status_icon,
293
GdkEventScroll *event,
294
GvcStreamStatusIcon *icon)
296
return gvc_channel_bar_scroll (GVC_CHANNEL_BAR (icon->priv->bar), event->direction);
300
gvc_icon_release_grab (GvcStreamStatusIcon *icon,
301
GdkEventButton *event)
306
display = gtk_widget_get_display (GTK_WIDGET (icon->priv->dock));
307
gdk_display_keyboard_ungrab (display, event->time);
308
gdk_display_pointer_ungrab (display, event->time);
309
gtk_grab_remove (icon->priv->dock);
312
gtk_widget_hide (icon->priv->dock);
316
on_dock_button_press (GtkWidget *widget,
317
GdkEventButton *event,
318
GvcStreamStatusIcon *icon)
320
if (event->type == GDK_BUTTON_PRESS) {
321
gvc_icon_release_grab (icon, event);
329
popdown_dock (GvcStreamStatusIcon *icon)
334
display = gtk_widget_get_display (icon->priv->dock);
335
gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
336
gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
337
gtk_grab_remove (icon->priv->dock);
340
gtk_widget_hide (icon->priv->dock);
344
on_dock_key_release (GtkWidget *widget,
346
GvcStreamStatusIcon *icon)
348
if (event->keyval == GDK_KEY_Escape) {
354
if (!gtk_bindings_activate_event (GTK_OBJECT (widget), event)) {
355
/* The popup hasn't managed the event, pass onto the button */
356
gtk_bindings_activate_event (GTK_OBJECT (user_data), event);
363
on_dock_scroll_event (GtkWidget *widget,
364
GdkEventScroll *event,
365
GvcStreamStatusIcon *icon)
367
/* Forward event to the status icon */
368
on_status_icon_scroll_event (NULL, event, icon);
373
update_icon (GvcStreamStatusIcon *icon)
379
gboolean can_decibel;
382
if (icon->priv->mixer_stream == NULL) {
386
volume = gvc_mixer_stream_get_volume (icon->priv->mixer_stream);
387
is_muted = gvc_mixer_stream_get_is_muted (icon->priv->mixer_stream);
388
db = gvc_mixer_stream_get_decibel (icon->priv->mixer_stream);
389
can_decibel = gvc_mixer_stream_get_can_decibel (icon->priv->mixer_stream);
392
if (volume == 0 || is_muted) {
395
n = 3 * volume / PA_VOLUME_NORM + 1;
403
/* apparently status icon will reset icon even if
404
* if doesn't change */
405
if (icon->priv->current_icon != n) {
406
gtk_status_icon_set_from_icon_name (GTK_STATUS_ICON (icon),
407
icon->priv->icon_names [n]);
408
icon->priv->current_icon = n;
413
markup = g_strdup_printf (
414
"<b>%s: %s</b>\n<small>%s</small>",
415
icon->priv->display_name,
417
gvc_mixer_stream_get_description (icon->priv->mixer_stream));
418
} else if (can_decibel && (db > PA_DECIBEL_MININFTY)) {
419
markup = g_strdup_printf (
420
"<b>%s: %.0f%%</b>\n<small>%0.2f dB\n%s</small>",
421
icon->priv->display_name,
422
100 * (float)volume / PA_VOLUME_NORM,
424
gvc_mixer_stream_get_description (icon->priv->mixer_stream));
425
} else if (can_decibel) {
426
markup = g_strdup_printf (
427
"<b>%s: %.0f%%</b>\n<small>-∞ dB\n%s</small>",
428
icon->priv->display_name,
429
100 * (float)volume / PA_VOLUME_NORM,
430
gvc_mixer_stream_get_description (icon->priv->mixer_stream));
432
markup = g_strdup_printf (
433
"<b>%s: %.0f%%</b>\n<small>%s</small>",
434
icon->priv->display_name,
435
100 * (float)volume / PA_VOLUME_NORM,
436
gvc_mixer_stream_get_description (icon->priv->mixer_stream));
438
gtk_status_icon_set_tooltip_markup (GTK_STATUS_ICON (icon), markup);
443
gvc_stream_status_icon_set_icon_names (GvcStreamStatusIcon *icon,
446
g_return_if_fail (GVC_IS_STREAM_STATUS_ICON (icon));
448
g_strfreev (icon->priv->icon_names);
449
icon->priv->icon_names = g_strdupv ((char **)names);
451
g_object_notify (G_OBJECT (icon), "icon-names");
455
on_stream_volume_notify (GObject *object,
457
GvcStreamStatusIcon *icon)
464
on_stream_is_muted_notify (GObject *object,
466
GvcStreamStatusIcon *icon)
473
gvc_stream_status_icon_set_display_name (GvcStreamStatusIcon *icon,
476
g_return_if_fail (GVC_STREAM_STATUS_ICON (icon));
478
g_free (icon->priv->display_name);
479
icon->priv->display_name = g_strdup (name);
481
g_object_notify (G_OBJECT (icon), "display-name");
485
gvc_stream_status_icon_set_mixer_stream (GvcStreamStatusIcon *icon,
486
GvcMixerStream *stream)
488
g_return_if_fail (GVC_STREAM_STATUS_ICON (icon));
490
if (stream != NULL) {
491
g_object_ref (stream);
494
if (icon->priv->mixer_stream != NULL) {
495
g_signal_handlers_disconnect_by_func (icon->priv->mixer_stream,
496
G_CALLBACK (on_stream_volume_notify),
498
g_signal_handlers_disconnect_by_func (icon->priv->mixer_stream,
499
G_CALLBACK (on_stream_is_muted_notify),
501
g_object_unref (icon->priv->mixer_stream);
502
icon->priv->mixer_stream = NULL;
505
icon->priv->mixer_stream = stream;
507
if (icon->priv->mixer_stream != NULL) {
510
g_object_ref (icon->priv->mixer_stream);
512
icon->priv->thaw = TRUE;
513
adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (icon->priv->bar)));
514
gtk_adjustment_set_value (adj,
515
gvc_mixer_stream_get_volume (icon->priv->mixer_stream));
516
icon->priv->thaw = FALSE;
518
g_signal_connect (icon->priv->mixer_stream,
520
G_CALLBACK (on_stream_volume_notify),
522
g_signal_connect (icon->priv->mixer_stream,
524
G_CALLBACK (on_stream_is_muted_notify),
530
g_object_notify (G_OBJECT (icon), "mixer-stream");
534
gvc_stream_status_icon_set_property (GObject *object,
539
GvcStreamStatusIcon *self = GVC_STREAM_STATUS_ICON (object);
542
case PROP_MIXER_STREAM:
543
gvc_stream_status_icon_set_mixer_stream (self, g_value_get_object (value));
545
case PROP_DISPLAY_NAME:
546
gvc_stream_status_icon_set_display_name (self, g_value_get_string (value));
548
case PROP_ICON_NAMES:
549
gvc_stream_status_icon_set_icon_names (self, g_value_get_boxed (value));
552
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
558
gvc_stream_status_icon_get_property (GObject *object,
563
GvcStreamStatusIcon *self = GVC_STREAM_STATUS_ICON (object);
564
GvcStreamStatusIconPrivate *priv = self->priv;
567
case PROP_MIXER_STREAM:
568
g_value_set_object (value, priv->mixer_stream);
570
case PROP_DISPLAY_NAME:
571
g_value_set_string (value, priv->display_name);
573
case PROP_ICON_NAMES:
574
g_value_set_boxed (value, priv->icon_names);
577
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
583
on_bar_is_muted_notify (GObject *object,
585
GvcStreamStatusIcon *icon)
589
is_muted = gvc_channel_bar_get_is_muted (GVC_CHANNEL_BAR (object));
591
if (gvc_mixer_stream_get_is_muted (icon->priv->mixer_stream) != is_muted) {
592
/* Update the stream before pushing the change */
593
gvc_mixer_stream_set_is_muted (icon->priv->mixer_stream, is_muted);
594
gvc_mixer_stream_change_is_muted (icon->priv->mixer_stream,
600
gvc_stream_status_icon_constructor (GType type,
601
guint n_construct_properties,
602
GObjectConstructParam *construct_params)
605
GvcStreamStatusIcon *icon;
610
object = G_OBJECT_CLASS (gvc_stream_status_icon_parent_class)->constructor (type, n_construct_properties, construct_params);
612
icon = GVC_STREAM_STATUS_ICON (object);
614
gtk_status_icon_set_from_icon_name (GTK_STATUS_ICON (icon),
615
icon->priv->icon_names[0]);
618
icon->priv->dock = gtk_window_new (GTK_WINDOW_POPUP);
619
gtk_widget_set_name (icon->priv->dock, "gvc-stream-status-icon-popup-window");
620
g_signal_connect (icon->priv->dock,
621
"button-press-event",
622
G_CALLBACK (on_dock_button_press),
624
g_signal_connect (icon->priv->dock,
626
G_CALLBACK (on_dock_key_release),
628
g_signal_connect (icon->priv->dock,
630
G_CALLBACK (on_dock_scroll_event),
633
gtk_window_set_decorated (GTK_WINDOW (icon->priv->dock), FALSE);
635
frame = gtk_frame_new (NULL);
636
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
637
gtk_container_add (GTK_CONTAINER (icon->priv->dock), frame);
639
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
640
gtk_container_set_border_width (GTK_CONTAINER (box), 2);
641
gtk_container_add (GTK_CONTAINER (frame), box);
643
icon->priv->bar = gvc_channel_bar_new ();
644
gvc_channel_bar_set_orientation (GVC_CHANNEL_BAR (icon->priv->bar),
645
GTK_ORIENTATION_VERTICAL);
647
gtk_box_pack_start (GTK_BOX (box), icon->priv->bar, TRUE, FALSE, 0);
648
g_signal_connect (icon->priv->bar,
650
G_CALLBACK (on_bar_is_muted_notify),
653
adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (icon->priv->bar)));
654
g_signal_connect (adj,
656
G_CALLBACK (on_adjustment_value_changed),
663
gvc_stream_status_icon_dispose (GObject *object)
665
GvcStreamStatusIcon *icon = GVC_STREAM_STATUS_ICON (object);
667
if (icon->priv->dock != NULL) {
668
gtk_widget_destroy (icon->priv->dock);
669
icon->priv->dock = NULL;
672
if (icon->priv->mixer_stream != NULL) {
673
g_object_unref (icon->priv->mixer_stream);
674
icon->priv->mixer_stream = NULL;
677
G_OBJECT_CLASS (gvc_stream_status_icon_parent_class)->dispose (object);
681
gvc_stream_status_icon_class_init (GvcStreamStatusIconClass *klass)
683
GObjectClass *object_class = G_OBJECT_CLASS (klass);
685
object_class->constructor = gvc_stream_status_icon_constructor;
686
object_class->finalize = gvc_stream_status_icon_finalize;
687
object_class->dispose = gvc_stream_status_icon_dispose;
688
object_class->set_property = gvc_stream_status_icon_set_property;
689
object_class->get_property = gvc_stream_status_icon_get_property;
691
g_object_class_install_property (object_class,
693
g_param_spec_object ("mixer-stream",
696
GVC_TYPE_MIXER_STREAM,
697
G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
698
g_object_class_install_property (object_class,
700
g_param_spec_string ("display-name",
702
"Name to display for this stream",
704
G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
705
g_object_class_install_property (object_class,
707
g_param_spec_boxed ("icon-names",
709
"Name of icon to display for this stream",
711
G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
713
g_type_class_add_private (klass, sizeof (GvcStreamStatusIconPrivate));
717
on_status_icon_visible_notify (GvcStreamStatusIcon *icon)
721
g_object_get (icon, "visible", &visible, NULL);
723
if (icon->priv->dock != NULL) {
724
gtk_widget_hide (icon->priv->dock);
730
gvc_stream_status_icon_init (GvcStreamStatusIcon *icon)
732
icon->priv = GVC_STREAM_STATUS_ICON_GET_PRIVATE (icon);
734
g_signal_connect (icon,
736
G_CALLBACK (on_status_icon_activate),
738
g_signal_connect (icon,
740
G_CALLBACK (on_status_icon_popup_menu),
742
g_signal_connect (icon,
744
G_CALLBACK (on_status_icon_scroll_event),
746
g_signal_connect (icon,
748
G_CALLBACK (on_status_icon_visible_notify),
751
icon->priv->thaw = FALSE;
755
gvc_stream_status_icon_finalize (GObject *object)
757
GvcStreamStatusIcon *stream_status_icon;
759
g_return_if_fail (object != NULL);
760
g_return_if_fail (GVC_IS_STREAM_STATUS_ICON (object));
762
stream_status_icon = GVC_STREAM_STATUS_ICON (object);
764
g_return_if_fail (stream_status_icon->priv != NULL);
766
g_strfreev (stream_status_icon->priv->icon_names);
768
G_OBJECT_CLASS (gvc_stream_status_icon_parent_class)->finalize (object);
771
GvcStreamStatusIcon *
772
gvc_stream_status_icon_new (GvcMixerStream *stream,
773
const char **icon_names)
776
icon = g_object_new (GVC_TYPE_STREAM_STATUS_ICON,
777
"mixer-stream", stream,
778
"icon-names", icon_names,
780
return GVC_STREAM_STATUS_ICON (icon);