setxkbmap - prevent layout amnesia

I use both the US* and the Czech keyboard layouts on my home desktop, but I use dwm which is quite minimalist and doesn't come with any way to switch between these. Nor should it - dwm is a window manager for hackers, so I bodged my own solution that displays the currently selected layout in dmenu and added the following section to my ~/.xprofile that let's me cycle between them using right-Windows key:
# enable US and CZ layouts, toggle with right-win, other possible grp:* values are...
#    grp:alt_caps_toggle         Alt+Caps Lock
#    grp:alt_space_toggle        Alt+Space
#    grp:menu_toggle             Menu
#    grp:lwin_toggle             Left Win
#    grp:win_space_toggle        Win+Space
#    grp:rwin_toggle             Right Win
setxkbmap -layout us,cz                 
setxkbmap -option                       
setxkbmap -option "grp:rwin_toggle"

However I noticed that periodically this would stop working, I'd be stuck on the US layout and my right-windows key would do nothing. Re-running the three setxkbmap commands would fix things up. 

This eventually annoyed me and I resolved to figure it out. Apparently Linux has some power-management module that will cause some USB devices to disconnect, and when they reconnect they appear as a different device without these keyboard settings.

Unsuprisingly my bodge was responsible for this. The correct solution here was to set my XKBLAYOUT to "uz,cz" and to add "grp:rwin_toggle" to my XKBOPTIONS in /etc/default/keyboard:

$ cat /etc/default/keyboard 

# Consult the keyboard(5) manual page.



Now my layout switching works a little better and my keyboard no longer has bouts of amnesia. That BACKSPACE="guess" line sure does look odd though, I'll need to come back to that at some point.

* = I have no need for the "pound" symbol anymore and US-layout keyboards are much more available

No, THIS is What Peak Hello World Looks Like

An article titled "This Is What Peak Hello World Looks Like" did the rounds on Hacker News (discussion here), where someone took the plain old printf("hello, world!")in C and transformed it step-by-step beyond recognition. This was a noble effort, but today I discovered an easier way to make Hello World more insane in a slightly different way:

    $ dotnet new console -o hello -lang F#
    The template "Console Application" was created successfully.

    Processing post-creation actions...
    Running 'dotnet restore' on hello/hello.fsproj...
      Restore completed in 209.4 ms for /home/sean/dev/dotnet/hello/hello.fsproj.

    Restore succeeded.
    $ cd hello
    $ cat Program.fs 
    // Learn more about F# at

    open System
    let main argv =
        printfn "Hello World from F#!"
        0 // return an integer exit code

That's it, an absolute monster of a program. But wait, "This is just a plain old Hello World in .NET! What's so insane about that?" I hear you ask. Well we're not quite done yet, let's get ready to share the executable with our colleague who wants to use our app so we'll add <PublishSingleFile>true</PublishSingleFile> to hello.fsproj - which will generate a single self-contained executable binary - and publish it:

    $ dotnet publish -r linux-x64 -c Release
    Microsoft (R) Build Engine version 16.5.0+d4cbfca49 for .NET Core
    Copyright (C) Microsoft Corporation. All rights reserved.

      Restore completed in 5.27 sec for /home/sean/dev/dotnet/hello/hello.fsproj.
      hello -> /home/sean/dev/dotnet/hello/bin/Release/netcoreapp3.1/linux-x64/hello.dll
      hello -> /home/sean/dev/dotnet/hello/bin/Release/netcoreapp3.1/linux-x64/publish/
    $ ./bin/Release/netcoreapp3.1/linux-x64/publish/hello
    Hello World from F#!
    $ ls -lh ./bin/Release/netcoreapp3.1/linux-x64/publish/hello
-rwxr-xr-x 1 sean sean 78M May 17 22:47 ./bin/Release/netcoreapp3.1/linux-x64/publish/hello

