Writing a new screen autolocker, with Go
Tags: go, c, linux, x11
Published: 2019-08-05 21:11:26

---
title: Writing a new screen autolocker, with Go
published: true
description: An article explaining the problem, the mid planning and devel experience of writing a X11 autolocker with Go and bindings from C libs. From the initial thoughts to the packaging for 2 Linux distros.
tags: golang, c, linux, X11
---
## Initial thoughts
Well, I've been using a tiling window manager for some weeks, but still there's something I'm not comfortable with; *the autolocker*. I'm using *xautolock(1)*, but it haven't implemented signal handling (USR1 to lock screen now, etc), so I'm thinking about creating my own.

## Initial planning
So I'll write my own replacement for *xautolock(1)*. What should the new software do:
* Watch X11 idle time (should have an interface)
* Listen USR{1,2} signals
* Send desktop notifications
* Parse arguments from command line
* Low start times
* Have as few dependencies as possible

I started looking on the *xautolock(1)* source code to see what did it do to check the idle time, so I found [this line](https://github.com/l0b0/xautolock/blob/master/src/engine.c#L47). I know it's not the official source, but this is enough. Now it's time to look on the `man(1)` pages for `XScreenSaver(3)`. Ok, there's a library with the things I need, so I should use a language that can call C functions with a reasonable overhead.

Here comes the inevitable **choose the right language**. My possible choices were:
* `Javascript` -> My first programming language. I thought it first, but as the last resort.
* `C` -> The best language to call C functions from :smile:. But I'm not so proficient on that lang. (Now I'm more confident writing things with it). Also, what about threads, I should learn how to use them, and it looks so complicated. Maybe another time.
* `Java` -> I've written tons of (shitty) programs with it, but I don't and won't use them. Java was discarded from the beginning.
* `Rust` -> A beautiful project to get started with Rust, but maybe it would not be a production-grade software.
* `Golang` -> I've learning it but I haven't used it on a formal project. On the tests, some concepts caught my attention like simple concurrency with goroutines and channels. Also has a [simple calling between C and Go](https://golang.org/cmd/cgo/)

So `golang` is the chosen one. Now it's time to design it.

Let's take the above steps from initial planning.
* X11 libraries docs are available under their man page, and functions are callable with cgo.
* Signal catching is easy with `os/signal` and `syscall`.
* Desktop notifications can be achieved with [go-notify](github.com/mqu/go-notify).
* Parsing arguments from the command line is possible, but the standard library package is too basic. I ended up with `github.com/akamensky/argparse`.
* Golang has a runtime statically linked to the final binary, but the startup times are very low and doesn't have a hard impact on the performance.
* Well, the dependencies. LibX11 for `XOpenDisplay(3)` and `xscrnsaver` extension to get idle time info. Also depends on libnotify. Some distributions split the libraries and headers. Headers are needed then compiling.

Now, let's start. `take ...` hmm, how do I name this thing? Sorry for the low effort name, but it's called `goautolock(1)`. Ok, `take goautolock&&git init`. (`take` is a function defined on [oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh) that runs `mkdir` with all parameters and does cd on the last one).

## Writing it
With the initial version I faced my first race conditions. Those weren't causing unexpected behavior, but go expects memory accesses to be synced, so I improved some features to convert some flags to channels. This change improved the performance (e.g. waiting 2 seconds at max to lock screen after SIGUSR1 vs locking immediately after getting a message on a channel).
That didn't fix all of the race conditions caused by the initial design, so a recent version implemented the mutexes provided by the go standard library.
It suffered many changes from the first time written. Take a look at the functions calling C libraries.
First time written:
![first implementation of C functions](https://thepracticaldev.s3.amazonaws.com/i/7rcdfsuhyfijuw9abavc.png)
Now looks like this:
![improved implementation](https://thepracticaldev.s3.amazonaws.com/i/9jmkzancbrvn284isu8f.png)
Note the calls to `C.free` to fix memory leaks.
When I got into signal handling, I thought: "There are 2 user defined signals without any special meaning, one of them will call the locker immediately, but ¿what about the other one?" So, I decided to stole a concept from LineageOS (formerly Cyanogen MOD) where you could add caffeine to prevent looking the screen for a time. The implementation on `goautolock(1)` is simple:
* Each SIGUSR2 adds X seconds to the lock delay. Take X like the value of `--caffeine` option (defaulting to 10).
* After time is elapsed *and* screen is locked, this extra delay is set to 0.

## Writing experience with golang
The first impression I had is that golang has a very limited set of options (coming from JavaScript that allows you to do whatever you want). And the error handling (split on panic and error returns) was chaotic at the first glance, but when used to it, the experience is the most consistent I had.
Also, it has some syntactic sugar like prepending `defer` to a expression to make it run after the actual function has been finished. This allows you to free C variables not managed by the GC just after the allocation, so you don't have to find the original declaration when you see a call to `C.free`.

## Documentation
`godoc` is a very powerful utility for generating docs from source code, but this is a user utility, so the docs should be on the standard `man(1)`. This was the most difficult part, using `groff(7)` to create a man page. There are a lot of macros to format the output. Now the result is available at the `/docs` folder on project (links on the bottom).

## Packaging
This is an utility for Linux, each distro has a different packaging system (I won't use flatpak or ~~spam~~ snap for this).
The first thing I did was to build a release binary on the GitLab pipeline.
On the other hand, I want goautolock to be updated on my laptop when I do `pakku -Syu`, so I uploaded it to the AUR. It was so easy using `ldd` and `namcap` and testing with `makepkg` on a clean chroot.

The other distro I use is Fedora. This gave me some headaches.
* Package names aren't the same nor similar to ArchLinux.
* Devel packages are separated from the main ones.
* Which utility should I use to create a RPM following a spec file? rpkg? a Makefile? fedpkg? tito?
* The AUR counterpart (saving the distances) on Fedora is the *COPR*. Let's start a repo!
* Wait, I want a RPM, what the hell is a SRPM?

After figuring out I should make another repo in [Fedora Pagure](https://pagure.io) to upload the spec and source tarball, I started using rpkg to build the srpm. And after a lot of failed builds it's finally uploaded to *Fedora COPR*. Then I started to pack some other projects, so everybody can easily use them.

## Links
And here's the link to the project and some others if you want to contribute or propose something:
* [GitLab](https://gitlab.com/mrvik/goautolock)
* [AUR](https://aur.archlinux.org/packages/goautolock/) for the fellow Arch users
* [Pagure.io](https://pagure.io/goautolock)
* [Fedora COPR](https://copr.fedorainfracloud.org/coprs/mrvik/goautolock)