Linux: User Environments and the Desktop Stack
Table of Contents
- Startup Files
- The Command Path
- Login and Non-Login Shells
- Startup File Order and Examples
- Shell Defaults: Prompt, Editor, Pager, umask
- The Desktop Stack
- Framebuffers
- The X Window System
- Wayland
- Window Managers, Toolkits and Desktop Environments
- D-Bus
- 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:
- Keep them simple. They run constantly. A slow or broken startup file affects every terminal, and errors can even prevent logins.
- Think about who else reads them. Scripts,
scpand remote commands start shells too. A startup file that prints output can break non-interactive tools likescpandrsync. - Don’t override system defaults without a reason. Distros ship sensible system-wide files in
/etc(/etc/profile,/etc/bashrc,/etc/profile.d/*.sh). - Keep them in version control. Many people keep dotfiles in a Git repo and symlink them (e.g. with GNU
stoworchezmoi).
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)
| Directory | Contents |
|---|---|
~/bin, ~/.local/bin | Your own scripts and user-installed tools (pip --user, pipx) |
/usr/local/bin | Software installed locally by the admin, not by the package manager |
/usr/bin | Software from distro packages |
/bin | Essential commands; on modern distros a symlink to /usr/bin (usrmerge) |
/usr/sbin, /sbin | System 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) inPATH, especially not at the front. A malicious file namedlsin some directory would run instead of the real one. Type./scriptinstead.
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 type | When | Example |
|---|---|---|
| Login shell | You log in: text console, SSH session, su -, bash -l | ssh t490 |
| Interactive non-login shell | A new shell from within a session: terminal window in a desktop, typing bash | Opening foot, Alacritty, GNOME Terminal |
| Non-interactive shell | Running 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 in | What |
|---|---|
~/.bash_profile | Little or nothing, just source ~/.bashrc. Maybe things that should run once per login (e.g. start an agent) |
~/.bashrc | PATH, 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)
| Escape | Meaning |
|---|---|
\u | Username |
\h | Hostname (short) |
\w | Current directory (~ for home) |
\W | Last part of the current directory |
\! | History number |
\s | Shell name |
\t | Time (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:
VISUAL: preferred full-screen editorEDITOR: fallback, historically for line editors
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:
| Option | Meaning |
|---|---|
m | Verbose prompt with percentage through the file |
e | Quit the second time you reach the end |
i | Case-insensitive search (unless the pattern has capitals) |
X | Don’t clear the screen on exit (output stays visible) |
F | Quit right away if the output fits on one screen |
R | Show 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)
- The text console (
/dev/tty1…) draws onto a framebuffer. - On modern systems, the kernel’s DRM/KMS (Direct Rendering Manager / Kernel Mode Setting) manages framebuffers, display modes and GPU memory. Device nodes are in
/dev/dri/(card0,renderD128). - The old
/dev/fb0interface still exists for simple use cases. - A compositor renders the whole desktop into a framebuffer and tells KMS to show it.
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:
- The
DISPLAYvariable tells clients which server to use:DISPLAY=:0. - The window manager is just another client with special rights to place and decorate windows. A separate compositor may handle effects and transparency.
- Any client can read keystrokes and screen contents of other clients. This is a major security weakness.
- Network transparency: clients can run on another machine (
ssh -X host firefox). - On modern desktops, X apps still run under Wayland via XWayland, a compatibility X server.
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:
| X11 | Wayland | |
|---|---|---|
| Who draws windows | X server, on request | Each client draws its own buffer |
| Window manager | Separate client | Part of the compositor |
| Isolation | Clients can spy on each other | Clients can’t read others’ input or screen |
| Screenshots, screen sharing, global hotkeys | Any app can do it | Via portals (xdg-desktop-portal) or compositor-specific protocols |
| Network transparency | Built in | Not built in (use waypipe, RDP) |
| Tearing/scaling | Common 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.
| Type | How | Examples |
|---|---|---|
| Stacking/floating | Windows overlap freely, move with the mouse | Openbox, Xfwm, Mutter |
| Tiling | Windows fill the screen without overlapping, keyboard driven | i3 (X11), Sway (Wayland, i3-compatible), Hyprland |
| Dynamic | Switch between layouts | dwm, 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.
- GTK: GNOME and many other apps (Firefox, GIMP).
- Qt: KDE apps, many cross-platform apps.
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:
.desktopfiles for application menus (/usr/share/applications,~/.local/share/applications)- XDG base directories:
~/.config,~/.local/share,~/.cache($XDG_CONFIG_HOMEetc.) $XDG_RUNTIME_DIR(/run/user/<UID>): per-user runtime files like the Wayland and D-Bus sockets
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") │
└─────────────────────────────────────────────────────────────────┘
| Bus | Who | Examples |
|---|---|---|
| System bus | System services, root-owned daemons | NetworkManager reports Wi-Fi changes; systemctl talks to systemd; udisks mounts USB drives |
| Session bus | Programs in your login session | notify-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
| Task | Command / file |
|---|---|
| Show environment | env, printenv VAR |
| Export a variable | export VAR=value |
| Add to PATH | PATH=$HOME/bin:$PATH; export PATH |
| Which program runs | type 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 |
| Prompt | PS1='\u@\h:\w\$ ' |
| Default editor / pager | EDITOR, VISUAL, PAGER, LESS |
| File creation mask | umask, umask 022 |
| X11 or Wayland? | echo $XDG_SESSION_TYPE |
| Wayland socket / compositor | echo $WAYLAND_DISPLAY, ss -xlp | grep wayland- |
| X display | echo $DISPLAY |
| DRM devices | ls /dev/dri/ |
| Watch D-Bus | dbus-monitor --system, dbus-monitor --session |
| List D-Bus services | busctl list, busctl --user list |
| Send a notification | notify-send "title" "text" |