September on Linux, Part 2: keyboard, applications and Hyprland

September on Linux, Part 2: keyboard, applications and Hyprland
Photo by Derek Oyen / Unsplash

The first file I needed on my new Linux installation was a keyboard layout stored on my NAS. I wanted to type the same Czech characters and punctuation that I could type on the Mac, and I had already exported the Apple layout rather than trying to remember every Option-key combination. Before I could use it, I needed to mount the share.

This article collects the desktop configuration from my first week: the SMB mount, the keyboard conversion, Japanese input, certificates, applications and shortcuts. The shell examples below use Bash syntax where they contain variables or substitutions; the configuration files themselves are independent of which interactive shell I use.

Mounting NAS under my home directory

I use TrueNAS with an SMB share. Dolphin could browse the server, but I wanted a stable path that scripts and other applications could open directly. I settled on ~/NAS, with a capital NAS because this is 2026 and directory names don’t need to be lowercased.

The client package and mount directory are straightforward:

sudo pacman -S cifs-utils
mkdir -p ~/NAS
sudo mkdir -p /etc/samba
sudoedit /etc/samba/credentials-nas

The credentials file contains the SMB account details:

username=milan
password=haha_wouldn't_it_be_funny_if_I_accidentally_copypasted_it_here

After saving and exiting the editor:

sudo chmod 600 /etc/samba/credentials-nas
id -u
id -g

My local UID and GID were both 1000. This is the corresponding /etc/fstab entry, with the server and share replaced by placeholders:

//hactar.lan/milan /home/milan/NAS cifs credentials=/etc/samba/credentials-nas,uid=1000,gid=1000,vers=3.1.1,iocharset=utf8,_netdev,nofail,x-systemd.automount,x-systemd.idle-timeout=600 0 0

Use an absolute path in fstab; it doesn’t expand ~. Change the username in /home/milan/NAS and the UID/GID for another computer. The uid and gid options control the local ownership presented by the mount. The credentials authenticate to the server.

x-systemd.automount makes access to the directory trigger the SMB mount. _netdev identifies it as a network filesystem, nofail lets boot continue when it isn’t available, and the idle timeout allows it to unmount after ten minutes without activity. A program that accesses an unavailable share can still wait for a network timeout; this doesn’t make missing storage instantaneous.

After editing fstab, the generated automount unit needs to be loaded and started:

sudo systemctl daemon-reload
sudo systemctl start home-milan-NAS.automount
ls ~/NAS
findmnt ~/NAS

For a different path, obtain the unit name with:

systemctl list-units --type=automount --all

I initially couldn’t find the automount unit at all. I had saved the file in the editor launched by sudoedit, but I hadn’t exited the editor 🤦 sudoedit was still holding the temporary copy, so systemd was reading the old fstab. Exiting the editor, reloading and starting the unit fixed it. Listing ~/NAS finally showed my files.

The next day the NAS interrupted the experiment for a less trivial reason. One disk repeatedly disappeared, with ATA timeouts and link resets in the logs 😱 Reseating it didn’t help, and it was still missing in a bay where another disk worked. I put the good disk back and was left with three of four drives and a degraded pool while arranging a replacement. This wasn’t caused by CachyOS, but it did make me postpone unnecessary NAS activity during the first week, including a proper Jellyfin playback test.

Exporting the Apple keyboard on the Mac

I use Apple’s Czech QWERTY layout, including its typography and dead keys. A similarly named Linux layout hadn’t produced the result I wanted, like I mentioned in part 1 of this series. The useful starting point was the layout actually active on the Mac.

On macOS, I used Ukelele:

  1. Select Czech – QWERTY as the active macOS input source.
  2. Open Ukelele and choose File → New From Current Input Source.
  3. Add metadata to the keyboard layout if wanted/needed.
  4. Save the captured layout as a .keylayout file.

That file describes the key mappings and modifier states. I gave it to ChatGPT to convert into a Kalamine TOML definition, and Kalamine then generated the platform files. To repeat this on a Mac with Homebrew:

brew install pipx
pipx install kalamine
kalamine build Czech-Apple-QWERTY.toml # output from ChatGPT

The build writes its outputs under dist/. The files needed on Linux are the generated .xkb_keymap and .xkb_symbols; I transferred the generated files through the NAS. Kalamine can also produce macOS and Windows files, but on Windows, I had already previously built an Apple-like Czech keyboard layout, and on macOS… well, the need for that is somewhat diminished 😏

