Brian Robert Callahan
academic, developer, with an eye towards a brighter techno-social life
All source code for this blog post can be found here.
Our last blog post blew up. It ended up in far more places than I would have anticipated. But one thing stuck out to me: people seem to think I'm very attached to my title. I am not. This site also serves as my professional site and I copied the header over from the main site to my blog. To avoid such things going forward, starting with this blog post I am dropping my title from the blog pages only. Unless you are a student of mine, or we are at some event where address by title is expected, I would much prefer to be called "Brian" than anything else. There are some people who call me "Doc" or "Dr. C" and that's cool too. But if I don't know you, and you are not a student or aspiring to become a student, Brian is fine.
But enough of that! Let's explore something interesting. Inspired by the SerenityOS lead, Andreas Kling, and his latest YouTube video on creating a simple screenshot utility for SerenityOS, I did some digging into the D package repository to see if any screenshot utilities were already available and if not, I would write my own. Armed with my gdc compiler, I figured it would be a good first project. And I could detail my experiences as I created this utility from start to finish. Let's walk through it and see what we learned from the experience.
The first thing I will say is, with some small exceptions, navigating the D package repository is an incredibly positive experience. I think it is based, at least partially, on the npm package repository. One thing that I really like about what the D team have done is have auto-generated documentation based on contextual comments in source code. Provided that the code author is diligent in writing good comments, you automagically get good, consistent, documentation across all D packages. See, for example, my pledged package which implements pledge(2) and unveil(2). The only real oddity I discovered with the D package repository is that the search is subtly broken with special characters. I reported the problem so hopefully it will be fixed soon.
In any event, searching for "screenshot" or any permutation of that yielded no results. Unfortunately, there are not that many standalone applications or utilities available in the D package repository. It is an opportunity for people looking to learn a new language and implement some helpful utilities. So let's help out and write a screenshot utility in D!
Since I am building and using this utility on OpenBSD, and I do not at the moment have any complex use cases, I decided to create an even more minimal clone of scrot. That is the utility I usually use on my machines when I want to take a screenshot. The only feature I use with scrot is the -d
flag to set a delay. I think it would be nice to replicate the automagic date becomes the output file name feature of scrot but also allow the user to provide their own output file name. Something like the following seems reasonable to me:
__progname [-d seconds] [name]
Now all we need is a catchy name for our utility. I have noticed that it seems to be a tradition to put the letter d into D language programs and libraries. I tried using an online thesaurus to give synonyms of screenshot or photo or image or anything along those lines, and I would select a name that began with d. Unfortunately, none existed! So another name had to be devised.
Perhaps it is a product of my age but I remembered when I was growing up how if you wanted to take pictures, you had to buy a film camera and then you would take however many pictures you got with that roll of film and then you would take it to a photo processing place and then come back and pick them up later. Also perhaps because I live in upstate NY, Kodak was a household name in film photography and it has a d in its name! Kodak had a brand of film called Kodachrome, which was also used as the title of a Top 10 hit for Paul Simon. And it sounded catchy enough for me! Our utility's synopsis therefore looks like this:
kodachrome [-d seconds] [name]
When dealing with screenshots, I prefer the PNG image format. And since we will be getting our X screens, it seems to me like we will need both of those libraries to achieve our goal of creating a screenshot utility. I hope there already are D bindings for both libraries otherwise I am about to have a lot more work to do!
First task is to run dub init
and provide all the metadata that my new project needs. Very nicely, dub init
will automagically offer to name your project whatever the directory name you run the command from is, so I made a new directory named kodachrome
, cd
'd into it, and let dub init
discover that for package naming.
For the other questions dub init
asks, I left the format for the dub files the default (json), for its description I said "A screenshot application for X" because the D community discourages mentioning D in the description. That makes sense to me, since it is going to be discoverable in the D package repository, and you probably know you are dealing with D code in that case. Author name is automagically discovered from your username, and so I left that as it was. For some reason I don't quite understand, dub init
defaults to a proprietary license for new projects. All of the D code is licensed under the Boost Software License - Version 1.0 so it seems like it would be nice to default to that. In any case, I chose the OpenBSD-standard ISC license. That is compatible with the Boost Software License, which was about to matter for me.
The copyright string I left as-is, since it was correct. Then it asks to lists dependencies. You can type in dependencies, one at a time, and dub will go find that package in the repository and add it and the latest version of that package to your project's dependency list. Very nice! Now was the time to discover if there are D bindings for the X11 and PNG libraries. Thankfully, yes: one for X11 and one for PNG. We should be good! All I had to do was answer "x11" and then "png-d" to the dependencies question and dub knew how to find the packages and record the latest version number for each. After that, dub happily reported that it had created all the package infrastructure for Kodachrome.
I did have to manually add libraries to link into dub.json
. Perhaps that can be added to the initial list of questions from dub init
but it was rather obvious to detect the issue (the compiler issued linker errors) and fix it (I looked at another package's dub.json
).
Now on to writing some code...
Since I am new to writing D, I don't have any handy boilerplate available to me to drop in. Other people probably do, and I know there is a package for boilerplate code generation but I think I would like to develop my own since D is unlikely for me to be more than a hobby language any time soon and that way I can develop my own voice. I suppose the first thing to do is the boilerplate for getopt since I have that -d
flag and an -h
flag to get help would be nice too. Here there are some niceties from D itself: according to the documentation for std.getopt, you get the -h
and --help
flags for free under .helpWanted
. One less thing to have to do.
The documentation did not make it immediately clear to me how to produce both long and short options. The otherwise quite detailed documentation strangely punts on exactly that question, saying instead to read the next section. It turns out you write "longopt|shortopt" and the long and short options will be discovered. For Kodachrome, that means writing "delay|d" to get both long and short options.
D main
functions take zero or one arguments. If it takes one argument, it is of type string[]
. Meaning we can't manipulate argc
like we might in C. However, D has a .length
that will return the number of arguments. It provides the number of arguments sans the getopt arguments. That means that you need to add one compared to the traditional argc -= optind;
dance in C: D will still count the command name with .length
after getopt. I presume this is intentional. I need to know the number of non-getopt arguments so that I can make sure there are only one or two arguments after the getopt and if there are exactly two, use the second one for the output file name.
The rest of the main
function is fairly straightfoward and does not look too dissimilar to what one might write in C (this wil become a theme...). I used Thread.sleep()
to delay based on the value of the -d
flag, or 3 if no flag, as that is what the forum recommended using. Clock.currTime()
is used to get the current time and then .toISOString()
will convert the current time into an ISO datetime string. Perfect. This part of development was very easy as there was ample documentation for what I wanted to accomplish. Now we can generate our output file names and delay the screenshot by a user-determined number of seconds. I chose to append the .png
extension myself using D's string concatenation, so if a user decides to specify an output file name, the extension should be omitted.
Next we have to interface with X in order to grab the screen itself. There is nothing remarkable to speak of here. I did exactly what I would do if I were writing this program in C. The x11 package was immensely helpful and made writing that code incredibly quick and straightforward.
Two out of three modules were fairly easy. It was seeming like I would be able to knock out this utility quickly. Unfortunately, I hit a serious roadblock with PNG export. While I did overcome the roadblocks, it is not particularly elegant and I had to duplicate previous work. I think it boils down to me not being familiar enough with how packages work on D. I could not get the png-d library to do what I wanted. It hides nearly all of libpng's features behind compile-time option switches, all set to false by default. I could not figure out how to turn things on, so I turned to searching for other libraries that might be able to help me.
I discovered dlib, which is a general-purpose library toolkit. It has rich image support. Unfortunately, there appeared to be no straightforward way to take an XImage and export a PNG with dlib. Worse still, I could not find any comprehensive documentation that might have helped me. Unlike the upstream D code, dlib appears to be spotty in terms of complete documentation. I wrestled with trying to figure out how dlib worked for far too long. And dlib did not even compile out of the box, due to missing support in the druntime library, which I have fixed.
Eventually, I resorted to porting my PNG export routine from my ncurses-based pixel art program, reimplementing the necessary libpng bindings from my experiences from writing pledged, and borrowing Hiltjo Posthuma's routine to convert from BGRA to RBGA since I had forgotten you had to do that. I will likely end up spinning off the PNG bindings into its own library and package. It is not actually complete, as I had extreme difficulties getting png_jmpbuf
to compile. I think it might be related to png_jmpbuf
really being a macro that expands out to something slightly complex, and D cannot handle that. I don't know. It might be OpenBSD-specific, for all I know. But the utility works, so it is a problem for another day.
This also means the PNG export module is little more than glorified C and really does not take advantage of any D features. That is both ok and not ok. It is ok in the sense that D advertises itself as a multi-paradigm language and so an imperative program is more than welcome and accepted and anticipated by the language authors. Not ok in the sense that I don't feel like I was able to give D much of a fair shot in respect to the unique features of D, particularly the object-oriented parts.
Update (3/28): After reading some more official documentation, I was able to figure out how to get rid of all the glorified C.
I will need to write more utilities to get a complete feel for the language. Fortunately, there is a lot of opportunity for utility writers in the D package repository. Perhaps I will even return to Kodachrome in the future and re-implement the PNG export in more idiomatic D. And I was able to improve OpenBSD support in D as well because of Kodachrome, so that's a win for me. I am lucky to generally have good relationships with my port upstreams. So much so that my name is now permanently etched in a (very minimal and probably not actually copyrightable) module! And as I mentioned previously, the D Language Foundation is happy to accept patches for OpenBSD. Win-win for everyone.
You can grab the code from the places D code is usually found: dub build -b release kodachrome
or GitHub.