Building a Custom Minimalistic UI on Debian: From Bare Display Server to Fully Functional User Interface with BSPWM and Polybar.
In the previous parts of this Debian 12 series of articles (4 parts), I covered many details about the Debian distribution — what Debian offers, how it packages software, how to install Debian, and how to administer your Debian system in a secure way. If you enjoyed it so far and I’ve sparked your curiosity about Debian, let’s move on to the most interesting part - building Debian custom UI!
In parts 3A and 3B, you may have noticed that all the screenshots I provided are just white text on a black screen. That’s because I installed a very minimal version of Debian that, at the beginning, had only the standard system utilities and no way to run any GUI applications, even if I wanted to. Why? Was something wrong with the graphics drivers? Nope. I have only one video card - NVIDIA (my Intel CPU doesn’t have an integrated video card), I did have Nouveau drivers for the NVIDIA GPU from the start.
By the way, I installed the proprietary NVIDIA drivers. I’ve described this process in detail in this article (I couldn’t integrate it here because it would make this article incredibly long).
If you’re familiar with Linux systems, you may know about distro flavors. They’re essentially different Desktop Environments (DEs) bundled with the distro of your choice. So, if you download an .iso
with a flavour, and install the system, you’ll have the Desktop Environment you picked. The most famous ones are GNOME, XFCE, and KDE Plasma. However, during installation I did not have any of them installed on my OS, so all I have is this “black" terminal. But are these well-known Desktop Environments are the only way to make your OS more user-friendly and get a UI? NOPE. Otherwise, I wouldn’t have written this article.
Will I install one of these Desktop Environments on my Debian setup and then show you how to customize it? NOPE. Why? Because it’s no fun. Also, it will bring me bloatware (in this case, software I will never use). To explain myself better, no matter how much you customize GNOME, GNOME is still GNOME. There’s no way to make it perfectly fit your needs while stripping away everything you NEVER use. The only DE I personally tolerate is XFCE. The rest? Meh. Basta. I deal with Windows on my work PC, eight hours a day, five days a week—enough of these cute "click-click" UIs.
For me, the most precious part of the process of Debian customization is that it brings you really close to the OS itself. Are you learning Rust? Curious about writing applications for Linux OSs (or even Windows)? But you’ve never gone far beyond GNOME and for you it is a magic box? Bad news: chances are, you won’t go far beyond web apps either.
Let's a bit leave for now UI, and I instead of writing a bullet list of this article content will show you schematically what this article for. So here is how I see basic needs of a user like me - the one who uses Debian for software development purposes.
What do we have here: input and output devices. As inputs, I have the keyboard and mouse, and also the microphone on the headset. For output, there’s the monitor, and for music or calls, the headset itself. For simplicity, I draw the mouse and keyboard using a wired connection. My mouse is wireless, but it’s important to note that if your keyboard and mouse use a dongle, they usually don’t rely on Bluetooth—they use radio frequency (RF) technology to communicate with their receivers (the dongles), so on the scheme they are not connected in any way to the Bluetooth dongle.
My keyboard is an amazing AKKO keyboard. Unfortunately, on Linux, it works only in wired mode, but I’m completely fine with that. On the other hand, my Marshall headset connects via Bluetooth. My monitor receives its output from the NVIDIA graphical processing unit.
NB! I mention the brands of the stuff I use not as an advertisement, but to show what works for me.
So, the goal is to install everything needed on my Debian to make all my input/output devices work flawlessly and to configure a custom Graphical Interface. Let's start with the latter.
I have made the decision to split the broad topic "Building a Custom Minimalistic UI on Debian: From Bare Display Server to Fully Functional Setup" due to the large amount of content. The first part, which you are reading now, focuses on the fundamental components installation and configuration. Here’s the roadmap:
➀ Understanding how UI and Desktop Environments work on Debian
➁ Display Servers, Display Communication Protocols and Windows Managers
➂ Installing and configuring BSPWM Windows Manager
➃ Installing Browser (Brave browser) and Terminal Emulator (Alacritty)
➄ About Desktop Bus and inter-process communication (IPC)
➅ Installing Status Bar utility (polybar), File Manager (thunar), App Launcher (dmenu), Notification Daemon (dunst) and Windows Compositor (picom).
The following sub-part will focus on configuring Bluetooth audio devices and Wi-Fi.
The final article in this Debian series will be dedicated to customization of the status bar (Polybar) and compositor configuration (picom). Moreover, I will install additional software that is unintegral part of my system's setup. By the end of that article, you’ll be fully equipped to diverge from my configuration files and create an even better UI of your taste—where only your imagination sets the limits!
Let's start!
➀ Understanding how UI and Desktop Environments work on Debian
I don’t know if you’re familiar with web development, but let me use it as an abstract example to explain how Desktop Environments (DE) work and how the GUI of applications function on your Debian.
_Imagine you visit a website DEV.TO, and you read one of my articles. You like it and decide to give it a unicorn as a reaction. You click on the unicorn, and it appears in the list of reactions under my article. That’s what you see on "your side".
But behind the scenes—on the server side—your click is actually an event. This event triggers a piece of code on the server. The logic might look something like this:
If 'unicorn' is clicked → increment the counter for 'unicorn' reactions on my article (identified by its unique ID).
Update the database → write the new number of unicorns to the DB for the article 'idX'.
So, anytime another person opens my article, they’ll see the updated number of unicorns already given to it, which will be fetched from statistics DB._
Here’s your explanation refined and tied to Debian apps, keeping your tone and style:
How is this related to any Debian app? It’s exactly the same. Take File Explorer, for example. You open this app, explore the contents of your directories, search for a file, or maybe move a file from one directory to another. You do all of this with a mouse via the GUI, perhaps using drag-and-drop. But behind all of this, there are commands being executed! These commands are similar to the ones you could easily run in the terminal yourself: ls
, mv
, cp
, find
, and so on.
The GUI acts as a layer that simplifies these operations, making them accessible through clicks, drags, and buttons. But at its core, the GUI is just executing the system commands. It’s like a web app sending events to the server—behind the visuals, there’s logic and execution happening.
The Desktop Environments, feature-rich GNOME for example, however still under have commands, even though the complexity of them is pretty high. When you double click on the icon of and App GNOME opens a window on the screen with this app, you can expand it or you can collapse it or you can drag it to the corner of the screen - what a magic! Does GNOME sends some "signals" to GPU to re-render the screen really quickly when something changes? NO. Here is the schematic representation of the "parties" involved into your System UI.
GNOME DE has several key components. The Window Manager is responsible for the functionality of floating windows—allowing you to drag, collapse, or expand them. The Compositor takes care of how these windows look, handling things like transparency, animations, and borders.
However, what exists outside of GNOME (or any other DE) is the Display Server. All Desktop Environments depend on the display server. It’s the display server that makes the fundamental difference between a terminal-only system and a system with a graphical UI.
➁ Display Communication Protocols, Display Servers and Windows Managers
About Display Servers and Display Communication Protocols
When it comes to display stuff, there are two major "display technologies" used by almost all Linux operating systems, and the same applies to Debian. Their names are the X Window System (aka X11 or just X) and Wayland. To reduce confusion with terminology related to X-* stuff: the X Window System is an X display communication protocol, the latest version of which is 11, so it’s also called X11.
In the X Window System, the X Server itself does not give the user the capability of managing windows that have been opened. Instead, this job is delegated to a program called a window manager.
Regarding Wayland, the job is delegated to display server called a compositor or compositing window manager. (WindowManager - Debian Wiki)
There is a quite of confusion with all these terms related to X11 and Wayland, display communication protocols, display servers. Let's take a browser as an example to see the difference. FYI, there are terminal-based browsers! However, let’s focus on a classic browser with GUI features like interactive tabs, status bars, etc.
When you launch a browser—even if you launch it from the terminal (yes, on Linux, you don’t need to click an icon or launch the program from a menu; you can launch it just fine from the terminal with a command i.e. brave-browser
)—it should appear on your screen. Based on where you click and what you do, the image on your screen changes immediately.
Obviously, some code handles this process, communicating your inputs and determining what should be displayed as outputs. In other words, there’s a display communication protocol. And this is where X11 and Wayland diverge—they ARE completely different protocols!
Wayland is a communication protocol that specifies the communication between a display server and its clients, as well as a C library implementation of that protocol. A display server using the Wayland protocol is called a Wayland compositor, because it additionally performs the task of a compositing window manager. (Source)
The X Window System core protocol is the base protocol of the X Window System, which is a networked windowing system for bitmap displays used to build graphical user interfaces on Unix, Unix-like, and other operating systems. The X Window System is based on a client–server model: a single server controls the input/output hardware, such as the screen, the keyboard, and the mouse; all application programs act as clients, interacting with the user and with the other clients via the server. (Source)
The key takeaway from these two quotes is NOT ONLY that the Wayland and X Window System protocols are different, BUT that they run on entirely different display servers. The X.Org Server is designed to run on the X11 communication protocol. Meanwhile, in case of Wayland, display servers are called the Wayland compositors and run upon the Wayland protocol.
However, to give you a clearer idea of how display servers and display communication protocols are distinct and separate things, let me inform you about existence of XWayland. XWayland is a set of patches applied to the X.Org server codebase that allow an X server to run on top of the Wayland protocol. These patches are developed and maintained by the Wayland developers to provide compatibility with X11 applications during the transition to Wayland.
A bit of background on Wayland: it’s much younger than X11, which was developed over 40 years ago. While X11 has been updated over time, it still struggles with an old-style architecture, legacy code, etc. Wayland, on the other hand, was created completely outside the X11 ecosystem, including X.Org display servers, so it has no dependency on that old technology.
However, Wayland isn’t brand new—it wasn’t released just last year. Its current status shows increasing adoption. Both GNOME and KDE Plasma desktop environments have transitioned to Wayland. Debian supports it seamlessly:
Xorg is the default X Window server since Debian 4.0 (etch). For Debian 10 and later, the default human interface protocol is Wayland. (source)
For a long time, the bottleneck was Nvidia’s proprietary drivers, but that issue has been resolved—Nvidia creates less and less troubles to Wayland based systems with which driver version update.
The display server communicates with its clients over the display server protocol, a communications protocol. The display server is a key component in any graphical user interface, specifically the windowing system.
So, here’s the answer to the rhetorical question I posed earlier in this article (After I installed a very minimal version of Debian that, at the beginning, had only the standard system utilities, why there is no way I can run any GUI applications?): After a minimal installation of Debian, the critical component missing is the Display Server. That’s why I can only use the terminal until I install a display server on the system.
Therefore, I have to proceed with installation of a display server. And after all this praise for Wayland—and maybe you've seen it hyped on Reddit and other forums— I will go for the X11 communication protocol and the X.Org Server. The reason? The key software I plan to use, bspwm
, requires it.
BSPWM is a tiling window manager. I’ll update the previous diagram to visualize how it fits into the system.
So, bspwm in my setup will be doing the job of windows management - open the windows for the apps when I launch any and will arrange them for me. What does it mean tiling?
In the context of window managers, tiling refers to a method of organizing and displaying application windows on a screen in a non-overlapping, grid-like manner. Unlike traditional stacking window managers (like those used in GNOME, KDE), where windows can overlap and need to be manually resized or moved, a tiling window manager automatically arranges windows to make the best use of available screen space.
bspwm
arranges windows as the leaves of a full binary tree.
bspwm
is very much in line with the "Debian way" of software—it doesn’t try to absorb a lot of functionality into itself. Instead, some functionality is delegated to other software. For example, in the diagram above, you can see picom
above bspwm
. picom
is a compositor responsible for adding extra effects to the windows managed by bspwm
, like transparency, animations, etc. However, bspwm
can run perfectly fine without it!
What bspwm
cannot work without is the sxhkd
software. This is because bspwm
doesn’t handle any keyboard or pointer inputs on its own. sxhkd
is needed to translate keyboard and pointer events into bspc
invocations. bspc
is a program that sends messages to bspwm
via its socket.
Display server: Xorg
Installing Xorg is as simple as running sudo apt install xserver-xorg-core
. However, if you previously installed NVIDIA drivers using the nvidia-driver
package (my case), it might already be installed. Below is the output of the command used to check if this package is already installed (dpkg -l | grep xserver-xorg*
):
The nvidia-driver
package pulled in xserver-xorg-video-nvidia
as a dependency, which, in turn, brought the xserver-xorg-core
package. Here is the code if your system does not have these packages installed:
dpkg -l | grep xserver-xorg*
#if output is empty:
#the X11 server itself without drivers and utilities:
$ sudo apt install xserver-xorg-core
#alternative for "full" xorg server installation:
#sudo apt install xorg
Note that you won't have the startx
command if you install just x11 server itself, and therefore you will not be able to start a graphical display right after installation. startx
command comes with the package xinit
.
$ sudo apt install xinit
After this, if I execute startx
, Xorg display server starts! This is how my screen changes:
You can see poorly displayed nvidia-smi
output because the terminal is a bit weird—it’s not using all the available space. By the way, you can see some colors in the terminal area. The default terminal emulator that is used is xterm
. It has its own "window", though! But there’s no window manager yet to handle it more properly. Here’s proof that it’s not the fault of a badly configured Xorg server not understanding my screen size: when I run xrandr
, I see the correct resolution for my monitor.
Windows Manager: BSPWM
For convenience, to proceed with the bspwm
installation, I return to the terminal space outside the display server. However, there’s no stopx
command to stop the X server started with startx
. So, I just have to kill the X server process. When I run ps aux | grep X
, I can retrieve the PID (process ID) and kill it with kill :
kill 1191
bspwm: Installation
sudo apt install bspwm
#this will install also sxhkd package
Now your window manager is installed. However, if you run bspwm
in the terminal, it won't work, and if you start the display server, you'll most likely see a black screen. This happens because the window manager is present but not properly configured. You can try it anyway—this will teach you how to open a new TTY session using shortcut keys if something goes wrong in future. For me, it's Ctrl+Alt+F_X_ (F2, F3, F4, F5 ecc - each F key opens new terminal session, or if they're already in use, I can switch between them). In this way you do not loose control over your machine and do not have to use emergency power off.
bspwm configuration: configuration directory
So, let's configure bspwm
and its ally sxhkd
! This is done via creating configuration files for them. But where?
The default configuration file is $XDG_CONFIG_HOME/bspwm/bspwmrc: this is simply a shell script that calls bspc. (Source: BSPWM GitHub page)
$XDG_CONFIG_HOME is an environmental variable, so you can check its value by running echo $XDG_CONFIG_HOME. I’m quite sure the output will most likely be empty. Why? First of all, what is XDG?
XDG originally stood for "X Desktop Group," but now it stands for "Cross-Desktop Group." When it comes to desktop environments like GNOME, XFCE, KDE Plasma, etc., you can actually have more than one installed on your system, and you can switch between them. However, their developers need to be somewhat coordinated—especially regarding configuration file directories—otherwise, the system can become a mess. This is where freedesktop.org steps in to help.
freedesktop.org hosts the development of free and open source software, focused on interoperability and shared technology for open-source graphical and desktop systems. We do not ourselves produce a desktop, but we aim to help others to do so. (Source)
And as part of this interoperability effort, there is a document called the XDG Base Directory Specification.
In this specification, you’ll find more details about $XDG_CONFIG_HOME:
$XDG_CONFIG_HOME defines the base directory relative to which user-specific configuration files should be stored. If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used. (Source)
If you have not set $XDG_CONFIG_HOME (i.e., the output of echo $XDG_CONFIG_HOME is empty), bspwm
will search for its configuration in $HOME/.config/bspwm/
. Your user's HOME directory should be set by default as it is one of the standard variables in Linux environments, and you can check it by running echo $HOME
. (When you run cd
, it brings you to your home directory exactly because of this variable.)
I want to draw your attention to the last two outputs from the screenshot. Here’s what I did: I logged in as the root user, and as you can see, the root user’s HOME
directory is different!
Why does this matter? Well, if you put BSPWM's configuration files in your current user’s home directory, other users won’t have access to the nicely configured UI (using bspwm
's and other tools' configuration files I’ll show you later). The root user won’t have access either.
So, what happens if you don’t set $XDG_CONFIG_HOME
and place the BSPWM and SXHKD configuration files in .config
within your user’s home directory (/home/<your-username>/.config
)? Here’s what will happen:
- When you log in as your default user and start the X display server, you’ll see your configured setup.
- When you log in as root and start the display server, you’ll see a black screen.
- When you log in as any other user, they’ll also see a black screen.
The solution to avoid these situations—if you truly need a shared configuration—is to place the configuration files somewhere outside of any single user’s home directory.
When it comes to X display server configurations, there’s a standardized location for this: /etc/xdg
. By placing the configuration files there, they become accessible system-wide for all users (for !reading!; a user without sudo
privileges cannot modify them.
For the sake of demonstration in this article, I’ll be using /etc/xdg
for BSPWM and SXHKD configurations. Even though it is possible to set XDG_CONFIG_HOME environmental variable to point to this directory, I would rather not, due to the fact that many apps will try to write something there, if during installation this $XDG_CONFIG_HOME variable is detected.
Luckily, both sxhkd
and bspwm
have option "-c" when they are executed. This option is meant to point to the "custom" location of configuration files.
bspwm configuration: configuration files
After bspwm installation you can find the examples of sxhkd
's and bspwm
's default configurations that can be used as a starting point for your custom configurations. The both configuration files can be found in /usr/share/doc/bspwm/examples
.
Here, I want to introduce some slang related to what I’m about to do. The process of customizing Linux OSs is often called "Linux ricing". This term is widely used on Reddit, especially in a subreddit dedicated to it, where people share their setups. Often, when a setup is particularly cool, you’ll see comments from others asking to share the "dot files".
The term "dot files" actually refers to sharing the configurations used to achieve a specific UI setup. However, "dot files" (or more correctly, DotFiles) is not just slang—there’s even a Debian wiki page dedicated to DotFiles.
Why DotFiles are called this? I mentioned earlier, the default path for BSPWM configs is $HOME/.config, which is a folder, not a file. But its name starts with a dot (.). If you navigate (cd
) to your home directory and list the contents with ls
, you’ll see just regular files and directories. However, if you run ls -a
, you’ll see much more, especially you will see files and directories starting with a (.).
To avoid making this article overly long, I’ll also share my "DotFiles" on GitHub.
Let's proceed with BSPWM configuration. You will have to copy "examples" of configuration to the directory where BSPWM will be searching it. If you are fine that configuration will work only for your current user you have to create subdirectories in $HOME/.config
and place configuration files there in a correct way. I will be keeping configuration files in /etc/xdg
.
$ cd (this will teleport you to your $HOME directory)
# you can double check
$ pwd
$ ls -a
#if you do not see .config directory in the output, you will have to create it
$ mkdir .config
#create a directory for bspwm config
$ mkdir .config/bspwm
#create a SEPARATE directory for sxhkd config
$ mkdir .config/sxhkd
# copy configuration files there:
$ cp /usr/share/doc/bspwm/examples/bspwmrc ./config/bspwm/
$ cp /usr/share/doc/bspwm/examples/sxhkdrc ./config/sxhkd/
#ALternative: I do slightly different - i copy configurations to /etc/xdg
#NB! you do not have to do both, you have to choose ONE source of your configuration files.
$ cd /etc/xdg
$ sudo mkdir bspwm #I can keep files in the same directory, as I will be pointing sxhkd and bspwm to their config files manually
$ sudo cp /usr/share/doc/bspwm/examples/bspwmrc bspwm/
$ sudo cp /usr/share/doc/bspwm/examples/sxhkdrc bspwm/
Now, I can start modifying them. I’ve chosen to keep BSPWM configuration accessible for all users, so I placed them outside my user's home directory. I have to use sudo
— elevated privileges —to modify these files that are in /etc
directory. If you keep your configs in $HOME/.config
, you don’t need sudo
!
This is the example of bspwmrc
configuration:
I’ll start by modifying it.
$ pwd
/etc/xdg #<--I am here
$ cd bspwm
$ sudo vim bspwmrc
#Let's see what is there:
#----------First Line-----------#
pgrep -x sxhkd > /dev/null || sxhkd &
# this command ensures that sxhkd is running along bspwm
#pgrep -x searches for processes sxhkd, if sxhkd is already running, pgrep exits with a success status
#> /dev/null discards the standard output of pgrep. This means the command doesn’t clutter your terminal with process IDs.
#|| - a logical OR operator. The command after the || only runs if previous command, pgrep, did not find sxhkd process. In such case, it starts sxhkd & in the background (& means run in the background).
#As I will not be keeping configiration files in the default directory, I have to inform sxhkd about where its configuration file is when it is launched:
#pgrep -x sxhkd > /dev/null || sxhkd -c "/etc/xdg/bspwm/sxhkdrc" &
#----------Second Line-----------#
bspc monitor -d I II III ...
# this line sets up "or workspaces", you can perceive them as tabs in the browser. You can navigate between them and have various applications opened. To avoid cluttering, I stop on 5 workspaces. There are Roman numbers by default, but you can name them as you want, 1,2,3 ecc or A,B,C... If you install fonts with icons later, you can use even Icons!
# I just trim everything after V.
bspc monitor -d I II III IV V
#NB! If you have more than one monitors, you can use separate bspc monitor commands to assign different sets of workspaces to each monitor.
#bspc monitor HDMI-1 -d I II III
#bspc monitor XX-1 -d IV V VI
#----------Lines 3-7 -----------#
bspc config ...
#these lines are about default configurations about arrangements of windows, for now i leave them as they are
#----------Lines 8-12 -----------#
bspc rule -a ...
#these lines set some specific rules for programs. I comment them out to keep the syntaxis for later, but I definitely do not need these exact rules:
#bspc rule -a ...
#bspc rule -a ...
#bspc rule -a ...
# ...
Next, I’ll move on to modifying sxhkdrc
. In this configuration file, I need to set the hotkey combinations that will handle opening programs for me.
➃ Installing Browser (Brave browser) and Terminal Emulator (Alacritty)
To start, the most crucial program I need is a terminal emulator. I’ll be using Alacritty. I also need a browser, and my favorite is Brave Browser, so I’ll have to install them first.
NB! Alacritty can run ONLY on a GPU, so if it is a problem for some reason, consider another option like Terminator. Another popular choice is the Kitty terminal.
#the current Alacritty version is 0.14. To get it, you will have to build Alacritty from source.
#this command will install for me alacritty v. 0.11,
$ sudo apt install alacritty
#to install brave browser, I will follow official installation guide, which included adding PPA to my apt sources list! I do it, because I trust Brave Browser, so there is no reason to not trust their PPA. However, if you do not share my point of view, go for something like Firefox.
$ sudo apt install curl
# the following command will fetch the signing key and store it in /usr/share/keyrings/
$ sudo curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg
#this command will add a PPA to the sources list from which apt update & upgrade system packages.
$ echo "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg] https://brave-browser-apt-release.s3.brave.com/ stable main"|sudo tee /etc/apt/sources.list.d/brave-browser-release.list
#to inform apt about new source, you have to run this:
$ sudo apt update
#now you can install brave browser from the repository of brave browser
$ sudo apt install brave-browser
Now, I need to go to the sxhkd
configuration file, sxhkdrc
, and add the hotkey shortcuts that will handle opening windows with these two apps for me!
$ pwd
/etc/xdg #<--I am here
$ cd bspwm
#if you placed config files in $HOME/.config:
#$ cd
#$ cd .config/sxhkd
$ sudo vim sxhkdrc #if you are in $HOME/... no need for sudo!
# I replace urxvt terminal emulator with alacritty
super + Return
alacritty
# I add a new shortcut for Brave Browser:
super + b
brave-browser
#A bit on syntaxis:
# - "super" is "Windows" key on your keyboard, in Linux it is called super
# - "+" stands for which key you will press together with "super" key
# - "Return" is Enter key
# - "b" is just a letter of my choice, notice, it is minuscle
# - indented command - this command should be exactly the same that is in use when you launch any app from terminal. If for launching apps before you used only Desktop icons, you will have to google a bit to find the exact commands.
Everything is almost ready! All that is left is just to tell X display server and X11 communication protocol which windows manager they have to start! To do this, you have to create another configuration file .xinitrc
. This file should be in your home directory, not in .config
, just in $HOME
directory.
# "teleport" to your home directory
$ cd
# check for the existence of .xinitrc file
$ ls -a
#if it is not there (most probably), you will have to create one
$ touch .xinitrc
Okay, now the .xinitrc
file is created, but it is empty. Inside, I need to add just a couple of lines to point to launching the bspwm
window manager.
This file will be automatically used by the startx
command, which starts a graphical X session.
NB! In classical desktop environments like GNOME, KDE, etc., the process of starting a graphical session is handled by a Display Manager. A Display Manager is the graphical UI application that you see with a login form prompting you to enter the username of the user you want to log in as and the corresponding password. For example:
The very famous choice for custom UI setups is LightDM, which provides a minimalistic GUI for logging in on the go. However, a Display Manager is optional and doesn’t add an extra layer of security. You can simply log in from the terminal and then use the startx
command to start an X graphical session. However, there is one thing that Display Managers handle automatically, which you will need to configure manually...
To command X11 to execute bspwm
when startx
command is launched is just exec bspwm
. However, to make all your applications with GUI to work properly, there should be another player involved in this. And this other participant is D-Bus a.k.a Desktop Bus.
➄ About Desktop Bus and inter-process communication (IPC)
D-Bus is a message bus system, a simple way for applications to talk to one another. In addition to inter-process communication, D-Bus helps coordinate process lifecycle; it makes it simple and reliable to code a "single instance" application or daemon, and to launch applications and daemons on demand when their services are needed.
D-Bus per se is a daemon, meaning it is a process that runs in the background. On Debian 12, it is managed by systemd
. The D-Bus daemon should be preinstalled and up and running; you can check it with systemctl status dbus
. However, there is quite different output if you run systemctl --user status dbus
:
So, by default, D-Bus System Message Bus is up and running, while D-Bus User Message Bus is not running!
Will your bspwm
setup work even like this? Yes. Will you be able to launch applications, yes, most probably. However, here is the log of Brave Browser when I simply started X session with exec bspwm
:
See: Brave is complaining (in the left terminal window, you can see many repeating errors related to bus: ERRROR:bus.cc: Failed to connect to the bus
), but running. For many applications it is crucial to have D-Bus Message Bus up and running for the X session they run in, so they will not function properly because they use it to communicate with other apps!
Having the D-Bus message bus running for the session is not trivial to configure. As I mentioned, the system D-Bus message bus is up and running right from the start of the boot process because it is one of the crucial systemd services.
Each session you start, when configured properly, must have a DBUS_SESSION_BUS_ADDRESS
. When you boot into a TTY (like in my case, without a GUI environment), the session is started, and systemd
handles it. So, I see this:
I logged in as my user, alisa
, and by default, Debian has the dbus-user-session
package installed. As a result, I have the DBUS_SESSION_BUS_ADDRESS
variable set, which is critical. Without this environment variable, applications you start are unaware of the D-Bus session. So, the objective is to have this variable set (echo $DBUS_SESSION_BUS_ADDRESS
should not be empty!).
When I switch to the root user with su -
, which does not retain any user environment variables, you can see that there is no D-Bus address. However, if I use su
without the -
, the situation is different. Please note that this is a weak example because such a method of logging in is not very thorough and is usually used for other purposes. In general, systemd user space and D-Bus User Message Bus configuration is quite complicated, so my explanation here may provide misleading information.
When you start an X server display with startx, you are starting a graphical session, and it behaves differently. It does not require a login, so systemd does not automatically start a D-Bus session for it.
You might think that after entering the graphical session, you can manually run systemctl --user start dbus
to start the D-Bus session. But this will not work because it’s not that simple. When you start it manually, it runs, and a socket is created. However, systemd behaves differently for users, and you will need to perform various additional steps to make it work properly, leveraging only systemd
capabilities and the dbus-user-session
package.
Since it is a bit quite of the scope of this article, I just recommend reading more on this topic for a deeper understanding, if you are curious. You can start from here.
If you follow a guide like this one, you’ll end up with a user D-Bus user session shared across all sessions and users. While functional, this approach can be considered suboptimal and not exactly the "Debian way," even though many desktop environments choose this route. For more details, you can refer to this communication.
However, if you're not keen on tweaking systemd
services and socket configurations, even here, Debian has you covered! The objective is to start the D-Bus user message bus for a to be initialized BSPWM graphical session (with exec bspwm
). There’s a command-line utility that handles this: dbus-launch
. This utility is part of the Debian dbus-x11
package.
Package: dbus-x11
simple interprocess messaging system (X11 deps)
D-Bus is a message bus, used for sending messages between applications. Conceptually, it fits somewhere in between raw sockets and CORBA in terms of complexity.
This package contains the dbus-launch utility, which automatically launches one D-Bus session bus per X11 display per user. If the dbus-user-session package is also installed, it takes precedence over this package. Source
I install it:
$ sudo apt install dbus-x11
If you have googled around bspwm
installation and configuration, you may have encountered the line you should add to your .xinitrc
to start bspwm
when graphical session is launched:
exec dbus-launch --exit-with-x11 bspwm
. This, however, is not entirely correct, as it should be exec dbus-launch --exit-with-session bspwm
.
I modify the .xinitrc
file
$ cd
$ vim .xinitrc
exec dbus-launch --exit-with-session bspwm -c "/etc/xdg/bspwm/bspwmrc"
#if you decided to place the configuration of bspwm in $HOME/.config, you do not need to specify path to configuration file!
# exec dbus-launch --exit-with-session bspwm
dbus-launch --exit-with-session bspwm -c "/etc/xdg/bspwm/bspwmrc"
works well for me because I include only the BSPWM launch command in .xinitrc
. All other programs that are part of the session and run in the background are started from the BSPWM configuration.
However, if you want to start them separately, you need a slightly different command: exec dbus-launch --exit-with-session ~/.xinitrc.
In my case, to ensure complete tidiness, i go a bit further, and add the check for D-Bus User Bus already running:
$ cd
$ vim .xinitr
if [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
exec dbus-launch --exit-with-session bspwm -c "/etc/xdg/bspwm/bspwmrc" exit
else
exec dbus-launch --exit-with-session bspwm -c "/etc/xdg/bspwm/bspwmrc"
fi
Finally, it's time to try it! In terminal I execute the command startx
:
Voila!
So, everything is ready with the base functionality! Now, I just need to proceed with installing additional software to complete my custom UI setup!
➅ Installing Status Bar utility (polybar), File Manager (thunar), App Launcher (dmenu), Notification Daemon (dunst) and Windows Compositor (picom).
Status Bar: Polybar
First, I need a status bar. In the context of a desktop environment, a status bar refers to:
For this I will be using Polybar, that is installed with:
sudo apt install polybar
Polybar is a highly customizable tool, though it comes with some limitations. For example, you cannot use SVG icons directly, but you can use fonts that support icons and include characters from those fonts. By default, this is how the status bar will look if you use default configuration:
There is the detailed documentation on customization: Polybar Wiki. Like BSPWM and SXHKD, Polybar is customizable through a configuration file:
By default, polybar will load the config file from ~/.config/polybar/config.ini, /etc/xdg/polybar/config.ini, or /etc/polybar/config.ini depending on which it finds first.
If you do not specify the name of the bar and your config file only contains a single bar, polybar will display that bar. Otherwise you have to explicitly specify bar name. (Source)
After installation, the default configuration file can be found at /etc/polybar/config.ini
. For now, I’ll leave the default configuration as it is. I plan to modify it in the next article, focusing on the main functionality I want to add or adjust. This includes status bar buttons for managing Wi-Fi connections, sound volume, Bluetooth connections, and display backlight.
In fact, I plan to have two Polybar status bars—one at the top and another at the bottom of the screen. You can create objects like icons or even simple text labels (as buttons) on Polybar and attach shell scripts to them. These scripts will define the functionality triggered by button presses, mouse clicks, arrow keys, or even the mouse wheel!
Notification daemon: dunst
A notification daemon handles the display of notifications, such as error messages or warnings.
Dunst is a highly configurable and lightweight notification daemon. It can be configured to display various useful notifications, such as messages from email, social media, and more. You can install it with:
sudo apt install dunst
The default configuration will be placed in /etc/xdg/dunst/dunstrc
. If you prefer to keep all configurations in $HOME/.config
, you’ll need to copy it there using:
$ cd
$ mkdir .config/dunst
$ cp /etc/xdg/dunst/dunstrc $HOME/.config/dunst
Compositor: picom
Although BSPWM’s configuration allows control over windows, it primarily focuses on how they are positioned and where the new windows are rendered. The appearance of windows, however, is managed by a compositor. In my case, this will be Picom.
I can customize the windows through Picom’s configuration file, where I can set parameters such as opacity, blurring, shadows, scaling factors, and more.
Like BSPWM, SXHKD, and Polybar, Picom can also be customized through its configuration file:
picom could read from a configuration file if libconfig support is compiled in. If --config is not used, picom will seek for a configuration file in $XDG_CONFIG_HOME/picom.conf (~/.config/picom.conf, usually), then $XDG_CONFIG_HOME/picom/picom.conf, then $XDG_CONFIG_DIRS/picom.conf (often /etc/xdg/picom.conf), then $XDG_CONFIG_DIRS/picom/picom.conf. (Source)
The default configuration example can be found at /usr/share/doc/picom/examples/picom.sample.config
. I copy it to my other configuration files so they remain organized and stored together.
$ cd /etc/xdg/bspwm
#alternatively, if you keep all configs in $HOME/.config:
# $ cd
# $ cd .config
# $ mkdir picom
# $ cp /usr/share/doc/picom/examples/picom.sample.config picom/
# $ mv picom/picom.sample.config picom/picom.config
$ sudo cp /usr/share/doc/picom/examples/picom.sample.config .
$ sudo mv picom.sample.config picom.config
All three tools—Polybar, Dunst, and Picom—need to run in the background rather than being launched on demand. To ensure this, I will need to modify the BSPWM configuration file so that not only SXHKD is launched together with BSPWM, but also these guys.
$ cd /etc/xdg/bspwm
# if you decided to keep configurations in $HOME/.config:
#$ cd
#$ cd .config/bspwm
$ sudo vim bspwmrc #you do not need sudo if you are in $HOME
#I add this lines after pgrep -x sxhkd....
# to launch polybar:
# terminate in "elegant" way all running polybarS, if there are some (alternative to kill)
polybar-msg cmd quit
# require logging of polybar
echo "---" | tee -a /tmp/polybar.log
# launch polybar in background and start logging. As for now I am using default polybar config, the command polybar will automatically fetch it, if the configuration is placed in one of the places, where polybar searches it (in my case it is in /etc/polybar/config.ini)
polybar 2>&1 | tee -a /tmp/polybar.log & disown
#launching picom
# for picom i specify path to configuration
pgrep -x picom > /dev/null || picom --config /etc/xdg/bspwm/picom.conf &
#launching dunst
pgrep -x dunst > /dev/null || dunst &
File Manager: Thunar
File Manager is the guy that you open to explore what's on your PC, find files, move/copy files and interact with files from USB pen drives:
I use Thunar, that can be installed with
sudo apt install thunar
App Launcher: dmenu
App Launcher is a tool that displays installed applications, allowing you to launch them with a mouse click. Additionally, it provides a search utility for quickly finding the app you’re looking for.
In classic desktop environments, an app launcher typically looks something like this—for example, GNOME's app launcher:
I am using dmenu. This app launcher is much more minimalistic compared to the GNOME app launcher displayed above. It won’t display all the icons or other extras, but it will allow you to search efficiently and launch apps with a click.
I prefer this minimalist approach because I rarely use app launcher anyway. Most of the time, I launch apps from the command line or by using SXHKD keybindings, which I configure for the apps I frequently use. dmenu
can be installed with this command:
sudo apt install suckless-tools
I need to add the Thunar and dmenu to the SXHKD configuration to bind hotkeys for launching them.
$ cd /etc/xdg/bspwm
# if you decided to keep configurations in $HOME/.config:
#$ cd
#$ cd .config/sxhkd
$ sudo vim sxhkdrc #you do not need sudo if you are in $HOME
#I add this lines only
# file manger
super + f
thunar
#dmenu is already there, if you kept the default config!
# super + @space
# dmenu_run
Here we are! Now, if I run startx from terminal, all my setup starts:
VOILA!
Top comments (0)