Recent posts (max 10) - Browse or Archive for more

The Sabaton Index

I enjoy music that tells a story. True stories are even better. For what has probably been a couple of years, I have stumbled across references to Sabaton and their songs about historical events (particularly battles, wars, and related topics). But I only recently investigated and listened to their work.

Oh. Wow.

They post videos on YouTube where they also have a Sabaton History channel that covers the history behind the songs. They also publish their discography with lyrics and some background.

Who would sing a song about the Holocaust? Sabaton did. Listen carefully to the lyrics, soak in the thunder of the music. Reflect on the history.

For me, a song is more meaningful when I know the story behind it and the words from which it is crafted. So I want to learn the history, read the lyrics, and then listen to the song. But Sabaton has created so much material, from lyrics to lyric videos to music videos to history videos, that it is hard to really wrap your head around it. It really needs some organization to navigate it.

So I made an attempt at it.

Generally, each row of the table lists the title of the song, a link to the official lyrics, links to the Sabaton History videos for that song, various videos of the song itself, and a brief comment noting the topic of the song. They publish multiple video variants for their songs, such as "lyric videos" which show the words as they sing, or "music videos" which sometimes take a more reenactment approach. Or a "story video", as with No Bullets Fly, or even a stop motion animation "block video" as with The Future Of Warfare. I've attempted to be fairly complete in terms of listing all songs for which I found information, even if I didn't find enough to fill out all the columns. I have listened to but a small fraction of the material indexed here, but I've found this structure to be useful in exploring Sabaton's work.

Find the full table at SabatonIndex.

Lego and aluminum phone camera tripod mount

For a family project, we needed a way to take pictures with the stability of a tripod. The best camera we own happens to be the one in a OnePlus 6 cellphone. We have a collapsible tripod with a standard mounting plate and bolt.

Those don't exactly attach to each other.

Ok, so how can we approach this? Well, the tripod has a 1/4" 20tpi captured bolt. So we need something rigid for it to screw into. I can drill and tap some aluminum to meet that interface. For holding the OnePlus 6, a frame built from Lego could work.

So all we need then is something that can bolt onto the tripod, and to which we can attach Lego pieces.

Studs look challenging to machine, and I expect they would be extremely finicky to get machined precisely enough to give the right amount of "clutch". And might be prone to damaging bricks, which we want to avoid. An easier approach is to drill holes compatible with Lego Technic pins.

The tripod attachment plate is roughly 4 studs by 6 studs in size. So a 4x4 brick with a 1/4" 20-tpi hole in the center, with no studs, and with Technic pin holes around the edge should give us a very versatile base from which to work.

For material, I started from an aluminum ingot I had cast from cans, and machined that using my CNC mill (a Grizzly G8689 mini-mill with a HeavyMetal CNC conversion kit).

To start, I machined an aluminum block 1.254" x 1.254" x 0.380". (Ok, fine, the actual part was off by 2 thou, but it was close enough for the task at hand.) Then I machined 3 Technic pin holes on each of the 4 sides of the part. That included the shoulder around the top of each hole. I used an 1/8" endmill, and machined the outer ring, then the main hole within that. For each of those operations, I helixed down to cut the body of the hole, then cut the bottom circle to get a flat bottom. I don't have the g-code I actually used handy, but this gcode file gives the basic idea. Of note, the two Technic pin holes at each corner overlap partially within the part, so only one Technic hole at each corner can be used at a time. I then drilled a 13/64" hole through the center of the square face of the part, and threaded the hole with a 1/4" 20-tpi tap.

machined-aluminum-adapter.jpg

(There are many readily-visible flaws in the part. The casting had a lot of porosity, the edge of the tapped hole is rough, and there are some ugly machining marks on part of it. That said, they have not impaired its functionality.)

aluminum-adapter-beside-technic-1x4.jpg

Once I verified that the part accepted Technic pins, and screwed down solidly on the tripod, I wrapped the part in Technic bricks and slapped a 6x6 plate on top of it. That gets us a Lego-compatible tripod.

aluminum-adapter-with-technic-pins.jpg

aluminum-adapter-with-brick-wall.jpg

aluminum-adapter-with-brick-wall-and-top-plate.jpg

I then handed the contraption and a OnePlus 6 to my oldest son, Samuel, with directions to design and build a way to mount the phone on the tripod.

