13th June 2016 - Window Manager , Nim , Programming
Lately I've been toying with the idea of creating a window manager for Linux. It's not that there is a lack of them, rather quite the opposite, the Linux world is full of them. But suffice to say that I have some ideas which I think would be a welcome addition and I've started tinkering with creating my own. If for nothing else it would be an interesting challenge. However to create a window manager you need to have some insight into how the whole system of windows and displays works in Linux. This can be a pretty daunting task, there is a lot of documentation written on the topic and most of it is incredibly dry and technical. While I don't have anything against dry and technical documentation when I'm looking for specific pieces of information it's not exactly something you'll find yourself reading for fun. So in order to get a glimpse into the world of WM creation without reading through tonnes and tonnes of information I decided to create an implementation of TinyWM.
So what is TinyWM?
TinyWM is a C program of no more than about 60 lines that interfaces with Xlib and implements some of the most rudimentary functionality a window manager needs. It allows the user to scale windows, move them around, and bring windows to the front of the window stack. It's not very practical and I doubt many people actually use it for anything productive but it serves as a nice example and starting point for WM creation. In fact quite a lot of projects have had their starting point with TinyWM. Many people have already ported it to various languages but my language of choice for this exercise was Nim.
So what is Nim?
Nim is a programming language which makes a nice compromise between low-level and abstraction. In it's normal form it compiles down to C which allows it to leverage the optimizations and portability the C compilers have achieved over the years. The fact that it compiles down to C also means that interfacing with C is a breeze. Nim can call C libraries, and C can even call Nim libraries. Along with this it gives you a soft real-time garbage collector which can be manually controlled and offers a syntax that makes it a joy to use. It also has some pretty extraordinary meta-programming capabilities allowing the programmer to change and extend it in a way that suits their needs. I will probably make a post sometime in the future about the merits of Nim but for now I can warmly recommend learnxinyminutes.com which has a neat overview of some of the syntax and features.
But enough beating around the bush, for those who simply came here for the implementation here it is. It clocks in at around 60 lines like the original, however this could be decreased further if a more idiomatic approach was taken. Because of the Nim/C compatibility the code looks almost exactly the same as well. Some things were done differently between the versions, some caused by the Xlib wrapper not working in exactly the same way as the original, some simply stylistic. But all in all it's pretty much the exact same code. You might also notice at the top that there are some converter functions, these allow Nim to automatically convert types that wouldn't strictly be allowed to be passed to the Nim functions. Notable is the boolean converter functions which converts the Xlib boolean to Nim boolean since C itself has no concept of a boolean type. The import statements at the top are simply for some thin wrappers around the C interface functions to make the C integration completely seamless.
import xlib, x converter toCint(x: TKeyCode): cint = x.cint converter int32toCint(x: int32): cint = x.cint converter int32toCUint(x: int32): cuint = x.cuint converter toTBool(x: bool): TBool = x.TBool converter toBool(x: TBool): bool = x.bool var display:PDisplay root:TWindow attr:TXWindowAttributes start:TXButtonEvent ev:TXEvent display = XOpenDisplay(nil) if display == nil: quit "Failed to open display" root = DefaultRootWindow(display) discard XGrabKey(display, XKeysymToKeycode(display, XStringToKeysym("F3")), AnyModifier, root, true, GrabModeAsync, GrabModeAsync) discard XGrabButton(display, 1, Mod1Mask, root, true, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None) discard XGrabButton(display, 3, Mod1Mask, root, true, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None) while true: discard XNextEvent(display,ev.addr) case ev.theType: of KeyPress: var kev = cast[PXKeyEvent](ev.addr) if not kev.subwindow.addr.isNil: discard XLowerWindow(display, kev.subwindow) of ButtonPress: var bev = cast[PXButtonEvent](ev.addr) if not bev.subwindow.addr.isNil: discard XGrabPointer(display, bev.subwindow, true, PointerMotionMask or ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime) discard XGetWindowAttributes(display, bev.subwindow, attr.addr); start = bev; of MotionNotify: var mnev = cast[PXMotionEvent](ev.addr) var bev = cast[PXButtonEvent](ev.addr) while XCheckTypedEvent(display,MotionNotify,ev.addr): continue var xdiff = bev.x_root - start.x_root ydiff = bev.y_root - start.y_root discard XMoveResizeWindow(display,mnev.window, attr.x + (if start.button==1: xdiff else: 0), attr.y + (if start.button==1: ydiff else: 0), max(1, attr.width + (if start.button==3: xdiff else: 0)), max(1, attr.height + (if start.button==3: ydiff else: 0))) of ButtonRelease: discard XUngrabPointer(display, CurrentTime) else: # Ignore unknown events continue