NWM - A Scrollable Tiling Window Manager

Table of Contents

Introduction

NWM (short for "No Window Manager" or "New Window Manager") is a tiling window manager for the X Window System. It is written in modern C++14 and provides both traditional master-stack tiling and a unique horizontal scrolling layout.

Unlike traditional window managers that require patching to add features, NWM follows a "configuration through compilation" approach where you edit the source code directly and rebuild. The codebase is intentionally kept clean and modular to make modifications straightforward.

Demo

What is a Tiling Window Manager?

A tiling window manager is a window manager with an organization of the screen into mutually non-overlapping frames, as opposed to the more common approach of coordinate-based stacking of overlapping objects (windows) that tries to fully emulate the desktop metaphor.

In simpler terms: instead of windows floating around and overlapping each other, a tiling window manager automatically arranges windows to use all available screen space without overlaps.

Why NWM?

NWM was created as a hobby project to explore what a tiling window manager could be if:

  1. It had a clean, modern codebase using C++14
  2. It didn't rely on patches for customization
  3. It offered a unique "scrollable" layout alongside traditional tiling
  4. It came with batteries included (status bar, system tray) but remained lightweight

Philosophy and Goals

Clean Code Over Patches

Many suckless-inspired projects use a patch system where features are added by applying diffs to the source code. This can lead to:

  • Patch conflicts
  • Difficulty maintaining multiple patches
  • Opaque code changes

NWM instead provides a clean, well-structured codebase where you can directly see and modify what you need. The configuration file (src/config.hpp) is actual C++ code, giving you the full power of the language for configuration.

Daily Drivable Defaults

The default configuration is meant to be usable immediately. You shouldn't need to spend hours configuring before you can use the window manager. The defaults include:

  • Sensible keybindings (Super/Windows key as modifier)
  • Built-in status bar with system information
  • System tray support
  • 9 workspaces
  • Both traditional tiling and scrollable layouts

Scrollable First

The unique feature of NWM is its horizontal scrolling layout. While most tiling window managers focus on vertical or grid layouts, NWM offers a side-by-side arrangement where you scroll through windows horizontally. This is particularly useful for:

  • Comparing files side-by-side
  • Working with many terminals simultaneously
  • Tasks that benefit from seeing two things at once

Extensible Through Source

Since configuration is done through C++ code, you have complete control over the window manager's behavior. Want to add a new keybinding? Edit the array. Want to change how windows are tiled? Modify the tiling functions. The entire source is available and documented.

Installation

Dependencies

NWM requires the following libraries to build and run:

Required Libraries

  • X11 (libX11): Core X Window System library
  • Xft (libXft): X FreeType library for font rendering
  • FreeType2 (libfreetype): Font rendering engine
  • Fontconfig (libfontconfig): Font configuration and customization library
  • Xrender (libXrender): X Rendering Extension library

Build Tools

  • C++ compiler with C++14 support (GCC 5+ or Clang 3.4+)
  • GNU Make

Installing Dependencies

Arch Linux

sudo pacman -S base-devel xorg-server libx11 libxft freetype2 fontconfig libxrender

Debian/Ubuntu

sudo apt install build-essential xorg libx11-dev libxft-dev libfreetype6-dev libfontconfig1-dev libxrender-dev

Fedora

sudo dnf install @development-tools xorg-x11-server-Xorg libX11-devel libXft-devel freetype-devel fontconfig-devel libXrender-devel

Gentoo

emerge --ask x11-base/xorg-server x11-libs/libX11 x11-libs/libXft media-libs/freetype media-libs/fontconfig x11-libs/libXrender

Void Linux

sudo xbps-install -S base-devel xorg libX11-devel libXft-devel freetype-devel fontconfig-devel libXrender-devel

Building from Source

Cloning the Repository

First, clone the NWM repository from GitHub:

git clone https://github.com/xsoder/nwm.git
cd nwm

Understanding the Build System

NWM uses a simple Makefile for building. The Makefile includes:

  • Compiler flags for optimization (-O3) and warnings (-Wall -Wextra)
  • Proper linking of required libraries
  • Installation targets for the binary and desktop entry

You can examine the Makefile to understand exactly what's being compiled and how.

Compiling

To compile NWM:

make

This will:

  1. Compile each source file (src/nwm.cpp, src/bar.cpp, src/tiling.cpp, src/systray.cpp) into object files
  2. Link all object files together with the required libraries
  3. Produce the nwm binary in the current directory

Installing System-Wide

To install NWM system-wide (requires root privileges):

sudo make install

This will:

  1. Install the nwm binary to /usr/local/bin/nwm
  2. Install the desktop entry to /usr/share/xsessions/nwm.desktop

The desktop entry allows display managers (like LightDM, GDM, SDDM) to show NWM as a session option at login.

Custom Installation Prefix

If you want to install to a different location:

make PREFIX=/custom/path install

For example, to install to your home directory:

make PREFIX=$HOME/.local install

Cleaning Build Files

To remove compiled object files and the binary:

make clean

Uninstalling

To remove NWM from your system:

sudo make uninstall

Nix/NixOS Installation

NWM includes a flake.nix for Nix users.

Building with Nix

nix build

Running with Nix

nix run

Development Shell

To enter a development environment with all dependencies:

nix develop

This provides a shell with all build tools, libraries, and useful utilities pre-installed.

Getting Started

Starting NWM

There are several ways to start NWM, depending on your setup.

Using a Display Manager (Recommended)

If you use a display manager (LightDM, GDM, SDDM, etc.), NWM will appear in the session list after installation. Simply:

  1. Log out or restart
  2. At the login screen, look for a session selector (usually a gear icon or dropdown menu)
  3. Select "NWM" from the list
  4. Enter your password and log in

This is the recommended method as it properly sets up the X session and environment variables.

Using startx with .xinitrc

If you prefer to use startx:

  1. Create or edit ~/.xinitrc:

    exec nwm
    

    Note: The exec command is important - it replaces the shell process with NWM. When NWM exits, the X session ends properly.

  2. Start X:

    startx
    

Complete .xinitrc Example

A more complete ~/.xinitrc that sets up a full environment:

#!/bin/sh

# Load X resources
[ -f ~/.Xresources ] && xrdb -merge ~/.Xresources

# Set keyboard repeat rate (delay, rate)
xset r rate 200 30

# Disable screen blanking
xset s off -dpms

# Set wallpaper (requires feh)
feh --bg-fill ~/Pictures/wallpaper.jpg &

# Start compositor for transparency/shadows (requires picom)
picom --config ~/.config/picom/picom.conf &

# System tray applications
nm-applet &          # NetworkManager
volumeicon &         # Volume control
blueman-applet &     # Bluetooth manager

# Auto-lock screen after 10 minutes (requires xautolock and slock)
xautolock -time 10 -locker slock &

# Start window manager (exec replaces the shell process with NWM)
# When NWM exits, the X session ends
exec nwm

Using Xinit Directly

For testing or debugging:

xinit /usr/local/bin/nwm -- :1

This starts NWM on display :1.

Testing in Xephyr

For development or testing without affecting your main session, use Xephyr (a nested X server):

# Start Xephyr on display :1
Xephyr -screen 1280x720 -ac :1 &

# Run NWM in that display
DISPLAY=:1 nwm

NWM includes a test script (test.sh) that automates this process.

First Steps

After starting NWM for the first time, you'll see:

  • An empty desktop (no windows)
  • A status bar at the bottom showing:
    • Workspace indicators (1-9)
    • Current layout mode ([TILE] or [SCROLL])
    • Current time and date
    • System information (CPU, RAM, disk, network)

Opening Your First Application

Press Super + Return to open a terminal. By default, NWM tries to launch st (Simple Terminal). If you don't have st installed, you'll need to either:

  1. Install st:

    # Arch
    sudo pacman -S st
    
    # Build from source
    git clone https://git.suckless.org/st
    cd st
    make && sudo make install
    
  2. Or change the terminal in src/config.hpp (see Configuration section)

Using dmenu

Press Super + d to open dmenu, an application launcher. Start typing the name of an application and press Enter to launch it.

If dmenu isn't installed:

# Arch
sudo pacman -S dmenu

# Build from source
git clone https://git.suckless.org/dmenu
cd dmenu
make && sudo make install

Opening Multiple Windows

Open several windows (e.g., press Super + Return three times). Notice how NWM automatically tiles them:

  • The first window occupies the left half (master area)
  • Additional windows stack on the right half

