The Perfect Panel: Integrating Polybar with Emacs

Check out the final code here.

You need a panel!

Polybar (https://polybar.github.io/) is:

  • Minimal
  • Configurable
  • Provides a reliable system tray
  • Easy to integrate with Emacs!

Installing Polybar

First install Polybar using your distro’s package manager. Strangely it’s not in Ubuntu 20.04!

Here’s how to compile it if your distro doesn’t have it (which is rare):

# Install dependencies on Ubuntu 20.04
sudo apt update
sudo apt install build-essential git cmake cmake-data pkg-config \
      python3-sphinx libcairo2-dev libxcb1-dev libxcb-util0-dev \
      libxcb-randr0-dev libxcb-composite0-dev python3-xcbgen xcb-proto \
      libxcb-image0-dev libxcb-ewmh-dev libxcb-icccm4-dev

# Clone the repo and compile version
git clone --recursive https://github.com/polybar/polybar
cd polybar
git checkout 3.5.2
./build.sh

NOTE: The build.sh script will ask you about features to enable in the Polybar build. It is necessary to say answer Y to the polybar-msg feature! You should also answer Y to the question about running sudo make install.

Also install some icon fonts:

sudo apt install fonts-font-awesome fonts-material-design-icons-iconfont

Basic Polybar config

Tangle this to .config/polybar/config

; Docs: https://github.com/polybar/polybar
;==========================================================

[settings]
screenchange-reload = true

[global/wm]
margin-top = 0
margin-bottom = 0

[colors]
background = #f0232635
background-alt = #576075
foreground = #A6Accd
foreground-alt = #555
primary = #ffb52a
secondary = #e60053
alert = #bd2c40
underline-1 = #c792ea

[bar/panel]
width = 100%
height = 35
offset-x = 0
offset-y = 0
fixed-center = true
enable-ipc = true

background = ${colors.background}
foreground = ${colors.foreground}

line-size = 2
line-color = #f00

border-size = 0
border-color = #00000000

padding-top = 5
padding-left = 1
padding-right = 1

module-margin = 1

font-0 = "Cantarell:size=18:weight=bold;2"
font-1 = "Font Awesome:size=14;2"
font-2 = "Material Icons:size=20;5"
font-3 = "Fira Mono:size=13;-3"

modules-right = cpu temperature battery date

tray-position = right
tray-padding = 2
tray-maxsize = 28

cursor-click = pointer
cursor-scroll = ns-resize

[module/cpu]
type = internal/cpu
interval = 2
format = <label> <ramp-coreload>
format-underline = ${colors.underline-1}
click-left = emacsclient -e "(proced)"
label = %percentage:2%%
ramp-coreload-spacing = 0
ramp-coreload-0 = ▁
ramp-coreload-0-foreground = ${colors.foreground-alt}
ramp-coreload-1 = ▂
ramp-coreload-2 = ▃
ramp-coreload-3 = ▄
ramp-coreload-4 = ▅
ramp-coreload-5 = ▆
ramp-coreload-6 = ▇

[module/date]
type = internal/date
interval = 5

date = "%a %b %e"
date-alt = "%A %B %d %Y"

time = %l:%M %p
time-alt = %H:%M:%S

format-prefix-foreground = ${colors.foreground-alt}
format-underline = ${colors.underline-1}

label = %date% %time%

[module/battery]
type = internal/battery
battery = BAT0
adapter = ADP1
full-at = 98
time-format = %-l:%M

label-charging = %percentage%% / %time%
format-charging = <animation-charging> <label-charging>
format-charging-underline = ${colors.underline-1}

label-discharging = %percentage%% / %time%
format-discharging = <ramp-capacity> <label-discharging>
format-discharging-underline = ${self.format-charging-underline}

format-full = <ramp-capacity> <label-full>
format-full-underline = ${self.format-charging-underline}

ramp-capacity-0 = 
ramp-capacity-1 = 
ramp-capacity-2 = 
ramp-capacity-3 = 
ramp-capacity-4 = 

animation-charging-0 = 
animation-charging-1 = 
animation-charging-2 = 
animation-charging-3 = 
animation-charging-4 = 
animation-charging-framerate = 750

[module/temperature]
type = internal/temperature
thermal-zone = 0
warn-temperature = 60

format = <label>
format-underline = ${colors.underline-1}
format-warn = <label-warn>
format-warn-underline = ${self.format-underline}

label = %temperature-c%
label-warn = %temperature-c%!
label-warn-foreground = ${colors.secondary}

Launch it with this command:

polybar panel

Starting Polybar from Emacs

(defvar efs/polybar-process nil
  "Holds the process of the running Polybar instance, if any")

(defun efs/kill-panel ()
  (interactive)
  (when efs/polybar-process
    (ignore-errors
      (kill-process efs/polybar-process)))
  (setq efs/polybar-process nil))

(defun efs/start-panel ()
  (interactive)
  (efs/kill-panel)
  (setq efs/polybar-process (start-process-shell-command "polybar" nil "polybar panel")))

Now we can start Polybar when EXWM starts up, inside of efs/exwm-init-hook:

;; Start the Polybar panel
(efs/start-panel)

NOTE: Disable exwm-systemtray before restarting Emacs so that the tray works!

Requesting information from Emacs

Use the power of emacsclient! We’ll cover this more in a video next week.

;; Make sure the server is started (better to do this in your main Emacs config!)
(server-start)

Use it to get the EXWM workspace number:

emacsclient -e "exwm-workspace-current-index"

Define a function to call the workspaces whatever you want!

(defun efs/polybar-exwm-workspace ()
  (pcase exwm-workspace-current-index
    (0 "")
    (1 "")
    (2 "")
    (3 "")
    (4 "")))

Try it out:

emacsclient -e "exwm-workspace-current-index"

Important caveat!

One thing to keep in mind is that this works well for global variables, but not so great for frame parameters! The timing has to be perfect to get the value of a frame parameter for the workspace frame you land on. It’s possible, but requires more code.

Adding a workspace indicator to the panel

modules-left = exwm-workspace

[module/exwm-workspace]
type = custom/ipc
hook-0 = emacsclient -e "(efs/polybar-exwm-workspace)" | sed -e 's/^"//' -e 's/"$//'
initial = 1
format-underline = ${colors.underline-1}
format-padding = 1

NOTE: The extra sed part is necessary! If you don’t have this command available, you can install it from your distro’s package repository.

Sending information from Emacs using hooks

Use the polybar-msg command to invoke a “hook index” to have the module update itself:

polybar-msg hook exwm 1

Learn more about the IPC module on the Polybar Wiki: https://github.com/polybar/polybar/wiki/Module:-ipc

Invoking the hook from within Emacs

(defun efs/send-polybar-hook (module-name hook-index)
  (start-process-shell-command "polybar-msg" nil (format "polybar-msg hook %s %s" module-name hook-index)))

(defun efs/send-polybar-exwm-workspace ()
  (efs/send-polybar-hook "exwm-workspace" 1))

  ;; Update panel indicator when workspace changes
  (add-hook 'exwm-workspace-switch-hook #'efs/send-polybar-exwm-workspace)

Check out the Polybar wiki

Learn how to configure everything else in Polybar:

https://github.com/polybar/polybar/wiki

Some useful bits from my own configuration:

Spotify now playing (requires the playerctl app)

Mail indicator for mu4e

Chat indicators for tracking.el

Subscribe to the System Crafters Newsletter!
Stay up to date with the latest System Crafters news and updates! Read the Newsletter page for more information.
Name (optional)
Email Address