83ddb92682198a01a38671b38e140a6f985e3e31 — Charles Daniels 1 year, 17 days ago efe3f2d
added ubuntu laptop experience article
3 files changed, 484 insertions(+), 1 deletions(-)

M src/index.md
M src/shell_music.md
A src/ubuntu_laptop.md
M src/index.md => src/index.md +1 -0
@@ 1,5 1,6 @@

<li><a href="ubuntu_laptop.html" title="2019-05-19">Building a Usable Ubuntu 18.04 Laptop Experience</code></a></li>
<li><a href="shell_music.html" title="2018-12-17">A Jukebox in <code>sh</code> with <code>netcat</code></a></li>
<li><a href="2018-09-13_background_interactive.html" title="2018-09-13">Daemonizing Interactive Programs with systemd</a></li>
<li><a href="2017-11-22_make-static-site.html" title="2017-11-22">Building a Static Site Generator in 44 Lines of Make</a></li>

M src/shell_music.md => src/shell_music.md +1 -1
@@ 1,6 1,6 @@
# A Jukebox in `sh` with `netcat`

*Written 2018-12-17*
*Published 2018-12-17*

Over winter break, some friends and I decided to spend a week working on a
programming project. One challenge was playing music - deciding whose computer

A src/ubuntu_laptop.md => src/ubuntu_laptop.md +482 -0
@@ 0,0 1,482 @@
# 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.

<h2 class=navhdr id="disclaimer">
	<a href=#disclaimer class=navhandle>¶ </a>

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.

<h2 class=navhdr id="experience">
	What Makes a Great Desktop Experience
	<a href=#experience class=navhandle>¶ </a>

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

* 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](https://git.sr.ht/~charles/provisioning) 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](https://git.sr.ht/~charles/dotfiles), which handle certain
user-specific aspects of my desktop environment such as WM configuration.

<h2 class=navhdr id="programs">
	<a href=#programs class=navhandle>¶ </a>

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

* [cwm](https://git.sr.ht/~charles/cwm) -- window manager and program launcher,
  I maintain my own fork of CWM for the purpose of a few custom patches

* [st](https://github.com/charlesdaniels/st) -- terminal emulator, again I
  maintain my own fork for the purpose of some minor custom patches

* [Firefox](https://www.mozilla.org/en-US/firefox/new/) -- web browsing

* [Caja](https://github.com/mate-desktop/caja)+[Engrampa](https://github.com/mate-desktop/engrampa)
  -- file management

* [Atril](https://github.com/mate-desktop/atril) -- PDF viewing

* [NeoVim](https://neovim.io/) -- text editor

* [Geeqie](http://www.geeqie.org/) -- image viewer

* [xfce4-appfinder](https://docs.xfce.org/xfce/xfce4-appfinder/start) --
  `.desktop` file search

* [LibreOffice](https://www.libreoffice.org/) -- editing MS Office documents

* [Scribus](https://www.scribus.net/) -- creating research posters

* [IPE](http://ipe.otfried.org/) -- drawing diagrams

* [pavucontrol](https://freedesktop.org/software/pulseaudio/pavucontrol/) --
  managing PulseAudio settings

* [nm-applet](http://manpages.ubuntu.com/manpages/trusty/man1/nm-applet.1.html)
  -- managing NetworkManager settings

* [stalonetray](http://stalonetray.sourceforge.net/) -- 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](https://www.codeweavers.com/) -- 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](https://mpv.io/) -- video playback

* [xfburn](https://goodies.xfce.org/projects/applications/xfburn) -- burning
  CDs (I prefer [cdparanoia](https://xiph.org/paranoia/) for ripping)

* [Exaile](https://www.exaile.org/) -- music manager and playback, I sync my
  library to [foobar2000](http://mobile.foobar2000.com/) via

* [vokoscreen](https://github.com/vkohaupt/vokoscreen) -- screen recording

* [scrot](http://manpages.ubuntu.com/manpages/trusty/man1/scrot.1.html) --
  capturing screenshots (mostly ia [this

* [xtrlock](http://manpages.ubuntu.com/manpages/xenial/man1/xtrlock.1x.html) --
  screen locker via

* [dunst](https://dunst-project.org/) -- notifications

* [arandr](https://christian.amsuess.com/tools/arandr/) -- display

<h2 class=navhdr id="lid">
	Handling Lid Events
	<a href=#lid class=navhandle>¶ </a>

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


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

<h2 class=navhdr id="lid">
	Handling Dock Events
	<a href=#lid class=navhandle>¶ </a>

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
but [that didn't last very
I had tried
[udev](http://manpages.ubuntu.com/manpages/bionic/en/man7/udev.7.html) 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](https://gist.github.com/seanf/e3be5bf745395d50e975) 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`
  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](http://manpages.ubuntu.com/manpages/bionic/en/man1/at.1posix.html) 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:


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

<h2 class=navhdr id="dpms">
	Screen Locking & Display Sleep
	<a href=#dpms class=navhandle>¶ </a>

As already discussed, I use xautolock to automatically lock the screen after a
fixed period of inactivity. I also use
[xset](http://manpages.ubuntu.com/manpages/bionic/en/man1/xset.1.html) 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:
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,
which uses [yad](https://yadbash.com/) 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,
which prevents duplicate cron entries from being created when updating.

<h2 class=navhdr id="media_keys">
	Handling Media Keys
	<a href=#media_keys class=navhandle>¶ </a>

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
The xbindkeys process gets launched by my
along with my window manager and a few other things. I use [this
to trigger the right
commands to set my volume level appropriately.

<h2 class=navhdr id="common_tasks">
	Sundry Common Tasks
	<a href=#common_tasks class=navhandle>¶ </a>

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
	  (for the entire screen) or
	  (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,

	* For the latter, I have a keybinding, Alt+m which runs `xautolock

* **Display management**

	* For the laptop on it's own, or on my home/work docking station, I use
	  my own
	  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
	  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

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

	* For GTK2,

	* For GTK3, [`~/.config/gtk-3.0/settings.ini`](https://git.sr.ht/~charles/dotfiles/tree/720ff3de4b21a81cc3727fa3f55f5fda43500684/overlay/.config/gtk-3.0/settings.ini)

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

<h2 class=navhdr id="pre_built">
	Why Not Use a Pre-Built Distribution?
	<a href=#pre_built class=navhandle>¶ </a>

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