There she blows! An absolute monster - a 78MB Hello World executable! This is actually a bit unfair, since it includes some stuff we might not need, so let's add <PublishTrimmed>true</PublishTrimmed> to hello.fsproj and rebuild:

    $ dotnet publish -r linux-x64 -c Release
    Microsoft (R) Build Engine version 16.5.0+d4cbfca49 for .NET Core
    Copyright (C) Microsoft Corporation. All rights reserved.
      Restore completed in 33.8 ms for /home/sean/dev/dotnet/hello/hello.fsproj.
      hello -> /home/sean/dev/dotnet/hello/bin/Release/netcoreapp3.1/linux-x64/hello.dll
      Optimizing assemblies for size, which may change the behavior of the app. Be sure to test after publishing. See:
      hello -> /home/sean/dev/dotnet/hello/bin/Release/netcoreapp3.1/linux-x64/publish/
    $ ./bin/Release/netcoreapp3.1/linux-x64/publish/hello
    Hello World from F#!
    $ ls -lh ./bin/Release/netcoreapp3.1/linux-x64/publish/hello
-rwxr-xr-x 1 sean sean 46M May 17 22:51 ./bin/Release/netcoreapp3.1/linux-x64/publish/hello

Ok, slightly better but that's still a hefty 46MB for Hello World. This is a bit disingenuous however - what's happening is that I've configured the project to be able to produce a self-contained executable, to do this the .NET Core runtime is bundled in the hello binary. So it's a little bit like distributing a little bit of application [byte]code, a bytecode interpreter, a JIT compiler and runtime libraries.

Jupyter - problems with dotnet-try

Since .NET Try supports F# officially I wanted to switch over my Jupyter notebook instance to use that instead of IFsharp. But I ran into a couple of frustrating issues that I wanted to document in case anyone else hit them and didn't know how to debug the issue.

Issue 1. dotnet-try installs but isn't usable

I followed Scott Hanselman's instructions to install dotnet-try, but when I tried to execute dotnet try jupyter install it seemed as though dotnet-try wasn't installed at all:

$ dotnet tool install dotnet-try --global
You can invoke the tool using the following command: dotnet-try
Tool 'dotnet-try' (version '1.0.19553.4') was successfully installed.

$ dotnet try jupyter install Could not execute because the specified command or file was not found. Possible reasons for this include: * You misspelled a built-in dotnet command. * You intended to execute a .NET Core program, but dotnet-try does not exist. * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH.

After a lot of head scratching I dug into the actual docs and learned that both .NET Core 3.0 and .NET Core 2.1 SDKs should be installed, while I only had .NET Core 3.1's SDK So after a quick sudo apt install dotnet-sdk-3.0 dotnet-sdk-2.1 I was successfully able to install the kernel and list it:

$ jupyter kernelspec list
Available kernels:
  .net-csharp    /home/notebook/.local/share/jupyter/kernels/.net-csharp
  .net-fsharp    /home/notebook/.local/share/jupyter/kernels/.net-fsharp
  mit-scheme     /usr/local/share/jupyter/kernels/mit-scheme
  python3        /usr/local/share/jupyter/kernels/python3

Issue 2. Jupyter can't run dotnet-try

However even though it was installed, each time I tried to create a new F# notebook Jupyter would give an error saying that it was unable to connect to the kernel. After taking a quick look at my logs I saw the same error as before!

Feb 09 13:19:11 aviemore jupyter[837]: [I 13:19:11.169 NotebookApp] KernelRestarter: restarting kernel (1/5), new random ports
Feb 09 13:19:11 aviemore jupyter[837]: Could not execute because the specified command or file was not found.
Feb 09 13:19:11 aviemore jupyter[837]: Possible reasons for this include:
Feb 09 13:19:11 aviemore jupyter[837]: * You misspelled a built-in dotnet command.
Feb 09 13:19:11 aviemore jupyter[837]: * You intended to execute a .NET Core program, but dotnet-try does not exist.
Feb 09 13:19:11 aviemore jupyter[837]: * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH.
Feb 09 13:19:14 aviemore jupyter[837]: [I 13:19:14.180 NotebookApp] KernelRestarter: restarting kernel (2/5), new random ports
Feb 09 13:19:14 aviemore jupyter[837]: Could not execute because the specified command or file was not found.
Feb 09 13:19:14 aviemore jupyter[837]: Possible reasons for this include:
Feb 09 13:19:14 aviemore jupyter[837]: * You misspelled a built-in dotnet command.
Feb 09 13:19:14 aviemore jupyter[837]: * You intended to execute a .NET Core program, but dotnet-try does not exist.
Feb 09 13:19:14 aviemore jupyter[837]: * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH. 
I restarted the service, the server, checked and it took a while to realise the root cause. What happened was, even though dotnet-try was in PATH when I switched over to my jupyter use, it wasn't the case when I ran it via systemd. It seems that /etc/profile - which runs all the scripts in /etc/profile.d, one of which adds the ~/.dotnet/tools dir to PATH - is not used when systemd starts the service. I don't know the correct way to address this, but I wrote a quick script to setup PATH correctly and added an EnvironmentFile to the [Service] section my notebook.service file to use it:
After I set this up and restarted notebook.service it was able to access dotnet-try and spin up the F# kernel correctly.

