1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2008 William Jon McCann
4 * Copyright (C) 2014 Michal Ratajsky <michal.ratajsky@gmail.com>
5 * Copyright (C) 2019 Victor Kareh <vkareh@vkareh.net>
6 * Copyright (C) 2014-2021 MATE Developers
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
21 *
22 */
23
24 #include <glib.h>
25 #include <glib/gi18n.h>
26 #include <gtk/gtk.h>
27 #include <gdk/gdkkeysyms.h>
28
29 #include <libmatemixer/matemixer.h>
30 #include <mate-panel-applet.h>
31
32 #define MATE_DESKTOP_USE_UNSTABLE_API
33 #include <libmate-desktop/mate-desktop-utils.h>
34
35 #include "gvc-channel-bar.h"
36 #include "gvc-stream-applet-icon.h"
37
38 struct _GvcStreamAppletIconPrivate
39 {
40 gchar **icon_names;
41 GtkImage *image;
42 GtkWidget *dock;
43 GtkWidget *bar;
44 guint current_icon;
45 gchar *display_name;
46 MateMixerStreamControl *control;
47 MatePanelAppletOrient orient;
48 guint size;
49 };
50
51 enum
52 {
53 PROP_0,
54 PROP_CONTROL,
55 PROP_DISPLAY_NAME,
56 PROP_ICON_NAMES,
57 N_PROPERTIES
58 };
59
60 static GParamSpec *properties[N_PROPERTIES] = { NULL, };
61
62 static void gvc_stream_applet_icon_finalize (GObject *object);
63
G_DEFINE_TYPE_WITH_PRIVATE(GvcStreamAppletIcon,gvc_stream_applet_icon,GTK_TYPE_EVENT_BOX)64 G_DEFINE_TYPE_WITH_PRIVATE (GvcStreamAppletIcon, gvc_stream_applet_icon, GTK_TYPE_EVENT_BOX)
65
66 static gboolean
67 popup_dock (GvcStreamAppletIcon *icon, guint time)
68 {
69 GtkAllocation allocation;
70 GdkDisplay *display;
71 GdkScreen *screen;
72 int x, y;
73 GdkMonitor *monitor_num;
74 GdkRectangle monitor;
75 GtkRequisition dock_req;
76
77 screen = gtk_widget_get_screen (GTK_WIDGET (icon));
78 gtk_widget_get_allocation (GTK_WIDGET (icon), &allocation);
79 gdk_window_get_origin (gtk_widget_get_window (GTK_WIDGET (icon)), &allocation.x, &allocation.y);
80
81 /* position roughly */
82 gtk_window_set_screen (GTK_WINDOW (icon->priv->dock), screen);
83 switch (icon->priv->orient) {
84 case MATE_PANEL_APPLET_ORIENT_LEFT:
85 case MATE_PANEL_APPLET_ORIENT_RIGHT:
86 gvc_channel_bar_set_orientation (GVC_CHANNEL_BAR (icon->priv->bar), GTK_ORIENTATION_HORIZONTAL);
87 break;
88 case MATE_PANEL_APPLET_ORIENT_UP:
89 case MATE_PANEL_APPLET_ORIENT_DOWN:
90 default:
91 gvc_channel_bar_set_orientation (GVC_CHANNEL_BAR (icon->priv->bar), GTK_ORIENTATION_VERTICAL);
92 }
93
94 monitor_num = gdk_display_get_monitor_at_point (gdk_screen_get_display (screen), allocation.x, allocation.y);
95 gdk_monitor_get_geometry (monitor_num, &monitor);
96
97 gtk_container_foreach (GTK_CONTAINER (icon->priv->dock), (GtkCallback) gtk_widget_show_all, NULL);
98 gtk_widget_get_preferred_size (icon->priv->dock, &dock_req, NULL);
99
100 if (icon->priv->orient == MATE_PANEL_APPLET_ORIENT_LEFT || icon->priv->orient == MATE_PANEL_APPLET_ORIENT_RIGHT) {
101 if (allocation.x + allocation.width + dock_req.width <= monitor.x + monitor.width)
102 x = allocation.x + allocation.width;
103 else
104 x = allocation.x - dock_req.width;
105
106 if (allocation.y + dock_req.height <= monitor.y + monitor.height)
107 y = allocation.y;
108 else
109 y = monitor.y + monitor.height - dock_req.height;
110 } else {
111 if (allocation.y + allocation.height + dock_req.height <= monitor.y + monitor.height)
112 y = allocation.y + allocation.height;
113 else
114 y = allocation.y - dock_req.height;
115
116 if (allocation.x + dock_req.width <= monitor.x + monitor.width)
117 x = allocation.x;
118 else
119 x = monitor.x + monitor.width - dock_req.width;
120 }
121
122 gtk_window_move (GTK_WINDOW (icon->priv->dock), x, y);
123
124 /* Without this, the popup window appears as a square after changing
125 * the orientation */
126 gtk_window_resize (GTK_WINDOW (icon->priv->dock), 1, 1);
127
128 gtk_widget_show_all (icon->priv->dock);
129
130 /* Grab focus */
131 gtk_grab_add (icon->priv->dock);
132
133 display = gtk_widget_get_display (icon->priv->dock);
134
135 do {
136 GdkSeat *seat = gdk_display_get_default_seat (display);
137 GdkWindow *window = gtk_widget_get_window (icon->priv->dock);
138
139 if (gdk_seat_grab (seat,
140 window,
141 GDK_SEAT_CAPABILITY_ALL,
142 TRUE,
143 NULL,
144 NULL,
145 NULL,
146 NULL) != GDK_GRAB_SUCCESS) {
147 gtk_grab_remove (icon->priv->dock);
148 gtk_widget_hide (icon->priv->dock);
149 break;
150 }
151 } while (0);
152
153 gtk_widget_grab_focus (icon->priv->dock);
154
155 return TRUE;
156 }
157
158 static gboolean
on_applet_icon_button_press(GtkWidget * applet_icon,GdkEventButton * event,GvcStreamAppletIcon * icon)159 on_applet_icon_button_press (GtkWidget *applet_icon,
160 GdkEventButton *event,
161 GvcStreamAppletIcon *icon)
162 {
163 if (event->button == 1) {
164 popup_dock (icon, GDK_CURRENT_TIME);
165 return TRUE;
166 }
167
168 /* Middle click acts as mute/unmute */
169 if (event->button == 2) {
170 gboolean is_muted = mate_mixer_stream_control_get_mute (icon->priv->control);
171
172 mate_mixer_stream_control_set_mute (icon->priv->control, !is_muted);
173 return TRUE;
174 }
175 return FALSE;
176 }
177
178 void
gvc_stream_applet_icon_set_mute(GvcStreamAppletIcon * icon,gboolean mute)179 gvc_stream_applet_icon_set_mute (GvcStreamAppletIcon *icon, gboolean mute)
180 {
181 mate_mixer_stream_control_set_mute (icon->priv->control, mute);
182 }
183
184 gboolean
gvc_stream_applet_icon_get_mute(GvcStreamAppletIcon * icon)185 gvc_stream_applet_icon_get_mute (GvcStreamAppletIcon *icon)
186 {
187 return mate_mixer_stream_control_get_mute (icon->priv->control);
188 }
189
190 void
gvc_stream_applet_icon_volume_control(GvcStreamAppletIcon * icon)191 gvc_stream_applet_icon_volume_control (GvcStreamAppletIcon *icon)
192 {
193 GError *error = NULL;
194
195 mate_gdk_spawn_command_line_on_screen (gtk_widget_get_screen (icon->priv->dock),
196 "mate-volume-control",
197 &error);
198
199 if (error != NULL) {
200 GtkWidget *dialog;
201
202 dialog = gtk_message_dialog_new (NULL,
203 0,
204 GTK_MESSAGE_ERROR,
205 GTK_BUTTONS_CLOSE,
206 _("Failed to start Sound Preferences: %s"),
207 error->message);
208 g_signal_connect (G_OBJECT (dialog),
209 "response",
210 G_CALLBACK (gtk_widget_destroy),
211 NULL);
212 gtk_widget_show (dialog);
213 g_error_free (error);
214 }
215 }
216
217 static gboolean
on_applet_icon_scroll_event(GtkWidget * event_box,GdkEventScroll * event,GvcStreamAppletIcon * icon)218 on_applet_icon_scroll_event (GtkWidget *event_box,
219 GdkEventScroll *event,
220 GvcStreamAppletIcon *icon)
221 {
222 return gvc_channel_bar_scroll (GVC_CHANNEL_BAR (icon->priv->bar), event->direction);
223 }
224
225 static void
gvc_icon_release_grab(GvcStreamAppletIcon * icon,GdkEventButton * event)226 gvc_icon_release_grab (GvcStreamAppletIcon *icon, GdkEventButton *event)
227 {
228 GdkDisplay *display = gtk_widget_get_display (icon->priv->dock);
229 GdkSeat *seat = gdk_display_get_default_seat (display);
230 gdk_seat_ungrab (seat);
231 gtk_grab_remove (icon->priv->dock);
232
233 /* Hide again */
234 gtk_widget_hide (icon->priv->dock);
235 }
236
237 static gboolean
on_dock_button_press(GtkWidget * widget,GdkEventButton * event,GvcStreamAppletIcon * icon)238 on_dock_button_press (GtkWidget *widget,
239 GdkEventButton *event,
240 GvcStreamAppletIcon *icon)
241 {
242 if (event->type == GDK_BUTTON_PRESS) {
243 gvc_icon_release_grab (icon, event);
244 return TRUE;
245 }
246
247 return FALSE;
248 }
249
250 static void
popdown_dock(GvcStreamAppletIcon * icon)251 popdown_dock (GvcStreamAppletIcon *icon)
252 {
253 GdkDisplay *display;
254
255 display = gtk_widget_get_display (icon->priv->dock);
256
257 GdkSeat *seat = gdk_display_get_default_seat (display);
258 gdk_seat_ungrab (seat);
259
260 /* Hide again */
261 gtk_widget_hide (icon->priv->dock);
262 }
263
264 /* This is called when the grab is broken for either the dock, or the scale */
265 static void
gvc_icon_grab_notify(GvcStreamAppletIcon * icon,gboolean was_grabbed)266 gvc_icon_grab_notify (GvcStreamAppletIcon *icon, gboolean was_grabbed)
267 {
268 if (was_grabbed != FALSE)
269 return;
270
271 if (gtk_widget_has_grab (icon->priv->dock) == FALSE)
272 return;
273
274 if (gtk_widget_is_ancestor (gtk_grab_get_current (), icon->priv->dock))
275 return;
276
277 popdown_dock (icon);
278 }
279
280 static void
on_dock_grab_notify(GtkWidget * widget,gboolean was_grabbed,GvcStreamAppletIcon * icon)281 on_dock_grab_notify (GtkWidget *widget,
282 gboolean was_grabbed,
283 GvcStreamAppletIcon *icon)
284 {
285 gvc_icon_grab_notify (icon, was_grabbed);
286 }
287
288 static gboolean
on_dock_grab_broken_event(GtkWidget * widget,gboolean was_grabbed,GvcStreamAppletIcon * icon)289 on_dock_grab_broken_event (GtkWidget *widget,
290 gboolean was_grabbed,
291 GvcStreamAppletIcon *icon)
292 {
293 gvc_icon_grab_notify (icon, FALSE);
294 return FALSE;
295 }
296
297 static gboolean
on_dock_key_release(GtkWidget * widget,GdkEventKey * event,GvcStreamAppletIcon * icon)298 on_dock_key_release (GtkWidget *widget,
299 GdkEventKey *event,
300 GvcStreamAppletIcon *icon)
301 {
302 if (event->keyval == GDK_KEY_Escape) {
303 popdown_dock (icon);
304 return TRUE;
305 }
306 return TRUE;
307 }
308
309 static gboolean
on_dock_scroll_event(GtkWidget * widget,GdkEventScroll * event,GvcStreamAppletIcon * icon)310 on_dock_scroll_event (GtkWidget *widget,
311 GdkEventScroll *event,
312 GvcStreamAppletIcon *icon)
313 {
314 /* Forward event to the applet icon */
315 on_applet_icon_scroll_event (NULL, event, icon);
316 return TRUE;
317 }
318
319 static void
gvc_stream_applet_icon_set_icon_from_name(GvcStreamAppletIcon * icon,const gchar * icon_name)320 gvc_stream_applet_icon_set_icon_from_name (GvcStreamAppletIcon *icon,
321 const gchar *icon_name)
322 {
323 GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
324 gint icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (icon));
325
326 cairo_surface_t* surface = gtk_icon_theme_load_surface (icon_theme, icon_name,
327 icon->priv->size,
328 icon_scale, NULL,
329 GTK_ICON_LOOKUP_FORCE_SIZE,
330 NULL);
331
332 gtk_image_set_from_surface (GTK_IMAGE (icon->priv->image), surface);
333 cairo_surface_destroy (surface);
334 }
335
336 static void
update_icon(GvcStreamAppletIcon * icon)337 update_icon (GvcStreamAppletIcon *icon)
338 {
339 guint volume = 0;
340 gdouble decibel = 0;
341 guint normal = 0;
342 gboolean muted = FALSE;
343 guint n = 0;
344 gchar *markup;
345 const gchar *description;
346 MateMixerStreamControlFlags flags;
347
348 if (icon->priv->control == NULL) {
349 /* Do not bother creating a tooltip for an unusable icon as it
350 * has no practical use */
351 gtk_widget_set_has_tooltip (GTK_WIDGET (icon), FALSE);
352 return;
353 } else
354 gtk_widget_set_has_tooltip (GTK_WIDGET (icon), TRUE);
355
356 flags = mate_mixer_stream_control_get_flags (icon->priv->control);
357
358 if (flags & MATE_MIXER_STREAM_CONTROL_MUTE_READABLE)
359 muted = mate_mixer_stream_control_get_mute (icon->priv->control);
360
361 if (flags & MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE) {
362 volume = mate_mixer_stream_control_get_volume (icon->priv->control);
363 normal = mate_mixer_stream_control_get_normal_volume (icon->priv->control);
364
365 /* Select an icon, they are expected to be sorted, the lowest index being
366 * the mute icon and the rest being volume increments */
367 if (volume <= 0 || muted)
368 n = 0;
369 else
370 n = CLAMP (3 * volume / normal + 1, 1, 3);
371 }
372 if (flags & MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL)
373 decibel = mate_mixer_stream_control_get_decibel (icon->priv->control);
374
375 /* Apparently applet icon will reset icon even if it doesn't change */
376 if (icon->priv->current_icon != n) {
377 gvc_stream_applet_icon_set_icon_from_name (icon, icon->priv->icon_names[n]);
378 icon->priv->current_icon = n;
379 }
380
381 description = mate_mixer_stream_control_get_label (icon->priv->control);
382
383 guint volume_percent = (guint) round (100.0 * volume / normal);
384 if (muted) {
385 markup = g_strdup_printf ("<b>%s: %s %u%%</b>\n<small>%s</small>",
386 icon->priv->display_name,
387 _("Muted at"),
388 volume_percent,
389 description);
390 } else if (flags & MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE) {
391 if (flags & MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL) {
392 if (decibel > -MATE_MIXER_INFINITY) {
393 markup = g_strdup_printf ("<b>%s: %u%%</b>\n"
394 "<small>%0.2f dB\n%s</small>",
395 icon->priv->display_name,
396 volume_percent,
397 decibel,
398 description);
399 } else {
400 markup = g_strdup_printf ("<b>%s: %u%%</b>\n"
401 "<small>-∞ dB\n%s</small>",
402 icon->priv->display_name,
403 volume_percent,
404 description);
405 }
406 } else {
407 markup = g_strdup_printf ("<b>%s: %u%%</b>\n<small>%s</small>",
408 icon->priv->display_name,
409 volume_percent,
410 description);
411 }
412 } else {
413 markup = g_strdup_printf ("<b>%s</b>\n<small>%s</small>",
414 icon->priv->display_name,
415 description);
416 }
417
418 gtk_widget_set_tooltip_markup (GTK_WIDGET (icon), markup);
419
420 g_free (markup);
421 }
422
423 void
gvc_stream_applet_icon_set_size(GvcStreamAppletIcon * icon,guint size)424 gvc_stream_applet_icon_set_size (GvcStreamAppletIcon *icon,
425 guint size)
426 {
427
428 /*Iterate through the icon sizes so they can be kept sharp*/
429 if (size < 22)
430 size = 16;
431 else if (size < 24)
432 size = 22;
433 else if (size < 32)
434 size = 24;
435 else if (size < 48)
436 size = 32;
437
438 icon->priv->size = size;
439 gvc_stream_applet_icon_set_icon_from_name (icon, icon->priv->icon_names[icon->priv->current_icon]);
440 }
441
442 void
gvc_stream_applet_icon_set_orient(GvcStreamAppletIcon * icon,MatePanelAppletOrient orient)443 gvc_stream_applet_icon_set_orient (GvcStreamAppletIcon *icon,
444 MatePanelAppletOrient orient)
445 {
446 /* Sometimes orient does not get properly defined especially on a bottom panel.
447 * Use the applet orientation if it is valid, otherwise set a vertical slider,
448 * otherwise bottom panels get a horizontal slider.
449 */
450 if (orient)
451 icon->priv->orient = orient;
452 else
453 icon->priv->orient = MATE_PANEL_APPLET_ORIENT_DOWN;
454 }
455
456 void
gvc_stream_applet_icon_set_icon_names(GvcStreamAppletIcon * icon,const gchar ** names)457 gvc_stream_applet_icon_set_icon_names (GvcStreamAppletIcon *icon,
458 const gchar **names)
459 {
460 g_return_if_fail (GVC_IS_STREAM_APPLET_ICON (icon));
461 g_return_if_fail (names != NULL && *names != NULL);
462
463 if (G_UNLIKELY (g_strv_length ((gchar **) names) != 4)) {
464 g_warn_if_reached ();
465 return;
466 }
467
468 g_strfreev (icon->priv->icon_names);
469
470 icon->priv->icon_names = g_strdupv ((gchar **) names);
471
472 /* Set the first icon as the initial one, the icon may be immediately
473 * updated or not depending on whether a stream is available */
474 gvc_stream_applet_icon_set_icon_from_name (icon, names[0]);
475 update_icon (icon);
476
477 g_object_notify_by_pspec (G_OBJECT (icon), properties[PROP_ICON_NAMES]);
478 }
479
480 static void
on_stream_control_volume_notify(MateMixerStreamControl * control,GParamSpec * pspec,GvcStreamAppletIcon * icon)481 on_stream_control_volume_notify (MateMixerStreamControl *control,
482 GParamSpec *pspec,
483 GvcStreamAppletIcon *icon)
484 {
485 update_icon (icon);
486 }
487
488 static void
on_stream_control_mute_notify(MateMixerStreamControl * control,GParamSpec * pspec,GvcStreamAppletIcon * icon)489 on_stream_control_mute_notify (MateMixerStreamControl *control,
490 GParamSpec *pspec,
491 GvcStreamAppletIcon *icon)
492 {
493 update_icon (icon);
494 }
495
496 void
gvc_stream_applet_icon_set_display_name(GvcStreamAppletIcon * icon,const gchar * name)497 gvc_stream_applet_icon_set_display_name (GvcStreamAppletIcon *icon,
498 const gchar *name)
499 {
500 g_return_if_fail (GVC_STREAM_APPLET_ICON (icon));
501
502 g_free (icon->priv->display_name);
503
504 icon->priv->display_name = g_strdup (name);
505 update_icon (icon);
506
507 g_object_notify_by_pspec (G_OBJECT (icon), properties[PROP_DISPLAY_NAME]);
508 }
509
510 void
gvc_stream_applet_icon_set_control(GvcStreamAppletIcon * icon,MateMixerStreamControl * control)511 gvc_stream_applet_icon_set_control (GvcStreamAppletIcon *icon,
512 MateMixerStreamControl *control)
513 {
514 g_return_if_fail (GVC_STREAM_APPLET_ICON (icon));
515
516 if (icon->priv->control == control)
517 return;
518
519 if (control != NULL)
520 g_object_ref (control);
521
522 if (icon->priv->control != NULL) {
523 g_signal_handlers_disconnect_by_func (G_OBJECT (icon->priv->control),
524 G_CALLBACK (on_stream_control_volume_notify),
525 icon);
526 g_signal_handlers_disconnect_by_func (G_OBJECT (icon->priv->control),
527 G_CALLBACK (on_stream_control_mute_notify),
528 icon);
529
530 g_object_unref (icon->priv->control);
531 }
532
533 icon->priv->control = control;
534
535 if (icon->priv->control != NULL) {
536 g_signal_connect (G_OBJECT (icon->priv->control),
537 "notify::volume",
538 G_CALLBACK (on_stream_control_volume_notify),
539 icon);
540 g_signal_connect (G_OBJECT (icon->priv->control),
541 "notify::mute",
542 G_CALLBACK (on_stream_control_mute_notify),
543 icon);
544
545 // XXX when no stream set some default icon and "unset" dock
546 update_icon (icon);
547 }
548
549 gvc_channel_bar_set_control (GVC_CHANNEL_BAR (icon->priv->bar), icon->priv->control);
550
551 g_object_notify_by_pspec (G_OBJECT (icon), properties[PROP_CONTROL]);
552 }
553
554 static void
gvc_stream_applet_icon_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)555 gvc_stream_applet_icon_set_property (GObject *object,
556 guint prop_id,
557 const GValue *value,
558 GParamSpec *pspec)
559 {
560 GvcStreamAppletIcon *self = GVC_STREAM_APPLET_ICON (object);
561
562 switch (prop_id) {
563 case PROP_CONTROL:
564 gvc_stream_applet_icon_set_control (self, g_value_get_object (value));
565 break;
566 case PROP_DISPLAY_NAME:
567 gvc_stream_applet_icon_set_display_name (self, g_value_get_string (value));
568 break;
569 case PROP_ICON_NAMES:
570 gvc_stream_applet_icon_set_icon_names (self, g_value_get_boxed (value));
571 break;
572 default:
573 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
574 break;
575 }
576 }
577
578 static void
gvc_stream_applet_icon_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)579 gvc_stream_applet_icon_get_property (GObject *object,
580 guint prop_id,
581 GValue *value,
582 GParamSpec *pspec)
583 {
584 GvcStreamAppletIcon *self = GVC_STREAM_APPLET_ICON (object);
585
586 switch (prop_id) {
587 case PROP_CONTROL:
588 g_value_set_object (value, self->priv->control);
589 break;
590 case PROP_DISPLAY_NAME:
591 g_value_set_string (value, self->priv->display_name);
592 break;
593 case PROP_ICON_NAMES:
594 g_value_set_boxed (value, self->priv->icon_names);
595 break;
596 default:
597 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
598 break;
599 }
600 }
601
602 static void
gvc_stream_applet_icon_dispose(GObject * object)603 gvc_stream_applet_icon_dispose (GObject *object)
604 {
605 GvcStreamAppletIcon *icon = GVC_STREAM_APPLET_ICON (object);
606
607 if (icon->priv->dock != NULL) {
608 gtk_widget_destroy (icon->priv->dock);
609 icon->priv->dock = NULL;
610 }
611
612 g_clear_object (&icon->priv->control);
613
614 G_OBJECT_CLASS (gvc_stream_applet_icon_parent_class)->dispose (object);
615 }
616
617 static void
gvc_stream_applet_icon_class_init(GvcStreamAppletIconClass * klass)618 gvc_stream_applet_icon_class_init (GvcStreamAppletIconClass *klass)
619 {
620 GObjectClass *object_class = G_OBJECT_CLASS (klass);
621 GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
622
623 object_class->finalize = gvc_stream_applet_icon_finalize;
624 object_class->dispose = gvc_stream_applet_icon_dispose;
625 object_class->set_property = gvc_stream_applet_icon_set_property;
626 object_class->get_property = gvc_stream_applet_icon_get_property;
627
628 properties[PROP_CONTROL] =
629 g_param_spec_object ("control",
630 "Control",
631 "MateMixer stream control",
632 MATE_MIXER_TYPE_STREAM_CONTROL,
633 G_PARAM_READWRITE |
634 G_PARAM_CONSTRUCT |
635 G_PARAM_STATIC_STRINGS);
636
637 properties[PROP_DISPLAY_NAME] =
638 g_param_spec_string ("display-name",
639 "Display name",
640 "Name to display for this stream",
641 NULL,
642 G_PARAM_READWRITE |
643 G_PARAM_CONSTRUCT |
644 G_PARAM_STATIC_STRINGS);
645
646 properties[PROP_ICON_NAMES] =
647 g_param_spec_boxed ("icon-names",
648 "Icon names",
649 "Name of icon to display for this stream",
650 G_TYPE_STRV,
651 G_PARAM_READWRITE |
652 G_PARAM_CONSTRUCT |
653 G_PARAM_STATIC_STRINGS);
654
655 gtk_widget_class_set_css_name (widget_class, "volume-applet");
656
657 g_object_class_install_properties (object_class, N_PROPERTIES, properties);
658 }
659
660 static void
on_applet_icon_visible_notify(GvcStreamAppletIcon * icon)661 on_applet_icon_visible_notify (GvcStreamAppletIcon *icon)
662 {
663 if (gtk_widget_get_visible (GTK_WIDGET (icon)) == FALSE)
664 gtk_widget_hide (icon->priv->dock);
665 }
666
667 static void
on_icon_theme_change(GtkSettings * settings,GParamSpec * pspec,GvcStreamAppletIcon * icon)668 on_icon_theme_change (GtkSettings *settings,
669 GParamSpec *pspec,
670 GvcStreamAppletIcon *icon)
671 {
672 gvc_stream_applet_icon_set_icon_from_name (icon, icon->priv->icon_names[icon->priv->current_icon]);
673 }
674
675 static void
gvc_stream_applet_icon_init(GvcStreamAppletIcon * icon)676 gvc_stream_applet_icon_init (GvcStreamAppletIcon *icon)
677 {
678 GtkWidget *frame;
679 GtkWidget *box;
680
681 icon->priv = gvc_stream_applet_icon_get_instance_private (icon);
682
683 icon->priv->image = GTK_IMAGE (gtk_image_new ());
684 gtk_container_add (GTK_CONTAINER (icon), GTK_WIDGET (icon->priv->image));
685
686 g_signal_connect (GTK_WIDGET (icon),
687 "button-press-event",
688 G_CALLBACK (on_applet_icon_button_press),
689 icon);
690 g_signal_connect (GTK_WIDGET (icon),
691 "scroll-event",
692 G_CALLBACK (on_applet_icon_scroll_event),
693 icon);
694 g_signal_connect (GTK_WIDGET (icon),
695 "notify::visible",
696 G_CALLBACK (on_applet_icon_visible_notify),
697 NULL);
698
699 /* Create the dock window */
700 icon->priv->dock = gtk_window_new (GTK_WINDOW_POPUP);
701
702 gtk_window_set_decorated (GTK_WINDOW (icon->priv->dock), FALSE);
703
704 g_signal_connect (G_OBJECT (icon->priv->dock),
705 "button-press-event",
706 G_CALLBACK (on_dock_button_press),
707 icon);
708 g_signal_connect (G_OBJECT (icon->priv->dock),
709 "key-release-event",
710 G_CALLBACK (on_dock_key_release),
711 icon);
712 g_signal_connect (G_OBJECT (icon->priv->dock),
713 "scroll-event",
714 G_CALLBACK (on_dock_scroll_event),
715 icon);
716 g_signal_connect (G_OBJECT (icon->priv->dock),
717 "grab-notify",
718 G_CALLBACK (on_dock_grab_notify),
719 icon);
720 g_signal_connect (G_OBJECT (icon->priv->dock),
721 "grab-broken-event",
722 G_CALLBACK (on_dock_grab_broken_event),
723 icon);
724
725 frame = gtk_frame_new (NULL);
726 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
727 gtk_container_add (GTK_CONTAINER (icon->priv->dock), frame);
728
729 icon->priv->bar = gvc_channel_bar_new (NULL);
730
731 gvc_channel_bar_set_orientation (GVC_CHANNEL_BAR (icon->priv->bar),
732 GTK_ORIENTATION_VERTICAL);
733
734 /* Set volume control frame, slider and toplevel window to follow panel theme */
735 GtkWidget *toplevel = gtk_widget_get_toplevel (icon->priv->dock);
736 GtkStyleContext *context;
737 context = gtk_widget_get_style_context (GTK_WIDGET(toplevel));
738 gtk_style_context_add_class(context,"mate-panel-applet-slider");
739
740 /* Make transparency possible in gtk3 theme */
741 GdkScreen *screen = gtk_widget_get_screen(GTK_WIDGET(toplevel));
742 GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
743 gtk_widget_set_visual(GTK_WIDGET(toplevel), visual);
744
745 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
746
747 gtk_container_set_border_width (GTK_CONTAINER (box), 2);
748 gtk_container_add (GTK_CONTAINER (frame), box);
749
750 gtk_box_pack_start (GTK_BOX (box), icon->priv->bar, TRUE, FALSE, 0);
751
752 g_signal_connect (gtk_settings_get_default (),
753 "notify::gtk-icon-theme-name",
754 G_CALLBACK (on_icon_theme_change),
755 icon);
756 }
757
758 static void
gvc_stream_applet_icon_finalize(GObject * object)759 gvc_stream_applet_icon_finalize (GObject *object)
760 {
761 GvcStreamAppletIcon *icon;
762
763 icon = GVC_STREAM_APPLET_ICON (object);
764
765 g_strfreev (icon->priv->icon_names);
766
767 g_signal_handlers_disconnect_by_func (gtk_settings_get_default (),
768 on_icon_theme_change,
769 icon);
770
771 G_OBJECT_CLASS (gvc_stream_applet_icon_parent_class)->finalize (object);
772 }
773
774 GvcStreamAppletIcon *
gvc_stream_applet_icon_new(MateMixerStreamControl * control,const gchar ** icon_names)775 gvc_stream_applet_icon_new (MateMixerStreamControl *control,
776 const gchar **icon_names)
777 {
778 return g_object_new (GVC_TYPE_STREAM_APPLET_ICON,
779 "control", control,
780 "icon-names", icon_names,
781 NULL);
782 }
783