Omahub
← All plugins
A

Monitor Desk

by amoltyagi

Manage resolution, refresh rate, scale, rotation, power and desktop layout, with independent Night Light and verified Keep/Revert.

Security review

No obvious issues detected

Deterministic scan — not a security guarantee

None
Risk level
None
Analyzed commit
f962e21
Scanned
1 week ago

No potentially dangerous behavior detected in the analyzed commit.

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

Low
AI risk level
Low
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
f962e21
Reviewed
1 week ago

Manual review found no malicious, obfuscated, or credential-harvesting code; the backend and Python helper use guarded file I/O, argv-based command execution, and fail-closed validation. The plugin's real capabilities (changing resolution, rotation, scale, power, and writing generated Hyprland Lua) are expected for a monitor manager and are protected by 20-second Keep/Revert trials, rollback, and last-display safeguards. The deterministic scan found nothing, and I found no reason to disagree.

  • The plugin intentionally modifies monitor configuration and generated Hyprland layout files, which could briefly disrupt the display if a setting fails, but changes are user-initiated and protected by rollback/trial safeguards.
  • Disabling monitors or changing modes is a powerful system-level operation, but the code protects the last usable display and rejects overlapping/invalid layouts.
  • The README's install instructions are standard `omarchy plugin add` commands, not a `curl | sh` or other arbitrary-code execution path.
  • The included Python Night Light service talks directly to the Wayland gamma-control protocol and persists preferences, but with symlink-safe atomic writes and strict input validation.
How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/amoltyagi/omarchy-monitor-switcher --enable
System #Hyprland #bar #quickshell

Monitor Desk

Your displays. One place.

Resolution, refresh rate, scale, rotation, power and layout for every monitor, in one panel for Omarchy. Every change is a verified before you keep it, and reverts by itself if you don't.

Formerly Monitor Switcher. Same plugin ID (case.monitor-switcher), commands, settings and shortcuts, so updating is seamless.

Install · Rotate a display · Use the panel · Keyboard · Command line · What's new in 3.2

<a href="preview.png"><img src="preview.png" alt="Monitor Desk open on an Omarchy desktop: MSI and LG in landscape, the Acer in portrait, with resolution, refresh rate, scale, rotation, power, Night Light, brightness and text size." width="1000"></a>

  • Click to edit. Resolution, refresh and scale sit right on each display.
  • Turn it portrait. New in 3.2: rotate from the button on each monitor's chin.
  • Drag to arrange. Snap screens together to match your desk.
  • Try it first. Display changes are verified and revert in 20 seconds unless you keep it.
  • Night Light per monitor. Warm one screen and leave another in daylight.
  • Switch it on. A power toggle under every monitor; the last one is protected.
  • Recover your desk. Restore the last verified layout for the monitors you have plugged in.

Settings persist across reloads and reboots. Colors follow your theme.

Portrait, in one click.

Click the small monitor button on a display's chin (or press O), pick an orientation, and watch the card turn. Screens next to it slide over so your pointer still crosses cleanly. Keep it, or do nothing and it reverts in 20 seconds.

<a href="preview-rotation.png"><img src="preview-rotation.png" alt="Rotation menu opened from the Acer's chin button on an Omarchy desktop: Landscape standard, Portrait turned right (checked), Landscape upside down and Portrait turned left." width="1000"></a>

How rotation works →

A place for every display.

Drag portrait and landscape screens into position, or choose left, right, above or below. Preview the layout, apply it, and keep it if it feels right.

<a href="preview-detail.png"><img src="preview-detail.png" alt="Arrangement view on an Omarchy desktop: MSI, LG and the portrait Acer as tiles in desktop space, with placement buttons and Apply arrangement." width="1000"></a>

Night Light for each monitor.

Each monitor keeps its own saved temperature. Its menu opens directly from its toggle, while the monitor details stay visible. See the Night Light menu →

Full-size captures: Main panel · Rotation · Arrangement · Night Light

This is an unofficial fork of Omarchy's omarchy.monitor widget, originally vendored from Omarchy 4.0.0 under MIT. It is not affiliated with or supported by Omarchy. Please report plugin issues to this repository. See UPSTREAM.md for attribution and maintenance notes.

Install

Requires Omarchy 4.x with its Lua-based Hyprland configuration and Quickshell. The backend uses Bash, jq, coreutils and util-linux tools supplied by Omarchy. Per-monitor Night Light also needs Python 3 and compositor support for wlr-gamma-control-v1 and wl_output version 4. No Python packages are required.

