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>-&#8734; 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