The Blog of Charles Daniels

Building a Usable Ubuntu 18.04 Laptop Experience

Published 2019-05-19

Those that know me will know that I generally prefer to run OpenBSD when I have a choice. However, I found myself needing to run a number of Linux-specific software packages. As Ubuntu is the most common (or at least among the most common) Linux distribution, I settled on that -- most commercial software that targets Linux specifically sports Ubuntu. It's taken a lot of trial, error, and scouring the internet to collect the required information to build a usable (by my standards) desktop experience based on Ubuntu 18, so I thought it might be helpful to others to document what I've figured out so far.

Because various people have asked me about my workflow and setup, I also discus certain aspects of how I actually use this system after it is set up. Those parts may be less useful to folks just wanting suspend/resume or docking to work properly, and can safely be skipped over.

I don't encourage you to do anything I describe here without fully understanding it. While I will link to a set of automation scripts that will provision the desktop experience that I describe, you should not run them without understanding every line of code and what it does. My configuration features some heavy handed opinions, and my automation will silently overwrite existing configs. Additionally, if run on an OS other than Ubuntu 18.04 Server, they may render your OS install broken.

Proceed at your own risk.

What makes a great desktop experience is inherently a matter of personal taste. You may have a different perspective, but these are the facets that I find to be important. Remember that I use a laptop (ThinkPad T430) as my daily driver, and docking station support is very important to my workflow -- this significantly colors some of the criteria I impose on my OS configuration.

A great desktop experience should...

The rest of this article will discuss how I accomplish these goals.

If you would like to follow along, I maintain a repository with an Ansible playbook to automatically convert an existing Ubuntu 18 server install into my preferred environment (see the ubuntu/ directory). This also automatically installs my dotfiles, which handle certain user-specific aspects of my desktop environment such as WM configuration.

The programs I use, which constitute my "desktop environment" are:

Fortunately, systemd-logind handles lid open/close events already, and will sleep or resume the system automatically. This means all we need to do is trigger a screen lock whenever the lid closes. While there are many approaches to this problem, the one that I like on Linux is to simply take advantage of systemd-suspend. This provides us a directory (/lib/systemd/system-sleep) where we can place scripts to be executed whenever the system is sleeping or resuming.

There are a couple of important caveats to keep in mind when writing an appropriate script:

This is my script:

#!/bin/sh

logging "lock handler got: $1/$2"
LOCK_USER="$(ps aux | grep xautolock | grep -v grep | cut -d' ' -f 1)"