omarchy plugin add https://github.com/amoltyagi/omarchy-monitor-switcher --enable
omarchy plugin disable omarchy.monitor

The second command avoids duplicate display widgets; it only changes your bar layout. Restore the built-in widget with omarchy plugin enable omarchy.monitor. The two plugins have separate IPC targets, but the built-in widget's runtime-only changes can conflict with this plugin's persisted settings.

On first use, the plugin adopts connected monitors and their current geometry and scale. Preferred-mode entries resolve to an advertised mode matching that geometry, preserving the running refresh rate when possible. Use the Hz chip to choose a different advertised rate.

Use the panel

Click the monitor icon in the bar to open the panel.

Control Behavior
Monitor screen Focus that desktop; editing a setting never changes its target
On/Off switch Persistently toggle that monitor; the last usable screen is protected
Rotation button (chin) Choose landscape, portrait (either way) or upside down, then Keep within 20 seconds
Resolution / Hz / scale chip Click, choose a supported value, then Keep within 20 seconds
Arrange… Preview positions by dragging or using placement buttons, then Apply
Night Light Open a menu anchored to its toggle (above when space is tight); choose Off or 2500–5000 K while monitor details remain visible
Restore working layout… Under More shortcuts; preview the last verified layout for this connected monitor combination
Brightness Adjust the focused monitor when brightness control is available
Text size Adjust shell/GTK text size through Omarchy's text-size command
More shortcuts Expand optional keyboard guidance; ? also toggles it
Wheel/touchpad scroll Scroll the panel without changing values

The gallery fits the host screen in logical coordinates and wraps to fewer columns when its controls would otherwise be crowded. Subtle casing and screen gradients provide depth while preserving readable labels and focus borders.

When a disabled monitor returns to an occupied saved position in a horizontal row, the switcher reopens its slot and shifts monitors to its right. For example, after joining 1 and 3, enabling 2 restores the 1–2–3 row. For other arrangements, it uses the nearest free adjoining edge. Failed changes restore the previous configuration; explicit overlapping arrangement edits are still rejected.

Each chip targets its own monitor, even when another desktop has focus. Brightness targets the focused monitor. Hz describes the compositor's display mode, not measured application FPS. Off/modeless screens identify saved settings explicitly instead of presenting them as live values.

During a display trial, other layout changes are blocked. Keep saves the choice; Revert restores the previous configuration immediately. Failed rollback reloads are retried. If a watchdog is interrupted, the next backend invocation recovers its expired trial.

Confirmation state stays in sync across panels. Selecting the current value does not start a trial, and focusing another monitor remains available. A normal timeout or an already-finished preview is not shown as a red error.

Keyboard Controls

Key Action
Up/Down or k/j Select displays, controls or picker options
Left/Right or h/l Walk selections or adjust brightness/text size
Enter/Space Focus the selected display, choose an option, or activate Keep/Revert
m / r / s Open resolution / refresh / scale for the selected display
t Open Night Light for the selected display
o Open rotation for the selected display
p Toggle the selected display
a Open/close Arrange
? Expand/collapse More shortcuts
Escape Close the picker, return from Arrange, then close the panel
Tab/Shift+Tab Move between shell panels

In Arrange, arrows place the selected display relative to the reference display; n selects the next display and Enter applies the preview. All monitor numbers match config order, regardless of their current physical position.

To toggle displays without opening the panel, add bindings to ~/.config/hypr/bindings.lua. Numbers follow config order, not connector names:

o.bind("SUPER + SHIFT + CTRL + 1", "Toggle display 1",
  os.getenv("HOME") .. "/.config/omarchy/plugins/case.monitor-switcher/bin/monitor-switcher toggle 1")
o.bind("SUPER + SHIFT + CTRL + 2", "Toggle display 2",
  os.getenv("HOME") .. "/.config/omarchy/plugins/case.monitor-switcher/bin/monitor-switcher toggle 2")
o.bind("SUPER + SHIFT + CTRL + 3", "Toggle display 3",
  os.getenv("HOME") .. "/.config/omarchy/plugins/case.monitor-switcher/bin/monitor-switcher toggle 3")

Descriptions also appear in Omarchy's SUPER+K keybindings sheet. Adjust the plugin path if you installed it under a different ID.

Command line

The plugin ID and command keep their original monitor-switcher names, so existing keybinds, scripts and configs continue to work.

The executable lives inside the plugin; installation does not add it to PATH. Use its full path, or enable the short command for the current shell:

