From 4de4c6757ad71cad0ee77ffdfde7fb19fb4541a8 Mon Sep 17 00:00:00 2001
From: Robert Tari <robert@tari.in>
Date: Tue, 17 Jun 2025 16:08:25 +0200
Subject: [PATCH 3/7] Separate layout and settings for hardware and on-screen
 keyboards

Refurbished by Mike Gabriel, avoiding white-space changes, variable renamings and internal code block moves.

Fixes: https://salsa.debian.org/ubports-team/lomiri-system-settings/-/issues/20
Signed-off-by: Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
---
 CMakeLists.txt        |   3 +-
 src/keyboard-lomiri.c | 346 +++++++++++++++++++++++++++++++++++++++++-
 src/keyboard-x11.c    |  16 +-
 src/keyboard.h        |   8 +-
 src/service.c         | 189 ++++++++++++++++++++---
 5 files changed, 526 insertions(+), 36 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 37de3115..30fed304 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,7 +38,8 @@ add_definitions (-DGETTEXT_PACKAGE="${GETTEXT_PACKAGE}" -DLOCALEDIR="${CMAKE_INS
 find_package (PkgConfig REQUIRED)
 include (CheckIncludeFile)
 include (FindPkgConfig)
-pkg_check_modules(SERVICE_DEPS REQUIRED glib-2.0>=2.36 gio-2.0>=2.36 libayatana-common>=0.9.11 accountsservice xkbcommon>=1.0.3 xkbregistry>=1.0.3)
+
+pkg_check_modules(SERVICE_DEPS REQUIRED glib-2.0>=2.36 gio-2.0>=2.36 libayatana-common>=0.9.11 accountsservice xkbcommon>=1.0.3 xkbregistry>=1.0.3 libudev)
 pkg_check_modules(X11_DEPS REQUIRED x11>=1.6.5 libxklavier>=5.3)
 include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})
 
diff --git a/src/keyboard-lomiri.c b/src/keyboard-lomiri.c
index bc6b3f93..d222a06a 100644
--- a/src/keyboard-lomiri.c
+++ b/src/keyboard-lomiri.c
@@ -17,10 +17,37 @@
 #include <act/act.h>
 #include <xkbcommon/xkbregistry.h>
 #include <glib-object.h>
+#include <libudev.h>
 #include "languages.h"
 #include "keyboard.h"
 #include "system-layouts.h"
 
+gchar *LOMIRI_TO_ISO[][2] =
+{
+    {"ar", "ara"},
+    {"bn", "bd"},
+    {"bn-probhat", "bd+probhat"},
+    {"bs", "ba"},
+    {"cs", "cz"},
+    {"da", "dk"},
+    {"el", "gr"},
+    {"en", "us"},
+    {"endv", "us+dvorak"},
+    {"eo", "epo"},
+    {"fa", "ir"},
+    {"fr-ch", "ch+fr"},
+    {"gd", "gb+gla"},
+    {"he", "il"},
+    {"ja", "jp"},
+    {"ko", "kr"},
+    {"nb", "no"},
+    {"sl", "si"},
+    {"sr", "rs"},
+    {"sv", "se"},
+    {"uk", "ua"},
+    {NULL, NULL}
+};
+
 enum
 {
     LAYOUT_CHANGED,
@@ -35,8 +62,16 @@ struct _KeyboardPrivate
     GHashTable *lLayouts;
     guint nLayout;
     GSList *lLayoutRec;
+    GSList *lLayoutRecOSK;
     GSList *lUsers;
     GSettings *pSettings;
+    struct udev *pUdev;
+    struct udev_monitor *pMonitor;
+    GIOChannel *pChannel;
+    gboolean bHardwareKeyboard;
+    gboolean bSoftwareKeyboard;
+    GSettings *pLomiriSettings;
+    GSettings *pMaliitSettings;
 };
 
 typedef KeyboardPrivate priv_t;
@@ -169,9 +204,20 @@ void keyboard_AddSource(Keyboard *pKeyboard)
     return;
 }
 
-guint keyboard_GetNumLayouts(Keyboard *pKeyboard)
+guint keyboard_GetNumLayouts(Keyboard *pKeyboard, gboolean bOSK)
 {
-    return g_slist_length (pKeyboard->pPrivate->lLayoutRec);
+    guint nLayouts = 0;
+
+    if (bOSK)
+    {
+        nLayouts = g_slist_length (pKeyboard->pPrivate->lLayoutRecOSK);
+    }
+    else
+    {
+        nLayouts = g_slist_length (pKeyboard->pPrivate->lLayoutRec);
+    }
+
+    return nLayouts;
 }
 
 guint keyboard_GetLayoutIndex (Keyboard *pKeyboard)
@@ -179,14 +225,25 @@ guint keyboard_GetLayoutIndex (Keyboard *pKeyboard)
     return pKeyboard->pPrivate->nLayout;
 }
 
