See you at NimConf 2026!

My talk on the tiny "initiative" I recently started, called "Nim for Desktop Linux" has been accepted for NimConf 2026. I last gave a talk in NimConf 2024, so I'm really excited to just show everyone what I've been working on thus far.

a basic gist

Nim has always been a bit behind in adopting Linux tech. It makes sense, because Linux's userland moves at a cheetah's pace, as compared to something like Win32, which moves slowly and doesn't deprecate stuff as eagerly.

Beforehand, the only ways to work with Wayland clients on Nim have been:

  • Use the xcb bindings - your client will run through XWayland, if supported by the compositor. It obviously will not be able to access a lot of functionality a native Wayland client could, and will also have weird, unfixable issues like not being able to nicely capture the screen.
  • Use a GUI toolkit like GTK4 or Qt. This is not really a fix, as your windowing code is just handled by the underlying C/C++ layer.
  • Use a toolkit like SDL3. Again, the same issue as above arises.

You could bind libwayland-client and work with it, but it's incredibly low-level and full of C-isms.

nayland comes to the rescue

Nayland is the first and foremost project under the initiative. Its primary and exclusive goal is to provide high-level, ORC-integrrated, (mostly) memory safe wrappers around lower-level C APIs, including the Wayland client core, the core protocol, and a bunch of other protocols.

To make this process as easy as possible, Nayland comes with two tools, bindgen and wrapgen.

bindgen's job is to take in a protocol XML file, call wayland-scanner on it to generate the equivalent C code, then use c2nim on that code to generate Nim code, fix it up to make it compile, then place it at src/nayland/bindings/protocols along with the protocol's "private code" (an extra C file compiled and linked into the final program to provide certain symbols).

wrapgen's job is to take in a protocol XML file too, but it instead parses the XML itself, then generates Nim code for high-level, ORC-integrated wrappers for those types. Unlike bindgen, this program is not 100% automated. A human still needs to intervene in the generated code to resolve all imports that a module needs.

Thanks to these two tools, Nayland now supports the following Wayland protocols: (* = partially incomplete)

  • Core (*)
  • XDG Shell (*)
  • Fractional Scale
  • XDG Decoration
  • System Bell
  • Idle Inhibit
  • Layer Shell
  • Cursor Shape
  • Tablet
  • Presentation Time
  • Content Type
  • Tearing Control
  • Linux DMA-BUF
  • KWin Blur

This is nice and all, but even these high-level wrappers only get you so far. That's where Surfer comes in.

surfer, a minimal and opinionated windowing library

Surfer is a high-level windowing library with first-class Wayland support. Its goal is to simply be a windowing library, and nothing else. It does not aim to do anything beyond that.

Another goal of Surfer is to not own the event loop. It can block in some cases when polling for events, yes, but the main loop is fully owned by the user of the library. Surfer simply provides a queue of events that the programmer can utilize.

For the software backend, it manages the shared memory buffer between your client and the compositor, resizing and reallocating it when required. For the OpenGL ES backend, it handles the initialization and hands you over a fully initialized context for usage. Out-of-the-box Vulkan support is planned to be implemented soon.

Surfer can not only be used to make regular desktop windows, but also desktop widgets. This is possible when the wlr-layer-shell protocol is supported by the host compositor. It also uses other protocols like fractional scale that it gets from Nayland, hiding all the complexities behind function calls and events in the queue.

Here is a basic window that uses EGL in Surfer.

## basic EGL drawing example with surfer
import std/[math]
#!fmt: off
import pkg/surfer/app,
       pkg/surfer/backend/wayland/bindings/[egl, gles2],
       pkg/[shakar, vmath]
#!fmt: on

proc main() {.inline.} =
  let app = newApp("OpenGL ES + EGL example", appId = "xyz.xtrayambak.surfer")
  app.initialize()
  app.createWindow(ivec2(640, 480), Renderer.GLES)

  var
    i = 0
    j = 1337
    k = 6767

  while not app.closureRequested:
    let eventOpt = app.flushQueue()
    if !eventOpt:
      continue

    let event = &eventOpt
    case event.kind
    of EventKind.RedrawRequested:
      glViewport(0, 0, app.windowSize.x, app.windowSize.y)
      glClearColor(
        math.sin(i.float32 / 256),
        math.sin(j.float32 / 256),
        math.sin(k.float32 / 256),
        255,
      )
      glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT)

      app.queueRedraw()

      inc i
      inc j
      inc k
    else:
      discard

when isMainModule:
  main()

ybus, a D-Bus client written in Nim

Now, for a change, let's talk about D-Bus. Thus far, we've only had the bindings for the C libdbus library for a long time. It works well out of the box, but it's not exactly native.

ybus is an implementation of the D-Bus wire protocol in pure Nim. On top of that, it also provides a synchronous UNIX socket client. It also handles the authentication phase for the D-Bus broker itself, as well as bus path resolution.

It ships with a tool, also called bindgen. This tool can:

  • connect to a running service and introspect it to get its XML file
  • or, read a pre-existing XML file given to it

Either ways, once it gets the service's XML definition, it can generate bindings for all functions specified there, allowing you to call them without writing a single line of a raw API call. The bindings will also be type-safe.

It's still a heavy work-in-progress, but I use it in some places already, like my terminal emulator, nitty.

some extras

If I get the time to, I'll also be trying to fit these into my talk:

  • overdrive: a high-level agnostic SIMD library that uses templates to boil down generic instructions into architecture-specific vector operations. Supports AVX2, SSE2/3/4.1 and NEON. It aims to maintain parity between all of these backends.
  • nim-url: a fast, safe WHATWG URL parser written in pure Nim. It has SIMD acceleration thanks to Overdrive, and is extensively fuzzed using LLVM's fuzzer every day.
  • elf_loader: a generic ELF loader for x86-64 Linux in pure Nim. It can not only load glibc-compiled shared objects on Linux, but also bionic-compiled (Android) shared objects on desktop Linux, by implementing the necessary relocation format (APS2 compression).
  • nitty: a GPU-accelerated terminal emulator written in Nim. Yes, it uses the Surfer library!

closure

That's basically what I'll be talking about in NimConf 2026. I'm incredibly excited for this, and I hope everyone else will be, too. I love Nim and it's great to see it grow as a programming language in a linear, albeit non-explosive way.

I'm also incredibly pumped about Andreas' talk on Nimony and Nim 3, can't wait to see the next evolution of my favorite language. :^)