tripod-with-adapter.jpg

In short order, he came back with this:

tripod-with-bracket-rear.jpg

tripod-with-bracket-front.jpg

While there is room for refinement and optimizing the part count, this thing is very solidly built and did the job well.

tripod-with-phone.jpg

Using LeoCAD, I modeled the Lego portion of the contraption,

camera-tripod-mount-overview.png

And I created build instructions using LPub3D for anyone looking to build their own.

Based on the success of this, I expect that the next time I need to interface between Lego and "the real world", I will use the same 4x4 Technic-compatible brick design and drill-and-tap holes to suit the need.

LeoCAD 21.06 - Packaged for Linux

LeoCAD is a CAD application for building digital models with Lego-compatible parts drawn from the LDraw parts library.

I packaged (as an rpm) the 21.06 release of LeoCAD for Fedora 34. This package requires the LDraw parts library package.

Install the binary rpm. The source rpm contains the files to allow you to rebuild the packge for another distribution.

leocad-21.06-1ec1.fc34.x86_64.rpm

leocad-21.06-1ec1.fc34.src.rpm

LDraw Parts Library 2020-03 - Packaged for Linux

LDraw.org maintains a library of Lego part models upon which a number of related tools such as LeoCAD, LDView and LPub rely.

I packaged the 2020-03 parts library for Fedora 34 to install to /usr/share/ldraw; it should be straight-forward to adapt to other distributions.

The *.noarch.rpm files are the ones to install, and the .src.rpm contains everything so it can be rebuilt for another rpm-based distribution.

ldraw_parts-202003-ec1.fc34.src.rpm

ldraw_parts-202003-ec1.fc34.noarch.rpm
ldraw_parts-creativecommons-202003-ec1.fc34.noarch.rpm
ldraw_parts-models-202003-ec1.fc34.noarch.rpm

Blinds Tilt Mechanism Repair

We have a large faux-wood blind on our dining room window. The window pane itself is 70"x70"; which makes for a great view. However, the tilt mechanism stopped working in fairly short order. The tilt mechanism uses a pull-cord based mechanism which is no longer offered in blinds. I replaced the mechanism the first time it failed, but when it failed the second time in much the same way, I could no longer find a replacement for the parts. The original mechanism failed because the interface between the cord spool and the shaft driving the worm gear which drove the tilting of the blinds had worn, become loose, and allowed the cord spool to rotate without turning the worm gear shaft. The replacement mechanism failed in a different place; it had a bushing to adapt the sqare rod that runs the length of the blinds to a hexagonal through-hole in the driving gear. That bushing was made of plastic and had worn to the point that the drive gear could rotate without rotating the square rod.

The original tilt mechanism was made largely from metal components, while the replacement I had purchased was largely plastic. Examining them both, I determined that it would be more straight-forward to machine a new pully for the original mechanism than to machine a drive gear for the replacement mechanism. The original pully was made of nylon; I chose to machine a replacement pully from aluminum bar stock.

Cutting the main profile of the spool body:

1-cutting-main-profile.jpg 2-cutting-deeper.jpg

And the conical recess in the spool: 3-conical-recess.jpg

Cutting the spool off the stock: 4-cutoff-conical.jpg 5-cutoff-flat.jpg

Milled the slot for the worm gear shaft and the holes required for the threading the pull cord through: 6-milled-slot-conical.jpg 7-milled-slot-flat.jpg

Installed in the blinds: 8-installed.jpg 9-installed-closeup.jpg

This has held up well over the past 8 months, and it was gratifying to be able to repair something when replacement parts were no longer available.

Building an electronics organizer

The number of tablets, laptops, phones, ... let's just say "electronic devices" around the house has increased noticeably over the years, and finding that we had them haphazardly piled upon the end-table, ready to cascade off into a pile of mutual destruction, I decided we needed a mechanism for organizing them and defining an official location where they belong.

Design:

Given that I wanted room for six devices up to 1" thick, that meant 7 vertical boards, 3/4" thick each, yielding a base board 11-1/4" long. Allowing 1/4" for kerf leaves me with some room for error, so that leaves 60.5" of material from which to cut 7 uprights. That will mean 6 kerfs, giving me 59" of remaining material to allocate among the uprights. Making the upgrights equal lengths gives a height a little under 8.5". Rather than doing that, I opted to make the upgrights 9", 8.5", 8", 8", 8", 8.5", and 9" tall, giving a more interesting shape to the final product and giving nice "round" numbers for the lengths.

