Dealing with "No Signal" on HDMI Input

Illustration

For my media PC I use old Intel’s gen 11 board. As I upgraded my Framework laptop, it just made sense to use it. Bazzite works flawlessly on it. At least until paired with my Samsung “smart” TV.

I might be turning into an old man yelling at clouds but it seems that smart TVs are getting dumber and dumber. My PC is connected to TV via HDMI cable and it works wonderfully. Until TV is off for some time. Then suddenly, TV cannot find HDMI source.

I already wrote about this so I will spare you details on how I figured the TV was to blame. Suffice it to say that I found the fix even back then. But now I had an issue with my fix. It was too slow.

You see, I decided to use 1 minute timer. Which means that, once TV has gone crazy, one had to wait a minute at worst. And that caused some impatience in my household. So, I needed a faster detection…

The new script at /usr/local/bin/dp-reconnect looks something like this:

#!/usr/bin/env bash

while(true); do
    SLEEP_INTERVAL=$(( 3 - `date +%S` % 3 ))
    sleep $SLEEP_INTERVAL

    DP_CONNECTED=0
    for DP_PATH in /sys/class/drm/card1-DP-*; do
        DP_STATUS=$( cat "$DP_PATH/status" | grep '^connected$' | xargs )
        if [[ "$DP_STATUS" == "connected" ]]; then
            DP_CONNECTED=1
            break
        fi
    done

    if [[ $DP_CONNECTED -ne 0 ]]; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') Display $( basename "$DP_PATH" ) connected"
    else
        echo "$(date '+%Y-%m-%d %H:%M:%S') No connected displays"
        chvt 2
        sleep 1
        chvt 1
    fi
done

Instead of “one-shot”, this script loops forever. The very first calculation is there just to slow things a bit and ensure payload is executed every 3 seconds. And yes, this could have been replaced with sleep 3 without any functionality loss. However, I like to “align” my execution times so I did it in slightly more complicated manner.

To run this code, we can simply create /etc/systemd/system/dp-reconnect.service with the following content:

[Unit]
Description=Switch terminal if no DP is connected

[Service]
ExecStart=/usr/local/bin/dp-reconnect
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

With all files in place, the only remaining thing was enabling the service:

sudo systemctl daemon-reload
sudo systemctl enable --now dp-reconnect.service

Now my TV reacts much faster, hopefully putting this story to an end.

Chrony Tracking in Milliseconds

If you are running NTP server based on Chrony, you are probably checking its status too. For me chronyc tracking has been almost perfect. The only thing I dislike is having seconds in outputs. For any decent NTP server, these values are sub-millisecond and thus output has a lot of zeros.

Fortunatelly, changing seconds to milliseconds is easy:

chronyc tracking | awk '
  /seconds/ {
    sub(/seconds/, "milliseconds")
    for (I=1; I<=NF; I++) {
      if ($I ~ /^[+-]?[0-9.]+$/) {
        $I = sprintf("%.6f", $I * 1000)
      }
    }
  } { print }'

This awk command will match any line that has word seconds with milliseconds and then multiply any number it finds with 1000. Once printed again, this means all numbers are in milliseconds, just as we wanted.

This does work but it will also adjust Update interval, which is one field that makes sense to be in seconds. So, we can limit how many changes are to be done.

chronyc tracking | awk '
  /seconds/ && count < 5 {
    sub(/seconds/, "milliseconds")
    for (I=1; I<=NF; I++) {
      if ($I ~ /^[+-]?[0-9.]+$/) {
        $I = sprintf("%.6f", $I * 1000)
      }
    }
    count++
  } { print }'

Here we just add counter to limit our changes to only the first 5 fields. Since chronyc tracking output is relatively stable, we got to “cheat” a bit.

If you’re using this as input for something else, this is sufficient. However, if you plan to look at it on the command line, you will notice that changed lines also had their spaces messed up. For that, here is the final form.

chronyc tracking | awk '
  /seconds/ && count < 5 {
    sub(/seconds/, "milliseconds")
    for (I=1; I<=NF; I++) {
      if ($I ~ /^[+-]?[0-9.]+$/) {
        $I = sprintf("%.6f", $I * 1000)
      }
    }
    count++
  } { print }' | awk '{
    sub(/:/, "\t")
    print
  }' | column -t -s$'\t' -o:

Here we make use of the column command to make a nice table using tab (\t) as an input and colon (:) as output separator. Reason we cannot use colons for input separation lies in ref time field which includes colons in the time string. So, in order for this to work, we also adjust input to column by replacing the first colon with tab and leaving the rest intact.

Now your output should look something like this:

