(define-module (tw services desktop) #:use-module (gnu) #:use-module (gnu home services) #:use-module (gnu home services mail) #:use-module (gnu home services mcron) #:use-module (gnu home services shepherd) #:use-module (gnu home services sound) #:use-module ((gnu packages admin) #:select (ansible shepherd)) #:use-module ((gnu packages aidc) #:select (qrencode)) #:use-module ((gnu packages bittorrent) #:select (transmission-remote-gtk)) #:use-module ((gnu packages calendar) #:select (khal)) #:use-module ((gnu packages check) #:select (actionlint)) #:use-module ((gnu packages chromium) #:select (ungoogled-chromium)) #:use-module ((gnu packages compton) #:select (picom)) #:use-module ((gnu packages dav) #:select (vdirsyncer)) #:use-module ((gnu packages fonts) #:select (font-fira-code font-fira-sans font-google-noto font-google-noto-emoji font-google-noto-sans-cjk font-google-noto-serif-cjk font-hermit font-inconsolata font-libertinus font-openmoji)) #:use-module ((gnu packages freedesktop) #:select (elogind wev wtype xdg-utils xdg-desktop-portal xdg-desktop-portal-wlr)) #:use-module ((gnu packages games) #:select (bsd-games)) #:use-module ((gnu packages gimp) #:select (gimp)) #:use-module ((gnu packages gl) #:select (mesa)) #:use-module ((gnu packages gnome) #:select (dconf dconf-editor network-manager-applet vinagre)) #:use-module ((gnu packages gnome-xyz) #:select (papirus-icon-theme)) #:use-module ((gnu packages gnuzilla) #:select (icecat)) #:use-module ((gnu packages graphviz) #:select (xdot)) #:use-module ((gnu packages image) #:select (grim slurp)) #:use-module ((gnu packages image-viewers) #:select (imv)) #:use-module ((gnu packages inkscape) #:select (inkscape)) #:use-module ((gnu packages libreoffice) #:select (libreoffice)) #:use-module ((gnu packages linux) #:select (acpilight procps powertop radeontop)) #:use-module ((gnu packages mail) #:select (aerc khard)) #:use-module ((gnu packages maths) #:select (qalculate-gtk)) #:use-module ((gnu packages messaging) #:select (nheko)) #:use-module ((gnu packages networking) #:select (blueman)) #:use-module ((gnu packages package-management) #:select (modules)) #:use-module ((gnu packages pdf) #:select (zathura zathura-pdf-poppler zathura-ps poppler-qt6)) #:use-module ((gnu packages pulseaudio) #:select (pulsemixer)) #:use-module ((gnu packages python-build) #:select (python-setuptools-scm)) #:use-module ((gnu packages python-check) #:select (python-mypy python-tox)) #:use-module ((gnu packages ssh) #:select (pdsh)) #:use-module ((gnu packages syndication) #:select (newsboat)) #:use-module ((gnu packages tcl) #:select (tk)) #:use-module ((gnu packages terminals) #:select (foot kitty)) #:use-module ((gnu packages tex) #:select (texlive-scheme-small texlive-latexmk texlive-moderncv texlive-fontawesome5 texlive-multirow texlive-arydshln texlive-libertine texlive-inconsolata texlive-newtx texlive-babel texlive-csquotes texlive-siunitx texlive-minted texlive-svg texlive-hyperref texlive-capt-of texlive-ulem texlive-trimspaces texlive-transparent texlive-graphics texlive-tools texlive-wrapfig texlive-amsmath texlive-amsfonts texlive-txfonts)) #:use-module ((gnu packages version-control) #:select (git)) #:use-module ((gnu packages video) #:select (mpv obs obs-pipewire-audio-capture)) #:use-module ((gnu packages web-browsers) #:select (lynx)) #:use-module ((gnu packages wm) #:select (i3lock dunst polybar swaybg swayfx swayidle swaylock waybar)) #:use-module ((gnu packages xdisorg) #:select (arandr numlockx hsetroot rofi rofi-calc rofi-wayland wl-clipboard wob xclip xdotool xsel)) #:use-module ((gnu packages xfce) #:select (xfce4-screenshooter)) #:use-module ((gnu packages xorg) #:select (xdpyinfo xev xfd xfontsel xinput xkill xprop xrandr xset xrdb xwininfo setxkbmap)) #:use-module ((games packages minecraft) #:select (prismlauncher)) #:use-module ((nongnu packages nvidia) #:select (nvda)) #:use-module ((nongnu packages nvidia) #:select (nvidia-system-monitor)) #:use-module (gnu services configuration) #:use-module (guix gexp) #:use-module (guix packages) #:use-module ((guix records) #:select (match-record)) #:use-module ((nongnu packages game-client) #:select (steam steam-nvidia)) #:use-module ((nongnu packages messaging) #:select (signal-desktop)) #:use-module ((nongnu packages nvidia) #:select (nvda nvidia-system-monitor)) #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) #:use-module (tw gexp) #:use-module ((tw packages alice) #:select (python-alibuild python-alidistlint)) #:use-module (tw packages catppuccin) #:use-module ((tw packages ci) #:select (hashicorp-packer-bin)) #:use-module ((tw packages games) #:select (szio-solitaire)) #:use-module ((tw packages mail) #:select (mutt_oauth2.py)) #:use-module ((tw packages scanner) #:select (simple-scan/airscan)) #:use-module ((tw packages xorg) #:select (xcwd)) #:use-module ((tw packages zoom) #:select (zoom/wayland)) #:use-module (tw theme) #:export (home-desktop-configuration home-monitor-configuration home-desktop-service-type home-wayland-desktop-configuration home-wayland-desktop-service-type home-gaming-configuration home-gaming-service-type home-pim-configuration home-pim-service-type)) (define common-desktop-packages (list ;; CLI tools bsd-games powertop (list git "send-email") pdsh qrencode texlive-scheme-small texlive-latexmk ;; For CV: texlive-moderncv texlive-fontawesome5 texlive-multirow texlive-arydshln texlive-libertine texlive-inconsolata texlive-newtx texlive-babel texlive-csquotes texlive-siunitx ;; For org-mode export: texlive-minted texlive-svg texlive-hyperref texlive-capt-of texlive-ulem texlive-trimspaces texlive-transparent texlive-graphics texlive-tools texlive-wrapfig texlive-amsmath texlive-amsfonts texlive-libertine texlive-newtx texlive-txfonts texlive-inconsolata ;; Work actionlint ansible hashicorp-packer-bin modules vinagre python-tox python-setuptools-scm python-mypy ;; Fonts font-hermit font-inconsolata font-fira-code font-fira-sans font-libertinus ;; Base Noto doesn't include CJK, so install those separately. font-google-noto font-google-noto-sans-cjk font-google-noto-serif-cjk font-google-noto-emoji font-openmoji ; for polybar ;; Theming papirus-icon-theme catppuccin-gtk-theme catppuccin-mocha-dark-cursors ;; Games (larger games installed in ~/.guix-profile to avoid frequent huge downloads). szio-solitaire ;; 0ad flightgear freeciv simutrans/pak128 warzone2100 widelands pioneer ;; GUI applications common to X11 and Wayland ;; acpilight is a drop-in xbacklight replacement, as xbacklight doesn't work on my system. acpilight blueman gimp inkscape icecat imv libreoffice mpv pulsemixer simple-scan/airscan transmission-remote-gtk tk xdg-utils xdot zoom/wayland zathura zathura-ps zathura-pdf-poppler poppler-qt6 ; for pdfunite, pdfseparate etc ungoogled-chromium ; needed e.g. for Interrail site & DRM video dconf dconf-editor)) ; required for config by blueman, cozy, ... (define common-xdg-configs `(("alibuild/disable-analytics" ; All alibuild needs is an empty file. ,(plain-file "alibuild-disable-analytics" "")) ("gtk-2.0/gtkrc" ,(local-file "files/gtk2.ini")) ("gtk-3.0/settings.ini" ,(local-file "files/gtk3.ini")) ("kdeglobals" ,catppuccin-kdeglobals) ("mimeapps.list" ,(local-file "files/mimeapps.list")) ("zathura/zathurarc" ,(local-file "files/zathurarc")) ("zathura/catppuccin" ,catppuccin-zathura))) (define common-home-files `((".icons/default/index.theme" ,(local-file "files/cursors.ini")) ;; With #:recursive? #t, Guix keeps the files' permission bits, i.e. makes them executable. (".local/bin/sessionmenu" ,(local-file "files/sessionmenu" #:recursive? #t)) (".local/bin/passmenu" ,(local-file "files/passmenu" #:recursive? #t)) (".local/bin/volume" ,(local-file "files/volume" #:recursive? #t)))) ;; Desktop services, including monitor layout (define-maybe/no-serialization string) (define (list-of-strings? thing) (and (list? thing) (every string? thing))) (define-configuration/no-serialization home-monitor-configuration (name string "The monitor's name in X11.") (xrandr-options (list-of-strings '()) "Options to pass to xrandr to configure this monitor.")) (define (list-of-monitors? thing) (and (list? thing) (every home-monitor-configuration? thing))) (define-configuration/no-serialization home-desktop-configuration (nvidia-driver? (boolean #f) "Adapt the X11 desktop for the proprietary NVIDIA driver?") (desktop-background (file-like (file-append tw-background "/share/backgrounds/tw/blobs.png")) "An image that will be set as the desktop background.") (battery-name maybe-string "The device name of the system's battery, if any. See @code{/sys/class/power_supply}.") (ac-adapter-name maybe-string "The device name of the system's mains power supply, if any. See @code{/sys/class/power_supply}.") ;; The default mirrors the previously hard-coded value from polybar.ini, for ;; compatibility. It is probably not a great default; "0" might be better. (thermal-zone (string "1") "The number of the thermal sensor used to show the system temperature. Run @code{grep '^' /sys/class/thermal/thermal_zone*/type} to list zones and their types.") (monitors list-of-monitors "List of monitor declarations to apply.")) (define polybar-wrapper (program-file "polybar-wrapper" #~(begin ;; This wrapper program checks that the monitor we want to start ;; polybar on is actually connected. (use-modules (ice-9 popen) (ice-9 rdelim)) (let* ((connected-str (string-append (getenv "POLYBAR_MONITOR") " connected")) (xrandr (open-pipe* OPEN_READ #$(file-append xrandr "/bin/xrandr") "-q")) (monitor-connected? (let loop ((line (read-line xrandr))) (cond ((eof-object? line) #f) ; we didn't find our monitor connected ((string-prefix? connected-str line) #t) ; the monitor we want is connected (else (loop (read-line xrandr))))))) ; keep looking (close-pipe xrandr) (when monitor-connected? (execl #$(file-append polybar "/bin/polybar") "polybar")))))) (define (polybar-service monitor battery-name ac-adapter-name thermal-zone) (shepherd-service (documentation (string-append "Polybar desktop bar for monitor " monitor ".")) (provision (list (symbol-append 'polybar- (string->symbol monitor)))) (requirement '(xorg-setup)) (start #~(make-forkexec-constructor (list #$polybar-wrapper) #:environment-variables (cons* #$(string-append "POLYBAR_MONITOR=" monitor) #$(string-append "POLYBAR_BATTERY=" (maybe-value battery-name "")) #$(string-append "POLYBAR_AC_ADAPTER=" (maybe-value ac-adapter-name "")) #$(string-append "POLYBAR_THERMAL_ZONE=" thermal-zone) (default-environment-variables)))) (stop #~(make-kill-destructor)))) (define (home-packages config) "Install packages I use frequently." (cons* ;; i3 and Xorg. i3 itself must be installed system-wide for gdm to pick it up. arandr dunst hsetroot kitty polybar rofi rofi-calc xclip xcwd xdotool xdpyinfo xev xfd xfontsel xinput xkill xprop xrandr xrdb xsel xset xwininfo xfce4-screenshooter ; only supports X11 for now: https://gitlab.xfce.org/apps/xfce4-screenshooter/-/merge_requests/52 common-desktop-packages)) (define xfce4-screenshooter.conf (mixed-text-file "xfce4-screenshooter.conf" "\ app=" imv "/bin/imv custom_action_command=none last_user= last_extension=png screenshot_dir=file:/home/timo/pictures enable_imgur_upload=false show_in_folder=false action=1 delay=0 region=3 show_mouse=0 show_border=1 ")) (define (home-xdg-configs config) "Configuration files that follow the XDG basedir spec." `(;; X11 environment configuration. ("X11/XCompose" ,(local-file "files/XCompose")) ; see also: $XCOMPOSEFILE variable ;; Configuration files for GUI programs in $XDG_CONFIG_HOME. Some ;; of these may also work under Wayland, but some are X11-specific. ("dunst/dunstrc" ,(local-file "files/dunstrc")) ("dunst/dunstrc.d/50-catppuccin.conf" ,catppuccin-dunstrc) ("i3/config" ,(local-file "files/i3.conf")) ;; TODO: "kdeglobals" works for some programs (e.g. kdeconnect-app), ;; but not for others (e.g. nheko, kdeconnect-settings)... ("kitty/diff.conf" ,(combined-text-file "kitty-diff.conf" (plain-file "kitty-diff-custom.conf" "pygments_style bw\n") catppuccin-kitty-diff)) ("kitty/kitty.conf" ,(combined-text-file "kitty.conf" (local-file "files/kitty.conf") catppuccin-kitty)) ("polybar/config.ini" ,(local-file "files/polybar.ini")) ("polybar/catppuccin.ini" ,catppuccin-polybar) ("rofi/config.rasi" ,(local-file "files/rofi.rasi")) ("rofi/themes/catppuccin.rasi" ,catppuccin-rofi) ("xfce4/xfce4-screenshooter" ,xfce4-screenshooter.conf) ,@common-xdg-configs)) (define (home-files config) "Extra configuration files and binaries that don't follow the XDG spec." `(;; https://sw.kovidgoyal.net/kitty/kittens/diff/ (".local/bin/kdiff" ; show a diff ,(program-file "kdiff" #~(apply execl #$(file-append kitty "/bin/kitty") "kitty" "+kitten" "diff" (cdr (command-line))))) (".local/bin/icat" ; kitty's "catimg" equivalent ,(program-file "icat" #~(apply execl #$(file-append kitty "/bin/kitty") "kitty" "+kitten" "icat" (cdr (command-line))))) (".local/bin/screenlock" ,(program-file "screenlock" #~(begin ; Wrapper around i3lock to turn off the screen and pause notifications. (system* #$(file-append dunst "/bin/dunstctl") "set-paused" "true") (system* #$(file-append xset "/bin/xset") "dpms" "0" "0" "5") ;; i3lock needs to have a `pam-root-service-type' entry or be ;; installed as a setuid binary for this to work. (system* #$(file-append i3lock "/bin/i3lock") "-nc" #$catppuccin-background-color) (system* #$(file-append xset "/bin/xset") "dpms" "600" "600" "600") (system* #$(file-append dunst "/bin/dunstctl") "set-paused" "false")))) ,@common-home-files)) (define (home-environment config) `(("TERMINAL" . "kitty") ("QT_X11_NO_MITSHM" . "1") ; fixes a Steam issue: https://gitlab.com/nonguix/nonguix/-/issues/267 ;; Smooth trackpad scrolling in Firefox/Icecat. ;; https://wiki.archlinux.org/index.php/Firefox/Tweaks#Pixel-perfect_trackpad_scrolling ("MOZ_USE_XINPUT2" . "1") ("_JAVA_OPTIONS" . ,(string-append "$_JAVA_OPTIONS${_JAVA_OPTIONS:+ }" "-Dawt.useSystemAAFontSettings=on -Dswing.aatext=true " "-Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel " "-Dswing.crossplatformlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel")))) (define (home-shepherd-services config) "Run various daemons in my user profile." (match-record config (nvidia-driver? desktop-background battery-name ac-adapter-name thermal-zone monitors) `(,(shepherd-service (documentation "NetworkManager applet; provides a GUI for network connections.") (provision '(nm-applet)) (requirement '(x11-display)) (start #~(make-forkexec-constructor (list #$(file-append network-manager-applet "/bin/nm-applet")))) (stop #~(make-kill-destructor))) ,(shepherd-service (documentation "Blueman applet; provides a GUI for connection to bluetooth devices.") (provision '(blueman-applet)) (requirement '(x11-display)) (start #~(make-forkexec-constructor (list #$(file-append blueman "/bin/blueman-applet")))) (stop #~(make-kill-destructor))) ,(shepherd-service (documentation "Dunst notification daemon; displays desktop notifications.") (provision '(dunst)) (requirement '(x11-display)) (start #~(make-forkexec-constructor (list #$(file-append dunst "/bin/dunst")))) (stop #~(make-kill-destructor))) ;; Picom needs to use the proprietary nvidia driver's libgl if that ;; driver is used for Xorg; plain Mesa won't work then. ,(let ((grafter (if nvidia-driver? (package-input-rewriting `((,mesa . ,(package (inherit mesa) (replacement nvda))))) identity))) (shepherd-service (documentation "Picom compositor; enables transparent windows in X.") (provision '(picom)) (requirement '(x11-display)) (start #~(make-forkexec-constructor (list #$(file-append (grafter picom) "/bin/picom") "--config" #$(local-file "files/picom.conf")))) (stop #~(make-kill-destructor)))) ,(shepherd-service (documentation "Source Xresources on login.") (provision '(xrdb)) (requirement '(x11-display)) (one-shot? #t) (start #~(lambda _ (invoke #$(file-append xrdb "/bin/xrdb") "-merge" #$(local-file "files/Xresources"))))) ;; By default, xdotool gets most of "#@\|~()<>[]{} wrong. Make ;; it use the correct keymap by re-setting the same one again. ,(shepherd-service (documentation "Fix X keyboard map on login; passmenu needs this.") (provision '(fix-xdotool)) (requirement '(x11-display)) (one-shot? #t) (modules '((ice-9 rdelim) (ice-9 regex) (ice-9 popen))) (start #~(lambda _ (let ((port (open-pipe* OPEN_READ #$(file-append setxkbmap "/bin/setxkbmap") "-query"))) (let loop ((line (read-line port))) (unless (eof-object? line) (let ((mtch (string-match "^layout:[[:space:]]*" line))) (if mtch (system* #$(file-append setxkbmap "/bin/setxkbmap") (match:suffix mtch)) (loop (read-line port)))))))))) ,(shepherd-service (documentation "Set up X displays on login.") (provision '(xorg-setup)) (one-shot? #t) (start #~(lambda _ (define (replace-home path) (if (string-prefix? "~/" path) (string-replace path (getenv "HOME") 0 1) path)) (system* #$(file-append numlockx "/bin/numlockx") "on") ;; Turn off the monitors if there is no input for 10 minutes. (system* #$(file-append xset "/bin/xset") "dpms" "600" "600" "600") (system* #$(file-append xrandr "/bin/xrandr") #$@(append-map (lambda (monitor) (match-record monitor (name xrandr-options) `("--output" ,name ,@xrandr-options))) monitors)) ;; Set the desktop background picture. Hopefully doing this just after ;; xrandr works and sets it for both screens. (system* #$(file-append hsetroot "/bin/hsetroot") "-cover" (replace-home #$desktop-background))))) ,@(map (compose (cut polybar-service <> battery-name ac-adapter-name thermal-zone) home-monitor-configuration-name) monitors)))) (define home-desktop-service-type (service-type (name 'desktop) (description "Configure the desktop environment.") (extensions (list (service-extension home-profile-service-type home-packages) (service-extension home-xdg-configuration-files-service-type home-xdg-configs) (service-extension home-files-service-type home-files) (service-extension home-environment-variables-service-type home-environment) (service-extension home-shepherd-service-type home-shepherd-services))))) ;;; Wayland-based desktop services (define-configuration/no-serialization home-wayland-desktop-configuration (num-cores integer "Number of cores on the system. Used for waybar CPU display.")) (define (home-wayland-packages config) "Install packages I use frequently." ;; Wayland-specific desktop tools (cons* swayfx dunst foot grim rofi-wayland slurp swaybg wev wob wtype wl-clipboard qalculate-gtk obs obs-pipewire-audio-capture ; to hack screen sharing xdg-desktop-portal xdg-desktop-portal-wlr ; to set XDG_DESKTOP_PORTAL_DIR search path common-desktop-packages)) (define %waybar-dunst-signal 13) (define (waybar-configuration num-cores) ;; For documentation, see: ;; https://github.com/Alexays/Waybar/wiki/Configuration (json-file "waybar.json" `((reload_style_on_change . #t) (layer . "top") ; put top bar above other windows' shadows (modules-left . #("sway/mode" "sway/workspaces" "custom/separator" "sway/window")) (modules-center . #()) (modules-right . #("network" "custom/separator" "memory" "custom/separator" "cpu" "custom/separator" "temperature" "custom/separator" "pulseaudio" "custom/separator" "battery" "custom/separator" "clock" "custom/dunst" "custom/separator" "tray")) (custom/separator (format . "ยท") (interval . "once") (tooltip . #f)) (sway/workspaces (all-outputs . #t) (format . "{name}")) (sway/window (max-length . 80) (icon . #t) (icon-size . 16)) (network (interval . 10) (format . "?? {ifname}") (format-linked . "๐ŸŒ") (format-disconnected . "๐ŸŒ") (format-disabled . "๐ŸŒ") (format-ethernet . "๐ŸŒ โ‡… {bandwidthTotalBytes}") (format-wifi . "{icon} {essid} โ‡… {bandwidthTotalBytes}") (format-icons . #("๐Ÿ“ถ" "๐Ÿ“ถ" "๐Ÿ“ถ")) (tooltip-format . "{ifname}") (tooltip-format-wifi . "{ifname}: {ipaddr} ({signalStrength}%, {frequency}GHz)") (tooltip-format-ethernet . "{ifname}: {ipaddr} ({bandwidthTotalBytes})")) (cpu (interval . 2) (format . ,(apply string-append "CPU " (map (cut format #f "{icon~d}" <>) (iota num-cores)))) (format-icons . #("โ–" "โ–‚" "โ–ƒ" "โ–„" "โ–…" "โ–†" "โ–‡" "โ–ˆ"))) (temperature (format . "๐ŸŒก {temperatureC}ยฐC") (format-critical . "๐ŸŒก {temperatureC}ยฐC")) (memory (interval . 5) (format . "RAM {used} GiB")) (battery (states (medium . 80) (critical . 20)) (format-discharging . "{icon} {capacity}% {power:.1f}W {time}") (format-charging . "๐Ÿ”Œ {capacity}% {power:.1f}W {time}") (format-full . "") (format-icons . #("๐Ÿ”‹" "๐Ÿ”‹" "๐Ÿ”‹" "๐Ÿ”‹" "๐Ÿ”‹"))) (pulseaudio (scroll-step . 1) (format . "{icon} {volume}%") (format-bluetooth . "{icon} {volume}%") (format-muted . "๐Ÿ”‡") (format-icons (default . #("๐Ÿ”ˆ" "๐Ÿ”‰" "๐Ÿ”Š"))) (tooltip-format . "{desc}: {volume}%") (on-click . "volume toggle-mute") (on-click-right . "foot pulsemixer")) (custom/dunst (exec . "echo '{\"alt\": \"'$(dunstctl is-paused)'\"}'") (return-type . "json") (interval . 10) (signal . ,%waybar-dunst-signal) ; SIGRTMIN + %waybar-dunst-signal (format-icons (true . "๐Ÿ”•") (false . "")) (format . "{icon}")) (clock (format . "{:%a, %e %b %H:%M}") (tooltip-format . "{calendar}") (calendar (mode . "year") (mode-mon-col . 3) (on-scroll . 1) (format (months . "{}") (days . "{}") (weekdays . "{}") (today . "{}")))) (tray (icon-size . 16) (spacing . 4))))) (define-public %sway-common-configuration (mixed-text-file "sway-greetd.conf" "\ font pango:Fira Sans 10 input type:keyboard { xkb_layout \"gb\" xkb_options \"caps:swapescape,parens:swap_brackets,terminate:ctrl_alt_bksp,compose:rctrl,keypad:oss,kpdl:kposs\" xkb_numlock enabled } input type:touchpad { # libinput configuration for touchpad dwt enabled # 'disabled while typing' middle_emulation enabled natural_scroll enabled click_method clickfinger scroll_method two_finger tap enabled tap_button_map lrm drag enabled drag_lock disabled } output * background " tw-background "/share/backgrounds/tw/blobs.png fill # swayfx effects blur enable shadows enable corner_radius 6 ")) (define (home-wayland-xdg-configs config) ;; Wayland also uses the XCompose file. (match-record config (num-cores) `(("X11/XCompose" ,(local-file "files/XCompose")) ; see also: $XCOMPOSEFILE variable ("dunst/dunstrc" ,(local-file "files/dunstrc")) ("dunst/dunstrc.d/50-catppuccin.conf" ,catppuccin-dunstrc) ("foot/foot.ini" ,(local-file "files/foot.ini")) ("foot/catppuccin.ini" ,catppuccin-foot) ("rofi/config.rasi" ,(local-file "files/rofi.rasi")) ("rofi/themes/catppuccin.rasi" ,catppuccin-rofi) ("sway/config" ,(combined-text-file "sway.conf" %sway-common-configuration (local-file "files/sway.conf"))) ("swaylock/config" ,(combined-text-file "swaylock.conf" (mixed-text-file "swaylock-settings.conf" "image=" tw-background "/share/backgrounds/tw/blobs.png\n" "scaling=fill\n" "font=Fira Sans\n" "font-size=12\n" "indicator-idle-visible\n" "show-keyboard-layout\n" "show-failed-attempts\n") catppuccin-swaylock)) ("waybar/config.jsonc" ,(waybar-configuration num-cores)) ("waybar/style.css" ,(local-file "files/waybar.css")) ("waybar/catppuccin.css" ,catppuccin-waybar) ,@common-xdg-configs))) (define (home-wayland-files config) `((".local/bin/wlprop" ,(local-file "files/wlprop" #:recursive? #t)) ,@common-home-files)) (define (home-wayland-environment config) `(;; Smooth trackpad scrolling in Firefox/Icecat. ;; https://wiki.archlinux.org/index.php/Firefox/Tweaks#Pixel-perfect_trackpad_scrolling ("MOZ_ENABLE_WAYLAND" . "1") ("TERMINAL" . "foot") ; needed for rofi-sensible-terminal ("QT_QPA_PLATFORM" . "wayland") ; needed for Qt apps, e.g. obs ("ELECTRON_OZONE_PLATFORM_HINT" . "wayland") ; for Electron apps, e.g. signal-desktop ("SDL_VIDEODRIVER" . "wayland") ; needed by SDL2 games, e.g. szio-solitaire ("XDG_CURRENT_DESKTOP" . "sway") ; not sure why this isn't set by default ("_JAVA_OPTIONS" . ,(string-append "$_JAVA_OPTIONS${_JAVA_OPTIONS:+ }" "-Dawt.useSystemAAFontSettings=on -Dswing.aatext=true " "-Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel " "-Dswing.crossplatformlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel")))) (define wayland-screenlock-program ;; Wrapper around swaylock to turn off the screen and pause notifications. (program-file "wayland-screenlock" ;; If swaylock is already running, don't lock again. #~(unless (zero? (status:exit-val (system* #$(file-append procps "/bin/pgrep") "-xU" (number->string (getuid)) "swaylock"))) ;; Rely on swayidle to turn off the screen after a timeout. (system* #$(file-append dunst "/bin/dunstctl") "set-paused" "true") (system* #$(file-append shepherd "/bin/herd") "kick-dunst" "waybar") (system* #$(file-append swaylock "/bin/swaylock")) (system* #$(file-append dunst "/bin/dunstctl") "set-paused" "false") (system* #$(file-append shepherd "/bin/herd") "kick-dunst" "waybar")))) (define (home-wayland-shepherd-services config) (define wayland-environment-variables (let ((vars '("XDG_RUNTIME_DIR" "WAYLAND_DISPLAY" "SWAYSOCK"))) #~(cons* #$@(map (lambda (var) #~(string-append #$var "=" (getenv #$var))) vars) (filter (lambda (assignment) (not (or #$@(map (lambda (var) #~(string-prefix? #$(string-append var "=") assignment)) vars)))) (default-environment-variables))))) (list (shepherd-service ; adjusted from Guix x11-display service (provision '(wayland-display)) (modules '((ice-9 ftw) (ice-9 match) (ice-9 receive) (srfi srfi-1))) (start #~(lambda* (#:optional (display (getenv "WAYLAND_DISPLAY")) (swaysock (getenv "SWAYSOCK"))) (define runtime-directory (or (getenv "XDG_RUNTIME_DIR") (format #f "/run/user/~d" (getuid)))) (define (correct-socket? prefix) (lambda (name) (let ((path (in-vicinity runtime-directory name))) (and (string-prefix? prefix name) (eq? 'socket (stat:type (stat path))) (access? path O_RDWR))))) (define (find-display delay) ;; Wait for an accessible socket to show up in RUNTIME-DIRECTORY, ;; up to DELAY seconds. (let loop ((attempts delay)) (define wayland-socket (find (correct-socket? "wayland-") (or (scandir runtime-directory) '()))) (define sway-socket (find (correct-socket? "sway-ipc.") (or (scandir runtime-directory) '()))) (cond ((and wayland-socket sway-socket) (let ((swaysock (in-vicinity runtime-directory sway-socket))) (format #t "Wayland server found at ~s. Sway socket found at ~s.~%" wayland-socket swaysock) (values wayland-socket swaysock))) ((zero? attempts) (format (current-error-port) "Wayland server or Sway socket did not show up; giving up.~%") (values #f #f)) (else (sleep 1) (loop (- attempts 1)))))) (receive (wayland-socket sway-socket) (find-display 10) (let ((display (or display wayland-socket)) (swaysock (or swaysock sway-socket))) (setenv "XDG_RUNTIME_DIR" runtime-directory) (when display (setenv "WAYLAND_DISPLAY" display)) (when swaysock (setenv "SWAYSOCK" swaysock)) display)))) (stop #~(lambda _ (unsetenv "WAYLAND_DISPLAY") (unsetenv "SWAYSOCK") #f)) (respawn? #f)) ;; Using `make-forkexec-constructor' does not work because we need to ;; inherit the variables from `wayland-display' at start time, not at ;; definition time. (shepherd-service (documentation "NetworkManager applet; provides a GUI for network connections.") (provision '(nm-applet)) (requirement '(wayland-display)) (start #~(lambda _ (fork+exec-command (list #$(file-append network-manager-applet "/bin/nm-applet")) #:environment-variables #$wayland-environment-variables))) (stop #~(make-kill-destructor))) (shepherd-service (documentation "Blueman applet; provides a GUI for connection to bluetooth devices.") (provision '(blueman-applet)) (requirement '(wayland-display)) (start #~(lambda _ (fork+exec-command (list #$(file-append blueman "/bin/blueman-applet")) #:environment-variables #$wayland-environment-variables))) (stop #~(make-kill-destructor))) (shepherd-service (documentation "Dunst notification daemon; displays desktop notifications.") (provision '(dunst)) (requirement '(wayland-display)) (start #~(lambda _ (fork+exec-command (list #$(file-append dunst "/bin/dunst")) #:environment-variables #$wayland-environment-variables))) (stop #~(make-kill-destructor))) (shepherd-service (documentation "XDG desktop portal dispatcher.") (provision '(xdg-desktop-portal)) (requirement '(wayland-display dbus)) (start #~(lambda _ (fork+exec-command (list #$(file-append xdg-desktop-portal "/libexec/xdg-desktop-portal") "-rv") #:environment-variables (cons* "XDG_CURRENT_DESKTOP=sway" #;(string-append "XDG_DESKTOP_PORTAL_DIR=" #$xdg-desktop-portal-wlr "/share/xdg-desktop-portal/portals") #$wayland-environment-variables)))) (stop #~(make-kill-destructor))) (shepherd-service (documentation "Screen sharing support for wlroots-based compositors.") (provision '(xdg-desktop-portal-wlr)) (requirement '(xdg-desktop-portal wayland-display dbus)) (start #~(lambda _ (fork+exec-command (list #$(file-append xdg-desktop-portal-wlr "/libexec/xdg-desktop-portal-wlr") "-rl" "DEBUG") #:environment-variables (cons* "XDG_CURRENT_DESKTOP=sway" #$wayland-environment-variables)))) (stop #~(make-kill-destructor))) (shepherd-service (documentation "Waybar desktop status bar daemon.") (provision '(waybar)) (requirement '(wayland-display)) (start #~(lambda _ (fork+exec-command (list #$(file-append waybar "/bin/waybar")) #:environment-variables #$wayland-environment-variables))) (stop #~(make-kill-destructor)) (actions (list (shepherd-action (name 'kick-dunst) (documentation "Tell waybar to update its Dunst status.") (procedure #~(lambda (waybar-pid . args) ;; See magic constant defined in waybar.jsonc. (kill waybar-pid (+ SIGRTMIN #$%waybar-dunst-signal)) #t)))))) (let ((swayidle.conf (mixed-text-file "swayidle.conf" "lock '" wayland-screenlock-program "' \n" "unlock '" procps "/bin/pkill -x -USR1 swaylock'\n" "before-sleep '" elogind "/bin/loginctl lock-session'\n" "timeout 300 '" elogind "/bin/loginctl lock-session && " swayfx "/bin/swaymsg \"output * power off\"' " ; no newline! "resume '" swayfx "/bin/swaymsg \"output * power on\"'\n" "timeout 600 '" elogind "/bin/loginctl suspend'\n"))) (shepherd-service (documentation "Manage the screen when idle or locked.") (provision '(swayidle)) (requirement '(wayland-display)) (start #~(lambda _ (fork+exec-command (list #$(file-append swayidle "/bin/swayidle") "-C" #$swayidle.conf) #:environment-variables #$wayland-environment-variables))) (stop #~(make-kill-destructor)) (actions (list (shepherd-configuration-action swayidle.conf) (shepherd-action (name 'idle) (documentation "Immediately enter idle state.") (procedure #~(lambda (swayidle-pid . args) (kill swayidle-pid SIGUSR1) #t))))))))) (define home-wayland-desktop-service-type (service-type (name 'wayland-desktop) (description "Configure a Wayland-based desktop environment.") (extensions (list (service-extension home-profile-service-type home-wayland-packages) (service-extension home-xdg-configuration-files-service-type home-wayland-xdg-configs) (service-extension home-files-service-type home-wayland-files) (service-extension home-environment-variables-service-type home-wayland-environment) (service-extension home-pipewire-service-type (const #t)) (service-extension home-shepherd-service-type home-wayland-shepherd-services))))) ;;; Gaming servicse (define-configuration/no-serialization home-gaming-configuration (nvidia-driver? (boolean #f) "Adapt the installed packages for the proprietary NVIDIA driver?")) (define (gaming-packages config) (match-record config (nvidia-driver?) (cons* prismlauncher (if nvidia-driver? ;; nvidia-system-monitor: Qt; installs "qnvsm" binary, but no .desktop file (list steam-nvidia nvidia-system-monitor) (list steam radeontop))))) (define (gaming-xdg-configs config) `(("guix-gaming-channels/games.scm" ,(scheme-file "guix-gaming-credentials.scm" #~(begin (use-modules (ice-9 rdelim) (ice-9 popen)) (define gog-credentials (let* ((pass (open-pipe* OPEN_READ "pass" "show" "software/games/gog")) (password (read-line pass)) (username (let loop ((line (read-line pass))) (cond ((eof-object? line) (error "Could not find username in pass output")) ((string-prefix? "username: " line) (string-trim (substring line (string-length "username:")))) (else (loop (read-line pass))))))) (close-pipe pass) `((email ,username) (password ,password)))) (make-gaming-config `((gog ,gog-credentials)))))))) (define (gaming-environment config) `(("GUIX_SANDBOX_EXTRA_SHARES" . ; mount savegame locations inside Steam container ,(string-join '("$HOME/savegames/banished=$HOME/.local/share/Steam/steamapps/compatdata/242920/pfx/drive_c/users/steamuser/Documents/Banished/Save" "$HOME/savegames/planetbase=$HOME/.local/share/Steam/steamapps/compatdata/403190/pfx/drive_c/users/steamuser/Documents/Planetbase" "$HOME/savegames/cities-skylines=$HOME/.local/share/Colossal Order/Cities_Skylines/Saves" "$HOME/savegames/surviving-mars=$HOME/.local/share/Surviving Mars/76561198130982912" "$HOME/savegames/colony-survival=$HOME/.local/share/Steam/steamapps/common/Colony Survival/gamedata/savegames" "$HOME/savegames/portal=$HOME/.local/share/Steam/steamapps/common/Portal/portal/save" "$HOME/savegames/spacechem=$HOME/.local/share/Zachtronics Industries/SpaceChem/save" "$HOME/savegames/tis-100=$HOME/.local/share/TIS-100/76561198130982912") ":")))) (define home-gaming-service-type (service-type (name 'gaming) (description "Configure games and gaming platforms.") (default-value (home-gaming-configuration)) (extensions (list (service-extension home-profile-service-type gaming-packages) (service-extension home-xdg-configuration-files-service-type gaming-xdg-configs) (service-extension home-environment-variables-service-type gaming-environment))))) ;; Personal Information Management (mail, calendar, contacts, etc) (define-configuration/no-serialization home-pim-configuration (work? (boolean #f) "Configure only programs relevant for work?")) (define (pim-packages config) "PIM programs and utilities." (match-record config (work?) (cons* vdirsyncer khal khard aerc lynx mutt_oauth2.py ; lynx for HTML mail (if work? '() (list nheko signal-desktop newsboat))))) (define (pim-xdg-configs config) "Configuration files for PIM programs." (match-record config (work?) `(("khal/config" ,(local-file "files/khal.conf")) ("khard/khard.conf" ,(local-file "files/khard.conf")) ("aerc/accounts.conf" ,(if work? (local-file "files/aerc/accounts.work.conf") (local-file "files/aerc/accounts.conf"))) ("aerc/aerc.conf" ,(local-file "files/aerc/aerc.conf")) ("aerc/binds.conf" ,(local-file "files/aerc/binds.conf")) ("aerc/filters" ,(local-file "files/aerc/filters" #:recursive? #t)) ;; I could also use the upstream catppuccin/aerc theme here (packaged as ;; catppuccin-aerc), but it's a bit too colourless for me. ("aerc/stylesets/catppuccin" ,(local-file "files/aerc/catppuccin.conf")) ("aerc/templates" ,(local-file "files/aerc/templates" #:recursive? #t)) ("vdirsyncer/config" ,(if work? (local-file "files/vdirsyncer.work.conf") (local-file "files/vdirsyncer.conf"))) ,@(if work? '() `(("newsboat/config" ,(local-file "files/newsboat.conf")) ("newsboat/config.catppuccin" ,catppuccin-newsboat)))))) (define (pim-cron-jobs config) "Periodic jobs to sync calendars and contacts." ;; To avoid popping up a password prompt every time these run, ;; gpg-agent needs a long-enough default-cache-ttl. (list #~(job "15 */4 * * *" #$(file-append vdirsyncer "/bin/vdirsyncer metasync")) #~(job "0,30 * * * *" #$(file-append vdirsyncer "/bin/vdirsyncer sync")))) (define-public home-pim-service-type (service-type (name 'pim) (description "Install and configure Personal Information Management software.") (default-value (home-pim-configuration)) (extensions (list (service-extension home-profile-service-type pim-packages) (service-extension home-xdg-configuration-files-service-type pim-xdg-configs) (service-extension home-mcron-service-type pim-cron-jobs)))))