pnmixer
Volume mixer for the system tray
ui-popup-window.c
Go to the documentation of this file.
1 /* ui-popup-window.c
2  * PNmixer is written by Nick Lanham, a fork of OBmixer
3  * which was programmed by Lee Ferrett, derived
4  * from the program "AbsVolume" by Paul Sherman
5  * This program is free software; you can redistribute
6  * it and/or modify it under the terms of the GNU General
7  * Public License v3. source code is available at
8  * <http://github.com/nicklan/pnmixer>
9  */
10 
39 #ifdef HAVE_CONFIG_H
40 #include "config.h"
41 #endif
42 
43 #include <stdlib.h>
44 #include <string.h>
45 #include <glib.h>
46 #include <gtk/gtk.h>
47 #include <gdk/gdkx.h>
48 #ifndef WITH_GTK3
49 #include <gdk/gdkkeysyms.h>
50 #endif
51 
52 #include "audio.h"
53 #include "prefs.h"
54 #include "support-intl.h"
55 #include "support-log.h"
56 #include "support-ui.h"
57 #include "ui-popup-window.h"
58 
59 #include "main.h"
60 
61 #ifdef WITH_GTK3
62 #define POPUP_WINDOW_HORIZONTAL_UI_FILE "popup-window-horizontal-gtk3.glade"
63 #define POPUP_WINDOW_VERTICAL_UI_FILE "popup-window-vertical-gtk3.glade"
64 #else
65 #define POPUP_WINDOW_HORIZONTAL_UI_FILE "popup-window-horizontal-gtk2.glade"
66 #define POPUP_WINDOW_VERTICAL_UI_FILE "popup-window-vertical-gtk2.glade"
67 #endif
68 
69 /* Helpers */
70 
71 /* Configure the appearance of the text that is shown around the volume slider,
72  * according to the current preferences.
73  */
74 static void
75 configure_vol_text(GtkScale *vol_scale)
76 {
77  gboolean enabled;
78  gint position;
79  GtkPositionType gtk_position;
80 
81  enabled = prefs_get_boolean("DisplayTextVolume", TRUE);
82  position = prefs_get_integer("TextVolumePosition", 0);
83 
84  gtk_position =
85  position == 0 ? GTK_POS_TOP :
86  position == 1 ? GTK_POS_BOTTOM :
87  position == 2 ? GTK_POS_LEFT :
88  GTK_POS_RIGHT;
89 
90  if (enabled) {
91  gtk_scale_set_draw_value(vol_scale, TRUE);
92  gtk_scale_set_value_pos(vol_scale, gtk_position);
93  } else {
94  gtk_scale_set_draw_value(vol_scale, FALSE);
95  }
96 }
97 
98 /* Configure the page and step increment of the volume slider,
99  * according to the current preferences.
100  */
101 static void
102 configure_vol_increment(GtkAdjustment *vol_scale_adj)
103 {
104  gdouble scroll_step;
105  gdouble fine_scroll_step;
106 
107  scroll_step = prefs_get_double("ScrollStep", 5);
108  fine_scroll_step = prefs_get_double("FineScrollStep", 1);
109 
110  gtk_adjustment_set_page_increment(vol_scale_adj, scroll_step);
111  gtk_adjustment_set_step_increment(vol_scale_adj, fine_scroll_step);
112 }
113 
114 /* Update the mute checkbox according to the current audio state. */
115 static void
116 update_mute_check(GtkToggleButton *mute_check, GCallback handler_func,
117  gpointer handler_data, gboolean has_mute, gboolean muted)
118 {
119  gint n_blocked;
120 
121  n_blocked = g_signal_handlers_block_by_func
122  (G_OBJECT(mute_check), DATA_PTR(handler_func), handler_data);
123  g_assert(n_blocked == 1);
124 
125  if (has_mute == FALSE) {
126  gtk_toggle_button_set_active(mute_check, TRUE);
127  gtk_widget_set_sensitive(GTK_WIDGET(mute_check), FALSE);
128  gtk_widget_set_tooltip_text(GTK_WIDGET(mute_check),
129  _("Soundcard has no mute switch"));
130  } else {
131  gtk_toggle_button_set_active(mute_check, muted);
132  gtk_widget_set_tooltip_text(GTK_WIDGET(mute_check), NULL);
133  }
134 
135  g_signal_handlers_unblock_by_func
136  (G_OBJECT(mute_check), DATA_PTR(handler_func), handler_data);
137 }
138 
139 /* Update the volume slider according to the current audio state. */
140 static void
141 update_volume_slider(GtkAdjustment *vol_scale_adj, gdouble volume)
142 {
143  gtk_adjustment_set_value(vol_scale_adj, volume);
144 }
145 
146 /* Grab mouse and keyboard */
147 #ifdef WITH_GTK3
148 #if GTK_CHECK_VERSION(3,20,0)
149 static void
150 grab_devices(GtkWidget *window)
151 {
152  GdkGrabStatus status;
153  GdkDevice *device;
154 
155  /* Grab the current event device */
156  device = gtk_get_current_event_device();
157  if (device == NULL) {
158  WARN("Couldn't get current device");
159  return;
160  }
161 
162  /* Grab every seat capabilities */
163  status = gdk_seat_grab(gdk_device_get_seat(device),
164  gtk_widget_get_window(window),
165  GDK_SEAT_CAPABILITY_ALL, TRUE,
166  NULL, NULL, NULL, NULL);
167  if (status != GDK_GRAB_SUCCESS)
168  WARN("Could not grab %s", gdk_device_get_name(device));
169 }
170 #else
171 static void
172 grab_devices(GtkWidget *window)
173 {
174  GdkDevice *pointer_dev;
175  GdkDevice *keyboard_dev;
176  GdkGrabStatus status;
177 
178  /* Grab the mouse */
179  pointer_dev = gtk_get_current_event_device();
180  if (pointer_dev == NULL) {
181  WARN("Couldn't get current device");
182  return;
183  }
184 
185  status = gdk_device_grab(pointer_dev,
186  gtk_widget_get_window(window),
187  GDK_OWNERSHIP_NONE,
188  TRUE,
189  GDK_BUTTON_PRESS_MASK,
190  NULL,
191  GDK_CURRENT_TIME);
192  if (status != GDK_GRAB_SUCCESS)
193  WARN("Could not grab %s", gdk_device_get_name(pointer_dev));
194 
195  /* Grab the keyboard */
196  keyboard_dev = gdk_device_get_associated_device(pointer_dev);
197  if (keyboard_dev == NULL) {
198  WARN("Couldn't get associated device");
199  return;
200  }
201 
202  status = gdk_device_grab(keyboard_dev,
203  gtk_widget_get_window(window),
204  GDK_OWNERSHIP_NONE,
205  TRUE,
206  GDK_KEY_PRESS_MASK,
207  NULL, GDK_CURRENT_TIME);
208  if (status != GDK_GRAB_SUCCESS)
209  WARN("Could not grab %s", gdk_device_get_name(keyboard_dev));
210 }
211 #endif /* GTK_CHECK_VERSION(3,20,0) */
212 #else
213 static void
214 grab_devices(GtkWidget *window)
215 {
216  gdk_pointer_grab(gtk_widget_get_window(window), TRUE,
217  GDK_BUTTON_PRESS_MASK, NULL, NULL,
218  GDK_CURRENT_TIME);
219  gdk_keyboard_grab(gtk_widget_get_window(window), TRUE,
220  GDK_CURRENT_TIME);
221 }
222 #endif /* WITH_GTK3 */
223 
224 /* Public functions & signal handlers */
225 
226 struct popup_window {
227  /* Audio system */
229  /* Widgets */
230  GtkWidget *popup_window;
231  GtkWidget *vol_scale;
232  GtkAdjustment *vol_scale_adj;
233  GtkWidget *mute_check;
234 };
235 
246 gboolean
247 on_popup_window_event(G_GNUC_UNUSED GtkWidget *widget, GdkEvent *event,
248  PopupWindow *window)
249 {
250  switch (event->type) {
251 
252  /* If a click happens outside of the popup, hide it */
253  case GDK_BUTTON_PRESS: {
254  gint x, y;
255 #ifdef WITH_GTK3
256  GdkDevice *device = gtk_get_current_event_device();
257 
258  if (!gdk_device_get_window_at_position(device, &x, &y))
259 #else
260  if (!gdk_window_at_pointer(&x, &y))
261 #endif
262  popup_window_hide(window);
263 
264  break;
265  }
266 
267  /* If 'Esc' is pressed, hide popup */
268  case GDK_KEY_PRESS:
269  if (event->key.keyval == GDK_KEY_Escape)
270  popup_window_hide(window);
271  break;
272 
273  /* Broken grab, hide popup */
274  case GDK_GRAB_BROKEN:
275  popup_window_hide(window);
276  break;
277 
278  /* Unhandle event, do nothing */
279  default:
280  break;
281  }
282 
283  return FALSE;
284 }
285 
302 void
303 on_vol_scale_value_changed(GtkRange *range, PopupWindow *window)
304 {
305  gdouble value;
306 
307  value = gtk_range_get_value(range);
308  audio_set_volume(window->audio, AUDIO_USER_POPUP, value, 0);
309 }
310 
318 void
319 on_mute_check_toggled(G_GNUC_UNUSED GtkToggleButton *button, PopupWindow *window)
320 {
322 }
323 
331 void
332 on_mixer_button_clicked(G_GNUC_UNUSED GtkButton *button, PopupWindow *window)
333 {
334  popup_window_hide(window);
336 }
337 
345 static void
346 on_audio_changed(G_GNUC_UNUSED Audio *audio, AudioEvent *event, gpointer data)
347 {
348  PopupWindow *window = (PopupWindow *) data;
349  GtkWidget *popup_window = window->popup_window;
350 
351  /* Nothing to do if the window is hidden.
352  * The window will be updated anyway when shown.
353  */
354  if (!gtk_widget_get_visible(popup_window))
355  return;
356 
357  /* Update mute checkbox */
358  update_mute_check(GTK_TOGGLE_BUTTON(window->mute_check),
359  G_CALLBACK(on_mute_check_toggled), window,
360  event->has_mute, event->muted);
361 
362  /* Update volume slider
363  * If the user changes the volume through the popup window,
364  * we MUST NOT update the slider value, it's been done already.
365  * It means that, as long as the popup window is visible,
366  * the slider value reflects the value set by user,
367  * and not the real value reported by the audio system.
368  */
369  if (event->user != AUDIO_USER_POPUP)
370  update_volume_slider(window->vol_scale_adj, event->volume);
371 }
372 
378 void
380 {
381  GtkWidget *popup_window = window->popup_window;
382  GtkWidget *vol_scale = window->vol_scale;
383  Audio *audio = window->audio;
384 
385  /* Update window elements at first */
386  update_mute_check(GTK_TOGGLE_BUTTON(window->mute_check),
387  G_CALLBACK(on_mute_check_toggled), window,
391 
392  /* Show the window */
393  gtk_widget_show_now(popup_window);
394 
395  /* Give focus to volume scale */
396  gtk_widget_grab_focus(vol_scale);
397 
398  /* Grab mouse and keyboard */
400 }
401 
407 void
409 {
410  gtk_widget_hide(window->popup_window);
411 }
412 
418 void
420 {
421  GtkWidget *popup_window = window->popup_window;
422 
423  if (gtk_widget_get_visible(popup_window))
424  popup_window_hide(window);
425  else
426  popup_window_show(window);
427 }
428 
429 /*
430  * Cleanup a popup window.
431  */
432 static void
434 {
435  DEBUG("Destroying");
436 
437  /* Disconnect audio signals */
439 
440  /* Destroy the Gtk window, freeing any resources */
441  gtk_widget_destroy(window->popup_window);
442 
443  /* Set the struct value to zero since it will be re-used */
444  memset(window, 0, sizeof(PopupWindow));
445 }
446 
447 /* Initialize a popup window.
448  * The struct is supposed to be empty at this point.
449  */
450 static void
452 {
453  gchar *uifile;
454  GtkBuilder *builder;
455 
456  /* Build UI file depending on slider orientation */
457  gchar *orientation;
458  orientation = prefs_get_string("SliderOrientation", "vertical");
459  if (!g_strcmp0(orientation, "horizontal"))
461  else
463  g_free(orientation);
464 
465  DEBUG("Building from ui file '%s'", uifile);
466  builder = gtk_builder_new_from_file(uifile);
467 
468  /* Save some widgets for later use */
469  assign_gtk_widget(builder, window, popup_window);
470  assign_gtk_widget(builder, window, mute_check);
471  assign_gtk_widget(builder, window, vol_scale);
472  assign_gtk_adjustment(builder, window, vol_scale_adj);
473 
474  /* Configure some widgets */
475  configure_vol_text(GTK_SCALE(window->vol_scale));
476  configure_vol_increment(GTK_ADJUSTMENT(window->vol_scale_adj));
477 
478  /* Connect ui signal handlers */
479  gtk_builder_connect_signals(builder, window);
480 
481  /* Connect audio signal handlers */
482  window->audio = audio;
484 
485  /* Cleanup */
486  g_object_unref(builder);
487  g_free(uifile);
488 }
489 
496 void
498 {
499  Audio *audio;
500 
501  /* Keep track of this pointer before cleaning up, we need it */
502  audio = window->audio;
503 
504  popup_window_cleanup(window);
505  popup_window_init(window, audio);
506 }
507 
513 void
515 {
516  popup_window_cleanup(window);
517  g_free(window);
518 }
519 
526 PopupWindow *
528 {
529  PopupWindow *window;
530 
531  window = g_new0(PopupWindow, 1);
532  popup_window_init(window, audio);
533 
534  return window;
535 }
void on_vol_scale_value_changed(GtkRange *range, PopupWindow *window)
GtkBuilder * gtk_builder_new_from_file(const gchar *filename)
Definition: support-ui.c:25
#define _(String)
Definition: support-intl.h:44
Internationalization support.
Logging support.
Header for audio.c.
#define DATA_PTR(ptr)
Definition: support-ui.h:52
#define POPUP_WINDOW_HORIZONTAL_UI_FILE
static Audio * audio
Definition: main.c:40
PopupWindow * popup_window_create(Audio *audio)
Header for support-ui.c.
void audio_signals_disconnect(Audio *audio, AudioCallback callback, gpointer data)
Definition: audio.c:318
#define assign_gtk_widget(builder, container, name)
Definition: support-ui.h:75
static void grab_devices(GtkWidget *window)
gboolean audio_has_mute(Audio *audio)
Definition: audio.c:378
void audio_toggle_mute(Audio *audio, AudioUser user)
Definition: audio.c:412
void audio_signals_connect(Audio *audio, AudioCallback callback, gpointer data)
Definition: audio.c:337
gboolean audio_is_muted(Audio *audio)
Definition: audio.c:395
static void configure_vol_increment(GtkAdjustment *vol_scale_adj)
Header for prefs.c.
#define POPUP_WINDOW_VERTICAL_UI_FILE
gboolean muted
Definition: audio.h:79
gboolean prefs_get_boolean(const gchar *key, gboolean def)
Definition: prefs.c:102
#define DEBUG(...)
Definition: support-log.h:38
void on_mixer_button_clicked(G_GNUC_UNUSED GtkButton *button, PopupWindow *window)
Header for main.c.
gboolean has_mute
Definition: audio.h:78
void popup_window_toggle(PopupWindow *window)
gchar * get_ui_file(const char *filename)
Definition: support-ui.c:58
void popup_window_destroy(PopupWindow *window)
void run_mixer_command(void)
Definition: main.c:79
static void on_audio_changed(G_GNUC_UNUSED Audio *audio, AudioEvent *event, gpointer data)
GtkWidget * vol_scale
GtkWidget * popup_window
Definition: audio.c:198
AudioUser user
Definition: audio.h:75
static void popup_window_cleanup(PopupWindow *window)
static void update_mute_check(GtkToggleButton *mute_check, GCallback handler_func, gpointer handler_data, gboolean has_mute, gboolean muted)
gchar * prefs_get_string(const gchar *key, const gchar *def)
Definition: prefs.c:171
gdouble audio_get_volume(Audio *audio)
Definition: audio.c:437
#define assign_gtk_adjustment(builder, container, name)
Definition: support-ui.h:81
void on_mute_check_toggled(G_GNUC_UNUSED GtkToggleButton *button, PopupWindow *window)
gint prefs_get_integer(const gchar *key, gint def)
Definition: prefs.c:125
void audio_set_volume(Audio *audio, AudioUser user, gdouble new_volume, gint dir)
Definition: audio.c:500
Header for ui-popup-window.c.
GtkAdjustment * vol_scale_adj
GtkWidget * mute_check
static void update_volume_slider(GtkAdjustment *vol_scale_adj, gdouble volume)
void popup_window_reload(PopupWindow *window)
gboolean on_popup_window_event(G_GNUC_UNUSED GtkWidget *widget, GdkEvent *event, PopupWindow *window)
void popup_window_hide(PopupWindow *window)
static void configure_vol_text(GtkScale *vol_scale)
void popup_window_show(PopupWindow *window)
gdouble volume
Definition: audio.h:80
#define WARN(...)
Definition: support-log.h:37
gdouble prefs_get_double(const gchar *key, gdouble def)
Definition: prefs.c:148
static void popup_window_init(PopupWindow *window, Audio *audio)