/*
* Copyright (C) 2002-2006 Sergey V. Udaltsov <svu@gnome.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <time.h>
#include <stdlib.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "xklavier_private.h"
#include "xklavier_private_xkb.h"
#ifdef HAVE_XINPUT
#include <X11/extensions/XI.h>
#include <X11/extensions/XInput.h>
#endif
#ifdef LIBXKBFILE_PRESENT
const gchar **
xkl_xkb_get_groups_names(XklEngine * engine)
{
return (const gchar **) xkl_engine_backend(engine, XklXkb,
group_names);
}
const gchar **
xkl_xkb_get_indicators_names(XklEngine * engine)
{
return (const gchar **) xkl_engine_backend(engine, XklXkb,
indicator_names);
}
gint
xkl_xkb_pause_listen(XklEngine * engine)
{
XkbSelectEvents(xkl_engine_get_display(engine),
xkl_engine_backend(engine, XklXkb, device_id),
XkbAllEventsMask, 0);
return 0;
}
gint
xkl_xkb_resume_listen(XklEngine * engine)
{
#ifdef HAVE_XINPUT
int xitype;
XEventClass xiclass;
#endif
/* What events we want */
#define XKB_EVT_MASK \
(XkbStateNotifyMask| \
XkbNamesNotifyMask| \
XkbControlsNotifyMask| \
XkbIndicatorStateNotifyMask| \
XkbIndicatorMapNotifyMask| \
XkbNewKeyboardNotifyMask)
Display *display = xkl_engine_get_display(engine);
XkbSelectEvents(display,
xkl_engine_backend(engine, XklXkb, device_id),
XKB_EVT_MASK, XKB_EVT_MASK);
#define XKB_STATE_EVT_DTL_MASK \
(XkbGroupStateMask)
XkbSelectEventDetails(display,
xkl_engine_backend(engine, XklXkb,
device_id),
XkbStateNotify, XKB_STATE_EVT_DTL_MASK,
XKB_STATE_EVT_DTL_MASK);
#define XKB_NAMES_EVT_DTL_MASK \
(XkbGroupNamesMask|XkbIndicatorNamesMask)
XkbSelectEventDetails(display,
xkl_engine_backend(engine, XklXkb,
device_id),
XkbNamesNotify, XKB_NAMES_EVT_DTL_MASK,
XKB_NAMES_EVT_DTL_MASK);
#ifdef HAVE_XINPUT
if (xkl_engine_priv(engine, features) & XKLF_DEVICE_DISCOVERY) {
DevicePresence(display, xitype, xiclass);
XSelectExtensionEvent(display,
xkl_engine_priv(engine, root_window),
&xiclass, 1);
xkl_engine_backend(engine, XklXkb, xi_event_type) = xitype;
} else
xkl_engine_backend(engine, XklXkb, xi_event_type) = -1;
#endif
return 0;
}
guint
xkl_xkb_get_max_num_groups(XklEngine * engine)
{
return xkl_engine_priv(engine,
features) & XKLF_MULTIPLE_LAYOUTS_SUPPORTED
? XkbNumKbdGroups : 1;
}
guint
xkl_xkb_get_num_groups(XklEngine * engine)
{
return xkl_engine_backend(engine, XklXkb,
cached_desc)->ctrls->num_groups;
}
#define KBD_MASK \
( 0 )
#define CTRLS_MASK \
( XkbSlowKeysMask )
#define NAMES_MASK \
( XkbGroupNamesMask | XkbIndicatorNamesMask )
void
xkl_xkb_free_all_info(XklEngine * engine)
{
gint i;
gchar **pi = xkl_engine_backend(engine, XklXkb, indicator_names);
XkbDescPtr desc;
for (i = 0; i < XkbNumIndicators; i++, pi++) {
/* only free non-empty ones */
if (*pi && **pi)
XFree(*pi);
}
desc = xkl_engine_backend(engine, XklXkb, cached_desc);
if (desc != NULL) {
int i;
char **group_name =
xkl_engine_backend(engine, XklXkb, group_names);
for (i = desc->ctrls->num_groups; --i >= 0; group_name++)
if (*group_name) {
XFree(*group_name);
*group_name = NULL;
}
XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
xkl_engine_backend(engine, XklXkb, cached_desc) = NULL;
}
/* just in case - never actually happens... */
desc = xkl_engine_backend(engine, XklXkb, actual_desc);
if (desc != NULL) {
XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
xkl_engine_backend(engine, XklXkb, actual_desc) = NULL;
}
}
static gboolean
xkl_xkb_load_actual_desc(XklEngine * engine)
{
gboolean rv = FALSE;
Status status;
Display *display = xkl_engine_get_display(engine);
XkbDescPtr desc = XkbGetMap(display, KBD_MASK,
xkl_engine_backend(engine, XklXkb,
device_id));
xkl_engine_backend(engine, XklXkb, actual_desc) = desc;
if (desc != NULL) {
rv = Success == (status = XkbGetControls(display,
CTRLS_MASK,
desc)) &&
Success == (status = XkbGetNames(display,
NAMES_MASK,
desc)) &&
Success == (status = XkbGetIndicatorMap(display,
XkbAllIndicatorsMask,
desc));
if (!rv) {
xkl_last_error_message =
"Could not load controls/names/indicators";
xkl_debug(0, "%s: %d\n", xkl_last_error_message,
status);
XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
xkl_engine_backend(engine, XklXkb, actual_desc) =
NULL;
}
}
return rv;
}
gboolean
xkl_xkb_if_cached_info_equals_actual(XklEngine * engine)
{
gint i;
Atom *pa1, *pa2;
gboolean rv = FALSE;
if (xkl_xkb_load_actual_desc(engine)) {
/* First, compare the number of groups */
XkbDescPtr cached =
xkl_engine_backend(engine, XklXkb, cached_desc);
XkbDescPtr actual =
xkl_engine_backend(engine, XklXkb, actual_desc);
if (cached->ctrls->num_groups == actual->ctrls->num_groups) {
/* Then, compare group names, just atoms */
pa1 = cached->names->groups;
pa2 = actual->names->groups;
for (i = cached->ctrls->num_groups; --i >= 0;
pa1++, pa2++)
if (*pa1 != *pa2)
break;
/* Then, compare indicator names, just atoms */
if (i < 0) {
pa1 = cached->names->indicators;
pa2 = actual->names->indicators;
for (i = XkbNumIndicators; --i >= 0;
pa1++, pa2++)
if (*pa1 != *pa2)
break;
rv = i < 0;
}
}
/*
* in case of failure, reuse in _XklXkbLoadAllInfo
* in case of success - free it
*/
if (rv) {
XkbFreeKeyboard(actual, XkbAllComponentsMask,
True);
xkl_engine_backend(engine, XklXkb, actual_desc) =
NULL;
}
} else {
xkl_debug(0,
"Could not load the XkbDescPtr for comparison\n");
}
return rv;
}
/*
* Load some XKB parameters
*/
gboolean
xkl_xkb_load_all_info(XklEngine * engine)
{
gint i;
Atom *pa;
gchar **group_name;
gchar **pi = xkl_engine_backend(engine, XklXkb, indicator_names);
Display *display = xkl_engine_get_display(engine);
XkbDescPtr actual =
xkl_engine_backend(engine, XklXkb, actual_desc);
XkbDescPtr cached;
if (actual == NULL)
if (!xkl_xkb_load_actual_desc(engine)) {
xkl_last_error_message = "Could not load keyboard";
return FALSE;
}
/* take it from the cache (in most cases LoadAll is called from ResetAll which in turn ...) */
cached = actual = xkl_engine_backend(engine, XklXkb, actual_desc);
xkl_engine_backend(engine, XklXkb, cached_desc) =
xkl_engine_backend(engine, XklXkb, actual_desc);
xkl_engine_backend(engine, XklXkb, actual_desc) = NULL;
/* First, output the number of the groups */
xkl_debug(200, "found %d groups\n", cached->ctrls->num_groups);
/* Then, cache (and output) the names of the groups */
pa = cached->names->groups;
group_name = xkl_engine_backend(engine, XklXkb, group_names);
for (i = cached->ctrls->num_groups; --i >= 0; pa++, group_name++) {
*group_name =
XGetAtomName(display,
*pa == None ? XInternAtom(display, "-",
False) : *pa);
xkl_debug(200, "Group %d has name [%s]\n", i, *group_name);
}
xkl_engine_priv(engine, last_error_code) =
XkbGetIndicatorMap(display, XkbAllIndicatorsMask, cached);
if (xkl_engine_priv(engine, last_error_code) != Success) {
xkl_last_error_message = "Could not load indicator map";
return FALSE;
}
/* Then, cache (and output) the names of the indicators */
pa = cached->names->indicators;
for (i = XkbNumIndicators; --i >= 0; pi++, pa++) {
Atom a = *pa;
if (a != None)
*pi = XGetAtomName(display, a);
else
*pi = "";
xkl_debug(200, "Indicator[%d] is %s\n", i, *pi);
}
xkl_debug(200, "Real indicators are %X\n",
cached->indicators->phys_indicators);
g_signal_emit_by_name(engine, "X-config-changed");
return TRUE;
}
void
xkl_xkb_lock_group(XklEngine * engine, gint group)
{
Display *display = xkl_engine_get_display(engine);
xkl_debug(100, "Posted request for change the group to %d ##\n",
group);
XkbLockGroup(display,
xkl_engine_backend(engine, XklXkb, device_id), group);
XSync(display, False);
}
/*
* Updates current internal state from X state
*/
void
xkl_xkb_get_server_state(XklEngine * engine, XklState * current_state_out)
{
XkbStateRec state;
Display *display = xkl_engine_get_display(engine);
current_state_out->group = 0;
if (Success ==
XkbGetState(display,
xkl_engine_backend(engine, XklXkb, device_id),
&state))
current_state_out->group = state.locked_group;
if (Success ==
XkbGetIndicatorState(display,
xkl_engine_backend(engine, XklXkb,
device_id),
¤t_state_out->indicators))
current_state_out->indicators &=
xkl_engine_backend(engine, XklXkb,
cached_desc)->indicators->
phys_indicators;
else
current_state_out->indicators = 0;
}
/*
* Actually taken from mxkbledpanel, valueChangedProc
*/
gboolean
xkl_xkb_set_indicator(XklEngine * engine, gint indicator_num, gboolean set)
{
XkbIndicatorMapPtr map;
Display *display = xkl_engine_get_display(engine);
XkbDescPtr cached =
xkl_engine_backend(engine, XklXkb, cached_desc);
map = cached->indicators->maps + indicator_num;
/* The 'flags' field tells whether this indicator is automatic
* (XkbIM_NoExplicit - 0x80), explicit (XkbIM_NoAutomatic - 0x40),
* or neither (both - 0xC0).
*
* If NoAutomatic is set, the server ignores the rest of the
* fields in the indicator map (i.e. it disables automatic control
* of the LED). If NoExplicit is set, the server prevents clients
* from explicitly changing the value of the LED (using the core
* protocol *or* XKB). If NoAutomatic *and* NoExplicit are set,
* the LED cannot be changed (unless you change the map first).
* If neither NoAutomatic nor NoExplicit are set, the server will
* change the LED according to the indicator map, but clients can
* override that (until the next automatic change) using the core
* protocol or XKB.
*/
switch (map->flags & (XkbIM_NoExplicit | XkbIM_NoAutomatic)) {
case XkbIM_NoExplicit | XkbIM_NoAutomatic:
{
/* Can do nothing. Just ignore the indicator */
return TRUE;
}
case XkbIM_NoAutomatic:
{
if (cached->names->indicators[indicator_num] !=
None)
XkbSetNamedIndicator(display,
xkl_engine_backend
(engine, XklXkb,
device_id),
cached->names->
indicators
[indicator_num], set,
False, NULL);
else {
XKeyboardControl xkc;
xkc.led = indicator_num;
xkc.led_mode =
set ? LedModeOn : LedModeOff;
XChangeKeyboardControl(display,
KBLed | KBLedMode,
&xkc);
XSync(display, False);
}
return TRUE;
}
case XkbIM_NoExplicit:
break;
}
/* The 'ctrls' field tells what controls tell this indicator to
* to turn on: RepeatKeys (0x1), SlowKeys (0x2), BounceKeys (0x4),
* StickyKeys (0x8), MouseKeys (0x10), AccessXKeys (0x20),
* TimeOut (0x40), Feedback (0x80), ToggleKeys (0x100),
* Overlay1 (0x200), Overlay2 (0x400), GroupsWrap (0x800),
* InternalMods (0x1000), IgnoreLockMods (0x2000),
* PerKeyRepeat (0x3000), or ControlsEnabled (0x4000)
*/
if (map->ctrls) {
gulong which = map->ctrls;
XkbGetControls(display, XkbAllControlsMask, cached);
if (set)
cached->ctrls->enabled_ctrls |= which;
else
cached->ctrls->enabled_ctrls &= ~which;
XkbSetControls(display, which | XkbControlsEnabledMask,
cached);
}
/* The 'which_groups' field tells when this indicator turns on
* for the 'groups' field: base (0x1), latched (0x2), locked (0x4),
* or effective (0x8).
*/
if (map->groups) {
gint i;
guint group = 1;
/* Turning on a group indicator is kind of tricky. For
* now, we will just Latch or Lock the first group we find
* if that is what this indicator does. Otherwise, we're
* just going to punt and get out of here.
*/
if (set) {
for (i = XkbNumKbdGroups; --i >= 0;)
if ((1 << i) & map->groups) {
group = i;
break;
}
if (map->which_groups & (XkbIM_UseLocked |
XkbIM_UseEffective)) {
/* Important: Groups should be ignored here - because they are handled separately! */
/* XklLockGroup( group ); */
} else if (map->which_groups & XkbIM_UseLatched)
XkbLatchGroup(display,
xkl_engine_backend(engine,
XklXkb,
device_id),
group);
else {
/* Can do nothing. Just ignore the indicator */
return TRUE;
}
} else
/* Turning off a group indicator will mean that we just
* Lock the first group that this indicator doesn't watch.
*/
{
for (i = XkbNumKbdGroups; --i >= 0;)
if (!((1 << i) & map->groups)) {
group = i;
break;
}
xkl_xkb_lock_group(engine, group);
}
}
/* The 'which_mods' field tells when this indicator turns on
* for the modifiers: base (0x1), latched (0x2), locked (0x4),
* or effective (0x8).
*
* The 'real_mods' field tells whether this turns on when one of
* the real X modifiers is set: Shift (0x1), Lock (0x2), Control (0x4),
* Mod1 (0x8), Mod2 (0x10), Mod3 (0x20), Mod4 (0x40), or Mod5 (0x80).
*
* The 'virtual_mods' field tells whether this turns on when one of
* the virtual modifiers is set.
*
* The 'mask' field tells what real X modifiers the virtual_modifiers
* map to?
*/
if (map->mods.real_mods || map->mods.mask) {
guint affect, mods;
affect = (map->mods.real_mods | map->mods.mask);
mods = set ? affect : 0;
if (map->which_mods &
(XkbIM_UseLocked | XkbIM_UseEffective))
XkbLockModifiers(display,
xkl_engine_backend(engine, XklXkb,
device_id),
affect, mods);
else if (map->which_mods & XkbIM_UseLatched)
XkbLatchModifiers(display,
xkl_engine_backend(engine,
XklXkb,
device_id),
affect, mods);
else {
return TRUE;
}
}
return TRUE;
}
#endif
gint
xkl_xkb_init(XklEngine * engine)
{
Display *display = xkl_engine_get_display(engine);
#ifdef LIBXKBFILE_PRESENT
gint opcode;
gboolean xkl_xkb_ext_present;
int xi_opc;
xkl_engine_priv(engine, backend_id) = "XKB";
xkl_engine_priv(engine, features) = XKLF_CAN_TOGGLE_INDICATORS |
XKLF_CAN_OUTPUT_CONFIG_AS_ASCII |
XKLF_CAN_OUTPUT_CONFIG_AS_BINARY;
xkl_engine_priv(engine, activate_config_rec) =
xkl_xkb_activate_config_rec;
xkl_engine_priv(engine, init_config_registry) =
xkl_xkb_init_config_registry;
xkl_engine_priv(engine, load_config_registry) =
xkl_xkb_load_config_registry;
xkl_engine_priv(engine, write_config_rec_to_file) =
xkl_xkb_write_config_rec_to_file;
xkl_engine_priv(engine, get_groups_names) =
xkl_xkb_get_groups_names;
xkl_engine_priv(engine, get_indicators_names) =
xkl_xkb_get_indicators_names;
xkl_engine_priv(engine, get_max_num_groups) =
xkl_xkb_get_max_num_groups;
xkl_engine_priv(engine, get_num_groups) = xkl_xkb_get_num_groups;
xkl_engine_priv(engine, lock_group) = xkl_xkb_lock_group;
xkl_engine_priv(engine, process_x_event) = xkl_xkb_process_x_event;
xkl_engine_priv(engine, process_x_error) = xkl_xkb_process_x_error;
xkl_engine_priv(engine, free_all_info) = xkl_xkb_free_all_info;
xkl_engine_priv(engine, if_cached_info_equals_actual) =
xkl_xkb_if_cached_info_equals_actual;
xkl_engine_priv(engine, load_all_info) = xkl_xkb_load_all_info;
xkl_engine_priv(engine, get_server_state) =
xkl_xkb_get_server_state;
xkl_engine_priv(engine, pause_listen) = xkl_xkb_pause_listen;
xkl_engine_priv(engine, resume_listen) = xkl_xkb_resume_listen;
xkl_engine_priv(engine, set_indicators) = xkl_xkb_set_indicators;
xkl_engine_priv(engine, finalize) = xkl_xkb_term;
if (getenv("XKL_XKB_DISABLE") != NULL)
return -1;
xkl_engine_priv(engine, backend) = g_new0(XklXkb, 1);
xkl_engine_backend(engine, XklXkb, device_id) = XkbUseCoreKbd;
xkl_xkb_ext_present = XkbQueryExtension(display,
&opcode,
&xkl_engine_backend(engine,
XklXkb,
event_type),
&xkl_engine_backend(engine,
XklXkb,
error_code),
NULL, NULL);
if (!xkl_xkb_ext_present)
return -1;
xkl_debug(160,
"xkbEvenType: %X, xkbError: %X, display: %p, root: "
WINID_FORMAT "\n", xkl_engine_backend(engine, XklXkb,
event_type),
xkl_engine_backend(engine, XklXkb, error_code), display,
xkl_engine_priv(engine, root_window));
xkl_engine_priv(engine, base_config_atom) =
XInternAtom(display, _XKB_RF_NAMES_PROP_ATOM, False);
xkl_engine_priv(engine, backup_config_atom) =
XInternAtom(display, "_XKB_RULES_NAMES_BACKUP", False);
xkl_engine_priv(engine, default_model) = "pc101";
xkl_engine_priv(engine, default_layout) = "us";
/* First, we have to assign xkl_vtable -
because this function uses it */
if (xkl_xkb_multiple_layouts_supported(engine))
xkl_engine_priv(engine, features) |=
XKLF_MULTIPLE_LAYOUTS_SUPPORTED;
#if HAVE_XINPUT
if (XQueryExtension
(display, "XInputExtension", &xi_opc,
&xkl_engine_backend(engine, XklXkb, xi_event_type),
&xkl_engine_backend(engine, XklXkb, xi_error_code))) {
XExtensionVersion *ev =
XGetExtensionVersion(display, "XInputExtension");
xkl_debug(150,
"XInputExtension found (%d, %d, %d) version %d.%d\n",
xi_opc, xkl_engine_backend(engine, XklXkb,
xi_event_type),
xkl_engine_backend(engine, XklXkb,
xi_error_code),
ev->major_version, ev->minor_version);
/* DevicePresence is available from XI 1.4 */
if ((ev->major_version * 10) + ev->minor_version >= 14) {
xkl_debug(200, "DevicePresence available\n");
xkl_engine_priv(engine, features) |=
XKLF_DEVICE_DISCOVERY;
} else {
xkl_debug(200, "DevicePresence not available\n");
}
XFree(ev);
} else {
xkl_debug(0, "XInputExtension not found\n");
xkl_engine_backend(engine, XklXkb, xi_event_type) = -1;
xkl_engine_backend(engine, XklXkb, xi_error_code) = -1;
}
#endif
return 0;
#else
xkl_debug(160,
"NO XKB LIBS, display: %p, root: " WINID_FORMAT
"\n", display, xkl_engine_priv(engine, root_window));
return -1;
#endif
}
void
xkl_xkb_term(XklEngine * engine)
{
}
#ifdef LIBXKBFILE_PRESENT
const gchar *
xkl_xkb_event_get_name(gint xkb_type)
{
/* Not really good to use the fact of consecutivity
but XKB protocol extension is already standartized so... */
static const gchar *evt_names[] = {
"XkbNewKeyboardNotify",
"XkbMapNotify",
"XkbStateNotify",
"XkbControlsNotify",
"XkbIndicatorStateNotify",
"XkbIndicatorMapNotify",
"XkbNamesNotify",
"XkbCompatMapNotify",
"XkbBellNotify",
"XkbActionMessage",
"XkbAccessXNotify",
"XkbExtensionDeviceNotify",
"LASTEvent"
};
xkb_type -= XkbNewKeyboardNotify;
if (xkb_type < 0 ||
xkb_type >= (sizeof(evt_names) / sizeof(evt_names[0])))
return "UNKNOWN/OOR";
return evt_names[xkb_type];
}
#endif