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.
- I use
- 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 cansu
to them. I solve this by usingps aux | grep xautolock | grep -v grep | cut -d' ' -f 1
. Do note that this will break if multiple instances ofxautolock
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 theDISPLAY
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}
andENV{ID_MODEL}
foradd
actions, these properties are not available forremove
actions. I wound up matching onENV{PRODUCT}
for theremove
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.
- I either use
screengrab
(for the entire screen) or
regiongrab
(for regions of the screen), which are both trivial wrappers around
- Check the time
- I open a terminal with Alt+Enter and run
date
.
- I open a terminal with Alt+Enter and run
- Check the battery level
- I open a terminal and run
acpi
.
- I open a terminal and run
- Connect to a wifi network
- Open
stalonetray
, thennm-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 withoutnm-applet
being open.
- Open
- Restart, shutdown, or lock the screen
- For the former two: the
restart
andshutdown
commands, respectively. - For the latter, I have a keybinding, Alt+m which runs
xautolock -locknow
.
- For the former two: the
- 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
.
- 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
- Manage volume
- I directly open
pavucontrol
, or use the volume keys on the laptop.
- I directly open
- 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).
- If I know the specific command I wish to run, I use cwm’s
- Configuring Themes
- For GTK2,
~/.gtkrc-2.0
- For GTK3,
~/.config/gtk-3.0/settings.ini
- I don’t use enough Qt programs to care about my Qt theme.
- For GTK2,
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).