Onscreen Keyboard integration with GTK+3 application

Hi there,
I am developing a GTK application with GTK+3.0 in eclipse for embedded processor Toradex Colibri iMX6DL running on embedded Linux and I am quite successful with my application. I am going to use this application in a touchscreen where keyboard will not be present. So can anyone suggest how to get onscreen keyboard when a entry is focused in gtk application.
Also is there any keyboard widget available in GTK by default or any-other library for keyboard.(The main thing is I want the keyboard to open inside my window not as a separate application

Thanks for your time.
Regards,
Nishanth

GTK provides extension points for input method modules, like IBus, to be able to insert text into the various text entry widgets.

GNOME Shell, for instance, uses IBus for its own on screen keyboard. There are other, stand alone projects in various state of maintenance, like Caribou.

Hi @ebassi,
Thanks for your suggestion and I am Working out on it. But we are trying to get the keyboard as a part of same application and not to open keyboard as a separate application like how android does.
We are planning of developing keyboard as a popup with set of buttons as in normal keyboards and when the key is pressed it returns its alphabet in the entry. Will it be reliable to do like this or do we have any-other method of implementing on-screen keyboard.

Hi Nishanth,

You can set up a group of buttons to work as a keyboard. Use a hashtable to relate or look up values in a table and then use those values for typing in the entry. That way you can factor in the shift key and other keys to get them to work. A short demo of the idea.

Eric

/*
   gcc -Wall keyboard1.c -o keyboard1 `pkg-config --cflags --libs gtk+-3.0`
   Tested with GTK3.22 and GTK3.22 on Ubuntu18.04   
*/
#include<gtk/gtk.h>

struct key{
    gint id;
    GtkWidget *button;
  };

static const gchar letters[18]="QWERTYASDFGHZXCVBN";
//Need single chars as strings.
static gchar single_char[2]={'A', '\0'};

static void button_clicked(GtkWidget *button, gpointer *user_data)
  {
    gpointer *button_index=g_hash_table_lookup((GHashTable*)user_data[0], button);
    g_print("Button index %i\n", (gint)(*button_index));
    gint index=(gint)(*button_index);
    single_char[0]=letters[index];
    gchar *string=g_strdup_printf("%s%s", gtk_entry_get_text(GTK_ENTRY(user_data[1])), single_char);
    gtk_entry_set_text(GTK_ENTRY(user_data[1]), string);
    g_free(string);
  }
int main(int argc, char *argv[])
  {
    gtk_init (&argc, &argv);
    gint i=0;
    gint j=0;
    
    GtkWidget *window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "Keyboard");
    gtk_window_set_default_size(GTK_WINDOW(window), 400, 200);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    GtkWidget *entry=gtk_entry_new();
    gtk_widget_set_hexpand(entry, TRUE);

    //Save buttons in an array.
    struct key k1;
    GArray *keyboard=g_array_new(FALSE, FALSE, sizeof(struct key));    
    for(i=0;i<18;i++)
      {
        single_char[0]=letters[i];
        k1.id=i;
        k1.button=gtk_button_new_with_label(single_char);
        g_array_append_val(keyboard, k1);
      }   
 
    //A hash table to look up array index values.
    struct key *p1=NULL;
    GHashTable *hash_table=g_hash_table_new(NULL, NULL);
    for(i=0;i<18;i++)
      {
        p1=&g_array_index(keyboard, struct key, i);
        g_hash_table_insert(hash_table, p1->button, &(p1->id));
      }

    gpointer user_data[2]={hash_table, entry};
    GtkWidget *grid1=gtk_grid_new();
    for(i=0;i<3;i++)
      {
        for(j=0;j<6;j++)
          {
            p1=&g_array_index(keyboard, struct key, i*6+j);
            gtk_grid_attach(GTK_GRID(grid1), p1->button, j, i, 1, 1);
            g_signal_connect(p1->button, "clicked", G_CALLBACK(button_clicked), user_data);
          }
      } 

    GtkWidget *scroll=gtk_scrolled_window_new(NULL, NULL);
    gtk_widget_set_vexpand(scroll, TRUE);
    gtk_widget_set_hexpand(scroll, TRUE);
    gtk_container_add(GTK_CONTAINER(scroll), grid1);

    GtkWidget *expander=gtk_expander_new("Keyboard");
    gtk_widget_set_vexpand(expander, TRUE);
    gtk_widget_set_hexpand(expander, TRUE);
    gtk_container_add(GTK_CONTAINER(expander), scroll);

    GtkWidget *grid2=gtk_grid_new();
    gtk_grid_attach(GTK_GRID(grid2), expander, 0, 0, 1, 1);
    gtk_grid_attach(GTK_GRID(grid2), entry, 0, 1, 1, 1);

    gtk_container_add(GTK_CONTAINER(window), grid2);

    gtk_widget_show_all(window);

    gtk_main();

    g_hash_table_destroy(hash_table);
    g_array_free(keyboard, TRUE);

    return 0;
  }  
1 Like

Hello @cecashon
Thank you very much for your support and the example given helps me a lot and i am going to starting coding for a complete keyboard in similar way.

