1 /*-
2  * Copyright (c) 2021 Rozhuk Ivan <rozhuk.im@gmail.com>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * Author: Rozhuk Ivan <rozhuk.im@gmail.com>
27  *
28  */
29 
30 
31 #include <sys/param.h>
32 #include <sys/types.h>
33 #include <inttypes.h>
34 
35 #include "gtk-mixer.h"
36 
37 
38 typedef struct gtk_mixer_tray_icon_s {
39 	GtkStatusIcon *status_icon;
40 	GtkMenu *menu;
41 	const char *icon_name;
42 	gmp_dev_p dev;
43 	gmp_dev_line_p dev_line;
44 } gm_tray_icon_t, *gm_tray_icon_p;
45 
46 
47 
48 static gboolean
gtk_mixer_tray_icon_scroll(GtkStatusIcon * status_icon __unused,GdkEventScroll * event,gpointer user_data)49 gtk_mixer_tray_icon_scroll(GtkStatusIcon *status_icon __unused,
50     GdkEventScroll *event, gpointer user_data) {
51 	gm_tray_icon_p tray_icon = user_data;
52 
53 	if (NULL == tray_icon)
54 		return (FALSE);
55 	if (0 != tray_icon->dev_line->is_read_only)
56 		return (FALSE);
57 
58 	switch (event->direction) {
59 	case GDK_SCROLL_UP:
60 	case GDK_SCROLL_DOWN:
61 		gmp_dev_line_vol_glob_add(tray_icon->dev_line,
62 		    ((GDK_SCROLL_UP == event->direction) ? 1 : -1));
63 		tray_icon->dev_line->is_updated = 1;
64 		tray_icon->dev_line->write_required ++;
65 		gmp_dev_write(tray_icon->dev, 0);
66 		break;
67 	default:
68 		break;
69 	}
70 
71 	return (FALSE);
72 }
73 
74 static gboolean
gtk_mixer_tray_icon_release(GtkStatusIcon * status_icon,GdkEventButton * event,gpointer user_data)75 gtk_mixer_tray_icon_release(GtkStatusIcon *status_icon,
76     GdkEventButton *event, gpointer user_data) {
77 	gm_tray_icon_p tray_icon = user_data;
78 
79 	if (NULL == tray_icon)
80 		return (FALSE);
81 	if (0 != tray_icon->dev_line->is_read_only)
82 		return (FALSE);
83 
84 	switch (event->button) {
85 	case 2:
86 		tray_icon->dev_line->state.is_enabled =
87 		    ((0 != tray_icon->dev_line->state.is_enabled) ? 0 : 1);
88 		tray_icon->dev_line->is_updated = 1; /* Mixer must update controls. */
89 		tray_icon->dev_line->write_required ++;
90 		gmp_dev_write(tray_icon->dev, 0);
91 		gtk_mixer_tray_icon_update(status_icon);
92 		break;
93 	case 3:
94 		//tray_icon_menu_show(user_data);
95 		break;
96 	}
97 
98 	return (FALSE);
99 }
100 
101 void
gtk_mixer_tray_icon_update(GtkStatusIcon * status_icon)102 gtk_mixer_tray_icon_update(GtkStatusIcon *status_icon) {
103 	gm_tray_icon_p tray_icon = g_object_get_data(G_OBJECT(status_icon),
104 	    "__gtk_mixer_tray_icon");
105 	const char *stock = NULL, *display_name = "";
106 	char tool_tip[256];
107 	int vol = 0, is_enabled = 0, is_capture = 0;
108 
109 	if (NULL == tray_icon)
110 		return;
111 	if (NULL != tray_icon->dev_line) {
112 		if (0 == tray_icon->dev_line->is_updated)
113 			return; /* No changes. */
114 		vol = gmp_dev_line_vol_max_get(tray_icon->dev_line);
115 		is_enabled = tray_icon->dev_line->state.is_enabled;
116 		is_capture = tray_icon->dev_line->is_capture;
117 		display_name = tray_icon->dev_line->display_name;
118 	}
119 
120 	/* Tool tip. */
121 	snprintf(tool_tip, sizeof(tool_tip), "%s: %i%%", display_name, vol);
122 	G_GNUC_BEGIN_IGNORE_DEPRECATIONS
123 	gtk_status_icon_set_tooltip_text(tray_icon->status_icon, tool_tip);
124 	G_GNUC_END_IGNORE_DEPRECATIONS
125 
126 	/* Icon. */
127 	stock = volume_stock_from_level(is_capture, is_enabled, vol,
128 	    tray_icon->icon_name);
129 	if (NULL != stock) { /* Update icon. */
130 		tray_icon->icon_name = stock;
131 		G_GNUC_BEGIN_IGNORE_DEPRECATIONS
132 		gtk_status_icon_set_from_icon_name(tray_icon->status_icon,
133 		    stock);
134 		G_GNUC_END_IGNORE_DEPRECATIONS
135 	}
136 }
137 
138 void
gtk_mixer_tray_icon_dev_set(GtkStatusIcon * status_icon,gmp_dev_p dev)139 gtk_mixer_tray_icon_dev_set(GtkStatusIcon *status_icon, gmp_dev_p dev) {
140 	gm_tray_icon_p tray_icon = g_object_get_data(G_OBJECT(status_icon),
141 	    "__gtk_mixer_tray_icon");
142 
143 	if (NULL == tray_icon)
144 		return;
145 
146 	if (NULL == dev ||
147 	    0 == dev->lines_count) {
148 		tray_icon->dev = NULL;
149 		tray_icon->dev_line = NULL;
150 		gtk_mixer_tray_icon_update(status_icon);
151 		return;
152 	}
153 	/* Use any line by default. */
154 	tray_icon->dev = dev;
155 	tray_icon->dev_line = &dev->lines[0];
156 	/* Try to get first playback device. */
157 	for (size_t i = 0; i < dev->lines_count; i ++) {
158 		if (dev->lines[i].is_capture)
159 			continue;
160 		tray_icon->dev_line = &dev->lines[i];
161 		break;
162 	}
163 	gtk_mixer_tray_icon_update(status_icon);
164 }
165 
166 static void
gtk_mixer_tray_icon_destroy(GtkWidget * window __unused,gpointer user_data)167 gtk_mixer_tray_icon_destroy(GtkWidget *window __unused, gpointer user_data) {
168 	gm_tray_icon_p tray_icon = user_data;
169 
170 	free(tray_icon);
171 }
172 
173 GtkStatusIcon *
gtk_mixer_tray_icon_create(void)174 gtk_mixer_tray_icon_create(void) {
175 	gm_tray_icon_p tray_icon;
176 
177 	tray_icon = calloc(1, sizeof(gm_tray_icon_t));
178 	if (NULL == tray_icon)
179 		return (NULL);
180 
181 	G_GNUC_BEGIN_IGNORE_DEPRECATIONS
182 	tray_icon->status_icon = gtk_status_icon_new();
183 	G_GNUC_END_IGNORE_DEPRECATIONS
184 	g_object_set_data(G_OBJECT(tray_icon->status_icon),
185 	    "__gtk_mixer_tray_icon", (void*)tray_icon);
186 
187 	g_signal_connect(tray_icon->status_icon,
188 	    "destroy",
189 	    G_CALLBACK(gtk_mixer_tray_icon_destroy), tray_icon);
190 	g_signal_connect(G_OBJECT(tray_icon->status_icon),
191 	    "scroll-event",
192 	    G_CALLBACK(gtk_mixer_tray_icon_scroll), tray_icon);
193 	g_signal_connect(G_OBJECT(tray_icon->status_icon),
194 	    "button-release-event",
195 	    G_CALLBACK(gtk_mixer_tray_icon_release), tray_icon);
196 
197 	gtk_mixer_tray_icon_update(tray_icon->status_icon);
198 
199 	G_GNUC_BEGIN_IGNORE_DEPRECATIONS
200 	gtk_status_icon_set_visible(tray_icon->status_icon, TRUE);
201 	G_GNUC_END_IGNORE_DEPRECATIONS
202 
203 	return (tray_icon->status_icon);
204 }
205