diff --git a/configuration.nix b/configuration.nix index 630f49f..423de88 100644 --- a/configuration.nix +++ b/configuration.nix @@ -74,6 +74,9 @@ hyprbars = inputs.hyprland-plugins.packages.${prev.system}.hyprbars; }; + ### Sway + #// sway = unstable.swayfx; + ### Development #// ciscoPacketTracer8 = local.ciscoPacketTracer8; } diff --git a/options/custom/desktops/default.nix b/options/custom/desktops/default.nix index 1fc15f0..a42c953 100644 --- a/options/custom/desktops/default.nix +++ b/options/custom/desktops/default.nix @@ -4,5 +4,8 @@ ... }: with lib; { - config.custom.desktops.hyprland.enable = config.custom.full; + config.custom.desktops = mkIf config.custom.full { + hyprland.enable = true; + sway.enable = true; + }; } diff --git a/options/custom/desktops/sway/binds.nix b/options/custom/desktops/sway/binds.nix new file mode 100644 index 0000000..2d88145 --- /dev/null +++ b/options/custom/desktops/sway/binds.nix @@ -0,0 +1,281 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + audio = "~/.local/bin/audio"; + clipse = "${pkgs.clipse}/bin/clipse"; + codium = "${config.home-manager.users.${config.custom.username}.programs.vscode.package}/bin/codium"; + firefox-esr = "${config.home-manager.users.${config.custom.username}.programs.firefox.finalPackage}/bin/firefox-esr"; + gnome-text-editor = "${pkgs.gnome-text-editor}/bin/gnome-text-editor"; + hyprctl = "${config.programs.hyprland.package}/bin/hyprctl"; + hyprlock = "${config.home-manager.users.${config.custom.username}.programs.hyprlock.package}/bin/hyprlock"; + hyprpicker = "${pkgs.hyprpicker}/bin/hyprpicker"; + inhibit = config.home-manager.users.${config.custom.username}.home.file.".local/bin/inhibit".source; + jq = "${pkgs.jq}/bin/jq"; + kill = "${pkgs.procps}/bin/kill"; + kitty = "${config.home-manager.users.${config.custom.username}.programs.kitty.package}/bin/kitty"; + left = config.home-manager.users.${config.custom.username}.home.file.".local/bin/left".source; + loginctl = "${pkgs.systemd}/bin/loginctl"; + menu = config.home-manager.users.${config.custom.username}.home.file.".local/bin/menu".source; + nautilus = "${pkgs.nautilus}/bin/nautilus"; + networkmanager_dmenu = "${pkgs.networkmanager_dmenu}/bin/networkmanager_dmenu"; + notify-send = "${pkgs.libnotify}/bin/notify-send"; + obsidian = "${pkgs.obsidian}/bin/obsidian"; + onlyoffice = "${pkgs.onlyoffice-bin}/bin/onlyoffice-desktopeditors --system-title-bar --xdg-desktop-portal"; + pkill = "${pkgs.procps}/bin/pkill"; + playerctl = "${pkgs.playerctl}/bin/playerctl"; + rofi-rbw = "${pkgs.rofi-rbw}/bin/rofi-rbw"; + rm = "${pkgs.coreutils}/bin/rm"; + screenshot = "~/.local/bin/screenshot"; + sleep = "${pkgs.coreutils}/bin/sleep"; + smile = "${pkgs.smile}/bin/smile"; + steam = "${config.programs.steam.package}/bin/steam"; + swayosd-client = "${pkgs.swayosd}/bin/swayosd-client"; + systemctl = "${pkgs.systemd}/bin/systemctl"; + toggle = "~/.local/bin/toggle"; + virt-manager = "${config.programs.virt-manager.package}/bin/virt-manager"; + vm = config.home-manager.users.${config.custom.username}.home.file.".local/bin/vm".source; + vrr = config.home-manager.users.${config.custom.username}.home.file.".local/bin/vrr".source; + walker = "${config.home-manager.users.${config.custom.username}.programs.walker.package}/bin/walker"; + waydroid = "${pkgs.waydroid}/bin/waydroid"; + window = config.home-manager.users.${config.custom.username}.home.file.".local/bin/window".source; + workspace = config.home-manager.users.${config.custom.username}.home.file.".local/bin/workspace".source; + zoom = config.home-manager.users.${config.custom.username}.home.file.".local/bin/zoom".source; + blackbox = "${pkgs.blackbox-terminal}/bin/blackbox"; + cliphist = "${pkgs.cliphist}/bin/cliphist"; + foot = "${config.home-manager.users.${config.custom.username}.programs.foot.package}/bin/foot"; + pgrep = "${pkgs.procps}/bin/pgrep"; + satty = "${pkgs.satty}/bin/satty"; + swaylock = "${config.home-manager.users.${config.custom.username} programs.swaylock.package}/bin/swaylock"; + swaymsg = "${config.home-manager.users.${config.custom.username}.wayland.windowManager.sway.package}/bin/swaymsg"; + swaynag = "${config.home-manager.users.${config.custom.username}.wayland.windowManager.sway.package}/bin/swaynag"; + wlfreerdp = "${pkgs.freerdp}/bin/wlfreerdp"; + wofi = "${config.home-manager.users.${config.custom.username}.programs.wofi.package}/bin/wofi"; + xfreerdp = "${pkgs.freerdp}/bin/xfreerdp"; + + cfg = config.custom.desktops.sway.binds; +in { + options.custom.desktops.sway.binds = { + enable = mkOption {default = false;}; + modifier = mkOption {default = config.home-manager.users.${config.custom.username}.wayland.windowManager.sway.config.modifier;}; + }; + + config.home-manager.users.${config.custom.username} = mkIf cfg.enable { + # https://nix-community.github.io/home-manager/options.xhtml#opt-wayland.windowManager.sway.config.keybindings + # https://i3wm.org/docs/userguide.html#keybindings + #?? man 5 sway + #?? wev + wayland.windowManager.sway = { + config = { + keybindings = let + # Keyboard modifiers + super = cfg.modifier; + ctrl = "Control"; + shift = "Shift"; + alt = "Mod1"; + + # Mouse bindings + left = "button1"; + middle = "button2"; + right = "button3"; + scroll_up = "button4"; + scroll_down = "button5"; + scroll_left = "button6"; + scroll_right = "button7"; + back = "button8"; + forward = "button9"; + + # Wrap keys in optional bindsym flags + #?? flags "FLAGS" { "KEY" = "COMMAND"; } + flags = args: keys: + lib.concatMapAttrs + (key: command: { + "${args} ${key}" = command; + }) + keys; + in + # Repeat keybindings + { + ### Media + # https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h + XF86AudioMute = "exec ${swayosd-client} --output-volume mute-toggle"; + XF86AudioLowerVolume = "exec ${swayosd-client} --output-volume lower"; + XF86AudioRaiseVolume = "exec ${swayosd-client} --output-volume raise"; + XF86AudioPlay = "exec ${playerctl} play-pause"; + XF86AudioPrev = "exec ${playerctl} previous"; + XF86AudioNext = "exec ${playerctl} next"; + XF86MonBrightnessDown = "exec ${swayosd-client} --brightness lower"; + XF86MonBrightnessUp = "exec ${swayosd-client} --brightness raise"; + + # TODO: Unused media key + #// XF86AudioMedia = "exec null"; + } + # Press keybindings + // flags "--no-repeat" + { + # TODO: Toggle left handed trackball + ### Scripts + "${super}+delete" = "exec inhibit"; + "${super}+${shift}+delete" = "exec vrr"; + "${super}+minus" = "exec audio Flat"; + "${super}+equal" = "exec audio Normalizer"; + "${super}+${shift}+${ctrl}+q" = "exec close"; + + ### Messages + "${ctrl}+delete" = "reload"; + "${ctrl}+${alt}+delete" = "exec ${loginctl} terminate-user ''"; + + # Windows + "${super}+q" = "kill"; + "${super}+${shift}+q" = "exec ${kill} -9 $(${swaymsg} -t get_tree | ${jq} '.. | select(.focused?==true).pid')"; + "${super}+grave" = "sticky toggle"; + "${super}+return" = "fullscreen toggle"; + "${super}+tab" = "focus next"; + "${super}+${shift}+tab" = "focus prev"; + + # Containers + "${super}+up" = "move up"; + "${super}+down" = "move down"; + "${super}+left" = "move left"; + "${super}+right" = "move right"; + "${super}+backslash" = "split toggle"; + "${super}+${shift}+backslash" = "split none"; + "${super}+backspace" = "focus mode_toggle"; + "${super}+${shift}+backspace" = "floating toggle"; + "${super}+bracketleft" = "layout toggle tabbed stacking"; + "${super}+bracketright" = "layout toggle split"; + "${super}+escape" = "focus parent"; + "${super}+${shift}+escape" = "focus child"; + + # Workspaces + "${super}+1" = "workspace 1"; + "${super}+${shift}+1" = "move to workspace 1"; + "${super}+2" = "workspace 2"; + "${super}+${shift}+2" = "move to workspace 2"; + "${super}+3" = "workspace 3"; + "${super}+${shift}+3" = "move to workspace 3"; + "${super}+4" = "workspace 4"; + "${super}+${shift}+4" = "move to workspace 4"; + "${super}+5" = "workspace 5"; + "${super}+${shift}+5" = "move to workspace 5"; + "${super}+6" = "workspace 6"; + "${super}+${shift}+6" = "move to workspace 6"; + "${super}+7" = "workspace 7"; + "${super}+${shift}+7" = "move to workspace 7"; + "${super}+8" = "workspace 8"; + "${super}+${shift}+8" = "move to workspace 8"; + "${super}+9" = "workspace 9"; + "${super}+${shift}+9" = "move to workspace 9"; + "${super}+0" = "workspace 10"; + "${super}+${shift}+0" = "move to workspace 0"; + "${super}+g" = "workspace game"; + "${super}+${shift}+g" = "move to workspace game"; + "${super}+${ctrl}+g" = "workspace gamescope"; + "${super}+z" = "exec workspace prev"; + "${super}+${shift}+z" = "move to workspace prev"; + "${super}+x" = "exec workspace next"; + "${super}+${shift}+x" = "move to workspace next"; + + # Scratchpads + "${super}+a" = "[con_mark=android] scratchpad show"; + "${super}+s" = "[con_mark=steam] scratchpad show"; + "${super}+w" = "[con_mark=vm] scratchpad show"; + "${super}+space" = "scratchpad show"; + "${super}+${shift}+space" = "move to scratchpad"; + "${super}+${middle}" = "move window to scratchpad"; + "${super}+${shift}+${middle}" = "move container to scratchpad"; + "${ctrl}+space" = "exec scratchpad dropdown ${foot} --app-id dropdown"; + + ### Commands + # Clipboard + "${super}+v" = "exec clipboard"; + "${super}+${shift}+v" = "exec ${cliphist} wipe && ${notify-send} cliphist 'Clipboard cleared' --urgency low"; + + # Color picker + "${super}+p" = "exec {hyprpicker} --autocopy"; + "${super}+${shift}+p" = "exec {hyprpicker} --autocopy --format rgb"; + + # Screenshot + print = "exec {grimblast} --freeze copysave area \"$XDG_SCREENSHOTS_DIR/$(date +'%F %H.%M.%S')\""; + "${super}+print" = "exec {grimblast} --freeze save area - | ${satty} --filename -"; + "${shift}+print" = "exec {grimblast} --freeze copysave output \"$XDG_SCREENSHOTS_DIR/$(date +'%F %H.%M.%S')\""; + "${super}+${shift}+print" = "exec {grimblast} --freeze save output - | ${satty} --filename -"; + + # Smart home + "${super}+${alt}+escape" = "exec lifx state --color red"; + "${super}+${alt}+1" = "exec lifx state --kelvin 1500"; + "${super}+${alt}+2" = "exec lifx state --kelvin 2500"; + "${super}+${alt}+3" = "exec lifx state --kelvin 3000"; + "${super}+${alt}+4" = "exec lifx state --kelvin 4000"; + "${super}+${alt}+5" = "exec lifx state --kelvin 5000"; + "${ctrl}+${alt}+space" = "exec lifx toggle"; + "${ctrl}+${alt}+1" = "exec lifx state --brightness 0.01"; + "${ctrl}+${alt}+2" = "exec lifx state --brightness 0.25"; + "${ctrl}+${alt}+3" = "exec lifx state --brightness 0.50"; + "${ctrl}+${alt}+4" = "exec lifx state --brightness 0.75"; + "${ctrl}+${alt}+5" = "exec lifx state --brightness 1.00"; + + ### Applications + "${super}+b" = "exec ${firefox-esr}"; + "${super}+c" = "exec ${codium}"; + "${super}+e" = "exec ${gnome-text-editor}"; + "${super}+f" = "exec ${nautilus}"; + "${super}+k" = "exec ${obsidian}"; + # "${super}+t" = "workspace terminal; exec launch terminal ${foot} --app-id terminal"; + "${super}+t" = "workspace terminal; exec ${kitty}"; + "${super}+${shift}+t" = "exec ${foot}"; + + # Kill applications + "${super}+${shift}+a" = "exec ${waydroid} session stop"; + "${super}+${shift}+s" = "exec ${pkill} steam"; + "${super}+${ctrl}+${shift}+g" = "exec ${pkill} gamescope"; + + # Remote desktop + # https://forum.level1techs.com/t/how-to-seamlessly-run-windows-10-apps-in-a-vm-in-linux/170417 + "${super}+${shift}+w" = "exec vm ${wlfreerdp} /cert:ignore /u:Myned /p:password /v:myndows /dynamic-resolution +gfx-progressive -grab-keyboard"; + "${super}+${ctrl}+${shift}+w" = "exec vm ${xfreerdp} /cert:ignore /u:Myned /p:password /app:explorer.exe /v:myndows +gfx-progressive -grab-keyboard"; + } + # Release keybindings + // flags "--no-repeat --release" + { + # Menus + super_l = "exec ${pkill} wofi || ${wofi} --show drun"; + "${ctrl}+super_l" = "exec ${pkill} wofi || calc"; + "${shift}+super_l" = "exec ${pkill} wofi || ${wofi} --show run"; + "${alt}+super_l" = "exec ${pkill} wofi || ${rofi-rbw}"; + "${ctrl}+${shift}+super_l" = "exec ${pkill} || ${networkmanager_dmenu}"; + + # Workspaces + "${super}+control_l" = "workspace music"; + "${super}+shift_l" = "workspace back_and_forth"; + "${super}+alt_l" = "workspace wallpaper"; + + # Scratchpad + "${super}+${shift}+control_l" = "exec hide Picture-in-Picture special:pip"; + } + # Lockscreen keybindings + // flags "--no-repeat --release --locked" + { + "${super}+l" = "exec ${loginctl} lock-session"; + }; + }; + + #// modes = { }; + + # Keybindings that are not supported by options + extraConfig = '' + # Gesture keybindings + bindgesture swipe:left workspace prev + bindgesture swipe:right workspace next + + # Switch keybindings + bindswitch lid:on exec ${loginctl} lock-session + #// bindswitch lid:on output '*' power off + #// bindswitch lid:off output '*' power on + ''; + }; + }; +} diff --git a/options/custom/desktops/sway/default.nix b/options/custom/desktops/sway/default.nix new file mode 100644 index 0000000..4928835 --- /dev/null +++ b/options/custom/desktops/sway/default.nix @@ -0,0 +1,59 @@ +{ + config, + lib, + ... +}: +with lib; let + cfg = config.custom.desktops.sway; +in { + options.custom.desktops.sway.enable = mkOption {default = false;}; + + config = mkIf cfg.enable { + custom.desktops.sway = mkIf config.custom.full { + binds.enable = true; + input.enable = true; + output.enable = true; + # TODO: rules.enable = true; + settings.enable = true; + #// swayfx.enable = true; + }; + + # https://wiki.nixos.org/wiki/Sway + # https://wiki.archlinux.org/title/Sway + # https://github.com/swaywm/sway + programs.sway.enable = true; + + home-manager.users.${config.custom.username} = { + #?? man sway[msg|-ipc] + wayland.windowManager.sway = { + enable = true; + + # TODO: Remove when fixed upstream + # https://github.com/swaywm/sway/pull/7780 + # Disable direct scanout for fullscreen vrr + # https://nix-community.github.io/home-manager/options.xhtml#opt-wayland.windowManager.sway.extraOptions + extraOptions = ["-Dnoscanout"]; + + # HACK: Export mapped home-manager variables in lieu of upstream fix + # https://github.com/nix-community/home-manager/issues/2659 + # https://nix-community.github.io/home-manager/options.xhtml#opt-wayland.windowManager.sway.extraSessionCommands + extraSessionCommands = with builtins; + concatStringsSep "\n" (attrValues + ( + mapAttrs + (name: value: "export ${name}=${toString value}") + config.home-manager.users.${config.custom.username}.home.sessionVariables + )); + + # Import some necessary variables from systemd + # https://nix-community.github.io/home-manager/options.xhtml#opt-wayland.windowManager.sway.systemd.variables + systemd.variables = ["--all"]; + + # Otherwise GTK applications take a while to start + # https://github.com/swaywm/sway/wiki#gtk-applications-take-20-seconds-to-start + # https://nix-community.github.io/home-manager/options.xhtml#opt-wayland.windowManager.sway.wrapperFeatures + wrapperFeatures.gtk = true; + }; + }; + }; +} diff --git a/options/custom/desktops/sway/input.nix b/options/custom/desktops/sway/input.nix new file mode 100644 index 0000000..fa8f0f3 --- /dev/null +++ b/options/custom/desktops/sway/input.nix @@ -0,0 +1,69 @@ +{ + config, + lib, + ... +}: +with lib; let + cfg = config.custom.desktops.sway.input; +in { + options.custom.desktops.sway.input.enable = mkOption {default = false;}; + + config.home-manager.users.${config.custom.username} = mkIf cfg.enable { + #?? man sway-input + wayland.windowManager.sway = let + kensington-orbit = "1149:32934:Kensington_ORBIT_WIRELESS_TB_Mouse"; + protoarc = "9741:4097:Nordic_2.4G_Wireless_Receiver_Mouse"; + in { + config = { + # https://nix-community.github.io/home-manager/options.xhtml#opt-wayland.windowManager.sway.config.input + #?? swaymsg -t get_inputs + input = { + # Keyboard + "type:keyboard" = { + repeat_delay = "400"; + repeat_rate = "40"; + }; + + # Mouse + "type:pointer" = { + accel_profile = "flat"; + }; + + # Touchpad + "type:touchpad" = { + accel_profile = "adaptive"; + click_method = "clickfinger"; # Multi-finger clicks + drag = "enabled"; + drag_lock = "enabled"; + dwt = "enabled"; # Disable while typing + natural_scroll = "enabled"; + #// pointer_accel = "0"; + tap = "enabled"; + }; + + # Individual devices + ${kensington-orbit} = { + accel_profile = "adaptive"; + left_handed = "enabled"; + middle_emulation = "enabled"; + natural_scroll = "enabled"; + pointer_accel = "-0.5"; + }; + + # ${protoarc} = { + # accel_profile = "flat"; + # pointer_accel = "0"; + # }; + }; + + #// seat = { }; + }; + + # BUG: Order must be specified for some settings + # https://github.com/swaywm/sway/issues/7271 + extraConfig = '' + input ${kensington-orbit} accel_profile adaptive + ''; + }; + }; +} diff --git a/options/custom/desktops/sway/output.nix b/options/custom/desktops/sway/output.nix new file mode 100644 index 0000000..586dc09 --- /dev/null +++ b/options/custom/desktops/sway/output.nix @@ -0,0 +1,26 @@ +{ + config, + lib, + ... +}: +with lib; let + cfg = config.custom.desktops.sway.output; +in { + options.custom.desktops.sway.output.enable = mkOption {default = false;}; + + config.home-manager.users.${config.custom.username} = mkIf cfg.enable { + # TODO: May need kanshi for output switching + # https://nix-community.github.io/home-manager/options.xhtml#opt-wayland.windowManager.sway.config.output + #?? man sway-output + #?? swaymsg -t get_outputs + wayland.windowManager.sway.config.output = { + # Default + "*" = { + adaptive_sync = "off"; # Explicitly use script/keybindings to toggle vrr + background = "#073642 solid_color"; # Fallback color + resolution = "${toString config.custom.width}x${toString config.custom.height}@${toString config.custom.refresh}Hz"; + scale = toString config.custom.scale; + }; + }; + }; +} diff --git a/options/custom/desktops/sway/settings.nix b/options/custom/desktops/sway/settings.nix new file mode 100644 index 0000000..7d1d56b --- /dev/null +++ b/options/custom/desktops/sway/settings.nix @@ -0,0 +1,227 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + audio = "~/.local/bin/audio"; + clipse = "${pkgs.clipse}/bin/clipse"; + codium = "${config.home-manager.users.${config.custom.username}.programs.vscode.package}/bin/codium"; + firefox-esr = "${config.home-manager.users.${config.custom.username}.programs.firefox.finalPackage}/bin/firefox-esr"; + gnome-text-editor = "${pkgs.gnome-text-editor}/bin/gnome-text-editor"; + hyprctl = "${config.programs.hyprland.package}/bin/hyprctl"; + hyprlock = "${config.home-manager.users.${config.custom.username}.programs.hyprlock.package}/bin/hyprlock"; + hyprpicker = "${pkgs.hyprpicker}/bin/hyprpicker"; + inhibit = config.home-manager.users.${config.custom.username}.home.file.".local/bin/inhibit".source; + jq = "${pkgs.jq}/bin/jq"; + kill = "${pkgs.procps}/bin/kill"; + kitty = "${config.home-manager.users.${config.custom.username}.programs.kitty.package}/bin/kitty"; + left = config.home-manager.users.${config.custom.username}.home.file.".local/bin/left".source; + loginctl = "${pkgs.systemd}/bin/loginctl"; + menu = config.home-manager.users.${config.custom.username}.home.file.".local/bin/menu".source; + nautilus = "${pkgs.nautilus}/bin/nautilus"; + networkmanager_dmenu = "${pkgs.networkmanager_dmenu}/bin/networkmanager_dmenu"; + notify-send = "${pkgs.libnotify}/bin/notify-send"; + obsidian = "${pkgs.obsidian}/bin/obsidian"; + onlyoffice = "${pkgs.onlyoffice-bin}/bin/onlyoffice-desktopeditors --system-title-bar --xdg-desktop-portal"; + pkill = "${pkgs.procps}/bin/pkill"; + playerctl = "${pkgs.playerctl}/bin/playerctl"; + rofi-rbw = "${pkgs.rofi-rbw}/bin/rofi-rbw"; + rm = "${pkgs.coreutils}/bin/rm"; + screenshot = "~/.local/bin/screenshot"; + sleep = "${pkgs.coreutils}/bin/sleep"; + smile = "${pkgs.smile}/bin/smile"; + steam = "${config.programs.steam.package}/bin/steam"; + swayosd-client = "${pkgs.swayosd}/bin/swayosd-client"; + systemctl = "${pkgs.systemd}/bin/systemctl"; + toggle = "~/.local/bin/toggle"; + virt-manager = "${config.programs.virt-manager.package}/bin/virt-manager"; + vm = config.home-manager.users.${config.custom.username}.home.file.".local/bin/vm".source; + vrr = config.home-manager.users.${config.custom.username}.home.file.".local/bin/vrr".source; + walker = "${config.home-manager.users.${config.custom.username}.programs.walker.package}/bin/walker"; + waydroid = "${pkgs.waydroid}/bin/waydroid"; + window = config.home-manager.users.${config.custom.username}.home.file.".local/bin/window".source; + workspace = config.home-manager.users.${config.custom.username}.home.file.".local/bin/workspace".source; + zoom = config.home-manager.users.${config.custom.username}.home.file.".local/bin/zoom".source; + sway-audio-idle-inhibit = "${pkgs.sway-audio-idle-inhibit}/bin/sway-audio-idle-inhibit"; + waybar = "${config.home-manager.users.${config.custom.username}.programs.waybar.package}/bin/waybar"; + + cfg = config.custom.desktops.sway.settings; +in { + options.custom.desktops.sway.settings.enable = mkOption {default = false;}; + + config.home-manager.users.${config.custom.username} = mkIf cfg.enable { + # https://nix-community.github.io/home-manager/options.xhtml#opt-wayland.windowManager.sway.config + # https://github.com/swaywm/sway/blob/master/config.in + # https://i3wm.org/docs/ + #?? man 5 sway + wayland.windowManager.sway = { + config = { + #!! Module order causes many issues, use extraConfig + #// assigns = { }; + + #?? man sway-bar + bars = []; # Disable swaybars + + # https://i3wm.org/docs/userguide.html#client_colors + colors = let + common = { + background = "#002b36"; + border = "#073642"; + childBorder = "#073642"; + indicator = "#d33682"; + text = "#93a1a1"; + }; + in { + background = common.background; + + focused = + common + // { + background = "#6c71c4"; + border = "#6c71c4"; + childBorder = "#6c71c4"; + text = "#002b36"; + }; + + focusedInactive = + common + // { + border = "#93a1a1"; + childBorder = "#93a1a1"; + }; + + unfocused = + common + // { + border = "#002b36"; + childBorder = "#002b36"; + }; + + urgent = common; + }; + + # BUG: floating_modifier does not clear release binds + # https://github.com/swaywm/sway/issues/4505 + #// floating = { }; + + focus = { + # BUG: Does not switch workspace on activation + # https://github.com/swaywm/sway/issues/7912 + newWindow = "focus"; + + wrapping = "force"; + }; + + fonts.size = 11.0; + + # BUG: Other gaps may disable swayfx corners, fixed in master + # https://github.com/WillPower3309/swayfx/issues/93 + # https://github.com/swaywm/sway/pull/8017 + gaps.inner = 20; + + modes = {}; # Disable modes + + #?? wev + modifier = "Mod4"; + + # https://i3wm.org/docs/userguide.html#_automatically_starting_applications_on_i3_startup + startup = let + # Wrap exec in quotes + #?? "COMMAND" + always = command: {command = "'${command}'";}; + once = command: {command = "'${command}'";}; + in [ + # (always "${pkill} sway-audio-idle-inhibit; ${sway-audio-idle-inhibit}") + # (always "${pkill} vrr-fs; vrr-fs") + + # # TODO: Use graphical-session.target when merged upstream + # # https://github.com/NixOS/nixpkgs/pull/218716 + # (always "${pkill} waybar; ${waybar}") + + # (once "${rm} ~/.config/qalculate/qalc.dmenu.history") # Clear calc history + # (once "${rm} ~/.cache/cliphist/db") # Clear clipboard database + # (once firefox-esr) + ]; + + window.commands = let + command = command: {inherit command;}; + + # Boilerplate criteria + #?? criteria = <"ATTR"|{ATTRS = "EXPR"}> <"EXPR"|null> + criteria = attr: expr: { + criteria = with builtins; + if isAttrs attr + then (mapAttrs (a: e: "^${e}$") attr) + else { + ${attr} = + if isNull expr + then true + else "^${expr}$"; + }; + }; + + app = expr: criteria "app_id" expr; + floating = criteria "floating" null; + mark = expr: criteria "con_mark" expr; + title = expr: criteria "title" expr; + + attrs = attrs: criteria attrs null; + in [ + ### Defaults + # HACK: Prefer default_floating_border when fixed upstream + # https://github.com/swaywm/sway/issues/7360 + (floating // command "border normal 0") + + ### Workspaces + # 1 + (attrs { + app_id = "firefox"; + title = ".*Firefox.*"; + } + // command "layout tabbed") + (attrs { + app_id = "firefox"; + title = "Extension.*"; + } + // command "floating enable") + (mark "browser" // command "move to workspace 1") + + # terminal + (mark "terminal" // command "move to workspace terminal") + + ### Scratchpads + (mark "dropdown" // command "move to scratchpad") + + ### Sticky + (mark "pip" // command "border none, floating enable, sticky enable") + ]; + }; + + #!! Applies to every move/workspace invocation + # The inverse of --no-auto-back-and-forth would be preferable + #// workspaceAutoBackAndForth = true; + + #// workspaceLayout = ""; + + #// workspaceOutputAssign = [ ]; + + # Commands not currently configurable via options + extraConfig = '' + default_border pixel 2 + + # BUG: Does not work + # https://github.com/swaywm/sway/issues/7360 + default_floating_border normal 0 + + # BUG: Unknown/invalid command + #// primary_selection disabled + + titlebar_border_thickness 2 + + workspace 1 + ''; + }; + }; +} diff --git a/options/custom/desktops/sway/swayfx.nix b/options/custom/desktops/sway/swayfx.nix new file mode 100644 index 0000000..ee18321 --- /dev/null +++ b/options/custom/desktops/sway/swayfx.nix @@ -0,0 +1,42 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.custom.desktops.sway.swayfx; +in { + options.custom.desktops.sway.swayfx.enable = mkOption {default = false;}; + + config.home-manager.users.${config.custom.username} = mkIf cfg.enable { + # https://github.com/WillPower3309/swayfx + wayland.windowManager.sway = { + # BUG: DRM build failure + # https://github.com/nix-community/home-manager/issues/5379 + checkConfig = false; + + # Polyfill home-manager wrappers + # https://github.com/nix-community/home-manager/blob/master/modules/services/window-managers/i3-sway/sway.nix#L334 + package = with config.wayland.windowManager.sway; + pkgs.sway.override { + extraSessionCommands = extraSessionCommands; + extraOptions = extraOptions; + withBaseWrapper = wrapperFeatures.base; + withGtkWrapper = wrapperFeatures.gtk; + }; + + # https://github.com/WillPower3309/swayfx?tab=readme-ov-file#new-configuration-options + extraConfig = '' + corner_radius 12 + default_dim_inactive 0.25 + dim_inactive_colors.unfocused #002b36 + scratchpad_minimize enable + shadows enable + shadows_on_csd enable + smart_corner_radius enable + #// titlebar_separator disable + ''; + }; + }; +} diff --git a/options/custom/scripts/default.nix b/options/custom/scripts/default.nix index 243fc68..ffe5d00 100644 --- a/options/custom/scripts/default.nix +++ b/options/custom/scripts/default.nix @@ -106,6 +106,12 @@ in { jq libnotify ]) + (bash "mark" [ + coreutils + gnugrep + libnotify + sway + ]) (bash "menu" [ coreutils hyprland @@ -137,6 +143,11 @@ in { libnotify power-profiles-daemon ]) + (bash "scratchpad" [ + coreutils + libnotify + sway + ]) (bash "screenshot" [ argc coreutils @@ -145,6 +156,10 @@ in { libnotify swappy ]) + (bash "socket" [ + coreutils + procps + ]) (bash "toggle" [ gnugrep hyprland @@ -165,6 +180,11 @@ in { libnotify tailscale ]) + (bash "vrr-fs" [ + jq + libnotify + sway + ]) (bash "vrr" [ hyprland jq @@ -185,6 +205,11 @@ in { hyprland jq ]) + (bash "workspace-sway" [ + jq + libnotify + sway + ]) (bash "workspace" [ argc coreutils diff --git a/options/custom/scripts/mark.sh b/options/custom/scripts/mark.sh new file mode 100644 index 0000000..2e79ba4 --- /dev/null +++ b/options/custom/scripts/mark.sh @@ -0,0 +1,12 @@ +#! /usr/bin/env bash + +# Exec with mark if mark is not present in tree then go to workspace +#?? mark MARK COMMANDS + +trap "notify-send '> mark' 󰃤" ERR + +if ! swaymsg -t get_marks | grep "$1"; then + swaymsg -- exec "${@:2}" || exit 1 + sleep 0.1 + swaymsg -- mark --replace "$1" +fi diff --git a/options/custom/scripts/scratchpad.sh b/options/custom/scripts/scratchpad.sh new file mode 100644 index 0000000..c69a80f --- /dev/null +++ b/options/custom/scripts/scratchpad.sh @@ -0,0 +1,11 @@ +#! /usr/bin/env bash + +# Toggle scratchpad window and launch with mark if needed +#?? scratchpad MARK COMMANDS + +trap "notify-send '> scratchpad' 󰃤" ERR + +mark "$@" && + sleep 0.1 # Ensure surface is created + +swaymsg "[con_mark=$1] scratchpad show" diff --git a/options/custom/scripts/socket.sh b/options/custom/scripts/socket.sh new file mode 100644 index 0000000..a23c924 --- /dev/null +++ b/options/custom/scripts/socket.sh @@ -0,0 +1,12 @@ +#! /usr/bin/env bash + +# Wrap commands in sway socket +# https://wiki.archlinux.org/title/Sway#Sway_socket_not_detected + +pid="$(pgrep -x sway)" +uid="$(id -u)" + +export SWAYSOCK=/run/user/"$uid"/sway-ipc."$uid"."$pid".sock +# TODO: Add Hyprland socket + +"$@" diff --git a/options/custom/scripts/vrr-fs.sh b/options/custom/scripts/vrr-fs.sh new file mode 100644 index 0000000..9768474 --- /dev/null +++ b/options/custom/scripts/vrr-fs.sh @@ -0,0 +1,33 @@ +#! /usr/bin/env bash + +# https://gist.github.com/GrabbenD/adc5a7a863cbd1553461376cf4c50467 + +trap "notify-send '> vrr-fs' 󰃤" ERR + +# List of supported outputs for VRR using serial number +#?? swaymsg -t get_outputs +output_vrr_whitelist=( + "HNBW401587" +) + +# Toggle VRR for fullscreen apps in specified displays +swaymsg -t subscribe -m '[ "window" ]' | while read -r window_json; do + window_event="$(echo "${window_json}" | jq -r '.change')" + + # Process only focus change and fullscreen toggle + if [[ "$window_event" = "focus" || "$window_event" = "fullscreen_mode" ]]; then + output_json="$(swaymsg -t get_outputs | jq -r '.[] | select(.focused == true)')" + output_name="$(echo "${output_json}" | jq -r '.name')" + output_serial="$(echo "${output_json}" | jq -r '.serial')" + + # Use only VRR in whitelisted outputs + if [[ "${output_vrr_whitelist[*]}" =~ ${output_serial} ]]; then + output_vrr_status="$(echo "${output_json}" | jq -r '.adaptive_sync_status')" + window_fullscreen_status="$(echo "${window_json}" | jq -r '.container.fullscreen_mode')" + + # Only update output if necessary to avoid flickering + [[ "$output_vrr_status" = "disabled" && "$window_fullscreen_status" = "1" ]] && swaymsg output "${output_name}" adaptive_sync 1 + [[ "$output_vrr_status" = "enabled" && "$window_fullscreen_status" = "0" ]] && swaymsg output "${output_name}" adaptive_sync 0 + fi + fi +done diff --git a/options/custom/scripts/workspace-sway.sh b/options/custom/scripts/workspace-sway.sh new file mode 100644 index 0000000..e6219f7 --- /dev/null +++ b/options/custom/scripts/workspace-sway.sh @@ -0,0 +1,21 @@ +#! /usr/bin/env bash + +# https://github.com/dongdigua/configs/blob/main/sway/scripts/workspace.sh + +trap "notify-send '> workspace' 󰃤" ERR + +current_workspace="$(swaymsg -t get_outputs | jq -r '.[] | select(.focused).current_workspace')" + +if [[ "$1" == "prev" ]]; then + to_workspace=$((current_workspace - 1)) +elif [[ "$1" == "next" ]]; then + to_workspace=$((current_workspace + 1)) +fi + +if (("$to_workspace" == 11)); then + to_workspace=1 +elif (("$to_workspace" == 0)); then + to_workspace=10 +fi + +swaymsg workspace number "$to_workspace"