2024-02-21 23:18:57 +00:00
{ config , lib , pkgs , . . . }:
# docs: https://nixos.wiki/wiki/Sway
# sway-config docs: `man 5 sway`
let
cfg = config . sane . programs . sway ;
wrapSway = configuredSway : let
swayLauncher = pkgs . writeShellScriptBin " s w a y " ''
2024-03-31 05:20:20 +00:00
test - e " $ ( d i r n a m e " $ SWAYSOCK " ) " || \
echo " w a r n i n g : r e q u i r e d d i r e c t o r y n o t f o u n d ( c r e a t e i t ? ) : $ ( d i r n a m e " $ SWAYSOCK " ) "
test - e " $ ( d i r n a m e " $ XDG_RUNTIME_DIR / $ WAYLAND_DISPLAY " ) " || \
echo " w a r n i n g : r e q u i r e d d i r e c t o r y n o t f o u n d ( c r e a t e i t ? ) : $ ( d i r n a m e " $ XDG_RUNTIME_DIR / $ WAYLAND_DISPLAY " ) "
2024-03-31 05:52:05 +00:00
test - e /tmp/.X11-unix || \
echo " w a r n i n g : r e q u i r e d d i r e c t o r y n o t f o u n d ( c r e a t e i t ? ) : / t m p / . X 1 1 - u n i x "
2024-03-18 02:02:24 +00:00
# delete DISPLAY-related vars from env before launch, else sway will try to connect to a remote display.
# (consider: nested sway sessions, where sway actually has a reason to read these)
2024-03-23 17:59:37 +00:00
exec env - u DISPLAY - u WAYLAND_DISPLAY " D E S I R E D _ W A Y L A N D _ D I S P L A Y = $ W A Y L A N D _ D I S P L A Y " $ { configuredSway } /bin/sway 2 > & 1
2024-02-21 23:18:57 +00:00
'' ;
in
pkgs . symlinkJoin {
inherit ( configuredSway ) meta version ;
name = " s w a y - w r a p p e d " ;
paths = [
swayLauncher
configuredSway
] ;
2024-02-27 16:11:10 +00:00
passthru . sway-unwrapped = configuredSway ;
2024-02-21 23:18:57 +00:00
} ;
2024-02-23 01:05:24 +00:00
swayPackage = wrapSway (
2024-03-11 05:53:14 +00:00
pkgs . waylandPkgs . sway-unwrapped . override {
wlroots = ( pkgs . waylandPkgs . wlroots . override {
# wlroots seems to launch Xwayland itself, and i can't easily just do that myself externally.
# so in order for the Xwayland it launches to be sandboxed, i need to patch the sandboxed version in here.
2024-02-27 16:11:10 +00:00
xwayland = config . sane . programs . xwayland . package ;
2024-03-11 05:53:14 +00:00
} ) . overrideAttrs ( upstream : {
# 2023/09/08: fix so clicking a notification can activate the corresponding window.
# - test: run dino, receive a message while tabbed away, click the desktop notification.
# - if sway activates the dino window (i.e. colors the workspace and tab), then all good
# - do all of this with only a touchscreen (e.g. on mobile phone) -- NOT a mouse/pointer
# 2023/12/17: this patch is still necessary
## what this patch does:
# - allows any wayland window to request activation, at any time.
# - traditionally, wayland only allows windows to request activation if
# the client requesting to transfer control has some connection to a recent user interaction.
# - e.g. the active window may transfer control to any window
# - a window which was very recently active may transfer control to itself
## alternative (longer-term) solutions:
# - fix this class of bug in gtk:
# - <https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/5782>
# - N.B.: this linked PR doesn't actually fix it
# - add xdg_activation_v1 support to SwayNC (my notification daemon):
# - <https://github.com/ErikReider/SwayNotificationCenter/issues/71>
# - mako notification daemon supports activation, can use as a reference
# - all of ~30 LoC, looks straight-forward
# - however, it's not clear that gtk4 (or dino) actually support this mode of activation.
# - i.e. my experience with dino is the same using mako as with SwayNC
postPatch = ( upstream . postPatch or " " ) + ''
substituteInPlace types/wlr_xdg_activation_v1.c \
- - replace-fail ' if ( token- > seat != NULL ) ' ' if ( false && token- > seat != NULL ) '
'' ;
} ) ;
2024-02-27 16:11:10 +00:00
# about xwayland:
# - required by many electron apps, though some electron apps support NIXOS_OZONE_WL=1 for native wayland.
# - when xwayland is enabled, KOreader incorrectly chooses the X11 backend
# -> slower; blurrier
# - xwayland uses a small amount of memory (like 30MiB, IIRC?)
enableXWayland = config . sane . programs . xwayland . enabled ;
}
2024-02-23 01:05:24 +00:00
) ;
2024-02-21 23:18:57 +00:00
in
{
sane . programs . sway = {
configOption = with lib ; mkOption {
default = { } ;
type = types . submodule {
options = {
extra_lines = mkOption {
type = types . lines ;
description = ''
extra lines to append to the sway config
'' ;
default = ''
# XXX: sway needs exclusive control of XF86Audio{Raise,Lower}Volume, so assign this from a block that it can override.
# TODO: factor the bindings out into proper options and be less hacky?
bindsym - - locked XF86AudioRaiseVolume exec $ volume_up
bindsym - - locked XF86AudioLowerVolume exec $ volume_down
'' ;
} ;
background = mkOption {
type = types . path ;
} ;
font = mkOption {
type = types . str ;
default = " p a n g o : m o n o s p a c e 1 1 " ;
description = ''
default font ( for e . g . window titles )
'' ;
} ;
mod = mkOption {
type = types . str ;
default = " M o d 4 " ;
description = ''
Super key ( for non-application shortcuts ) .
- " M o d 1 " for Alt
- " M o d 4 " for logo key
'' ;
} ;
workspace_layout = mkOption {
type = types . str ;
default = " d e f a u l t " ;
description = ''
how to arrange windows within new workspaces , by default :
- " d e f a u l t " ( split )
- " t a b b e d "
- etc
'' ;
} ;
} ;
} ;
} ;
packageUnwrapped = swayPackage ;
suggestedPrograms = [
" g u i A p p s "
" b l u e b e r r y " # GUI bluetooth manager
" b r i g h t n e s s c t l "
" c o n k y " # for a nice background
2024-05-10 17:51:37 +00:00
# "fcitx5" # input method; emoji # TODO(2024/05/10): this broke 2-ish weeks ago, glitches input into any textbox, possibly only when xwayland is enabled?
2024-02-21 23:18:57 +00:00
" f o n t c o n f i g "
# "gnome.gnome-bluetooth" # XXX(2023/05/14): broken
# "gnome.gnome-control-center" # XXX(2023/06/28): depends on webkitgtk4_1
2024-04-27 09:51:55 +00:00
" n e t w o r k m a n a g e r "
2024-02-21 23:23:52 +00:00
" p i p e w i r e "
2024-02-21 23:18:57 +00:00
" p l a y e r c t l " # for waybar & particularly to have playerctld running
2024-02-26 21:22:03 +00:00
" r o f i " # menu/launcher
2024-02-28 14:44:05 +00:00
" r o f i - s n i p p e t s "
2024-05-14 04:17:05 +00:00
" s 6 - r c " # used by sway config
2024-03-02 05:57:24 +00:00
" s a n e - s c r e e n s h o t "
2024-04-30 19:03:08 +00:00
" s a n e - o p e n "
2024-03-11 07:53:48 +00:00
# "splatmoji" # used by sway config
2024-02-21 23:18:57 +00:00
" s w a y - c o n t r i b . g r i m s h o t " # used by sway config
2024-03-06 20:55:01 +00:00
" s w a y i d l e " # enable if you need it
2024-02-21 23:18:57 +00:00
" s w a y l o c k " # used by sway config
" s w a y n o t i f i c a t i o n c e n t e r " # notification daemon
2024-03-08 11:43:44 +00:00
" s y s v o l " # volume notifier
2024-02-21 23:18:57 +00:00
" u n l 0 k r " # greeter
" w a y b a r " # used by sway config
" w d i s p l a y s " # like xrandr
2024-03-08 09:56:15 +00:00
" w i r e p l u m b e r " # used by sway config
2024-02-21 23:18:57 +00:00
" w l - c l i p b o a r d "
" x d g - d e s k t o p - p o r t a l "
# xdg-desktop-portal-gtk provides portals for:
# - org.freedesktop.impl.portal.Access
# - org.freedesktop.impl.portal.Account
# - org.freedesktop.impl.portal.DynamicLauncher
# - org.freedesktop.impl.portal.Email
# - org.freedesktop.impl.portal.FileChooser
# - org.freedesktop.impl.portal.Inhibit
# - org.freedesktop.impl.portal.Notification
# - org.freedesktop.impl.portal.Print
# and conditionally (i.e. unless buildPortalsInGnome = false) for:
# - org.freedesktop.impl.portal.AppChooser (@appchooser_iface@)
# - org.freedesktop.impl.portal.Lockdown (@lockdown_iface@)
# - org.freedesktop.impl.portal.Settings (@settings_iface@)
# - org.freedesktop.impl.portal.Wallpaper (@wallpaper_iface@)
" x d g - d e s k t o p - p o r t a l - g t k "
# xdg-desktop-portal-wlr provides portals for screenshots/screen sharing
" x d g - d e s k t o p - p o r t a l - w l r "
" x d g - t e r m i n a l - e x e c " # used by sway config
] ;
2024-03-02 06:28:06 +00:00
sandbox . method = " b w r a p " ;
sandbox . wrapperType = " i n p l a c e " ;
2024-03-31 05:52:05 +00:00
sandbox . net = " a l l " ; # TODO: shouldn't be needed! but without this, mouse/kb hotplug doesn't work.
2024-03-02 06:28:06 +00:00
sandbox . whitelistAudio = true ; # it runs playerctl directly
sandbox . whitelistDbus = [ " s y s t e m " " u s e r " ] ; # to e.g. launch apps
sandbox . whitelistDri = true ;
2024-03-23 15:33:23 +00:00
sandbox . whitelistX = true ; # sway invokes xwayland itself
2024-03-02 06:28:06 +00:00
sandbox . whitelistWayland = true ;
2024-03-31 05:52:05 +00:00
sandbox . extraRuntimePaths = [ " / " ] ; # TODO: should need just "sway". but even if i sandbox EVERY entry under run individually, it fails!
2024-03-02 06:28:06 +00:00
sandbox . extraPaths = [
" / d e v / i n p u t "
2024-03-31 05:52:05 +00:00
" / r u n / s y s t e m d / s e s s i o n s "
2024-03-02 06:28:06 +00:00
" / r u n / u d e v "
" / s y s / c l a s s / b a c k l i g h t "
" / s y s / c l a s s / d r m "
" / s y s / c l a s s / i n p u t "
" / s y s / d e v / c h a r "
" / s y s / d e v i c e s "
] ;
2024-03-08 03:44:33 +00:00
sandbox . extraHomePaths = [
# bind ALL of ~/.config/sway into the sandbox instead of just the individual configs.
# this way `swaymsg -- reload` can work even if the fd for ~/.config/sway/config changes.
" . c o n f i g / s w a y "
] ;
2024-03-02 06:28:06 +00:00
2024-02-21 23:18:57 +00:00
fs . " . c o n f i g / x d g - d e s k t o p - p o r t a l / s w a y - p o r t a l s . c o n f " . symlink . text = ''
# portals.conf docs: <https://flatpak.github.io/xdg-desktop-portal/docs/portals.conf.html>
[ preferred ]
default = wlr ; gtk
'' ;
2024-03-01 03:18:37 +00:00
fs . " . c o n f i g / s w a y / c o n f i g " . symlink . target = pkgs . substituteAll {
src = ./sway-config ;
inherit ( cfg . config )
background
extra_lines
font
mod
workspace_layout
;
xwayland = if config . sane . programs . xwayland . enabled then " e n a b l e " else " d i s a b l e " ;
2024-02-21 23:18:57 +00:00
} ;
2024-03-18 02:02:24 +00:00
env . XDG_CURRENT_DESKTOP = " s w a y " ;
2024-03-20 03:19:30 +00:00
# sway defaults to auto-generating a unix domain socket named "sway-ipc.$UID.NNNN.sock",
# which allows for multiple sway sessions under the same user.
2024-03-23 11:33:58 +00:00
# but the unpredictability makes static sandboxing & such difficult, so hardcode it.
# place it in a subdirectory for the same reason:
env . SWAYSOCK = " $ X D G _ R U N T I M E _ D I R / s w a y / s w a y - i p c . s o c k " ;
2024-03-18 02:02:24 +00:00
# TODO: ensure this is reliable? might not work across sway restarts, etc.
env . DISPLAY = " : 0 " ;
2024-03-23 15:33:23 +00:00
# docs: <https://discourse.ubuntu.com/t/environment-variables-for-wayland-hackers/12750>
2024-03-23 17:59:37 +00:00
# N.B.: gtk apps support absolute paths for this; webkit apps (e.g. geary) support only relative paths (relative to $XDG_RUNTIME_DIR)
env . WAYLAND_DISPLAY = " w a y l a n d / w a y l a n d - 1 " ;
2024-03-18 02:02:24 +00:00
services . sway = {
2024-03-21 04:51:56 +00:00
description = " s w a y : t i l i n g w a y l a n d d e s k t o p e n v i r o n m e n t " ;
2024-03-23 13:04:48 +00:00
dependencyOf = [ " g r a p h i c a l - s e s s i o n " ] ;
2024-03-23 15:33:23 +00:00
command = pkgs . writeShellScript " s w a y - s t a r t " ''
# have to create these directories before launching sway so that they're available in the sandbox
mkdir - p " $ ( d i r n a m e " $ SWAYSOCK " ) "
2024-03-23 17:59:37 +00:00
mkdir - p " $ ( d i r n a m e " $ XDG_RUNTIME_DIR / $ WAYLAND_DISPLAY " ) "
2024-03-23 15:33:23 +00:00
mkdir - p /tmp/.X11-unix # for Xwayland
exec sway
'' ;
2024-03-23 17:59:37 +00:00
readiness . waitExists = [ " $ S W A Y S O C K " " $ X D G _ R U N T I M E _ D I R / $ W A Y L A N D _ D I S P L A Y " ] ;
2024-02-21 23:18:57 +00:00
} ;
2024-03-23 13:04:48 +00:00
# link the graphical-session into the default target, so sway gets auto-started
services . graphical-session . partOf = [ " d e f a u l t " ] ;
2024-02-21 23:18:57 +00:00
} ;
sane . gui . gtk = lib . mkIf cfg . enabled {
enable = lib . mkDefault true ;
# gtk-theme = lib.mkDefault "Fluent-Light-compact";
gtk-theme = lib . mkDefault " T o k y o n i g h t - L i g h t - B " ;
# icon-theme = lib.mkDefault "HighContrast"; # 4/5 coverage on moby
# icon-theme = lib.mkDefault "WhiteSur"; # 3.5/5 coverage on moby, but it provides a bunch for Fractal/Dino
# icon-theme = lib.mkDefault "Humanity"; # 3.5/5 coverage on moby, but it provides the bookmark icon
# icon-theme = lib.mkDefault "Paper"; # 3.5/5 coverage on moby, but it provides the bookmark icon
# icon-theme = lib.mkDefault "Nordzy"; # 3/5 coverage on moby
# icon-theme = lib.mkDefault "Fluent"; # 3/5 coverage on moby
# icon-theme = lib.mkDefault "Colloid"; # 3/5 coverage on moby
# icon-theme = lib.mkDefault "Qogir"; # 2.5/5 coverage on moby
# icon-theme = lib.mkDefault "rose-pine-dawn"; # 2.5/5 coverage on moby
# icon-theme = lib.mkDefault "Flat-Remix-Grey-Light"; # requires qtbase
} ;
2024-02-21 23:46:25 +00:00
2024-02-21 23:18:57 +00:00
# TODO: this can go elsewhere
2024-02-21 23:46:25 +00:00
hardware . bluetooth . enable = lib . mkIf cfg . enabled true ;
services . blueman . enable = lib . mkIf cfg . enabled true ;
2024-02-21 23:18:57 +00:00
# gsd provides Rfkill, which is required for the bluetooth pane in gnome-control-center to work
# services.gnome.gnome-settings-daemon.enable = true;
# start the components of gsd we need at login
# systemd.user.targets."org.gnome.SettingsDaemon.Rfkill".wantedBy = [ "graphical-session.target" ];
# go ahead and `systemctl --user cat gnome-session-initialized.target`. i dare you.
# the only way i can figure out how to get Rfkill to actually load is to just disable all the shit it depends on.
# it doesn't actually seem to need ANY of them in the first place T_T
# systemd.user.targets."gnome-session-initialized".enable = false;
# bluez can't connect to audio devices unless pipewire is running.
# a system service can't depend on a user service, so just launch it at graphical-session
}