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.
 
 
 
 
 
 

358 lines
13 KiB

  1. /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
  2. *
  3. * Copyright (C) 2008 Red Hat, Inc.
  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 "config.h"
  22. #include <string.h>
  23. #include <glib.h>
  24. #include <glib/gi18n.h>
  25. #include <glib-object.h>
  26. #include <gtk/gtk.h>
  27. #include <libmatemixer/matemixer.h>
  28. #include "gvc-applet.h"
  29. #include "gvc-stream-status-icon.h"
  30. #define GVC_APPLET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_APPLET, GvcAppletPrivate))
  31. static const gchar *icon_names_output[] = {
  32. "audio-volume-muted",
  33. "audio-volume-low",
  34. "audio-volume-medium",
  35. "audio-volume-high",
  36. NULL
  37. };
  38. static const gchar *icon_names_input[] = {
  39. "audio-input-microphone-muted",
  40. "audio-input-microphone-low",
  41. "audio-input-microphone-medium",
  42. "audio-input-microphone-high",
  43. NULL
  44. };
  45. struct _GvcAppletPrivate
  46. {
  47. GvcStreamStatusIcon *icon_input;
  48. GvcStreamStatusIcon *icon_output;
  49. gboolean running;
  50. MateMixerContext *context;
  51. MateMixerStream *input;
  52. };
  53. static void gvc_applet_class_init (GvcAppletClass *klass);
  54. static void gvc_applet_init (GvcApplet *applet);
  55. G_DEFINE_TYPE (GvcApplet, gvc_applet, G_TYPE_OBJECT)
  56. static void
  57. update_icon_input (GvcApplet *applet)
  58. {
  59. MateMixerStreamControl *control = NULL;
  60. gboolean show = FALSE;
  61. /* Enable the input icon in case there is an input stream present and there
  62. * is a non-mixer application using the input */
  63. if (applet->priv->input != NULL) {
  64. const gchar *app_id;
  65. const GList *inputs =
  66. mate_mixer_stream_list_controls (applet->priv->input);
  67. control = mate_mixer_stream_get_default_control (applet->priv->input);
  68. while (inputs != NULL) {
  69. MateMixerStreamControl *input =
  70. MATE_MIXER_STREAM_CONTROL (inputs->data);
  71. MateMixerStreamControlRole role =
  72. mate_mixer_stream_control_get_role (input);
  73. if (role == MATE_MIXER_STREAM_CONTROL_ROLE_APPLICATION) {
  74. MateMixerAppInfo *app_info =
  75. mate_mixer_stream_control_get_app_info (input);
  76. app_id = mate_mixer_app_info_get_id (app_info);
  77. if (app_id == NULL) {
  78. /* A recording application which has no
  79. * identifier set */
  80. g_debug ("Found a recording application control %s",
  81. mate_mixer_stream_control_get_label (input));
  82. if G_UNLIKELY (control == NULL) {
  83. /* In the unlikely case when there is no
  84. * default input control, use the application
  85. * control for the icon */
  86. control = input;
  87. }
  88. show = TRUE;
  89. break;
  90. }
  91. if (strcmp (app_id, "org.mate.VolumeControl") != 0 &&
  92. strcmp (app_id, "org.gnome.VolumeControl") != 0 &&
  93. strcmp (app_id, "org.PulseAudio.pavucontrol") != 0) {
  94. g_debug ("Found a recording application %s", app_id);
  95. if G_UNLIKELY (control == NULL)
  96. control = input;
  97. show = TRUE;
  98. break;
  99. }
  100. }
  101. inputs = inputs->next;
  102. }
  103. if (show == TRUE)
  104. g_debug ("Input icon enabled");
  105. else
  106. g_debug ("There is no recording application, input icon disabled");
  107. }
  108. gvc_stream_status_icon_set_control (applet->priv->icon_input, control);
  109. gtk_status_icon_set_visible (GTK_STATUS_ICON (applet->priv->icon_input), show);
  110. }
  111. static void
  112. update_icon_output (GvcApplet *applet)
  113. {
  114. MateMixerStream *stream;
  115. MateMixerStreamControl *control = NULL;
  116. stream = mate_mixer_context_get_default_output_stream (applet->priv->context);
  117. if (stream != NULL)
  118. control = mate_mixer_stream_get_default_control (stream);
  119. gvc_stream_status_icon_set_control (applet->priv->icon_output, control);
  120. if (control != NULL) {
  121. g_debug ("Output icon enabled");
  122. gtk_status_icon_set_visible (GTK_STATUS_ICON (applet->priv->icon_output),
  123. TRUE);
  124. }
  125. else {
  126. g_debug ("There is no output stream/control, output icon disabled");
  127. gtk_status_icon_set_visible (GTK_STATUS_ICON (applet->priv->icon_output),
  128. FALSE);
  129. }
  130. }
  131. static void
  132. on_input_stream_control_added (MateMixerStream *stream,
  133. const gchar *name,
  134. GvcApplet *applet)
  135. {
  136. MateMixerStreamControl *control;
  137. control = mate_mixer_stream_get_control (stream, name);
  138. if G_LIKELY (control != NULL) {
  139. MateMixerStreamControlRole role =
  140. mate_mixer_stream_control_get_role (control);
  141. /* Non-application input control doesn't affect the icon */
  142. if (role != MATE_MIXER_STREAM_CONTROL_ROLE_APPLICATION)
  143. return;
  144. }
  145. /* Either an application control has been added or we couldn't
  146. * read the control, this shouldn't happen but let's revalidate the
  147. * icon to be sure if it does */
  148. update_icon_input (applet);
  149. }
  150. static void
  151. on_input_stream_control_removed (MateMixerStream *stream,
  152. const gchar *name,
  153. GvcApplet *applet)
  154. {
  155. /* The removed stream could be an application input, which may cause
  156. * the input status icon to disappear */
  157. update_icon_input (applet);
  158. }
  159. static gboolean
  160. update_default_input_stream (GvcApplet *applet)
  161. {
  162. MateMixerStream *stream;
  163. stream = mate_mixer_context_get_default_input_stream (applet->priv->context);
  164. if (stream == applet->priv->input)
  165. return FALSE;
  166. /* The input stream has changed */
  167. if (applet->priv->input != NULL) {
  168. g_signal_handlers_disconnect_by_data (G_OBJECT (applet->priv->input),
  169. applet);
  170. g_object_unref (applet->priv->input);
  171. }
  172. applet->priv->input = g_object_ref (stream);
  173. if (applet->priv->input != NULL) {
  174. g_signal_connect (G_OBJECT (applet->priv->input),
  175. "control-added",
  176. G_CALLBACK (on_input_stream_control_added),
  177. applet);
  178. g_signal_connect (G_OBJECT (applet->priv->input),
  179. "control-removed",
  180. G_CALLBACK (on_input_stream_control_removed),
  181. applet);
  182. }
  183. /* Return TRUE if the default input stream has changed */
  184. return TRUE;
  185. }
  186. static void
  187. on_context_state_notify (MateMixerContext *context,
  188. GParamSpec *pspec,
  189. GvcApplet *applet)
  190. {
  191. MateMixerState state = mate_mixer_context_get_state (context);
  192. switch (state) {
  193. case MATE_MIXER_STATE_FAILED:
  194. g_warning ("Failed to connect to a sound system");
  195. break;
  196. case MATE_MIXER_STATE_READY:
  197. update_default_input_stream (applet);
  198. /* Each status change may affect the visibility of the icons */
  199. update_icon_output (applet);
  200. update_icon_input (applet);
  201. break;
  202. default:
  203. break;
  204. }
  205. }
  206. static void
  207. on_context_default_input_stream_notify (MateMixerContext *context,
  208. GParamSpec *pspec,
  209. GvcApplet *applet)
  210. {
  211. if (update_default_input_stream (applet) == FALSE)
  212. return;
  213. update_icon_input (applet);
  214. }
  215. static void
  216. on_context_default_output_stream_notify (MateMixerContext *control,
  217. GParamSpec *pspec,
  218. GvcApplet *applet)
  219. {
  220. update_icon_output (applet);
  221. }
  222. void
  223. gvc_applet_start (GvcApplet *applet)
  224. {
  225. g_return_if_fail (GVC_IS_APPLET (applet));
  226. if G_UNLIKELY (applet->priv->running == TRUE)
  227. return;
  228. if G_UNLIKELY (mate_mixer_context_open (applet->priv->context) == FALSE) {
  229. /* Normally this should never happen, in the worst case we
  230. * should end up with the Null module */
  231. g_warning ("Failed to connect to a sound system");
  232. }
  233. g_debug ("Applet has been started");
  234. applet->priv->running = TRUE;
  235. }
  236. static void
  237. gvc_applet_dispose (GObject *object)
  238. {
  239. GvcApplet *applet = GVC_APPLET (object);
  240. if (applet->priv->input != NULL) {
  241. g_signal_handlers_disconnect_by_data (G_OBJECT (applet->priv->input),
  242. applet);
  243. g_clear_object (&applet->priv->input);
  244. }
  245. g_clear_object (&applet->priv->context);
  246. g_clear_object (&applet->priv->icon_input);
  247. g_clear_object (&applet->priv->icon_output);
  248. G_OBJECT_CLASS (gvc_applet_parent_class)->dispose (object);
  249. }
  250. static void
  251. gvc_applet_class_init (GvcAppletClass *klass)
  252. {
  253. GObjectClass *object_class = G_OBJECT_CLASS (klass);
  254. object_class->dispose = gvc_applet_dispose;
  255. g_type_class_add_private (klass, sizeof (GvcAppletPrivate));
  256. }
  257. static void
  258. gvc_applet_init (GvcApplet *applet)
  259. {
  260. applet->priv = GVC_APPLET_GET_PRIVATE (applet);
  261. applet->priv->icon_input = gvc_stream_status_icon_new (NULL, icon_names_input);
  262. applet->priv->icon_output = gvc_stream_status_icon_new (NULL, icon_names_output);
  263. gvc_stream_status_icon_set_display_name (applet->priv->icon_input, _("Input"));
  264. gvc_stream_status_icon_set_display_name (applet->priv->icon_output, _("Output"));
  265. gtk_status_icon_set_title (GTK_STATUS_ICON (applet->priv->icon_input),
  266. _("Microphone Volume"));
  267. gtk_status_icon_set_title (GTK_STATUS_ICON (applet->priv->icon_output),
  268. _("Sound Output Volume"));
  269. applet->priv->context = mate_mixer_context_new ();
  270. mate_mixer_context_set_app_name (applet->priv->context,
  271. _("MATE Volume Control Applet"));
  272. mate_mixer_context_set_app_id (applet->priv->context, GVC_APPLET_DBUS_NAME);
  273. mate_mixer_context_set_app_version (applet->priv->context, VERSION);
  274. mate_mixer_context_set_app_icon (applet->priv->context, "multimedia-volume-control");
  275. g_signal_connect (G_OBJECT (applet->priv->context),
  276. "notify::state",
  277. G_CALLBACK (on_context_state_notify),
  278. applet);
  279. g_signal_connect (G_OBJECT (applet->priv->context),
  280. "notify::default-input-stream",
  281. G_CALLBACK (on_context_default_input_stream_notify),
  282. applet);
  283. g_signal_connect (G_OBJECT (applet->priv->context),
  284. "notify::default-output-stream",
  285. G_CALLBACK (on_context_default_output_stream_notify),
  286. applet);
  287. }
  288. GvcApplet *
  289. gvc_applet_new (void)
  290. {
  291. return g_object_new (GVC_TYPE_APPLET, NULL);
  292. }