Materials:

  • 1x 6-foot, 1x8 poplar lumber
  • 14x 1-1/4" pocket-hole screws
  • wood stain

Tools:

  • pocket-hole jig
  • drill motor
  • tablesaw with fence and miter gauge
  • mitersaw

(Ideally, anyway; I actually made do with the tablesaw, but some of the cuts suffered for it.)

  • bench sander
  • several bar clamps with rubber feet

Cutting:

I cut the 11-1/4" base, two 9" uprights, two 8.5" uprights, and three 8" uprights. Then drilled two pocket-holes in each of the uprights, but _not_ in the base. I slightly rounded the corners of the wood on the bench sander, and touched up a couple of the cut faces where I had accidentally wiggled the board and gotten a less-than-perfect cut.

cut-and-drilled.jpg

Staining:

With a brush, I applied stain to one side of each of the pieces, wiped them down with a rag, applied stain to the edges (except the bottom edges of the uprights since those would not be visible) and wiped them down again, and then applied stain to the reverse side of each upright and wiped them all down. Then allowed them to dry overnight.

stained.jpg

Assembly:

The gaps between the uprights are too narrow to get a drill motor between them, so all the uprights have the pockethole screws facing the same direction, and I screwed them to the base in order from one end. Using the table saw as a clamping structure worked out very well for keeping the uprights aligned with the base and at right angles to it.

assembly-upright-3.jpg

assembled.jpg

Installed:

And here it is placed on a deep shelf with powerstrips behind it for the wall-warts and chargers.

installed.jpg

All-in-all, this worked out very well for what I was attempting to solve, and it came together with a lot less time invested than I had expected.

More fun with the bash prompt

A few years ago, I posted some fun Bash prompt tools, part of which was adding emoticons to the prompt based on the previous command's exit code. I figured it was time to revisit that bit of fun with a couple of enhancements.

First, a bit of code cleanup so color choices are more obvious, and add faces for SIGILL and SIGKILL.

#!/bin/bash
# source this
function prompt_smile () {
    local retval=$?
    local red=196
    local yellow=226
    local green=46
    local darkgreen=28
    if [ $retval -eq 0 ]; then
        color=$green
        face=":)"
    elif [ $retval -eq 1 ]; then
        color=$red
        face=":("
    elif [ $retval -eq 130 ]; then # INT
        color=$yellow
        face=":|"
    elif [ $retval -eq 132 ]; then # ILL
        color=$darkgreen
        face=":-&"
    elif [ $retval -eq 137 ]; then # KILL
        color=$red
        face="X_X"
    elif [ $retval -eq 139 ]; then # SEGV
        color=$red
        face=">_<"
    elif [ $retval -eq 143 ]; then # TERM
        color=$red
        face="x_x"
    else
        color=$red
        face="O_o"
    fi
    echo -e "\001$(tput setaf $color; tput bold)\002$face\001$(tput sgr0)\002"
    return $retval # preserve the value of $?
}
PS1="$PS1\$(prompt_smile) "

Download

When sourced into your shell with . promptsmile.sh, you get results like this:

bash-4.4$ . promptsmile.sh
bash-4.4$ :) false
bash-4.4$ :( true
bash-4.4$ :) sleep 60 & X=$!; (sleep 1; kill -INT $X) & fg %1
[1] 26699
[2] 26700
sleep 60

[2]+  Done                    ( sleep 1; kill -INT $X )
bash-4.4$ :| sleep 60 & X=$!; (sleep 1; kill -ILL $X) & fg %1
[1] 26709
[2] 26710
sleep 60
Illegal instruction (core dumped)
[2]   Done                    ( sleep 1; kill -ILL $X )
bash-4.4$ :-& sleep 60 & X=$!; (sleep 1; kill -KILL $X) & fg %1
[1] 26776
[2] 26777
sleep 60
Killed
[2]+  Done                    ( sleep 1; kill -KILL $X )
bash-4.4$ X_X sleep 60 & X=$!; (sleep 1; kill -SEGV $X) & fg %1
[1] 26788
[2] 26789
sleep 60
Segmentation fault (core dumped)
[2]   Done                    ( sleep 1; kill -SEGV $X )
bash-4.4$ >_< sleep 60 & X=$!; (sleep 1; kill -TERM $X) & fg %1
[1] 26852
[2] 26853
sleep 60
Terminated
[2]+  Done                    ( sleep 1; kill -TERM $X )
bash-4.4$ x_x (exit 4)
bash-4.4$ O_o true
bash-4.4$ :) exit

