Open-source project
bea4dev/ShojiWM avatar
bea4dev/ShojiWM

ShojiWM and the Wayland compositor whose window manager is a config file

The most customizable Wayland compositor with TypeScript(tsx).

754 stars47 forksRustMIT

At a glance

What is it?
ShojiWM is a Wayland compositor written in Rust that ships only a compositor and a default window-management config, so the window manager becomes something you program in TypeScript, in Rust, or in any language that implements a runtime interface. The upside is total control over decorations, shaders and layout, the cost is an alpha release and a project that expects you to build the window manager yourself.
Who is it for?
Adopt ShojiWM if you want to own the window manager of your Wayland session outright, to the point of writing the decoration buttons and the shader pipeline yourself, and if you are ready to treat the config as application code.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A compositor with no fixed window manager

ShojiWM is a Wayland compositor whose distinguishing decision is what it refuses to bundle. The README makes the comparison with Niri and Hyprland directly: those projects bundle a window manager and a compositor together, while ShojiWM provides only the compositor, plus window-manager functionality as a default config.

The consequence is stated plainly. The window-management part is something you program entirely and freely yourself, which is the justification for the project's own slogan, calling itself the most customizable Wayland compositor with TypeScript. The comparison table the README publishes makes the same point in capability terms: server-side decoration customization through a standard API is marked as built in for ShojiWM and absent for the other two, and building your own window-management strategy in TypeScript is marked the same way, with Hyprland marked partial because version 0.55 and later add custom layouts and event scripting through Lua rather than TypeScript, with core WM behaviour remaining built in.

The feature checklist covers the rest of what a compositor is expected to provide: window management, animations, screenshots and screen sharing through xdg-desktop-portal-shojiwm, XWayland support through xwayland-satellite, custom shaders, layer shell support, multi-monitor support, and Intel, AMD and NVIDIA GPU support.

The audience is therefore narrow and specific. A user who wants a Wayland session and is willing to write the window manager is the target, and the repository makes that concrete with examples for a KDE Plasma shell, an output composition and an output overlay.

A language-neutral runtime interface with three ways in

The architecture that makes the slogan true is a split between the compositor core and the config runtime. The core, the crate named shojiwm_lib, talks to its config through a language-neutral runtime interface, and ShojiWM ships two runtimes of its own while allowing a third kind to be written by anyone.

TypeScript and TSX is the default rather than a requirement. It runs in the shoji_wm crate on an embedded Deno and V8 runtime, and it is the runtime that supports hot reload with the Super, Shift and R key combination. The Rust option is the shojiwm_rs crate, which offers the same reactive model as the TypeScript SDK, with signals, view builders and a COMPOSITOR object, and the difference that matters operationally is that your Rust config compiles into its own compositor binary rather than being interpreted.

The third door is the important one for anyone who will not write TypeScript or Rust. Implementing ConfigRuntime and RuntimeLauncher from the shojiwm_lib runtime_api module lets you write a config in C#, Lua, Python or anything else, in its own crate, without linking V8. That is what makes the claim about customisation credible for people whose toolchain is not the one the project ships: the interface is the contract, and the language is your choice.

The whole default config is also ported to Rust as an example, which is useful documentation in its own right, since it shows the same user interface expressed in two languages.

A close button written as a reactive component

The README illustrates the point with a window close button, and the example is worth reading closely because it shows what the reactive model looks like in practice. The component takes a window, keeps a hover flag in local state, and derives the border colour from that state:

