Menú TUI para cambiar el modo de pantalla en Hyprland o niri (duplicado / extendido / solo externa / solo interna), tipo el Win+P de Windows. Pensado para laptop + monitor externo por HDMI/DP.
Tres archivos, lógica separada de interfaz y de compositor:
displaymode-hyprland.sh— lógica para Hyprland (config Lua 0.55+), víahyprctl eval.displaymode-niri.sh— lógica para niri, víaniri msg output.displaymode-tui.sh— interfaz visual (gum). Detecta sola qué compositor corre ($HYPRLAND_INSTANCE_SIGNATURE/$NIRI_SOCKET) y llama al script de lógica correspondiente. No tiene lógica propia.
Cualquiera de los dos scripts de lógica se puede usar solo, sin la TUI, directo desde CLI o un keybind.
Con el config Lua nuevo de Hyprland (0.55+) — el que usa archivos .lua en vez de hyprland.conf — el comando clásico para cambiar monitores no funciona:
$ hyprctl keyword monitor "HDMI-A-1,preferred,0x0,1"
keyword can't work with non-legacy parsers. Use eval.
Y herramientas como wdisplays o nwg-displays solo prenden/apagan pantallas, no permiten elegir mirror vs extend vs solo-una como en Windows. displaymode-hyprland.sh usa hyprctl eval + la función Lua hl.monitor{} que sí funciona con el config nuevo.
niri usa su propio IPC (niri msg output <salida> on|off|position|mode|...), sin nada de lo anterior — displaymode-niri.sh es una implementación aparte, no un parche del script de Hyprland.
niri no tiene mirror/duplicado de pantalla a nivel compositor — no existe esa acción en niri msg output --help a la fecha. displaymode-niri.sh lo simula con wl-mirror: lanza una ventana fullscreen sobre la pantalla externa que muestra un screencopy en vivo de la interna. Visualmente es lo mismo que un mirror real, pero es un proceso aparte (no una config de salida) — por eso cualquier otro modo lo mata primero. Necesita wl-mirror instalado; si no está, la TUI esconde esa opción sola.
- Hyprland (config Lua 0.55+) o niri
jqgum(opcional, solo paradisplaymode-tui.sh— la lógica sola no lo necesita)wl-mirror(opcional, solo para el modomirroren niri)
git clone https://github.com/MxguelS/displaymode.git ~/.local/share/displaymode
ln -s ~/.local/share/displaymode/displaymode-hyprland.sh ~/.local/bin/displaymode-hyprland.sh
ln -s ~/.local/share/displaymode/displaymode-niri.sh ~/.local/bin/displaymode-niri.sh
ln -s ~/.local/share/displaymode/displaymode-tui.sh ~/.local/bin/displaymode-tui.sh(o copiá la carpeta a donde quieras, no tiene rutas hardcodeadas — displaymode-tui.sh encuentra a los otros dos solo, por estar en el mismo directorio)
displaymode-hyprland.sh mirror|extend|external|internal # Hyprland, directo
displaymode-niri.sh mirror|extend|external|internal # niri, directo (mirror necesita wl-mirror)
displaymode-hyprland.sh --list # nombres de pantalla detectados
displaymode-hyprland.sh --help
displaymode-tui.sh # menú interactivo, detecta el compositor soloHyprland (config Lua):
hl.bind("SUPER + P", hl.dsp.exec_cmd("kitty --title displaymode -e /ruta/a/displaymode-tui.sh"))niri (config.kdl):
binds {
Mod+P { spawn "kitty" "--title" "displaymode" "-e" "/ruta/a/displaymode-tui.sh"; }
}(cualquier terminal sirve, no tiene que ser kitty)
Pantalla interna = la primera que matchee eDP-* (estándar en laptops). Pantalla externa = la primera pantalla conectada que no sea la interna. Pensado para el caso típico laptop + 1 monitor externo — con 2+ externos toma el primero que encuentre.
hyprctl monitors -j(sinall) oculta las pantallas que están en mirror — hay que usarhyprctl monitors all -jpara detectarlas bien.- En
hl.monitor{}, el campomirrorhay que pasarlo explícito como"none"para salir de un mirror previo; si lo omitís, Hyprland no lo limpia solo. - Al apagar una pantalla y prender otra muy rápido seguido, el driver puede pisarse asignando CRTCs y dejar ambas en negro por un momento (visto en hardware real, log de
aquamarine:drm: eDP-1 is disabled, releasing crtc 95). Por eso el script siempre prende el destino primero y apaga la otra después, nunca al revés — así nunca hay una ventana de tiempo con cero pantallas activas. Si aun así te quedan las pantallas en negro:hyprctl reloaddesde otra terminal (o TTY, Ctrl+Alt+F2) las recupera. - Si usás submaps Lua para binds (
hl.define_submap), para salir hay que hacerhl.dsp.submap("reset")—"reset"es la palabra especial, no"default"ni cualquier otro nombre. Usar el nombre equivocado deja todos los binds fuera del submap sin responder, sin ningún error visible.
- Mismo criterio de seguridad que en Hyprland: prender destino antes de apagar la otra, nunca al revés.
- El "mirror" es
wl-mirrorcorriendo aparte, no una config de compositor — por esoextend/external/internalsiempre lo matan primero (pkill -x wl-mirror), para no dejarlo colgado mostrando una pantalla que ya cambió de layout. displaymode-niri.sh external/internal(que apagan una pantalla) no se probaron con hardware real todavía — se armaron contra la sintaxis documentada deniri msg output --help, pero no hubo un segundo monitor conectado a mano para el test en vivo. Si algo sale raro, avisá / abrí un issue.
Uso libre, sin garantías — probado en un setup específico (Arch + Hyprland/niri + Caelestia/iNiR), puede necesitar ajustes en otros setups.