diff options
Diffstat (limited to 'src/keyboard-x11.c')
-rw-r--r-- | src/keyboard-x11.c | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/src/keyboard-x11.c b/src/keyboard-x11.c new file mode 100644 index 00000000..11c6d93f --- /dev/null +++ b/src/keyboard-x11.c @@ -0,0 +1,319 @@ +/* + * Copyright 2021 Robert Tari <robert@tari.in> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <X11/XKBlib.h> +#include <libxklavier/xklavier.h> +#include "languages.h" +#include "keyboard.h" + +enum +{ + LAYOUT_CHANGED, + CONFIG_CHANGED, + LAST_SIGNAL +}; + +static guint m_lSignals[LAST_SIGNAL]; + +struct _KeyboardPrivate +{ + GPollFD cPollFD; + GSourceFuncs cSourceFuncs; + XklEngine *pEngine; + GHashTable *lLayouts; + Display *pDisplay; + guint nLayout; + gint nXkbEventType; + XklConfigRec *pConfigRec; +}; + +typedef KeyboardPrivate priv_t; + +G_DEFINE_TYPE_WITH_PRIVATE(Keyboard, keyboard, G_TYPE_OBJECT) + +typedef struct _Layout +{ + gchar *sId; + gchar *sLanguage; + gchar *sDescription; + +} Layout; + +typedef struct _LayoutParser +{ + const gchar *sLayout; + const gchar *sLanguage; + Keyboard *pKeyboard; + +} LayoutParser; + +typedef struct _Source +{ + GSource cSource; + Keyboard *pKeyboard; + +} Source; + +static gboolean onCheckEvent(Display *pDisplay, XEvent *pEvent, XPointer pData) +{ + gint *pXkbEventType = (gint*)pData; + + if (pEvent->type == *pXkbEventType) + { + XkbEvent *pXkbEvent = (XkbEvent*)pEvent; + + if (pXkbEvent->any.xkb_type == XkbStateNotify || pXkbEvent->any.xkb_type == XkbNamesNotify) + { + return TRUE; + } + } + + return FALSE; +} + +static gboolean onCheckSource(GSource *pSource) +{ + Keyboard *pKeyboard = ((Source*)pSource)->pKeyboard; + XEvent cEvent; + gboolean bEvent = XCheckIfEvent(pKeyboard->pPrivate->pDisplay, &cEvent, onCheckEvent, (XPointer)&pKeyboard->pPrivate->nXkbEventType); + + if (bEvent) + { + XklConfigRec *pConfigRec = xkl_config_rec_new(); + xkl_config_rec_get_from_server(pConfigRec, pKeyboard->pPrivate->pEngine); + gboolean bConfigChanged = FALSE; + gboolean bLayoutChanged = FALSE; + + if (!xkl_config_rec_equals(pKeyboard->pPrivate->pConfigRec, pConfigRec)) + { + gboolean bBadSignal = FALSE; + + if (g_strv_length(pKeyboard->pPrivate->pConfigRec->layouts) > g_strv_length(pConfigRec->layouts)) + { + // Pairing a Bluetooth device does this sometimes + if (pConfigRec->variants[0] == NULL) + { + bBadSignal = TRUE; + } + else + { + xkl_engine_lock_group(pKeyboard->pPrivate->pEngine, 0); + pKeyboard->pPrivate->nLayout = 0; + bLayoutChanged = TRUE; + } + } + + if (!bBadSignal) + { + xkl_config_rec_get_from_server(pKeyboard->pPrivate->pConfigRec, pKeyboard->pPrivate->pEngine); + bConfigChanged = TRUE; + } + } + + g_object_unref(pConfigRec); + pConfigRec = NULL; + + if (((XkbEvent*)&cEvent)->any.xkb_type == XkbStateNotify && !bConfigChanged && ((XkbEvent*)&cEvent)->state.group != pKeyboard->pPrivate->nLayout && ((XkbEvent*)&cEvent)->state.group < g_strv_length(pKeyboard->pPrivate->pConfigRec->layouts)) + { + pKeyboard->pPrivate->nLayout = ((XkbEvent*)&cEvent)->state.group; + bLayoutChanged = TRUE; + } + + if (bLayoutChanged) + { + g_signal_emit(pKeyboard, m_lSignals[LAYOUT_CHANGED], 0); + } + + if (bConfigChanged) + { + g_signal_emit(pKeyboard, m_lSignals[CONFIG_CHANGED], 0); + } + } + + return FALSE; +} + +static void freeLayout(gpointer pData) +{ + Layout *pLayout = pData; + + g_return_if_fail(pLayout != NULL); + + g_free(pLayout->sId); + g_free(pLayout->sLanguage); + g_free(pLayout->sDescription); + g_slice_free(Layout, pLayout); +} + +static void onParseLayouts(XklConfigRegistry *pRegistry, const XklConfigItem * pItem, gpointer pData) +{ + LayoutParser *pLayoutParser = (LayoutParser*)pData; + Layout *pLayout = g_slice_new0(Layout); + + if (pLayoutParser->sLayout) + { + pLayout->sId = g_strjoin("+", pLayoutParser->sLayout, pItem->name, NULL); + pLayout->sLanguage = g_strdup(lookupLanguage(pLayout->sId)); + pLayout->sDescription = g_strdup(pItem->description); + } + else + { + pLayout->sId = g_strdup(pItem->name); + pLayout->sLanguage = g_strdup(lookupLanguage(pLayout->sId)); + pLayout->sDescription = g_strdup(pItem->description); + } + + g_hash_table_replace(pLayoutParser->pKeyboard->pPrivate->lLayouts, pLayout->sId, pLayout); + + if (pLayoutParser->sLayout == NULL) + { + LayoutParser cLayoutParser; + cLayoutParser.sLayout = pItem->name; + cLayoutParser.pKeyboard = pLayoutParser->pKeyboard; + cLayoutParser.sLanguage = lookupLanguage(cLayoutParser.sLayout); + + xkl_config_registry_foreach_layout_variant(pRegistry, pItem->name, onParseLayouts, &cLayoutParser); + } +} + +void keyboard_AddSource(Keyboard *pKeyboard) +{ + XkbQueryExtension(pKeyboard->pPrivate->pDisplay, 0, &pKeyboard->pPrivate->nXkbEventType, 0, 0, 0); + XkbSelectEventDetails(pKeyboard->pPrivate->pDisplay, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask); + + pKeyboard->pPrivate->cPollFD.fd = ConnectionNumber(pKeyboard->pPrivate->pDisplay); + pKeyboard->pPrivate->cPollFD.events = G_IO_IN | G_IO_HUP | G_IO_ERR; + pKeyboard->pPrivate->cPollFD.revents = 0; + pKeyboard->pPrivate->cSourceFuncs.prepare = NULL; + pKeyboard->pPrivate->cSourceFuncs.check = onCheckSource; + pKeyboard->pPrivate->cSourceFuncs.dispatch = NULL; + pKeyboard->pPrivate->cSourceFuncs.finalize = NULL; + + GSource *pSource = g_source_new(&pKeyboard->pPrivate->cSourceFuncs, sizeof(Source)); + ((Source*)pSource)->pKeyboard = pKeyboard; + g_source_add_poll(pSource, &pKeyboard->pPrivate->cPollFD); + g_source_attach(pSource, NULL); +} + +guint keyboard_GetNumLayouts(Keyboard *pKeyboard) +{ + return g_strv_length(pKeyboard->pPrivate->pConfigRec->layouts); +} + +void keyboard_GetLayout(Keyboard *pKeyboard, gint nLayout, gchar **pLanguage, gchar **pDescription) +{ + if (nLayout == -1) + { + nLayout = pKeyboard->pPrivate->nLayout; + } + + gchar *sLayout = pKeyboard->pPrivate->pConfigRec->layouts[nLayout]; + gchar *sVariant = pKeyboard->pPrivate->pConfigRec->variants[nLayout]; + gchar *sId; + + if (sVariant && strlen(sVariant)) + { + sId = g_strconcat(sLayout, "+", sVariant, NULL); + } + else + { + sId = g_strdup(sLayout); + } + + const Layout *pLayout; + g_hash_table_lookup_extended(pKeyboard->pPrivate->lLayouts, sId, NULL, (gpointer*)&pLayout); + + if (pLanguage != NULL) + { + *pLanguage = g_strdup(pLayout->sLanguage); + } + + if (pDescription != NULL) + { + *pDescription = g_strdup(pLayout->sDescription); + } + + g_free(sId); +} + +void keyboard_SetLayout(Keyboard *pKeyboard, gint nLayout) +{ + xkl_engine_lock_group(pKeyboard->pPrivate->pEngine, nLayout); + pKeyboard->pPrivate->nLayout = nLayout; + g_signal_emit(pKeyboard, m_lSignals[LAYOUT_CHANGED], 0); +} + +static void onDispose(GObject *pObject) +{ + Keyboard *self = G_KEYBOARD(pObject); + + if (self->pPrivate->lLayouts) + { + g_hash_table_destroy(self->pPrivate->lLayouts); + } + + if (self->pPrivate->pConfigRec) + { + g_object_unref(self->pPrivate->pConfigRec); + self->pPrivate->pConfigRec = NULL; + } + + G_OBJECT_CLASS(keyboard_parent_class)->dispose(pObject); +} + +static void keyboard_class_init(KeyboardClass *klass) +{ + GObjectClass *pClass = G_OBJECT_CLASS(klass); + pClass->dispose = onDispose; + m_lSignals[LAYOUT_CHANGED] = g_signal_new(KEYBOARD_LAYOUT_CHANGED, G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + m_lSignals[CONFIG_CHANGED] = g_signal_new(KEYBOARD_CONFIG_CHANGED, G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); +} + +Keyboard* keyboard_new() +{ + GObject *pObject = g_object_new(G_TYPE_KEYBOARD, NULL); + + return G_KEYBOARD(pObject); +} + +static void keyboard_init(Keyboard *self) +{ + self->pPrivate = keyboard_get_instance_private(self); + self->pPrivate->pDisplay = XOpenDisplay(NULL); + + g_assert(self->pPrivate->pDisplay); + + self->pPrivate->pEngine = xkl_engine_get_instance(self->pPrivate->pDisplay); + + g_assert(self->pPrivate->pEngine); + + self->pPrivate->lLayouts = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, freeLayout); + XklConfigRegistry *pRegistry = xkl_config_registry_get_instance(self->pPrivate->pEngine); + xkl_config_registry_load(pRegistry, TRUE); + + LayoutParser cLayoutParser; + cLayoutParser.sLayout = NULL; + cLayoutParser.pKeyboard = self; + cLayoutParser.sLanguage = NULL; + xkl_config_registry_foreach_layout(pRegistry, onParseLayouts, &cLayoutParser); + + xkl_engine_start_listen(self->pPrivate->pEngine, XKLL_TRACK_KEYBOARD_STATE); + + self->pPrivate->pConfigRec = xkl_config_rec_new(); + xkl_config_rec_get_from_server(self->pPrivate->pConfigRec, self->pPrivate->pEngine); + XklState *pState = xkl_engine_get_current_state(self->pPrivate->pEngine); + self->pPrivate->nLayout = pState->group; +} |