Marco's Blog

All content personal opinions or work.
en eo

Fixing KSnapshot Quirks - and Programming the Linux Shell in the Process

2013-11-24 25 min read Howto marco

KSnapshot is the best of programs, and KSnapshot is the worst of programs.

First of all, KSnapshot is a terribly nifty utility that takes screenshots of the current screen. It’s very flexible and allows you to grab the whole screen, a single window on the screen, an area of a window, a random rectangle on the screen, or even some weird shape that you draw on the screen. It’s got tons of opportunities for you to record what’s going on.

Also, it is very powerful in what you can do with the grabbed stuff: you can save it in a variety of formats, you can send it on to some other application (for instance, email or photo editing). But wait! There is more! You can request a delay, so you can arrange the screen just the way you want (for instance, if you want to grab a video still); you can have or remove window decorations; and you can or may not include the mouse, if it happens to be where you are grabbing.

To top it all off, KSnapshot is integrated into the default desktop. When you hit the Print Screen key, it pops up, with a grab of the screen (minus KSnapshot) already in it. Save it, and you are done!

Sadly, KSnapshots also has a series of stupid quirks that are largely unexplained. What’s quirky is that the functionality is there, it’s just unreachable.

No Extension

The first one, and it’s really an annoyance, is that while KSnapshot knows about a ton of file formats and continues to save in them once you tell it to, by default it just falls back to PNG and adds no file extension. So your lovely screenshots will all be named something of your choosing, but with no .png extension. Now you know: just set the format once, and KSnapshot will remember. Why it doesn’t so automatically, nobody knows.

The problem, of course, is that you end up with a bunch of files without extension, and applications don’t know what to do with them. Some do (Gwenview) but some (especially web forms that strangely still insist on file extensions) don’t.

No Auto-Save

Once you specify a folder into which grabs are stored, and a generic file naming convention, there isn’t a whole lot of reason to make it hard for you to save them. Instead, you only have a “Save As” option. You should just be able to hit a Save button that doesn’t ask a lot of questions and simply dumps the file onto the hard drive using the same location and format as the last grab.

Strangely, as we’ll see later, the function to do just that is implemented. Not only, it is visible to the whole universe. Why there is no keyboard shortcut or button to access it, I don’t know.

No Memory of Snapshot Type

When KSnapshot comes up, it automatically takes a screen shot of the whole screen. You can save it or discard it. But you can’t get a screenshot the way you specified. For instance, you may have said that you want to take grabs of a rectangular region – but KSnapshot still takes a shot of the entire screen.

The problem here is technical: KSnapshot doesn’t need your help to store the whole screen, but if you want a region, it has to ask you which region.

Multiple Instances by Default

Whenever you hit Print Screen, KDE launches a new instance of KSnapshot. That’s horribly inconvenient, because it’s the kind of app you want only once. If you plan on making multiple shots, you wouldn’t want to have to remember where you set the correct options.

But the implementation of KSnapshot is not a singleton, nor is its invocation: when you hit Print Screen, KDE simply launches a new instance of KSnapshot.

Fixing That with DBus

KDE used to have its own form of inter-process communication, called DCOP. Sadly, it was abandoned in favor or a more light-weight IPC system that is shared with non-KDE applications, called DBus.

While DCOP was infinitely more scriptable, DBus is still powerful. It works by implementing a message bus in the computer. Applications can register as interested parties (listeners) and can broadcast messages. The messages will be sent to all listeners that are registered and allowed to listen. It’s really powerful.

Because text mode apps and scripts are people, too, they had to get access to DBus. There are oodles of applications that make DBus accessible, but here we’ll use **qdbus**. It’s the one that ships with the KDE distribution, so it’s available when KSnapshot is.

Using qdbus

Just try firing up qdbus:

qdbus

You should see a whole bunch of gobbledygook. That’s the list of all the registered targets (participants) on DBus. Try looking for the one with “ksnapshot” in its name:

qdbus | grep ksnapshot

(There could be more than one – there is one per KSnapshot instance)

The output of the command above should be something like:

org.kde.ksnapshot-7740

Where the org.kde.ksnapshot- part is constant. The number at the end is the Process Identifier, the thing that UNIX (and Linux) uses to manage processes.

