Introducing GUI Vista

Nick Apperley
6 min readJan 17, 2020

On the open source side there is a saying, ”scratch your own itch”. I prefer the alternative saying, ”fulfil your own need”. My need to fulfil is to have a easy/quick way to develop native GUI applications on Linux (also includes the IoT area) that provides reasonably good performance, is easy to deploy, and has low maintenance. With Linux GTK is the defacto GUI toolkit which is used by all major desktop Linux distributions (Ubuntu, Linux Mint, and Fedora).

Many programming languages can be used to develop GTK applications, however there isn’t a single programming language that would help fulfil my need. All the major languages used in GTK application development have issues:

Python

  • Slow Performance
  • Difficult to deploy applications
  • Not suitable for developing applications at a large scale
  • Cannot develop native applications

C

  • Difficult to develop applications (low productivity)
  • High learning curve
  • Language evolves at a slow pace
  • High level of maintenance (can ”easily shoot your own foot”)

Vala

  • Poor tooling/documentation
  • Niche language that is restricted to the Gnome platform
  • Immature language that isn’t stable
  • Language evolves at a glacial pace (very few resources allocated to language development)

In the end the decision was made to go with Kotlin even though there isn’t a Kotlin library for developing GTK applications, which is where GUI Vista comes in. Originally GUI Vista was developed as a single library (GUI Vista Core), however it was realised that some other Gnome libraries would need to be covered since GTK has many dependencies. Hence GUI Vista is now a toolkit (collection of libraries) for developing GTK applications, and covers some other Gnome libraries.

Basic GUI Program

Do the following with each GUI Vista library (1. Core, 2. IO, 3. GUI):

  1. Clone the library
  2. Change working directory to where the library was cloned to
  3. Run the publishLinuxX64PublicationToMavenLocal Gradle task

After doing the above create a new Kotlin Native project that targets linuxx64. Insert mavenLocal() into the repositories block of the build file. Add the library as a dependency in the build file:

implementation("org.guivista:guivista-gui-linuxx64:0.1-SNAPSHOT")

Once that is done you can then start to use the GUI library.

GUI Library Usage

A basic GTK program using this library requires using a GuiApplication object that has the Application ID, connecting the activate signal, and creating the AppWindow UI in the slot (event handler) for the activate signal.

  1. Create the main.kt file with a main function
  2. Define the top level appWin variable:
private lateinit var appWin: AppWindow

3. Define the top level activateApplication function (handles creating the AppWindow UI):

private fun activateApplication(app: CPointer<GApplication>, userData: gpointer) {}

4. Print the Application ID, and create the AppWindow UI in the activateApplication function:

private fun activateApplication(app: CPointer<GApplication>, userData: gpointer) {println("Application ID: ${Application(appPtr = app).appId}")appWin.createUi {title = "GUI App"visible = true}}

5. Create a instance of Application, pass through the org.example.basicgui Application ID, and call the use function with a lambda:

GuiApplication(id = "org.example.basicgui").use {}

6. In the lambda initialise appWin, connect the activate signal, and run the application:

GuiApplication("org.example.basicgui").use {appWin = AppWin(this)connectActivateSignal(staticCFunction(::activateApplication), fetchEmptyDataPointer())println("Application Status: ${run()}")}

7. Insert the following imports:

  • gtk3.GApplication
  • gtk3.gpointer
  • kotlinx.cinterop.CPointer
  • kotlinx.cinterop.StableRef
  • kotlinx.cinterop.staticCFunction
  • org.guiVista.gui.Application
  • org.guiVista.gui.window.AppWindow
  • org.guiVista.gui.fetchEmptyDataPointer

After completing the steps above the main.kt file should look like the following:

import gtk3.GApplicationimport gtk3.gpointerimport kotlinx.cinterop.CPointerimport kotlinx.cinterop.StableRefimport kotlinx.cinterop.staticCFunctionimport org.guiVista.gui.GuiApplicationimport org.guiVista.gui.window.AppWindowimport org.guiVista.gui.fetchEmptyDataPointerprivate lateinit var appWin: AppWindowfun main() {GuiApplication(id = "org.example.basicgui").use {appWin = AppWindow(this)connectActivateSignal(staticCFunction(::activateApplication), fetchEmptyDataPointer())println("Application Status: ${run()}")}}@Suppress("UNUSED_PARAMETER")private fun activateApplication(app: CPointer<GApplication>, userData: gpointer) {appWin.createUi {title = "Basic GUI"visible = true}}

API Design

