Andrey's Blog

Linux: User Environments and the Desktop Stack

Table of Contents

  1. Startup Files
  2. The Command Path
  3. Login and Non-Login Shells
  4. Startup File Order and Examples
  5. Shell Defaults: Prompt, Editor, Pager, umask
  6. The Desktop Stack
  7. Framebuffers
  8. The X Window System
  9. Wayland
  10. Window Managers, Toolkits and Desktop Environments
  11. D-Bus
  12. Cheat Sheet

1. Startup Files

Startup files (dotfiles) are shell scripts that run when a shell or session starts. They set up your environment: the command path, prompt, default editor, aliases and so on.

Guidelines for writing them:

Environment variables vs shell variables:

 PAGER=less              ← shell variable: only this shell sees it
 export PAGER            ← environment variable: copied to every child process
 export PAGER=less       ← same, in one line
 bash (PAGER=less, exported)
   ├── man       sees PAGER=less ✓
   └── git log   sees PAGER=less ✓

 bash (PAGER=less, NOT exported)
   └── man       sees nothing → uses its own default
env                 # all environment variables
printenv PATH       # one variable

2. The Command Path

PATH is a colon-separated list of directories. When you type a command without a /, the shell searches these directories left to right and runs the first match.

A minimal path:

/usr/local/bin
/usr/bin
/bin
 You type:  python3

 PATH = /home/juser/bin : /usr/local/bin : /usr/bin : /bin
              │                 │              │
              ▼                 ▼              ▼
          python3?  no      python3?  no    python3?  YES → /usr/bin/python3 runs
                                                     (search stops here)
DirectoryContents
~/bin, ~/.local/binYour own scripts and user-installed tools (pip --user, pipx)
/usr/local/binSoftware installed locally by the admin, not by the package manager
/usr/binSoftware from distro packages
/binEssential commands; on modern distros a symlink to /usr/bin (usrmerge)
/usr/sbin, /sbinSystem administration commands

Add a directory:

PATH=$HOME/bin:$PATH        # prepend: your versions win
PATH=$PATH:$HOME/bin        # append: system versions win
export PATH

⚠️ Never put . (the current directory) in PATH, especially not at the front. A malicious file named ls in some directory would run instead of the real one. Type ./script instead.

Finding out what runs:

type ls          # alias, function, builtin, or file path
command -v git   # path of the command (POSIX, good in scripts)
which -a python3 # every matching file in PATH
hash -r          # clear bash's cache of command locations after moving programs

3. Login and Non-Login Shells

Bash reads different startup files depending on how it was started:

Shell typeWhenExample
Login shellYou log in: text console, SSH session, su -, bash -lssh t490
Interactive non-login shellA new shell from within a session: terminal window in a desktop, typing bashOpening foot, Alacritty, GNOME Terminal
Non-interactive shellRunning a script./backup.sh, ssh host 'cmd'

Check what you have:

shopt -q login_shell && echo login || echo non-login
echo $0          # "-bash" (leading dash) = login shell
[[ $- == *i* ]] && echo interactive

4. Startup File Order and Examples

