1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  *
3  * Copyright © 2001 Ximian, Inc.
4  * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
5  * Written by Sergey V. Oudaltsov <svu@users.sourceforge.net>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street - Suite 500, Boston, MA 02110-1335, USA.
20  *
21  */
22 
23 #include "config.h"
24 
25 #include <sys/types.h>
26 #include <sys/wait.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <errno.h>
32 
33 #include <locale.h>
34 
35 #include <glib.h>
36 #include <glib/gi18n.h>
37 #include <gdk/gdk.h>
38 #include <gdk/gdkx.h>
39 #include <gtk/gtk.h>
40 
41 #include <X11/XKBlib.h>
42 #include <X11/keysym.h>
43 
44 #include "cinnamon-settings-profile.h"
45 #include "csd-keyboard-manager.h"
46 #include "csd-enums.h"
47 
48 #include "csd-keyboard-xkb.h"
49 
50 #define CSD_KEYBOARD_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CSD_TYPE_KEYBOARD_MANAGER, CsdKeyboardManagerPrivate))
51 
52 #ifndef HOST_NAME_MAX
53 #  define HOST_NAME_MAX 255
54 #endif
55 
56 #define CSD_KEYBOARD_DIR "org.cinnamon.settings-daemon.peripherals.keyboard"
57 
58 #define KEY_REPEAT         "repeat"
59 #define KEY_CLICK          "click"
60 #define KEY_INTERVAL       "repeat-interval"
61 #define KEY_DELAY          "delay"
62 #define KEY_CLICK_VOLUME   "click-volume"
63 #define KEY_NUMLOCK_STATE  "numlock-state"
64 
65 #define KEY_BELL_VOLUME    "bell-volume"
66 #define KEY_BELL_PITCH     "bell-pitch"
67 #define KEY_BELL_DURATION  "bell-duration"
68 #define KEY_BELL_MODE      "bell-mode"
69 
70 struct CsdKeyboardManagerPrivate
71 {
72 	guint      start_idle_id;
73         GSettings *settings;
74         gboolean   have_xkb;
75         gint       xkb_event_base;
76         CsdNumLockState old_state;
77 };
78 
79 static void     csd_keyboard_manager_finalize    (GObject                 *object);
80 
81 G_DEFINE_TYPE (CsdKeyboardManager, csd_keyboard_manager, G_TYPE_OBJECT)
82 
83 static gpointer manager_object = NULL;
84 
85 static gboolean
xkb_set_keyboard_autorepeat_rate(guint delay,guint interval)86 xkb_set_keyboard_autorepeat_rate (guint delay, guint interval)
87 {
88         return XkbSetAutoRepeatRate (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
89                                      XkbUseCoreKbd,
90                                      delay,
91                                      interval);
92 }
93 
94 static void
numlock_xkb_init(CsdKeyboardManager * manager)95 numlock_xkb_init (CsdKeyboardManager *manager)
96 {
97         Display *dpy = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
98         gboolean have_xkb;
99         int opcode, error_base, major, minor;
100 
101         have_xkb = XkbQueryExtension (dpy,
102                                       &opcode,
103                                       &manager->priv->xkb_event_base,
104                                       &error_base,
105                                       &major,
106                                       &minor)
107                 && XkbUseExtension (dpy, &major, &minor);
108 
109         if (have_xkb) {
110                 XkbSelectEventDetails (dpy,
111                                        XkbUseCoreKbd,
112                                        XkbStateNotifyMask,
113                                        XkbModifierLockMask,
114                                        XkbModifierLockMask);
115         } else {
116                 g_warning ("XKB extension not available");
117         }
118 
119         manager->priv->have_xkb = have_xkb;
120 }
121 
122 static unsigned
numlock_NumLock_modifier_mask(void)123 numlock_NumLock_modifier_mask (void)
124 {
125         Display *dpy = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
126         return XkbKeysymToModifiers (dpy, XK_Num_Lock);
127 }
128 
129 static void
numlock_set_xkb_state(CsdNumLockState new_state)130 numlock_set_xkb_state (CsdNumLockState new_state)
131 {
132         unsigned int num_mask;
133         Display *dpy = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
134         if (new_state != CSD_NUM_LOCK_STATE_ON && new_state != CSD_NUM_LOCK_STATE_OFF)
135                 return;
136         num_mask = numlock_NumLock_modifier_mask ();
137         XkbLockModifiers (dpy, XkbUseCoreKbd, num_mask, new_state == CSD_NUM_LOCK_STATE_ON ? num_mask : 0);
138 }
139 
140 static GdkFilterReturn
numlock_xkb_callback(GdkXEvent * xev_,GdkEvent * gdkev_,gpointer user_data)141 numlock_xkb_callback (GdkXEvent *xev_,
142                       GdkEvent  *gdkev_,
143                       gpointer   user_data)
144 {
145         XEvent *xev = (XEvent *) xev_;
146 	XkbEvent *xkbev = (XkbEvent *) xev;
147         CsdKeyboardManager *manager = (CsdKeyboardManager *) user_data;
148 
149         if (xev->type != manager->priv->xkb_event_base)
150 		return GDK_FILTER_CONTINUE;
151 
152 	if (xkbev->any.xkb_type != XkbStateNotify)
153 		return GDK_FILTER_CONTINUE;
154 
155 	if (xkbev->state.changed & XkbModifierLockMask) {
156 		unsigned num_mask = numlock_NumLock_modifier_mask ();
157 		unsigned locked_mods = xkbev->state.locked_mods;
158 		CsdNumLockState numlock_state;
159 
160 		numlock_state = (num_mask & locked_mods) ? CSD_NUM_LOCK_STATE_ON : CSD_NUM_LOCK_STATE_OFF;
161 
162 		if (numlock_state != manager->priv->old_state) {
163 			g_settings_set_enum (manager->priv->settings,
164 					     KEY_NUMLOCK_STATE,
165 					     numlock_state);
166 			manager->priv->old_state = numlock_state;
167 		}
168 	}
169 
170         return GDK_FILTER_CONTINUE;
171 }
172 
173 static void
numlock_install_xkb_callback(CsdKeyboardManager * manager)174 numlock_install_xkb_callback (CsdKeyboardManager *manager)
175 {
176         if (!manager->priv->have_xkb)
177                 return;
178 
179         gdk_window_add_filter (NULL,
180                                numlock_xkb_callback,
181                                manager);
182 }
183 
184 static guint
_csd_settings_get_uint(GSettings * settings,const char * key)185 _csd_settings_get_uint (GSettings  *settings,
186 			const char *key)
187 {
188 	guint value;
189 
190 	g_settings_get (settings, key, "u", &value);
191 	return value;
192 }
193 
194 static void
apply_settings(GSettings * settings,const char * key,CsdKeyboardManager * manager)195 apply_settings (GSettings          *settings,
196                 const char         *key,
197                 CsdKeyboardManager *manager)
198 {
199         XKeyboardControl kbdcontrol;
200         gboolean         repeat;
201         gboolean         click;
202         guint            interval;
203         guint            delay;
204         int              click_volume;
205         int              bell_volume;
206         int              bell_pitch;
207         int              bell_duration;
208         CsdBellMode      bell_mode;
209         gboolean         rnumlock;
210 
211         if (g_strcmp0 (key, KEY_NUMLOCK_STATE) == 0)
212                 return;
213 
214         repeat        = g_settings_get_boolean  (settings, KEY_REPEAT);
215         click         = g_settings_get_boolean  (settings, KEY_CLICK);
216         interval      = _csd_settings_get_uint  (settings, KEY_INTERVAL);
217         delay         = _csd_settings_get_uint  (settings, KEY_DELAY);
218         click_volume  = g_settings_get_int   (settings, KEY_CLICK_VOLUME);
219         bell_pitch    = g_settings_get_int   (settings, KEY_BELL_PITCH);
220         bell_duration = g_settings_get_int   (settings, KEY_BELL_DURATION);
221 
222         bell_mode = g_settings_get_enum (settings, KEY_BELL_MODE);
223         bell_volume   = (bell_mode == CSD_BELL_MODE_ON) ? 50 : 0;
224 
225         gdk_x11_display_error_trap_push (gdk_display_get_default ());
226         if (repeat) {
227                 gboolean rate_set = FALSE;
228 
229                 XAutoRepeatOn (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
230                 /* Use XKB in preference */
231                 rate_set = xkb_set_keyboard_autorepeat_rate (delay, interval);
232 
233                 if (!rate_set)
234                         g_warning ("Neither XKeyboard not Xfree86's keyboard extensions are available,\n"
235                                    "no way to support keyboard autorepeat rate settings");
236         } else {
237                 XAutoRepeatOff (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
238         }
239 
240         /* as percentage from 0..100 inclusive */
241         if (click_volume < 0) {
242                 click_volume = 0;
243         } else if (click_volume > 100) {
244                 click_volume = 100;
245         }
246         kbdcontrol.key_click_percent = click ? click_volume : 0;
247         kbdcontrol.bell_percent = bell_volume;
248         kbdcontrol.bell_pitch = bell_pitch;
249         kbdcontrol.bell_duration = bell_duration;
250         XChangeKeyboardControl (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
251                                 KBKeyClickPercent | KBBellPercent | KBBellPitch | KBBellDuration,
252                                 &kbdcontrol);
253 
254 	if (g_strcmp0 (key, "remember-numlock-state") == 0 || key == NULL) {
255 		rnumlock      = g_settings_get_boolean  (settings, "remember-numlock-state");
256 
257 		manager->priv->old_state = g_settings_get_enum (manager->priv->settings, KEY_NUMLOCK_STATE);
258 
259 		if (manager->priv->have_xkb && rnumlock)
260 			numlock_set_xkb_state (manager->priv->old_state);
261 	}
262 
263         XSync (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), FALSE);
264         gdk_x11_display_error_trap_pop_ignored (gdk_display_get_default ());
265 }
266 
267 void
csd_keyboard_manager_apply_settings(CsdKeyboardManager * manager)268 csd_keyboard_manager_apply_settings (CsdKeyboardManager *manager)
269 {
270         apply_settings (manager->priv->settings, NULL, manager);
271 }
272 
273 static gboolean
start_keyboard_idle_cb(CsdKeyboardManager * manager)274 start_keyboard_idle_cb (CsdKeyboardManager *manager)
275 {
276         cinnamon_settings_profile_start (NULL);
277 
278         g_debug ("Starting keyboard manager");
279 
280         manager->priv->have_xkb = 0;
281         manager->priv->settings = g_settings_new (CSD_KEYBOARD_DIR);
282 
283         /* Essential - xkb initialization should happen before */
284         csd_keyboard_xkb_init (manager);
285 
286         numlock_xkb_init (manager);
287 
288         /* apply current settings before we install the callback */
289         csd_keyboard_manager_apply_settings (manager);
290 
291         g_signal_connect (G_OBJECT (manager->priv->settings), "changed",
292                           G_CALLBACK (apply_settings), manager);
293 
294         numlock_install_xkb_callback (manager);
295 
296         cinnamon_settings_profile_end (NULL);
297 
298         manager->priv->start_idle_id = 0;
299 
300         return FALSE;
301 }
302 
303 gboolean
csd_keyboard_manager_start(CsdKeyboardManager * manager,GError ** error)304 csd_keyboard_manager_start (CsdKeyboardManager *manager,
305                             GError            **error)
306 {
307         cinnamon_settings_profile_start (NULL);
308 
309         manager->priv->start_idle_id = g_idle_add ((GSourceFunc) start_keyboard_idle_cb, manager);
310 
311         cinnamon_settings_profile_end (NULL);
312 
313         return TRUE;
314 }
315 
316 void
csd_keyboard_manager_stop(CsdKeyboardManager * manager)317 csd_keyboard_manager_stop (CsdKeyboardManager *manager)
318 {
319         CsdKeyboardManagerPrivate *p = manager->priv;
320 
321         g_debug ("Stopping keyboard manager");
322 
323         if (p->settings != NULL) {
324                 g_object_unref (p->settings);
325                 p->settings = NULL;
326         }
327 
328         if (p->have_xkb) {
329                 gdk_window_remove_filter (NULL,
330                                           numlock_xkb_callback,
331                                           manager);
332         }
333 
334         csd_keyboard_xkb_shutdown ();
335 }
336 
337 static GObject *
csd_keyboard_manager_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_properties)338 csd_keyboard_manager_constructor (GType                  type,
339                                   guint                  n_construct_properties,
340                                   GObjectConstructParam *construct_properties)
341 {
342         CsdKeyboardManager      *keyboard_manager;
343 
344         keyboard_manager = CSD_KEYBOARD_MANAGER (G_OBJECT_CLASS (csd_keyboard_manager_parent_class)->constructor (type,
345                                                                                                       n_construct_properties,
346                                                                                                       construct_properties));
347 
348         return G_OBJECT (keyboard_manager);
349 }
350 
351 static void
csd_keyboard_manager_class_init(CsdKeyboardManagerClass * klass)352 csd_keyboard_manager_class_init (CsdKeyboardManagerClass *klass)
353 {
354         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
355 
356         object_class->constructor = csd_keyboard_manager_constructor;
357         object_class->finalize = csd_keyboard_manager_finalize;
358 
359         g_type_class_add_private (klass, sizeof (CsdKeyboardManagerPrivate));
360 }
361 
362 static void
csd_keyboard_manager_init(CsdKeyboardManager * manager)363 csd_keyboard_manager_init (CsdKeyboardManager *manager)
364 {
365         manager->priv = CSD_KEYBOARD_MANAGER_GET_PRIVATE (manager);
366 }
367 
368 static void
csd_keyboard_manager_finalize(GObject * object)369 csd_keyboard_manager_finalize (GObject *object)
370 {
371         CsdKeyboardManager *keyboard_manager;
372 
373         g_return_if_fail (object != NULL);
374         g_return_if_fail (CSD_IS_KEYBOARD_MANAGER (object));
375 
376         keyboard_manager = CSD_KEYBOARD_MANAGER (object);
377 
378         g_return_if_fail (keyboard_manager->priv != NULL);
379 
380         if (keyboard_manager->priv->start_idle_id != 0) {
381                 g_source_remove (keyboard_manager->priv->start_idle_id);
382                 keyboard_manager->priv->start_idle_id = 0;
383         }
384 
385         G_OBJECT_CLASS (csd_keyboard_manager_parent_class)->finalize (object);
386 }
387 
388 CsdKeyboardManager *
csd_keyboard_manager_new(void)389 csd_keyboard_manager_new (void)
390 {
391         if (manager_object != NULL) {
392                 g_object_ref (manager_object);
393         } else {
394                 manager_object = g_object_new (CSD_TYPE_KEYBOARD_MANAGER, NULL);
395                 g_object_add_weak_pointer (manager_object,
396                                            (gpointer *) &manager_object);
397         }
398 
399         return CSD_KEYBOARD_MANAGER (manager_object);
400 }
401