diff --git a/dotfiles/config/hypr/hyprland.conf b/dotfiles/config/hypr/hyprland.conf index 524b907a..a493ba0a 100644 --- a/dotfiles/config/hypr/hyprland.conf +++ b/dotfiles/config/hypr/hyprland.conf @@ -525,6 +525,7 @@ bind = $hyper, R, exec, rofi-systemd bind = $hyper, slash, exec, toggle_taffybar bind = $hyper, 9, exec, start_synergy.sh bind = $hyper, I, exec, rofi_select_input.hs +bind = $hyper, backslash, exec, /home/imalison/dotfiles/dotfiles/lib/functions/monitor_input toggle bind = $hyper, O, exec, rofi_paswitch bind = $hyper, W, exec, rofi_wallpaper.sh bind = $hyper, Y, exec, rofi_agentic_skill diff --git a/dotfiles/lib/functions/monitor_input b/dotfiles/lib/functions/monitor_input new file mode 100755 index 00000000..17e7539e --- /dev/null +++ b/dotfiles/lib/functions/monitor_input @@ -0,0 +1,93 @@ +#!/usr/bin/env zsh + +function monitor_input_usage { + cat <<'EOF' +Usage: + monitor_input [toggle|dp|usb-c|hdmi1|hdmi2] + +Switches the current DDC/CI monitor input. On the MSI MPG341CX OLED: + dp -> DisplayPort + usb-c -> USB-C / Type-C + toggle -> DisplayPort <-> USB-C + +Environment: + MONITOR_INPUT_DDC_DISPLAY ddcutil display number, default: 1 +EOF +} + +function monitor_input_notify { + local message="$1" + if command -v notify-send >/dev/null 2>&1; then + notify-send "Monitor input" "$message" + fi +} + +function monitor_input_current { + local display="$1" + ddcutil --display "$display" getvcp 60 --terse 2>/dev/null \ + | grep -Eo 'x[0-9a-fA-F]+' \ + | tail -1 +} + +function monitor_input { + local target="${1:-toggle}" + local display="${MONITOR_INPUT_DDC_DISPLAY:-1}" + local value label current + + if [[ "$target" == "-h" || "$target" == "--help" ]]; then + monitor_input_usage + return 0 + fi + + if ! command -v ddcutil >/dev/null 2>&1; then + echo "monitor_input: ddcutil is not available on PATH" >&2 + monitor_input_notify "ddcutil is not available" + return 1 + fi + + case "${target:l}" in + toggle) + current="$(monitor_input_current "$display")" + case "${current:l}" in + x0f) + target="dp" + ;; + x10) + target="usb-c" + ;; + *) + echo "monitor_input: current input is '${current:-unknown}', defaulting to DisplayPort" >&2 + target="dp" + ;; + esac + ;; + esac + + case "${target:l}" in + dp|displayport|display-port) + value="0x10" + label="DisplayPort" + ;; + usb-c|usbc|type-c|typec) + value="0x0f" + label="USB-C" + ;; + hdmi1|hdmi-1) + value="0x11" + label="HDMI 1" + ;; + hdmi2|hdmi-2) + value="0x12" + label="HDMI 2" + ;; + *) + monitor_input_usage >&2 + return 2 + ;; + esac + + monitor_input_notify "Switching to $label" + ddcutil --display "$display" --noverify setvcp 60 "$value" +} + +monitor_input "$@" diff --git a/nixos/hyprland.nix b/nixos/hyprland.nix index d41bfe6b..2e4aaa9a 100644 --- a/nixos/hyprland.nix +++ b/nixos/hyprland.nix @@ -19,6 +19,12 @@ let # Needed for hyprlock authentication without PAM fallback warnings. security.pam.services.hyprlock = {}; + # DDC/CI monitor control for keyboard-driven input switching. + hardware.i2c = { + enable = true; + group = "video"; + }; + programs.hyprland = { enable = true; # Keep Hyprland and plugins on a matched flake input for ABI compatibility. @@ -147,6 +153,7 @@ let swappy # Screenshot annotation nwg-displays # GUI monitor arrangement mpv # Graphical screensaver payload + ddcutil # Monitor input switching over DDC/CI # For scripts jq