1 //##############################################################################
2 // volumeicon
3 //
4 // volumeicon.c - implements the gtk status icon and preferences window
5 //
6 // Copyright 2011 Maato
7 //
8 // Authors:
9 // Maato <maato@softwarebakery.com>
10 //
11 // This program is free software: you can redistribute it and/or modify it
12 // under the terms of the GNU General Public License version 3, as published
13 // by the Free Software Foundation.
14 //
15 // This program is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranties of
17 // MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
18 // PURPOSE. See the GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License along
21 // with this program. If not, see <http://www.gnu.org/licenses/>.
22 //##############################################################################
23
24 #include <stdlib.h>
25 #include <assert.h>
26 #include <gtk/gtk.h>
27 #include <glib/gstdio.h>
28 #include <glib/gi18n.h>
29 #include <signal.h>
30 #include <unistd.h>
31
32 #ifdef COMPILEWITH_NOTIFY
33 #ifndef NOTIFY_CHECK_VERSION
34 #define NOTIFY_CHECK_VERSION(a,b,c) 0
35 #endif
36 #include <libnotify/notify.h>
37 #endif
38 #ifdef COMPILEWITH_OSS
39 #include "oss_backend.h"
40 #else
41 #include "alsa_backend.h"
42 #endif
43 #include "keybinder.h"
44 #include "config.h"
45
46 enum HOTKEY
47 {
48 UP,
49 DOWN,
50 MUTE
51 };
52
53 enum NOTIFICATION
54 {
55 NOTIFICATION_NATIVE,
56 #ifdef COMPILEWITH_NOTIFY
57 NOTIFICATION_LIBNOTIFY,
58 #endif
59 N_NOTIFICATIONS
60 };
61
62 //##############################################################################
63 // Definitions
64 //##############################################################################
65 // Resources
66 #define PREFERENCES_UI_FILE DATADIR "/gui/preferences.ui"
67 #define ICONS_DIR DATADIR "/icons"
68 #define APP_ICON DATADIR "/gui/appicon.svg"
69
70 // About
71 #define APPNAME "Volume Icon"
72 #define COPYRIGHT "Copyright (c) Maato 2011"
73 #define COMMENTS "Volume control for your system tray."
74 #define WEBSITE "http://softwarebakery.com/maato/volumeicon.html"
75
76 // Scale constants
77 #define SCALE_HIDE_DELAY 500
78 #define TIMER_INTERVAL 50
79
80 //##############################################################################
81 // Type definitions
82 //##############################################################################
83 typedef struct
84 {
85 GtkBuilder * builder;
86 GtkWidget * window;
87 GtkAdjustment * volume_adjustment;
88 GtkEntry * mixer_entry;
89 GtkComboBox * device_combobox;
90 GtkListStore * device_store;
91 GtkComboBox * channel_combobox;
92 GtkListStore * channel_store;
93 GtkComboBox * theme_combobox;
94 GtkListStore * theme_store;
95 GtkCheckButton * use_panel_specific_icons_checkbutton;
96 GtkListStore * hotkey_store;
97 GtkButton * close_button;
98 GtkRadioButton * mute_radiobutton;
99 GtkRadioButton * slider_radiobutton;
100 GtkRadioButton * mmb_mute_radiobutton;
101 GtkRadioButton * mmb_mixer_radiobutton;
102 GtkCheckButton * use_horizontal_slider_checkbutton;
103 GtkCheckButton * show_sound_level_checkbutton;
104 GtkCellRenderer * hotkey_accel;
105 GtkCellRendererToggle * hotkey_toggle;
106 GtkCheckButton * use_transparent_background_checkbutton;
107 GtkCheckButton * show_notification_checkbutton;
108 GtkComboBox * notification_combobox;
109 GtkListStore * notification_store;
110 } PreferencesGui;
111
112 //##############################################################################
113 // Static variables
114 //##############################################################################
115 #ifdef COMPILEWITH_NOTIFY
116 static NotifyNotification * m_notification = NULL;
117 #endif
118 static GtkWindow *m_popup_window = NULL;
119 static GtkImage *m_popup_icon = NULL;
120 static GtkProgressBar *m_pbar = NULL;
121 static guint m_timeout_id = 0;
122
123 static GtkStatusIcon * m_status_icon = NULL;
124 static GtkWidget * m_scale_window = NULL;
125 static GtkWidget * m_scale = NULL;
126 static gboolean m_setting_scale_value = FALSE;
127 static PreferencesGui * gui = NULL;
128
129 // Backend Interface
130 static void (*backend_setup)(const gchar * card, const gchar * channel,
131 void (*volume_changed)(int,gboolean)) = NULL;
132 static void (*backend_set_channel)(const gchar * channel) = NULL;
133 static void (*backend_set_volume)(int volume) = NULL;
134 static void (*backend_set_mute)(gboolean mute) = NULL;
135 static int (*backend_get_volume)(void) = NULL;
136 static gboolean (*backend_get_mute)(void) = NULL;
137 static const gchar * (*backend_get_channel)(void) = NULL;
138 static const GList * (*backend_get_channel_names)(void) = NULL;
139 static const gchar * (*backend_get_device)(void) = NULL;
140 static const GList * (*backend_get_device_names)(void) = NULL;
141
142 // Status
143 static int m_volume = 0;
144 static gboolean m_mute = FALSE;
145
146 // Icons
147 #define ICON_COUNT 8
148 static GdkPixbuf * m_icons[ICON_COUNT];
149
150 //##############################################################################
151 // Function prototypes
152 //##############################################################################
153 static void volume_icon_on_volume_changed(int volume, gboolean mute);
154 static void status_icon_update(gboolean mute, gboolean force);
155 static void hotkey_handle(const char * key, void * user_data);
156 static void volume_icon_load_icons();
157 static void scale_update();
158 static void notification_show();
159
160 //##############################################################################
161 // Static functions
162 //##############################################################################
163 // Helper
clamp_volume(int value)164 static inline int clamp_volume(int value)
165 {
166 if(value < 0) return 0;
167 if(value > 100) return 100;
168 return value;
169 }
170
populate_device_model_and_combobox(PreferencesGui * gui)171 static void populate_device_model_and_combobox(PreferencesGui * gui)
172 {
173 // Clean existing data
174 gtk_list_store_clear(gui->device_store);
175
176 // Fill the channel model and combobox
177 GtkTreeIter tree_iter;
178 const GList * list_iter = backend_get_device_names();
179 while(list_iter)
180 {
181 gtk_list_store_append(gui->device_store, &tree_iter);
182 gtk_list_store_set(gui->device_store, &tree_iter, 0,
183 (gchar*)list_iter->data, -1);
184 if(g_strcmp0((gchar*)list_iter->data, backend_get_device()) == 0)
185 gtk_combo_box_set_active_iter(gui->device_combobox, &tree_iter);
186 list_iter = g_list_next(list_iter);
187 }
188 }
189
populate_channel_model_and_combobox(PreferencesGui * gui)190 static void populate_channel_model_and_combobox(PreferencesGui * gui)
191 {
192 // Clean existing data
193 gtk_list_store_clear(gui->channel_store);
194
195 // Fill the channel model and combobox
196 GtkTreeIter tree_iter;
197 const GList * list_iter = backend_get_channel_names();
198 while(list_iter)
199 {
200 gtk_list_store_append(gui->channel_store, &tree_iter);
201 gtk_list_store_set(gui->channel_store, &tree_iter, 0,
202 (gchar*)list_iter->data, -1);
203 if(g_strcmp0((gchar*)list_iter->data, backend_get_channel()) == 0)
204 gtk_combo_box_set_active_iter(gui->channel_combobox, &tree_iter);
205 list_iter = g_list_next(list_iter);
206 }
207 }
208
209 // Preferences handlers
preferences_window_delete_event(GtkWidget * widget,GdkEvent * event,gpointer user_data)210 static gboolean preferences_window_delete_event(GtkWidget * widget,
211 GdkEvent * event, gpointer user_data)
212 {
213 gtk_widget_destroy(gui->window);
214 return FALSE;
215 }
216
preferences_window_destroy(GObject * object,gpointer user_data)217 static void preferences_window_destroy(GObject * object, gpointer user_data)
218 {
219 g_free(gui);
220 gui = NULL;
221 config_write();
222 }
223
preferences_close_button_clicked(GtkWidget * widget,gpointer user_data)224 static void preferences_close_button_clicked(GtkWidget * widget,
225 gpointer user_data)
226 {
227 gtk_widget_destroy(gui->window);
228 }
229
preferences_mute_radiobutton_toggled(GtkToggleButton * togglebutton,gpointer user_data)230 static void preferences_mute_radiobutton_toggled(GtkToggleButton * togglebutton,
231 gpointer user_data)
232 {
233 gboolean active = gtk_toggle_button_get_active(togglebutton);
234 config_set_left_mouse_slider(!active);
235 }
236
preferences_mmb_radiobutton_toggled(GtkToggleButton * togglebutton,gpointer user_data)237 static void preferences_mmb_radiobutton_toggled(GtkToggleButton * togglebutton,
238 gpointer user_data)
239 {
240 gboolean active = gtk_toggle_button_get_active(togglebutton);
241 config_set_middle_mouse_mute(!active);
242 }
243
244 static void scale_setup();
245
preferences_use_horizontal_slider_checkbutton_toggled(GtkCheckButton * widget,gpointer user_data)246 static void preferences_use_horizontal_slider_checkbutton_toggled(GtkCheckButton * widget,
247 gpointer user_data)
248 {
249 gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
250 config_set_use_horizontal_slider(active);
251 gtk_widget_destroy(m_scale);
252 gtk_widget_destroy(m_scale_window);
253 scale_setup();
254 }
255
preferences_show_sound_level_checkbutton_toggled(GtkCheckButton * widget,gpointer user_data)256 static void preferences_show_sound_level_checkbutton_toggled(GtkCheckButton * widget,
257 gpointer user_data)
258 {
259 gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
260 config_set_show_sound_level(active);
261 gtk_scale_set_draw_value(GTK_SCALE(m_scale), active);
262 }
263
preferences_theme_combobox_changed(GtkComboBox * widget,gpointer user_data)264 static void preferences_theme_combobox_changed(GtkComboBox * widget,
265 gpointer user_data)
266 {
267 GtkTreeIter iter;
268
269 // Get the theme from the combobox
270 if(gtk_combo_box_get_active_iter(gui->theme_combobox, &iter))
271 {
272 gchar * theme;
273 gtk_tree_model_get(GTK_TREE_MODEL(gui->theme_store), &iter, 0,
274 &theme, -1);
275 config_set_theme(theme);
276 g_free(theme);
277
278 // Update the sensitivity of `use_panel_specific_icons_checkbutton'.
279 gtk_widget_set_sensitive(
280 GTK_WIDGET(gui->use_panel_specific_icons_checkbutton),
281 config_get_use_gtk_theme());
282 }
283 volume_icon_load_icons();
284 status_icon_update(m_mute, TRUE);
285 }
286
preferences_device_combobox_changed(GtkComboBox * widget,gpointer user_data)287 static void preferences_device_combobox_changed(GtkComboBox * widget,
288 gpointer user_data)
289 {
290 GtkTreeIter iter;
291 if(gtk_combo_box_get_active_iter(gui->device_combobox, &iter))
292 {
293 gchar * device;
294 gtk_tree_model_get(GTK_TREE_MODEL(gui->device_store), &iter, 0,
295 &device, -1);
296 backend_setup(device, NULL, volume_icon_on_volume_changed);
297 config_set_card(device);
298 config_set_channel(backend_get_channel());
299 m_volume = clamp_volume(backend_get_volume());
300 m_mute = backend_get_mute();
301 g_free(device);
302
303 populate_channel_model_and_combobox(gui);
304 }
305 }
306
preferences_channel_combobox_changed(GtkComboBox * widget,gpointer user_data)307 static void preferences_channel_combobox_changed(GtkComboBox * widget,
308 gpointer user_data)
309 {
310 GtkTreeIter iter;
311
312 // Get the channel from the combobox
313 if(gtk_combo_box_get_active_iter(gui->channel_combobox, &iter))
314 {
315 gchar * channel;
316 gtk_tree_model_get(GTK_TREE_MODEL(gui->channel_store), &iter, 0,
317 &channel, -1);
318 backend_set_channel(channel);
319 config_set_channel(channel);
320 g_free(channel);
321 m_volume = clamp_volume(backend_get_volume());
322 m_mute = backend_get_mute();
323 }
324 status_icon_update(m_mute, TRUE);
325 scale_update();
326 }
327
preferences_volume_adjustment_changed(GtkSpinButton * spinbutton,gpointer user_data)328 static void preferences_volume_adjustment_changed(GtkSpinButton * spinbutton,
329 gpointer user_data)
330 {
331 config_set_stepsize((int)gtk_adjustment_get_value(gui->volume_adjustment));
332 }
333
preferences_mixer_entry_changed(GtkEditable * editable,gpointer user_data)334 static void preferences_mixer_entry_changed(GtkEditable * editable,
335 gpointer user_data)
336 {
337 config_set_helper(gtk_entry_get_text(gui->mixer_entry));
338 }
339
preferences_use_panel_specific_icons_checkbutton_toggled(GtkCheckButton * widget,gpointer user_data)340 static void preferences_use_panel_specific_icons_checkbutton_toggled(
341 GtkCheckButton *widget, gpointer user_data)
342 {
343 config_set_use_panel_specific_icons(
344 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
345 status_icon_update(m_mute, TRUE);
346 }
347
preferences_hotkey_toggle_toggled(GtkCellRendererToggle * cell_renderer,gchar * path,gpointer user_data)348 static void preferences_hotkey_toggle_toggled(GtkCellRendererToggle * cell_renderer,
349 gchar * path, gpointer user_data)
350 {
351 GtkTreeIter iter;
352
353 if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(gui->hotkey_store), &iter, path))
354 {
355 gboolean enabled = FALSE;
356 gchar * accel_name = NULL;
357 enum HOTKEY hotkey = 0;
358 gtk_tree_model_get(GTK_TREE_MODEL(gui->hotkey_store), &iter, 1, &accel_name, 2, &hotkey, 3, &enabled, -1);
359 enabled = !enabled;
360
361 if(!enabled)
362 keybinder_unbind(accel_name, hotkey_handle);
363
364 if(enabled && !keybinder_bind(accel_name, hotkey_handle, (void*)hotkey))
365 {
366 g_fprintf(stderr, "Failed to bind %s\n", accel_name);
367 }
368 else
369 {
370 gtk_list_store_set(GTK_LIST_STORE(gui->hotkey_store), &iter, 3, enabled, -1);
371 switch(hotkey)
372 {
373 case UP:
374 config_set_hotkey_up_enabled(enabled);
375 break;
376 case DOWN:
377 config_set_hotkey_down_enabled(enabled);
378 break;
379 case MUTE:
380 config_set_hotkey_mute_enabled(enabled);
381 break;
382 default:
383 break;
384 }
385 }
386
387 g_free(accel_name);
388 }
389 }
390
preferences_hotkey_accel_edited(GtkCellRendererAccel * renderer,gchar * path,guint accel_key,GdkModifierType mask,guint hardware_keycode,gpointer user_data)391 static void preferences_hotkey_accel_edited(GtkCellRendererAccel * renderer,
392 gchar * path, guint accel_key, GdkModifierType mask, guint hardware_keycode,
393 gpointer user_data)
394 {
395 GtkTreeIter iter;
396
397 if(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(gui->hotkey_store), &iter, path))
398 {
399 enum HOTKEY hotkey = 0;
400 gboolean enabled = FALSE;
401 gchar * old_value = NULL;
402 gchar * new_value = gtk_accelerator_name(accel_key, mask);
403 gtk_tree_model_get(GTK_TREE_MODEL(gui->hotkey_store), &iter, 1, &old_value, 2, &hotkey, 3, &enabled, -1);
404
405 if(enabled && !keybinder_bind(new_value, hotkey_handle, (void*)hotkey))
406 {
407 g_fprintf(stderr, "Failed to bind %s\n", new_value);
408 }
409 else
410 {
411 gtk_list_store_set(GTK_LIST_STORE(gui->hotkey_store), &iter, 1, new_value, -1);
412 keybinder_unbind(old_value, hotkey_handle);
413 switch(hotkey)
414 {
415 case UP:
416 config_set_hotkey_up(new_value);
417 break;
418 case DOWN:
419 config_set_hotkey_down(new_value);
420 break;
421 case MUTE:
422 config_set_hotkey_mute(new_value);
423 break;
424 default:
425 break;
426 }
427 }
428 g_free(new_value);
429 g_free(old_value);
430 }
431 }
432
preferences_use_transparent_background_checkbutton_toggled(GtkCheckButton * widget,gpointer user_data)433 static void preferences_use_transparent_background_checkbutton_toggled(GtkCheckButton * widget,
434 gpointer user_data)
435 {
436 gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
437 config_set_use_transparent_background(active);
438 gtk_widget_destroy(m_scale);
439 gtk_widget_destroy(m_scale_window);
440 scale_setup();
441 }
442
preferences_show_notification_checkbutton_toggled(GtkCheckButton * widget,gpointer user_data)443 static void preferences_show_notification_checkbutton_toggled(
444 GtkCheckButton *widget, gpointer user_data)
445 {
446 gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
447 gtk_widget_set_sensitive(GTK_WIDGET(gui->notification_combobox), active);
448 config_set_show_notification(active);
449 }
450
preferences_notification_combobox_changed(GtkComboBox * widget,gpointer user_data)451 static void preferences_notification_combobox_changed(
452 GtkComboBox *widget, gpointer user_data)
453 {
454 GtkTreeIter iter;
455
456 // Get the channel from the combobox
457 if (gtk_combo_box_get_active_iter(gui->notification_combobox, &iter))
458 {
459 gint type;
460 gtk_tree_model_get(GTK_TREE_MODEL(gui->notification_store), &iter, 1,
461 &type, -1);
462 config_set_notification_type(type);
463 }
464 }
465
466 // Menu handlers
menu_preferences_on_activate(GtkMenuItem * menuitem,gpointer user_data)467 static void menu_preferences_on_activate(GtkMenuItem * menuitem,
468 gpointer user_data)
469 {
470 if(gui)
471 {
472 gtk_window_present(GTK_WINDOW(gui->window));
473 return;
474 }
475
476 gui = (PreferencesGui *)g_malloc(sizeof *gui);
477
478 gui->builder = gtk_builder_new();
479 gtk_builder_add_from_file(gui->builder, PREFERENCES_UI_FILE, NULL);
480
481 // Get widgets from builder
482 #define getobj(x) gtk_builder_get_object(gui->builder,x)
483 gui->window = GTK_WIDGET(getobj("window"));
484 gui->volume_adjustment = GTK_ADJUSTMENT(getobj("volume_adjustment"));
485 gui->mixer_entry = GTK_ENTRY(getobj("mixer_entry"));
486 gui->device_combobox = GTK_COMBO_BOX(getobj("device_combobox"));
487 gui->device_store = GTK_LIST_STORE(getobj("device_name_model"));
488 gui->channel_combobox = GTK_COMBO_BOX(getobj("channel_combobox"));
489 gui->channel_store = GTK_LIST_STORE(getobj("channel_name_model"));
490 gui->theme_combobox = GTK_COMBO_BOX(getobj("theme_combobox"));
491 gui->theme_store = GTK_LIST_STORE(getobj("theme_name_model"));
492 gui->use_panel_specific_icons_checkbutton = GTK_CHECK_BUTTON(getobj("use_panel_specific_icons_checkbutton"));
493 gui->hotkey_store = GTK_LIST_STORE(getobj("hotkey_binding_model"));
494 gui->close_button = GTK_BUTTON(getobj("close_button"));
495 gui->mute_radiobutton = GTK_RADIO_BUTTON(getobj("mute_radiobutton"));
496 gui->slider_radiobutton = GTK_RADIO_BUTTON(getobj("slider_radiobutton"));
497 gui->mmb_mute_radiobutton = GTK_RADIO_BUTTON(getobj("mmb_mute_radiobutton"));
498 gui->mmb_mixer_radiobutton = GTK_RADIO_BUTTON(getobj("mmb_mixer_radiobutton"));
499 gui->use_horizontal_slider_checkbutton = GTK_CHECK_BUTTON(getobj("use_horizontal_slider"));
500 gui->show_sound_level_checkbutton = GTK_CHECK_BUTTON(getobj("show_sound_level"));
501 gui->hotkey_accel = GTK_CELL_RENDERER(getobj("hotkey_cellrendereraccel"));
502 gui->hotkey_toggle = GTK_CELL_RENDERER_TOGGLE(getobj("hotkey_cellrenderertoggle"));
503 gui->use_transparent_background_checkbutton = GTK_CHECK_BUTTON(getobj("use_transparent_background"));
504 gui->show_notification_checkbutton = GTK_CHECK_BUTTON(getobj("show_notifications"));
505 gui->notification_combobox = GTK_COMBO_BOX(getobj("notification_combobox"));
506 gui->notification_store = GTK_LIST_STORE(getobj("notification_type_model"));
507 #undef getobj
508
509 // Set the window icon
510 gtk_window_set_default_icon_from_file(APP_ICON, NULL);
511
512 // Set the radiobuttons
513 if(config_get_left_mouse_slider())
514 {
515 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gui->slider_radiobutton),
516 TRUE);
517 }
518 else
519 {
520 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gui->mute_radiobutton),
521 TRUE);
522 }
523
524 if(config_get_middle_mouse_mute())
525 {
526 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gui->mmb_mute_radiobutton),
527 TRUE);
528 }
529 else
530 {
531 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gui->mmb_mixer_radiobutton),
532 TRUE);
533 }
534
535 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gui->use_horizontal_slider_checkbutton),
536 config_get_use_horizontal_slider());
537
538 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gui->show_sound_level_checkbutton),
539 config_get_show_sound_level());
540
541 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gui->use_transparent_background_checkbutton),
542 config_get_use_transparent_background());
543
544 gtk_toggle_button_set_active(
545 GTK_TOGGLE_BUTTON(gui->show_notification_checkbutton),
546 config_get_show_notification());
547
548 populate_device_model_and_combobox(gui);
549 populate_channel_model_and_combobox(gui);
550
551 // Fill the theme name model and combobox
552 GtkTreeIter tree_iter;
553 gtk_list_store_append(gui->theme_store, &tree_iter);
554 gtk_list_store_set(gui->theme_store, &tree_iter, 0, "Default", -1);
555 gboolean use_gtk_theme = config_get_use_gtk_theme();
556 if(use_gtk_theme)
557 gtk_combo_box_set_active_iter(gui->theme_combobox, &tree_iter);
558 GDir * themedir = g_dir_open(ICONS_DIR, 0, NULL);
559 if (themedir)
560 {
561 const gchar * name;
562 while((name = g_dir_read_name(themedir)))
563 {
564 gtk_list_store_append(gui->theme_store, &tree_iter);
565 gtk_list_store_set(gui->theme_store, &tree_iter, 0, name, -1);
566 if(g_strcmp0(name, config_get_theme()) == 0)
567 gtk_combo_box_set_active_iter(gui->theme_combobox, &tree_iter);
568 }
569 g_dir_close(themedir);
570 }
571 gtk_widget_set_sensitive(
572 GTK_WIDGET(gui->use_panel_specific_icons_checkbutton), use_gtk_theme);
573 gtk_toggle_button_set_active(
574 GTK_TOGGLE_BUTTON(gui->use_panel_specific_icons_checkbutton),
575 config_get_use_panel_specific_icons());
576
577 // Fill the hotkey binding model
578 gtk_list_store_append(gui->hotkey_store, &tree_iter);
579 gtk_list_store_set(gui->hotkey_store, &tree_iter, 0, _("Volume Up"), 1,
580 config_get_hotkey_up(), 2, (int)UP, 3, config_get_hotkey_up_enabled(), -1);
581 gtk_list_store_append(gui->hotkey_store, &tree_iter);
582 gtk_list_store_set(gui->hotkey_store, &tree_iter, 0, _("Volume Down"), 1,
583 config_get_hotkey_down(), 2, (int)DOWN, 3, config_get_hotkey_down_enabled(), -1);
584 gtk_list_store_append(gui->hotkey_store, &tree_iter);
585 gtk_list_store_set(gui->hotkey_store, &tree_iter, 0, _("Mute"), 1,
586 config_get_hotkey_mute(), 2, (int)MUTE, 3, config_get_hotkey_mute_enabled(), -1);
587
588 // Fill the notification type model.
589 gtk_list_store_append(gui->notification_store, &tree_iter);
590 gtk_list_store_set(gui->notification_store, &tree_iter, 0,
591 _("GTK+ Popup Window"), 1, (gint)NOTIFICATION_NATIVE,
592 -1);
593 #ifdef COMPILEWITH_NOTIFY
594 gtk_list_store_append(gui->notification_store, &tree_iter);
595 gtk_list_store_set(gui->notification_store, &tree_iter, 0,
596 _("libnotify"), 1, (gint)NOTIFICATION_LIBNOTIFY, -1);
597 #endif
598 gint notification_type = config_get_notification_type();
599 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(gui->notification_store),
600 &tree_iter, NULL, notification_type);
601 gtk_combo_box_set_active_iter(gui->notification_combobox, &tree_iter);
602 gtk_widget_set_sensitive(GTK_WIDGET(gui->notification_combobox),
603 config_get_show_notification());
604
605 // Initialize widgets
606 gtk_entry_set_text(gui->mixer_entry, config_get_helper());
607 gtk_adjustment_set_value(gui->volume_adjustment,
608 (gdouble)config_get_stepsize());
609
610 // Connect signals
611 g_signal_connect(G_OBJECT(gui->window), "destroy", G_CALLBACK(
612 preferences_window_destroy), NULL);
613 g_signal_connect(G_OBJECT(gui->window), "delete-event", G_CALLBACK(
614 preferences_window_delete_event), NULL);
615 g_signal_connect(G_OBJECT(gui->close_button), "clicked", G_CALLBACK(
616 preferences_close_button_clicked), NULL);
617 g_signal_connect(G_OBJECT(gui->device_combobox), "changed", G_CALLBACK(
618 preferences_device_combobox_changed), NULL);
619 g_signal_connect(G_OBJECT(gui->channel_combobox), "changed", G_CALLBACK(
620 preferences_channel_combobox_changed), NULL);
621 g_signal_connect(G_OBJECT(gui->theme_combobox), "changed", G_CALLBACK(
622 preferences_theme_combobox_changed), NULL);
623 g_signal_connect(G_OBJECT(gui->volume_adjustment), "value-changed",
624 G_CALLBACK(preferences_volume_adjustment_changed), NULL);
625 g_signal_connect(G_OBJECT(gui->mixer_entry), "changed", G_CALLBACK(
626 preferences_mixer_entry_changed), NULL);
627 g_signal_connect(
628 G_OBJECT(gui->use_panel_specific_icons_checkbutton), "toggled",
629 G_CALLBACK(preferences_use_panel_specific_icons_checkbutton_toggled),
630 NULL);
631 g_signal_connect(G_OBJECT(gui->mute_radiobutton), "toggled", G_CALLBACK(
632 preferences_mute_radiobutton_toggled), NULL);
633 g_signal_connect(G_OBJECT(gui->mmb_mixer_radiobutton), "toggled", G_CALLBACK(
634 preferences_mmb_radiobutton_toggled), NULL);
635 g_signal_connect(G_OBJECT(gui->use_horizontal_slider_checkbutton), "toggled", G_CALLBACK(
636 preferences_use_horizontal_slider_checkbutton_toggled), NULL);
637 g_signal_connect(G_OBJECT(gui->show_sound_level_checkbutton), "toggled", G_CALLBACK(
638 preferences_show_sound_level_checkbutton_toggled), NULL);
639 g_signal_connect(G_OBJECT(gui->hotkey_accel), "accel-edited", G_CALLBACK(
640 preferences_hotkey_accel_edited), NULL);
641 g_signal_connect(G_OBJECT(gui->hotkey_toggle), "toggled", G_CALLBACK(
642 preferences_hotkey_toggle_toggled), NULL);
643 g_signal_connect(G_OBJECT(gui->use_transparent_background_checkbutton), "toggled", G_CALLBACK(
644 preferences_use_transparent_background_checkbutton_toggled), NULL);
645 g_signal_connect(
646 G_OBJECT(gui->show_notification_checkbutton), "toggled",
647 G_CALLBACK(preferences_show_notification_checkbutton_toggled), NULL);
648 g_signal_connect(
649 G_OBJECT(gui->notification_combobox), "changed",
650 G_CALLBACK(preferences_notification_combobox_changed), NULL);
651
652 gtk_widget_show_all(gui->window);
653 }
654
menu_quit_on_activate(GtkMenuItem * menuitem,gpointer user_data)655 static void menu_quit_on_activate(GtkMenuItem * menuitem, gpointer user_data)
656 {
657 // Destroy the preferences window on shutdown to make sure the current
658 // settings are saved as well.
659 if (gui)
660 gtk_widget_destroy(gui->window);
661 gtk_main_quit();
662 }
663
menu_about_on_activate(GtkMenuItem * menuitem,gpointer user_data)664 static void menu_about_on_activate(GtkMenuItem * menuitem, gpointer user_data)
665 {
666 GtkWidget * aboutDialog = gtk_about_dialog_new();
667 gtk_about_dialog_set_program_name(GTK_ABOUT_DIALOG(aboutDialog), APPNAME);
668 gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(aboutDialog), VERSION);
669 gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(aboutDialog),
670 gdk_pixbuf_new_from_file(APP_ICON, NULL));
671 gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(aboutDialog), COPYRIGHT);
672 gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(aboutDialog), COMMENTS);
673 gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(aboutDialog), WEBSITE);
674 gtk_dialog_run(GTK_DIALOG(aboutDialog));
675 gtk_widget_destroy(aboutDialog);
676 }
677
volume_icon_launch_helper()678 static void volume_icon_launch_helper()
679 {
680 assert(config_get_helper() != NULL);
681 pid_t pid = fork();
682 if(pid == 0)
683 {
684 execl("/bin/sh", "/bin/sh", "-c", config_get_helper(), (char*)NULL);
685 _exit(0);
686 }
687 }
688
menu_volcontrol_on_activate(GtkMenuItem * menuitem,gpointer user_data)689 static void menu_volcontrol_on_activate(GtkMenuItem * menuitem,
690 gpointer user_data)
691 {
692 volume_icon_launch_helper();
693 }
694
scale_point_in_rect(GdkRectangle * rect,gint x,gint y)695 static gboolean scale_point_in_rect(GdkRectangle * rect, gint x, gint y)
696 {
697 return (x >= rect->x &&
698 x <= rect->x + rect->width &&
699 y >= rect->y &&
700 y <= rect->y + rect->height);
701 }
702
scale_timeout(gpointer data)703 static gboolean scale_timeout(gpointer data)
704 {
705 static int counter = SCALE_HIDE_DELAY;
706 GdkRectangle window;
707 GdkRectangle icon;
708
709 gtk_window_get_position(GTK_WINDOW(m_scale_window), &window.x, &window.y);
710 gtk_window_get_size(GTK_WINDOW(m_scale_window), &window.width, &window.height);
711 gtk_status_icon_get_geometry(m_status_icon, NULL, &icon, NULL);
712
713 GdkWindow *root_window;
714 GdkDeviceManager *device_manager;
715 GdkDevice *pointer;
716 gint x, y;
717
718 root_window = gdk_screen_get_root_window(gtk_widget_get_screen(m_scale_window));
719 device_manager = gdk_display_get_device_manager(gdk_window_get_display(root_window));
720 pointer = gdk_device_manager_get_client_pointer(device_manager);
721 gdk_window_get_device_position(root_window, pointer, &x, &y, NULL);
722
723 if(scale_point_in_rect(&window, x, y) || scale_point_in_rect(&icon, x, y))
724 {
725 counter = SCALE_HIDE_DELAY;
726 return TRUE;
727 }
728 else if(counter > 0)
729 {
730 counter -= TIMER_INTERVAL;
731 return TRUE;
732 }
733 gtk_widget_hide(m_scale_window);
734 return FALSE;
735 }
736
737 // StatusIcon handlers
status_icon_on_button_press(GtkStatusIcon * status_icon,GdkEventButton * event,gpointer user_data)738 static gboolean status_icon_on_button_press(GtkStatusIcon * status_icon,
739 GdkEventButton * event, gpointer user_data)
740 {
741 if(event->button == 1 && config_get_left_mouse_slider())
742 {
743 if(gtk_widget_get_visible(m_scale_window))
744 {
745 gtk_widget_hide(m_scale_window);
746 return TRUE;
747 }
748
749 gint sizex;
750 gint sizey;
751 gtk_window_get_size(GTK_WINDOW(m_scale_window), &sizex, &sizey);
752
753 gint x = (gint)event->x_root - sizex / 2;
754 gint y = (gint)event->y_root - sizey;
755
756 if(gtk_status_icon_is_embedded(m_status_icon))
757 {
758 GdkRectangle area;
759 gtk_status_icon_get_geometry(m_status_icon, NULL, &area, NULL);
760 if(config_get_use_horizontal_slider())
761 {
762 y = area.y + area.height / 2 - sizey / 2;
763 if(area.x > sizex) // popup left
764 x = area.x - sizex;
765 else // popup right
766 x = area.x + area.width;
767 }
768 else
769 {
770 x = area.x + area.width / 2 - sizex / 2;
771 if(area.y > sizey) // popup up
772 y = area.y - sizey;
773 else // popup down
774 y = area.y + area.height;
775 }
776 }
777
778 gtk_window_move(GTK_WINDOW(m_scale_window), x, y);
779 gtk_window_present_with_time(GTK_WINDOW(m_scale_window), event->time);
780 g_timeout_add(TIMER_INTERVAL, scale_timeout, NULL);
781 }
782 else if((event->button == 1 && !config_get_left_mouse_slider()) ||
783 (event->button == 2 && config_get_middle_mouse_mute()))
784 {
785 m_mute = !m_mute;
786 backend_set_volume(m_volume);
787 backend_set_mute(m_mute);
788 status_icon_update(m_mute, FALSE);
789 }
790 else if(event->button == 2)
791 {
792 volume_icon_launch_helper();
793 }
794 else
795 {
796 return FALSE;
797 }
798
799 return TRUE;
800 }
801
status_icon_on_scroll_event(GtkStatusIcon * status_icon,GdkEventScroll * event,gpointer user_data)802 static void status_icon_on_scroll_event(GtkStatusIcon * status_icon,
803 GdkEventScroll * event, gpointer user_data)
804 {
805 switch(event->direction)
806 {
807 case(GDK_SCROLL_UP):
808 case(GDK_SCROLL_RIGHT):
809 m_volume = clamp_volume(m_volume + config_get_stepsize());
810 break;
811 case(GDK_SCROLL_DOWN):
812 case(GDK_SCROLL_LEFT):
813 m_volume = clamp_volume(m_volume - config_get_stepsize());
814 break;
815 default:
816 break;
817 }
818
819 backend_set_volume(m_volume);
820 if(m_mute)
821 {
822 m_mute = FALSE;
823 backend_set_mute(m_mute);
824 }
825 status_icon_update(m_mute, FALSE);
826 scale_update();
827 notification_show();
828 }
829
status_icon_on_popup_menu(GtkStatusIcon * status_icon,guint button,guint activation_time,gpointer user_data)830 static void status_icon_on_popup_menu(GtkStatusIcon * status_icon, guint button,
831 guint activation_time, gpointer user_data)
832 {
833 GtkWidget * gtkMenu = gtk_menu_new();
834
835 GtkWidget * volcontrol = gtk_image_menu_item_new_with_label(_("Open Mixer"));
836 GtkWidget * separator1 = gtk_separator_menu_item_new();
837 GtkWidget * preferences = gtk_image_menu_item_new_from_stock(
838 "gtk-preferences", NULL);
839 GtkWidget * about = gtk_image_menu_item_new_from_stock("gtk-about", NULL);
840 GtkWidget * separator2 = gtk_separator_menu_item_new();
841 GtkWidget * quit = gtk_image_menu_item_new_from_stock("gtk-quit", NULL);
842 g_signal_connect(G_OBJECT(volcontrol), "activate",
843 G_CALLBACK(menu_volcontrol_on_activate), NULL);
844 g_signal_connect(G_OBJECT(preferences), "activate",
845 G_CALLBACK(menu_preferences_on_activate), NULL);
846 g_signal_connect(G_OBJECT(quit), "activate",
847 G_CALLBACK(menu_quit_on_activate), NULL);
848 g_signal_connect(G_OBJECT(about), "activate",
849 G_CALLBACK(menu_about_on_activate), NULL);
850
851 gtk_menu_shell_append(GTK_MENU_SHELL(gtkMenu), volcontrol);
852 gtk_menu_shell_append(GTK_MENU_SHELL(gtkMenu), separator1);
853 gtk_menu_shell_append(GTK_MENU_SHELL(gtkMenu), preferences);
854 gtk_menu_shell_append(GTK_MENU_SHELL(gtkMenu), about);
855 gtk_menu_shell_append(GTK_MENU_SHELL(gtkMenu), separator2);
856 gtk_menu_shell_append(GTK_MENU_SHELL(gtkMenu), quit);
857
858 gtk_widget_show_all(gtkMenu);
859
860 gtk_menu_popup(GTK_MENU(gtkMenu), NULL, NULL,
861 gtk_status_icon_position_menu, status_icon,
862 button, activation_time);
863 }
864
status_icon_get_number(int volume,gboolean mute)865 static int status_icon_get_number(int volume, gboolean mute)
866 {
867 if(mute) return 1;
868 if(volume <= 0) return 1;
869 if(volume <= 16) return 2;
870 if(volume <= 33) return 3;
871 if(volume <= 50) return 4;
872 if(volume <= 67) return 5;
873 if(volume <= 84) return 6;
874 if(volume <= 99) return 7;
875 return 8;
876 }
877
878 // Use the ignore_cache parameter to force the status icon to be loaded
879 // from file, for example after a theme change.
status_icon_update(gboolean mute,gboolean ignore_cache)880 static void status_icon_update(gboolean mute, gboolean ignore_cache)
881 {
882 static int volume_cache = -1;
883 static int icon_cache = -1;
884 int volume = backend_get_volume();
885
886 int icon_number = status_icon_get_number(volume, mute);
887 if(icon_number != icon_cache || ignore_cache)
888 {
889 const gchar *icon_name;
890
891 if (icon_number == 1)
892 icon_name = "audio-volume-muted";
893 else if (icon_number <= 3)
894 icon_name = "audio-volume-low";
895 else if (icon_number <= 6)
896 icon_name = "audio-volume-medium";
897 else
898 icon_name = "audio-volume-high";
899
900 if(config_get_use_gtk_theme())
901 {
902 // Check if we are supposed to use the *-panel variant of an icon.
903 // Note that this only makes sense if we're using the default GTK
904 // icon theme as the icons that ship with volumeicon don't have
905 // panel-specific versions.
906 GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
907 gchar *panel_icon_name = g_strdup_printf("%s-panel", icon_name);
908 if (config_get_use_panel_specific_icons() &&
909 gtk_icon_theme_has_icon(icon_theme, panel_icon_name))
910 {
911 gtk_status_icon_set_from_icon_name(
912 m_status_icon, panel_icon_name);
913 }
914 else
915 {
916 gtk_status_icon_set_from_icon_name(m_status_icon, icon_name);
917 }
918 g_free(panel_icon_name);
919 }
920 else
921 {
922 gtk_status_icon_set_from_pixbuf(
923 m_status_icon, m_icons[icon_number-1]);
924 }
925
926 // Always use the current GTK icon theme for notifications.
927 #ifdef COMPILEWITH_NOTIFY
928 notify_notification_update(m_notification, APPNAME, NULL, icon_name);
929 #endif
930 gtk_image_set_from_icon_name(m_popup_icon, icon_name,
931 GTK_ICON_SIZE_LARGE_TOOLBAR);
932
933 icon_cache = icon_number;
934 }
935
936 if((volume != volume_cache || ignore_cache) && backend_get_channel())
937 {
938 gchar buffer[32];
939 g_sprintf(buffer, "%s: %d%%", backend_get_channel(), volume);
940 gtk_status_icon_set_tooltip_text(m_status_icon, buffer);
941
942 #ifdef COMPILEWITH_NOTIFY
943 notify_notification_set_hint_int32(m_notification, "value",
944 (gint)volume);
945 #endif
946 gtk_progress_bar_set_fraction(m_pbar, volume / 100.0);
947
948 volume_cache = volume;
949 }
950 }
951
icon_theme_on_changed(GtkIconTheme * icon_theme,gpointer user_data)952 static void icon_theme_on_changed(GtkIconTheme *icon_theme, gpointer user_data)
953 {
954 status_icon_update(m_mute, TRUE);
955 }
956
status_icon_setup(gboolean mute)957 static void status_icon_setup(gboolean mute)
958 {
959 GtkIconTheme *icon_theme = gtk_icon_theme_get_default();
960
961 g_signal_connect(G_OBJECT(icon_theme), "changed",
962 G_CALLBACK(icon_theme_on_changed), NULL);
963
964 m_status_icon = gtk_status_icon_new();
965 g_signal_connect(G_OBJECT(m_status_icon), "button_press_event",
966 G_CALLBACK(status_icon_on_button_press), NULL);
967 g_signal_connect(G_OBJECT(m_status_icon), "scroll_event",
968 G_CALLBACK(status_icon_on_scroll_event), NULL);
969 g_signal_connect(G_OBJECT(m_status_icon), "popup-menu",
970 G_CALLBACK(status_icon_on_popup_menu), NULL);
971 status_icon_update(mute, FALSE);
972 gtk_status_icon_set_visible(m_status_icon, TRUE);
973 }
974
volume_icon_on_volume_changed(int volume,gboolean mute)975 static void volume_icon_on_volume_changed(int volume, gboolean mute)
976 {
977 m_mute = mute;
978 m_volume = clamp_volume(volume);
979 status_icon_update(m_mute, FALSE);
980 scale_update();
981 }
982
volume_icon_load_icons()983 static void volume_icon_load_icons()
984 {
985 if(config_get_use_gtk_theme())
986 return;
987
988 static gboolean icons_loaded = FALSE;
989 const gchar *theme = config_get_theme();
990 int i;
991
992 for(i = 0; i < ICON_COUNT; i++)
993 {
994 gchar *icon_path = g_strdup_printf(ICONS_DIR"/%s/%d.png", theme, i+1);
995 if(icons_loaded && m_icons[i])
996 g_object_unref(m_icons[i]);
997 m_icons[i] = gdk_pixbuf_new_from_file(icon_path, NULL);
998 if(!m_icons[i])
999 g_message("Failed to load '%s'", icon_path);
1000 g_free(icon_path);
1001 }
1002 icons_loaded = TRUE;
1003 }
1004
scale_update()1005 static void scale_update()
1006 {
1007 assert(m_scale != NULL);
1008 m_setting_scale_value = TRUE;
1009 gtk_range_set_value(GTK_RANGE(m_scale), (double)m_volume);
1010 m_setting_scale_value = FALSE;
1011 }
1012
scale_value_changed(GtkRange * range,gpointer user_data)1013 static void scale_value_changed(GtkRange * range, gpointer user_data)
1014 {
1015 if(m_setting_scale_value)
1016 return;
1017 double value = gtk_range_get_value(range);
1018 m_volume = clamp_volume((int)value);
1019 backend_set_volume(m_volume);
1020 if(m_mute)
1021 {
1022 m_mute = FALSE;
1023 backend_set_mute(m_mute);
1024 }
1025 status_icon_update(m_mute, FALSE);
1026 scale_update();
1027 }
1028
hide_popup(gpointer user_data)1029 static gboolean hide_popup(gpointer user_data)
1030 {
1031 m_timeout_id = 0;
1032 gtk_widget_hide(GTK_WIDGET(m_popup_window));
1033 return FALSE;
1034 }
1035
notification_show()1036 static void notification_show()
1037 {
1038 if (config_get_show_notification())
1039 {
1040 gint type = config_get_notification_type();
1041 if (type == NOTIFICATION_NATIVE)
1042 {
1043 gtk_widget_show_all(GTK_WIDGET(m_popup_window));
1044 if (m_timeout_id)
1045 g_source_remove(m_timeout_id);
1046 m_timeout_id = g_timeout_add(1500, (GSourceFunc)hide_popup, NULL);
1047 }
1048 #ifdef COMPILEWITH_NOTIFY
1049 else
1050 {
1051 if (m_timeout_id)
1052 g_source_remove(m_timeout_id);
1053 hide_popup(NULL);
1054 notify_notification_show(m_notification, NULL);
1055 }
1056 #endif
1057 }
1058 else
1059 {
1060 if (m_timeout_id)
1061 g_source_remove(m_timeout_id);
1062 hide_popup(NULL);
1063 }
1064 }
1065
render_widget(cairo_t * cairo_context,gint width,gint height)1066 static void render_widget (cairo_t *cairo_context, gint width, gint height)
1067 {
1068 cairo_set_source_rgba (cairo_context, 1.0, 1.0, 1.0, 0.0);
1069 cairo_set_operator (cairo_context, CAIRO_OPERATOR_SOURCE);
1070 cairo_paint (cairo_context);
1071 }
1072
update_widget(GtkWidget * widget,gint width,gint height)1073 static void update_widget (GtkWidget *widget, gint width, gint height)
1074 {
1075 cairo_surface_t *mask;
1076 cairo_region_t *mask_region;
1077
1078 mask = cairo_image_surface_create(CAIRO_FORMAT_A1, width, height);
1079 if (cairo_surface_status(mask) == CAIRO_STATUS_SUCCESS) {
1080
1081 cairo_t *cairo_context = cairo_create(mask);
1082 if (cairo_status(cairo_context) == CAIRO_STATUS_SUCCESS) {
1083
1084 render_widget(cairo_context, width, height);
1085 cairo_destroy(cairo_context);
1086
1087 mask_region = gdk_cairo_region_create_from_surface(mask);
1088
1089 gtk_widget_input_shape_combine_region(widget, NULL);
1090 if (!gtk_widget_is_composited(widget))
1091 gtk_widget_input_shape_combine_region(widget, mask_region);
1092
1093 gtk_widget_shape_combine_region(widget, NULL);
1094 if (!gtk_widget_is_composited(widget))
1095 gtk_widget_shape_combine_region(widget, mask_region);
1096
1097 cairo_region_destroy(mask_region);
1098 }
1099
1100 cairo_surface_destroy(mask);
1101 }
1102 }
1103
on_configure(GtkWidget * widget,GdkEventConfigure * event,gpointer user_data)1104 static gboolean on_configure (GtkWidget *widget, GdkEventConfigure *event, gpointer user_data)
1105 {
1106 static gint width = 0, height = 0;
1107 if (width != event->width || height != event->height) {
1108 width = event->width;
1109 height = event->height;
1110 update_widget (widget, width, height);
1111 }
1112 return FALSE;
1113 }
1114
on_draw(GtkWidget * widget,cairo_t * cairo_context,gpointer user_data)1115 static gboolean on_draw (GtkWidget *widget, cairo_t *cairo_context, gpointer user_data)
1116 {
1117 render_widget(cairo_context,
1118 gtk_widget_get_allocated_width(widget),
1119 gtk_widget_get_allocated_height(widget));
1120 return FALSE;
1121 }
1122
on_composited_changed(GtkWidget * window,gpointer user_data)1123 static void on_composited_changed (GtkWidget* window, gpointer user_data)
1124 {
1125 gtk_widget_destroy(m_scale);
1126 gtk_widget_destroy(m_scale_window);
1127 scale_setup();
1128 }
1129
scale_setup()1130 static void scale_setup()
1131 {
1132 GdkScreen *screen;
1133
1134 if(config_get_use_horizontal_slider())
1135 m_scale = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0.0, 100.0, 1.0);
1136 else
1137 m_scale = gtk_scale_new_with_range(GTK_ORIENTATION_VERTICAL, 0.0, 100.0, 1.0);
1138 gtk_range_set_inverted(GTK_RANGE(m_scale), TRUE);
1139 gtk_scale_set_draw_value(GTK_SCALE(m_scale), config_get_show_sound_level());
1140
1141 m_scale_window = gtk_window_new(GTK_WINDOW_POPUP);
1142
1143 screen = gtk_widget_get_screen(GTK_WIDGET(m_scale_window));
1144 if (gdk_screen_is_composited(screen) && config_get_use_transparent_background()) {
1145 GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
1146 if (visual) {
1147 gtk_widget_set_visual(GTK_WIDGET(m_scale_window), visual);
1148 gtk_widget_set_app_paintable(GTK_WIDGET(m_scale_window), TRUE);
1149 gtk_widget_realize(GTK_WIDGET(m_scale_window));
1150 gdk_window_set_background_pattern(gtk_widget_get_window(GTK_WIDGET(m_scale_window)), NULL);
1151 gtk_window_set_type_hint(GTK_WINDOW(m_scale_window), GDK_WINDOW_TYPE_HINT_DOCK);
1152
1153 g_signal_connect(G_OBJECT(m_scale_window), "draw", G_CALLBACK(on_draw), NULL);
1154 g_signal_connect(G_OBJECT(m_scale_window), "configure-event", G_CALLBACK(on_configure), NULL);
1155
1156 update_widget(GTK_WIDGET(m_scale_window),
1157 gtk_widget_get_allocated_width(GTK_WIDGET(m_scale_window)),
1158 gtk_widget_get_allocated_height(GTK_WIDGET(m_scale_window)));
1159 }
1160 }
1161
1162 gtk_window_set_decorated(GTK_WINDOW(m_scale_window), FALSE);
1163 gtk_window_set_skip_pager_hint(GTK_WINDOW(m_scale_window), TRUE);
1164 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(m_scale_window), TRUE);
1165 gtk_window_set_keep_above(GTK_WINDOW(m_scale_window), TRUE);
1166 gtk_container_add(GTK_CONTAINER(m_scale_window), m_scale);
1167
1168 if(config_get_use_horizontal_slider()) {
1169 gtk_window_set_default_size(GTK_WINDOW(m_scale_window), 120, 0);
1170 gtk_scale_set_value_pos(GTK_SCALE(m_scale), GTK_POS_RIGHT);
1171 } else {
1172 gtk_window_set_default_size(GTK_WINDOW(m_scale_window), 0, 120);
1173 gtk_scale_set_value_pos(GTK_SCALE(m_scale), GTK_POS_TOP);
1174 }
1175
1176 gtk_widget_show(m_scale);
1177 scale_update();
1178
1179 g_signal_connect(G_OBJECT(m_scale), "value-changed",
1180 G_CALLBACK(scale_value_changed), NULL);
1181 g_signal_connect(G_OBJECT(m_scale_window), "composited-changed",
1182 G_CALLBACK(on_composited_changed), NULL);
1183 }
1184
hotkey_handle(const char * key,void * user_data)1185 static void hotkey_handle(const char * key, void * user_data)
1186 {
1187 enum HOTKEY hotkey = (enum HOTKEY)user_data;
1188 if(hotkey == MUTE)
1189 {
1190 m_mute = !m_mute;
1191 backend_set_volume(m_volume);
1192 backend_set_mute(m_mute);
1193 status_icon_update(m_mute, FALSE);
1194 }
1195 else
1196 {
1197 int step = config_get_stepsize();
1198 m_volume = clamp_volume(m_volume + (hotkey == UP ? step : -step));
1199 backend_set_volume(m_volume);
1200 status_icon_update(m_mute, FALSE);
1201 }
1202 scale_update();
1203 notification_show();
1204 }
1205
1206 //##############################################################################
1207 // Exported functions
1208 //##############################################################################
main(int argc,char * argv[])1209 int main(int argc, char * argv[])
1210 {
1211 bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
1212 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
1213 textdomain(GETTEXT_PACKAGE);
1214
1215 // Initialize gtk with arguments
1216 GError * error = 0;
1217 gchar * config_name = 0;
1218 gchar * device_name = 0;
1219 gboolean print_version = FALSE;
1220 GOptionEntry options[] = {
1221 { "config", 'c', 0, G_OPTION_ARG_FILENAME, &config_name,
1222 _("Alternate name to use for config file, default is volumeicon"), "name" },
1223 { "device", 'd', 0, G_OPTION_ARG_STRING, &device_name,
1224 _("Mixer device name"), "name" },
1225 { "version", 'v', 0, G_OPTION_ARG_NONE, &print_version,
1226 _("Output version number and exit"), NULL },
1227 { NULL }
1228 };
1229 if(!gtk_init_with_args(&argc, &argv, "", options, "", &error)) {
1230 if(error) {
1231 g_printerr("%s\n", error->message);
1232 }
1233 return EXIT_FAILURE;
1234 }
1235 signal(SIGCHLD, SIG_IGN);
1236
1237 if(print_version) {
1238 g_fprintf(stdout, "%s %s\n", APPNAME, VERSION);
1239 return EXIT_SUCCESS;
1240 }
1241
1242 // Setup OSD Notification
1243 #ifdef COMPILEWITH_NOTIFY
1244 if (notify_init(APPNAME))
1245 {
1246 #if NOTIFY_CHECK_VERSION(0,7,0)
1247 m_notification = notify_notification_new(APPNAME, NULL, NULL);
1248 #else
1249 m_notification = notify_notification_new(APPNAME, NULL, NULL, NULL);
1250 #endif
1251 }
1252
1253 if (m_notification != NULL) {
1254 notify_notification_set_timeout(m_notification, NOTIFY_EXPIRES_DEFAULT);
1255 notify_notification_set_hint_string(m_notification, "synchronous", "volume");
1256 }
1257 else
1258 {
1259 g_fprintf(stderr, "Failed to initialize notifications\n");
1260 }
1261 #endif
1262
1263 /* Set up the popup window. */
1264 m_popup_window = (GtkWindow *)gtk_window_new(GTK_WINDOW_POPUP);
1265 gtk_container_set_border_width(GTK_CONTAINER(m_popup_window), 10);
1266 gtk_window_set_default_size(m_popup_window, 180, -1);
1267 gtk_window_set_position(m_popup_window, GTK_WIN_POS_CENTER);
1268
1269 m_popup_icon = (GtkImage *)gtk_image_new();
1270
1271 /* Initialize the progress bar. */
1272 m_pbar = (GtkProgressBar *)gtk_progress_bar_new();
1273 gtk_progress_bar_set_fraction(m_pbar, 0.0);
1274 gtk_orientable_set_orientation(GTK_ORIENTABLE(m_pbar),
1275 GTK_ORIENTATION_HORIZONTAL);
1276 gtk_progress_bar_set_show_text(m_pbar, TRUE);
1277 gtk_widget_show(GTK_WIDGET(m_pbar));
1278
1279 /* Add icon image and progress bar to hbox. */
1280 GtkBox *hbox = (GtkBox *)gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
1281 gtk_box_pack_start(
1282 GTK_BOX(hbox), GTK_WIDGET(m_popup_icon), FALSE, FALSE, 0);
1283 gtk_box_pack_start(
1284 GTK_BOX(hbox), GTK_WIDGET(m_pbar), TRUE, TRUE, 0);
1285
1286 gtk_container_add(GTK_CONTAINER(m_popup_window), GTK_WIDGET(hbox));
1287
1288 // Setup backend
1289 #ifdef COMPILEWITH_OSS
1290 backend_setup = &oss_setup;
1291 backend_set_channel = &oss_set_channel;
1292 backend_set_volume = &oss_set_volume;
1293 backend_get_volume = &oss_get_volume;
1294 backend_set_mute = &oss_set_mute;
1295 backend_get_mute = &oss_get_mute;
1296 backend_get_channel = &oss_get_channel;
1297 backend_get_channel_names = &oss_get_channel_names;
1298 backend_get_device = &oss_get_device;
1299 backend_get_device_names = &oss_get_device_names;
1300 #else
1301 backend_setup = &asound_setup;
1302 backend_set_channel = &asound_set_channel;
1303 backend_set_volume = &asound_set_volume;
1304 backend_get_volume = &asound_get_volume;
1305 backend_get_mute = &asound_get_mute;
1306 backend_set_mute = &asound_set_mute;
1307 backend_get_channel = &asound_get_channel;
1308 backend_get_channel_names = &asound_get_channel_names;
1309 backend_get_device = &asound_get_device;
1310 backend_get_device_names = &asound_get_device_names;
1311 #endif
1312
1313 // Setup
1314 config_initialize(config_name);
1315 backend_setup(device_name ? device_name : config_get_card(), config_get_channel(), volume_icon_on_volume_changed);
1316 m_volume = clamp_volume(backend_get_volume());
1317 m_mute = backend_get_mute();
1318 volume_icon_load_icons();
1319 status_icon_setup(m_mute);
1320 scale_setup();
1321 gint notification_type = config_get_notification_type();
1322 if (notification_type < 0 || notification_type >= N_NOTIFICATIONS)
1323 config_set_notification_type((gint)NOTIFICATION_NATIVE);
1324
1325 keybinder_init();
1326 if(config_get_hotkey_up_enabled() &&
1327 !keybinder_bind(config_get_hotkey_up(), hotkey_handle, (void*)UP))
1328 g_fprintf(stderr, "Failed to bind %s\n", config_get_hotkey_up());
1329 if(config_get_hotkey_down_enabled() &&
1330 !keybinder_bind(config_get_hotkey_down(), hotkey_handle, (void*)DOWN))
1331 g_fprintf(stderr, "Failed to bind %s\n", config_get_hotkey_down());
1332 if(config_get_hotkey_mute_enabled() &&
1333 !keybinder_bind(config_get_hotkey_mute(), hotkey_handle, (void*)MUTE))
1334 g_fprintf(stderr, "Failed to bind %s\n", config_get_hotkey_mute());
1335
1336 // Main Loop
1337 gtk_main();
1338
1339 #ifdef COMPILEWITH_NOTIFY
1340 g_object_unref(G_OBJECT(m_notification));
1341 notify_uninit();
1342 #endif
1343 gtk_widget_destroy(GTK_WIDGET(m_popup_window));
1344
1345 return EXIT_SUCCESS;
1346 }
1347