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