One bit of feedback I received was that the use of :) vs x_x meant that command prompts would shift by a character, and it would be better to have all the emoticons be of the same width. So if you prefer your faces all the same width, promptsmile-3wide.sh gives you more consistent line lengths:

#!/bin/bash
# source this
function prompt_smile () {
    local retval=$?
    local red=196
    local yellow=226
    local green=46
    local darkgreen=28
    if [ $retval -eq 0 ]; then
        color=$green
        face=":-)"
    elif [ $retval -eq 1 ]; then
        color=$red
        face=":-("
    elif [ $retval -eq 130 ]; then # INT
        color=$yellow
        face=":-|"
    elif [ $retval -eq 132 ]; then # ILL
        color=$darkgreen
        face=":-&"
    elif [ $retval -eq 137 ]; then # KILL
        color=$red
        face="X_X"
    elif [ $retval -eq 139 ]; then # SEGV
        color=$red
        face=">_<"
    elif [ $retval -eq 143 ]; then # TERM
        color=$red
        face="x_x"
    else
        color=$red
        face="O_o"
    fi
    echo -e "\001$(tput setaf $color; tput bold)\002$face\001$(tput sgr0)\002"
    return $retval # preserve the value of $?
}
PS1="$PS1\$(prompt_smile) "

Download

Which looks like this:

bash-4.4$ . promptsmile-3wide.sh
bash-4.4$ :-) false
bash-4.4$ :-( true
bash-4.4$ :-) sleep 60 & X=$!; (sleep 1; kill -INT $X) & fg %1
[1] 26914
[2] 26915
sleep 60

[2]+  Done                    ( sleep 1; kill -INT $X )
bash-4.4$ :-| sleep 60 & X=$!; (sleep 1; kill -ILL $X) & fg %1
[1] 26925
[2] 26926
sleep 60
Illegal instruction (core dumped)
[2]   Done                    ( sleep 1; kill -ILL $X )
bash-4.4$ :-& sleep 60 & X=$!; (sleep 1; kill -KILL $X) & fg %1
[1] 26991
[2] 26992
sleep 60
Killed
[2]+  Done                    ( sleep 1; kill -KILL $X )
bash-4.4$ X_X sleep 60 & X=$!; (sleep 1; kill -SEGV $X) & fg %1
[1] 27001
[2] 27002
sleep 60
Segmentation fault (core dumped)
[2]   Done                    ( sleep 1; kill -SEGV $X )
bash-4.4$ >_< sleep 60 & X=$!; (sleep 1; kill -TERM $X) & fg %1
[1] 27065
[2] 27066
sleep 60
Terminated
[2]+  Done                    ( sleep 1; kill -TERM $X )
bash-4.4$ x_x (exit 4)
bash-4.4$ O_o true
bash-4.4$ :-) exit

For that old-school style, the text-based emoticons work well, but systems that support emojis are becoming rather common-place, so we can use UTF-8 to get little emotional faces in our prompts:

#!/bin/bash
# source this
function prompt_emoji () {
    local retval=$?
    local red=196
    local yellow=226
    local green=46
    local darkgreen=28
    if [ $retval -eq 0 ]; then
        color=$yellow
        face=$'\360\237\230\200' # :D
    elif [ $retval -eq 1 ]; then
        color=$yellow
        face=$'\360\237\230\246' # :(
    elif [ $retval -eq 130 ]; then # INT
        color=$yellow
        face=$'\360\237\230\220' # :|
    elif [ $retval -eq 132 ]; then # ILL
        color=$darkgreen
        #face=$'\360\237\244\242' # nauseated # get a rectangle in Konsole
        face=$'\360\237\230\223' # cold sweat face
    elif [ $retval -eq 137 ]; then # KILL
        color=$yellow
        face=$'\360\237\230\265' # x_x
    elif [ $retval -eq 139 ]; then # SEGV
        #color=$yellow
        #face=$'\360\237\244\250' # Face with one eyebrow raised # get a rectangle in Konsole
        color=$red
        face=$'\360\237\230\240' # Angry face
    elif [ $retval -eq 143 ]; then # TERM
        color=$yellow
        face=$'\360\237\230\243' # >_<
    else
        color=$yellow
        face=$'\360\237\230\245' # ;(
    fi
    echo -e "\001$(tput setaf $color; tput bold)\002$face\001$(tput sgr0)\002"
    return $retval # preserve the value of $?
}
PS1="$PS1\$(prompt_emoji) "

