Compare commits
1
Commits
4c0a4db72c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7121eb62d2
|
+51
-48
@@ -58,13 +58,11 @@ So implementing it myself would be a good way for me to both understand how wind
|
||||
|
||||
#### 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.
|
||||
A graphical environment needs to allow multiple applications to run at once, all on the same display area (the screen). Each application window generally only knows what is to be drawn inside its own area, but higher-level interactions like resizing them, hiding or restoring the windows, their stacking order etc. need to be managed by a common program. This is necesary for most general purpose operating systems, as users expect to be able to move resize and manage the applications they use. [source]
|
||||
|
||||
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 dimensions and behaviour of application windows on a graphical display.
|
||||
|
||||
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:
|
||||
A window manager typically handles several responsibilities: [source?](https://pcpartsgeek.com/window-manager/)
|
||||
- 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.
|
||||
@@ -72,13 +70,12 @@ A window manager typically handles several responsibilities:
|
||||
|
||||
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]
|
||||
- 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, file manager and similar software. [See nomenclature for definition of a DE]
|
||||
- No decorations: I do not plan on drawing any decorations or border for the windows.
|
||||
- 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.
|
||||
|
||||
@@ -106,13 +103,13 @@ The aim is to design and implement a functional, keyboard-driven tiling window m
|
||||
|
||||
### 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.
|
||||
Microsoft Windows and macOS account for most desktop operating systems used worldwide [source], but 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.
|
||||
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 [source], it does not provide a way to replace the system's existing window manager, making it not possible to build 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.
|
||||
macOS also integrates window management into its graphical stack through components such as WindowServer [source]. Although applications can provide alternative window arrangement behaviours, it is not as simple as it is in 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`).
|
||||
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 their Linux device; as such, they can start their own programs as the graphical session directly from the TTY or through their init system (like `systemd`) without restrictions.
|
||||
|
||||
The two main display server protocols to consider for Linux are:
|
||||
|
||||
@@ -122,22 +119,22 @@ The two main display server protocols to consider for Linux are:
|
||||
### 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.
|
||||
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 this analysis. However, the distribution was quite different on 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).
|
||||
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.
|
||||
[comparison of X11 and Wayland](https://www.linuxteck.com/x11-vs-wayland/) and [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.
|
||||
An important difference 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].
|
||||
Under X11, the display server and window manager are 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 an established protocol to control window placement, focus, etc. [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].
|
||||
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 on its own. Meaning the server itself manages the responsibility of the window manager (and vice versa). [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.
|
||||
|
||||
@@ -145,7 +142,7 @@ Although libraries like wlroots can be used to simplify this for a Wayland imple
|
||||
|
||||
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.
|
||||
One particularly important feature for my own workflow is X11's native 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 work remotely.
|
||||
|
||||
### Existing systems.
|
||||
|
||||
@@ -153,13 +150,18 @@ One particularly important feature for my workflow is X11's support for remote g
|
||||
|
||||
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.
|
||||
Its primary focus is tiling, where windows are arranged to occupy all the available screen space.
|
||||
|
||||
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.
|
||||
It achieves this by using a tree-based container system to represent window arrangements. Containers can be split horizontally or vertically, allowing users to get complex layouts while filling the entire screen.
|
||||
|
||||
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.
|
||||
It also does Window Decorations, that show a window's name and focus.
|
||||
|
||||
i3 can be configured through a static plain-text configuration file. Apart from settings like locales, startup apps etc. they can define shortcuts for particular keys in this config file. These shortcuts can perform actions such as navigating between windows, changing their sizes, switching workspaces or launching external commands.
|
||||
|
||||
i3 also supports multiple workspaces, allowing windows to be organised into separate virtual desktops (that can also be defined in the configuration file).
|
||||
|
||||
The main features I identified from i3 that could be relevant to this project are:
|
||||
- Window Decorations
|
||||
- Automatic window tiling.
|
||||
- Horizontal and vertical window splitting.
|
||||
- Keyboard-driven window navigation and focus management.
|
||||
@@ -177,15 +179,16 @@ The main features I identified from i3 that could be relevant to this project ar
|
||||
|
||||
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.
|
||||
From my research, it has similar core functionality to i3.
|
||||
But its approach to internal representation of the windows 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.
|
||||
Where i3 uses a tree-based container system to represent window arrangements, Xmonad uses a composable layout system.
|
||||
Meaning the core Window manager itself uses a subsystem to decide how the windows are placed, which by default is similar to i3, but the user can replace it with their own layout systems unlike i3.
|
||||
|
||||
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 is possible because unlike the plain text configuration system i3 uses, Xmonad is configured in haskell.
|
||||
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.
|
||||
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 way to customise the window manager's behaviour.
|
||||
|
||||
## Feature list.
|
||||
|
||||
@@ -204,6 +207,7 @@ Potential features I could add to my project.
|
||||
| 9 | Scriptable configuration. | Want |
|
||||
| 10 | IPC interface for remote control. | Could |
|
||||
| 11 | Multi-monitor support. | Won't |
|
||||
| 12 | Window decorations. | Won't |
|
||||
|
||||
---
|
||||
|
||||
@@ -211,12 +215,12 @@ Potential features I could add to my project.
|
||||
|
||||
## 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.
|
||||
Xlib and XCB are two C libraries that provide an interface for applications to communicate with an Xorg server. Both implement the X11 client-side protocol, allowing applications to perform operations like creating windows, modifying their properties, handling input events and controlling their position. Using such a library is very useful as directly attempting to communicate with the Xorg server is out of the scope of this project.
|
||||
> 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.
|
||||
XCB is newer than Xlib, and it offers more fine-grained control over each operation, as it tries to map its working closer to the actual protocol used. This makes operations in XCB more verbose than in Xlib. But it is also in some ways better, it uses a more heavily asynchronous model as compared to Xlib giving some performance gains.
|
||||
|
||||
I decided to use XCB to communicate with and control the X server.
|
||||
I decided to use XCB to communicate with and control the X server as I found it easier to use.
|
||||
|
||||
## Language
|
||||
|
||||
@@ -224,24 +228,23 @@ 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.
|
||||
My first prototype used standard Ruby, calling XCB through Ruby's FFI bindings. I used a C shim file which I compiled with simpler functions for the operations I actually use. Then I wrote FFI bindings to bind these symbols to the Ruby code. I started with this because I already knew the language well, and it let me learn the X11 protocol (connections, event loops, window handling, key grabs, etc.) in a familiar environment.
|
||||
|
||||
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.
|
||||
This worked great as a learning tool. I got a basic window manager running in a couple of days, and it confirmed my understanding of the X11 event model.
|
||||
|
||||
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.
|
||||
But I abandoned it for two reasons.
|
||||
1. The first was performance. Ruby is not built for performance, it is built for ease of use. [source]
|
||||
2. The second was structure. The prototype had grown into a collection of scripts rather than a single program as I wanted. It also meant users would need a Ruby interpreter and the FFI gem installed, which makes it harder to distribute and test on new systems.
|
||||
|
||||
Ruby was the right tool for prototyping but the wrong one for the finished product.
|
||||
Ruby is the right tool for prototyping but not for a 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.)
|
||||
For the second version, I wanted a compiled, to make distribution easier. I chose Haskell because a tiling window manager is largely a state-transformation problem. And the layout tree is far more fun to implement in a functional language with pattern matching. 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.
|
||||
The state model was noticeably cleaner than in Ruby where everything was stored in hashmaps, 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.
|
||||
But I stopped because of the toolchain. A full GHC installation took around 9 GB of disk space, it took 3.7 seconds for every build on my device, and I found the surrounding tooling (Stack and Cabal) difficult to work with. This 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.
|
||||
|
||||
@@ -251,12 +254,12 @@ 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.
|
||||
It also has similar pattern matching and data types definition and usage meaning it was far easier to make this switch.
|
||||
|
||||
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.
|
||||
And the compiler and tooling are far lighter (395MB for my ocaml installation and 463ms to build a similar project 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. And it also has an interpreted mode, making quick testing easy too.
|
||||
|
||||
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.
|
||||
However, OCaml has a smaller ecosystem, and no maintained XCB binding library 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
|
||||
@@ -301,13 +304,13 @@ More specifically, I will use the `mruby` version, as it lets us embed a lightwe
|
||||
|
||||
## System Architecture
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
It also gets the events (for windows, input, displays etc.) from the X server through the XCB library.
|
||||
|
||||
The WM also hosts the Ruby VM implemented by the mruby library.
|
||||
The window manager also hosts the Ruby VM implemented by the mruby library. It starts an instance at startup and loads any configuration once, and the script is itself allowed to reload itself or even call and run other scripts.
|
||||
|
||||
I wrote the bindings for both libraries myself in OCaml `ctypes`, using C shims where necessary.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user