Hi Nishanth,

I don’t have language expertise and think international keyboard layouts are a tough problem. Being an English speaker, an English only keyboard can be done but I don’t know how keys and keyboards are mapped in different languages or if there is a standard way of going about it. Maybe someone else knows more than I do about that.

Eric

Thank you @cecashon,
We will be using the english keyboard only so there will not be issue and once again thank you for the example
Regards
Nishanth

Hi @cecashon, @ebassi,
Thank you guys for your support and now i came up with a idea of how to implement a keyboard inside a GTK application but i am facing an issue with the size of the buttons. I created the keyboard layout in GLADE and in preview it looks fine but when i run the code in a embedded processor running embedded Linux, the size of the buttons looks very large and i really cant reduce the size. So is there any possiblities of reducing the button size lesser than its minimum size?

To style the buttons you can use CSS. The button size will dynamically change with the label font size. In the keyboard1.c code try.

...
    gtk_container_add(GTK_CONTAINER(window), grid2);

    gchar *css_string=g_strdup("label{font-size: 30px; font-weight: bold;}");
    GError *css_error=NULL;
    GtkCssProvider *provider=gtk_css_provider_new();
    gtk_css_provider_load_from_data(provider, css_string, -1, &css_error);
    gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    if(css_error!=NULL)
      {
        g_print("CSS loader error %s\n", css_error->message);
        g_error_free(css_error);
      }
    g_object_unref(provider);
    g_free(css_string);

    gtk_widget_show_all(window);
...

and test some font sizes out. This is tested on GTK3.22. If you are using a version of GTK before 3.20 “label” in the CSS string will be “GtkLabel”.

Eric

Thank you @cecashon. It helped me a lot and i have some issues with adding the background color for the buttons. I am elaborating my doubts,
1.I tried with the above code and i am quite successful in changing the size of the button and in the similar way can you please share some examples of how to change the size of combobox and entries.
2.I tried this code
.myButton
{
font-size: 30px;
font-weight: bold;
color:red;
background-color:red;
min-width:5px;
min-height:5px
}
Everything worked well expect the background color. So i changed background-color with background, the color of the button changed but i was not in a correct way but if i click a buttSO how to add background color to a button using CSS.

Thank you for your time

If possible, check your application with the GTK inspector. So you can actually see the widgets hierarchy and their CSS properties.

$ GTK_DEBUG=interactive ./yourapp .

It is possible that you are setting the background color properly, but, there could be another widget inside your button that is overlapping, or even just another CSS property taking precedence e.g.

.myButton {
   background-color: red;
   background-image: none;	
}

I developed some applications with an on-screen keyboard, and I quickly abandoned the “grid of buttons” approach. I used an external process (specifically onboard, but any keyboard supporting the XEmbed protocol would work) and embedded the keyboard in my dialog with a GtkSocket.

/* The keyboard process must be started *before* your application.
   ui.keyboard_plug is the XID to embed returned by that process. */
if (ui.keyboard_plug > 0) {
    widget = gtk_socket_new();
    gtk_widget_set_size_request(widget, -1, 280);
    gtk_widget_show(widget);
    gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(ui.input_dialog)), widget);
    gtk_socket_add_id(GTK_SOCKET(widget), (Window) ui.keyboard_plug);
}

After that I just traversed all my UI looking for GtkEntry instances and attached an handler to their "button-press-event" signal.

This approach is simple and effective but it has some drawbacks:

  1. AFAIK it works only on X11;
  2. any click on the entries will open up the keyboard;
  3. because of 2, I had to use a hack to be able to click the up and down arrows of GtkSpinButton without opening the keyboard dialog.
1 Like

Hi @ntd,
Thanks for your reply and i am going to try with your idea. Can you explain me wheather this onboard need to be downloaded and installed or it is already available in the Os and also here ui.keyboard_plug refers to what?
Thanks for your time
Regards
Nishanth

In the OS I use, onboard it is already packaged (pacman -S onboard on Archlinux or apt install onboard on Debian). I suspect many other distro have it ready to install.

I connect it to my application by passing the XID via program arguments, e.g. by adding something like the following to the X11 startup script:

# Always respawn a new virtual keyboard instance
killall onboard
onboard -e -l Phone -t ModelM -s 800x600 > kbdpid &
sleep 4
myapp --keyboard-plug=$(cat kbdpid)

There is definitely room for improvements here, but this is what I came out with after a few testing.

ui.keyboard_plug is just the integer passed in as argument. The ending result should be similar to the following screenshot:

Hi Nicola,

It seems that Onboard is already available on Ubuntu18.04. If you use GIO you can get your window id and not need to start Onboard before your app. Looked interesting so I gave it a try. A lot that can be improved here also.

Eric


/*
   Test getting an onboard keyboard as a plug in a socket.

   gcc -Wall onboard_socket1.c -o onboard_socket1 `pkg-config --cflags --libs gtk+-3.0`

   Tested on Ubuntu18.04 and GTK3.22
*/

#include<gtk/gtk.h>
#include<gtk/gtkx.h>
#include<stdlib.h>

static GPid child_pid=0;