tsx
const CloseButton = ({ window }: { window: WaylandWindow }) => {
  const [hover, setHover] = useState(false);

  const borderColor = hover((hover) => (hover ? "#00000000" : "#F0808030"));

The button is a Box that holds a Button and, when the pointer is over it, an Image of the close glyph:

tsx
    <Box style={{ position: "relative", flexShrink: 0 }}>
      <Button
        onHoverChange={setHover}
        style={{
          width: 16,
          height: 16,
          borderRadius: 8,
          background: "#FFFFFF20",
          border: { px: 1, color: borderColor },
        }}
        onClick={window.close}
      />
      {icon}
    </Box>

Two details make this more than a styling demo. The state is read through a call, with hover invoked as a function, and the border colour is a value derived from that state, so the appearance updates on hover without the code imperatively touching the border. And the click handler is bound straight to window.close, which is the shape of a real window management policy rather than a mock.

The README notes that when you run it, the button's composition changes reactively on hover, so its appearance updates. The alternative would have been a compositor that hard-codes a close button and a decoration layout, which is the thing this project set out not to build.

The same button in Rust, built into the compositor

The Rust runtime is not a different design with the same output. It offers the same reactive model, with signals and view builders, and the close button reads as a builder chain:

rust
fn close_button(window: Window) -> Element {
    let hover = signal(false);
    let border = hover.map(|hover| if *hover { hex("#00000000") } else { hex("#F0808030") });

    Flex::column()
        .style(Style::new().relative().flex_shrink(0.0))
        .child(
            Button::new()
                .on_hover_change(move |value| hover.set(value))
                .on_click(move || window.close())

The signal is created once and mapped into the border value, so the same derive-on-state idea is expressed with a Rust type rather than a JavaScript closure. The child_dyn call in the fuller example makes the icon conditional on the current hover value, which is the dynamic part of the composition.

What changes for the user is the deployment model. A TypeScript config runs inside the embedded Deno and V8 runtime and can be reloaded while the session is live, which is a fast iteration loop for tweaking decorations. A Rust config compiles into the compositor binary, so changing it means rebuilding, but you get the same result without a JavaScript engine in the process.

The Cargo workspace lists the four crates that make up the project, the core library, the compositor, the Rust config runtime and the desktop portal, and it configures a release-fast profile for the edit-test loop with the same optimization level as release but parallel codegen, no LTO and incremental rebuilds. The comment in the manifest is explicit that benchmarks and tagged releases should be built with release, which is a good sign about how the maintainer treats the difference between a fast dev build and a fast production build.

Running it: a Nix shell and one cargo command

The quick start in the README is two commands, and it assumes Nix:

bash
nix develop
cargo run --release -p shoji_wm -- --dev

The nix develop step enters the development environment defined by the flake files in the repository, and the cargo command builds and launches the compositor in development mode. The README points to the getting started guide for source installs, and the installation docs cover the NixOS module route, which the README describes as experimental support.

If you prefer to stay in Rust, the README gives a way to look at the default config without launching anything:

bash
nix develop
cargo run --release -p shojiwm_rs --example default_config

That runs the ported Rust example config, which is a reasonable way to read the shipped configuration before you change it, since the config is where the window manager actually lives.

The JavaScript side of the project is a monorepo with a single package manifest, and its scripts are oriented around checking rather than shipping:

json
  "scripts": {
    "typecheck": "tsc -p packages/shoji_wm/tsconfig.json && tsc -p packages/config/tsconfig.json",
    "test:overlays": "node --import tsx --test tests/output-overlay.test.ts && tsc -p tests/tsconfig.overlays.json",
    "test:output-power": "node --import tsx --test tests/output-power.test.ts"
  },

The tests run through tsx and the type check covers both the TypeScript runtime package and the config package, so the config is type-checked in CI rather than only at runtime when the compositor loads it.

Where it stands against the compositors people already use

The README publishes a capability table across Niri, Hyprland and ShojiWM, with a legend for built in, partial and no. Reading the footnotes matters, because they are where the differences are defined.

On the shader pipeline, the table marks Hyprland partial on the grounds that it offers a single full-screen screen shader rather than a per-element pipeline, and Niri partial because custom GLSL there is limited to window open, close and resize animations. ShojiWM is marked as having a custom shader pipeline API, and the same distinction applies to gaming support: Niri is marked partial because it supports VRR but not a tearing or immediate-flip mode, while Hyprland and ShojiWM are both marked as supporting Linux gaming including tearing.

XWayland is the one row where ShojiWM and Niri are aligned, with first-class xwayland-satellite support, and Hyprland marked as having none.

The README itself qualifies the table, noting that the comparison reflects each project at the time of writing and inviting corrections. That is a fair caveat for a table of fast-moving software, and it is also a reminder that the feature lists of Niri and Hyprland change on their own schedule, while ShojiWM's window manager stays yours to write.

For an evaluation, the useful question is not which row wins. It is whether the rows you care about, decorations you program yourself, a shader pipeline you control, and a language your team already writes, are the ones ShojiWM claims outright.

An alpha tag, a permissive licence and a busy main branch

The release history is short. There is one tagged release, 0.0.1-alpha, published on 2026-06-20, and the last push to the repository was on 2026-10-18, so the tree is moving faster than the tags. The repository is not archived, and the licence is MIT, with a LICENSE file at the root, which is the permissive end of the scale and carries the usual obligation to keep the copyright notice in copies and derivatives.

The documentation is published in two languages, English and Japanese, at the project's GitHub Pages site, with a section dedicated to configuration languages and the Rust API, plus the getting started and installation pages referenced by the README.

The repository layout suggests where the project is heading. There is a knowledges/ directory, a dist/ directory, a tools/ directory, a packages/ directory for the TypeScript monorepo and a nix/ directory, alongside the Rust sources and the examples. A language server backend directory appears in the source tree, and the feature list does not mention editor tooling, so that part of the work is newer than the documentation.

The honest summary is that this is a project with a clear architectural idea, a documented extension point for other languages, and an alpha tag. The window manager you get is the one you write, and the cost of that freedom is that nothing arrives finished.

Editorial conclusion

Adopt ShojiWM if you want to own the window manager of your Wayland session outright, to the point of writing the decoration buttons and the shader pipeline yourself, and if you are ready to treat the config as application code. Skip it if you want a ready-made desktop behaviour, because the default config is a starting point rather than a product, and hold off if an alpha release is a problem, since the only tag is 0.0.1-alpha from 2026-06-20 even though the last push was on 2026-10-18. Verify first that nix develop followed by cargo run --release -p shoji_wm -- --dev brings up a session on your GPU, since the feature list covers Intel, AMD and NVIDIA but the README documents no per-driver setup steps.

Frequently asked questions

How do I run ShojiWM from source?

The README's quick start is nix develop followed by cargo run --release -p shoji_wm -- --dev, with source installs documented in the getting started guide and a NixOS module described as experimental support in the installation docs.

Which languages can a ShojiWM config be written in?

TypeScript and TSX is the default, running on an embedded Deno and V8 runtime, Rust is available through the shojiwm_rs crate and compiles into its own compositor binary, and any other language can be used by implementing ConfigRuntime and RuntimeLauncher from shojiwm_lib::runtime_api without linking V8.

Does ShojiWM support XWayland and screen sharing?

XWayland support comes through xwayland-satellite, and screenshots and screen sharing are provided through the xdg-desktop-portal-shojiwm portal. Custom shaders, layer shell, multi-monitor and Intel, AMD and NVIDIA GPU support are also listed as supported features.

How do I reload a ShojiWM config while the session is running?

The TypeScript and TSX runtime supports hot reload with the Super, Shift and R key combination. The Rust runtime does not reload in place, since a Rust config compiles into the compositor binary.

What is the release status of ShojiWM?

The only tagged release is 0.0.1-alpha, published on 2026-06-20, and the last push to the repository was on 2026-10-18. The project is not archived and is licensed under MIT.

Official sources

  1. bea4dev/ShojiWM on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/bea4dev-shojiwm.svg)](https://hysenlabs.com/projects/bea4dev-shojiwm)

Was this useful?