Categories
Code Linux

project change directory

I generally keep my development projects under a common directory like ~/dev/ or ~/projects/. Here is bash shell script code which I use to quickly jump into and between projects on the command line. It includes auto completion, which is a pretty important part of its usefulness. You can put in your ~/.bashrc file and adapt the PROJECTS_PATH variable to suit your own needs.

# Project cd
PROJECTS_PATH=~/dev

# Main command function
pcd() {
    local args=() op=cd opt OPTIND
    # Option parsing ergonomics: allow options anywhere in command line
    while [ $# -gt 0 ]; do
        while getopts 'p' opt; do
            [ $opt = p ] && op=pushd || return 1
        done
        shift $((OPTIND-1)) && OPTIND=1
        [ $# -gt 0 ] && args+=("$1") && shift
    done
    
    local path="$PROJECTS_PATH/${args[0]}/${args[1]}"
    if [ "${args[0]}" = .. ]; then
        local gitroot=$(git rev-parse --show-toplevel 2>/dev/null)
        if [ -d "$gitroot" ]; then
            path="$gitroot/${args[1]}"
        fi
    fi
    
    if [ -d "$path" ]; then
        $op "$path"
        [ $op = cd ] && pwd
    fi
}

# Completion function for pcd
_pcdcomp() {
    [ "$1" != pcd ] && return 1
    COMPREPLY=()

    # Current word being completed
    local word=${COMP_WORDS[$COMP_CWORD]}
    # IFS must be set to a single newline so compgen suggestions with spaces work
    local IFS=$'\n' pdir_idx= sdir_idx= i comp_opt=$(compgen -W '-p' -- "$word")

    # Scan command line state
    for ((i=1; i<${#COMP_WORDS[*]}; i++)); do
        if [ "${COMP_WORDS[$i]:0:1}" != - ]; then
            [ -z "$pdir_idx" ] && pdir_idx=$i && continue
            [ -z "$sdir_idx" ] && sdir_idx=$i
        elif [ "${COMP_WORDS[$i]}" = '-p' -a $i -ne $COMP_CWORD ]; then
            comp_opt=
        fi
    done

    # By default, all completions are suffixed with a space, so cursor jumps to
    # next command argument when a completion is selected uniquely, except for
    # the project subdir argument. We handle this manually, since adjusting the
    # 'nospace' option dynamically with compopt has proven to be unreliable.
    local add_space_to_completions=1
    
    # Provide completions according to command line state
    if [ $COMP_CWORD = ${pdir_idx:--1} ]; then
        # State: project argument
        
        if [ "${word:0:1}" = . ]; then
            COMPREPLY=('..')
        else
            COMPREPLY=($(cd "$PROJECTS_PATH" && compgen -X \*.git -d -- "$word"))
        fi
        if [ "$comp_opt" ]; then
            COMPREPLY+=("$comp_opt")
        fi
    elif [ $COMP_CWORD = ${sdir_idx:--1} ]; then
        # State: project subdir argument
        
        local project_root="$PROJECTS_PATH"/"${COMP_WORDS[$pdir_idx]}" git_root
        if [ "${COMP_WORDS[$pdir_idx]}" = .. ]; then
            git_root=$(git rev-parse --show-toplevel 2>/dev/null) && project_root=$git_root
        fi
        
        COMPREPLY=($(cd "$project_root" 2>/dev/null && compgen -X \*.git -S/ -d -- "$word"))
        if [ ${#COMPREPLY[*]} -gt 0 ]; then
            # Avoid space after subdir argument, to allow for drilling while completing
            add_space_to_completions=
        elif [ -z "$word" ]; then
            # No available subdirs for selected project and empty current arg, offer '.' and options
            COMPREPLY=('.')
            if [ "$comp_opt" ]; then
                COMPREPLY+=("$comp_opt")
            fi
        fi
    elif [ "$comp_opt" ]; then
        # State: end of regular args or other
        
        COMPREPLY+=("$comp_opt")
    fi
    
    # Post process, do shell safe name quoting and possibly add space to each completion:
    for ((i=0; i<${#COMPREPLY[*]}; i++)); do
        COMPREPLY[$i]=$(printf "%q${add_space_to_completions:+ }" "${COMPREPLY[$i]}")
    done
}

# Bind completion function to command:
complete -o nospace -F _pcdcomp pcd

You can also find the code on Github: https://github.com/oyvindstegard/pcd

How to use

After loading the code, type pcd and hit TAB to see completion of all project directories. Hit ENTER to jump to a selected project. It will also complete into a project sub-directory as optional second argument. To jump up to a project root directory you can use pcd .. – this works if it is a git repository. You can combine it with a second arg to drill into another directory tree of the same project. Lastly, you can use the option -p to use pushd instead cd when changing directory.

Screencast which shows how the pcd command works.
Screencast which shows how the pcd command works.

Notes on implementation of a completion function

As you may have noticed, the code for the programmable completion is a lot more complex than the actual command. In my experience, getting ergonomically pleasing and sufficiently intelligent command line completion tend to become more finicky than what I envision initially. The command line, argument types and cursor position combined constitute several intermediate and final states to handle. Typically, the current word to complete will depend on both preceding and succeeding command line context.

Things to consider

  • Adding command options in addition to regular arguments complicates matters. You will have more state to handle, and you shouldn’t provide completion for the same option twice, unless that is valid for your command.
  • You need to parse the entire command line state every time your completion function is invoked, so you have good enough contextual information about what completions to provide at the cursor. Don’t offer to complete something which would produce an invalid command.
  • You really need to learn exactly how the shell behaves with regard to the completion variables, the completion related built-in commands and options.
  • Avoid slow/heavy commands in your completion function, because user experience will suffer greatly when pressing TAB causes the shell to hang for a long time without any feedback. Completion data which is expensive to compute or fetch should be cached.
  • When debugging a completion function, you don’t really want to output anything directly to the terminal, as it will visually interfere with the command your are testing and printed completions, causing a jarring experience. Instead, what I recommend is to append whatever debug logging you have to a dedicated file and tail that file in another terminal window while testing.
Categories
Code Linux

Query installed packages

Sometimes I just want to quickly find an installed package and show its version. Maybe I don’t even care if it’s a deb, snap or rpm package. Maybe I’d just like know if it is installed at all. And I’d like to search for it using words.

Here is a shell function qi (query installed) that can be added to ~/.bashrc or similar, which will list all installed deb, snap and/or rpm packages with version in a friendly format. You can easily narrow down the results by supplying words to filter. It mostly uses awk(1) to accomplish the task.

function qi() {
  (
      type dpkg-query &>/dev/null && \
        dpkg-query -W -f '${db:Status-Abbrev} ${Package} ${Version}\n'
      type snap &>/dev/null && \
        snap list|sed -e '/^Name/d' -e 's/^/snap /'
      type rpm &>/dev/null && \
        rpm -qa --qf 'rpm %{NAME} %{VERSION}-%{RELEASE}\n'
  ) | awk -v argline="$*" \
      'BEGIN { split(argline, fwords, / +/) }
       function include(package) {
         for (i in fwords) {
           if (index(package, tolower(fwords[i])) == 0) {
             return 0
           }
         }
         return 1
       }
       /^(ii|rpm)/ && include($2 $3) {
         printf("%-30s %s\n", $2, $3)
       }
       /^snap/ && include($2 $3 "[snap]") {
         printf("%-30s %-20s [snap]\n", $2, $3)
       }
      '
}

Update 17.12.2022: this version works seamlessly across Redhat and Debian based distros (also those not using snap).

The function consists of two main parts:

  1. A subshell which lists all installed packages in a particular format. It tests for available package managers and queries with those that are available. Its output stream is piped directly to an awk program.
  2. The awk program which does the filtering (highlighted in dark blue).

List all installed packages

$ qi
accountsservice                0.6.55-0ubuntu12~20.04.5
acl                            2.2.53-6
acpi-support                   0.143
acpid                          1:2.0.32-1ubuntu1
adduser                        3.118ubuntu2
adwaita-icon-theme             3.36.1-2ubuntu0.20.04.2
aisleriot                      1:3.22.9-1
alsa-base                      1.0.25+dfsg-0ubuntu5
alsa-topology-conf             1.2.2-1
alsa-ucm-conf                  1.2.2-1ubuntu0.13
[... 1945 more lines not shown here]

List packages matching words

$ qi chrom
chrome-gnome-shell             10.1-5
libchromaprint1                1.4.3-3build1
chromium                       107.0.5304.121       [snap]

Snap packages can be distinguished from debs by the [snap] marker in the third column.

$ qi image linux
linux-image-5.14.0-1054-oem    5.14.0-1054.61
linux-image-5.15.0-53-generic  5.15.0-53.59~20.04.1
linux-image-generic-hwe-20.04  5.15.0.53.59~20.04.21
linux-image-oem-20.04d         5.14.0.1054.52

Use multiple words to narrow down, and the ordering does not matter. All supplied words must be substrings of the package name and version for a match to occur.

More examples

List only snap packages:

$ qi \[snap\]
bare                           1.0                  [snap]
chromium                       107.0.5304.121       [snap]
core                           16-2.57.4            [snap]
core18                         20221103             [snap]
core20                         20221027             [snap]
cups                           2.4.2-4              [snap]
docker                         20.10.17             [snap]
[...]

Search within -dev packages:

$ qi -dev ssl
libssl-dev                     1.1.1f-1ubuntu2.16
$ qi gtk dev
libgtk-3-dev                   3.24.20-0ubuntu1.1

If you want grep filtering, just use grep:

$ qi|grep -E 'ssl|tls'
libcurl3-gnutls                7.68.0-1ubuntu2.14
libgnutls30                    3.6.13-2ubuntu1.7
libio-socket-ssl-perl          2.067-1
libneon27-gnutls               0.30.2-4
libnet-smtp-ssl-perl           1.04-1
libnet-ssleay-perl             1.88-2ubuntu1
libssl-dev                     1.1.1f-1ubuntu2.16
libssl1.1                      1.1.1f-1ubuntu2.16
openssl                        1.1.1f-1ubuntu2.16
[...]

Other package formats

It should be a simple matter to add support for other package formats. Just add to the commands which supplies the package lists in the first sub shell, keeping in mind the common output format. Then possibly add prefix patterns for the awk program to recognize and match on lines from other package managers.

Categories
Hardware Linux

Ubuntu 20.04 on Thinkpad T14 gen 2 (Intel)

AnnouncedLate 2021
CPU, GPU, RAMIntel i5-1135G7 @ 2.40GHz 4 cores (8 threads), Iris Xe graphics, 16GiB soldered DDR4 3200 MHz (and one free slot for upgrades)
Screen14″ FHD (1920×1080) IPS 300nits Anti-glare
StorageSK Hynix NVMe 512GB
Lenovo part number 20W000R1MX (Lenovo Product Specification Reference)

TL; DR

This is a laptop with a slightly boring, but professional and discrete design, which I like. It’s a sturdy workhorse and has a quality feel to it – typical business segment. Overall Ubuntu Linux works great and this is probably as good as it gets regarding out of the box Linux compatibility, unless going for specialized commercial Linux offerings like a System76.

There are some issues with TrackPoint smoothness relating to kernel input drivers, and you may experience issues with the keyboard.

Read on for details.

What works fine

Installation alongside side Windows 10 Pro

For dual boot installation, I freed up around 300 GB of storage space for Ubuntu using Windows disk administration. Installation of Ubuntu 20.04 on new partition went without issues. Grub becomes primary boot loader with Windows as menu option.

Graphics and screen

Both Wayland and Xorg sessions work fine and perform well. Brightness controls are functional and screen is surprisingly bright and crisp.

Power management and cooling

The CPU cores are automatically clocked between 1,2GHz and 3,5GHz, depending on load. The fan rarely makes any noise at all on normal light weight usage, and it is subtle even when it needs to run faster.

The battery is a 51Wh SMP, and charge thresholds can be controlled at runtime. I typically set mine to stop charging at 85% when at home, to increase battery lifespan. You can control this by writing percentages to the files:

/sys/class/power_supply/BAT0/charge_stop_threshold
/sys/class/power_supply/BAT0/charge_start_threshold

The laptop has a USB-C port for charging, and when using the accompanying 65W A/C power supply, charging is fast. It will also accept charge from other power supplies, like common mobile quick chargers, but the BIOS may issue a warning on boot if the charger is too weak.

Update 2023-01-14: you can check out the following script for a handy tool that shows Thinkpad battery information on the command line. It also displays the charge stop and start thresholds.

Suspend to RAM

Suspend to RAM and resume works fine. There are some ACPI errors appearing in the kernel log upon resume:

ACPI BIOS Error (bug): Could not resolve symbol [\_SB.PC00.RP09.PEGP.DDNT], AE_NOT_FOUND (20210730/psargs-330)
[...]
ACPI Error: Aborting method \_SB.PC00.LPCB.EC.SEN4._TMP due to previous error (AE_NOT_FOUND) (20210730/psparse-529)

I haven’t look into these, probably buggy firmware code. It does not seem to cause any real problems.

Power usage in suspended state is nothing out of the ordinary. I would have liked to have hibernate option available, but this is apparently disabled due to locked down kernel and secure boot.

Audio

The HDA audio chip identifies as Realtek ALC257. I have not noticed any issues and it works well. The laptop speakers are a bit disappointing and sound rather weak, but I rarely rely on them anyway.

Wireless networking

The wifi chip is detected as Intel(R) Wi-Fi 6 AX201 160MHz, REV=0x354 by the iwlwifi driver. No issues experienced, wireless networking is solid.

Also note that there is no RJ45 ethernet port on this laptop model.

Bluetooth

Bluetooth is usually a bit buggy on Linux, and it can be a hit and miss experience, depending on the devices your are trying to connect. For the Thinkpad T14, it uses an Intel Bluetooth chip connected to the USB bus.

I’ve tested connectivity with a pair of Bose QC35 headsets and a Samsung Galaxy mobile phone, both work.

Special keys

Most Fn-special keys work fine: audio controls, mic mute, brightness, flight mode. There are some that generate no key events in Xorg: Fn+F9 through F11. These are labeled with symbols for chat, answer call and hangup call.

Firmware upgrades

The Thinkpad firmware can be automatically updated using the built-in fwupdmgr application. Which is a great improvement from earlier days of having to flash updates from thumb drives or being forced to use Windows.

Fingerprint reader

The fingerprint reader works out of the box. You can enroll your prints in the standard Ubuntu settings app.

Webcam

It works fine, but is only a 720p camera.

Problems 💀

Poor keyboard quality control

I noticed the left trackpad mouse button had erratic click detection – some clicks were missed. I primarily use the TrackPoint and rely on the physical mouse buttons beneath the space bar. Having premium on-site support, I decided to call for assistance, suspecting hardware malfunction. Lenovo sent a service technician a few days later, and the entire keyboard was replaced, at no additional cost. The new keyboard is fine.

Speaking of keyboard, the keys are a bit too hesitant and mushy for my taste. But solid.

TrackPoint cursor movement

TrackPoint cap
TrackPoint cap

The [Elan] TrackPoint cursor movement is slightly rough and jittery (using Xorg), resulting in reduced precision. It is almost like not enough input events are generated per time during movement. Also, the following message is printed in the kernel log:

psmouse serio1: synaptics: Your touchpad (PNP: LEN2072 PNP0f13) says it can support a different bus.
If i2c-hid and hid-rmi are not used, you might want to try setting psmouse.synaptics_intertouch to 1 and report this to linux-input@vger.kernel.org.

I have experimented with the suggested psmouse module setting, and it does indeed result in a smoother cursor. However, it also causes TrackPoint to randomly stop working, which is a deal breaker. I have not found a solution to this problem yet, but trust it will improve with future kernels.

Swapped Ctrl and Fn-keys

Thinkpads come with physically swapped left control and Fn-key compared to most keyboards. So you have this layout on the left bottom row:

[Fn] [Ctrl] [Win] [Alt] [ Space ] …

Being an Emacs user, this can be uncomfortable, since the distance between the control key and frequently used letter combinations is too short. Not to mention having to mentally re-calibrate your typing when switching between the Thinkpad and other keyboards. Fortunately the BIOS allows you to software swap the function of these keys, so the Fn-key becomes the left control. But the Fn-key is physically smaller and provides a poorer “left control experience”.


Show Thinkpad battery info on the command line

You can use the following shell script to check your battery vitals from the command line. Copy-paste the code into a file tpbinfo, save it somewhere in your PATH and make it executable with:
chmod +x tpbinfo

#!/bin/sh

set -e

cd /sys/class/power_supply/BAT0

exec awk '
  NR == 1 { cstop=$1 }
  NR == 2 { cstart=$1 }
  NR == 3 { status=$0 }
  NR == 4 { enow=$1 }
  NR == 5 { efull=$1 }
  NR == 6 { efull_design=$1 }

  END {
    charge_percent = (enow / efull) * 100
    capacity_percent = (efull / efull_design) > 1 ? 100 : (efull / efull_design)*100
    print "# Thinkpad battery info"
    printf("%-23s %.1f%%\n", "Charge:", charge_percent)
    printf("%-23s %s\n", "Status:", status)
    printf("%-23s %d%%\n", "Charge start threshold:", cstart)
    printf("%-23s %d%%\n", "Charge stop threshold:", cstop)
    printf("%-23s %d%%\n", "Capacity:", capacity_percent)
  }
' charge_stop_threshold charge_start_threshold status energy_now energy_full energy_full_design

Running tpbinfo produces something like the following output:

# Thinkpad battery info
Charge:                 79,2%
Status:                 Discharging
Charge start threshold: 70%
Charge stop threshold:  85%
Capacity:               99%