-void keyboard_GetLayout(Keyboard *pKeyboard, gint nLayout, gchar **pLanguage, gchar **pDescription, gchar **pId)
+void keyboard_GetLayout(Keyboard *pKeyboard, gboolean bOSK, gint nLayout, gchar **pLanguage, gchar **pDescription, gchar **pId)
 {
     if (nLayout == -1)
     {
         nLayout = pKeyboard->pPrivate->nLayout;
     }
 
-    gchar *sLayout = g_slist_nth_data (pKeyboard->pPrivate->lLayoutRec, nLayout);
+    GSList *lLayoutRec = NULL;
+
+    if (bOSK)
+    {
+        lLayoutRec = pKeyboard->pPrivate->lLayoutRecOSK;
+    }
+    else
+    {
+        lLayoutRec = pKeyboard->pPrivate->lLayoutRec;
+    }
+
+    gchar *sLayout = g_slist_nth_data (lLayoutRec, nLayout);
     const Layout *pLayout;
     g_hash_table_lookup_extended(pKeyboard->pPrivate->lLayouts, sLayout, NULL, (gpointer*)&pLayout);
 
@@ -206,7 +263,7 @@ void keyboard_GetLayout(Keyboard *pKeyboard, gint nLayout, gchar **pLanguage, gc
     }
 }
 
-void keyboard_SetLayout(Keyboard *pKeyboard, gint nLayout)
+void keyboard_SetLayoutHardware(Keyboard *pKeyboard, gint nLayout)
 {
     if (isGreeter() == FALSE)
     {
@@ -298,11 +355,62 @@ void keyboard_SetLayout(Keyboard *pKeyboard, gint nLayout)
     }
 }
 
+void keyboard_SetLayoutSoftware(Keyboard *pKeyboard, gint nLayout)
+{
+    if (isGreeter() == FALSE)
+    {
+        gchar *sId = g_slist_nth_data (pKeyboard->pPrivate->lLayoutRecOSK, nLayout);
+        guint nId = 0;
+        gchar *sLayout = NULL;
+
+        while (LOMIRI_TO_ISO[nId][0] != NULL)
+        {
+            gboolean bEqual = g_str_equal (LOMIRI_TO_ISO[nId][1], sId);
+
+            if (bEqual)
+            {
+                sLayout = LOMIRI_TO_ISO[nId][0];
+
+                break;
+            }
+
+            nId++;
+        }
+
+        if (!sLayout)
+        {
+            sLayout = sId;
+        }
+
+        g_settings_set_string (pKeyboard->pPrivate->pMaliitSettings, "active-language", sLayout);
+    }
+    else
+    {
+        // TODO
+    }
+}
+
+void keyboard_SetLayout(Keyboard *pKeyboard, gint nLayout, gboolean bOSK)
+{
+    if (bOSK)
+    {
+        keyboard_SetLayoutSoftware(pKeyboard, nLayout);
+    }
+    else
+    {
+        keyboard_SetLayoutHardware(pKeyboard, nLayout);
+    }
+}
+
 static void onDispose(GObject *pObject)
 {
     Keyboard *self = G_KEYBOARD(pObject);
     g_signal_handlers_disconnect_by_data (self->pPrivate->pSettings, self);
     g_clear_object (&self->pPrivate->pSettings);
+    g_signal_handlers_disconnect_by_data (self->pPrivate->pLomiriSettings, self);
+    g_clear_object (&self->pPrivate->pLomiriSettings);
+    g_signal_handlers_disconnect_by_data (self->pPrivate->pMaliitSettings, self);
+    g_clear_object (&self->pPrivate->pMaliitSettings);
 
     if (self->pPrivate->lLayouts)
     {
@@ -314,11 +422,31 @@ static void onDispose(GObject *pObject)
         g_slist_free_full(self->pPrivate->lLayoutRec, g_free);
     }
 
+    if (self->pPrivate->lLayoutRecOSK)
+    {
+        g_slist_free_full (self->pPrivate->lLayoutRecOSK, g_free);
+    }
+
     if (self->pPrivate->lUsers)
     {
         g_slist_free(self->pPrivate->lUsers);
     }
 
+    if (self->pPrivate->pChannel)
+    {
+        g_io_channel_unref (self->pPrivate->pChannel);
+    }
+
+    if (self->pPrivate->pMonitor)
+    {
+        udev_monitor_unref (self->pPrivate->pMonitor);
+    }
+
+    if (self->pPrivate->pUdev)
+    {
+        udev_unref (self->pPrivate->pUdev);
+    }
+
     G_OBJECT_CLASS(keyboard_parent_class)->dispose(pObject);
 }
 
@@ -376,10 +504,150 @@ static void onSourcesChanged (GSettings *pSettings, const gchar *sKey, gpointer
     }
 }
 