Download

Which will give something maybe like this. The way the faces are rendered will depend on your terminal. For Konsole, these are simple line art; for Gnome-terminal some match Konsole, others have a more blob-like shape; and here they're rendered by your browser.

bash-4.4$ . promptemoji.sh
bash-4.4$ 😀 false
bash-4.4$ 😦 true
bash-4.4$ 😀 sleep 60 & X=$!; (sleep 1; kill -INT $X) & fg %1
[1] 27143
[2] 27144
sleep 60

[2]+  Done                    ( sleep 1; kill -INT $X )
bash-4.4$ 😐 sleep 60 & X=$!; (sleep 1; kill -ILL $X) & fg %1
[1] 27154
[2] 27155
sleep 60
Illegal instruction (core dumped)
[2]   Done                    ( sleep 1; kill -ILL $X )
bash-4.4$ 😓 sleep 60 & X=$!; (sleep 1; kill -KILL $X) & fg %1
[1] 27220
[2] 27221
sleep 60
Killed
[2]+  Done                    ( sleep 1; kill -KILL $X )
bash-4.4$ 😵 sleep 60 & X=$!; (sleep 1; kill -SEGV $X) & fg %1
[1] 27232
[2] 27233
sleep 60
Segmentation fault (core dumped)
[2]   Done                    ( sleep 1; kill -SEGV $X )
bash-4.4$ 😠 sleep 60 & X=$!; (sleep 1; kill -TERM $X) & fg %1
[1] 27295
[2] 27296
sleep 60
Terminated
[2]+  Done                    ( sleep 1; kill -TERM $X )
bash-4.4$ 😣 (exit 4)
bash-4.4$ 😥 true
bash-4.4$ 😀 exit

Beyond that, you'll find that your terminal may render a different subset of emojis than what mine does. I found a useful site for finding emojis with octal UTF-8 which makes it easy to update promptemoji.sh with something that suits your particular set of software.

And for ANSI colors, you may find this reference handy.

Go then, and liven up your own bash prompts, even more!

LDraw Parts Library 2019-02 - Packaged for Linux

LDraw.org maintains a library of Lego part models upon which a number of related tools such as LeoCAD, LDView and LPub rely.

I packaged the 2019-02 parts library for Fedora 29 to install to /usr/share/ldraw; it should be straight-forward to adapt to other distributions.

The *.noarch.rpm files are the ones to install, and the .src.rpm contains everything so it can be rebuilt for another rpm-based distribution.

ldraw_parts-201902-ec1.fc29.src.rpm

ldraw_parts-201902-ec1.fc29.noarch.rpm
ldraw_parts-creativecommons-201902-ec1.fc29.noarch.rpm
ldraw_parts-models-201902-ec1.fc29.noarch.rpm

LeoCAD 19.07.1 - Packaged for Linux

LeoCAD is a CAD application for building digital models with Lego-compatible parts drawn from the LDraw parts library.

I packaged (as an rpm) the 19.07.1 release of LeoCAD for Fedora 29. This package requires the LDraw parts library package.

Install the binary rpm. The source rpm contains the files to allow you to rebuild the packge for another distribution.

leocad-19.07.1-1ec1.fc29.x86_64.rpm

leocad-19.07.1-1ec1.fc29.src.rpm

Grid-based Tiling Window Management

Many years ago, a coworker of mine showed me Window's "quick tiling" feature, where you would press Window-LeftArrow or Window-RightArrow to snap the current window to the left or right half of the screen. I then found that KDE on Linux had that same feature and the ability to snap to the upper-left, lower-left, upper-right, or lower-right quarter of the screen. I assigned those actions to the Meta-Home, Meta-End, Meta-PgUp, and Meta-PgDn shortcuts. (I'm going to use "Meta" as a generic term to mean the modifier key that on Windows machines has a Windows logo, on Linux machines has a Ubuntu or Tux logo, and Macs call "command".) Being able to arrange windows on screen quickly and neatly with keyboard shortcuts worked extremely well and quickly became a capability central to how I work.

