A tui/cli program for tweaking niri wm display outputs dynamically without modifying configurations.
Rust 99%
Nix <1%
Shell <1%

README.md

DYNAMONIX

Note

This project is maintained with the assistance of AI tools. All changes are subject to manual review and a comprehensive test suite to ensure stability and quality.

What? #

A tui/cli program for tweaking niri wm display outputs dynamically without modifying the underlying configuration. Dynamonix speaks to the compositor only through the niri socket. It does not read the niri configuration file. It does not write the niri configuration file.

Why? #

Setting up new monitors/display outputs and adjusting the layout requires making changes to the niri configuration file even if you only need to use that display output layout for a brief period. Now if niri is configured using nix/home-manager, you'll also need to do a home-manager switch.

Dynamonix lets you tweak the current display output layout quickly with a pretty TUI without changing your niri configuration file. All changes made by dynamonix are in-memory and ephemeral. To use an arrangement again later, save it as a profile. A profile goes in a file of dynamonix, and your niri configuration file stays as it is.

 dynamonix   3 outputs  ·  3 on  ·  2 pending changes
╭ arrangement ───────────────────────────────╮╭ outputs ──────────────────╮
│                                            ││▍● DP-2      3440x1440 ×1  │
│    ┌───────────────┐      ┌────────────┐   ││ ● HDMI-A-1  3840x2160 ×1  │
│    │ DP-2          │      │ HDMI-A-1   │   ││ ● eDP-1     1920x1200 ×1  │
│    │ 3440x1440 ×1  │      │ 3840x2160  │   │╰───────────────────────────╯
│    └───────┬───────┴──────┴────────────┘   │╭ properties ───────────────╮
│            │ eDP-1                         ││ mode      3440x1440 ★ 1/3 │
│            │ 1920x1200 ×1                  ││ rate      143.999 Hz      │
│            └───────────────────────────────┤│ scale     ×1              │
╰────────────────────────────────────────────╯╰───────────────────────────╯

The compositor forgets these changes in two conditions:

  • The compositor stops.
  • The niri configuration file changes, and the compositor reads it again.

To keep an arrangement after a restart, save it as a profile, or write it in your niri configuration file.

Installation #

Cargo #

The program needs Rust 1.90 or a later version.

cargo install dynamonix

Nix #

The repository holds a flake. The flake builds the program for x86_64-linux and for aarch64-linux.

Run the program one time. This command installs nothing:

nix run git+https://git.devtechnica.com/sreedev/dynamonix

Put the program in your profile:

nix profile install git+https://git.devtechnica.com/sreedev/dynamonix

Add the flake to the inputs of your own flake. The follows line keeps one copy of nixpkgs:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    dynamonix = {
      url = "git+https://git.devtechnica.com/sreedev/dynamonix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs =
    { nixpkgs, dynamonix, ... }:
    {
      nixosConfigurations.example = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          (
            { pkgs, ... }:
            {
              nixpkgs.overlays = [ dynamonix.overlays.default ];
              environment.systemPackages = [ pkgs.dynamonix ];
            }
          )
        ];
      };
    };
}

The overlay gives the name pkgs.dynamonix. A configuration that uses no overlay takes the package from the flake directly:

environment.systemPackages = [ dynamonix.packages.x86_64-linux.default ];

Use #

Start the program in a niri session:

dynamonix

Show the outputs and stop:

dynamonix list

Show the interface with three monitors of a sample. This mode does not need a compositor:

dynamonix --demo

Show the outputs as JSON for a script. The command writes one JSON object and nothing else:

dynamonix list --json

Save the arrangement of the outputs with a name:

dynamonix save desk

Send the arrangement of a saved profile without the interface:

dynamonix apply desk

Let the program send the profile of a new group of monitors with no question:

dynamonix --auto

Show the profiles that you saved. This command does not need a compositor:

dynamonix profiles

Use a different socket:

dynamonix --socket /run/user/1000/niri.wayland-2.12345.sock

Profiles #

A profile holds an arrangement of your monitors with a name. The program keeps the profiles in $XDG_CONFIG_HOME/dynamonix/profiles.json. It uses ~/.config/dynamonix/profiles.json when the environment gives no absolute $XDG_CONFIG_HOME.

A profile records the make, the model and the serial number of each monitor. It does not record the name of the output. A profile therefore finds the same monitors after you move a cable to a different connector.

The apply command obeys the same safety rules as the interface. It examines the layout before it sends the layout. The command shows no interface, so it works in a niri key binding, in a script, and in a hotplug rule. The exit status tells a script what occurred:

Status Condition
0 The compositor has the layout of the profile.
1 The program stopped with an error.
2 The program or the compositor did not accept the layout.

Autoswitch #

You connect a monitor or you remove a monitor while the interface runs. The program searches the profiles for the new group of monitors. When a profile matches, the program shows the name of the profile, and you press Enter to send it. The program sends nothing before that.

Start the program with --auto to send the matching profile with no question. A group with no profile changes nothing.

The safety rules hold in both conditions. The program does not send a layout with an error. It reports the error instead.

Keys #

Key Operation
← ↑ ↓ → or h j k l Move the selected output 48 pixels. The output aligns with the outputs near it.
Shift and an arrow key Move the output one pixel. The output does not align.
Tab Move the attention between the map and the list.
m / M Select the next mode or the previous mode.
+ / - Make the scale factor larger or smaller.
r / R Turn the output 90 degrees.
e Turn the output on or off.
v Turn the variable refresh rate on or off.
a Put all the outputs in one row.
c Put all the outputs in one column.
n Move the arrangement to the origin.
u Remove the last change.
Ctrl and r Return the change that the last u removed.
U Remove all your changes. A u after it returns them.
Enter Send the changes to the compositor.
q or Esc Stop the program.

Pointer #

Press an output in the map and move the pointer. The output follows the pointer and aligns with the outputs near it, with the same rules as the keys. Hold Shift to move with no alignment. The movement of one hold makes one change, and one u removes it. A press outside of every output moves nothing.

Safety #

The program examines an arrangement before it sends the arrangement. It stops two conditions:

  • The program does not turn off the last output. A computer with no output is difficult to use.
  • The program does not send a mode that the monitor does not have.

The program gives a warning for two other conditions. It does not stop them:

  • Two outputs cover the same area.
  • The outputs make more than one group, with a space between the groups.

Behavior of the compositor #

The program measured this behavior on niri 26.04:

  • The compositor makes the logical size from the physical size. It divides the physical size by the scale factor. It then removes the fraction.
  • The compositor accepts a scale factor between 0.1 and 10.0. It changes a value that is outside of these limits. It gives no error.
  • The compositor accepts a change for a monitor that is not connected. It keeps the change. It applies the change when the monitor connects.
  • The compositor has no event for a change of the outputs. A program that wants to know about a new monitor must ask the compositor again. The program therefore asks the compositor at a regular time. It also asks immediately after an other event.

Documentation #

The wiki holds the documents of the project:

License #

Refer to the LICENSE file at the root of the repository.