+static void onSoftwareKeyboardEnabled (GSettings *pSettings, const gchar *sKey, gpointer pData)
+{
+    Keyboard *self = G_KEYBOARD (pData);
+    self->pPrivate->bSoftwareKeyboard = g_settings_get_boolean (pSettings, "always-show-osk");
+    g_signal_emit (self, m_lSignals[CONFIG_CHANGED], 0);
+    g_signal_emit (self, m_lSignals[LAYOUT_CHANGED], 0);
+}
+
+static void onSoftwareLayoutChanged (GSettings *pSettings, const gchar *sKey, gpointer pData)
+{
+    Keyboard *pKeyboard = G_KEYBOARD (pData);
+    g_signal_emit (pKeyboard, m_lSignals[LAYOUT_CHANGED], 0);
+}
+
+static void onSoftwareLayoutsChanged (GSettings *pSettings, const gchar *sKey, gpointer pData)
+{
+    Keyboard *pKeyboard = G_KEYBOARD (pData);
+    gboolean bsignal = FALSE;
+
+    if (pKeyboard->pPrivate->lLayoutRecOSK)
+    {
+        g_slist_free_full (g_steal_pointer (&pKeyboard->pPrivate->lLayoutRecOSK), g_free);
+        bsignal = TRUE;
+    }
+
+    GStrv lLayouts = g_settings_get_strv (pSettings, "enabled-languages");
+    guint nLayouts = g_strv_length (lLayouts);
+
+    if (lLayouts)
+    {
+        for (guint nLayout = 0; nLayout < nLayouts; nLayout++)
+        {
+            guint nId = 0;
+            gchar *sLayout = NULL;
+
+            while (LOMIRI_TO_ISO[nId][0] != NULL)
+            {
+                gboolean bEqual = g_str_equal (LOMIRI_TO_ISO[nId][0], lLayouts[nLayout]);
+
+                if (bEqual)
+                {
+                    sLayout = g_strdup (LOMIRI_TO_ISO[nId][1]);
+
+                    break;
+                }
+
+                nId++;
+            }
+
+            if (!sLayout)
+            {
+                sLayout = g_strdup (lLayouts[nLayout]);
+            }
+
+            pKeyboard->pPrivate->lLayoutRecOSK = g_slist_append (pKeyboard->pPrivate->lLayoutRecOSK, sLayout);
+        }
+
+        g_strfreev (lLayouts);
+    }
+
+    if (bsignal)
+    {
+        g_signal_emit (pKeyboard, m_lSignals[CONFIG_CHANGED], 0);
+        g_signal_emit (pKeyboard, m_lSignals[LAYOUT_CHANGED], 0);
+    }
+}
+
+static bool udevDeviceIsHardwareKeyboard (struct udev_device *pDevice)
+{
+    const gchar *sValue = udev_device_get_property_value (pDevice, "ID_INPUT_KEYBOARD");
+    gint nCompared = g_strcmp0 (sValue, "1");
+
+    return nCompared == 0;
+}
+
+static gboolean udevHasHardwareKeyboard (struct udev *pUdev)
+{
+    struct udev_enumerate *pEnumerate = udev_enumerate_new (pUdev);
+    udev_enumerate_add_match_subsystem (pEnumerate, "input");
+    udev_enumerate_scan_devices (pEnumerate);
+    struct udev_list_entry *lEntries = udev_enumerate_get_list_entry (pEnumerate);
+    struct udev_list_entry *pEntry;
+    gboolean bFound = FALSE;
+
+    udev_list_entry_foreach (pEntry, lEntries)
+    {
+        const gchar *sPath = udev_list_entry_get_name (pEntry);
+        struct udev_device *pDevice = udev_device_new_from_syspath (pUdev, sPath);
+        gboolean bKeyboard = udevDeviceIsHardwareKeyboard (pDevice);
+
+        if (bKeyboard)
+        {
+            bFound = TRUE;
+            udev_device_unref (pDevice);
+
+            break;
+        }
+
+        udev_device_unref (pDevice);
+    }
+
+    udev_enumerate_unref (pEnumerate);
+
+    return bFound;
+}
+
+static gboolean onUdevEvent (GIOChannel *pChannel, GIOCondition nCondition, gpointer pData)
+{
+    Keyboard *self = G_KEYBOARD (pData);
+    struct udev_device *pDevice = udev_monitor_receive_device (self->pPrivate->pMonitor);
+
+    if (pDevice)
+    {
+        gboolean bKeyboard = udevDeviceIsHardwareKeyboard (pDevice);
+
+        if (bKeyboard)
+        {
+            struct udev *pUdev = udev_device_get_udev (pDevice);
+            self->pPrivate->bHardwareKeyboard = udevHasHardwareKeyboard (pUdev);
+            g_signal_emit (self, m_lSignals[CONFIG_CHANGED], 0);
+            g_signal_emit (self, m_lSignals[LAYOUT_CHANGED], 0);
+        }
+
+        udev_device_unref (pDevice);
+    }
+
+    return TRUE;
+}
+
+gboolean keyboard_hasHardwareKeyboard (Keyboard *self)
+{
+    return self->pPrivate->bHardwareKeyboard;
+}
+
+gboolean keyboard_hasSoftwareKeyboard (Keyboard *self)
+{
+    return self->pPrivate->bSoftwareKeyboard;
+}
+
 static void keyboard_init(Keyboard *self)
 {
     self->pPrivate = keyboard_get_instance_private(self);
     self->pPrivate->lLayoutRec = NULL;
+    self->pPrivate->lLayoutRecOSK = NULL;
     self->pPrivate->lLayouts = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, freeLayout);
 
     // Read all available layouts
