1 /* statusicon.c
2 *
3 * Copyright (C) 2016 Dmytro Poltavchenko <dmytro.poltavchenko@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include "statusicon.h"
21
22 GtkWidget *lb_mouse_popup;
23 GtkWidget *rb_mouse_popup;
24 #ifdef HAVE_APPINDICATOR
25 AppIndicator *appindicator;
26 #endif
27 GtkStatusIcon *trayicon;
28 GHashTable *icon_cache;
29 statusicon_type icon_type;
30
statusicon_new(void)31 void statusicon_new(void) {
32 #ifdef HAVE_APPINDICATOR
33 appindicator =
34 app_indicator_new("example-simple-client", "indicator-messages",
35 APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
36 icon_type = APPINDICATOR;
37 app_indicator_set_status(appindicator, APP_INDICATOR_STATUS_ACTIVE);
38 APP_INDICATOR_GET_CLASS(appindicator)->fallback = appindicator_fallback;
39 APP_INDICATOR_GET_CLASS(appindicator)->unfallback = appindicator_unfallback;
40 g_signal_connect(G_OBJECT(appindicator), "scroll-event",
41 G_CALLBACK(appindicator_icon_scrolled), NULL);
42 #else
43 trayicon = gtk_status_icon_new();
44 icon_type = SYSTRAY;
45
46 g_signal_connect(G_OBJECT(trayicon), "activate",
47 G_CALLBACK(gtk_status_icon_clicked), NULL);
48
49 g_signal_connect(G_OBJECT(trayicon), "scroll-event",
50 G_CALLBACK(gtk_status_icon_scrolled), NULL);
51
52 g_signal_connect(G_OBJECT(trayicon), "popup-menu",
53 G_CALLBACK(gtk_status_icon_popup_menu), NULL);
54 #endif
55
56 icon_cache =
57 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref);
58
59 statusicon_update_menu();
60 statusicon_update_current_image();
61 }
62
gtk_status_icon_clicked(GtkStatusIcon * status_icon,gpointer data)63 void gtk_status_icon_clicked(GtkStatusIcon *status_icon, gpointer data) {
64 if (status_icon == NULL)
65 return;
66
67 if (xkb_config_get_group_count() > 2) {
68 gtk_menu_popup_at_pointer(GTK_MENU(lb_mouse_popup), NULL);
69 } else {
70 xkb_config_next_group();
71 }
72 }
73
gtk_status_icon_scrolled(GtkStatusIcon * status_icon,GdkEventScroll * event,gpointer data)74 gboolean gtk_status_icon_scrolled(GtkStatusIcon *status_icon,
75 GdkEventScroll *event, gpointer data) {
76 if (status_icon == NULL)
77 return FALSE;
78
79 switch (event->direction) {
80 case GDK_SCROLL_UP:
81 case GDK_SCROLL_RIGHT:
82 xkb_config_prev_group();
83 return TRUE;
84 case GDK_SCROLL_DOWN:
85 case GDK_SCROLL_LEFT:
86 xkb_config_next_group();
87 return TRUE;
88 default:
89 return FALSE;
90 }
91
92 return FALSE;
93 }
94
gtk_status_icon_popup_menu(GtkStatusIcon * status_icon,guint button,guint activate_time,gpointer data)95 void gtk_status_icon_popup_menu(GtkStatusIcon *status_icon, guint button,
96 guint activate_time, gpointer data) {
97 if (status_icon == NULL)
98 return;
99
100 gtk_menu_popup_at_pointer(GTK_MENU(rb_mouse_popup), NULL);
101 }
102
statusicon_set_group(GtkWidget * item,gpointer data)103 void statusicon_set_group(GtkWidget *item, gpointer data) {
104 gint group = GPOINTER_TO_INT(data);
105 xkb_config_set_group(group);
106 }
107
statusicon_update_current_image(void)108 void statusicon_update_current_image(void) {
109 const gchar *group_name = xkb_config_get_group_name(-1);
110 const gchar *variant = xkb_config_get_variant(-1);
111 gchar *filepath = xkb_util_get_flag_filename(group_name, variant);
112
113 if (icon_type == SYSTRAY) {
114 if (trayicon == NULL)
115 return;
116
117 GdkPixbuf *pixmap;
118 if (!g_hash_table_lookup_extended(icon_cache, filepath, NULL,
119 (gpointer)&pixmap)) {
120 pixmap = gdk_pixbuf_new_from_file(filepath, NULL);
121 g_hash_table_insert(icon_cache, g_strdup(filepath), pixmap);
122 }
123
124 if (!pixmap) {
125 g_warning("Can't load image from %s\n", filepath);
126 return;
127 }
128
129 gtk_status_icon_set_from_pixbuf(trayicon, pixmap);
130 gtk_status_icon_set_tooltip_text(
131 trayicon, g_strdup(gettext(xkb_config_get_pretty_layout_name(-1))));
132 } else if (icon_type == APPINDICATOR) {
133 #ifdef HAVE_APPINDICATOR
134 app_indicator_set_icon(appindicator, filepath);
135 #endif
136 }
137 }
138
statusicon_update_menu(void)139 void statusicon_update_menu(void) {
140 // Left button click menu
141 gint i;
142
143 statusicon_destroy_menu(lb_mouse_popup);
144
145 lb_mouse_popup = gtk_menu_new();
146
147 gint width, height;
148 gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
149
150 for (i = 0; i < xkb_config_get_group_count(); i++) {
151 gchar *imgfilename = xkb_util_get_flag_filename(
152 xkb_config_get_group_name(i), xkb_config_get_variant(i));
153 GdkPixbuf *handle = gdk_pixbuf_new_from_file_at_scale(imgfilename, width,
154 height, TRUE, NULL);
155 g_free(imgfilename);
156
157 gchar *layout_string =
158 g_strdup(gettext(xkb_config_get_pretty_layout_name(i)));
159 GtkWidget *menu_item = gtk_image_menu_item_new_with_label(layout_string);
160 g_free(layout_string);
161
162 g_signal_connect(G_OBJECT(menu_item), "activate",
163 G_CALLBACK(statusicon_set_group), GINT_TO_POINTER(i));
164
165 if (handle) {
166 GtkWidget *image = gtk_image_new();
167
168 gtk_image_set_from_pixbuf(GTK_IMAGE(image), handle);
169 gtk_widget_show(image);
170 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image);
171
172 g_object_unref(handle);
173 }
174
175 gtk_widget_show(menu_item);
176
177 gtk_menu_shell_append(GTK_MENU_SHELL(lb_mouse_popup), menu_item);
178 }
179
180 // Right button click menu
181 GtkWidget *mi = NULL;
182
183 statusicon_destroy_menu(rb_mouse_popup);
184
185 if (icon_type == APPINDICATOR) {
186 // Separator
187 mi = gtk_separator_menu_item_new();
188 gtk_widget_show(mi);
189 gtk_menu_shell_append(GTK_MENU_SHELL(lb_mouse_popup), mi);
190 gtk_widget_set_sensitive(mi, FALSE);
191 rb_mouse_popup = lb_mouse_popup;
192 } else if (icon_type == SYSTRAY) {
193 rb_mouse_popup = gtk_menu_new();
194 }
195
196 mi = gtk_image_menu_item_new_from_stock("gtk-about", NULL);
197 g_signal_connect(G_OBJECT(mi), "activate", (GCallback)xkb_about, NULL);
198 gtk_menu_shell_append(GTK_MENU_SHELL(rb_mouse_popup), mi);
199 gtk_widget_show(mi);
200
201 if (icon_type == SYSTRAY) {
202 // Separator
203 mi = gtk_separator_menu_item_new();
204 gtk_widget_show(mi);
205 gtk_menu_shell_append(GTK_MENU_SHELL(rb_mouse_popup), mi);
206 gtk_widget_set_sensitive(mi, FALSE);
207 }
208
209 mi = gtk_image_menu_item_new_from_stock("gtk-quit", NULL);
210 g_signal_connect(G_OBJECT(mi), "activate", (GCallback)xkb_main_quit, NULL);
211 gtk_menu_shell_append(GTK_MENU_SHELL(rb_mouse_popup), mi);
212 gtk_widget_show(mi);
213
214 #ifdef HAVE_APPINDICATOR
215 if (icon_type == APPINDICATOR)
216 app_indicator_set_menu(appindicator, GTK_MENU(rb_mouse_popup));
217 #endif
218 }
219
statusicon_destroy_menu(GtkWidget * menu)220 void statusicon_destroy_menu(GtkWidget *menu) {
221 if (menu) {
222 gtk_widget_destroy(menu);
223 g_object_ref_sink(menu);
224 g_object_unref(menu);
225 }
226 }
227
statusicon_free(void)228 void statusicon_free(void) {
229 statusicon_destroy_menu(rb_mouse_popup);
230 statusicon_destroy_menu(lb_mouse_popup);
231
232 g_hash_table_destroy(icon_cache);
233
234 if (trayicon)
235 g_object_unref(trayicon);
236 }
237
238 #ifdef HAVE_APPINDICATOR
appindicator_fallback(AppIndicator * indicator)239 GtkStatusIcon *appindicator_fallback(AppIndicator *indicator) {
240 icon_type = SYSTRAY;
241
242 if (trayicon) {
243 gtk_status_icon_set_visible(trayicon, TRUE);
244 statusicon_update_menu();
245 statusicon_update_current_image();
246 return trayicon;
247 }
248
249 trayicon = gtk_status_icon_new();
250
251 g_signal_connect(G_OBJECT(trayicon), "activate",
252 G_CALLBACK(gtk_status_icon_clicked), NULL);
253
254 g_signal_connect(G_OBJECT(trayicon), "scroll-event",
255 G_CALLBACK(gtk_status_icon_scrolled), NULL);
256
257 g_signal_connect(G_OBJECT(trayicon), "popup-menu",
258 G_CALLBACK(gtk_status_icon_popup_menu), NULL);
259
260 statusicon_update_menu();
261 statusicon_update_current_image();
262
263 return trayicon;
264 }
265
appindicator_unfallback(AppIndicator * indicator,GtkStatusIcon * status_icon)266 void appindicator_unfallback(AppIndicator *indicator,
267 GtkStatusIcon *status_icon) {
268 gtk_status_icon_set_visible(status_icon, FALSE);
269 icon_type = APPINDICATOR;
270 statusicon_update_menu();
271 statusicon_update_current_image();
272 }
273
appindicator_icon_scrolled(AppIndicator * indicator,gint delta,GdkScrollDirection direction,gpointer user_data)274 void appindicator_icon_scrolled(AppIndicator *indicator, gint delta,
275 GdkScrollDirection direction,
276 gpointer user_data) {
277 switch (direction) {
278 case GDK_SCROLL_UP:
279 case GDK_SCROLL_RIGHT:
280 xkb_config_prev_group();
281 break;
282 case GDK_SCROLL_DOWN:
283 case GDK_SCROLL_LEFT:
284 xkb_config_next_group();
285 break;
286 }
287 }
288
289 #endif
290