The conversion preserved the four main printable layers: base, Shift, Option/AltGr and Shift+Option/AltGr (AltGr always sounded like a very angry version of the Alt key to me…). The conversion report compared 196 main-key positions with the captured source. Its custom dead-key slot went to the Czech caron behaviour. There was a limitation around Apple’s less commonly used comma-accent combinations for Į and Ų, but I have never used those characters in my life before, so… we’re all good!

Installing the generated files on Linux

Linux doesn’t need Ukelele or Kalamine installed – only the Kalamine output was needed, so I copied both generated XKB files from the NAS into a local configuration directory so the keyboard won't depend on the share being mounted at login:

mkdir -p ~/.config/xkb
cp ~/NAS/Klávesnice/Apple\ QWERTY/czapple.xkb_* ~/.config/xkb

My first working setup loaded the full keymap through the input section in ~/.config/hypr/config/inputs.lua:

hl.config({
    input = {
        kb_file = "/home/milan/.config/xkb/czapple.xkb_keymap",
    },
})

Add that setting to the existing input table, keeping the other settings. hyprctl devices showed the active keymap as “Apple-style Czech QWERTY for macOS, Linux and Windows”. Noctalia briefly labelled it US, then corrected itself; the actual typed characters were the useful test.

Later, while setting up XIV, I found that the desktop layout working didn’t mean Wine was using it correctly. I installed the generated symbols as the system’s named custom layout:

sudo cp ~/.config/xkb/czapple.xkb_symbols /usr/share/X11/xkb/symbols/custom
sudo chmod 644 /usr/share/X11/xkb/symbols/custom

Then I removed the kb_file setting and put this in the same input table:

hl.config({
    input = {
        kb_layout = "custom",
    },
})

After saving the file, my Czech Apple keyboard layout was active in both native and Wine applications, so the only cool thing remaining would be to also have it available on the login screen – which is now possible thanks to a system-wide installation!

The login screen has its own keyboard setting. For Noctalia Greeter, the documented location is /var/lib/noctalia-greeter/greeter.toml:

[keyboard]
layout = "custom"
numlock = true

Japanese input with Fcitx5 and Mozc

The custom layout handles physical keys. Japanese conversion is provided by Mozc through Fcitx5. My requirements were romaji input, 漢字 conversion, holding Shift for カタカナ, and keeping the input mode consistent when I switched windows.

The package set used for this setup was Fcitx5, Mozc, the configuration tool and the GTK/Qt integration packages. On CachyOS, the installation recipe is:

sudo pacman -S fcitx5 fcitx5-mozc fcitx5-configtool fcitx5-gtk fcitx5-qt
fcitx5 -d
fcitx5-configtool

I started it manually before changing to autostart later. In the configuration tool, add Mozc alongside the direct keyboard input method. I replaced the initial English (US) keyboard entry with Czech; the custom Apple-style mapping continued working, and Noctalia notification finally alternated between Czech and Mozc.

These were the settings that made it behave the way I wanted:

Where Setting
Fcitx5, Global Options Trigger Input Method: Ctrl+Space
Fcitx5, Global Options Share Input State: All
Fcitx5, Global Options Reset state on Focus In: No
Mozc’s own settings window Input mode: Romaji
Mozc’s own settings window Keymap: Kotoeri
Mozc, Advanced → Input Assistance Shift key mode switch: Katakana
Mozc addon configuration Always use Japanese keyboard layout: off

The katakana setting is inside the actual Mozc settings window, one level beyond the outer Fcitx addon settings – a distinction which cost me quite some searching. Sharing input state globally also fixed the more irritating behaviour where switching applications changed whether I was typing Japanese – like Windows used to do in the past.

For a quick test, type nihongo, check that it composes as にほんご, then press Space for 日本語. Check the Shift behaviour and switch between two applications while Mozc is enabled. Finally, turn it off and check the Czech punctuation again. I confirmed these settings worked in my desktop setup.

We can have Hyprland autostart Fcitx5 by adjusting ~/.config/hypr/config/autostart.lua:

hl.on("hyprland.start", function()
  hl.exec_cmd("fcitx5 -d")
end)

Finally, add a Catppuccin Macchiato theme to match the rest of the system.