@@ -427,6 +695,25 @@ static void keyboard_init(Keyboard *self)
 
     rxkb_context_unref(pContext);
 
+    // Lomiri-specific layouts
+    const gchar *LAYOUTS[][3] =
+    {
+        {"emoji", "emoji", "Emoji"},
+        {"Bn", "bn-avro", "Bangla (Avro)"},
+        {"Zn", "chewing", "Chinese (Chewing)"},
+        {"Zn", "pinyin", "Chinese (Pinyin)"}
+    };
+
+    for (guint nLayout = 0; nLayout < 3; nLayout++)
+    {
+        Layout *pLayout = g_slice_new0 (Layout);
+        pLayout->sId = g_strdup (LAYOUTS[nLayout][1]);
+        pLayout->sLanguage = g_strdup (LAYOUTS[nLayout][0]);
+        pLayout->sDescription = g_strdup (LAYOUTS[nLayout][2]);
+        g_hash_table_replace (self->pPrivate->lLayouts, pLayout->sId, pLayout);
+    }
+    //~Lomiri-specific layouts
+
     if (isGreeter() == FALSE)
     {
         self->pPrivate->nLayout = 0;
@@ -497,4 +784,53 @@ static void keyboard_init(Keyboard *self)
             g_signal_connect_object(pManager, "notify::is-loaded", G_CALLBACK(onManagerLoaded), self, G_CONNECT_SWAPPED);
         }
     }
+
+    // Watch for a hardware keyboard
+    self->pPrivate->pUdev = udev_new ();
+    self->pPrivate->pMonitor = udev_monitor_new_from_netlink (self->pPrivate->pUdev, "udev");
+    udev_monitor_filter_add_match_subsystem_devtype (self->pPrivate->pMonitor, "input", NULL);
+    udev_monitor_enable_receiving (self->pPrivate->pMonitor);
+    gint nFd = udev_monitor_get_fd (self->pPrivate->pMonitor);
+    self->pPrivate->bHardwareKeyboard = udevHasHardwareKeyboard (self->pPrivate->pUdev);
+    self->pPrivate->pChannel = g_io_channel_unix_new (nFd);
+    g_io_add_watch (self->pPrivate->pChannel, G_IO_IN, onUdevEvent, self);
+    //~Watch for a hardware keyboard
+
+    // Watch software keyboard
+    GSettingsSchemaSource *pSource = g_settings_schema_source_get_default ();
+    GSettingsSchema *pSchema = NULL;
+
+    if (pSource)
+    {
+        pSchema = g_settings_schema_source_lookup (pSource, "com.lomiri.Shell", FALSE);
+
+        if (pSchema)
+        {
+            g_settings_schema_unref (pSchema);
+            self->pPrivate->pLomiriSettings = g_settings_new ("com.lomiri.Shell");
+            g_signal_connect (self->pPrivate->pLomiriSettings, "changed::always-show-osk", G_CALLBACK (onSoftwareKeyboardEnabled), self);
+            onSoftwareKeyboardEnabled (self->pPrivate->pLomiriSettings, "always-show-osk", self);
+        }
+        else
+        {
+            g_error ("Panic: no com.lomiri.Shell schema found");
+        }
+
+        pSchema = g_settings_schema_source_lookup (pSource, "com.lomiri.keyboard.maliit", FALSE);
+
+        if (pSchema)
+        {
+            g_settings_schema_unref (pSchema);
+            self->pPrivate->pMaliitSettings = g_settings_new ("com.lomiri.keyboard.maliit");
+            g_signal_connect (self->pPrivate->pMaliitSettings, "changed::enabled-languages", G_CALLBACK (onSoftwareLayoutsChanged), self);
+            onSoftwareLayoutsChanged (self->pPrivate->pMaliitSettings, "enabled-languages", self);
+            g_signal_connect (self->pPrivate->pMaliitSettings, "changed::active-language", G_CALLBACK (onSoftwareLayoutChanged), self);
+            onSoftwareLayoutChanged (self->pPrivate->pMaliitSettings, "active-language", self);
+        }
+        else
+        {
+            g_error ("Panic: no com.lomiri.keyboard.maliit schema found");
+        }
+    }
+    //~Watch software keyboard
 }
diff --git a/src/keyboard-x11.c b/src/keyboard-x11.c
index 27dfb97c..89e78517 100644
--- a/src/keyboard-x11.c
+++ b/src/keyboard-x11.c
@@ -369,7 +369,7 @@ void keyboard_AddSource(Keyboard *pKeyboard)
     }
 }
 
-guint keyboard_GetNumLayouts(Keyboard *pKeyboard)
+guint keyboard_GetNumLayouts(Keyboard *pKeyboard, gboolean bOSK)
 {
     guint nLayouts = 0;
 
@@ -390,7 +390,7 @@ guint keyboard_GetLayoutIndex (Keyboard *pKeyboard)
     return pKeyboard->pPrivate->nLayout;
 }
 