static void plug_added(GtkSocket *socket, gpointer data);
static void add_plug(GtkWidget *widget, gpointer *data);
static gboolean watch_out_channel(GIOChannel *channel, GIOCondition cond, gpointer *data);
static void quit_program(GtkWidget *widget, gpointer data);

int main(int argc, char *argv[])
  {
    gtk_init(&argc, &argv);

    GtkWidget *window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "Socket");
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(window), 500, 300);
    g_signal_connect(window, "destroy", G_CALLBACK(quit_program), NULL);

    GtkWidget *entry=gtk_entry_new();
    gtk_widget_set_hexpand(entry, TRUE);

    GtkWidget *socket=gtk_socket_new();
    gtk_widget_set_size_request(socket, 500, 200);
    gtk_widget_set_hexpand(socket, TRUE);
    gtk_widget_set_vexpand(socket, TRUE);
    g_signal_connect(socket, "plug-added", G_CALLBACK(plug_added), NULL);

    GtkWidget *expander=gtk_expander_new("Keyboard");
    gtk_container_add(GTK_CONTAINER(expander), socket);

    GtkWidget *button=gtk_button_new_with_label("Get Keyboard");
    gtk_widget_set_hexpand(button, TRUE);
    gpointer data[]={button, socket, entry, expander};
    g_signal_connect(button, "clicked", G_CALLBACK(add_plug), data);

    GtkWidget *grid=gtk_grid_new();
    gtk_container_add(GTK_CONTAINER(window), grid);
    gtk_grid_attach(GTK_GRID(grid), button, 0, 0, 1, 1);
    gtk_grid_attach(GTK_GRID(grid), entry, 0, 1, 1, 1);
    gtk_grid_attach(GTK_GRID(grid), expander, 0, 2, 1, 1);

    gtk_widget_show_all(window);

    gtk_main();

    return 0;   
  }
static void plug_added(GtkSocket *socket, gpointer data)
  {
    g_print("Keyboard Added\n");
  }
static void add_plug(GtkWidget *widget, gpointer *data)
  {
    g_print("Add Plug\n");
    gboolean retval;
    gchar *cmd=g_strdup("onboard -e -l Phone -t ModelM -s 400x300");
    gchar **arg_v = NULL;
    gint std_out=0;
    GError *error=NULL;
    GIOChannel *std_out_ch;

    g_shell_parse_argv(cmd, NULL, &arg_v, NULL);
    retval=g_spawn_async_with_pipes(NULL, arg_v, NULL, G_SPAWN_DO_NOT_REAP_CHILD|G_SPAWN_SEARCH_PATH, NULL, NULL, &child_pid, NULL, &std_out, NULL, &error);

    g_strfreev(arg_v);

    if(retval)
      {
        gtk_widget_set_sensitive(widget, FALSE);
        std_out_ch=g_io_channel_unix_new(std_out);
        g_io_add_watch(std_out_ch, G_IO_IN | G_IO_HUP, (GIOFunc)watch_out_channel, data);
        g_print("Channel Added\n");
      }
    else 
      {
        g_print("Couldn't start plug. %s\n", error->message);
        g_error_free(error);
      }
  }
static gboolean watch_out_channel(GIOChannel *channel, GIOCondition cond, gpointer *data)
  {
    g_print("Watch Out\n");
    if(cond==G_IO_HUP)
      {
        g_print("Unreference Channel\n");
        g_io_channel_unref(channel);
        return FALSE;
      } 
    else
      {
        gsize size;
        gchar *string1=NULL;
        g_io_channel_read_line(channel, &string1, &size, NULL, NULL);
        g_print("Window ID %s", string1);
        if(atoi(string1)>0)
          {
            gtk_socket_add_id(GTK_SOCKET(data[1]), (Window)atoi(string1));
            gtk_expander_set_expanded(GTK_EXPANDER(data[3]), TRUE);
            gtk_widget_grab_focus(GTK_WIDGET(data[2]));
          }
        if(g_strcmp0("dis\n", string1)==0)
          {
            gchar *string2=g_strdup_printf("kill %i", (gint)child_pid);
            g_print("%s\n", string2);
            g_spawn_command_line_async(string2, NULL); 
            g_spawn_close_pid(child_pid);
            g_free(string2);
            gtk_widget_set_sensitive(GTK_WIDGET(data[0]), TRUE);
          }
        g_free(string1);
        return TRUE;
      }
  }
static void quit_program(GtkWidget *widget, gpointer data)
  {
    g_print("Pid %i\n", child_pid);
    if(child_pid!=0) 
      {
        gchar *string=g_strdup_printf("kill %i", (gint)child_pid);
        g_print("%s\n", string);
        g_spawn_command_line_async(string, NULL); 
        g_spawn_close_pid(child_pid);
        g_free(string);
      }
    gtk_main_quit();
  }

Yes, this is definitely an improvement over my approach above.

The rationale of using an XID was to decouple the virtual keyboard from my app and share it across different processes (I restart the app when non-trivial customizations happen, e.g. when changing locale). After onboard crashed the first time I quickly realized the weakness of this approach.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.