From b85daf94830a3ca39d59237fdb07bc33ede1c450 Mon Sep 17 00:00:00 2001 From: Robert Tari Date: Sun, 24 Jan 2021 03:38:44 +0100 Subject: 100% re-write of the keyboard indicator in plain C. --- src/service.c | 412 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 src/service.c (limited to 'src/service.c') diff --git a/src/service.c b/src/service.c new file mode 100644 index 00000000..5353fb24 --- /dev/null +++ b/src/service.c @@ -0,0 +1,412 @@ +#include +#include +#include "service.h" +#include "utils.h" + +#define BUS_NAME "org.ayatana.indicator.keyboard" +#define BUS_PATH "/org/ayatana/indicator/keyboard" + +static guint m_nSignal = 0; + +enum +{ + SECTION_HEADER = (1 << 0), + SECTION_LAYOUTS = (1 << 1), + SECTION_SETTINGS = (1 << 2) +}; + +enum +{ + PROFILE_PHONE, + PROFILE_DESKTOP, + PROFILE_GREETER, + N_PROFILES +}; + +static const char * const m_lMenuNames[N_PROFILES] = +{ + "phone", + "desktop", + "greeter" +}; + +struct ProfileMenuInfo +{ + GMenu *pMenu; + GMenu *pSubmenu; + guint nExportId; +}; + +struct _IndicatorKeyboardServicePrivate +{ + GCancellable *pCancellable; + guint nOwnId; + guint nActionsId; + GDBusConnection *pConnection; + gboolean bMenusBuilt; + struct ProfileMenuInfo lMenus[N_PROFILES]; + GSimpleActionGroup *pActionGroup; + GSimpleAction *pHeaderAction; + GSimpleAction *pSettingsAction; + GSimpleAction *pLayoutAction; + GMenu *pLayoutSection; + Keyboard *pKeyboard; +}; + +typedef IndicatorKeyboardServicePrivate priv_t; + +G_DEFINE_TYPE_WITH_PRIVATE(IndicatorKeyboardService, indicator_keyboard_service, G_TYPE_OBJECT) + +static GVariant* createHeaderState(IndicatorKeyboardService *self) +{ + GVariantBuilder cBuilder; + g_variant_builder_init(&cBuilder, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add(&cBuilder, "{sv}", "title", g_variant_new_string(_("Keyboard"))); + g_variant_builder_add(&cBuilder, "{sv}", "visible", g_variant_new_boolean(TRUE)); + + gchar *sLanguage; + keyboard_GetLayout(self->pPrivate->pKeyboard, -1, &sLanguage, NULL); + + gchar *sIcon = g_strconcat("ayatana-indicator-keyboard-", sLanguage, NULL); + g_free(sLanguage); + + GIcon *pIcon = g_themed_icon_new_with_default_fallbacks(sIcon); + g_free(sIcon); + g_variant_builder_add(&cBuilder, "{sv}", "accessible-desc", g_variant_new_string(_("Current keyboard layout"))); + + if (pIcon) + { + GVariant *pSerialized = g_icon_serialize(pIcon); + + if (pSerialized != NULL) + { + g_variant_builder_add(&cBuilder, "{sv}", "icon", pSerialized); + g_variant_unref(pSerialized); + } + + g_object_unref(pIcon); + } + + return g_variant_builder_end(&cBuilder); +} + +static GMenuModel* createDesktopLayoutSection(IndicatorKeyboardService *self, int nProfile) +{ + self->pPrivate->pLayoutSection = g_menu_new(); + + guint nLayouts = keyboard_GetNumLayouts(self->pPrivate->pKeyboard); + + for (guint nLayout = 0; nLayout < nLayouts; nLayout++) + { + gchar *sLanguage; + gchar *sDescription; + keyboard_GetLayout(self->pPrivate->pKeyboard, nLayout, &sLanguage, &sDescription); + GMenuItem *pItem = g_menu_item_new(sDescription, NULL); + g_free(sDescription); + g_menu_item_set_action_and_target_value(pItem, "indicator.layout", g_variant_new_byte(nLayout)); + g_menu_item_set_attribute_value(pItem, "x-ayatana-layout", g_variant_new_byte(nLayout)); + gchar *sIcon = g_strconcat("ayatana-indicator-keyboard-", sLanguage, NULL); + g_free(sLanguage); + GIcon *pIcon = g_themed_icon_new_with_default_fallbacks(sIcon); + g_free(sIcon); + GVariant *pSerialized = g_icon_serialize(pIcon); + + if (pSerialized != NULL) + { + g_menu_item_set_attribute_value(pItem, G_MENU_ATTRIBUTE_ICON, pSerialized); + g_variant_unref(pSerialized); + } + + g_object_unref(pIcon); + + g_menu_append_item(self->pPrivate->pLayoutSection, pItem); + g_object_unref(pItem); + } + + return G_MENU_MODEL(self->pPrivate->pLayoutSection); +} + +static GMenuModel* createDesktopSettingsSection(IndicatorKeyboardService *self) +{ + GMenu * pMenu = g_menu_new(); + g_menu_append(pMenu, _("Keyboard Settings..."), "indicator.settings"); + + return G_MENU_MODEL(pMenu); +} + +static void rebuildSection(GMenu *pMenu, int nPos, GMenuModel *pModel) +{ + g_menu_remove(pMenu, nPos); + g_menu_insert_section(pMenu, nPos, NULL, pModel); + g_object_unref(pModel); +} + +static void rebuildNow(IndicatorKeyboardService *self, guint nSections) +{ + struct ProfileMenuInfo *pInfoDesktop = &self->pPrivate->lMenus[PROFILE_DESKTOP]; + struct ProfileMenuInfo *pInfoGreeter = &self->pPrivate->lMenus[PROFILE_GREETER]; + + if (nSections & SECTION_HEADER) + { + g_simple_action_set_state(self->pPrivate->pHeaderAction, createHeaderState(self)); + } + + if (!self->pPrivate->bMenusBuilt) + { + return; + } + + if (nSections & SECTION_LAYOUTS) + { + rebuildSection(pInfoDesktop->pSubmenu, 0, createDesktopLayoutSection(self, PROFILE_DESKTOP)); + rebuildSection(pInfoGreeter->pSubmenu, 0, createDesktopLayoutSection(self, PROFILE_GREETER)); + } + + if (nSections & SECTION_SETTINGS) + { + rebuildSection(pInfoDesktop->pSubmenu, 1, createDesktopSettingsSection(self)); + } +} + +static void createMenu(IndicatorKeyboardService *self, int nProfile) +{ + GMenu *pMenu; + GMenu *pSubmenu; + GMenuItem *pItem; + GMenuModel *lSections[16]; + guint nSection = 0; + + g_assert(0 <= nProfile && nProfile < N_PROFILES); + g_assert(self->pPrivate->lMenus[nProfile].pMenu == NULL); + + // Build the sections + if (nProfile == PROFILE_PHONE) + { + lSections[nSection++] = createDesktopLayoutSection(self, nProfile); + lSections[nSection++] = createDesktopSettingsSection(self); + } + else if (nProfile == PROFILE_DESKTOP) + { + lSections[nSection++] = createDesktopLayoutSection(self, nProfile); + lSections[nSection++] = createDesktopSettingsSection(self); + } + else if (nProfile == PROFILE_GREETER) + { + lSections[nSection++] = createDesktopLayoutSection(self, nProfile); + } + + // Add sections to the submenu + pSubmenu = g_menu_new(); + + for (guint i = 0; i < nSection; ++i) + { + g_menu_append_section(pSubmenu, NULL, lSections[i]); + g_object_unref(lSections[i]); + } + + // Add submenu to the header + pItem = g_menu_item_new(NULL, "indicator._header"); + g_menu_item_set_attribute(pItem, "x-ayatana-type", "s", "org.ayatana.indicator.root"); + g_menu_item_set_submenu(pItem, G_MENU_MODEL(pSubmenu)); + g_object_unref(pSubmenu); + + // Add header to the menu + pMenu = g_menu_new(); + g_menu_append_item(pMenu, pItem); + g_object_unref(pItem); + + self->pPrivate->lMenus[nProfile].pMenu = pMenu; + self->pPrivate->lMenus[nProfile].pSubmenu = pSubmenu; +} + +static void onLayoutChanged(Keyboard *pKeyboard, gpointer pData) +{ + IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pData); + rebuildNow(self, SECTION_HEADER); +} + +static void onConfigChanged(Keyboard *pKeyboard, gpointer pData) +{ + IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pData); + rebuildNow(self, SECTION_LAYOUTS); +} + +static void onLayoutSelected(GSimpleAction *pAction, GVariant *pVariant, gpointer pData) +{ + IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pData); + const guint8 nLayout = g_variant_get_byte(pVariant); + keyboard_SetLayout(self->pPrivate->pKeyboard, nLayout); +} + +static void onSettings(GSimpleAction *pAction, GVariant *pVariant, gpointer pUserData) +{ + if (is_mate()) + { + execute_command("mate-keyboard-properties"); + } +} + +static void initActions(IndicatorKeyboardService *self) +{ + GSimpleAction *pAction; + self->pPrivate->pActionGroup = g_simple_action_group_new(); + + pAction = g_simple_action_new_stateful("_header", NULL, createHeaderState(self)); + g_action_map_add_action(G_ACTION_MAP(self->pPrivate->pActionGroup), G_ACTION(pAction)); + self->pPrivate->pHeaderAction = pAction; + + pAction = g_simple_action_new("layout", G_VARIANT_TYPE_BYTE); + g_action_map_add_action(G_ACTION_MAP(self->pPrivate->pActionGroup), G_ACTION(pAction)); + self->pPrivate->pLayoutAction = pAction; + g_signal_connect(pAction, "activate", G_CALLBACK(onLayoutSelected), self); + + pAction = g_simple_action_new("settings", NULL); + g_action_map_add_action(G_ACTION_MAP(self->pPrivate->pActionGroup), G_ACTION(pAction)); + self->pPrivate->pSettingsAction = pAction; + g_signal_connect(pAction, "activate", G_CALLBACK(onSettings), self); +} + +static void onBusAcquired(GDBusConnection *pConnection, const gchar *sName, gpointer pData) +{ + IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pData); + + g_debug("bus acquired: %s", sName); + + self->pPrivate->pConnection = (GDBusConnection*)g_object_ref(G_OBJECT (pConnection)); + guint nId; + GError *pError = NULL; + + // Export the actions + if ((nId = g_dbus_connection_export_action_group(pConnection, BUS_PATH, G_ACTION_GROUP(self->pPrivate->pActionGroup), &pError))) + { + self->pPrivate->nActionsId = nId; + } + else + { + g_warning("cannot export action group: %s", pError->message); + g_clear_error(&pError); + } + + GString *pPath = g_string_new(NULL); + + // Export the menus + for (int nProfile = 0; nProfile < N_PROFILES; ++nProfile) + { + struct ProfileMenuInfo *pInfo = &self->pPrivate->lMenus[nProfile]; + + g_string_printf(pPath, "%s/%s", BUS_PATH, m_lMenuNames[nProfile]); + + if ((nId = g_dbus_connection_export_menu_model(pConnection, pPath->str, G_MENU_MODEL(pInfo->pMenu), &pError))) + { + pInfo->nExportId = nId; + } + else + { + g_warning("cannot export %s menu: %s", pPath->str, pError->message); + g_clear_error (&pError); + } + } + + g_string_free(pPath, TRUE); +} + +static void unexport(IndicatorKeyboardService *self) +{ + // Unexport the menus + for (int nProfile = 0; nProfile < N_PROFILES; ++nProfile) + { + guint *nId = &self->pPrivate->lMenus[nProfile].nExportId; + + if (*nId) + { + g_dbus_connection_unexport_menu_model(self->pPrivate->pConnection, *nId); + *nId = 0; + } + } + + // Unexport the actions + if (self->pPrivate->nActionsId) + { + g_dbus_connection_unexport_action_group(self->pPrivate->pConnection, self->pPrivate->nActionsId); + self->pPrivate->nActionsId = 0; + } +} + +static void onNameLost(GDBusConnection *pConnection, const gchar *sName, gpointer pData) +{ + IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pData); + + g_debug("%s %s name lost %s", G_STRLOC, G_STRFUNC, sName); + + unexport(self); +} + +static void onDispose(GObject *pObject) +{ + IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pObject); + + if (self->pPrivate->pKeyboard != NULL) + { + g_object_unref(G_OBJECT(self->pPrivate->pKeyboard)); + self->pPrivate->pKeyboard = NULL; + } + + if (self->pPrivate->nOwnId) + { + g_bus_unown_name(self->pPrivate->nOwnId); + self->pPrivate->nOwnId = 0; + } + + unexport(self); + + if (self->pPrivate->pCancellable != NULL) + { + g_cancellable_cancel(self->pPrivate->pCancellable); + g_clear_object(&self->pPrivate->pCancellable); + } + + g_clear_object (&self->pPrivate->pSettingsAction); + g_clear_object (&self->pPrivate->pLayoutAction); + g_clear_object (&self->pPrivate->pHeaderAction); + g_clear_object (&self->pPrivate->pActionGroup); + g_clear_object (&self->pPrivate->pConnection); + + G_OBJECT_CLASS(indicator_keyboard_service_parent_class)->dispose(pObject); +} + +static void indicator_keyboard_service_init(IndicatorKeyboardService *self) +{ + self->pPrivate = indicator_keyboard_service_get_instance_private(self); + self->pPrivate->pCancellable = g_cancellable_new(); + self->pPrivate->pKeyboard = keyboard_new(); + g_signal_connect(self->pPrivate->pKeyboard, KEYBOARD_LAYOUT_CHANGED, G_CALLBACK(onLayoutChanged), self); + g_signal_connect(self->pPrivate->pKeyboard, KEYBOARD_CONFIG_CHANGED, G_CALLBACK(onConfigChanged), self); + initActions(self); + + for (int nProfile = 0; nProfile < N_PROFILES; ++nProfile) + { + createMenu(self, nProfile); + } + + self->pPrivate->bMenusBuilt = TRUE; + self->pPrivate->nOwnId = g_bus_own_name(G_BUS_TYPE_SESSION, BUS_NAME, G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, onBusAcquired, NULL, onNameLost, self, NULL); +} + +static void indicator_keyboard_service_class_init(IndicatorKeyboardServiceClass *klass) +{ + GObjectClass *pClass = G_OBJECT_CLASS(klass); + pClass->dispose = onDispose; + m_nSignal = g_signal_new("name-lost", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(IndicatorKeyboardServiceClass, pNameLost), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); +} + +IndicatorKeyboardService *indicator_keyboard_service_new() +{ + GObject *pObject = g_object_new(INDICATOR_TYPE_KEYBOARD_SERVICE, NULL); + + return INDICATOR_KEYBOARD_SERVICE(pObject); +} + +void indicator_keyboard_service_AddKeyboardSource(IndicatorKeyboardService *self) +{ + keyboard_AddSource(self->pPrivate->pKeyboard); +} -- cgit v1.2.3