Reference ID    : 81060F1C (time-a-g.nist.gov)
Stratum         : 2
Ref time (UTC)  : Sun May 24 00:00:01 2026
System time     : 0.207166 milliseconds fast of NTP time
Last offset     : 0.010373 milliseconds
RMS offset      : 0.052737 milliseconds
Frequency       : 23.102 ppm slow
Residual freq   : +0.000 ppm
Skew            : 0.006 ppm
Root delay      : 24.370797 milliseconds
Root dispersion : 1.106555 milliseconds
Update interval : 1033.8 seconds
Leap status     : Normal

PTClipboard

As I was switching to Linux, so did my code base. I admit, most of code didn’t really care on which platform it was running but I wasn’t so fortunate with all of it. One annoying issue was clipboard handling. Linux simply did its clipboard differently. So I decided to solve this problem for me. Twice.

My first attempt at solving this problem was a simple X11 clipboard library. It worked like a charm. And then the whole migration toward Wayland happened. While most desktop environments still have X11 compatibility, writing was on the wall for this one.

So, I decided to expand functionality a bit. The new library still supports handling X11 clipboard text. And to it adds both Wayland and Windows support. That way the same library can be used in a really cross-platform manner. Well, almost cross-platform - MacOS support is missing as I don’t really have any devices to test it on.

Now, this library is small in scope - only plain-text is supported. While I do have plans to adjust this a bit, I doubt it will go much beyond this. Idea is to have it as simple as possible, not to provide the full clipboard access. And simple it is:

using Medo;

PTClipboard.SetText("My text.");
var text = PTClipboard.GetText();

And that is pretty much all. Yes, there is also support for primary selection under linux (aka as “middle-click clipboard”) with syntax just slightly longer:

using Medo;

PTClipboard.Selection.SetText("My text.");
var text = PTClipboard.Selection.GetText();

And setup is pretty much non-existent as code should detect system it is running on automatically.

You can get package at NuGet and check source code at GitHub.

Reinstalling Kubuntu 26.04 on Encrypted ZFS Root

Recently I wrote about installing Kubuntu on ZFS. However, what to do if you already have a working setup and you just want reinstall OS?

In my case, I want to preserve my ZFS pool. Additionally, I want to retain all my ZFS datasets, except for the dataset that holds my file system. And yes, system is a bit peculiar but it’s nothing too special.

As one would assume, procedure is quite similar to the original installation. In order to fully understand why certain steps are in, I would suggest reading the original post. If you have a different setup, steps will differ, but the gist of it will remain the same.

As before, we start with root terminal.

sudo -i

Step to install the packages is also the same, as one would expect.

apt update
apt install -y gdisk zfsutils-linux

I setup the variables to help me to work.

DISK1=/dev/disk/by-id/<disk>
HOST=<host>
USERNAME=<user>

Since we don’t want to repartition disk, we can immediately skip to the step of fetching partition UUIDs.

DISK1P1=`blkid -s PARTUUID -o value $DISK1-part1`
DISK1P2=`blkid -s PARTUUID -o value $DISK1-part2`
DISK1P3=`blkid -s PARTUUID -o value $DISK1-part3`
DISK1P4=`blkid -s PARTUUID -o value $DISK1-part4`

As my setup is on top of LUKS, it is necessary to open the mapper.

cryptsetup luksOpen \
    --persistent --allow-discards \
    --perf-no_write_workqueue --perf-no_read_workqueue \
    /dev/disk/by-partuuid/$DISK1P4 $DISK1P4

And now we can import the pool. Note that we will load it at /mnt/install so that it will match the original setup.

zpool import -N -R /mnt/install -f ${HOST^}

Now we can destroy our previous system. There is no coming back after this step.

zfs destroy ${HOST^}/System

Alternatively, if you want to keep it for reference, you can just rename it.

zfs rename ${HOST^}/System ${HOST^}/System.Old
zfs set mountpoint=/oldsys ${HOST^}/System.Old

With this in place, we get to create a new system dataset.

zfs create \
    -o devices=on \
    -o canmount=noauto -o mountpoint=/ \
    ${HOST^}/System
zfs mount ${HOST^}/System

Importantly, I also mount my home dataset so that my user stuff is there if I need it.

zfs mount ${HOST^}/Home

Lastly, we clean boot and EFI partition.

dd if=/dev/zero of=/dev/disk/by-partuuid/$DISK1P1 bs=1M
dd if=/dev/zero of=/dev/disk/by-partuuid/$DISK1P2 bs=1M

With all this in place, you can simply continue with the original guide at the step of formatting boot partition (mkfs.ext4). And that’s it - your OS will be as new.

Thou Shalt Not Init 6

I have been using different *nixes for a while now. Long enough that one simply gets used to some things. For example, I still turn off my OS using init 0 and reboot it using init 6. It’s just a habit I couldn’t shake.

Well, I guess those days are over. As of Ubuntu 26.04 these old commands will now give error:

Excess arguments

Well, there go decades of muscle memory…