You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

831 lines
30 KiB

  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. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, write to the Free Software
  18. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
  19. *
  20. */
  21. #include <glib.h>
  22. #include <glib/gi18n.h>
  23. #include <gtk/gtk.h>
  24. #include <gdk/gdkkeysyms.h>
  25. #include <libmatemixer/matemixer.h>
  26. #define MATE_DESKTOP_USE_UNSTABLE_API
  27. #include <libmate-desktop/mate-desktop-utils.h>
  28. #include "gvc-channel-bar.h"
  29. #include "gvc-stream-status-icon.h"
  30. #define GVC_STREAM_STATUS_ICON_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_STREAM_STATUS_ICON, GvcStreamStatusIconPrivate))
  31. struct _GvcStreamStatusIconPrivate
  32. {
  33. gchar **icon_names;
  34. GtkWidget *dock;
  35. GtkWidget *bar;
  36. guint current_icon;
  37. gchar *display_name;
  38. MateMixerStreamControl *control;
  39. };
  40. enum
  41. {
  42. PROP_0,
  43. PROP_CONTROL,
  44. PROP_DISPLAY_NAME,
  45. PROP_ICON_NAMES,
  46. N_PROPERTIES
  47. };
  48. static GParamSpec *properties[N_PROPERTIES] = { NULL, };
  49. static void gvc_stream_status_icon_class_init (GvcStreamStatusIconClass *klass);
  50. static void gvc_stream_status_icon_init (GvcStreamStatusIcon *stream_status_icon);
  51. static void gvc_stream_status_icon_finalize (GObject *object);
  52. G_DEFINE_TYPE (GvcStreamStatusIcon, gvc_stream_status_icon, GTK_TYPE_STATUS_ICON)
  53. static gboolean
  54. popup_dock (GvcStreamStatusIcon *icon, guint time)
  55. {
  56. GdkRectangle area;
  57. GtkOrientation orientation;
  58. GdkDisplay *display;
  59. GdkScreen *screen;
  60. int x;
  61. int y;
  62. int monitor_num;
  63. GdkRectangle monitor;
  64. GtkRequisition dock_req;
  65. screen = gtk_status_icon_get_screen (GTK_STATUS_ICON (icon));
  66. if (gtk_status_icon_get_geometry (GTK_STATUS_ICON (icon),
  67. &screen,
  68. &area,
  69. &orientation) == FALSE) {
  70. g_warning ("Unable to determine geometry of status icon");
  71. return FALSE;
  72. }
  73. /* position roughly */
  74. gtk_window_set_screen (GTK_WINDOW (icon->priv->dock), screen);
  75. gvc_channel_bar_set_orientation (GVC_CHANNEL_BAR (icon->priv->bar),
  76. 1 - orientation);
  77. monitor_num = gdk_screen_get_monitor_at_point (screen, area.x, area.y);
  78. gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
  79. gtk_container_foreach (GTK_CONTAINER (icon->priv->dock),
  80. (GtkCallback) gtk_widget_show_all, NULL);
  81. #if GTK_CHECK_VERSION (3, 0, 0)
  82. gtk_widget_get_preferred_size (icon->priv->dock, &dock_req, NULL);
  83. #else
  84. gtk_widget_size_request (icon->priv->dock, &dock_req);
  85. #endif
  86. if (orientation == GTK_ORIENTATION_VERTICAL) {
  87. if (area.x + area.width + dock_req.width <= monitor.x + monitor.width)
  88. x = area.x + area.width;
  89. else
  90. x = area.x - dock_req.width;
  91. if (area.y + dock_req.height <= monitor.y + monitor.height)
  92. y = area.y;
  93. else
  94. y = monitor.y + monitor.height - dock_req.height;
  95. } else {
  96. if (area.y + area.height + dock_req.height <= monitor.y + monitor.height)
  97. y = area.y + area.height;
  98. else
  99. y = area.y - dock_req.height;
  100. if (area.x + dock_req.width <= monitor.x + monitor.width)
  101. x = area.x;
  102. else
  103. x = monitor.x + monitor.width - dock_req.width;
  104. }
  105. gtk_window_move (GTK_WINDOW (icon->priv->dock), x, y);
  106. /* Without this, the popup window appears as a square after changing
  107. * the orientation */
  108. gtk_window_resize (GTK_WINDOW (icon->priv->dock), 1, 1);
  109. gtk_widget_show_all (icon->priv->dock);
  110. /* Grab focus */
  111. gtk_grab_add (icon->priv->dock);
  112. display = gtk_widget_get_display (icon->priv->dock);
  113. #if GTK_CHECK_VERSION (3, 0, 0)
  114. do {
  115. GdkDeviceManager *manager = gdk_display_get_device_manager (display);
  116. if (gdk_device_grab (gdk_device_manager_get_client_pointer (manager),
  117. gtk_widget_get_window (icon->priv->dock),
  118. GDK_OWNERSHIP_NONE,
  119. TRUE,
  120. GDK_BUTTON_PRESS_MASK |
  121. GDK_BUTTON_RELEASE_MASK |
  122. GDK_POINTER_MOTION_MASK |
  123. GDK_SCROLL_MASK,
  124. NULL,
  125. time) != GDK_GRAB_SUCCESS) {
  126. gtk_grab_remove (icon->priv->dock);
  127. gtk_widget_hide (icon->priv->dock);
  128. }
  129. } while (0);
  130. #else
  131. if (gdk_pointer_grab (gtk_widget_get_window (icon->priv->dock),
  132. TRUE,
  133. GDK_BUTTON_PRESS_MASK |
  134. GDK_BUTTON_RELEASE_MASK |
  135. GDK_POINTER_MOTION_MASK |
  136. GDK_SCROLL_MASK,
  137. NULL, NULL,
  138. time) != GDK_GRAB_SUCCESS) {
  139. gtk_grab_remove (icon->priv->dock);
  140. gtk_widget_hide (icon->priv->dock);
  141. return FALSE;
  142. }
  143. if (gdk_keyboard_grab (gtk_widget_get_window (icon->priv->dock),
  144. TRUE,
  145. time) != GDK_GRAB_SUCCESS) {
  146. gdk_display_pointer_ungrab (display, time);
  147. gtk_grab_remove (icon->priv->dock);
  148. gtk_widget_hide (icon->priv->dock);
  149. return FALSE;
  150. }
  151. #endif
  152. gtk_widget_grab_focus (icon->priv->dock);
  153. return TRUE;
  154. }
  155. static void
  156. on_status_icon_activate (GtkStatusIcon *status_icon, GvcStreamStatusIcon *icon)
  157. {
  158. popup_dock (icon, GDK_CURRENT_TIME);
  159. }
  160. static gboolean
  161. on_status_icon_button_press (GtkStatusIcon *status_icon,
  162. GdkEventButton *event,
  163. GvcStreamStatusIcon *icon)
  164. {
  165. /* Middle click acts as mute/unmute */
  166. if (event->button == 2) {
  167. gboolean is_muted = mate_mixer_stream_control_get_mute (icon->priv->control);
  168. mate_mixer_stream_control_set_mute (icon->priv->control, !is_muted);
  169. return TRUE;
  170. }
  171. return FALSE;
  172. }
  173. static void
  174. on_menu_mute_toggled (GtkMenuItem *item, GvcStreamStatusIcon *icon)
  175. {
  176. gboolean is_muted;
  177. is_muted = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
  178. mate_mixer_stream_control_set_mute (icon->priv->control, is_muted);
  179. }
  180. static void
  181. on_menu_activate_open_volume_control (GtkMenuItem *item,
  182. GvcStreamStatusIcon *icon)
  183. {
  184. GError *error = NULL;
  185. mate_gdk_spawn_command_line_on_screen (gtk_widget_get_screen (icon->priv->dock),
  186. "mate-volume-control",
  187. &error);
  188. if (error != NULL) {
  189. GtkWidget *dialog;
  190. dialog = gtk_message_dialog_new (NULL,
  191. 0,
  192. GTK_MESSAGE_ERROR,
  193. GTK_BUTTONS_CLOSE,
  194. _("Failed to start Sound Preferences: %s"),
  195. error->message);
  196. g_signal_connect (G_OBJECT (dialog),
  197. "response",
  198. G_CALLBACK (gtk_widget_destroy),
  199. NULL);
  200. gtk_widget_show (dialog);
  201. g_error_free (error);
  202. }
  203. }
  204. static void
  205. on_status_icon_popup_menu (GtkStatusIcon *status_icon,
  206. guint button,
  207. guint activate_time,
  208. GvcStreamStatusIcon *icon)
  209. {
  210. GtkWidget *menu;
  211. GtkWidget *item;
  212. menu = gtk_menu_new ();
  213. #if GTK_CHECK_VERSION (3, 0, 0)
  214. /*Set up theme and transparency support*/
  215. GtkWidget *toplevel = gtk_widget_get_toplevel (menu);
  216. /* Fix any failures of compiz/other wm's to communicate with gtk for transparency */
  217. GdkScreen *screen = gtk_widget_get_screen(GTK_WIDGET(toplevel));
  218. GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
  219. gtk_widget_set_visual(GTK_WIDGET(toplevel), visual);
  220. /* Set menu and it's toplevel window to follow panel theme */
  221. GtkStyleContext *context;
  222. context = gtk_widget_get_style_context (GTK_WIDGET(toplevel));
  223. gtk_style_context_add_class(context,"gnome-panel-menu-bar");
  224. gtk_style_context_add_class(context,"mate-panel-menu-bar");
  225. #endif
  226. item = gtk_check_menu_item_new_with_mnemonic (_("_Mute"));
  227. gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
  228. mate_mixer_stream_control_get_mute (icon->priv->control));
  229. g_signal_connect (G_OBJECT (item),
  230. "toggled",
  231. G_CALLBACK (on_menu_mute_toggled),
  232. icon);
  233. gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
  234. #if GTK_CHECK_VERSION (3, 10, 0)
  235. item = gtk_menu_item_new_with_mnemonic (_("_Sound Preferences"));
  236. #else
  237. item = gtk_image_menu_item_new_with_mnemonic (_("_Sound Preferences"));
  238. do {
  239. GtkWidget *image;
  240. image = gtk_image_new_from_icon_name ("multimedia-volume-control",
  241. GTK_ICON_SIZE_MENU);
  242. gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
  243. } while (0);
  244. #endif
  245. g_signal_connect (G_OBJECT (item),
  246. "activate",
  247. G_CALLBACK (on_menu_activate_open_volume_control),
  248. icon);
  249. gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
  250. gtk_widget_show_all (menu);
  251. gtk_menu_popup (GTK_MENU (menu),
  252. NULL,
  253. NULL,
  254. gtk_status_icon_position_menu,
  255. status_icon,
  256. button,
  257. activate_time);
  258. }
  259. static gboolean
  260. on_status_icon_scroll_event (GtkStatusIcon *status_icon,
  261. GdkEventScroll *event,
  262. GvcStreamStatusIcon *icon)
  263. {
  264. return gvc_channel_bar_scroll (GVC_CHANNEL_BAR (icon->priv->bar), event->direction);
  265. }
  266. static void
  267. gvc_icon_release_grab (GvcStreamStatusIcon *icon, GdkEventButton *event)
  268. {
  269. #if GTK_CHECK_VERSION (3, 0, 0)
  270. gdk_device_ungrab (event->device, event->time);
  271. #else
  272. GdkDisplay *display;
  273. display = gtk_widget_get_display (GTK_WIDGET (icon->priv->dock));
  274. gdk_display_keyboard_ungrab (display, event->time);
  275. gdk_display_pointer_ungrab (display, event->time);
  276. #endif
  277. gtk_grab_remove (icon->priv->dock);
  278. /* Hide again */
  279. gtk_widget_hide (icon->priv->dock);
  280. }
  281. static gboolean
  282. on_dock_button_press (GtkWidget *widget,
  283. GdkEventButton *event,
  284. GvcStreamStatusIcon *icon)
  285. {
  286. if (event->type == GDK_BUTTON_PRESS) {
  287. gvc_icon_release_grab (icon, event);
  288. return TRUE;
  289. }
  290. return FALSE;
  291. }
  292. static void
  293. popdown_dock (GvcStreamStatusIcon *icon)
  294. {
  295. GdkDisplay *display;
  296. display = gtk_widget_get_display (icon->priv->dock);
  297. #if GTK_CHECK_VERSION (3, 0, 0)
  298. GdkDeviceManager *manager = gdk_display_get_device_manager (display);
  299. gdk_device_ungrab (gdk_device_manager_get_client_pointer (manager),
  300. GDK_CURRENT_TIME);
  301. #else
  302. gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
  303. gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
  304. gtk_grab_remove (icon->priv->dock);
  305. #endif
  306. /* Hide again */
  307. gtk_widget_hide (icon->priv->dock);
  308. }
  309. /* This is called when the grab is broken for either the dock, or the scale */
  310. static void
  311. gvc_icon_grab_notify (GvcStreamStatusIcon *icon, gboolean was_grabbed)
  312. {
  313. if (was_grabbed != FALSE)
  314. return;
  315. if (gtk_widget_has_grab (icon->priv->dock) == FALSE)
  316. return;
  317. if (gtk_widget_is_ancestor (gtk_grab_get_current (), icon->priv->dock))
  318. return;
  319. popdown_dock (icon);
  320. }
  321. static void
  322. on_dock_grab_notify (GtkWidget *widget,
  323. gboolean was_grabbed,
  324. GvcStreamStatusIcon *icon)
  325. {
  326. gvc_icon_grab_notify (icon, was_grabbed);
  327. }
  328. static gboolean
  329. on_dock_grab_broken_event (GtkWidget *widget,
  330. gboolean was_grabbed,
  331. GvcStreamStatusIcon *icon)
  332. {
  333. gvc_icon_grab_notify (icon, FALSE);
  334. return FALSE;
  335. }
  336. static gboolean
  337. on_dock_key_release (GtkWidget *widget,
  338. GdkEventKey *event,
  339. GvcStreamStatusIcon *icon)
  340. {
  341. if (event->keyval == GDK_KEY_Escape) {
  342. popdown_dock (icon);
  343. return TRUE;
  344. }
  345. return TRUE;
  346. }
  347. static gboolean
  348. on_dock_scroll_event (GtkWidget *widget,
  349. GdkEventScroll *event,
  350. GvcStreamStatusIcon *icon)
  351. {
  352. /* Forward event to the status icon */
  353. on_status_icon_scroll_event (NULL, event, icon);
  354. return TRUE;
  355. }
  356. static void
  357. update_icon (GvcStreamStatusIcon *icon)
  358. {
  359. guint volume = 0;
  360. gdouble decibel = 0;
  361. guint normal = 0;
  362. gboolean muted = FALSE;
  363. guint n = 0;
  364. gchar *markup;
  365. const gchar *description;
  366. MateMixerStreamControlFlags flags;
  367. if (icon->priv->control == NULL) {
  368. /* Do not bother creating a tooltip for an unusable icon as it
  369. * has no practical use */
  370. gtk_status_icon_set_has_tooltip (GTK_STATUS_ICON (icon), FALSE);
  371. return;
  372. } else
  373. gtk_status_icon_set_has_tooltip (GTK_STATUS_ICON (icon), TRUE);
  374. flags = mate_mixer_stream_control_get_flags (icon->priv->control);
  375. if (flags & MATE_MIXER_STREAM_CONTROL_MUTE_READABLE)
  376. muted = mate_mixer_stream_control_get_mute (icon->priv->control);
  377. if (flags & MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE) {
  378. volume = mate_mixer_stream_control_get_volume (icon->priv->control);
  379. normal = mate_mixer_stream_control_get_normal_volume (icon->priv->control);
  380. /* Select an icon, they are expected to be sorted, the lowest index being
  381. * the mute icon and the rest being volume increments */
  382. if (volume <= 0 || muted)
  383. n = 0;
  384. else
  385. n = CLAMP (3 * volume / normal + 1, 1, 3);
  386. }
  387. if (flags & MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL)
  388. decibel = mate_mixer_stream_control_get_decibel (icon->priv->control);
  389. /* Apparently status icon will reset icon even if it doesn't change */
  390. if (icon->priv->current_icon != n) {
  391. gtk_status_icon_set_from_icon_name (GTK_STATUS_ICON (icon),
  392. icon->priv->icon_names[n]);
  393. icon->priv->current_icon = n;
  394. }
  395. description = mate_mixer_stream_control_get_label (icon->priv->control);
  396. if (muted) {
  397. markup = g_strdup_printf ("<b>%s: %s</b>\n<small>%s</small>",
  398. icon->priv->display_name,
  399. _("Muted"),
  400. description);
  401. } else if (flags & MATE_MIXER_STREAM_CONTROL_VOLUME_READABLE) {
  402. gdouble display_volume = 100 * volume / normal;
  403. if (flags & MATE_MIXER_STREAM_CONTROL_HAS_DECIBEL) {
  404. if (decibel > -MATE_MIXER_INFINITY) {
  405. markup = g_strdup_printf ("<b>%s: %.0f%%</b>\n"
  406. "<small>%0.2f dB\n%s</small>",
  407. icon->priv->display_name,
  408. display_volume,
  409. decibel,
  410. description);
  411. } else {
  412. markup = g_strdup_printf ("<b>%s: %.0f%%</b>\n"
  413. "<small>-&#8734; dB\n%s</small>",
  414. icon->priv->display_name,
  415. display_volume,
  416. description);
  417. }
  418. } else {
  419. markup = g_strdup_printf ("<b>%s: %.0f%%</b>\n<small>%s</small>",
  420. icon->priv->display_name,
  421. display_volume,
  422. description);
  423. }
  424. } else {
  425. markup = g_strdup_printf ("<b>%s</b>\n<small>%s</small>",
  426. icon->priv->display_name,
  427. description);
  428. }
  429. gtk_status_icon_set_tooltip_markup (GTK_STATUS_ICON (icon), markup);
  430. g_free (markup);
  431. }
  432. void
  433. gvc_stream_status_icon_set_icon_names (GvcStreamStatusIcon *icon,
  434. const gchar **names)
  435. {
  436. g_return_if_fail (GVC_IS_STREAM_STATUS_ICON (icon));
  437. g_return_if_fail (names != NULL && *names != NULL);
  438. if (G_UNLIKELY (g_strv_length ((gchar **) names) != 4)) {
  439. g_warn_if_reached ();
  440. return;
  441. }
  442. g_strfreev (icon->priv->icon_names);
  443. icon->priv->icon_names = g_strdupv ((gchar **) names);
  444. /* Set the first icon as the initial one, the icon may be immediately
  445. * updated or not depending on whether a stream is available */
  446. gtk_status_icon_set_from_icon_name (GTK_STATUS_ICON (icon), names[0]);
  447. update_icon (icon);
  448. g_object_notify_by_pspec (G_OBJECT (icon), properties[PROP_ICON_NAMES]);
  449. }
  450. static void
  451. on_stream_control_volume_notify (MateMixerStreamControl *control,
  452. GParamSpec *pspec,
  453. GvcStreamStatusIcon *icon)
  454. {
  455. update_icon (icon);
  456. }
  457. static void
  458. on_stream_control_mute_notify (MateMixerStreamControl *control,
  459. GParamSpec *pspec,
  460. GvcStreamStatusIcon *icon)
  461. {
  462. update_icon (icon);
  463. }
  464. void
  465. gvc_stream_status_icon_set_display_name (GvcStreamStatusIcon *icon,
  466. const gchar *name)
  467. {
  468. g_return_if_fail (GVC_STREAM_STATUS_ICON (icon));
  469. g_free (icon->priv->display_name);
  470. icon->priv->display_name = g_strdup (name);
  471. update_icon (icon);
  472. g_object_notify_by_pspec (G_OBJECT (icon), properties[PROP_DISPLAY_NAME]);
  473. }
  474. void
  475. gvc_stream_status_icon_set_control (GvcStreamStatusIcon *icon,
  476. MateMixerStreamControl *control)
  477. {
  478. g_return_if_fail (GVC_STREAM_STATUS_ICON (icon));
  479. if (icon->priv->control == control)
  480. return;
  481. if (control != NULL)
  482. g_object_ref (control);
  483. if (icon->priv->control != NULL) {
  484. g_signal_handlers_disconnect_by_func (G_OBJECT (icon->priv->control),
  485. G_CALLBACK (on_stream_control_volume_notify),
  486. icon);
  487. g_signal_handlers_disconnect_by_func (G_OBJECT (icon->priv->control),
  488. G_CALLBACK (on_stream_control_mute_notify),
  489. icon);
  490. g_object_unref (icon->priv->control);
  491. }
  492. icon->priv->control = control;
  493. if (icon->priv->control != NULL) {
  494. g_signal_connect (G_OBJECT (icon->priv->control),
  495. "notify::volume",
  496. G_CALLBACK (on_stream_control_volume_notify),
  497. icon);
  498. g_signal_connect (G_OBJECT (icon->priv->control),
  499. "notify::mute",
  500. G_CALLBACK (on_stream_control_mute_notify),
  501. icon);
  502. // XXX when no stream set some default icon and "unset" dock
  503. update_icon (icon);
  504. }
  505. gvc_channel_bar_set_control (GVC_CHANNEL_BAR (icon->priv->bar), icon->priv->control);
  506. g_object_notify_by_pspec (G_OBJECT (icon), properties[PROP_CONTROL]);
  507. }
  508. static void
  509. gvc_stream_status_icon_set_property (GObject *object,
  510. guint prop_id,
  511. const GValue *value,
  512. GParamSpec *pspec)
  513. {
  514. GvcStreamStatusIcon *self = GVC_STREAM_STATUS_ICON (object);
  515. switch (prop_id) {
  516. case PROP_CONTROL:
  517. gvc_stream_status_icon_set_control (self, g_value_get_object (value));
  518. break;
  519. case PROP_DISPLAY_NAME:
  520. gvc_stream_status_icon_set_display_name (self, g_value_get_string (value));
  521. break;
  522. case PROP_ICON_NAMES:
  523. gvc_stream_status_icon_set_icon_names (self, g_value_get_boxed (value));
  524. break;
  525. default:
  526. G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  527. break;
  528. }
  529. }
  530. static void
  531. gvc_stream_status_icon_get_property (GObject *object,
  532. guint prop_id,
  533. GValue *value,
  534. GParamSpec *pspec)
  535. {
  536. GvcStreamStatusIcon *self = GVC_STREAM_STATUS_ICON (object);
  537. switch (prop_id) {
  538. case PROP_CONTROL:
  539. g_value_set_object (value, self->priv->control);
  540. break;
  541. case PROP_DISPLAY_NAME:
  542. g_value_set_string (value, self->priv->display_name);
  543. break;
  544. case PROP_ICON_NAMES:
  545. g_value_set_boxed (value, self->priv->icon_names);
  546. break;
  547. default:
  548. G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  549. break;
  550. }
  551. }
  552. static void
  553. gvc_stream_status_icon_dispose (GObject *object)
  554. {
  555. GvcStreamStatusIcon *icon = GVC_STREAM_STATUS_ICON (object);
  556. if (icon->priv->dock != NULL) {
  557. gtk_widget_destroy (icon->priv->dock);
  558. icon->priv->dock = NULL;
  559. }
  560. g_clear_object (&icon->priv->control);
  561. G_OBJECT_CLASS (gvc_stream_status_icon_parent_class)->dispose (object);
  562. }
  563. static void
  564. gvc_stream_status_icon_class_init (GvcStreamStatusIconClass *klass)
  565. {
  566. GObjectClass *object_class = G_OBJECT_CLASS (klass);
  567. object_class->finalize = gvc_stream_status_icon_finalize;
  568. object_class->dispose = gvc_stream_status_icon_dispose;
  569. object_class->set_property = gvc_stream_status_icon_set_property;
  570. object_class->get_property = gvc_stream_status_icon_get_property;
  571. properties[PROP_CONTROL] =
  572. g_param_spec_object ("control",
  573. "Control",
  574. "MateMixer stream control",
  575. MATE_MIXER_TYPE_STREAM_CONTROL,
  576. G_PARAM_READWRITE |
  577. G_PARAM_CONSTRUCT |
  578. G_PARAM_STATIC_STRINGS);
  579. properties[PROP_DISPLAY_NAME] =
  580. g_param_spec_string ("display-name",
  581. "Display name",
  582. "Name to display for this stream",
  583. NULL,
  584. G_PARAM_READWRITE |
  585. G_PARAM_CONSTRUCT |
  586. G_PARAM_STATIC_STRINGS);
  587. properties[PROP_ICON_NAMES] =
  588. g_param_spec_boxed ("icon-names",
  589. "Icon names",
  590. "Name of icon to display for this stream",
  591. G_TYPE_STRV,
  592. G_PARAM_READWRITE |
  593. G_PARAM_CONSTRUCT |
  594. G_PARAM_STATIC_STRINGS);
  595. g_object_class_install_properties (object_class, N_PROPERTIES, properties);
  596. g_type_class_add_private (klass, sizeof (GvcStreamStatusIconPrivate));
  597. }
  598. static void
  599. on_status_icon_visible_notify (GvcStreamStatusIcon *icon)
  600. {
  601. if (gtk_status_icon_get_visible (GTK_STATUS_ICON (icon)) == FALSE)
  602. gtk_widget_hide (icon->priv->dock);
  603. }
  604. static void
  605. gvc_stream_status_icon_init (GvcStreamStatusIcon *icon)
  606. {
  607. GtkWidget *frame;
  608. GtkWidget *box;
  609. icon->priv = GVC_STREAM_STATUS_ICON_GET_PRIVATE (icon);
  610. g_signal_connect (G_OBJECT (icon),
  611. "activate",
  612. G_CALLBACK (on_status_icon_activate),
  613. icon);
  614. g_signal_connect (G_OBJECT (icon),
  615. "button-press-event",
  616. G_CALLBACK (on_status_icon_button_press),
  617. icon);
  618. g_signal_connect (G_OBJECT (icon),
  619. "popup-menu",
  620. G_CALLBACK (on_status_icon_popup_menu),
  621. icon);
  622. g_signal_connect (G_OBJECT (icon),
  623. "scroll-event",
  624. G_CALLBACK (on_status_icon_scroll_event),
  625. icon);
  626. g_signal_connect (G_OBJECT (icon),
  627. "notify::visible",
  628. G_CALLBACK (on_status_icon_visible_notify),
  629. NULL);
  630. /* Create the dock window */
  631. icon->priv->dock = gtk_window_new (GTK_WINDOW_POPUP);
  632. gtk_window_set_decorated (GTK_WINDOW (icon->priv->dock), FALSE);
  633. g_signal_connect (G_OBJECT (icon->priv->dock),
  634. "button-press-event",
  635. G_CALLBACK (on_dock_button_press),
  636. icon);
  637. g_signal_connect (G_OBJECT (icon->priv->dock),
  638. "key-release-event",
  639. G_CALLBACK (on_dock_key_release),
  640. icon);
  641. g_signal_connect (G_OBJECT (icon->priv->dock),
  642. "scroll-event",
  643. G_CALLBACK (on_dock_scroll_event),
  644. icon);
  645. g_signal_connect (G_OBJECT (icon->priv->dock),
  646. "grab-notify",
  647. G_CALLBACK (on_dock_grab_notify),
  648. icon);
  649. g_signal_connect (G_OBJECT (icon->priv->dock),
  650. "grab-broken-event",
  651. G_CALLBACK (on_dock_grab_broken_event),
  652. icon);
  653. frame = gtk_frame_new (NULL);
  654. gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
  655. gtk_container_add (GTK_CONTAINER (icon->priv->dock), frame);
  656. icon->priv->bar = gvc_channel_bar_new (NULL);
  657. gvc_channel_bar_set_orientation (GVC_CHANNEL_BAR (icon->priv->bar),
  658. GTK_ORIENTATION_VERTICAL);
  659. #if GTK_CHECK_VERSION (3, 0, 0)
  660. /* Set volume control frame, slider and toplevel window to follow panel theme */
  661. GtkWidget *toplevel = gtk_widget_get_toplevel (icon->priv->dock);
  662. GtkStyleContext *context;
  663. context = gtk_widget_get_style_context (GTK_WIDGET(toplevel));
  664. gtk_style_context_add_class(context,"mate-panel-applet-slider");
  665. /* Make transparency possible in gtk3 theme */
  666. GdkScreen *screen = gtk_widget_get_screen(GTK_WIDGET(toplevel));
  667. GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
  668. gtk_widget_set_visual(GTK_WIDGET(toplevel), visual);
  669. #endif
  670. #if GTK_CHECK_VERSION (3, 0, 0)
  671. box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
  672. #else
  673. box = gtk_vbox_new (FALSE, 6);
  674. #endif
  675. gtk_container_set_border_width (GTK_CONTAINER (box), 2);
  676. gtk_container_add (GTK_CONTAINER (frame), box);
  677. gtk_box_pack_start (GTK_BOX (box), icon->priv->bar, TRUE, FALSE, 0);
  678. }
  679. static void
  680. gvc_stream_status_icon_finalize (GObject *object)
  681. {
  682. GvcStreamStatusIcon *icon;
  683. icon = GVC_STREAM_STATUS_ICON (object);
  684. g_strfreev (icon->priv->icon_names);
  685. G_OBJECT_CLASS (gvc_stream_status_icon_parent_class)->finalize (object);
  686. }
  687. GvcStreamStatusIcon *
  688. gvc_stream_status_icon_new (MateMixerStreamControl *control,
  689. const gchar **icon_names)
  690. {
  691. return g_object_new (GVC_TYPE_STREAM_STATUS_ICON,
  692. "control", control,
  693. "icon-names", icon_names,
  694. NULL);
  695. }