Then I bought a 4K monitor.

With a 4K monitor, I could still arrange windows in the same way, but now I had 4 times the number of pixels. There was room on the screen to have a lot more windows that I could see at the same time and remain readable. I wanted a 4x4 grid on the screen, with the ability to move windows around on that grid, but also to resize windows to use multiple cells within that grid.

Further complicating matters is the fact that I use that 4K monitor along with the laptop's !FullHD screen which is 1920x1080. Dividing that screen into a 4x4 grid would be awkward; I wanted to retain a 2x2 grid for that screen, and keep a consistent mechanism for moving windows around on that screen and across screens.

KDE (Linux)

Unfortunately, KDE does not have features to support such a setup. So I went looking for a programatic way to control window size and placement on KDE/X11. I found three commandline tools that among them offered primitives I could build upon: xdotool, wmctrl, and xprop.

My solution was to write a Python program which took two arguments: a command and a direction.

The commands were 'move', 'grow', and 'shrink', and the directions 'left', 'right', 'up', and 'down'. And one additional command 'snap' with the location 'here' to snap the window to the nearest matching grid cells. The program would identify the currently active window, determine which grid cell was a best match for the action, and execute the appropriate xdotool commands. Then I associated keyboard shortcuts with those commands. Meta-Arrow keys for moving, Meta-Ctrl-Arrow keys to grow the window by a cell in the given direction, Meta-Shift-Arrow to shrink the window by a cell from the given direction, and Meta-Enter to snap to the closest cell.

system-settings-config.png

Conceptually, that's not all that complicated to implement, but in practice:

Window geometry has to be adjusted for window decorations. But there appears to be a bug with setting the position of a window. The window coordinates used by the underlying tools for setting and getting the geometries do not include the frame, except for setting the position of the window, on windows that have a 'client' of the machine name instead of N/A. Getting the position, getting the size, and setting the size, all use the non-frame values. Windows with a client of N/A use the non-frame values for everything. A border width by title bar height offset error for only some of the windows proved to be a vexing bug to track down.

The space on a secondary monitor where the taskbar would be is also special, even if there is no task bar on that monitor; attempting to move a window into that space causes the window to shift up out of that space, so there remains an unused border on the bottom of the screen. Annoying, but I have found no alternative.

Move operations are not instantaneous, so setting a location and immediately querying it will yield the old coordinates for a short period.

A window which is maximized does not respond to the resize and move commands (and attempting it will cause xdotool to hang for 15 seconds), so that has to be detected and unmaximized.

A window which has been "Quick Tiled" using KDE's native quick-tiling feature acts like a maximized window, but does not set the maximized vert or maximized horz state flags, so cannot be detected with xprop, and to get it out of the KDE quick tiled state, it must be maximized and then unmaximized. So attempting to move a KDE quick tiled window leads to a 15 second pause, then the window maximizing briefly, and then resizing to the desired size. In practice, this is not much of an issue since my tool has completely replaced my use of KDE's quick-tiling.

OS X

I recently whined to a friend about not having the same window management setup on OS X; and he pointed me in the direction of a rather intriguing open source tool called Hammerspoon which lets you write Lua code to automate tasks in OS X and can assign keyboard shortcuts to those actions. That has a grid module that offers the necessary primitives to accomplish the same goal.

After installing Hammerspoon, launching it, and enabling Accessibility for Hammerspoon (so that the OS will let it control application windows), use init.lua as your ~/.hammerspoon/init.lua and reload the Hammerspoon config. This will set up the same set of keyboard shortcuts for moving application windows around as described in the KDE (Linux) section. For those who use OS X as their primary system, that set of shortcuts are going to conflict with (and therefore override) many of the standard keyboard shortcuts. Changing the keyboard shortcuts to add the Option key as part of the set of modifiers for all of the shortcuts should avoid those collisions at the cost of either needing another finger in the chord or putting a finger between the Option and Command keys to hit them together with one finger.

I was pleasantly surprised with how easily I could implement this approach using Hammerspoon.

Demo

Simple demo of running this on KDE:

(And that beautiful background is a high resolution photo by a friend and colleague, Sai Rupanagudi.)