C vs C++
Xsnow is written in C. However, because I needed a reliable hash function to maintain a cache of bird surfaces, some C++ has been crept in. To store the bird surfaces, an 'unordered_map' is used if available. Otherwise, 'map' is used, which is not implemented as a hash function but as a binary tree. For housekeeping of the snow flakes, an '(unordered) set' is used. The WWW recommends to compile the main program with C++ when using a mix of C and C++, so a very small main() is written in C++, which calls the original C main() function. All sources have been adapted to compile using C++, so, if you are a C++ adept, you can do:
CC=g++ ./configure make make install
The original xsnow-1.42 has an more or less ad-hoc timing mechanism. The program offers many flags to tweak the speed of Santa, the speed of snow and the like. Xsnow-2.0 has a more straight forward timing mechanism, in principle, every speed is defined in pixels/second.
The updating and drawing of Santa are decoupled, so the refreshing rate of Santa does not affect His speed.
Fallen snow (bottom and on windows) is nice in xsnow-1.42, but after running for some time, the visualization of fallen snow is not realistic. Therefore I use now a complete new strategy for the handling of fallen snow.
The handling of wind is changed in xsnow-2.0: it is now more or less windy all the time, but the original code from xsnow-1.42 is still used to experience more dramatic wind effects. The algorithm for the sensibility to wind of Santa and snow has been changed: The velocities of Santa and snow are changed by the wind.
In xsnow-1.42 an fixed-length array was used to hold the snow flakes. In xsnow-2.0, the flakes are in a double-linked list. But now, the housekeeping is done with a C++ 'set' or 'unordered set'. This makes it possible to add snow flakes at random times, for example when converting fallen snow to snow flakes. Furthermore, the amount of snow is now determined by the rate the flakes are produced and removed. The production rate is given in flakes per second per pixel (i.e. the width of the window measured in pixels). This has the effect that, in a steady state situation, a larger window shows more flakes than a smaller one.
Since I stumbled upon https://www.cairographics.org/Xlib/, I decided to use only GTK/cairo to make the animations.
The preferred way for xsnow to make it's drawings is to use a transparent, click-through window that covers the whole screen.
This requires a compositing window manager, which is not always available. In those cases painting is done at the root-window (or window 'pcmanfm' when running LXDE). Using XdbeSwapBuffers and friends a double buffer is used. When this is not desirable (for example when running xsnow together with xpenguins), then you can specify the flag '-doublebuffer 0', at the cost of some flicker in the animations. Also, when you specify '-xwininfo' or '-id <window>', painting in the chosen window is done using double buffering, unless '-doublebuffer 0' is specified.
The contours of the fallen snow on windows and bottom of the screen are determined by splines through randomly chosen points. Now and then, the splines are recalculated using other random points.
The aurora is simulated by drawing a number of vertical aurora lines next to and over each other.
The shape of the bottom of the aurora (the S-like curves you see in many aurora pictures) is generated by creating a spline through (at the moment of writing) 8 equidistant points with random y-values. Using this spline, a number larger than the width of the requested aurora of equidistant points are interpolated.
- Note: all splines are computed following Steffen's method, see this article, which has the advantage that overshoots cannot occur.
Subsequently, the points are 'slanted' i.e: the x-values are modified using the corresponding y-values and the points are rotated. Then, an array is produced which contains the x-y values, taking care that adjacent points differ 1 (one) in x-value.
The number of points will in general be larger than the width of the aurora, and the same x-value will occur more than once. All x-values from the lowest to the highest will be present in the resulting array. The code along with some comment is in aurora.c: create_aurora_base().
The height and the alpha values of the corona lines are determined using spline functions.
It is arranged that the top of the aurora shows a faint purple glow, and that the aurora is green at the bottom fading out towards the top.
The start and the end of the aurora are blurred by applying a diminishing alpha value, as well as the bottom.
During the simulation, the following parameters are gradually changed:
- the numbers defining the splines (shape, height, alpha)
- the overall alpha value of the aurora
- the rotation angle
Because computation of the aurora surface costs quite some CPU time, the construction of this surface is done in a separate thread to prevent a choppy moving of the other animations.
Using above computations, a reasonable aurora is obtained, but when the aurora takes a turn to the left, a sharp boundary on the right side of the turn is produced. Vice versa for a turn to the right. This can be dealt with using a global blur function, like in here and here. Alas, these algorithms are much to cpu-intensive. That is understandable: every pixel of the surface is visited and acted upon.
So, I added some code to detect where the turning points are, and generate extra aurora lines with diminishing alpha to get rid of the sharp edges.
A note about splines
In the famous book 'Numerical Recipes in C' a spline generating function is given, along with a function to interpolate using the spline. Alas, this code is still licensed (after all this years!). Implementation using for example 'Einführing in die Numerische Mathematik Ⅰ (Josef Stoer)' is too complicated for me, and cheating by changing the NR code is way below Santa's standards. So I am happily using the spline functions in the excellent GNU Scientific Library (libgsl). As I understand, the NR license was one of the reasons to create libgsl...
Btw: as far as I know, Numerical Recipes has no code for Steffen's algorithm used in xsnow.
A note about xinerama (multi-monitor setup) and tiling window managers
Placing the snow-window in a specific monitor, using Xinerama.h was not very difficult in for example xfce. There, all monitors are part of the same workspace. But in a tiling window manager, things are different. For example, in bspwm, one monitor shows desktop 0, while the other monitor is on desktop 10. Xsnow wants to know in what desktop a window is situated, and wants to know if a workspace is visible. The first one is easy and was already solved, but the second one: which workspaces are visible: I found no easy solution. Luckily, XineramaQueryScreens() gives the coordinates and sizes of the screens, and I solved the issue as follows: In each centre of the xinerama screens, place a small window and ask, using xdo_get_desktop_for_window(), in which workspace the window is situated. That workspace must than be visible.
A note about using X11: the xdo library: libxdo
I spent some time to get the snow-window in the correct location, serving at least the most used desktop systems: gnome, cinnamon, kde(plasma), xfce, fvwm and some more. I could have saved time if I was aware of libxdo (part of the famous xdotool program). Surely, the creators of libxdo are much more experienced than I am. So, I am using now libxdo wherever things get hairy. I am not sure if libxdo is general available outside of debian, so I incorporated the source in xsnow.
Multi language support
Xsnow now speaks some European languages, next to English. The translations are done using the program 'trans' from the debian package 'translate-shell'. I made some simple Python scripts to automate things. As to be expected: many translations will not be optimal. You are encouraged to contact me if you want to do something about that.
As noted above, the aurora is computed in a separate thread. Inspired by the success, multithreading is also implemented for the computing of fallen snow surfaces and the speed of the birds.
In all cases, multithreading is accomplished by starting a thread that only ends when xsnow ends. Coordination is achieved by the use of semaphores.
The gain in speed is the largest in the aurora case, the aurora thread can be busy for 30 to 70%, according to the program 'htop'. The other threads use only a few percents.
Self replication is implemented by including an
ascii-representation of a tar file, made by
dist. Xsnow is not really a self-replication
program, because the ascii representation is not part of
the tar file, but it works beautifully:
xsnow -selfrep | tar zxf - cd xsnow-3.7.4 ./configure make # test: src/xsnow # to let it snow src/xsnow -selfrep | tar zxf - # to create also here xsnow-3.7.4 cd xsnow-3.7.4 ./configure make # etc.
Window manager issues
An in-depth story about difficult to find good managers in general. Question: does this also apply for window managers?
Here follows some information about the cooperation of xsnow and a few window managers, taken from the man page:
In general, xsnow works better when using a compositing window manager like xcompmgr, compton or picom. However, with some window managers (FVWM for example), the xsnow-window is transparent, but not click-through. Flags to be tried in this case include: -root, -doublebuffer, -xwininfo, -id. Here follow some window managers with their issues: Tiling window managers Here you need to float windows with class=Xsnow. AWESOME Without compositor: no issues. With compositor: no click-through xsnow window, and issues with multi-monitor setup. BSPWM No issues if you add to your bspwmrc (the bspwm configuration file): bspc rule -a Xsnow state=floating border=off CINNAMON No issues. DWM No issues, except the "Below Windows" setting in the "settings" panel. ENLIGHTENMENT With one monitor: no issuses. With more montors: probems with showing in 'all monitors' FLUXBOX Without compositor: no issues. With compositor: no click-through xsnow window FVWM Without compositor: no issues. With compositor: no click-through xsnow window GNOME on Xorg No issues. GNOME on Wayland Most windows don't catch snow. HERBSTLUFTWM No issues. I3 Without compositor: windows don't catch snow, use the next line in "config": for_window [class="Xsnow"] floating enable;border none With compositor: unworkable. JVM No issues. LXDE With compositor: no issues. Without compositor: works with one monitor. Maybe you need to run with the flag -xwininfo LXQT Without compositor: unworkable. With compositor: no issues. MATE No issues. OPENBOX No issues. PLASMA (KDE) No issues. SPECTRWM Various issues. In any case you need in spectrwm.conf: quirk[Xsnow] = FLOAT TWM Without compositor: no issues. With compositor: no click-through xsnow window and you need to tweak settings->lift snow on windows. WINDOW MAKER Without compositor: no issues. With compositor: no click-through xsnow window XFCE No issues when compositing is on, unworkable when compositing is off. See settings -> Window Manager Tweaks -> Compositor XMONAD No issues if you add to your xmonad.hs: import XMonad.Hooks.EwmhDesktops xmonad $ ewmh $ defaultConfig in the ManageHook section: className = ? "Xsnow" --> doFloat
Xsnow tries to find out if it can create a transparent click-through window. If successful, xsnow will create one. In general for this a running compositing window manager (xcompmgr, picom, compton) is needed. Alas, in some cases, the window created by xsnow is transparent, but not click-through.
If not successful, xsnow will use the root window, except:
In LXDE, xsnow will snow on the window with the name 'pcmanfm'. If you have a multi-monitor setup, changes are that the wrong window is chosen. Remedy: start xsnow like this:
xsnow -xwininfoand click on the desktop.
In XFCE, xsnow will snow on the window with the name 'Desktop'.
If xsnow is runnig, but you don't see anything (this happens for example in the LXQt environment)
Try to start a compositing window manager first, like xcompmgr, compton or picom, like:
Xsnow can use any window, you can choose one (using
xsnow -xwininfo), or you can specify a window-id (using
xsnow -id window-id). This feature does not always works perfect.
Raspberry 64 bit, and maybe others: xsnow is confused by the 'xcompmgr' daemon, which results in a black background. The reason:
xcompmgris started with the flag
-a, this seems to be beneficial for Openbox, but is detrimental for xsnow and others. I hope to find a fix for this, for now I have this remedy (I don't know if this is detrimental for Openbox...):
- On the command line, before starting xsnow:
killall xcompmgr nohup xcompmgr -n &
Or: start xsnow with
xsnow -xwininfoand click on the desktop for a less than perfect xsnow experience.
Or (permanent solution): edit
/usr/lib/raspi-config/cmstart.sh, change the flag
-nand restart your system. I don't know if this is detrimental for Openbox...
Xsnow by default starts on all workspaces. In the
settingstab, you can choose: run on all workspaces or run only in the current workspace.
Among the downloads I included shell scripts 'startondesktop' and 'startonalldesktops' (I should have called then 'startonworkspace' and 'startonworkspaces'...) that you can try to start xsnow on (a subset of) all workspaces if xsnow itself does not run in all workspaces on your system.
The location of the top of the windows (we need that to figure out where snow should fall on), including the decorations, is a known issue on the web. I found a solution for this issue: see the code in wmctrl.c (from the xsnow tarball). And, by looking at the code of the programs wmctrl, xwininfo and xprop, I was able to incorporate the things I need in xsnow with respect to choosing a window, locate a window etc.
It seems there is something wrong with the Breeze theme: xsnow works, but the buttons behave strangely.
Before version 2.10, xsnow refused to run on Wayland (that is the proposed successor of X11 and is the default on recent Debian-derived installations). I was prepared for major code changes in xsnow, but, after lots of googling, I found a solution, at least for xsnow: simply set the environment variable GDK_BACKEND=x11. So, before the gtk environment is initialized (using gtk_init()), xsnow sets (using setenv()) that variable to the desired value. Of course, the official GDK documentation has something about the issue . It could very well be that things work thanks to XWayland, I really don't know very much about X11 and Wayland. In Wayland, applications have only a restricted control over the windows that are not owned by the application. So, xsnow cannot snow in other windows. But for many of the windows the information about the where-abouts are available to xsnow. For example, xsnow is snowing on the windows of firefox, gimp, gvim, xsnow, xterm, xclock and xcalc, but not on the windows of aislerioth, gedit, nautilus and gnome-terminal.