Second click on weather plugin icon should hide conditions dialog if it's shown.
[lxde/lxpanel.git] / src / plugins / kbled / kbled.c
1 /**
2 * Copyright (c) 2006 LxDE Developers, see the file AUTHORS for details.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22
23 #include "plugin.h"
24
25 #include <gtk/gtk.h>
26 #include <gdk/gdk.h>
27 #include <gdk/gdkx.h>
28 #include <glib/gi18n.h>
29 #include <gdk-pixbuf/gdk-pixbuf.h>
30 #include <string.h>
31
32 #include <X11/XKBlib.h>
33
34 #include "misc.h"
35 #include "icon-grid.h"
36
37 static const char * on_icons_theme[] = {
38 "capslock-on",
39 "numlock-on",
40 "scrllock-on"
41 };
42
43 static const char * off_icons_theme[] = {
44 "capslock-off",
45 "numlock-off",
46 "scrllock-off"
47 };
48
49 static const char * on_icons[] = {
50 "capslock-on.png",
51 "numlock-on.png",
52 "scrllock-on.png"
53 };
54
55 static const char * off_icons[] = {
56 "capslock-off.png",
57 "numlock-off.png",
58 "scrllock-off.png"
59 };
60
61 static int xkb_event_base = 0;
62 static int xkb_error_base = 0;
63
64 /* Private context for keyboard LED plugin. */
65 typedef struct {
66 LXPanel * panel; /* Back pointer to panel */
67 config_setting_t *settings;
68 GtkWidget *indicator_image[3]; /* Image for each indicator */
69 unsigned int current_state; /* Current LED state, bit encoded */
70 gboolean visible[3]; /* True if control is visible (per user configuration) */
71 } KeyboardLEDPlugin;
72
73 static void kbled_update_image(KeyboardLEDPlugin * kl, int i, unsigned int state);
74 static void kbled_update_display(KeyboardLEDPlugin * kl, unsigned int state);
75 static void kbled_destructor(gpointer user_data);
76
77 static void kbled_theme_changed(GtkWidget * widget, KeyboardLEDPlugin * kl)
78 {
79 /* Set orientation into the icon grid. */
80
81 /* Do a full redraw. */
82 int current_state = kl->current_state;
83 kl->current_state = ~ kl->current_state;
84 kbled_update_display(kl, current_state);
85 }
86
87 /* Update image to correspond to current state. */
88 static void kbled_update_image(KeyboardLEDPlugin * kl, int i, unsigned int state)
89 {
90 if(lxpanel_image_set_icon_theme(kl->panel, kl->indicator_image[i], (state ? on_icons_theme[i] : off_icons_theme[i])) != TRUE) {
91 char * file = g_build_filename(
92 PACKAGE_DATA_DIR "/images",
93 ((state) ? on_icons[i] : off_icons[i]),
94 NULL);
95 lxpanel_image_set_from_file(kl->panel, kl->indicator_image[i], file);
96 g_free(file);
97 }
98 }
99
100 /* Redraw after Xkb event or initialization. */
101 static void kbled_update_display(KeyboardLEDPlugin * kl, unsigned int new_state)
102 {
103 int i;
104 for (i = 0; i < 3; i++)
105 {
106 /* If the control changed state, redraw it. */
107 int current_is_lit = kl->current_state & (1 << i);
108 int new_is_lit = new_state & (1 << i);
109 if (current_is_lit != new_is_lit)
110 kbled_update_image(kl, i, new_is_lit);
111 }
112
113 /* Save new state. */
114 kl->current_state = new_state;
115 }
116
117 /* GDK event filter. */
118 static GdkFilterReturn kbled_event_filter(GdkXEvent * gdkxevent, GdkEvent * event, KeyboardLEDPlugin * kl)
119 {
120 /* Look for XkbIndicatorStateNotify events and update the display. */
121 XEvent * xev = (XEvent *) gdkxevent;
122 if (xev->xany.type == xkb_event_base + XkbEventCode)
123 {
124 XkbEvent * xkbev = (XkbEvent *) xev;
125 if (xkbev->any.xkb_type == XkbIndicatorStateNotify)
126 kbled_update_display(kl, xkbev->indicators.state);
127 }
128 return GDK_FILTER_CONTINUE;
129 }
130
131 /* Plugin constructor. */
132 static GtkWidget *kbled_constructor(LXPanel *panel, config_setting_t *settings)
133 {
134 /* Allocate and initialize plugin context and set into Plugin private data pointer. */
135 KeyboardLEDPlugin * kl = g_new0(KeyboardLEDPlugin, 1);
136 GtkWidget *p;
137 int i;
138 unsigned int current_state;
139 Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
140
141 kl->panel = panel;
142 kl->settings = settings;
143 kl->visible[0] = FALSE;
144 kl->visible[1] = TRUE;
145 kl->visible[2] = TRUE;
146
147 /* Load parameters from the configuration file. */
148 if (config_setting_lookup_int(settings, "ShowCapsLock", &i))
149 kl->visible[0] = i != 0;
150 if (config_setting_lookup_int(settings, "ShowNumLock", &i))
151 kl->visible[1] = i != 0;
152 if (config_setting_lookup_int(settings, "ShowScrollLock", &i))
153 kl->visible[2] = i != 0;
154
155 /* Allocate top level widget and set into Plugin widget pointer. */
156 p = panel_icon_grid_new(panel_get_orientation(panel),
157 panel_get_icon_size(panel),
158 panel_get_icon_size(panel),
159 0, 0, panel_get_height(panel));
160 lxpanel_plugin_set_data(p, kl, kbled_destructor);
161 gtk_widget_add_events(p, GDK_BUTTON_PRESS_MASK);
162 g_signal_connect(panel_get_icon_theme(panel), "changed", G_CALLBACK(kbled_theme_changed), kl);
163
164 /* Then allocate three images for the three indications, but make them visible only when the configuration requests. */
165 for (i = 0; i < 3; i++)
166 {
167 kl->indicator_image[i] = gtk_image_new();
168 gtk_container_add(GTK_CONTAINER(p), kl->indicator_image[i]);
169 gtk_widget_set_visible(kl->indicator_image[i], kl->visible[i]);
170 }
171
172 /* Initialize Xkb extension if not yet done. */
173 if (xkb_event_base == 0)
174 {
175 int opcode;
176 int maj = XkbMajorVersion;
177 int min = XkbMinorVersion;
178 if ( ! XkbLibraryVersion(&maj, &min))
179 return 0;
180 if ( ! XkbQueryExtension(xdisplay, &opcode, &xkb_event_base, &xkb_error_base, &maj, &min))
181 return 0;
182 }
183
184 /* Add GDK event filter and enable XkbIndicatorStateNotify events. */
185 gdk_window_add_filter(NULL, (GdkFilterFunc) kbled_event_filter, kl);
186 if ( ! XkbSelectEvents(xdisplay, XkbUseCoreKbd, XkbIndicatorStateNotifyMask, XkbIndicatorStateNotifyMask))
187 return 0;
188
189 /* Get current indicator state and update display.
190 * Force current state to differ in all bits so a full redraw will occur. */
191 XkbGetIndicatorState(xdisplay, XkbUseCoreKbd, &current_state);
192 kl->current_state = ~ current_state;
193 kbled_update_display(kl, current_state);
194
195 /* Show the widget. */
196 return p;
197 }
198
199 /* Plugin destructor. */
200 static void kbled_destructor(gpointer user_data)
201 {
202 KeyboardLEDPlugin * kl = (KeyboardLEDPlugin *) user_data;
203
204 /* Remove GDK event filter. */
205 gdk_window_remove_filter(NULL, (GdkFilterFunc) kbled_event_filter, kl);
206 g_signal_handlers_disconnect_by_func(panel_get_icon_theme(kl->panel),
207 kbled_theme_changed, kl);
208 g_free(kl);
209 }
210
211 /* Callback when the configuration dialog has recorded a configuration change. */
212 static gboolean kbled_apply_configuration(gpointer user_data)
213 {
214 KeyboardLEDPlugin * kl = lxpanel_plugin_get_data(user_data);
215 int i;
216
217 for (i = 0; i < 3; i++)
218 gtk_widget_set_visible(kl->indicator_image[i], kl->visible[i]);
219 config_group_set_int(kl->settings, "ShowCapsLock", kl->visible[0]);
220 config_group_set_int(kl->settings, "ShowNumLock", kl->visible[1]);
221 config_group_set_int(kl->settings, "ShowScrollLock", kl->visible[2]);
222 return FALSE;
223 }
224
225 /* Callback when the configuration dialog is to be shown. */
226 static GtkWidget *kbled_configure(LXPanel *panel, GtkWidget *p)
227 {
228 KeyboardLEDPlugin * kl = lxpanel_plugin_get_data(p);
229 GtkWidget * dlg = lxpanel_generic_config_dlg(_("Keyboard LED"),
230 panel, kbled_apply_configuration, p,
231 _("Show CapsLock"), &kl->visible[0], CONF_TYPE_BOOL,
232 _("Show NumLock"), &kl->visible[1], CONF_TYPE_BOOL,
233 _("Show ScrollLock"), &kl->visible[2], CONF_TYPE_BOOL,
234 NULL);
235 gtk_widget_set_size_request(GTK_WIDGET(dlg), 200, -1); /* Improve geometry */
236 return dlg;
237 }
238
239 /* Callback when panel configuration changes. */
240 static void kbled_panel_configuration_changed(LXPanel *panel, GtkWidget *p)
241 {
242 /* Set orientation into the icon grid. */
243 KeyboardLEDPlugin * kl = lxpanel_plugin_get_data(p);
244
245 panel_icon_grid_set_geometry(PANEL_ICON_GRID(p), panel_get_orientation(panel),
246 panel_get_icon_size(panel),
247 panel_get_icon_size(panel),
248 0, 0, panel_get_height(panel));
249
250 /* Do a full redraw. */
251 int current_state = kl->current_state;
252 kl->current_state = ~ kl->current_state;
253 kbled_update_display(kl, current_state);
254 }
255
256 FM_DEFINE_MODULE(lxpanel_gtk, kbled)
257
258 /* Plugin descriptor. */
259 LXPanelPluginInit fm_module_init_lxpanel_gtk = {
260 .name = N_("Keyboard LED"),
261 .description = N_("Indicators for CapsLock, NumLock, and ScrollLock keys"),
262
263 .new_instance = kbled_constructor,
264 .config = kbled_configure,
265 .reconfigure = kbled_panel_configuration_changed
266 };