Fun with manpages

When reading about a Unix command or C library function it's relatively common to see it suffixed with a number in brackets. This is to make it clear what exactly you're talking about, so if someone is discussing mknod they might write mknod(1) they're talking about the shell command or mknod(2) if they mean the syscall. The number used refers to the manpage section, so to see the manpage for the shell function:

$ man 1 mknod

And to see what the syscall is all about:

$ man 2 mknod

According the man manpage on my system there are eight standard manpage sections in total, with one non-standard section:

  1. Executable programs or shell commands
  2. System calls (functions provided by the kernel)
  3. Library calls (functions within program libraries)
  4. Special files (usually found in /dev)
  5. File formats and conventions eg /etc/passwd
  6. Games
  7. Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7)
  8. System administration commands (usually only for root)
  9. Kernel routines [Non standard]
Since something can be present in more than one section, I wondered which symbol had the most manpages so I wrote a script to look through each of the directories in /usr/share/man/man[1-8], list and parse the gzipped filenames (they're usually named symbol.sectionnumber.gz) and then find out the sections they're all present in:
import os
import re
from collections import defaultdict

manpage_gz_pattern = "(.*)\.\w+.gz"
manpage_dir_base = "/usr/share/man"
manpage_sections = range(1, 9)
manpage_entries = defaultdict(list)

for manpage_section in manpage_sections:
    manpage_section_dir = os.path.join(manpage_dir_base, f"man{str(manpage_section)}")
    manpage_section_contents = os.listdir(manpage_section_dir)

    for manpage_entry_filename in manpage_section_contents:
        gz_entry = re.match(manpage_gz_pattern, manpage_entry_filename)
        manpage_entry = gz_entry.groups()[0] 
        manpage_entries[manpage_entry] += [(manpage_section, manpage_entry_filename)]

for section_count in manpage_sections:
    number_of_manpages = len([ m for m in manpage_entries if len(manpage_entries[m]) == section_count])
    print(f"number of manpages in {section_count} sections: {number_of_manpages}")

The results are:
$ python -i
number of manpages in 1 sections: 10763
number of manpages in 2 sections: 107
number of manpages in 3 sections: 7
number of manpages in 4 sections: 0
number of manpages in 5 sections: 0
number of manpages in 6 sections: 0
number of manpages in 7 sections: 0
number of manpages in 8 sections: 1
There's seemingly a clear winner, you can find a single symbol in all eight standard manpage sections. However this is a little misleading because after a bit of inspection this symbol is "intro" - it is not a shell command, syscall, stdlib function, game or anything like that - it's a manpage that describes a bit about each section.

So ignoring intro the most common symbols and their manpage entries are
  • mdoc (mdoc.1.gz, mdoc.5.gz, mdoc.7.gz)
  • locale (locale.1.gz, locale.5.gz, locale.7.gz)
  • hostname (hostname.1.gz, hostname.5.gz, hostname.7.gz)
  • passwd (passwd.1ssl.gz, passwd.1.gz, passwd.5.gz)
  • time (time.2.gz, time.3am.gz, time.7.gz)
  • readdir (readdir.2.gz, readdir.3am.gz, readdir.3.gz)
  • random (random.3.gz, random.4.gz, random.7.gz)
This reveals something else interesting - the section needn't be a number. The two commands are both valid and access completely separate manpages:

$ man 1ssl passwd
$ man 1 passwd

I used to think that each time I opened a manpage I learn something completely new and unexpected - but I never thought I'd find something interesting just by looking at the manpages' gzipped filenames!

F# - Polymorphic parameter overflow

I was mucking around in F# over lunch and started thinking about how it assigns names to type variables (see "Automatic Generalization" of the Type Inference page of the F# docs for more info). By way of introduction, let's create a function foo that takes a single parameter without an explicitly defined type and see what its signature looks like:
> let foo x = x;;
val foo : x:'a -> 'a
So the parameter x is assigned the type variable 'a - which makes sense, the first unknown type gets named after the first letter of the alphabet. And of course it follows that if we have a function with two parameters the second type is called ...
> let foo x y = (x,y);;
val foo : x:'a -> y:'b -> 'a * 'b
... 'b! Ok now what happens when we've got a function with a ridiculous amount of parameters? We'll run out of lower-case letters eventually. When that happens do start using upper-case letters? Non-ASCII? Something else? I quickly hacked together a python script to generate functions with arbitrary parameters, created one with 40 and pasted it into the F# REPL and ...
> let foo x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x20 x21 x22 x23 x24 x25 x26 x27 x28 x29 x30 x31 x32 x33 x34 x35 x36 x37 x38 x39 = (x0,x1,x2,x- 3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39);;
val foo :
  x0:'a ->
    x1:'b ->
      x2:'c ->
        x3:'d ->
          x4:'e ->
            x5:'f ->
              x6:'g ->
                x7:'h ->
                  x8:'i ->
                    x9:'j ->
                      x10:'k ->
                        x11:'l ->
                          x12:'m ->
                            x13:'n ->
                              x14:'o ->
                                x15:'p ->
                                  x16:'q ->
                                    x17:'r ->
                                      x18:'s ->
                                        x19:'t ->
                                          x20:'a1 ->
                                            x21:'a2 ->
                                              x22:'a3 ->
                                                x23:'a4 ->
                                                  x24:'a5 ->
                                                    x25:'a6 ->
                                                      x26:'a7 ->
                                                        x27:'a8 ->
                                                          x28:'a9 ->
                                                            x29:'a10 ->
                                                              x30:'a11 ->
                                                                x31:'a12 ->
                                                                  x32:'a13 ->
                                                                    x33:'a14 ->
                                                                      x34:'a15 ->
                                                                        x35:'a16 ->
                                                                          x36:'a17 ->
                                                                            x37:'a18 ->
                                                                              x38:'a19 ->
                                                                                x39:'a20 ->
                                                                                  'a *
                                                                                  'b *
                                                                                  'c *
                                                                                  'd *
                                                                                  'e *
                                                                                  'f *
                                                                                  'g *
                                                                                  'h *
                                                                                  'i *
                                                                                  'j *
                                                                                  'k *
                                                                                  'l *
                                                                                  'm *
                                                                                  'n *
                                                                                  'o *
                                                                                  'p *
                                                                                  'q *
                                                                                  'r *
                                                                                  's *
                                                                                  't *
                                                                                  'a1 *
                                                                                  'a2 *
                                                                                  'a3 *
                                                                                  'a4 *
                                                                                  'a5 *
                                                                                  'a6 *
                                                                                  'a7 *
                                                                                  'a8 *
                                                                                  'a9 *
                                                                                  'a10 *
                                                                                  'a11 *
                                                                                  'a12 *
                                                                                  'a13 *
                                                                                  'a14 *
                                                                                  'a15 *
                                                                                  'a16 *
                                                                                  'a17 *
                                                                                  'a18 *
                                                                                  'a19 *
So 'a up to 't ... but then numbered 'as after that. I've no idea why it stops at 't, then just counts up from 'a. Interestingly this is the case with F# under Mono on Linux and under the official MS runtime on VS2017 on Windows. OCaml, which F# is heavily influenced by, does not exhibit this behaviour - it refers to the unknown types as a..z, a1..z1, a2...z2, etc.

That's reasonbaly interesting and all, but since I had already written a script to generate these F# files I figured I'd keep trying with more and more parameters to see what happens. After adding a hundred parameters at a time I soon hit an interesting warning between 400 and 500 params:

Warning: line too long, ignoring some characters
It turned out we hit some line-length restriction in either the parser or the readline equivalent that fsi.exe uses. A bit of experimentation later I found that when the function had 429 parameters it parsed just fine ... but 430 parameters caused the warning.

429 parameters: 4093 characters
430 parameters: 4104 characters

Minor sidebar - I was going to just say we hit a restriction where 4096 bytes is the max line length, but obviously 1 character isn't necessarily 1 byte if we're using some Unicode representation, and a nice way to check that we're using Unicode is to punch in identifiers that would definitely be in different encodings in an ASCII representation - I used Georgian and Czech characters:
> let სამი = 3;;
val სამი : int = 3

> let čtyři = 4;;
val čtyři : int = 4

> სამი + čtyři;;
val it : int = 7
When I changed my program to start each parameter with the Georgian character "ა" (i.e. let bar ა0 ა1... etc) which cannot be represented in a single byte in UTF-8 we hit the error after only 162 parameters. So it's not a character limit, but a buffer of bytes that we filled - and the size of the buffer is ... uh 4096 bytes.

Since I wanted to pull this thread until I hit some logical conclusion I tweaked my program to split the insanely long lines so they wouldn't hit this error and continued to add more parameters. The next issue I encountered was after trying a function with a couple thousand parameters, where I encountered the following error:

/home/sean/stdin(6636,1): error FS0039: The value or constructor 'x1328' is not defined. Maybe you want one of the following:
So it seems we've bumped up into another limit - somehow 1327 parameters is ok but F# loses track of the 1328th one, thinking it doesn't exist. Is this a bug? Probably, there should maybe be a more helpful error message. Is it important? Probably not, if your code contains a function with upwards of a thousand parameters then this is the least of your problems.

Debian - Building XMMS 1.2.11 on a modern linux system

Like most people I've recently been consuming all my media via paid streaming services like Netflix and iTunes. The other day however I needed to play an MP3 on my laptop running Debian and instinctively wanted to reach for xmms. Sadly nowadays the original xmms isn't available on Debian, only an "xmms2" package which is much newer and was reworked into some client/server model. I don't really want to figure out how to configure this correctly, to the extent that I was willing to build the original xmms from source ...

Trying the naive "./configure && make && sudo make install" method doesn't go very well when running Debian stretch:
sean@seoul ~/d/s/xmms-1.2.11> ./configure
checking build system type... x86_64-unknown-linux-gnu


*** The glib-config script installed by GLIB could not be found
*** If GLIB was installed in PREFIX, make sure PREFIX/bin is in
*** your path, or set the GLIB_CONFIG environment variable to the
*** full path to glib-config.
configure: error: *** GLIB >= 1.2.2 not installed - please install first ***
As it turns out I wasn't able to find a pre-built version of GLIB 1.x or (a subsequent dependency) GTK 1.x, I found some sources (GLIB 1.2 and GTK+ 1.2) but these were hitting an error when running ./configure which indicated that the CPU wasn't supported. These libraries pre-date the x86-64 era so my processor wasn't recognised. The fix was to simply drop in a newer config.sub. There was one more issue with the G_GNUC_PRETTY_FUNCTION macro but I resolved that too - I put them onto GitHub as glib-1.x and gtk-1.x in case anyone else wants to use this. Installing them is easy:
$ git clone
$ cd gtk-1.x
$ ./configure --prefix=/usr && make
$ sudo make install

$ git clone
$ cd glib-1.x
$ ./configure --prefix=/usr && make
$ sudo make install
Once these are in place we can grab the "latest" old XMMS sources from and build those:
$ curl -LO
$ tar -xzf xmms-1.2.11.tar.gz
$ cd xmms-1.2.11
$ ./configure && make
$ sudo make install

Then if all is well then the original (and best!) xmms should be installed into your path, so you can go download some lovely skin of a brushed aluminium late-90s Sony CD player ... though it might be a little bit tiny if you use a HiDPI screen:

Czech - Cases are hard

Grammatical cases are a common stumbling block for native English speakers learning another language. This might be because cases are sort of invisible in English and they're not taught in school so it's it's hard to see why they would even matter.

However in languages like Czech cases are a really important concept, and if you don't wrap your head around them you'll struggle to make yourself understood. To fully comprehend this importance we need an example - Petr and Pavel, who are not friends.

Accusative - Peter is punching Pavel

In Czech the verb "to punch" is "bít", whose third person conjugation is "bije" so if we want to describe this situation in Czech we might naively start out with something like this ...

Petr bije Pavel

However this isn't quite enough because it's not actually clear who is punching who. You should be able to rearrange the parts of this sentence in many ways in Czech without any ambiguity. But if we can rearrange the sentence as Pavel bije Petr or bije Pavel Petr then how do we tell our Czech friends who is the puncher and who is the punchee? This is where we need to modify case of some words to help clear things up a little.

In Czech we indicate the subject (puncher) using the nominative case. In our sentence this is Petr and the nominative case of Petr is simply Petr.

The object (the punchee) of the sentence is indicated using accusative case - which for Pavel is Pavla. Which gives us:

Petr bije Pavla

So hopefully you can see why exactly cases are so important, if you don't learn them you're going to confuse a lot of Czech people and have a lot of frustrating conversations. Let's take Petr and Pavel and explore some other cases.

Genitive - Petr reads Helena's book

If Petr is finished punching Pavel and just wants chill with his friend Helena's book, we need to use the Genitive case to indicate it belongs to her:

Petr čte knihu Heleny

Subject = nominative of Petr = Petr

Verb = third person singular conjugation of čist = čtu

Object = kniha in accusative case = knihu

Possessor = Helena in genitive case = Heleny

Dative - Petr reads to Pavel

If he decides to read the book to Pavel in a curious attempt at reconciliation, we need to use the Dative case:

Petr čte knihu Pavlovi

Receiver = Pavel in dative case = Pavlovi

Instrumental - Petr reads with Pavel

If this reconciliation is successful and Petr reads the book with Pavel we need to use the Instrumental case:

Petr čte knihu s Pavlem

Instrument = Pavel in instrumental case = Pavlem

Locative - Petr reads about Pavel

Maybe it's weird to describe Pavel as the "instrument", but just go with it because in the next sentence Petr is reading a book about Pavel and in this situation we use the Locative case:

Petr čte knihu o Pavlovi

preposition "o" (meaning "about") requires locative case of Pavel = Pavlovi

Vocative - Petr says goodbye to Pavel

Finally, to draw this ridiculous situation to a close Petr says goodbye to Pavel where we use the Locative case:

Na shledanou Pavle!

addressing Pavel requires vocative case of Pavel = Pavle!


This is only a quick-n-dirty summary restricted to the seven Czech cases, but it should indicate why each case is important. The Ústav pro jazyk český Akademie věd ČR (Language Institute of the Czech Academy of Science) have a really useful page if you want to see what a word looks like in different cases: Just enter your word into the "Slovníková část" section and hit "Hledej"

IIS - Annoying error with .NET Core

I deployed a .NET Core application recently into an environment which previously had only run .NET Framework apps, but got a tricky HTTP Error 500.19 - Internal Server Error suggesting there was a problem with my Web.config:

What was also peculiar was that when I tried to enable stdout logging using the IIS UI I got an error "There was an error while performing this operation" with blank "Details" and "Error" but with the "Filename" set to the path to my Web.config. This was true for any of the configuration sections:

However after a lot of head scratching and googling turned up nothing my colleague realised that this environment was missing the Windows Server Hosting bundle, available from the .NET Core downloads section under the "Other Windows Downloads" section. Once this was installed everything worked as expected.

Czech - Telling The Time

I've been learning Czech on and off for the last few years, but now and again I discover that there are some basic things that I never quite learned properly. The most recent of these was my ability to tell the time. When I wanted to learn this properly I found that there are very few places that teach this at the level I wanted - they were either too simplistic, misleading or written in a way that is much too hard to quickly digest. I ended up writing the below for myself and for anyone else who wants to learn.

To simplify each explanation I'm assuming you're familiar with the following:
  • what I mean by "Nominative", "Genitive" and "Accusative" case
  • the verb "být"
  • the noun "hodina"
  • numbers from 1 - 60

Additionally plurals behave kinda funny in Czech. My friends and I all first learned this when counting beer so I'm gonna use "beer plurals" as shorthand for the following behaviour when you have ...

  • one of something you give the noun in singular Nominative (jedno pivojedna hodina, jedna minuta)
  • two, three or four of something you give the noun in plural Nominative (dvě piva, dvě hodiny, dvě minuty)
  • five or more of something you give the noun in plural Genitive (pět pivpět hodinpět minut)

Quick Reference

Firstly in case you want to just quickly reference, here's a table that demonstrates most of the cases:

time Czech time Czech
1:00 je jedna hodina 4:00 jsou čtyři hodiny
1:10 je deset minut po jedné 4:10 je deset minut po čtvrté
1:15 je čtvrt na dvě 4:15 je čtvrt na pět
1:30 je půl druhé 4:30 je půl paté
1:45 je tři čtvrtě na dvě 4:45 je tři čtvrtě na pět
1:50 je za deset minut dvě 4:50 je za deset minut pět
2:00 jsou dvě hodiny 5:00 je pět hodin
2:10 je deset minut po druhé
5:10 je deset minut po paté
2:15 je čtvrt na tři
5:15 je čtvrt na sest
2:30 je půl třetí
5:30 je půl sesté
2:45 je tri čtvrtě na tři
5:45 je tři čtvrtě na sest
2:50 je za deset minut tři
5:50 je za deset minut šest hodin
3:00 jsou tři hodiny
3:10 je deset minut po třetí
3:15 je čtvrt na čtyři
3:30 je půl čtvrté
3:45 je tři čtvrtě na čtyři
3:50 je za deset minut čtyři

Whole Hours

If it's exactly H o'clock, we say something like "it is H hours". The word for "hours" is "hodina" and this is the first example of the beer plurals I described above. There are only 12 possibilities here so it's not too hard to just memorise the below:

Half Hours

Times that in English are exactly half past an hour in Czech are said to be a half of the next hour, so instead of "half past one" we say something like "a half of two". There's a minor complication - the hour is given in the genitive singular feminine. They behave like adjectives because ultimately they are adjectives modifying the noun "hodina". So je půl jedné means "it is half of the first (hour)"

Again there are only 12 combinations so if my description doesn't make much sense you can just memorise the below without too much trouble:

Quarter Hours

Similar to half-hours when we talk about quarter past or quarter to an hour in Czech we talk about quarters of an hour. So 12:15 is "a quarter of one" or čtvrt na jednu (the hour part is in the Accusative case) and 12:45 is "three quarters of one" or tři čtvrtě na jednu (with čtvrtě being the plural of čtvrt)

Minutes After The Hour

If we have a time up to half past the hour, we write it really similar to the English - so 1:10 is "je jedna minuta po jedné". It's tricky:
  • "minuta" behaves like our "beer plurals" - 1 minuta, 2/3/4 minuty, 5/6/7/etc minut
  • the hour is the genitive singular, and since "hodina" is feminine we have jedné, druhé, třetí, etc

Minutes Before The Hour

Finally for minutes before an hour, we write something like "in M minutes it is H o'clock" - so 1:50 this is "je za deset minut dve". Again we have the "beer plural" for minutminuta, minuty, minut.

24 Hour Time. 

When reading from a watch, computer of phone it seems many Czechs will just say the hour and limit component separately. For example the other day when we were leaving the Maximus spa my girlfriend asked the lady at the counter what time the shuttle bus to the nearest tram stop left, she pulled up a schedule and said “šestnáct dvacet” - 16:20.

The only quirk is 1-9 minutes past the hour where you say the minutes with a leading nula - so 16:05 would be šestnáct nula pět.

Firefox Developer - Gnome and Debian 9 quickstart

Using Firefox on Debian 9 is a little frustrating as the packages available from default APT sources are older “ESR” releases (firefox-esr, 52.0 at the time of writing). Getting the Dev or Nightly builds is pretty straight forward but if you use Gnome you probably want a launcher, and it might not be very obvious how to do this.

First grab the sources or prebuilt binaries:

    $ curl -LO ""

Extract them into /opt/firefox-dev:

    $ tar -xjf firefox-57.0b12.tar.bz2 && sudo mv firefox /opt/firefox-dev

Open up a text editor and create /use/share/applications/firefox-dev.desktop as follows

    [Desktop Entry]
    Name=Firefox Dev
    Comment=Browse the World Wide Web
    GenericName=Web Browser
    X-GNOME-FullName=Firefox Dev Web Browser
    Exec=/opt/firefox-dev/firefox %u

Copy the icon and run gtk-update-icon-cache so that the icon appears as expected.

    $ sudo cp /opt/firefox-dev/browser/icons/mozicon128.png /usr/share/icons/hicolor/128x128/apps/firefox-dev.png
    $ sudo gtk-update-icon-cache -f /usr/share/icons/hicolor

And that's it! You should have a nice desktop icon for Firefox Developer Edition you can use. I also did the same for Firefox Nightly:

For updates you can clean out the directory and repeat the process with the latest tar.bz2 file ... or you can change the permissions in the firefox-dev directory so you have write access, and auto-updates will work.