-void keyboard_GetLayout(Keyboard *pKeyboard, gint nLayout, gchar **pLanguage, gchar **pDescription, gchar **pId)
+void keyboard_GetLayout(Keyboard *pKeyboard, gboolean bOSK, gint nLayout, gchar **pLanguage, gchar **pDescription, gchar **pId)
 {
     if (nLayout == -1)
     {
@@ -442,7 +442,7 @@ void keyboard_GetLayout(Keyboard *pKeyboard, gint nLayout, gchar **pLanguage, gc
     }
 }
 
-void keyboard_SetLayout(Keyboard *pKeyboard, gint nLayout)
+void keyboard_SetLayout(Keyboard *pKeyboard, gint nLayout, gboolean bOSK)
 {
     if (isGreeter() == FALSE)
     {
@@ -572,6 +572,16 @@ static void onUserChanged (GDBusConnection *pConnection, const gchar *sSender, c
     }
 }
 
+gboolean keyboard_hasHardwareKeyboard (Keyboard *self)
+{
+    return TRUE;
+}
+
+gboolean keyboard_hasSoftwareKeyboard (Keyboard *self)
+{
+    return FALSE;
+}
+
 static void keyboard_init(Keyboard *self)
 {
     self->pPrivate = keyboard_get_instance_private(self);
diff --git a/src/keyboard.h b/src/keyboard.h
index 49c21056..fd30cf6d 100644
--- a/src/keyboard.h
+++ b/src/keyboard.h
@@ -46,10 +46,12 @@ struct _KeyboardClass
 GType keyboard_get_type(void);
 Keyboard* keyboard_new();
 void keyboard_AddSource(Keyboard *pKeyboard);
-guint keyboard_GetNumLayouts(Keyboard *pKeyboard);
+guint keyboard_GetNumLayouts(Keyboard *pKeyboard, gboolean bOSK);
 guint keyboard_GetLayoutIndex (Keyboard *pKeyboard);
-void keyboard_GetLayout(Keyboard *pKeyboard, gint nLayout, gchar **pLanguage, gchar **pDescription, gchar **pId);
-void keyboard_SetLayout(Keyboard *pKeyboard, gint nLayout);
+void keyboard_GetLayout(Keyboard *pKeyboard, gboolean bOSK, gint nLayout, gchar **pLanguage, gchar **pDescription, gchar **pId);
+void keyboard_SetLayout(Keyboard *pKeyboard, gint nLayout, gboolean bOSK);
+gboolean keyboard_hasHardwareKeyboard(Keyboard *pKeyboard);
+gboolean keyboard_hasSoftwareKeyboard(Keyboard *pKeyboard);
 
 G_END_DECLS
 
diff --git a/src/service.c b/src/service.c
index f4b086cc..ee37ef96 100644
--- a/src/service.c
+++ b/src/service.c
@@ -25,14 +25,19 @@
 
 #define ICON_DEFAULT "input-keyboard"
 
+#define HWKBD FALSE
+#define OSK   TRUE
+
 static guint m_nSignal = 0;
 static void *m_pLibHandle = NULL;
 static Keyboard* (*m_fnKeyboardNew)();
 static void (*m_fnKeyboardAddSource)(Keyboard *pKeyboard);
-static guint (*m_fnKeyboardGetNumLayouts)(Keyboard *pKeyboard);
+static guint (*m_fnKeyboardGetNumLayouts)(Keyboard *pKeyboard, gboolean bOSK);
 static guint (*m_fnKeyboardGetLayoutIndex)(Keyboard *pKeyboard);
-static void (*m_fnKeyboardGetLayout)(Keyboard *pKeyboard, gint nLayout, gchar **pLanguage, gchar **pDescription, gchar **pId);
-static void (*m_fnKeyboardSetLayout)(Keyboard *pKeyboard, gint nLayout);
+static void (*m_fnKeyboardGetLayout)(Keyboard *pKeyboard, gboolean bOSK, gint nLayout, gchar **pLanguage, gchar **pDescription, gchar **pId);
+static void (*m_fnKeyboardSetLayout)(Keyboard *pKeyboard, gint nLayout, gboolean bOSK);
+static gboolean (*m_fnKeyboardHasHardwareKeyboard)(Keyboard *pKeyboard);
+static gboolean (*m_fnKeyboardHasSoftwareKeyboard)(Keyboard *pKeyboard);
 
 enum
 {
@@ -77,6 +82,7 @@ struct _IndicatorKeyboardServicePrivate
     GSimpleAction *pSettingsAction;
     GSimpleAction *pDisplayAction;
     GSimpleAction *pLayoutAction;
+    GSimpleAction *pOSKLayoutAction;
     GMenu *pLayoutSection;
     Keyboard *pKeyboard;
     GSettings *pSettings;
@@ -120,8 +126,9 @@ static GVariant* createHeaderState(IndicatorKeyboardService *self, int nProfile)
     }
     else
     {
+        gboolean bHardwareKeyboard = m_fnKeyboardHasHardwareKeyboard (self->pPrivate->pKeyboard);
         gchar *sLanguage;
-        m_fnKeyboardGetLayout(self->pPrivate->pKeyboard, -1, &sLanguage, NULL, NULL);
+        m_fnKeyboardGetLayout(self->pPrivate->pKeyboard, self->pPrivate->bLomiri && !bHardwareKeyboard, -1, &sLanguage, NULL, NULL);
 
         gchar *sIcon = g_strconcat("ayatana-indicator-keyboard-", sLanguage, NULL);
         g_free(sLanguage);
@@ -148,20 +155,64 @@ static GVariant* createHeaderState(IndicatorKeyboardService *self, int nProfile)
     return g_variant_builder_end(&cBuilder);
 }
 
-static GMenuModel* createLayoutSection(IndicatorKeyboardService *self)
+static GMenuModel* createLayoutSection(IndicatorKeyboardService *self, gboolean bOSK)
 {
     self->pPrivate->pLayoutSection = g_menu_new();
+    gboolean bCreate = FALSE;
+
+    if (self->pPrivate->bLomiri)
+    {
+        gboolean bHardwareKeyboard = m_fnKeyboardHasHardwareKeyboard (self->pPrivate->pKeyboard);
+
+        if (!bOSK)
+        {
+            if (bHardwareKeyboard)
+            {
+                g_menu_append (self->pPrivate->pLayoutSection, _("External Keyboard"), NULL);
+                bCreate = TRUE;
+            }
+        }
+        else if (bOSK)
+        {
+            gboolean bSoftwareKeyboard = m_fnKeyboardHasSoftwareKeyboard (self->pPrivate->pKeyboard);
 
-    guint nLayouts = m_fnKeyboardGetNumLayouts(self->pPrivate->pKeyboard);
+            if (bSoftwareKeyboard || !bHardwareKeyboard)
+            {
+                g_menu_append (self->pPrivate->pLayoutSection, _("On-Screen Keyboard"), NULL);
+                bCreate = TRUE;
+            }
+        }
+    }
+    else if (!bOSK) {
+        bCreate = TRUE;
+    }
+
+    if (!bCreate)
+    {
+        return G_MENU_MODEL(self->pPrivate->pLayoutSection);
+    }
+
+    guint nLayouts = m_fnKeyboardGetNumLayouts(self->pPrivate->pKeyboard, bOSK);
 
     for (guint nLayout = 0; nLayout < nLayouts; nLayout++)
     {
         gchar *sLanguage;
         gchar *sDescription;
-        m_fnKeyboardGetLayout(self->pPrivate->pKeyboard, nLayout, &sLanguage, &sDescription, NULL);
+        m_fnKeyboardGetLayout(self->pPrivate->pKeyboard, bOSK, nLayout, &sLanguage, &sDescription, NULL);
         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));
+        gchar *sAction = NULL;
+
+        if (bOSK)
+        {
+            sAction = "indicator.osklayout";
+        }
+        else
+        {
+            sAction = "indicator.layout";
+        }
+
+        g_menu_item_set_action_and_target_value(pItem, sAction, 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);
@@ -184,11 +235,12 @@ static GMenuModel* createLayoutSection(IndicatorKeyboardService *self)
     return G_MENU_MODEL(self->pPrivate->pLayoutSection);
 }
 
-static GMenuModel* createSettingsSection(IndicatorKeyboardService *self)
+static GMenuModel* createSettingsSection(IndicatorKeyboardService *self, gboolean bOSK)
 {
     GMenu * pMenu = g_menu_new();
+    gboolean bUbuntuTouch = ayatana_common_utils_is_ubuntutouch ();
 
-    if (self->pPrivate->bLomiri && (!ayatana_common_utils_is_ubuntutouch()))
+    if (self->pPrivate->bLomiri && bOSK && !bUbuntuTouch)
     {
         GMenuItem *pItem = g_menu_item_new (_("Always show OSK"), "indicator.osk(true)");
         g_menu_item_set_attribute (pItem, "x-ayatana-type", "s", "org.ayatana.indicator.switch");
@@ -196,7 +248,38 @@ static GMenuModel* createSettingsSection(IndicatorKeyboardService *self)
         g_object_unref (pItem);
     }
 
-    g_menu_append(pMenu, _("Keyboard Settings…"), "indicator.settings");
+    gchar *sAction = NULL;
+
+    if (self->pPrivate->bLomiri)
+    {
+        gboolean bHardwareKeyboard = m_fnKeyboardHasHardwareKeyboard (self->pPrivate->pKeyboard);
+
+        if (!bOSK)
+        {
+            if (bHardwareKeyboard)
+            {
+                sAction = "indicator.settings";
+            }
+        }
+        else if (bOSK)
+        {
+            gboolean bSoftwareKeyboard = m_fnKeyboardHasSoftwareKeyboard (self->pPrivate->pKeyboard);
+
+            if (bSoftwareKeyboard || !bHardwareKeyboard)
+            {
+                sAction = "indicator.osksettings";
+            }
+        }
+    }
+    else if (!bOSK)
+    {
+        sAction = "indicator.settings";
+    }
+
+    if (sAction)
+    {
+        g_menu_append(pMenu, _("Keyboard Settings…"), sAction);
+    }
 
     return G_MENU_MODEL(pMenu);
 }
@@ -204,7 +287,22 @@ static GMenuModel* createSettingsSection(IndicatorKeyboardService *self)
 static GMenuModel* createDisplaySection (IndicatorKeyboardService *self)
 {
     GMenu * pMenu = g_menu_new ();
-    g_menu_append (pMenu, _("Show Current Layout"), "indicator.display");
+    gboolean bDisplay = TRUE;
+
+    if (self->pPrivate->bLomiri)
+    {
+        gboolean bHardwareKeyboard = m_fnKeyboardHasHardwareKeyboard (self->pPrivate->pKeyboard);
+
+        if (!bHardwareKeyboard)
+        {
+            bDisplay = FALSE;
+        }
+    }
+
+    if (bDisplay)
+    {
+        g_menu_append (pMenu, _("Show Current Layout"), "indicator.display");
+    }
 
     return G_MENU_MODEL (pMenu);
 }
@@ -236,9 +334,12 @@ static void rebuildNow(IndicatorKeyboardService *self, guint nSections)
 
     if (nSections & SECTION_LAYOUTS)
     {
-        rebuildSection(pInfoDesktop->pSubmenu, 0, createLayoutSection(self));
-        rebuildSection(pInfoPhone->pSubmenu, 0, createLayoutSection(self));
-        rebuildSection(pInfoGreeter->pSubmenu, 0, createLayoutSection(self));
+        rebuildSection(pInfoDesktop->pSubmenu, 0, createLayoutSection(self, HWKBD));
+        rebuildSection(pInfoDesktop->pSubmenu, 3, createLayoutSection(self, OSK));
+        rebuildSection(pInfoPhone->pSubmenu, 0, createLayoutSection(self, HWKBD));
+        rebuildSection(pInfoPhone->pSubmenu, 2, createLayoutSection(self, OSK));
+        rebuildSection(pInfoGreeter->pSubmenu, 0, createLayoutSection(self, HWKBD));
+        rebuildSection(pInfoGreeter->pSubmenu, 1, createLayoutSection(self, OSK));
     }
 
     if (nSections & SECTION_DISPLAY)
@@ -248,8 +349,10 @@ static void rebuildNow(IndicatorKeyboardService *self, guint nSections)
 
     if (nSections & SECTION_SETTINGS)
     {
-        rebuildSection(pInfoDesktop->pSubmenu, 2, createSettingsSection(self));
-        rebuildSection(pInfoPhone->pSubmenu, 2, createSettingsSection(self));
+        rebuildSection(pInfoDesktop->pSubmenu, 2, createSettingsSection(self, HWKBD));
+        rebuildSection(pInfoDesktop->pSubmenu, 4, createSettingsSection(self, OSK));
+        rebuildSection(pInfoPhone->pSubmenu, 1, createSettingsSection(self, HWKBD));
+        rebuildSection(pInfoPhone->pSubmenu, 3, createSettingsSection(self, OSK));
     }
 }
 
@@ -267,18 +370,23 @@ static void createMenu(IndicatorKeyboardService *self, int nProfile)
     // Build the sections
     if (nProfile == PROFILE_PHONE)
     {
-        lSections[nSection++] = createLayoutSection(self);
-        lSections[nSection++] = createSettingsSection(self);
+        lSections[nSection++] = createLayoutSection(self, HWKBD);
+        lSections[nSection++] = createSettingsSection(self, HWKBD);
+        lSections[nSection++] = createLayoutSection(self, OSK);
+        lSections[nSection++] = createSettingsSection(self, OSK);
     }
     else if (nProfile == PROFILE_DESKTOP)
     {
-        lSections[nSection++] = createLayoutSection(self);
+        lSections[nSection++] = createLayoutSection(self, HWKBD);
         lSections[nSection++] = createDisplaySection(self);
-        lSections[nSection++] = createSettingsSection(self);
+        lSections[nSection++] = createSettingsSection(self, HWKBD);
+        lSections[nSection++] = createLayoutSection(self, OSK);
+        lSections[nSection++] = createSettingsSection(self, OSK);
     }
     else if (nProfile == PROFILE_GREETER)
     {
-        lSections[nSection++] = createLayoutSection(self);
+        lSections[nSection++] = createLayoutSection(self, HWKBD);
+        lSections[nSection++] = createLayoutSection(self, OSK);
     }
 
     // Add sections to the submenu
@@ -317,13 +425,21 @@ static void onConfigChanged(Keyboard *pKeyboard, gpointer pData)
 {
     IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pData);
     rebuildNow(self, SECTION_LAYOUTS);
+    rebuildNow(self, SECTION_SETTINGS);
 }
 
 static void onLayoutSelected(GSimpleAction *pAction, GVariant *pVariant, gpointer pData)
 {
     IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pData);
     const guint8 nLayout = g_variant_get_byte(pVariant);