As a toolkit GUI Vista is split into three libraries:

  • Core: Contains core functionality (based on GLib)
  • GUI: Covers developing a GTK application (based on GTK)
  • IO: Has common Input/Output functionality (based on GIO)

By heading down the toolkit route it helps to make the APIs modular, and reduces the likelihood of scope creep getting in. In the Core library ObjectBase (a interface) represents GObject which is the core primitive used by all GObject based libraries (eg GTK). Considering that performance is important many defined properties don’t use a backing field since that would likely double memory usage on a significant scale. A minor downside is that a default value cannot be assigned. Below is a example of a property without a backing field:

interface EntryBase : WidgetBase {// ...var text: Stringset(value) = gtk_entry_set_text(gtkEntryPtr, value)get() = gtk_entry_get_text(gtkEntryPtr)?.toKString() ?: ""// ...}

By far the biggest tradeoff made was to manually bind some of the Gnome libraries instead of using GIR to generate the bindings. As a result more work is required to develop/test the bindings, and API coverage is incomplete. With the upsides it means a custom design can be easily incorporated, Kotlin APIs are more Kotlinic (aka idiomatic Kotlin), and additional type safety can be applied.

In the event that the Gnome C libraries need to be used as a escape hatch (eg some functionality isn’t covered) many of the GUI Vista APIs provide C pointers, eg:

interface EntryBase : WidgetBase {val gtkEntryPtr: CPointer<GtkEntry>?get() = gtkWidgetPtr?.reinterpret()// ...}

Each defined C pointer uses the following naming format, namespaceTypePtr (eg gtkEntryPtr). Many of the C pointers provide a read only reference to prevent mutability issues (especially in concurrency situations). Some of the Kotlin classes can be recreated using a C pointer (eg GuiApplication), which can be handy in callbacks without having to deal with the stable reference boilerplate, eg:

private fun activateApplication(app: CPointer<GApplication>, userData: gpointer) {println("Application ID: ${GuiApplication(appPtr = app).appId}")// ...}

Many foundational parts of the GUI library use interfaces to minimise tight coupling, and separate instantiation from design (eg WidgetBase, and Widget) when dealing with the GTK APIs. There are some odd examples in the GTK APIs where something should be a interface but is a class instead, eg GtkWidget.

Challenges

The two biggest annoyances are non existent support for navigating documentation/source in IntelliJ, and reference documentation isn’t automatically generated when publishing the libraries. With the former it has meant doing a lot of context switching, which has reached the point where it has slowed down development. As for the latter one has to remember to generate the documentation, otherwise there will be no reference documentation to rely on in a readable format. Kotlin’s slow compiler doesn’t help development, and really slows down incremental/experimental development to a crawl.

Event handling with C libraries via Kotlin Native can be “a major pain in the void” at times. There are numerous pitfalls that can befall you if your’e not careful. One major pitfall is that Kotlin class instances can’t be captured in the event handler (either a function or lambda). In order to get around that pitfall some stable reference boilerplate will need to be implemented. Callbacks have to be created manually which can be tedious. IntelliJ doesn’t provide an option to generate callbacks. Currently there is an existing IntelliJ issue that covers this option. Please vote for the issue.

Here is a example of the boilerplate:

// ...interface ApplicationBase {// ...fun connectActivateSignal(slot: CPointer<CFunction<(app: CPointer<GApplication>, userData: gpointer) -> Unit>>,userData: gpointer): ULong =connectGtkSignal(obj = gAppPtr, signal = "activate", slot = slot, data = userData)}class Application(val id: String) : ApplicationBase {// ...}fun main() {val app = Application("org.example.sampleapp")app.connectActivateSignal(staticCFunction(::activateApplication))}private fun activateApplication(app: CPointer<GApplication>, userData: gpointer) {TODO("Implement this C callback")}

Many people will not realise that the GTK/Gnome libraries suffer from bad documentation. At times this can create difficulties when learning how to use the APIs, and may even involve some guess work. Even worse is that with most APIs it isn’t known when something has been introduced (eg a function), aka which version. This can bring about some nasty surprises when running a program that relies on some of these libraries (can be a disaster waiting to happen).

Conclusion

I enjoyed the challenge of developing a toolkit that fulfils a need. On the Kotlin side I am putting forth the idea of having the Kotlin compiler as a prototyping environment, which would really speed up development in many situations where compilation needs to be lightning fast. Some programming languages have very fast compilers (Turbo Pascal, Go etc). Why can’t Kotlin be the same?

More work/support needs to be put into ensuring that tooling is at a good level for Kotliners that develop/use Kotlin Native libraries.

--

--