Skip to content

Instantly share code, notes, and snippets.

@kyokley
Last active June 13, 2024 20:32
Show Gist options
  • Save kyokley/169646c27ed2dcc470b6 to your computer and use it in GitHub Desktop.
Save kyokley/169646c27ed2dcc470b6 to your computer and use it in GitHub Desktop.
Docking/Undocking a Lenovo laptop running xmonad

Docking/Undocking a Lenovo laptop running xmonad

Introduction

I ran into this issue while at work and came up with this solution. This gist is mostly just documentation for myself so I can remember what I did 6 months from now. I am in no way saying that this is the right way to fix things but I'll be thrilled if this is in any way helpful to others. That being said, I make no guarantees that bad things will not happen to your machine if you follow these directions. Proceed at your own risk.

Although, I mention using the following setup to resolve an issue I was having using xmonad as my windows manager, I don't think there's anything special about it. It should be possible to use the following steps with any windows manager.

Problem

Setting up multiple monitors in xmonad requires defining the screen configuration using xrandr. The problem is that xmonad does not respond to a laptop being placed or removed from a docking station. Obviously, the solution is to update the monitor configuration on the docking/undocking event which is what I hope to document here.

Step 1: Creating screen configuration script

The first step is to create a script that can determine which monitors are connected and configure them appropriately. In my particular case, there were 3 screens that would come into play. I have 2 external monitors when the laptop is docked (DP-2 and DP-3) and just the LCD screen when undocked (LVDS-1). You'll have to check xrandr for the settings in your particular situation.

Below is the script I am currently using:

#!/bin/bash
# /home/yokley/.xmonad/dock_helper.sh

FIRST_EXTERNAL='DP-2'
SECOND_EXTERNAL='DP-3'
LCD_SCREEN='LVDS-1'

logger 'Running dock/undock script'

# Start by activating the laptop screen
xrandr --output ${FIRST_EXTERNAL} --off --output ${SECOND_EXTERNAL} --off > /tmp/dock_helper.log 2>&1
logger "Setting ${LCD_SCREEN} as primary monitor"
xrandr --output ${LCD_SCREEN} --auto --primary >> /tmp/dock_helper.log 2>&1

# Attempt to determine which external screens are attached
xrandr | grep "${FIRST_EXTERNAL} connected" >> /tmp/dock_helper.log 2>&1
if [ $? -eq 0 ]; then
    logger "Found ${FIRST_EXTERNAL}. Setting ${FIRST_EXTERNAL} as primary and turning ${LCD_SCREEN} off"
    xrandr --output ${LCD_SCREEN} --off --output ${FIRST_EXTERNAL} --auto --primary >> /tmp/dock_helper.log 2>&1
    xrandr | grep "${SECOND_EXTERNAL} connected" >> /tmp/dock_helper.log 2>&1
    if [ $? -eq 0 ]; then
        logger "Found ${SECOND_EXTERNAL}. Setting ${SECOND_EXTERNAL} to the right of ${FIRST_EXTERNAL}"
        xrandr --output ${LCD_SCREEN} --off --output ${SECOND_EXTERNAL} --auto --right-of ${FIRST_EXTERNAL} >> /tmp/dock_helper.log 2>&1
    fi
else
    xrandr | grep "${SECOND_EXTERNAL} connected" >> /tmp/dock_helper.log 2>&1
    if [ $? -eq 0 ]; then
        logger "Found ${SECOND_EXTERNAL}. Setting ${SECOND_EXTERNAL} as primary and turning ${LCD_SCREEN} off"
        xrandr --output ${LCD_SCREEN} --off --output ${SECOND_EXTERNAL} --auto --primary >> /tmp/dock_helper.log 2>&1
    fi
fi

The script is pretty straightforward. It starts by always activating the LCD screen. From there it determines if any external monitors are connected. If so, it places the external and deactivates the LCD screen. There's plenty of logging which isn't necessary but was definitely useful while I was debugging.

Step 2: Wrap the helper script