case $1/$2 in
        pre/*)
                DISPLAY=:0 sudo -u "$LOCK_USER" xautolock -locknow
                logging "locked display with user $LOCK_USER"
                ;;
        post/*)
                DISPLAY=:0 sudo -u "$LOCK_USER" xautolock -locknow
                DISPLAY=:0 sudo -u "$LOCK_USER" sh -c "~$LOCK_USER/bin/configure-display"
                logging "configured display with user $LOCK_USER"
                ;;
esac

Note that I also call configure-display whenever the system wakes, in case I have docked or undocked while the system was sleeping. I also lock the screen whenever waking from sleep, just to be safe (since xautolock will never spawn more than once instance of the screen locker anyway).

Correct handling of dock events has been an issue I've struggled with for years on various Linux and BSD variants, and have finally found a solution that seems to work reliably. Of course I thought that when I initially tried using acpid, but that didn't last very long. I had tried udev in the past (the last time I was a Linux user before I spent a long stint on OpenBSD), but wasn't successful in getting it to work. This time around it seems to have done the trick.

For those unfamiliar with udev, it's a daemon that allows you to trigger various events whenever the system detects a change in hardware. It is frequently used to set the permissions on device nodes to allow certain users or groups to use special pieces of hardware, or to automatically mount partitions on external disks when they become available.

In our case, we want to bind a script to one of two possible events: the USB device corresponding to my dock being connected, and being disconnected. We can find this USB device via lsusb | grep -i lenovo, which returns Bus 002 Device 012: ID 17ef:100a Lenovo ThinkPad Mini Dock Plus Series 3. We can also see this by running udevadm monitor and docking/undocking and observing what USB devices are add-ed or remove-ed. This will help us figure out the specific udev properties that we can match against to configure our script. I found this guide helpful.

Of course, this approach has some caveats:

Here are my udev rules:

/etc/udev/rules.d/81-thinkpad-dock.rules:

SUBSYSTEM=="usb", ACTION=="add", ENV{ID_VENDOR}=="17ef", ENV{ID_MODEL}=="100a", RUN+="/etc/thinkpad/thinkpad-dock"

/etc/udev/rules.d/81-thinkpad-undock.rules:

SUBSYSTEM=="usb", ACTION=="remove", ENV{PRODUCT}=="17ef/100a/0" RUN+="/etc/thinkpad/thinkpad-undock"

Both the thinkpad-dock and thinkpad-undock scripts are identical:

#!/usr/bin/env bash
echo 'su username -c "DISPLAY=:0 ~username/bin/configure-display"' | at now

As already discussed, I use xautolock to automatically lock the screen after a fixed period of inactivity. I also use xset to configure the time after which my display will time out (eg. xset dpms 600 600 600 will cause the display to power off after 10 minutes of inactivity). However, different uses will necessitate different periods of inactivity. To this end, I have a script: apply-dpms, which looks at a file in ~/.config and sets the correct timeouts based on it's contents and weather or not an AC adapter is connected. I also have a helper utility, dpmsconf, which uses yad to display a simple configuration menu to manage this file.

To make sure that the configured state is consistent with the current state, I use a cron job apply-dpms every minute, and also whenever the monitors are configured. This cron job can be installed with a third script, install_dpmsconf.sh, which prevents duplicate cron entries from being created when updating.

My laptop features volume up, down, and mute keys, which I like to use when it is not docked (when the laptop is docked, I have speakers with a physical volume knob which I use in lieu of software control). In most Linux desktop environments, a background daemon will run to catch such key events and modify the volume level appropriately. As my setup does not feature such a daemon, I use xbindkeys. The xbindkeys process gets launched by my .xsession, along with my window manager and a few other things. I use this .xbindkeysrc to trigger the right pactl commands to set my volume level appropriately.

Many people are surprised to see that I do not use a "full fat" desktop environment, nor even a task bar on my laptop, and wonder what my workflow is for common tasks people generally like to do. This is a non-exhaustive list of such tasks and how I complete them...

I have often been asked why I prefer to roll my desktop experience myself, rather than using a pre-built spin/flavor/distro/whatever. Aside from having fairly specific preferences in keybindings and software selection, I've yet to find a Linux distribution that works out of the box for use on laptops. Maybe I'm just unlucky, but I experienced serious issues that rendered Ubuntu 18.04 Desktop, Xubuntu 18.04, and centOS 7 unusable on both my ThinkPad X220 and T430, both laptops that have a good reputation for Linux compatibility.

With Ubuntu Desktop, I found that the GNOME environment performed so poorly that it just wasn't usable. In the applications search menu, it would often take over 10 seconds from pressing a key to it appearing in the search bar. Animations while switching desktops or windows stuttered and lagged considerably. Both of the noted laptops have i7 processors with 3rd/4th gen Intel graphics, and both have had their cooling systems cleaned and fresh thermal paste applied within the past 6 months.

On Xubuntu, though the desktop environment was serviceable enough, any time xfce4-power-manager would put the display to sleep, it could not be woken back up without switching to a TTY and back. Additionally, light-locker would crash Xorg any time I attempted to log in with it, rendering screen locking functionally broken. Some google searching and fussing with packages produced little in the way of results.

Finally, with centOS, I found the OS was simply unsuited to my use case. Power and display management may have worked fine, and performance may have been great, but I wrote it off fairly quickly since a lot of software I would like to run is simply not available.

All that said, I'm not writing this article to criticize these Linux distributions. If one of them works for you on your machine, then that's great. The point of this article is to talk about building a desktop experience that I like and work on the computers that I have from scratch (i.e. starting with Ubuntu Server and building up).