oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git
14,580 bytes raw
1
#+AUTHOR: Tony
2
#+STARTUP: overview
3
4
[[file:./images/oxwm1.png]]
5
6
* Table of Contents :toc:
7
- [[#oxwm--dwm-but-better-and-oxidized][OXWM — DWM but Better (and oxidized)]]
8
- [[#installation][Installation]]
9
  - [[#nixos-with-flakes][NixOS (with Flakes)]]
10
  - [[#arch-linux][Arch Linux]]
11
  - [[#building-from-source][Building from Source]]
12
  - [[#setting-up-oxwm][Setting up OXWM]]
13
- [[#configuration][Configuration]]
14
- [[#contributing][Contributing]]
15
- [[#key-bindings][Key Bindings]]
16
- [[#features][Features]]
17
- [[#testing-with-xephyr][Testing with Xephyr]]
18
- [[#project-structure][Project Structure]]
19
- [[#architecture-notes][Architecture Notes]]
20
  - [[#tag-system][Tag System]]
21
  - [[#status-bar][Status Bar]]
22
  - [[#layout-system][Layout System]]
23
- [[#current-todo-list][Current Todo List:]]
24
  - [[#priority-high-02][PRIORITY High]]
25
  - [[#priority-medium-14][PRIORITY Medium]]
26
  - [[#priority-low-11][PRIORITY Low]]
27
- [[#development-roadmap][Development Roadmap]]
28
  - [[#current-focus][Current Focus]]
29
  - [[#future-enhancements][Future Enhancements]]
30
- [[#license][License]]
31
32
* OXWM — DWM but Better (and oxidized)
33
A dynamic window manager written in Rust, inspired by dwm but designed to evolve on its own. Configuration is done in Rust source code, ditching the suckless philosophy of *"edit + recompile."*, and instead focusing on lowering friction for users, with sane defaults and no arbitrary elitism enforcing bad variable names and bad file structure topology.
34
35
* Installation
36
** NixOS (with Flakes)
37
*** Adding OXWM to your flake inputs
38
Add oxwm to your =flake.nix= inputs:
39
40
#+begin_src nix
41
{
42
  inputs = {
43
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
44
    oxwm.url = "github:tonybanters/oxwm";
45
    oxwm.inputs.nixpkgs.follows = "nixpkgs";
46
  };
47
48
  outputs = { self, nixpkgs, oxwm, ... }: {
49
    nixosConfigurations.yourhost = nixpkgs.lib.nixosSystem {
50
      system = "x86_64-linux";
51
      modules = [
52
        ./configuration.nix
53
        oxwm.nixosModules.default
54
      ];
55
    };
56
  };
57
}
58
#+end_src
59
60
*** Enabling OXWM in configuration.nix
61
Add this to your =configuration.nix=:
62
63
#+begin_src nix
64
{ config, pkgs, ... }:
65
66
{
67
  services.xserver = {
68
    enable = true;
69
    
70
    windowManager.oxwm.enable = true;
71
  };
72
73
  # Recommended: Install a display manager
74
  services.xserver.displayManager.lightdm.enable = true;
75
  # Or use another display manager like sddm, gdm, etc.
76
}
77
#+end_src
78
79
*** Initialize your config
80
After rebuilding your system with =sudo nixos-rebuild switch=, log in via your display manager.
81
82
On first launch, your initial config file will be automatically created and placed in =~/.config/oxwm/config.ron=. Edit it and reload with =Mod+Shift+R=.
83
84
*** Advanced: Using a specific oxwm version
85
If you want to pin or customize the oxwm package:
86
87
#+begin_src nix
88
{
89
  services.xserver.windowManager.oxwm = {
90
    enable = true;
91
    # Use a specific version or build with custom options
92
    package = oxwm.packages.${pkgs.system}.default;
93
  };
94
}
95
#+end_src
96
97
*** Development setup with Nix
98
For development, use the provided dev shell:
99
100
#+begin_src sh
101
# Clone the repository
102
git clone https://github.com/tonybanters/oxwm
103
cd oxwm
104
105
# Enter the development environment
106
nix develop
107
108
# Build and test
109
cargo build
110
just test
111
#+end_src
112
113
** Arch Linux
114
115
AUR: [[https://aur.archlinux.org/packages/oxwm-git][AUR URL]]
116
#+begin_src
117
yay -S oxwm-git
118
#+end_src
119
This will automatically put a desktop session file into your xsessions directory.
120
121
Manually:
122
Install dependencies:
123
#+begin_src sh
124
sudo pacman -S rust cargo libx11 libxft freetype2 fontconfig pkg-config
125
#+end_src
126
See Build from source
127
128
** Building from Source
129
#+begin_src sh
130
git clone https://github.com/tonybanters/oxwm
131
cd oxwm
132
cargo build --release
133
sudo cp target/release/oxwm /usr/local/bin/
134
#+end_src
135
136
Or use the justfile:
137
#+begin_src sh
138
just install
139
#+end_src
140
141
** Setting up OXWM
142
*** Without a display manager (startx)
143
Add the following to your =~/.xinitrc=:
144
#+begin_src sh
145
exec oxwm
146
#+end_src
147
148
Then start X with:
149
#+begin_src sh
150
startx
151
#+end_src
152
153
*** With a display manager
154
If using a display manager (LightDM, GDM, SDDM), OXWM should appear in the session list after installation.
155
156
* Configuration
157
OXWM was inspired by dwm, but ditched the suckless philosophy. This philosophy quite literally discourages users from using the software for the sake of 'elitism'. I find that quite nonsensical, so I went ahead and created this project to be user friendly. The configuration is done by editing =~/.config/oxwm/config.ron= and the binary can be reloaded with a hotkey (Super+Shift+R by default).
158
159
Edit =~/.config/oxwm/config.ron= to customize:
160
- Keybindings
161
- Colors and appearance
162
- Status bar blocks
163
- Gaps and borders
164
- Terminal and applications
165
166
After making changes, reload OXWM with =Mod+Shift+R= 
167
168
* Contributing
169
When contributing to OXWM:
170
171
1. Never commit your personal =~/.config/oxwm/config.ron=
172
2. Only modify =templates/config.ron= if adding new configuration options
173
3. Test your changes with =just test= using Xephyr/Xwayland
174
4. Document any new features or keybindings
175
176
* Key Bindings
177
Default keybindings (customizable in config.rs):
178
179
| Binding           | Action                  |
180
|-------------------+-------------------------|
181
| Super+Return      | Spawn terminal          |
182
| Super+J/K         | Cycle focus down/up     |
183
| Super+Q           | Kill focused window     |
184
| Super+Shift+Q     | Quit WM                 |
185
| Super+Shift+R     | Hot reload WM           |
186
| Super+1-9         | View tag 1-9            |
187
| Super+Shift+1-9   | Move window to tag 1-9  |
188
| Super+S           | Screenshot (maim)       |
189
| Super+D           | dmenu launcher          |
190
| Super+A           | Toggle gaps             |
191
| Super+Shift+F     | Toggle fullscreen       |
192
| Super+Shift+Space | Toggle floating         |
193
194
* Features
195
- Dynamic tiling layout with master/stack
196
- Tag-based workspaces (9 tags by default)
197
- Configurable gaps between windows
198
- Status bar with modular block system
199
  - Battery, RAM, datetime, shell commands
200
  - Custom colors and update intervals
201
  - Click-to-switch tags
202
- Window focus cycling
203
- Hot reload without restarting X
204
- Persistent window tags across restarts
205
- Mouse hover to focus
206
- Border indicators for focused windows
207
- Fullscreen mode
208
- Move Windows with Super HJKL
209
210
* Testing with Xephyr
211
Test OXWM in a nested X server without affecting your current session:
212
213
#+begin_src sh
214
just test
215
#+end_src
216
217
This starts Xephyr on display :1 and launches OXWM inside it.
218
219
Or manually:
220
#+begin_src sh
221
Xephyr -screen 1280x800 :1 &
222
DISPLAY=:1 cargo run
223
#+end_src
224
225
* Project Structure
226
#+begin_src sh
227
src/
228
├── main.rs
229
│   └── main()
230
│       └── Creates WindowManager and calls .run()
231
232
├── window_manager.rs                    [CORE - X11 event handling]
233
│   ├── struct WindowManager
234
│   │   ├── connection: RustConnection   [X11 connection]
235
│   │   ├── windows: Vec<Window>         [All managed windows]
236
│   │   ├── focused_window: Option<Window>
237
│   │   ├── layout: Box<dyn Layout>
238
│   │   ├── window_tags: HashMap<Window, TagMask>
239
│   │   ├── selected_tags: TagMask
240
│   │   └── bar: Bar                     [Status bar]
241
│   │
242
│   ├── new()                            [Initialize WM, grab root, restore tags, scan windows]
243
│   ├── run()                            [Main event loop with block updates]
244
│   ├── handle_event()                   [Route X11 events]
245
│   │   ├── MapRequest    → add window, apply layout, update bar, save tag
246
│   │   ├── UnmapNotify   → remove window, update bar
247
│   │   ├── DestroyNotify → remove window, update bar
248
│   │   ├── KeyPress      → get action, handle it (includes Restart)
249
│   │   ├── ButtonPress   → handle bar clicks
250
│   │   └── Expose        → redraw bar
251
│   ├── handle_key_action()              [Execute keyboard actions]
252
│   ├── get_saved_selected_tags()        [Restore selected tags from _NET_CURRENT_DESKTOP]
253
│   ├── save_selected_tags()             [Persist selected tags to root window]
254
│   ├── get_saved_tag()                  [Restore window tag from _NET_CLIENT_INFO]
255
│   ├── save_client_tag()                [Persist window tag to window property]
256
│   ├── scan_existing_windows()          [Manage windows on startup]
257
│   ├── remove_window()                  [Remove from Vec, reapply layout]
258
│   ├── set_focus()                      [Focus window, update visuals]
259
│   ├── cycle_focus()                    [Move focus to next/prev window]
260
│   ├── view_tag()                       [Switch to tag/workspace, update visibility]
261
│   ├── move_to_tag()                    [Move window to tag]
262
│   ├── update_bar()                     [Calculate occupied tags, redraw bar]
263
│   ├── update_focus_visuals()           [Set border colors]
264
│   ├── update_window_visibility()       [Map/unmap windows based on tags]
265
│   └── apply_layout()                   [Position all windows below bar]
266
267
├── config.rs                            [CONFIGURATION - all settings here]
268
│   ├── BORDER_WIDTH, BORDER_FOCUSED, BORDER_UNFOCUSED
269
│   ├── FONT                             [XFT font string]
270
│   ├── TAG_COUNT, TAGS                  [Workspace configuration]
271
│   ├── TERMINAL, MODKEY
272
│   ├── ColorScheme                      [Foreground, background, border colors]
273
│   ├── SCHEME_NORMAL, SCHEME_OCCUPIED, SCHEME_SELECTED
274
│   ├── KEYBINDINGS                      [All keybinds as const array]
275
│   └── STATUS_BLOCKS                    [Block configurations with format, command, interval]
276
277
├── bar/
278
│   ├── mod.rs                           [Re-exports: Bar, BlockCommand, BlockConfig]
279
│   ├── bar.rs
280
│   │   ├── struct Bar                   [Status bar window with XFT support]
281
│   │   ├── new()                        [Create bar X11 window, load font, init blocks]
282
│   │   ├── draw()                       [Render tags + blocks with underlines]
283
│   │   ├── update_blocks()              [Update block content based on intervals]
284
│   │   ├── handle_click()               [Detect which tag was clicked]
285
│   │   └── invalidate()                 [Mark bar as needing redraw]
286
│   ├── font.rs
287
│   │   ├── struct Font                  [XFT font wrapper]
288
│   │   ├── struct FontDraw              [XFT drawing context]
289
│   │   └── draw_text()                  [Render text with color]
290
│   └── blocks/
291
│       ├── mod.rs                       [Block trait, BlockConfig, BlockCommand enum]
292
│       ├── battery.rs                   [Battery status block]
293
│       ├── datetime.rs                  [Date/time formatting block]
294
│       └── shell.rs                     [Shell command execution block]
295
296
├── keyboard/
297
│   ├── mod.rs                           [Re-exports]
298
│   ├── keycodes.rs                      [Key constants: Q, J, RETURN, etc]
299
│   └── handlers.rs
300
│       ├── enum KeyAction               [Spawn, KillClient, FocusStack, ViewTag, Restart, etc]
301
│       ├── enum Arg                     [None, Int, Str, Array]
302
│       ├── struct Key                   [Keybinding definition]
303
│       ├── setup_keybinds()             [Register keys with X11]
304
│       └── handle_key_press()           [Parse KeyPressEvent → KeyAction]
305
306
└── layout/
307
    ├── mod.rs                           [Layout trait definition]
308
    └── tiling.rs
309
        └── TilingLayout::arrange()      [Calculate window positions]
310
#+end_src
311
312
* Architecture Notes
313
** Tag System
314
Tags are implemented as bitmasks (TagMask = u32), allowing windows to belong to multiple tags simultaneously. Each window has an associated TagMask stored in a HashMap. Tags persist across WM restarts using X11 properties (_NET_CURRENT_DESKTOP for selected tags, _NET_CLIENT_INFO for per-window tags).
315
316
** Status Bar
317
The bar uses a performance-optimized approach with a modular block system:
318
- Only redraws when invalidated
319
- Pre-calculates tag widths on creation
320
- Blocks update independently based on their configured intervals
321
- Supports custom colors and underline indicators
322
- Easily extensible - add new block types in src/bar/blocks/
323
324
** Layout System
325
The tiling layout divides the screen into a master area (left half) and stack area (right half). The master window occupies the full height of the master area, while stack windows split the stack area vertically. Gaps are configurable and can be toggled at runtime.
326
327
* TODO Current Todo List:
328
** PRIORITY High [0/2]
329
- [ ] Convert keycodes to keysyms for cross-keyboard compatibility
330
  - Current hardcoded keycodes only work on specific keyboards
331
  - Need to use XKeysymToKeycode() for runtime conversion
332
  - Follow DWM's approach: keysym → keycode conversion
333
- [ ] Fix fullscreen to persist across tags
334
  - Fullscreen state should be maintained when switching tags
335
  - Window should remain fullscreen when returning to its tag
336
337
** PRIORITY Medium [1/4]
338
- [ ] Add keybindings to increase/decrease window size
339
  - Master area resize (Mod+H / Mod+L)
340
  - Individual window resize for floating windows
341
- [ ] Keychords:
342
  - Desired Behaviour:
343
#+begin_src rust
344
// Keychord: Mod+f, then f (same key twice)
345
(keys: [
346
    (modifiers: [Mod], key: F),
347
    (modifiers: [], key: F),
348
], action: Spawn, arg: "repos-dmenu.sh"),
349
350
#+end_src
351
- [X] Fix cursor on hover for bar
352
  - Bar should show pointer cursor on hover
353
  - Indicate clickable tag areas
354
- [ ] Add guess_terminal() function to default config.rs
355
  - Auto-detect available terminal emulator
356
  - Priority order: st → alacritty → kitty → wezterm → xterm
357
  - Fallback to xterm if none found
358
359
** PRIORITY Low [1/1]
360
- [X] Create AUR package
361
  - Write PKGBUILD
362
  - Submit to AUR
363
  - Add installation instructions to README
364
* Development Roadmap
365
** Current Focus
366
- Multi-monitor support
367
- Additional layouts (monocle, floating)
368
- Master area resizing
369
- Window swapping in layout
370
371
** Future Enhancements
372
- Per-window floating behavior
373
- Per-program rules (auto-tag assignment, floating rules)
374
- External bar support (polybar, etc)
375
- Scratchpad functionality
376
- Window minimize/restore
377
378
* License
379
[[https://www.gnu.org/licenses/gpl-3.0.en.html][GPL v3]]