Switching Focus

Press Super + j and Super + k to cycle through windows. The focused window has a colored border (default: pink #FF5577).

Closing Windows

Press Super + q to close the currently focused window. Most applications will ask you to save any unsaved work.

Trying Scroll Mode

Press Super + t to toggle between tile mode and scroll mode. In scroll mode, windows are arranged side-by-side. Use Super + Left/Right arrow or Super + Mouse Wheel to scroll through them.

Understanding Layouts

NWM provides two main layout modes, each suited for different workflows.

Master-Stack Layout (Traditional Tiling)

This is the default layout mode and is similar to other tiling window managers like dwm, i3, or xmonad.

How It Works

The screen is divided into two areas:

  1. Master Area: The left side, typically occupied by your main window (e.g., your code editor)
  2. Stack Area: The right side, where additional windows are stacked vertically

Visual Representation

With one window:

┌──────────────────────┐
│                      │
│                      │
│      Window 1        │
│    (Fullscreen)      │
│                      │
│                      │
└──────────────────────┘

With two windows:

┌─────────────┬────────┐
│             │        │
│             │        │
│  Window 1   │  Win 2 │
│  (Master)   │        │
│             │        │
│             │        │
└─────────────┴────────┘

With three or more windows:

┌─────────────┬────────┐
│             │  Win 2 │
│             ├────────┤
│  Window 1   │  Win 3 │
│  (Master)   ├────────┤
│             │  Win 4 │
│             ├────────┤
│             │  Win 5 │
└─────────────┴────────┘

Master Area Size

The master area occupies 50% of the screen width by default. You can adjust this:

  • Super + h: Decrease master width
  • Super + l: Increase master width

The adjustment is made in increments defined by RESIZE_STEP (default: 40 pixels).

Making a Window Master

The "master" window is simply the first window in the window list. To make any window the master:

  1. Focus the window you want to make master
  2. Press Super + Shift + h repeatedly until it's in the first position

Use Cases

This layout is ideal for:

  • Coding with a large editor and smaller auxiliary windows (terminal, browser, etc.)
  • Writing with a document on the left and references on the right
  • Any workflow with one primary application and several supporting ones

Horizontal Scroll Layout

This is NWM's unique feature and differentiates it from most other tiling window managers.

How It Works

Windows are arranged side-by-side in a horizontal row. Each window occupies 50% of the screen width. You scroll horizontally to see windows that don't fit on the screen.

Visual Representation

With windows 1, 2, 3 visible (viewport can show 2 windows):

┌──────────┬──────────┬──────────┐
│          │          │          │
│ Window 1 │ Window 2 │ Window 3 │
│          │          │          │
└──────────┴──────────┴──────────┘
└─ Visible ─┘          └─ Scroll right to see

After scrolling right:

┌──────────┬──────────┬──────────┐
│          │          │          │
│ Window 1 │ Window 2 │ Window 3 │
│          │          │          │
└──────────┴──────────┴──────────┘
           └─ Visible ─┘

Scrolling

You can scroll through windows using:

  • Super + Left arrow: Scroll left
  • Super + Right arrow: Scroll right
  • Super + Mouse Wheel: Scroll with mouse

The scroll amount is defined by SCROLL_STEP (default: 500 pixels, but divided by 3 in practice).

Auto-scroll to Focused Window

When you focus a window that's off-screen, NWM automatically scrolls to make it visible. This happens when:

  • Using Super + j or Super + k to change focus
  • Clicking on a window in the bar
  • Opening a new window

Use Cases

This layout is ideal for:

  • Comparing multiple files side-by-side
  • Working with many terminals simultaneously
  • Any task where you want to see exactly two things at once
  • Presentations where you switch between different views

Toggling Between Layouts

Press Super + t to toggle between master-stack and horizontal scroll layouts. The current layout is shown in the status bar:

  • [TILE]: Master-stack mode
  • [SCROLL]: Horizontal scroll mode

When switching layouts:

  • Your windows remain in the same order
  • The scroll offset is reset to 0
  • Window focus is preserved

Gaps and Borders

Gaps

Gaps are the spaces between windows and between windows and screen edges. NWM includes gaps by default (defined by GAP_SIZE, default: 6 pixels).

To toggle gaps on/off: Super + a

With gaps disabled, windows will be directly adjacent to each other and screen edges.

Borders

Each window has a border that indicates focus:

  • Unfocused border: Dark gray (#181818 by default, defined by BORDER_COLOR)
  • Focused border: Pink (#FF5577 by default, defined by FOCUS_COLOR)

Border width is defined by BORDER_WIDTH (default: 3 pixels).

Floating and fullscreen windows have reduced or no borders.

Configuration

NWM follows the suckless philosophy: configuration is done by editing the source code and recompiling. This gives you complete control and makes the configuration explicit and type-safe.

Configuration File Location

The main configuration file is src/config.hpp. This is a C++ header file included by the main window manager code.

Basic Configuration Structure

src/config.hpp contains:

  1. #define macros for simple values
  2. Static arrays for keybindings
  3. Command definitions for applications

Editing and Applying Configuration

  1. Edit src/config.hpp
  2. Recompile: make clean && make
  3. Reinstall: sudo make install
  4. Restart NWM (log out and back in, or killall nwm && nwm if running from terminal)

Appearance Configuration

Window Borders

#define BORDER_WIDTH        3         // Width in pixels
#define BORDER_COLOR        0x181818  // Unfocused border (dark gray)
#define FOCUS_COLOR         0xFF5577  // Focused border (pink)

Colors are in hexadecimal RGB format: 0xRRGGBB

  • 0xFF0000 = Pure red
  • 0x00FF00 = Pure green
  • 0x0000FF = Pure blue
  • 0xFFFFFF = White
  • 0x000000 = Black

Gaps

#define GAP_SIZE            6         // Gap in pixels between windows

Set to 0 for no gaps by default.

Bar Position

#define BAR_POSITION        1         // 0 = top, 1 = bottom

Font

#define FONT                "DejaVu Sans Mono:size=10"

Font format follows Xft font specification:

  • "Family Name:size=SIZE"
  • "Family Name:size=SIZE:style=Bold"
  • "Family Name:size=SIZE:antialias=true"

To list available fonts:

fc-list
# Or for monospace fonts only:
fc-list :mono

Common choices:

  • "monospace:size=10" (uses system default monospace font)
  • "Liberation Mono:size=10"
  • "Inconsolata:size=11"
  • "Fira Code:size=10"
  • "JetBrains Mono:size=10"

Workspace Labels

static const std::vector<std::string> WIDGET = {
    "1","2","3","4","5","6","7","8","9"
};

You can customize these to any strings:

static const std::vector<std::string> WIDGET = {
    "web", "code", "term", "chat", "mail", "media", "7", "8", "9"
};

Or use Unicode symbols:

static const std::vector<std::string> WIDGET = {
    "一", "二", "三", "四", "五", "六", "七", "八", "九"  // Chinese numerals
};

Layout Behavior

#define RESIZE_STEP         40        // Master resize increment in pixels
#define SCROLL_STEP         500       // Horizontal scroll distance

Application Configuration

Define commands for applications you want to launch:

static const char *termcmd[]    = { "st",        NULL };
static const char *emacs[]      = { "emacs",     NULL };
static const char *browser[]    = { "chromium",  NULL };

Each command is a NULL-terminated array of strings. The first element is the program name, followed by any arguments:

static const char *term_float[] = { "st", "-t", "floating", NULL };
static const char *browser_priv[] = { "firefox", "--private-window", NULL };

Keybindings Configuration

Keybindings are defined in the keys[] array. Each entry consists of:

  1. Modifier mask (MODKEY, MODKEY|ShiftMask, etc.)
  2. Key symbol (XK_Return, XK_a, etc.)
  3. Function pointer (what to execute)
  4. Argument (passed to the function)

Basic Structure

static struct {
    unsigned int mod;           // Modifier key(s)
    KeySym keysym;             // Key symbol
    void (*func)(void*, nwm::Base&);  // Function to call
    const void *arg;           // Argument to pass
} keys[] = {
    { MODKEY,           XK_Return,      spawn,          termcmd },
    { MODKEY,           XK_q,           close_window,   NULL },
    // ... more keybindings
};

Modifier Keys

#define MODKEY Mod4Mask  // Super/Windows key (default)

Available modifiers:

  • Mod1Mask = Alt key
  • Mod4Mask = Super/Windows key
  • ShiftMask = Shift key
  • ControlMask = Ctrl key
  • LockMask = Caps Lock

Combine modifiers with |:

MODKEY | ShiftMask           // Super + Shift
MODKEY | ControlMask         // Super + Ctrl
MODKEY | ShiftMask | Mod1Mask  // Super + Shift + Alt

To change the main modifier to Alt:

#define MODKEY Mod1Mask

Key Symbols

Key symbols are X11 keysyms defined in <X11/keysym.h>. Common ones:

  • Letters
    XK_a through XK_z  // Lowercase letters
    XK_A through XK_Z  // Uppercase letters (use ShiftMask)
    
  • Numbers
    XK_0 through XK_9  // Number keys
    
  • Function Keys
    XK_F1 through XK_F12
    
  • Special Keys
    XK_Return       // Enter
    XK_space        // Spacebar
    XK_BackSpace    // Backspace
    XK_Tab          // Tab
    XK_Escape       // Escape
    
    // Arrow keys
    XK_Left, XK_Right, XK_Up, XK_Down
    
    // Navigation
    XK_Home, XK_End, XK_Page_Up, XK_Page_Down
    
    // Other
    XK_Print        // Print Screen
    XK_Insert       // Insert
    XK_Delete       // Delete
    
  • Media Keys
    XK_AudioRaiseVolume
    XK_AudioLowerVolume
    XK_AudioMute
    XK_AudioPlay
    XK_AudioStop
    XK_AudioPrev
    XK_AudioNext
    XK_MonBrightnessUp
    XK_MonBrightnessDown
    

Available Functions

Functions you can bind to keys:

  • Application Launching
    • spawn: Launch an application (pass command array as argument)
  • Window Management
    • close_window: Close focused window (argument: NULL)
    • toggle_fullscreen: Toggle fullscreen mode (argument: NULL)
    • toggle_float: Toggle floating mode for focused window (argument: NULL)
  • Focus and Navigation
    • focus_next: Focus next window (argument: NULL)
    • focus_prev: Focus previous window (argument: NULL)
    • swap_next: Swap focused window with next (argument: NULL)
    • swap_prev: Swap focused window with previous (argument: NULL)
  • Layout
    • toggle_layout: Toggle between tile and scroll mode (argument: NULL)
    • resize_master: Resize master area (argument: (void*)PIXELS or (void*)-PIXELS)
    • scroll_left: Scroll left in scroll mode (argument: NULL)
    • scroll_right: Scroll right in scroll mode (argument: NULL)
  • Workspace
    • switch_workspace: Switch to workspace (argument: (void*)&wsN where N is workspace number)
    • move_to_workspace: Move focused window to workspace (argument: (void*)&wsN)
  • System
    • toggle_gap: Toggle gaps on/off (argument: NULL)
    • toggle_bar: Toggle status bar visibility (argument: NULL)
    • quit_wm: Quit NWM (argument: NULL)

Example Keybindings

  • Launching Applications
    // Define commands
    static const char *termcmd[]    = { "st", NULL };
    static const char *browser[]    = { "firefox", NULL };
    static const char *editor[]     = { "nvim", NULL };
    static const char *files[]      = { "thunar", NULL };
    
    // Bind to keys
    { MODKEY,           XK_Return,      spawn,          termcmd },
    { MODKEY,           XK_b,           spawn,          browser },
    { MODKEY,           XK_e,           spawn,          editor },
    { MODKEY,           XK_f,           spawn,          files },
    
  • Window Management
    { MODKEY,           XK_q,           close_window,   NULL },
    { MODKEY,           XK_f,           toggle_fullscreen, NULL },
    { MODKEY|ShiftMask, XK_space,       toggle_float,   NULL },
    
  • Layout Control
    { MODKEY,           XK_t,           toggle_layout,  NULL },
    { MODKEY,           XK_h,           resize_master,  (void*)-RESIZE_STEP },
    { MODKEY,           XK_l,           resize_master,  (void*)RESIZE_STEP },
    { MODKEY,           XK_Left,        scroll_left,    NULL },
    { MODKEY,           XK_Right,       scroll_right,   NULL },
    
  • Workspaces
    // Define workspace variables
    static const int ws0 = 0;
    static const int ws1 = 1;
    // ... up to ws8 = 8
    
    // Switch to workspace
    { MODKEY,           XK_1,           switch_workspace, (void*)&ws0 },
    { MODKEY,           XK_2,           switch_workspace, (void*)&ws1 },
    // ... and so on
    
    // Move window to workspace
    { MODKEY|ShiftMask, XK_1,           move_to_workspace, (void*)&ws0 },
    { MODKEY|ShiftMask, XK_2,           move_to_workspace, (void*)&ws1 },
    // ... and so on
    
  • Media Keys
    static const char *vol_up[]     = { "pactl", "set-sink-volume", "@DEFAULT_SINK@", "+5%", NULL };
    static const char *vol_down[]   = { "pactl", "set-sink-volume", "@DEFAULT_SINK@", "-5%", NULL };
    static const char *vol_mute[]   = { "pactl", "set-sink-mute", "@DEFAULT_SINK@", "toggle", NULL };
    
    { 0,                XK_AudioRaiseVolume, spawn,     vol_up },
    { 0,                XK_AudioLowerVolume, spawn,     vol_down },
    { 0,                XK_AudioMute,        spawn,     vol_mute },
    

    Note: 0 means no modifier is required.

Mouse Bindings

Mouse bindings are hardcoded in the source (src/nwm.cpp) but can be modified:

  • Super + Left Click: Move floating window
  • Super + Right Click: Resize floating window
  • Super + Mouse Wheel: Scroll through workspaces (in scroll mode) or switch workspaces

To modify mouse behavior, edit the handle_button_press and handle_motion_notify functions in src/nwm.cpp.

Advanced Bar Configuration

The status bar's appearance is configured in src/bar.cpp. While most users won't need to edit this, you can customize:

Bar Colors

Located in src/bar.cpp:

#define BAR_HEIGHT 30
#define BAR_BG_COLOR        0x181818  // Background
#define BAR_FG_COLOR        0xCCCCCC  // Normal text
#define BAR_ACTIVE_COLOR    0xFF5577  // Active workspace
#define BAR_INACTIVE_COLOR  0x666666  // Inactive workspace
#define BAR_ACCENT_COLOR    0x88AAFF  // Accent (layout mode)
#define BAR_WARNING_COLOR   0xFFAA00  // Warning (high CPU/RAM)
#define BAR_CRITICAL_COLOR  0xFF5555  // Critical (very high usage)
#define BAR_HOVER_COLOR     0x333333  // Hover background

Update Interval

The bar updates system information every 2 seconds. To change this, modify src/bar.cpp:

void nwm::bar_update_system_info(Base &base) {
    auto now = std::chrono::steady_clock::now();
    auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
        now - base.bar.sys_info.last_update).count();

    if (elapsed < 2) return;  // Change this value
    // ...
}

Keybindings

This section provides a complete reference of all default keybindings. Remember that Mod refers to the Super (Windows) key by default.

Application Launchers

Keybinding Action
Mod + Return Launch terminal (st)
Mod + d Launch dmenu (application menu)
Mod + b Launch browser (chromium)
Mod + c Launch editor (emacs)
Mod + s Take screenshot
Mod + Shift + s Take screenshot (select area)
Mod + m Run custom script (master)
Mod + z Launch zoomer (boomer)

Window Management

Keybinding Action
Mod + q Close focused window
Mod + f Toggle fullscreen
Mod + Shift + Space Toggle floating mode
Mod + Left Click Drag floating window
Mod + Right Click Resize floating window

Focus and Navigation

Keybinding Action
Mod + j Focus next window
Mod + k Focus previous window
Mod + Shift + h Swap focused window with previous
Mod + Shift + l Swap focused window with next

Layout Management

Keybinding Action
Mod + t Toggle layout (tile ↔ scroll)
Mod + h Decrease master window size
Mod + l Increase master window size
Mod + a Toggle gaps on/off
Mod + r Toggle status bar visibility

Horizontal Scroll (Scroll Mode Only)

Keybinding Action
Mod + Left Scroll left
Mod + Right Scroll right
Mod + Mouse Wheel Scroll horizontally

Workspace Management

Keybinding Action
Mod + 1-9 Switch to workspace 1-9
Mod + Shift + 1-9 Move focused window to workspace 1-9
Mouse Wheel (on bar) Scroll through workspaces
Left Click (on bar) Switch to clicked workspace

System

Keybinding Action
Mod + Shift + q Quit NWM

Window Management

This section explains how windows are managed in NWM, including tiling, floating, and fullscreen modes.

Window States

A window in NWM can be in one of several states:

Tiled

  • Default state for most windows
  • Managed by the active layout (master-stack or horizontal scroll)
  • Cannot be moved or resized directly with the mouse
  • Position and size determined by the layout algorithm

Floating

  • Window can be freely moved and resized
  • Always drawn on top of tiled windows
  • Useful for dialogs, utility windows, or when you need precise positioning
  • Toggle with Mod + Shift + Space

Fullscreen

  • Window covers the entire screen, including the bar
  • All other windows are hidden
  • Border is removed
  • Toggle with Mod + f

Auto-Float Detection

NWM automatically makes certain windows float based on their properties:

Window Types That Auto-Float

  • Dialog windows (_NET_WM_WINDOW_TYPE_DIALOG)
  • Splash screens (_NET_WM_WINDOW_TYPE_SPLASH)
  • Utility windows (_NET_WM_WINDOW_TYPE_UTILITY)
  • Windows with _NET_WM_STATE_MODAL state
  • Windows with _NET_WM_STATE_ABOVE state
  • Transient windows (windows with WM_TRANSIENT_FOR hint)
  • Windows with fixed size (minsize == maxsize and < 800x600)

Examples of Auto-Floating Windows

  • Firefox's "Save As" dialog
  • GIMP's tool windows
  • Application preferences windows
  • File picker dialogs
  • Error/warning dialogs

If a window auto-floats and you want it tiled, press Mod + Shift + Space to toggle it.

Window Ignoring

Some windows are completely ignored by NWM and aren't managed:

Ignored Window Types

  • Desktop windows (_NET_WM_WINDOW_TYPE_DESKTOP)
  • Dock windows (_NET_WM_WINDOW_TYPE_DOCK)
  • Notification windows (_NET_WM_WINDOW_TYPE_NOTIFICATION)
  • Tooltip windows (_NET_WM_WINDOW_TYPE_TOOLTIP)
  • Menu windows (dropdown, popup, combo)
  • Windows with override_redirect flag

Examples

  • Desktop environment panels (if any)
  • Notification daemons (Dunst, notify-osd)
  • Tooltip popups
  • Dropdown menus

These windows appear and disappear as needed and are always on top.

Moving Windows

Within the Current Workspace

In tile mode:

  • Mod + Shift + h: Swap focused window with previous
  • Mod + Shift + l: Swap focused window with next

In scroll mode:

  • Same keybindings work
  • When you swap, the scroll position adjusts to keep the focused window visible

Floating windows:

  • Mod + Left Click and drag

Between Workspaces

  • Mod + Shift + [1-9]: Move focused window to workspace [1-9]
  • The window disappears from current workspace and appears in target workspace
  • Focus remains on current workspace (window moves but you don't follow)

Resizing Windows

Tiled Windows in Master-Stack Mode

  • Mod + h: Decrease master area width (increases stack area)
  • Mod + l: Increase master area width (decreases stack area)

This affects the master/stack split ratio. All tiled windows are then resized to fit the new ratio.

Tiled Windows in Scroll Mode

Window sizes in scroll mode are fixed at 50% screen width and full height (minus bar and gaps). Manual resizing isn't available in scroll mode.

Floating Windows

  • Mod + Right Click and drag: Resize from bottom-right corner
  • Minimum size enforced: 100x100 pixels

Fullscreen Windows

Fullscreen windows cannot be resized while in fullscreen mode. Exit fullscreen first (Mod + f).

Closing Windows

Press Mod + q to close the focused window.

How It Works

NWM sends a WM_DELETE_WINDOW message to the window, which is the polite way to ask an X11 application to close. This allows the application to:

  • Save unsaved work
  • Show a "Are you sure?" dialog
  • Clean up resources
  • Close gracefully

If a Window Won't Close

Some misbehaving applications may ignore the close request. In that case:

# Find the window's process
xprop _NET_WM_PID | grep -o '[0-9]*'
# Click on the window when cursor changes

# Kill the process
kill <PID>

# Or force kill
kill -9 <PID>

Or use xkill:

xkill
# Click on the window to kill it

Focus Model

NWM uses "focus follows mouse" by default. This means:

Focus Behavior

  • Moving the mouse cursor over a window automatically focuses it
  • You don't need to click to focus
  • The focused window receives keyboard input
  • Only one window can be focused at a time

Visual Indication

  • Focused window has a colored border (default: pink #FF5577)
  • Unfocused windows have a dark gray border (default: #181818)
  • Focused workspace in bar is highlighted

Manual Focus Control

  • Mod + j: Focus next window (cycles through all windows)
  • Mod + k: Focus previous window (cycles in reverse)

When you manually change focus, the mouse cursor doesn't move. To avoid accidentally refocusing when moving the mouse, some users prefer click-to-focus. This would require modifying the source code to remove EnterWindowMask from window event masks.

Window Ordering and Stacking

In Tile Mode

  • Tiled windows don't overlap, so stacking order doesn't matter much
  • Floating windows are always drawn above tiled windows
  • Fullscreen window is drawn above everything (except ignored windows like notifications)

In Scroll Mode

  • Same as tile mode
  • Windows are arranged in a horizontal row
  • Order in the row matches the order in the window list

Master Position

  • The "master" window is the first window in the workspace's window list
  • It's not a special property, just the position in the list
  • Any window can become master by being swapped to position 0

Changing Order

  • Mod + Shift + h: Move current window earlier in list (toward position 0)
  • Mod + Shift + l: Move current window later in list

Workspace Management

Workspaces (also called "tags" or "virtual desktops" in other window managers) allow you to organize windows into separate groups.

Understanding Workspaces

NWM provides 9 workspaces by default (can be changed by recompiling with a different NUM_WORKSPACES value).

What Are Workspaces?

Think of workspaces as separate desktops, each with its own set of windows. Only one workspace is visible at a time. Switching workspaces is instant.

Properties of Each Workspace

  • Independent window list
  • Independent layout mode (each workspace can be in tile or scroll mode)
  • Independent scroll offset (for scroll mode)
  • Independent master area size (for tile mode)
  • Independent focused window

Use Cases

Common ways to organize workspaces:

  • Workspace 1: Web browser
  • Workspace 2: Code editor and terminals
  • Workspace 3: Email client
  • Workspace 4: Chat applications (Slack, Discord, etc.)
  • Workspace 5: Music player
  • Workspace 6-9: Additional tasks

Or by project:

  • Workspace 1: Project A (editor, terminals, browser)
  • Workspace 2: Project B
  • Workspace 3: Project C
  • etc.

Switching Workspaces

Keyboard

  • Mod + 1: Switch to workspace 1
  • Mod + 2: Switch to workspace 2
  • Mod + 9: Switch to workspace 9

Switching workspaces:

  1. Unmaps (hides) all windows in current workspace
  2. Changes current workspace to target
  3. Maps (shows) all windows in target workspace
  4. Restores focus to the last focused window in target workspace

Mouse (via Status Bar)

  • Click on a workspace indicator (the numbers in the bar)
  • Scroll the mouse wheel over the bar to cycle through workspaces

Visual Feedback

The status bar shows all workspaces:

  • Active workspace: Highlighted background (default: darker gray)
  • Workspaces with windows: Normal background
  • Empty workspaces: Dimmed

Moving Windows Between Workspaces

Mod + Shift + [1-9]: Move focused window to workspace [1-9]

What Happens

  1. Window is removed from current workspace's window list
  2. Window is added to target workspace's window list
  3. Window is unmapped (hidden)
  4. Focus moves to next window in current workspace (if any)
  5. Window will be visible when you switch to target workspace

Important Notes

  • Moving a window doesn't switch workspaces
  • You stay in the current workspace after moving a window
  • If you move the only window, the workspace becomes empty
  • You can move floating and fullscreen windows (they exit fullscreen first)

Empty Workspaces

An empty workspace has no windows in it. This is normal and fine.

Behavior

  • Shows the desktop (wallpaper if set)
  • Shows only the status bar
  • Pressing Mod + j/k (focus next/prev) does nothing
  • Opening a new window automatically places it in the current workspace

Workspace Persistence

What's Preserved

  • Window positions in the window list
  • Layout mode (tile vs scroll)
  • Master area size
  • Scroll offset

What's Not Preserved

  • Workspaces are not saved between sessions
  • When you quit NWM, all workspace information is lost
  • On next startup, all existing windows go to workspace 0

Session Management

For persistence across reboots, you'd need session management (not currently implemented in NWM). Most users simply reopen their applications and reorganize.

Alternative: Use a session manager like tmux for terminals, and browser session restore for web browsers.

Changing Number of Workspaces

To have more or fewer workspaces:

  1. Edit src/nwm.hpp:

    #define NUM_WORKSPACES 12  // Change from 9 to desired number
    
  2. Edit src/config.hpp to add workspace labels:

    static const std::vector<std::string> WIDGET = {
        "1","2","3","4","5","6","7","8","9","10","11","12"
    };
    
  3. Add keybindings for the new workspaces in src/config.hpp:

    static const int ws9 = 9;
    static const int ws10 = 10;
    static const int ws11 = 11;
    
    // In keys[] array:
    { MODKEY,           XK_0,           switch_workspace, (void*)&ws9 },
    // Note: You might need to use different keys or key combinations
    // since keyboard only has 1-9 number keys
    
  4. Recompile: make clean && make && sudo make install

The Status Bar

The status bar provides information about your workspaces and system at a glance.

Bar Layout

The bar is divided into several sections:

┌─────────────────────────────────────────────────────────────────┐
│ [1][2][3][4].. [TILE] │ 12:30  Mon Jan 15 │ CPU 15% RAM 45% .. │
└─────────────────────────────────────────────────────────────────┘
 └─ Workspaces ─┘└─ Mode─┘└──── Time ────────┘└── System Info ───┘

Workspace Indicators

The left side shows all workspaces:

Visual States

  • Active workspace: Dark background, pink text
  • Workspace with windows: Medium background, white text
  • Empty workspace: No background, gray text
  • Hover: Light background when mouse is over it

Interaction

  • Click: Switch to that workspace
  • Scroll wheel: Cycle through workspaces
  • Hover: Highlights to show it's clickable

Layout Indicator

Shows current layout mode:

  • [TILE]: Master-stack tiling mode
  • [SCROLL]: Horizontal scroll mode

This updates immediately when you toggle with Mod + t.

Time and Date

Displays current time and date in the center:

  • Format: HH:MM Day Mon DD
  • Example: 14:30 Mon Jan 15
  • Updates every second

System Information

The right side shows system stats:

Displayed Information

  • CPU: CPU usage percentage
  • RAM: Memory usage percentage
  • DISK: Disk usage percentage for root partition (~/)
  • DOWN: Download speed (KB/s or MB/s)
  • UP: Upload speed (KB/s or MB/s)
  • BAT: Battery percentage (if present)
  • CHG: Shows when charging

Update Interval

  • System info updates every 2 seconds
  • Network speeds are calculated since last update

Color Coding

  • Normal: Light gray text
  • Warning: Orange text (CPU or RAM > 75%)
  • Critical: Red text (CPU or RAM > 90%)

System Tray

The system tray appears on the right side of the bar, between the system info and the edge.

What Appears Here

  • NetworkManager icon
  • Volume control icon
  • Bluetooth icon
  • Notification icons
  • Any application that uses the system tray protocol

Icons are 20×20 pixels by default with 4px padding between them.

Toggling Bar Visibility

Press Mod + r to hide/show the status bar.

When Hidden

  • Windows expand to use the full vertical space
  • All bar functionality is lost (can't click workspaces, see time, etc.)
  • System tray icons are also hidden

When to Hide

  • Presentations or screen recordings
  • Maximizing screen space for reading/viewing
  • Playing fullscreen games (though fullscreen mode already covers the bar)

Bar Position

The bar can be at the top or bottom of the screen. This is configured in src/config.hpp:

#define BAR_POSITION        1         // 0 = top, 1 = bottom

Top Bar (BAR_POSITION 0)

  • Bar at top, windows below
  • Traditional placement
  • Easier to see when focused on top of screen

Bottom Bar (BAR_POSITION 1)

  • Bar at bottom, windows above
  • Default in NWM
  • Keeps bar near taskbar location in other DEs
  • Easier to access with mouse (less distance to move)

After changing, recompile and restart NWM.

Customizing Bar Appearance

See the "Advanced Bar Configuration" section in Configuration for details on changing colors, fonts, and update intervals.

System Tray

The system tray (also called notification area) allows applications to display small status icons in the bar.

What is the System Tray?

The system tray is a common desktop feature where applications can place small icons to indicate status, provide quick access, or show notifications.

Common System Tray Applications

  • Network managers: NetworkManager (nm-applet), ConnMan, wicd
  • Volume controls: volumeicon, pasystray
  • Bluetooth: blueman-applet, blueberry
  • Cloud storage: Dropbox, Nextcloud, Google Drive
  • Messaging: Slack, Discord, Telegram (minimized)
  • Media players: Spotify, VLC (with tray plugin)
  • System monitors: CPU/memory monitors, battery monitors

Using the System Tray

Starting Tray Applications

Start applications normally, and they'll add their icon to the tray:

nm-applet &
volumeicon &
blueman-applet &

Add these to your ~/.xinitrc to start automatically:

#!/bin/sh
# ... other startup commands
nm-applet &
volumeicon &
blueman-applet &
exec nwm

Interacting with Tray Icons

  • Left click: Usually shows the main window or menu
  • Right click: Usually shows a context menu
  • Middle click: Application-specific action

Each application defines its own behavior.

Tray Icon Appearance

  • Icons are 20×20 pixels
  • Background matches bar background
  • Icons are raised above the bar
  • 4px padding between icons

Technical Details

NWM implements the _NET_SYSTEM_TRAY protocol, specifically:

  • _NET_SYSTEM_TRAY_Sn selection (where n is screen number)
  • XEMBED protocol for embedding windows
  • _NET_SYSTEM_TRAY_OPCODE for dock requests

Supported Features

  • Multiple tray icons
  • Icon removal and addition
  • Dynamic reordering
  • 32-bit ARGB visuals (transparency support)
  • Horizontal orientation

Limitations

  • Single system tray per X screen
  • No icon size customization per icon (all icons are same size)
  • No icon tooltips (applications may implement their own)

Troubleshooting Tray Issues

Icons Not Appearing

  1. Check if another tray is running:

    xprop -root _NET_SYSTEM_TRAY_S0
    

    If this shows a window ID, another tray owns the selection.

  2. Start the application after NWM: System tray applications need the tray to exist before they start. If you start the app before NWM, it won't see the tray.
  3. Restart the application:

    killall nm-applet && nm-applet &
    

Icons Are Too Large/Small

Icon size is hardcoded in src/systray.cpp:

#define TRAY_ICON_SIZE 20

Change this value and recompile to adjust icon size.

Tray Icons Overlap System Info

This is normal if you have many tray icons. The system info shifts left to make room. If it's a problem:

  • Close some tray applications
  • Hide less important system info (requires code modification)
  • Use a longer bar by increasing screen width (not really a solution)

Disabling the System Tray

If you don't use the system tray and want to disable it:

Comment out the initialization in src/nwm.cpp:

void nwm::init(Base &base) {
    // ... other initialization
    // systray_init(base);  // Comment this out
    // ... rest of init
}

And in the cleanup:

void nwm::cleanup(Base &base) {
    // ... other cleanup
    // systray_cleanup(base);  // Comment this out
    // ... rest of cleanup
}

Recompile and reinstall.

Advanced Configuration

This section covers advanced topics for users who want to deeply customize NWM.

Adding Custom Functions

You can add entirely new functionality by writing C++ functions and binding them to keys.

Example: Toggle Window Opacity

  1. Add function declaration in src/nwm.hpp:

    void toggle_opacity(void *arg, Base &base);
    
  2. Implement in src/nwm.cpp:

    void nwm::toggle_opacity(void *arg, Base &base) {
        (void)arg;
        if (!base.focused_window) return;
    
        // Toggle between opaque and semi-transparent
        static bool is_transparent = false;
        is_transparent = !is_transparent;
    
        unsigned long opacity = is_transparent ? 0xDDFFFFFF : 0xFFFFFFFF;
        Atom opacity_atom = XInternAtom(base.display, "_NET_WM_WINDOW_OPACITY", False);
    
        XChangeProperty(base.display, base.focused_window->window,
                       opacity_atom, XA_CARDINAL, 32,
                       PropModeReplace, (unsigned char*)&opacity, 1);
    }
    
  3. Add keybinding in src/config.hpp:

    { MODKEY,           XK_o,           toggle_opacity,   NULL },
    
  4. Recompile and install

Example: Cycle Through Layouts

Add a function to cycle through multiple layouts (not just two):

void nwm::cycle_layouts(void *arg, Base &base) {
    (void)arg;
    static int current_layout = 0;
    current_layout = (current_layout + 1) % 3;

    switch(current_layout) {
        case 0:
            base.horizontal_mode = false;
            tile_windows(base);
            break;
        case 1:
            base.horizontal_mode = true;
            tile_horizontal(base);
            break;
        case 2:
            // Implement a grid layout or monocle layout here
            break;
    }

    bar_draw(base);
}

Modifying Layout Algorithms

The tiling algorithms are in src/tiling.cpp.

Creating a Monocle Layout

Monocle layout shows one window at a time, fullscreen (but with bar visible):

void nwm::tile_monocle(Base &base) {
    auto &current_ws = get_current_workspace(base);

    if (current_ws.windows.empty()) return;

    int screen_width = WIDTH(base.display, base.screen);
    int screen_height = HEIGHT(base.display, base.screen);
    int bar_height = base.bar_visible ? base.bar.height : 0;
    int usable_height = screen_height - bar_height;
    int y_start = (base.bar_position == 0) ? bar_height : 0;

    // Hide all windows except focused
    for (auto &w : current_ws.windows) {
        if (w.window == base.focused_window->window) {
            w.x = 0;
            w.y = y_start;
            w.width = screen_width;
            w.height = usable_height;
            XMoveResizeWindow(base.display, w.window, w.x, w.y, w.width, w.height);
            XMapWindow(base.display, w.window);
        } else {
            XUnmapWindow(base.display, w.window);
        }
    }

    XFlush(base.display);
}

Then add this to a layout cycle or bind it to a key.

Creating a Grid Layout

Grid layout arranges windows in a grid:

void nwm::tile_grid(Base &base) {
    auto &current_ws = get_current_workspace(base);

    std::vector<ManagedWindow*> tiled_windows;
    for (auto &w : current_ws.windows) {
        if (!w.is_floating && !w.is_fullscreen) {
            tiled_windows.push_back(&w);
        }
    }

    if (tiled_windows.empty()) return;

    int screen_width = WIDTH(base.display, base.screen);
    int screen_height = HEIGHT(base.display, base.screen);
    int bar_height = base.bar_visible ? base.bar.height : 0;
    int usable_height = screen_height - bar_height;
    int y_start = (base.bar_position == 0) ? bar_height : 0;

    // Calculate grid dimensions
    int cols = std::ceil(std::sqrt(tiled_windows.size()));
    int rows = std::ceil((float)tiled_windows.size() / cols);

    int win_width = screen_width / cols - 2 * base.gaps;
    int win_height = usable_height / rows - 2 * base.gaps;

    for (size_t i = 0; i < tiled_windows.size(); ++i) {
        int col = i % cols;
        int row = i / cols;

        tiled_windows[i]->x = col * (win_width + 2 * base.gaps) + base.gaps;
        tiled_windows[i]->y = row * (win_height + 2 * base.gaps) + base.gaps + y_start;
        tiled_windows[i]->width = win_width;
        tiled_windows[i]->height = win_height;

        XMoveResizeWindow(base.display, tiled_windows[i]->window,
                         tiled_windows[i]->x, tiled_windows[i]->y,
                         tiled_windows[i]->width, tiled_windows[i]->height);
    }

    XFlush(base.display);
}

Multi-Monitor Support

NWM currently doesn't support multiple monitors natively. However, you can use Xinerama or RandR to treat multiple monitors as one large screen.

Using xrandr

# List monitors
xrandr

# Arrange monitors
xrandr --output HDMI-1 --auto --left-of eDP-1

Add to ~/.xinitrc to make permanent (before exec nwm).

NWM will treat the combined area as one screen. You can use workspaces to separate monitors logically (e.g., workspaces 1-5 on left monitor, 6-9 on right).

True Multi-Monitor Support

Implementing true multi-monitor support would require:

  1. Detecting monitors with Xinerama or RandR
  2. Creating separate workspaces per monitor
  3. Modifying tiling algorithms to work per-monitor
  4. Handling focus across monitors

This is a significant undertaking and not currently planned, but contributions are welcome.

Startup Hooks

Currently, NWM doesn't have built-in startup hooks. Use ~/.xinitrc or systemd user services for startup tasks:

Using .xinitrc

#!/bin/sh

# Custom startup script
~/.config/nwm/startup.sh &

exec nwm

Using Systemd User Services

Create ~/.config/systemd/user/nwm-startup.service:

[Unit]
Description=NWM Startup Tasks
After=graphical-session.target

[Service]
Type=oneshot
ExecStart=/home/yourusername/.config/nwm/startup.sh

[Install]
WantedBy=graphical-session.target

Enable:

systemctl --user enable nwm-startup.service

Custom Bar Widgets

The bar is rendered in src/bar.cpp. You can add custom widgets by modifying the bar_draw function.

Example: Adding Weather

  1. Create a function to fetch weather:

    std::string get_weather() {
        // Use curl or libcurl to fetch from weather API
        // Parse JSON response
        // Return formatted string like "☀️ 72°F"
        return "☀️ 72°F";
    }
    
  2. Add to bar in bar_draw:

    std::string weather = get_weather();
    XftDrawStringUtf8(base.bar.xft_draw, &base.bar.xft_fg, base.xft_font,
                     weather_x, y_offset,
                     (XftChar8*)weather.c_str(), weather.length());
    
  3. Call periodically (every 10 minutes) in the main event loop

IPC (Inter-Process Communication)

NWM doesn't currently implement IPC, but you could add it:

Using Unix Domain Sockets

Create a socket in init that listens for commands:

// Pseudo-code
void nwm::init_ipc(Base &base) {
    int sock = socket(AF_UNIX, SOCK_STREAM, 0);
    // Bind to /tmp/nwm-socket
    // Listen for connections
    // Handle commands in event loop
}

Then you could create a client program:

#!/bin/bash
# nwm-msg - Send message to NWM
echo "$1" | nc -U /tmp/nwm-socket

Usage:

nwm-msg "workspace 2"
nwm-msg "toggle-layout"

Using X11 Properties

Simpler approach: set X properties on root window:

void nwm::check_commands(Base &base) {
    Atom command_atom = XInternAtom(base.display, "NWM_COMMAND", False);
    // Read property
    // Execute command
    // Delete property
}

Call periodically or on PropertyNotify events.

Client:

xprop -root -f NWM_COMMAND 8s -set NWM_COMMAND "workspace 2"

Session Management

To save and restore window positions:

  1. On quit, save window information to ~/.config/nwm/session
  2. On start, read session file and match windows to saved positions

This requires:

  • Identifying windows (by WMCLASS, WMNAME, etc.)
  • Saving workspace, position, size, state
  • Re-managing windows after they're mapped

Complex but doable. i3 has session management you can reference.

Troubleshooting

NWM Won't Start

Symptom: Black screen or immediate return to login

Possible causes and solutions:

  1. Missing dependencies

    # Verify X11 libraries
    ldd /usr/local/bin/nwm
    # Should show no "not found"
    
  2. Font not found
    • Check ~/.xsession-errors or /var/log/Xorg.0.log
    • Change FONT in src/config.hpp to a font you have:

      fc-list | grep -i mono
      
  3. Another WM is running
    • Only one window manager can control the X server at a time
    • Kill other WM: killall openbox or killall i3
  4. X server not running

    echo $DISPLAY
    # Should show :0 or :1
    

Debugging

Run NWM from a terminal to see error messages:

# In an existing X session
nwm

# Or in Xephyr for testing
Xephyr -screen 1280x720 :1 &
DISPLAY=:1 nwm

Check logs:

cat ~/.xsession-errors
tail -f /var/log/Xorg.0.log

Status Bar Not Showing

Symptom: Bar is missing or blank

  1. Bar hidden
    • Press Mod + r to toggle bar visibility
  2. Font rendering issue
    • Install required font:

      sudo pacman -S ttf-dejavu
      # or
      sudo apt install fonts-dejavu
      
    • Or change FONT in config
  3. Xft library missing

    ldd /usr/local/bin/nwm | grep Xft
    
  4. Bar colors same as background
    • Check BARFGCOLOR and BARBGCOLOR are different

Keybindings Not Working

Symptom: Pressing key combinations does nothing

  1. Wrong modifier key
    • Verify Super key works: xev and press Super key
    • Check output for Mod4
    • If not, change to Alt: #define MODKEY Mod1Mask
  2. Key conflict
    • Another program may be grabbing the key
    • Check with: xev (press key and see if events appear)
  3. NumLock/CapsLock interference
    • NWM tries to handle this, but some keyboards are tricky
    • Try disabling NumLock
  4. Keybinding not compiled in
    • Check src/config.hpp for the keybinding
    • Recompile: make clean && make && sudo make install

Testing Keys

Use xev:

xev
# Press keys and observe output
# Look for KeyPress events with keysym names

Windows Not Tiling

Symptom: Windows float instead of tiling

  1. Window is meant to float
    • Dialogs, splash screens, etc. auto-float
    • Toggle: Mod + Shift + Space
  2. Only one window
    • A single window fills the screen
    • Open more windows to see tiling
  3. In scroll mode with one window visible
    • Scroll to see other windows: Mod + Left/Right
  4. Window manager not actually managing the window
    • Window might be override-redirect (like dmenu)
    • Check with: xprop (click on window, look for override redirect: True)

High CPU Usage

Symptom: NWM using significant CPU

Likely causes:

  1. Frequent bar updates
    • The bar redraws and updates system info every 2 seconds
    • Normal CPU usage: 0-2%
    • If higher, check for bugs in system info gathering
  2. Many tray icons
    • Each tray icon is a separate X window
    • More icons = more overhead
  3. X11 performance
    • Some X drivers are slow
    • Try different compositor settings or disable compositor
  4. Infinite loop or bug
    • If CPU is constantly high (>50%), there's a bug
    • Check with: top or htop
    • Report bug with details

Reducing CPU Usage

  • Increase bar update interval (edit src/bar.cpp)
  • Close unused tray applications
  • Disable picom/compositor

System Tray Icons Not Appearing

Symptom: Tray icons missing

  1. Started before NWM
    • Solution: Restart the application

      killall nm-applet && nm-applet &
      
  2. Another tray is running
    • Only one system tray can run at a time
    • Check:

      xprop -root | grep SYSTEM_TRAY
      
    • Kill other tray or quit other WM
  3. Application doesn't support system tray
    • Not all applications have tray icons
    • Check application documentation

Mouse Not Working for Window Operations

Symptom: Can't drag or resize windows with mouse

  1. Wrong modifier key
    • Must hold Mod (Super) key while dragging/resizing
    • Try Alt if Super doesn't work
  2. Window is tiled
    • Tiled windows can't be moved/resized with mouse
    • Toggle floating: Mod + Shift + Space
  3. Mouse bindings not grabbed
    • Check src/nwm.cpp for XGrabButton calls
    • Should grab Button1 and Button3 with MODKEY

Window Focus Issues

Symptom: Can't focus window or wrong window is focused

  1. Mouse outside window
    • Focus follows mouse
    • Move mouse over the window you want focused
  2. Window is unmapped
    • Check if window is on current workspace
    • Switch workspaces: Mod + 1-9
  3. Window is behind floating window
    • Floating windows are always on top
    • Close or move floating window

Application-Specific Issues

Java Applications (IntelliJ, Android Studio, etc.)

Java applications may not recognize NWM. Add to ~/.xinitrc before exec nwm:

export _JAVA_AWT_WM_NONREPARENTING=1

Or before starting the application:

_JAVA_AWT_WM_NONREPARENTING=1 idea.sh

Electron Applications

Some Electron apps have issues with tiling WMs. Try:

# For VS Code
code --disable-gpu

Games

Fullscreen games should work with Mod + f for fullscreen mode. If not:

# Force window mode
game-binary -windowed

Wine Applications

Wine apps may need:

# In wine config
winetricks settings
# Enable "Emulate a virtual desktop"

Crashes and Segfaults

Symptom: NWM crashes or displays "Segmentation fault"

  1. Recompile with debug info

    make clean
    CXXFLAGS = "-std=c++14 -O3 -Wall -Wextra -Wpedantic -Werror -Wstrict-aliasing -g" make
    
  2. Run with gdb

    gdb /usr/local/bin/nwm
    (gdb) run
    # Wait for crash
    (gdb) backtrace
    
  3. Report bug
    • Copy backtrace
    • Note what you were doing when it crashed
    • Open issue on GitHub with details

Getting More Help

If these troubleshooting steps don't help:

  1. Check existing issues
  2. Ask for help
    • Open a new issue with:
      • OS and version
      • NWM version (git rev-parse HEAD)
      • Contents of ~/.xsession-errors
      • Steps to reproduce
      • Expected vs actual behavior
  3. Enable verbose logging
    • Add debug printf statements
    • Recompile and observe output

Contributing

NWM is a hobby project and welcomes contributions!

Ways to Contribute

Report Bugs

Found a bug? Please open an issue on GitHub with:

  • Operating system and version
  • NWM version (git commit hash)
  • Steps to reproduce the bug
  • Expected behavior vs actual behavior
  • Relevant log output from ~/.xsession-errors
  • Screenshots if applicable

Suggest Features

Have an idea? Open an issue with the "enhancement" label.

Please note: NWM aims to stay relatively simple and focused. Not all feature requests will be accepted. Features that significantly increase complexity or add many dependencies are less likely to be included.

Preferred features:

  • Improvements to existing functionality
  • Bug fixes
  • Performance optimizations
  • Better documentation
  • Cleaner code organization

Less likely to be accepted:

  • Multi-monitor support (complex, better handled by other WMs)
  • External configuration files (goes against suckless philosophy)
  • Built-in application launchers (use dmenu/rofi)
  • Extensive theming system (just edit the source)

Submit Code

Pull requests are welcome! Please:

  1. Fork the repository

    # On GitHub, click "Fork"
    git clone https://github.com/YOUR_USERNAME/nwm.git
    cd nwm
    
  2. Create a branch

    git checkout -b feature/my-new-feature
    # or
    git checkout -b fix/bug-description
    
  3. Make your changes
    • Follow existing code style
    • Add comments for complex logic
    • Test thoroughly
  4. Test your changes
    • Use the included test.sh script
    • Test in Xephyr before testing on main session
    • Ensure existing functionality still works
  5. Commit with clear messages

    git commit -m "Add feature: description of feature"
    # or
    git commit -m "Fix bug: description of bug and solution"
    
  6. Push and create pull request

    git push origin feature/my-new-feature
    # Then open PR on GitHub
    

Improve Documentation

Documentation is always in need of improvement:

  • Fix typos and grammatical errors
  • Clarify confusing sections
  • Add examples
  • Translate to other languages
  • Create tutorials or guides
  • Make video walkthroughs

Documentation contributions are just as valuable as code!

Spread the Word

Help others discover NWM:

  • Star the repository on GitHub
  • Share on social media or forums
  • Write blog posts about your experience
  • Create rice/showcase posts with NWM

Code Style Guidelines

To keep the codebase consistent:

C++ Style

  • Indentation: 4 spaces (no tabs)
  • Braces: Opening brace on same line

    if (condition) {
        // code
    }
    
  • Naming:
    • Functions: snake_case
    • Variables: snake_case
    • Classes/Structs: PascalCase
    • Constants: UPPER_CASE
  • Comments: Use // for single-line, /* */ for multi-line
  • Includes: System headers first, then local headers

File Organization

  • Header files: .hpp
  • Implementation: .cpp
  • Keep headers minimal (declarations only)
  • Implementation in .cpp files

Commit Messages

Follow these conventions:

  • First line: Brief summary (50 chars or less)
  • Blank line
  • Detailed explanation if needed
Add horizontal scroll layout

Implements a new layout mode where windows are arranged side-by-side
and can be scrolled through horizontally. This provides an alternative
to the traditional master-stack layout.

Development Setup

Requirements

  • Git
  • C++ compiler (GCC 5+ or Clang 3.4+)
  • Make
  • X11 development libraries
  • Text editor (vim, emacs, VS Code, etc.)
  • Xephyr (for testing)

Building for Development

# Clone your fork
git clone https://github.com/YOUR_USERNAME/nwm.git
cd nwm

# Build
make

# Test in Xephyr
./test.sh

# Or manually:
Xephyr -screen 1280x720 :1 &
DISPLAY=:1 ./nwm

Debugging

Compile with debug symbols:

make clean
CXXFLAGS = "-std=c++14 -O3 -Wall -Wextra -Wpedantic -Werror -Wstrict-aliasing -g" make

Run in gdb:

Xephyr -screen 1280x720 :1 &
DISPLAY=:1 gdb ./nwm
(gdb) run

Or with valgrind for memory leaks:

DISPLAY=:1 valgrind --leak-check=full ./nwm

Code Structure

Understanding the codebase:

  • src/nwm.cpp
    • Main window manager logic
    • Event loop
    • Window management (manage, unmanage, focus)
    • Event handlers (key press, button press, map request, etc.)
    • Initialization and cleanup
  • src/nwm.hpp
    • Main header file
    • Structure definitions (Base, ManagedWindow, Workspace)
    • Function declarations
    • Macros
  • src/tiling.cpp
    • Layout algorithms
    • tile_windows(): Master-stack layout
    • tile_horizontal(): Horizontal scroll layout
    • Window arrangement functions
  • src/bar.cpp
    • Status bar rendering
    • System information gathering (CPU, RAM, disk, network, battery)
    • Workspace indicators
    • Time display
    • Mouse interaction with bar
  • src/bar.hpp
    • Bar structure definitions
    • Bar function declarations
  • src/systray.cpp
    • System tray implementation
    • XEMBED protocol
    • Tray icon management

    *#### src/systray.hpp

    • System tray structures
    • System tray function declarations
  • src/config.hpp
    • User configuration
    • Keybindings array
    • Application commands
    • Visual settings (colors, fonts, gaps)
  • Makefile
    • Build configuration
    • Compiler flags
    • Installation targets
  • flake.nix
    • Nix package definition
    • Development environment

Testing

Before submitting a pull request:

  1. Test basic functionality
    • Opening/closing windows
    • Switching workspaces
    • Changing layouts
    • Resizing and moving windows
    • Fullscreen mode
    • Floating windows
  2. Test with multiple applications
    • Terminals
    • Browsers
    • Text editors
    • Floating dialogs
  3. Test error cases
    • What happens with no windows?
    • What if you try to close the last window?
    • What if a keybinding has a NULL argument when it expects one?
  4. Check for memory leaks

    valgrind --leak-check=full ./nwm
    # Run for a while, open/close windows, switch workspaces
    # Exit and check for leaks
    
  5. Test on real hardware
    • Xephyr is great for development, but test on actual X session before finalizing

License

NWM is licensed under the MIT License. By contributing, you agree that your contributions will be licensed under the same license.

See the LICENSE file for the full license text.

Code of Conduct

Be respectful and constructive:

  • Respect different opinions and experiences
  • Accept constructive criticism gracefully
  • Focus on what's best for the project and community
  • Show empathy towards other contributors

Unacceptable behavior:

  • Harassment or discriminatory language
  • Personal attacks
  • Trolling or insulting comments
  • Publishing others' private information

Violations may result in removal of contributions or ban from the project.

Questions?

If you're unsure about something:

  • Open an issue for discussion
  • Ask in a pull request
  • Check existing issues and PRs for similar questions

Don't be afraid to ask! Everyone was a beginner once.

Appendix

Glossary

  • Bar/Status Bar: The horizontal strip (usually at top or bottom) showing information
  • Compositor: Software that provides visual effects (transparency, shadows, animations)
  • Desktop Environment (DE): A complete graphical interface (GNOME, KDE, XFCE)
  • Display Manager: Login screen software (LightDM, GDM, SDDM)
  • dmenu: Dynamic menu application launcher
  • dwm: Dynamic Window Manager, a minimalist tiling WM from suckless
  • Float/Floating: Window that can be freely moved and resized
  • Focus: The window that receives keyboard input
  • Master: The primary window in master-stack layout
  • picom: Compositor for X11 (formerly compton)
  • Stack: Secondary area in master-stack layout with vertically stacked windows
  • Suckless: Philosophy and organization behind dwm, st, dmenu
  • System Tray: Area where applications place notification icons
  • Tiled/Tiling: Windows arranged automatically without overlaps
  • Window Manager (WM): Software that controls window placement and appearance
  • Workspace/Virtual Desktop/Tag: Separate window groups
  • X11/X Window System: Display server protocol for Unix-like systems
  • Xephyr: Nested X server for testing
  • Xft: X FreeType library for font rendering

Comparison with Other Window Managers

vs dwm

NWM is inspired by dwm but differs in:

  • Language: C++14 vs C
  • Patches: Direct source editing vs patch system
  • Layouts: Includes horizontal scroll layout
  • Bar: Built-in status bar with system info
  • System Tray: Built-in support

Similar to dwm:

  • Configuration through source
  • Minimal by default
  • Master-stack layout
  • Keyboard-focused
  • Fast and lightweight

vs i3

i3 is more feature-complete but:

  • Configuration: i3 uses config file, NWM uses source
  • Layouts: i3 has more layout modes, NWM has scroll mode
  • IPC: i3 has comprehensive IPC, NWM doesn't (yet)
  • Multi-monitor: i3 supports multiple monitors natively

NWM is simpler and more hackable, i3 is more powerful and configurable without recompiling.

vs bspwm

bspwm is also minimal but:

  • Configuration: bspwm uses external config (bspc), NWM uses source
  • Layout: bspwm uses binary tree, NWM uses master-stack/scroll
  • Philosophy: bspwm is more Unix-philosophy (separate concerns)

vs xmonad

xmonad is configured in Haskell:

  • Language: Haskell vs C++
  • Flexibility: xmonad is extremely flexible, NWM is simpler
  • Learning curve: xmonad requires Haskell knowledge

vs awesome

awesome is configured in Lua:

  • Language: Lua for config vs C++ for NWM
  • Features: awesome has more widgets and features
  • Complexity: awesome is more complex

NWM is simpler and more focused on core tiling functionality.

Useful Resources

Similar Projects

  • dwm - Dynamic Window Manager
  • i3 - Improved Tiling WM
  • bspwm - Binary Space Partitioning WM
  • xmonad - Haskell-based WM
  • Qtile - Python-based WM

### Suckless Tools

  • st - Simple Terminal
  • dmenu - Dynamic Menu
  • slock - Simple Screen Locker

Community

Changelog

Version 0.1.0 (Initial Release)

  • Master-stack tiling layout
  • Horizontal scroll layout
  • 9 workspaces
  • Built-in status bar with system information
  • System tray support
  • Fullscreen and floating window support
  • Mouse support for moving/resizing
  • Configurable through source code
  • Basic EWMH compliance

FAQ (Quick Reference)

How do I change the terminal?

Edit src/config.hpp:

static const char *termcmd[] = { "alacritty", NULL };

Then recompile.

How do I change the modifier key?

Edit src/config.hpp:

#define MODKEY Mod1Mask  // For Alt key

How do I add more workspaces?

Edit src/nwm.hpp:

#define NUM_WORKSPACES 12

Then add labels and keybindings in src/config.hpp.

Can I use NWM on Wayland?

No, NWM is an X11 window manager and requires Xorg.

Why no multi-monitor support?

Multi-monitor support is complex and not currently a priority. You can use xrandr to combine monitors into one large screen.

How do I set a wallpaper?

Use feh or nitrogen:

feh --bg-fill ~/Pictures/wallpaper.jpg

Add to ~/.xinitrc for persistence.

Why do I need to recompile for configuration changes?

This is the suckless philosophy: configuration is code. It ensures:

  • Type safety
  • No runtime parsing overhead
  • Full power of C++
  • Explicit configuration
  • Forces you to understand what you're changing

Is NWM suitable for beginners?

NWM is relatively simple compared to many window managers, but it does require:

  • Comfort with command line
  • Basic understanding of X11
  • Willingness to edit C++ code
  • Ability to compile from source

If you've never used a tiling window manager, you might want to start with i3 (no compilation needed) then try NWM.

Where can I find example configurations?

Check:

  • The default src/config.hpp
  • GitHub issues for user-shared configs
  • r/unixporn posts using NWM

Contact

Thank you for using NWM!

If you find NWM useful, please star the repository on GitHub and share it with others who might be interested in a clean, hackable tiling window manager.

Date: 2025

Author: xsoder

Created: 2025-10-16 Thu 23:50