-    m_fnKeyboardSetLayout(self->pPrivate->pKeyboard, nLayout);
+    m_fnKeyboardSetLayout(self->pPrivate->pKeyboard, nLayout, HWKBD);
+}
+
+static void onOSKLayoutSelected (GSimpleAction *pAction, GVariant *pVariant, gpointer pData)
+{
+    IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE (pData);
+    const guint8 nLayout = g_variant_get_byte (pVariant);
+    m_fnKeyboardSetLayout (self->pPrivate->pKeyboard, nLayout, OSK);
 }
 
 static void onSettings(GSimpleAction *pAction, GVariant *pVariant, gpointer pData)
@@ -340,6 +456,11 @@ static void onSettings(GSimpleAction *pAction, GVariant *pVariant, gpointer pDat
     }
 }
 
+static void onOSKSettings(GSimpleAction *pAction, GVariant *pVariant, gpointer pData)
+{
+    ayatana_common_utils_open_url ("settings:///system/sw-keyboard-layouts");
+}
+
 static void onDisplay (GSimpleAction *pAction, GVariant *pVariant, gpointer pData)
 {
     IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE (pData);
@@ -358,7 +479,7 @@ static void onDisplay (GSimpleAction *pAction, GVariant *pVariant, gpointer pDat
     {
 
         sProgram = "tecla";
-        m_fnKeyboardGetLayout (self->pPrivate->pKeyboard, -1, NULL, NULL, &sArgs);
+        m_fnKeyboardGetLayout (self->pPrivate->pKeyboard, HWKBD, -1, NULL, NULL, &sArgs);
     }
     else
     {
@@ -403,6 +524,14 @@ static void initActions(IndicatorKeyboardService *self)
     self->pPrivate->pLayoutAction = pAction;
     g_signal_connect(pAction, "activate", G_CALLBACK(onLayoutSelected), self);
 
+    if (self->pPrivate->bLomiri)
+    {
+        pAction = g_simple_action_new("osklayout", G_VARIANT_TYPE_BYTE);
+        g_action_map_add_action(G_ACTION_MAP(self->pPrivate->pActionGroup), G_ACTION(pAction));
+        self->pPrivate->pOSKLayoutAction = pAction;
+        g_signal_connect(pAction, "activate", G_CALLBACK(onOSKLayoutSelected), self);
+    }
+
     if (self->pPrivate->bLomiri && (!ayatana_common_utils_is_ubuntutouch()))
     {
         gboolean bOsk = g_settings_get_boolean (self->pPrivate->pLomiriSettings, "always-show-osk");
@@ -418,6 +547,14 @@ static void initActions(IndicatorKeyboardService *self)
     self->pPrivate->pSettingsAction = pAction;
     g_signal_connect(pAction, "activate", G_CALLBACK(onSettings), self);
 
+    if (self->pPrivate->bLomiri)
+    {
+        pAction = g_simple_action_new ("osksettings", 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 (onOSKSettings), self);
+    }
+
     pAction = g_simple_action_new ("display", NULL);
     g_action_map_add_action (G_ACTION_MAP (self->pPrivate->pActionGroup), G_ACTION (pAction));
     self->pPrivate->pDisplayAction = pAction;
@@ -534,6 +671,7 @@ static void onDispose(GObject *pObject)
     g_clear_object (&self->pPrivate->pSettingsAction);
     g_clear_object (&self->pPrivate->pDisplayAction);
     g_clear_object (&self->pPrivate->pLayoutAction);
+    g_clear_object (&self->pPrivate->pOSKLayoutAction);
 
     for (int nProfile = 0; nProfile < N_PROFILES; ++nProfile)
     {
@@ -556,6 +694,7 @@ static void onSettingsChanged(GSettings *pSettings, gchar *sKey, gpointer pData)
 {
     IndicatorKeyboardService *self = INDICATOR_KEYBOARD_SERVICE(pData);
     rebuildNow(self, SECTION_HEADER);
+    rebuildNow(self, SECTION_SETTINGS);
 }
 
 static void indicator_keyboard_service_init(IndicatorKeyboardService *self)
@@ -589,6 +728,8 @@ static void indicator_keyboard_service_init(IndicatorKeyboardService *self)
     m_fnKeyboardGetLayoutIndex = dlsym(m_pLibHandle, "keyboard_GetLayoutIndex");
     m_fnKeyboardGetLayout = dlsym(m_pLibHandle, "keyboard_GetLayout");
     m_fnKeyboardSetLayout = dlsym(m_pLibHandle, "keyboard_SetLayout");
+    m_fnKeyboardHasHardwareKeyboard = dlsym(m_pLibHandle, "keyboard_hasHardwareKeyboard");
+    m_fnKeyboardHasSoftwareKeyboard = dlsym(m_pLibHandle, "keyboard_hasSoftwareKeyboard");
     self->pPrivate = indicator_keyboard_service_get_instance_private(self);
     self->pPrivate->bLomiri = bLomiri;
     self->pPrivate->pCancellable = g_cancellable_new();
-- 
2.47.2

