In the past months I’ve started working on a side project: a custom Telegram desktop client that I’ve called OmbraChat. The idea was to add some extra privacy features and a layer of encryption using PGP.
In this article, I won’t go into detail about the reasons that led me to write this app, but I would like to talk about what I learned during the development process. I am primarily a web developer, and this was my first time trying to write a complex desktop app. I never imagined I would encounter so many problems and now I understand why Electron is so widely used.
My primary requirement was to be able to use the app both on my laptops (Ubuntu 24 and Debian 12) and on my Linux phone (running FuriOS, based on Debian 13). It shouldn’t be that difficult, right?
In the beginning, the most obvious choice seemed to use the GTK library, specifically GTK4, due to its compatibility with Phosh, the phone’s interface. Phosh relies heavily on GTK4 and libadwaita, which not only provides good mobile support but also looks pretty.
Telegram integration had to be done using TDLib, which is written in C++ and can be easily called from C using the TDLib JSON interface. This also allows to create bindings for other languages.
I needed a language that has good GTK bindings and works well with C. Probably the most obvious choice would have been to use Rust, but I was a bit scared. Indeed, in the past I had already looked at the code of another Telegram client written in Rust and GTK4. I had tried to fix a bug in it and it seemed quite complex and difficult to debug.
So, I ended up in exploring Vala, a less-known language, which looks similar to Java or C# and compiles in C. Having worked a lot with Java, it looked easier than Rust to me.
Vala has some quite interesting concepts, but it’s not exactly the best language to work with.
First of all, despite being around from 2006, the compiler looks a bit immature. Indeed, it might happen that the Vala code compiles to C, but the generated C code contains syntax errors and doesn’t compile. The simplest example I’ve found for this consists in adding a method get_type()
to a Vala class:
public class MyClass {
public string get_type() {
return "MyClass";
}
}
public static int main(string[] args) {
var my_instance = new MyClass();
print("The type of my_instance is: %s\n", my_instance.get_type());
return 0;
}
This looks fine to the Valac compiler, that generates the C code. However, all classes inherits from GObject
, that already defines a get_type()
method. As a result, Valac ends up adding the method twice, leading to confusing errors in the subsequent CC steps.
And there are many other tricky issues, like the need to add your interfaces in a specific order, marked as a Vala bug in 2011 and never fixed, the need to think about memory management, which is not trivial, and the need to rely on the repo vala-extra-vapis for using many libraries, repo which is often not well updated.
But Vala wasn’t the main problem: it was GTK4 and Libadwaita.
I started developing the app on Ubuntu 24, which runs on a good laptop that I use mainly for work. I reached a good point, where I was able to send and receive basic messages:
Only later I tried to compile the app on my other laptop, which is for my personal use and runs Debian 12. I really should have done this test much earlier! The app wouldn’t compile because I had used a version of GTK that was too new. I needed a code that could work from GTK 4.8.x to GTK 4.18.x and from Libadwaita 1.2.x and Libadwaita 1.4.x.
I tried to adapt the code, removing the newer methods, hoping to be able to write something that can be compiled both on older and new versions, but it was simply not possible. Many base methods of the GTK Widget
class were deprecated in version 4.10.x and I had based the whole UI on the NavigationView class, that was introduced in Libadwaita 1.4.x. I basically had to rewrite everything. This raised a question: will maintaining this stuff be a nightmare? If GTK changes so quickly, will I be able to keep up with these changes?
The most obvious option was to use Flatpak, which I wanted to avoid. I dediced to try anyway, but it brought many other issues. For example, if the Vala libraries inside Flatpak are different from the libraries outside, code completion and syntax highlighting don’t work correctly, and you have to find a way to configure the language server to work with Flatpak. Moreover, I wanted to support native GPG integration (with the GPGME package) and it looks that there are several issues regarding the GPG pinentry not working from Flatpak. Weeks later, I would discarded the idea of using native GPG and opted to use PGP keys managed directly by the application. However, at that moment, it was one of the various issues that pushed me away from using Flatpak.
AppImage could’t be used too, because the idea behind the AppImage is to compile the app for older versions, to run them also in the newer. But for my app it was the opposite: it compiled only in the newer distributions.
For a while I investigated the possibility to use Vala with a graphic library different than GTK. I explored a bit raylib, a spartan graphics library made mostly for game programming, which has a Vala binding. The project would have taken a very different direction: it would have been much more nerdy, without the fancy GTK graphics, but I was fine with that. The developer of raylib provides also a set of utilities called raygui, that can be used on the top of raylib to develop basic apps. I had some fun and I learned the concept of immediate vs retained mode in computer graphics, which I didn’t know. However, my first test was basically unusable on the phone, so I dismissed the idea. Maybe I’ll use that for some other minimalistic projects.
Another idea was to switch to Qt. There is no Vala binding for Qt, so I had to switch language too. Qt preferred language is C++, but I didn’t want to learn it. There is also official support for Python, but I wanted something compiled. There are many unofficial bindings for other languages, but the ones that I looked into seemed incompleted or not very updated.
So, I made my second big mistake: why not trying JavaFX? That, on paper, had some advantages:
- I had an extensive Java knowledge;
- TDLib provides a build script to generate the code needed to call TDLib from Java, through JNI;
- In theory, the same app can run in desktop, mobile and embedded systems;
To avoid being surprised by incompatibility issues, I created an “Hello World” app and I tried to run it on all my devices. It worked as expected. After all, is Java… “Write once, run anywhere”, right? It turned out I would have done some more tests before rewriting everything…
The app was running fine on my laptops:
During development, I had discovered some annoying things about JavaFX:
- There is no trivial way to make a selectable text label;
- By default it uses modules, that cause a lot of headaches considering that a lot of libraries still don’t use them; In particular I used TwelveMonkeys ImageIO to load webp images and I had to patch the jars in order to use
jlink
(the command that produces a self-contained executable bundle); - There is no support for the primary selection, which is the possibility of x11 to do cut and paste using the middle click of the mouse;
These things were annoying, but none of them was a real blocker. Then I tested it on the phone…
Only certain touch events seemed to work, it was impossible to scroll without using the scrollbar and very difficult to change a selection on a dropdown. Moreover, the virtual keyboard didn’t pop up when I pressed inside text inputs. It was basically unusable.
I investigated the causes of the issues and it resulted that the app was running on Wayland, but using x11 compatibility mode. Then, I’ve found several JavaFX issues related to Wayland and touch events. One of them, specific for the scrolling, was marked as fixed, but it didn’t seem to be fixed to me. Then, I discovered that it is the JDK itself to have poor Wayland support. I discovered the existence of the OpenJDK Project Wakefield, which has the goal of bringing “Wayland desktop support for JDK on Linux”. Summarizing: Java desktop apps on Wayland suck. And, yes, this happened in 2025.
So, guess what I did? I trashed everything and started rewriting it all for the third time.
I started thinking that probably Electron is so popular because, being based on a browser, it doesn’t have all these compatibility issues. Still, I didn’t want to use Electron, mainly because I didn’t like the idea to call TDLib from Node.js.
I looked for Electron alternatives and I discovered Tauri, which is similar to Electron, but has a Rust backend and leverages the system’s webview, resulting in significantly reduced application size and improved performance.
I gave it a try, and I liked it. This time my “Hello world” contained also something to scroll and some dropdowns. Everything worked fine, so I started working on it. For the frontend I used Vue.js.
The third edition of the app looks good:
For now I’ve encountered some minor issues, but nothing critical:
- AppImage doesn’t work with Wayland, but it is not a problem, since I’m able to install the .deb;
- My friend using Slackware is unable to set the light theme: it always stays on dark mode;
- I had to put an ugly margin on the right because the scrollbar overlaps on buttons positioned on the edges, reducing their clickable area; this is probably a Webkit bug;
Overall, I am quite satisfied with how Tauri is performing. And, finally, I have something that really runs on all my devices.
Did I waste a lot of time? Absolutely, but I also gained a lot of knowledge in the process.