My i3 dual screen workflow

Using the i3 tiling window manager on two screens („outputs“) can be challenging. The number of workspaces grows twice as fast than with a single screen setup and it is easy to lose track of the numbers and contents of workspaces. For me personally it is more intuitive to remember a certain sequence of workspaces on each screen, which seemingly extends above and below the workspace presently displayed. (It may be that this intuition has been coined by using the GNOME Shell over extended periods.)

In order to achieve a comparable user experience within i3, I use the following Python script with the keybindings presented below. The script is inspired by an article on the i3 homepage by user captnfab. It has one dependency: ziberna/i3-py, which can be installed with pip3 install i3-py. As is apparent from the keybindings, Ctrl+Alt+Up/Down are used to switch the present workspace on the focused output. With the same keys together with +Shift you can take the focused window with you.

#!/usr/bin/python3
#
# i3-switch-workspace.py
# by Fabian Stanke
#
# Sequentially switch workspaces on present output

import i3
import argparse

parser = argparse.ArgumentParser(
    description='i3 workspace switcher.')

parser.add_argument(
    '--move', action='store_true',
    help='take the focused container with you when moving.')
parser.add_argument(
    'direction', choices=['next', 'prev'],
    help='defines in which direction to switch.')

args = parser.parse_args()

workspaces = i3.get_workspaces()

# Determine focused workspace (and thus the focused output)
focused_ws = next((w for w in workspaces if w['focused']))

# Collect all workspaces of the focused output
ws_names = list(w['name'] 
                for w in workspaces 
                if w['output'] == focused_ws['output'])

# Determine position of focused worspace in that collection
idx = ws_names.index(focused_ws['name'])
target = focused_ws['name']

if args.direction == 'next':
	# Determine next workspace	

	if (idx + 1 < len(ws_names)):
		target = ws_names[idx + 1]
	else:
		# Determine last number used on this output
		maxidx = 1
		# Determine unused numbers 
		used = {}
		for w in workspaces:
			try:
				widx = int(w['name'])
				used[widx] = True
				if w['output'] == focused_ws['output']:
					maxidx = max(widx, maxidx)
			except:
				continue
		# Increment to create new name
		while used.get(maxidx, False):
			print(maxidx)
			maxidx += 1
		target = str(maxidx)


elif args.direction == 'prev':
	# Determine previous workspace

	if (idx - 1 >= 0):
		target = ws_names[idx - 1]
	#else remain at first workspace

if args.move:
	# Move the focused container to the target workspace first
	i3.command('move', 'container to workspace ' + target)

# Switch
#print("switch to " + target)
i3.workspace(target)

My preferred keybindings to actually use the above script are:

bindsym Ctrl+Mod1+Down exec i3-switch-workspace.py next
bindsym Ctrl+Mod1+Up exec i3-switch-workspace.py prev
bindsym Ctrl+Mod1+Shift+Down exec i3-switch-workspace.py --move next
bindsym Ctrl+Mod1+Shift+Up exec i3-switch-workspace.py --move prev

Interoperating Exim with Rspamd

Recently I have been implementing Rspamd for providing some automatic filtering for the emails hosted on my server, which are presently delivered by Exim to the Dovecot LDA.

For integrating Exim with Rspamd, I followed the Rspamd manual, but thing weren’t exactly working as expected.

I was frequently getting error messages such as:

Feb  7 11:10:44 stanke rspamd[2939]: (normal) <3a1c27>; task; accept_socket: accepted connection from 127.0.0.1 port 51289
Feb  7 11:10:44 stanke rspamd[2939]: (normal) <3a1c27>; task; rspamd_worker_error_handler: abnormally closing connection from: 127.0.0.1, error: IO timeout

with Exim at the same time reporting

2016-02-07 11:10:44 1aSMIm-0005QH-3P spam acl condition: Broken pipe on spamd socket
2016-02-07 11:10:44 1aSMIm-0005QH-3P H=mail1.mcsignup.com [205.201.139.33]:49767 Warning: ACL "warn" statement skipped: condition test deferred

In the majority of cases however the scan succeeded, so that a general misconfiguration could be ruled out. For some reason the socket connection between Exim and Rspamd seemed to break in some cases. I tried playing with the spamd_address options of exim, in particular with tmo and retry (see the Exim documentation on content scanning at ACL time), but to no avail.

