From c9f2d4eccf67d2d010b3fbc41e7fef6e84368531 Mon Sep 17 00:00:00 2001 From: Cookiez Date: Fri, 12 Jun 2026 10:56:57 +0200 Subject: [PATCH] Add MangoWM configuration and scripts for window management on scrolling layout --- flake.nix | 6 ++ mango/default.nix | 20 ++++ mango/home.nix | 208 ++++++++++++++++++++++++++++++++++++ mango/scripts/layout-cmd.sh | 21 ++++ mango/scripts/resize.sh | 130 ++++++++++++++++++++++ modules/default.nix | 1 + 6 files changed, 386 insertions(+) create mode 100644 mango/default.nix create mode 100644 mango/home.nix create mode 100644 mango/scripts/layout-cmd.sh create mode 100644 mango/scripts/resize.sh diff --git a/flake.nix b/flake.nix index 125600b..6b72521 100644 --- a/flake.nix +++ b/flake.nix @@ -30,6 +30,11 @@ inputs.home-manager.follows = "home-manager"; }; + mangowm = { + url = "github:mangowm/mango"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + stylix = { url = "github:nix-community/stylix/master"; #Had to use branch or it would not build corrently inputs.nixpkgs.follows = "nixpkgs"; @@ -79,6 +84,7 @@ home-manager, plasma-manager, noctalia, + mangowm, nixos-hardware, aagl, stylix, diff --git a/mango/default.nix b/mango/default.nix new file mode 100644 index 0000000..e6b2314 --- /dev/null +++ b/mango/default.nix @@ -0,0 +1,20 @@ +{ + inputs, + pkgs, + ... +}: { + imports = [ + inputs.mangowm.nixosModules.mango + ../modules/hyprlock + ]; + + environment.systemPackages = with pkgs; [ + libnotify #Needed to send notifications myself (Like when reloading the config) + ]; + + home-manager.sharedModules = [ + ./home.nix + ]; + + programs.mango.enable = true; +} diff --git a/mango/home.nix b/mango/home.nix new file mode 100644 index 0000000..5170578 --- /dev/null +++ b/mango/home.nix @@ -0,0 +1,208 @@ +{ + pkgs, + inputs, + lib, + ... +}: let + # ── External scripts ── + resizeScript = pkgs.writeShellScriptBin "resize.sh" (builtins.readFile ./scripts/resize.sh); + layoutCmdScript = pkgs.writeShellScriptBin "layout-cmd.sh" (builtins.readFile ./scripts/layout-cmd.sh); + + # Helper to get the bin path + resizePath = "${resizeScript}/bin/resize.sh"; + layoutCmdPath = "${layoutCmdScript}/bin/layout-cmd.sh"; + + tagBinds = lib.concatLists ( + builtins.genList (i: let + n = toString (i + 1); + in [ + "SUPER,${n},view,${n}" + "SUPER+CTRL,${n},tag,${n}" + ]) + 9 + ); + + # All key bindings + defaultBinds = + # --- Common/Session --- + [ + "SUPER+SHIFT,E,quit" + #"SUPER+CTRL,R,reload_config" + "SUPER+CTRL,R,spawn_shell,mmsg dispatch reload_config && notify-send -u low -t 500 \"MangoWM\" \"Reloaded ✓\"" + ] + # --- Screenshot --- + ++ [ + "SUPER+SHIFT,S,spawn,noctalia msg screenshot-region" + ] + # --- Launchers --- + ++ [ + "SUPER,T,spawn,kitty" # Mod+T → terminal + "SUPER,D,spawn,noctalia msg panel-toggle launcher" # Mod+D → launcher + #"SUPER+SHIFT,L,spawn,noctalia msg session lock" # Super+L → lock + "SUPER+SHIFT,L,spawn,hyprlock" + ] + # --- Media keys --- + ++ [ + "NONE,XF86AudioRaiseVolume,spawn,noctalia msg volume-up" + "NONE,XF86AudioLowerVolume,spawn,noctalia msg volume-down" + "NONE,XF86AudioMute,spawn,noctalia msg volume-mute" + "NONE,XF86AudioMicMute,spawn,noctalia msg mic-mute" + "NONE,XF86AudioPlay,spawn,playerctl play-pause" + "NONE,XF86AudioStop,spawn,playerctl stop" + "NONE,XF86AudioPrev,spawn,playerctl previous" + "NONE,XF86AudioNext,spawn,playerctl next" + ] + # --- Brightness --- + ++ [ + "NONE,XF86MonBrightnessUp,spawn,noctalia msg brightness-up" + "NONE,XF86MonBrightnessDown,spawn,noctalia msg brightness-down" + ] + # --- Focus (directional) --- + ++ [ + "SUPER,Left,focusdir,left" + "SUPER,Down,focusdir,down" + "SUPER,Up,focusdir,up" + "SUPER,Right,focusdir,right" + # vim bindings + "SUPER,H,focusdir,left" + "SUPER,J,focusdir,down" + "SUPER,K,focusdir,up" + "SUPER,L,focusdir,right" + ] + # --- Move windows (swap with neighbor) --- + ++ [ + "SUPER+CTRL,Left,exchange_client,left" + "SUPER+CTRL,Down,exchange_client,down" + "SUPER+CTRL,Up,exchange_client,up" + "SUPER+CTRL,Right,exchange_client,right" + # vim bindings + "SUPER+CTRL,H,exchange_client,left" + "SUPER+CTRL,J,exchange_client,down" + "SUPER+CTRL,K,exchange_client,up" + "SUPER+CTRL,L,exchange_client,right" + ] + # --- Tags (workspaces) 1-9 --- + ++ tagBinds # Generated list of binds for tags 1-9 + ++ [ + "SUPER,U,viewtoleft_have_client" + "SUPER,I,viewtoright_have_client" + "SUPER+SHIFT,U,tagtoleft" + "SUPER+SHIFT,I,tagtoright" + ] + # --- Close window --- + ++ [ + "SUPER,Q,killclient" + ] + # --- Layout / sizing --- + ++ [ + "SUPER+SHIFT,R,switch_layout" # Cycle layouts + "SUPER,F,spawn_shell,${layoutCmdPath} 'S:${resizePath} fullscreen' 'mmsg dispatch togglemaximizescreen'" + "SUPER+SHIFT,F,togglefullscreen" + "SUPER,C,centerwin" # Center floating window + + # For Scroller + "SUPER,ssharp,spawn_shell,${layoutCmdPath} 'S:${resizePath} down' 'notify-send \"Title\" \"Down\"'" + #"SUPER,ssharp,spawn_shell,${resizePath} down" + "SUPER,dead_acute,spawn_shell,${layoutCmdPath} 'S:${resizePath} up' 'notify-send \"Title\" \"Up\"'" + #"SUPER,dead_acute,spawn_shell,${resizePath} up" + ] + # --- Floating --- + ++ [ + "SUPER,V,togglefloating" + ] + # --- Power off monitors --- + ++ [ + "SUPER+SHIFT,P,spawn,mmsg dispatch toggle_monitor,eDP-1" + ]; + + # Extra config for settings that don't have structured Nix attrs + extraConf = '' + # ── Input ── + xkb_rules_layout=de + numlockon=1 + mouse_accel_profile=1 + mouse_accel_speed=-0.4 + + # ── Appearance / Theming ── + focuscolor=0xffc87faa + bordercolor=0x505050ff + urgentcolor=0x9b0000ff + borderpx=2 + + # ── Blur ── + blur=1 + + blur_params_num_passes=2 + blur_params_radius=5 + blur_params_noise=0.02 + blur_params_brightness=1 + blur_params_contrast=1 + blur_params_saturation=1 + + # window-rule geometry-corner-radius 12 + border_radius=12 + + # Opacity + focused_opacity=1.0 + unfocused_opacity=0.95 + + # ── Layout ── + gappih=10 + gappiv=10 + gappoh=10 + gappov=10 + + # Master-stack settings (tile layout) + default_mfact=0.5 + default_nmaster=1 + new_is_master=1 + + # Scroller + scroller_default_proportion=0.5 + scroller_focus_center=0 + scroller_default_proportion_single=1.0 + + # Layout cycling order (Mod+R) + circle_layout=scroller,tile,center_tile,grid + + # ── Monitor ── + monitorrule=name:^eDP-1$,width:2880,height:1920,refresh:120,x:0,y:0,scale:2 + + # ── Autostart ── + exec-once=noctalia + exec-once=lxpolkit + exec-once = ${pkgs.kdePackages.kwallet-pam}/libexec/pam_kwallet_init + + exec-once="rm -rf ''${XDG_RUNTIME_DIR:-/tmp}/mango-resize" + + tagrule=id:1,layout_name:scroller + tagrule=id:2,layout_name:scroller + tagrule=id:3,layout_name:scroller + tagrule=id:4,layout_name:scroller + tagrule=id:5,layout_name:scroller + tagrule=id:6,layout_name:scroller + tagrule=id:7,layout_name:scroller + tagrule=id:8,layout_name:scroller + tagrule=id:9,layout_name:scroller + ''; +in { + imports = [ + inputs.mangowm.hmModules.mango + ../modules/noctalia/home.nix + ]; + + wayland.windowManager.mango = { + enable = true; + + # Additional autostart (most handled in extraConfig exec-once) + autostart_sh = ""; + + settings = { + # Key bindings (flat list; keymode for submaps below if needed) + bind = defaultBinds; + }; + + # Raw config lines for options without structured Nix attrs + extraConfig = extraConf; + }; +} diff --git a/mango/scripts/layout-cmd.sh b/mango/scripts/layout-cmd.sh new file mode 100644 index 0000000..cdfede4 --- /dev/null +++ b/mango/scripts/layout-cmd.sh @@ -0,0 +1,21 @@ +#!/bin/sh +set -eu + +tag_json=$(mmsg get all-tags) +symbol=$(printf '%s' "$tag_json" | jq -r ' + .all_tags[0].tags[] | select(.is_active == true) | .layout +' | head -1) + +arg1="$1" +target_layout="${arg1%%:*}" + +for arg in "${@:1:$#-1}"; do + layout="${arg%%:*}" + cmd="${arg#*:}" + if [ "$symbol" = "$layout" ]; then + eval "$cmd" + exit 0 + fi +done + +eval "${@: -1}" diff --git a/mango/scripts/resize.sh b/mango/scripts/resize.sh new file mode 100644 index 0000000..66a69e6 --- /dev/null +++ b/mango/scripts/resize.sh @@ -0,0 +1,130 @@ +#!/bin/sh +set -eu + +# 1️⃣ Presets +PRESETS=" 0.15 0.30 0.40 0.50 0.60 0.70 0.80 0.90 1.00" + +# Per-window saved-proportion directory +PROPORTION_DIR="${XDG_RUNTIME_DIR:-/tmp}/mango-resize" + +# 2️⃣ Helper: abort with a clear message if a required binary is missing +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { + printf "❌ Required command '%s' not found in PATH. Install it first.\n" "$1" >&2 + exit 127 + } +} +need_cmd mmsg +need_cmd jq +need_cmd awk +need_cmd wc +need_cmd sed + +# 3️⃣ Get the *currently focused* client. +client_json=$(mmsg get focusing-client || true) + +# 3a️⃣ Extract the window's unique id (used as the per-window state key) +win_id=$(printf '%s' "$client_json" | jq -r '.id // empty' || true) +if [ -z "$win_id" ]; then + printf "⚠️ Could not determine window id – aborting.\n" >&2 + exit 1 +fi + +# Per-window state file +PROPORTION_FILE="$PROPORTION_DIR/$win_id" + +# 3b️⃣ Extract current client width +current=$(printf '%s' "$client_json" | jq -r '.width // empty' || true) + +if [ -z "$current" ]; then + printf "⚠️ No focusing client reported – using monitor width as current size.\n" + current=$(mmsg get all-monitors | jq -r ' + if type == "array" then + (map(select(.active == true))[0].width) + else + (.monitors | map(select(.active == true))[0].width) + end + ' || true) +fi + +# 4️⃣ Get the *usable* work‑area width +usable_width=$(mmsg get workarea 2>/dev/null || true) +if [ -n "$usable_width" ]; then + usable_width=$(printf '%s' "$usable_width" | jq -r '.width // empty') +fi +if [ -z "$usable_width" ]; then + usable_width=$(mmsg get all-monitors | jq -r ' + if type == "array" then + (map(select(.active == true))[0].width) + else + (.monitors | map(select(.active == true))[0].width) + end + ') +fi + +# 5️⃣ Compute the proportion +approx=$(awk -v cw="$current" -v uw="$usable_width" ' + BEGIN { + if (uw <= 0) { print "0.50"; exit } + printf "%.4f\n", cw / uw + }') + +# 6️⃣ Find the nearest preset index +nearest_index=$(awk -v target="$approx" ' + BEGIN { + split("'"$PRESETS"'", a, " "); + best_i = 1; + best_d = 999; + for (i = 1; i <= length(a); i++) { + d = a[i] - target; + if (d < 0) d = -d; + if (d < best_d) { + best_d = d; + best_i = i; + } + } + print best_i; + }') + +# 7️⃣ Apply the direction argument +case "${1:-}" in + up) new_index=$((nearest_index + 1)) ;; + down) new_index=$((nearest_index - 1)) ;; + fullscreen) + # Check if currently fullscreen (approx >= 0.95 → treat as fullscreen) + is_fullscreen=$(awk -v a="$approx" 'BEGIN { print (a >= 0.95) ? "1" : "0" }') + if [ "$is_fullscreen" = "1" ]; then + # Currently fullscreen → restore this window's saved proportion + if [ -f "$PROPORTION_FILE" ]; then + saved=$(cat "$PROPORTION_FILE") + mmsg dispatch "set_proportion,$saved" + printf "✅ [win %s] Restored proportion to %s (fullscreen toggle off)\n" "$win_id" "$saved" + rm -f "$PROPORTION_FILE" + else + printf "⚠️ [win %s] No saved proportion – defaulting to 0.50\n" "$win_id" >&2 + mmsg dispatch "set_proportion,0.50" + fi + else + # Not fullscreen → save this window's proportion & go fullscreen + saved=$(printf '%s\n' $PRESETS | sed -n "${nearest_index}p") + mkdir -p "$PROPORTION_DIR" + printf '%s' "$saved" > "$PROPORTION_FILE" + mmsg dispatch "set_proportion,1.00" + printf "✅ [win %s] Saved proportion %s, set to 1.00 (fullscreen toggle on)\n" "$win_id" "$saved" + fi + exit 0 + ;; + *) printf "❌ Usage: %s [up|down|fullscreen]\n" "$0" >&2; exit 1 ;; +esac + +# 8️⃣ Clamp +count=$(printf '%s\n' $PRESETS | wc -l) +if [ "$new_index" -lt 1 ]; then new_index=1; fi +if [ "$new_index" -gt "$count" ]; then new_index=$count; fi + +# 9️⃣ Resolve the new preset value +new_value=$(printf '%s\n' $PRESETS | sed -n "${new_index}p") + +# 🔟 Dispatch +mmsg dispatch "set_proportion,$new_value" +printf "✅ Set proportion to %s (preset %s of %s)\n" "$new_value" "$new_index" "$count" diff --git a/modules/default.nix b/modules/default.nix index 7a6b4bd..3d0dfa2 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -3,6 +3,7 @@ ./configuration.nix #./hardware-configuration.nix + ../mango ../plasma ../niri ];