# A Window Management System *Daanish Syed* > AQA Extended Project Qualification Level 3. November 2026 --- # Table of contents 1. Nomenclature 2. Analysis - Project Motivation - Problem Definition - Aims and Objectives 3. Background Research --- # Nomenclature 1. Window Manager: A program responsible for controlling the placement, appearance and behaviour of application windows within a graphical user interface. 2. Paradigms: General approaches or styles used when designing and structuring software. Examples include procedural, object-oriented and functional programming. 3. Graphical Stack: The collection of software components responsible for displaying and managing a graphical user interface, including display servers, window managers, compositors and graphical libraries. 4. Desktop Environment: A collection of software that provides a complete graphical desktop experience. This commonly includes a window manager, file manager, panel, application launcher and system settings. 5. Scripting: A script is a relatively short and simple set of instructions that typically automates an otherwise manual process. The act of writing a script is called scripting. A scripting language, or script language, is a programming language used for scripting. > https://en.wikipedia.org/wiki/Scripting_language 6. Embedded Scripting Language: A programming language designed to be integrated into another program (the host). It allows scripts to be interpreted at runtime, enabling users to customise or extend the host program's functionality without modifying or recompiling its source code. The host program can also expose its own functions and features for scripts to use. 7. C-ABI (C Application Binary Interface): An application binary interface (ABI) is an interface exposed by software that is defined for in-process machine code access. Often, the exposing software is a library, and the consumer is a program. The C ABI is the most commonly used ABI format across major operating systems and languages. > https://en.wikipedia.org/wiki/Application_binary_interface#C 8. FFI (Foreign Function Interface): A mechanism that allows a program written in one programming language to call functions or access functionality written in another language. It is commonly used to connect a high-level language to libraries written for C. 9. C-Shims 10. VM --- # Analysis ## Project Motivation I have been trying to implement every useful bit of an operating system myself to understand how everything works deeply, and a major part of that graphical stack is the window management system. I have been using tiling window management systems on my personal computer and have grown to really love the keyboard-driven interaction. I feel like a window management system is arguably the most common thing most people interface with on their devices, and yet they never think about it. So implementing it myself would be a good way for me to both understand how window management systems work and explore different programming paradigms and architectures. ## Problem Definition #### The general problem A graphical desktop environment allows multiple applications to run simultaneously, each displaying its contents in its own window. However, these windows all share a common display area (the screen), meaning the operating system needs a way to manage how they are positioned, sized and displayed. Individual applications are generally only responsible for drawing their own contents within their respective windows. They generally do not determine where their windows should be positioned, how large they should be, or how they should interact with other windows. This creates the need for a window manager (WM): software responsible for managing the arrangement and behaviour of application windows on a graphical display. A window manager typically handles several responsibilities: - Placement and sizing: Where new windows are created, their size, and allowing the user to move or resize them. - Focus and stacking: Deciding which window receives keyboard input and controlling the order in which windows appear when they overlap. - Workspaces: Allowing windows to be organised into separate virtual desktops that users can switch between. - Input handling: Responding to global keyboard shortcuts and mouse actions used to control window management. These responsibilities form the basis of the software I intend to develop as part of this project. [source] #### Constraints To keep the project manageable and define a clear scope, I will place several constraints on the window manager's functionality. - Window manager only: I will be developing only a window manager rather than a complete desktop environment. This means I will not develop additional desktop components such as a taskbar, application launcher, or file manager. [See nomenclature for definition of a DE] - Tiling window management: Windows will automatically be arranged to fill the available screen space rather than being freely positioned or overlapping one another. - Configuration: The software will provide a mechanism for users to configure its behaviour, but I will not be developing a graphical settings application. ## Aims and Objectives #### Aim The aim is to design and implement a functional, keyboard-driven tiling window manager for Linux. The WM should provide reliable window arrangement, keyboard-based navigation through configurable shortcuts, with enough functionality to support everyday desktop use. #### Objectives 1. Research into existing Window Management systems. 2. Investigate suitable programming languages and libraries. 3. Design an abstract representation for the window information to be stored internally and updated. 4. Implement automatic tiling and layout algorithms. 5. Implement window lifecycle and input handling. 6. Allow customising shortcuts and window actions. 7. Test and evaluate the system as a whole. --- # Background Research ## Window Management Systems ### Operating System choice Although Microsoft Windows and macOS account for most desktop operating systems used worldwide [source], this project does not target either. This decision is due to the differences in their graphical architectures and the degree of control they provide to applications for managing other windows. On Microsoft Windows, desktop composition and the presentation of application windows are handled by the Desktop Window Manager (DWM) [source]. DWM is an integrated component of the Windows graphical environment, responsible for compositing individual application surfaces into the final desktop image. Although Windows provides APIs that allow applications to manipulate window positions and dimensions, these do not provide an equivalent mechanism for straightforwardly replacing the system's existing window management infrastructure, making it unsuitable for understanding a real Window management system rather than building one from the ground up. Similarly, macOS integrates window management into its graphical environment through components such as WindowServer [source]. Although third-party applications can provide alternative window arrangement behaviours, replacing the underlying system-level window management functionality is not generally supported through a simple, user-replaceable interface. But even that isn't as simple as Linux. So if I want to build my own window manager, Linux provides a much more suitable environment. Linux is highly customisable, and the user can choose the graphical session on Linux devices; as such, they can start their own programs as the graphical session directly from the TTY or through their init system (like `systemd`). The two main display server protocols to consider for Linux are: * [Wayland](https://wayland.freedesktop.org/) * [X11](https://www.x.org/wiki/) ### X11 vs Wayland Wayland (started in 2008) is a much newer display standard compared to X11, which has been around since 1984. Although X11 has received many updates, the core philosophy remains the same. Wayland has been steadily replacing X11 across Linux distributions recently. Data from [Linux Hardware](https://linux-hardware.org/?view=os_display_server) shows Wayland's adoption increasing significantly, although this shift is relatively recent. KDE's own metrics are also very useful. [David Edmundson's analysis of the KDE metrics](https://blog.davidedmundson.co.uk/blog/metrics-in-kde-are-they-useful/) showed an overall split of approximately **45% Wayland and 55% X11** at the time of the analysis. However, the distribution was quite different on newer systems, where Wayland was the default: these systems showed closer to **80% Wayland and 20% X11**. This suggests that much of X11's remaining usage comes from existing installations rather than from users actively choosing it over Wayland, as most people go with whatever default is used. Several technical reasons explain this shift. X11 has grown significantly more complex over time, while Wayland was designed to address many of its issues. Wayland provides stronger security boundaries, reduces unnecessary frame-buffer copying and memory usage, and has a more straightforward approach to compositing and preventing issues such as screen tearing. These differences are discussed further in [this comparison of X11 and Wayland](https://www.linuxteck.com/x11-vs-wayland/) and in [Phoronix's discussion of the current X11/XWayland situation](https://www.phoronix.com/review/x_wayland_situation). An important architectural distinction between X11 and Wayland is the responsibility assigned to the window manager and display server. Under X11, the display server and window manager operate as separate components. The X server provides the underlying infrastructure for managing display resources, processing drawing requests and dispatching input events. The window manager operates as a separate X11 client, communicating with the server through established conventions to control window placement, focus and other management policies [source]. Wayland adopts a different architecture in which the compositor also acts as the display server. Rather than relying on a separate, interchangeable window manager, the compositor manages application surfaces, handles input, and draws the final output [source]. This difference between the two protocols has significant implications for the scope of this project. Because in a Wayland compositor the same program must implement Window management, application buffer handling, compositing, any effects and the final draw to the hardware. While in X11, the window manager is separate from the server, meaning it only has to implement the window management system, and the Xorg server handles compositing and hardware management. Although libraries like wlroots can be used to simplify this for a Wayland implementation, it still adds unnecessary complexity beyond the original scope for this project. So I chose X11 as the display protocol for this project because it separates display-server functionality from window management, allowing me to focus on developing window management algorithms, data structures, and user interaction rather than implementing an entire display server and compositor. One particularly important feature for my workflow is X11's support for remote graphical applications over **SSH through X11 forwarding** [source]. I regularly use SSH to work with remote servers, and this provides a straightforward way of running graphical applications remotely. Wayland does not provide an equivalent native mechanism with the same workflow [source], meaning additional applications [source] would be required to achieve the same. ### Existing systems. #### i3 i3 is a popular, lightweight, keyboard-driven tiling window manager for X11, written in C. Its primary focus is automatic tiling, where windows are arranged to occupy all the available screen space. i3 uses a tree-based container system to represent window arrangements. Containers can be split horizontally or vertically, allowing users to get complex layouts and nested arrangements while filling the entire screen. Users primarily interact with i3 through keyboard shortcuts, which can be configured through a static plain-text configuration file. These shortcuts can perform actions such as navigating between windows, changing their sizes, switching workspaces and launching external commands. i3 also supports multiple workspaces, allowing windows to be organised into separate virtual desktops. Additional functionality includes floating windows, multi-monitor support and an IPC interface that allows external programs to communicate with and control the window manager. The main features I identified from i3 that could be relevant to this project are: - Automatic window tiling. - Horizontal and vertical window splitting. - Keyboard-driven window navigation and focus management. - Adjustable window sizes. - Multiple workspaces. - Configurable keyboard shortcuts. - Execution of external commands through shortcuts. - Multi-monitor support. - Floating windows. - IPC interface for remote control. ^ i3 (2026). i3 User's Guide. [online] i3wm.org. Available at: https://i3wm.org/docs/userguide.html [Accessed 7 October 2026]. #### Xmonad Xmonad is a tiling window manager for X11, written in Haskell. Although it provides much of the same core functionality as i3, its approach to window arrangement and configuration is quite different. Where i3 uses a tree-based container system to represent window arrangements, Xmonad uses a composable layout system. Layouts determine how windows are arranged and can be combined or extended to create more complex arrangements. This provides a more modular approach to implementing tiling algorithms. Perhaps the most interesting aspect of Xmonad is its configuration system. Rather than using a dedicated configuration syntax like i3, Xmonad uses Haskell itself. This means configuration isn't limited to changing predefined settings: users can write code to define custom layouts, window management behaviour, keyboard shortcuts, and other functionality. This provides considerable flexibility and extensibility, allowing users to customise the window manager's behaviour far beyond what a conventional static configuration file would allow. The ability to use a general-purpose programming language for configuration is an interesting concept that I may consider incorporating into my own implementation, as it could provide a powerful mechanism for customising window management behaviour. ## Feature list. Potential features I could add to my project. | | Feature | Priority | | --- | --- | --- | | 1 | Automatic window tiling. | Must | | 2 | Horizontal and vertical window splitting. | Must | | 3 | Keyboard-driven window navigation and focus management. | Must | | 4 | Configurable keyboard shortcuts. | Must | | 5 | Execution of external commands through shortcuts. | Must | 6 | Adjustable window sizes. | Want | | 7 | Multiple workspaces. | Want | | 8 | Floating windows. | Want | | 9 | Scriptable configuration. | Want | | 10 | IPC interface for remote control. | Could | | 11 | Multi-monitor support. | Won't | --- # Technologies ## XCB vs Xlib Xlib and XCB are two C libraries that provide an interface for applications to communicate with an X11 server. Both implement the X11 client-side protocol, allowing applications to perform operations such as creating windows, modifying their properties, handling input events and controlling their position. > https://www.x.org/guide/xlib-and-xcb/ XCB is newer than Xlib, and it offers more fine-grained control. This makes operations in XCB more verbose than in Xlib. I decided to use XCB to communicate with and control the X server. ## Language To choose a language for the project, I first made prototypes in a few languages. ### Ruby My first version used standard Ruby, calling XCB through Ruby's FFI bindings. I started here because I already knew the language well, and it let me learn the X11 protocol (connections, event loops, window handling, key grabs, etc.) without also learning a new language. This worked as a learning tool. I got a basic window manager running in a couple of weeks, and it confirmed that my understanding of the X event model was correct. I abandoned it for two reasons. 1. The first was performance. Ruby is not built for performance. 2. The second was structure. The prototype had grown into a collection of scripts rather than a single program, with no clear module boundaries and no compile-time checking of the layout logic. For software whose main failure mode is crashing a user's desktop, finding errors only at runtime was a poor fit. It also meant users would need a Ruby interpreter and the FFI gem installed, which weakens the distribution criterion. Ruby was the right tool for prototyping but the wrong one for the finished product. ### Haskell For the second version, I wanted a compiled, statically typed language. I chose Haskell because a tiling window manager is largely a state-transformation problem: the layout tree, the workspaces and the focus are all data, and each event produces a new state. Haskell's algebraic data types, pattern matching and emphasis on pure functions suit this well. (Even `Xmonad` is written in Haskell, which supports this reasoning.) I built [Add actual feature set I made here.] in Haskell. The state model was noticeably cleaner than in Ruby, and the project looked much better written. I stopped because of the toolchain. A full GHC installation took around 9 GB of disk space, [built it and add time taken here.] for a full build on [Hardware information], and I found the surrounding tooling (Stack and Cabal, package resolution, version compatibility) difficult to work with. Each of these slowed down the edit, build and test loop, which matters when most of the work is trial and error against a live X server. Haskell showed me that this functional language style was perfect for this problem, but the toolchain did not suit my workflow. ### Final choice: OCaml I moved to OCaml because it kept what worked in Haskell and removed what didn't. It kept a very similar code style, so much of my Haskell design carried over directly. It also kept pattern matching and immutable data structures as-is, and the recursive function style for a layout tree is the best way to work with such a project. And the compiler and tooling are far lighter (395MB, 463ms [on the same hardware]), and `dune` handles building and dependency management with far less friction than my experience with Stack. OCaml also compiles to native code, giving a single binary and solving the distribution problem that Ruby had. However, OCaml has a smaller ecosystem, and no maintained XCB bindings suited my needs. So I wrote the bindings myself using ctypes, with a few small C shims where the C API was awkward to call directly. This was more work than using an existing library directly. ## Embedded Scripting As I plan to make the window manager configurable at runtime using a script, I need an embedded scripting system to read the configuration and evaluate it in the correct context. We can also see a relatively comprehensive [list](https://dbohdan.github.io/embedded-scripting-languages/) of embedded scripting languages; common ones include Lua, JavaScript variants, Squirrel, and Ruby variants (mruby in particular). This list includes languages with a C-ABI, meaning they can be used by most programming languages with FFI. ^ Rordrigues, C. (2023). Embedded Scripting Languages. [online] caiorss.github.io. Available at: https://caiorss.github.io/C-Cpp-Notes/embedded_scripting_languages.html [Accessed 13 May 2026]. ^ dbohdan. (2022). Embedded scripting languages. [online] Available at: https://dbohdan.github.io/embedded-scripting-languages/ [Accessed 13 May 2026]. #### Why Mruby Why Ruby is great for development for the runtime of this WM: - Dynamic typing. It has almost no type declarations for variables, and duck typing means any object with particular behaviour can be treated the same way. - Expressive syntax. Ruby has one of the most expressive syntaxes, which greatly improves code readability; it allows operator overloading, very clean DSL structures, minimal punctuation, and many syntactic shortcuts. - Very rich standard library. The Ruby stdlib has many helpers for JSON, sockets, networking, file management, multithreading, regex, etc. - It also has a lot of built-in data types with helper methods that allow doing a lot of stuff. It has integers with no hard maximum, floating-point numbers, strings, and arrays, but it also has symbols, hashmaps, and regex, etc., for much faster development. And as its official website states, Ruby is easy to write and easy to read, with natural syntax like spoken language. > Ruby's expressive syntax allows you to write complex logic concisely. By leveraging powerful features like metaprogramming and blocks, you can reduce repetition and focus on solving core problems. With rich testing frameworks, you can maintain quality while achieving rapid development cycles. > Ruby has a simple and intuitive syntax that reads like natural language. By eliminating complex symbols and verbose constructs, Ruby's design philosophy allows you to express what you want directly. With minimal boilerplate and high readability, it's beginner-friendly and maintainable for experienced developers. (these are citations) For these reasons, and because of my personal familiarity with the language, I plan to use Ruby to write the WM configuration. More specifically, I will use the `mruby` version, as it lets us embed a lightweight Ruby VM in our program so the scripts can run without an external interpreter. `Mruby` is a very lightweight implementation of the Ruby runtime. ^ Mruby.org. (2026). mruby - Lightweight Ruby. [online] Available at: https://mruby.org/ [Accessed 13 May 2026]. ^ Ruby (2019). Ruby Programming Language. [online] Ruby-lang.org. Available at: https://www.ruby-lang.org/en/ [Accessed 13 May 2026]. --- # Design ## System Architecture ![Architecture Image](./images/arch.png) The window manager controls the windows by communicating with the X server through the XCB library, using the OCaml FFI library `ctypes-foreign`. It also gets the window and other events from the X server through the XCB library. The WM also hosts the Ruby VM implemented by the mruby library. I wrote the bindings for both libraries myself in OCaml `ctypes`, using C shims where necessary. ## Internal Architecture Internally, the project has 5 modules. 1. `decl` - Declares commonly used types. 2. `bindings` - Declares functions bound to the mruby VM (for use in the config files) 3. `kutu` - Declares the actual core layout system, event management, state, etc. 4. `mruby` - Mruby bindings using ctypes 5. `xcb` - XCB bindings using ctypes. ## Toolchain dune + nix + gcc (for mruby). (Maybe put Dune and Nix file explanation here.) ## Window Representation and Data Structures ```ocaml type rect = { rect_x : int; rect_y : int; rect_width : int; rect_height : int } type direction = Horizontal | Vertical type layout = | Leaf of { layout_window : Xcb.Types.Window.t } | Split of { layout_direction : direction; layout_ratio : float; layout_l : layout; layout_r : layout; } type window_result = { wr_rect : Decl.Types.rect; wr_window : Xcb.Types.Window.t; } type workspace = { mutable ws_root : layout option; mutable ws_fwindows : Xcb.Types.Window.t list; } ``` ## Layout Algorithms ```ocaml let split_rect (rect : Decl.Types.rect) direction ratio = match direction with | Horizontal -> let left_width = int_of_float (float rect.rect_width *. ratio) in let right_width = rect.rect_width - left_width in let left = { Decl.Types.rect_x = rect.rect_x; Decl.Types.rect_y = rect.rect_y; Decl.Types.rect_width = left_width; Decl.Types.rect_height = rect.rect_height; } in let right = { Decl.Types.rect_x = rect.rect_x + left_width; Decl.Types.rect_y = rect.rect_y; Decl.Types.rect_width = right_width; Decl.Types.rect_height = rect.rect_height; } in (left, right) | Vertical -> let top_height = int_of_float (float rect.rect_height *. ratio) in let bottom_height = rect.rect_height - top_height in let top = { Decl.Types.rect_x = rect.rect_x; Decl.Types.rect_y = rect.rect_y; Decl.Types.rect_width = rect.rect_width; Decl.Types.rect_height = top_height; } in let bottom = { Decl.Types.rect_x = rect.rect_x; Decl.Types.rect_y = rect.rect_y + top_height; Decl.Types.rect_width = rect.rect_width; Decl.Types.rect_height = bottom_height; } in (top, bottom) let rec calculate rect = function | None -> [] | Some l -> ( match l with | Leaf { layout_window } -> [ { wr_rect = rect; wr_window = layout_window } ] | Split { layout_direction; layout_ratio; layout_l; layout_r } -> let left_rect, right_rect = split_rect rect layout_direction layout_ratio in let left_windows = calculate left_rect (Some layout_l) in let right_windows = calculate right_rect (Some layout_r) in left_windows @ right_windows) ``` ## Configuration and mruby Integration ```ocaml let register_callbacks state = register_exit state; Window_op.register_kill state; Window_op.register_toggle_float state; Ws_op.register_jump state; Ws_op.register_ws_count state; Ws_op.register_ws_empty state; Keybinds.register_bind state; Keybinds.register_unbind state ``` --- # Development Go through git commits and try to explain each major step and problems and solutions i came up with. and things i eventually changed from original versions. (not sure if i should include sections on my log for the ruby and haskell versions, though i feel like it would get too long if i did.) --- # Testing ## Automated tests for data structure. ## User testing & Performance testing ## Comparision against original feature list and what i did go through any why i dropped teh other bits. --- # Conclusion ## A more generic success conslusion and objectives ## What i learnt from it. ## Potential improvements. --- # Bibliography # Gannt chart # Evaluvation of each source from bibliography. # Screenshots etc.