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