export PATH="$HOME/.config/omarchy/plugins/case.monitor-switcher/bin:$PATH"

Commands accept an output name (DP-3), case-insensitive alias (MSI), or 1-based config-order number (1):

monitor-switcher state                   # monitor table and overlap warnings
monitor-switcher state --json            # metadata, modes and pending trial
monitor-switcher toggle 1
monitor-switcher enable MSI
monitor-switcher disable DP-2
monitor-switcher scale MSI 1.875          # round to a Hyprland-compatible scale
monitor-switcher scale-trial MSI 1.875    # same setting with Keep/Revert
monitor-switcher focus LG                # focus without changing monitor power
monitor-switcher mode MSI 2560x1440@239.85 # try an exact advertised mode
monitor-switcher refresh MSI 240         # begin trial; prints confirmation token
monitor-switcher rotate Acer 90          # rotation trial: 0, 90, 180, 270, next, prev
monitor-switcher confirm <token>         # keep the trial mode
monitor-switcher revert <token>          # restore previous settings
monitor-switcher apply                   # apply the saved layout
monitor-switcher recover                 # try the last verified layout for this monitor set

Refresh requests must match an advertised rate, e.g. 74.98 rather than 75 when the monitor advertises 74.98. Only connected, active monitors can start a refresh trial.

Rotate a display

Click the small monitor button on a display's chin (or press o) and choose an orientation. The card reshapes, the screen turns, and Keep saves it. If a sideways pointer makes that awkward, do nothing: it reverts after 20 seconds. Labels describe which way you physically turn the monitor, so choose Portrait · turned right after turning it clockwise.

A turned screen changes its desktop footprint, so the switcher treats it like a pivot stand: in a row the screen keeps its left edge and centres vertically, and displays to its right slide over to stay touching (columns work the same way, downward). In other layouts it pivots about its centre, or takes the nearest free touching edge. If no valid layout exists, rotation is refused before anything changes; use Arrange first. Half turns (0°↔180°) move nothing.

monitor-switcher rotate Acer 90      # 0, 90, 180, 270; next/prev turn by 90°
monitor-switcher rotate 3 t5         # raw Hyprland transform 0–7 (4–7 mirror)

Degrees keep any mirroring configured in transform; the panel offers only the four unmirrored rotations. Touchscreen and tablet input mapping is not rotated by this plugin.

Arrange displays

Click Arrange…, turn on at least two displays, and drag their rectangles to match your desk. Edges snap together. Alternatively select a display, choose a reference, and use Left/Right/Above/Below. Align in a row previews the saved config order. Apply arrangement starts a verified 20-second trial.

The map shows logical desktop space (resolution divided by scaling), so its proportions can differ from the physical-size illustrations in the overview. Overlaps and disconnected islands are rejected to keep pointer travel usable.

monitor-switcher plan                    # compute layout without applying it
monitor-switcher move LG 2048x0          # pin an origin in logical pixels
monitor-switcher move LG above MSI       # left-of, right-of, above or below
monitor-switcher swap MSI LG             # swap pack-order entries; clear their pins
monitor-switcher pack                    # clear all pins; pack left-to-right
monitor-switcher arrange '[{"output":"DP-3","x":0,"y":0},{"output":"DP-2","x":2048,"y":0}]'

Geometry comes from configured resolution, scale and rotation, not a temporary live fallback. plan does not apply a layout, but may update config metadata. Use this plugin or another monitor-layout manager, not both: tools writing rules for the same output can overwrite each other.

Night Light and recovery

Night Light is independent for each active monitor. Click its toggle to open the temperature menu; the other displays keep their own settings. The menu opens above the toggle when there is not enough room below. Off restores that output's original gamma; the cool illustration means no plugin warming, not an additional blue filter. Preferences follow the monitor identity when available and reapply on reconnect or shell restart. An unavailable or conflicting gamma controller produces a visible error. A separate global Night Light, such as hyprsunset, still affects every monitor and can compound this plugin's warming.

Successful applies and confirmed trials save the latest verified layout for up to eight connected monitor combinations. Restore working layout… starts a normal Keep/Revert trial; plugging in monitors does not automatically switch profiles. A failed trial never replaces the verified snapshot. Rollback keeps its backup until the surviving monitors match the previous working state. Power changes continue even if disabling a monitor destroys its panel.

Configuration

Edit ~/.config/monitor-switcher/config.json, then run monitor-switcher apply. Array order determines packing and shortcut numbers:

