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

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.

Get out of my way so I can use my programs and get work done.

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[1] 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[2], which handle certain user-specific aspects of my desktop environment such as WM configuration.

[1] - my provisioning scripts

[2] - my dotfiles

2021-03-30 update: at the time this post was written, the linked repository[1] was at commit 3838387081fdf53dc99286309dd910db7ea3e428. The Ubuntu 18.04 provisioning scripts now live in the bionic/ directory. However, I don't use Ubuntu on my personal machines anymore, so any Ubuntu-related provisioning scripts may be out of date.


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: file manager

Engrampa: archive manager

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: stand-alone system tray

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

cdparanoia: ripping CDs

Exaile: music manager and playback

I use ftpmusync to sync my media library to Foobar2000 on my iPhone.

vokoscreen: screen recording

scrot: capturing screenshots

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[3]. This provides us a directory (/lib/systemd/system-sleep) where we can place scripts to be executed whenever the system is sleeping or resuming.

[3] - systemd-suspend(8)

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

This is my script:


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

case $1/$2 in
                DISPLAY=:0 sudo -u "$LOCK_USER" xautolock -locknow
                logging "locked display with user $LOCK_USER"
                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"

Note that I also call configure-display[4] 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).

[4] - configure-display

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[5], but that didn't last very long[6]. I had tried udev[4] 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.

[5] - Hooking into docking and undocking events to run scripts

[6] - TP_HKEY_EV_HOTPLUG_DOCK and TP_HKEY_EV_HOTPLUG_UNDOCK not seen by acpi_listen

[7] - udev(7)

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[8] helpful.

[8] - seanf/81-thinkpad-dock.rules

Of course, this approach has some caveats:

[9] - Udev not doing remove rules?

[10] - at(1)

Here are my udev rules:


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


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[11] 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[12], 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[13], which uses yad[14] to display a simple configuration menu to manage this file.

[11] - xset(1)

[12] - apply-dpms

[13] - dpmsconf

[14] - yad

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,[14], 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[15]. The xbindkeys process gets launched by my .xsession[16], along with my window manager and a few other things. I use this .xbindkeysrc[17] to trigger the right pactl[18] commands to set my volume level appropriately.

[15] - xbindkeys(1)

[16] - ~/.xsession

[17] - ~/.xbindkeysrc

[18] - pactl(1)

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 use one of:

screengrab (for the entire screen)

regiongrab (for regions of the screen)

are 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[19] 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".

[19] - configure-display

Manage volume

I directly open "pavucontrol", or use the volume keys on the laptop.

Manage packages

I use the apt[20] 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.

[20] - apt(8)

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 holding 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

For GTK2:


For GTK3:


I don't use enough Qt programs to care about my Qt theme.

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

Copyright 2019 Charles Daniels.

This work is licensed under CC-BY-SA 4.0