How I created my own GoGui for the SalixEDA installer

🔧 For developers
This article is a technical journey into the world of building GUIs in Go. If you are just a user of SalixEDA and want to learn about the program's new features — this article may seem too deep for you. But if you are a developer or just curious about "how it's made" — welcome!

How I created my own GoGui for the SalixEDA installer

During the rebranding process, the SalixEDA program received not only a new name and a new website. I decided to completely replace the installation system.

Spoiler: in the end, I wrote my own GUI framework in Go from scratch, using C bindings and a font embedded in the binary. But let's go through it step by step.

Why the old installation system no longer suited me

The previous system was built on the excellent QtInstallerFramework. It has many advantages:

  • Cross-platform (Windows and Linux "out of the box")
  • Multilingual (the installer's language depends on the user's settings)
  • Supports offline and online installation
  • Automatic uninstaller

The system is beyond praise! But there is one "but".

The QtInstallerFramework has no built-in update checking. It is assumed that the installed program itself should handle this, which will call the updater when necessary.

I wanted the installer to handle the update checking itself. Here's the scheme that emerged:

  1. The main program never runs on its own.
  2. The installer-updater always runs.
  3. It periodically checks the server for updates.
  4. If an update is available — it offers the user to install it.
  5. After the update — it launches the current version of the program.
  6. If the user declines — it launches the current version.

In addition to checking for updates, this approach has another advantage: for Linux, it is necessary to set the environment variables required for the main program to work. This task is also convenient to delegate to the installer-updater-launcher.

And you can also add an uninstall function to it — so you don't have to create a separate program with a GUI. The installer is already perfectly aware of all installed files.

Why Go?

Around the same time, I had another approach to the Go language. And I really liked it as a solution for service tasks. A global component library and private cloud libraries had already been built on it. And besides, it has a wonderful ability to build applications for different OSes.

I thought: "What if I make this installer-updater-launcher in Go?" And I promptly set about implementing it.

First attempt: Fyne

For the GUI, I preliminarily chose Fyne — it's essentially the only cross-platform GUI library for Go (although with caveats: for Linux you need to install OpenGL support libraries, which are most likely already installed on the user's system).

Using vibe coding, I quickly wrote the core and the interface, tested it on Linux. Everything worked. Things were moving towards completion, and I decided to check how it all looked on Windows. And then it started... First, a song and dance to get it to compile at all. I managed to overcome that, but another problem came: the application crashed on startup. And here I got stuck for seven long days. Nothing helped, and the worst part was that diagnostics didn't help — the application crashed before any diagnostics could be performed.

Debugging and an unexpected turn

Then I decided to try the simplest option — create an empty Windows window using only system tools (WinAPI). Here too, failure awaited — the application also crashed on startup. However, the possibility of diagnostics appeared.

I quickly found the cause of the crash — and the window started working.

I have some experience with QML, and I decided to try building the interface "manually" in this simplest window. Especially since for an installer the interface is not very complex.

Unexpectedly, the whole system turned out to be:

  • Compact
  • Without external dependencies
  • Simple and understandable

The aesthetics suffered, of course, but for this application, aesthetics are secondary. After all, this is not the main application, but an auxiliary one. Besides, if I spent more time, I could achieve aesthetics as well.

The saddest part was that I had to abandon the dialog for selecting the installation directory. The reason is the lack of simple access to the native dialog. And while this is solvable on Windows, on Linux it's bad. Of course, without problems, I could implement this dialog manually, but I haven't done that yet, considering that very few people choose an alternative installation directory.

Linux: first experience with X11

While everything was clear to me with Windows (I have experience programming with WinAPI), with Linux everything was for the first time. I had never programmed a Linux window (X11) using only system calls.

Here again, vibe coding came to the rescue. It turned out that it's not so scary.

Architecture of the resulting GoGui

As a result, a GUI "library" for Go was born:

  • System-dependent parts — two small C files, which are linked into the Go program build depending on the target system.
  • Low-level graphics — also in C, but already common for Windows and Linux.
  • Object hierarchy (like in QML) — implemented in Go, and for direct drawing, C functions are called.

The hardest part: text and fonts

The "stinkiest" part in terms of drawing turned out to be text. Unicode support was required for a multilingual interface.

It quickly became apparent that the font systems in Windows and Linux are not very similar. To avoid requiring the installation of third-party programs or libraries, I decided to embed the font directly into the installer.

I took a TTF file of a ready-made font and inserted it directly into the GUI code. It remained necessary to parse this file and draw characters.

Here stb_truetype.h by Sean Barrett came to the rescue, for which a huge human THANK YOU! Without this library, the project could not have been completed.

Polishing and the final result

After that, some polishing was done:

  • Differences between operating systems were leveled out
  • Drawing artifacts were eliminated
  • An uninstall function was added (so as not to create a separate GUI program)

Currently, this GoGui is used for the installer-updater-launcher-uninstaller program. All in one package!

Thoughts about the future

I even thought about releasing this GUI library as a simple solution for Go programs. For now, I decided not to bother, although the ability to build applications on Linux for both Linux and Windows (and potentially for Android) is impressive.

Maybe someday I will return to this idea. For now, the library lives inside the SalixEDA installer and does its job perfectly.

Conclusion: what I took away from this experience

  • Don't be afraid to write "your own wheels". Sometimes it's faster than figuring out someone else's bugs.
  • Vibe coding works. Especially when you have a clear understanding of what needs to be done.
  • stb_truetype.h is magic. Huge thanks to Sean Barrett.
  • Go + C is a great combination for system programming with high-level logic.

The source code is open

The installer program (and all of GoGui along with it) is located in the main SalixEDA repository on GitHub:

https://github.com/SalixEDA/SalixEDA.git

The installer source code can be found in the installer/ subdirectory. I invite you to study, fork, and improve!