Which files bash reads

                   bash starts
                       │
          ┌────────────┴─────────────┐
          │ Login shell?             │
          └────┬────────────────┬────┘
           yes │                │ no
               ▼                ▼
   ┌──────────────────────┐  ┌───────────────────────────┐
   │ /etc/profile         │  │ Interactive?              │
   │ (+ /etc/profile.d/*) │  └──────┬─────────────┬──────┘
   └──────────┬───────────┘     yes │             │ no
              ▼                     ▼             ▼
   ┌──────────────────────┐  ┌──────────────┐  ┌─────────────────┐
   │ FIRST one found of:  │  │ /etc/bashrc  │  │ file named in   │
   │  ~/.bash_profile     │  │ ~/.bashrc    │  │ $BASH_ENV       │
   │  ~/.bash_login       │  └──────────────┘  │ (usually none)  │
   │  ~/.profile          │                    └─────────────────┘
   └──────────┬───────────┘
              ▼
   on logout: ~/.bash_logout

(/etc/bashrc on Fedora/RHEL is loaded from ~/.bashrc; Debian/Ubuntu call it /etc/bash.bashrc and bash reads it directly.)

The problem: a login shell doesn’t read ~/.bashrc, and a non-login shell doesn’t read ~/.bash_profile. The standard fix is to put everything in ~/.bashrc and load it from ~/.bash_profile:

# ~/.bash_profile
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

Rule of thumb:

Put it inWhat
~/.bash_profileLittle or nothing, just source ~/.bashrc. Maybe things that should run once per login (e.g. start an agent)
~/.bashrcPATH, environment variables, aliases, functions, prompt, shell options

Example .bashrc

# Command path.
PATH=/usr/local/bin:/usr/bin:/bin:/usr/games
PATH=$HOME/bin:$PATH

# PS1 is the regular prompt.
# Substitutions include:
#   \u username   \h hostname     \w current directory
#   \! history number  \s shell name  \$ $ if regular user (# for root)
PS1='\u\$ '

# EDITOR and VISUAL determine the editor that programs such as less
# and mail clients invoke when asked to edit a file.
EDITOR=vi
VISUAL=vi

# PAGER is the default text file viewer for programs such as man.
PAGER=less

# These are some handy options for less.
# A different style is LESS=FRX
# (F=quit at end, R=show raw characters, X=don't use alt screen)
LESS=meiX

# You must export environment variables.
export PATH EDITOR VISUAL PAGER LESS

# By default, give other users read-only access to most new files.
umask 022

Common additions:

# Only continue for interactive shells (keeps scp/rsync working)
case $- in *i*) ;; *) return ;; esac

# Aliases
alias ll='ls -lh'
alias k='kubectl'

# History
HISTSIZE=10000
HISTCONTROL=ignoreboth      # skip duplicates and lines starting with a space
shopt -s histappend         # append instead of overwrite

# Per-tool snippets in separate files
for f in ~/.bashrc.d/*.sh; do [ -r "$f" ] && . "$f"; done

Apply changes to the current shell without logging out:

. ~/.bashrc          # or: source ~/.bashrc

Desktop and systemd sessions

Graphical sessions often don’t start a login shell, so variables in ~/.bash_profile may not reach GUI apps. On systemd-based desktops, put session-wide variables in ~/.config/environment.d/*.conf:

# ~/.config/environment.d/10-editor.conf
EDITOR=nvim
PATH=$HOME/.local/bin:$PATH

Check with systemctl --user show-environment.


5. Shell Defaults: Prompt, Editor, Pager, umask

Prompt (PS1)

EscapeMeaning
\uUsername
\hHostname (short)
\wCurrent directory (~ for home)
\WLast part of the current directory
\!History number
\sShell name
\tTime (HH:MM:SS)
\$$ for regular users, # for root
PS1='\u@\h:\w\$ '                             # juser@t490:~/projects$
PS1='\[\e[32m\]\u@\h\[\e[0m\]:\w\$ '          # with color (\[ \] mark non-printing parts)

Wrap color codes in \[ … \], or bash miscounts the prompt length and line editing breaks. Prompt frameworks like Starship generate fancy prompts for any shell.

Editor

Programs that need you to edit text (git commit messages, crontab -e, sudoedit, less pressing v) look at:

Set both to the same editor: export EDITOR=nvim VISUAL=nvim.

Pager

PAGER is the viewer for long output (man, git log, journalctl). Usually less.

LESS options from the example:

OptionMeaning
mVerbose prompt with percentage through the file
eQuit the second time you reach the end
iCase-insensitive search (unless the pattern has capitals)
XDon’t clear the screen on exit (output stays visible)
FQuit right away if the output fits on one screen
RShow color escape codes (needed for colored git/journalctl output)

umask

The umask removes permission bits from newly created files and directories.

               files start at   directories start at
                    666                777
 umask 022  →   −   022            −   022
                ───────            ───────
                    644 rw-r--r--      755 rwxr-xr-x    ← others can read

 umask 077  →       600 rw-------      700 rwx------    ← private
umask           # show (e.g. 0022)
umask -S        # symbolic: u=rwx,g=rx,o=rx
umask 077       # private files from now on in this shell

Many distros (including Fedora) default to 022 for root and 002 for regular users with a personal group of the same name.


6. The Desktop Stack

A Linux desktop is a stack of separate pieces. Each can be swapped independently.

 ┌────────────────────────────────────────────────────────────────────┐
 │ Applications             Firefox, file manager, terminal           │
 ├────────────────────────────────────────────────────────────────────┤
 │ Toolkits                 GTK, Qt  (buttons, menus, text widgets)   │
 ├────────────────────────────────────────────────────────────────────┤
 │ Desktop environment      GNOME, KDE Plasma, Xfce                   │
 │   (panels, settings, notifications, file manager — optional)       │
 ├────────────────────────────────────────────────────────────────────┤
 │ Display server /         Wayland compositor (Mutter, KWin, Sway)   │
 │ window manager           or X server + window manager              │
 ├────────────────────────────────────────────────────────────────────┤
 │ Graphics stack           Mesa (OpenGL/Vulkan), libinput (input)    │
 ├────────────────────────────────────────────────────────────────────┤
 │ Kernel                   DRM/KMS (display), evdev (input), fb      │
 ├────────────────────────────────────────────────────────────────────┤
 │ Hardware                 GPU, monitor, keyboard, mouse             │
 └────────────────────────────────────────────────────────────────────┘

 Alongside everything:  D-Bus (messages between all of these)

Sway (Fedora Sway Atomic) is a good example: there’s no full desktop environment, just a Wayland compositor that is also a tiling window manager, with separate tools for the bar (waybar), launcher, notifications and so on.


7. Framebuffers

A framebuffer is a block of memory holding the image shown on screen: one value per pixel. Whatever writes into it appears on the display.

 framebuffer memory               monitor
 ┌───┬───┬───┬───┬───┐          ┌───────────────┐
 │px │px │px │px │px │ ───────► │               │
 ├───┼───┼───┼───┼───┤ scanout  │    image      │
 │px │px │px │px │px │          │               │
 └───┴───┴───┴───┴───┘          └───────────────┘
  each pixel = color value (e.g. 32 bits RGBA)
ls -l /dev/dri/
cat /sys/class/drm/card*-*/status     # which outputs have a connected monitor

8. The X Window System

X11 (X.Org) is the traditional Linux graphics system, from the 1980s. It uses a client/server model: the X server owns the screen and input devices, and applications are X clients that ask the server to draw.

  ┌──────────┐   ┌──────────┐   ┌───────────────────┐
  │ Firefox  │   │ xterm    │   │ Window manager    │   ← all are X clients
  │ (client) │   │ (client) │   │ (client too!)     │
  └────┬─────┘   └────┬─────┘   └─────────┬─────────┘
       │ X protocol (Unix socket /tmp/.X11-unix/X0, or network)
       ▼              ▼                   ▼
  ┌─────────────────────────────────────────────────┐
  │ X server (Xorg)                                 │
  │ owns the screen, keyboard, mouse                │
  └────────────────────┬────────────────────────────┘
                       ▼
                 kernel (DRM/KMS, evdev) → hardware

Key ideas:

echo $DISPLAY
xprop                  # click a window to see its properties (X only)
xev                    # print X input events

9. Wayland

Wayland replaces X11. It’s a protocol, not a program: the compositor implements it and combines the roles of display server, window manager and compositor in one process.

  ┌──────────┐   ┌──────────┐   ┌───────────────────┐
  │ Firefox  │   │ foot     │   │ X11 app           │
  │          │   │ terminal │   │   via XWayland    │
  └────┬─────┘   └────┬─────┘   └─────────┬─────────┘
       │ each client renders its OWN window into a buffer (GPU)
       │ and hands it to the compositor
       │ Wayland protocol (Unix socket $XDG_RUNTIME_DIR/wayland-1)
       ▼              ▼                   ▼
  ┌──────────────────────────────────────────────────┐
  │ Wayland compositor                               │
  │ (Sway, GNOME Mutter, KDE KWin, Hyprland)         │
  │  • window management   • input routing           │
  │  • combines all buffers into the final image     │
  └────────────────────┬─────────────────────────────┘
                       ▼
                 kernel (DRM/KMS, libinput/evdev) → hardware

Differences from X11:

X11Wayland
Who draws windowsX server, on requestEach client draws its own buffer
Window managerSeparate clientPart of the compositor
IsolationClients can spy on each otherClients can’t read others’ input or screen
Screenshots, screen sharing, global hotkeysAny app can do itVia portals (xdg-desktop-portal) or compositor-specific protocols
Network transparencyBuilt inNot built in (use waypipe, RDP)
Tearing/scalingCommon problems“Every frame is perfect” design, per-monitor scaling

Find the Wayland socket and the compositor:

echo $XDG_SESSION_TYPE           # wayland or x11
echo $WAYLAND_DISPLAY            # e.g. wayland-1
ss -xlp | grep wayland-          # listening Unix socket and the process owning it

ss -x lists Unix domain sockets, -l listening ones, -p the process. The output shows something like /run/user/1000/wayland-1 … users:(("sway",pid=1234,…)), which tells you which program is your compositor.


10. Window Managers, Toolkits and Desktop Environments

Window managers

The window manager decides where windows go, their size and focus, and draws decorations.

TypeHowExamples
Stacking/floatingWindows overlap freely, move with the mouseOpenbox, Xfwm, Mutter
TilingWindows fill the screen without overlapping, keyboard driveni3 (X11), Sway (Wayland, i3-compatible), Hyprland
DynamicSwitch between layoutsdwm, awesome

Under Wayland, the window manager is the compositor.

Toolkits

Toolkits are libraries that give applications ready-made widgets (buttons, menus, dialogs) and handle talking to X11 or Wayland.

A toolkit backend chooses the display protocol: GDK_BACKEND=wayland or QT_QPA_PLATFORM=wayland. Apps without Wayland support run through XWayland.

Desktop environments

A desktop environment bundles a window manager/compositor with a consistent set of tools: panels, settings app, file manager, notifications, screen locker. Examples: GNOME (GTK), KDE Plasma (Qt), Xfce, Cinnamon.

Freedesktop.org standards let parts from different environments cooperate:


11. D-Bus

D-Bus (Desktop Bus) is a message system that lets processes talk to each other: call methods, read properties and receive signals (events), without each pair needing its own protocol.

There are two buses:

 ┌──────────────────────── SYSTEM BUS ─────────────────────────────┐
 │  /run/dbus/system_bus_socket     one per machine                │
 │                                                                 │
 │  systemd   NetworkManager   UPower   udisks2   logind   polkit  │
 └───────────────────────────▲─────────────────────────────────────┘
                             │  e.g. "USB stick inserted", "battery 20%"
                             │
 ┌──────────────────────── SESSION BUS ────────────────────────────┐
 │  $XDG_RUNTIME_DIR/bus  (/run/user/1000/bus)   one per user login │
 │                                                                 │
 │  notification daemon   media player   file manager   portals    │
 │  terminal / apps   (e.g. "show notification", "pause music")    │
 └─────────────────────────────────────────────────────────────────┘
BusWhoExamples
System busSystem services, root-owned daemonsNetworkManager reports Wi-Fi changes; systemctl talks to systemd; udisks mounts USB drives
Session busPrograms in your login sessionnotify-send → notification daemon; media keys → music player (MPRIS); screen sharing via xdg-desktop-portal

Message addressing:

 service (bus name)         object path                  interface.method
 org.freedesktop.Notifications  /org/freedesktop/Notifications  org.freedesktop.Notifications.Notify

Socket activation: a service doesn’t need to be running. When a message arrives for a known bus name, D-Bus (or systemd) starts the service on demand.

Watching and calling

dbus-monitor --system          # watch messages on the system bus (may need sudo)
dbus-monitor --session         # watch messages in your session
busctl list                    # services on the system bus
busctl --user list             # services on the session bus
busctl tree org.freedesktop.NetworkManager
notify-send "Hello" "from D-Bus"     # sends a message on the session bus

Try it: run dbus-monitor --session in one terminal and notify-send test in another. You’ll see the Notify method call pass by.

On Fedora, the D-Bus daemon is dbus-broker. systemctl itself is a D-Bus client of systemd.


12. Cheat Sheet

TaskCommand / file
Show environmentenv, printenv VAR
Export a variableexport VAR=value
Add to PATHPATH=$HOME/bin:$PATH; export PATH
Which program runstype cmd, command -v cmd, which -a cmd
Reload bashrc. ~/.bashrc
Login shell?shopt -q login_shell && echo login
Login shell files/etc/profile, ~/.bash_profile
Interactive shell files~/.bashrc
GUI session variables~/.config/environment.d/*.conf
PromptPS1='\u@\h:\w\$ '
Default editor / pagerEDITOR, VISUAL, PAGER, LESS
File creation maskumask, umask 022
X11 or Wayland?echo $XDG_SESSION_TYPE
Wayland socket / compositorecho $WAYLAND_DISPLAY, ss -xlp | grep wayland-
X displayecho $DISPLAY
DRM devicesls /dev/dri/
Watch D-Busdbus-monitor --system, dbus-monitor --session
List D-Bus servicesbusctl list, busctl --user list
Send a notificationnotify-send "title" "text"