I chose to wrap the helper script to keep things as clear as possible. The wrapper script finds the users that are currently logged in and runs the helper script on them.

#!/bin/bash
# /home/yokley/.xmonad/dock.sh

# wait for the dock state to change
sleep 1
USERS=$(last | grep still | cut -d " " -f1 | uniq)

for user in "${USERS}"; do
    case "$1" in
        "0")
            #undocked event
            logger 'undocked event'
            DISPLAY=:0.0 su ${user} -c "bash /home/yokley/.xmonad/dock_helper.sh"
        ;;
        "1")
            #docked event
            logger 'docked event'
            DISPLAY=:0.0 su ${user} -c "bash /home/yokley/.xmonad/dock_helper.sh"
        ;;
    esac
done
exit 0

Step 3: Finding the docking/undocking event

The next trick is to find the events that are triggered when the machine is docked or undocked. This is accomplished by watching the udev output. While running the following command:

sudo udevadm monitor --environment

try removing and replacing the computer from the dock. You'll need to look for the kernel output for the dock.

As an example, here's what my output looks like:

UDEV  [237867.780512] remove   /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.5/1-1.5:1.0 (usb)
ACTION=remove
DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.5/1-1.5:1.0
DEVTYPE=usb_interface
ID_MODEL_FROM_DATABASE=ThinkPad Mini Dock Plus Series 3
ID_USB_CLASS_FROM_DATABASE=Hub
ID_USB_PROTOCOL_FROM_DATABASE=TT per port
ID_VENDOR_FROM_DATABASE=Lenovo
INTERFACE=9/0/2
MODALIAS=usb:v17EFp100Ad0000dc09dsc00dp02ic09isc00ip02in00
PRODUCT=17ef/100a/0
SEQNUM=2949
SUBSYSTEM=usb
TYPE=9/0/2
USEC_INITIALIZED=81445

UDEV  [237876.534823] add      /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.5/1-1.5:1.0 (usb)
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.5/1-1.5:1.0
DEVTYPE=usb_interface
ID_MODEL_FROM_DATABASE=ThinkPad Mini Dock Plus Series 3
ID_USB_CLASS_FROM_DATABASE=Hub
ID_USB_PROTOCOL_FROM_DATABASE=TT per port
ID_VENDOR_FROM_DATABASE=Lenovo
INTERFACE=9/0/1
MODALIAS=usb:v17EFp100Ad0000dc09dsc00dp02ic09isc00ip01in00
PRODUCT=17ef/100a/0
SEQNUM=2955
SUBSYSTEM=usb
TYPE=9/0/2
USEC_INITIALIZED=76521500

There's actually a lot more than this but I've removed the uninteresting parts. Notice that the first block of output is created when the laptop is removed from the dock while the second block occurs on re-docking.

Step 4: Create a new udev rule

After determining the event to watch, it is necessary to add rules for the event to execute the dock script. I created a new file in /etc/udev/rules.d named 99-Lenovo-dock.rules containing the following:

ENV{ID_MODEL_FROM_DATABASE}=="ThinkPad Mini Dock Plus Series 3", ENV{ID_USB_CLASS_FROM_DATABASE}=="Hub", ACTION=="add", RUN+="/home/yokley/.xmonad/dock.sh 1"
ENV{ID_MODEL_FROM_DATABASE}=="ThinkPad Mini Dock Plus Series 3", ENV{ID_USB_CLASS_FROM_DATABASE}=="Hub", ACTION=="remove", RUN+="/home/yokley/.xmonad/dock.sh 0"

Hopefully, that's it. Now whenever the machine is docked or undocked, the monitor configuration should be updated.

Conclusion

This setup was enough to get my screens set up correctly whenever I moved between being docked and undocked. I also had an issue with the machine going to sleep when the lid was closed. I solved this issue by disabling the close lid event (this is accomplished by setting HandleLidSwitch=ignore in /etc/systemd/logind.conf). It's not the most elegant solution and I'm hoping to figure out how to make that behavior dependent on dock state as well but that's a problem for another day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment