Author's Note: This article was originally written for Debian 12 Stable (Bookworm). However, with the release of Debian 13 approaching -and since I’m currently using Debian Testing (Trixie) - I’ll be sharing screenshots and configs from my own setup. Things might look slightly different on your end. That said, some aspects may vary significantly (or even a lot), and I’ll make sure to point those out.
Customization is one of those things where, if you want your setup to look cool, you sometimes need cutting-edge versions of software.
Most of the content here will still work on Debian Stable. Just keep in mind that some minor functionalities in certain apps might not.
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 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).
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). 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!
1. 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.
2. 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!
NB! If you cannot launch the X server with startx
and your error says something like (EE) no screens/output found (EE), it’s most likely an issue with the NVIDIA not writing anything properly into the Xorg configuration file after installation. That happened to me once on Debian Sid. The solution from the Debian Wiki: NVIDIA Proprietary Driver:
As the NVIDIA driver is not autodetected by Xorg, a configuration file is required to be supplied. Modern Debian packages for the NVIDIA driver should not require you to do anything listed here as they handle this automatically during installation, but if you run into issues, or are using a much older version of Debian, you may try going through these steps.
Automatic:
Install thenvidia-xconfig
package, then runsudo nvidia-xconfig
. It will automatically generate a Xorg configuration file at /etc/X11/xorg.conf.
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.
3. Installing and configuring BSPWM Windows Manager
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.
If you really need a setup for multiple users, you can place all configurations I mention below in /etc/xdg/..
repeating the directory structure I will use. Both sxhkd
and bspwm
have option "-c" when they are executed. This option is meant to point to the "custom" location of configuration files. Both software is searching for location of their config by interrogating environmental variable XDG_CONFIG_HOME
. If this variable is not set, the fallback default directory for config is $HOME/.config/...
. Even though it is possible to set XDG_CONFIG_HOME environmental variable to point to /etc/xdg/
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.
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/
Now, I can start modifying them. If you’ve chosen to keep BSPWM configuration accessible for all users and placed them outside of user's home
directory (in /etc/xdg
), you will 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.
$ cd .config/bspwm
$ sudo vim.tiny 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.
4. Installing Browser (Brave browser) and Terminal Emulator (Alacritty)
To start, the most crucial program I need is a terminal emulator. There are 2 cool Terminal emulators, Kitty and Alacritty. I will be installing Kitty. 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 Kitty or another option like Terminator.
Debian 12 Stable vs Debian Testing Alert!
Current version of Kitty: version 0.41.1
Kitty version in bookworm repo: 0.26.5-5
Kitty version in trixie repo: 0.39.1-1
Current version of Alacritty: 0.15.1
Alacritty version in bookworm repo: 0.11.0-4
Alacritty version in trixie repo: 0.15.1-1
The difference between Alacritty from bookworm repo vs trixie repo is quite notable, because alacritty of version 0.11 uses .yaml configuration files and alacritty of version 0.15 .toml configuration file.
On Debian stable to get Alacritty version is 0.15, you will have to build Alacritty from source.
In install Kitty:
$ sudo apt install kitty
$ kitty -v
kitty 0.39.1 created by Kovid Goyal
To install Brave Browser I execute a single command:
# $ sudo apt install curl
$ curl -fsS https://dl.brave.com/install.sh | sh
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!
$ cd
$ sudo vim.tiny .config/sxhkd/sxhkdrc
# I replace urxvt terminal emulator with kitty
super + Return
kitty
# 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:
However, Display Managers are not always GUI-based; sometimes they are TUI (Text User Interface):
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.
5. 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.tiny .xinitrc
exec dbus-launch --exit-with-session bspwm
#if you decided to place the configuration of bspwm in /etc/xdg, you do need to specify path to configuration file!
# exec dbus-launch --exit-with-session bspwm -c "/etc/xdg/bspwm/bspwmrc"
dbus-launch --exit-with-session bspwm
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 .xinitrc
if [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
exec dbus-launch --exit-with-session bspwm
else
exit 0
fi
Finally, it's time to try it! In terminal I execute the command startx
:
You can see that the previous DBus-related error, “ERROR: bus.cc: Failed to connect to the bus” has been resolved. However, you might notice other errors—there are some related to kwalletd
. It’s happening because Brave Browser tries to launch KDE Wallet Manager to use it a security mechanism for saved passwords storage. KDE Wallet Manager is part of the KDE Plasma desktop environment, and since I don’t have KDE Plasma installed, I don’t have the wallet manager either.
That said, Brave still runs fine without it. If you’d like to use KDE Wallet Manager, it’s easy to install with sudo apt install kwalletmanager
. Don't worry it will not install you KDE Plasma DE, even though you will see a quite long list of stuff to be installed into your system.
At the next startx
session, you’ll most likely be prompted to configure the manager by either setting a password or pointing to an existing GPG key (you need to generate that beforehand, details about how to do it are here).
However, KWallet may not work properly or might not pop up at all if there’s another missing configuration—the XDG desktop portal. I’ll discuss the XDG Desktop Portal in my next article, when I’ll be setting up the sound server to configure audio devices. It’s important for the sound server, so I decided to move that explanation to the next part, so I can show you more logs.
For now, for my standards everything is running as it should, and the base functionality is ready! Now, I just need to proceed with installing additional software to complete my custom UI setup!
6. Installing Status Bar utility (polybar), File Manager (thunar), App Launcher (dmenu) and Notification Daemon (dunst).
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
How to launch Dunst?
If you've ever seen some dotfiles of other people who use Dunst in their Linux ricing setups, maybe you noticed that they just add the line dunst &
to their bspwmrc
config. If you copied the config files to $HOME/.config/dunst
, that's fine, dunst will find its config there. However, if you kept the config files where they were originally placed in /etc/xdg/dunst/dunstrc
(like I do), you need to tell Dunst to look for them there with -conf
argument (if you did not set explicitly $XDG_HOME_CONFIG
to point to /etc/xdg
).
However, there is something more than configuration. Spoiler: If you just add dunst &
to your bspwmrc
after instalaltion, DUNST WILL NOT WORK, and you will most likely not notice it (it will just silently go in error, if you do not set for it logging). Now I will explain why.
First, Dunst and Polybar are different. Polybar runs in the background, and so does Dunst. But Dunst is a notification daemon, meaning it's a service, that can be managed by systemd
like other daemons. You can manage it with systemctl
, and it should be available so that apps needing to notify you about something will have Dunst up and running when your X server is running. Let me show you some logs from the console before I start the display server with startx
.
If you try to start dunst
from console (no graphical session), it will go in error:
$ systemctl --user start dunst
...
Dec 30 11:07:49 wonderland systemd[946]: Starting dunst.service - Dunst notification daemon...
Dec 30 11:07:49 wonderland dunst[1061]: WARNING: Cannot open X11 display.
Dec 30 11:07:49 wonderland dunst[1061]: ERROR: [ get_x11_output:0065] Couldn't initialize X11 output. Aborting...
Dec 30 11:07:49 wonderland systemd[946]: dunst.service: Main process exited, code=killed, status=5/TRAP
Dec 30 11:07:49 wonderland systemd[946]: dunst.service: Failed with result 'signal'.
Dec 30 11:07:49 wonderland systemd[946]: Failed to start dunst.service - Dunst notification daemon.
$ dunst
WARNING: Cannot open X11 display.
ERROR: [ get_x11_output:0065] Couldn't initialize X11 output. Aborting...
So, the error is explicit: it cannot find the X11 output, which is pretty logical because the X server is not running and graphical X session was not started yet.
Okay, I start the server and graphical session with startx
, and then I restart dunst:
$ systemctl --user restart dunst
Job for dunst.service failed because a fatal signal was delivered to the control process.
See "systemctl --user status dunst.service" and "journalctl --user -xeu dunst.service" for details.
Same error!
But as you can see, $DISPLAY
is not empty when I echo
it. So, what’s the problem?
The issue is that I tried to start Dunst before without X11, and with the unset DISPLAY
variable. So even when I restarted it now, from active graphical session, it goes into an error. How can I fix this?
I need to make sure the DISPLAY
environment variable is available for systemd
user services before I restart Dunst. I can import this environment variable for systemd user services with:
$ systemctl --user import-environment DISPLAY
$ systemctl --user restart dunst
Voila:
So, the important part about starting Dunst is that before you can start it, you need to import the DISPLAY
environment variable using:
$ systemctl --user import-environment DISPLAY
When you start the graphical session with startx
from the console, the DISPLAY
variable is not available there initially. After graphical X session starts, it becomes available, but Dunst doesn't automatically get informed about it.
I prefer to launch Dunst using systemctl
rather than dunst &
. If you placed all configurations in /etc/xdg
rather than in $HOME/.config
, to launch Dunst with systemctl
you will also need to pass the path to the config file. I don’t have to do this as I've placed the Dunst configuration in $HOME/.config/dunst/dunstrc
.
You can find the configuration files for user-space services managed by systemd in /usr/lib/systemd/user
$ ls /usr/lib/systemd/user
$ sudo vim.tint /usr/lib/systemd/user/dunst.service
[Unit]
Description=Dunst notification daemon
Documentation=man:dunst(1)
PartOf=graphical-session.target
[Service]
Type=dbus
BusName=org.freedesktop.Notifications
ExecStart=/usr/bin/dunst -conf /etc/xdg/dunst/dunstrc
I added the argument to point to the config file:
ExecStart=/usr/bin/dunst -conf /etc/xdg/dunst/dunstrc
Now I can add both Polybar and Dunst to the bspwmrc
so they are launched automatically by bpc
. I will need to modify the BSPWM configuration file.
$ cd
$ vim.tiny .config/bspwmrc #you do not need sudo if you are in $HOME
#I add this lines after pgrep -x sxhkd....
#DISPLAY env import
systemctl --user import-environment DISPLAY
#DUNST Launching
systemctl --user start dunst
#POLYBAR Launching
# 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
XDG desktop portal
XDG Desktop Portal
A portal frontend service for Flatpak and other desktop containment frameworks.
xdg-desktop-portal works by exposing a series of D-Bus interfaces known as portals under a well-known name (org.freedesktop.portal.Desktop) and object path (/org/freedesktop/portal/desktop). (Source)
Mostly, it’s needed for containerized software like Flatpaks, as the quote above says. However, it’s not only for this, and even if you don’t use Flatpak, you could still run into problems. A very trivial case could be with a browser like Mozilla or any other when you try to upload something, and the file chooser dialog is needed. It won’t work without the portal.
Portals were designed for use with applications sandboxed through Flatpak, but any application can use portals to provide uniform access to features independent of desktops and toolkits. This is commonly used, for example, to allow screen sharing on Wayland via PipeWire, or to use file open and save dialogs on Firefox that use the same toolkit as your current desktop environment. (Arch Wiki: XDG Desktop Portal)
All well-known Desktop environments like KDE Plasma, Gnome, XFCE etc. use XDG portals, however, what is important is that XDG Desktop Portal has a backend and different DEs use different backends.
When an application sends a request to the portal, it is handled by xdg-desktop-portal, which then forwards it to a backend implementation. This allows implementations to provide suitable user interfaces that fit into the user's desktop environments, and access environment-specific APIs for requests like opening a URI or recording the screen. Multiple backends can be installed and used at the same time.(Arch Wiki: XDG Desktop Portal Backends)
Each XDG portal backend is based on a specific Toolkit. For example GNOME's desktop portal backend is based on GTK v.4; while KDE Plasma's backend is based on Qt 6. And this is actually why you cannot customize DEs in a similar way. For example, you cannot install a Theme for KDE Plasma 6 and try to apply it to GNOME, it will just not work. Same even for KDE Plasma 5 and KDE Plasma 6 - first uses a Qt 5 and the latter one Qt 6.
Anyway, with BSPWM, what should you use? Well, there is a generic backend for custom DEs that is based on GTK v.3. This GTK 3.0 Toolkit will enable you later to apply Themes for your DE! For example, if you like Dracula Theme, you will be able to apply it DE-wide : colors, fonts ecc.
However, remember, XDG desktop portal with at least one backend is not just a question of whether you will be able to apply custom themes or not later, it is about providing applications the means for their proper functioning!
XDG Desktop Portal can be installed with sudo apt install xdg-desktop-portal
on Debian. The common mistake is to install only this package. This package does not bring you any "default" backend mentioned above.
I proceed with installation of xdg-desktop-portal
first.
Package: xdg-desktop-portal
desktop integration portal for Flatpak and Snap
xdg-desktop-portal provides a portal frontend service for Flatpak, Snap, and possibly other desktop containment/sandboxing frameworks. This service is made available to the sandboxed application, and provides mediated D-Bus interfaces for file access, URI opening, printing and similar desktop integration features. (Source)
$ sudo apt install xdg-desktop-portal
$ systemctl --user start xdg-desktop-portal
$ systemctl --user status xdg-desktop-portal
● xdg-desktop-portal.service - Portal service
Loaded: loaded (/usr/lib/systemd/user/xdg-desktop-portal.service; static)
Active: active (running) since Mon 2024-12-30 13:29:28 CET; 1s ago
....
Mar 30 13:29:28 wonderland systemd[1017]: Starting xdg-desktop-portal.service - Portal service...
Mar 30 13:29:28 wonderland xdg-desktop-por[7132]: No skeleton to export
Mar 30 13:29:28 wonderland systemd[1017]: Started xdg-desktop-portal.service - Portal service.
As you can see, the XDG Desktop Portal service is reporting that no backend was found: xdg-desktop-portal[7132]: No skeleton to export.
I install "generic" backend xdg-desktop-portal-gtk
that uses GTK 3 Toolkit.
$ sudo apt install xdg-desktop-portal-gtk
# you can check that the warning about Skeleton is gone
$ systemctl --user restart xdg-desktop-portal
$ systemctl --user status xdg-desktop-portal
In general, GTK will be completely enough for my setup. But please note, some apps, especially Flatpaks depend on other services that require a specific backend, like GNOME's one. If a Flatpak doesn’t work with GTK generic backend, you’ll have to install another backend. As I mentioned above, multiple backends can be installed and used at the same time. XDG desktop portal will redirect app's requests to the right backend.
File manager
yazi
$ sudo apt install ffmpeg 7zip jq poppler-utils fd-find ripgrep fzf zoxide imagemagick
sudo apt install debootstrap
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!