What seems to have fixed the problem however was to define two entries for spam servers that Exim can connect to, despite the fact that they both refer to the same Rspamd instance. Exim seems to simply switch to the next (in fact same) server when seeing a „broken pipe“ and the reconnect seems to suffice to iron things out. Here is the relevant snippet to be inserted in /etc/exim4/conf.d/main/02_exim4-config_options:

spamd_address = 127.0.0.1 11333 tmo=1m retry=10s variant=rspamd : \
                127.0.0.1 11333 tmo=1m retry=10s variant=rspamd

fgallery on Mac OS X

fgallery is a generator for beautiful static web galleries, which I used e.g. for producing hochzeitsball.eu. The instructions on it’s website for running on OS X however do not overlap with my preferences (i.e. are not based on Homebrew).

To get fgallery running on OS X using Homebrew, the following steps worked for me:

brew install imagemagick lcms2 jpeg jpegoptim pngcrush fbida
cpan -i JSON JSON::XS Image::ExifTool
export PERL5LIB=$BREWPATH/Cellar/exiftool/9.61/libexec/lib:$HOME/perl5/lib/perl5
./fgallery --help

Android Z-Push

For weeks I had the intention to use my Android phone with Zarafa and Z-Push. However, my phone always reported that protocol version provided by the server would not be supported. I had a day off today and dug into the code (first Z-Push, then CyanogenMod 10) to track down the problem. It seemed like there would be a problem with the OPTIONS request in ActiveSync and the headers sent by Z-Push. I ended up using Fiddler to inspect the HTTPS traffic, in particular the headers and it turned out that Z-Push did not send the headers to identify the ActiveSync protocol. But the PHP-code (already messed with debugging output of my own) did send the headers.

Surprisingly, a minimal PHP script that did nothing by sending headers also worked. After a while it seems like the PHP output buffering (ob_start) dropped the headers, but then ob_clean or _flush at before and/or after the header call didn’t change anything.

Staring at the Fiddler traffic of the working testing code brought the saving idea: I had mod_pagespeed enabled (which I noticed, because it also added a header of its own). Turning it off made everything work!

Conclusion: Z-Push is incompatible with mod_pagespeed!

Umlaute von Zotero nach BibTeX exportieren

Mag sein, dass dieser Artikel eine beinahe inexistente (Wissens-) Lücke füllt. Für all jene, denen weder BibTeX noch Zotero etwas sagt, ist er mit Sicherheit nicht lesenswert, aber allein die Möglichkeit, dass ich selbst nochmal auf das geschilderte Problem treffe, rechtfertigt ihn dennoch.

Das Problem von dem ich spreche ist der Dateiexport von Zotero, einem Programm das ich zur Verwaltung meiner Bibliographie verwende, nach BibTeX, dem Dateiformat das ich zur automatischen Erstellung der Referenzen und des Quellenverzeichnisses in meiner Dissertation verwende. Wenn nun im Titel von verwendeten Quellen oder in Namen von deren Autoren deutsche Umlaute oder diakritische Zeichen (wie z.B. französische Akzente) vorkommen, werden diese in der exportierten Datei unverändert wiedergegeben, weil Zotero die ausgegebene Datei per Vorgabe als UTF-8 kodiert. Diese Kodierung wird aber von BibTeX nicht richtig verarbeitet.

Für diese Problem gibt es zwei Lösungswege, wobei ich leider viel Zeit mit dem ersten verloren habe:

  1. Man kann versuchen, BibTeX zur richtigen Verarbeitung der UTF-8 Datei zu bringen, bzw. einen dementsprechenden Ersatz für BibTeX einzusetzen. Diesbezüglich gibt es jede Menge Diskussionen im Internet, die die Hoffnung (leider) nicht schnell genug sterben lassen. Der Vollständigkeit halber sei hier beispielhaft auf bibtex8 verwiesen.
  2. Viel einfacher ist es jedoch, Zotero dazu zu bringen, die kritischen Zeichen entsprechend der LaTeX Syntax auszugeben. Das schafft man aber nicht, indem man selbst die Einträge entsprechend ändert, sondern indem man die Dateikodierung für den Export ändert.

    Dazu muss man lediglich in den Einstellungen von Zotero in der Kategorie Export ein Häkchen bei „Option für Zeichenkodierung beim Export anzeigen“ setzen. Beim nächsten BibTeX Export ist dann einfach „Westlich (ISO-8859-1)“ aus der Liste auszuwählen und schon erstellt Zotero eine problemlos verwendbare BibTeX Datei.

