{ config, lib, pkgs, inputs, ... }: let nathan = config.nathan; in with lib; { config = mkIf nathan.programs.swaywm.enable (let swaylock-package = inputs.nixpkgs-unstable.legacyPackages.${pkgs.system}.swaylock-effects; swaylock-command = '' ${swaylock-package}/bin/swaylock --screenshots --grace 30 --indicator --clock --timestr "%-I:%M:%S %p" --datestr "%A %Y-%M-%d" --effect-blur 20x3''; fuzzel-command = '' fuzzel -f "Iosevka Sans Quasi:size=16" -b "181818cc" -S "b9b9b9ff" -s "252525cc" -t "777777ff" -B 5 -r 5 -C "70b433cc" --width=80 --lines=25''; notif-package = inputs.nixpkgs-unstable.legacyPackages.${pkgs.system}.swaynotificationcenter; shortcuts = inputs.self.packages.${pkgs.system}.shortcuts; in { home.packages = with pkgs; [ # General killall # Locking and display management wdisplays swaylock-package swayidle # Clipboard wl-clipboard cliphist # Notifications notif-package # Terminal alacritty # glib for sound stuff glib # Glpaper for the background (glpaper.overrideAttrs (old: { src = fetchFromSourcehut { owner = "~scoopta"; repo = "glpaper"; vc = "hg"; rev = "14c158c495451bd9dbdc31a8dfc97388db5e7279"; hash = "sha256-jYOUmyXwTffzwUmYEY1SQx2ClZiRiMZLanveE9zWOLI="; }; })) # Screenshots (inputs.hyprland-contrib.packages.${pkgs.system}.grimblast.override { hyprland = config.wayland.windowManager.hyprland.package; }) # fuzzel for launcher fuzzel # for image viewing inputs.self.packages.${pkgs.system}.swayimg # For private browsing tor-browser-bundle-bin # For non-emacs pdf reading evince # Productivity libreoffice-fresh ## Hyprland addons font-awesome inputs.nixpkgs-unstable.legacyPackages.${pkgs.system}.hyprpaper swayosd inputs.self.packages.${pkgs.system}.pyprland # For fancy x11 scaling sommelier # Polkit libsForQt5.polkit-kde-agent polkit_gnome # Auto workspace renaming inputs.self.packages.${pkgs.system}.hyprland-autoname-workspaces ]; ######################### ## Hyprland ######################### # Enable and configure hyperland wayland.windowManager.hyprland = { enable = true; xwayland = { enable = true; hidpi = false; }; recommendedEnvironment = true; systemdIntegration = false; extraConfig = '' # Set environment variables to force wayland env = XCURSOR_SIZE,24 env = GDK_BACKEND,wayland,x11 env = QT_QPA_PLATFORM,wayland;xcb env = SDL_VIDEODRIVER,wayland env = CLUTTER_BACKEND,wayland input { kb_layout = us kb_variant = kb_model = kb_options = kb_rules = follow_mouse = 1 } general { # See https://wiki.hyprland.org/Configuring/Variables/ for more gaps_in = 5 gaps_out = 10 border_size = 3 col.active_border = rgba(3fc5b7ee) rgba(70b433ee) 45deg col.inactive_border = rgba(252525aa) layout = dwindle } decoration { rounding = 10 blur = true blur_size = 3 blur_passes = 1 blur_new_optimizations = true drop_shadow = true shadow_range = 4 shadow_render_power = 3 col.shadow = rgba(181818ee) } animations { enabled = true; bezier = myBezier, 0.05, 0.9, 0.1, 1.05 animation = windows, 1, 7, myBezier animation = windowsOut, 1, 7, default, popin 80% animation = border, 1, 10, default animation = borderangle, 1, 8, default animation = fade, 1, 7, default animation = workspaces, 1, 6, default } dwindle { # See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more pseudotile = true # master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below preserve_split = true # you probably want this } gestures { workspace_swipe = false } $mainMod = SUPER bind = $mainMod, Q, exec, alacritty bind = $mainMod, C, killactive, # bind = $mainMod, M, exit, bind = $mainMod, E, exec, dolphin bind = $mainMod, V, togglefloating, bind = $mainMod, R, exec, ${fuzzel-command} --prompt='❯ ' bind = $mainMod, P, pseudo, # dwindle bind = $mainMod, B, togglesplit, # dwindle bind = $mainMod, Z, exec, ${swaylock-command} # Move focus with mainMod + arrow keys bind = $mainMod, H, movefocus, l bind = $mainMod, L, movefocus, r bind = $mainMod, K, movefocus, u bind = $mainMod, J, movefocus, d # Switch workspaces with mainMod + [0-9] bind = $mainMod, 1, workspace, 1 bind = $mainMod, 2, workspace, 2 bind = $mainMod, 3, workspace, 3 bind = $mainMod, 4, workspace, 4 bind = $mainMod, 5, workspace, 5 bind = $mainMod, 6, workspace, 6 bind = $mainMod, 7, workspace, 7 bind = $mainMod, 8, workspace, 8 bind = $mainMod, 9, workspace, 9 bind = $mainMod, 0, workspace, 10 # Move active window to a workspace with mainMod + SHIFT + [0-9] bind = $mainMod SHIFT, 1, movetoworkspace, 1 bind = $mainMod SHIFT, 2, movetoworkspace, 2 bind = $mainMod SHIFT, 3, movetoworkspace, 3 bind = $mainMod SHIFT, 4, movetoworkspace, 4 bind = $mainMod SHIFT, 5, movetoworkspace, 5 bind = $mainMod SHIFT, 6, movetoworkspace, 6 bind = $mainMod SHIFT, 7, movetoworkspace, 7 bind = $mainMod SHIFT, 8, movetoworkspace, 8 bind = $mainMod SHIFT, 9, movetoworkspace, 9 bind = $mainMod SHIFT, 0, movetoworkspace, 10 # Scroll through existing workspaces with mainMod + scroll bind = $mainMod, mouse_down, workspace, e+1 bind = $mainMod, mouse_up, workspace, e-1 # Move/resize windows with mainMod + LMB/RMB and dragging bindm = $mainMod, mouse:272, movewindow bindm = $mainMod, mouse:273, resizewindow # Media key binds bind = , xf86audioraisevolume, exec, swayosd --output-volume=raise bind = SHIFT, xf86audioraisevolume, exec, swayosd --output-volume=+2 bind = , xf86audiolowervolume, exec, swayosd --output-volume=lower bind = SHIFT, xf86audiolowervolume, exec, swayosd --output-volume=-2 bind = , xf86audiomute, exec, swayosd --output-volume mute-toggle bind = , xf86audioplay, exec, playerctl play-pause bind = , xf86audionext, exec, playerctl next # Screenshots bind = , print, exec, grimblast --scale 1 copysave area ~/Pictures/Screenshots/$(date -Iseconds).png bind = SHIFT, print, exec, grimblast --scale 1 copysave output ~/Pictures/Screenshots/$(date -Iseconds).png # Caps lock indicator bindr = CAPS, caps_lock, exec, swayosd --caps-lock # systemd integration exec-once=dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP && sleep 2 && systemctl --user start hyprland-session.target # Notif bind bind = $mainMod, T, exec, swaync-client -t -sw # Shortcut dispatcher bind = $mainMod, W, exec, ${shortcuts}/bin/shortcuts ~/.config/shortcuts/shortcuts.toml ## Window rules # Gamescope - vmware windowrulev2 = float,class:^(.gamescope-wrapped)$,title:VMware Workstation windowrulev2 = noborder,class:^(.gamescope-wrapped)$,title:VMware Workstation windowrulev2 = rounding 0,class:^(.gamescope-wrapped)$,title:VMware Workstation # Bluetooth manager windowrulev2 = float,class:^(.blueman-manager-wrapped)$ windowrulev2 = center,class:^(.blueman-manager-wrapped)$ ## Scratchpads # Alacritty bind = SUPER ALT, Q, exec, pypr toggle term $dropterm = ^(alacritty-dropterm)$ windowrule = float,$dropterm windowrule = size 40% 50%,$dropterm windowrule = workspace special silent,$dropterm windowrule = center,$dropterm # Emacs bind = SUPER ALT, W, exec, pypr toggle emacs $scratchmacs = title:^(scratchmacs)$ windowrulev2 = float,$scratchmacs windowrulev2 = size 40% 50%,$scratchmacs windowrulev2 = workspace special silent,$scratchmacs windowrulev2 = center,$scratchmacs # Ario bind = SUPER ALT, E, exec, pypr toggle ario $dropario = class:^(ario)$ windowrulev2 = float,$dropario windowrulev2 = size 50% 70%,$dropario windowrulev2 = workspace special silent,$dropario windowrulev2 = center,$dropario # Spotify bind = SUPER ALT, R, exec, pypr toggle spotify $dropspotify = title:^(Spotify)$ windowrulev2 = float,$dropspotify windowrulev2 = size 50% 70%,$dropspotify windowrulev2 = workspace special silent,$dropspotify windowrulev2 = center,$dropspotify # Clipboard history management exec-once=wl-paste --watch cliphist store bind = $mainMod, X, exec, cliphist list | ${fuzzel-command} --prompt=' ❯ ' --dmenu | cliphist decode | wl-copy ''; }; # systemd session target # Specified manually so we can work around an issue with the loading systemd.user.targets.hyprland-session = { Unit = { Description = "Hyprland compositor session"; Documentation = [ "man:systemd.special(7)" ]; BindsTo = [ "graphical-session.target" ]; Wants = [ "graphical-session-pre.target" ]; After = [ "graphical-session-pre.target" ]; }; }; ######################### ## Hyprland Addons ######################### # Setup polkit as a service systemd.user.services.polkit-gnome = { Unit = { Description = "Polkit gnome authentication agent"; After = [ "graphical-session.target" ]; }; Service = { Type = "simple"; ExecStart = "${pkgs.polkit_gnome}/libexec/polkit-gnome-authentication-agent-1"; Restart = "on-failure"; }; Install = { WantedBy = [ "hyprland-session.target" ]; }; }; # Setup swayosd as a service systemd.user.services.swayosd = { Unit = { Description = "Swayosd"; After = [ "graphical-session.target" ]; }; Service = { Type = "simple"; ExecStart = "${pkgs.swayosd}/bin/swayosd"; Restart = "on-failure"; }; Install = { WantedBy = [ "hyprland-session.target" ]; }; }; # Shortcut dispatcher xdg.configFile."shortcuts/shortcuts.toml" = { text = '' directories = ["~/.config/shortcuts/shortcuts", "${shortcuts}/shortcuts"] picker_command = "${ builtins.replaceStrings [ ''"'' ] [ ''\"'' ] (fuzzel-command + " --prompt=' ❯ ' ") } --dmenu" ''; onChange = '' mkdir -p ~/.config/shortcuts/shortcuts ''; }; # Setup pyprland systemd.user.services.pypr = { Unit = { Description = "pypr"; After = [ "tray.target" ]; }; Service = { Type = "simple"; ExecStart = "${inputs.self.packages.${pkgs.system}.pyprland}/bin/pypr"; Restart = "on-failure"; }; Install = { WantedBy = [ "hyprland-session.target" ]; }; }; # And its config file xdg.configFile."hypr/pyprland.json" = let emacsStart = pkgs.writeShellScript "emacsStart.sh" '' exec emacsclient -c --frame-parameters='(quote (name . "scratchmacs"))' ''; # https://github.com/hyprland-community/pyprland/issues/6 # "emacs": { # "command": "${emacsStart}", # "animation": "", # "unfocus": "hide" # } in { text = '' { "pyprland": { "plugins": [ "scratchpads" ] }, "scratchpads": { "term": { "command": "alacritty --class alacritty-dropterm --working-directory ~ -e tmux new -A -s scratch", "animation": "", "unfocus": "hide" }, "ario": { "command": "ario", "animation": "", "unfocus": "hide" }, "spotify": { "command": "spotify", "animation": "", "unfocus": "hide" } } } ''; onChange = "systemctl --user restart pypr"; }; # Bluetooth applet services.blueman-applet.enable = true; ######################### ## SwayNotificationCenter (notifications) ######################### systemd.user.services.swaync = { Unit = { Description = "SwayNotificationCenter"; After = [ "graphical-session.target" ]; }; Service = { Type = "simple"; ExecStart = "${notif-package}/bin/swaync"; Restart = "on-failure"; }; Install = { WantedBy = [ "hyprland-session.target" ]; }; }; ######################### ## Swayidle ######################### services.swayidle = { enable = true; timeouts = [ # Lock the screen after 5 minutes of inactivity { timeout = 300; command = builtins.replaceStrings [ "%" ] [ "%%" ] swaylock-command; } # Turn off the displays after 10 minutes of inactivity { timeout = 600; command = "hyprctl dispatch dpms off"; resumeCommand = "hyprctl dispatch dpms on"; } ]; }; systemd.user.services.swayidle = { Install = { WantedBy = [ "hyprland-session.target" ]; }; }; ######################### ## Waybar ######################### # https://github.com/DN-debug/waybar-examples/tree/main/waybar-examples/sway programs.waybar = { enable = true; package = (inputs.nixpkgs-unstable.legacyPackages."${pkgs.system}".waybar.override { withMediaPlayer = true; }).overrideAttrs (old: { mesonFlags = old.mesonFlags ++ [ "-Dexperimental=true" ]; }); systemd = { enable = false; }; settings = { mainBar = { layer = "top"; position = "top"; height = 30; spacing = 0; margin-top = 5; margin-bottom = 0; # modules-left = [ "wlr/workspaces" "hyprland/submap" ]; # modules-center = [ "hyprland/window" ]; # modules-right = [ "mpd" "clock" "tray" ]; modules-center = [ "custom/launcher" "wlr/workspaces" "custom/media" # "hyprland/window" "idle_inhibitor" "pulseaudio" "network" "cpu" "memory" # "temperature" "tray" "custom/notification" "clock" # "custom/power" ]; "custom/launcher" = { format = ""; on-click = fuzzel-command + " --prompt='❯ ' "; on-click-right = "killall fuzzel"; }; "idle_inhibitor" = { format = "{icon}"; format-icons = { activated = ""; deactivated = ""; }; }; pulseaudio = { scroll-step = 1; format = "{volume}% {icon}"; format-bluetooth = "{volume}% {icon}"; format-bluetooth-muted = "{icon} {format_source}"; format-muted = "{format_source}"; format-source = ""; format-source-muted = ""; format-icons = { headphone = ""; hands-free = ""; headset = ""; phone = ""; portable = ""; car = ""; default = [ "" "" "" ]; }; on-click = "pavucontrol"; ignored-sinks = [ "Easy Effects Sink" ]; }; network = { # // "interface": "wlp2*", // (Optional) To force the use of this interface format-wifi = "{essid} ({signalStrength}%) "; format-ethernet = "Connected  "; tooltip-format = "{ifname} via {gwaddr} "; format-linked = "{ifname} (No IP) "; format-disconnected = "Disconnected ⚠"; format-alt = "{ifname}: {ipaddr}/{cidr}"; on-click-right = "bash ~/.config/rofi/wifi_menu/rofi_wifi_menu"; }; cpu = { format = "{usage}% "; tooltip = false; }; memory = { format = "{}% ({used}GB/{total}GB) "; }; "tray" = { icon-size = 20; spacing = 10; }; "clock" = { format = "{:%I:%M%p %Y-%m-%d}"; }; "custom/media" = { format = "{icon} {}"; return-type = "json"; max-length = 50; format-icons = { spotify = " "; firefox = " "; mopidy = " "; default = " "; }; escape = true; exec = "waybar-mediaplayer.py 2> /dev/null"; on-click = "playerctl play-pause"; }; "custom/notification" = { tooltip = false; format = "{icon}"; format-icons = { notification = "  "; none = "  "; dnd-notification = "  "; dnd-none = ""; inhibited-notification = "  "; inhibited-none = "  "; dnd-inhibited-notification = "  "; dnd-inhibited-none = "  "; }; return-type = "json"; exec-if = "which swaync-client"; exec = "swaync-client -swb"; on-click = "swaync-client -t -sw"; on-click-right = "swaync-client -d -sw"; escape = true; }; }; }; style = '' * { border: none; border-radius: 0px; font-family: Roboto, Helvetica, Arial, sans-serif; font-size: 12px; min-height: 0; } window#waybar { background-color: transparent; color: #ffffff; transition-property: background-color; transition-duration: .5s; } window#waybar.hidden { opacity: 0.2; } #workspaces button { background: #1f1f1f; color: #ffffff; border-radius: 20px; } /* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */ #workspaces button:hover { background: lightblue; color: black; border-bottom: 3px solid #ffffff; } #workspaces button.active { background: #3f3f3f; } #workspaces button.active:hover { background: lightblue; color: black; border-bottom: 3px solid #ffffff; } #workspaces button.urgent { background-color: #eb4d4b; } #mode { background-color: #64727D; border-bottom: 3px solid #ffffff; } #clock, #battery, #cpu, #memory, #disk, #temperature, #backlight, #network, #pulseaudio, #custom-media, #custom-launcher, #custom-power, #tray, #mode, #idle_inhibitor, #mpd { padding: 0 10px; color: black; } #mpd { background-color: #8EC5FC; background-image: linear-gradient(62deg, #8EC5FC 0%, #E0C3FC 100%); color: black; border-radius: 20px; margin-right: 5px; } #window, #workspaces { margin: 0px 4px; } /* If workspaces is the leftmost module, omit left margin */ .modules-left > widget:first-child > #workspaces { margin-left: 0px; } /* If workspaces is the rightmost module, omit right margin */ .modules-right > widget:last-child > #workspaces { margin-right: 0px; } #clock { background-color: #FA8BFF; background-image: linear-gradient(-45deg, #FA8BFF 0%, #2BD2FF 52%, #2BD2FF 90%); color: black; border-radius: 0px 20px 20px 0px; margin-right: 4px; } #battery { background-color: #ffffff; color: #000000; } #battery.charging, #battery.plugged { color: #ffffff; background-color: #26A65B; } @keyframes blink { to { background-color: #ffffff; color: #000000; } } #battery.critical:not(.charging) { background-color: #f53c3c; color: #ffffff; animation-name: blink; animation-duration: 0.5s; animation-timing-function: linear; animation-iteration-count: infinite; animation-direction: alternate; } label:focus { background-color: #000000; } #cpu { background-color: #FA8BFF; background-image: linear-gradient(45deg, #2BD2FF 0%, #2BD2FF 52%, #2BD2FF 90%); color: black; } #custom-notification { background-color: #FA8BFF; background-image: linear-gradient(45deg, #2BD2FF 0%, #2BD2FF 52%, #2BD2FF 90%); color: black; } #memory { background-color: #FA8BFF; background-image: linear-gradient(45deg, #2BD2FF 0%, #2BD2FF 52%, #2BD2FF 90%); color: black; } #disk { background-color: #FA8BFF; background-image: linear-gradient(45deg, #2BD2FF 0%, #2BD2FF 52%, #2BD2FF 90%); color: black; } #backlight { background-color: #FA8BFF; background-image: linear-gradient(45deg, #2BD2FF 0%, #2BD2FF 52%, #2BD2FF 90%); } #network { background-color: #FA8BFF; background-image: linear-gradient(45deg, #2BD2FF 0%, #2BD2FF 52%, #2BD2FF 90%); color: black; } #network.disconnected { background-color: #FA8BFF; background-image: linear-gradient(45deg, #2BD2FF 0%, #2BD2FF 52%, #2BD2FF 90%); color: red; } #pulseaudio { background-color: #FA8BFF; background-image: linear-gradient(45deg, #2BD2FF 0%, #2BD2FF 52%, #2BD2FF 90%); color: black; } #pulseaudio.muted { background-color: #FA8BFF; background-image: linear-gradient(45deg, #2BD2FF 0%, #2BD2FF 52%, #2BD2FF 90%); color: red; } #custom-media { background-color: #8EC5FC; background-image: linear-gradient(62deg, #8EC5FC 0%, #E0C3FC 100%); color: black; border-radius: 20px; margin-right: 5px; } #custom-media { background-color: #8EC5FC; background-image: linear-gradient(62deg, #8EC5FC 0%, #E0C3FC 100%); color: black; border-radius: 20px; margin-right: 5px; } #custom-media.custom-spotify { background-color: #8EC5FC; background-image: linear-gradient(62deg, #8EC5FC 0%, #E0C3FC 100%); color: black; border-radius: 20px; margin-right: 5px; } #custom-media.custom-vlc { background-color: #8EC5FC; background-image: linear-gradient(62deg, #8EC5FC 0%, #E0C3FC 100%); color: black; border-radius: 20px; margin-right: 5px; } #custom-power{ background-color: #FA8BFF; background-image: linear-gradient(45deg, #FA8BFF 0%, #2BD2FF 52%, #2BD2FF 90%); font-size: 16px; border-radius: 19px; } #custom-launcher{ background-color: #FA8BFF; background-image: linear-gradient(-45deg, #FA8BFF 0%, #2BD2FF 52%, #2BD2FF 90%); font-size: 18px; border-radius: 20px; } #custom-updater { background-color: #FA8BFF; background-image: linear-gradient(45deg, #2BD2FF 0%, #2BD2FF 52%, #2BD2FF 90%); color: black; } #temperature { background-color: #FA8BFF; background-image: linear-gradient(45deg, #2BD2FF 0%, #2BD2FF 52%, #2BD2FF 90%); color: black; } #temperature.critical { background-color: #eb4d4b; } #tray { background-color: #FA8BFF; background-image: linear-gradient(-45deg, #2BD2FF 0%, #2BD2FF 52%, #2BD2FF 90%); color: black; } #tray > .passive { -gtk-icon-effect: dim; background-color: #FA8BFF; background-image: linear-gradient(-45deg, #FA8BFF 0%, #2BD2FF 52%, #2BD2FF 90%); color: black; } #tray > .needs-attention { -gtk-icon-effect: highlight; background-color: #FA8BFF; background-image: linear-gradient(-45deg, #FA8BFF 0%, #2BD2FF 52%, #2BD2FF 90%); color: black; } #idle_inhibitor { background-color: #FA8BFF; background-image: linear-gradient(45deg, #FA8BFF 0%, #2BD2FF 52%, #2BD2FF 90%); border-radius: 20px 0px 0px 20px; } #idle_inhibitor.activated { background-color: #FA8BFF; background-image: linear-gradient(45deg, #FA8BFF 0%, #2BD2FF 52%, #2BD2FF 90%); color: black; border-radius: 20px 0px 0px 20px; } #language { background-color: #FA8BFF; background-image: linear-gradient(45deg, #2BD2FF 0%, #2BD2FF 52%, #2BD2FF 90%); color: black; min-width: 16px; } #keyboard-state { background: #97e1ad; color: #000000; min-width: 16px; } #keyboard-state > label { padding: 0px 5px; } #keyboard-state > label.locked { background: rgba(0, 0, 0, 0.2); } ''; }; # Override the service to run during graphical-session-pre.target systemd.user.services.waybar = { Unit = { Description = "Highly customizable Wayland bar for Sway and Wlroots based compositors."; Documentation = "https://github.com/Alexays/Waybar/wiki"; Before = [ "tray.target" ]; }; Service = { ExecStart = "${config.programs.waybar.package}/bin/waybar"; ExecReload = "${pkgs.coreutils}/bin/kill -SIGUSR2 $MAINPID"; ExecStartPost = "${pkgs.coreutils}/bin/sleep 1"; Restart = "on-failure"; KillMode = "mixed"; }; Install = { WantedBy = lib.mkForce [ "hyprland-session-pre.target" ]; }; }; ######################### ## EasyEffects ######################### services.easyeffects.enable = true; ######################### ## Create tray target to fix some things ######################### systemd.user.targets.tray = { Unit = { Description = "Home Manager System Tray"; Requires = [ "graphical-session-pre.target" "waybar.service" ]; After = [ "waybar.service" ]; }; }; ######################### ## Default applications ######################### xdg.mimeApps.defaultApplications = { # Make all supported images open in swayimg "image/jpeg" = [ "swayimg.desktop" ]; "image/png" = [ "swayimg.desktop" ]; "image/gif" = [ "swayimg.desktop" ]; "image/svg+xml" = [ "swayimg.desktop" ]; "image/webp" = [ "swayimg.desktop" ]; "image/avif" = [ "swayimg.desktop" ]; "image/tiff" = [ "swayimg.desktop" ]; "image/bmp" = [ "swayimg.desktop" ]; "application/pdf" = [ "org.gnome.Evince.desktop" ]; "text/html" = [ "firefox.desktop" ]; }; }); }