DEV Community

MrViK
MrViK

Posted on

Writing a new screen autolocker, with Go

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. 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 😄. 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

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.
  • 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 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
Now looks like this:
improved implementation
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 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:

Top comments (0)