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.
 
 
 
 
 
 

2430 lines
91 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 "config.h"
  22. #include <glib.h>
  23. #include <glib/gi18n.h>
  24. #include <glib-object.h>
  25. #include <gdk/gdk.h>
  26. #include <gdk/gdkkeysyms.h>
  27. #include <gtk/gtk.h>
  28. #include <libmatemixer/matemixer.h>
  29. #include "gvc-channel-bar.h"
  30. #include "gvc-balance-bar.h"
  31. #include "gvc-combo-box.h"
  32. #include "gvc-mixer-dialog.h"
  33. #include "gvc-sound-theme-chooser.h"
  34. #include "gvc-level-bar.h"
  35. #include "gvc-speaker-test.h"
  36. #include "gvc-utils.h"
  37. #define GVC_MIXER_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_DIALOG, GvcMixerDialogPrivate))
  38. struct _GvcMixerDialogPrivate
  39. {
  40. MateMixerContext *context;
  41. MateMixerBackendFlags backend_flags;
  42. GHashTable *bars;
  43. GtkWidget *notebook;
  44. GtkWidget *output_bar;
  45. GtkWidget *input_bar;
  46. GtkWidget *input_level_bar;
  47. GtkWidget *effects_bar;
  48. GtkWidget *output_stream_box;
  49. GtkWidget *hw_box;
  50. GtkWidget *hw_treeview;
  51. GtkWidget *hw_settings_box;
  52. GtkWidget *hw_profile_combo;
  53. GtkWidget *input_box;
  54. GtkWidget *output_box;
  55. GtkWidget *applications_box;
  56. GtkWidget *applications_window;
  57. GtkWidget *no_apps_label;
  58. GtkWidget *output_treeview;
  59. GtkWidget *output_settings_frame;
  60. GtkWidget *output_settings_box;
  61. GtkWidget *output_balance_bar;
  62. GtkWidget *output_fade_bar;
  63. GtkWidget *output_lfe_bar;
  64. GtkWidget *output_port_combo;
  65. GtkWidget *input_treeview;
  66. GtkWidget *input_port_combo;
  67. GtkWidget *input_settings_box;
  68. GtkSizeGroup *size_group;
  69. gdouble last_input_peak;
  70. guint num_apps;
  71. };
  72. enum {
  73. ICON_COLUMN,
  74. NAME_COLUMN,
  75. LABEL_COLUMN,
  76. ACTIVE_COLUMN,
  77. SPEAKERS_COLUMN,
  78. NUM_COLUMNS
  79. };
  80. enum {
  81. HW_ICON_COLUMN,
  82. HW_NAME_COLUMN,
  83. HW_LABEL_COLUMN,
  84. HW_STATUS_COLUMN,
  85. HW_PROFILE_COLUMN,
  86. HW_NUM_COLUMNS
  87. };
  88. enum {
  89. PAGE_EFFECTS,
  90. PAGE_HARDWARE,
  91. PAGE_INPUT,
  92. PAGE_OUTPUT,
  93. PAGE_APPLICATIONS
  94. };
  95. enum {
  96. PROP_0,
  97. PROP_CONTEXT
  98. };
  99. static const guint tab_accel_keys[] = {
  100. GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5
  101. };
  102. static void gvc_mixer_dialog_class_init (GvcMixerDialogClass *klass);
  103. static void gvc_mixer_dialog_init (GvcMixerDialog *dialog);
  104. static void gvc_mixer_dialog_finalize (GObject *object);
  105. static void add_stream (GvcMixerDialog *dialog,
  106. MateMixerStream *stream);
  107. static void add_application_control (GvcMixerDialog *dialog,
  108. MateMixerStreamControl *control);
  109. static void remove_stream (GvcMixerDialog *dialog,
  110. const gchar *name);
  111. static void remove_application_control (GvcMixerDialog *dialog,
  112. const gchar *name);
  113. static void bar_set_stream (GvcMixerDialog *dialog,
  114. GtkWidget *bar,
  115. MateMixerStream *stream);
  116. static void bar_set_stream_control (GvcMixerDialog *dialog,
  117. GtkWidget *bar,
  118. MateMixerStreamControl *control);
  119. G_DEFINE_TYPE (GvcMixerDialog, gvc_mixer_dialog, GTK_TYPE_DIALOG)
  120. static MateMixerSwitch *
  121. find_stream_port_switch (MateMixerStream *stream)
  122. {
  123. const GList *switches;
  124. switches = mate_mixer_stream_list_switches (stream);
  125. while (switches != NULL) {
  126. MateMixerStreamSwitch *swtch = MATE_MIXER_STREAM_SWITCH (switches->data);
  127. if (!MATE_MIXER_IS_STREAM_TOGGLE (swtch) &&
  128. mate_mixer_stream_switch_get_role (swtch) == MATE_MIXER_STREAM_SWITCH_ROLE_PORT)
  129. return MATE_MIXER_SWITCH (swtch);
  130. switches = switches->next;
  131. }
  132. return NULL;
  133. }
  134. static MateMixerSwitch *
  135. find_device_profile_switch (MateMixerDevice *device)
  136. {
  137. const GList *switches;
  138. switches = mate_mixer_device_list_switches (device);
  139. while (switches != NULL) {
  140. MateMixerDeviceSwitch *swtch = MATE_MIXER_DEVICE_SWITCH (switches->data);
  141. if (mate_mixer_device_switch_get_role (swtch) == MATE_MIXER_DEVICE_SWITCH_ROLE_PROFILE)
  142. return MATE_MIXER_SWITCH (swtch);
  143. switches = switches->next;
  144. }
  145. return NULL;
  146. }
  147. static MateMixerStream *
  148. find_device_test_stream (GvcMixerDialog *dialog, MateMixerDevice *device)
  149. {
  150. const GList *streams;
  151. streams = mate_mixer_device_list_streams (device);
  152. while (streams != NULL) {
  153. MateMixerStream *stream;
  154. MateMixerDirection direction;
  155. stream = MATE_MIXER_STREAM (streams->data);
  156. direction = mate_mixer_stream_get_direction (stream);
  157. if (direction == MATE_MIXER_DIRECTION_OUTPUT) {
  158. MateMixerStreamControl *control;
  159. control = mate_mixer_stream_get_default_control (stream);
  160. if (mate_mixer_stream_control_get_num_channels (control) > 0)
  161. return stream;
  162. }
  163. streams = streams->next;
  164. }
  165. return FALSE;
  166. }
  167. static gboolean
  168. find_tree_item_by_name (GtkTreeModel *model,
  169. const gchar *name,
  170. guint column,
  171. GtkTreeIter *iter)
  172. {
  173. gboolean found = FALSE;
  174. if (!gtk_tree_model_get_iter_first (model, iter))
  175. return FALSE;
  176. do {
  177. gchar *n;
  178. gtk_tree_model_get (model, iter, column, &n, -1);
  179. if (!g_strcmp0 (name, n))
  180. found = TRUE;
  181. g_free (n);
  182. } while (!found && gtk_tree_model_iter_next (model, iter));
  183. return found;
  184. }
  185. static void
  186. update_default_tree_item (GvcMixerDialog *dialog,
  187. GtkTreeModel *model,
  188. MateMixerStream *stream)
  189. {
  190. GtkTreeIter iter;
  191. const gchar *name = NULL;
  192. if (gtk_tree_model_get_iter_first (model, &iter) == FALSE)
  193. return;
  194. /* The supplied stream is the default, or the selected item. Traverse
  195. * the item list and mark each item as being selected or not. Also do not
  196. * presume some known stream is selected and allow NULL here. */
  197. if (stream != NULL)
  198. name = mate_mixer_stream_get_name (stream);
  199. do {
  200. gchar *n;
  201. gtk_tree_model_get (model, &iter,
  202. NAME_COLUMN, &n,
  203. -1);
  204. gtk_list_store_set (GTK_LIST_STORE (model),
  205. &iter,
  206. ACTIVE_COLUMN, !g_strcmp0 (name, n),
  207. -1);
  208. g_free (n);
  209. } while (gtk_tree_model_iter_next (model, &iter));
  210. }
  211. static void
  212. update_output_settings (GvcMixerDialog *dialog)
  213. {
  214. MateMixerStream *stream;
  215. MateMixerStreamControl *control;
  216. MateMixerStreamControlFlags flags;
  217. MateMixerSwitch *port_switch;
  218. gboolean has_settings = FALSE;
  219. g_debug ("Updating output settings");
  220. if (dialog->priv->output_balance_bar != NULL) {
  221. gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box),
  222. dialog->priv->output_balance_bar);
  223. dialog->priv->output_balance_bar = NULL;
  224. }
  225. if (dialog->priv->output_fade_bar != NULL) {
  226. gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box),
  227. dialog->priv->output_fade_bar);
  228. dialog->priv->output_fade_bar = NULL;
  229. }
  230. if (dialog->priv->output_lfe_bar != NULL) {
  231. gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box),
  232. dialog->priv->output_lfe_bar);
  233. dialog->priv->output_lfe_bar = NULL;
  234. }
  235. if (dialog->priv->output_port_combo != NULL) {
  236. gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box),
  237. dialog->priv->output_port_combo);
  238. dialog->priv->output_port_combo = NULL;
  239. }
  240. /* Get the control currently associated with the output slider */
  241. control = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->output_bar));
  242. if (control == NULL) {
  243. g_debug ("There is no control for the default output stream");
  244. gtk_widget_hide (dialog->priv->output_settings_frame);
  245. return;
  246. }
  247. flags = mate_mixer_stream_control_get_flags (control);
  248. /* Enable balance bar if it is available */
  249. if (flags & MATE_MIXER_STREAM_CONTROL_CAN_BALANCE) {
  250. dialog->priv->output_balance_bar =
  251. gvc_balance_bar_new (control, BALANCE_TYPE_RL);
  252. gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (dialog->priv->output_balance_bar),
  253. dialog->priv->size_group,
  254. TRUE);
  255. gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box),
  256. dialog->priv->output_balance_bar,
  257. FALSE, FALSE, 6);
  258. gtk_widget_show (dialog->priv->output_balance_bar);
  259. has_settings = TRUE;
  260. }
  261. /* Enable fade bar if it is available */
  262. if (flags & MATE_MIXER_STREAM_CONTROL_CAN_FADE) {
  263. dialog->priv->output_fade_bar =
  264. gvc_balance_bar_new (control, BALANCE_TYPE_FR);
  265. gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (dialog->priv->output_fade_bar),
  266. dialog->priv->size_group,
  267. TRUE);
  268. gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box),
  269. dialog->priv->output_fade_bar,
  270. FALSE, FALSE, 6);
  271. gtk_widget_show (dialog->priv->output_fade_bar);
  272. has_settings = TRUE;
  273. }
  274. /* Enable subwoofer volume bar if subwoofer is available */
  275. if (mate_mixer_stream_control_has_channel_position (control, MATE_MIXER_CHANNEL_LFE)) {
  276. dialog->priv->output_lfe_bar =
  277. gvc_balance_bar_new (control, BALANCE_TYPE_LFE);
  278. gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (dialog->priv->output_lfe_bar),
  279. dialog->priv->size_group,
  280. TRUE);
  281. gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box),
  282. dialog->priv->output_lfe_bar,
  283. FALSE, FALSE, 6);
  284. gtk_widget_show (dialog->priv->output_lfe_bar);
  285. has_settings = TRUE;
  286. }
  287. /* Get owning stream of the control */
  288. stream = mate_mixer_stream_control_get_stream (control);
  289. if (G_UNLIKELY (stream == NULL))
  290. return;
  291. /* Enable the port selector if the stream has one */
  292. port_switch = find_stream_port_switch (stream);
  293. if (port_switch != NULL) {
  294. dialog->priv->output_port_combo =
  295. gvc_combo_box_new (port_switch, _("Co_nnector:"));
  296. gvc_combo_box_set_size_group (GVC_COMBO_BOX (dialog->priv->output_port_combo),
  297. dialog->priv->size_group,
  298. FALSE);
  299. gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box),
  300. dialog->priv->output_port_combo,
  301. TRUE, FALSE, 6);
  302. gtk_widget_show (dialog->priv->output_port_combo);
  303. has_settings = TRUE;
  304. }
  305. if (has_settings == TRUE)
  306. gtk_widget_show (dialog->priv->output_settings_frame);
  307. else
  308. gtk_widget_hide (dialog->priv->output_settings_frame);
  309. }
  310. static void
  311. set_output_stream (GvcMixerDialog *dialog, MateMixerStream *stream)
  312. {
  313. GtkTreeModel *model;
  314. MateMixerSwitch *swtch;
  315. MateMixerStreamControl *control;
  316. control = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->output_bar));
  317. if (control != NULL) {
  318. /* Disconnect port switch of the previous stream */
  319. if (dialog->priv->output_port_combo != NULL) {
  320. swtch = g_object_get_data (G_OBJECT (dialog->priv->output_port_combo),
  321. "switch");
  322. if (swtch != NULL)
  323. g_signal_handlers_disconnect_by_data (G_OBJECT (swtch),
  324. dialog);
  325. }
  326. }
  327. bar_set_stream (dialog, dialog->priv->output_bar, stream);
  328. if (stream != NULL) {
  329. const GList *controls;
  330. controls = mate_mixer_context_list_stored_controls (dialog->priv->context);
  331. /* Move all stored controls to the newly selected default stream */
  332. while (controls != NULL) {
  333. MateMixerStream *parent;
  334. MateMixerStreamControl *control;
  335. control = MATE_MIXER_STREAM_CONTROL (controls->data);
  336. parent = mate_mixer_stream_control_get_stream (control);
  337. /* Prefer streamless controls to stay the way they are, forcing them to
  338. * a particular owning stream would be wrong for eg. event controls */
  339. if (parent != NULL && parent != stream) {
  340. MateMixerDirection direction =
  341. mate_mixer_stream_get_direction (parent);
  342. if (direction == MATE_MIXER_DIRECTION_OUTPUT)
  343. mate_mixer_stream_control_set_stream (control, stream);
  344. }
  345. controls = controls->next;
  346. }
  347. }
  348. model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview));
  349. update_default_tree_item (dialog, model, stream);
  350. update_output_settings (dialog);
  351. }
  352. static void
  353. on_context_default_output_stream_notify (MateMixerContext *context,
  354. GParamSpec *pspec,
  355. GvcMixerDialog *dialog)
  356. {
  357. MateMixerStream *stream;
  358. stream = mate_mixer_context_get_default_output_stream (context);
  359. set_output_stream (dialog, stream);
  360. }
  361. #define DECAY_STEP .15
  362. static void
  363. on_stream_control_monitor_value (MateMixerStream *stream,
  364. gdouble value,
  365. GvcMixerDialog *dialog)
  366. {
  367. GtkAdjustment *adj;
  368. if (dialog->priv->last_input_peak >= DECAY_STEP) {
  369. if (value < dialog->priv->last_input_peak - DECAY_STEP) {
  370. value = dialog->priv->last_input_peak - DECAY_STEP;
  371. }
  372. }
  373. dialog->priv->last_input_peak = value;
  374. adj = gvc_level_bar_get_peak_adjustment (GVC_LEVEL_BAR (dialog->priv->input_level_bar));
  375. if (value >= 0)
  376. gtk_adjustment_set_value (adj, value);
  377. else
  378. gtk_adjustment_set_value (adj, 0.0);
  379. }
  380. static void
  381. update_input_settings (GvcMixerDialog *dialog)
  382. {
  383. MateMixerStream *stream;
  384. MateMixerStreamControl *control;
  385. MateMixerStreamControlFlags flags;
  386. MateMixerSwitch *port_switch;
  387. g_debug ("Updating input settings");
  388. if (dialog->priv->input_port_combo != NULL) {
  389. gtk_container_remove (GTK_CONTAINER (dialog->priv->input_settings_box),
  390. dialog->priv->input_port_combo);
  391. dialog->priv->input_port_combo = NULL;
  392. }
  393. /* Get the control currently associated with the input slider */
  394. control = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->input_bar));
  395. if (control == NULL)
  396. return;
  397. flags = mate_mixer_stream_control_get_flags (control);
  398. /* Enable level bar only if supported by the control */
  399. if (flags & MATE_MIXER_STREAM_CONTROL_HAS_MONITOR)
  400. g_signal_connect (G_OBJECT (control),
  401. "monitor-value",
  402. G_CALLBACK (on_stream_control_monitor_value),
  403. dialog);
  404. /* Get owning stream of the control */
  405. stream = mate_mixer_stream_control_get_stream (control);
  406. if (G_UNLIKELY (stream == NULL))
  407. return;
  408. /* Enable the port selector if the stream has one */
  409. port_switch = find_stream_port_switch (stream);
  410. if (port_switch != NULL) {
  411. dialog->priv->input_port_combo =
  412. gvc_combo_box_new (port_switch, _("Co_nnector:"));
  413. gvc_combo_box_set_size_group (GVC_COMBO_BOX (dialog->priv->input_port_combo),
  414. dialog->priv->size_group,
  415. FALSE);
  416. gtk_box_pack_start (GTK_BOX (dialog->priv->input_settings_box),
  417. dialog->priv->input_port_combo,
  418. TRUE, TRUE, 0);
  419. gtk_widget_show (dialog->priv->input_port_combo);
  420. }
  421. }
  422. static void
  423. on_stream_control_mute_notify (MateMixerStreamControl *control,
  424. GParamSpec *pspec,
  425. GvcMixerDialog *dialog)
  426. {
  427. /* Stop monitoring the input stream when it gets muted */
  428. if (mate_mixer_stream_control_get_mute (control) == TRUE)
  429. mate_mixer_stream_control_set_monitor_enabled (control, FALSE);
  430. else
  431. mate_mixer_stream_control_set_monitor_enabled (control, TRUE);
  432. }
  433. static void
  434. set_input_stream (GvcMixerDialog *dialog, MateMixerStream *stream)
  435. {
  436. GtkTreeModel *model;
  437. MateMixerSwitch *swtch;
  438. MateMixerStreamControl *control;
  439. control = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->input_bar));
  440. if (control != NULL) {
  441. /* Disconnect port switch of the previous stream */
  442. if (dialog->priv->input_port_combo != NULL) {
  443. swtch = g_object_get_data (G_OBJECT (dialog->priv->input_port_combo),
  444. "switch");
  445. if (swtch != NULL)
  446. g_signal_handlers_disconnect_by_data (G_OBJECT (swtch),
  447. dialog);
  448. }
  449. /* Disable monitoring of the previous control */
  450. g_signal_handlers_disconnect_by_func (G_OBJECT (control),
  451. G_CALLBACK (on_stream_control_monitor_value),
  452. dialog);
  453. mate_mixer_stream_control_set_monitor_enabled (control, FALSE);
  454. }
  455. bar_set_stream (dialog, dialog->priv->input_bar, stream);
  456. if (stream != NULL) {
  457. const GList *controls;
  458. guint page = gtk_notebook_get_current_page (GTK_NOTEBOOK (dialog->priv->notebook));
  459. controls = mate_mixer_context_list_stored_controls (dialog->priv->context);
  460. /* Move all stored controls to the newly selected default stream */
  461. while (controls != NULL) {
  462. MateMixerStream *parent;
  463. control = MATE_MIXER_STREAM_CONTROL (controls->data);
  464. parent = mate_mixer_stream_control_get_stream (control);
  465. /* Prefer streamless controls to stay the way they are, forcing them to
  466. * a particular owning stream would be wrong for eg. event controls */
  467. if (parent != NULL && parent != stream) {
  468. MateMixerDirection direction =
  469. mate_mixer_stream_get_direction (parent);
  470. if (direction == MATE_MIXER_DIRECTION_INPUT)
  471. mate_mixer_stream_control_set_stream (control, stream);
  472. }
  473. controls = controls->next;
  474. }
  475. if (page == PAGE_INPUT) {
  476. MateMixerStreamControl *control =
  477. gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->input_bar));
  478. if (G_LIKELY (control != NULL))
  479. mate_mixer_stream_control_set_monitor_enabled (control, TRUE);
  480. }
  481. /* Enable/disable the peak level monitor according to mute state */
  482. g_signal_connect (G_OBJECT (stream),
  483. "notify::mute",
  484. G_CALLBACK (on_stream_control_mute_notify),
  485. dialog);
  486. }
  487. model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview));
  488. update_default_tree_item (dialog, model, stream);
  489. update_input_settings (dialog);
  490. }
  491. static void
  492. on_context_default_input_stream_notify (MateMixerContext *context,
  493. GParamSpec *pspec,
  494. GvcMixerDialog *dialog)
  495. {
  496. MateMixerStream *stream;
  497. g_debug ("Default input stream has changed");
  498. stream = mate_mixer_context_get_default_input_stream (context);
  499. set_input_stream (dialog, stream);
  500. }
  501. static GtkWidget *
  502. create_bar (GvcMixerDialog *dialog, gboolean use_size_group, gboolean symmetric)
  503. {
  504. GtkWidget *bar;
  505. bar = gvc_channel_bar_new (NULL);
  506. if (use_size_group == TRUE)
  507. gvc_channel_bar_set_size_group (GVC_CHANNEL_BAR (bar),
  508. dialog->priv->size_group,
  509. symmetric);
  510. g_object_set (G_OBJECT (bar),
  511. "orientation", GTK_ORIENTATION_HORIZONTAL,
  512. "show-mute", TRUE,
  513. "show-icons", TRUE,
  514. "show-marks", TRUE,
  515. "extended", TRUE, NULL);
  516. return bar;
  517. }
  518. static void
  519. bar_set_stream (GvcMixerDialog *dialog,
  520. GtkWidget *bar,
  521. MateMixerStream *stream)
  522. {
  523. MateMixerStreamControl *control = NULL;
  524. if (stream != NULL)
  525. control = mate_mixer_stream_get_default_control (stream);
  526. bar_set_stream_control (dialog, bar, control);
  527. }
  528. static void
  529. bar_set_stream_control (GvcMixerDialog *dialog,
  530. GtkWidget *bar,
  531. MateMixerStreamControl *control)
  532. {
  533. const gchar *name;
  534. MateMixerStreamControl *previous;
  535. previous = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (bar));
  536. if (previous == control)
  537. return;
  538. if (previous != NULL) {
  539. name = mate_mixer_stream_control_get_name (previous);
  540. g_debug ("Removing stream control %s from bar %s",
  541. name,
  542. gvc_channel_bar_get_name (GVC_CHANNEL_BAR (bar)));
  543. g_signal_handlers_disconnect_by_data (G_OBJECT (previous), dialog);
  544. /* This may not do anything because we no longer have the information
  545. * about the owning stream, in case it was an input stream, make
  546. * sure to disconnected from the peak level monitor */
  547. mate_mixer_stream_control_set_monitor_enabled (previous, FALSE);
  548. g_hash_table_remove (dialog->priv->bars, name);
  549. }
  550. gvc_channel_bar_set_control (GVC_CHANNEL_BAR (bar), control);
  551. if (control != NULL) {
  552. name = mate_mixer_stream_control_get_name (control);
  553. g_debug ("Setting stream control %s for bar %s",
  554. name,
  555. gvc_channel_bar_get_name (GVC_CHANNEL_BAR (bar)));
  556. g_hash_table_insert (dialog->priv->bars,
  557. (gpointer) name,
  558. bar);
  559. gtk_widget_set_sensitive (GTK_WIDGET (bar), TRUE);
  560. } else
  561. gtk_widget_set_sensitive (GTK_WIDGET (bar), TRUE);
  562. }
  563. static void
  564. add_application_control (GvcMixerDialog *dialog, MateMixerStreamControl *control)
  565. {
  566. MateMixerStream *stream;
  567. MateMixerStreamControlMediaRole media_role;
  568. MateMixerAppInfo *info;
  569. MateMixerDirection direction = MATE_MIXER_DIRECTION_UNKNOWN;
  570. GtkWidget *bar;
  571. const gchar *app_id;
  572. const gchar *app_name;
  573. const gchar *app_icon;
  574. media_role = mate_mixer_stream_control_get_media_role (control);
  575. /* Add stream to the applications page, but make sure the stream qualifies
  576. * for the inclusion */
  577. info = mate_mixer_stream_control_get_app_info (control);
  578. if (info == NULL)
  579. return;
  580. /* Skip streams with roles we don't care about */
  581. if (media_role == MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_EVENT ||
  582. media_role == MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_TEST ||
  583. media_role == MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_ABSTRACT ||
  584. media_role == MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_FILTER)
  585. return;
  586. app_id = mate_mixer_app_info_get_id (info);
  587. /* These applications may have associated streams because they do peak
  588. * level monitoring, skip these too */
  589. if (!g_strcmp0 (app_id, "org.mate.VolumeControl") ||
  590. !g_strcmp0 (app_id, "org.gnome.VolumeControl") ||
  591. !g_strcmp0 (app_id, "org.PulseAudio.pavucontrol"))
  592. return;
  593. app_name = mate_mixer_app_info_get_name (info);
  594. if (app_name == NULL)
  595. app_name = mate_mixer_stream_control_get_label (control);
  596. if (app_name == NULL)
  597. app_name = mate_mixer_stream_control_get_name (control);
  598. if (G_UNLIKELY (app_name == NULL))
  599. return;
  600. bar = create_bar (dialog, FALSE, FALSE);
  601. g_object_set (G_OBJECT (bar),
  602. "show-marks", FALSE,
  603. "extended", FALSE,
  604. NULL);
  605. /* By default channel bars use speaker icons, use microphone icons
  606. * instead for recording applications */
  607. stream = mate_mixer_stream_control_get_stream (control);
  608. if (stream != NULL)
  609. direction = mate_mixer_stream_get_direction (stream);
  610. if (direction == MATE_MIXER_DIRECTION_INPUT)
  611. g_object_set (G_OBJECT (bar),
  612. "low-icon-name", "audio-input-microphone-low",
  613. "high-icon-name", "audio-input-microphone-high",
  614. NULL);
  615. app_icon = mate_mixer_app_info_get_icon (info);
  616. if (app_icon == NULL) {
  617. if (direction == MATE_MIXER_DIRECTION_INPUT)
  618. app_icon = "audio-input-microphone";
  619. else
  620. app_icon = "applications-multimedia";
  621. }
  622. gvc_channel_bar_set_name (GVC_CHANNEL_BAR (bar), app_name);
  623. gvc_channel_bar_set_icon_name (GVC_CHANNEL_BAR (bar), app_icon);
  624. gtk_box_pack_start (GTK_BOX (dialog->priv->applications_box),
  625. bar,
  626. FALSE, FALSE, 12);
  627. bar_set_stream_control (dialog, bar, control);
  628. dialog->priv->num_apps++;
  629. gtk_widget_hide (dialog->priv->no_apps_label);
  630. gtk_widget_show (bar);
  631. }
  632. static void
  633. on_stream_control_added (MateMixerStream *stream,
  634. const gchar *name,
  635. GvcMixerDialog *dialog)
  636. {
  637. MateMixerStreamControl *control;
  638. MateMixerStreamControlRole role;
  639. control = mate_mixer_stream_get_control (stream, name);
  640. if G_UNLIKELY (control == NULL)
  641. return;
  642. role = mate_mixer_stream_control_get_role (control);
  643. if (role == MATE_MIXER_STREAM_CONTROL_ROLE_APPLICATION)
  644. add_application_control (dialog, control);
  645. }
  646. static void
  647. on_stream_control_removed (MateMixerStream *stream,
  648. const gchar *name,
  649. GvcMixerDialog *dialog)
  650. {
  651. MateMixerStreamControl *control;
  652. control = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->input_bar));
  653. if (control != NULL) {
  654. const gchar *input_name = mate_mixer_stream_control_get_name (control);
  655. if (strcmp (name, input_name) == 0) {
  656. // XXX probably can't even happen, but handle it somehow
  657. return;
  658. }
  659. }
  660. control = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->output_bar));
  661. if (control != NULL) {
  662. const gchar *input_name = mate_mixer_stream_control_get_name (control);
  663. if (strcmp (name, input_name) == 0) {
  664. // XXX probably can't even happen, but handle it somehow
  665. return;
  666. }
  667. }
  668. /* No way to be sure that it is an application control, but we don't have
  669. * any other than application bars that could match the name */
  670. remove_application_control (dialog, name);
  671. }
  672. static void
  673. add_stream (GvcMixerDialog *dialog, MateMixerStream *stream)
  674. {
  675. GtkTreeModel *model = NULL;
  676. GtkTreeIter iter;
  677. const gchar *speakers = NULL;
  678. const GList *controls;
  679. gboolean is_default = FALSE;
  680. MateMixerDirection direction;
  681. direction = mate_mixer_stream_get_direction (stream);
  682. if (direction == MATE_MIXER_DIRECTION_INPUT) {
  683. MateMixerStream *input;
  684. input = mate_mixer_context_get_default_input_stream (dialog->priv->context);
  685. if (stream == input) {
  686. bar_set_stream (dialog, dialog->priv->input_bar, stream);
  687. update_input_settings (dialog);
  688. is_default = TRUE;
  689. }
  690. model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview));
  691. }
  692. else if (direction == MATE_MIXER_DIRECTION_OUTPUT) {
  693. MateMixerStream *output;
  694. MateMixerStreamControl *control;
  695. output = mate_mixer_context_get_default_output_stream (dialog->priv->context);
  696. if (stream == output) {
  697. bar_set_stream (dialog, dialog->priv->output_bar, stream);
  698. update_output_settings (dialog);
  699. is_default = TRUE;
  700. }
  701. model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview));
  702. control = mate_mixer_stream_get_default_control (stream);
  703. if (G_LIKELY (control != NULL))
  704. speakers = gvc_channel_map_to_pretty_string (control);
  705. }
  706. controls = mate_mixer_stream_list_controls (stream);
  707. while (controls != NULL) {
  708. MateMixerStreamControl *control = MATE_MIXER_STREAM_CONTROL (controls->data);
  709. MateMixerStreamControlRole role;
  710. role = mate_mixer_stream_control_get_role (control);
  711. if (role == MATE_MIXER_STREAM_CONTROL_ROLE_APPLICATION)
  712. add_application_control (dialog, control);
  713. controls = controls->next;
  714. }
  715. if (model != NULL) {
  716. const gchar *name;
  717. const gchar *label;
  718. name = mate_mixer_stream_get_name (stream);
  719. label = mate_mixer_stream_get_label (stream);
  720. gtk_list_store_append (GTK_LIST_STORE (model), &iter);
  721. gtk_list_store_set (GTK_LIST_STORE (model),
  722. &iter,
  723. NAME_COLUMN, name,
  724. LABEL_COLUMN, label,
  725. ACTIVE_COLUMN, is_default,
  726. SPEAKERS_COLUMN, speakers,
  727. -1);
  728. }
  729. // XXX find a way to disconnect when removed
  730. g_signal_connect (G_OBJECT (stream),
  731. "control-added",
  732. G_CALLBACK (on_stream_control_added),
  733. dialog);
  734. g_signal_connect (G_OBJECT (stream),
  735. "control-removed",
  736. G_CALLBACK (on_stream_control_removed),
  737. dialog);
  738. }
  739. static void
  740. update_device_test_visibility (GvcMixerDialog *dialog)
  741. {
  742. MateMixerDevice *device;
  743. MateMixerStream *stream;
  744. device = g_object_get_data (G_OBJECT (dialog->priv->hw_profile_combo), "device");
  745. if (G_UNLIKELY (device == NULL)) {
  746. return;
  747. }
  748. stream = find_device_test_stream (dialog, device);
  749. g_object_set (G_OBJECT (dialog->priv->hw_profile_combo),
  750. "show-button", (stream != NULL),
  751. NULL);
  752. }
  753. static void
  754. on_context_stream_added (MateMixerContext *context,
  755. const gchar *name,
  756. GvcMixerDialog *dialog)
  757. {
  758. MateMixerStream *stream;
  759. MateMixerDirection direction;
  760. GtkWidget *bar;
  761. stream = mate_mixer_context_get_stream (context, name);
  762. if (G_UNLIKELY (stream == NULL))
  763. return;
  764. direction = mate_mixer_stream_get_direction (stream);
  765. /* If the newly added stream belongs to the currently selected device and
  766. * the test button is hidden, this stream may be the one to allow the
  767. * sound test and therefore we may need to enable the button */
  768. if (dialog->priv->hw_profile_combo != NULL && direction == MATE_MIXER_DIRECTION_OUTPUT) {
  769. MateMixerDevice *device1;
  770. MateMixerDevice *device2;
  771. device1 = mate_mixer_stream_get_device (stream);
  772. device2 = g_object_get_data (G_OBJECT (dialog->priv->hw_profile_combo),
  773. "device");
  774. if (device1 == device2) {
  775. gboolean show_button;
  776. g_object_get (G_OBJECT (dialog->priv->hw_profile_combo),
  777. "show-button", &show_button,
  778. NULL);
  779. if (show_button == FALSE)
  780. update_device_test_visibility (dialog);
  781. }
  782. }
  783. bar = g_hash_table_lookup (dialog->priv->bars, name);
  784. if (G_UNLIKELY (bar != NULL))
  785. return;
  786. add_stream (dialog, stream);
  787. }
  788. static void
  789. remove_stream (GvcMixerDialog *dialog, const gchar *name)
  790. {
  791. GtkWidget *bar;
  792. GtkTreeIter iter;
  793. GtkTreeModel *model;
  794. bar = g_hash_table_lookup (dialog->priv->bars, name);
  795. if (bar != NULL) {
  796. g_debug ("Removing stream %s from bar %s",
  797. name,
  798. gvc_channel_bar_get_name (GVC_CHANNEL_BAR (bar)));
  799. bar_set_stream (dialog, bar, NULL);
  800. }
  801. /* Remove from any models */
  802. model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview));
  803. if (find_tree_item_by_name (GTK_TREE_MODEL (model),
  804. name,
  805. NAME_COLUMN,
  806. &iter) == TRUE)
  807. gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
  808. model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview));
  809. if (find_tree_item_by_name (GTK_TREE_MODEL (model),
  810. name,
  811. NAME_COLUMN,
  812. &iter) == TRUE)
  813. gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
  814. }
  815. static void
  816. remove_application_control (GvcMixerDialog *dialog, const gchar *name)
  817. {
  818. GtkWidget *bar;
  819. bar = g_hash_table_lookup (dialog->priv->bars, name);
  820. if (G_UNLIKELY (bar == NULL))
  821. return;
  822. g_debug ("Removing application stream %s", name);
  823. /* We could call bar_set_stream_control here, but that would pointlessly
  824. * invalidate the channel bar, so just remove it ourselves */
  825. g_hash_table_remove (dialog->priv->bars, name);
  826. gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (bar)), bar);
  827. if (G_UNLIKELY (dialog->priv->num_apps <= 0)) {
  828. g_warn_if_reached ();
  829. dialog->priv->num_apps = 1;
  830. }
  831. dialog->priv->num_apps--;
  832. if (dialog->priv->num_apps == 0)
  833. gtk_widget_show (dialog->priv->no_apps_label);
  834. }
  835. static void
  836. on_context_stream_removed (MateMixerContext *context,
  837. const gchar *name,
  838. GvcMixerDialog *dialog)
  839. {
  840. if (dialog->priv->hw_profile_combo != NULL) {
  841. gboolean show_button;
  842. g_object_get (G_OBJECT (dialog->priv->hw_profile_combo),
  843. "show-button", &show_button,
  844. NULL);
  845. if (show_button == TRUE)
  846. update_device_test_visibility (dialog);
  847. }
  848. remove_stream (dialog, name);
  849. }
  850. static void
  851. on_context_stored_control_added (MateMixerContext *context,
  852. const gchar *name,
  853. GvcMixerDialog *dialog)
  854. {
  855. MateMixerStreamControl *control;
  856. MateMixerStreamControlMediaRole media_role;
  857. control = MATE_MIXER_STREAM_CONTROL (mate_mixer_context_get_stored_control (context, name));
  858. if (G_UNLIKELY (control == NULL))
  859. return;
  860. media_role = mate_mixer_stream_control_get_media_role (control);
  861. if (media_role == MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_EVENT)
  862. bar_set_stream_control (dialog, dialog->priv->effects_bar, control);
  863. }
  864. static void
  865. on_context_stored_control_removed (MateMixerContext *context,
  866. const gchar *name,
  867. GvcMixerDialog *dialog)
  868. {
  869. GtkWidget *bar;
  870. bar = g_hash_table_lookup (dialog->priv->bars, name);
  871. if (bar != NULL) {
  872. /* We only use a stored control in the effects bar */
  873. if (G_UNLIKELY (bar != dialog->priv->effects_bar)) {
  874. g_warn_if_reached ();
  875. return;
  876. }
  877. bar_set_stream (dialog, bar, NULL);
  878. }
  879. }
  880. static gchar *
  881. device_status (MateMixerDevice *device)
  882. {
  883. guint inputs = 0;
  884. guint outputs = 0;
  885. gchar *inputs_str = NULL;
  886. gchar *outputs_str = NULL;
  887. const GList *streams;
  888. /* Get number of input and output streams in the device */
  889. streams = mate_mixer_device_list_streams (device);
  890. while (streams != NULL) {
  891. MateMixerStream *stream = MATE_MIXER_STREAM (streams->data);
  892. MateMixerDirection direction;
  893. direction = mate_mixer_stream_get_direction (stream);
  894. if (direction == MATE_MIXER_DIRECTION_INPUT)
  895. inputs++;
  896. else if (direction == MATE_MIXER_DIRECTION_OUTPUT)
  897. outputs++;
  898. streams = streams->next;
  899. }
  900. if (inputs == 0 && outputs == 0) {
  901. /* translators:
  902. * The device has been disabled */
  903. return g_strdup (_("Disabled"));
  904. }
  905. if (outputs > 0) {
  906. /* translators:
  907. * The number of sound outputs on a particular device */
  908. outputs_str = g_strdup_printf (ngettext ("%u Output",
  909. "%u Outputs",
  910. outputs),
  911. outputs);
  912. }
  913. if (inputs > 0) {
  914. /* translators:
  915. * The number of sound inputs on a particular device */
  916. inputs_str = g_strdup_printf (ngettext ("%u Input",
  917. "%u Inputs",
  918. inputs),
  919. inputs);
  920. }
  921. if (inputs_str != NULL && outputs_str != NULL) {
  922. gchar *ret = g_strdup_printf ("%s / %s",
  923. outputs_str,
  924. inputs_str);
  925. g_free (outputs_str);
  926. g_free (inputs_str);
  927. return ret;
  928. }
  929. if (inputs_str != NULL)
  930. return inputs_str;
  931. return outputs_str;
  932. }
  933. static void
  934. update_device_info (GvcMixerDialog *dialog, MateMixerDevice *device)
  935. {
  936. GtkTreeModel *model = NULL;
  937. GtkTreeIter iter;
  938. const gchar *label;
  939. const gchar *profile_label = NULL;
  940. gchar *status;
  941. MateMixerSwitch *profile_switch;
  942. model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->hw_treeview));
  943. if (find_tree_item_by_name (model,
  944. mate_mixer_device_get_name (device),
  945. HW_NAME_COLUMN,
  946. &iter) == FALSE)
  947. return;
  948. label = mate_mixer_device_get_label (device);
  949. profile_switch = find_device_profile_switch (device);
  950. if (profile_switch != NULL) {
  951. MateMixerSwitchOption *active;
  952. active = mate_mixer_switch_get_active_option (profile_switch);
  953. if (G_LIKELY (active != NULL))
  954. profile_label = mate_mixer_switch_option_get_label (active);
  955. }
  956. status = device_status (device);
  957. gtk_list_store_set (GTK_LIST_STORE (model),
  958. &iter,
  959. HW_LABEL_COLUMN, label,
  960. HW_PROFILE_COLUMN, profile_label,
  961. HW_STATUS_COLUMN, status,
  962. -1);
  963. g_free (status);
  964. }
  965. static void
  966. on_device_profile_active_option_notify (MateMixerDeviceSwitch *swtch,
  967. GParamSpec *pspec,
  968. GvcMixerDialog *dialog)
  969. {
  970. MateMixerDevice *device;
  971. device = mate_mixer_device_switch_get_device (swtch);
  972. update_device_info (dialog, device);
  973. }
  974. static void
  975. add_device (GvcMixerDialog *dialog, MateMixerDevice *device)
  976. {
  977. GtkTreeModel *model;
  978. GtkTreeIter iter;
  979. GIcon *icon;
  980. const gchar *name;
  981. const gchar *label;
  982. gchar *status;
  983. const gchar *profile_label = NULL;
  984. MateMixerSwitch *profile_switch;
  985. model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->hw_treeview));
  986. name = mate_mixer_device_get_name (device);
  987. label = mate_mixer_device_get_label (device);
  988. if (find_tree_item_by_name (GTK_TREE_MODEL (model),
  989. name,
  990. HW_NAME_COLUMN,
  991. &iter) == FALSE)
  992. gtk_list_store_append (GTK_LIST_STORE (model), &iter);
  993. icon = g_themed_icon_new_with_default_fallbacks (mate_mixer_device_get_icon (device));
  994. profile_switch = find_device_profile_switch (device);
  995. if (profile_switch != NULL) {
  996. MateMixerSwitchOption *active;
  997. active = mate_mixer_switch_get_active_option (profile_switch);
  998. if (G_LIKELY (active != NULL))
  999. profile_label = mate_mixer_switch_option_get_label (active);
  1000. g_signal_connect (G_OBJECT (profile_switch),
  1001. "notify::active-option",
  1002. G_CALLBACK (on_device_profile_active_option_notify),
  1003. dialog);
  1004. }
  1005. status = device_status (device);
  1006. gtk_list_store_set (GTK_LIST_STORE (model),
  1007. &iter,
  1008. HW_NAME_COLUMN, name,
  1009. HW_LABEL_COLUMN, label,
  1010. HW_ICON_COLUMN, icon,
  1011. HW_PROFILE_COLUMN, profile_label,
  1012. HW_STATUS_COLUMN, status,
  1013. -1);
  1014. g_free (status);
  1015. }
  1016. static void
  1017. on_context_device_added (MateMixerContext *context, const gchar *name, GvcMixerDialog *dialog)
  1018. {
  1019. MateMixerDevice *device;
  1020. device = mate_mixer_context_get_device (context, name);
  1021. if (G_UNLIKELY (device == NULL))
  1022. return;
  1023. add_device (dialog, device);
  1024. }
  1025. static void
  1026. on_context_device_removed (MateMixerContext *context,
  1027. const gchar *name,
  1028. GvcMixerDialog *dialog)
  1029. {
  1030. GtkTreeIter iter;
  1031. GtkTreeModel *model;
  1032. /* Remove from the device model */
  1033. model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->hw_treeview));
  1034. if (find_tree_item_by_name (GTK_TREE_MODEL (model),
  1035. name,
  1036. HW_NAME_COLUMN,
  1037. &iter) == TRUE)
  1038. gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
  1039. }
  1040. static void
  1041. make_label_bold (GtkLabel *label)
  1042. {
  1043. PangoFontDescription *font_desc;
  1044. font_desc = pango_font_description_new ();
  1045. pango_font_description_set_weight (font_desc, PANGO_WEIGHT_BOLD);
  1046. /* This will only affect the weight of the font, the rest is
  1047. * from the current state of the widget, which comes from the
  1048. * theme or user prefs, since the font desc only has the
  1049. * weight flag turned on. */
  1050. #if GTK_CHECK_VERSION (3, 0, 0)
  1051. gtk_widget_override_font (GTK_WIDGET (label), font_desc);
  1052. #else
  1053. gtk_widget_modify_font (GTK_WIDGET (label), font_desc);
  1054. #endif
  1055. pango_font_description_free (font_desc);
  1056. }
  1057. static void
  1058. on_input_radio_toggled (GtkCellRendererToggle *renderer,
  1059. gchar *path_str,
  1060. GvcMixerDialog *dialog)
  1061. {
  1062. GtkTreeModel *model;
  1063. GtkTreeIter iter;
  1064. GtkTreePath *path;
  1065. gboolean toggled = FALSE;
  1066. gchar *name = NULL;
  1067. model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview));
  1068. path = gtk_tree_path_new_from_string (path_str);
  1069. gtk_tree_model_get_iter (model, &iter, path);
  1070. gtk_tree_path_free (path);
  1071. gtk_tree_model_get (model, &iter,
  1072. NAME_COLUMN, &name,
  1073. ACTIVE_COLUMN, &toggled,
  1074. -1);
  1075. if (toggled ^ 1) {
  1076. MateMixerStream *stream;
  1077. MateMixerBackendFlags flags;
  1078. stream = mate_mixer_context_get_stream (dialog->priv->context, name);
  1079. if (G_UNLIKELY (stream == NULL)) {
  1080. g_warn_if_reached ();
  1081. g_free (name);
  1082. return;
  1083. }
  1084. g_debug ("Default input stream selection changed to %s", name);
  1085. // XXX cache this
  1086. flags = mate_mixer_context_get_backend_flags (dialog->priv->context);
  1087. if (flags & MATE_MIXER_BACKEND_CAN_SET_DEFAULT_INPUT_STREAM)
  1088. mate_mixer_context_set_default_input_stream (dialog->priv->context, stream);
  1089. else
  1090. set_input_stream (dialog, stream);
  1091. }
  1092. g_free (name);
  1093. }
  1094. static void
  1095. on_output_radio_toggled (GtkCellRendererToggle *renderer,
  1096. gchar *path_str,
  1097. GvcMixerDialog *dialog)
  1098. {
  1099. GtkTreeModel *model;
  1100. GtkTreeIter iter;
  1101. GtkTreePath *path;
  1102. gboolean toggled = FALSE;
  1103. gchar *name = NULL;
  1104. model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview));
  1105. path = gtk_tree_path_new_from_string (path_str);
  1106. gtk_tree_model_get_iter (model, &iter, path);
  1107. gtk_tree_path_free (path);
  1108. gtk_tree_model_get (model, &iter,
  1109. NAME_COLUMN, &name,
  1110. ACTIVE_COLUMN, &toggled,
  1111. -1);
  1112. if (toggled ^ 1) {
  1113. MateMixerStream *stream;
  1114. MateMixerBackendFlags flags;
  1115. stream = mate_mixer_context_get_stream (dialog->priv->context, name);
  1116. if (G_UNLIKELY (stream == NULL)) {
  1117. g_warn_if_reached ();
  1118. g_free (name);
  1119. return;
  1120. }
  1121. g_debug ("Default output stream selection changed to %s", name);
  1122. // XXX cache this
  1123. flags = mate_mixer_context_get_backend_flags (dialog->priv->context);
  1124. if (flags & MATE_MIXER_BACKEND_CAN_SET_DEFAULT_OUTPUT_STREAM)
  1125. mate_mixer_context_set_default_output_stream (dialog->priv->context, stream);
  1126. else
  1127. set_output_stream (dialog, stream);
  1128. }
  1129. g_free (name);
  1130. }
  1131. static void
  1132. stream_name_to_text (GtkTreeViewColumn *column,
  1133. GtkCellRenderer *cell,
  1134. GtkTreeModel *model,
  1135. GtkTreeIter *iter,
  1136. gpointer user_data)
  1137. {
  1138. gchar *label;
  1139. gchar *speakers;
  1140. gtk_tree_model_get (model, iter,
  1141. LABEL_COLUMN, &label,
  1142. SPEAKERS_COLUMN, &speakers,
  1143. -1);
  1144. if (speakers != NULL) {
  1145. gchar *str = g_strdup_printf ("%s\n<i>%s</i>",
  1146. label,
  1147. speakers);
  1148. g_object_set (cell, "markup", str, NULL);
  1149. g_free (str);
  1150. g_free (speakers);
  1151. } else {
  1152. g_object_set (cell, "text", label, NULL);
  1153. }
  1154. g_free (label);
  1155. }
  1156. static gint
  1157. compare_stream_treeview_items (GtkTreeModel *model,
  1158. GtkTreeIter *a,
  1159. GtkTreeIter *b,
  1160. gpointer user_data)
  1161. {
  1162. gchar *desc_a = NULL;
  1163. gchar *desc_b = NULL;
  1164. gint result;
  1165. gtk_tree_model_get (model, a,
  1166. LABEL_COLUMN, &desc_a,
  1167. -1);
  1168. gtk_tree_model_get (model, b,
  1169. LABEL_COLUMN, &desc_b,
  1170. -1);
  1171. if (desc_a == NULL) {
  1172. g_free (desc_b);
  1173. return -1;
  1174. }
  1175. if (desc_b == NULL) {
  1176. g_free (desc_a);
  1177. return 1;
  1178. }
  1179. result = g_ascii_strcasecmp (desc_a, desc_b);
  1180. g_free (desc_a);
  1181. g_free (desc_b);
  1182. return result;
  1183. }
  1184. static GtkWidget *
  1185. create_stream_treeview (GvcMixerDialog *dialog, GCallback on_toggled)
  1186. {
  1187. GtkWidget *treeview;
  1188. GtkListStore *store;
  1189. GtkCellRenderer *renderer;
  1190. GtkTreeViewColumn *column;
  1191. treeview = gtk_tree_view_new ();
  1192. gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
  1193. store = gtk_list_store_new (NUM_COLUMNS,
  1194. G_TYPE_ICON,
  1195. G_TYPE_STRING,
  1196. G_TYPE_STRING,
  1197. G_TYPE_BOOLEAN,
  1198. G_TYPE_STRING);
  1199. gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
  1200. GTK_TREE_MODEL (store));
  1201. renderer = gtk_cell_renderer_toggle_new ();
  1202. gtk_cell_renderer_toggle_set_radio (GTK_CELL_RENDERER_TOGGLE (renderer), TRUE);
  1203. column = gtk_tree_view_column_new_with_attributes (NULL,
  1204. renderer,
  1205. "active", ACTIVE_COLUMN,
  1206. NULL);
  1207. gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
  1208. g_signal_connect (G_OBJECT (renderer),
  1209. "toggled",
  1210. G_CALLBACK (on_toggled),
  1211. dialog);
  1212. gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (treeview), -1,
  1213. _("Name"),
  1214. gtk_cell_renderer_text_new (),
  1215. stream_name_to_text,
  1216. NULL, NULL);
  1217. /* Keep the list of streams sorted by the name */
  1218. gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
  1219. LABEL_COLUMN,
  1220. GTK_SORT_ASCENDING);
  1221. gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
  1222. LABEL_COLUMN,
  1223. compare_stream_treeview_items,
  1224. NULL, NULL);
  1225. return treeview;
  1226. }
  1227. static void
  1228. on_device_profile_changing (GvcComboBox *combobox,
  1229. MateMixerSwitchOption *option,
  1230. GvcMixerDialog *dialog)
  1231. {
  1232. g_debug ("Changing device profile");
  1233. // TODO
  1234. }
  1235. static void
  1236. on_test_speakers_clicked (GvcComboBox *widget, GvcMixerDialog *dialog)
  1237. {
  1238. GtkWidget *d,
  1239. *test,
  1240. *container;
  1241. gchar *title;
  1242. MateMixerDevice *device;
  1243. MateMixerStream *stream;
  1244. device = g_object_get_data (G_OBJECT (widget), "device");
  1245. if (G_UNLIKELY (device == NULL)) {
  1246. g_warn_if_reached ();
  1247. return;
  1248. }
  1249. stream = find_device_test_stream (dialog, device);
  1250. if (G_UNLIKELY (stream == NULL)) {
  1251. g_warn_if_reached ();
  1252. return;
  1253. }
  1254. title = g_strdup_printf (_("Speaker Testing for %s"),
  1255. mate_mixer_device_get_label (device));
  1256. d = gtk_dialog_new_with_buttons (title,
  1257. GTK_WINDOW (dialog),
  1258. GTK_DIALOG_MODAL |
  1259. GTK_DIALOG_DESTROY_WITH_PARENT,
  1260. "gtk-close",
  1261. GTK_RESPONSE_CLOSE,
  1262. NULL);
  1263. g_free (title);
  1264. gtk_window_set_resizable (GTK_WINDOW (d), FALSE);
  1265. test = gvc_speaker_test_new (stream);
  1266. gtk_widget_show (test);
  1267. container = gtk_dialog_get_content_area (GTK_DIALOG (d));
  1268. gtk_container_add (GTK_CONTAINER (container), test);
  1269. gtk_dialog_run (GTK_DIALOG (d));
  1270. gtk_widget_destroy (d);
  1271. }
  1272. static void
  1273. on_device_selection_changed (GtkTreeSelection *selection, GvcMixerDialog *dialog)
  1274. {
  1275. GtkTreeIter iter;
  1276. gchar *name;
  1277. MateMixerDevice *device;
  1278. MateMixerSwitch *profile_switch;
  1279. g_debug ("Device selection changed");
  1280. if (dialog->priv->hw_profile_combo != NULL) {
  1281. gtk_container_remove (GTK_CONTAINER (dialog->priv->hw_settings_box),
  1282. dialog->priv->hw_profile_combo);
  1283. dialog->priv->hw_profile_combo = NULL;
  1284. }
  1285. if (gtk_tree_selection_get_selected (selection, NULL, &iter) == FALSE)
  1286. return;
  1287. gtk_tree_model_get (gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->hw_treeview)),
  1288. &iter,
  1289. HW_NAME_COLUMN, &name,
  1290. -1);
  1291. device = mate_mixer_context_get_device (dialog->priv->context, name);
  1292. if (G_UNLIKELY (device == NULL)) {
  1293. g_warn_if_reached ();
  1294. g_free (name);
  1295. return;
  1296. }
  1297. g_free (name);
  1298. /* Profile/speaker test combo */
  1299. profile_switch = find_device_profile_switch (device);
  1300. if (profile_switch != NULL) {
  1301. dialog->priv->hw_profile_combo =
  1302. gvc_combo_box_new (profile_switch, _("_Profile:"));
  1303. g_object_set (G_OBJECT (dialog->priv->hw_profile_combo),
  1304. "button-label", _("Test Speakers"),
  1305. NULL);
  1306. g_signal_connect (G_OBJECT (dialog->priv->hw_profile_combo),
  1307. "changing",
  1308. G_CALLBACK (on_device_profile_changing),
  1309. dialog);
  1310. g_signal_connect (G_OBJECT (dialog->priv->hw_profile_combo),
  1311. "button-clicked",
  1312. G_CALLBACK (on_test_speakers_clicked),
  1313. dialog);
  1314. g_object_set_data_full (G_OBJECT (dialog->priv->hw_profile_combo),
  1315. "device",
  1316. g_object_ref (device),
  1317. g_object_unref);
  1318. gtk_box_pack_start (GTK_BOX (dialog->priv->hw_settings_box),
  1319. dialog->priv->hw_profile_combo,
  1320. TRUE, TRUE, 6);
  1321. /* Enable the speaker test button if the selected device
  1322. * is capable of sound output */
  1323. update_device_test_visibility (dialog);
  1324. gtk_widget_show (dialog->priv->hw_profile_combo);
  1325. }
  1326. }
  1327. static void
  1328. on_notebook_switch_page (GtkNotebook *notebook,
  1329. GtkWidget *page,
  1330. guint page_num,
  1331. GvcMixerDialog *dialog)
  1332. {
  1333. MateMixerStreamControl *control;
  1334. // XXX because this is called too early in constructor
  1335. if (G_UNLIKELY (dialog->priv->input_bar == NULL))
  1336. return;
  1337. control = gvc_channel_bar_get_control (GVC_CHANNEL_BAR (dialog->priv->input_bar));
  1338. if (control == NULL)
  1339. return;
  1340. if (page_num == PAGE_INPUT)
  1341. mate_mixer_stream_control_set_monitor_enabled (control, TRUE);
  1342. else
  1343. mate_mixer_stream_control_set_monitor_enabled (control, FALSE);
  1344. }
  1345. static void
  1346. device_name_to_text (GtkTreeViewColumn *column,
  1347. GtkCellRenderer *cell,
  1348. GtkTreeModel *model,
  1349. GtkTreeIter *iter,
  1350. gpointer user_data)
  1351. {
  1352. gchar *label = NULL;
  1353. gchar *profile = NULL;
  1354. gchar *status = NULL;
  1355. gtk_tree_model_get (model, iter,
  1356. HW_LABEL_COLUMN, &label,
  1357. HW_PROFILE_COLUMN, &profile,
  1358. HW_STATUS_COLUMN, &status,
  1359. -1);
  1360. if (G_LIKELY (status != NULL)) {
  1361. gchar *str;
  1362. if (profile != NULL)
  1363. str = g_strdup_printf ("%s\n<i>%s</i>\n<i>%s</i>",
  1364. label,
  1365. status,
  1366. profile);
  1367. else
  1368. str = g_strdup_printf ("%s\n<i>%s</i>",
  1369. label,
  1370. status);
  1371. g_object_set (cell, "markup", str, NULL);
  1372. g_free (str);
  1373. g_free (profile);
  1374. g_free (status);
  1375. } else
  1376. g_object_set (cell, "text", label, NULL);
  1377. g_free (label);
  1378. }
  1379. static gint
  1380. compare_device_treeview_items (GtkTreeModel *model,
  1381. GtkTreeIter *a,
  1382. GtkTreeIter *b,
  1383. gpointer user_data)
  1384. {
  1385. gchar *desc_a = NULL;
  1386. gchar *desc_b = NULL;
  1387. gint result;
  1388. gtk_tree_model_get (model, a,
  1389. HW_LABEL_COLUMN, &desc_a,
  1390. -1);
  1391. gtk_tree_model_get (model, b,
  1392. HW_LABEL_COLUMN, &desc_b,
  1393. -1);
  1394. result = g_ascii_strcasecmp (desc_a, desc_b);
  1395. g_free (desc_a);
  1396. g_free (desc_b);
  1397. return result;
  1398. }
  1399. static GtkWidget *
  1400. create_device_treeview (GvcMixerDialog *dialog, GCallback on_changed)
  1401. {
  1402. GtkWidget *treeview;
  1403. GtkListStore *store;
  1404. GtkCellRenderer *renderer;
  1405. GtkTreeViewColumn *column;
  1406. GtkTreeSelection *selection;
  1407. treeview = gtk_tree_view_new ();
  1408. gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
  1409. selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
  1410. g_signal_connect (G_OBJECT (selection),
  1411. "changed",
  1412. on_changed,
  1413. dialog);
  1414. store = gtk_list_store_new (HW_NUM_COLUMNS,
  1415. G_TYPE_ICON,
  1416. G_TYPE_STRING,
  1417. G_TYPE_STRING,
  1418. G_TYPE_STRING,
  1419. G_TYPE_STRING);
  1420. gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
  1421. GTK_TREE_MODEL (store));
  1422. renderer = gtk_cell_renderer_pixbuf_new ();
  1423. g_object_set (G_OBJECT (renderer),
  1424. "stock-size",
  1425. GTK_ICON_SIZE_DIALOG,
  1426. NULL);
  1427. column = gtk_tree_view_column_new_with_attributes (NULL,
  1428. renderer,
  1429. "gicon", HW_ICON_COLUMN,
  1430. NULL);
  1431. gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
  1432. gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (treeview), -1,
  1433. _("Name"),
  1434. gtk_cell_renderer_text_new (),
  1435. device_name_to_text,
  1436. NULL, NULL);
  1437. /* Keep the list of streams sorted by the name */
  1438. gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
  1439. HW_LABEL_COLUMN,
  1440. GTK_SORT_ASCENDING);
  1441. gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
  1442. HW_LABEL_COLUMN,
  1443. compare_device_treeview_items,
  1444. NULL, NULL);
  1445. return treeview;
  1446. }
  1447. static void
  1448. dialog_accel_cb (GtkAccelGroup *accelgroup,
  1449. GObject *object,
  1450. guint key,
  1451. GdkModifierType mod,
  1452. GvcMixerDialog *self)
  1453. {
  1454. gint num = -1;
  1455. gint i;
  1456. for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
  1457. if (tab_accel_keys[i] == key) {
  1458. num = i;
  1459. break;
  1460. }
  1461. }
  1462. if (num != -1)
  1463. gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
  1464. }
  1465. static void
  1466. create_page_effects (GvcMixerDialog *self)
  1467. {
  1468. GtkWidget *box;
  1469. GtkWidget *label;
  1470. GtkWidget *chooser;
  1471. #if GTK_CHECK_VERSION (3, 0, 0)
  1472. box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
  1473. #else
  1474. box = gtk_vbox_new (FALSE, 6);
  1475. #endif
  1476. gtk_container_set_border_width (GTK_CONTAINER (box), 12);
  1477. label = gtk_label_new (_("Sound Effects"));
  1478. gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook),
  1479. box,
  1480. label);
  1481. /*
  1482. * Create a volume slider for the sound effect sounds.
  1483. *
  1484. * Only look for a stored control because regular controls only exist
  1485. * for short time periods when an event sound is played.
  1486. */
  1487. if (self->priv->backend_flags & MATE_MIXER_BACKEND_HAS_STORED_CONTROLS) {
  1488. GvcChannelBar *bar;
  1489. const GList *list;
  1490. bar = GVC_CHANNEL_BAR (create_bar (self, TRUE, TRUE));
  1491. gtk_box_pack_start (GTK_BOX (box),
  1492. GTK_WIDGET (bar),
  1493. FALSE, FALSE, 0);
  1494. gvc_channel_bar_set_show_marks (bar, FALSE);
  1495. gvc_channel_bar_set_extended (bar, FALSE);
  1496. gvc_channel_bar_set_name (bar, _("_Alert volume: "));
  1497. /* Find an event role stored control */
  1498. list = mate_mixer_context_list_stored_controls (self->priv->context);
  1499. while (list != NULL) {
  1500. MateMixerStreamControl *control = MATE_MIXER_STREAM_CONTROL (list->data);
  1501. MateMixerStreamControlMediaRole media_role;
  1502. media_role = mate_mixer_stream_control_get_media_role (control);
  1503. if (media_role == MATE_MIXER_STREAM_CONTROL_MEDIA_ROLE_EVENT) {
  1504. bar_set_stream_control (self, GTK_WIDGET (bar), control);
  1505. break;
  1506. }
  1507. list = list->next;
  1508. }
  1509. self->priv->effects_bar = GTK_WIDGET (bar);
  1510. }
  1511. chooser = gvc_sound_theme_chooser_new ();
  1512. gtk_box_pack_start (GTK_BOX (box),
  1513. chooser,
  1514. TRUE, TRUE, 6);
  1515. }
  1516. static GObject *
  1517. gvc_mixer_dialog_constructor (GType type,
  1518. guint n_construct_properties,
  1519. GObjectConstructParam *construct_params)
  1520. {
  1521. GObject *object;
  1522. GvcMixerDialog *self;
  1523. GtkWidget *main_vbox;
  1524. GtkWidget *label;
  1525. GtkWidget *alignment;
  1526. GtkWidget *box;
  1527. GtkWidget *sbox;
  1528. GtkWidget *ebox;
  1529. GtkTreeSelection *selection;
  1530. GtkAccelGroup *accel_group;
  1531. GClosure *closure;
  1532. GtkTreeIter iter;
  1533. gint i;
  1534. const GList *list;
  1535. object = G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->constructor (type,
  1536. n_construct_properties,
  1537. construct_params);
  1538. self = GVC_MIXER_DIALOG (object);
  1539. gtk_dialog_add_button (GTK_DIALOG (self), "gtk-close", GTK_RESPONSE_OK);
  1540. main_vbox = gtk_dialog_get_content_area (GTK_DIALOG (self));
  1541. gtk_box_set_spacing (GTK_BOX (main_vbox), 2);
  1542. gtk_container_set_border_width (GTK_CONTAINER (self), 6);
  1543. #if GTK_CHECK_VERSION (3, 0, 0)
  1544. self->priv->output_stream_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
  1545. #else
  1546. self->priv->output_stream_box = gtk_hbox_new (FALSE, 12);
  1547. #endif
  1548. alignment = gtk_alignment_new (0, 0, 1, 1);
  1549. gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 12, 0, 0, 0);
  1550. gtk_container_add (GTK_CONTAINER (alignment), self->priv->output_stream_box);
  1551. gtk_box_pack_start (GTK_BOX (main_vbox),
  1552. alignment,
  1553. FALSE, FALSE, 0);
  1554. self->priv->output_bar = create_bar (self, TRUE, TRUE);
  1555. gvc_channel_bar_set_name (GVC_CHANNEL_BAR (self->priv->output_bar),
  1556. _("_Output volume: "));
  1557. gtk_widget_show (self->priv->output_bar);
  1558. gtk_widget_set_sensitive (self->priv->output_bar, FALSE);
  1559. gtk_box_pack_start (GTK_BOX (self->priv->output_stream_box),
  1560. self->priv->output_bar, TRUE, TRUE, 12);
  1561. self->priv->notebook = gtk_notebook_new ();
  1562. gtk_box_pack_start (GTK_BOX (main_vbox),
  1563. self->priv->notebook,
  1564. TRUE, TRUE, 0);
  1565. g_signal_connect (G_OBJECT (self->priv->notebook),
  1566. "switch-page",
  1567. G_CALLBACK (on_notebook_switch_page),
  1568. self);
  1569. gtk_container_set_border_width (GTK_CONTAINER (self->priv->notebook), 5);
  1570. /* Set up accels (borrowed from Empathy) */
  1571. accel_group = gtk_accel_group_new ();
  1572. gtk_window_add_accel_group (GTK_WINDOW (self), accel_group);
  1573. for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
  1574. closure = g_cclosure_new (G_CALLBACK (dialog_accel_cb),
  1575. self,
  1576. NULL);
  1577. gtk_accel_group_connect (accel_group,
  1578. tab_accel_keys[i],
  1579. GDK_MOD1_MASK,
  1580. 0,
  1581. closure);
  1582. }
  1583. g_object_unref (accel_group);
  1584. /* Create notebook pages */
  1585. create_page_effects (self);
  1586. #if GTK_CHECK_VERSION (3, 0, 0)
  1587. self->priv->hw_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  1588. #else
  1589. self->priv->hw_box = gtk_vbox_new (FALSE, 12);
  1590. #endif
  1591. gtk_container_set_border_width (GTK_CONTAINER (self->priv->hw_box), 12);
  1592. label = gtk_label_new (_("Hardware"));
  1593. gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook),
  1594. self->priv->hw_box,
  1595. label);
  1596. box = gtk_frame_new (_("C_hoose a device to configure:"));
  1597. label = gtk_frame_get_label_widget (GTK_FRAME (box));
  1598. make_label_bold (GTK_LABEL (label));
  1599. gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
  1600. gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE);
  1601. gtk_box_pack_start (GTK_BOX (self->priv->hw_box), box, TRUE, TRUE, 0);
  1602. alignment = gtk_alignment_new (0, 0, 1, 1);
  1603. gtk_container_add (GTK_CONTAINER (box), alignment);
  1604. gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0);
  1605. self->priv->hw_treeview = create_device_treeview (self,
  1606. G_CALLBACK (on_device_selection_changed));
  1607. gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->priv->hw_treeview);
  1608. box = gtk_scrolled_window_new (NULL, NULL);
  1609. gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box),
  1610. GTK_POLICY_NEVER,
  1611. GTK_POLICY_AUTOMATIC);
  1612. gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box),
  1613. GTK_SHADOW_IN);
  1614. gtk_container_add (GTK_CONTAINER (box), self->priv->hw_treeview);
  1615. gtk_container_add (GTK_CONTAINER (alignment), box);
  1616. selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->hw_treeview));
  1617. gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
  1618. box = gtk_frame_new (_("Settings for the selected device:"));
  1619. label = gtk_frame_get_label_widget (GTK_FRAME (box));
  1620. make_label_bold (GTK_LABEL (label));
  1621. gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE);
  1622. gtk_box_pack_start (GTK_BOX (self->priv->hw_box), box, FALSE, TRUE, 12);
  1623. #if GTK_CHECK_VERSION (3, 0, 0)
  1624. self->priv->hw_settings_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  1625. #else
  1626. self->priv->hw_settings_box = gtk_vbox_new (FALSE, 12);
  1627. #endif
  1628. gtk_container_add (GTK_CONTAINER (box), self->priv->hw_settings_box);
  1629. #if GTK_CHECK_VERSION (3, 0, 0)
  1630. self->priv->input_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  1631. #else
  1632. self->priv->input_box = gtk_vbox_new (FALSE, 12);
  1633. #endif
  1634. gtk_container_set_border_width (GTK_CONTAINER (self->priv->input_box), 12);
  1635. label = gtk_label_new (_("Input"));
  1636. gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook),
  1637. self->priv->input_box,
  1638. label);
  1639. self->priv->input_bar = create_bar (self, TRUE, TRUE);
  1640. gvc_channel_bar_set_name (GVC_CHANNEL_BAR (self->priv->input_bar),
  1641. _("_Input volume: "));
  1642. gvc_channel_bar_set_low_icon_name (GVC_CHANNEL_BAR (self->priv->input_bar),
  1643. "audio-input-microphone-low");
  1644. gvc_channel_bar_set_high_icon_name (GVC_CHANNEL_BAR (self->priv->input_bar),
  1645. "audio-input-microphone-high");
  1646. gtk_widget_show (self->priv->input_bar);
  1647. gtk_widget_set_sensitive (self->priv->input_bar, FALSE);
  1648. alignment = gtk_alignment_new (0, 0, 1, 1);
  1649. gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0);
  1650. gtk_container_add (GTK_CONTAINER (alignment), self->priv->input_bar);
  1651. gtk_box_pack_start (GTK_BOX (self->priv->input_box),
  1652. alignment,
  1653. FALSE, FALSE, 0);
  1654. #if GTK_CHECK_VERSION (3, 0, 0)
  1655. box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
  1656. sbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
  1657. ebox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
  1658. #else
  1659. box = gtk_hbox_new (FALSE, 6);
  1660. sbox = gtk_hbox_new (FALSE, 6);
  1661. ebox = gtk_hbox_new (FALSE, 6);
  1662. #endif
  1663. gtk_box_pack_start (GTK_BOX (self->priv->input_box),
  1664. box,
  1665. FALSE, FALSE, 6);
  1666. gtk_box_pack_start (GTK_BOX (box),
  1667. sbox,
  1668. FALSE, FALSE, 0);
  1669. label = gtk_label_new (_("Input level:"));
  1670. gtk_box_pack_start (GTK_BOX (sbox),
  1671. label,
  1672. FALSE, FALSE, 0);
  1673. gtk_size_group_add_widget (self->priv->size_group, sbox);
  1674. self->priv->input_level_bar = gvc_level_bar_new ();
  1675. gvc_level_bar_set_orientation (GVC_LEVEL_BAR (self->priv->input_level_bar),
  1676. GTK_ORIENTATION_HORIZONTAL);
  1677. gvc_level_bar_set_scale (GVC_LEVEL_BAR (self->priv->input_level_bar),
  1678. GVC_LEVEL_SCALE_LINEAR);
  1679. gtk_box_pack_start (GTK_BOX (box),
  1680. self->priv->input_level_bar,
  1681. TRUE, TRUE, 6);
  1682. gtk_box_pack_start (GTK_BOX (box),
  1683. ebox,
  1684. FALSE, FALSE, 0);
  1685. gtk_size_group_add_widget (self->priv->size_group, ebox);
  1686. #if GTK_CHECK_VERSION (3, 0, 0)
  1687. self->priv->input_settings_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
  1688. #else
  1689. self->priv->input_settings_box = gtk_hbox_new (FALSE, 6);
  1690. #endif
  1691. gtk_box_pack_start (GTK_BOX (self->priv->input_box),
  1692. self->priv->input_settings_box,
  1693. FALSE, FALSE, 0);
  1694. box = gtk_frame_new (_("C_hoose a device for sound input:"));
  1695. label = gtk_frame_get_label_widget (GTK_FRAME (box));
  1696. make_label_bold (GTK_LABEL (label));
  1697. gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
  1698. gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE);
  1699. gtk_box_pack_start (GTK_BOX (self->priv->input_box), box, TRUE, TRUE, 0);
  1700. alignment = gtk_alignment_new (0, 0, 1, 1);
  1701. gtk_container_add (GTK_CONTAINER (box), alignment);
  1702. gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0);
  1703. self->priv->input_treeview =
  1704. create_stream_treeview (self, G_CALLBACK (on_input_radio_toggled));
  1705. gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->priv->input_treeview);
  1706. box = gtk_scrolled_window_new (NULL, NULL);
  1707. gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box),
  1708. GTK_POLICY_NEVER,
  1709. GTK_POLICY_AUTOMATIC);
  1710. gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box),
  1711. GTK_SHADOW_IN);
  1712. gtk_container_add (GTK_CONTAINER (box), self->priv->input_treeview);
  1713. gtk_container_add (GTK_CONTAINER (alignment), box);
  1714. selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->input_treeview));
  1715. gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
  1716. /* Output page */
  1717. #if GTK_CHECK_VERSION (3, 0, 0)
  1718. self->priv->output_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  1719. #else
  1720. self->priv->output_box = gtk_vbox_new (FALSE, 12);
  1721. #endif
  1722. gtk_container_set_border_width (GTK_CONTAINER (self->priv->output_box), 12);
  1723. label = gtk_label_new (_("Output"));
  1724. gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook),
  1725. self->priv->output_box,
  1726. label);
  1727. box = gtk_frame_new (_("C_hoose a device for sound output:"));
  1728. label = gtk_frame_get_label_widget (GTK_FRAME (box));
  1729. make_label_bold (GTK_LABEL (label));
  1730. gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
  1731. gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE);
  1732. gtk_box_pack_start (GTK_BOX (self->priv->output_box), box, TRUE, TRUE, 0);
  1733. alignment = gtk_alignment_new (0, 0, 1, 1);
  1734. gtk_container_add (GTK_CONTAINER (box), alignment);
  1735. gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0);
  1736. self->priv->output_treeview = create_stream_treeview (self,
  1737. G_CALLBACK (on_output_radio_toggled));
  1738. gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->priv->output_treeview);
  1739. box = gtk_scrolled_window_new (NULL, NULL);
  1740. gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box),
  1741. GTK_POLICY_NEVER,
  1742. GTK_POLICY_AUTOMATIC);
  1743. gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box),
  1744. GTK_SHADOW_IN);
  1745. gtk_container_add (GTK_CONTAINER (box), self->priv->output_treeview);
  1746. gtk_container_add (GTK_CONTAINER (alignment), box);
  1747. selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->output_treeview));
  1748. gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
  1749. box = gtk_frame_new (_("Settings for the selected device:"));
  1750. label = gtk_frame_get_label_widget (GTK_FRAME (box));
  1751. make_label_bold (GTK_LABEL (label));
  1752. gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE);
  1753. gtk_box_pack_start (GTK_BOX (self->priv->output_box), box, FALSE, FALSE, 12);
  1754. #if GTK_CHECK_VERSION (3, 0, 0)
  1755. self->priv->output_settings_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
  1756. #else
  1757. self->priv->output_settings_box = gtk_vbox_new (FALSE, 0);
  1758. #endif
  1759. gtk_container_add (GTK_CONTAINER (box), self->priv->output_settings_box);
  1760. self->priv->output_settings_frame = box;
  1761. /* Applications */
  1762. self->priv->applications_window = gtk_scrolled_window_new (NULL, NULL);
  1763. gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (self->priv->applications_window),
  1764. GTK_POLICY_NEVER,
  1765. GTK_POLICY_AUTOMATIC);
  1766. gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (self->priv->applications_window),
  1767. GTK_SHADOW_IN);
  1768. #if GTK_CHECK_VERSION (3, 0, 0)
  1769. self->priv->applications_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  1770. #else
  1771. self->priv->applications_box = gtk_vbox_new (FALSE, 12);
  1772. #endif
  1773. gtk_container_set_border_width (GTK_CONTAINER (self->priv->applications_box), 12);
  1774. #if GTK_CHECK_VERSION (3, 8, 0)
  1775. gtk_container_add (GTK_CONTAINER (self->priv->applications_window),
  1776. self->priv->applications_box);
  1777. #else
  1778. gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (self->priv->applications_window),
  1779. self->priv->applications_box);
  1780. #endif
  1781. label = gtk_label_new (_("Applications"));
  1782. gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook),
  1783. self->priv->applications_window,
  1784. label);
  1785. self->priv->no_apps_label = gtk_label_new (_("No application is currently playing or recording audio."));
  1786. gtk_box_pack_start (GTK_BOX (self->priv->applications_box),
  1787. self->priv->no_apps_label,
  1788. TRUE, TRUE, 0);
  1789. gtk_widget_show_all (main_vbox);
  1790. list = mate_mixer_context_list_streams (self->priv->context);
  1791. while (list != NULL) {
  1792. add_stream (self, MATE_MIXER_STREAM (list->data));
  1793. list = list->next;
  1794. }
  1795. list = mate_mixer_context_list_devices (self->priv->context);
  1796. while (list != NULL) {
  1797. add_device (self, MATE_MIXER_DEVICE (list->data));
  1798. list = list->next;
  1799. }
  1800. selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->hw_treeview));
  1801. /* Select the first device in the list */
  1802. // XXX handle no devices
  1803. if (gtk_tree_selection_get_selected (selection, NULL, NULL) == FALSE) {
  1804. GtkTreeModel *model =
  1805. gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->hw_treeview));
  1806. if (gtk_tree_model_get_iter_first (model, &iter))
  1807. gtk_tree_selection_select_iter (selection, &iter);
  1808. }
  1809. return object;
  1810. }
  1811. static MateMixerContext *
  1812. gvc_mixer_dialog_get_context (GvcMixerDialog *dialog)
  1813. {
  1814. return dialog->priv->context;
  1815. }
  1816. static void
  1817. gvc_mixer_dialog_set_context (GvcMixerDialog *dialog, MateMixerContext *context)
  1818. {
  1819. dialog->priv->context = g_object_ref (context);
  1820. g_signal_connect (G_OBJECT (dialog->priv->context),
  1821. "stream-added",
  1822. G_CALLBACK (on_context_stream_added),
  1823. dialog);
  1824. g_signal_connect (G_OBJECT (dialog->priv->context),
  1825. "stream-removed",
  1826. G_CALLBACK (on_context_stream_removed),
  1827. dialog);
  1828. g_signal_connect (G_OBJECT (dialog->priv->context),
  1829. "device-added",
  1830. G_CALLBACK (on_context_device_added),
  1831. dialog);
  1832. g_signal_connect (G_OBJECT (dialog->priv->context),
  1833. "device-removed",
  1834. G_CALLBACK (on_context_device_removed),
  1835. dialog);
  1836. g_signal_connect (G_OBJECT (dialog->priv->context),
  1837. "notify::default-input-stream",
  1838. G_CALLBACK (on_context_default_input_stream_notify),
  1839. dialog);
  1840. g_signal_connect (G_OBJECT (dialog->priv->context),
  1841. "notify::default-output-stream",
  1842. G_CALLBACK (on_context_default_output_stream_notify),
  1843. dialog);
  1844. g_signal_connect (G_OBJECT (dialog->priv->context),
  1845. "stored-control-added",
  1846. G_CALLBACK (on_context_stored_control_added),
  1847. dialog);
  1848. g_signal_connect (G_OBJECT (dialog->priv->context),
  1849. "stored-control-removed",
  1850. G_CALLBACK (on_context_stored_control_removed),
  1851. dialog);
  1852. dialog->priv->backend_flags = mate_mixer_context_get_backend_flags (context);
  1853. g_object_notify (G_OBJECT (dialog), "context");
  1854. }
  1855. static void
  1856. gvc_mixer_dialog_set_property (GObject *object,
  1857. guint prop_id,
  1858. const GValue *value,
  1859. GParamSpec *pspec)
  1860. {
  1861. GvcMixerDialog *self = GVC_MIXER_DIALOG (object);
  1862. switch (prop_id) {
  1863. case PROP_CONTEXT:
  1864. gvc_mixer_dialog_set_context (self, g_value_get_object (value));
  1865. break;
  1866. default:
  1867. G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  1868. break;
  1869. }
  1870. }
  1871. static void
  1872. gvc_mixer_dialog_get_property (GObject *object,
  1873. guint prop_id,
  1874. GValue *value,
  1875. GParamSpec *pspec)
  1876. {
  1877. GvcMixerDialog *self = GVC_MIXER_DIALOG (object);
  1878. switch (prop_id) {
  1879. case PROP_CONTEXT:
  1880. g_value_set_object (value, gvc_mixer_dialog_get_context (self));
  1881. break;
  1882. default:
  1883. G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  1884. break;
  1885. }
  1886. }
  1887. static void
  1888. gvc_mixer_dialog_dispose (GObject *object)
  1889. {
  1890. GvcMixerDialog *dialog = GVC_MIXER_DIALOG (object);
  1891. if (dialog->priv->context != NULL) {
  1892. g_signal_handlers_disconnect_by_data (G_OBJECT (dialog->priv->context),
  1893. dialog);
  1894. g_clear_object (&dialog->priv->context);
  1895. }
  1896. G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->dispose (object);
  1897. }
  1898. static void
  1899. gvc_mixer_dialog_class_init (GvcMixerDialogClass *klass)
  1900. {
  1901. GObjectClass *object_class = G_OBJECT_CLASS (klass);
  1902. object_class->constructor = gvc_mixer_dialog_constructor;
  1903. object_class->dispose = gvc_mixer_dialog_dispose;
  1904. object_class->finalize = gvc_mixer_dialog_finalize;
  1905. object_class->set_property = gvc_mixer_dialog_set_property;
  1906. object_class->get_property = gvc_mixer_dialog_get_property;
  1907. g_object_class_install_property (object_class,
  1908. PROP_CONTEXT,
  1909. g_param_spec_object ("context",
  1910. "Context",
  1911. "MateMixer context",
  1912. MATE_MIXER_TYPE_CONTEXT,
  1913. G_PARAM_READWRITE |
  1914. G_PARAM_CONSTRUCT_ONLY |
  1915. G_PARAM_STATIC_STRINGS));
  1916. #if GTK_CHECK_VERSION (3, 20, 0)
  1917. GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  1918. gtk_widget_class_set_css_name (widget_class, "GvcMixerDialog");
  1919. #endif
  1920. g_type_class_add_private (klass, sizeof (GvcMixerDialogPrivate));
  1921. }
  1922. static void
  1923. gvc_mixer_dialog_init (GvcMixerDialog *dialog)
  1924. {
  1925. dialog->priv = GVC_MIXER_DIALOG_GET_PRIVATE (dialog);
  1926. dialog->priv->bars = g_hash_table_new (g_str_hash, g_str_equal);
  1927. dialog->priv->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
  1928. }
  1929. static void
  1930. gvc_mixer_dialog_finalize (GObject *object)
  1931. {
  1932. GvcMixerDialog *dialog;
  1933. dialog = GVC_MIXER_DIALOG (object);
  1934. g_hash_table_destroy (dialog->priv->bars);
  1935. G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->finalize (object);
  1936. }
  1937. GvcMixerDialog *
  1938. gvc_mixer_dialog_new (MateMixerContext *context)
  1939. {
  1940. return g_object_new (GVC_TYPE_MIXER_DIALOG,
  1941. "icon-name", "multimedia-volume-control",
  1942. "title", _("Sound Preferences"),
  1943. "context", context,
  1944. NULL);
  1945. }
  1946. gboolean
  1947. gvc_mixer_dialog_set_page (GvcMixerDialog *self, const gchar *page)
  1948. {
  1949. guint num = 0;
  1950. g_return_val_if_fail (GVC_IS_MIXER_DIALOG (self), FALSE);
  1951. if (page != NULL) {
  1952. if (g_str_equal (page, "effects"))
  1953. num = PAGE_EFFECTS;
  1954. else if (g_str_equal (page, "hardware"))
  1955. num = PAGE_HARDWARE;
  1956. else if (g_str_equal (page, "input"))
  1957. num = PAGE_INPUT;
  1958. else if (g_str_equal (page, "output"))
  1959. num = PAGE_OUTPUT;
  1960. else if (g_str_equal (page, "applications"))
  1961. num = PAGE_APPLICATIONS;
  1962. }
  1963. gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
  1964. return TRUE;
  1965. }