The first few lines of my Xinitrc are from /etc/X11/xinit/xinitrc. They pull in different X configuration, and also run any scripts from /etc/X11/xinit/xinitrc.d/.

WARNING: Screen lockers on X11 are absolutely not secure!! Do not use a screen locker if you require high security!! (in fact, you might want to consider even using X11 in the first place...)

I lock my desktop using a combination of i3lock(1) and xautolock(1) with some help from xset(1) to change dpms timeout before and after locking:

screen_locker="xset dpms 0 0 10 dpms force off; i3lock --nofork -befc 000000; xset dpms 0 0 0"
locktime=30 # Default screen lock timeout in minutes


# Automatically lock after $locktime minutes using i3lock
xautolock -time $locktime -locker "$screen_locker" -detectsleep &

The locker is defined in \(screen_locker, then used later along with \)locktime to start xautolock(1). When $screen_locker is run, the following happens:

  1. DPMS timeout is set to 10 seconds
  2. DPMS forces the display off now
  3. i3lock is run with these options:
    • --nofork: Don't fork to the background
    • -b: Beep on incorrect password attempt
    • -e: Ignore empty password
    • -f: Show number of failed attempts, if any
    • -c 000000: Background color should be #000000
  4. When i3lock exits, DPMS timeout is set to 0 for never

Whenever I wish to lock the screen, I need only run xautolock -locknow making sure that $DISPLAY is set correctly. For example, my i3 config is set up to run that command for Ctrl+Alt+l.

Also note that the definition of \(screen_locker and \)locktime are separated from the invocation of xautolock(1) by all the host-specific configuration. This is to allow setting \(locktime on a host-by-host basis. Given the first step of the locker is to use DPMS to turn the display off, this effectively sets the display sleep timeout to \)locktime.

As far as power saving options go, $screen_locker could be a little more general by using a variable for the last invocation of xset(1), but I don't have a need for it.

All display output configuration, touchscreen configuration, or any other configuration that should change based on display connectivity takes place in the srandrd(1) configuration. In fact, ~/.xinitrc will completely abort if srandrd isn't installed. This makes display layouts and touchscreen easier to maintain in the long run.

The "configuration" can be any executable file which will be run with the environment variable $SRANDRD_ACTION set to a string formatted as <output> <connected|disconnected|unknown>, where <output> is the name of the output according to xrandr(1). I use a bash script for this purpose.

This script is set up to be one monolithic "configuration" for multiple hosts by using a case statement that tests against \((hostname) before attempting to handle \)SRANDRD_ACTION. If multiple configuration files become necessary at some point, it would be trivial to break out each host's configurations into their own files and do something similar to source ~/.config/srandrd/$(hostname).conf, but it's not necessary for the time being.

Once the hostname is determined, I begin by setting a few variables with host-specific information. Setting these variables before handling $SRANDRD_ACTION is beneficial for situations where a software or hardware change might change any one of these values, in which case, the information will only need to be updated in one place instead of several places throughout the script.

Finally, $SRANDRD_ACTION is handled with a case statement. This section of the script can be arbitrarily complicated, but for my simple usage, every output that needs to be handled needs both a connected and disconnected case defined. In the most complicated case, I do 3 things: Run xrandr(1) to configure outputs, configure the touchscreen with xinput(1), and set PulseAudio's default sink with pacmd(1).

When using a touchscreen with multiple monitors, it is possible that the touchscreen will be mapped to cover the entire canvas instead of only the display to which it is attached. This results in situations where touch events on one side of the display will be close to accurate, but touch events on the other side of the display begin to manipulate the cursor on the 2nd display.

This is fairly easy to rectify using xinput --map-to-output <device id> <output name> where <device id> is the id number of the touchscreen as reported by xinput list, and <output name> is the xrandr name of the output you wish to map the touchscreen to.

This may seem easy enough to script by finding the device id once, then adding an appropriate line in ~/.xinitrc, but the device id is not static, and may change between boots.

For this reason, it is more appropriate to match against an attribute of the device that will not change. In the case of xinput(1), the best option for this situation is the device name. The device id can easily be found by running xinput list --id-only <device name>. This can easily be strung together with a subshell to do everything on one line. For instance, if you wish to map a device named ELAN Touchscreen to an output named eDP-1, the resulting line might look like the following:

xinput --map-to-output $(xinput list --id-only "ELAN Touchscreen") eDP-1

This command must be run after every time the display configuration changes, so it is possible that the command will need to exist several times in the srandrd(1) configuration. Since the only unique pieces of information between hosts will be the device name and the output name, it is easier to use a function instead of duplicating the same line over and over:

maptouchscreen() { 
	# maptouchscreen <name> <output>
	local name="$1" # <name> should be a full name from the output of `xinput list`
	local output="$2" # <output> should be an xrandr output name
	xinput --map-to-output $(xinput list --id-only "$name") "$output"

This allows you to replace any instance of the full xinput(1) command with maptouchscreen <device name> <output>.

Note: If there are spaces in the device name, bash will glob the crap out of it, so be sure to double quote the device name when calling maptouchscreen().

I want the default PulseAudio sink to change to HDMI when HDMI is plugged in, and back to analog when disconnected.

Changing the default sink in pulse is as easy as running pacmd set-default-sink <sink id> where <sink id> is the id of the desired sink. Unfortunately, this is another situation where the id might change unexpectedly. We also don't have an easy interface to determine the id number from a name like we did with xinput(1), so we're forced to parse the output of pactl list sinks short like so:

pacmd set-default-sink $(pactl list sinks short | grep "hdmi" | grep -o "^\S\+")

This command lists all sinks in short form, then greps for a line containing "hdmi", uses grep to only print the first character (the sink id), then sets the default sink with pacmd(1). Since this will also need to be run any time the display configuration changes, a function is, again, appropriate:

setpasink() { 
	# setpasink <name>
	# Find a unique string in the output of `pacmd list short` to use for <name>
	pacmd set-default-sink $(pactl list sinks short | grep "$1" | grep -o "^\S\+")

This allows you to change the default PulseAudio sink with setpasink <name> where <name> is any arbitrary string that is unique to the line that corresponds to the desired sink in the output of pactl list sinks short.