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.
```sh
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:
```sh
nix run git+https://git.devtechnica.com/sreedev/dynamonix
```
Put the program in your profile:
```sh
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:
```nix
{
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:
```nix
environment.systemPackages = [ dynamonix.packages.x86_64-linux.default ];
```
## Use
Start the program in a niri session:
```sh
dynamonix
```
Show the outputs and stop:
```sh
dynamonix list
```
Show the interface with three monitors of a sample. This mode does not need a compositor:
```sh
dynamonix --demo
```
Show the outputs as JSON for a script. The command writes one JSON object and
nothing else:
```sh
dynamonix list --json
```
Save the arrangement of the outputs with a name:
```sh
dynamonix save desk
```
Send the arrangement of a saved profile without the interface:
```sh
dynamonix apply desk
```
Let the program send the profile of a new group of monitors with no question:
```sh
dynamonix --auto
```
Show the profiles that you saved. This command does not need a compositor:
```sh
dynamonix profiles
```
Use a different socket:
```sh
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](https://git.devtechnica.com/sreedev/dynamonix/wiki) holds the
documents of the project:
- [Architecture](https://git.devtechnica.com/sreedev/dynamonix/wiki/Architecture)
- [Tests](https://git.devtechnica.com/sreedev/dynamonix/wiki/Tests)
- [Continuous integration](https://git.devtechnica.com/sreedev/dynamonix/wiki/Continuous-integration)
## License
Refer to the [LICENSE](LICENSE) file at the root of the repository.