Now we’ll see an interesting trick that DBus likes doing: partial completion. Just like simply typing qdbus gave you a list of targets, typing qdbus with the name of a target (“service” in DBus speak) gives you a name of available paths. For KSnapshot, running qdbus org.kde.ksnapshot-7740 on my machine returns:

/
/MainApplication
/KSnapshot

You don’t have to worry about the different paths. But the same trick with completion works with paths, too. When I run qdbus org.kde.ksnapshot-7740 /KSnapshot, I get:

method void org.kde.ksnapshot.exit()
method int org.kde.ksnapshot.grabMode()
method bool org.kde.ksnapshot.save(QString filename)
method void org.kde.ksnapshot.setGrabMode(int grab)
method void org.kde.ksnapshot.setTime(int newTime)
method void org.kde.ksnapshot.setURL(QString newURL)
method void org.kde.ksnapshot.slotCopy()
method void org.kde.ksnapshot.slotGrab()
method void org.kde.ksnapshot.slotMovePointer(int x, int y)
method void org.kde.ksnapshot.slotOpen(QString application)
method void org.kde.ksnapshot.slotSave()
method void org.kde.ksnapshot.slotSaveAs()
method int org.kde.ksnapshot.timeout()
method QString org.kde.ksnapshot.url()
method QDBusVariant org.freedesktop.DBus.Properties.Get(QString interface_name, QString property_name)
method QVariantMap org.freedesktop.DBus.Properties.GetAll(QString interface_name)
method void org.freedesktop.DBus.Properties.Set(QString interface_name, QString property_name, QDBusVariant value)
method QString org.freedesktop.DBus.Introspectable.Introspect()
method QString org.freedesktop.DBus.Peer.GetMachineId()
method void org.freedesktop.DBus.Peer.Ping()

That looks like a lot of garbage, but it’s actually what I was looking for: the names of the things I can call inside KSnapshot.

What do you mean, Marco? Did I just hear that?

See that first line? The one that proudly proclaims, “method void org.kde.ksnapshot.exit()"? Well, that’s what a programmer calls a signature, which means: a definition of what it is (a method), what it’s called (org.kde.ksnapshot.exit), what arguments it needs (none, since there are none listed inside the parentheses), and what values it returns (none, since it returns void).

With the brash enthusiasm of the expert programmer, you should now understand what you need to do: invoke the exit method! You do it like so (I miss Ron Popeil!):

qdbus org.kde.ksnapshot-7740 /KSnapshot org.kde.ksnapshot.exit

If you ran that, your KSnapshot instance should have died. Mine did, so now I have to go and start a new one and find the process ID as above.

What Should Our Improved KSnapshot Do?

I would like to retain all the flexibility and power of KSnapshot, but make it easier to use. For that purpose, I suggest two modes of interaction:

  1. Setting options
  2. Screen grabbing

Setting options is just a fancy way of saying: start the thing on your own, make your choices, and save them. Strangely, KSnapshot has no options, preferences, or settings dialog (which is really odd, considering how many of those darn things every single KDE applications offers). But you can still choose your preferences, by simply clicking the controls on the main interface and saving one screenshot.

So, to set options, all you do is start KSnapshot and save one screen. End of story. All your settings are saved.

Screen grabbing itself should be a painless affair. You should just hit a control sequence (a shortcut) and the screen grab should start. If you chose to have a full screen grab, you don’t have to do anything at all – the screen is grabbed for you. If you chose an option that requires selection, you will be placed in selection mode, and once you are done, your grab is saved automatically. You don’t have to do anything else. Isn’t that wonderful?

What Implementations Could We Choose?

Obviously, we could download the KSnapshot source code and change things around there. Since we are not adding any functionality, that shouldn’t be too hard.

You can get the source code with git clone git://anongit.kde.org/ksnapshot. You’ll see it is fairly short, with only a few files. It would be easy to go in and change.

I didn’t go that route for several reasons:

  • The source code is short because it wisely re-uses tons of KDE code. Why re-invent the wheel? On the flip side (and that’s a problem KDE developers seem to be unaware of) it’s really hard from the source code to tell what libraries are required for the build, or how you build in the first place. Even when you figure out what it is that you need, you have to download tons of different libraries, and you’ll have to keep them up with updates.
  • The changes you make to a KDE project are not saved to the project. First of all, my implementation may not be to everyone’s liking, and second I am not a registered project contributor. So all the toiling for a “better”interface results in conflicts next time KSnapshot is updated.
  • Scripting is fun! Really, this mini-project is more about learning shell and DBus scripting than about having the perfect screenshotter.