[
  { "output": "DP-3", "alias": "MSI", "scale": 1.875, "mode": "3840x2160@240" },
  { "output": "DP-2", "alias": "LG", "scale": 1.25, "mode": "3840x1600@74.98" },
  { "output": "DP-1", "alias": "Acer", "scale": 1.875, "transform": 0 }
]
Field Default Meaning
output Required Connector name from hyprctl monitors all
alias Monitor model Display name and optional CLI identifier
scale Current scale Per-monitor scale factor
mode preferred Hyprland mode, such as 3840x2160@240
transform 0 Rotation/reflection; 1 is 90°, 2 is 180°, 3 is 270°; 4–7 add mirroring. Set with rotate or the panel
position Automatic packing Optional pinned origin, such as 2048x0
mode_w, mode_h Managed Geometry snapshots for preferred mode; do not edit manually
identity Managed EDID make/model/serial used for unambiguous connector changes

Each connector must appear only once. The backend reports duplicate entries instead of generating competing rules. Temporary FALLBACK outputs are not adopted. A modeless output remains visible with a recovery status; it does not hide the other monitors.

Keep a wildcard fallback in ~/.config/hypr/monitors.lua for unmanaged outputs, rather than competing explicit rules:

hl.monitor({ output = "", mode = "preferred", position = "auto", scale = 1 })
State file Purpose
~/.config/monitor-switcher/night-light.json Per-monitor Night Light preferences
~/.local/state/monitor-switcher/working-layouts.json Last verified layout for up to eight monitor combinations
~/.config/monitor-switcher/config.json Monitor order and settings
~/.local/state/monitor-switcher/state.json Disabled outputs
~/.local/state/monitor-switcher/refresh-pending.json Temporary trial and rollback backup
~/.local/state/omarchy/toggles/hypr/monitor-switcher.lua Generated rules sourced on reload

File access is guarded and bounded; writes are staged and atomically renamed. Layout commands are serialized. Refresh trials back up both config and generated rules, validate reload results, and verify the active mode before confirmation.

Update

For a regular Git-managed installation:

omarchy plugin update case.monitor-switcher

The updater shows the diff, fast-forwards after confirmation, and validates locally. Marketplace verification is a separate exact-commit snapshot; it does not pin an installed plugin to that commit.

Maintainers: bump manifest.json, update CHANGELOG.md and root preview.png, then push. The marketplace's daily refresh can pick up new metadata and previews. To verify the new snapshot, submit the Verify or update a listed plugin form with the full new HEAD SHA and action Verify and publish a newer upstream commit. Passing automation still requires marketplace-maintainer approval. See the marketplace update guide.

Development

For live development, link the repository into the user plugin directory:

ln -s ~/Work/omarchy-monitor-switcher ~/.config/omarchy/plugins/case.monitor-switcher
omarchy plugin validate ~/Work/omarchy-monitor-switcher
omarchy restart shell

Validate using the real repository path, not the installed symlink. Backend edits apply on the next invocation. Restart the shell if QML changes do not appear. Never modify packaged Omarchy files.

node --test tests/*.test.js
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -p 'test_*.py'
bash -n bin/monitor-switcher bin/monitor-action
QT_QPA_PLATFORM=offscreen QT_QUICK_BACKEND=software QT_QPA_PLATFORMTHEME= \
  /usr/lib/qt6/bin/qmltestrunner -input tests/ui -import tests/ui/imports

Backend tests use temporary HOME directories and a fail-closed compositor stub, not live displays. They cover mode selection, fractional rates, pending trials, watchdog recovery, rollback, concurrency, unsafe paths and layout guards. Model tests cover refresh matching, gallery proportions, exact scale stops and arrangement snapping/connectivity. Offscreen QML tests exercise the actual interactive components with isolated theme tokens: dragging, per-card targeting, wheel behavior and pending power switches. They never call a live monitor backend.

Before shipping, inspect the live gallery and keyboard navigation, confirm that scrolling cannot edit values, and check that toggles survive a reload. IPC state: omarchy-shell case.monitor-switcher state. See UPSTREAM.md before syncing changes from Omarchy's widget.

Uninstall

Confirm or revert any pending refresh trial before removing the plugin.

omarchy plugin enable omarchy.monitor
omarchy plugin remove case.monitor-switcher
rm -f ~/.local/state/omarchy/toggles/hypr/monitor-switcher.lua
hyprctl reload

Restore any explicit monitor rules you need in ~/.config/hypr/monitors.lua.

License

MIT. See LICENSE for plugin code and LICENSE.upstream for the Omarchy-derived widget code.