Skip to main content

Building a Usable Ubuntu 18.04 Laptop Experience

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.

Disclaimer

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

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…

  • Handle lid close/open reliably by sleeping or hibernating the machine, and also locking the screen.
  • Handle dock/undock reliable by setting an appropriate display configuration and screen locker timeout.
  • Generally exhibit as little latency as possible while responding to inputs.
  • Behave consistently.
  • Use as few system resources as possible.
    • I disable all animations & compositing, since they don’t really do anything for me, and consume resources.
  • Get out of my way so I can use my programs and get work done.
    • I use cwm with only a 2px border around each window, no title bars, and no “status bar” / “task bar”. I have found that these features do not make me more productive, and can be distracting. All information that would be presented in this way can be obtained quickly in other ways.
  • Make efficient use of screen real estate.

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.

Programs

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

  • cwm – window manager and program launcher, I maintain my own fork of CWM for the purpose of a few custom patches
  • st – terminal emulator, again I maintain my own fork for the purpose of some minor custom patches
  • Firefox – web browsing
  • Caja+Engrampa – file management
  • Atril – PDF viewing
  • NeoVim – text editor
  • Geeqie – image viewer
  • xfce4-appfinder.desktop file search
  • LibreOffice – editing MS Office documents
  • Scribus – creating research posters
  • IPE – drawing diagrams
  • pavucontrol – managing PulseAudio settings
  • nm-applet – managing NetworkManager settings
  • stalonetray – cwm does not feature a system tray, but sometimes I need to use programs such as nm-applet, so stalonetray allows me to have a small window where system tray icons appear I usually leave stalonetray running only for as long as I need to use the relevant program
  • CrossOver – for running Windows programs for the rare occasions I need to do so, this is the commercial version of WINE, which both supports the WINE project and saves me the hassle of managing WINE prefixes myself
  • mpv – video playback
  • xfburn – burning CDs (I prefer cdparanoia for ripping)
  • Exaile – music manager and playback, I sync my library to foobar2000 via ftpmusync
  • vokoscreen – screen recording
  • scrot – capturing screenshots (mostly ia this script
  • xtrlock – screen locker via xautolock
  • dunst – notifications
  • arandr – display configuration

Handling Lid Events

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:

  • We don’t want more than once instance of the screen locker running at once, hence we use xautolock, which features a -locknow flag to trigger the screen locker immediately (having no effect if the screen is already locked.
  • The script will be run as root, not as your user account, this means that we need to find out who is running xautolock so we can su to them. I solve this by using ps aux | grep xautolock | grep -v grep | cut -d' ' -f 1. Do note that this will break if multiple instances of xautolock are running either on the same or different user accounts – that should only ever be the case in multi-seat configurations, which are uncommon these days.
  • The script will not have an appropriate DISPLAY variable set. I wrote mine to assume that the DISPLAY is always :0, which is the default. This will break if you have Xorg configured to use a non-standard display or in multi-seat configurations. Presumably if you are doing either of those things, you know what you are doing.

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).

Handling Dock Events

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:

  • While we can match against ENV{ID_VENDOR} and ENV{ID_MODEL} for add actions, these properties are not available for remove actions. I wound up matching on ENV{PRODUCT} for the remove action.
  • It isn’t convenient to pass any parameters to scripts run by udev; rather than fussing with string escaping, I placed my dock and undock handling scripts in /etc/thinkpad/thinpad-dock (and -undock).
  • Scripts that take a long time to run can cause trouble with udev. This can manifest as later udev events being significantly delayed in being handled, which might cause second order effects like USB devices not working. To this end, I use at to create a background job that runs immediately triggering my monitor reconfiguration script, which can take several seconds to run.

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

Screen Locking & Display Sleep

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.

Handling Media Keys

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.

Sundry Common Tasks

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…

  • Take a screenshot
    • I either use screengrab (for the entire screen) or regiongrab (for regions of the screen), which are both trivial wrappers around scrot which place a time-stamped png file on my desktop containing the screenshot or region. This is only a few keystrokes: Alt+d, the first few characters of the script, then Enter.
  • Check the time
    • I open a terminal with Alt+Enter and run date.
  • Check the battery level
    • I open a terminal and run acpi.
  • Connect to a wifi network
    • Open stalonetray, then nm-applet, and connect as one would on any other desktop environment. Note that once a network has been configured once in this way, NetworkManager will remember it and connect automatically without nm-applet being open.
  • Restart, shutdown, or lock the screen
    • For the former two: the restart and shutdown commands, respectively.
    • For the latter, I have a keybinding, Alt+m which runs xautolock -locknow.
  • Display management
    • For the laptop on it’s own, or on my home/work docking station, I use my own configure-display script, which runs both as described in this article on dock/undock/suspend/resume events, and also with the key binding Alt+o. This script loops over the scripts in ~/.screenlayout/ and keeps running them until one exists with a non-zero exit code. These scripts are usually generated via `arandr.
    • For projectors and the like, I use arandr.
  • Manage volume
    • I directly open pavucontrol, or use the volume keys on the laptop.
  • Manage packages
    • I use the apt CLI to install packages and updates. I do not get alerts when updates are ready, but I check for and install them manually once or twice a week.
  • Window management
    • cwm is a bit atypical, in that it is a floating-first window manager which feels like a tiling wm such as dwm or i3 in many ways due to it’s minimalism.
    • Windows cannot be minimized, I either close them, or switch to another window group (which is more or less the same thing as a workspace in other desktop environments.
    • Common window management tasks are done using either the keyboard, or by a combination of the mouse and keyboard. For example, resizing a window is done by hilding Alt while dragging a window with the right mouse button.
  • Launching programs
    • If I know the specific command I wish to run, I use cwm’s exec-menu, which I have bound to Alt+d (muscle memory from my days as an i3+dmenu user). This will allow arbitrary commands to be run, and has completion for any binaries that appear in $PATH.
    • Otherwise, I use xfce4-appfinder, which works similarly to most “start menu” work-alikes for Linux, in that it scans the .desktop files available on the system and allows them to be searched and executed conveniently. This is especially useful for Windows programs running under CrossOver, which automatically creates such .desktop files but not binaries in $PATH. I have this bound to Alt+Space (muscle memory from my time as a macOS user).
  • Configuring Themes

Why Not Use a Pre-Built Distribution?

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).