A second implementation strategy would have been to clone KSnapshot, modified at random. But I really didn’t like the idea, because there is nothing new I want.

A third implementation would have been a script in a powerful scripting language, like Python. But that seemed overkill.

The fourth, and final implementation is as a shell script. The shell is an incredibly poor scripting environment, with all sorts of quirks and foibles, but it’s also universal and lightweight. We’ll see the problems we encounter: they are typical of shell scripting for graphical applications. But in the end, we’ll get exactly what I want – which is really all that matters, right?

Implementation Strategy

Let’s look at the steps we want to perform in our script:

  1. Check if ksnapshot is running. Start an instance if it isn’t.
  2. Check if a default format is set. If not, set the file extension to .png
  3. If there is a screenshot already taken, save it before you start a new one
  4. Here things split. We have two cases
    1. The grab mode is set to “entire screen”. Life is easy in this case: we just take a screenshot and don’t have to wait. If there already was a screenshot taken, we don’t even have to do that
    2. The grab mode is set to anything else, in which case the user has to specify what “else” is to be selected. In this case, we have to wait for the user to make a selection and save the shot once the selection is complete
  5. Once the screenshot is done, we shut down KSnapshot if we started it

Notice that there are a few items here that are debatable. For instance, there is really no need to shut down KSnapshot even if we started it. It’s my personal preference, because I’d rather start an instance manually if I want to set options, but you may think differently.

The good thing about a shell script is that it’s freaky easy to change that kind of behavior. You just comment out the line that shuts down ksnapshot and you get what you want.

Check if KSnapshot is Running

There are two completely different approaches to test for KSnapshot. You can either check for a process by the name ksnapshot, or you can check for a DBus service. The two are not perfectly equivalent, since the process may exist but not have registered the service yet. Technically, you could also have the process die and DBus not know about it yet, but in my experience that’s not a real issue.

To test for the running process, we use the utility pidof. It’s part of every Linux system (in Ubuntu it’s in the sysvinit-utils, which are part of the base install), so you don’t have to worry about finding it.

pidof is almost custom-made for what we need: it takes the name of the executable you want to find and returns a list of all processes matching. It’s even smart enough to have the -s option, which is short for “single shot” and returns only one (random) of the matching processes.

pidof -s ksnapshot will return the process ID or a running ksnapshot process. If none is running, then it will of course return empty:

pid="pidof -s ksnapshot"

Here you see shell syntax at work: pid= is a variable assigment. After that occurs, we can substitute the value of the variable in our script by typing $pid.

Next, the double quotes are used for grouping. The shell likes using spaces to separate arguments, but allows for double quotes to override the spaces. If you typed ls Program Files the shell would look for two files (ls being short for “list”), Program and Files. To find a file named Program Files, you would have to use double quotes, as in ls "Program Files".

Finally, the “backtick” is yet another special shell trick. It means that instead of what’s inside the backticks directly, the shell needs to take the argument as a shell command, execute it, and return the output inside the backticks.

Let’s assume that the command pidof -s ksnapshot returns the value 1984. Then the assignment above would be equivalent to pid="1984"

So, now that we have these pieces together, we can tell if ksnapshot is running: if the assignment above is empty, then it isn’t. Otherwise, it is.

Testing Variables

How do we test for something in the shell? There is a command for that! It’s called (very imaginatively) test. As one of the rare exceptions in UNIX, it also has a nickname, “[ ]” and it’s this nickname that is used most often.

If you want to test if a variable is empty, you would use test with the -z (for zero) option. Since we named our variable, pid, the test would look like this: [ -z "$pid" ]

Notice the use of the double quotes. It is not required, but if pid happens to be empty, then [ -z $pid ] translates to [ -z ], and the shell will wonder what it’s testing, since there is nothing there. It’s one of the strange pitfalls of shell scripting, and you should simply get used to using double quotes around variable substitutions as often as you can. It’s easier to deal with the few times when they are not needed than with the mysterious error messages you get when they are.

The command to check if something is the case is another duh-worthy one: if. To check if the pid is empty, we write:

if [ -z "$pid" ]

