Since MadLadSquad maintains numerous applications, we decided to create this page as tips for developers of non-standard applications.

To define a standard application, any application that isn't special i.e. a text editor or calculator

Developing graphical IMs for IBus

IBus is an Input Method(from now referred to as an IM) framework for Unix systems. It uses a dbus-like architecture.

Adding the metadata file

To integrate your project with IBus, create a <project name here>.xml file that is similar to this:

<?xml version="1.0" encoding="utf-8"?>
<component>
    <name>org.freedesktop.IBus.UntitledIBusHandwriting</name>
    <description>The UntitledIBusHandwriting engine</description>
    <exec>/usr/libexec/UntitledIBusHandwriting --ibus</exec>
    <version>1.0.0.0</version>
    <author>MadLadSquad &lt;contact@madladsquad.com&gt;</author>
    <license>MIT</license>
    <homepage>https://madladsquad.com/docs/UntitledIBusHandwriting/</homepage>
    <textdomain>UntitledIBusHandwriting</textdomain>

    <engines>
        <engine>
            <name>UntitledIBusHandwriting</name>
            <language>bg</language>
            <license>MIT</license>
            <author>MadLadSquad &lt;contact@madladsquad.com&gt;</author>
            <icon>/usr/share/UntitledIBusHandwriting/icons/untitled-ibus-handwriting.png</icon>
            <layout>default</layout>
            <longname>UntitledIBusHandwriting</longname>
            <description>The UntitledIBusHandwriting engine</description>
            <rank>0</rank>
        </engine>
    </engines>

</component>

This file defines metadata about your IM, such as the language. This particular example is from the UntitledIBusHandwriting repository.

The XML file should be installed to $XDG_DATA_DIRS/ibus/component, which in most cases resolves to /usr/share/ibus/component.

IBus IMs are stored under /usr/libexec so make sure to make a link with of your application to this directory when developing or simply install the binary as a build step.

Compiling with IBus

To set up your project, add the following lines between the Add subdirectories and Add compile options sections of your CMakeLists.txt file:

# Custom section
include(${CMAKE_ROOT}/Modules/FindPkgConfig.cmake)
pkg_check_modules(IBus REQUIRED ibus-1.0)

if(IBus_FOUND)
    include_directories(${IBus_INCLUDE_DIRS})
    link_directories(${IBus_LIBRARY_DIRS})
endif(IBus_FOUND)

They include the pkg-config find function, try to find IBus and if it's found, set the include and link directories to contain the files required by IBus

Next, add ${IBus_LIBRARIES} to the target_link_libraries of your project, like this:

target_link_libraries(UntitledImGuiFramework glfw GLEW OpenGL util pthread yaml-cpp dl freetype vulkan
        ${IBus_LIBRARIES})

Adding code to deal with IBus

Now that you have all things set up to build. You can add the IBus code. There are 2 components:

  1. The IBus callbacks
  2. The IBus initialization code

Initialization

To initialize IBus like the above example, simply use code similar to this:

void ibusMain(char* executable, bool bIBusAvailable)
{
   IBusComponent* component;
   IBusEngineDesc* desc;
   static IBusFactory* factory = nullptr;

   // TODO: Make this non-hardcoded and use the relevant variables
   static constexpr char icondir[4096]= "/usr/share/icons/untitled-ibus-handwriting.png";

   ibus_init();
   bus = ibus_bus_new();

   g_signal_connect(bus, "disconnected", G_CALLBACK(shutdown), nullptr);

   factory = ibus_factory_new(ibus_bus_get_connection(bus));
   ibus_bus_request_name(bus, "org.freedesktop.IBus.UntitledIBusHandwriting", 0);

   if (!bIbusAvailable)
   {
       char* binaryPath;
       binaryPath = realpath(executable, nullptr);

       component = ibus_component_new("org.freedesktop.IBus.UntitledIBusHandwriting",
                                      "The UntitledIBusHandwriting engine", "1.0.0.0", "MIT", "contact@madladsquad.com", "https://madladsquad.com/docs/UntitledIBusHandwriting/",
                                      binaryPath, "UntitledIBusHandwriting");

       desc = ibus_engine_desc_new("UntitledIBusHandwriting", "handwrite",
                                   "The UntitledIBusHandwriting engine", "bg", "MIT",
                                   "contact@madladsquad.com", icondir, "default");

       ibus_component_add_engine(component, desc);
       free(binaryPath);
   }
   else
   {
       component = ibus_component_new("org.freedesktop.IBus.UntitledIBusHandwriting",
                                      "The UntitledIBusHandwriting engine", "1.0.0.0", "MIT", "contact@madladsquad.com", "https://madladsquad.com/docs/UntitledIBusHandwriting/",
                                      "/usr/share/", "UntitledIBusHandwriting");
   }
   ibus_bus_register_component(bus, component);
   ibus_factory_add_engine(factory, "UntitledIBusHandwriting", IBUS_TYPE_CUSTOM_ENGINE);
   g_object_unref(component);
   ibus_main();
}

The ibus_main function is blocking, so make sure to upload this entire function on a separate thread. To exit from the infinite loop created from ibus_main call ibus_quit(), and after that you can join the thread.

Notice how most of the calls here require parts of the metadata set in the XML file. You can use an additional XML parser to parse the data so that you don't repeat yourself.

The executable argument of the function should be the executable path as set in the main function in your program as argv[0]. The bIbusAvailable boolean is generally set when the application is launched with the --ibus argument.

Setting up the IBus callbacks

Check out Engine.hpp and Engine.cpp of the UntitledIBusHandwriting project.

Setting up the window

Finally, you have to add additional settings to make the window be compatible with IBus conventions. The properties of the window should be the following:

  1. The window should be always on top
  2. The window should not be able to be minimised, maximised, iconified, etc.
  3. The window should not take focus away from the other window, even when events are active
  4. It's preferable that it's not shown on the pager or task bar

On X11, we can use Window::Platform::setWindowType to set the type of the window. In our case, X11_WINDOW_TYPE_SPLASH fits exactly our description.

We can also call Window::Platform::setWindowAlwaysOnTop to set it as always on top.