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