The if statement wants the usual then, and has the usual else. It unusually uses “fi” as a termination, and you have to be careful about the exact syntax, since if is followed by an expression, then is followed by an expression, and else if followed by an expression. That means the shell has to know where to stop with the expression, which is either a newline or a semicolon.

Long story short, put “then,” “else,” and “fi” on their own separate lines, without mixing them with other commands.

Commented Code for Testing

The bulk of this initial part explained, the first lines of the script looks like this:

# first, find the running instance of ksnapshot
wasrunning="yes"
pid="pidof -s ksnapshot"
if [ -z "$pid" ]
then
  wasrunning=""
  ksnapshot &
  pid="pidof -s ksnapshot"
fi
if [ -z "$pid" ]
then
  echo "Could not start ksnapshot (Unknown error). Exiting..."
  exit 1
fi
# wait for the service to come online
e_val="dummy"
while [ "$e_val" ]
do
  e_val=qdbus org.kde.ksnapshot-$pid /KSnapshot org.freedesktop.DBus.Peer.Ping 2>&1``
  sleep 1
done

Let’s see how this all works. First, we set a variable (named wasrunning) to store whether ksnapshot was running when we started. We will need this at the end, when we decide to shut down the application if we started it in the first place.

I use a trick from programming, which is to set the variable to a specific state (“yes”) and unset it if that state is not met. So, unless I prove that ksnapshot wasn’t running, it will be assumed it was. That’s good, because it doesn’t allow the variable to be in an indeterminate state.

Next, we store the process ID of a random ksnapshot process in the variable pid. If that variable is empty ( if [ -z “$pid” ] ), we know we have to start a process. First, we set the variable wasrunning to empty (we could have set it to “no”, but the test for empty doesn’t require comparison and is easier to write). Next, we start ksnapshot in the background (with the appended &, which means something like: start the process and don’t wait for it to end).

Once we have started the process, we can save that process’s ID in the variable pid. If pid is still empty, then we can’t start ksnapshot, and we can’t perform a screenshot. I decided not to display anything to the user, since it’s not likely to happen, but instead of exiting, we should present an error message.

Once we have the process ID, we have to wait for the DBus service to be available. For that, we use a while loop, which repeats itself until a particular condition is not met. Just like we had if; then; fi, here we have while; do; done. Another trick here: we stop when the variable is empty, for which we don’t need a special operator (like -z for test). Also, we set the variable to something non-empty when we go into the loop, so that we know the loop is going to be executed at least once.

The command we invoke in there is something we talked about: we call qdbus with the correct parameters to interface with the Ping method of the ksnapshot service. If we get a return value (empty), it is working, otherwise we get an error. Because we don’t really care why the service is not running, all we do is concatenate error output to the standard output: since Ping returns empty, if there is a non-empty return value, it must be an error and we assume the service is not up yet.

The strange-looking syntax 2>&1 does that: it collects standard and error output and puts them together as standard output. That’s because the channel 1 in processes is reserved for standard output, while the channel 2 is standard error. The syntax above, then, takes channel 2 (error) and dumps (>) into channel 1. If you had used the syntax 1>&2, you probably would have sent everything to standard error. (WARNING: I didn’t actually test this, and if you happen to cause a heart attack or a nuclear war because you tried, it’s your fault!!!)

Setting the Extension if Not Set

After this slightly complicated piece of explanation, a welcome simple task (sort of): find the current file name (the name under which the screen shot would be saved by default); determine if it has an extension, and if not set it to .png.

The only thing that complicates matters here is that there is no easy command to figure out the extension of a file in UNIX. There is one to remove a known extension (strangely), but none to figure out the extension itself.

The shell presents a weird syntax, though, that can be used for that purpose. Instead of writing $filename, you can write ${filename}. The content of the braces will be treated as a variable name and substitution occurs. If you add modifiers to the variable name, things slightly change. For instance, if you prepend # to the variable name, as in ${#filename}, you get the number of characters in the content.

The one modifier we are interested in is #. It follows the variable name and is in turn followed by a “glob expression,” which is the patterns you would use to list files.

For instance, when you want to list files with the extension .png, you would type ls *.png. The # modifier says that the shell should return the content of the variable minus the longest match to the glob expression. So, if the variable filename contains the value filename=/home/marco/Pictures/family.jpg, then ${filename##*/} will return the file name minus the part that matches */, which would be /home/marco/Pictures/. It would return family.jpg.

