Xmonad and Xmobar for laptop

Here I publish my Xmonad and Xmobar setup for the laptop. Key features of this setup is ability of the proper dynamic monitor setup: laptop screen + second HDMI connected monitor.

  • Monitor can be connected and disconnected any time.
  • Each monitor has its instance of Xmobar.
  • Instances of the Xmobar are added/removed dynamically.

I use systemd service for the detection of the external monitor/screen attachment/detachment.

Screenshot

Screenshot of Xmobar on two screens

Xmobar dual screen

Note: To obtain all necessary files, please go to my Github repo: https://github.com/alexeygumirov/xmonad-xmobar-laptop/.

Sequence (logic)

  1. I start all applets, notify daemon and system tray application (trayer) in the .xprofile.
    • It also launches displaymode script, which detects if the laptop lid is open or closed and whether HDMI monitor is attached or not. Depending on the condition it sets up appropriate displays configuration with xrandr.
  2. In order to detect attachment or detachment of the screen I made my own systemd service for this event. (See chapter below).
  • Both systemd service and displaymode script restart xmonad process when display setup is changed because xmonad dynamically spawns necessary number of xmobar instances on the screen.

Systemd service setup

Without desktop environment automatic detection of the attachment and detachment of the external monitor does not work. I took initial idea from here: Arch Linux forum. In order to implement this, three things are needed:

  1. Script to detect monitor state and execute action
  2. Systemd service using this script
  3. UDEV rule which is triggered by the device event and launches this systemd service

1. The script

I created the script /usr/local/bin/hdmi-unplug with the following content: This script updates Xmonad or Qtile depending on the Xsession configuration.

 1#!/usr/bin/sh
 2
 3######################################
 4## /usr/local/bin/hdmi_unplug
 5######################################
 6X_USER=alexgum
 7export DISPLAY=:0
 8export XAUTHORITY=/home/$X_USER/.Xauthority
 9export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
10HDMI_STATUS=$(cat /sys/devices/pci0000:00/0000:00:08.1/0000:05:00.0/drm/card0/card0-HDMI-A-1/status)
11LID_STATE=$(cat /proc/acpi/button/lid/LID/state | cut -d':' -f2 | tr -d ' ')
12
13connect()
14{   
15    case $1 in
16        "open")
17            # I don't want automatic desktop extension on the output.
18            # I prefer manual switching to the desired mode.
19            xrandr --output eDP --primary --auto --output HDMI-A-0 --off
20            ;;
21        "closed")
22            xrandr --output eDP --off --output HDMI-A-0 --primary --auto
23            if [ ! -z "$(pgrep -fa Xsession | grep qtile)" ]
24            then
25                qtile-cmd -o cmd -f restart 2> /dev/null
26            fi
27            if [ ! -z "$(pgrep -fa Xsession | grep xmonad)" ]
28            then
29                echo " " > /tmp/displaymode #to display icons on Xmobar
30                echo "HDMI" >> /tmp/displaymode
31                xmonad --restart
32            fi
33            nitrogen --restore
34            ;;
35    esac
36}
37
38disconnect(){
39    xrandr --output eDP --primary --auto --output HDMI-A-0 --off
40    if [ ! -z "$(pgrep -fa Xsession | grep qtile)" ]
41    then
42        qtile-cmd -o cmd -f restart 2> /dev/null
43    fi
44    if [ ! -z "$(pgrep -fa Xsession | grep xmonad)" ]
45    then
46        echo " " > /tmp/displaymode # to display icons on Xmobar
47        echo "default" >> /tmp/displaymode
48        xmonad --restart
49    fi
50    nitrogen --restore
51}
52
53if [ "${HDMI_STATUS}" = "disconnected" ]; then
54    disconnect
55elif [ "${HDMI_STATUS}" = "connected" ]; then
56    connect "${LID_STATE}"
57fi
58
59exit

2. The Systemd service

Then I created /etc/systemd/system/hdmi-unplug.service file with the following content:

[Unit]
Description=HDMI Monitor unplug

[Service]
Type=simple
RemainAfterExit=no
User=alexgum
ExecStart=/usr/local/bin/hdmi-unplug

[Install]
WantedBy=multi-user.target

3. UDEV rule

And, finally, UDEV rule /etc/udev/rules.d/95-hdmi-unplug.rules:

# My rule to automatically unplug external HDMI monitor when disconnected
ACTION=="change", KERNEL=="card0", SUBSYSTEM=="drm", RUN+="/usr/bin/systemctl start hdmi-unplug.service"

Xmonad.hs critical section

Full xmonad.hs configuration you can find here. Here is just show essential part of the code.

I use XMonad.Layout.IndependentScreens to detect number of screens. Then I put myLogHook in a separate definition (you can check it in the configuration file). It allows to make main part short can clean and I can easier redirect log to both xmobar pipes when they are open.

And then in the main function:

 1main = do
 2    nScreens <- countScreens
 3    if nScreens == 1
 4        then do 
 5            xmproc0 <- spawnPipe "xmobar -x 0 /home/alexgum/.config/xmobar/xmobarrc"
 6            xmonad $ docks defaults {
 7                    manageHook = manageDocks <+> namedScratchpadManageHook scratchpads <+> manageHook defaults
 8                    , layoutHook = avoidStruts $ layoutHook defaults
 9                    , logHook = myLogHook xmproc0
10                    }
11        else do
12            xmproc0 <- spawnPipe "xmobar -x 0 /home/alexgum/.config/xmobar/xmobarrc"
13            xmproc1 <- spawnPipe "xmobar -x 1 /home/alexgum/.config/xmobar/xmobarrc1"
14            xmonad $ docks defaults {
15                    manageHook = manageDocks <+> namedScratchpadManageHook scratchpads <+> manageHook defaults
16                    , layoutHook = avoidStruts $ layoutHook defaults
17                    , logHook = myLogHook xmproc0 >> myLogHook xmproc1
18                    }

Each time when xmobar is restarted, it re-detects number of displays and spawns necessary number of bars.

The only exception is mirror mode (xrandr --output eDP --primary --auto --output HDMI-A-0 --auto --same-as eDP), then no xmobar restart is needed because I want to keep main screen xmobar being projected.