Nur „echte“ Benutzerkonten anzeigen

Um am Login Bildschirm von Fedora nur jene Konten anzuzeigen, die sich in letzter Zeit eingeloggt haben, muss man nur eine kleine Änderung an der /etc/gdm/custom.conf Datei vornehmen:

--- old/custom.conf	2011-07-07 09:35:01.462829111 +0200
+++ new/custom.conf	2011-07-07 09:30:27.146258540 +0200
@@ -7,6 +7,7 @@
 [xdmcp]
 
 [greeter]
+IncludeAll=false
 
 [chooser]
 

Damit werden nicht oder zB nur per scp genutzte Benutzernamen ausgeblendet.

Fedora 15 on NVIDIA NVS 3100M

In an earlier post, I already wrote about how to install the proprietary video driver on Fedora. In a brief update I suggested to change the acpi_sleep kernel option to accomplish proper display wake-up from standby. Unfortunately, that solution doesn’t work reliably in my experience. Today I found a solution that seems to successfully work around the problem of the black screen by waking up the screen „manually“.

Gernot Walzl wrote a „nasty workaround“ (quoting himself) in the form of the following script:

#!/bin/sh

# nvidia_dpms_fix.sh
# 2011-06-12
# by Gernot WALZL

# nasty workaround for nvidia drivers to resume from dpms off/suspend

export DISPLAY=:0

getXauthority () {
  export XAUTHORITY=$(ps -C X -f | grep "$DISPLAY" \
    | sed -n 's/.* -auth \([^ ]*\).*/\1/p')
}

handle_line () {
  if echo "$1" | grep -e "(EE) NVIDIA.* DisplayPort link training failed" \
      > /dev/null; then
    getXauthority
    xset dpms force on
  fi
}

tail --follow=name /var/log/Xorg.0.log --retry --lines=1 2> /dev/null \
  | while read line; do
    handle_line "$line"
  done

To actually make this do its job, I copied it into /usr/local/bin, made it executable and created an autostart entry for GNOME 3 by saving the following lines under ~/.config/autostart/nvidia_dpms_fix.sh.desktop

[Desktop Entry]
Type=Application
Exec=/usr/local/bin/nvidia_dpms_fix.sh
Hidden=false
X-GNOME-Autostart-enabled=true
Name=NVidia DPMS fix
Comment=Wakes up the display properly

I believe the problem that is being worked around is indicated by a line in the Xorg.0.log:

(WW) NVIDIA(GPU-0): AUO (DFP-3): Failed to set DisplayPort power state

Although I could only test this on my machine (HP EliteBook 8440p), with some luck this works for all cases where a similar log message is found.

Go C10k

Inspired by (admittedly „ancient“) part 1 of a very instructive post series by Richard Jones and while waiting for Channels API support in Google’s new Go App Engine, I decided to make a quick port of Richard’s mochiconntest_web Erlang module to Go.

Source first, discussion later:

package main

import (
	"fmt"
	"http"
	"log"
	"time"
)

func main() {
	http.HandleFunc("/test/", feed)
	if err := http.ListenAndServe("127.0.0.1:8080", nil); err != nil {
		log.Fatal(err)
	}
}

func feed(w http.ResponseWriter, r *http.Request) {
	ch := time.Tick(1000000000)
	fmt.Fprintf(w, "Goconntest welcomes you! Your Id: %s\n", r.URL.String())
	w.(http.Flusher).Flush()
	for N := 1; ; N++ {
		<-ch
		n, err := fmt.Fprintf(w, "Chunk %d for id %s\n", N, r.URL.String())
		if err != nil {
			log.Print(err)
			break
		} else if n == 0 {
			log.Print("Nothing written")
			break
		}
		w.(http.Flusher).Flush()
	}
}

Of course this could be stripped down by ignoring (i.e. not handling) errors, but IMHO this doesn’t fall behind the Erlang implementation in terms of beauty; well, my sense of „beauty“ anyway.

You might have noted, that I’m not using the time.Sleep function, but instead a seemingly „artificial“ construct with a time.Ticker channel. This internally allows for a multiplexing of the Goroutines spawned by the http module to only a few system threads, scheduling the network operations via epoll.

Update 2011-05-23: Removed usage of ChunkedWriter from Go source according to a hint from Brad Fitzpatrick.

Update 2011-05-25: The flushing by w.(http.Flusher).Flush() is necessary to make sure that the chunks get written in-time. Thanks to Brad Fitzpatrick for pointing this out.

For the client side, I used Richard’s floodtest.erl module, only removing the {version, 1.1} line from the http:request, because that option doesn’t seem to be supported by Erlang R14B.

The result from my Intel(R) Core(TM) i7 CPU M 620 @ 2.67GHz:

The RSS usage of goconntest_web converges at 321MB i.e. roughly 32KB per connection (13KB less than the ad-hoc Erlang implementation). Also note, that Go indeed spawned only 4 threads on this quad-core machine to handle the 10k open connections.

The only disappointment was the rather high CPU usage. Although I’m aware that the screen-shot is far from any acceptable benchmark measure, still 1.5h CPU time seems to be more than „practically nothing“ Richard reported for the Erlang implementation.

Update 2011-05-25: While testing the new code above, I logged the process statistics with pidstat and then made the following plot (Python script, data file):

Mind the logarithmic time scale! The dashed line indicates the ramp-up of opened connections to 10k (theoretical, not measured). The colorful background is a stacked plot of the per-thread CPU usage with one color per kernel thread-id. Obviously Go spawns more threads than there are cores. Finally, the thick black line is the resident memory usage, which seems to converge much later than the CPU usage.

Thanks for reading. Maybe, if the App Engine Channels API takes long enough, I will continue my investigations.

xsession-errors

Ever since I upgraded my notebook with a SSD, I was looking for a way to minimise or avoid unnecessary write accesses. The tools of choice (on Linux at least) are iostat for a rough summary and pidstat for the details. With the help of those two one can easily figure out which processes are responsible for write accesses.

Once a writing process is identified, lsof can tell you which files are actually written. Depending on what you want to achieve, you can then continue to relocate that file to a different disk (e.g. if you have two disks and want one of them to sleep most of the time) or to memory (using tmpfs). The latter obviously means that the written data will be lost at the next reboot, but sometimes this is perfectly fine e.g. for the files inside /tmp. Therefore, it is generally a good idea to move that directory into volatile memory following the instructions on the openSUSE Wiki.

All good and well if you actually manage to relocate or move a file. Often file locations are a matter of configuration and otherwise you can help yourself with dynamic links. But there was one very special file on my openSUSE 11.4 installation that withstood all my assaults for quiet some time. I’m speaking of a beast called .xsession-errors residing in your $HOME directory. Created by KDM, one would expect to be able to configure the location of that file. Indeed, there is a configuration option called ClientLogFile specifically for that purpose. Unfortunately this is only the first and easier of two necessary steps:

  1. As root open your /usr/share/kde4/config/kdm/kdmrc, go to a section labelled [X-:0-Core] (there may be multiple of those, but don’t worry and just pick the last one) and add the following line:
    ClientLogFile=../../tmp/xsession-errors-%u

    This will move the file into the /tmp directory. Mind the path relative to $HOME.

  2. Now the hidden piece: again as root open your /etc/X11/xdm/Xsession and make the following changes (I deliberately use the patch syntax here i.e. remove lines with a minus sign and add those with a plus sign):
    --- Xsession.old        2011-05-01 19:46:40.000000000 +0200
    +++ Xsession    2011-05-02 22:18:39.000000000 +0200
    @@ -123,8 +123,8 @@
         # GDM seems to handle this its self
         test -z "$GDMSESSION" || break
    
    -    # Once if KDM does handle this its self
    -    #test -z "$KDMSESSION" || break
    +    # KDM handles this itself
    +    test -z "$KDE_SESSION_VERSION" || break
    
         # Avoid bad symbolic links
         case "$errfile" in

Done; logout, restart KDM, re-login and check if the xsession-errors* exists at the new location. If so, remove your old one and cheer to a long living SSD. Only, of course, if the new location is not on your SSD, but e.g. in memory. It doesn’t hurt to re-check with pidstat.