Well, after that explanation, you probably already guessed that the correct match expression is *., which will return the extension, without the dot.

So, all we have to do now is get the current file name, for which there is a DBus method. We store the value, figure out the extension, and if it’s empty (did I hear -z?), we append the default extension .png and save it using the corresponding DBus method. Thankfully, both are provided.

The code is fairly straightforward, and I’ll just append it here without comment:

# if there is no default format, set PNG
url="qdbus org.kde.ksnapshot-$pid /KSnapshot org.kde.ksnapshot.url"
ext="${url#*.}"
if [ -z "$ext" ]
then
  echo "Setting url $url to PNG"
  qdbus org.kde.ksnapshot-$pid /KSnapshot org.kde.ksnapshot.setUrl $url.png
fi

Figure Out If There is a Screenshot Waiting to be Saved

If we started KSnapshot, it would have taken a screenshot automatically on start. If we didn’t, there might still be a screenshot waiting to be saved and we don’t want to have the user lose it.

Unfortunately, KSnapshot has no DBus method to determine whether there is an unsaved screenshot. The only way we can figure that piece out (and we’ll need it later, too), is by checking the window title. If there is an unsaved screenshot, the window title will include the string “modified”.

It’s fairly easy to get the information on a given window with the utility xwininfo. We can get that same utility to spit out a list of all windows with the options -root -tree, which starts with the root window and shows all descendants of the root, including our KSnapshot process.

KSnapshot, fortunately, includes its name in the window title, so we can just look for that. The first argument returned is the window ID, the second the title (in double quotes) and the rest is window information like size, etc. I am going to use the output to store the window ID, since we’ll have to check the same window for modification later. And just because I am in the mood for evil, I will use a different utility to get to the window title. We have to run another process to assign to a second variable anyway. might as well learn something new!

This second utility is xprop. It is used to get information about a window, just like xwininfo, but is much, much more detailed. Given a window ID, it returns tons of information, including the title under the variable NET_WM_NAME. We don’t really care about the title, only if it has the string “modified” in it (change it for various languages). If it does, the screenshot needs to be saved / is ready. If it isn’t, the string is empty and we have to do nothing.

Now we need a little logic. We now we can discard the default screen grab from KSnapshot starting, if we started KSnapshot ourselves and the grab mode is not full screen. If we didn’t start KSnapshot, then the grab potentially comes from the user and we need to save it. If the grab mode is full screen, on the other hand, the screen grab we got is the one we want and we are done. Easy peasy. That’s what this section does:

# find the window ID
win_id=xwininfo -root -tree | awk ‘/KSnapshot/ {print $1}’ `# modified="`xwininfo -id $win_id | awk -F \" '{print $2}'`"` `modified=`xprop -id $win_id | grep NET_WM_NAME | grep modified
mode=qdbus org.kde.ksnapshot-$pid /KSnapshot org.kde.ksnapshot.grabMode `if [ "$modified" ]` `then` `  # save if it already says modified, so we know we have to wait for the string to show again` `  url=`qdbus org.kde.ksnapshot-$pid /KSnapshot org.kde.ksnapshot.url
  qdbus org.kde.ksnapshot-$pid /KSnapshot org.kde.ksnapshot.slotSave
  # but discard the saved grab if not already running and mode other than 0
  if [ -z "$wasrunning"  -a $mode -ne 0 ]
  then
    qdbus org.kde.ksnapshot-$pid /KSnapshot org.kde.ksnapshot.setURL "$url"
  fi
fi

Notice how the comments in the code highlight the fact we need to save at this point, more because we want to get rid of the “modified” in the title than because we want the actual result. That’s important for the next part.

Take a Snapshot and Save It

If you selected a grab mode other than full screen, you as a user need to specify what should be saved. You do so by clicking around with your mouse, indicating which window, part of a window, rectangular region, etc. you want. The problem here is that KSnapshot does not emit a signal on the DBus when the user is done, which is kinda unforgivable. Instead, we have to sit there and watch until the window title changes to “blah blah modified blah blah.” When that happens, we know the user is done, and we can save.

The downside to that approach is that we need to check at periodic intervals for changes. Fortunately, it’s not computationally expensive to check the window title. Unfortunately, we have to choose a reasonable interval. Most UNIX implementations have a timer with second resolution, called sleep (the current Linux implementation is capable of any-length intervals).