Private certificates and Vivaldi

My Vaultwarden instance uses a certificate issued by my private CA. I added the root certificate to system trust with:

cp ~/NAS/Dokumenty/betelgeuse-ca.crt ~/Dokumenty
sudo trust anchor --store ~/Dokumenty/betelgeuse-ca.crt
sudo update-ca-trust

I checked the service with curl and OpenSSL, then opened it in Vivaldi. The system checks and browser access worked, as did the Bitwarden extension:

curl -I https://vaultwarden.lan/
openssl s_client -connect vaultwarden.lan:443 -servername vaultwarden.lan -verify_return_error </dev/null

The Bitwarden desktop application, CLI, and Noctalia plugin also worked initially. The CLI later needed explicit CA configuration in the XIV login script because it runs through Node.js and was launched outside my interactive Fish environment.

Setting Vivaldi as the default browser exposed another environment issue. I found BROWSER=firefox in the session configuration, changed it to Vivaldi, and needed to log out and back in for the new session to inherit it. In this CachyOS setup, ~/.config/uwsm/env was one place defining it:

export BROWSER=vivaldi

xdg-settings also objected while BROWSER was set. Temporarily removing the variable for that command let me set the desktop association:

set -e BROWSER
xdg-settings set default-web-browser vivaldi-stable.desktop
xdg-mime default vivaldi-stable.desktop x-scheme-handler/https
xdg-mime default vivaldi-stable.desktop x-scheme-handler/http
xdg-mime default vivaldi-stable.desktop text/html
xdg-settings get default-web-browser

After the relog and association change, links opened in the browser I wanted. Vivaldi also reported hardware-accelerated video decode, which is nice.

Applications and package commands

I had to relearn pacman’s flags after years of Homebrew. The ones I actually need to remember are:

pacman -Ss package-name       # search repository packages
pacman -Si package-name       # inspect a repository package
pacman -Q package-name        # query an installed package
sudo pacman -Syu              # update the system
sudo pacman -S package-name   # install a package

The pacman manual explains the operation letters: S works with repository packages, Q queries installed ones. AUR builds add another step; I used paru for XIVLauncher-RB later.

Vesktop worked, including screen sharing. Plezy logged into Jellyfin, but with the NAS degraded, I left playback testing for later. Spotify worked through spotify-launcher and went on workspace P on the second display. OBS could capture a window; switching Output settings to Advanced exposed the H.264 and HEVC hardware encoder choices that weren’t visible in the initial view. DaVinci Resolve installed with sudo pacman -S davinci-resolve and launched. I hadn’t yet completed an edit or export in it.

Those are different levels of testing, but they were enough to get the everyday desktop usable while I concentrated on XIV.

Keeping the shortcuts I already knew

I wanted Super+letter to switch to a workspace, as it did in AeroSpace. A and X went on the main display, D and Q on the second. I left Vivaldi placement manual because I wanted several browser windows across workspaces A and Q. Super+Space stayed the Noctalia launcher, Ctrl+Space belonged to Japanese input, and Super+Return opened a terminal. I kept Super+V for the clipboard and gave up V as a workspace name.

The Czech number row needed physical-key bindings. The key that would be 4 on a US layout produces č on mine, so these working screenshot bindings use its keycode:

local mainMod = "SUPER"
local noctCall = "noctalia msg "

hl.bind(mainMod .. " + code:13",
    hl.dsp.exec_cmd(noctCall .. "screenshot-fullscreen"))
hl.bind(mainMod .. " + SHIFT + code:13",
    hl.dsp.exec_cmd(noctCall .. "screenshot-region"))

That gives me the full screenshot and region-selection actions without changing the typing layout.

Two small input settings made a large difference to daily use:

hl.config({
    input = {
        natural_scroll = true,
        follow_mouse = 2,
    },
})

Natural scrolling immediately matched what my hand expected. follow_mouse = 2 gave me the unfocused-window scrolling behaviour I wanted without moving keyboard focus into the window under the pointer.

I didn’t manage to reproduce every part of AeroSpace. I tried hy3 for equal-column layouts, worked through a missing build dependency and a build failure, then still disliked the resizing behaviour. By this point, though, I could type, browse, use my NAS and move around the desktop comfortably – so even though some habits will have to be readjusted, it doesn’t seem like I’ll have to relearn much.

I could finally spend the next session on the game itself.