The code here is very much derivative of what we had above. The main difference is that we use two new DBus methods: slotGrab and slotSave. You may wonder what’s with the “slot” prefix: QT, the library underlying KDE, uses a slot/signal model, which means that libraries talk to each other a lot like processes on DBus: one component registers a “slot,” which is a function with defined input, activity, and output; other components emit “signals” that are sent to the slots. That’s particularly cool when you realize that KDE makes keyboard shortcuts and menu items into signal generators: you can access any slot function in a program by adding a shortcut or menu item that emits the right signal. So, slotGrab is the function used internally when you act on the user interface to start a grab. slotSave, on the other hand, is an anomaly: while it is exported as a DBus method, it is not available in the user interface – the method available is slotSaveAs.

I am not sure why KDE developers don’t export all slots available by default. Surely there are no security reasons: if you have access to DBus, you have access to the command line, and you can do pretty much the same things. But apparently that’s the case: the development teams routinely answer questions about HOWTO this and that by saying they are going to make a slot available (or emit a signal) on DBus, and the user can script whatever she wants.

But here is the code:

# grab a new screenshot if it was already running or the mode is not 0
if [ "$wasrunning" -o $mode -ne 0 ]
then
  qdbus org.kde.ksnapshot-$pid /KSnapshot org.kde.ksnapshot.slotGrab

  # wait for the window to report the grab is done
  modified=""
  while [ -z "$modified" ]
  do
  modified="xprop -id $win_id | grep ‘NET_WM_NAME.*modified’"    sleep 1
  done

  # save that shot in the preselected directory/format
  qdbus org.kde.ksnapshot-$pid /KSnapshot org.kde.ksnapshot.slotSave
fi

Finally, Exit

Now, as mentioned, this is a personal preference. I don’t like KSnapshot windows around the screen, but a lot of my distaste comes from the hordes of windows when I go on a screenshotting orgy. One window, as this utility ensures, is fine. But you can easily omit this.

Now, the logic is simple: if KSnapshot was not running and we started it (which is stored in the variable “wasrunning”), we shut it down:

# shut down if we started it
if [ -z $wasrunning ]
then
  qdbus org.kde.ksnapshot-$pid /KSnapshot org.kde.ksnapshot.exit
fi

Integrating into KDE

This is all nice and fine, but if you have to start up this utility from the command line every time you want a screenshot, it’s too much work.

Instead, we are going to do exactly what KDE does by default: we are going to change the global shortcut table. It’s really easy to do, and maybe doing it for this particular case will inspire you to create more, and possibly more useful shortcuts.

First, go to System Settings. There are many ways to get there, but it’s usually in the Favorites list in your Start menu. If it isn’t, then type “system settings” in the search box in that same menu.

In the first row of System Settings is an option, Shortcuts and Gestures. Select that one. You will be presented with a tabbed interface, tabs on the left.

In the main interface, expand Preset Actions. You should see an item Print Screen that is checked. That’s the default KSnapshot. You can now either edit this item or create a new one. I’ll show you how to create a new one because I am a sadist.

Click on Preset Actions, and go to the menu below that reads Edit. There, go to New > Global Shortcut > Command/URL.

You’ll get a New Action under Preset Actions. Change its name to something meaningful, like “Marco’s Gracefully Improved Screen Printing, Please Send Money.”

On the right, the main part of the window, you see three tabs: Comment, Trigger, and Action. The comment is not mandatory, so feel free to type whatever you like in there (it’s a good place to put all your hate messages to me and my puny script!!! send praise to my inbox, on the other hand.).

The Trigger is the key combination that will – you guessed it! – trigger the – you guessed it! – action. In that tab there is going to be a label, “Shortcut:”, a button, and an eraser. You just click on the button (it should say None before you do) and then you press a key combination (I have Shift + Print Screen). Once you are done with the key combination, it will show in the button. If the combination is already used, you’ll get a message.

In the Action tab, you fill in the location of the screenshot.sh as you downloaded it below. Apply saves your new shortcut, and you will now be able to run screenshot.sh whenever you hit your favorite key combination!

That’s All, Folks!

You can download the entire script (plus any modifications I’ve made since writing this article) at the link below.

{jd_file file==6}